UNPKG

jalhyd

Version:

JaLHyd, a Javascript Library for Hydraulics

389 lines 16.5 kB
"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