remous.ts 24.8 KB
Newer Older
import { ParamsSection, acSection } from "./section/section_type";
import { XOR, Result, round } from "./base";
import { IParamsEquation, ParamDefinition, ParamCalculability, ComputeNodeType, ParamDomainValue } from "./param";
import { Dichotomie } from "./dichotomie";
import { Nub } from "./nub";
import { ErrorCode, ErrorMessage } from "./util/error";
import { } from "./util/";

export enum MethodeResolution {
	Trapezes, EulerExplicite, RungeKutta4
}

/**
 * paramètres pour les courbes de remous
 */
export class CourbeRemousParams implements IParamsEquation {
	/**
	 * Débit amont
	 */
	// private _Qamont: ParamDefinition;

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

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

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

	// constructor(rQamont: number, rYamont: number, rYAval: number, meth: MethodeResolution) {
	constructor(rYamont: number, rYAval: number, meth: MethodeResolution) {
		// this._Qamont = new ParamDefinition(ComputeNodeType.CourbeRemous, 'Qam', ParamDomainValue.POS, rQamont);
		this._Yamont = new ParamDefinition(ComputeNodeType.CourbeRemous, 'Yam', ParamDomainValue.POS, rYamont);
		this._Yaval = new ParamDefinition(ComputeNodeType.CourbeRemous, 'Yav', ParamDomainValue.POS, rYAval);
		this._methodeResolution = meth;
	}

	// get Qamont() {
	// 	return this._Qamont;
	// }

	get Yamont() {
		return this._Yamont;
	}

	get Yaval() {
		return this._Yaval;
	}

	get methodeResolution() {
		return this._methodeResolution;
	}
}


/**
 * Calcul d'une courbe de remous
 */
export class CourbeRemous extends Nub {
	[key: string]: any; // pour pouvoir faire this['methode]();

	private _debugDicho: boolean = false;

	//const DBG = false; /// Pour loguer les messages de debug de cette classe

	// public $oP; /// Paramètres de la section
	// public $oSect; /// Section du bief
	// private $oLog; /// Journal de calcul

	/**
	 * Pas de discrétisation de l'espace (positif en partant de l'aval, négatif en partant de l'amont)
	 */
	// private Dx: number;
	Dx: number; // TODO à remettre en privé

	// private VarCal: number; /// Variable calculée Y pour la dichotomie (intégration trapèze)

	private Sn: acSection;
	private prmSect: ParamsSection;
	private prmCR: CourbeRemousParams;

	//private HautCritique: number;	// Yc de la section

	/**
	 * dernière erreur rencontrée
	 */
	private _lastError: Result;

	// constructor(s: acSection, crp: CourbeRemousParams, log: cLog) {
	constructor(s: acSection, crp: CourbeRemousParams) {
		this.Sn = s;
		this.prmSect = s.prms;
		this.prmCR = crp;
		//this.HautCritique = s.Calc("Yc", this.prmSect.Y.v);
		this.Sn.Calc("Yc");
	}

	protected setParametersCalculability() {
		this._prms.Y.calculability = ParamCalculability.DICHO;
	}

	public Equation(sVarCalc: string): Result {
		if (sVarCalc == "Hs") {
			// Equation de l'intégration par la méthode des trapèzes
			// let res: number = this.Sn.Calc('Hs', this.VarCal) - this.Sn.Calc('J', this.VarCal) / 2 * this.Dx;
			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;
			return new Result(res);
		}

		throw "CourbeRemous.Equation() : paramètre " + sVarCalc + " non pris en charge";
	}

	public get lastError() {
		return this._lastError;
	}

	/**
	 * Calcul de dy/dx (utilisé par Euler explicite et Runge-Kutta 4)
	 * @param Y Tirant d'eau initial
	 */
	private Calc_dYdX(Y: number): number {
		// 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));
	}

	/**
	 * 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 {
		// L'appel à Calc('J') avec Y en paramètre réinitialise toutes les données dépendantes de la ligne d'eau
		let Y2 = Y + this.Dx * this.Calc_dYdX(Y);
		if (XOR(this.Dx > 0, !(Y2 < this.Sn.HautCritique)))
			return new Result(undefined, new ErrorMessage(ErrorCode.ERROR_REMOUS_ARRET_CRITIQUE));

		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 {
		// L'appel à Calc('J') avec Y en paramètre réinitialise toutes les données dépendantes de la ligne d'eau
		//$rDx = $this ->rDx;
		let Dx = this.Dx;
		//$rk1 = $this ->Calc_dYdX($Y);
		let k1 = this.Calc_dYdX(Y);

		let hc = this.Sn.HautCritique;

		//if ($this ->rDx > 0 xor !($Y + $rDx / 2 * $rk1 < $this ->oSect ->rHautCritique)) { return false; }
		if (XOR(Dx > 0, !(Y + Dx / 2 * k1 < hc)))
			return undefined;

		//$rk2 = $this ->Calc_dYdX($Y + $rDx / 2 * $rk1);
		let k2 = this.Calc_dYdX(Y + Dx / 2 * k1);

		//if ($this ->rDx > 0 xor !($Y + $rDx / 2 * $rk2 < $this ->oSect ->rHautCritique)) { return false; }
		if (XOR(Dx > 0, !(Y + Dx / 2 * k2 < hc)))
			return undefined;

		//$rk3 = $this ->Calc_dYdX($Y + $rDx / 2 * $rk2);
		let k3 = this.Calc_dYdX(Y + Dx / 2 * k2);

		//if ($this ->rDx > 0 xor !($Y + $rDx / 2 * $rk3 < $this ->oSect ->rHautCritique)) { return false; }
		if (XOR(Dx > 0, !(Y + Dx / 2 * k3 < hc)))
			return undefined;

		//$rk4 = $this ->Calc_dYdX($Y + $rDx * $rk3);
		let k4 = this.Calc_dYdX(Y + Dx * k3);

		//$Yout = $Y + $rDx / 6 * ($rk1 + 2 * ($rk2 + $rk3) + $rk4);
		let Yout = Y + Dx / 6 * (k1 + 2 * (k2 + k3) + k4);

		//if ($this ->rDx > 0 xor !($Yout < $this ->oSect ->rHautCritique)) { return false; }
		if (XOR(Dx > 0, !(Yout < hc)))
			return new Result(undefined, new ErrorMessage(ErrorCode.ERROR_REMOUS_ARRET_CRITIQUE));

		return new Result(Yout);
	}

	/**
	 * Equation de l'intégration par la méthode des trapèzes
	 */
	// private Calc_Y_Trapez_Fn(): number {
	// 	// return $this ->oSect ->Calc('Hs', $this ->VarCal) - $this ->oSect ->Calc('J', $this ->VarCal) / 2 * $this ->rDx;
	// 	return this.Sn.Calc('Hs', this.VarCal) - this.Sn.Calc('J', this.VarCal) / 2 * this.Dx;
	// }

	/**
	 * 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 {
		//include_spip('hyd_inc/dichotomie.class');
		//$this ->VarCal = &$Y;
		// this.VarCal = Y;

		// $oDicho = new cDichotomie($this ->oLog, $this, 'Calc_Y_Trapez_Fn', false);
		let Dicho = new Dichotomie(this, "Y", this._debugDicho, "Hs");

		// Calcul de H + J * \Delta x / 2
		// $Trapez_Fn = $this ->oSect ->Calc('Hs', $this ->VarCal) + $this ->oSect ->Calc('J', $this ->VarCal) / 2 * $this ->rDx;
		// let Trapez_Fn = this.Sn.Calc('Hs', this.VarCal) + this.Sn.Calc('J', this.VarCal) / 2 * this.Dx
		let Trapez_Fn = this.Sn.Calc('Hs', Y) + this.Sn.Calc('J', Y) / 2 * this.Dx

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

		// list($Y2, $flag) = $oDicho ->calculer($Trapez_Fn, $this ->oP ->rPrec, $this ->oSect ->rHautCritique);
		// let r: Result = Dicho.Dichotomie(Trapez_Fn, this.prmSect.Prec.v, this.Sn.HautCritique);
		let r: Result = Dicho.Dichotomie(Trapez_Fn, this.prmSect.Prec.v, Y);

		// if ($flag < 0) {
		if (r.code != ErrorCode.ERROR_OK)
			return r;

		let Y2 = r.vCalc;
		// } elseif($this ->rDx > 0 xor !($Y2 < $this ->oSect ->rHautCritique)) {
		if (XOR(this.Dx > 0, !(Y2 < this.Sn.HautCritique)))
			return new Result(undefined, new ErrorMessage(ErrorCode.ERROR_REMOUS_ARRET_CRITIQUE));

		return new Result(Y2);
	}

	/**
	 * 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.prmCR.methodeResolution) {
			case MethodeResolution.Trapezes:
				return this.Calc_Y_Trapez(Y);

			case MethodeResolution.RungeKutta4:
				return this.Calc_Y_RK4(Y);

			case MethodeResolution.EulerExplicite:
				return this.Calc_Y_EulerExplicite(Y);

			// default:
			// 	throw "CourbeRemous.Calc_Y() : type de méthode de résolution " + MethodeResolution[this.prmCR.methodeResolution] + " non pris en charge";
		}
	}

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

		for (let i in o)
			res++;

		return res;
	}

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

		for (let i in o)
			res = o[i];

		return res;
	}

	// private maxKey(o: { [key: number]: any }): number {
	// 	let res: number = - 1e+9;

	// 	for (let i in o) {
	// 		let v: number = +i;
	// 		if (v > res)
	// 			res = v;
	// 	}

	// 	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): { [key: number]: number } {
		//	$trY = array();
		let trY: { [key: number]: number; } = {};
		//let n = -1;
		// let m: Map<number, number>;

		// if ($this ->rDx > 0) {
		if (this.Dx > 0) {
			// Calcul depuis l'aval
			var Deb: number = this.prmSect.Long.v;
			var Fin: number = 0;
		}
		else {
			// Calcul depuis l'amont
			Deb = 0;
			Fin = this.prmSect.Long.v;
		}
		// $dx = - $this ->rDx;
		let dx = - this.Dx;
		//	spip_log($this, 'hydraulic', _LOG_DEBUG);

		// $trY[sprintf('%1.'.round($this ->oP ->iPrec).'f', $xDeb)] = (real)$rYCL;
		let lastY = YCL;
		trY[round(Deb, this.prmSect.iPrec.v)] = lastY;
		//n++;

		// Boucle de calcul de la courbe de remous
		// for ($x = $xDeb + $dx; ($dx > 0 && $x <= $xFin) || ($dx < 0 && $x >= $xFin); $x += $dx) {
		for (let x = Deb + dx; (dx > 0 && x <= Fin) || (dx < 0 && x >= Fin); x += dx) {
			// $rY = (real)$this->Calc_Y(end($trY), $sResolution);
			// this.debug("lastY", lastY);
			let 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) {
			if (rY.code == ErrorCode.ERROR_OK) {
				// if (end($trY) > $this ->oSect ->rHautNormale xor $rY > $this ->oSect ->rHautNormale) {
				if (XOR(lastY > this.Sn.HautNormale, rY.vCalc > this.Sn.HautNormale)) {
					// 	$this ->oLog ->Add(_T('hydraulic:pente_forte').' '.$x. ' m ('._T('hydraulic:reduire_pas').')', true);
					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)");
				}

				// $trY[sprintf('%1.'.round($this ->oP ->iPrec).'f', $x)] = $rY;
				trY[round(x, this.prmSect.iPrec.v)] = rY.vCalc;
				// n++;
			} else {
				// $this ->oLog ->Add(_T('hydraulic:arret_calcul').' '.$x. ' m');
				this.debug("Arrêt du calcul : Hauteur critique atteinte à l'abscisse " + x + " m");
				this._lastError = rY;
				break;
			}
			lastY = rY.vCalc;
		}

		return trY;
	}

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

	/**
	 * @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": number[]
	} {
		// $this ->creer_section_param();

		// On transforme les champs du tableau des données du formulaire en variables
		// extract($this ->data, EXTR_OVERWRITE | EXTR_REFS);

		// include_spip('hyd_inc/courbe_remous');

		// $oLog = &$this ->oLog;

		this._lastError = undefined;

		// // On calcule les données pour créer un cache et afficher le résultat
		// $this ->oLog ->Add(_T('hydraulic:largeur_berge').' = '.format_nombre($this ->oSn ->rLargeurBerge, $this ->oP ->iPrec).' m');
		// $this ->oLog ->Add(_T('hydraulic:h_critique').' = '.format_nombre($this ->oSn ->CalcGeo('Yc'), $this ->oP ->iPrec).' m');
		// $this ->oLog ->Add(_T('hydraulic:h_normale').' = '.format_nombre($this ->oSn ->CalcGeo('Yn'), $this ->oP ->iPrec).' m');

		let Yc: number = this.Sn.Calc("Yc");
		this.debug("largeur berge " + this.Sn.Calc("B"));
		this.debug("hauteur critique " + Yc);
		this.Sn.HautNormale = this.Sn.Calc("Yn");
		this.debug("hauteur normale " + this.Sn.HautNormale);

		// Calcul des courbes de remous
		// $aC = array(); // deux items (Flu et Tor) composé d'un vecteur avec key=X et value=Y
		let crbFlu: { [key: number]: number; } = undefined;
		let crbTor: { [key: number]: number; } = undefined;

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

		// Calcul depuis l'aval
		// if ($this ->oSn ->rHautCritique <= $rYaval) {
		if (this.Sn.HautCritique <= this.prmCR.Yaval.v) {
			// $this ->oLog ->Add(_T('hydraulic:calcul_fluvial'));
			this.debug("Condition limite aval (" + this.prmCR.Yaval.v + ") >= Hauteur critique (" + this.Sn.HautCritique + ") : calcul de la partie fluviale à partir de l'aval");
			// $oCRF = new cCourbeRemous($this ->oLog, $this ->oP, $this ->oSn, $rDx);
			// $aC['Flu'] = $oCRF ->calcul($rYaval, $rLong, $Methode);
			this.Dx = this.prmSect.Dx.v;
			crbFlu = this.calcul(this.prmCR.Yaval.v);
		}
		else {
			// 	$this ->oLog ->Add(_T('hydraulic:pas_calcul_depuis_aval'), true);
			this.debug("Condition limite aval (" + this.prmCR.Yaval.v + ") < Hauteur critique (" + this.Sn.HautCritique + ") : pas de calcul possible depuis l'aval");
		}

		this.debug("flu ");
		this.logObject(crbFlu);

		// Calcul depuis l'amont
		// if ($this ->oSn ->rHautCritique >= $rYamont) {
		if (this.Sn.HautCritique >= this.prmCR.Yamont.v) {
			// $this ->oLog ->Add(_T('hydraulic:calcul_torrentiel'));
			this.debug("Condition limite amont (" + this.prmCR.Yamont.v + ") <= Hauteur critique (" + this.Sn.HautCritique + ") : calcul de la partie torrentielle à partir de l'amont");
			// $oCRT = new cCourbeRemous($this ->oLog, $this ->oP, $this ->oSn, -$rDx);
			// $aC['Tor'] = $oCRT ->calcul($rYamont, $rLong, $Methode);
			this.Dx = -this.prmSect.Dx.v;
			crbTor = this.calcul(this.prmCR.Yamont.v);
		}
		else {
			// 	$this ->oLog ->Add(_T('hydraulic:pas_calcul_depuis_amont'), true);
			this.debug("Condition limite amont (" + this.prmCR.Yamont.v + ") > Hauteur critique (" + this.Sn.HautCritique + ") : pas de calcul possible depuis l'amont");
		}
		// spip_log($aC, 'hydraulic', _LOG_DEBUG);

		this.debug("tor");
		this.logObject(crbTor);

		// Détection du ressaut hydraulique
		let nFlu: number = this.size(crbFlu);
		let nTor: number = this.size(crbTor);
		// $bDetectRessaut = true;
		// if ($bDetectRessaut && isset($aC['Flu']) && isset($aC['Tor'])) {
		// if (crbFlu != undefined && crbTor != undefined) {
		if (nFlu != 0 && nTor != 0) {
			// if (count($aC['Flu']) > count($aC['Tor']) || (count($aC['Flu']) == count($aC['Tor']) && $this ->oSn ->Calc('Imp', end($aC['Flu'])) > $this ->oSn ->Calc('Imp', end($aC['Tor'])))) {
			let xMaxFlu = crbFlu[0];
			let xMaxTor = this.last(crbTor);
			// this.debug("end flu " + xMaxFlu);
			// this.debug("end tor " + xMaxTor);
			// this.debug("nFlu " + nFlu);
			// this.debug("nTor " + nTor);
			// this.debug("Imp flu " + this.Sn.Calc('Imp', xMaxFlu));
			// this.debug("Imp tor " + this.Sn.Calc('Imp', xMaxTor));
			// if (nFlu > nTor || (nFlu == nTor && this.Sn.Calc('Imp', this.last(crbFlu)) > this.Sn.Calc('Imp', this.last(crbTor)))) {
			if (nFlu > nTor || (nFlu == nTor && this.Sn.Calc('Imp', xMaxFlu) > this.Sn.Calc('Imp', xMaxTor))) {
				// La courbe fluviale va jusqu'au bout
				// $sCC = 'Flu';
				var crbComplete = crbFlu;  // courbe calculée sur tout le bief
				// $sCN = 'Tor';
				var crbPartielle = crbTor;  // courbe calculée sur une partie seulement du bief
				var iSens = 1; // On cherche l'aval du ressaut
				//$sSens = _T('hydraulic:amont');
				var 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
				// $sCC = 'Tor';
				crbComplete = crbTor;
				// $sCN = 'Flu';
				crbPartielle = crbFlu;
				iSens = -1; // On cherche l'amont du ressaut
				// $sSens = _T('hydraulic:aval');
				sSens = "aval";
				this.debug("complete=tor, partielle=flu");
				// this.debug("complete(tor)");
				// this.debug(crbComplete);
				// this.debug("partielle(flu)");
				// this.debug(crbPartielle);
			}

			// $trX = array_reverse(array_keys($aC[$sCN])); // Parcours des sections de la ligne d'eau la plus courte
			let trX: string[] = Object.keys(crbPartielle);
			if (iSens == -1)
				trX.sort((a, b) => {
					if (+a > +b) return 1;
					if (+a < +b) return -1;
					return 0;
				});
			else
				trX.sort((a, b) => {
					if (+a > +b) return -1;
					if (+a < +b) return 1;
					return 0;
				});
			let trXr = trX.slice(0); // copie
			trXr.reverse();

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

			let bRessaut = false;
			let Dx = this.prmSect.Dx.v;

			// foreach($trX as $rX) {
			// for (let irX in trX) {
			// for (let irX = 0; irX < trX.length - 1; irX++) {
			for (let irX = 0; irX < trX.length; irX++) {
				let 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
				// $Yco = $this ->oSn ->Calc('Yco', $aC[$sCN][$rX]); // Y conjugué
				let Yco = this.Sn.Calc('Yco', crbPartielle[rX]); // Y conjugué
				this.debug("rX=" + rX + " Yco(Ypartiel=" + crbPartielle[rX] + ")=" + Yco);

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

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

				// $xRst = sprintf('%1.'.round($this ->oP ->iPrec).'f', $xRst);
				xRst = round(xRst, this.prmSect.iPrec.v);

				this.debug("xRst=" + xRst);

				//spip_log("\nrX=$rX xRst=$xRst Yco=$Yco",'hydraulic',_LOG_DEBUG);
				// if (isset($aC[$sCC][$xRst])) {
				if (crbComplete[xRst] != undefined) {
					// Hauteur décalée de la longueur du ressaut (il faut gérer la pente du fond)
					// $Ydec = $aC[$sCC][$xRst] + $rLongRst * $this ->oP ->rIf * $iSens;
					let Ydec: number = crbComplete[xRst] + rLongRst * this.prmSect.If.v * iSens;
					this.debug("Ydec=" + Ydec);
					this.debug("imp(Ycomplet[xRst=" + xRst + "]=" + crbComplete[xRst] + ")=" + this.Sn.Calc('Imp', crbComplete[xRst]));
					this.debug("imp(Ypartiel[rX=" + rX + "]=" + crbPartielle[rX] + ")=" + this.Sn.Calc('Imp', crbPartielle[rX]));

					// spip_log("\nrX=$rX xRst=$xRst Yco=$Yco Ydec=$Ydec", 'hydraulic', _LOG_DEBUG);
					// if (($Yco - $Ydec) > 0) {
					// if (iSens == 1 ? Yco > Ydec : Yco < Ydec) {
					if (Yco > Ydec) {
						// $this ->oLog ->Add(_T('hydraulic:ressaut_hydrau', array('Xmin'=>min($rX, $xRst), 'Xmax'=>max($rX, $xRst))));
						this.debug("Ressaut hydraulique détecté entre les abscisses " + Math.min(rX, xRst) + " et " + Math.max(rX, xRst));
						// spip_log("rX=$rX xRst=$xRst", 'hydraulic', _LOG_DEBUG);
						// this.debug("rX=" + rX + " xRst=" + xRst);
						// Modification de la ligne d'eau CC
						// foreach(array_keys($aC[$sCN]) as $rXCC) {
						// for (let pi in crbPartielle) {
						// for (let pi in trXr) {
						for (let pi of trXr) {
							let rXCC: number = +pi;
							// this.debug("rXCC=" + rXCC);
							// if ($iSens * ($rXCC - $rX) < 0) {
							if (iSens * (rXCC - rX) < 0) {
								// unset($aC[$sCC][$rXCC]);
								delete crbComplete[rXCC];
								this.debug("Modification de la ligne d'eau complète : suppression de la valeur à rX=" + rXCC);
								// } elseif($rXCC == $rX) {
							} else if (rXCC == rX) {
								// $aC[$sCC][$rXCC] = $aC[$sCN][$rXCC];
								this.debug("Modification de la ligne d'eau complète : valeur " + crbComplete[rXCC] + " remplacée par " + crbPartielle[rXCC] + " à rX=" + rXCC);
								crbComplete[rXCC] = crbPartielle[rXCC];
								this.debug("Fin de la modification de la ligne d'eau complète");
								break;
							}
						}

						// Modification de la ligne d'eau CN
						// foreach($trX as $rXCN) {
						for (let xcn of trX) {
							let rXCN = +xcn;
							// this.debug("rXCN=" + rXCN);
							// if ($iSens * ($rXCN - $xRst) > 0) {
							if (iSens * (rXCN - xRst) > 0) {
								// unset($aC[$sCN][$rXCN]);
								this.debug("Modification de la ligne d'eau partielle : suppression de la valeur à rX=" + rXCN);
								delete crbPartielle[rXCN];
								// } elseif($rXCN == $xRst) {
							} else if (rXCN == xRst) {
								// $aC[$sCN][$rXCN] = $aC[$sCC][$rXCN];
								this.debug("Modification de la ligne d'eau partielle : valeur " + crbPartielle[rXCN] + " remplacée par " + crbComplete[rXCN] + " à rX=" + rXCN);
								crbPartielle[rXCN] = crbComplete[rXCN];
								this.debug("Fin de la modification de la ligne d'eau partielle");
								break;
							}
						}
						bRessaut = true;
						break;
					}
				}
			}
			if (!bRessaut) {
				// Le ressaut est en dehors du canal
				//	$this ->oLog ->Add(_T('hydraulic:ressaut_dehors', array('Sens' => $sSens, 'X' => end($trX))));
				this.debug("Ressaut hydraulique détecté à l'" + sSens + " de l'abscisse " + this.last(trX));
				// $aC[$sCN] = array();
				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("partielle (" + (iSens == 1 ? "tor" : "flu") + ") modifiée");
		this.logObject(crbPartielle);

		// Définition des abscisses
		// $trX = array();
		let trX: string[] = [];

		// if (isset($aC['Flu'])) $trX = array_merge($trX, array_keys($aC['Flu']));
		// if (crbFlu != undefined)
		if (nFlu != 0)
			trX = Object.keys(crbFlu);

		// if (isset($aC['Tor'])) $trX = array_merge($trX, array_keys($aC['Tor']));
		// if (crbTor != undefined)
		if (nTor != 0)
			trX = trX.concat(Object.keys(crbTor));
		// this.debug("trX=" + trX);

		// $trX = array_unique($trX, SORT_NUMERIC);
		// sort($trX, SORT_NUMERIC);
		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);

		// Calcul de la variable à calculer
		// $this ->data['ValCal'] = $val_a_cal;

		// $tRes = array();
		let tRes: number[] = [];
		// if ($val_a_cal != 'none') {
		// if (val_a_cal != undefined && crbFlu != undefined && crbTor != undefined) {
		if (val_a_cal != undefined && nFlu != 0 && nTor != 0) {
			// foreach($trX as $rX) {
			for (let rX of trX) {
				// $rY = false;
				let rY = undefined;

				// if (isset($aC['Flu'][$rX]) && !isset($aC['Tor'][$rX])) {
				if (crbFlu[+rX] != undefined && crbTor[+rX] == undefined) {
					// $rY = $aC['Flu'][$rX];
					rY = crbFlu[+rX];
				}
				// if (isset($aC['Tor'][$rX])) {
				if (crbTor[+rX] != undefined) {
					// if (!isset($aC['Flu'][$rX]) || (isset($aC['Flu'][$rX]) && $aC['Flu'][$rX] == $aC['Tor'][$rX])) {
					if (crbFlu[+rX] == undefined || (crbFlu[+rX] != undefined && crbFlu[+rX] == crbTor[+rX])) {
						// $rY = $aC['Tor'][$rX];
						rY = crbTor[+rX];
					}

					// if ($rY !== false) {
					if (rY != undefined)
						// if (!in_array($val_a_cal, array('Yn', 'Yc', 'Hsc'))) {
						// $tRes[$rX] = $this ->oSn ->Calc($val_a_cal, $rY);
						// 	}
						// 	else {
						// $tRes[$rX] = $this ->oSn ->CalcGeo($val_a_cal, $rY);
						// 	}
						// }
						tRes[+rX] = this.Sn.Calc(val_a_cal, rY);
				}
			}

			/*		
			return array_merge(
				$aC,
				array(
					'trX' => $trX,
					'tRes' => $tRes
				)
			);
			/* */
		}

		return { "flu": crbFlu, "tor": crbTor, "trX": trX, "tRes": tRes };
	}
}