diff --git a/spec/iterator/param_equation.spec.ts b/spec/iterator/param_equation.spec.ts
index b8b881f063981292fdc837ce7af13eaccaf6bdab..40660ada8280255a156208939a42079e52c3da92 100644
--- a/spec/iterator/param_equation.spec.ts
+++ b/spec/iterator/param_equation.spec.ts
@@ -1,29 +1,54 @@
-/// <reference path="../../node_modules/@types/jasmine/index.d.ts" />
+/**
+ * IMPORTANT !
+ * Décommenter temporairement la ligne suivante (import { } from "./mock_jasmine")
+ * Pour exécuter ce code dans le débugger.
+ * Faire de même avec le fichier test_func.ts
+ */
+// import { describe, expect, it, xdescribe, xit } from "../mock_jasmine";
 
 import { ConduiteDistribParams, ConduiteDistrib } from "../../src/cond_distri";
+import { ParallelStructureParams } from "../../src/structure/parallel_structure_params";
+import { ParallelStructure } from "../../src/structure/parallel_structure";
+import { Structure } from "../../src/structure/structure";
+import { CreateStructure } from "../../src/structure/factory_structure";
+import { StructureType, LoiDebit } from "../../src/structure/structure_props";
+import { IParamDefinitionIterator } from "../../src/param/params-equation";
 
-describe("iterator ConduiteDistribParams : ", () => {
-    it("test 1", () => {
-        const cdp: ConduiteDistribParams = new ConduiteDistribParams(1, 2, 3, 4, 5)
+function checkParams(pdi: IParamDefinitionIterator, symbols: string[], values: number[]) {
+    let n = 0;
+    for (const p of pdi) {
+        expect(p.symbol === symbols[n]);
+        if (n < values.length)
+            expect(p.v === values[n]);
+        n++;
+    }
+    expect(n === symbols.length).toBeTruthy();
+}
 
-        let n = 0;
-        for (let p of cdp) {
-            n++;
-            expect(p.v == n);
-        }
-        expect(n === 5);
+describe("iterator  : ", () => {
+    it("ConduiteDistribParams", () => {
+        const peq: ConduiteDistribParams = new ConduiteDistribParams(1, 2, 3, 4, 5);
+        const symbs = ["Q", "D", "J", "Lg", "Nu"];
+        const vals = [1, 2, 3, 4, 5];
+        checkParams(peq.iterator, symbs, vals);
     });
 
-    it("test 2", () => {
-        const cdp: ConduiteDistribParams = new ConduiteDistribParams(1, 2, 3, 4, 5)
+    it("ParallelStructureParams 1", () => {
+        const peq: ParallelStructureParams = new ParallelStructureParams(1, 2, 3);
+        const symbs = ["Q", "Z1", "Z2"];
+        const vals = [1, 2, 3];
+        checkParams(peq.iterator, symbs, vals);
+    });
 
-        const symbs = ["Q", "D", "J", "Lg", "Nu"];
-        let n = 0;
-        for (const p of cdp.iterator) {
-            expect(p.v == n + 1);
-            expect(p.symbol == symbs[n]);
-            n++;
-        }
-        expect(n === 5).toBeTruthy();
+    it("ParallelStructureParams 2", () => {
+        const psp: ParallelStructureParams = new ParallelStructureParams(1, 2, 3);
+        const pst = new ParallelStructure(psp);
+
+        const st: Structure = CreateStructure(StructureType.SeuilRectangulaire, LoiDebit.Cem88d);
+        pst.addStructure(st);
+
+        const symbs = ["Q", "Z1", "Z2", "Cd", "h1", "h2", "L", "Q", "W", "Z1", "Z2", "ZDV"];
+        const vals = [1, 2, 3];
+        checkParams(pst.parameterIterator, symbs, vals);
     });
 });
diff --git a/spec/iterator/paramvalues_iterator.spec.ts b/spec/iterator/paramvalues_iterator.spec.ts
index b92b76790b3116f423b8638b990bc6cb30ee7d47..238b64b503e76072787e6bb8e807fa1fb52fefe3 100644
--- a/spec/iterator/paramvalues_iterator.spec.ts
+++ b/spec/iterator/paramvalues_iterator.spec.ts
@@ -1,5 +1,3 @@
-/// <reference path="../../node_modules/@types/jasmine/index.d.ts" />
-
 import { ParamValues, ParamValueMode, ParamValueIterator } from "../../src/param/param-values"
 
 function checkNumberList(it: ParamValueIterator, exp: number[]) {
@@ -14,7 +12,7 @@ function checkNumberList(it: ParamValueIterator, exp: number[]) {
 describe('paramvalues iterator : ', () => {
     it("vide (1)", () => {
         const p = new ParamValues();
-        expect(() => p.getValuesIterator()).toThrow(new Error("ParamValueIterator : mode de génération de valeurs CALCUL incorrect"));
+        expect(() => p.getValuesIterator()).toThrow(new Error("ParamValues : mode de valeurs undefined incorrect"));
     });
 
     it("vide (2)", () => {
diff --git a/spec/structure/parallel_structure.spec.ts b/spec/structure/parallel_structure.spec.ts
index 1f2d7d9b9ccbbd7204fe4e28ee0774d245b85bb1..78f1ca3edfe8a8443f4a7ebc9288c8fa7910d15a 100644
--- a/spec/structure/parallel_structure.spec.ts
+++ b/spec/structure/parallel_structure.spec.ts
@@ -7,7 +7,8 @@
 // import { describe, expect, it, xdescribe, xit } from "../mock_jasmine";
 
 import { ParamCalculability } from "../../src/param/param-definition";
-import { CreateStructure, loiAdmissibles, LoiDebit, StructureType } from "../../src/structure/factory_structure";
+import { CreateStructure } from "../../src/structure/factory_structure";
+import { LoiDebit, StructureType, loiAdmissibles } from "../../src/structure/structure_props";
 import { ParallelStructure } from "../../src/structure/parallel_structure";
 import { ParallelStructureParams } from "../../src/structure/parallel_structure_params";
 import { Structure } from "../../src/structure/structure";
diff --git a/spec/structure/structure_kivi.spec.ts b/spec/structure/structure_kivi.spec.ts
index 1db9a2b5335eb3dca76b5eb6a1d369e64eed6bd9..1286b02b87ea3183cbb4ea328586eccb8d205414 100644
--- a/spec/structure/structure_kivi.spec.ts
+++ b/spec/structure/structure_kivi.spec.ts
@@ -7,7 +7,8 @@
 // import { describe, expect, it, xdescribe, xit } from "../mock_jasmine";
 
 import { StructureFlowMode, StructureFlowRegime, MessageCode } from "../../src";
-import { CreateStructure, LoiDebit, StructureType } from "../../src/structure/factory_structure";
+import { CreateStructure } from "../../src/structure/factory_structure";
+import { LoiDebit, StructureType } from "../../src/structure/structure_props";
 import { StructureKivi } from "../../src/structure/structure_kivi";
 import { StructureKiviParams } from "../../src/structure/structure_kivi_params";
 import { testStructure } from "./structure_test";
diff --git a/src/base.ts b/src/base.ts
index 95f88b73cec1aa1c9a65082e35b2e22592df4ff9..7ab2bd593ae09221a212033076f395370f355f3a 100644
--- a/src/base.ts
+++ b/src/base.ts
@@ -1,10 +1,17 @@
 /**
  * Gestion des messages de debogage dans la console
- * @note Etendre cette classe pour toutes les classes à debugguer
+ * @note Implémenter cette interface pour toutes les classes à debugguer
  * Dans le constructeur, utiliser super(true) pour debugger la classe, super(false) sinon.
  */
+
+export interface IDebug {
+    debug(s: any): void;
+
+    readonly DBG: boolean;
+}
+
 // tslint:disable-next-line:max-classes-per-file
-export abstract class Debug {
+export class Debug {
     /**
      * @param _DBG Flag de débuggage
      */
diff --git a/src/compute-node.ts b/src/compute-node.ts
index acc5e5b432f2a038355e24a2a00ba525bf24c1d6..88ff3470faae9b9fbb1d386699967982268c1966 100644
--- a/src/compute-node.ts
+++ b/src/compute-node.ts
@@ -1,7 +1,8 @@
-import { Debug } from "./base";
-import { ParamsEquation } from "./param/params-equation";
+import { Debug, IDebug } from "./base";
+import { ParamsEquation, IParamDefinitionIterator } from "./param/params-equation";
 import { ParamDefinition } from "./param/param-definition";
 import { ParamValueMode } from "./param/param-values";
+import { JalhydObject } from "./jalhyd_object";
 
 /**
  * type de calculette
@@ -22,20 +23,21 @@ export enum ComputeNodeType {
     // types de sections
     SectionTrapeze, SectionRectangle, SectionCercle, SectionPuissance,
     // types d'ouvrages hydrauliques
-    StructureRectangle,
-    // ouvrages hydrauliques : Kindsvater-Carter & Villemonte
-    StructureKIVI
+    StructureRectangle
 }
 
 /**
  * noeud de calcul
  */
 // tslint:disable-next-line:max-classes-per-file
-export abstract class ComputeNode extends Debug {
+export abstract class ComputeNode extends JalhydObject implements IDebug {
     protected _prms: ParamsEquation;
 
+    private _debug: Debug;
+
     constructor(prms: ParamsEquation, dbg: boolean = false) {
-        super(dbg);
+        super();
+        this._debug = new Debug(dbg);
         this._prms = prms;
         if (!this._prms.calculabilityDefined) {
             this._prms.resetParametersCalculability();
@@ -44,24 +46,37 @@ export abstract class ComputeNode extends Debug {
         this._prms.DefineCalculability();
     }
 
+    public get parameters(): ParamsEquation {
+        return this._prms;
+    }
+
     public getParameter(name: string): ParamDefinition {
-        return this._prms.getParameter(name);
+        for (const p of this.parameterIterator)
+            if (p.symbol === name)
+                return p;
+        return undefined;
     }
 
     public getFirstAnalyticalParameter(): ParamDefinition {
-        return this._prms.getFirstAnalyticalParameter();
+        for (const p of this.parameterIterator)
+            if (p.isAnalytical())
+                return p;
+        return undefined;
     }
 
-    public initParametersValueMode(computedParam: ParamDefinition, variatedParam?: ParamDefinition, variatedMode?: ParamValueMode) {
-        for (const k in this._prms.map) {
-            if (k == computedParam.symbol)
-                this._prms.map[k].paramValues.valueMode = ParamValueMode.CALCUL;
-            else if (variatedParam && k == variatedParam.symbol)
-                this._prms.map[k].paramValues.valueMode = variatedMode;
-            else
-                this._prms.map[k].paramValues.valueMode = ParamValueMode.SINGLE;
-        }
+    public get parameterIterator(): IParamDefinitionIterator {
+        return this._prms.iterator;
     }
 
     protected abstract setParametersCalculability(): void;
+
+    // interface IDebug
+
+    debug(s: any) {
+        this._debug.debug(s);
+    }
+
+    public get DBG(): boolean {
+        return this._debug.DBG;
+    }
 }
diff --git a/src/dichotomie.ts b/src/dichotomie.ts
index 568fdc977acef3867fb907c88d0f33111cb2166e..43a29c63b06331eb24da29326bf3979b2647aa47 100644
--- a/src/dichotomie.ts
+++ b/src/dichotomie.ts
@@ -118,6 +118,13 @@ export class Dichotomie extends Debug {
      */
     private _startIntervalMaxSteps = 100;
 
+    /**
+     * nombre d'itérations maxi pour la recherche dichotomique
+     */
+    private _maxIterations: number = 100;
+
+    private _currentIterations: number;
+
     /**
      * nom du paramètre calculé analytiquement par Equation()
      */
@@ -139,6 +146,10 @@ export class Dichotomie extends Debug {
         }
     }
 
+    public set maxIterations(n: number) {
+        this._maxIterations = Math.max(n, 1);
+    }
+
     /**
      * Valeur inconnue à rechercher
      */
@@ -147,7 +158,7 @@ export class Dichotomie extends Debug {
     }
 
     private set vX(vCalc: number) {
-        this._paramX.v = vCalc;
+        this._paramX.setValue(vCalc);
     }
 
     /**
@@ -188,6 +199,7 @@ export class Dichotomie extends Debug {
         const r = this.getStartInterval(rTarget, rInit);
         if (r.ok) {
             const interv: SearchInterval = r.intSearch;
+            this._currentIterations = 0;
             // Dichotomie
             return this.dichoSearch(rTarget, rTol, interv.min, interv.max, interv.targetLower, interv.targetUpper);
         } else {
@@ -371,6 +383,13 @@ export class Dichotomie extends Debug {
      */
     // tslint:disable-next-line:variable-name
     private dichoSearch(Ytarg: number, rTol: number, Xmin: number, Xmax: number, Ymin: number, Ymax: number): Result {
+        this._currentIterations++;
+        if (this._currentIterations > this._maxIterations) {
+            const r = new Result();
+            r.globalLog.add(new Message(MessageCode.ERROR_DICHO_CONVERGE));
+            return r;
+        }
+
         this.debug("dichoSearch : yTarget " + Ytarg + " X " + Xmin + "->" + Xmax + " tol " + rTol);
 
         // tslint:disable-next-line:variable-name
diff --git a/src/index.ts b/src/index.ts
index 4cc0584dd590e9af70101e7e5ded51e27a3653dd..f904a4e2888d429cf7ea21fe4a2ed3d091308033 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -5,13 +5,15 @@ export * from "./param/param-domain";
 export * from "./param/params-equation";
 export * from "./param/param-values";
 export * from "./compute-node";
-export * from "./parameters";
 export * from "./nub";
+export * from "./nub_factory";
+export * from "./session_nub";
 export * from "./cond_distri";
 export * from "./dichotomie";
 export * from "./lechaptcalmon";
 export * from "./regime_uniforme";
 export * from "./remous";
+export * from "./section/section_nub";
 export * from "./section/section_type";
 export * from "./section/section_trapez";
 export * from "./section/section_rectang";
@@ -25,6 +27,7 @@ export * from "./util/result";
 export * from "./util/resultelement";
 export * from "./util/pair";
 export * from "./util/interval";
+export * from "./util/observer";
 export * from "./pab/pab_dimension";
 export * from "./pab/pab_puissance";
 export * from "./util/iterator";
@@ -34,4 +37,5 @@ export * from "./structure/parallel_structure_params";
 export * from "./structure/structure";
 export * from "./structure/structure_params";
 export * from "./structure/factory_structure";
+export * from "./structure/structure_props";
 export * from "./jalhyd_object";
diff --git a/src/nub.ts b/src/nub.ts
index 6ed251bdaa9a0f77a2468aeefcfe08fcf9e1c39b..26f285efb765b6246d56e01cd459fdd35a023056 100644
--- a/src/nub.ts
+++ b/src/nub.ts
@@ -39,10 +39,12 @@ export abstract class Nub extends ComputeNode {
      * @param rPrec précision de calcul
      */
     public Calc(sVarCalc: string, rInit?: number, rPrec: number = 0.001): Result {
+        const computedVar = this.getParameter(sVarCalc);
+
         if (rInit === undefined) {
-            rInit = this._prms.map[sVarCalc].v;
+            rInit = computedVar.v;
         }
-        if (this._prms.map[sVarCalc].isAnalytical()) {
+        if (computedVar.isAnalytical()) {
             this._result = this.Equation(sVarCalc);
             return this._result;
         }
@@ -53,21 +55,26 @@ export abstract class Nub extends ComputeNode {
             return this._result;
         }
         const sAnalyticalPrm: string = this.getFirstAnalyticalParameter().symbol;
-        this._prms.map[sVarCalc].v = resSolve.vCalc;
+        computedVar.setValue(resSolve.vCalc);
         const res: Result = this.Equation(sAnalyticalPrm);
         res.vCalc = resSolve.vCalc;
         this._result = res;
         return res;
     }
 
-    public CalcSerie(rPrec: number = 0.001, rInit?: number): Result {
+    /**
+     * effectue une série de calculs sur un paramètre
+     * @param rPrec précision de calcul
+     * @param rInit solution approximative du paramètre
+     * @param sDonnee éventuel symbole du paramètre à calculer
+     */
+    public CalcSerie(rPrec: number = 0.001, rInit?: number, sDonnee?: string): Result {
         const res = new Result();
         this._result = res;
 
         let variatedParam: ParamDefinition;
         let computedParam: ParamDefinition;
-        for (const k in this._prms.map) {
-            const p: ParamDefinition = this._prms.map[k];
+        for (const p of this.parameterIterator) {
 
             switch (p.valueMode) {
                 case ParamValueMode.LISTE:
@@ -79,32 +86,41 @@ export abstract class Nub extends ComputeNode {
                     break;
 
                 case ParamValueMode.CALCUL:
-                    if (computedParam == undefined)
-                        computedParam = p;
-                    else
-                        throw new Error(`CalcSerie() : il y plusieurs paramètres à calculer (au moins ${computedParam.symbol} et ${p.symbol})`);
+                    if (sDonnee == undefined) {
+                        if (computedParam == undefined)
+                            computedParam = p;
+                        else
+                            throw new Error(`CalcSerie() : il y plusieurs paramètres à calculer (au moins ${computedParam.symbol} et ${p.symbol})`);
+                    }
                     break;
             }
         }
 
-        if (computedParam == undefined)
-            throw new Error(`CalcSerie() : aucun paramètre à calculer`);
+        if (sDonnee)
+            var computedSymbol: string = sDonnee;
+        else {
+            if (computedParam == undefined)
+                throw new Error(`CalcSerie() : aucun paramètre à calculer`);
+            computedSymbol = computedParam.symbol;
+        }
 
         if (rInit === undefined)
-            rInit = this._prms.map[computedParam.symbol].v;
+            rInit = computedParam.v;
 
         if (variatedParam == undefined)
-            this.Calc(computedParam.symbol, rInit, rPrec); // résultat dans this._result
+            this._result = this.Calc(computedSymbol, rInit, rPrec); // résultat dans this._result
         else {
             const res = new Result();
             variatedParam.paramValues.initIterator();
             while (variatedParam.paramValues.hasNext) {
                 variatedParam.paramValues.next;
-                this.Calc(computedParam.symbol, rInit, rPrec);  // résultat dans this._result
-                res.addResultElement(this._result.resultElement);
-                res.addLog(this._result.log);
-                if (this._result.ok)
+                this.Calc(computedSymbol, rInit, rPrec);  // résultat dans this._result
+                if (this._result.ok) {
+                    res.addResultElement(this._result.resultElement);
+                    res.addLog(this._result.log);
                     rInit = this._result.resultElement.vCalc;
+                }
+                res.globalLog.addLog(this._result.globalLog);
             }
             this._result = res;
         }
@@ -121,7 +137,7 @@ export abstract class Nub extends ComputeNode {
     private Solve(sVarCalc: string, rInit: number, rPrec: number): Result {
         const dicho: Dichotomie = new Dichotomie(this, sVarCalc, this.DBG);
         dicho.startIntervalMaxSteps = this._dichoStartIntervalMaxSteps;
-        const target = this._prms.getFirstAnalyticalParameter();
+        const target = this.getFirstAnalyticalParameter();
         return dicho.Dichotomie(target.v, rPrec, rInit);
     }
 
diff --git a/src/nub_factory.ts b/src/nub_factory.ts
new file mode 100644
index 0000000000000000000000000000000000000000..56a6a1287fde232880e405d22be2e21d9bf32455
--- /dev/null
+++ b/src/nub_factory.ts
@@ -0,0 +1,275 @@
+import { ComputeNodeType, CalculatorType } from "./compute-node"
+import { Nub } from "./nub"
+import { SessionNub, Props } from "./session_nub"
+import { ConduiteDistribParams, ConduiteDistrib } from "./cond_distri";
+import { LechaptCalmonParams, LechaptCalmon } from "./lechaptcalmon";
+
+import { acSection } from "./section/section_type";
+import { ParamsSectionTrapez, cSnTrapez } from "./section/section_trapez";
+import { ParamsSectionRectang, cSnRectang } from "./section/section_rectang";
+import { ParamsSectionCirc, cSnCirc } from "./section/section_circulaire";
+import { ParamsSectionPuiss, cSnPuiss } from "./section/section_puissance";
+import { SectionParametree } from "./section/section_nub";
+import { RegimeUniforme } from "./regime_uniforme";
+import { CourbeRemousParams, MethodeResolution, CourbeRemous } from "./remous";
+import { PabDimensionParams, PabDimension } from "./pab/pab_dimension";
+import { PabPuissance, PabPuissanceParams } from "./pab/pab_puissance";
+import { CreateStructure } from "./structure/factory_structure";
+import { StructureType, LoiDebit } from "./structure/structure_props";
+import { Structure } from "./structure/structure";
+import { ParallelStructure } from "./structure/parallel_structure";
+import { ParallelStructureParams } from "./structure/parallel_structure_params";
+import { RectangularStructureParams } from "./structure/structure_cem88d";
+
+export class NubFactory {
+    private _defaultPrecision: number = 0.001;
+
+    private static _instance: NubFactory; // instance pour le pattern singleton
+
+    private _session: SessionNub[];
+
+    private constructor() {
+        this._session = [];
+    }
+
+    public static getInstance() {
+        if (NubFactory._instance == undefined)
+            NubFactory._instance = new NubFactory();
+        return NubFactory._instance;
+    }
+
+    public setDefaultPrecision(p: number) {
+        this._defaultPrecision = p;
+    }
+
+    private newSessionNub(p: Props | {}): SessionNub {
+        const params = p instanceof Props ? p : new Props(p);
+        const nub = this.createNub(params);
+        return new SessionNub(nub, params);
+    }
+
+    /**
+     * créé un Nub et l'ajoute à la session
+     */
+    public createSessionNub(p: Props | {}): SessionNub {
+        const res = this.newSessionNub(p);
+        this._session.push(res);
+        return res;
+    }
+
+    public findSessionNub(params: Props | {}): SessionNub {
+        for (const n of this._session)
+            if (n.hasProperties(params))
+                return n;
+        return undefined;
+    }
+
+    private replaceStructureNub(oldNub: Nub, newNub: Nub) {
+        const b1 = oldNub instanceof Structure;
+        const b2 = newNub instanceof Structure;
+        if ((b1 && !b2) || (!b1 && b2))
+            throw new Error(`NubFactory.replaceStructureNub() : arguments incorrects (${typeof (oldNub)}/${typeof (newNub)}`);
+        if (b1 && b2) {
+            for (const sn of this._session)
+                if (sn.nub instanceof ParallelStructure) {
+                    const psn = sn.nub as ParallelStructure;
+                    let i = 0;
+                    for (const st of psn.structures) {
+                        if (st.uid == oldNub.uid) {
+                            psn.replaceStructure(i, newNub as Structure);
+                            return;
+                        }
+                        i++;
+                    }
+                }
+            throw new Error(`NubFactory.replaceStructureNub() : la structure (uid ${oldNub.uid}) à remplacer n'a pas été trouvée`);
+        }
+    }
+
+    /**
+     * remplace un SessionNub par un nouveau
+     * @param sn SessionNub à remplacer
+     * @param params propriété du nouveau SessionNub
+     */
+    public replaceSessionNub(sn: SessionNub, params: Props): SessionNub {
+        let i = 0;
+        for (const n of this._session) {
+            if (n.uid == sn.uid) {
+                const newNub = this.newSessionNub(params);
+                this._session[i] = newNub;
+                this.replaceStructureNub(sn.nub, newNub.nub);
+                return newNub;
+            }
+            i++;
+        }
+
+        // pas trouvé
+        return undefined;
+    }
+
+    /**
+     * créé un Nub
+     * @param calcType type de Nub
+     * @param nodeType sous type de Nub
+     * @param params paramètres supplémentaires spécifiques
+     */
+    private createNub(params: Props): Nub {
+        const calcType: CalculatorType = params.getPropValue("calcType");
+        const nodeType: ComputeNodeType = params.getPropValue("nodeType");
+
+        switch (calcType) {
+            case CalculatorType.ConduiteDistributrice:
+                {
+                    const prms = new ConduiteDistribParams(3, // débit Q
+                        1.2, // diamètre D
+                        0.6, // perte de charge J
+                        100, // Longueur de la conduite Lg
+                        1e-6, // Viscosité dynamique Nu
+                    );
+
+                    return new ConduiteDistrib(prms);
+                }
+
+            case CalculatorType.LechaptCalmon:
+                {
+                    const prms = new LechaptCalmonParams(3, // débit
+                        1.2, // diamètre
+                        0.6, /// perte de charge
+                        100, // longueur du toyo
+                        1.863, // paramètre L du matériau
+                        2, // paramètre M du matériau
+                        5.33// paramètre N du matériau
+                    );
+                    return new LechaptCalmon(prms);
+                }
+
+            case CalculatorType.SectionParametree:
+                return new SectionParametree(this.createSection(nodeType));
+
+            case CalculatorType.RegimeUniforme:
+                const sect: acSection = this.createSection(nodeType);
+                const ru = new RegimeUniforme(sect);
+                return ru;
+
+
+            case CalculatorType.CourbeRemous:
+                {
+                    const sect: acSection = this.createSection(nodeType);
+                    const prms = new CourbeRemousParams(sect, 0.15, // Yamont = tirant amont
+                        0.4, // Yaval = tirant aval
+                        100,  // Long= Longueur du bief
+                        5,  // Dx=Pas d'espace
+                        MethodeResolution.EulerExplicite
+                    );
+                    return new CourbeRemous(prms);
+                }
+
+            case CalculatorType.PabDimensions:
+                {
+                    const prms = new PabDimensionParams(
+                        2,      // Longueur L
+                        1,      // Largeur W
+                        0.5,    // Tirant d'eau Y
+                        2       // Volume V
+                    );
+                    return new PabDimension(prms);
+                }
+
+            case CalculatorType.PabPuissance:
+                {
+                    const prms = new PabPuissanceParams(
+                        0.3,      // Chute entre bassins DH (m)
+                        0.1,      // Débit Q (m3/s)
+                        0.5,    // Volume V (m3)
+                        588.6   // Puissance dissipée Pv (W/m3)
+                    );
+                    return new PabPuissance(prms);
+                }
+
+            case CalculatorType.Structure:
+                const structType: StructureType = params.getPropValue("structureType");
+                const loiDebit: LoiDebit = params.getPropValue("loiDebit");
+                return CreateStructure(structType, loiDebit);
+
+            case CalculatorType.ParallelStructure:
+                {
+                    const prms = new ParallelStructureParams(0.5, // Q
+                        102, // Z1
+                        101.5 // Z2 
+                    );
+                    return new ParallelStructure(prms);
+                }
+
+            default:
+                throw new Error(`NubFactory.createNub() : calculatrice '${CalculatorType[calcType]}' / noeud de calcul '${ComputeNodeType[nodeType]}' non pris en charge`);
+        }
+    }
+
+    private createSection(nt: ComputeNodeType): acSection {
+        switch (nt) {
+            case ComputeNodeType.None: // pour les paramètres communs, n'importe quelle section convient
+            case ComputeNodeType.SectionTrapeze:
+                {
+                    const prms = new ParamsSectionTrapez(2.5, // largeur de fond
+                        0.56, // fruit
+                        0.8, // tirant d'eau
+                        40, //  Ks=Strickler
+                        1.2,  //  Q=Débit
+                        0.001, //  If=pente du fond
+                        this._defaultPrecision, // précision
+                        1, // YB= hauteur de berge
+                    );
+
+                    return new cSnTrapez(prms);
+                }
+
+
+            case ComputeNodeType.SectionRectangle:
+                {
+                    const prms = new ParamsSectionRectang(0.8, // tirant d'eau
+                        2.5, // largeur de fond
+                        40, //  Ks=Strickler
+                        1.2, // Q=Débit
+                        0.001, // If=pente du fond
+                        this._defaultPrecision, // précision
+                        1 // YB=hauteur de berge
+                    );
+                    return new cSnRectang(prms);
+                }
+
+            case ComputeNodeType.SectionCercle:
+                {
+                    const prms = new ParamsSectionCirc(2, // diamètre
+                        0.8, // tirant d'eau
+                        40, //  Ks=Strickler
+                        1.2,  //  Q=Débit
+                        0.001, //  If=pente du fond
+                        this._defaultPrecision, // précision
+                        1, // YB= hauteur de berge
+                    );
+                    return new cSnCirc(prms);
+                }
+
+            case ComputeNodeType.SectionPuissance:
+                {
+                    const prms = new ParamsSectionPuiss(0.5, // coefficient
+                        0.8, // tirant d'eau
+                        4, // largeur de berge
+                        40, //  Ks=Strickler
+                        1.2,  //  Q=Débit
+                        0.001, //  If=pente du fond
+                        this._defaultPrecision, // précision
+                        1, // YB= hauteur de berge
+                    );
+                    return new cSnPuiss(prms);
+                }
+
+            default:
+                throw new Error(`type de section ${ComputeNodeType[nt]} non pris en charge`);
+        }
+    }
+
+    // public get sessionNubIterator(): IterableIterator<SessionNub> {
+    //     return this._session[Symbol.iterator](); // crée un itérateur à partir d'un tableau
+    // }
+}
\ No newline at end of file
diff --git a/src/param/param-base.ts b/src/param/param-base.ts
index b5ee3c26c9f58f70d47e8a1af193afe98f7f80cd..6731c4566b1389a5a2526233db51ab429c055d64 100644
--- a/src/param/param-base.ts
+++ b/src/param/param-base.ts
@@ -31,7 +31,9 @@ export class BaseParam extends JalhydObject {
         this._symbol = symb;
 
         this._paramValues = new ParamValues();
-        this._paramValues.singleValue = val;
+        this._paramValues.setSingleValue(val);
+        if (val !== undefined)
+            this._paramValues.valueMode = ParamValueMode.SINGLE;
 
         if (d instanceof ParamDomain) {
             this._domain = d;
@@ -78,9 +80,7 @@ export class BaseParam extends JalhydObject {
 
     public setValue(val: number) {
         this.checkValue(val);
-        this._paramValues.singleValue = val;
-
-        //        console.log("setting param " + this._symbol + " id=" + this._id + " to " + val); // A VIRER
+        this._paramValues.setSingleValue(val);
     }
 
     public get uncheckedValue(): number {
diff --git a/src/param/param-values.ts b/src/param/param-values.ts
index 62d132ba666f730aec92977eab55e5a1f088b545..3793c823327e4d88396f1afce8de97590e1f546f 100644
--- a/src/param/param-values.ts
+++ b/src/param/param-values.ts
@@ -211,23 +211,22 @@ export class ParamValues {
 
     constructor() {
         this._singleValue = new DefinedNumber();
-        this.valueMode = ParamValueMode.CALCUL;
     }
 
     public setValues(o: number | any, max?: number, step?: number) {
         if (typeof (o) === "number") {
             if (max == undefined) {
-                this.valueMode = ParamValueMode.SINGLE;
+                this._valueMode = ParamValueMode.SINGLE;
                 this._singleValue.value = o as number;
             } else {
-                this.valueMode = ParamValueMode.MINMAX;
+                this._valueMode = ParamValueMode.MINMAX;
                 this._minValue = o as number;
                 this._maxValue = max;
                 this._stepValue = step;
             }
         }
         else if (Array.isArray(o)) {
-            this.valueMode = ParamValueMode.LISTE;
+            this._valueMode = ParamValueMode.LISTE;
             this._valueList = o;
         }
         else
@@ -286,9 +285,8 @@ export class ParamValues {
         return this._singleValue.uncheckedValue;
     }
 
-    public set singleValue(v: number) {
+    public setSingleValue(v: number) {
         this._singleValue.value = v;
-        this.valueMode = ParamValueMode.SINGLE;
     }
 
     public get isDefined() {
@@ -301,7 +299,7 @@ export class ParamValues {
 
     public set min(v: number) {
         this._minValue = v;
-        this.valueMode = ParamValueMode.MINMAX;
+        this._valueMode = ParamValueMode.MINMAX;
     }
 
     public get max() {
@@ -310,7 +308,7 @@ export class ParamValues {
 
     public set max(v: number) {
         this._maxValue = v;
-        this.valueMode = ParamValueMode.MINMAX;
+        this._valueMode = ParamValueMode.MINMAX;
     }
 
     public get stepRefValue(): Pair {
@@ -325,7 +323,7 @@ export class ParamValues {
 
     public set step(v: number) {
         this._stepValue = v;
-        this.valueMode = ParamValueMode.MINMAX;
+        this._valueMode = ParamValueMode.MINMAX;
     }
 
     public get valueList() {
@@ -335,7 +333,7 @@ export class ParamValues {
 
     public set valueList(l: number[]) {
         this._valueList = l;
-        this.valueMode = ParamValueMode.LISTE;
+        this._valueMode = ParamValueMode.LISTE;
     }
 
     /**
@@ -378,17 +376,4 @@ export class ParamValues {
         this._singleValue.value = this._iterator.next().value;
         return this._singleValue.value;
     }
-
-    /**
-     * copie des membres
-     */
-    public copyMembers(n: ParamValues) {
-        n._valueMode = this.valueMode;
-        n._singleValue = new DefinedNumber(this._singleValue.value);
-        n._minValue = this._minValue;
-        n._maxValue = this._maxValue;
-        n._stepValue = this._stepValue;
-        if (this._valueList != undefined)
-            n._valueList = this._valueList.slice(0); // copie
-    }
 }
diff --git a/src/param/params-equation.ts b/src/param/params-equation.ts
index 94ea6bcd25ed57b07cd2d2cb1843f5797fc45568..e84f4525aa3d7bde134836dc269a3034fc65b846 100644
--- a/src/param/params-equation.ts
+++ b/src/param/params-equation.ts
@@ -2,6 +2,79 @@ import { MapIterator } from "../util/iterator"
 
 import { ParamDefinition } from "./param-definition";
 
+export interface IParamDefinitionIterator extends IterableIterator<ParamDefinition> {
+}
+
+/**
+ * itérateur sur les paramètres d'une seule instance de ParamsEquation
+ */
+export class ParamDefinitionIterator implements IParamDefinitionIterator {
+    private _mapIterator: MapIterator<ParamDefinition>;
+
+    constructor(_params: ParamsEquation) {
+        this._mapIterator = new MapIterator(_params.map);
+    }
+
+    public next(): IteratorResult<ParamDefinition> {
+        return this._mapIterator.next();
+    }
+
+    public [Symbol.iterator](): IterableIterator<ParamDefinition> {
+        return this;
+    }
+}
+
+/**
+ * itérateur sur les paramètres d'un tableau de de ParamsEquation
+ */
+export class ParamsEquationArrayIterator implements IParamDefinitionIterator {
+    private _paramsEqs: ParamsEquation[];
+
+    private _index: number = 0;
+
+    private _currentMapIterator: MapIterator<ParamDefinition>;
+
+    constructor(p: ParamsEquation[]) {
+        this._paramsEqs = p;
+    }
+
+    private nextIterator() {
+        if (this._index < this._paramsEqs.length)
+            this._currentMapIterator = new MapIterator(this._paramsEqs[this._index++].map);
+        else
+            this._currentMapIterator = undefined;
+    }
+
+    private get done(): IteratorResult<ParamDefinition> {
+        return {
+            done: true,
+            value: undefined
+        };
+    }
+
+    public next(): IteratorResult<ParamDefinition> {
+        if (this._currentMapIterator == undefined)
+            this.nextIterator();
+
+        if (this._currentMapIterator) {
+            var res = this._currentMapIterator.next();
+            if (res.done) {
+                this.nextIterator();
+                if (this._currentMapIterator)
+                    res = this._currentMapIterator.next();
+            }
+        }
+        else
+            res = this.done;
+
+        return res;
+    }
+
+    public [Symbol.iterator](): IterableIterator<ParamDefinition> {
+        return this;
+    }
+}
+
 /**
  * liste des paramètres d'une équation
  */
@@ -24,67 +97,27 @@ export abstract class ParamsEquation implements Iterable<ParamDefinition> {
     }
 
     public hasParameter(name: string): boolean {
-        for (const ps in this._paramMap) {
-            if (this._paramMap.hasOwnProperty(ps)) {
-                const p: ParamDefinition = this._paramMap[ps];
-                if (p.symbol === name) {
-                    return true;
-                }
-            }
-        }
-
+        for (const p of this)
+            if (p.symbol === name)
+                return true;
         return false;
     }
 
-    public getParameter(name: string): ParamDefinition {
-        for (const ps in this._paramMap) {
-            if (this._paramMap.hasOwnProperty(ps)) {
-                const p: ParamDefinition = this._paramMap[ps];
-                if (p.symbol === name) {
-                    return p;
-                }
-            }
-        }
-
-        throw new Error("ParamsEquation.getParameter() : invalid parameter name " + name);
-    }
-
-    public getFirstAnalyticalParameter(): ParamDefinition {
-        for (const ps in this._paramMap) {
-            if (this._paramMap.hasOwnProperty(ps)) {
-                const p: ParamDefinition = this._paramMap[ps];
-                if (p.isAnalytical()) {
-                    return p;
-                }
-            }
-        }
-        return undefined;
-    }
-
     public get map(): { [key: string]: ParamDefinition } {
         return this._paramMap;
     }
 
     public resetParametersCalculability() {
-        for (const ps in this._paramMap) {
-            if (this._paramMap.hasOwnProperty(ps)) {
-                const p: ParamDefinition = this._paramMap[ps];
-                p.calculability = undefined
-            }
-        }
+        for (const p of this)
+            p.calculability = undefined
     }
 
     public checkParametersCalculability() {
         const res = [];
 
-        for (const ps in this._paramMap) {
-            if (this._paramMap.hasOwnProperty(ps)) {
-                const p: ParamDefinition = this._paramMap[ps];
-                if (p.calculability === undefined) {
-                    res.push(p.symbol);
-                }
-            }
-        }
+        for (const p of this)
+            if (p.calculability === undefined)
+                res.push(p.symbol);
 
         if (res.length > 0) {
             throw new Error("Calculability of parameter(s) " + res.toString() + " has not been defined");
@@ -95,8 +128,8 @@ export abstract class ParamsEquation implements Iterable<ParamDefinition> {
         return this.iterator;
     }
 
-    public get iterator() {
-        return new MapIterator(this._paramMap);
+    public get iterator(): IParamDefinitionIterator {
+        return new ParamDefinitionIterator(this);
     }
 
     protected addParamDefinition(p: ParamDefinition) {
@@ -106,10 +139,7 @@ export abstract class ParamsEquation implements Iterable<ParamDefinition> {
     }
 
     protected addParamDefinitions(ps: ParamsEquation) {
-        for (const pi in ps._paramMap) {
-            if (ps._paramMap.hasOwnProperty(pi)) {
-                this.addParamDefinition(ps._paramMap[pi]);
-            }
-        }
+        for (const p of ps)
+            this.addParamDefinition(p);
     }
 }
diff --git a/src/parameters.ts b/src/parameters.ts
deleted file mode 100644
index 88275fa9623a42e5d2d063966277e464fbf23b2e..0000000000000000000000000000000000000000
--- a/src/parameters.ts
+++ /dev/null
@@ -1,170 +0,0 @@
-import { ComputeNodeType, CalculatorType } from "./compute-node"
-import { ParamsEquation } from "./param/params-equation";
-import { ParamDefinition } from "./param/param-definition";
-import { ConduiteDistribParams, ConduiteDistrib } from "./cond_distri";
-import { LechaptCalmonParams, LechaptCalmon } from "./lechaptcalmon";
-import { acSection } from "./section/section_type";
-import { ParamsSectionTrapez, cSnTrapez } from "./section/section_trapez";
-import { ParamsSectionRectang, cSnRectang } from "./section/section_rectang";
-import { ParamsSectionCirc, cSnCirc } from "./section/section_circulaire";
-import { ParamsSectionPuiss, cSnPuiss } from "./section/section_puissance";
-import { RegimeUniforme } from "./regime_uniforme";
-import { CourbeRemous, CourbeRemousParams } from "./remous";
-import { PabDimensionParams, PabDimension } from "./pab/pab_dimension";
-import { PabPuissanceParams, PabPuissance } from "./pab/pab_puissance";
-import { HashTable } from "./util/hashtable";
-import { ParallelStructureParams } from "./structure/parallel_structure_params";
-import { ParallelStructure } from "./structure/parallel_structure";
-import { RectangularStructureParams } from "./structure/rectangular_structure_params";
-import { StructureCem88d } from "./structure/structure_cem88d";
-import { StructureKiviParams } from "./structure/structure_kivi_params";
-import { StructureKivi } from "./structure/structure_kivi";
-
-
-export class ComputeNodeParameters {
-    private static _instance: ComputeNodeParameters;
-
-    private _nodes: HashTable;
-
-    private constructor() {
-        this._nodes = new HashTable();
-    }
-
-    public static getInstance() {
-        if (ComputeNodeParameters._instance == undefined)
-            ComputeNodeParameters._instance = new ComputeNodeParameters();
-        return ComputeNodeParameters._instance;
-    }
-
-    private createSection(nt: ComputeNodeType): acSection {
-        switch (nt) {
-            case ComputeNodeType.None: // pour les paramètres communs, n'importe quelle section convient
-            case ComputeNodeType.SectionTrapeze:
-                {
-                    let cn = new ParamsSectionTrapez(1, 0.5, undefined, undefined,
-                        1, undefined, 0.1, 1);
-                    let n = new cSnTrapez(cn); // pour initialiser la calculabilité des paramètres
-                    return n;
-                }
-
-            case ComputeNodeType.SectionRectangle:
-                {
-                    let cn = new ParamsSectionRectang(undefined, 1, undefined, 1,
-                        undefined, 0.1, 1);
-                    let n = new cSnRectang(cn); // pour initialiser la calculabilité des paramètres
-                    return n;
-                }
-
-            case ComputeNodeType.SectionCercle:
-                {
-                    let cn = new ParamsSectionCirc(1, undefined, undefined, 1,
-                        undefined, 0.1, 1);
-                    let n = new cSnCirc(cn); // pour initialiser la calculabilité des paramètres
-                    return n;
-                }
-
-            case ComputeNodeType.SectionPuissance:
-                {
-                    let cn = new ParamsSectionPuiss(0.5, undefined, 1, undefined,
-                        1, undefined, 0.1, 1);
-                    let n = new cSnPuiss(cn); // pour initialiser la calculabilité des paramètres
-                    return n;
-                }
-
-            default:
-                throw new Error(`type de section ${ComputeNodeType[nt]} non pris en charge`);
-        }
-    }
-
-    private createComputeNodeParameters(calcType: CalculatorType, nodeType: ComputeNodeType): ParamsEquation {
-        switch (calcType) {
-            case CalculatorType.ConduiteDistributrice:
-                {
-                    const cn = new ConduiteDistribParams(undefined, undefined, undefined, undefined, undefined);
-                    const n = new ConduiteDistrib(cn); // pour initialiser la calculabilité des paramètres
-                    return cn;
-                }
-
-            case CalculatorType.LechaptCalmon:
-                {
-                    const cn = new LechaptCalmonParams(undefined, undefined, undefined, undefined, undefined, undefined, undefined);
-                    const n = new LechaptCalmon(cn); // pour initialiser la calculabilité des paramètres
-                    return cn;
-                }
-
-            case CalculatorType.SectionParametree:
-                {
-                    const sect: acSection = this.createSection(nodeType);
-                    return sect.prms;
-                }
-
-            case CalculatorType.RegimeUniforme:
-                const sect: acSection = this.createSection(nodeType);
-                const ru = new RegimeUniforme(sect); // pour initialiser la calculabilité des paramètres
-                return sect.prms;
-
-
-            case CalculatorType.CourbeRemous:
-                {
-                    const sect: acSection = this.createSection(nodeType);
-                    const crp = new CourbeRemousParams(sect, undefined, undefined, undefined, undefined, undefined);
-                    const ru = new CourbeRemous(crp); // pour initialiser la calculabilité des paramètres
-                    return crp;
-                }
-
-            case CalculatorType.PabDimensions:
-                {
-                    let cn = new PabDimensionParams(undefined, undefined, undefined);
-                    let n = new PabDimension(cn); // pour initialiser la calculabilité des paramètres
-                    return cn;
-                }
-
-            case CalculatorType.PabPuissance:
-                {
-                    let cn = new PabPuissanceParams(undefined, undefined, undefined);
-                    let n = new PabPuissance(cn); // pour initialiser la calculabilité des paramètres
-                    return cn;
-                }
-
-            case CalculatorType.Structure:
-                switch (nodeType) {
-                    case ComputeNodeType.None: // pour les paramètres communs, n'importe quelle structure convient
-                    case ComputeNodeType.StructureRectangle:
-                        let cn = new RectangularStructureParams(undefined, 0.5, 2, 1, undefined, undefined);
-                        let n = new StructureCem88d(cn);
-                        return cn;
-
-                    case ComputeNodeType.StructureKIVI:
-                        {
-                            let cn = new StructureKiviParams(undefined, 1, 2, 1, undefined, undefined, undefined, undefined);
-                            let n = new StructureKivi(cn);
-                            return cn;
-                        }
-
-                    default:
-                        throw new Error(`ComputeNodeParameters.createComputeNodeParameters() : calculatrice '${CalculatorType[calcType]}' / noeud de calcul '${ComputeNodeType[nodeType]}' non pris en charge`);
-                }
-
-            case CalculatorType.ParallelStructure:
-                {
-                    let cn = new ParallelStructureParams(undefined, undefined, undefined);
-                    let n = new ParallelStructure(cn);
-                    return cn;
-                }
-
-            default:
-                throw new Error(`ComputeNodeParameters.createComputeNodeParameters() : calculatrice '${CalculatorType[calcType]}' / noeud de calcul '${ComputeNodeType[nodeType]}' non pris en charge`);
-        }
-    }
-
-    public getComputeNodeParameter(calcType: CalculatorType, nodeType: ComputeNodeType, symbol: string): ParamDefinition {
-        const key = { calcType, nodeType };
-        let params = this._nodes.get(key);
-        if (params == undefined) {
-            params = this.createComputeNodeParameters(calcType, nodeType);
-            this._nodes.put(key, params);
-        }
-
-        return params.map[symbol];
-    }
-}
diff --git a/src/remous.ts b/src/remous.ts
index 3e02d648b599003609f2de6d1cdec9dd54496e1d..6c3ff39a118f985843978792639fb19ce0bcff79 100644
--- a/src/remous.ts
+++ b/src/remous.ts
@@ -432,42 +432,8 @@ export class CourbeRemous extends Nub {
         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]);
-                }
-            }
+        // compilation des résultats
 
-            this.debug("extra param " + val_a_cal);
-            this.logObject(tRes);
-        }
-
-        const hasRes = Object.keys(tRes).length > 0;
         for (const x of trX) {
 
             let ligneDeau;
@@ -488,13 +454,25 @@ export class CourbeRemous extends Nub {
                 re.addExtraResult("tor", crbTor[x]);
             }
 
-            if (hasRes && tRes[x]) {
-                re.addExtraResult("tRes", tRes[x]);
-            }
-
             res.addResultElement(re);
         }
 
+        // Calcul de la variable à calculer
+
+        const tRes: { [key: number]: number } = {};
+        if (val_a_cal) {
+            for (const re of res.resultElements) {
+                const rY = re.vCalc;
+                if (rY !== undefined) {
+                    const rVar = this.Sn.Calc(val_a_cal, rY);
+                    if (!rVar.ok)
+                        res.addLog(rVar.log);
+                    else
+                        re.addExtraResult("tRes", rVar.vCalc);
+                }
+            }
+        }
+
         return res;
     }
 
diff --git a/src/section/section_nub.ts b/src/section/section_nub.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ad9068b19cc2d69e6dfe96a1886ebbfcd788aaf6
--- /dev/null
+++ b/src/section/section_nub.ts
@@ -0,0 +1,228 @@
+import { Nub } from "../nub";
+import { acSection } from "./section_type";
+import { Result } from "../util/result";
+import { ParamDefinition, ParamCalculability } from "../param/param-definition";
+import { ParamDomain, ParamDomainValue } from "../param/param-domain";
+import { ParamValueMode } from "../param/param-values";
+import { ResultElement } from "../util/resultelement";
+
+/**
+ * Nub sur les sections paramétrées
+ */
+export class SectionParametree extends Nub {
+    private _section: acSection;
+
+    private _sectionVars: { [key: string]: ParamDefinition };
+
+    constructor(sect: acSection, dbg: boolean = false) {
+        super(sect.prms, dbg);
+        this._section = sect;
+        this.initSectionVars();
+    }
+
+    private initSectionVars() {
+        this._sectionVars = {};
+        this.initSectionVar("Hs");
+        this.initSectionVar("Hsc");
+        this.initSectionVar("B");
+        this.initSectionVar("P");
+        this.initSectionVar("S");
+        this.initSectionVar("R");
+        this.initSectionVar("V");
+        this.initSectionVar("Fr");
+        this.initSectionVar("Yc");
+        this.initSectionVar("Yn");
+        this.initSectionVar("Yf");
+        this.initSectionVar("Yt");
+        this.initSectionVar("Yco");
+        this.initSectionVar("J");
+        this.initSectionVar("Imp");
+        this.initSectionVar("Tau0");
+        this.initSectionVar("I-J");
+    }
+
+    private initSectionVar(symbol: string) {
+        this._sectionVars[symbol] = this.createSectionVar(symbol);
+    }
+
+    private createSectionVar(symbol: string): ParamDefinition {
+        switch (symbol) {
+            case "Hs":
+            case "Hsc":
+            case "B":
+            case "P":
+            case "S":
+            case "R":
+            case "V":
+            case "Fr":
+            case "Yc":
+            case "Yn":
+            case "Yf":
+            case "Yt":
+            case "Yco":
+            case "J":
+            case "Imp":
+            case "Tau0":
+                var dom = new ParamDomain(ParamDomainValue.POS_NULL);
+                break;
+
+            case "I-J":
+                var dom = new ParamDomain(ParamDomainValue.ANY);
+                break;
+
+            default:
+                throw new Error(`SectionParametree.createSectionVar() : symbole ${symbol} non pris en charge`);
+        }
+
+        const res = new ParamDefinition(symbol, dom);
+        res.calculability = ParamCalculability.EQUATION;
+        return res;
+    }
+
+    public get section(): acSection {
+        return this._section;
+    }
+
+    public getParameter(name: string): ParamDefinition {
+        let res = super.getParameter(name);
+        if (res === undefined) {
+            // pas trouvé -> il s'agit peut être d'une variable dans le genre Hs, B, P, ...
+            res = this._sectionVars[name];
+
+            if (!res)
+                throw new Error(`SectionParametree.getParameter() : nom de paramètre ${name} incorrect`);
+        }
+        return res;
+    }
+
+    public getFirstAnalyticalParameter(): ParamDefinition {
+        let res = super.getFirstAnalyticalParameter();
+        if (res)
+            return res;
+
+        for (const k in this._sectionVars) {
+            const p = this._sectionVars[k];
+            if (p.isAnalytical)
+                return p;
+        }
+        return undefined;
+    }
+
+    /**
+     * paramétrage de la calculabilité des paramètres
+     */
+    protected setParametersCalculability() {
+    }
+
+    Equation(sVarCalc: string): Result {
+        switch (sVarCalc) {
+            case "Hs":
+            case "Hsc":
+            case "B":
+            case "P":
+            case "S":
+            case "R":
+            case "V":
+            case "Fr":
+            case "Yc":
+            case "Yn":
+            case "Yf":
+            case "Yt":
+            case "Yco":
+            case "J":
+            case "Imp":
+            case "Tau0":
+            case "I-J":
+                return this._section.Calc(sVarCalc, this.getParameter("Y").v);
+
+            default:
+                throw new Error(`SectionParam.Equation() : calcul sur ${sVarCalc} non implémenté`);
+        }
+    }
+
+    private hasVariatedParameter(): boolean {
+        for (const p of this.parameterIterator) {
+            switch (p.valueMode) {
+                case ParamValueMode.LISTE:
+                case ParamValueMode.MINMAX:
+                    return true;
+            }
+        }
+        return false;
+    }
+
+    private addExtraResultFromVar(varCalc: string, Y: number, re: ResultElement) {
+        const r: Result = this._section.Calc(varCalc, Y);
+        if (r.ok)
+            re.addExtraResult(varCalc, r.vCalc);
+        else
+            re.log.addLog(r.log);
+    }
+
+    public CalcSerie(rPrec: number = 0.001, rInit?: number, sDonnee?: string): Result {
+        //paramètre à varier ?
+
+        if (this.hasVariatedParameter())
+            return super.CalcSerie(rPrec, rInit, sDonnee);
+
+        // sinon, on stocke toutes les valeurs des variables à calcul dans les résultats supplémentaires
+
+        const Y = this.getParameter("Y").v; // tirant d'eau original (doit être fourni à acSection.Calc() sous peine d'être modifié par les appels successifs car c'est en même temps un paramètre et une variable temporaire)
+        const result = new Result();
+        const re = new ResultElement();
+        result.addResultElement(re);
+
+        // charge spécifique
+        this.addExtraResultFromVar("Hs", Y, re);
+
+        // charge critique
+        this.addExtraResultFromVar("Hsc", Y, re);
+
+        // largeur au miroir
+        this.addExtraResultFromVar("B", Y, re);
+
+        // périmètre hydraulique
+        this.addExtraResultFromVar("P", Y, re);
+
+        // surface hydraulique
+        this.addExtraResultFromVar("S", Y, re);
+
+        // rayon hydraulique
+        this.addExtraResultFromVar("R", Y, re);
+
+        // vitesse moyenne
+        this.addExtraResultFromVar("V", Y, re);
+
+        // nombre de Froude
+        this.addExtraResultFromVar("Fr", Y, re);
+
+        // tirant d'eau critique
+        this.addExtraResultFromVar("Yc", Y, re);
+
+        // tirant d'eau normal
+        this.addExtraResultFromVar("Yn", Y, re);
+
+        // tirant d'eau fluvial
+        this.addExtraResultFromVar("Yf", Y, re);
+
+        // tirant d'eau torrentiel
+        this.addExtraResultFromVar("Yt", Y, re);
+
+        // tirant d'eau conjugué
+        this.addExtraResultFromVar("Yco", Y, re);
+
+        // perte de charge
+        this.addExtraResultFromVar("J", Y, re);
+
+        // Variation linéaire de l'énergie spécifique
+        this.addExtraResultFromVar("I-J", Y, re);
+
+        // impulsion hydraulique
+        this.addExtraResultFromVar("Imp", Y, re);
+
+        // contrainte de cisaillement
+        this.addExtraResultFromVar("Tau0", Y, re);
+
+        return result;
+    }
+}
diff --git a/src/session_nub.ts b/src/session_nub.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d1f320d1505063d53fcfcd41ea0d9d2c8aaa32cf
--- /dev/null
+++ b/src/session_nub.ts
@@ -0,0 +1,128 @@
+import { Nub } from "./nub";
+import { IObservable, Observer, Observable } from "./util/observer";
+
+/**
+ * gestion d'un ensemble de propriétés (clé/valeur)
+ */
+export class Props implements IObservable {
+    // implémentation de IObservable par délégation
+    private _observable: Observable;
+
+    constructor(private _props: any = {}) {
+        this._observable = new Observable();
+    }
+
+    public hasProperties(props: Props | {}): boolean {
+        const keys = Object.keys(this._props);
+        const p = props instanceof Props ? props.props : props;
+
+        // if (keys.length != Object.keys(p).length)
+        //     return false;
+
+        for (const k of keys)
+            if (this._props[k] !== p[k])
+                return false;
+
+        return true;
+    }
+
+    public getPropValue(key: string): any {
+        return this._props[key];
+    }
+
+    private notifyPropChange(prop: string, val: any, sender: any) {
+        this.notifyObservers({
+            "action": "propertyChange",
+            "name": prop,
+            "value": val
+        }, sender)
+    }
+
+    public setPropValue(key: string, val: any, sender?: any): boolean {
+        const oldValue = this._props[key];
+        const changed = oldValue !== val;
+        if (changed) {
+            this._props[key] = val;
+            this.notifyPropChange(key, val, sender);
+        }
+        return changed;
+    }
+
+    public get props() {
+        return this._props;
+    }
+
+    public clone(): Props {
+        const res = new Props();
+        for (const k in this._props)
+            res._props[k] = this._props[k];
+        return res;
+    }
+
+    public toString(): string {
+        let res = "[";
+        for (const k in this._props) {
+            if (res != "[")
+                res += ", ";
+            res += `${k}:${this.props[k]}`;
+        }
+        res += "]"
+        return res;
+    }
+
+    // interface IObservable
+
+    /**
+     * ajoute un observateur à la liste
+     */
+    public addObserver(o: Observer) {
+        this._observable.addObserver(o);
+    }
+
+    /**
+     * supprime un observateur de la liste
+     */
+    public removeObserver(o: Observer) {
+        this._observable.removeObserver(o);
+    }
+
+    /**
+     * notifie un événement aux observateurs
+     */
+    public notifyObservers(data: any, sender?: any) {
+        this._observable.notifyObservers(data, sender);
+    }
+}
+
+/**
+ * Nub utilisé dans une session
+ */
+export class SessionNub {
+    private _props: Props;
+
+    constructor(private _nub: Nub, props: Props | {}) {
+        if (this._nub == undefined)
+            throw new Error(`NgNub.constructor() : argument invalide`);
+
+        if (props instanceof Props)
+            this._props = props.clone();
+        else
+            this._props = new Props(props);
+    }
+
+    public get nub() {
+        return this._nub;
+    }
+
+    public get uid(): number {
+        return this._nub.uid;
+    }
+
+    public get properties() {
+        return this._props;
+    }
+
+    public hasProperties(p: Props | {}): boolean {
+        return this._props.hasProperties(p);
+    }
+}
diff --git a/src/structure/factory_structure.ts b/src/structure/factory_structure.ts
index 787daa1f7999c71e0143ca7aea8ba98c11a47d0e..abda575bea35b86a4986a4b28f3ef1e73465997f 100644
--- a/src/structure/factory_structure.ts
+++ b/src/structure/factory_structure.ts
@@ -8,40 +8,7 @@ import { StructureKiviParams } from "./structure_kivi_params";
 import { StructureOrificeFree } from "./structure_orifice_free";
 import { StructureOrificeSubmerged } from "./structure_orifice_submerged";
 import { StructureWeirFree } from "./structure_weir_free";
-
-export enum StructureType {
-    SeuilRectangulaire, VanneRectangulaire
-    // VanneCirculaire,
-    // VanneTrapezoidale, SeuilTrapezoidal
-}
-
-export enum LoiDebit {
-    // loi de débit Déversoir / Orifice Cemagref 1988
-    Cem88d,
-    // loi de débit Déversoir / Vanne de fond Cemagref 1988
-    Cem88v,
-    // loi de débit Cunge 1980
-    Cunge80,
-    // loi de débit pour vanne dénoyée
-    OrificeFree,
-    // loi de débit pour vanne noyée
-    OrificeSubmerged,
-    // loi de débit pour seuil dénoyé
-    WeirFree,
-    // Loi Kindsvater-Carter et Villemonte
-    KIVI
-}
-
-export const loiAdmissibles: { [key: string]: LoiDebit[] } = {
-    SeuilRectangulaire: [
-        LoiDebit.Cem88d, LoiDebit.Cem88v, LoiDebit.Cunge80, LoiDebit.WeirFree,
-        LoiDebit.KIVI
-    ],
-    VanneRectangulaire: [
-        LoiDebit.Cem88d, LoiDebit.Cem88v, LoiDebit.Cunge80, LoiDebit.OrificeFree,
-        LoiDebit.OrificeSubmerged
-    ]
-};
+import { StructureType, LoiDebit, StructureProperties } from "./structure_props";
 
 export function CreateStructure(structureType: StructureType, loiDebit: LoiDebit, dbg: boolean = false): Structure {
     const rectStructPrms: RectangularStructureParams = new RectangularStructureParams(
@@ -57,14 +24,14 @@ export function CreateStructure(structureType: StructureType, loiDebit: LoiDebit
         case StructureType.VanneRectangulaire:
             rectStructPrms.W.v = 0.5;
             rectStructPrms.Cd.v = 0.6; // Cd pour une vanne rectangulaire
-            if (!(loiAdmissibles.VanneRectangulaire.includes(loiDebit))) {
+            if (!(StructureProperties.isCompatibleValues(StructureType.VanneRectangulaire, loiDebit))) {
                 throw new Error(
                     `la loi de débit ${LoiDebit[loiDebit]} n'est pas admissible pour les vannes rectangulaires`
                 );
             }
             break;
         case StructureType.SeuilRectangulaire:
-            if (!loiAdmissibles.SeuilRectangulaire.includes(loiDebit)) {
+            if (!(StructureProperties.isCompatibleValues(StructureType.SeuilRectangulaire, loiDebit))) {
                 throw new Error(
                     `la loi de débit ${LoiDebit[loiDebit]} n'est pas admissible pour les seuils rectangulaires`
                 );
@@ -96,6 +63,7 @@ export function CreateStructure(structureType: StructureType, loiDebit: LoiDebit
 
         case LoiDebit.WeirFree:
             return new StructureWeirFree(rectStructPrms, dbg);
+
         case LoiDebit.KIVI:
             const structKiviPrm: StructureKiviParams = new StructureKiviParams(
                 8.516, // Q
@@ -107,8 +75,8 @@ export function CreateStructure(structureType: StructureType, loiDebit: LoiDebit
                 0.001,  // béta
                 100);    // ZRAM : cote Radier Amont
             return new StructureKivi(structKiviPrm, dbg);
+
         default:
             throw new Error(`type de LoiDebit ${LoiDebit[loiDebit]} non pris en charge`);
-
     }
 }
diff --git a/src/structure/parallel_structure.ts b/src/structure/parallel_structure.ts
index 8d2b46ab60bd5cadee49503d3ea5067e9fdaf7a7..d4dc9749f93e70512ae7b5b4500f012226feaa5b 100644
--- a/src/structure/parallel_structure.ts
+++ b/src/structure/parallel_structure.ts
@@ -4,6 +4,7 @@ import { Message } from "../util/message";
 import { Result } from "../util/result";
 import { ParallelStructureParams } from "./parallel_structure_params";
 import { Structure } from "./structure";
+import { IParamDefinitionIterator, ParamsEquation, ParamsEquationArrayIterator } from "../param/params-equation";
 
 /**
  * Interface pour mémoriser le n° d'ouvrage et le paramètre à calculer
@@ -46,6 +47,14 @@ export class ParallelStructure extends Nub {
         this.updateStructuresH1H2();
     }
 
+    public get parameterIterator(): IParamDefinitionIterator {
+        const prms: ParamsEquation[] = [];
+        prms.push(this._prms);
+        for (const st of this.structures)
+            prms.push(st.parameters);
+        return new ParamsEquationArrayIterator(prms);
+    }
+
     /**
      * Ajout d'une structure en parallèle
      * @param structure La structure à rajouter
@@ -54,6 +63,19 @@ export class ParallelStructure extends Nub {
         this.structures.push(structure);
     }
 
+    /**
+     * remplace une structure hydraulique
+     * @param index indice de la structure dans le tableau
+     * @param structure nouvelle structure
+     */
+    public replaceStructure(index: number, structure: Structure) {
+        if (index > -1 && index < this.structures.length) {
+            this.structures[index] = structure;
+        } else {
+            throw new Error(`ParallelStructure.replaceStructure invalid index ${index}`);
+        }
+    }
+
     /**
      * Supprime une structure hydraulique
      * @param index numéro de la structure dans le tableau
@@ -117,7 +139,7 @@ export class ParallelStructure extends Nub {
             case "Q":
                 res = super.Calc(sVarCalc, rInit, rPrec);
                 if (res.ok) {
-                    this.prms.map[sVarCalc].v = res.vCalc;
+                    this.getParameter(sVarCalc).setValue(res.vCalc);
                 }
                 break;
             default:
@@ -125,7 +147,7 @@ export class ParallelStructure extends Nub {
                 const sVC = this.getStructureVarCalc(sVarCalc);
                 res = this.CalcStructPrm(sVC, rInit, rPrec);
                 if (res.ok) {
-                    this.structures[sVC.index].prms.map[sVC.prm].v = res.vCalc;
+                    this.structures[sVC.index].getParameter(sVC.prm).setValue(res.vCalc);
                 }
         }
         if (res.ok) {
@@ -137,6 +159,7 @@ export class ParallelStructure extends Nub {
                 }
             }
         }
+        this._result = res;
         return res;
     }
 
@@ -167,8 +190,12 @@ export class ParallelStructure extends Nub {
     private getStructureVarCalc(sVarCalc: string): IStructureVarCalc {
         let sIndex: string;
         let sPrm: string;
+        if (sVarCalc.indexOf(".") == -1)
+            throw new Error(`getStructureVarCalc() : erreur d'analyse de ${sVarCalc}, (pas de la forme n.X)`);
         [sIndex, sPrm] = sVarCalc.split(".");
         const i = parseInt(sIndex, 10);
+        if (i === NaN)
+            throw new Error(`getStructureVarCalc() : erreur d'analyse de ${sVarCalc} (${sIndex} n'est pas un nombre)`);
         return { index: i, prm: sPrm };
     }
 
@@ -180,7 +207,7 @@ export class ParallelStructure extends Nub {
      */
     private CalcStructPrm(sVC: IStructureVarCalc, rInit?: number, rPrec: number = 0.001): Result {
         // Le débit restant sur la structure en calcul est :
-        this.structures[sVC.index].prms.Q.v = this.prms.Q.v - this.CalcQ(sVC.index).vCalc;
+        this.structures[sVC.index].prms.Q.setValue(this.prms.Q.v - this.CalcQ(sVC.index).vCalc);
 
         // Calcul du paramètre de la structure en calcul
         return this.structures[sVC.index].Calc(sVC.prm, rInit, rPrec);
diff --git a/src/structure/structure.ts b/src/structure/structure.ts
index 5e73d099bbd97798118b5e4f05855a926f4732d4..69ce4fea56d0962496d4b83b722be91fc48254d4 100644
--- a/src/structure/structure.ts
+++ b/src/structure/structure.ts
@@ -77,7 +77,7 @@ export abstract class Structure extends Nub {
     public Calc(sVarCalc: string, rInit?: number, rPrec: number = 0.001): Result {
         // Gestion de l'exception de calcul de W sur les seuils
         if (rInit === undefined) {
-            rInit = this._prms.map[sVarCalc].v;
+            rInit = this.getParameter(sVarCalc).v;
         }
         if (sVarCalc === "W" && rInit === Infinity) {
             throw new Error("Structure:Calc : Calcul de W impossible sur un seuil");
diff --git a/src/structure/structure_props.ts b/src/structure/structure_props.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7c7d63a4b41962f54cd52afee7203785cadc1ac5
--- /dev/null
+++ b/src/structure/structure_props.ts
@@ -0,0 +1,63 @@
+export enum StructureType {
+    SeuilRectangulaire, VanneRectangulaire
+    // VanneCirculaire,
+    // VanneTrapezoidale, SeuilTrapezoidal
+}
+
+export enum LoiDebit {
+    // loi de débit Déversoir / Orifice Cemagref 1988
+    Cem88d,
+    // loi de débit Déversoir / Vanne de fond Cemagref 1988
+    Cem88v,
+    // loi de débit Cunge 1980
+    Cunge80,
+    // loi de débit pour vanne dénoyée
+    OrificeFree,
+    // loi de débit pour vanne noyée
+    OrificeSubmerged,
+    // loi de débit pour seuil dénoyé
+    WeirFree,
+    // Loi Kindsvater-Carter et Villemonte
+    KIVI
+}
+
+export const loiAdmissibles: { [key: string]: LoiDebit[] } = {
+    SeuilRectangulaire: [
+        LoiDebit.Cem88d, LoiDebit.Cem88v, LoiDebit.Cunge80, LoiDebit.WeirFree,
+        LoiDebit.KIVI
+    ],
+    VanneRectangulaire: [
+        LoiDebit.Cem88d, LoiDebit.Cem88v, LoiDebit.Cunge80, LoiDebit.OrificeFree,
+        LoiDebit.OrificeSubmerged
+    ]
+};
+
+export class StructureProperties {
+    /**
+     * @return true si les valeurs de StructureType et LoiDebit sont compatibles
+     */
+    public static isCompatibleValues(struct: StructureType, loi: LoiDebit): boolean {
+        return loiAdmissibles[StructureType[struct]].includes(loi);
+    }
+
+    /**
+     * @return la 1ère valeur de StructureType compatible avec la loi de débit
+     */
+    public static findCompatibleStructure(loi: LoiDebit): StructureType {
+        for (const st in loiAdmissibles) {
+            const lds: LoiDebit[] = loiAdmissibles[st];
+            for (const ld of lds)
+                if (ld === loi)
+                    return (<any>StructureType)[st];
+        }
+        return undefined;
+    }
+
+    /**
+     * @return la 1ère valeur de LoiDebit compatible avec le type de structure
+     */
+    public static findCompatibleLoiDebit(struct: StructureType): LoiDebit {
+        const sst: string = StructureType[struct];
+        return loiAdmissibles[sst][0];
+    }
+}
\ No newline at end of file
diff --git a/src/util/message.ts b/src/util/message.ts
index 0a08e11ef7623473d6a073b1a78898b1eddb368c..f30b1fa04e624689e28e67de5de562b39eebf792 100644
--- a/src/util/message.ts
+++ b/src/util/message.ts
@@ -2,233 +2,231 @@ export enum MessageCode {
     /**
      * pas de pb !
      */
-    ERROR_OK = 0,
+    ERROR_OK,
 
     /**
      * la dichotomie n'a pas pu trouver automatiquement d'intervalle de départ
      * car la valeur initiale de la variable est trop haute
      */
-    ERROR_DICHO_INITVALUE_HIGH = -1,
+    ERROR_DICHO_INITVALUE_HIGH,
 
     /**
      * la dichotomie n'a pas pu trouver automatiquement d'intervalle de départ
      * car la valeur initiale de la variable est trop basse
      */
-    ERROR_DICHO_INITVALUE_LOW = -2,
+    ERROR_DICHO_INITVALUE_LOW,
 
     /**
      * la dichotomie n'a pas pu trouver automatiquement d'intervalle de départ
      * car la valeur cible de la fonction n'existe pas pour des valeurs de la
      * variable dans son domaine de définition, cad il n'existe pas de solution
      */
-    ERROR_DICHO_INIT_DOMAIN = -3,
+    ERROR_DICHO_INIT_DOMAIN,
 
     /**
      * la dichotomie n'a pas pu converger
      */
-    ERROR_DICHO_CONVERGE = -4,
+    ERROR_DICHO_CONVERGE,
 
     /**
      * le pas pour la recherche de l'intervalle de départ est =0
      */
-    ERROR_DICHO_NULL_STEP = -5,
+    ERROR_DICHO_NULL_STEP,
 
     /**
      * l'augmentation du pas pour la recherche de l'intervalle de départ est incorrecte (=0)
      */
-    ERROR_DICHO_INVALID_STEP_GROWTH = -6,
+    ERROR_DICHO_INVALID_STEP_GROWTH,
 
     /**
      * impossible de déterminer le sens de variation de la fonction
      */
-    ERROR_DICHO_FUNCTION_VARIATION = -7,
+    ERROR_DICHO_FUNCTION_VARIATION,
 
     /**
      * les bornes de l'intervalle d'un ParamDomain sont incorrectes
      */
-    ERROR_PARAMDOMAIN_INTERVAL_BOUNDS = -100,
+    ERROR_PARAMDOMAIN_INTERVAL_BOUNDS,
 
     /**
      * la valeur du ParamDomain est incorrecte
      */
-    ERROR_PARAMDOMAIN_INVALID = -101,
+    ERROR_PARAMDOMAIN_INVALID,
 
     /**
      * la calculabilité d'un ParamDefinition est non définie
      */
-    ERROR_PARAMDEF_CALC_UNDEFINED = -200,
+    ERROR_PARAMDEF_CALC_UNDEFINED,
 
     /**
      * la valeur d'un ParamDefinition est non définie
      */
-    ERROR_PARAMDEF_VALUE_UNDEFINED = -201,
+    ERROR_PARAMDEF_VALUE_UNDEFINED,
 
     /**
      * la valeur d'un ParamDefinition ne peut pas être changée
      */
-    ERROR_PARAMDEF_VALUE_FIXED = -202,
+    ERROR_PARAMDEF_VALUE_FIXED,
 
     /**
      * la valeur d'un ParamDefinition ne peut pas être > 0
      */
-    ERROR_PARAMDEF_VALUE_POS = -203,
+    ERROR_PARAMDEF_VALUE_POS,
 
     /**
      * la valeur d'un ParamDefinition ne peut pas être >= 0
      */
-    ERROR_PARAMDEF_VALUE_POSNULL = -204,
+    ERROR_PARAMDEF_VALUE_POSNULL,
 
     /**
      * la valeur d'un ParamDefinition ne peut pas être = 0
      */
-    ERROR_PARAMDEF_VALUE_NULL = -205,
+    ERROR_PARAMDEF_VALUE_NULL,
 
     /**
      * la valeur d'un ParamDefinition est en dehors de son intervalle autorisé
      */
-    ERROR_PARAMDEF_VALUE_INTERVAL = -206,
+    ERROR_PARAMDEF_VALUE_INTERVAL,
 
     /**
      * la valeur passée à une méthode de la classe Interval est undefined
      */
-    ERROR_INTERVAL_UNDEF = -300,
+    ERROR_INTERVAL_UNDEF,
 
     /**
      * la valeur passée à une méthode de la classe Interval est hors de l'intervalle défini
      */
-    ERROR_INTERVAL_OUTSIDE = -301,
+    ERROR_INTERVAL_OUTSIDE,
 
     /**
      * internationalisation : langue non prise en charge
      */
-    ERROR_LANG_UNSUPPORTED = -400,
+    ERROR_LANG_UNSUPPORTED,
 
     /**
      * courbes de remous : Arrêt du calcul : hauteur critique atteinte à l'abscisse x
      */
-    WARNING_REMOUS_ARRET_CRITIQUE = -500,
+    WARNING_REMOUS_ARRET_CRITIQUE,
 
     /**
      * courbe de remous : Condition limite aval >= Hauteur critique : calcul de la partie fluviale à partir de l'aval
      */
-    INFO_REMOUS_CALCUL_FLUVIAL = -501,
+    INFO_REMOUS_CALCUL_FLUVIAL,
 
     /**
      * courbe de remous : Condition limite amont <= Hauteur critique : calcul de la partie torrentielle à partir de l'amont
      */
-    INFO_REMOUS_CALCUL_TORRENTIEL = -502,
+    INFO_REMOUS_CALCUL_TORRENTIEL,
 
     /**
      * courbe de remous : ressaut hydraulique détecté à l'amont/aval de l'abscisse x
      */
-    INFO_REMOUS_RESSAUT_DEHORS = -503,
+    INFO_REMOUS_RESSAUT_DEHORS,
 
     /**
      * courbe de remous : Largeur au niveau des berges
      */
-    INFO_REMOUS_LARGEUR_BERGE = -504,
+    INFO_REMOUS_LARGEUR_BERGE,
 
     /**
      * courbe de remous : Tirant d'eau critique
      */
-    INFO_REMOUS_H_CRITIQUE = -505,
+    INFO_REMOUS_H_CRITIQUE,
 
     /**
      * courbe de remous : Tirant d'eau normal
      */
-    INFO_REMOUS_H_NORMALE = -506,
+    INFO_REMOUS_H_NORMALE,
 
     /**
      * courbe de remous : Ressaut hydraulique détecté entre les abscisses Xmin et Xmax m
      */
-    INFO_REMOUS_RESSAUT_HYDRO = -507,
+    INFO_REMOUS_RESSAUT_HYDRO,
 
     /**
      * courbe de remous : La pente de la ligne d'eau est trop forte à l'abscisse x m
      */
-    ERROR_REMOUS_PENTE_FORTE = -508,
+    ERROR_REMOUS_PENTE_FORTE,
 
     /**
      * courbe de remous : Condition limite aval < Hauteur critique : pas de calcul possible depuis l'aval
      */
-    ERROR_REMOUS_PAS_CALCUL_DEPUIS_AVAL = -509,
+    ERROR_REMOUS_PAS_CALCUL_DEPUIS_AVAL,
 
     /**
      * courbe de remous : Condition limite amont > Hauteur critique : pas de calcul possible depuis l'amont
      */
-    ERROR_REMOUS_PAS_CALCUL_DEPUIS_AMONT = -510,
+    ERROR_REMOUS_PAS_CALCUL_DEPUIS_AMONT,
 
     /**
      * section : Non convergence du calcul de la hauteur critique (Méthode de Newton)
      */
-    ERROR_SECTION_NON_CONVERGENCE_NEWTON_HCRITIQUE = -600,
+    ERROR_SECTION_NON_CONVERGENCE_NEWTON_HCRITIQUE,
 
     /**
      * section : Non convergence du calcul de la hauteur normale (Méthode de Newton)
      */
-    ERROR_SECTION_NON_CONVERGENCE_NEWTON_HNORMALE = -601,
+    ERROR_SECTION_NON_CONVERGENCE_NEWTON_HNORMALE,
 
     /**
      * section : Non convergence du calcul de la hauteur conjuguée (Méthode de Newton)
      */
-    ERROR_SECTION_NON_CONVERGENCE_NEWTON_HCONJUG = -602,
+    ERROR_SECTION_NON_CONVERGENCE_NEWTON_HCONJUG,
 
     /**
      * section : Non convergence du calcul de la hauteur correspondante (Méthode de Newton) pour le calcul de la hauteur fluviale
      */
-    ERROR_SECTION_NON_CONVERGENCE_NEWTON_HFLU = -603,
+    ERROR_SECTION_NON_CONVERGENCE_NEWTON_HFLU,
 
     /**
      * section : Non convergence du calcul de la hauteur correspondante (Méthode de Newton) pour le calcul de la hauteur torrentielle
      */
-    ERROR_SECTION_NON_CONVERGENCE_NEWTON_HTOR = -604,
+    ERROR_SECTION_NON_CONVERGENCE_NEWTON_HTOR,
 
     /**
      * section : La pente est négative ou nulle, la hauteur normale est infinie
      */
-    ERROR_SECTION_PENTE_NEG_NULLE_HNORMALE_INF = -605,
+    ERROR_SECTION_PENTE_NEG_NULLE_HNORMALE_INF,
 
     /**
      * section : calcul impossible à cause d'un périmètre nul
      */
-    ERROR_SECTION_PERIMETRE_NUL = -606,
+    ERROR_SECTION_PERIMETRE_NUL,
 
     /**
      * section : calcul impossible à cause d'un rayon nul
      */
-    ERROR_SECTION_RAYON_NUL = -607,
+    ERROR_SECTION_RAYON_NUL,
 
     /**
      * section : calcul impossible à cause d'une surface nulle
      */
-    ERROR_SECTION_SURFACE_NULLE = -608,
+    ERROR_SECTION_SURFACE_NULLE,
 
     /**
      * newton : pas de convergence
      */
-    ERROR_NEWTON_NON_CONVERGENCE = -700,
+    ERROR_NEWTON_NON_CONVERGENCE,
 
     /**
      * newton : dérivée nulle
      */
-    ERROR_NEWTON_DERIVEE_NULLE = -701,
+    ERROR_NEWTON_DERIVEE_NULLE,
 
     /**
      * Le paramètre "Cote de radier" ne peut pas être calculé avec cette loi de débit
      */
-    ERROR_STRUCTURE_ZDV_PAS_CALCULABLE = -800,
-
+    ERROR_STRUCTURE_ZDV_PAS_CALCULABLE,
 
     /**
      * StructureKivi : La pelle du seuil doit mesurer au moins 0,1 m. Le coefficient béta est forcé à 0.
      */
-    WARNING_STRUCTUREKIVI_PELLE_TROP_FAIBLE = 100,
+    WARNING_STRUCTUREKIVI_PELLE_TROP_FAIBLE,
 
     /**
      * StructureKivi : h/p ne doit pas être supérieur à 2,5. h/p est forcé à 2,5.
      */
-    WARNING_STRUCTUREKIVI_HP_TROP_ELEVE = 101,
-
+    WARNING_STRUCTUREKIVI_HP_TROP_ELEVE,
 }
 
 /**
diff --git a/src/util/observer.ts b/src/util/observer.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6a17beb902450e1e8f7da5c3d21023a49fd03fca
--- /dev/null
+++ b/src/util/observer.ts
@@ -0,0 +1,53 @@
+export interface Observer {
+    update(sender: any, data: any): void;
+}
+
+export interface IObservable {
+    /**
+     * ajoute un observateur à la liste
+     */
+    addObserver(o: Observer): void;
+
+    /**
+     * supprime un observateur de la liste
+     */
+    removeObserver(o: Observer): void;
+
+    /**
+     * notifie un événement aux observateurs
+     */
+    notifyObservers(data: any, sender?: any): void;
+}
+
+export class Observable implements IObservable {
+    private _observers: Observer[];
+
+    constructor() {
+        this._observers = [];
+    }
+
+    /**
+     * ajoute un observateur à la liste
+     */
+    public addObserver(o: Observer) {
+        if (this._observers.indexOf(o) == -1)
+            this._observers.push(o);
+    }
+
+    /**
+     * supprime un observateur de la liste
+     */
+    public removeObserver(o: Observer) {
+        this._observers = this._observers.filter(a => a !== o);
+    }
+
+    /**
+     * notifie un événement aux observateurs
+     */
+    public notifyObservers(data: any, sender?: any) {
+        if (sender == undefined)
+            sender = this;
+        for (let o of this._observers)
+            o.update(sender, data);
+    }
+}
diff --git a/src/util/result.ts b/src/util/result.ts
index 70d7026bd3facc0ab83ee721318852ca0b80a653..baca22343183e8b927f8720cbf30af0529a69980 100644
--- a/src/util/result.ts
+++ b/src/util/result.ts
@@ -36,6 +36,20 @@ export class Result {
         return this._globalLog;
     }
 
+    /**
+     * @return true si il y a au moins un message dans le log global
+     */
+    public get hasGlobalLog(): boolean {
+        return this.globalLog.messages.length > 0;
+    }
+
+    /**
+     * @return true si il y a au moins un message dans le log global ou dans les ResultElement
+     */
+    public get hasLog(): boolean {
+        return (this.nbResultElements > 0 && this.log.messages.length > 0) || this.hasGlobalLog;
+    }
+
     /**
      * Retourne le résultat du premier ResultElement
      */