import { CalculatorType, ComputeNode } from "./compute-node"; import { Dichotomie } from "./dichotomie"; import { acSection, IParamDefinitionIterator, Pab, ParamDefinition, ParamsEquation, ParamsEquationArrayIterator, Session, Structure } from "./index"; import { LinkedValue } from "./linked-value"; import { ParamCalculability, ParamFamily } 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"; import { ResultElement } from "./util/resultelement"; /** * 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; } /** * Local setter to set results of Equation() / Solve() / … as current * ResultElement, instead of overwriting the whole Result object * (used by CalcSerie with varying parameters) */ protected set currentResult(r: Result) { // this.setCurrentResult(r); if (! this._result) { this.initNewResultElement(); } this._result.resultElement = r.resultElement; } /** 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 array with the calculable parameters */ public get calculableParameters(): ParamDefinition[] { const calcPrms: ParamDefinition[] = []; for (const p of this.parameterIterator) { if ( p.symbol !== "Pr" && p.visible && [ParamCalculability.DICHO, ParamCalculability.EQUATION].includes(p.calculability) ) { calcPrms.push(p); } } return calcPrms; } /** * 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; } /** * 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(): CalculatorType { 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; /** * 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(); /** résultat de Calc()/CalcSerie() */ protected _result: Result; /** 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; private _firstAnalyticalPrmSymbol: string; 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 { // @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; /** * Calculate and put in cache the symbol of first parameter calculable analytically */ get firstAnalyticalPrmSymbol(): string { if (this._firstAnalyticalPrmSymbol === undefined) { this._firstAnalyticalPrmSymbol = this.getFirstAnalyticalParameter().symbol; } return this._firstAnalyticalPrmSymbol; } /** * Run Equation with first analytical parameter to compute * Returns the result in number form */ public EquationFirstAnalyticalParameter(): number { return this.Equation(this.firstAnalyticalPrmSymbol).vCalc; } /** * 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.currentResult = this.Equation(sVarCalc); this._result.symbol = sVarCalc; return this._result; } const resSolve: Result = this.Solve(sVarCalc, rInit); if (!resSolve.ok) { this.currentResult = resSolve; this._result.symbol = 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.currentResult = res; this._result.symbol = sVarCalc; this.notifyResultUpdated(); return this._result; } /** * 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; } // reinit Result and children Results this.reinitResult(); if (variated.length === 0) { // no parameter is varying // prepare a new slot to store results this.initNewResultElement(); this.doCalc(computedSymbol, rInit); // résultat dans this.currentResult (resultElement le plus récent) 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; } // prepare a new slot to store results this.initNewResultElement(); // calculate this.doCalc(computedSymbol, rInit); // résultat dans this.currentResult (resultElement le plus récent) if (this._result.resultElement.ok) { rInit = this._result.resultElement.vCalc; } // 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.symbol = realSymbol; } this.notifyResultUpdated(); return this._result; } 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 ( ( (this instanceof Structure && p.symbol !== "Q" && ! this.isSiblingOf(src.nubUid)) || this instanceof acSection ) && (p.valueMode === ParamValueMode.CALCUL) ) { // trick to expose p as 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._resultsFamilies) { // if I don't depend on your result, you may depend on mine ! if (! this.dependsOnNubResult(src.parentNub)) { const erk = Object.keys(this._resultsFamilies); // browse extra results for (const erSymbol of erk) { const erFamily = this._resultsFamilies[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 * - 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 targetted * 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 * 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() ) { 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 * @param symbol symbol of the target parameter whose value change triggered this method; * if current Nub targets this symbol, it will be considered dependent * @param includeValuesLinks if true, even if this Nub targets only non-calculated non-modified * parameters, it will be considered dependent @see jalhyd#98 */ public resultDependsOnNub( uid: string, visited: string[] = [], symbol?: string, includeValuesLinks: boolean = false ): 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, symbol, includeValuesLinks)) { 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, symbol, includeValuesLinks)) { return true; } } // does any of our children' parameters depend on the target Nub ? for (const c of this.getChildren()) { if (c.resultDependsOnNub(uid, visited, symbol, includeValuesLinks)) { 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; } /** * 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 * @param nubUidsInSession UIDs of Nubs that will be saved in session along with this one; * useful to determine if linked parameters must be kept as links * or have their value copied (if target is not in UIDs list) */ public serialise(extra?: object, nubUidsInSession?: string[]) { return JSON.stringify(this.objectRepresentation(extra, nubUidsInSession)); } /** * Returns an object representation of the Nub's current state * @param extra extra key-value pairs, for ex. calculator title in GUI * @param nubUidsInSession UIDs of Nubs that will be saved in session along with this one; * useful to determine if linked parameters must be kept as links * or have their value copied (if target is not in UIDs list) */ public objectRepresentation(extra?: object, nubUidsInSession?: string[]): object { let ret: any = { uid: this.uid, props: Session.invertEnumKeysAndValuesInProperties(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(nubUidsInSession)); } } // iterate over children Nubs for (const child of this._children) { ret.children.push(child.objectRepresentation(undefined, nubUidsInSession)); } 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): { 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 { // tslint:disable-next-line:no-console 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) { // decode properties const props = Session.invertEnumKeysAndValuesInProperties(s.props, true); // create the Nub const subNub = Session.getInstance().createNub(new Props(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 */ 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) { // tslint:disable-next-line:no-console console.error(`fixLinks: defineReference error` + ` (${this.uid}.${param.symbol} => ${destNub.uid}.${p.targetParam})`); // forward error ret.hasErrors = true; } } else { // tslint:disable-next-line:no-console 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})`); } // postprocessing this.adjustChildParameters(child); // ensure one parameter is calculated if (resetCalcParam) { this.resetDefaultCalculatedParam(); } // add reference to parent collection (this) child.parent = this; } /** * 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; } /** * 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`); } /** * Adds a new empty ResultElement to the current Result object, so that * computation result is stored into it, via set currentResult(); does * the same for all children */ public initNewResultElement() { if (! this._result) { this._result = new Result(undefined, this); } // push empty result element for further affectation va set currentResult() this._result.addResultElement(new ResultElement()); // do the same for children for (const c of this._children) { c.initNewResultElement(); } } /** * Sets this._result to a new empty Result, before starting a new CalcSerie(); * does the same for all children */ public reinitResult() { this._result = new Result(undefined, this); for (const c of this._children) { c.reinitResult(); } } // 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); } /** * Retrieves the ParamFamily associated to the given symbol, whether it is a * parameter or a result */ public getFamily(symbol: string): ParamFamily { // is it a parameter ? let p; try { // SectionNub.getParameter() (for ex.) throws exceptions p = this.getParameter(symbol); } catch (e) { // silent fail } if (p) { return p.family; } // is it a result ? if (symbol in this.resultsFamilies) { return this.resultsFamilies[symbol]; } // give up return undefined; } 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; } } } /** * 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); } }