remous.ts 31.4 KB
Newer Older
import { Dichotomie } from "./dichotomie";
import { Nub } from "./nub";
import { ParamsEquation } from "./param/params-equation";
import { ParamDefinition, ParamCalculability } from "./param/param-definition";
import { ParamDomainValue } from "./param/param-domain";
import { acSection, ParamsSection } from "./section/section_type";
import { Message, MessageCode } from "./util/message";
import { Result } from "./util/result";
import { ResultElement } from "./util/resultelement";
import { ParamValueIterator, ParamValues, BaseParam } from ".";
export class CourbeRemousParams extends ParamsEquation {
    /**
     * section associée
     */
    private _section: acSection;

    /**
     * Débit amont
     */
    // private _Qamont: ParamDefinition;

    /**
     * Tirant imposé à l'amont
     */
    private _Yamont: ParamDefinition;

    /**
     * Tirant imposé à l'aval
     */
    private _Yaval: ParamDefinition;

    /**
     * Longueur du bief
     */
    private _Long: ParamDefinition;

    /**
     * Pas de discrétisation de l'espace (positif en partant de l'aval, négatif en partant de l'amont)
     */
    private _Dx: ParamDefinition;

    /**
     * Méthode de résolution de l'équation différentielle
     */
    private _methodeResolution: MethodeResolution;

    constructor(s: acSection, rYamont: number, rYAval: number, rLong: number, rDx: number, meth: MethodeResolution) {
        super();
        this._section = s;
        this._Yamont = new ParamDefinition("Yamont", ParamDomainValue.POS, rYamont);
        this._Yaval = new ParamDefinition("Yaval", ParamDomainValue.POS, rYAval);
        this._Long = new ParamDefinition("Long", ParamDomainValue.POS, rLong);
        this._Dx = new ParamDefinition("Dx", ParamDomainValue.POS, rDx);
        this._methodeResolution = meth;

        this.addParamDefinition(this._Yamont);
        this.addParamDefinition(this._Yaval);
        this.addParamDefinition(this._Long);
        this.addParamDefinition(this._Dx);
        this.addParamDefinitions(this._section.prms);
Dorchies David's avatar
Dorchies David committed
        this.DefineCalculability(); // Pour ne pas remettre en undefined les prms de _section
    }

    get Sn() {
        return this._section;
    }

    get Yamont() {
        return this._Yamont;
    }

    get Yaval() {
        return this._Yaval;
    }

    get Long() {
        return this._Long;
    }

    get Dx(): ParamDefinition {
        return this._Dx;
    }

    get methodeResolution() {
        return this._methodeResolution;
    }
// tslint:disable-next-line:max-classes-per-file
    [key: string]: any; // pour pouvoir faire this['methode]();

    private _debugDicho: boolean = false;

    /**
     * Journal de calcul
     */
    // private _log: cLog;

    private prmSect: ParamsSection;

    constructor(crp: CourbeRemousParams, dbg: boolean = false) {
        super(crp, dbg);
        // this._log = crp.Sn.log;
        this.prmSect = crp.Sn.prms;
        this.Sn.Calc("Yc");
    }

    /**
     * @param val_a_cal nom de la variable à calculer
     */
    public calculRemous(val_a_cal: string):
        // {
        // "flu": { [key: number]: number; },
        // "tor": { [key: number]: number; },
        // "trX": string[],
        // "tRes": { [key: number]: number }
        // }
        Result {
        const res = new Result();

        // let Yc: number = this.Sn.Calc("Yc");
        const rYC = this.Sn.Calc("Yc");
        if (!rYC.ok) {
            res.addLog(rYC.log);
            return res;
        }
        const Yc: number = rYC.vCalc;

        const rB: Result = this.Sn.Calc("B", this.Sn.prms.YB.v);
        if (!rB.ok) {
            res.addLog(rB.log);
            return res;
        }

        let m: Message = new Message(MessageCode.INFO_REMOUS_LARGEUR_BERGE);
        // m.extraVar["B"] = this.Sn.Calc("B", this.Sn.prms.YB.v);
        m.extraVar.B = rB.vCalc;
        // this._log.add(m);
        res.addMessage(m);

        m = new Message(MessageCode.INFO_REMOUS_H_CRITIQUE);
        m.extraVar.Yc = Yc;
        // this._log.add(m);
        res.addMessage(m);

        const rYN = this.Sn.Calc("Yn");
        if (!rYN.ok) {
            res.addLog(rYN.log);
            return res;
        }

        const Yn = rYN.vCalc;
        m = new Message(MessageCode.INFO_REMOUS_H_NORMALE);
        m.extraVar.Yn = Yn;
        // this._log.add(m);
        res.addMessage(m);

        // this.debug("largeur berge " + this.Sn.Calc("B"));
        // const rB2: Result = this.Sn.Calc("B")
        // this.debug("largeur berge " + rB2.vCalc);
        // this.debug("hauteur critique " + Yc);
        // this.Sn.HautNormale = this.Sn.Calc("Yn");
        // this.debug("hauteur normale " + this.Sn.HautNormale);
        // this.debug("hauteur normale " + Yn);

        // Calcul des courbes de remous

        xValues.setValues(0, this.prms.Long.v, this.prms.Dx.v)
        // let crbFlu: { [key: number]: number; } = this.calculFluvial();
        const rCourbeFlu: ResultElement = this.calculFluvial(xValues);
        // if (!rCourbeFlu.ok) {
        res.addLog(rCourbeFlu.log);
        // 	return res;
        // }

        // let crbTor: { [key: number]: number; } = this.calculTorrentiel();
        const rCourbeTor: ResultElement = this.calculTorrentiel(xValues);
        // if (!rCourbeTor.ok) {
        res.addLog(rCourbeTor.log);
        // 	return res;
        // }

        let crbFlu = rCourbeFlu.getExtraResult("trY");
        if (crbFlu === undefined) {
            crbFlu = {};
        }
        let crbTor = rCourbeTor.getExtraResult("trY");
        if (crbTor === undefined) {
            crbTor = {};
        }

        // this.debug("HautCritique ", this.Sn.HautCritique);

        this.debug("flu ");
        // this.logObject(crbFlu);
        this.debug(JSON.stringify(crbFlu));

        this.debug("tor");
        // this.logObject(crbTor);
        this.debug(JSON.stringify(crbTor));

        // tslint:disable-next-line:forin
        for (const xflu in crbFlu) {
            const yflu = crbFlu[xflu];
            // this.debug("imp x " + xflu + " flu " + this.Sn.Calc('Imp', yflu));
        }

        // tslint:disable-next-line:forin
        for (const xtor in crbTor) {
            const ytor = crbTor[xtor];
            // this.debug("imp x " + xtor + " tor " + this.Sn.Calc('Imp', ytor));
        }

        // Détection du ressaut hydraulique
        const nFlu: number = this.size(crbFlu);
        const nTor: number = this.size(crbTor);
        if (nFlu !== 0 && nTor !== 0) {
            const firstYFlu = crbFlu[0];
            const lastYTor = this.last(crbTor);
            // this.debug("end flu " + firstYFlu);
            // this.debug("end tor " + lastYTor);
            // this.debug("nFlu " + nFlu);
            // this.debug("nTor " + nTor);
            // this.debug("Imp flu " + this.Sn.Calc('Imp', firstYFlu));
            // this.debug("Imp tor " + this.Sn.Calc('Imp', lastYTor));
            let crbComplete: any;
            let crbPartielle: any;
            let iSens: number;
            let sSens: string;
            if (nFlu > nTor || (nFlu === nTor && Yn < Yc)) {
                // La courbe fluviale va jusqu'au bout
                crbComplete = crbFlu;  // courbe calculée sur tout le bief
                crbPartielle = crbTor;  // courbe calculée sur une partie seulement du bief
                iSens = 1; // On cherche l'aval du ressaut
                sSens = "amont";
                this.debug("complete=flu, partielle=tor");
                // this.debug("complete(flu)");
                // this.debug(crbComplete);
                // this.debug("partielle(tor)");
                // this.debug(crbPartielle);
            } else {
                // La courbe torrentielle va jusqu'au bout
                crbComplete = crbTor;
                crbPartielle = crbFlu;
                iSens = -1; // On cherche l'amont du ressaut
                sSens = "aval";
                this.debug("complete=tor, partielle=flu");
                // this.debug("complete(tor)");
                // this.debug(crbComplete);
                // this.debug("partielle(flu)");
                // this.debug(crbPartielle);
            }

            // Parcours des sections de la ligne d'eau la plus courte
            const trX: string[] = Object.keys(crbPartielle);
            if (iSens === -1) {// tri dans l'ordre croissant { {
                trX.sort((a, b) => {
                    if (+a > +b) { return 1; }
                    if (+a < +b) { return -1; }
                    return 0;
                });
            } else { // tri dans l'ordre décroissant { {
                trX.sort((a, b) => {
                    if (+a > +b) { return -1; }
                    if (+a < +b) { return 1; }
                    return 0;
                });
            }

            const trXr = trX.slice(0); // copie
            trXr.reverse();

            // this.debug("trX");
            // this.debug(trX);

            let bRessaut = false;

            for (let irX = 0; irX < trX.length; irX++) {
                const rX: number = +trX[irX];
                // this.debug("irX=" + irX);
                // this.debug("rX=" + rX);
                // this.debug("partielle[" + rX + "]=" + crbPartielle[rX]);

                // Calcul de l'abscisse de la section dans l'autre régime
                const rYCO = this.Sn.Calc("Yco", crbPartielle[rX]); // Y conjugué
                if (!rYCO.ok) {
                    res.addLog(rYCO.log);
                    return res;
                }
                const Yco = rYCO.vCalc;
                // this.debug("rX=" + rX + " Yco(Ypartiel=" + crbPartielle[rX] + ")=" + Yco);

                const rLongRst = 5 * Math.abs(crbPartielle[rX] - Yco); // Longueur du ressaut
                // this.debug("longueur ressaut=" + rLongRst);

                let xRst = rX + Math.round(iSens * rLongRst / Dx) * Dx; // Abscisse où comparer Yconj et Y
                // this.debug("xRst=" + xRst);
                // let rxRst = rX + iSens * rLongRst; // Abscisse réelle du ressaut
                // this.debug("xRst reel=" + (rX + iSens * rLongRst));

                xRst = round(xRst, this.prmSect.iPrec.v);
                // this.debug("xRst (arr)=" + xRst);

                const impYpartielle = this.Sn.Calc("Imp", crbPartielle[rX]);
                // this.debug("imp(Ypartiel[rX=" + rX + "]=" + crbPartielle[rX] + ")=" + impYpartielle);
                if (!impYpartielle.ok) {
                    res.addLog(impYpartielle.log);
                    return res;
                }

                if (crbComplete[xRst] !== undefined) {
                    // Hauteur décalée de la longueur du ressaut (il faut gérer la pente du fond)
                    const Ydec: number = crbComplete[xRst] + rLongRst * this.prmSect.If.v * iSens;
                    // this.debug("Ydec=" + Ydec);
                    const impYcomplete = this.Sn.Calc("Imp", crbComplete[xRst]);
                    // this.debug("imp(Ycomplet[xRst=" + xRst + "]=" + crbComplete[xRst] + ")=" + impYcomplete);
                    if (!impYcomplete.ok) {
                        res.addLog(impYcomplete.log);
                        return res;
                    }

                    if (impYpartielle.vCalc > impYcomplete.vCalc) {
                        this.debug(
                            "Ressaut hydraulique détecté entre les abscisses " +
                            Math.min(rX, xRst) + " et " + Math.max(rX, xRst));
                        m = new Message(MessageCode.INFO_REMOUS_RESSAUT_HYDRO);
                        m.extraVar.xmin = Math.min(rX, xRst);
                        m.extraVar.xmax = Math.max(rX, xRst);
                        // this._log.add(m);
                        res.addMessage(m);
                        // this.debug("rX=" + rX + " xRst=" + xRst);

                        // Modification de la ligne d'eau complète
                        for (const pi of trXr) {
                            const rXCC: number = +pi;
                            // this.debug("rXCC=" + rXCC);
                            if (iSens * (rXCC - rX) <= 0) {
                                delete crbComplete[rXCC];
                                this.debug(
                                    "Modification de la ligne d'eau complète : suppression de la valeur à rXCC=" +
                                    rXCC + ", rX=" + rX + ", iSens*(rXCC-rX)=" + (iSens * (rXCC - rX))
                                );
                            }
                        }

                        // Modification de la ligne d'eau partielle
                        for (const xcn of trX) {
                            const rXCN = +xcn;
                            // this.debug("rXCN=" + rXCN);
                            if (iSens * (rXCN - xRst) >= 0) {
                                this.debug(
                                    "Modification de la ligne d'eau partielle : suppression de la valeur à rX=" + rXCN
                                );
                                delete crbPartielle[rXCN];
                            }
                        }
                        bRessaut = true;
                        break;
                    }
                }
            }
            if (!bRessaut) {
                // Le ressaut est en dehors du canal
                m = new Message(MessageCode.INFO_REMOUS_RESSAUT_DEHORS);
                m.extraVar.sens = sSens;
                m.extraVar.x = +this.last(trX);
                //  this._log.add(m);
                res.addMessage(m);

                this.debug("Ressaut hydraulique détecté à l'" + sSens + " de l'abscisse " + this.last(trX));
                if (iSens === 1) {
                    crbTor = {};
                } else {
                    crbFlu = {};
                }
                crbPartielle = {};  // pour le log uniquement, à virer
            }
            this.debug("complete (" + (iSens === 1 ? "flu" : "tor") + ") modifiée");
            // this.logObject(crbComplete);
            this.debug(JSON.stringify(crbComplete));
            this.debug("partielle (" + (iSens === 1 ? "tor" : "flu") + ") modifiée");
            // this.logObject(crbPartielle);
            this.debug(JSON.stringify(crbPartielle));
        }

        // Définition des abscisses
        let trX: number[] = [];

        if (nFlu !== 0) {
            trX = Object.keys(crbFlu).map((k) => +k);
        }

        if (nTor !== 0) {
            const kTor = Object.keys(crbTor).map(k => +k);
            trX = trX.concat(kTor);
        }
        // this.debug("trX=" + trX);

        trX.sort((a, b) => {
            if (a > b) { return 1; }
            if (a < b) { return -1; }
            return 0;
        });
        // this.debug("trX tri=" + trX);
        trX = trX.filter((elem, index, array) => {
            if (index > 0) {
                return elem !== array[index - 1];
            }
            return true;
        });
        // this.debug("trX unique=" + trX);

        this.debug("abscisses ");
        this.logArray(trX);

        // Calcul de la variable à calculer

        const tRes: { [key: number]: number } = {};
        if (val_a_cal !== undefined && (nFlu !== 0 || nTor !== 0)) {
            for (const rX of trX) {
                let rY: number;
                const hasFlu: boolean = crbFlu[rX] !== undefined;
                const hasTor: boolean = crbTor[rX] !== undefined;

                if (hasFlu && !hasTor) {
                    rY = crbFlu[rX];
                }

                if (hasTor) {
                    if (!hasFlu || (hasFlu && crbFlu[rX] === crbTor[rX])) {
                        rY = crbTor[rX];
                    }
                }

                if (rY !== undefined) {
                    // tRes[+rX] = this.Sn.Calc(val_a_cal, rY);
                    const rVar = this.Sn.Calc(val_a_cal, rY);
                    if (!rVar.ok) {
                        res.addLog(rVar.log);
                        return res;
                    }
                    tRes[rX] = rVar.vCalc;
                    this.debug("X=" + rX + " Calc(" + val_a_cal + ", Y=" + rY + ")=" + tRes[rX]);
                }
            }

            this.debug("extra param " + val_a_cal);
            this.logObject(tRes);
        }

        const hasRes = Object.keys(tRes).length > 0;
        for (const x of trX) {

            let ligneDeau;
            } else if (crbTor[x] === undefined) {
                ligneDeau = Math.max(crbFlu[x], crbTor[x]);
            const re = new ResultElement(ligneDeau);

                re.addExtraResult("flu", crbFlu[x]);
                re.addExtraResult("tor", crbTor[x]);
                re.addExtraResult("tRes", tRes[x]);

        return res;
    }

    public Calc(sVarCalc: string, rInit?: number, rPrec: number = 0.001): Result {
        if (sVarCalc === "Hs") {
            return this.Equation(sVarCalc);
        }
        throw new Error("CourbeRemous.Calc() : parameter " + sVarCalc + " not allowed");
    }

    public Equation(sVarCalc: string): Result {
        if (sVarCalc === "Hs") {
            // Equation de l'intégration par la méthode des trapèzes
            this.Sn.Reset();  // pour forcer le calcul avec la nouvelle valeur de prmSect.Y

            // let res: number = this.Sn.Calc('Hs') - this.Sn.Calc('J') / 2 * this.Dx;

            const rHS = this.Sn.Calc("Hs");
            if (!rHS.ok) {
                return rHS;
            }

            const rJ = this.Sn.Calc("J");
            if (!rJ.ok) {
                return rJ;
            }

            const res: number = rHS.vCalc - rJ.vCalc / 2 * this.Dx;
            return new Result(res);
        }

        throw new Error("CourbeRemous.Equation() : parameter " + sVarCalc + " not allowed");
    }

    private get Sn(): acSection {
        return this.prms.Sn;
    }

    private get prms(): CourbeRemousParams {
        return this._prms as CourbeRemousParams;
    }

    protected setParametersCalculability() {
        this.prms.map.Y.calculability = ParamCalculability.DICHO;
        this.prms.Long.calculability = ParamCalculability.FREE;
        this.prms.map.Dx.calculability = ParamCalculability.FREE;
        this.prms.map.Yamont.calculability = ParamCalculability.FREE;
        this.prms.map.Yaval.calculability = ParamCalculability.FREE;
    }

    /**
     * calcul de la ligne fluviale depuis l'aval (si possible)
     */
    private calculFluvial(xValues: ParamValues): ResultElement {
        if (!this.Sn.HautCritique.ok) {
            return new ResultElement(new Message(MessageCode.WARNING_REMOUS_ARRET_CRITIQUE));
        }

        let res: ResultElement;

        // Calcul depuis l'aval
        if (this.Sn.HautCritique.vCalc <= this.prms.Yaval.v) {
            // this._log.add(new Message(MessageCode.ERROR_REMOUS_CALCUL_FLUVIAL));

            this.debug(
                `Condition limite aval (${this.prms.Yaval.v}) >= ` +
                `Hauteur critique (${this.Sn.HautCritique}) : calcul de la partie fluviale à partir de l'aval`);
            this.Dx = this.prms.Dx.v;
            xValues.initIterator(true);
            res = this.calcul(this.prms.Yaval.v, xValues);
            res.insertMessage(new Message(MessageCode.INFO_REMOUS_CALCUL_FLUVIAL));
        } else {
            this.debug(
                "Condition limite aval (" + this.prms.Yaval.v +
                ") < Hauteur critique (" + this.Sn.HautCritique +
                ") : pas de calcul possible depuis l'aval");
            // this._log.add(new Message(MessageCode.ERROR_REMOUS_PAS_CALCUL_DEPUIS_AVAL));
            res = new ResultElement();
            res.addMessage(new Message(MessageCode.ERROR_REMOUS_PAS_CALCUL_DEPUIS_AVAL));
        }

        return res;
    }

    /**
     * calcul de la ligne torrentielle depuis l'amont (si possible)
     */
    private calculTorrentiel(xValues: ParamValues): ResultElement {
        if (!this.Sn.HautCritique.ok) {
            return new ResultElement(new Message(MessageCode.WARNING_REMOUS_ARRET_CRITIQUE));
        }

        let res: ResultElement;

        // Calcul depuis l'amont
        if (this.Sn.HautCritique.vCalc >= this.prms.Yamont.v) {
            // this._log.add(new Message(MessageCode.ERROR_REMOUS_CALCUL_TORRENTIEL));

            this.debug(
                "Condition limite amont (" + this.prms.Yamont.v +
                ") <= Hauteur critique (" + this.Sn.HautCritique +
                ") : calcul de la partie torrentielle à partir de l'amont");
            this.Dx = -this.prms.Dx.v;
            xValues.initIterator(false);
            res = this.calcul(this.prms.Yamont.v, xValues);
            res.insertMessage(new Message(MessageCode.INFO_REMOUS_CALCUL_TORRENTIEL));
        } else {
            // this._log.add(new Message(MessageCode.ERROR_REMOUS_PAS_CALCUL_DEPUIS_AMONT));
            this.debug(
                "Condition limite amont (" + this.prms.Yamont.v +
                ") > Hauteur critique (" + this.Sn.HautCritique +
                ") : pas de calcul possible depuis l'amont");
            res = new ResultElement();
            res.addMessage(new Message(MessageCode.ERROR_REMOUS_PAS_CALCUL_DEPUIS_AMONT));
        }

        return res;
    }

    /**
     * Calcul de dy/dx (utilisé par Euler explicite et Runge-Kutta 4)
     * @param Y Tirant d'eau initial
     */
    private Calc_dYdX(Y: number): Result {
        // L'appel à Calc('J') avec Y en paramètre réinitialise toutes les données dépendantes de la ligne d'eau
        // return - (this.prmSect.If.v - this.Sn.Calc('J', Y)) / (1 - Math.pow(this.Sn.Calc('Fr', Y), 2));

        const rJ = this.Sn.Calc("J", Y);
        if (!rJ.ok) {
            return rJ;
        }

        const rFR = this.Sn.Calc("Fr", Y);
        if (!rFR.ok) {
            return rFR;
        }

        const v = - (this.prmSect.If.v - rJ.vCalc) / (1 - Math.pow(rFR.vCalc, 2));
        return new Result(v);
    }

    /**
     * Calcul du point suivant de la courbe de remous par la méthode Euler explicite.
     * @param Y Tirant d'eau initial
     * @return Tirant d'eau
     */
    private Calc_Y_EulerExplicite(Y: number): Result {
        if (!this.Sn.HautCritique.ok) {
            return new Result(new Message(MessageCode.WARNING_REMOUS_ARRET_CRITIQUE));
        }

        // L'appel à Calc('J') avec Y en paramètre réinitialise toutes les données dépendantes de la ligne d'eau

        const rDXDY = this.Calc_dYdX(Y);
        if (!rDXDY.ok) {
            return rDXDY;
        }

        // let Y2 = Y + this.Dx * this.Calc_dYdX(Y);
        const Y2 = Y + this.Dx * rDXDY.vCalc;

        return new Result(Y2);
    }

    /**
     * Calcul du point suivant de la courbe de remous par la méthode RK4.
     * @param Y Tirant d'eau initial
     * @return Tirant d'eau
     */
    private Calc_Y_RK4(Y: number): Result {
        if (!this.Sn.HautCritique.ok) {
            return new Result(new Message(MessageCode.WARNING_REMOUS_ARRET_CRITIQUE));
        }

        // L'appel à Calc('J') avec Y en paramètre réinitialise toutes les données dépendantes de la ligne d'eau
        const rDx = this.Dx;

        // let k1 = this.Calc_dYdX(Y);
        const rDXDY: Result = this.Calc_dYdX(Y);
        if (!rDXDY.ok) {
            return rDXDY;
        }
        const k1 = rDXDY.vCalc;

        const hc = this.Sn.HautCritique.vCalc;

        if (XOR(rDx > 0, !(Y + rDx / 2 * k1 < hc))) {
            return new Result(new Message(MessageCode.WARNING_REMOUS_ARRET_CRITIQUE));
        }

        // let k2 = this.Calc_dYdX(Y + Dx / 2 * k1);
        const rDXDY2: Result = this.Calc_dYdX(Y + rDx / 2 * k1);
        if (!rDXDY2.ok) {
            return rDXDY2;
        }
        const k2 = rDXDY2.vCalc;

        if (XOR(rDx > 0, !(Y + rDx / 2 * k2 < hc))) {
            return new Result(new Message(MessageCode.WARNING_REMOUS_ARRET_CRITIQUE));
        }

        // let k3 = this.Calc_dYdX(Y + Dx / 2 * k2);
        const rDXDY3: Result = this.Calc_dYdX(Y + rDx / 2 * k2);
        if (!rDXDY3.ok) {
            return rDXDY3;
        }
        const k3 = rDXDY3.vCalc;

        if (XOR(rDx > 0, !(Y + rDx / 2 * k3 < hc))) {
            return new Result(new Message(MessageCode.WARNING_REMOUS_ARRET_CRITIQUE));
        }

        // let k4 = this.Calc_dYdX(Y + Dx * k3);
        const rDXDY4: Result = this.Calc_dYdX(Y + rDx * k3);
        if (!rDXDY4.ok) {
            return rDXDY4;
        }
        const k4 = rDXDY4.vCalc;

        const Yout = Y + rDx / 6 * (k1 + 2 * (k2 + k3) + k4);

        // if ($this ->rDx > 0 xor !($Yout < $this ->oSect ->rHautCritique)) { return false; }
        if (XOR(rDx > 0, !(Yout < hc))) {
            const res = new Result(new Message(MessageCode.WARNING_REMOUS_ARRET_CRITIQUE));
        }

        return new Result(Yout);
    }

    /**
     * Calcul du point suivant de la courbe de remous par la méthode de l'intégration par trapèze
     * @param Y Tirant d'eau initial
     * @return Tirant d'eau
     */
    private Calc_Y_Trapez(Y: number): Result {
        if (!this.Sn.HautCritique.ok) {
            return new Result(new Message(MessageCode.WARNING_REMOUS_ARRET_CRITIQUE));
        }

        const dicho = new Dichotomie(this, "Y", this._debugDicho, "Hs");

        // Calcul de H + J * \Delta x / 2
        // let Trapez_Fn = this.Sn.Calc('Hs', Y) + this.Sn.Calc('J', Y) / 2 * this.Dx
        const rHS: Result = this.Sn.Calc("Hs", Y);
        if (!rHS.ok) {
            return rHS;
        }

        const rJ: Result = this.Sn.Calc("J", Y);
        if (!rJ.ok) {
            return rJ;
        }

        let trapezFn = rHS.vCalc + rJ.vCalc / 2 * this.Dx;

        // H est la charge totale. On se place dans le référentiel ou Zf de la section à calculer = 0
        trapezFn -= this.Dx * this.prmSect.If.v;

        const r: Result = dicho.Dichotomie(trapezFn, this.prmSect.Prec.v, Y);
        if (!r.ok) {
            return r;
        }

        // return new Result(Y2);
        return r;
    }

    /**
     * Calcul du point suivant d'une courbe de remous
     * @param Y Tirant d'eau initial
     * @return Tirant d'eau
     */
    private Calc_Y(Y: number): Result {
        // let funcCalcY = 'Calc_Y_' + Resolution;
        // return this[funcCalcY](Y);
        switch (this.prms.methodeResolution) {
            case MethodeResolution.Trapezes:
                res = this.Calc_Y_EulerExplicite(Y);
                break;
            default:
                throw new Error("CourbeRemous.Calc_Y() : type de méthode de résolution " +
                    MethodeResolution[this.prms.methodeResolution] + " non pris en charge");
        }
        if (!res.ok || XOR(this.Dx > 0, !(res.vCalc < this.Sn.HautCritique.vCalc))) {
            return new Result(new Message(MessageCode.WARNING_REMOUS_ARRET_CRITIQUE));
    }

    private size(o: {}): number {
        let res: number = 0;

        for (const i in o) {
            res++;
        }

        return res;
    }

    private last(o: any): any {
        let res: any;

        for (const i in o) {
            res = o[i];
        }

        return res;
    }

    /**
     * Calcul d'une courbe de remous en fluvial ou torrentiel
     * @param YCL Condition limite amont (torrentiel) ou aval (fluvial)
     */
    private calcul(YCL: number, varParam: ParamValues): ResultElement {
        const trY: { [key: number]: number; } = {};
        const res = new ResultElement();

        let lastY = YCL;
        trY[round(varParam.next, this.prmSect.iPrec.v)] = lastY;
        while (varParam.hasNext) {
            const x = varParam.next;
            // this.debug("lastY " + lastY);
            const rY: Result = this.Calc_Y(lastY);
            // this.debug("calcul : x " + x + " y " + rY.vCalc);
            // this.debug("trY ");
            // this.logObject(trY);
            // this.debug("end trY " + this.last(trY));
            // this.debug("Yn " + this.Sn.HautNormale);

            if (rY.ok) {
                // on vérifie qu'on ne traverse pas la hauteur normale (à la précision de calcul près)
                const prec: number = this.prms.map.Prec.v;
                const b1: boolean = lastY - this.Sn.HautNormale > prec;
                const b2: boolean = rY.vCalc - this.Sn.HautNormale > prec;
                if (XOR(b1, b2)) {
                    this.debug(
                        "La pente de la ligne d'eau est trop forte à l'abscisse "
                        + x + " m (Il faudrait réduire le pas de discrétisation)"
                    );

                    const m: Message = new Message(MessageCode.ERROR_REMOUS_PENTE_FORTE);
                    m.extraVar.x = x;
                    // this._log.add(m);
                    res.addMessage(m);
                }

                trY[round(x, this.prmSect.iPrec.v)] = rY.vCalc;
            } else {
                const m = new Message(MessageCode.WARNING_REMOUS_ARRET_CRITIQUE);
                m.extraVar.x = x;
                // this._log.add(m);
                res.addMessage(m);
                this.debug("Arrêt du calcul : Hauteur critique atteinte à l'abscisse " + x + " m");
                break;
            }
            lastY = rY.vCalc;
        }

        // return trY;
        res.addExtraResult("trY", trY);
        return res;
    }

    private logArray(a: any[]) {
        let s = "[";
        let first = true;
        for (const e of a) {
            if (!first) {
                s += ",";
            }
            s += String(e);
            first = false;
        }
        s += "]";
        this.debug(s);
    }

    private logObject(o: { [key: number]: number }) {
        if (o === undefined) {
            this.debug("<undefined>");
        } else {
            const ks: string[] = Object.keys(o);
            ks.sort((a, b) => {
                if (+a > +b) { return 1; }
                if (+a < +b) { return -1; }
                return 0;
            });
            for (const k of ks) {
                this.debug("[" + (+k).toFixed(3) + "]=" + o[+k]);
            }
        }
    }