From 95065a5a786a2cf4ea52ee353259ab2cee1c6b51 Mon Sep 17 00:00:00 2001 From: Niklas Fehn Date: Mon, 23 Mar 2020 13:03:50 +0100 Subject: [PATCH] improve usability of ParameterHandler --- doc/news/changes/minor/20200323NiklasFehn | 7 + include/deal.II/base/parameter_handler.h | 63 +++++++-- source/base/parameter_handler.cc | 62 ++++++++- .../parameter_handler/parameter_handler_25.cc | 126 ++++++++++++++++++ .../parameter_handler_25.output | 28 ++++ 5 files changed, 275 insertions(+), 11 deletions(-) create mode 100644 doc/news/changes/minor/20200323NiklasFehn create mode 100644 tests/parameter_handler/parameter_handler_25.cc create mode 100644 tests/parameter_handler/parameter_handler_25.output diff --git a/doc/news/changes/minor/20200323NiklasFehn b/doc/news/changes/minor/20200323NiklasFehn new file mode 100644 index 0000000000..9d9541bcbd --- /dev/null +++ b/doc/news/changes/minor/20200323NiklasFehn @@ -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. +
+(Niklas Fehn, 2020/03/23) diff --git a/include/deal.II/base/parameter_handler.h b/include/deal.II/base/parameter_handler.h index ea4d32ca30..ebc1bdf261 100644 --- a/include/deal.II/base/parameter_handler.h +++ b/include/deal.II/base/parameter_handler.h @@ -1056,11 +1056,6 @@ public: * Declare a new entry with name entry, default and for which any * input has to match the pattern (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 after 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::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 void @@ -1139,7 +1152,8 @@ public: ParameterType & parameter, const std::string & documentation = "", const Patterns::PatternBase &pattern = - *Patterns::Tools::Convert::to_pattern()); + *Patterns::Tools::Convert::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 + 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 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> 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::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::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 = diff --git a/source/base/parameter_handler.cc b/source/base/parameter_handler.cc index 9e3f158eaa..82167e8a61 100644 --- a/source/base/parameter_handler.cc +++ b/source/base/parameter_handler.cc @@ -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(); + 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 set_status = + std::pair(has_to_be_set, false); + entries_set_status.insert( + std::pair>(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(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 +ParameterHandler::get_entries_wrongly_not_set() const +{ + std::set 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 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 index 0000000000..805daa494c --- /dev/null +++ b/tests/parameter_handler/parameter_handler_25.cc @@ -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 + +#include + +#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 index 0000000000..469de72374 --- /dev/null +++ b/tests/parameter_handler/parameter_handler_25.output @@ -0,0 +1,28 @@ + +DEAL:: +DEAL::successful +DEAL:: +-------------------------------------------------------- +An error occurred in file in function + void dealii::ParameterHandler::set(const string&, const string&) +The violated condition was: + false +Additional information: + You can't ask for entry you have not yet declared. +-------------------------------------------------------- + +DEAL:: +-------------------------------------------------------- +An error occurred in file 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. +-------------------------------------------------------- + -- 2.39.5