nub.ts 42.11 KiB
import { CalculatorType, ComputeNode } from "./compute-node"; // nghyd build fails when commenting CalculatorType @WTF
import { Dichotomie } from "./dichotomie";
import { acSection, IParamDefinitionIterator, Pab, ParamDefinition, ParamsEquation,
         ParamsEquationArrayIterator, Session, Structure } from "./index";
import { LinkedValue } from "./linked-value";
import { ParamCalculability } from "./param/param-definition";
import { ParamValueMode } from "./param/param-value-mode";
import { ParamValues } from "./param/param-values";
import { Props } from "./props";
import { IObservable, Observable, Observer } from "./util/observer";
import { Result } from "./util/result";
/**
 * Classe abstraite de Noeud de calcul dans une session :
 * classe de base pour tous les calculs
export abstract class Nub extends ComputeNode implements IObservable {
    public get result(): Result {
        return this._result;
    /** Returns Props object (observable set of key-values) associated to this Nub */
    public get properties(): Props {
        // completes props with calcType if not already set
        this._props.setPropValue("calcType", this.calcType);
        return this._props;
    public set properties(props: Props) {
        this._props = props.clone();
    /**
     * return ParamsEquation of all children recursively
    public get childrenPrms(): ParamsEquation[] {
        const prms: ParamsEquation[] = [];
        if (this._children.length) { // if called within constructor, default class member value is not set yet
            for (const child of this._children) {
                prms.push(child.prms);
                if (child.getChildren()) {
                    if (child.getChildren().length) {
                        Nub.concatPrms(prms, child.childrenPrms);
        return prms;
    /**
     * Returns an iterator over :
     *  - own parameters (this._prms)
     *  - children parameters (this._children[*]._prms)
    public get parameterIterator(): IParamDefinitionIterator {
        const prms: ParamsEquation[] = [];
        prms.push(this._prms);
        if (this._children) {
            Nub.concatPrms(prms, this.childrenPrms);
        return new ParamsEquationArrayIterator(prms);
    protected get progress() {
        return this._progress;
    /**
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
* Updates the progress percentage and notifies observers, * at most once per 300ms */ protected set progress(v: number) { this._progress = v; const currentTime = new Date().getTime(); if ( (currentTime - this.previousNotificationTimestamp) > 30 || v === 100 ) { // console.log(">> notifying !"); this.notifyProgressUpdated(); this.previousNotificationTimestamp = currentTime; } } public get calcType() { return this._calcType; } public get calculatedParam(): ParamDefinition { return this._calculatedParam; } /** * Sets p as the parameter to be computed; sets it to CALC mode */ public set calculatedParam(p: ParamDefinition) { this._calculatedParam = p; this._calculatedParam.valueMode = ParamValueMode.CALCUL; } /** * Returns a parameter descriptor compatible with Calc() methods, * ie. a symbol string for a Nub's main parameter, or an object * of the form { uid: , symbol: } for a Nub's sub-Nub parameter * (ex: Structure parameter in ParallelStructure) */ public get calculatedParamDescriptor(): string | { uid: string, symbol: string } { if (this.uid === this._calculatedParam.nubUid) { return this._calculatedParam.symbol; } else { return { uid: this._calculatedParam.nubUid, symbol: this._calculatedParam.symbol }; } } protected static concatPrms(p1: ParamsEquation[], p2: ParamsEquation[]): ParamsEquation[] { const p3: ParamsEquation[] = p1; for (const p of p2) { p3.push(p); } return p3; } private static progressPercentageAccordedToChainCalculation = 50; /** paramétrage de la dichotomie */ public dichoStartIntervalMaxSteps: number = 100; /** pointer to parent Nub */ public parent: Nub; /** parameter that is to be computed by default - to be overloaded by child classes */ protected _defaultCalculatedParam: ParamDefinition; /** parameter that is to be computed */ protected _calculatedParam: ParamDefinition;
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
/** résultat de Calc()/CalcSerie() */ protected _result: Result; /** * List of children Nubs; browsed by parameters iterator. * - for ParallelStructures: contains 0 or more Structures * - for SectionNub : contains exactly 1 acSection */ protected _children: Nub[]; /** properties describing the Nub type */ protected _props: Props = new Props(); /** implémentation par délégation de IObservable */ private _observable: Observable; /** a rough indication of calculation progress, between 0 and 100 */ private _progress: number = 0; /** allows notifying of progress every X milliseconds only */ private previousNotificationTimestamp = 0; public constructor(prms: ParamsEquation, dbg: boolean = false) { super(prms, dbg); this._children = []; this._observable = new Observable(); this._defaultCalculatedParam = this.getFirstAnalyticalParameter(); this.resetDefaultCalculatedParam(); } /** * Finds the previous calculated parameter and sets its mode to SINGLE */ public unsetCalculatedParam(except: ParamDefinition) { for (const p of this.parameterIterator) { if ( p.valueMode === ParamValueMode.CALCUL && p !== except ) { p.setValueMode(ParamValueMode.SINGLE, false); } } } /** * Tries to reset the calculated parameter, successively, to : * - the default one if it is in SINGLE mode * - the first SINGLE calculable parameter other than requirer * - the first MINMAX/LISTE calculable parameter other than requirer * * If no default calculated parameter is defined, does nothing. */ public resetDefaultCalculatedParam(requirer?: ParamDefinition) { if (this._defaultCalculatedParam) { // if default calculated param is not eligible to CALC mode if ( requirer === this._defaultCalculatedParam || ! [ ParamValueMode.SINGLE, ParamValueMode.MINMAX, ParamValueMode.LISTE ].includes(this._defaultCalculatedParam.valueMode ) ) { // first SINGLE calculable parameter if any const newCalculatedParam = this.findFirstCalculableParameter(requirer); if (newCalculatedParam) { this.calculatedParam = newCalculatedParam; } else {
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
// @TODO throws when a new linkable nub is added, and all parameters are already linked ! throw Error("resetDefaultCalculatedParam : could not find any SINGLE/MINMAX/LISTE parameter"); } } else { // default one this.calculatedParam = this._defaultCalculatedParam; } } else { // do nothing (ex: Section Paramétrée) } } /** * Returns the first visible calculable parameter other than "Pr", * and other than otherThan, that is set to SINGLE, MINMAX or LISTE * mode (there might be none) */ public findFirstCalculableParameter(otherThan?: ParamDefinition) { for (const p of this.parameterIterator) { if ( [ ParamCalculability.EQUATION, ParamCalculability.DICHO ].includes(p.calculability) && p.symbol !== "Pr" && p.visible && p !== otherThan && [ ParamValueMode.SINGLE, ParamValueMode.MINMAX, ParamValueMode.LISTE ].includes(p.valueMode) ) { return p; } } return undefined; } /** * Formule utilisée pour le calcul analytique (solution directe ou méthode de résolution spécifique) */ public abstract Equation(sVarCalc: string): Result; /** * Calcul d'une équation quelle que soit l'inconnue à calculer; déclenche le calcul en * chaîne des modules en amont si nécessaire * @param sVarCalc nom de la variable à calculer * @param rInit valeur initiale de la variable à calculer dans le cas de la dichotomie */ public Calc(sVarCalc?: string, rInit?: number): Result { let computedVar; // overload calculated param at execution time ? if (sVarCalc) { computedVar = this.getParameter(sVarCalc); } else { computedVar = this.calculatedParam; } if (rInit === undefined) { rInit = computedVar.v; } if (computedVar.isAnalytical()) { this._result = this.Equation(sVarCalc); this._result.name = sVarCalc; return this._result; } const resSolve: Result = this.Solve(sVarCalc, rInit); if (!resSolve.ok) { this._result = resSolve; this._result.name = sVarCalc; return this._result; } const sAnalyticalPrm: string = this.getFirstAnalyticalParameter().symbol; computedVar.v = resSolve.vCalc; const res: Result = this.Equation(sAnalyticalPrm);
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
res.vCalc = resSolve.vCalc; this._result = res; this._result.name = sVarCalc; this.notifyResultUpdated(); return res; } /** * Calculates required Nubs so that all input data is available; * uses 50% of the progress */ public triggerChainCalculation() { const requiredNubs1stLevel = this.getRequiredNubs(); if (requiredNubs1stLevel.length > 0) { const progressStep = Nub.progressPercentageAccordedToChainCalculation / requiredNubs1stLevel.length; for (const rn of requiredNubs1stLevel) { rn.CalcSerie(); this.progress += progressStep; } // round progress to accorded percentage this.progress = Nub.progressPercentageAccordedToChainCalculation; } } /** * Effectue une série de calculs sur un paramètre; déclenche le calcul en chaîne * des modules en amont si nécessaire * @param rInit solution approximative du paramètre * @param sDonnee éventuel symbole / paire symbole-uid du paramètre à calculer */ public CalcSerie(rInit?: number, sDonnee?: any): Result { // variated parameters caracteristics const variated: Array<{ param: ParamDefinition, values: ParamValues }> = []; // prepare calculation this.progress = 0; this.triggerChainCalculation(); this.copySingleValuesToSandboxValues(); // check which values are variating, if any for (const p of this.parameterIterator) { if ( p.valueMode === ParamValueMode.LISTE || p.valueMode === ParamValueMode.MINMAX || ( p.valueMode === ParamValueMode.LINK && p.isReferenceDefined() && p.referencedValue.hasMultipleValues() ) ) { variated.push({ param: p, // extract variated values from variated Parameters // (in LINK mode, proxies to target data) values: p.paramValues }); } } // find calculated parameter const computedSymbol = this.findCalculatedParameter(sDonnee); if (rInit === undefined && this.calculatedParam) { rInit = this.calculatedParam.v; } if (variated.length === 0) { // no parameter is varying this._result = this.doCalc(computedSymbol, rInit);
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
this.progress = 100; } else { // at least one parameter is varying // find longest series let longest = 0; for (let i = 0; i < variated.length; i++) { if (variated[i].values.valuesIterator.count() > variated[longest].values.valuesIterator.count()) { longest = i; } } const size = variated[longest].values.valuesIterator.count(); // grant the remaining percentage of the progressbar to local calculation // (should be 50% if any chain calculation occurred, 100% otherwise) let progressStep; const remainingProgress = 100 - this.progress; progressStep = remainingProgress / size; const res = new Result(undefined, this); // (re)init all iterators for (const v of variated) { if (v === variated[longest]) { v.values.initValuesIterator(false); } else { v.values.initValuesIterator(false, size); } } // iterate over longest series (in fact any series would do) while (variated[longest].values.hasNext) { // get next value for all variating parameters for (const v of variated) { const currentIteratorValue = v.values.next(); v.param.v = currentIteratorValue.value; } // calculate this.doCalc(computedSymbol, rInit); // résultat dans this._result if (this._result.ok) { res.addResultElement(this._result.resultElement); res.addLog(this._result.log); rInit = this._result.resultElement.vCalc; } res.globalLog.addLog(this._result.globalLog); // update progress this.progress += progressStep; } this._result = res; // round progress to 100% this.progress = 100; } if (computedSymbol !== undefined) { const realSymbol = (typeof computedSymbol === "string") ? computedSymbol : computedSymbol.symbol; this._result.name = realSymbol; } this.notifyResultUpdated(); return this._result; } /** * Renvoie la valeur actuelle d'un paramètre (valeur utilisateur ou résultat) * @param p Paramètre */ public V(p: ParamDefinition): number { if (p.valueMode === ParamValueMode.CALCUL) { if (this.result !== undefined) { if (this.result.ok) {
421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
return this.result.vCalc; } } throw new Error(`Attempt to read the result of ${p.symbol} but none found`); } return p.v; } public addChild(child: Nub, after?: number) { if (after !== undefined) { this._children.splice(after + 1, 0, child); } else { this._children.push(child); } // add reference to parent collection (this) child.parent = this; // propagate precision child.prms.Pr.setValue(this.prms.Pr.v); // does not write to .v to bypass calculability control // postprocessing this.adjustChildParameters(child); } public getChildren(): Nub[] { return this._children; } public getParent(): Nub { return this.parent; } /** * Returns true if all parameters are valid; used to check validity of * parameters linked to Nub results */ public isComputable() { let valid = true; for (const p of this.prms) { valid = valid && p.isValid; } return valid; } /** * Liste des valeurs (paramètre, résultat, résultat complémentaire) liables à un paramètre * @param src paramètre auquel lier d'autres valeurs */ public getLinkableValues(src: ParamDefinition): LinkedValue[] { let res: LinkedValue[] = []; const symbol = src.symbol; // If parameter comes from the same Nub, its parent or any of its children, // no linking is possible at all. // Different Structures in the same parent can get linked to each other. if (! this.isParentOrChildOf(src.nubUid)) { // 1. own parameters for (const p of this._prms) { // if symbol and Nub type are identical, or if family is identical if ( (p.symbol === symbol && this.calcType === src.nubCalcType) || (p.family !== undefined && (p.family === src.family)) ) { // if it is safe to link p's value to src if (p.isLinkableTo(src)) { // if p is a CALC param of a Structure other than "Q" // (structures always have Q as CALC param and cannot have another) // or a CALC param of a Section, that is not sibling of the target // (to prevent circular dependencies among ParallelStructures), // expose its parent if (
491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
( (this instanceof Structure && p.symbol !== "Q" && ! this.isSiblingOf(src.nubUid)) || this instanceof acSection ) && (p.valueMode === ParamValueMode.CALCUL) ) { // trick to expose p a a result of the parent Nub res.push(new LinkedValue(this.parent, p, p.symbol)); } else { // do not suggest parameters that are already linked to another one if (p.valueMode !== ParamValueMode.LINK) { res.push(new LinkedValue(this, p, p.symbol)); } } } } } // 2. extra results if (this._extraResultsFamilies) { // if I don't depend on your result, you may depend on mine ! if (! this.dependsOnNubResult(src.parentNub)) { const erk = Object.keys(this._extraResultsFamilies); // browse extra results for (const erSymbol of erk) { const erFamily = this._extraResultsFamilies[erSymbol]; // if family is identical if (erFamily === src.family) { res.push(new LinkedValue(this, undefined, erSymbol)); } } } } } // 3. children Nubs, except for PAB if (! (this instanceof Pab)) { for (const cn of this.getChildren()) { res = res.concat(cn.getLinkableValues(src)); } } return res; } /** * Returns true if the given Nub UID is either of : * - the current Nub UID * - the current Nub's parent Nub UID * - the UID of any of the current Nub's children */ public isParentOrChildOf(uid: string): boolean { if (this.uid === uid) { return true; } const parent = this.getParent(); if (parent && parent.uid === uid) { return true; } for (const c of this.getChildren()) { if (c.uid === uid) { return true; } } return false; } /** * Returns true if the given Nub UID : * - is the current Nub UID
561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
* - is the UID of any of the current Nub's siblings (children of the same parent) */ public isSiblingOf(uid: string): boolean { if (this.uid === uid) { return true; } const parent = this.getParent(); if (parent) { for (const c of parent.getChildren()) { if (c.uid === uid) { return true; } } return true; } return false; } /** * Returns all Nubs whose results are required by the given one, * without following links (stops when it finds a Nub that has to * be calculated). Used to trigger chain calculation. */ public getRequiredNubs(visited: string[] = []) { const requiredNubs: Nub[] = []; // prevent loops if (! visited.includes(this.uid)) { visited.push(this.uid); // inspect all target Nubs for (const p of this.parameterIterator) { if (p.valueMode === ParamValueMode.LINK && p.isReferenceDefined()) { const nub = p.referencedValue.nub; // a Nub is required if I depend on its result if (this.dependsOnNubResult(nub, false)) { requiredNubs.push(nub); } } } } return requiredNubs; } /** * Returns all Nubs whose parameters or results are targettes * by the given one. * (used for dependencies checking at session saving time) */ public getTargettedNubs(visited: string[] = []) { const targettedNubs: Nub[] = []; // prevent loops if (! visited.includes(this.uid)) { visited.push(this.uid); // inspect all target Nubs for (const p of this.parameterIterator) { if (p.valueMode === ParamValueMode.LINK && p.isReferenceDefined()) { targettedNubs.push(p.referencedValue.nub); } } } return targettedNubs; } /** * Returns true if * - this Nub * - any of this Nub's children * - this Nub's parent
631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
* depends on * - the result of the given Nub * - the result of any of the given Nub's children * - the result of the given Nub's parent * * (ie. "my family depends on the result of your family") * * If followLinksChain is false, will limit the exploration to the first target level only */ public dependsOnNubResult(nub: Nub, followLinksChain: boolean = true): boolean { let thisFamily: Nub[] = [this]; const tp = this.getParent(); if (tp) { thisFamily = thisFamily.concat(tp); } thisFamily = thisFamily.concat(this.getChildren()); let yourFamily: Nub[] = []; const you = nub; if (you) { yourFamily = [you]; const yp = you.getParent(); if (yp) { yourFamily = yourFamily.concat(yp); } yourFamily = yourFamily.concat(you.getChildren()); } let depends = false; outerloop: for (const t of thisFamily) { // look for a parameter linked to the result of any Nub of yourFamily for (const p of t.prms) { if (p.valueMode === ParamValueMode.LINK) { if (p.isLinkedToResultOfNubs(yourFamily, followLinksChain)) { // dependence found ! depends = true; break outerloop; } } } } return depends; } /** * Returns true if this nub (parent and children included) * directly requires (1st level only) @TODO follow links ? * the given parameter */ public dependsOnParameter(src: ParamDefinition) { let thisFamily: Nub[] = [this]; const tp = this.getParent(); if (tp) { thisFamily = thisFamily.concat(tp); } thisFamily = thisFamily.concat(this.getChildren()); let depends = false; outerloop: for (const t of thisFamily) { // look for a parameter of thisFamily that is directly linked to src for (const p of t.prms) { if ( p.valueMode === ParamValueMode.LINK && p.isReferenceDefined() && p.referencedValue.isParameter()
701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
) { if ( p.referencedValue.nub.uid === src.nubUid && p.referencedValue.symbol === src.symbol ) { // dependence found ! depends = true; break outerloop; } } } } return depends; } /** * Returns true if the computation of the current Nub (parent and children * included) directly requires (1st level only), anything (parameter or result) * from the given Nub UID "uid", its parent or any of its children * @param uid */ public resultDependsOnNub(uid: string, visited: string[] = []): boolean { if (uid !== this.uid && ! visited.includes(this.uid)) { visited.push(this.uid); // does any of our parameters depend on the target Nub ? for (const p of this._prms) { if (p.valueMode === ParamValueMode.LINK) { if (p.dependsOnNubFamily(uid)) { return true; } } } // does any of our parent's parameters depend on the target Nub ? const parent = this.getParent(); if (parent) { if (parent.resultDependsOnNub(uid, visited)) { return true; } } // does any of our children' parameters depend on the target Nub ? for (const c of this.getChildren()) { if (c.resultDependsOnNub(uid, visited)) { return true; } } } return false; } /** * Returns true if the computation of the current Nub has multiple values * (whether it is already computed or not), by detecting if any parameter, * linked or not, is variated */ public resultHasMultipleValues(): boolean { let hmv = false; for (const p of this.parameterIterator) { if (p.valueMode === ParamValueMode.MINMAX || p.valueMode === ParamValueMode.LISTE) { hmv = true; } else if (p.valueMode === ParamValueMode.LINK && p.isReferenceDefined()) { // indirect recursivity hmv = hmv || p.referencedValue.hasMultipleValues(); } } return hmv; } /**
771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840
* Sets the current result to undefined, as well as the results * of all depending Nubs; also invalidates all fake ParamValues * held by LinkedValues pointing to this result */ public resetResult(visited: string[] = []) { this._result = undefined; visited.push(this.uid); const dependingNubs = Session.getInstance().getDependingNubs(this.uid); for (const dn of dependingNubs) { if (! visited.includes(dn.uid)) { dn.resetResult(visited); } dn.resetLinkedParamValues(this); } } /** * For all parameters pointing to the result of the given Nub, * invalidates fake ParamValues held by pointed LinkedValue */ public resetLinkedParamValues(nub: Nub) { for (const p of this.parameterIterator) { if (p.valueMode === ParamValueMode.LINK && p.isReferenceDefined()) { if ( (p.referencedValue.isResult() || p.referencedValue.isExtraResult()) && p.referencedValue.nub.uid === nub.uid ) { p.referencedValue.invalidateParamValues(); } } } } /** * Returns a JSON representation of the Nub's current state * @param extra extra key-value pairs, for ex. calculator title in GUI */ public serialise(extra?: object) { return JSON.stringify(this.objectRepresentation(extra)); } /** * Returns an object representation of the Nub's current state * @param extra extra key-value pairs, for ex. calculator title in GUI */ public objectRepresentation(extra?: object) { let ret: any = { uid: this.uid, props: this.properties.props, }; if (extra) { ret = {...ret, ...{ meta: extra } }; // merge properties } ret = {...ret, ...{ children: [], parameters: [] } }; // extend here to make "parameters" the down-most key // iterate over local parameters const localParametersIterator = new ParamsEquationArrayIterator([this._prms]); for (const p of localParametersIterator) { if (p.visible) { ret.parameters.push(p.objectRepresentation()); } } // iterate over children Nubs for (const child of this._children) { ret.children.push(child.objectRepresentation()); } return ret;
841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
} /** * Fills the current Nub with parameter values, provided an object representation * @param obj object representation of a Nub content (parameters) * @returns the calculated parameter found, if any - used by child Nub to notify * its parent of the calculated parameter to set */ public loadObjectRepresentation(obj: any): { p: ParamDefinition, hasErrors: boolean } { // return value const ret: { p: ParamDefinition, hasErrors: boolean } = { p: undefined, hasErrors: false }; // set parameter modes and values if (obj.parameters && Array.isArray(obj.parameters)) { for (const p of obj.parameters) { const param = this.getParameter(p.symbol); if (param) { // load parameter const pRet = param.loadObjectRepresentation(p, false); if (pRet.calculated) { ret.p = param; } // forward errors if (pRet.hasErrors) { ret.hasErrors = true; } } else { console.error(`session file : cannot find parameter ${p.symbol} in target Nub`); ret.hasErrors = true; } } // define calculated param at Nub level // @TODO except if we are a Section / Structure / Cloisons ? if (ret.p) { this.calculatedParam = ret.p; } } // iterate over children if any if (obj.children && Array.isArray(obj.children)) { for (const s of obj.children) { // create the Nub const subNub = Session.getInstance().createNub(new Props(s.props), this); // try to keep the original ID if (! Session.getInstance().uidAlreadyUsed(s.uid)) { subNub.setUid(s.uid); } const childRet = subNub.loadObjectRepresentation(s); // add Structure to parent this.addChild(subNub); // set calculated parameter for child ? if (childRet.p) { this.calculatedParam = childRet.p; } // forward errors if (childRet.hasErrors) { ret.hasErrors = true; } } } return ret; } /** * Once session is loaded, run a second pass on all linked parameters to * reset their target if needed */
911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980
public fixLinks(obj: any): { hasErrors: boolean } { // return value const ret = { hasErrors: false }; if (obj.parameters && Array.isArray(obj.parameters)) { for (const p of obj.parameters) { const mode: ParamValueMode = (ParamValueMode as any)[p.mode]; // get enum index for string value if (mode === ParamValueMode.LINK) { const param = this.getParameter(p.symbol); // formulaire dont le Nub est la cible du lien const destNub = Session.getInstance().findNubByUid(p.targetNub); if (destNub) { try { param.defineReference(destNub, p.targetParam); } catch (err) { console.error(`fixLinks: defineReference error` + ` (${this.uid}.${param.symbol} => ${destNub.uid}.${p.targetParam})`); // forward error ret.hasErrors = true; } } else { console.error("fixLinks : cannot find target Nub"); // forward error ret.hasErrors = true; } } } } return ret; } /** * Replaces a child Nub * @param index index of child in _children array * @param child new child * @param resetCalcParam if true, resets default calculated parameter after replacing child * @param keepParametersState if true, mode and value of parameters will be kept between old and new section */ public replaceChild(index: number, child: Nub, resetCalcParam: boolean = false, keepParametersState: boolean = true ) { if (index > -1 && index < this._children.length) { const hasOldChild = (this._children[index] !== undefined); const parametersState: any = {}; // store old parameters state if (keepParametersState && hasOldChild) { for (const p of this._children[index].parameterIterator) { parametersState[p.symbol] = p.objectRepresentation(); } } // replace child this._children[index] = child; // reapply parameters state if (keepParametersState && hasOldChild) { for (const p of this._children[index].parameterIterator) { if (p.symbol in parametersState) { p.loadObjectRepresentation(parametersState[p.symbol]); } } } } else { throw new Error(`${this.constructor.name}.replaceChild invalid index ${index}` + ` (children length: ${this._children.length})`); } if (resetCalcParam) { this.resetDefaultCalculatedParam();
981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050
} // add reference to parent collection (this) child.parent = this; // postprocessing this.adjustChildParameters(child); } /** * Finds oldChild in the list, and replaces it at the same index with newChild; * if the current calculated parameter belonged to the old child, resets a default one * @param oldChild child to get the index for * @param newChild child to set at this index */ public replaceChildInplace(oldChild: Nub, newChild: Nub) { const calcParamLost = ( typeof this.calculatedParamDescriptor !== "string" && this.calculatedParamDescriptor.uid === oldChild.uid ); const index = this.getIndexForChild(oldChild); if (index === -1) { throw new Error("old child not found"); } this.replaceChild(index, newChild, calcParamLost); } /** * Returns the current index of the given child if any, * or else returns -1 * @param child child or child UID to look for */ public getIndexForChild(child: Nub | string): number { let index: number = -1; const uid = (child instanceof Nub) ? child.uid : child; for (let i = 0; i < this._children.length; i++) { if (this._children[i].uid === uid) { index = i; } } return index; } /** * @return true if given Nub id a child of current Nub */ public hasChild(child: Nub): boolean { return this.hasChildUid(child.uid); } /** * @return true if given Nub uid id a child of current Nub */ public hasChildUid(childUid: string): boolean { for (const s of this._children) { if (s.uid === childUid) { return true; } } return false; } /** * @return child having the given UID */ public getChild(uid: string): Nub { for (const s of this._children) { if (s.uid === uid) { return s; } } return undefined;
1051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120
} /** * Moves a child to first position */ public moveChildUp(child: Nub) { let i = 0; for (const s of this._children) { if (s.uid === child.uid && i > 0) { const t = this._children[i - 1]; this._children[i - 1] = this._children[i]; this._children[i] = t; return; } i++; } } /** * Moves a child to last position */ public moveChildDown(child: Nub) { let i = 0; for (const s of this._children) { if (s.uid === child.uid && i < this._children.length - 1) { const t = this._children[i]; this._children[i] = this._children[i + 1]; this._children[i + 1] = t; return; } i++; } } /** * Removes a child * @param index */ public deleteChild(index: number) { if (index > -1) { this._children.splice(index, 1); } else { throw new Error(`${this.constructor.name}.deleteChild invalid index=${index}`); } } /** * Returns the position or the current structure (starting at 0) * in the parent Nub */ public findPositionInParent(): number { if (this.parent) { return this.parent.getIndexForChild(this); } throw new Error(`Nub of class ${this.constructor.name} has no parent`); } // interface IObservable /** * ajoute un observateur à la liste */ public addObserver(o: Observer) { this._observable.addObserver(o); } /** * supprime un observateur de la liste */ public removeObserver(o: Observer) {
1121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190
this._observable.removeObserver(o); } /** * notifie un événement aux observateurs */ public notifyObservers(data: any, sender?: any) { this._observable.notifyObservers(data, sender); } protected findCalculatedParameter(sDonnee: any): any { let computedSymbol: any; if (sDonnee) { computedSymbol = sDonnee; } else { if (this.calculatedParam === undefined) { throw new Error(`CalcSerie() : aucun paramètre à calculer`); } computedSymbol = this.calculatedParamDescriptor; } return computedSymbol; } protected doCalc(computedSymbol?: any, rInit?: number) { return this.Calc(computedSymbol, rInit); } /** * Returns values of parameters and the result of the calculation for the calculated parameter * @param sVarCalc Symbol of the calculated param * @param result Result object of the calculation */ protected getParamValuesAfterCalc(sVarCalc: string, result: Result): { [key: string]: number } { const cPrms: { [key: string]: number } = {}; for (const p of this.parameterIterator) { if (p.symbol === sVarCalc) { cPrms[p.symbol] = result.vCalc; } else { cPrms[p.symbol] = p.v; } } return cPrms; } /** * For all SINGLE, LINK and CALC parameters, copies singleValue to * sandbox value (.v) before calculation */ protected copySingleValuesToSandboxValues() { for (const p of this.parameterIterator) { switch (p.valueMode) { case ParamValueMode.SINGLE: case ParamValueMode.CALCUL: p.v = p.singleValue; break; case ParamValueMode.LINK: if (p.referencedValue) { p.v = p.referencedValue.getValue(); } else { throw Error("Nub.CalcSerie() : linked value not defined"); } break; default: // variable mode, let iterator do the job p.v = undefined; } } } /**
1191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236
* To call Nub.calc(…) from indirect child */ protected nubCalc(sVarCalc: string, rInit?: number): Result { return Nub.prototype.Calc.call(this, sVarCalc, rInit); } /** * Used by GUI to update results display */ protected notifyResultUpdated() { this.notifyObservers({ action: "resultUpdated" }, this); } /** * Used by GUI to update progress bar */ protected notifyProgressUpdated() { this.notifyObservers({ action: "progressUpdated", value: this._progress }, this); } /** * Optional postprocessing after adding / replacing a child */ // tslint:disable-next-line:no-empty protected adjustChildParameters(child: Nub) {} /** * Résoud l'équation par une méthode numérique * @param sVarCalc nom de la variable à calculer * @param rInit valeur initiale de la variable à calculer dans le cas de la dichotomie */ private Solve(sVarCalc: string, rInit: number): Result { const dicho: Dichotomie = new Dichotomie(this, sVarCalc, this.DBG); dicho.startIntervalMaxSteps = this.dichoStartIntervalMaxSteps; const target = this.getFirstAnalyticalParameter(); const rPrec = this._prms.Pr.v; return dicho.Dichotomie(target.v, rPrec, rInit); } }