diff --git a/e2e/calculate-all-params.e2e-spec.ts b/e2e/calculate-all-params.e2e-spec.ts
index 6f5632baf39b9aacf2333cb68154865592796f64..9533c2fca374524be2406530d9c2c63526783d37 100644
--- a/e2e/calculate-all-params.e2e-spec.ts
+++ b/e2e/calculate-all-params.e2e-spec.ts
@@ -18,7 +18,12 @@ describe("ngHyd − calculate all parameters of all calculators", () => {
   });
 
   // get calculators list (IDs) @TODO read it from config, but can't import jalhyd here :/
-  const calcTypes = [ 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 17, 18, 19, 20, 21 ];
+  const calcTypes = [
+    0, 1, 2, 3, 4, 5, 6, 8, 9, 10,
+    11, 12, 13, 15, 17, 18, 19, 20,
+    21,
+    // 22 - Solveur is not calculated here because it is not independent
+  ];
 
   // for each calculator
   for (const ct of calcTypes) {
diff --git a/e2e/check-translations.e2e-spec.ts b/e2e/check-translations.e2e-spec.ts
index 2aef4ff34220586ff8209a79fdc03202028d900e..b0c10f97084467c212a1497b3425d4b21b9cbe47 100644
--- a/e2e/check-translations.e2e-spec.ts
+++ b/e2e/check-translations.e2e-spec.ts
@@ -25,7 +25,7 @@ describe("ngHyd − check translation of all calculators", () => {
   });
 
   // get calculators list (IDs) @TODO read it from config, but can't import jalhyd here :/
-  const calcTypes = [ 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 17, 18, 19, 20, 21 ];
+  const calcTypes = [ 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 17, 18, 19, 20, 21, 22 ];
 
   // options of "Language" selector on preferences page
   const langs = [ "English", "Français" ];
@@ -67,12 +67,13 @@ describe("ngHyd − check translation of all calculators", () => {
           // check that "compute" button is active
           const calcButton = calcPage.getCalculateButton();
           const disabledState = await calcButton.getAttribute("disabled");
-          expect(disabledState).not.toBe("true");
-          // click "compute" button
-          await calcButton.click();
-          // check that result is not empty
-          const hasResults = await calcPage.hasResults();
-          expect(hasResults).toBe(true);
+          if (! disabledState) {
+            // click "compute" button
+            await calcButton.click();
+            // check that result is not empty
+            const hasResults = await calcPage.hasResults();
+            expect(hasResults).toBe(true);
+          }
 
           // check absence of "*** message not found" in whole DOM
           expect(await browser.getPageSource()).not.toContain("*** message not found");
diff --git a/e2e/clone-all-calc.e2e-spec.ts b/e2e/clone-all-calc.e2e-spec.ts
index e5b77dcd1b84d0d34687a628f4c52f930eab779b..8c32c1ff622ac258da4f6cd15fc6e03d7c434146 100644
--- a/e2e/clone-all-calc.e2e-spec.ts
+++ b/e2e/clone-all-calc.e2e-spec.ts
@@ -18,7 +18,12 @@ describe("ngHyd − clone all calculators with all possible <select> values", ()
   });
 
   // get calculators list (IDs) @TODO read it from config, but can't import jalhyd here :/
-  const calcTypes = [ 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 17, 18, 19, 20, 21 ];
+  const calcTypes = [
+    0, 1, 2, 3, 4, 5, 6, 8, 9, 10,
+    11, 12, 13, 15, 17, 18, 19, 20,
+    21,
+    // 22 - Solveur is not cloned here because it is not independent
+  ];
 
   // for each calculator
   for (const ct of calcTypes) {
diff --git a/e2e/session/session-solveur-chutes.json b/e2e/session/session-solveur-chutes.json
new file mode 100644
index 0000000000000000000000000000000000000000..3142108ec2b5a85559d8ba422d3ad0c7a618705b
--- /dev/null
+++ b/e2e/session/session-solveur-chutes.json
@@ -0,0 +1,124 @@
+{
+    "header": {
+        "source": "jalhyd",
+        "format_version": "1.3",
+        "created": "2019-10-22T08:08:13.816Z"
+    },
+    "settings": {
+        "precision": 0.0001,
+        "maxIterations": 100,
+        "displayPrecision": 3
+    },
+    "documentation": "",
+    "session": [
+        {
+            "uid": "NjRvcG",
+            "props": {
+                "calcType": "PabChute"
+            },
+            "meta": {
+                "title": "PAB : chute"
+            },
+            "children": [],
+            "parameters": [
+                {
+                    "symbol": "Z1",
+                    "mode": "SINGLE",
+                    "value": 2.1513761467889907
+                },
+                {
+                    "symbol": "Z2",
+                    "mode": "SINGLE",
+                    "value": 0.8669724770642202
+                },
+                {
+                    "symbol": "DH",
+                    "mode": "CALCUL"
+                }
+            ]
+        },
+        {
+            "uid": "dnV4bD",
+            "props": {
+                "calcType": "PabNombre"
+            },
+            "meta": {
+                "title": "PAB : nombre"
+            },
+            "children": [],
+            "parameters": [
+                {
+                    "symbol": "DHT",
+                    "mode": "LINK",
+                    "targetNub": "NjRvcG",
+                    "targetParam": "DH"
+                },
+                {
+                    "symbol": "N",
+                    "mode": "SINGLE",
+                    "value": 10
+                },
+                {
+                    "symbol": "DH",
+                    "mode": "CALCUL"
+                }
+            ]
+        },
+        {
+            "uid": "OHBpcz",
+            "props": {
+                "calcType": "PabPuissance"
+            },
+            "meta": {
+                "title": "PAB : puissance"
+            },
+            "children": [],
+            "parameters": [
+                {
+                    "symbol": "DH",
+                    "mode": "LINK",
+                    "targetNub": "dnV4bD",
+                    "targetParam": "DH"
+                },
+                {
+                    "symbol": "Q",
+                    "mode": "SINGLE",
+                    "value": 0.1
+                },
+                {
+                    "symbol": "V",
+                    "mode": "SINGLE",
+                    "value": 0.5
+                },
+                {
+                    "symbol": "PV",
+                    "mode": "CALCUL"
+                }
+            ]
+        },
+        {
+            "uid": "ODM0Z2",
+            "props": {
+                "calcType": "Solveur",
+                "nubToCalculate": "OHBpcz",
+                "searchedParameter": "NjRvcG/Z2"
+            },
+            "meta": {
+                "title": "Solveur"
+            },
+            "children": [],
+            "parameters": [
+                {
+                    "symbol": "Xinit",
+                    "mode": "SINGLE",
+                    "value": 0.5
+                },
+                {
+                    "symbol": "Ytarget",
+                    "mode": "SINGLE",
+                    "value": 252
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/e2e/solveur.e2e-spec.ts b/e2e/solveur.e2e-spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..84b3df0e9d0b143a9ee460b07be960195e762a82
--- /dev/null
+++ b/e2e/solveur.e2e-spec.ts
@@ -0,0 +1,135 @@
+import { AppPage } from "./app.po";
+import { ListPage } from "./list.po";
+import { CalculatorPage } from "./calculator.po";
+import { Navbar } from "./navbar.po";
+import { browser } from "protractor";
+import { SideNav } from "./sidenav.po";
+
+/**
+ * Clone calculators
+ */
+describe("Solveur - ", () => {
+  let startPage: AppPage;
+  let listPage: ListPage;
+  let calcPage: CalculatorPage;
+  let navbar: Navbar;
+  let sidenav: SideNav;
+
+  beforeEach(() => {
+    startPage = new AppPage();
+    listPage = new ListPage();
+    calcPage = new CalculatorPage();
+    navbar = new Navbar();
+    sidenav = new SideNav();
+  });
+
+  it("load > calculate", async () => {
+    await startPage.navigateTo();
+
+    await navbar.clickMenuButton();
+    await browser.sleep(200);
+
+    await sidenav.clickLoadSessionButton();
+    await browser.sleep(200);
+
+    await sidenav.loadSessionFile("./session/session-solveur-chutes.json");
+    await browser.sleep(200);
+
+    expect(await navbar.getAllCalculatorTabs().count()).toBe(4);
+    await navbar.clickCalculatorTab(3); // n°3 should be the latest
+
+    // check input values
+    expect(await calcPage.getInputById("Xinit").getAttribute("value")).toBe("0.5");
+    expect(await calcPage.getInputById("Ytarget").getAttribute("value")).toBe("252");
+    // check Nub to calculate
+    const ntc = calcPage.getSelectById("select_target_nub");
+    const ntcV = await calcPage.getSelectValueText(ntc);
+    expect(ntcV).toContain("PAB : puissance / Puissance dissipée (PV)");
+    // check searched Parameter
+    const sp = calcPage.getSelectById("select_searched_param");
+    const spV = await calcPage.getSelectValueText(sp);
+    expect(spV).toContain("Z2 - Cote aval (PAB : chute)");
+
+    // check that "compute" button is active
+    const calcButton = calcPage.getCalculateButton();
+    const disabledState = await calcButton.getAttribute("disabled");
+    expect(disabledState).not.toBe("true");
+    // click "compute" button
+    await calcButton.click();
+    // check that result is not empty
+    const hasResults = await calcPage.hasResults();
+    expect(hasResults).toBe(true);
+  });
+
+  it("create > feed > calculate > clone > calculate clone", async () => {
+    await startPage.navigateTo();
+
+    // 1. create empty Solveur
+    await listPage.clickMenuEntryForCalcType(22); // Solveur
+    await browser.sleep(500);
+
+    // 2. create PAB:Chute, PAB:Nombre and PAB:Puissance linked to one another
+    await navbar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(12); // PAB:Chute
+    await browser.sleep(500);
+    await navbar.clickNewCalculatorButton();
+
+    await listPage.clickMenuEntryForCalcType(13); // PAB:Nombre
+    await browser.sleep(500);
+    // link DHT to PAB:Chute.DH
+    const dht = calcPage.getInputById("DHT");
+    await calcPage.setParamMode(dht, "link");
+    // Calculate DH
+    const dh_nombre = calcPage.getInputById("DH");
+    await calcPage.setParamMode(dh_nombre, "cal");
+
+    await navbar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(6); // PAB:Puissance
+    await browser.sleep(500);
+    // link DH to PAB:Nombre.DH
+    const dh_puiss = calcPage.getInputById("DH");
+    await calcPage.setParamMode(dh_puiss, "link");
+
+    // Go back to Solveur
+    await navbar.clickCalculatorTab(0);
+
+    await calcPage.changeSelectValue(calcPage.getSelectById("select_target_nub"), 1); // "Puissance / PV"
+    await browser.sleep(500);
+    await calcPage.changeSelectValue(calcPage.getSelectById("select_searched_param"), 2); // "Chute / Z2"
+    await browser.sleep(500);
+    await calcPage.getInputById("Ytarget").sendKeys("318");
+
+    // check that "compute" button is active
+    const calcButton = calcPage.getCalculateButton();
+    const disabledState = await calcButton.getAttribute("disabled");
+    expect(disabledState).not.toBe("true");
+    // click "compute" button
+    await calcButton.click();
+    // check that result is not empty
+    const hasResults = await calcPage.hasResults();
+    expect(hasResults).toBe(true);
+
+    // otherwise clickCloneCalcButton() fails with "Element is not clickable at point"
+    await browser.executeScript("window.scrollTo(0, 0);");
+    await calcPage.clickCloneCalcButton();
+    await browser.sleep(500);
+
+    // 4. check existence of the cloned module
+    expect(await navbar.getAllCalculatorTabs().count()).toBe(5);
+    await navbar.clickCalculatorTab(4); // n°4 should be the latest
+
+    // check that result is empty
+    const hasResultsClone1 = await calcPage.hasResults();
+    expect(hasResultsClone1).toBe(false);
+
+    // check that "compute" button is active
+    const calcButtonClone = calcPage.getCalculateButton();
+    const disabledStateClone = await calcButtonClone.getAttribute("disabled");
+    expect(disabledStateClone).not.toBe("true");
+    // click "compute" button
+    await calcButtonClone.click();
+    // check that result is not empty
+    const hasResultsClone2 = await calcPage.hasResults();
+    expect(hasResultsClone2).toBe(true);
+  });
+});
diff --git a/jalhyd_branch b/jalhyd_branch
index 0c6550147d6ae5cd2a34cbe0d6523c6fde1c533a..4517376e60041f9bc7c35409eab062c5cd55c1d3 100644
--- a/jalhyd_branch
+++ b/jalhyd_branch
@@ -1 +1 @@
-55-ajout-d-un-module-de-calcul-des-cotes-amont-aval-d-un-bief
+152-solveur-multi-modules
diff --git a/src/app/calculators/solveur/solveur.config.json b/src/app/calculators/solveur/solveur.config.json
new file mode 100644
index 0000000000000000000000000000000000000000..40b0bbb8cb7976a6da52b2e0f6491d08aa6e5f48
--- /dev/null
+++ b/src/app/calculators/solveur/solveur.config.json
@@ -0,0 +1,34 @@
+[
+    {
+        "id": "fs_target",
+        "type": "fieldset",
+        "fields": [
+            {
+                "id": "select_target_nub",
+                "type": "select_reference",
+                "reference": "nub",
+                "source": "solveur_target"
+            },
+            "Ytarget"
+        ]
+    },
+    {
+        "id": "fs_searched",
+        "type": "fieldset",
+        "fields": [
+            {
+                "id": "select_searched_param",
+                "type": "select_reference",
+                "reference": "parameter",
+                "source": "solveur_searched"
+            },
+            "Xinit"
+        ]
+    },
+    {
+        "type": "options",
+        "targetNubSelectId": "select_target_nub",
+        "searchedParamSelectId": "select_searched_param",
+        "_help": "solveur.html"
+    }
+]
\ No newline at end of file
diff --git a/src/app/calculators/solveur/solveur.en.json b/src/app/calculators/solveur/solveur.en.json
new file mode 100644
index 0000000000000000000000000000000000000000..df14b3764ae45a10c2ec502dfe8076ec409ed7f6
--- /dev/null
+++ b/src/app/calculators/solveur/solveur.en.json
@@ -0,0 +1,11 @@
+{
+    "fs_target": "Target parameter characteristics",
+    "fs_searched": "Searched parameter characteristics",
+
+    "Ytarget": "Value of target parameter",
+    "Xinit": "Initial value for searched parameter",
+    "X": "Value for searched parameter",
+
+    "select_target_nub": "Module and parameter to calculate",
+    "select_searched_param": "Searched parameter"
+}
\ No newline at end of file
diff --git a/src/app/calculators/solveur/solveur.fr.json b/src/app/calculators/solveur/solveur.fr.json
new file mode 100644
index 0000000000000000000000000000000000000000..1439bd8daa2cf73e9c871a5a9a6fc989d5747ab3
--- /dev/null
+++ b/src/app/calculators/solveur/solveur.fr.json
@@ -0,0 +1,11 @@
+{
+    "fs_target": "Caractéristiques du paramètre cible",
+    "fs_searched": "Caractéristiques du paramètre recherché",
+
+    "Ytarget": "Valeur du paramètre cible",
+    "Xinit": "Valeur initiale du paramètre recherché",
+    "X": "Valeur du paramètre recherché",
+
+    "select_target_nub": "Module et paramètre à calculer",
+    "select_searched_param": "Paramètre recherché"
+}
\ No newline at end of file
diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts
index 82dc8d735da50552c627ec2263a82298cfd7c9c1..18b6f57aa9eb8905666c87cbfe410a841ed8f47b 100644
--- a/src/app/components/generic-calculator/calculator.component.ts
+++ b/src/app/components/generic-calculator/calculator.component.ts
@@ -122,8 +122,6 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe
         this.intlService = ServiceFactory.instance.i18nService;
         this.formulaireService = ServiceFactory.instance.formulaireService;
 
-        this.matomoTracker.trackPageView("calculator");
-
         // hotkeys listeners
         this.hotkeysService.add(new Hotkey("alt+w", AppComponent.onHotkey(this.closeCalculator, this)));
         this.hotkeysService.add(new Hotkey("alt+d", AppComponent.onHotkey(this.cloneCalculator, this)));
@@ -376,6 +374,8 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe
                     this._calculatorNameComponent.model = this._formulaire;
                     // reload localisation in all cases
                     this.formulaireService.loadUpdateFormulaireLocalisation(this._formulaire);
+                    // call Form init hook
+                    this._formulaire.onCalculatorInit();
                     break;
             }
         } else if (sender instanceof FormulaireDefinition) {
@@ -530,6 +530,11 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe
         return (this.isPAB || this.isMRC);
     }
 
+    // true if current Nub is Solveur
+    public get isSolveur() {
+        return this.is(CalculatorType.Solveur);
+    }
+
     // true if current Nub is PAB
     public get isPAB() {
         return this.is(CalculatorType.Pab);
diff --git a/src/app/components/generic-input/generic-input.component.ts b/src/app/components/generic-input/generic-input.component.ts
index 2e076ed1c7725df657f8b104bf4be15a728e073c..f49b62c0e4ebdfc7d6f8815b6266a017f9db1ae6 100644
--- a/src/app/components/generic-input/generic-input.component.ts
+++ b/src/app/components/generic-input/generic-input.component.ts
@@ -220,7 +220,11 @@ export abstract class GenericInputComponent implements OnChanges {
      * MAJ et validation de l'UI
      */
     protected updateAndValidateUI() {
-        this._uiValue = String(this.getModelValue());
+        if (this.getModelValue() !== undefined) {
+            this._uiValue = String(this.getModelValue());
+        } else {
+            this._uiValue = "";
+        }
         this.validateUI();
     }
 
diff --git a/src/app/components/modules-diagram/modules-diagram.component.ts b/src/app/components/modules-diagram/modules-diagram.component.ts
index 68297ad6cf25be2f61179a9aa57158fea3c4c688..d84bfdac71cab208f840fd3c9cdded1b60b64e2d 100644
--- a/src/app/components/modules-diagram/modules-diagram.component.ts
+++ b/src/app/components/modules-diagram/modules-diagram.component.ts
@@ -16,7 +16,8 @@ import {
     LoiDebit,
     Nub,
     MacrorugoCompound,
-    Pab
+    Pab,
+    Solveur
 } from "jalhyd";
 
 import { I18nService } from "../../services/internationalisation.service";
@@ -43,7 +44,7 @@ export class ModulesDiagramComponent implements AfterContentInit, AfterViewCheck
     private nativeElement: any;
 
     @ViewChild("diagram", { static: true })
-    public diagram;
+    public diagram: any;
 
     public error: boolean;
 
@@ -179,7 +180,7 @@ export class ModulesDiagramComponent implements AfterContentInit, AfterViewCheck
                 // simple Nub (no children)
                 def.push(f.uid + "(\"" + f.calculatorName + "\")");
             }
-            // fnid all linked parameters
+            // find all linked parameters
             for (const p of nub.parameterIterator) {
                 if (p.valueMode === ParamValueMode.LINK && p.isReferenceDefined()) {
                     const target = p.referencedValue.nub;
@@ -190,6 +191,19 @@ export class ModulesDiagramComponent implements AfterContentInit, AfterViewCheck
                     def.push(nub.uid + "-->|" + symb + "|" + target.uid);
                 }
             }
+            // add Solveur links
+            if (nub instanceof Solveur) {
+                const ntc = nub.nubToCalculate;
+                const sp = nub.searchedParameter;
+                const reads = this.intlService.localizeText("INFO_DIAGRAM_SOLVEUR_READS");
+                const finds = this.intlService.localizeText("INFO_DIAGRAM_SOLVEUR_FINDS");
+                if (ntc !== undefined) {
+                    def.push(nub.uid + "-->|" + reads + ":" + ntc.calculatedParam.symbol + "|" + ntc.uid);
+                }
+                if (sp !== undefined) {
+                    def.push(sp.nubUid + "-->|" + finds + ":" + sp.symbol + "|" + nub.uid);
+                }
+            }
         }
 
         return def.join("\n");
diff --git a/src/app/components/select-field-line/select-field-line.component.ts b/src/app/components/select-field-line/select-field-line.component.ts
index 04a69146bdd988ebf4d4f52a1a7322e5dc56294e..e9615dbed86deef53b350b622f965be998275511 100644
--- a/src/app/components/select-field-line/select-field-line.component.ts
+++ b/src/app/components/select-field-line/select-field-line.component.ts
@@ -1,8 +1,9 @@
-import { Component, Input } from "@angular/core";
+import { Component, Input, OnInit } from "@angular/core";
 
 import { SelectField } from "../../formulaire/select-field";
 import { SelectEntry } from "../../formulaire/select-entry";
 import { I18nService } from "../../services/internationalisation.service";
+import { SelectFieldReference } from "../../formulaire/select-field-reference";
 
 @Component({
     selector: "select-field-line",
@@ -11,7 +12,7 @@ import { I18nService } from "../../services/internationalisation.service";
         "./select-field-line.component.scss"
     ]
 })
-export class SelectFieldLineComponent {
+export class SelectFieldLineComponent implements OnInit {
 
     /** aide en ligne */
     protected helpLink: string | { [key: string]: string };
@@ -83,4 +84,11 @@ export class SelectFieldLineComponent {
     public get uitextOpenHelp() {
         return this.i18nService.localizeText("INFO_CALCULATOR_OPEN_HELP");
     }
+
+    // called every time we navigate to the module
+    ngOnInit(): void {
+        if (this._select instanceof SelectFieldReference) {
+            this._select.updateEntries();
+        }
+    }
 }
diff --git a/src/app/formulaire/definition/concrete/form-solveur.ts b/src/app/formulaire/definition/concrete/form-solveur.ts
new file mode 100644
index 0000000000000000000000000000000000000000..231f815b08b1f5a9030be690e26929cfad44e831
--- /dev/null
+++ b/src/app/formulaire/definition/concrete/form-solveur.ts
@@ -0,0 +1,95 @@
+import { IObservable, ParamDefinition, Solveur } from "jalhyd";
+
+import { FormulaireBase } from "./form-base";
+import { SelectFieldNub } from "../../select-field-nub";
+import { SelectFieldParameter } from "../../select-field-parameter";
+import { NgParameter } from "../../ngparam";
+
+/**
+ * Formulaire pour les Solveurs
+ */
+export class FormulaireSolveur extends FormulaireBase {
+
+    /** id of select configuring target Nub */
+    private _targetNubSelectId: string;
+
+    /** id of select configuring searched param */
+    private _searchedParamSelectId: string;
+
+    protected parseOptions(json: {}) {
+        super.parseOptions(json);
+        this._targetNubSelectId = this.getOption(json, "targetNubSelectId");
+        this._searchedParamSelectId = this.getOption(json, "searchedParamSelectId");
+    }
+
+    protected completeParse(json: {}) {
+        super.completeParse(json);
+        if (this._targetNubSelectId) {
+            const sel = this.getFormulaireNodeById(this._targetNubSelectId);
+            if (sel) {
+                sel.addObserver(this);
+                // force 1st observation
+                (sel as SelectFieldNub).notifySelectValueChanged();
+            }
+        }
+        if (this._searchedParamSelectId) {
+            const sel = this.getFormulaireNodeById(this._searchedParamSelectId);
+            if (sel) {
+                sel.addObserver(this);
+                // force 1st observation
+                (sel as SelectFieldNub).notifySelectValueChanged();
+            }
+        }
+
+    }
+
+    private debugState() {
+        const sol = this._currentNub as Solveur;
+        const spm = sol.searchedParameter;
+        console.log(
+            `ETAT:\n X.singleValue=${sol.prms.X ? sol.prms.X.singleValue : "UNDEF"}\n Y.singleValue=${sol.prms.Y.singleValue}`
+            + `\n Xinit.singleValue=${sol.prms.Xinit.singleValue}\n Ytarget.singleValue=${sol.prms.Ytarget.singleValue}`
+            + `\n searchedParam.singleValue=${spm ? spm.singleValue : "UNDEF"}`
+        );
+    }
+
+    // interface Observer
+
+    public update(sender: IObservable, data: any) {
+        super.update(sender, data);
+        if (sender instanceof SelectFieldNub) {
+            if (data.action === "select") {
+                // update Solveur property: Nub to calculate
+                try {
+                    // if searchedParam is set to a value that won't be available anymore
+                    // once nubToCalculate is updated, setPropValue throws an error, but
+                    // nubToCalculate is updated anyway; here, just inhibit the error
+                    this._currentNub.properties.setPropValue("nubToCalculate", data.value.value);
+                } catch (e) { }
+                // refresh parameters selector
+                const sel = this.getFormulaireNodeById(this._searchedParamSelectId) as SelectFieldParameter;
+                if (sel) {
+                    sel.updateEntries();
+                    // reflect changes in GUI
+                    const inputYtarget = this.getFormulaireNodeById("Ytarget") as NgParameter;
+                    inputYtarget.notifyValueModified(this);
+                }
+            }
+        }
+        if (sender instanceof SelectFieldParameter) {
+            if (data.action === "select") {
+                // update Solveur property: searched Parameter
+                try {
+                    const p: ParamDefinition = data.value.value;
+                    this._currentNub.properties.setPropValue(
+                        "searchedParameter",
+                        p.nubUid + "/" + p.symbol
+                    );
+                } catch (e) { }
+                // reflect changes in GUI
+                const inputXinit = this.getFormulaireNodeById("Xinit") as NgParameter;
+                inputXinit.notifyValueModified(this);
+            }
+        }
+    }
+}
diff --git a/src/app/formulaire/definition/form-definition.ts b/src/app/formulaire/definition/form-definition.ts
index b178d3a3db9ca3098f58050f2ffb1223d05d484f..5aa007c8aff8314b63f92235241162864c108912 100644
--- a/src/app/formulaire/definition/form-definition.ts
+++ b/src/app/formulaire/definition/form-definition.ts
@@ -452,6 +452,12 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
         return new TopFormulaireElementIterator(this);
     }
 
+    /**
+     * Appelé par CalculatorComponent lrosque le Formulaire est chargé dans la vue,
+     * c'est à dire lorsqu'on affiche un module de calcul à l'écran
+     */
+    public onCalculatorInit() {}
+
     //  interface Observer
 
     public update(sender: any, data: any) {
diff --git a/src/app/formulaire/fieldset.ts b/src/app/formulaire/fieldset.ts
index b5d4978c5be82673ce8d59cccf9b0b20c41c9266..0681164586f2f3c9ac4b8e798ea6dd2963ee1772 100644
--- a/src/app/formulaire/fieldset.ts
+++ b/src/app/formulaire/fieldset.ts
@@ -10,6 +10,7 @@ import {
     GrilleType,
     GrilleProfile,
     BiefRegime,
+    Solveur,
 } from "jalhyd";
 
 import { FormulaireElement } from "./formulaire-element";
@@ -20,6 +21,8 @@ import { FormulaireDefinition } from "./definition/form-definition";
 import { StringMap } from "../stringmap";
 import { FormulaireNode } from "./formulaire-node";
 import { FieldsetContainer } from "./fieldset-container";
+import { SelectFieldNub } from "./select-field-nub";
+import { SelectFieldParameter } from "./select-field-parameter";
 
 export class FieldSet extends FormulaireElement implements Observer {
     /**
@@ -96,6 +99,28 @@ export class FieldSet extends FormulaireElement implements Observer {
         return res;
     }
 
+    private parse_select_reference(json: {}): SelectField {
+        const refType = json["reference"];
+        const source = json["source"];
+        let res: SelectField;
+        if (source === undefined || source === "") {
+            throw new Error(`Fieldset.parse_select_reference(): "source" must not be empty`);
+        }
+        switch (refType) {
+            case "nub": // @TODO upstreamNub / downstreamNub ?
+                res = new SelectFieldNub(this, source);
+                break;
+            case "parameter":
+                res = new SelectFieldParameter(this, source);
+                break;
+            default:
+                throw new Error(`Fieldset.parse_select_reference(): unknown reference type ${refType}`);
+        }
+        res.parseConfig(json);
+        res.addObserver(this);
+        return res;
+    }
+
     public get properties(): Props {
         return this.nub.properties;
     }
@@ -185,6 +210,11 @@ export class FieldSet extends FormulaireElement implements Observer {
                     this.addField(param);
                     break;
 
+                case "select_reference":
+                    param = this.parse_select_reference(field);
+                    this.addField(param);
+                    break;
+
             }
         }
     }
diff --git a/src/app/formulaire/ngparam.ts b/src/app/formulaire/ngparam.ts
index 5387c058dffb94a62d1419095b4fc2dffc98781d..b5d15d216c23923cdb955c86d91854f4d6f64744 100644
--- a/src/app/formulaire/ngparam.ts
+++ b/src/app/formulaire/ngparam.ts
@@ -113,9 +113,8 @@ export class NgParameter extends InputField implements Observer {
                         const cVal = ref.nub.result.getCalculatedValues();
                         valuePreview = fv(cVal[0]) + " … " + fv(cVal[cVal.length - 1]);
                     } else {
-                        const vCalc = ref.nub.result.vCalc;
-                        if (vCalc) {
-                            valuePreview = fv(vCalc);
+                        if (ref.nub.result.resultElements.length > 0 && ref.nub.result.vCalc) {
+                            valuePreview = fv(ref.nub.result.vCalc);
                         } else {
                             // computation has been run but has failed
                             valuePreview = i18n.localizeText("INFO_PARAMFIELD_CALCULATION_FAILED");
diff --git a/src/app/formulaire/select-field-nub.ts b/src/app/formulaire/select-field-nub.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b19492cc0abbdb67f34c4c8325cd3fc694fbdaca
--- /dev/null
+++ b/src/app/formulaire/select-field-nub.ts
@@ -0,0 +1,45 @@
+import { SelectFieldReference } from "./select-field-reference";
+import { SelectEntry } from "./select-entry";
+import { ServiceFactory } from "../services/service-factory";
+import { decodeHtml } from "../util";
+
+import { Session, Solveur } from "jalhyd";
+
+/**
+ * A select field that populates itself with references to Nubs
+ */
+export class SelectFieldNub extends SelectFieldReference {
+
+    protected initSelectedValue() {
+        const nub = this.parentForm.currentNub;
+        if (nub instanceof Solveur) {
+            const ntc = nub.nubToCalculate;
+            if (ntc !== undefined) {
+                this.setValueFromId(this._entriesBaseId + ntc.uid);
+            }
+        }
+    }
+
+    /**
+     * Populates entries with available references
+     */
+    protected populate() {
+        switch (this._source) {
+            case "solveur_target": // Solveur, paramètre cible (à calculer)
+                // find all Nubs having at least one link to another Nub's result
+                const fs = ServiceFactory.instance.formulaireService;
+                const downstreamNubs = Session.getInstance().getDownstreamNubs();
+                for (const dn of downstreamNubs) {
+                    const calc = fs.getFormulaireFromId(dn.uid).calculatorName;
+                    let label = calc;
+                    if (dn.calculatedParam !== undefined) {
+                        const varName = fs.expandVariableName(dn.calcType, dn.calculatedParam.symbol);
+                        label += ` / ${varName} (${dn.calculatedParam.symbol})`;
+                    }
+                    this.addEntry(new SelectEntry(this._entriesBaseId + dn.uid, dn.uid, decodeHtml(label)));
+                }
+                break;
+        }
+    }
+
+}
diff --git a/src/app/formulaire/select-field-parameter.ts b/src/app/formulaire/select-field-parameter.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b979a98f72b292ff818869c847939c70c29a5232
--- /dev/null
+++ b/src/app/formulaire/select-field-parameter.ts
@@ -0,0 +1,44 @@
+import { SelectFieldReference } from "./select-field-reference";
+import { SelectEntry } from "./select-entry";
+import { decodeHtml } from "../util";
+import { ServiceFactory } from "../services/service-factory";
+
+import { Nub, Solveur } from "jalhyd";
+
+/**
+ * A select field that populates itself with references to ParamDefinitions
+ */
+export class SelectFieldParameter extends SelectFieldReference {
+
+    protected initSelectedValue() {
+        const nub = this.parentForm.currentNub;
+        if (nub instanceof Solveur) {
+            const sp = nub.searchedParameter;
+            if (sp !== undefined) {
+                this.setValueFromId(this._entriesBaseId + sp.nubUid + "_" + sp.symbol);
+            }
+        }
+    }
+
+    /**
+     * Populates entries with available references
+     */
+    protected populate() {
+        switch (this._source) {
+            case "solveur_searched": // Solveur, paramètre recherché (à faire varier)
+                // find all non-calculated, non-linked parameters of all Nubs that
+                // the current "target" Nub depends on (if any)
+                const fs = ServiceFactory.instance.formulaireService;
+                const ntc: Nub = (this.parentForm.currentNub as Solveur).nubToCalculate;
+                const searchableParams = Solveur.getDependingNubsSearchableParams(ntc);
+                for (const p of searchableParams) {
+                    const calc = fs.getFormulaireFromId(p.parentNub.uid).calculatorName;
+                    const varName = fs.expandVariableName(p.parentNub.calcType, p.symbol);
+                    const label = `${p.symbol} - ${varName} (${calc})`;
+                    this.addEntry(new SelectEntry(this._entriesBaseId + p.nubUid + "_" + p.symbol, p, decodeHtml(label)));
+                }
+                break;
+        }
+    }
+
+}
diff --git a/src/app/formulaire/select-field-reference.ts b/src/app/formulaire/select-field-reference.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3a0d8396c06c242fdde23616513868aade5d7023
--- /dev/null
+++ b/src/app/formulaire/select-field-reference.ts
@@ -0,0 +1,107 @@
+import { SelectField } from "./select-field";
+import { SelectEntry } from "./select-entry";
+import { FormulaireNode } from "./formulaire-node";
+
+/**
+ * A select field that populates itself with references to
+ * available objects (for ex. Nub or ParamDefinition)
+ */
+export abstract class SelectFieldReference extends SelectField {
+
+    /** source identifier for populate() method */
+    protected _source: string;
+
+    constructor(parent: FormulaireNode, source: string) {
+        super(parent);
+        this._source = source;
+    }
+
+    protected abstract initSelectedValue();
+
+    /**
+     * Populates entries with available references
+     */
+    protected abstract populate();
+
+    /**
+     * Once config is parsed, init original value from model
+     * (needs config, for this._entriesBaseId to be set)
+     */
+    protected afterParseConfig() {
+        this.populate();
+        this.initSelectedValue();
+    }
+
+    /**
+     * Reloads available entries, trying to keep the current selected
+     * value; does not notify observers if value did not change
+     */
+    public updateEntries() {
+        // store previous selected entry
+        const pse = this._selectedEntry;
+        // empty
+        this.clearEntries();
+        // populate
+        this.populate();
+        // keep previously selected entry if possible
+        if (pse && pse.id) {
+            this.setValueFromId(pse.id);
+        } else {
+            // if no entry is available anymore, unset value
+            if (this.entries.length === 0) {
+                super.setValue(undefined);
+            } else {
+                this.setDefaultValue();
+            }
+        }
+    }
+
+    /**
+     * Updates selectedValue; notifies observers only if
+     * value.id has changed
+     */
+    public setValue(v: SelectEntry) {
+        const previousSelectedEntry = this._selectedEntry;
+        this._selectedEntry = v;
+        if (
+            ! previousSelectedEntry
+            || (previousSelectedEntry.id !== v.id)
+        ) {
+            this.notifySelectValueChanged();
+        }
+    }
+
+    public notifySelectValueChanged() {
+        this.notifyObservers({
+            "action": "select",
+            "value": this._selectedEntry
+        }, this);
+    }
+
+    /**
+     * Sets value from given ID; if it was not found, sets the
+     * first available entry as selectedValue
+     */
+    public setValueFromId(id: string) {
+        let found = false;
+        for (const e of this._entries) {
+            if (e.id === id) {
+                found = true;
+                this.setValue(e);
+            }
+        }
+        if (! found) {
+            this.setDefaultValue();
+        }
+    }
+
+    protected setDefaultValue() {
+        // default to first available entry if any
+        if (this._entries.length > 0) {
+            this.setValue(this._entries[0]);
+        } else {
+            // notify observers that no value is selected anymore
+            this.notifySelectValueChanged();
+        }
+    }
+}
diff --git a/src/app/formulaire/select-field.ts b/src/app/formulaire/select-field.ts
index 308b319b08bab11b65ededa940cc947143cb7e7c..b2ea54a8c44b80617e149fd9e6f05dc907a57239 100644
--- a/src/app/formulaire/select-field.ts
+++ b/src/app/formulaire/select-field.ts
@@ -8,7 +8,10 @@ import {
     StructureType,
     LoiDebit,
     GrilleType,
-    GrilleProfile
+    GrilleProfile,
+    Solveur,
+    ParamValueMode,
+    Session
  } from "jalhyd";
 
 import { Field } from "./field";
@@ -74,6 +77,11 @@ export class SelectField extends Field {
      */
     protected populate() { }
 
+    /**
+     * Triggered at the end of parseConfig()
+     */
+    protected afterParseConfig() { }
+
     public getSelectedEntryFromValue(val: any): SelectEntry {
         for (const se of this._entries) {
             if (se.value === val) {
@@ -105,7 +113,9 @@ export class SelectField extends Field {
     public updateLocalisation(loc: StringMap) {
         super.updateLocalisation(loc);
         for (const e of this._entries) {
-            e.label = loc[e.id];
+            if (loc[e.id] !== undefined) {
+                e.label = loc[e.id];
+            }
         }
     }
 
@@ -189,5 +199,7 @@ export class SelectField extends Field {
                 this.addEntry(new SelectEntry(this._entriesBaseId + BiefRegime.Torrentiel, BiefRegime.Torrentiel));
                 break;
         }
+
+        this.afterParseConfig();
     }
 }
diff --git a/src/app/services/formulaire.service.ts b/src/app/services/formulaire.service.ts
index 65a60d25bc8ea319c99c4b369e0ce228ff0ba21a..02593060993d7f2112d7d06628cf42225afc0d2d 100644
--- a/src/app/services/formulaire.service.ts
+++ b/src/app/services/formulaire.service.ts
@@ -39,6 +39,7 @@ import { FormulaireMacrorugoCompound } from "../formulaire/definition/concrete/f
 import { FormulaireLechaptCalmon } from "../formulaire/definition/concrete/form-lechapt-calmon";
 import { FormulaireGrille } from "../formulaire/definition/concrete/form-grille";
 import { FormulaireBief } from "../formulaire/definition/concrete/form-bief";
+import { FormulaireSolveur } from "../formulaire/definition/concrete/form-solveur";
 
 @Injectable()
 export class FormulaireService extends Observable {
@@ -84,6 +85,7 @@ export class FormulaireService extends Observable {
         this.calculatorPaths[CalculatorType.Grille] = "grille";
         this.calculatorPaths[CalculatorType.Pente] = "pente";
         this.calculatorPaths[CalculatorType.Bief] = "bief";
+        this.calculatorPaths[CalculatorType.Solveur] = "solveur";
     }
 
     private get _intlService(): I18nService {
@@ -329,6 +331,10 @@ export class FormulaireService extends Observable {
                 f = new FormulaireBief();
                 break;
 
+            case CalculatorType.Solveur:
+                f = new FormulaireSolveur();
+                break;
+
             default:
                 f = new FormulaireBase();
         }
diff --git a/src/app/util.ts b/src/app/util.ts
index 6d6a10bb17488544685d8517037e8b4896274ae3..e4d3e1f1bf9eecaf247a42521ae971bdaf86320f 100644
--- a/src/app/util.ts
+++ b/src/app/util.ts
@@ -30,3 +30,14 @@ export function fv(p: NgParameter | number): string {
 
     return formattedValue(value, nDigits);
 }
+
+/**
+ * Trick to decode HTML entities in a string
+ * https://stackoverflow.com/a/7394787/5986614
+ * @param html string containing HTML entities, like &nbsp;
+ */
+export function decodeHtml(html: string): string {
+    const txt = document.createElement("textarea");
+    txt.innerHTML = html;
+    return txt.value;
+}
diff --git a/src/locale/messages.en.json b/src/locale/messages.en.json
index 5a3e63c6445c7fe7a9f8b2fe07197f56a4bd164e..b6db3ee3226f5c823c7a3251e76536054c448cb6 100644
--- a/src/locale/messages.en.json
+++ b/src/locale/messages.en.json
@@ -4,10 +4,10 @@
     "WARNING_DOWNSTREAM_ELEVATION_POSSIBLE_SUBMERSION": "Downstream elevation is higher than weir elevation (possible submersion)",
     "WARNING_NOTCH_SUBMERSION_GREATER_THAN_07": "Notch formula is discouraged when submersion is greater than 0.7",
     "WARNING_SLOT_SUBMERSION_NOT_BETWEEN_07_AND_09": "Slot formula is discouraged when submersion is lower than 0.7 or greater than 0.9",
-    "ERROR_ABSTRACT": "%nb% errors occurred during calculation",
+    "WARNING_ERRORS_ABSTRACT": "%nb% errors occurred during calculation",
     "ERROR_BIEF_Z1_CALC_FAILED": "Unable to calculate upstream elevation (calculation interrupted before upstream)",
     "ERROR_BIEF_Z2_CALC_FAILED": "Unable to calculate downstream elevation (calculation interrupted before downstream)",
-    "ERROR_DICHO_CONVERGE": "Dichotomy could not converge",
+    "ERROR_DICHO_CONVERGE": "Dichotomy could not converge. Last approximation: %lastApproximation%",
     "ERROR_DICHO_FUNCTION_VARIATION": "unable to determinate function direction of variation",
     "ERROR_DICHO_INIT_DOMAIN": "Dichotomy: target %targetSymbol%=%targetValue% does not exist for variable %variableSymbol% valued in interval %variableInterval%",
     "ERROR_DICHO_INVALID_STEP_GROWTH": "Dichotomy (initial interval search): invalid null step growth",
@@ -15,6 +15,8 @@
     "ERROR_DICHO_TARGET_TOO_HIGH": "Dichotomy: the solution %targetSymbol%=%targetValue% is greater than the maximum computable value %targetSymbol%(%variableSymbol%=%variableExtremeValue%)=%extremeTarget%)",
     "ERROR_DICHO_TARGET_TOO_LOW": "Dichotomy: the solution %targetSymbol%=%targetValue%  is lower than the minimum computable value %targetSymbol%(%variableSymbol%=%variableExtremeValue%)=%extremeTarget%)",
     "ERROR_ELEVATION_ZI_LOWER_THAN_Z2": "Upstream elevation is lower than downstream elevation",
+    "ERROR_IN_CALC_CHAIN": "An error occurred in calculation chain",
+    "WARNING_ERROR_IN_CALC_CHAIN_STEPS": "Errors occurred during chain calculation",
     "ERROR_INTERVAL_OUTSIDE": "Interval: value %value% is outside of %interval%",
     "ERROR_INTERVAL_UNDEF": "Interval: invalid 'undefined' value",
     "ERROR_INVALID_AT_POSITION": "Position %s:",
@@ -80,6 +82,8 @@
     "INFO_COURBEREMOUS_TITRE": "Backwater curves",
     "INFO_DEVER_TITRE_COURT": "Free weir",
     "INFO_DEVER_TITRE": "Free flow weir stage-discharge laws",
+    "INFO_DIAGRAM_SOLVEUR_FINDS": "finds",
+    "INFO_DIAGRAM_SOLVEUR_READS": "reads",
     "INFO_DIAGRAM_TITLE": "Calculation modules diagram",
     "INFO_DIAGRAM_DRAWING_ERROR": "Error while drawing diagram",
     "INFO_DIAGRAM_CALCULATED_PARAM": "calculated parameter",
@@ -431,6 +435,8 @@
     "INFO_SNACKBAR_RESULTS_CALCULATED": "Results calculated for",
     "INFO_SNACKBAR_RESULTS_INVALIDATED": "Results invalidated for",
     "INFO_SNACKBAR_SETTINGS_SAVED": "Settings saved on this device",
+    "INFO_SOLVEUR_TITRE": "Multimodule solver",
+    "INFO_SOLVEUR_TITRE_COURT": "Solver",
     "INFO_THEME_CREDITS": "Credit",
     "INFO_THEME_DEVALAISON_TITRE": "Downstream migration",
     "INFO_THEME_DEVALAISON_DESCRIPTION": "Tools for dimensioning the structures present on the water intakes of hydroelectric power plants known as \"ichthyocompatible\" and consisting of fine grid planes associated with one or more outlets.",
@@ -453,7 +459,7 @@
     "INFO_EXAMPLE_LABEL_PAB_COMPLETE": "Standard fish ladder",
     "INFO_EXAMPLES_TITLE": "Examples",
     "INFO_EXAMPLES_SUBTITLE": "Load standard examples",
-    "WARNING_ABSTRACT": "%nb% warnings occurred during calculation",
+    "WARNING_WARNINGS_ABSTRACT": "%nb% warnings occurred during calculation",
     "WARNING_REMOUS_ARRET_CRITIQUE": "Calculation stopped: critical elevation reached at abscissa %x%",
     "WARNING_STRUCTUREKIVI_HP_TROP_ELEVE": "h/p must not be greater than 2.5. h/p is forced to 2.5",
     "WARNING_STRUCTUREKIVI_PELLE_TROP_FAIBLE": "Threshold height should be greater than 0.1 m. Beta coefficient is forced to 0",
@@ -466,5 +472,6 @@
     "WARNING_DOWNSTREAM_BOTTOM_HIGHER_THAN_WATER": "Downstream water elevation is lower or equal to bottom elevation",
     "WARNING_YN_SECTION_PENTE_NEG_NULLE_HNORMALE_INF": "Normal depth: slope is negative or zero, normal depth is infinite",
     "WARNING_YN_SECTION_NON_CONVERGENCE_NEWTON_HNORMALE": "Normal depth: non convergence of the calculation (Newton's method)",
-    "WARNING_SESSION_LOAD_NOTES_MERGED": "Notes have been merged"
+    "WARNING_SESSION_LOAD_NOTES_MERGED": "Notes have been merged",
+    "WARNING_VALUE_ROUNDED_TO_INTEGER": "Value of %symbol% was rounded to %rounded%"
 }
diff --git a/src/locale/messages.fr.json b/src/locale/messages.fr.json
index 523d6cd37a8604dc9edf94669385423c0cd835f2..f7dd7975fd553ea703cb40945bf09ca14980a8a8 100644
--- a/src/locale/messages.fr.json
+++ b/src/locale/messages.fr.json
@@ -4,10 +4,10 @@
     "WARNING_DOWNSTREAM_ELEVATION_POSSIBLE_SUBMERSION": "La cote de l'eau aval est plus élevée que la cote du seuil (ennoiement possible)",
     "WARNING_NOTCH_SUBMERSION_GREATER_THAN_07": "La formule de l'échancrure n'est pas conseillée pour un ennoiement supérieur à 0.7",
     "WARNING_SLOT_SUBMERSION_NOT_BETWEEN_07_AND_09": "La formule de la fente n'est pas conseillée pour un ennoiement inférieur à 0.7 et supérieur à 0.9",
-    "ERROR_ABSTRACT": "%nb% erreurs rencontrées lors du calcul",
+    "WARNING_ERRORS_ABSTRACT": "%nb% erreurs rencontrées lors du calcul",
     "ERROR_BIEF_Z1_CALC_FAILED": "Impossible de calculer la cote amont (calcul interrompu avant l'amont)",
     "ERROR_BIEF_Z2_CALC_FAILED": "Impossible de calculer la cote aval (calcul interrompu avant l'aval)",
-    "ERROR_DICHO_CONVERGE": "La dichotomie n'a pas pu converger",
+    "ERROR_DICHO_CONVERGE": "La dichotomie n'a pas pu converger. Dernière approximation: %lastApproximation%",
     "ERROR_DICHO_FUNCTION_VARIATION": "Dichotomie&nbsp;: impossible de determiner le sens de  variation de la fonction",
     "ERROR_DICHO_INIT_DOMAIN": "Dichotomie&nbsp;: la valeur cible %targetSymbol%=%targetValue% n'existe pas pour la variable %variableSymbol% prise dans l'intervalle %variableInterval%",
     "ERROR_DICHO_INVALID_STEP_GROWTH": "Dichotomie&nbsp;: l'augmentation du pas pour la recherche de l'intervalle de départ est incorrecte (=0)",
@@ -15,6 +15,8 @@
     "ERROR_DICHO_TARGET_TOO_HIGH": "Dichotomie&nbsp;: la solution %targetSymbol%=%targetValue% est supérieure à la valeur maximale calculable %targetSymbol%(%variableSymbol%=%variableExtremeValue%)=%extremeTarget%)",
     "ERROR_DICHO_TARGET_TOO_LOW": "Dichotomie&nbsp;: la solution %targetSymbol%=%targetValue% est inférieure à la valeur minimale calculable %targetSymbol%(%variableSymbol%=%variableExtremeValue%)=%extremeTarget%)",
     "ERROR_ELEVATION_ZI_LOWER_THAN_Z2": "La cote amont est plus basse que la cote aval",
+    "ERROR_IN_CALC_CHAIN": "Une erreur est survenue dans la chaîne de calcul",
+    "WARNING_ERROR_IN_CALC_CHAIN_STEPS": "Des erreurs sont survenues durant le calcul en chaîne",
     "ERROR_INTERVAL_OUTSIDE": "Intervalle&nbsp;: la valeur %value% est hors de l'intervalle %interval%",
     "ERROR_INTERVAL_UNDEF": "Interval&nbsp;: valeur 'undefined' incorrecte",
     "ERROR_INVALID_AT_POSITION": "Position %s :",
@@ -80,6 +82,8 @@
     "INFO_COURBEREMOUS_TITRE": "Courbes de remous",
     "INFO_DEVER_TITRE_COURT": "Déver. dénoyés",
     "INFO_DEVER_TITRE": "Lois de déversoirs dénoyés",
+    "INFO_DIAGRAM_SOLVEUR_FINDS": "trouve",
+    "INFO_DIAGRAM_SOLVEUR_READS": "lit",
     "INFO_DIAGRAM_TITLE": "Diagramme des modules de calcul",
     "INFO_DIAGRAM_DRAWING_ERROR": "Erreur lors du dessin du diagramme",
     "INFO_DIAGRAM_CALCULATED_PARAM": "paramètre calculé",
@@ -430,6 +434,8 @@
     "INFO_SNACKBAR_RESULTS_CALCULATED": "Résultats calculés pour",
     "INFO_SNACKBAR_RESULTS_INVALIDATED": "Résultats invalidés pour",
     "INFO_SNACKBAR_SETTINGS_SAVED": "Paramètres enregistrés sur cet appareil",
+    "INFO_SOLVEUR_TITRE": "Solveur multimodule",
+    "INFO_SOLVEUR_TITRE_COURT": "Solveur",
     "INFO_THEME_CREDITS": "Crédit",
     "INFO_THEME_DEVALAISON_TITRE": "Dévalaison",
     "INFO_THEME_DEVALAISON_DESCRIPTION": "Outils de dimensionnements des ouvrages présents sur les prises d'eau des centrales hydroélectriques dites \"ichtyocompatibles\" et constituées de plans de grilles fines associés à un ou plusieurs exutoires.",
@@ -452,7 +458,7 @@
     "INFO_EXAMPLE_LABEL_PAB_COMPLETE": "Passe à bassins type",
     "INFO_EXAMPLES_TITLE": "Exemples",
     "INFO_EXAMPLES_SUBTITLE": "Charger des exemples types",
-    "WARNING_ABSTRACT": "%nb% avertissements rencontrés lors du calcul",
+    "WARNING_WARNINGS_ABSTRACT": "%nb% avertissements rencontrés lors du calcul",
     "WARNING_REMOUS_ARRET_CRITIQUE": "Arrêt du calcul&nbsp;: hauteur critique atteinte à l'abscisse %x%",
     "WARNING_STRUCTUREKIVI_HP_TROP_ELEVE": "h/p ne doit pas être supérieur à 2,5. h/p est forcé à 2,5",
     "WARNING_STRUCTUREKIVI_PELLE_TROP_FAIBLE": "La pelle du seuil doit mesurer au moins 0,1 m. Le coefficient béta est forcé à 0",
@@ -465,5 +471,6 @@
     "WARNING_DOWNSTREAM_BOTTOM_HIGHER_THAN_WATER": "La cote de l'eau à l'aval est plus basse ou égale à la cote de fond",
     "WARNING_YN_SECTION_PENTE_NEG_NULLE_HNORMALE_INF": "Hauteur normale: pente négative ou nulle, hauteur normale infinie",
     "WARNING_YN_SECTION_NON_CONVERGENCE_NEWTON_HNORMALE": "Hauteur normale: non convergence du calcul (méthode de Newton)",
-    "WARNING_SESSION_LOAD_NOTES_MERGED": "Les notes ont été fusionnées"
+    "WARNING_SESSION_LOAD_NOTES_MERGED": "Les notes ont été fusionnées",
+    "WARNING_VALUE_ROUNDED_TO_INTEGER": "La valeur de %symbol% a été arrondie à %rounded%"
 }