diff --git a/spec/pab/pab.spec.ts b/spec/pab/pab.spec.ts index d5dbc1c2ddb50510adc060c712dec5da0fb337c0..a927c4a85e19c73bb1d1b4f1dd6208c0055d3445 100644 --- a/spec/pab/pab.spec.ts +++ b/spec/pab/pab.spec.ts @@ -205,11 +205,11 @@ describe("Class Pab: ", () => { describe("when Z1 is too low compared to ZDV", () => { it("logs should appear if dichotomy does not converge", () => { pab.calculatedParam = pab.prms.Q; - pab.prms.Z1.singleValue = 78.26; // @WTF 78.27 in the example above works, 78.26 fails ! + pab.prms.Z1.singleValue = 76.60; pab.CalcSerie(); expect(pab.result.hasLog).toBe(true); expect(pab.result.resultElement.log.messages[0].code) - .toBe(MessageCode.ERROR_DICHO_CONVERGE); // @TODO or other error message ? + .toBe(MessageCode.ERROR_PAB_Z1_LOWER_THAN_UPSTREAM_WALL); // @TODO or other error message ? }); }); @@ -220,7 +220,7 @@ describe("Class Pab: ", () => { pab.CalcSerie(); expect(pab.result.nbResultElements).toBe(3); expect(pab.result.hasLog).toBe(true); - expect(pab.result.resultElements[0].log.messages[0].code).toBe(MessageCode.ERROR_Z1_LOWER_THAN_Z2); + expect(pab.result.resultElements[0].log.messages[0].code).toBe(MessageCode.ERROR_PAB_Z1_LOWER_THAN_Z2); }); }); diff --git a/src/dichotomie.ts b/src/dichotomie.ts index 27a1059ba7dc5f5800a8fefc0a8a4c9fffcab6db..177159c2a920bd43aad6a9032ca499ac9dd23ad4 100644 --- a/src/dichotomie.ts +++ b/src/dichotomie.ts @@ -131,6 +131,11 @@ export class Dichotomie extends Debug { */ private analyticalSymbol: string; + /** + * Targetted value calculated by the function + */ + private _target: number; + /** * Construction de la classe. * @param nub Noeud de calcul contenant la méthode de calcul Equation @@ -185,6 +190,7 @@ export class Dichotomie extends Debug { * @param rInit valeur initiale approximative de x */ public Dichotomie(rTarget: number, rTol: number, rInit: number): Result { + this._target = rTarget; // console.log("-----"); // for (let x = 0; x <= 1; x += 0.1) // console.log(this.CalculX(x).vCalc); @@ -201,15 +207,34 @@ export class Dichotomie extends Debug { if (r.ok) { const inter: SearchInterval = r.intSearch; this._currentIterations = 0; - // Dichotomie - return this.dichoSearch( - rTarget, - rTol, + // Recherche du zero par la méthode de Brent + const s: number = this.uniroot( + this.CalcZero, + this, inter.min, inter.max, - inter.targets.getVal(inter.minIndex), - inter.targets.getVal(inter.maxIndex) + rTol ); + if (s === undefined) { + this.debug( + "Dichotomie : Non convergence" + ); + return new Result(new Message(MessageCode.ERROR_DICHO_CONVERGE)); + } else { + this.debug( + `Dichotomie : ${this.analyticalSymbol}(${this.sVarCalc}=${s}) = ${rTarget}` + ); + return new Result(s); + } + // Dichotomie + // this.dichoSearch( + // rTarget, + // rTol, + // inter.min, + // inter.max, + // inter.targets.getVal(inter.minIndex), + // inter.targets.getVal(inter.maxIndex) + // ); } else { return new Result(r.res); } @@ -223,9 +248,9 @@ export class Dichotomie extends Debug { */ private Calcul(): Result { const r: Result = this.nub.Equation(this.analyticalSymbol); - this.debug( - "dicho : Calcul(vX=" + this.sVarCalc + "=" + this.vX + ") -> " + - this.analyticalSymbol + "=" + r.vCalc); + // this.debug( + // "dicho : Calcul(vX=" + this.sVarCalc + "=" + this.vX + ") -> " + + // this.analyticalSymbol + "=" + r.vCalc); return r; } @@ -251,7 +276,8 @@ export class Dichotomie extends Debug { * @param intMax intervalle maximum (domaine de définition de la variable) */ private searchTarget(rTarget: number, intSearch: SearchInterval, intMax: Interval) { - this.debug("searchTarget : Target " + rTarget + " dans " + intSearch.toString()); + // tslint:disable-next-line:max-line-length + this.debug(`searchTarget debut: Target ${this.analyticalSymbol}=${rTarget} dans ${this.sVarCalc}=${intSearch.toString()}`); let n = 0; let ok: boolean = false; @@ -267,6 +293,8 @@ export class Dichotomie extends Debug { intSearch.growStep(2); intSearch.next(); } while (n++ < this._startIntervalMaxSteps); + // tslint:disable-next-line:max-line-length + this.debug(`searchTarget fin: Target ${this.analyticalSymbol}=${rTarget} dans ${this.sVarCalc}=${intSearch.toString()}`); return { ok, intSearch }; } @@ -409,7 +437,8 @@ export class Dichotomie extends Debug { return r; } - this.debug("dichoSearch : yTarget " + Ytarg + " X " + Xmin + "->" + Xmax + " tol " + rTol); + // tslint:disable-next-line:max-line-length + this.debug(`dichoSearch : yTarget ${this.analyticalSymbol}= ${Ytarg} ${this.sVarCalc}=[${Xmin}; ${Xmax}] tol ${rTol}`); // tslint:disable-next-line:variable-name const Xmid = (Xmin + Xmax) / 2; @@ -437,4 +466,112 @@ export class Dichotomie extends Debug { } } + private CalcZero(x: number): number { + this.vX = x; + const r: Result = this.nub.Equation(this.analyticalSymbol); + if (r.ok) { + // this.debug(`CalcZero ${this.sVarCalc} = ${x} => ${this.analyticalSymbol} = ${r.vCalc}`); + return r.vCalc - this._target; + } else { + return undefined; + } + } + + /** + * 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. + * + */ + private uniroot<T>(func: (param: number) => number, thisArg: T, lowerLimit: number, upperLimit: number, + errorTol: number = 0, maxIter: number = 1000 + ): number { + 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; + + 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 || fb === 0) { + return b; // Acceptable approx. is found + } + + // 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 + } + } + return undefined; + + } + } diff --git a/src/pab/pab.ts b/src/pab/pab.ts index b479e182564124d2543eaac3395db5e23bd9cc62..b34fef885025806575d6ae067fa3daedf4c7c472 100644 --- a/src/pab/pab.ts +++ b/src/pab/pab.ts @@ -12,6 +12,7 @@ import { Result } from "../util/result"; import { CloisonAval, ParallelStructureParams } from "./cloison_aval"; import { Cloisons } from "./cloisons"; import { PabParams } from "./pab_params"; +import { ResultElement } from "../util/resultelement"; export { PabParams }; @@ -108,12 +109,22 @@ export class Pab extends Nub { * @param sVarCalc Calcul */ public Calc(sVarCalc: string, rInit?: number): Result { + const r: Result = new Result(new ResultElement()); if (!this.downWall.checkVanneLevante()) { - this.currentResult = new Result(new Message(MessageCode.ERROR_CLOISON_AVAL_UN_OUVRAGE_REGULE)); - return this.result; + r.resultElement.addMessage(new Message(MessageCode.ERROR_CLOISON_AVAL_UN_OUVRAGE_REGULE)); + } + if (sVarCalc === "Q") { + if (this.prms.Z1.v < this.prms.Z2.v) { + r.resultElement.addMessage(new Message(MessageCode.ERROR_PAB_Z1_LOWER_THAN_Z2)); + } + this.children[0].prms.Z1.v = this.prms.Z1.v; + this.children[0].prms.Z2.v = -Infinity; + if (this.children[0].CalcQ().vCalc < 1E-7) { + r.resultElement.addMessage(new Message(MessageCode.ERROR_PAB_Z1_LOWER_THAN_UPSTREAM_WALL)); + } } - if (sVarCalc === "Q" && this.prms.Z1.v < this.prms.Z2.v) { - this.currentResult = new Result(new Message(MessageCode.ERROR_Z1_LOWER_THAN_Z2)); + if (r.hasErrorMessages) { + this.currentResult = r; return this.result; } return super.Calc(sVarCalc, rInit); @@ -333,6 +344,7 @@ export class Pab extends Nub { } private dbgWall(cl: ParallelStructure) { + if (!this.DBG) { return; } let s: string = ""; for (const p of cl.prms) { s += p.symbol + " = " + p.v + "; "; diff --git a/src/util/message.ts b/src/util/message.ts index e67ca8a43e97b0cbce7b0d077162225e5f6f98ba..30682bf048eb2cff38ffdf7caec9ce6e223bb6b5 100644 --- a/src/util/message.ts +++ b/src/util/message.ts @@ -119,9 +119,14 @@ export enum MessageCode { ERROR_CLOISON_AVAL_UN_OUVRAGE_REGULE, /** - * La cote amont est plus basse que la cote aval + * PAB : La cote amont est plus basse que la cote aval */ - ERROR_Z1_LOWER_THAN_Z2, + ERROR_PAB_Z1_LOWER_THAN_Z2, + + /** + * PAB : La cote amont est plus basse que la cloison amont de la passe + */ + ERROR_PAB_Z1_LOWER_THAN_UPSTREAM_WALL, /** * internationalisation : langue non prise en charge