nub.ts 18.79 KiB
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}`); } } }