An error occurred while loading the file. Please try again.
-
Mathias Chouet authoredb6cc92bf
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);
}
}