dichotomie.ts 16.59 KiB
import { BoolIdentity, Debug } from "./internal_modules";
import { Nub } from "./internal_modules";
import { ParamDefinition } from "./internal_modules";
import { ParamDomain, ParamDomainValue } from "./internal_modules";
import { SessionSettings } from "./internal_modules";
import { Interval } from "./internal_modules";
import { Message, MessageCode } from "./internal_modules";
import { Result } from "./internal_modules";
import { SearchInterval } from "./internal_modules";
/**
 * calcul par dichotomie
 * principe : y=f(x1,x2,...), on connait une valeur de y,
 * on cherche par ex le x2 correspondant, les autres xi étant fixés.
 * la méthode Equation() calcule analytiquement y=f(xi)
export class Dichotomie extends Debug {
    /**
     * définition de la variable de la fonction
    private _paramX: ParamDefinition;
    /**
     * nombre d'étapes de recherche de l'intervalle de départ
    private _startIntervalMaxSteps = 50;
    /**
     * nom du paramètre calculé analytiquement par Equation()
    private analyticalSymbol: string;
    /**
     * Targetted value calculated by the function
    private _target: number;
    private _func: () => number;
    /**
     * si > 0, un calcul dichotomique est en cours
    private static _inDicho: number = 0;
    /**
     * Construction de la classe.
     * @param nub Noeud de calcul contenant la méthode de calcul Equation
     * @param sVarCalc Nom de la variable à calculer
    constructor(private nub: Nub, private sVarCalc: string, dbg: boolean = false, func?: () => number) {
        super(dbg);
        this._paramX = this.nub.getParameter(this.sVarCalc);
        if (func !== undefined) {
            this._func = func;
        } else {
            this._func = this.nub.EquationFirstAnalyticalParameter;
            this.analyticalSymbol = this.nub.firstAnalyticalPrmSymbol;
    private set vX(vCalc: number) {
        this._paramX.v = vCalc;
    /**
     * étapes de recherche de l'intervalle de départ
    get startIntervalMaxSteps() {
        return this._startIntervalMaxSteps;
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
set startIntervalMaxSteps(n: number) { this._startIntervalMaxSteps = n; } public static get inDico(): boolean { return Dichotomie._inDicho > 0; } public CalculX(x: number): number { this.vX = x; return this.Calcul(); } /** * trouve la valeur x pour y=f(x) avec un y donné * @param rTarget valeur de y * @param rTol tolérance pour l'arrêt de la recherche * @param rInit valeur initiale approximative de x */ public Dichotomie(rTarget: number, rTol: number, rInit: number): Result { Dichotomie._inDicho++; this._target = rTarget; // console.log("-----"); // for (let x = 0; x <= 1; x += 0.1) // console.log(this.CalculX(x).vCalc); // console.log("-----"); // recherche de l'intervalle de départ this.debug( "Valeur cible de " + this.analyticalSymbol + "=" + rTarget + ", valeur initiale de " + this.sVarCalc + "=" + rInit ); try { const r = this.getStartInterval(rTarget, rInit); if (r.ok) { const inter: SearchInterval = r.intSearch; // Recherche du zero par la méthode de Brent const s = this.uniroot( this.CalcZero, this, inter.min, inter.max, rTol ); if (s.found) { this.debug( `${this.analyticalSymbol}(${this.sVarCalc}=${s.value}) = ${rTarget}` ); Dichotomie._inDicho--; return new Result(s.value); } else { this.debug( "Non convergence" ); Dichotomie._inDicho--; return new Result(new Message(MessageCode.ERROR_DICHO_CONVERGE, { lastApproximation: s.value }), this.nub); } } else { Dichotomie._inDicho--; return new Result(r.res); } } catch (e) { // un appel à Calcul() a généré une erreur Dichotomie._inDicho--;
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
return this.nub.result; } } public debug(s: string) { if (this.DBG) { super.debug("Dichotomie: " + s); } } /** * Calcul de l'équation analytique. * @note Wrapper vers this.nub.Equation pour simplifier le code. * On utilise la première variable du tableau des variables pouvant être calculée analytiquement * Il faudra s'assurer que cette première variable correspond à la méthode de calcul la plus rapide */ private Calcul(): number { // return this.nub.Equation(this.analyticalSymbol).vCalc; return this._func.call(this.nub); } /** * Détermine si une fonction est croissante ou décroissante. * @param x point auquel on calcul la dérivée * @param dom domaine de définition de la variable */ private isIncreasingFunction(x: number, dom: Interval): boolean { let epsilon = 1e-8; for (let i = 0; i < 20; i++) { const bounds = new Interval(x - epsilon, x + epsilon); bounds.setInterval(bounds.intersect(dom)); // au cas où l'on sorte du domaine de la variable de la fonction const y1 = this.CalculX(bounds.min); const y2 = this.CalculX(bounds.max); if (Math.abs(y2 - y1) > 1E-6) return y2 > y1; epsilon *= 10; } return true; } /** * recherche l'intervalle contenant la valeur cible * @param rTarget valeur cible * @param intSearch intervalle de départ * @param intMax intervalle maximum (domaine de définition de la variable) */ private searchTarget(rTarget: number, intSearch: SearchInterval, intMax: Interval) { this.debug(`searchTarget debut: Target ${this.analyticalSymbol}=${rTarget} dans` + ` ${this.sVarCalc}=${intSearch.toString()}`); let n = 0; let ok: boolean = false; let bAllowRestart = false; do { intSearch.setInterval(intSearch.intersect(intMax)); if (intSearch.length === 0) { this.debug(`searchTarget length= 0: ${this.sVarCalc}=${intSearch.toString()}`) break; } if (intSearch.hasTargetValue(rTarget)) { ok = true; break; } intSearch.growStep(2); intSearch.next(); if (bAllowRestart) { intSearch.checkDirection() } else if (n > this._startIntervalMaxSteps / 2) { bAllowRestart = true; intSearch.reInit();
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
} } while (n++ < this._startIntervalMaxSteps); this.debug(`searchTarget fin: Target ${this.analyticalSymbol}=${rTarget} dans` + ` ${this.sVarCalc}=${intSearch.toString()}`); return { ok, intSearch }; } /** * Détermine l'intervalle de recherche initial * @param rTarget valeur cible de la fonction * @param rInit valeur initiale de la variable */ private getStartInterval(rTarget: number, rInit: number): any { const prmDom: ParamDomain = this._paramX.domain; const min: number = prmDom.minValue; const max: number = prmDom.maxValue; if (prmDom.domain === ParamDomainValue.NOT_NULL) { if (rInit === 0) { rInit = 1e-8; } } const intMax: Interval = new Interval(min, max); try { intMax.checkValue(rInit); } catch (m) { return { ok: false, res: m }; } // sens de variation de la fonction const inc = this.isIncreasingFunction(rInit, intMax); let step: number = 0.001; if (BoolIdentity(this.CalculX(rInit) > rTarget, inc)) { step = -step; // par ex, la fonction est croissante et la valeur initiale // de la variable a une image par la fonction > valeur cible } // initialisation de l'intervalle de recherche const intSearch1: SearchInterval = new SearchInterval(this, rInit, step, this.DBG); // on cherche dans une première direction let a = this.searchTarget(rTarget, intSearch1, intMax); if (a.ok) { return a; } // il se peut que la fonction ne soit pas monotone et qu'il faille chercher dans l'autre direction const intSearch2 = new SearchInterval(this, rInit, -step, this.DBG); a = this.searchTarget(rTarget, intSearch2, intMax); if (a.ok) { return a; } // gestion de l'erreur // la valeur cible de la fonction est elle trouvable ? let res: Message; let errDomain = false; switch (prmDom.domain) { case ParamDomainValue.INTERVAL: const si: SearchInterval = new SearchInterval(this, intMax.min, intMax.max - intMax.min); errDomain = !si.hasTargetValue(rTarget); break; case ParamDomainValue.POS: case ParamDomainValue.POS_NULL: const y = this.CalculX(1e-8);