From 3099299d3fb62370f9a304aa6b9fb44d0d9e4e7b Mon Sep 17 00:00:00 2001
From: Kilian Weishaupt <kilian.weishaupt@iws.uni-stuttgart.de>
Date: Tue, 13 Jul 2021 14:26:50 +0200
Subject: [PATCH] [python] Adapt FVProblem and add PorousMediumFlowProblem

---
 dumux/python/CMakeLists.txt                   |   1 +
 dumux/python/common/fvproblem.hh              |  86 ++++++++-----
 dumux/python/porousmediumflow/CMakeLists.txt  |   3 +
 dumux/python/porousmediumflow/problem.hh      | 119 ++++++++++++++++++
 python/dumux/CMakeLists.txt                   |   1 +
 python/dumux/common/__init__.py               |  16 +--
 python/dumux/porousmediumflow/CMakeLists.txt  |   3 +
 python/dumux/porousmediumflow/__init__.py     |  36 ++++++
 test/python/test_explicit_transport_cctpfa.py |   5 +-
 test/python/test_fvproblem.py                 |   4 +-
 10 files changed, 235 insertions(+), 39 deletions(-)
 create mode 100644 dumux/python/porousmediumflow/CMakeLists.txt
 create mode 100644 dumux/python/porousmediumflow/problem.hh
 create mode 100644 python/dumux/porousmediumflow/CMakeLists.txt
 create mode 100644 python/dumux/porousmediumflow/__init__.py

diff --git a/dumux/python/CMakeLists.txt b/dumux/python/CMakeLists.txt
index ee2d01c436..4065a887e9 100644
--- a/dumux/python/CMakeLists.txt
+++ b/dumux/python/CMakeLists.txt
@@ -3,3 +3,4 @@ add_subdirectory(common)
 add_subdirectory(discretization)
 add_subdirectory(io)
 add_subdirectory(material)
+add_subdirectory(porousmediumflow)
diff --git a/dumux/python/common/fvproblem.hh b/dumux/python/common/fvproblem.hh
index 5da3adc737..d4f9c8285c 100644
--- a/dumux/python/common/fvproblem.hh
+++ b/dumux/python/common/fvproblem.hh
@@ -42,12 +42,11 @@ namespace Dumux::Python {
  * \ingroup Common
  * \brief A C++ wrapper for a Python problem
  */
-template<class GridGeometry_, class PrimaryVariables, class SpatialParams_>
+template<class GridGeometry_, class PrimaryVariables, bool enableInternalDirichletConstraints_>
 class FVProblem
 {
 public:
     using GridGeometry = GridGeometry_;
-    using SpatialParams = SpatialParams_;
     using Scalar = typename PrimaryVariables::value_type;
     using NumEqVector = Dune::FieldVector<Scalar, PrimaryVariables::dimension>;
     using Element = typename GridGeometry::GridView::template Codim<0>::Entity;
@@ -61,25 +60,37 @@ public:
     using BoundaryTypes = Dumux::BoundaryTypes<PrimaryVariables::dimension>;
 
     FVProblem(std::shared_ptr<const GridGeometry> gridGeometry,
-              std::shared_ptr<const SpatialParams> spatialParams,
               pybind11::object pyProblem)
     : gridGeometry_(gridGeometry)
-    , spatialParams_(spatialParams)
     , pyProblem_(pyProblem)
-    {}
-
-    std::string name() const
+    , name_("python_problem")
+    , paramGroup_("")
     {
-        return pyProblem_.attr("name").template cast<std::string>();
+        if (pybind11::hasattr(pyProblem_, "name"))
+            name_ = pyProblem.attr("name")().template cast<std::string>();
+
+        if (pybind11::hasattr(pyProblem_, "paramGroup"))
+            paramGroup_ = pyProblem.attr("paramGroup")().template cast<std::string>();
     }
 
+    const std::string& name() const
+    { return name_; }
+
+    const std::string& paramGroup() const
+    { return paramGroup_; }
+
     BoundaryTypes boundaryTypes(const Element &element,
                                 const SubControlVolume &scv) const
     {
         if constexpr (!isBox)
             DUNE_THROW(Dune::InvalidStateException, "boundaryTypes(..., scv) called for cell-centered method.");
         else
-            return pyProblem_.attr("boundaryTypes")(element, scv).template cast<BoundaryTypes>();
+        {
+            if (pybind11::hasattr(pyProblem_, "boundaryTypes"))
+                return pyProblem_.attr("boundaryTypes")(element, scv).template cast<BoundaryTypes>();
+            else
+                return pyProblem_.attr("boundaryTypesAtPos")(scv.dofPosition()).template cast<BoundaryTypes>();
+        }
     }
 
     BoundaryTypes boundaryTypes(const Element &element,
@@ -88,7 +99,12 @@ public:
         if constexpr (isBox)
             DUNE_THROW(Dune::InvalidStateException, "boundaryTypes(..., scvf) called for box method.");
         else
-            return pyProblem_.attr("boundaryTypes")(element, scvf).template cast<BoundaryTypes>();
+        {
+            if (pybind11::hasattr(pyProblem_, "boundaryTypes"))
+                return pyProblem_.attr("boundaryTypes")(element, scvf).template cast<BoundaryTypes>();
+            else
+                return pyProblem_.attr("boundaryTypesAtPos")(scvf.ipGlobal()).template cast<BoundaryTypes>();
+        }
     }
 
     PrimaryVariables dirichlet(const Element &element,
@@ -97,7 +113,12 @@ public:
         if constexpr (!isBox)
             DUNE_THROW(Dune::InvalidStateException, "dirichlet(scv) called for cell-centered method.");
         else
-            return pyProblem_.attr("dirichlet")(element, scv).template cast<PrimaryVariables>();
+        {
+            if (pybind11::hasattr(pyProblem_, "dirichlet"))
+                return pyProblem_.attr("dirichlet")(element, scv).template cast<PrimaryVariables>();
+            else
+                return pyProblem_.attr("dirichletAtPos")(scv.dofPosition()).template cast<PrimaryVariables>();
+        }
     }
 
     PrimaryVariables dirichlet(const Element &element,
@@ -106,7 +127,12 @@ public:
         if constexpr (isBox)
             DUNE_THROW(Dune::InvalidStateException, "dirichlet(scvf) called for box method.");
         else
-            return pyProblem_.attr("dirichlet")(element, scvf).template cast<PrimaryVariables>();
+        {
+            if (pybind11::hasattr(pyProblem_, "dirichlet"))
+                return pyProblem_.attr("dirichlet")(element, scvf).template cast<PrimaryVariables>();
+            else
+                return pyProblem_.attr("dirichletAtPos")(scvf.ipGlobal()).template cast<PrimaryVariables>();
+        }
     }
 
     template<class ElementVolumeVariables, class ElementFluxVariablesCache>
@@ -116,7 +142,10 @@ public:
                         const ElementFluxVariablesCache& elemFluxVarsCache,
                         const SubControlVolumeFace& scvf) const
     {
-        return pyProblem_.attr("neumann")(element, fvGeometry, scvf).template cast<NumEqVector>();
+        if (pybind11::hasattr(pyProblem_, "neumann"))
+            return pyProblem_.attr("neumann")(element, fvGeometry, scvf).template cast<NumEqVector>();
+        else
+            return pyProblem_.attr("neumannAtPos")(scvf.ipGlobal()).template cast<NumEqVector>();
     }
 
     template<class ElementVolumeVariables>
@@ -125,7 +154,10 @@ public:
                        const ElementVolumeVariables& elemVolVars,
                        const SubControlVolume &scv) const
     {
-        return pyProblem_.attr("source")(element, fvGeometry, scv).template cast<NumEqVector>();
+        if (pybind11::hasattr(pyProblem_, "source"))
+            return pyProblem_.attr("source")(element, fvGeometry, scv).template cast<NumEqVector>();
+        else
+            return pyProblem_.attr("sourceAtPos")(scv.dofPosition()).template cast<NumEqVector>();
     }
 
     NumEqVector sourceAtPos(const GlobalPosition &globalPos) const
@@ -139,7 +171,10 @@ public:
                                 const ElementVolumeVariables& elemVolVars,
                                 const SubControlVolume& scv) const
     {
-        return pyProblem_.attr("scvPointSources")(element, fvGeometry, scv).template cast<NumEqVector>();
+        if (pybind11::hasattr(pyProblem_, "scvPointSources"))
+            return pyProblem_.attr("scvPointSources")(element, fvGeometry, scv).template cast<NumEqVector>();
+        else
+            return NumEqVector(0.0);
     }
 
     template<class Entity>
@@ -161,13 +196,8 @@ public:
         return pyProblem_.attr("temperatureAtPos")(globalPos).template cast<Scalar>();
     }
 
-    const std::string paramGroup() const
-    {
-        return pyProblem_.attr("paramGroup")().template cast<std::string>();
-    }
-
     static constexpr bool enableInternalDirichletConstraints()
-    { return false; } // TODO
+    { return enableInternalDirichletConstraints_; }
 
     /*!
      * \brief Add source term derivative to the Jacobian
@@ -180,20 +210,18 @@ public:
                               const VolumeVariables& volVars,
                               const SubControlVolume& scv) const
     {
-        pyProblem_.attr("addSourceDerivatives")(block, element, fvGeometry, scv).template cast<Scalar>();
+        if (pybind11::hasattr(pyProblem_, "addSourceDerivatives"))
+            pyProblem_.attr("addSourceDerivatives")(block, element, fvGeometry, scv);
     }
 
-
     const GridGeometry& gridGeometry() const
     { return *gridGeometry_; }
 
-    const SpatialParams& spatialParams() const
-    { return *spatialParams_; }
-
 private:
     std::shared_ptr<const GridGeometry> gridGeometry_;
-    std::shared_ptr<const SpatialParams> spatialParams_;
     pybind11::object pyProblem_;
+    std::string name_;
+    std::string paramGroup_;
 };
 
 // Python wrapper for the above FVProblem C++ class
@@ -204,11 +232,9 @@ void registerFVProblem(pybind11::handle scope, pybind11::class_<Problem, options
     using namespace Dune::Python;
 
     using GridGeometry = typename Problem::GridGeometry;
-    using SpatialParams = typename Problem::SpatialParams;
     cls.def(pybind11::init([](std::shared_ptr<const GridGeometry> gridGeometry,
-                              std::shared_ptr<const SpatialParams> spatialParams,
                               pybind11::object p){
-        return std::make_shared<Problem>(gridGeometry, spatialParams, p);
+        return std::make_shared<Problem>(gridGeometry, p);
     }));
 
     cls.def_property_readonly("name", &Problem::name);
diff --git a/dumux/python/porousmediumflow/CMakeLists.txt b/dumux/python/porousmediumflow/CMakeLists.txt
new file mode 100644
index 0000000000..0b1c7f97be
--- /dev/null
+++ b/dumux/python/porousmediumflow/CMakeLists.txt
@@ -0,0 +1,3 @@
+file(GLOB DUMUX_PYTHON_POROUSMEDIUMFLOW_HEADERS *.hh *.inc)
+install(FILES ${DUMUX_PYTHON_POROUSMEDIUMFLOW_HEADERS}
+        DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/dumux/python/porousmediumflow)
diff --git a/dumux/python/porousmediumflow/problem.hh b/dumux/python/porousmediumflow/problem.hh
new file mode 100644
index 0000000000..30c72f3b40
--- /dev/null
+++ b/dumux/python/porousmediumflow/problem.hh
@@ -0,0 +1,119 @@
+// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+// vi: set et ts=4 sw=4 sts=4:
+/****************************************************************************
+ *   See the file COPYING for full copying permissions.                      *
+ *                                                                           *
+ *   This program 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.                                     *
+ *                                                                           *
+ *   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.   *
+ *****************************************************************************/
+/*!
+ * \file
+ * \brief TODO: docme!
+ */
+
+#ifndef DUMUX_PYTHON_POROUSMEDIUMFLOW_PROBLEM_HH
+#define DUMUX_PYTHON_POROUSMEDIUMFLOW_PROBLEM_HH
+
+
+#include <dune/python/pybind11/pybind11.h>
+
+#include <dumux/python/common/fvproblem.hh>
+
+namespace Dumux::Python {
+
+/*!
+ * \ingroup Common
+ * \brief A C++ wrapper for a Python PorousMediumFlow problem
+ */
+template<class GridGeometry_, class PrimaryVariables, class SpatialParams_,
+         bool enableInternalDirichletConstraints>
+class PorousMediumFlowProblem : public FVProblem<GridGeometry_, PrimaryVariables, enableInternalDirichletConstraints>
+{
+    using ParentType = FVProblem<GridGeometry_, PrimaryVariables, enableInternalDirichletConstraints>;
+public:
+    using GridGeometry = GridGeometry_;
+    using SpatialParams = SpatialParams_;
+    using Scalar = typename PrimaryVariables::value_type;
+    using NumEqVector = Dune::FieldVector<Scalar, PrimaryVariables::dimension>;
+    using Element = typename GridGeometry::GridView::template Codim<0>::Entity;
+    using FVElementGeometry = typename GridGeometry::LocalView;
+    using SubControlVolume = typename GridGeometry::SubControlVolume;
+    using SubControlVolumeFace = typename GridGeometry::SubControlVolumeFace;
+    using GlobalPosition = typename Element::Geometry::GlobalCoordinate;
+
+    static constexpr bool isBox = GridGeometry::discMethod == DiscretizationMethod::box;
+    static constexpr std::size_t numEq = static_cast<std::size_t>(PrimaryVariables::dimension);
+    using BoundaryTypes = Dumux::BoundaryTypes<PrimaryVariables::dimension>;
+
+    PorousMediumFlowProblem(std::shared_ptr<const GridGeometry> gridGeometry,
+                            std::shared_ptr<const SpatialParams> spatialParams,
+                            pybind11::object pyProblem)
+    : ParentType(gridGeometry, pyProblem)
+    , spatialParams_(spatialParams)
+    {}
+
+    const SpatialParams& spatialParams() const
+    { return *spatialParams_; }
+
+private:
+    std::shared_ptr<const SpatialParams> spatialParams_;
+};
+
+// Python wrapper for the above FVProblem C++ class
+template<class Problem, class... options>
+void registerPorousMediumFlowProblem(pybind11::handle scope, pybind11::class_<Problem, options...> cls)
+{
+    using pybind11::operator""_a;
+    using namespace Dune::Python;
+
+    using GridGeometry = typename Problem::GridGeometry;
+    using SpatialParams = typename Problem::SpatialParams;
+    cls.def(pybind11::init([](std::shared_ptr<const GridGeometry> gridGeometry,
+                              std::shared_ptr<const SpatialParams> spatialParams,
+                              pybind11::object p){
+        return std::make_shared<Problem>(gridGeometry, spatialParams, p);
+    }));
+
+    cls.def_property_readonly("name", &Problem::name);
+    cls.def_property_readonly("numEq", [](Problem&){ return Problem::numEq; });
+
+    using GridView = typename GridGeometry::GridView;
+    using Element = typename GridView::template Codim<0>::Entity;
+    using Vertex = typename GridView::template Codim<GridView::dimension>::Entity;
+
+    if constexpr (Problem::isBox)
+    {
+        using SCV = typename Problem::SubControlVolume;
+        cls.def("boundaryTypes", pybind11::overload_cast<const Element&, const SCV&>(&Problem::boundaryTypes, pybind11::const_), "element"_a, "scv"_a);
+        cls.def("dirichlet", pybind11::overload_cast<const Element&, const SCV&>(&Problem::dirichlet, pybind11::const_), "element"_a, "scv"_a);
+    }
+    else
+    {
+        using SCVF = typename Problem::SubControlVolumeFace;
+        cls.def("boundaryTypes", pybind11::overload_cast<const Element&, const SCVF&>(&Problem::boundaryTypes, pybind11::const_), "element"_a, "scvf"_a);
+        cls.def("dirichlet", pybind11::overload_cast<const Element&, const SCVF&>(&Problem::dirichlet, pybind11::const_), "element"_a, "scvf"_a);
+    }
+
+    cls.def("neumann", &Problem::template neumann<decltype(std::ignore), decltype(std::ignore)>);
+    cls.def("source", &Problem::template source<decltype(std::ignore)>);
+    cls.def("sourceAtPos", &Problem::sourceAtPos);
+    cls.def("initial", &Problem::template initial<Element>);
+    cls.def("initial", &Problem::template initial<Vertex>);
+    cls.def("extrusionFactor", &Problem::template extrusionFactor<decltype(std::ignore)>);
+    cls.def("gridGeometry", &Problem::gridGeometry);
+    cls.def("spatialParams", &Problem::spatialParams);
+}
+
+} // end namespace Dumux::Python
+
+#endif
diff --git a/python/dumux/CMakeLists.txt b/python/dumux/CMakeLists.txt
index 88b4fe39c8..db25915644 100644
--- a/python/dumux/CMakeLists.txt
+++ b/python/dumux/CMakeLists.txt
@@ -3,6 +3,7 @@ add_subdirectory(common)
 add_subdirectory(discretization)
 add_subdirectory(material)
 add_subdirectory(io)
+add_subdirectory(porousmediumflow)
 
 add_python_targets(dumux
   __init__
diff --git a/python/dumux/common/__init__.py b/python/dumux/common/__init__.py
index 516a08206c..67a55de3a4 100644
--- a/python/dumux/common/__init__.py
+++ b/python/dumux/common/__init__.py
@@ -13,14 +13,15 @@ from dune.common.hashit import hashIt
 # class MyProblem:
 #    ...
 #
-def FVProblem(gridGeometry, spatialParams):
-
+def FVProblem(gridGeometry, enableInternalDirichletConstraints=False):
     def createModule(numEq):
         priVarType = "Dune::FieldVector<double, {}>".format(numEq)
         ggType = gridGeometry._typeName
-        spatialParamsType = spatialParams._typeName
-        problemType = "Dumux::Python::FVProblem<{}, {}, {}>".format(ggType, priVarType, spatialParamsType)
-        includes = gridGeometry._includes + spatialParams._includes + ["dumux/python/common/fvproblem.hh"]
+        enableIntDirConstraint = "true" if enableInternalDirichletConstraints else "false"
+        problemType = "Dumux::Python::FVProblem<{}, {}, {}>".format(
+            ggType, priVarType, enableIntDirConstraint
+        )
+        includes = gridGeometry._includes + ["dumux/python/common/fvproblem.hh"]
         moduleName = "fvproblem_" + hashIt(problemType)
         holderType = "std::shared_ptr<{}>".format(problemType)
         generator = SimpleGenerator("FVProblem", "Dumux::Python")
@@ -31,7 +32,8 @@ def FVProblem(gridGeometry, spatialParams):
         module = createModule(Cls.numEq)
 
         def createFVProblem():
-            return module.FVProblem(gridGeometry, spatialParams, Cls())
+            return module.FVProblem(gridGeometry, Cls())
+
         return createFVProblem
 
     return FVProblemDecorator
@@ -49,7 +51,7 @@ def BoundaryTypes(numEq=1):
         moduleName = "boundarytypes_" + hashIt(typeName)
         generator = SimpleGenerator("BoundaryTypes", "Dumux::Python")
         module = generator.load(includes, typeName, moduleName)
-        globals().update({cacheKey : module.BoundaryTypes})
+        globals().update({cacheKey: module.BoundaryTypes})
     return globals()[cacheKey]()
 
 
diff --git a/python/dumux/porousmediumflow/CMakeLists.txt b/python/dumux/porousmediumflow/CMakeLists.txt
new file mode 100644
index 0000000000..9b6c72a388
--- /dev/null
+++ b/python/dumux/porousmediumflow/CMakeLists.txt
@@ -0,0 +1,3 @@
+add_python_targets(porousmediumflow
+  __init__
+)
diff --git a/python/dumux/porousmediumflow/__init__.py b/python/dumux/porousmediumflow/__init__.py
new file mode 100644
index 0000000000..9db28887b5
--- /dev/null
+++ b/python/dumux/porousmediumflow/__init__.py
@@ -0,0 +1,36 @@
+from dune.generator.generator import SimpleGenerator
+from dune.common.hashit import hashIt
+
+# A problem decorator generator for Python problems
+#
+# from dumux.common import PorousMediumFlowProblem
+# @PorousMediumFlowProblem(gridGeometry)
+# class MyProblem:
+#    ...
+#
+def PorousMediumFlowProblem(gridGeometry, spatialParams, enableInternalDirichletConstraints=False):
+    def createModule(numEq):
+        priVarType = f"Dune::FieldVector<double, {numEq}>"
+        ggType = gridGeometry._typeName
+        spatialParamsType = spatialParams._typeName
+        enableIntDirConstraint = "true" if enableInternalDirichletConstraints else "false"
+        problemType = f"Dumux::Python::PorousMediumFlowProblem<{ggType}, {priVarType}, {spatialParamsType}, {enableIntDirConstraint}>"
+        includes = (
+            gridGeometry._includes
+            + spatialParams._includes
+            + ["dumux/python/porousmediumflow/problem.hh"]
+        )
+        moduleName = "fvproblem_" + hashIt(problemType)
+        generator = SimpleGenerator("PorousMediumFlowProblem", "Dumux::Python")
+        module = generator.load(includes, problemType, moduleName, holder="std::shared_ptr")
+        return module
+
+    def PorousMediumFlowProblemDecorator(Cls):
+        module = createModule(Cls.numEq)
+
+        def createPorousMediumFlowProblem():
+            return module.PorousMediumFlowProblem(gridGeometry, spatialParams, Cls())
+
+        return createPorousMediumFlowProblem
+
+    return PorousMediumFlowProblemDecorator
diff --git a/test/python/test_explicit_transport_cctpfa.py b/test/python/test_explicit_transport_cctpfa.py
index 06b18e5605..efa9282389 100755
--- a/test/python/test_explicit_transport_cctpfa.py
+++ b/test/python/test_explicit_transport_cctpfa.py
@@ -33,10 +33,13 @@ elementMapper = gridView.indexSet
 # Define problem (inital/boundary condtions) #
 ##############################################
 
+
 @FVProblem(gridGeometry)
 class Problem:
     numEq = 1
-    name = "finitevolume"
+
+    def name(self):
+        return "finitevolume"
 
     def boundaryTypes(self, element, scv):
         bTypes = BoundaryTypes(self.numEq)
diff --git a/test/python/test_fvproblem.py b/test/python/test_fvproblem.py
index 74ecbf1057..cb3a64bb0a 100644
--- a/test/python/test_fvproblem.py
+++ b/test/python/test_fvproblem.py
@@ -29,7 +29,9 @@ gridGeometry.update()
 @FVProblem(gridGeometry)
 class Problem:
     numEq = 2
-    name = "python_problem"
+
+    def name(self):
+        return "python_problem"
 
     def boundaryTypes(self, element, scv):
         bTypes = BoundaryTypes(self.numEq)
-- 
GitLab