jalhyd
Version:
JaLHyd, a Javascript Library for Hydraulics
389 lines • 16.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Dichotomie = void 0;
const internal_modules_1 = require("./internal_modules");
const internal_modules_2 = require("./internal_modules");
const internal_modules_3 = require("./internal_modules");
const internal_modules_4 = require("./internal_modules");
const internal_modules_5 = require("./internal_modules");
const internal_modules_6 = require("./internal_modules");
const internal_modules_7 = require("./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)
*/
class Dichotomie extends internal_modules_1.Debug {
/**
* Construction de la classe.
* @param nub Noeud de calcul contenant la méthode de calcul Equation
* @param sVarCalc Nom de la variable à calculer
*/
constructor(nub, sVarCalc, dbg = false, func) {
super(dbg);
this.nub = nub;
this.sVarCalc = sVarCalc;
/**
* nombre d'étapes de recherche de l'intervalle de départ
*/
this._startIntervalMaxSteps = 50;
this._paramX = this.nub.getParameter(this.sVarCalc);
if (func !== undefined) {
this._func = func;
}
else {
this._func = this.nub.EquationFirstAnalyticalParameter;
this.analyticalSymbol = this.nub.firstAnalyticalPrmSymbol;
}
}
set vX(vCalc) {
this._paramX.v = vCalc;
}
/**
* étapes de recherche de l'intervalle de départ
*/
get startIntervalMaxSteps() {
return this._startIntervalMaxSteps;
}
set startIntervalMaxSteps(n) {
this._startIntervalMaxSteps = n;
}
static get inDicho() {
return Dichotomie._inDicho > 0;
}
CalculX(x) {
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
*/
Dichotomie(rTarget, rTol, rInit) {
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 = 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 internal_modules_6.Result(s.value);
}
else {
this.debug("Non convergence");
Dichotomie._inDicho--;
return new internal_modules_6.Result(new internal_modules_5.Message(internal_modules_5.MessageCode.ERROR_DICHO_CONVERGE, {
lastApproximation: s.value
}), this.nub);
}
}
else {
Dichotomie._inDicho--;
return new internal_modules_6.Result(r.res);
}
}
catch (e) {
// un appel à Calcul() a généré une erreur
Dichotomie._inDicho--;
return this.nub.result;
}
}
debug(s) {
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
*/
Calcul() {
// 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
*/
isIncreasingFunction(x, dom) {
let epsilon = 1e-8;
for (let i = 0; i < 20; i++) {
const bounds = new internal_modules_4.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)
*/
searchTarget(rTarget, intSearch, intMax) {
this.debug(`searchTarget debut: Target ${this.analyticalSymbol}=${rTarget} dans`
+ ` ${this.sVarCalc}=${intSearch.toString()}`);
let n = 0;
let ok = 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();
}
} 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
*/
getStartInterval(rTarget, rInit) {
const prmDom = this._paramX.domain;
const min = prmDom.minValue;
const max = prmDom.maxValue;
if (prmDom.domain === internal_modules_2.ParamDomainValue.NOT_NULL) {
if (rInit === 0) {
rInit = 1e-8;
}
}
const intMax = new internal_modules_4.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 = 0.001;
if ((0, internal_modules_1.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 = new internal_modules_7.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 internal_modules_7.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;
let errDomain = false;
switch (prmDom.domain) {
case internal_modules_2.ParamDomainValue.INTERVAL:
const si = new internal_modules_7.SearchInterval(this, intMax.min, intMax.max - intMax.min);
errDomain = !si.hasTargetValue(rTarget);
break;
case internal_modules_2.ParamDomainValue.POS:
case internal_modules_2.ParamDomainValue.POS_NULL:
const y = this.CalculX(1e-8);
errDomain = inc && (rTarget < y);
break;
case internal_modules_2.ParamDomainValue.ANY:
break;
default:
throw new Error("unsupported parameter domain value");
}
if (errDomain) {
res = new internal_modules_5.Message(internal_modules_5.MessageCode.ERROR_DICHO_INIT_DOMAIN);
res.extraVar.targetSymbol = this.analyticalSymbol; // symbole de la variable calculée par la fonction
res.extraVar.targetValue = rTarget; // valeur cible pour la fonction
// intervalle de valeurs pour la variable d'entrée de la fonction
res.extraVar.variableInterval = intMax.toString();
res.extraVar.variableSymbol = this._paramX.symbol; // symbole de la variable d'entrée de la fonction
}
else {
// Fusion des infos des deux intervalles explorés
const intFus = []; // Signification des indices : 0,1 : minmax target; 2, 3 : variables associées
if (isNaN(intSearch1.targets.max) || isNaN(intSearch2.targets.max)) {
// bug targets en NaN en prod mais pas en debug pas à pas en explorant les variables
intSearch1.updateTargets();
intSearch2.updateTargets();
}
if (intSearch1.targets.max > intSearch2.targets.max) {
intFus[1] = intSearch1.targets.max;
intFus[3] = intSearch1.getVal(intSearch1.targets.maxIndex);
}
else {
intFus[1] = intSearch2.targets.max;
intFus[3] = intSearch2.getVal(intSearch2.targets.maxIndex);
}
if (intSearch1.targets.min < intSearch2.targets.min) {
intFus[0] = intSearch1.targets.min;
intFus[2] = intSearch1.getVal(intSearch1.targets.minIndex);
}
else {
intFus[0] = intSearch2.targets.min;
intFus[2] = intSearch2.getVal(intSearch2.targets.minIndex);
}
if (intFus[1] < rTarget) {
res = new internal_modules_5.Message(internal_modules_5.MessageCode.ERROR_DICHO_TARGET_TOO_HIGH);
res.extraVar.extremeTarget = intFus[1];
res.extraVar.variableExtremeValue = intFus[3];
}
else {
res = new internal_modules_5.Message(internal_modules_5.MessageCode.ERROR_DICHO_TARGET_TOO_LOW);
res.extraVar.extremeTarget = intFus[0];
res.extraVar.variableExtremeValue = intFus[2];
}
res.extraVar.variableSymbol = this._paramX.symbol; // symbole de la variable de la fonction
res.extraVar.targetSymbol = this.analyticalSymbol; // symbole de la variable calculée par la fonction
res.extraVar.targetValue = rTarget; // valeur cible pour la fonction
}
return { ok: false, res };
}
CalcZero(x) {
this.vX = x;
return this.Calcul() - this._target;
}
/**
* Searches the interval from <tt>lowerLimit</tt> to <tt>upperLimit</tt>
* for a root (i.e., zero) of the function <tt>func</tt> with respect to
* its first argument using Brent's method root-finding algorithm.
*
* Translated from zeroin.c in http://www.netlib.org/c/brent.shar.
*
* Copyright (c) 2012 Borgar Thorsteinsson <borgar@borgar.net>
* MIT License, http://www.opensource.org/licenses/mit-license.php
*
* @param {function} func function for which the root is sought.
* @param {number} lowerlimit the lower point of the interval to be searched.
* @param {number} upperlimit the upper point of the interval to be searched.
* @param {number} errorTol the desired accuracy (convergence tolerance).
* @param {number} maxIter the maximum number of iterations.
* @returns an estimate for the root within accuracy.
*
*/
uniroot(func, thisArg, lowerLimit, upperLimit, errorTol = 0, maxIter) {
let a = lowerLimit;
let b = upperLimit;
let c = a;
let fa = func.call(thisArg, a);
let fb = func.call(thisArg, b);
let fc = fa;
let tolAct; // Actual tolerance
let newStep; // Step at this iteration
let prevStep; // Distance from the last but one to the last approximation
let p; // Interpolation step is calculated in the form p/q; division is delayed until the last moment
let q;
if (maxIter === undefined) {
maxIter = internal_modules_3.SessionSettings.maxIterations;
}
while (maxIter-- > 0) {
prevStep = b - a;
if (Math.abs(fc) < Math.abs(fb)) {
// Swap data for b to be the best approximation
a = b, b = c, c = a;
fa = fb, fb = fc, fc = fa;
}
tolAct = 1e-15 * Math.abs(b) + errorTol / 2;
newStep = (c - b) / 2;
if (Math.abs(newStep) <= tolAct
&& (Math.abs(fb) < errorTol
|| Math.abs(fb - fc) < errorTol * 1E5)) {
// Acceptable approx. is found
return {
found: true,
value: b
};
}
// Decide if the interpolation can be tried
if (Math.abs(prevStep) >= tolAct && Math.abs(fa) > Math.abs(fb)) {
// If prev_step was large enough and was in true direction, Interpolatiom may be tried
let t1;
let cb;
let t2;
cb = c - b;
if (a === c) { // If we have only two distinct points linear interpolation can only be applied
t1 = fb / fa;
p = cb * t1;
q = 1.0 - t1;
}
else { // Quadric inverse interpolation
q = fa / fc, t1 = fb / fc, t2 = fb / fa;
p = t2 * (cb * q * (q - t1) - (b - a) * (t1 - 1));
q = (q - 1) * (t1 - 1) * (t2 - 1);
}
if (p > 0) {
q = -q; // p was calculated with the opposite sign; make p positive
}
else {
p = -p; // and assign possible minus to q
}
if (p < (0.75 * cb * q - Math.abs(tolAct * q) / 2) &&
p < Math.abs(prevStep * q / 2)) {
// If (b + p / q) falls in [b,c] and isn't too large it is accepted
newStep = p / q;
}
// If p/q is too large then the bissection procedure can reduce [b,c] range to more extent
}
if (Math.abs(newStep) < tolAct) { // Adjust the step to be not less than tolerance
newStep = (newStep > 0) ? tolAct : -tolAct;
}
a = b, fa = fb; // Save the previous approx.
b += newStep, fb = func.call(thisArg, b); // Do step to a new approxim.
if ((fb > 0 && fc > 0) || (fb < 0 && fc < 0)) {
c = a, fc = fa; // Adjust c for it to have a sign opposite to that of b
}
}
// No acceptable approximation was found
return {
found: false,
value: b
};
}
}
exports.Dichotomie = Dichotomie;
/**
* si > 0, un calcul dichotomique est en cours
*/
Dichotomie._inDicho = 0;
//# sourceMappingURL=dichotomie.js.map