An error occurred while loading the file. Please try again.
-
Grand Francois authored
refs #302
1a2aab3e
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);