diff --git a/spec/solveur/solveur.spec.ts b/spec/solveur/solveur.spec.ts index 978dab0bb14dfcd6f2d7af6a8b050598ceb55099..680d44a9207675b32e22c77f4c09475e69fb94a6 100644 --- a/spec/solveur/solveur.spec.ts +++ b/spec/solveur/solveur.spec.ts @@ -25,11 +25,9 @@ describe("solveur multi-modules", () => { pn.prms.DHT.defineReference(pc, "DH"); pp.prms.DH.defineReference(pn, "DH"); // solveur - const s = new Solveur( - new SolveurParams(pc.prms.Z1, 324.907), - false - ); - s.properties.setPropValue("nubToCalculate", pp.uid); + const s = new Solveur(new SolveurParams(324.907), false); + s.nubToCalculate = pp; + s.searchedParameter = pc.prms.Z1; const res = s.CalcSerie(); expect(res.vCalc).toBeCloseTo(2.156, 3); }); @@ -50,12 +48,9 @@ describe("solveur multi-modules", () => { pn.prms.DHT.defineReference(pc, "DH"); pp.prms.DH.defineReference(pn, "DH"); // solveur - const s = new Solveur( - new SolveurParams(pc.prms.Z1, 324.907), - false - ); + const s = new Solveur(new SolveurParams(324.907), false); expect(() => { - s.properties.setPropValue("nubToCalculate", pp.uid); + s.nubToCalculate = pp; }).toThrowError("Solveur.update(): Nub to calculate must not have multiple values"); }); @@ -73,13 +68,11 @@ describe("solveur multi-modules", () => { Session.getInstance().registerNub(pn); Session.getInstance().registerNub(pp); // solveur - const s = new Solveur( - new SolveurParams(pc.prms.Z1, 324.907), - false - ); + const s = new Solveur(new SolveurParams(324.907), false); + s.searchedParameter = pc.prms.Z1; expect(() => { - s.properties.setPropValue("nubToCalculate", pp.uid); - }).toThrowError("Solveur.update(): Nub to calculate is not linked to result of X's parent Nub"); + s.nubToCalculate = pp; + }).toThrowError("Solveur.update(): Nub to calculate is not linked to result of searchedParameter parent Nub"); }); it("test 4: PAB Chute <= PAB Nombre <= PAB Puissance, redéfinition des paramètres", () => { @@ -97,14 +90,11 @@ describe("solveur multi-modules", () => { pn.prms.DHT.defineReference(pc, "DH"); pp.prms.DH.defineReference(pn, "DH"); // solveur - const s = new Solveur( - // new SolveurParams(pc.prms.Z1, 324.907), - new SolveurParams(pc.prms.Z2, 123), - false - ); - s.properties.setPropValue("nubToCalculate", pp.uid); + const s = new Solveur(new SolveurParams(123), false); + s.nubToCalculate = pp; + s.searchedParameter = pc.prms.Z2; // defining values a-posteriori - s.prms.X = pc.prms.Z1; + s.searchedParameter = pc.prms.Z1; s.prms.Ytarget.singleValue = 324.907; s.prms.Xinit.singleValue = 1.9; const res = s.CalcSerie(); @@ -126,11 +116,9 @@ describe("solveur multi-modules", () => { pn.prms.DHT.defineReference(pc, "DH"); pp.prms.DH.defineReference(pn, "DH"); // solveur - const s = new Solveur( - new SolveurParams(pc.prms.Z1), - false - ); - s.properties.setPropValue("nubToCalculate", pp.uid); + const s = new Solveur(new SolveurParams(), false); + s.nubToCalculate = pp; + s.searchedParameter = pc.prms.Z1; s.prms.Ytarget.setValues(100, 500, 50); const res = s.CalcSerie(); const expectedValues = [ 1.01, 1.264, 1.519, 1.774, 2.029, 2.284, 2.539, 2.794, 3.048 ]; @@ -140,4 +128,29 @@ describe("solveur multi-modules", () => { } }); + it("test 6: target nubs / searched params lists", () => { + // contexte + const pc = new PabChute(new PabChuteParams(2, 0.5, 666)); + pc.calculatedParam = pc.prms.DH; + const pn = new PabNombre(new PabNombreParams(666, 10, 666)); + pn.calculatedParam = pn.prms.DH; + const pp = new PabPuissance(new PabPuissanceParams(666, 0.1, 0.5, 666)); + pp.calculatedParam = pp.prms.PV; + Session.getInstance().clear(); + Session.getInstance().registerNub(pc); + Session.getInstance().registerNub(pn); + Session.getInstance().registerNub(pp); + pn.prms.DHT.defineReference(pc, "DH"); + pp.prms.DH.defineReference(pn, "DH"); + // check lists + const targetNubs = Session.getInstance().getDownstreamNubs(); + expect(targetNubs.length).toBe(2); // pn, pp + const searchedParamsPp = Solveur.getDependingNubsSearchableParams(pp); + expect(searchedParamsPp.length).toBe(3); // pc.Z1, pc.Z2, pn.N + expect(searchedParamsPp.map((p) => p.symbol)).toEqual([ "N", "Z1", "Z2" ]); + const searchedParamsPn = Solveur.getDependingNubsSearchableParams(pn); + expect(searchedParamsPn.length).toBe(2); // pc.Z1, pc.Z2 + expect(searchedParamsPn.map((p) => p.symbol)).toEqual([ "Z1", "Z2" ]); + }); + }); diff --git a/src/nub.ts b/src/nub.ts index d35727caa0c395064d91b4a2dddf4c76e358fa14..e644b1af74281716166b8d946131822d08e992ff 100644 --- a/src/nub.ts +++ b/src/nub.ts @@ -672,7 +672,7 @@ export abstract class Nub extends ComputeNode implements IObservable { * without following links (stops when it finds a Nub that has to * be calculated). Used to trigger chain calculation. */ - public getRequiredNubs(visited: string[] = []) { + public getRequiredNubs(visited: string[] = []): Nub[] { const requiredNubs: Nub[] = []; // prevent loops if (! visited.includes(this.uid)) { @@ -693,6 +693,19 @@ export abstract class Nub extends ComputeNode implements IObservable { return requiredNubs; } + /** + * Returns all Nubs whose results are required by the given one, + * following links. Used by Solveur. + */ + public getRequiredNubsDeep(visited: string[] = []): Nub[] { + let requiredNubs: Nub[] = this.getRequiredNubs(visited); + for (const rn of requiredNubs) { + requiredNubs = requiredNubs.concat(rn.getRequiredNubsDeep(visited)); + } + // @TODO deduplicate ? + return requiredNubs; + } + /** * Returns all Nubs whose parameters or results are targetted * by the given one. diff --git a/src/session.ts b/src/session.ts index bc352c1866d9bd279f11b2f521bb1fe22c12ba58..9bb601eab6575f84aa884ddf933dbd55922a619b 100644 --- a/src/session.ts +++ b/src/session.ts @@ -66,6 +66,8 @@ import { JetParams } from "./devalaison/jet_params"; import { Pente } from "./pente"; import { PenteParams } from "./pente_params"; import { Bief } from "./remous/bief"; +import { Solveur } from "./solveur/solveur"; +import { SolveurParams } from "./solveur/solveur_params"; export class Session { @@ -560,6 +562,13 @@ export class Session { ); break; + case CalculatorType.Solveur: + // const nubToCalc: BiefRegime = params.getPropValue("nubToCalculate"); + nub = new Solveur( + new SolveurParams(undefined) + ); + break; + default: throw new Error( `Session.createNub() : type de module '${CalculatorType[calcType]}' non pris en charge` @@ -602,7 +611,7 @@ export class Session { * @param includeValuesLinks if true, even Nubs targetting non-calculated non-modified parameters * will be considered dependent @see jalhyd#98 */ - public getDependingNubs(uid: string, symbol?: string, includeValuesLinks: boolean = false) { + public getDependingNubs(uid: string, symbol?: string, includeValuesLinks: boolean = false): Nub[] { const dependingNubs: Nub[] = []; for (const n of this._nubs) { if (n.uid !== uid && n.resultDependsOnNub(uid, [], symbol, includeValuesLinks)) { @@ -612,6 +621,20 @@ export class Session { return dependingNubs; } + /** + * Returns all Nubs depending on the result of at least one other Nub. + * Used by Solveur to find available "target" nubs to calculate. + */ + public getDownstreamNubs(): Nub[] { + const downstreamNubs: Nub[] = []; + for (const n of this._nubs) { + if (n.getRequiredNubs().length > 0) { + downstreamNubs.push(n); + } + } + return downstreamNubs; + } + /** * Returns a list of nub/symbol couples, that can be linked to the given * parameter, among all current nubs diff --git a/src/solveur/solveur.ts b/src/solveur/solveur.ts index 8c4a0e3f60f29a6a18011f866d51f4249db4d69d..a04c81d40027f2d7988e2943e068747bae00a8fb 100644 --- a/src/solveur/solveur.ts +++ b/src/solveur/solveur.ts @@ -1,6 +1,7 @@ import { CalculatorType } from "../compute-node"; import { Nub } from "../nub"; -import { ParamCalculability } from "../param/param-definition"; +import { ParamCalculability, ParamDefinition } from "../param/param-definition"; +import { ParamValueMode } from "../param/param-value-mode"; import { Session } from "../session"; import { Observer } from "../util/observer"; import { Result } from "../util/result"; @@ -8,6 +9,26 @@ import { SolveurParams } from "./solveur_params"; export class Solveur extends Nub implements Observer { + /** + * Finds all parameters whose value can be searched for by dichotomy, for + * a given value of calculated param of given Nub + * @param nub Nub calculated by Solveur + */ + public static getDependingNubsSearchableParams(nub: Nub): ParamDefinition[] { + const searchableParams: ParamDefinition[] = []; + if (nub !== undefined) { + const upstreamNubs = nub.getRequiredNubsDeep(); + for (const un of upstreamNubs) { + for (const p of un.parameterIterator) { + if (! [ ParamValueMode.CALCUL, ParamValueMode.LINK ].includes(p.valueMode)) { + searchableParams.push(p); + } + } + } + } + return searchableParams; + } + constructor(prms: SolveurParams, dbg: boolean = false) { super(prms, dbg); this._calcType = CalculatorType.Solveur; @@ -15,6 +36,8 @@ export class Solveur extends Nub implements Observer { this.prms.addObserver(this); // UID of Session Nub to calculate, the result of the which must be this.prms.Ytarget.singleValue this.properties.setPropValue("nubToCalculate", ""); + // UID of Session Nub / symbol of source parameter to vary (the "searched" parameter) + this.properties.setPropValue("searchedParameter", ""); // calculated param should always be pseudo-parameter "X" (the one whose value we're looking for) this._defaultCalculatedParam = prms.X; this.resetDefaultCalculatedParam(); @@ -27,16 +50,54 @@ export class Solveur extends Nub implements Observer { /** finds the Nub to calculate by its UID */ public get nubToCalculate(): Nub { let nub: Nub; - const nubUID = this._props.getPropValue("nubToCalculate"); + const nubUID: string = this._props.getPropValue("nubToCalculate"); if (nubUID !== undefined && nubUID !== "") { nub = Session.getInstance().findNubByUid(nubUID); if (nub === undefined) { - throw new Error(`Solveur: cannot find Nub ${nubUID} in session`); + throw new Error(`Solveur.get nubToCalculate(): cannot find Nub ${nubUID} in session`); } } return nub; } + /** defines the Nub to calculate by setting property "nubToCalculate" to the UID of the given Nub */ + public set nubToCalculate(n: Nub) { + this.properties.setPropValue("nubToCalculate", n.uid); + } + + /** finds the source parameter whose value we're looking for, by its Nub UID / symbol */ + public get searchedParameter(): ParamDefinition { + let p: ParamDefinition; + const nubUIDAndSymbol: string = this._props.getPropValue("searchedParameter"); + if (nubUIDAndSymbol !== undefined && nubUIDAndSymbol !== "") { + const slashPos = nubUIDAndSymbol.indexOf("/"); + const nubUID = nubUIDAndSymbol.substring(0, slashPos); + const paramSymbol = nubUIDAndSymbol.substring(slashPos + 1); + // console.log("(i) searched param Nub UID / symbol :", nubUID, paramSymbol); + if (nubUID) { + const nub: Nub = Session.getInstance().findNubByUid(nubUID); + if (nub === undefined) { + throw new Error(`Solveur.get searchedParameter(): cannot find Nub ${nubUID} in session`); + } + if (paramSymbol) { + p = nub.getParameter(paramSymbol); + if (p === undefined) { + throw new Error(`Solveur.get searchedParameter(): cannot find Parameter ${paramSymbol} in Nub`); + } + } + } + } + return p; + } + + /** + * defines the searched parameter by setting property "searchedParameter" to the UID of the + * parameter's Nub / the parameter's symbol + */ + public set searchedParameter(p: ParamDefinition) { + this.properties.setPropValue("searchedParameter", p.nubUid + "/" + p.symbol); + } + public CalcSerie(rInit?: number): Result { // affect initial value of X for Dichotomie search this.prms.X.singleValue = this.prms.Xinit.currentValue; @@ -56,7 +117,7 @@ export class Solveur extends Nub implements Observer { // set the Y value we have to obtain this.prms.Y.v = this.prms.Ytarget.v; // set the current value of X, determined by Dichotomie, on the upstream Nub - this.prms.Xsource.singleValue = this.prms.X.v; + this.searchedParameter.singleValue = this.prms.X.v; // calculate Nubs chain const res = this.nubToCalculate.CalcSerie(); return res; @@ -71,16 +132,31 @@ export class Solveur extends Nub implements Observer { // interface Observer public update(sender: any, data: any): void { - // console.log("Solveur.update()", sender.constructor.name, data); if (data.action === "propertyChange") { - if (data.name === "nubToCalculate") { + if (data.name === "nubToCalculate" || data.name === "searchedParameter") { const n = this.nubToCalculate; - if (n !== undefined) { - if (! n.dependsOnNubResult(this.prms.Xsource.parentNub)) { - throw new Error("Solveur.update(): Nub to calculate is not linked to result of X's parent Nub"); + const p = this.searchedParameter; + if (n !== undefined && p !== undefined) { + if (! n.dependsOnNubResult(p.parentNub)) { + throw new Error( + "Solveur.update(): Nub to calculate is not linked to result of searchedParameter parent Nub" + ); } - if (n.resultHasMultipleValues()) { - throw new Error("Solveur.update(): Nub to calculate must not have multiple values"); + } + if (data.name === "nubToCalculate") { + if (n !== undefined) { + if (n.resultHasMultipleValues()) { + throw new Error("Solveur.update(): Nub to calculate must not have multiple values"); + } + } + } + if (data.name === "searchedParameter") { + if (p !== undefined) { + if (p.valueMode !== ParamValueMode.SINGLE) { + throw new Error("Solveur.update(): searched parameter X must be in SINGLE mode"); + } + // update pseudo-parameter X + this.prms.setX(p); } } } diff --git a/src/solveur/solveur_params.ts b/src/solveur/solveur_params.ts index d3f8a41009eaa25ef03b3e4a62f62d169ecc7e8c..dee786bd1e72e2a6d12ccf907f78e293b6b5659d 100644 --- a/src/solveur/solveur_params.ts +++ b/src/solveur/solveur_params.ts @@ -12,9 +12,6 @@ export class SolveurParams extends ParamsEquation implements IObservable { /** X : paramètre à faire varier par Dichotomie, dont la valeur sera reportée dans paramètre du Nub amont */ private _X: ParamDefinition; - /** X : pointeur sur le paramètre du Nub amont */ - private _Xsource: ParamDefinition; - /** Y : pseudo-paramètre à calculer, pour la dichotomie (déclenchera en fait le calcul du Nub aval) */ private _Y: ParamDefinition; @@ -27,19 +24,17 @@ export class SolveurParams extends ParamsEquation implements IObservable { /** implémentation de IObservable par délégation */ private _observable: Observable; - constructor(X?: ParamDefinition, rYtarget?: number, rXinit?: number) { + /** + * Nub to calculate and searched parameter must be set through Solveur properties, + * or .nubToCalculate() and .searchedParameter() setters + * @param rYtarget value we want to obtain for Y + * @param rXinit initial value f searched parameter; will be overwritten as soon + * as Solveur's property "searchedParam" is updated + */ + constructor(rYtarget?: number, rXinit?: number) { super(); this._observable = new Observable(); - let xInitVal = rXinit; - this._Xinit = new ParamDefinition(this, "Xinit", ParamDomainValue.ANY); - if (X !== undefined) { - // initial value for X : if not given, use X's singleValue - if (rXinit === undefined) { - xInitVal = X.singleValue; - } - X.singleValue = xInitVal; - this.X = X; - } + this._Xinit = new ParamDefinition(this, "Xinit", ParamDomainValue.ANY, undefined, rXinit); this._Ytarget = new ParamDefinition(this, "Ytarget", ParamDomainValue.ANY, undefined, rYtarget); this._Y = new ParamDefinition(this, "Y", ParamDomainValue.ANY); @@ -52,9 +47,7 @@ export class SolveurParams extends ParamsEquation implements IObservable { return this._X; } - /** update parameter to vary; copies the parameter to _Xsource and updates pseudo-parameter _X */ - public set X(X: ParamDefinition) { - this._Xsource = X; + public setX(X: ParamDefinition) { // copy of real X, to be used by Dichotomie() that will look for its value this._X = new ParamDefinition(this, "X", X.domain, X.unit, X.singleValue, undefined, false); // Update Xinit @@ -71,10 +64,6 @@ export class SolveurParams extends ParamsEquation implements IObservable { }, this); } - public get Xsource(): ParamDefinition { - return this._Xsource; - } - public get Y(): ParamDefinition { return this._Y; }