nub.ts 38.64 KiB
import { CalculatorType, ComputeNode } from "./compute-node"; // nghyd build fails when commenting CalculatorType @WTF
import { Dichotomie } from "./dichotomie";
import { acSection, IParamDefinitionIterator, 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 {
    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;
    /** 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;
    public constructor(prms: ParamsEquation, dbg: boolean = false) {
        super(prms, dbg);
        this._children = [];
        this._observable = new Observable();
        this._defaultCalculatedParam = this.getFirstAnalyticalParameter();
        this.resetDefaultCalculatedParam();
    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;
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
public set properties(props: Props) { this._props = props.clone(); } /** * 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) { // if called within constructor, default class member value is not set yet for (const child of this._children) { prms.push(child.prms); } } return new ParamsEquationArrayIterator(prms); } protected get progress() { return this._progress; } /** * Updates the progress percentage and notifies observers */ protected set progress(v: number) { this._progress = v; this.notifyProgressUpdated(); } 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 }; } } /** * Resets the calculated parameter to the default one if it is in SINGLE * mode, or else to the first calculable parameter other than requirer. * If no default calculated parameter is defined, does nothing. */
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
public resetDefaultCalculatedParam(requirer?: ParamDefinition) { if (this._defaultCalculatedParam) { // if default calculated param is not eligible to CALC mode if ( requirer === this._defaultCalculatedParam || this._defaultCalculatedParam.valueMode !== ParamValueMode.SINGLE ) { // first SINGLE calculable parameter if any const newCalculatedParam = this.findFirstSingleParameter(requirer); if (newCalculatedParam) { this.calculatedParam = newCalculatedParam; } else { throw Error("resetDefaultCalculatedParam : could not find any SINGLE 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 mode * (there might be none) */ public findFirstSingleParameter(otherThan?: ParamDefinition) { for (const p of this.parameterIterator) { if ( [ ParamCalculability.EQUATION, ParamCalculability.DICHO ].includes(p.calculability) && p.symbol !== "Pr" && p.visible && p !== otherThan && p.valueMode === ParamValueMode.SINGLE ) { return p; } } return undefined; } /** * Resets all parameters to SINGLE mode, except sourceParam (the one that asked * for the change) and parameters in LINK mode. * If exceptMode is defined, parameters in this mode won't be affected either. * * Used to ensure that * - exactly one parameter is in CALCUL mode * - at most one parameter variates (MIMAX / LISTE mode) */ public ensureParametersConsistency(sourceParam: ParamDefinition, exceptMode: ParamValueMode|ParamValueMode[] = []) { if (! Array.isArray(exceptMode)) { exceptMode = [ exceptMode ]; } // console.log(`nub ${this.uid} (${this.constructor.name})`); // console.log(`(calculated parameter: ${this.calculatedParam.symbol})`); for (const p of this.parameterIterator) { if ( p !== sourceParam && ! exceptMode.includes(p.valueMode) && ( // don't touch linked parameters p.valueMode !== ParamValueMode.LINK || ( // unless they are linked to multiple values… p.isReferenceDefined() && p.hasMultipleValues // …and we're not skipping multiple values && ! exceptMode.includes(ParamValueMode.MINMAX)
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
&& ! exceptMode.includes(ParamValueMode.LISTE) ) ) && p.valueMode !== ParamValueMode.SINGLE ) { // console.log(">> resetting", p.symbol); // "false" prevents infinite loop when managing CALC params p.setValueMode(ParamValueMode.SINGLE, false); } } } /** * 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); 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;
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
} // 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 { // instance de ParamValues utilisée pour le paramètre varié (qui peut être un paramètre référencé (importé)) let variatedParam: ParamDefinition; let variatedValues: ParamValues; this.progress = 0; this.triggerChainCalculation(); this.copySingleValuesToSandboxValues(); // check which values are variating, if any for (const p of this.parameterIterator) { switch (p.valueMode) { case ParamValueMode.SINGLE: case ParamValueMode.CALCUL: break; case ParamValueMode.LISTE: case ParamValueMode.MINMAX: variatedParam = this.setVariatedValues(p, variatedParam); break; case ParamValueMode.LINK: if ( p.isReferenceDefined() && p.referencedValue.hasMultipleValues() ) { variatedParam = this.setVariatedValues(p, variatedParam); } break; default: // tslint:disable-next-line:max-line-length throw new Error(`CalcSerie() : valeur de ParamValueMode ${ParamValueMode[p.valueMode]} non prise en charge`); } } let computedSymbol: any; if (sDonnee) { computedSymbol = sDonnee; } else { if (this.calculatedParam === undefined) { throw new Error(`CalcSerie() : aucun paramètre à calculer`); } computedSymbol = this.calculatedParamDescriptor; } if (rInit === undefined) { rInit = this.calculatedParam.v; } if (variatedParam === undefined) { this._result = this.Calc(computedSymbol, rInit); // résultat dans this._result // update progress to 100% this.progress = 100; } else { // extract variated values from variated Parameter
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
// (in LINK mode, proxies to target data) variatedValues = variatedParam.paramValues; // 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; const nbValues = variatedValues.valuesIterator.count(); progressStep = remainingProgress / nbValues; const res = new Result(undefined, this); variatedValues.initValuesIterator(false); while (variatedValues.hasNext) { const currentIteratorValue = variatedValues.next(); variatedParam.v = currentIteratorValue.value; // copy to local sandbox value this.Calc(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; } 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) { 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); }
421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
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, // expose its parent if ( ( (this instanceof Structure && p.symbol !== "Q") || 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 { 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];
491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
// if family is identical if (erFamily === src.family) { res.push(new LinkedValue(this, undefined, erSymbol)); } } } } } // 3. children Nubs 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 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[] = []) { let 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); } else { // check if linked Nub needs the result of another one, in // which case the latter's calculation is needed too requiredNubs = requiredNubs.concat(nub.getRequiredNubs(visited)); } } } } return requiredNubs; } /**
561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
* Returns true if * - this Nub * - any of this Nub's children * - this Nub's parent * 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) {
631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
if ( p.valueMode === ParamValueMode.LINK && p.isReferenceDefined() && p.referencedValue.isParameter() ) { 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 { for (const p of this.parameterIterator) { if (p.valueMode === ParamValueMode.MINMAX || p.valueMode === ParamValueMode.LISTE) { return true; } else if (p.valueMode === ParamValueMode.LINK && p.isReferenceDefined()) { // indirect recursivity return p.referencedValue.hasMultipleValues(); } } return false;
701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
} /** * 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());
771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840
} return ret; } /** * 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): ParamDefinition { let calculatedParam: ParamDefinition; // 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) { throw new Error(`session file : cannot find parameter ${p.symbol} in target Nub`); } // load parameter const iscalculated = param.loadObjectRepresentation(p, false); if (iscalculated) { calculatedParam = param; } } // define calculated param at Nub level // @TODO except if we are a Section / Structure / PabCloisons ? if (calculatedParam) { this.calculatedParam = calculatedParam; } } // 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 childCalculatedParam = subNub.loadObjectRepresentation(s); // add Structure to parent this.addChild(subNub); // set calculated parameter for child ? if (childCalculatedParam) { this.calculatedParam = childCalculatedParam; } } } return calculatedParam; } /** * Once session is loaded, run a second pass on all linked parameters to * reset their target if needed */ public fixLinks(obj: any) { 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) { param.defineReference(destNub, p.targetParam); } else {
841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
// quit console.error("fixLinks : cannot find target Nub"); } } } } } /** * 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(); } // 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); }
911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980
/** * 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; } /** * 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) {
981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050
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) { this._observable.removeObserver(o); } /** * notifie un événement aux observateurs */ public notifyObservers(data: any, sender?: any) { this._observable.notifyObservers(data, sender); } /** * 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();
105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119
} else { throw Error("Nub.CalcSerie() : linked value not defined"); } break; default: // variable mode, let iterator do the job p.v = undefined; } } } /** * To call Nub.calc(…) from indirect child */ protected nubCalc(sVarCalc: string, rInit?: number): Result { return Nub.prototype.Calc.call(this, sVarCalc, rInit); } /** * Sets the variated values and warns if there were already some */ protected setVariatedValues(newValues: ParamDefinition, oldValues: ParamDefinition): ParamDefinition { if (oldValues === undefined) { return newValues; } else { // tslint:disable-next-line:max-line-length throw new Error(`CalcSerie() : Paramètres à varier redondant : ${newValues.name}`); } } /** * 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); } }