From 6e06e823edace0956b8532bebde82058f0f3a14f Mon Sep 17 00:00:00 2001
From: Timo Koch <timo.koch@iws.uni-stuttgart.de>
Date: Wed, 15 Apr 2020 22:30:34 +0200
Subject: [PATCH] [python] Add first Python bindings for the TimeLoop and the
 GridGeometry

---
 CMakeLists.txt                              |   6 +
 dumux/python/CMakeLists.txt                 |   2 +
 dumux/python/common/CMakeLists.txt          |   3 +
 dumux/python/common/timeloop.hh             |  48 ++++++++
 dumux/python/discretization/gridgeometry.hh | 128 ++++++++++++++++++++
 python/CMakeLists.txt                       |   2 +
 python/dumux/CMakeLists.txt                 |   2 +
 python/dumux/common/CMakeLists.txt          |   5 +
 python/dumux/common/__init__.py             |   1 +
 python/dumux/common/_common.cc              |  27 +++++
 python/dumux/discretization/CMakeLists.txt  |   3 +
 python/dumux/discretization/__init__.py     |  21 ++++
 python/setup.py.in                          |  11 ++
 13 files changed, 259 insertions(+)
 create mode 100644 dumux/python/CMakeLists.txt
 create mode 100644 dumux/python/common/CMakeLists.txt
 create mode 100644 dumux/python/common/timeloop.hh
 create mode 100644 dumux/python/discretization/gridgeometry.hh
 create mode 100644 python/CMakeLists.txt
 create mode 100644 python/dumux/CMakeLists.txt
 create mode 100644 python/dumux/common/CMakeLists.txt
 create mode 100644 python/dumux/common/__init__.py
 create mode 100644 python/dumux/common/_common.cc
 create mode 100644 python/dumux/discretization/CMakeLists.txt
 create mode 100644 python/dumux/discretization/__init__.py
 create mode 100644 python/setup.py.in

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8abf0ab4a7..ae7dcbcbef 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -33,5 +33,11 @@ add_subdirectory(dumux)
 add_subdirectory(test EXCLUDE_FROM_ALL)
 add_subdirectory(examples EXCLUDE_FROM_ALL)
 
+# if Python bindings are enabled, include necessary sub directories.
+if(DUNE_ENABLE_PYTHONBINDINGS)
+  add_subdirectory("python")
+  dune_python_install_package(PATH "python")
+endif()
+
 # finalize the dune project, e.g. generating config.h etc.
 finalize_dune_project(GENERATE_CONFIG_H_CMAKE)
diff --git a/dumux/python/CMakeLists.txt b/dumux/python/CMakeLists.txt
new file mode 100644
index 0000000000..4f95771a66
--- /dev/null
+++ b/dumux/python/CMakeLists.txt
@@ -0,0 +1,2 @@
+add_subdirectory(common)
+add_subdirectory(discretization)
diff --git a/dumux/python/common/CMakeLists.txt b/dumux/python/common/CMakeLists.txt
new file mode 100644
index 0000000000..b507d026f0
--- /dev/null
+++ b/dumux/python/common/CMakeLists.txt
@@ -0,0 +1,3 @@
+install(FILES
+timeloop.hh
+DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/dumux/python/common)
diff --git a/dumux/python/common/timeloop.hh b/dumux/python/common/timeloop.hh
new file mode 100644
index 0000000000..03a165a154
--- /dev/null
+++ b/dumux/python/common/timeloop.hh
@@ -0,0 +1,48 @@
+#ifndef DUMUX_PYTHON_COMMON_TIMELOOP_HH
+#define DUMUX_PYTHON_COMMON_TIMELOOP_HH
+
+#include <dumux/common/timeloop.hh>
+
+#include <dune/python/pybind11/pybind11.h>
+#include <dune/python/pybind11/stl.h>
+
+namespace Dumux::Python {
+
+template <class Scalar, class... options>
+void registerTimeLoop(pybind11::handle scope,
+                      pybind11::class_<CheckPointTimeLoop<Scalar>, options...> cls)
+{
+    using pybind11::operator""_a;
+
+    using TimeLoop = CheckPointTimeLoop<Scalar>;
+    cls.def(pybind11::init([](Scalar startTime, Scalar dt, Scalar endTime, bool verbose){
+        return new TimeLoop(startTime, dt, endTime, verbose);
+    }), "startTime"_a, "dt"_a, "endTime"_a, "verbose"_a=true);
+
+    cls.def("time", &TimeLoop::time);
+    cls.def("timeStepSize", &TimeLoop::timeStepSize);
+    cls.def("start", &TimeLoop::start);
+    cls.def("reset", &TimeLoop::reset, "startTime"_a, "dt"_a, "endTime"_a, "verbose"_a=true);
+    cls.def("advanceTimeStep", &TimeLoop::advanceTimeStep);
+    cls.def("setTimeStepSize", &TimeLoop::setTimeStepSize, "dt"_a);
+    cls.def("setMaxTimeStepSize", &TimeLoop::setMaxTimeStepSize, "dt"_a);
+    cls.def("finished", &TimeLoop::finished);
+    cls.def("reportTimeStep", &TimeLoop::reportTimeStep);
+    cls.def("finalize", [](TimeLoop& self){ self.finalize(); });
+    cls.def("setPeriodicCheckPoint", &TimeLoop::setPeriodicCheckPoint, "interval"_a, "offset"_a=0.0);
+    cls.def("isCheckPoint", &TimeLoop::isCheckPoint);
+    cls.def("setCheckPoints", [](TimeLoop& self, const std::vector<double>& checkPoints) {
+        self.setCheckPoint(checkPoints);
+    });
+}
+
+template<class Scalar>
+void registerTimeLoop(pybind11::handle scope, const char *clsName = "TimeLoop")
+{
+    pybind11::class_<CheckPointTimeLoop<Scalar>> cls(scope, clsName);
+    registerTimeLoop(scope, cls);
+}
+
+} // namespace Dumux::Python
+
+#endif
diff --git a/dumux/python/discretization/gridgeometry.hh b/dumux/python/discretization/gridgeometry.hh
new file mode 100644
index 0000000000..619b1e468c
--- /dev/null
+++ b/dumux/python/discretization/gridgeometry.hh
@@ -0,0 +1,128 @@
+#ifndef DUMUX_PYTHON_DISCRETIZATION_GRIDGEOMETRY_HH
+#define DUMUX_PYTHON_DISCRETIZATION_GRIDGEOMETRY_HH
+
+#include <memory>
+#include <dune/python/pybind11/pybind11.h>
+#include <dune/python/common/typeregistry.hh>
+
+namespace Dumux::Python {
+
+namespace Impl {
+
+template<class SCV>
+void registerSubControlVolume(pybind11::handle scope)
+{
+    using namespace Dune::Python;
+
+    auto [cls, addedToRegistry] = insertClass<SCV>(
+        scope, "SubControlVolume",
+        GenerateTypeName("SubControlVolume"),
+        IncludeFiles{"dumux/python/discretization/gridgeometry.hh"}
+    );
+
+    if (addedToRegistry)
+    {
+        using pybind11::operator""_a;
+
+        cls.def("center", &SCV::center);
+        cls.def("volume", &SCV::volume);
+        cls.def("dofIndex", &SCV::dofIndex);
+        cls.def("localDofIndex", &SCV::localDofIndex);
+        cls.def("dofPosition", &SCV::dofPosition);
+        cls.def("elementIndex", &SCV::elementIndex);
+
+    }
+}
+
+template<class SCVF>
+void registerSubControlVolumeFace(pybind11::handle scope)
+{
+    using namespace Dune::Python;
+
+    auto [cls, addedToRegistry] = insertClass<SCVF>(
+        scope, "SubControlVolumeFace",
+        GenerateTypeName("SubControlVolumeFace"),
+        IncludeFiles{"dumux/python/discretization/gridgeometry.hh"}
+    );
+
+    if (addedToRegistry)
+    {
+        using pybind11::operator""_a;
+
+        cls.def("center", &SCVF::center);
+        cls.def("area", &SCVF::area);
+        cls.def("ipGlobal", &SCVF::ipGlobal);
+        cls.def("boundary", &SCVF::boundary);
+        cls.def("unitOuterNormal", &SCVF::unitOuterNormal);
+        cls.def("insideScvIdx", &SCVF::insideScvIdx);
+        cls.def("outsideScvIdx", [](SCVF& self){ return self.outsideScvIdx(); });
+        cls.def("index", &SCVF::index);
+    }
+}
+
+template<class FVEG>
+void registerFVElementGeometry(pybind11::handle scope)
+{
+    using namespace Dune::Python;
+
+    auto [cls, addedToRegistry] = insertClass<FVEG>(
+        scope, "FVElementGeometry",
+        GenerateTypeName("FVElementGeometry"),
+        IncludeFiles{"dumux/python/discretization/gridgeometry.hh"}
+    );
+
+    if (addedToRegistry)
+    {
+        using pybind11::operator""_a;
+
+        cls.def("numScvf", &FVEG::numScv);
+        cls.def("numScv", &FVEG::numScv);
+        cls.def("bind", &FVEG::bind, "element"_a);
+        cls.def("hasBoundaryScvf", &FVEG::hasBoundaryScvf);
+        cls.def("scvs", [](FVEG& self){
+            const auto range = scvs(self);
+            return pybind11::make_iterator(range.begin(), range.end());
+        }, pybind11::keep_alive<0, 1>());
+        cls.def("scvfs", [](FVEG& self){
+            const auto range = scvfs(self);
+            return pybind11::make_iterator(range.begin(), range.end());
+        }, pybind11::keep_alive<0, 1>());
+    }
+}
+
+} // end namespace Impl
+
+// see python/dumux/discretization/__init__.py for how this is used for JIT compilation
+template <class GG, class... Options>
+void registerGridGeometry(pybind11::handle scope, pybind11::class_<GG, Options...> cls)
+{
+    using pybind11::operator""_a;
+    using namespace Dune::Python;
+
+    using GridView = typename GG::GridView;
+
+    cls.def(pybind11::init([](GridView gridView){
+        return std::make_unique<GG>(gridView);
+    }), "gridView"_a);
+
+    cls.def("update", &GG::update);
+    cls.def("numDofs", &GG::numDofs);
+    cls.def("numScv", &GG::numScv);
+    cls.def("numScvf", &GG::numScvf);
+
+    using SubControlVolume = typename GG::SubControlVolume;
+    Impl::registerSubControlVolume<SubControlVolume>(scope);
+
+    using SubControlVolumeFace = typename GG::SubControlVolumeFace;
+    Impl::registerSubControlVolumeFace<SubControlVolumeFace>(scope);
+
+    // also compile the corresponding local view
+    using LocalView = typename GG::LocalView;
+    Impl::registerFVElementGeometry<LocalView>(scope);
+    // and make it accessible
+    cls.def("localView", [](GG& self){ return localView(self); });
+}
+
+} // namespace Dumux::Python
+
+#endif
diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt
new file mode 100644
index 0000000000..0c4ddccf90
--- /dev/null
+++ b/python/CMakeLists.txt
@@ -0,0 +1,2 @@
+add_subdirectory(dumux)
+configure_file(setup.py.in setup.py)
diff --git a/python/dumux/CMakeLists.txt b/python/dumux/CMakeLists.txt
new file mode 100644
index 0000000000..4f95771a66
--- /dev/null
+++ b/python/dumux/CMakeLists.txt
@@ -0,0 +1,2 @@
+add_subdirectory(common)
+add_subdirectory(discretization)
diff --git a/python/dumux/common/CMakeLists.txt b/python/dumux/common/CMakeLists.txt
new file mode 100644
index 0000000000..38d491fa22
--- /dev/null
+++ b/python/dumux/common/CMakeLists.txt
@@ -0,0 +1,5 @@
+add_python_targets(common
+  __init__
+)
+dune_add_pybind11_module(NAME _common)
+set_property(TARGET _common PROPERTY LINK_LIBRARIES dunecommon dunegrid APPEND)
diff --git a/python/dumux/common/__init__.py b/python/dumux/common/__init__.py
new file mode 100644
index 0000000000..c897d03434
--- /dev/null
+++ b/python/dumux/common/__init__.py
@@ -0,0 +1 @@
+from ._common import *
diff --git a/python/dumux/common/_common.cc b/python/dumux/common/_common.cc
new file mode 100644
index 0000000000..49bca34533
--- /dev/null
+++ b/python/dumux/common/_common.cc
@@ -0,0 +1,27 @@
+// -*- 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/>.   *
+ *****************************************************************************/
+
+#include <dune/python/pybind11/pybind11.h>
+#include <dumux/python/common/timeloop.hh>
+
+PYBIND11_MODULE(_common, module)
+{
+    // export time loop
+    Dumux::Python::registerTimeLoop<double>(module);
+}
diff --git a/python/dumux/discretization/CMakeLists.txt b/python/dumux/discretization/CMakeLists.txt
new file mode 100644
index 0000000000..22b582c564
--- /dev/null
+++ b/python/dumux/discretization/CMakeLists.txt
@@ -0,0 +1,3 @@
+add_python_targets(discretization
+  __init__
+)
diff --git a/python/dumux/discretization/__init__.py b/python/dumux/discretization/__init__.py
new file mode 100644
index 0000000000..e4d73fb277
--- /dev/null
+++ b/python/dumux/discretization/__init__.py
@@ -0,0 +1,21 @@
+from dune.generator.generator import SimpleGenerator
+from dune.common.hashit import hashIt
+
+# construct a GridGeometry from a gridView
+# the grid geometry is JIT compiled
+def GridGeometry(gridView, discMethod="cctpfa"):
+    includes = gridView._includes + ["dumux/python/discretization/gridgeometry.hh"]
+
+    if discMethod == "cctpfa":
+        includes += ["dumux/discretization/cellcentered/tpfa/fvgridgeometry.hh"]
+        typeName = "Dumux::CCTpfaFVGridGeometry<" + gridView._typeName + ">"
+    elif discMethod == "box":
+        includes += ["dumux/discretization/box/fvgridgeometry.hh"]
+        typeName = "Dumux::BoxFVGridGeometry<double, " + gridView._typeName + ">"
+    else:
+        raise ValueError("Unknown discMethod {}".format(discMethod))
+
+    moduleName = "gridgeometry_" + hashIt(typeName)
+    generator = SimpleGenerator("GridGeometry", "Dumux::Python")
+    module = generator.load(includes, typeName, moduleName)
+    return module.GridGeometry(gridView)
diff --git a/python/setup.py.in b/python/setup.py.in
new file mode 100644
index 0000000000..8cb1413ea2
--- /dev/null
+++ b/python/setup.py.in
@@ -0,0 +1,11 @@
+from setuptools import setup, find_namespace_packages
+
+setup(name="dumux",
+      description="Python lib for dumux",
+      version="${DUMUX_VERSION}",
+      author="Timo Koch",
+      packages = find_namespace_packages(include=['dumux.*']),
+      zip_safe = 0,
+      package_data = {'': ['*.so']},
+      install_requires = ['portalocker'],
+)
-- 
GitLab