From 97c33cae10c0204ca9955c256182a760c66ffa9b Mon Sep 17 00:00:00 2001
From: mattisnowman <matthias@rauter.it>
Date: Sat, 10 Dec 2022 15:43:20 +0100
Subject: [PATCH] ENH: Added faTwoLayerAvalancheFoam, a solver for mixed dense
 flow, powder cloud avalanches

---
 .../faTwoLayerAvalancheFoam/Make/files        |   3 +
 .../faTwoLayerAvalancheFoam/Make/options      |  21 ++
 .../PFcreateFaFields.H                        | 166 ++++++++++++
 .../PFcreateSubmodels.H                       |  24 ++
 .../PFreadTransportProperties.H               |  40 +++
 .../SHcreateFaFields.H                        | 128 +++++++++
 .../SHcreateSubmodels.H                       |  14 +
 .../SHreadTransportProperties.H               |  33 +++
 .../advanceDenseFlowModel.H                   |  75 ++++++
 .../advancePowderCloudModel.H                 | 114 ++++++++
 .../faTwoLayerAvalancheFoam/calcBasalstress.H |  13 +
 .../faTwoLayerAvalancheFoam.C                 | 245 ++++++++++++++++++
 .../readSolutionControls.H                    |  16 ++
 .../surfaceCourantNo1.H                       |  59 +++++
 .../surfaceCourantNo2.H                       |  59 +++++
 src/avalanche/Make/files                      |   5 +
 .../couplingInertial/couplingInertial.C       | 114 ++++++++
 .../couplingInertial/couplingInertial.H       | 132 ++++++++++
 .../coupling/couplingModel/couplingModel.C    | 100 +++++++
 .../coupling/couplingModel/couplingModel.H    | 210 +++++++++++++++
 .../coupling/couplingModel/couplingModelNew.C |  70 +++++
 21 files changed, 1641 insertions(+)
 create mode 100644 applications/solvers/faTwoLayerAvalancheFoam/Make/files
 create mode 100644 applications/solvers/faTwoLayerAvalancheFoam/Make/options
 create mode 100644 applications/solvers/faTwoLayerAvalancheFoam/PFcreateFaFields.H
 create mode 100644 applications/solvers/faTwoLayerAvalancheFoam/PFcreateSubmodels.H
 create mode 100644 applications/solvers/faTwoLayerAvalancheFoam/PFreadTransportProperties.H
 create mode 100644 applications/solvers/faTwoLayerAvalancheFoam/SHcreateFaFields.H
 create mode 100644 applications/solvers/faTwoLayerAvalancheFoam/SHcreateSubmodels.H
 create mode 100644 applications/solvers/faTwoLayerAvalancheFoam/SHreadTransportProperties.H
 create mode 100644 applications/solvers/faTwoLayerAvalancheFoam/advanceDenseFlowModel.H
 create mode 100644 applications/solvers/faTwoLayerAvalancheFoam/advancePowderCloudModel.H
 create mode 100644 applications/solvers/faTwoLayerAvalancheFoam/calcBasalstress.H
 create mode 100644 applications/solvers/faTwoLayerAvalancheFoam/faTwoLayerAvalancheFoam.C
 create mode 100644 applications/solvers/faTwoLayerAvalancheFoam/readSolutionControls.H
 create mode 100644 applications/solvers/faTwoLayerAvalancheFoam/surfaceCourantNo1.H
 create mode 100644 applications/solvers/faTwoLayerAvalancheFoam/surfaceCourantNo2.H
 create mode 100644 src/avalanche/coupling/couplingInertial/couplingInertial.C
 create mode 100644 src/avalanche/coupling/couplingInertial/couplingInertial.H
 create mode 100644 src/avalanche/coupling/couplingModel/couplingModel.C
 create mode 100644 src/avalanche/coupling/couplingModel/couplingModel.H
 create mode 100644 src/avalanche/coupling/couplingModel/couplingModelNew.C

diff --git a/applications/solvers/faTwoLayerAvalancheFoam/Make/files b/applications/solvers/faTwoLayerAvalancheFoam/Make/files
new file mode 100644
index 0000000..e1f1c5c
--- /dev/null
+++ b/applications/solvers/faTwoLayerAvalancheFoam/Make/files
@@ -0,0 +1,3 @@
+faTwoLayerAvalancheFoam.C
+
+EXE = $(FOAM_MODULE_APPBIN)/faTwoLayerAvalancheFoam
diff --git a/applications/solvers/faTwoLayerAvalancheFoam/Make/options b/applications/solvers/faTwoLayerAvalancheFoam/Make/options
new file mode 100644
index 0000000..8e8eb40
--- /dev/null
+++ b/applications/solvers/faTwoLayerAvalancheFoam/Make/options
@@ -0,0 +1,21 @@
+sinclude $(GENERAL_RULES)/module-path-user
+
+/* Failsafe - user location */
+ifeq (,$(strip $(FOAM_MODULE_APPBIN)))
+    FOAM_MODULE_APPBIN = $(FOAM_USER_APPBIN)
+endif
+ifeq (,$(strip $(FOAM_MODULE_LIBBIN)))
+    FOAM_MODULE_LIBBIN = $(FOAM_USER_LIBBIN)
+endif
+
+EXE_INC = \
+    -I$(LIB_SRC)/finiteArea/lnInclude \
+    -I$(LIB_SRC)/finiteVolume/lnInclude \
+    -I$(LIB_SRC)/meshTools/lnInclude \
+    -I$(LIB_SRC)/cfdTools/general/lnInclude \
+    -I../../../src/avalanche/lnInclude
+
+EXE_LIBS = \
+    -lfiniteArea \
+    -lfiniteVolume \
+    -L$(FOAM_LIBBIN) -lfaAvalanche
diff --git a/applications/solvers/faTwoLayerAvalancheFoam/PFcreateFaFields.H b/applications/solvers/faTwoLayerAvalancheFoam/PFcreateFaFields.H
new file mode 100644
index 0000000..51a1b7e
--- /dev/null
+++ b/applications/solvers/faTwoLayerAvalancheFoam/PFcreateFaFields.H
@@ -0,0 +1,166 @@
+    Info << "Reading field h2" << endl;
+    areaScalarField h2
+    (
+        IOobject
+        (
+            "h2",
+            runTime.timeName(),
+            mesh,
+            IOobject::MUST_READ,
+            IOobject::AUTO_WRITE
+        ),
+        aMesh
+    );
+
+
+    Info << "Reading field Us2" << endl;
+    areaVectorField Us2
+    (
+        IOobject
+        (
+            "Us2",
+            runTime.timeName(),
+            mesh,
+            IOobject::MUST_READ,
+            IOobject::AUTO_WRITE
+        ),
+        aMesh
+    );
+
+    areaVectorField UsDiff
+    (
+        IOobject
+        (
+            "UsDiff",
+            runTime.timeName(),
+            mesh,
+            IOobject::NO_READ,
+            IOobject::NO_WRITE
+        ),
+        Us2-Us1
+    );
+
+    Info << "Reading field c2" << endl;
+    areaScalarField c2
+    (
+        IOobject
+        (
+            "c2",
+            runTime.timeName(),
+            mesh,
+            IOobject::MUST_READ,
+            IOobject::AUTO_WRITE
+        ),
+        aMesh
+    );
+
+    areaScalarField powderLayerAlone
+    (
+        IOobject
+        (
+            "powderLayerAlone",
+            runTime.timeName(),
+            mesh,
+            IOobject::NO_READ,
+            IOobject::AUTO_WRITE
+        ),
+        aMesh,
+        dimensionedScalar(dimless)
+    );
+
+    edgeScalarField phis2
+    (
+        IOobject
+        (
+            "phis2",
+            runTime.timeName(),
+            mesh,
+            IOobject::READ_IF_PRESENT,
+            IOobject::AUTO_WRITE
+        ),
+        fac::interpolate(Us2) & aMesh.Le()
+    );
+
+    edgeScalarField phi2s2
+    (
+        IOobject
+        (
+            "phi2s2",
+            runTime.timeName(),
+            mesh,
+            IOobject::READ_IF_PRESENT,
+            IOobject::AUTO_WRITE
+        ),
+        fac::interpolate(h2*Us2) & aMesh.Le()
+    );
+
+    areaScalarField geff
+    (
+        IOobject
+        (
+            "geff",
+            runTime.timeName(),
+            mesh,
+            IOobject::NO_READ,
+            IOobject::AUTO_WRITE
+        ),
+        gn
+    );
+
+    areaScalarField Sdp
+    (
+        IOobject
+        (
+            "Sdp",
+            runTime.timeName(),
+            mesh,
+            IOobject::NO_READ,
+            IOobject::AUTO_WRITE
+        ),
+        aMesh,
+        dimensionedScalar(dimVelocity)
+    );
+
+    areaScalarField Spd
+    (
+        IOobject
+        (
+            "Spd",
+            runTime.timeName(),
+            mesh,
+            IOobject::NO_READ,
+            IOobject::AUTO_WRITE
+        ),
+        aMesh,
+        dimensionedScalar(dimVelocity)
+    );
+
+    areaScalarField Sap
+    (
+        IOobject
+        (
+            "Sap",
+            runTime.timeName(),
+            mesh,
+            IOobject::NO_READ,
+            IOobject::NO_WRITE
+        ),
+        aMesh,
+        dimensionedScalar(dimVelocity)
+    );
+
+    areaVectorField tau2
+    (
+        IOobject
+        (
+            "tau2",
+            runTime.timeName(),
+            mesh,
+            IOobject::NO_READ,
+            IOobject::AUTO_WRITE
+        ),
+        aMesh,
+        dimensionedVector(dimPressure/dimDensity)
+    );
+
+    Info << endl;
diff --git a/applications/solvers/faTwoLayerAvalancheFoam/PFcreateSubmodels.H b/applications/solvers/faTwoLayerAvalancheFoam/PFcreateSubmodels.H
new file mode 100644
index 0000000..9440ca5
--- /dev/null
+++ b/applications/solvers/faTwoLayerAvalancheFoam/PFcreateSubmodels.H
@@ -0,0 +1,24 @@
+autoPtr<suspensionFrictionModel> friction2
+(
+    suspensionFrictionModel::New(transportProperties2, UsDiff, h2, c2)
+);
+
+autoPtr<suspensionEntrainmentModel> entrainment2
+(
+    suspensionEntrainmentModel::New(transportProperties2, Us2, h2, hentrain, c2, tau2)
+);
+
+autoPtr<ambientEntrainmentModel> ambientEntrainment2
+(
+    ambientEntrainmentModel::New(transportProperties2, Us2, h2, c2)
+);
+
+autoPtr<suspensionDepositionModel> sedimentation2
+(
+    suspensionDepositionModel::New(transportProperties2, Us2, h2, c2, tau2)
+);
+
+autoPtr<couplingModel> coupling
+(
+    couplingModel::New(transportProperties, Us1, h1, pb1, Us2, h2, c2)
+);
diff --git a/applications/solvers/faTwoLayerAvalancheFoam/PFreadTransportProperties.H b/applications/solvers/faTwoLayerAvalancheFoam/PFreadTransportProperties.H
new file mode 100644
index 0000000..8e50736
--- /dev/null
+++ b/applications/solvers/faTwoLayerAvalancheFoam/PFreadTransportProperties.H
@@ -0,0 +1,40 @@
+dictionary transportProperties2 = transportProperties.subDict("powderCloud");
+
+dimensionedScalar hmin2("hmin", dimLength, transportProperties2);
+
+dimensionedScalar cmin2("cmin", dimless, transportProperties2);
+
+dimensionedScalar h02("h0", dimLength, transportProperties2);
+
+dimensionedScalar u02("u0", dimVelocity, transportProperties2);
+
+dimensionedScalar xi2("xi", dimless, transportProperties2);
+
+dimensionedScalar R2("R", dimless, transportProperties2);
+
+dimensionedScalar Ds2("Ds", dimLength, transportProperties2);
+
+dimensionedScalar nu2("nu", dimViscosity, transportProperties2);
+
+dimensionedScalar packingd2("phi0", dimless, transportProperties2);
+
+dimensionedScalar hwem2("hwem", dimLength, transportProperties2);
+
+dimensionedScalar hentmin2("hentmin", dimLength, transportProperties2);
+
+Switch waterSink2(transportProperties2.getOrDefault<Switch>("waterSink", false));
+
+Switch bindHeight2(transportProperties.getOrDefault<Switch>("bindHeight", true));
+
+Switch correctMomentum2(transportProperties.getOrDefault<Switch>("correctMomentum", true));
+
+Info << "Running powder cloud with" << endl
+     << "    xi2 " << xi1 << endl
+     << "    hwem " << hwem2 << endl
+     << "    hmin " << hmin2 << endl
+     << "    cmin " << cmin2 << endl
+     << "    h0   " << h02 << endl
+     << "    u0   " << u02 << endl
+     << "    waterSink is " << waterSink2 << endl
+     << "    bindHeight is " << bindHeight2 << endl << endl;
+
diff --git a/applications/solvers/faTwoLayerAvalancheFoam/SHcreateFaFields.H b/applications/solvers/faTwoLayerAvalancheFoam/SHcreateFaFields.H
new file mode 100644
index 0000000..8daefd0
--- /dev/null
+++ b/applications/solvers/faTwoLayerAvalancheFoam/SHcreateFaFields.H
@@ -0,0 +1,128 @@
+    Info << "Reading field h1" << endl;
+    areaScalarField h1
+    (
+        IOobject
+        (
+            "h1",
+            runTime.timeName(),
+            mesh,
+            IOobject::MUST_READ,
+            IOobject::AUTO_WRITE
+        ),
+        aMesh
+    );
+
+
+    Info << "Reading field Us1" << endl;
+    areaVectorField Us1
+    (
+        IOobject
+        (
+            "Us1",
+            runTime.timeName(),
+            mesh,
+            IOobject::MUST_READ,
+            IOobject::AUTO_WRITE
+        ),
+        aMesh
+    );
+
+
+    edgeScalarField phis1
+    (
+        IOobject
+        (
+            "phis1",
+            runTime.timeName(),
+            mesh,
+            IOobject::READ_IF_PRESENT,
+            IOobject::AUTO_WRITE
+        ),
+        fac::interpolate(Us1) & aMesh.Le()
+    );
+
+
+    edgeScalarField phi2s1
+    (
+        IOobject
+        (
+            "phi2s1",
+            runTime.timeName(),
+            mesh,
+            IOobject::READ_IF_PRESENT,
+            IOobject::AUTO_WRITE
+        ),
+        fac::interpolate(h1*Us1) & aMesh.Le()
+    );
+
+
+    areaVectorField n = aMesh.faceAreaNormals();
+
+    areaScalarField gn
+    (
+        IOobject
+        (
+            "gn",
+            runTime.timeName(),
+            mesh,
+            IOobject::NO_READ,
+            IOobject::NO_WRITE
+        ),
+        g & n
+    );
+
+    areaVectorField gs
+    (
+        IOobject
+        (
+            "gs",
+            runTime.timeName(),
+            mesh,
+            IOobject::NO_READ,
+            IOobject::NO_WRITE
+        ),
+        g - gn*n
+    );
+
+    areaScalarField pb1
+    (
+        IOobject
+        (
+            "pb",
+            runTime.timeName(),
+            mesh,
+            IOobject::NO_READ,
+            IOobject::AUTO_WRITE
+        ),
+        aMesh,
+        dimensionedScalar(dimPressure)
+    );
+
+    areaVectorField tau1
+    (
+        IOobject
+        (
+            "tau",
+            runTime.timeName(),
+            mesh,
+            IOobject::NO_READ,
+            IOobject::AUTO_WRITE
+        ),
+        aMesh,
+        dimensionedVector(dimPressure)
+    );
+
+    //entrainment height
+    areaScalarField hentrain
+    (
+        IOobject
+        (
+            "hentrain",
+            runTime.timeName(),
+            mesh,
+            IOobject::READ_IF_PRESENT,
+            IOobject::AUTO_WRITE
+        ),
+        aMesh,
+        dimensionedScalar(dimLength)
+    );
diff --git a/applications/solvers/faTwoLayerAvalancheFoam/SHcreateSubmodels.H b/applications/solvers/faTwoLayerAvalancheFoam/SHcreateSubmodels.H
new file mode 100644
index 0000000..e615796
--- /dev/null
+++ b/applications/solvers/faTwoLayerAvalancheFoam/SHcreateSubmodels.H
@@ -0,0 +1,14 @@
+autoPtr<frictionModel> friction1
+(
+    frictionModel::New(transportProperties1, Us1, h1, pb1)
+);
+
+autoPtr<entrainmentModel> entrainment1
+(
+    entrainmentModel::New(transportProperties1, Us1, h1, hentrain, pb1, tau1)
+);
+
+autoPtr<depositionModel> deposition1
+(
+    depositionModel::New(transportProperties1, Us1, h1, hentrain, pb1, tau1)
+);
diff --git a/applications/solvers/faTwoLayerAvalancheFoam/SHreadTransportProperties.H b/applications/solvers/faTwoLayerAvalancheFoam/SHreadTransportProperties.H
new file mode 100644
index 0000000..3c2dd0c
--- /dev/null
+++ b/applications/solvers/faTwoLayerAvalancheFoam/SHreadTransportProperties.H
@@ -0,0 +1,33 @@
+
+IOdictionary transportProperties
+(
+    IOobject
+    (
+        "transportProperties",
+        runTime.constant(),
+        mesh,
+        IOobject::MUST_READ,
+        IOobject::NO_WRITE
+    )
+);
+
+dictionary transportProperties1 = transportProperties.subDict("denseFlow");
+
+dimensionedScalar hmin1("hmin", dimLength, transportProperties1);
+
+dimensionedScalar xi1("xi", dimless, transportProperties1);
+
+dimensionedScalar xit1("xit", dimless, transportProperties1);
+
+dimensionedScalar packingd("phi", dimless, transportProperties1);
+
+Switch pressureFeedback1(transportProperties1.get<Switch>("pressureFeedback"));
+
+Switch bindHeight1(transportProperties1.getOrDefault<Switch>("bindHeight", true));
+
+Info << "Running dense flow with" << endl
+     << "    xi " << xi1 << endl
+     << "    xi_t" << xit1 << endl
+     << "    hmin " << hmin1 << endl
+     << "    pressureFeedback is " << pressureFeedback1 << endl
+     << "    bindHeight is " << bindHeight1 << endl << endl;
diff --git a/applications/solvers/faTwoLayerAvalancheFoam/advanceDenseFlowModel.H b/applications/solvers/faTwoLayerAvalancheFoam/advanceDenseFlowModel.H
new file mode 100644
index 0000000..629bef8
--- /dev/null
+++ b/applications/solvers/faTwoLayerAvalancheFoam/advanceDenseFlowModel.H
@@ -0,0 +1,75 @@
+#include "calcBasalstress.H"
+
+const areaVectorField & tauSc1 = friction1->tauSc();
+const areaScalarField & tauSp1 = friction1->tauSp();
+
+faVectorMatrix Us1Eqn
+(
+    fam::ddt(h1, Us1)
+  + xi1*fam::div(phi2s1, Us1)
+  + tauSc1
+  + fam::Sp(tauSp1, Us1)
+ ==
+    gs*h1
+  - fac::grad(pb1*h1/(2.*friction1->rho()))
+  - fam::Sp
+    (
+        xit1*Sdp/packingd,
+        Us1
+    )
+  + Spd*Us2
+);
+
+
+if (!final)
+    Us1Eqn.relax();
+
+SolverPerformance<vector> Us1Residual = solve(Us1Eqn);
+
+tau1 = (tauSc1 + tauSp1*Us1)*friction1->rho();
+phis1 = (fac::interpolate(Us1) & aMesh.Le());
+
+const areaScalarField & Sm1 = entrainment1->Sm();
+const areaScalarField & Sd1 = deposition1->Sd();
+
+
+h1.storePrevIter();
+
+faScalarMatrix h1Eqn
+(
+    fam::ddt(h1)
+  + fam::div(phis1, h1)
+ ==
+    Sm1/(hentrain+dimensionedScalar("hentrainmin", dimLength, 1e-5))*hentrain
+  - fam::Sp
+    (
+        Sd1/(h1 + dimensionedScalar("small", dimLength, 1e-5)),
+        h1
+    )
+  + Spd/(c2+dimensionedScalar("cmin", dimless, 1e-7))*c2/packingd
+  - fam::Sp
+    (
+        Sdp/(h1 + dimensionedScalar("small", dimLength, 1e-5))/packingd,
+        h1
+    )
+);
+
+solverPerformance h1Residual = h1Eqn.solve();
+
+phi2s1 = h1Eqn.flux();
+
+if (!final)
+    h1.relax();
+
+
+// Bind h
+if (bindHeight1)
+{
+    h1 = max
+    (
+        h1,
+        hmin1
+    );
+}
+h1.correctBoundaryConditions();
+Us1.correctBoundaryConditions();
diff --git a/applications/solvers/faTwoLayerAvalancheFoam/advancePowderCloudModel.H b/applications/solvers/faTwoLayerAvalancheFoam/advancePowderCloudModel.H
new file mode 100644
index 0000000..1ac657e
--- /dev/null
+++ b/applications/solvers/faTwoLayerAvalancheFoam/advancePowderCloudModel.H
@@ -0,0 +1,114 @@
+
+UsDiff = Us2-Us1;
+
+const areaVectorField & tauSc2 = friction2->tauSc();
+const areaScalarField & tauSp2 = friction2->tauSp();
+
+geff = max(gn - (fac::ndiv(phis2, Us2)&n), dimensionedScalar(dimVelocity/dimTime));
+
+//Parker et al. (1986), Eq. (5)
+faVectorMatrix Us2Eqn
+(
+    fam::ddt(h2, Us2)
+  + xi2*fam::div(phi2s2, Us2)
+  + tauSc2                // ustar**2 expl. part
+  - tauSp2*Us1            // ustar**2 expl. part
+  + fam::Sp(tauSp2, Us2)  // ustar**2 impl. part
+  ==
+    R2*gs*c2*h2
+  - 1./2.*R2*fac::grad(geff*c2*h2*h2)
+  + xit1*Sdp*Us1*(1.+R2)
+  - fam::Sp
+    (
+        Spd,
+        Us2
+    )
+);
+
+
+if (correctMomentum2)
+{
+    Us2Eqn += R2*c2*fam::ddt(h2, Us2)
+            + R2*h2*Us2*fac::ddt(c2)
+            + xi2*R2*fam::div(fac::interpolate(c2)*phi2s2, Us2);
+}
+
+if (!final)
+    Us2Eqn.relax();
+
+SolverPerformance<vector> Us2Residual = solve(Us2Eqn);
+
+Us2.correctBoundaryConditions();
+
+phis2 = (fac::interpolate(Us2) & aMesh.Le());
+tau2 = tauSc2 + tauSp2*Us2;
+
+//Calculate sediment entrainment
+areaScalarField Sm2 = entrainment2->Sm();
+
+//no entrainment if there is not a minimal flow thickness and not if the dense flow layer is below
+Sm2 = Sm2*pos(h2-hentmin2)*powderLayerAlone;
+Sm2 = min(Sm2, hentrain/runTime.deltaT());
+
+//Calculate ambient fluid entrainment
+Sap = ambientEntrainment2->Sm();
+Sap = Sap*pos(hwem2-h2); //clip entrainment if maximum water height is reached
+
+//Additional sink term for water
+const areaScalarField vs(R2*geff*Ds2*Ds2/18./nu2); //Terminal velocity
+const areaScalarField dw(pos(h2-hmin2*2.)*vs);
+
+
+//Parker et al. (1986), Eq. (3)
+faScalarMatrix h2Eqn
+(
+    fam::ddt(h2)
+  + fam::div(phis2, h2)
+  ==
+    Sap
+  + Sdp/(h1 + dimensionedScalar("small", dimLength, 1e-5)) * h1 / packingd2
+  - fam::Sp
+    (
+        Spd/(h2 + dimensionedScalar("small", dimLength, 1e-5)) / packingd2,
+        h2
+    )
+);
+
+if (waterSink2)
+{
+    h2Eqn -= fam::Sp(dw/(h2+dimensionedScalar("hmin", dimLength, 1e-5)),h2);
+}
+
+solverPerformance h2Residual = h2Eqn.solve();
+
+phi2s2 = h2Eqn.flux();
+
+if (!final)
+{
+     h2.relax();
+}
+
+if (bindHeight2)
+{
+    h2 = max(h2, hmin2);
+}
+
+
+faScalarMatrix c2Eqn
+(
+    fam::ddt(h2, c2)
+  + fam::div(phi2s2, c2)
+ ==
+    Sm2/(hentrain+dimensionedScalar("hentrainmin", dimLength, 1e-5))*hentrain
+  + Sdp/(h1 + dimensionedScalar("small", dimLength, 1e-5)) * h1
+  - fam::Sp(Spd/(c2+dimensionedScalar("cmin", dimless, 1e-7)), c2)
+);
+
+if(!final)
+    c2Eqn.relax();
+
+solverPerformance c2Residual = c2Eqn.solve();
+
+if (bindHeight2)
+    c2 = max(c2, cmin2);
+
diff --git a/applications/solvers/faTwoLayerAvalancheFoam/calcBasalstress.H b/applications/solvers/faTwoLayerAvalancheFoam/calcBasalstress.H
new file mode 100644
index 0000000..233375b
--- /dev/null
+++ b/applications/solvers/faTwoLayerAvalancheFoam/calcBasalstress.H
@@ -0,0 +1,13 @@
+
+pb1 = -friction1->rho()*xi1*fac::ndiv(phi2s1, Us1)&n;
+
+if (pressureFeedback1)
+{
+    pb1 -= fac::ngrad(pb1*h1/2.)&n;
+}
+
+pb1 += friction1->rho()*(g*h1)&n;
+
+// Clipping
+pb1 = Foam::max(pb1, dimensionedScalar(dimPressure));
+
diff --git a/applications/solvers/faTwoLayerAvalancheFoam/faTwoLayerAvalancheFoam.C b/applications/solvers/faTwoLayerAvalancheFoam/faTwoLayerAvalancheFoam.C
new file mode 100644
index 0000000..97e693b
--- /dev/null
+++ b/applications/solvers/faTwoLayerAvalancheFoam/faTwoLayerAvalancheFoam.C
@@ -0,0 +1,245 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     | avalanche module
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2022 Matthias Rauter
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Application
+    faSavageHutterFoam
+
+Description
+    A depth-integrated solver for mixed snow avalanches.
+    The dense flow is described with the Savage-Hutter model.
+    The powder cloud is described with the Parker-Fukushima model.
+    The coupling model is new.
+
+    Publication in preparation.
+
+Author
+    Matthias Rauter matthias@rauter.it
+
+\*---------------------------------------------------------------------------*/
+
+#include "fvCFD.H"
+#include "faCFD.H"
+#include "frictionModel.H"
+#include "entrainmentModel.H"
+#include "depositionModel.H"
+#include "suspensionFrictionModel.H"
+#include "suspensionEntrainmentModel.H"
+#include "ambientEntrainmentModel.H"
+#include "suspensionDepositionModel.H"
+#include "couplingModel.H"
+#include "SolverPerformance.H"
+
+#define CREATE_FIELDS SHcreateFaFields.H
+#define CREATE_FIELDS_2 PFcreateFaFields.H
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+int main(int argc, char *argv[])
+{
+    argList::addNote
+    (
+        "(avalanche)\n"
+        "A depth-integrated solver for mixed snow avalanches using"
+        " Finite Area Methods."
+    );
+
+    #include "postProcessFA.H"
+
+    #include "setRootCase.H"
+    #include "createTime.H"
+    #include "createMesh.H"
+    #include "createFaMesh.H"
+    #include "readGravitationalAcceleration.H"
+
+    #include "SHreadTransportProperties.H"
+    #include "PFreadTransportProperties.H"
+
+    #include "SHcreateFaFields.H"
+    #include "PFcreateFaFields.H"
+
+    #include "SHcreateSubmodels.H"
+    #include "PFcreateSubmodels.H"
+
+    #include "createTimeControls.H"
+
+    Info<< "\nStarting time loop\n" << endl;
+
+    #include "readSolutionControls.H"
+
+    Info<< nl
+        << "Numerical settings" << nl
+        << "    max number of iterations " << nCorr << nl
+        << "    min number of iterations " << minCorr << nl
+        << "    TOL h " << hResidualMax << nl
+        << "    TOL Us " << UsResidualMax << nl 
+        << "    TOL c " << cResidualMax << nl << endl;
+
+    const bool initDeltaT = runTime.controlDict().get<bool>("initDeltaT");
+
+    if (initDeltaT)
+    {
+        Info<< "Initializing Delta T" << endl;
+        #include "readTimeControls.H"
+        #include "surfaceCourantNo1.H"
+        #include "surfaceCourantNo2.H"
+
+        runTime.setDeltaT
+        (
+            min(maxCo/(max(CoNum1, CoNum2) + SMALL)*runTime.deltaT().value(), maxDeltaT)
+        );
+    }
+
+    bool final = false;
+
+    while (runTime.run())
+    {
+        #include "readSolutionControls.H"
+        #include "readTimeControls.H"
+        #include "surfaceCourantNo1.H"
+        #include "surfaceCourantNo2.H"
+
+        scalar CoNum = max(CoNum1, CoNum2);
+        #include "setDeltaT.H"
+
+        runTime++;
+
+        Info<< "Time = " << runTime.timeName() << nl << endl;
+        final = false;
+
+        for (int iCorr = 0; ; iCorr++)
+        {
+
+            //Calculate coupling fluxes
+            Sdp = coupling->Sdp();
+            Sdp = min(Sdp, h1/runTime.deltaT());
+
+            //Calculate sedimation rate
+            Spd = sedimentation2->Sd();
+            Spd = min(Spd, h2*c2/runTime.deltaT());
+
+            //Info << "Upward mass flux = [" << min(Sdp).value() << ", " << max(Sdp).value() << "]" << endl;
+            //Info << "Upward momentum flux = [" << min(Sdp*mag(Us1)).value() << ", " << max(Sdp*mag(Us1)).value() << "]" << endl;
+
+            powderLayerAlone = pos(hmin1*3-h1);
+
+            #include "advanceDenseFlowModel.H"
+
+            #include "advancePowderCloudModel.H"
+
+            //Keep track of intact snow cover
+            faScalarMatrix hentrainEqn
+            (
+                fam::ddt(hentrain)
+              ==
+                Sd1
+                - fam::Sp
+                (
+                    Sm1/(hentrain + dimensionedScalar("small", dimLength, SMALL)),
+                    hentrain
+                )
+              - fam::Sp(
+                    Sm2/(hentrain+dimensionedScalar("small", dimLength, SMALL))/packingd,
+                    hentrain)
+
+            );
+
+            hentrainEqn.solve();
+
+
+
+            if (final)
+            {
+                if
+                (
+                    h1Residual.initialResidual() < hResidualMax
+                 && mag(Us1Residual.initialResidual()) < UsResidualMax
+                 && h2Residual.initialResidual() < hResidualMax
+                 && mag(Us2Residual.initialResidual()) < UsResidualMax
+                 && c2Residual.initialResidual() < cResidualMax
+                )
+                {
+                    Info<< "Layer 1: Reached residual in h1 = "
+                        << h1Residual.initialResidual()
+                        << " < " << hResidualMax
+                        << " and in Us1 = "
+                        << Us1Residual.initialResidual()
+                        << " < " << UsResidualMax << endl
+                        << "Layer 2: Reached residual in h2 = "
+                        << h2Residual.initialResidual()
+                        << " < " << hResidualMax
+                        << " and in Us2 = "
+                        << Us2Residual.initialResidual()
+                        << " < " << UsResidualMax
+                        << ", in c2 = "
+                        << c2Residual.initialResidual()
+                        << " < " << cResidualMax << ", " << endl
+                        << "stopping loop!" << endl;
+                }
+                else
+                {
+                    Info<< "Reached maximum numbers of iterations, "
+                        << "stopping loop!" << endl;
+                }
+
+                const scalar Vsl = gSum((hentrain*aMesh.S())())/packingd.value();
+                const scalar Vdl = gSum((h1*aMesh.S())())/packingd.value();
+                const scalar Vpl = gSum((h2*c2*aMesh.S())());
+                Info << "Grain vol. in staionary layer = " << Vsl << endl;
+                Info << "Grain vol. in dense flow layer = " << Vdl << endl;
+                Info << "Grain vol. in powder cloud layer = " << Vpl << endl;
+                Info << "Grain vol. in all layers = " << Vsl + Vdl + Vpl << endl;
+                break;
+            }
+
+            if
+            (
+                (
+                    h1Residual.initialResidual() < hResidualMax
+                 && mag(Us1Residual.initialResidual()) < UsResidualMax
+                 && iCorr >= minCorr
+                )
+             || iCorr >= nCorr
+            )
+            {
+                final = true;
+            }
+        }
+
+        if (runTime.outputTime())
+        {
+            runTime.write();
+        }
+
+        runTime.printExecutionTime(Info);
+    }
+
+
+    Info<< nl << "End" << endl;
+
+    return 0;
+}
+
+
+// ************************************************************************* //
diff --git a/applications/solvers/faTwoLayerAvalancheFoam/readSolutionControls.H b/applications/solvers/faTwoLayerAvalancheFoam/readSolutionControls.H
new file mode 100644
index 0000000..5e537e3
--- /dev/null
+++ b/applications/solvers/faTwoLayerAvalancheFoam/readSolutionControls.H
@@ -0,0 +1,16 @@
+
+    scalar nCorr =
+            aMesh.solutionDict().getOrDefault<int>("nOuterCorrectors", 50);
+
+    scalar minCorr =
+            aMesh.solutionDict().getOrDefault<int>("minCorrectors", 3);
+
+    scalar hResidualMax =
+        aMesh.solutionDict().getOrDefault<scalar>("hResidualMax", 0.);
+
+    scalar UsResidualMax =
+        aMesh.solutionDict().getOrDefault<scalar>("UsResidualMax", 1);
+
+    scalar cResidualMax =
+        aMesh.solutionDict().getOrDefault<scalar>("cResidualMax", 1);
+
diff --git a/applications/solvers/faTwoLayerAvalancheFoam/surfaceCourantNo1.H b/applications/solvers/faTwoLayerAvalancheFoam/surfaceCourantNo1.H
new file mode 100644
index 0000000..abc0b69
--- /dev/null
+++ b/applications/solvers/faTwoLayerAvalancheFoam/surfaceCourantNo1.H
@@ -0,0 +1,59 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | foam-extend: Open Source CFD
+   \\    /   O peration     | Version:     3.2
+    \\  /    A nd           | Web:         http://www.foam-extend.org
+     \\/     M anipulation  | For copyright notice see file Copyright
+-------------------------------------------------------------------------------
+License
+    This file is part of foam-extend.
+
+    foam-extend is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by the
+    Free Software Foundation, either version 3 of the License, or (at your
+    option) any later version.
+
+    foam-extend is distributed in the hope that it will be useful, but
+    WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with foam-extend.  If not, see <http://www.gnu.org/licenses/>.
+
+Global
+    surfaceCourantNo
+
+Author
+    Hrvoje Jasak, Wikki Ltd.  All rights reserved.
+
+Description
+    Calculates and outputs the mean and maximum Courant Numbers for the
+    Finite Area method.
+
+\*---------------------------------------------------------------------------*/
+
+scalar CoNum1 = 0.0;
+scalar meanCoNum1 = 0.0;
+scalar velMag1 = 0.0;
+if (aMesh.nInternalEdges())
+{
+    edgeScalarField edgeSpeed
+    (
+        (xi1*fac::interpolate(Us1) & aMesh.Le()/aMesh.magLe())
+      + Foam::sqrt(max(dimensionedScalar("gh_min", dimensionSet(0, 2, -2, 0, 0, 0, 0), 0), fac::interpolate(h1)*fac::interpolate(g&n)))
+    );
+
+    velMag1 = max(edgeSpeed.internalField()).value();
+
+    CoNum1 = max(aMesh.edgeInterpolation::deltaCoeffs()*edgeSpeed.internalField()).value()*runTime.deltaT().value();
+
+    meanCoNum1 = sum(aMesh.edgeInterpolation::deltaCoeffs()*edgeSpeed.internalField()*aMesh.magLe()).value()/gSum(aMesh.magLe())*runTime.deltaT().value();
+}
+
+
+Info<< "Layer 1: Courant Number mean: " << meanCoNum1
+    << " max: " << CoNum1
+    << " velocity max magnitude: " << velMag1 << endl;
+
+// ************************************************************************* //
diff --git a/applications/solvers/faTwoLayerAvalancheFoam/surfaceCourantNo2.H b/applications/solvers/faTwoLayerAvalancheFoam/surfaceCourantNo2.H
new file mode 100644
index 0000000..77e64e3
--- /dev/null
+++ b/applications/solvers/faTwoLayerAvalancheFoam/surfaceCourantNo2.H
@@ -0,0 +1,59 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | foam-extend: Open Source CFD
+   \\    /   O peration     | Version:     3.2
+    \\  /    A nd           | Web:         http://www.foam-extend.org
+     \\/     M anipulation  | For copyright notice see file Copyright
+-------------------------------------------------------------------------------
+License
+    This file is part of foam-extend.
+
+    foam-extend is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by the
+    Free Software Foundation, either version 3 of the License, or (at your
+    option) any later version.
+
+    foam-extend is distributed in the hope that it will be useful, but
+    WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with foam-extend.  If not, see <http://www.gnu.org/licenses/>.
+
+Global
+    surfaceCourantNo
+
+Author
+    Hrvoje Jasak, Wikki Ltd.  All rights reserved.
+
+Description
+    Calculates and outputs the mean and maximum Courant Numbers for the
+    Finite Area method.
+
+\*---------------------------------------------------------------------------*/
+
+scalar CoNum2 = 0.0;
+scalar meanCoNum2 = 0.0;
+scalar velMag2 = 0.0;
+if (aMesh.nInternalEdges())
+{
+    edgeScalarField edgeSpeed
+    (
+        (xi2*fac::interpolate(Us2) & aMesh.Le()/aMesh.magLe())
+      + Foam::sqrt(max(dimensionedScalar("gh_min", dimensionSet(0, 2, -2, 0, 0, 0, 0), 0), fac::interpolate(R2*c2*h2)*fac::interpolate(gn)))
+    );
+
+    velMag2 = max(edgeSpeed.internalField()).value();
+
+    CoNum2 = max(aMesh.edgeInterpolation::deltaCoeffs()*edgeSpeed.internalField()).value()*runTime.deltaT().value();
+
+    meanCoNum2 = sum(aMesh.edgeInterpolation::deltaCoeffs()*edgeSpeed.internalField()*aMesh.magLe()).value()/gSum(aMesh.magLe())*runTime.deltaT().value();
+}
+
+
+Info<< "Layer 2: Courant Number mean: " << meanCoNum2
+    << " max: " << CoNum2
+    << " velocity max magnitude: " << velMag2 << endl;
+
+// ************************************************************************* //
diff --git a/src/avalanche/Make/files b/src/avalanche/Make/files
index 52f8cdd..a19a1cb 100644
--- a/src/avalanche/Make/files
+++ b/src/avalanche/Make/files
@@ -1,3 +1,8 @@
+coupling = coupling
+$(coupling)/couplingModel/couplingModel.C
+$(coupling)/couplingModel/couplingModelNew.C
+$(coupling)/couplingInertial/couplingInertial.C
+
 deposition = deposition
 $(deposition)/depositionModel/depositionModel.C
 $(deposition)/depositionModel/depositionModelNew.C
diff --git a/src/avalanche/coupling/couplingInertial/couplingInertial.C b/src/avalanche/coupling/couplingInertial/couplingInertial.C
new file mode 100644
index 0000000..ece4e98
--- /dev/null
+++ b/src/avalanche/coupling/couplingInertial/couplingInertial.C
@@ -0,0 +1,114 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     | avalanche module
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2022 Matthias Rauter
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Author
+    Matthias Rauter matthias@rauter.it
+
+\*---------------------------------------------------------------------------*/
+
+#include "fvCFD.H"
+#include "faCFD.H"
+#include "couplingInertial.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace couplingModels
+{
+    defineTypeNameAndDebug(couplingInertial, 0);
+    addToRunTimeSelectionTable(couplingModel, couplingInertial, dictionary);
+}
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::couplingModels::couplingInertial::couplingInertial
+(
+    const dictionary& couplingProperties,
+    const areaVectorField& Us1,
+    const areaScalarField& h1,
+    const areaScalarField& pb1,
+    const areaVectorField& Us2,
+    const areaScalarField& h2,
+    const areaScalarField& c2
+)
+:
+    couplingModel(type(), couplingProperties, Us1, h1, pb1, Us2, h2, c2),
+    I0_("I0", dimless, coeffDict_),
+    u0_("u0", dimless, coeffDict_),
+    d_("d", dimLength, coeffDict_),
+    rhos_("rhos", dimDensity, coeffDict_),
+    I_
+    (
+        IOobject
+        (
+            "I",
+            Us1_.time().timeName(),
+            Us1_.db(),
+            IOobject::NO_READ,
+            IOobject::AUTO_WRITE
+        ),
+        Us1_.mesh(),
+        dimensionedScalar(dimless)
+    )
+{
+    Info << "    " << u0_ << nl 
+         << "    " << d_ << nl 
+         << "    " << rhos_ << nl << endl;
+}
+
+
+// * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * * //
+
+const Foam::areaScalarField&
+Foam::couplingModels::couplingInertial::Sdp() const
+{
+    //Inertial number at the base, assuming it is constant along flow depth
+    I_ = 5./2.*mag(Us1_)/(h1_+dimensionedScalar(dimLength, 1e-2))*d_*sqrt(rhos_/(pb1_+dimensionedScalar(dimPressure, 1e-2)));
+    Sdp_ = mag(Us1_)*u0_*mag(I_-I0_)*h1_/(h1_+dimensionedScalar(dimLength, 1e-2));
+    return Sdp_;
+}
+
+
+bool Foam::couplingModels::couplingInertial::read
+(
+    const dictionary& couplingProperties
+)
+{
+    readDict(type(), couplingProperties);
+
+    coeffDict_.readEntry("u0", u0_);
+    coeffDict_.readEntry("I0", I0_);
+    coeffDict_.readEntry("d", d_);
+    coeffDict_.readEntry("rhos", rhos_);
+
+    return true;
+}
+
+
+// ************************************************************************* //
diff --git a/src/avalanche/coupling/couplingInertial/couplingInertial.H b/src/avalanche/coupling/couplingInertial/couplingInertial.H
new file mode 100644
index 0000000..cc8fdb7
--- /dev/null
+++ b/src/avalanche/coupling/couplingInertial/couplingInertial.H
@@ -0,0 +1,132 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     | avalanche module
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2022 Matthias Rauter
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::couplingModels::couplingInertial
+Description
+    A coupling model for mixed snow avalanches following the scaling with the
+    Inertial number I.
+
+    Publication in preparation.
+
+SourceFiles
+    couplingInertial.C
+
+Author
+    Matthias Rauter matthias@rauter.it
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef couplingInertial_H
+#define couplingInertial_H
+
+#include "couplingModel.H"
+#include "dimensionedScalar.H"
+#include "volFields.H"
+#include "IOdictionary.H"
+#include "typeInfo.H"
+#include "runTimeSelectionTables.H"
+#include "dimensionedScalar.H"
+#include "tmp.H"
+#include "autoPtr.H"
+#include "faMatrices.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace couplingModels
+{
+
+/*---------------------------------------------------------------------------*\
+                           Class Erosionenergy Declaration
+\*---------------------------------------------------------------------------*/
+
+class couplingInertial
+:
+    public couplingModel
+{
+    // Private data
+
+
+        //- reference inertial number
+        dimensionedScalar I0_;
+
+        //- coupling volume flux at I-I0 = 1
+        dimensionedScalar u0_;
+
+        //- particle diameter for calculation I
+        dimensionedScalar d_;
+
+        //- rhos for calculating I
+        dimensionedScalar rhos_;
+
+        //- Inertial number
+        mutable areaScalarField I_;
+public:
+
+    //- Runtime type information
+    TypeName("couplingInertial");
+
+
+    // Constructors
+
+        //- Construct from components
+        couplingInertial
+        (
+            const dictionary& couplingProperties,
+            const areaVectorField& Us1,
+            const areaScalarField& h1,
+            const areaScalarField& pb1,
+            const areaVectorField& Us2,
+            const areaScalarField& h2,
+            const areaScalarField& c2
+        );
+
+
+    //- Destructor
+    virtual ~couplingInertial() = default;
+
+
+    // Member Functions
+
+        //- Return the volume flux
+        virtual const areaScalarField& Sdp() const;
+
+        //- Read couplingProperties dictionary
+        virtual bool read(const dictionary& couplingProperties);
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace frictionModels
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/avalanche/coupling/couplingModel/couplingModel.C b/src/avalanche/coupling/couplingModel/couplingModel.C
new file mode 100644
index 0000000..5f980c9
--- /dev/null
+++ b/src/avalanche/coupling/couplingModel/couplingModel.C
@@ -0,0 +1,100 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     | avalanche module
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2022 Matthias Rauter
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Author
+    Matthias Rauter matthias@rauter.it
+
+\*---------------------------------------------------------------------------*/
+
+#include "couplingModel.H"
+#include "fvCFD.H"
+#include "faCFD.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    defineTypeNameAndDebug(couplingModel, 0);
+    defineRunTimeSelectionTable(couplingModel, dictionary);
+}
+
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+void Foam::couplingModel::readDict
+(
+    const word& type,
+    const dictionary& dict
+)
+{
+    couplingProperties_ = dict;
+    coeffDict_ = couplingProperties_.optionalSubDict(type + "Coeffs");
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::couplingModel::couplingModel
+(
+    const word& type,
+    const dictionary& couplingProperties,
+    const areaVectorField& Us1,
+    const areaScalarField& h1,
+    const areaScalarField& pb1,
+    const areaVectorField& Us2,
+    const areaScalarField& h2,
+    const areaScalarField& c2
+)
+:
+    couplingProperties_(couplingProperties),
+    coeffDict_
+    (
+        couplingProperties_.optionalSubDict(type + "Coeffs")
+    ),
+    Us1_(Us1),
+    h1_(h1),
+    pb1_(pb1),
+    Us2_(Us2),
+    h2_(h2),
+    c2_(c2),
+    Sdp_
+    (
+        IOobject
+        (
+            "Sdp",
+            Us1_.time().timeName(),
+            Us1_.db(),
+            IOobject::NO_READ,
+            IOobject::NO_WRITE
+        ),
+        Us1_.mesh(),
+        dimensionedScalar(dimVelocity)
+    )
+{
+    Info<< "    with " << endl;
+}
+
+
+// ************************************************************************* //
diff --git a/src/avalanche/coupling/couplingModel/couplingModel.H b/src/avalanche/coupling/couplingModel/couplingModel.H
new file mode 100644
index 0000000..74eb4a8
--- /dev/null
+++ b/src/avalanche/coupling/couplingModel/couplingModel.H
@@ -0,0 +1,210 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     | avalanche module
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2022 Matthias Rauter
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Namespace
+    Foam::couplingModel
+
+Description
+    A namespace for various coupling model implementations for mixed snow
+    avalanches. This model couples two shallow flow layers.
+
+Class
+    Foam::couplingModel
+
+Description
+    An abstract base class for coupling models.
+
+SourceFiles
+    couplingModel.C
+    couplingModelNew.C
+
+Author
+    Matthias Rauter matthias@rauter.it
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef couplingModel_H
+#define couplingModel_H
+
+#include "IOdictionary.H"
+#include "typeInfo.H"
+#include "runTimeSelectionTables.H"
+#include "dimensionedScalar.H"
+#include "tmp.H"
+#include "autoPtr.H"
+#include "faMatrices.H"
+#include "areaFieldsFwd.H"
+#include "FieldFields.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+/*---------------------------------------------------------------------------*\
+                         Class couplingModel Declaration
+\*---------------------------------------------------------------------------*/
+
+class couplingModel
+{
+protected:
+
+    // Protected data
+
+        dictionary couplingProperties_;
+
+        //- Model coefficients dictionary
+        dictionary coeffDict_;
+
+        //- Reference to the surface velocity field
+        const areaVectorField& Us1_;
+
+        //- Reference to the flow height field
+        const areaScalarField& h1_;
+
+        //- Reference to the basal pressure field
+        const areaScalarField& pb1_;
+
+        //- Reference to the surface velocity field
+        const areaVectorField& Us2_;
+
+        //- Reference to the flow height field
+        const areaScalarField& h2_;
+
+        //- Reference to the phase fraction field
+        const areaScalarField& c2_;
+
+        //- Flux term
+        mutable areaScalarField Sdp_;
+
+
+    // Protected Member Functions
+
+        //- Read/update the couplingProperties and coeffDict dictionaries
+        void readDict(const word& type, const dictionary& dict);
+
+
+        //- Disallow copy construct
+        couplingModel(const couplingModel&) = delete;
+
+        //- Disallow default bitwise assignment
+        void operator=(const couplingModel&) = delete;
+
+
+public:
+
+    //- Runtime type information
+    TypeName("couplingModel");
+
+
+    // Declare run-time constructor selection table
+
+#ifndef SWIG
+        declareRunTimeSelectionTable
+        (
+            autoPtr,
+            couplingModel,
+            dictionary,
+            (
+                const dictionary& couplingProperties,
+                const areaVectorField& Us1,
+                const areaScalarField& h1,
+                const areaScalarField& pb1,
+                const areaVectorField& Us2,
+                const areaScalarField& h2,
+                const areaScalarField& c2
+            ),
+            (couplingProperties, Us1, h1, pb1, Us2, h2, c2)
+        );
+#endif
+
+
+    // Selectors
+
+        //- Return a reference to the selected coupling model
+        static autoPtr<couplingModel> New
+        (
+            const dictionary& couplingProperties,
+            const areaVectorField& Us1,
+            const areaScalarField& h1,
+            const areaScalarField& pb1,
+            const areaVectorField& Us2,
+            const areaScalarField& h2,
+            const areaScalarField& c2
+        );
+
+
+    // Constructors
+
+        //- Construct from components
+        couplingModel
+        (
+            const word& type,
+            const dictionary& couplingProperties,
+            const areaVectorField& Us1,
+            const areaScalarField& h1,
+            const areaScalarField& pb1,
+            const areaVectorField& Us2,
+            const areaScalarField& h2,
+            const areaScalarField& c2
+        );
+
+
+    //- Destructor
+    virtual ~couplingModel() = default;
+
+
+    // Member Functions
+
+        //- Read couplingProperties dictionary
+        virtual bool read(const dictionary& couplingProperties) = 0;
+
+        //- Return the coupling properties dictionary
+        const dictionary& couplingProperties() const
+        {
+            return couplingProperties_;
+        }
+
+        //- Const access to the model coefficients dictionary
+        virtual const dictionary& coeffDict() const
+        {
+            return coeffDict_;
+        }
+
+        //- Return the Source by coupling
+        virtual const areaScalarField& Sdp() const = 0;
+
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/avalanche/coupling/couplingModel/couplingModelNew.C b/src/avalanche/coupling/couplingModel/couplingModelNew.C
new file mode 100644
index 0000000..84694c2
--- /dev/null
+++ b/src/avalanche/coupling/couplingModel/couplingModelNew.C
@@ -0,0 +1,70 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     | avalanche module
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2022 Matthias Rauter
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Author
+    Matthias Rauter matthias@rauter.it
+
+\*---------------------------------------------------------------------------*/
+
+#include "couplingModel.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+Foam::autoPtr<Foam::couplingModel> Foam::couplingModel::New
+(
+    const dictionary& dict,
+    const areaVectorField& Us1,
+    const areaScalarField& h1,
+    const areaScalarField& pb1,
+    const areaVectorField& Us2,
+    const areaScalarField& h2,
+    const areaScalarField& c2
+)
+{
+    const word modelName(dict.get<word>("couplingModel"));
+
+    Info<< "Selecting coupling model " << modelName << endl;
+
+    auto cstrIter = dictionaryConstructorTablePtr_->cfind(modelName);
+
+    if (!cstrIter.found())
+    {
+        FatalIOErrorInLookup
+        (
+            dict,
+            "couplingModel",
+            modelName,
+            *dictionaryConstructorTablePtr_
+        ) << exit(FatalIOError);
+    }
+
+    return autoPtr<couplingModel>
+    (
+        cstrIter()(dict, Us1, h1, pb1, Us2, h2, c2)
+    );
+}
+
+
+// ************************************************************************* //
-- 
GitLab