An error occurred while loading the file. Please try again.
-
Mathias Chouet authored7930e1f4
import { ParamDefinition, Session } from ".";
import { ComputeNode } from "./compute-node";
import { Dichotomie } from "./dichotomie";
import { INamedIterableValues, IterableValues } from "./param/param-value-iterator";
import { ParamValueMode } from "./param/param-value-mode";
import { Props } from "./props";
import { Result } from "./util/result";
import { LinkedValue } from "./value_ref/object_ref";
/**
* Classe abstraite de Noeud de calcul dans une session :
* classe de base pour tous les calculs
*/
export abstract class Nub extends ComputeNode {
/** paramétrage de la dichotomie */
public dichoStartIntervalMaxSteps: number = 100;
/** résultat de Calc()/CalcSerie() */
protected _result: Result;
private _props: Props = new Props();
public get result(): Result {
return this._result;
}
/** Returns Props object (observable set of key-values) associated to this Nub */
public get properties(): Props {
return this._props;
}
public set properties(props: Props) {
this._props = props.clone();
}
/** Rapid accessor for calculator type */
public get calcType() {
return this._props.getPropValue("calcType");
}
/**
* 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
* @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 {
const computedVar = this.getParameter(sVarCalc);
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;
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
computedVar.setValue(resSolve.vCalc);
const res: Result = this.Equation(sAnalyticalPrm);
res.vCalc = resSolve.vCalc;
this._result = res;
this._result.name = sVarCalc;
return res;
}
/**
* Returns the parameter set to CALC mode, if any
*/
public getComputedParam(): ParamDefinition {
for (const p of this._prms) {
if (p.valueMode === ParamValueMode.CALCUL) {
return p;
}
}
return undefined;
}
/**
* effectue une série de calculs sur un paramètre
* @param rInit solution approximative du paramètre
* @param sDonnee éventuel symbole du paramètre à calculer
*/
public CalcSerie(rInit?: number, sDonnee?: string): Result {
let computedParam: ParamDefinition;
// instance de ParamValues utilisée pour le paramètre varié (qui peut être un paramètre référencé (importé))
let variatedValues: IterableValues;
for (const p of this.parameterIterator) {
if (p.valueMode === ParamValueMode.CALCUL) {
if (sDonnee === undefined) {
if (computedParam === undefined) {
computedParam = p;
} else {
// tslint:disable-next-line:max-line-length
throw new Error(`CalcSerie() : il y plusieurs paramètres à calculer (au moins ${computedParam.symbol} et ${p.symbol})`);
}
}
}
// checks which values are variating, if any
switch (p.valueMode) {
case ParamValueMode.SINGLE:
break;
case ParamValueMode.LISTE:
case ParamValueMode.MINMAX:
variatedValues = this.setVariatedValues(p, variatedValues);
break;
case ParamValueMode.CALCUL:
// Le paramètre lié est un résultat de calcul
// @TODO check this (check ultimate target param value mode ?)
// @TODO WTF comment un mode CALCUL peut-il avoir une référence ??
if (
p.isReferenceDefined()
&& p.referencedValue.isResult()
&& (p.referencedValue.element as Result).nbResultElements > 1
) {
variatedValues = this.setVariatedValues(p, variatedValues);
}
break;
case ParamValueMode.LINK:
if (
p.isReferenceDefined()
&& p.referencedValue.isResult()
&& (p.referencedValue.element as Result).nbResultElements > 1
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
) {
variatedValues = this.setVariatedValues(p, variatedValues);
}
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: string;
if (sDonnee) {
computedSymbol = sDonnee;
} else {
if (computedParam === undefined) {
throw new Error(`CalcSerie() : aucun paramètre à calculer`);
}
computedSymbol = computedParam.symbol;
}
if (rInit === undefined) {
rInit = computedParam.v;
}
if (variatedValues === undefined) {
this._result = this.Calc(computedSymbol, rInit); // résultat dans this._result
} else {
const res = new Result();
variatedValues.initValuesIterator(false);
while (variatedValues.hasNext) {
// tslint:disable-next-line:no-unused-expression
variatedValues.next();
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);
}
this._result = res;
}
this._result.name = computedSymbol;
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;
}
// generic methods to ease iteration
public getChildren(): Nub[] {
return [];
}
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
public getParent(): Nub {
return undefined;
}
/**
* 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, no linking is possible at all;
// different Structures in the same parent are different Nubs so they
// can get linked to each other
if (src.nubUid !== this.uid) {
// 1. 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)) {
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.nubUid)) {
const erk = Object.keys(this._extraResultsFamilies);
// browse extra results
for (const erSymbol of erk) {
const erFamily = this._extraResultsFamilies[erSymbol];
// if family is identical
if (erFamily === src.family) {
res.push(new LinkedValue(this, undefined, erSymbol));
}
}
}
}
}
// 3. children Nubs
for (const cn of this.getChildren()) {
res = res.concat(cn.getLinkableValues(src));
}
return res;
}
/**
* 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")
*/
public dependsOnNubResult(uid: string): boolean {
let thisFamily: Nub[] = [this];
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
const tp = this.getParent();
if (tp) {
thisFamily = thisFamily.concat(tp);
}
thisFamily = thisFamily.concat(this.getChildren());
let yourFamily: Nub[] = [];
const you = Session.getInstance().findNubByUid(uid);
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)) {
// 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;
}
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
/**
* 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._prms) {
if (p.valueMode === ParamValueMode.MINMAX || p.valueMode === ParamValueMode.LISTE) {
return true;
} else if (p.valueMode === ParamValueMode.LINK) {
// indirect recursivity
return p.referencedValue.hasMultipleValues();
}
}
return false;
}
/**
* Sets the current result to undefined, as well as the results
* of all depending Nubs
*/
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);
}
}
}
/**
* 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.getPropertiesData(),
};
if (extra) {
ret = {...ret, ...{ meta: extra } }; // merge properties
}
ret = {...ret, ...{ parameters: [] } }; // extend here to make "parameters" the down-most key
// iterate over parameters
for (const p of this.parameterIterator) {
if (p.visible) {
ret.parameters.push(p.objectRepresentation());
}
}
return ret;
}
/**
* Fills the current Nub with parameter values, provided an object representation
* @param obj object representation of a Nub content (parameters)
*/
public loadObjectRepresentation(obj: any) {
421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
// 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`);
}
// set mode
const mode: ParamValueMode = (ParamValueMode as any)[p.mode]; // get enum index for string value
param.valueMode = mode;
// set value(s)
switch (mode) {
case ParamValueMode.SINGLE:
param.setValue(p.value);
break;
case ParamValueMode.MINMAX:
param.paramValues.min = p.min;
param.paramValues.max = p.max;
param.paramValues.step = p.step;
break;
case ParamValueMode.LISTE:
param.paramValues.valueList = p.values;
break;
case ParamValueMode.CALCUL:
// nothing to do
break;
case ParamValueMode.LINK:
// formulaire dont le Nub est la cible du lien
const destNub = Session.getInstance().findNubByUid(p.targetNub);
if (destNub) {
param.defineReference(destNub, p.targetParam);
} // si la cible du lien n'existe pas, Session.fixLinks() est censé s'en occuper
break;
default:
throw new Error(`session file : invalid value mode '${p.mode}' in param object`);
}
}
}
}
/**
* 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 {
// quit
console.error("fixLinks : cannot find target Nub");
}
}
}
}
}
// overloaded by ParallelStructure
public hasStructureUid(uid: string): boolean {
491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
return false;
}
// overloaded by ParallelStructure
public hasStructure(structure: Nub): boolean {
return false;
}
/**
* To call Nub.calc(…) from indirect child
*/
protected nubCalc(sVarCalc: string, rInit?: number): Result {
return Nub.prototype.Calc.call(this, sVarCalc, rInit);
}
/**
* Returns inner properties dataset associated to this Nub
*/
protected getPropertiesData(): object {
return this._props.props;
}
/**
* 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);
}
/**
* Sets the variated values and warns if there were already some
*/
private setVariatedValues(newValues: INamedIterableValues, oldValues: IterableValues) {
if (oldValues === undefined) {
return newValues;
} else {
// tslint:disable-next-line:max-line-length
throw new Error(`CalcSerie() : Paramètres à varier redondant : ${newValues.name}`);
}
}
}