]> https://gitweb.dealii.org/ - dealii.git/commitdiff
improve usability of ParameterHandler 9718/head
authorNiklas Fehn <fehn@lnm.mw.tum.de>
Mon, 23 Mar 2020 12:03:50 +0000 (13:03 +0100)
committerNiklas Fehn <fehn@lnm.mw.tum.de>
Tue, 7 Apr 2020 13:21:18 +0000 (15:21 +0200)
doc/news/changes/minor/20200323NiklasFehn [new file with mode: 0644]
include/deal.II/base/parameter_handler.h
source/base/parameter_handler.cc
tests/parameter_handler/parameter_handler_25.cc [new file with mode: 0644]
tests/parameter_handler/parameter_handler_25.output [new file with mode: 0644]

diff --git a/doc/news/changes/minor/20200323NiklasFehn b/doc/news/changes/minor/20200323NiklasFehn
new file mode 100644 (file)
index 0000000..9d9541b
--- /dev/null
@@ -0,0 +1,7 @@
+New: ParameterHandler: add functionality ensuring that parameters are 
+indeed set in order to avoid errors that remain unrecognized otherwise.
+By extending the interfaces of declare_entry() and add_parameter(),
+parameters can be declared as mandatory parameters that must be set
+when parsing from an input file or by a set() function.
+<br>
+(Niklas Fehn, 2020/03/23)
index ea4d32ca3008ce58f7b605ed485616a7927383d6..ebc1bdf2618b92e2eee4c9a42126b8ccb1899e93 100644 (file)
@@ -1056,11 +1056,6 @@ public:
    * Declare a new entry with name <tt>entry</tt>, default and for which any
    * input has to match the <tt>pattern</tt> (default: any pattern).
    *
-   * The last parameter defaulting to an empty string is used to add a
-   * documenting text to each entry which will be printed as a comment when
-   * this class is asked to write out all declarations to a stream using the
-   * print_parameters() function.
-   *
    * The function generates an exception of type ExcValueDoesNotMatchPattern
    * if the default value doesn't match the given pattern, using the C++ throw
    * mechanism. However, this exception is only generated <i>after</i> the
@@ -1068,6 +1063,17 @@ public:
    * for a parameter is possible, you can then catch and ignore this
    * exception.
    *
+   * The parameter @p documentation defaulting to an empty string is used
+   * to add a documenting text to each entry which will be printed as a comment
+   * when this class is asked to write out all declarations to a stream using
+   * the print_parameters() function.
+   *
+   * The parameter @p has_to_be_set can be used in order to declare this
+   * parameter as a parameter whose default value has to be overwritten by
+   * one of the methods provided by this class. Whether a parameter has been set
+   * succesfully can be queried by the functions get_entries_wrongly_not_set()
+   * and assert_that_entries_have_been_set().
+   *
    * @note An entry can be declared more than once without generating an
    * error, for example to override an earlier default value.
    */
@@ -1075,7 +1081,8 @@ public:
   declare_entry(const std::string &          entry,
                 const std::string &          default_value,
                 const Patterns::PatternBase &pattern = Patterns::Anything(),
-                const std::string &          documentation = "");
+                const std::string &          documentation = "",
+                const bool                   has_to_be_set = false);
 
   /**
    * Attach an action to the parameter with name @p entry in the current
@@ -1132,6 +1139,12 @@ public:
    *
    * By default, the pattern to use is obtained by calling the function
    * Patterns::Tools::Convert<T>::to_pattern(), but a custom one can be used.
+   *
+   * The parameter @p has_to_be_set can be used in order to declare this
+   * parameter as a parameter whose default value has to be overwritten by
+   * one of the methods provided by this class. Whether a parameter has been set
+   * succesfully can be queried by the functions get_entries_wrongly_not_set()
+   * and assert_that_entries_have_been_set().
    */
   template <class ParameterType>
   void
@@ -1139,7 +1152,8 @@ public:
                 ParameterType &              parameter,
                 const std::string &          documentation = "",
                 const Patterns::PatternBase &pattern =
-                  *Patterns::Tools::Convert<ParameterType>::to_pattern());
+                  *Patterns::Tools::Convert<ParameterType>::to_pattern(),
+                const bool has_to_be_set = false);
 
   /**
    * Create an alias for an existing entry. This provides a way to refer to a
@@ -1488,6 +1502,25 @@ public:
   bool
   operator==(const ParameterHandler &prm2) const;
 
+  /**
+   * Return a set of parameter names (including subsection names) corresponding
+   * to those entries of the parameter handler that have not been set by one of
+   * the functions parsing parameters from an input file or by an explicit call
+   * to one of the set() functions, but that have been declared as mandatory
+   * parameters that must be set (through the last argument of the
+   * declare_entry() function or add_parameter() function).
+   */
+  std::set<std::string>
+  get_entries_wrongly_not_set() const;
+
+  /**
+   * Asserts that those entries of the parameter handler with flag
+   * `has_to_be_set = true` have been set. An exception is invoked
+   * if at least one of these parameters has not been set.
+   */
+  void
+  assert_that_entries_have_been_set() const;
+
   /**
    * @addtogroup Exceptions
    * @{
@@ -1639,6 +1672,16 @@ private:
    */
   std::unique_ptr<boost::property_tree::ptree> entries;
 
+  /**
+   * A map that stores a pair of boolean variables for each entry
+   * added to the parameter handler. The first bool describes whether the
+   * parameter has to be set according to the last argument of the functions
+   * declare_entry() or add_parameter(), and the second bool contains the
+   * information whether the parameter has been set by any of the functions
+   * parsing input parameters or by a set function of this class.
+   */
+  std::map<std::string, std::pair<bool, bool>> entries_set_status;
+
   /**
    * A list of patterns that are used to describe the parameters of this
    * object. Every nodes in the property tree corresponding to a parameter
@@ -2192,7 +2235,8 @@ void
 ParameterHandler::add_parameter(const std::string &          entry,
                                 ParameterType &              parameter,
                                 const std::string &          documentation,
-                                const Patterns::PatternBase &pattern)
+                                const Patterns::PatternBase &pattern,
+                                const bool                   has_to_be_set)
 {
   static_assert(std::is_const<ParameterType>::value == false,
                 "You tried to add a parameter using a type "
@@ -2202,7 +2246,8 @@ ParameterHandler::add_parameter(const std::string &          entry,
                 Patterns::Tools::Convert<ParameterType>::to_string(
                   parameter, pattern.clone()),
                 pattern,
-                documentation);
+                documentation,
+                has_to_be_set);
 
   std::string        path = get_current_full_path(entry);
   const unsigned int pattern_index =
index 9e3f158eaaf9b1e5173c11fc4ad62f558dbb5180..82167e8a61bbd8c176e1dfc276e0b6f55ef5e9aa 100644 (file)
@@ -473,7 +473,7 @@ ParameterHandler::parse_input(std::istream &     input,
     }
 
   // While it does not make much sense for anyone to actually do this, allow
-  // the last line to end in a backslash. to do do, we need to parse
+  // the last line to end in a backslash. To do so, we need to parse
   // whatever was left in the stash of concatenated lines
   if (is_concatenated)
     scan_line_or_cleanup(fully_concatenated_line, filename, current_line_n);
@@ -740,6 +740,7 @@ void
 ParameterHandler::clear()
 {
   entries = std_cxx14::make_unique<boost::property_tree::ptree>();
+  entries_set_status.clear();
 }
 
 
@@ -748,7 +749,8 @@ void
 ParameterHandler::declare_entry(const std::string &          entry,
                                 const std::string &          default_value,
                                 const Patterns::PatternBase &pattern,
-                                const std::string &          documentation)
+                                const std::string &          documentation,
+                                const bool                   has_to_be_set)
 {
   entries->put(get_current_full_path(entry) + path_separator + "value",
                default_value);
@@ -757,6 +759,13 @@ ParameterHandler::declare_entry(const std::string &          entry,
   entries->put(get_current_full_path(entry) + path_separator + "documentation",
                documentation);
 
+  // initialize with false
+  const std::pair<bool, bool> set_status =
+    std::pair<bool, bool>(has_to_be_set, false);
+  entries_set_status.insert(
+    std::pair<std::string, std::pair<bool, bool>>(get_current_full_path(entry),
+                                                  set_status));
+
   patterns.reserve(patterns.size() + 1);
   patterns.emplace_back(pattern.clone());
   entries->put(get_current_full_path(entry) + path_separator + "pattern",
@@ -1131,6 +1140,14 @@ ParameterHandler::set(const std::string &entry_string,
 
       // finally write the new value into the database
       entries->put(path + path_separator + "value", new_value);
+
+      auto map_iter = entries_set_status.find(path);
+      if (map_iter != entries_set_status.end())
+        map_iter->second = std::pair<bool, bool>(map_iter->second.first, true);
+      else
+        AssertThrow(false,
+                    ExcMessage("Could not find parameter " + path +
+                               " in map entries_set_status."));
     }
   else
     AssertThrow(false, ExcEntryUndeclared(entry_string));
@@ -1967,6 +1984,47 @@ ParameterHandler::operator==(const ParameterHandler &prm2) const
 
 
 
+std::set<std::string>
+ParameterHandler::get_entries_wrongly_not_set() const
+{
+  std::set<std::string> entries_wrongly_not_set;
+
+  for (auto it : entries_set_status)
+    if (it.second.first == true && it.second.second == false)
+      entries_wrongly_not_set.insert(it.first);
+
+  return entries_wrongly_not_set;
+}
+
+
+
+void
+ParameterHandler::assert_that_entries_have_been_set() const
+{
+  const std::set<std::string> entries_wrongly_not_set =
+    this->get_entries_wrongly_not_set();
+
+  if (entries_wrongly_not_set.size() > 0)
+    {
+      std::string list_of_missing_parameters = "\n\n";
+      for (const auto &it : entries_wrongly_not_set)
+        list_of_missing_parameters += "  " + it + "\n";
+      list_of_missing_parameters += "\n";
+
+      AssertThrow(
+        entries_wrongly_not_set.size() == 0,
+        ExcMessage(
+          "Not all entries of the parameter handler that were declared with "
+          "`has_to_be_set = true` have been set. The following parameters " +
+          list_of_missing_parameters +
+          " have not been set. "
+          "A possible reason might be that you did not add these parameter to "
+          "the input file or that their spelling is not correct."));
+    }
+}
+
+
+
 MultipleParameterLoop::MultipleParameterLoop()
   : n_branches(0)
 {}
diff --git a/tests/parameter_handler/parameter_handler_25.cc b/tests/parameter_handler/parameter_handler_25.cc
new file mode 100644 (file)
index 0000000..805daa4
--- /dev/null
@@ -0,0 +1,126 @@
+// ---------------------------------------------------------------------
+//
+// Copyright (C) 2019 by the deal.II authors
+//
+// This file is part of the deal.II library.
+//
+// The deal.II library is free software; you can use it, redistribute
+// it, and/or modify it under the terms of the GNU Lesser General
+// Public License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// The full text of the license can be found in the file LICENSE.md at
+// the top level directory of deal.II.
+//
+// ---------------------------------------------------------------------
+
+#include <deal.II/base/parameter_handler.h>
+
+#include <map>
+
+#include "../tests.h"
+
+void
+success()
+{
+  unsigned int dim       = 2;
+  std::string  precision = "double";
+
+  ParameterHandler prm;
+  prm.enter_subsection("General");
+  // this one does not have to be set
+  prm.add_parameter("dim",
+                    dim,
+                    "Number of space dimensions",
+                    Patterns::Integer(2, 3));
+  // this one has to be set
+  prm.declare_entry("Precision",
+                    precision,
+                    Patterns::Selection("float|double"),
+                    "Floating point precision",
+                    true);
+  prm.leave_subsection();
+
+  // this try-catch simulates parsing from an incomplete/incorrect input file
+  try
+    {
+      prm.enter_subsection("General");
+      prm.set("Precision", "float");
+      prm.leave_subsection();
+    }
+  catch (std::exception &exc)
+    {
+      deallog << exc.what() << std::endl;
+    }
+
+  // check set status
+  try
+    {
+      prm.assert_that_entries_have_been_set();
+    }
+  catch (std::exception &exc)
+    {
+      deallog << exc.what() << std::endl;
+    }
+
+  deallog << std::endl << "successful" << std::endl;
+}
+
+void
+fail()
+{
+  unsigned int dim       = 2;
+  std::string  precision = "double";
+
+  ParameterHandler prm;
+  prm.enter_subsection("General");
+  // both parameters have to be set
+  prm.add_parameter(
+    "dim", dim, "Number of space dimensions", Patterns::Integer(2, 3), true);
+  prm.add_parameter("Precision",
+                    precision,
+                    "Floating point precision",
+                    Patterns::Selection("float|double"),
+                    true);
+  prm.leave_subsection();
+
+  // this try-catch simulates parsing from an incomplete/incorrect input file
+  try
+    {
+      prm.enter_subsection("General");
+      prm.set("Precison", "float"); // here is a typo!
+      // dim is not set!
+      prm.leave_subsection();
+    }
+  catch (std::exception &exc)
+    {
+      deallog << exc.what() << std::endl;
+    }
+
+  // check set status
+  try
+    {
+      prm.assert_that_entries_have_been_set();
+    }
+  catch (std::exception &exc)
+    {
+      deallog << exc.what() << std::endl;
+    }
+}
+
+
+int
+main()
+{
+  initlog();
+  deallog.get_file_stream().precision(3);
+
+  try
+    {
+      success();
+      fail();
+    }
+  catch (std::exception &exc)
+    {
+      deallog << exc.what() << std::endl;
+    }
+}
diff --git a/tests/parameter_handler/parameter_handler_25.output b/tests/parameter_handler/parameter_handler_25.output
new file mode 100644 (file)
index 0000000..469de72
--- /dev/null
@@ -0,0 +1,28 @@
+
+DEAL::
+DEAL::successful
+DEAL::
+--------------------------------------------------------
+An error occurred in file <parameter_handler.cc> in function
+    void dealii::ParameterHandler::set(const string&, const string&)
+The violated condition was:
+    false
+Additional information:
+    You can't ask for entry <Precison> you have not yet declared.
+--------------------------------------------------------
+
+DEAL::
+--------------------------------------------------------
+An error occurred in file <parameter_handler.cc> in function
+    void dealii::ParameterHandler::assert_that_entries_have_been_set() const
+The violated condition was:
+    entries_wrongly_not_set.size() == 0
+Additional information:
+    Not all entries of the parameter handler that were declared with `has_to_be_set = true` have been set. The following parameters
+
+  General.Precision
+  General.dim
+
+ have not been set. A possible reason might be that you did not add these parameter to the input file or that their spelling is not correct.
+--------------------------------------------------------
+

In the beginning the Universe was created. This has made a lot of people very angry and has been widely regarded as a bad move.

Douglas Adams


Typeset in Trocchi and Trocchi Bold Sans Serif.