From 515db02cbab1426d2f8b2b3ca83ff2637db8d904 Mon Sep 17 00:00:00 2001
From: Timo Koch <timo.koch@iws.uni-stuttgart.de>
Date: Fri, 29 Sep 2017 14:25:43 +0200
Subject: [PATCH] Implement logging parameter tree as alternative to
 paramproperties

---
 dumux/common/basicproperties.hh               |   2 +
 dumux/common/loggingparametertree.hh          | 229 ++++++++++++++++++
 dumux/io/gridcreator.hh                       |  98 ++++----
 test/common/CMakeLists.txt                    |   9 +-
 test/common/parameters/CMakeLists.txt         |   2 +
 test/common/parameters/params.input           |   6 +
 .../parameters/test_loggingparametertree.cc   |  59 +++++
 .../2p/implicit/incompressible/test_2p.cc     |  11 +-
 8 files changed, 353 insertions(+), 63 deletions(-)
 create mode 100644 dumux/common/loggingparametertree.hh
 create mode 100644 test/common/parameters/CMakeLists.txt
 create mode 100644 test/common/parameters/params.input
 create mode 100644 test/common/parameters/test_loggingparametertree.cc

diff --git a/dumux/common/basicproperties.hh b/dumux/common/basicproperties.hh
index 4c99830742..ff95c3827e 100644
--- a/dumux/common/basicproperties.hh
+++ b/dumux/common/basicproperties.hh
@@ -105,6 +105,7 @@ NEW_PROP_TAG(GridVariables);
  * The default is to not limit the step size.
  */
 NEW_PROP_TAG(TimeLoopMaxTimeStepSize);
+NEW_PROP_TAG(TimeManagerMaxTimeStepSize);
 
 //! the maximum allowed number of timestep divisions for the
 //! Newton solver
@@ -135,6 +136,7 @@ SET_TYPE_PROP(NumericModel, PrimaryVariables, typename GET_PROP_TYPE(TypeTag, Nu
 
 //! use an unlimited time step size by default
 SET_SCALAR_PROP(NumericModel, TimeLoopMaxTimeStepSize, std::numeric_limits<typename GET_PROP_TYPE(TypeTag,Scalar)>::max());
+SET_SCALAR_PROP(NumericModel, TimeManagerMaxTimeStepSize, std::numeric_limits<typename GET_PROP_TYPE(TypeTag,Scalar)>::max());
 
 //! set number of maximum timestep divisions to 10
 SET_INT_PROP(NumericModel, TimeLoopMaxTimeStepDivisions, 10);
diff --git a/dumux/common/loggingparametertree.hh b/dumux/common/loggingparametertree.hh
new file mode 100644
index 0000000000..436e1f6b15
--- /dev/null
+++ b/dumux/common/loggingparametertree.hh
@@ -0,0 +1,229 @@
+// -*- 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 2 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 A parameter tree that logs which parameters have been used
+ */
+#ifndef DUMUX_LOGGING_PARAMETER_TREE_HH
+#define DUMUX_LOGGING_PARAMETER_TREE_HH
+
+#include <iomanip>
+#include <iostream>
+#include <string>
+
+#include <dune/common/parametertree.hh>
+
+namespace Dumux
+{
+
+/*!
+ * \ingroup Common
+ * \brief A parameter tree that logs which parameters have been used
+ */
+class LoggingParameterTree
+{
+
+public:
+    /*
+     * \brief A logging parameter tree is always attached to an existingparameter tree
+     */
+    LoggingParameterTree() = delete;
+
+    /*
+     * \brief Create LoggingParameterTree from ParameterTree
+     */
+    LoggingParameterTree(const Dune::ParameterTree& params)
+    : params_(params) {}
+
+    /** \brief test for key
+     *
+     * Tests whether given key exists.
+     *
+     * \param key key name
+     * \return true if key exists in structure, otherwise false
+     */
+    bool hasKey(const std::string& key) const
+    { return params_.hasKey(key); }
+
+
+    /** \brief print the hierarchical parameter tree to stream
+     *
+     * \param stream the output stream to print to
+     */
+    void report(std::ostream& stream = std::cout) const
+    { params_.report(stream); }
+
+    /** \brief print distinct substructure to stream
+     *
+     * Prints all entries with given prefix.
+     *
+     * \param stream Stream to print to
+     * \param prefix for key and substructure names
+     */
+    void reportAll(std::ostream& stream = std::cout) const
+    {
+        stream << "\n# Runtime-specified parameters used:" << std::endl;
+        usedRuntimeParams_.report(stream);
+
+        const auto unusedParams = getUnusedKeys();
+        if (!unusedParams.empty())
+        {
+            stream << "\n# Unused parameters:" << std::endl;
+            for (const auto& key : unusedParams)
+                stream << key << " = \"" << params_[key] << "\"" << std::endl;
+        }
+    }
+
+
+    /** \brief get value as string
+     *
+     * Returns pure string value for given key.
+     *
+     * \param key key name
+     * \param defaultValue default if key does not exist
+     * \return value as string
+     * \note This might be quite slow so call it only once,
+     *       e.g. on initialization of the class configured by runtime parameters
+     */
+    std::string get(const std::string& key, const std::string& defaultValue) const
+    {
+        if (params_.hasKey(key))
+        {
+            // log that we used this parameter
+            usedRuntimeParams_[key] = params_[key];
+            return params_[key];
+        }
+
+        return defaultValue;
+    }
+
+    /** \brief get value as string
+     *
+     * Returns pure string value for given key.
+     *
+     * \todo This is a hack so get("my_key", "xyz") compiles
+     * (without this method "xyz" resolves to bool instead of std::string)
+     * \param key key name
+     * \param defaultValue default if key does not exist
+     * \return value as string
+     * \note This might be quite slow so call it only once,
+     *       e.g. on initialization of the class configured by runtime parameters
+     */
+    std::string get(const std::string& key, const char* defaultValue) const
+    {
+        if (params_.hasKey(key))
+        {
+            // log that we used this parameter
+            usedRuntimeParams_[key] = params_[key];
+            return params_[key];
+        }
+
+        return defaultValue;
+    }
+
+
+    /** \brief get value converted to a certain type
+     *
+     * Returns value as type T for given key.
+     *
+     * \tparam T type of returned value.
+     * \param key key name
+     * \param defaultValue default if key does not exist
+     * \return value converted to T
+     * \note This might be quite slow so call it only once,
+     *       e.g. on initialization of the class configured by runtime parameters
+     */
+    template<typename T>
+    T get(const std::string& key, const T& defaultValue) const
+    {
+        if (params_.hasKey(key))
+        {
+            // log that we used this parameter
+            usedRuntimeParams_[key] = params_[key];
+            return params_.template get<T>(key);
+        }
+
+        return defaultValue;
+    }
+
+    /** \brief Get value
+     *
+     * \tparam T Type of the value
+     * \param key Key name
+     * \throws RangeError if key does not exist
+     * \throws NotImplemented Type is not supported
+     * \return value as T
+     * \note This might be quite slow so call it only once,
+     *       e.g. on initialization of the class configured by runtime parameters
+     */
+    template <class T>
+    T get(const std::string& key) const
+    {
+        if (params_.hasKey(key))
+        {
+            // log that we used this parameter
+            usedRuntimeParams_[key] = params_[key];
+        }
+
+        return params_.template get<T>(key);
+    }
+
+    /** \brief Find the keys that haven't been used yet
+     *
+     * \retuns unusedParams Container storing unused keys
+     * \note Useful for debugging purposes
+     */
+    std::vector<std::string> getUnusedKeys() const
+    {
+        std::vector<std::string> unusedParams;
+        findUnusedKeys(params_, unusedParams);
+        return unusedParams;
+    }
+
+private:
+    /** \brief Find the keys that haven't been used yet recursively
+     *
+     * \param tree The tree to look in for unused keys
+     * \param unusedParams Container to store unused keys
+     * \param prefix the prefix attached to the key
+     */
+    void findUnusedKeys(const Dune::ParameterTree& tree,
+                        std::vector<std::string>& unusedParams,
+                        const std::string& prefix = "") const
+    {
+        // loop over all keys of the current tree
+        // store keys which were not accessed
+        const auto& keys = tree.getValueKeys();
+        for (const auto& key : keys)
+            if (!usedRuntimeParams_.hasKey(prefix + key))
+                unusedParams.push_back(prefix + key);
+
+        // recursively loop over all subtrees
+        const auto& subTreeKeys = tree.getSubKeys();
+        for (const auto& key : subTreeKeys)
+            findUnusedKeys(tree.sub(key), unusedParams, prefix + key + ".");
+    }
+
+    const Dune::ParameterTree& params_;
+    mutable Dune::ParameterTree usedRuntimeParams_;
+};
+
+} // end namespace Dumux
+
+#endif
diff --git a/dumux/io/gridcreator.hh b/dumux/io/gridcreator.hh
index e12a9cce72..d0f61feaca 100644
--- a/dumux/io/gridcreator.hh
+++ b/dumux/io/gridcreator.hh
@@ -338,14 +338,11 @@ protected:
     /*!
      * \brief Refines a grid after construction if GridParameterGroup.Refinement is set in the input file
      */
-    static void maybeRefineGrid()
+    template<class Parameters>
+    static void maybeRefineGrid(const Parameters& params, const std::string& gridKey)
     {
-        try {
-            const int level = GET_RUNTIME_PARAM_FROM_GROUP_CSTRING(TypeTag, int, GET_PROP_VALUE(TypeTag, GridParameterGroup).c_str(), Refinement);
-            grid().globalRefine(level);
-        }
-        catch (ParameterException &e) {}
-        catch (...) { throw; }
+        if (params.hasKey(gridKey + ".Refinement"))
+            grid().globalRefine(params.template get<int>(gridKey + ".Refinement"));
     }
 
     /*!
@@ -391,7 +388,8 @@ public:
     /*!
      * \brief Make the grid. This is implemented by specializations of this class.
      */
-    static void makeGrid()
+    template<class Parameters>
+    static void makeGrid(const Parameters& params)
     {
         DUNE_THROW(Dune::NotImplemented,
             "The GridCreator for grid type " << Dune::className<Grid>() << " is not implemented! Consider providing your own GridCreator.");
@@ -480,74 +478,66 @@ public:
     /*!
      * \brief Make the grid. This is implemented by specializations of this method.
      */
-    static void makeGrid()
+    template<class Parameters>
+    static void makeGrid(const Parameters& params)
     {
         // First try to create it from a DGF file in GridParameterGroup.File
-        try {
-            const std::string fileName = GET_RUNTIME_PARAM_FROM_GROUP_CSTRING(TypeTag, std::string, GET_PROP_VALUE(TypeTag, GridParameterGroup).c_str(), File);
-            ParentType::makeGridFromDgfFile(fileName);
-            postProcessing_();
+        const std::string gridKey = params.get("GridParameterGroup", "Grid");
+        if (params.hasKey(gridKey + ".File"))
+        {
+            ParentType::makeGridFromDgfFile(params.template get<std::string>(gridKey + ".File"));
+            postProcessing_(params, gridKey);
             return;
         }
-        catch (ParameterException &e) {}
-        catch (...) { throw; }
 
         // Then look for the necessary keys to construct from the input file
-        try {
-            // The required parameters
-            typedef Dune::FieldVector<ct, dim> GlobalPosition;
-            const GlobalPosition upperRight = GET_RUNTIME_PARAM_FROM_GROUP_CSTRING(TypeTag, GlobalPosition, GET_PROP_VALUE(TypeTag, GridParameterGroup).c_str(), UpperRight);
+        if (!params.hasKey(gridKey + ".UpperRight"))
+            DUNE_THROW(ParameterException, "Please supply the mandatory parameter "
+                                          << gridKey <<  ".UpperRight or a grid file in "
+                                          << gridKey << ".File.");
 
-            // The optional parameters (they have a default)
-            typedef std::array<int, dim> CellArray;
-            CellArray cells;
-            std::fill(cells.begin(), cells.end(), 1);
-            try { cells = GET_RUNTIME_PARAM_FROM_GROUP_CSTRING(TypeTag, CellArray, GET_PROP_VALUE(TypeTag, GridParameterGroup).c_str(), Cells); }
-            catch (ParameterException &e) { }
+        // get the upper right corner coordinates
+        const auto upperRight = params.template get<Dune::FieldVector<ct, dim>>(gridKey + ".UpperRight");
 
-            typedef std::bitset<dim> BitSet;
-            BitSet periodic;
-            try { periodic = GET_RUNTIME_PARAM_FROM_GROUP_CSTRING(TypeTag, BitSet, GET_PROP_VALUE(TypeTag, GridParameterGroup).c_str(), Periodic);}
-            catch (ParameterException &e) { }
+        // number of cells in each direction
+        std::array<int, dim> cells; cells.fill(1);
+        cells = params.template get<std::array<int, dim>>(gridKey + ".Cells", cells);
 
-            // get the overlap dependent on some template parameters
-            int overlap = YaspOverlapHelper<TypeTag>::getOverlap();
+        // periodic boundaries
+        const auto periodic = params.template get<std::bitset<dim>>(gridKey + ".Periodic", std::bitset<dim>());
 
-            bool default_lb = false;
-            CellArray partitioning;
-            try { partitioning = GET_RUNTIME_PARAM_FROM_GROUP_CSTRING(TypeTag, CellArray, GET_PROP_VALUE(TypeTag, GridParameterGroup).c_str(), Partitioning);}
-            catch (ParameterException &e) { default_lb = true; }
+        // get the overlap dependent on some template parameters
+        const int overlap = YaspOverlapHelper<TypeTag>::getOverlap();
 
-            //make the grid
-            if (default_lb)
-                ParentType::gridPtr() = std::make_shared<Grid>(upperRight, cells, periodic, overlap);
-            else
-            {
-                typename Dune::YaspFixedSizePartitioner<dim> lb(partitioning);
-                ParentType::gridPtr() = std::make_shared<Grid>(upperRight, cells, periodic, overlap, typename Grid::CollectiveCommunicationType(), &lb);
-            }
-            postProcessing_();
+        bool default_lb = !params.hasKey(gridKey + ".Partitioning");
+
+        // make the grid
+        if (default_lb)
+        {
+            // construct using default load balancing
+            ParentType::gridPtr() = std::make_shared<Grid>(upperRight, cells, periodic, overlap);
         }
-        catch (ParameterException &e) {
-                DUNE_THROW(ParameterException, "Please supply the mandatory parameter "
-                                              << GET_PROP_VALUE(TypeTag, GridParameterGroup) <<  ".UpperRight or a grid file in "
-                                              << GET_PROP_VALUE(TypeTag, GridParameterGroup) << ".File.");
+        else
+        {
+            // construct using user defined partitioning
+            const auto partitioning = params.template get<std::array<int, dim>>(gridKey + ".Partitioning");
+            Dune::YaspFixedSizePartitioner<dim> lb(partitioning);
+            ParentType::gridPtr() = std::make_shared<Grid>(upperRight, cells, periodic, overlap, typename Grid::CollectiveCommunicationType(), &lb);
         }
-        catch (...) { throw; }
+        postProcessing_(params, gridKey);
     }
 
 private:
     /*!
      * \brief Postprocessing for YaspGrid
      */
-    static void postProcessing_()
+    template<class Parameters>
+    static void postProcessing_(const Parameters& params, const std::string& gridKey)
     {
         // Check if should refine the grid
-        bool keepPhysicalOverlap = true;
-        try { keepPhysicalOverlap = GET_RUNTIME_PARAM_FROM_GROUP_CSTRING(TypeTag, bool, GET_PROP_VALUE(TypeTag, GridParameterGroup).c_str(), KeepPhysicalOverlap);}
-        catch (ParameterException &e) { }
+        bool keepPhysicalOverlap = params.template get<bool>(gridKey + ".KeepPhysicalOverlap", true);
         ParentType::grid().refineOptions(keepPhysicalOverlap);
-        ParentType::maybeRefineGrid();
+        ParentType::maybeRefineGrid(params, gridKey);
     }
 };
 
diff --git a/test/common/CMakeLists.txt b/test/common/CMakeLists.txt
index 1fed13a61f..c1ef7651ad 100644
--- a/test/common/CMakeLists.txt
+++ b/test/common/CMakeLists.txt
@@ -1,4 +1,5 @@
-add_subdirectory("generalproblem")
-add_subdirectory("propertysystem")
-add_subdirectory("spline")
-add_subdirectory("boundingboxtree")
+add_subdirectory(generalproblem)
+add_subdirectory(propertysystem)
+add_subdirectory(spline)
+add_subdirectory(boundingboxtree)
+add_subdirectory(parameters)
diff --git a/test/common/parameters/CMakeLists.txt b/test/common/parameters/CMakeLists.txt
new file mode 100644
index 0000000000..45293e09ce
--- /dev/null
+++ b/test/common/parameters/CMakeLists.txt
@@ -0,0 +1,2 @@
+dune_add_test(SOURCES test_loggingparametertree.cc)
+dune_symlink_to_source_files(FILES "params.input")
diff --git a/test/common/parameters/params.input b/test/common/parameters/params.input
new file mode 100644
index 0000000000..c86b64973b
--- /dev/null
+++ b/test/common/parameters/params.input
@@ -0,0 +1,6 @@
+[TimeLoop]
+TEnd = 1e6
+
+[Grid]
+Cells = 100 100
+Bells = 10 10
diff --git a/test/common/parameters/test_loggingparametertree.cc b/test/common/parameters/test_loggingparametertree.cc
new file mode 100644
index 0000000000..6af28f6398
--- /dev/null
+++ b/test/common/parameters/test_loggingparametertree.cc
@@ -0,0 +1,59 @@
+#include <config.h>
+#include <iostream>
+
+#include <dune/common/parametertreeparser.hh>
+#include <dune/common/parallel/mpihelper.hh>
+#include <dune/common/exceptions.hh>
+
+#include <dumux/common/parameterparser.hh>
+#include <dumux/common/loggingparametertree.hh>
+
+int main (int argc, char *argv[]) try
+{
+    // maybe initialize mpi
+    Dune::MPIHelper::instance(argc, argv);
+
+    // parse the input file into the parameter tree
+    Dune::ParameterTree tree;
+    Dumux::ParameterParser::parseInputFile(argc, argv, tree, "params.input");
+
+    // attach the tree to the logging tree
+    Dumux::LoggingParameterTree params(tree);
+
+    // use some default parameters
+    bool DUNE_UNUSED(enableGravity) = params.get<bool>("Problem.EnableGravity", true);
+
+    // use some given parameters
+    const auto DUNE_UNUSED(cells) = params.get<std::array<int, 2>>("Grid.Cells", {1, 1});
+    const auto DUNE_UNUSED(tEnd) = params.get<double>("TimeLoop.TEnd");
+
+    // check the unused keys
+    const auto unused = params.getUnusedKeys();
+    if (unused.size() != 1)
+        DUNE_THROW(Dune::InvalidStateException, "There should be exactly one unused key!");
+    else if (unused[0] != "Grid.Bells")
+        DUNE_THROW(Dune::InvalidStateException, "Unused key \"Grid.Bells\" not found!");
+
+    params.reportAll();
+
+    return 0;
+}
+// //////////////////////////////////
+//   Error handler
+// /////////////////////////////////
+catch (const Dune::RangeError &e) {
+    std::cout << e << std::endl;
+    return 1;
+}
+catch (const Dune::Exception& e) {
+    std::cout << e << std::endl;
+    return 1;
+}
+catch (const std::exception& e) {
+    std::cout << e.what() << std::endl;
+    return 1;
+}
+catch (...) {
+    std::cout << "Unknown exception!" << std::endl;
+    return 1;
+}
diff --git a/test/porousmediumflow/2p/implicit/incompressible/test_2p.cc b/test/porousmediumflow/2p/implicit/incompressible/test_2p.cc
index 5063cdb9d2..fe62b55b56 100644
--- a/test/porousmediumflow/2p/implicit/incompressible/test_2p.cc
+++ b/test/porousmediumflow/2p/implicit/incompressible/test_2p.cc
@@ -40,6 +40,7 @@
 #include <dumux/common/dumuxmessage.hh>
 #include <dumux/common/defaultusagemessage.hh>
 #include <dumux/common/parameterparser.hh>
+#include <dumux/common/loggingparametertree.hh>
 
 #include <dumux/linear/seqsolverbackend.hh>
 #include <dumux/nonlinear/newtonmethod.hh>
@@ -89,16 +90,13 @@ int main(int argc, char** argv) try
     // check first if the user provided an input file through the command line, if not use the default
     const auto parameterFileName = ParameterTree::tree().hasKey("ParameterFile") ? GET_RUNTIME_PARAM(TypeTag, std::string, ParameterFile) : "";
     ParameterParser::parseInputFile(argc, argv, ParameterTree::tree(), parameterFileName);
+    LoggingParameterTree params(ParameterTree::tree());
 
     //////////////////////////////////////////////////////////////////////
     // try to create a grid (from the given grid file or the input file)
     /////////////////////////////////////////////////////////////////////
 
-    try { GridCreator::makeGrid(); }
-    catch (...) {
-        std::cout << "\n\t -> Creation of the grid failed! <- \n\n";
-        throw;
-    }
+    GridCreator::makeGrid(params);
     GridCreator::loadBalance();
 
     ////////////////////////////////////////////////////////////
@@ -211,7 +209,10 @@ int main(int argc, char** argv) try
 
     // print dumux end message
     if (mpiHelper.rank() == 0)
+    {
+        params.reportAll();
         DumuxMessage::print(/*firstCall=*/false);
+    }
 
     return 0;
 
-- 
GitLab