From 2d94f2e78db4803d099f340b9c1586bc4d8f576a Mon Sep 17 00:00:00 2001 From: Wolfgang Bangerth Date: Wed, 12 Apr 2017 12:48:39 -0600 Subject: [PATCH] Add the possibility of executing an 'action' to ParameterHandler. --- include/deal.II/base/parameter_handler.h | 159 ++++++++++++++++++++++- source/base/parameter_handler.cc | 75 ++++++++++- 2 files changed, 228 insertions(+), 6 deletions(-) diff --git a/include/deal.II/base/parameter_handler.h b/include/deal.II/base/parameter_handler.h index 80230f5101..61c2f25435 100644 --- a/include/deal.II/base/parameter_handler.h +++ b/include/deal.II/base/parameter_handler.h @@ -1,6 +1,6 @@ // --------------------------------------------------------------------- // -// Copyright (C) 1998 - 2016 by the deal.II authors +// Copyright (C) 1998 - 2017 by the deal.II authors // // This file is part of the deal.II library. // @@ -1156,6 +1156,110 @@ namespace Patterns * know the values of parameters not specified in the input file. * * + * + *

Adding Actions to Parameters

+ * + * It is often convenient to have something happen as soon as a parameter + * value is read. This could be a check that it is valid -- say, that a + * file that is listed in the parameter file exists -- or to initiate + * something else in response, such as setting a variable outside the + * ParameterHandler (as in the example shown below). In almost all cases, + * this "action" could also be initiated once all parameters are read + * via parse_input(), but it is sometimes convenient to do it + * right away. + * + * This is facilitated by the add_action() function that can be called + * after declaring a parameter via declare_entry(). "Actions" are in essence + * pointers to functions that will be called for parameters that have + * associated actions. These functions take the value of a parameter as + * argument, and can then do whatever they want with it -- e.g., save it + * somewhere outside the ParameterHandler object. (Exactly when the + * action is called is described in the documentation of the + * add_action() function.) Of course, in C++ one doesn't usually pass + * around the address of a function, but an action can be a function-like + * object (taking a string as argument) that results from calling + * @p std::bind, or more conveniently, it can be a + * lambda + * function that has the form + * @code + * [] (const std::string &value) { ... do something with the value ... } + * @endcode + * and that is attached to a specific parameter. + * + * A typical example of such an action would be as follows: let's assume + * that you have a program that declares a parameter for the number + * of iterations it is going to run, say + * @code + * class MyAlgorithm + * { + * public: + * void run (); + * private: + * unsigned int n_iterations; + * }; + * @endcode + * then one could obtain this parameter from a parameter file using a + * code snippet in @p run() as follows: + * @code + * void MyAlgorithm::run () + * { + * ParameterHandler prm; + * prm.declare_entry ("Number of iterations", // name of parameter + * "10", // default value + * Patterns::Integer(1,100),// allowed values: 1...100 + * "The number of ..."); // some documentation, to be completed + * + * // next read the parameter from an input file... + * prm.parse_input ("my_algorithm.prm"); + * + * // ...and finally get the value for use in the program: + * n_iterations = prm.get_integer ("Number of iterations"); + * + * ... actual code doing something useful follows here... + * @endcode + * + * This two-step process -- first declaring the parameter, and later reading + * it -- is a bit cumbersome because one has to first declare all + * parameters and at a later time retrieve them from the ParameterHandler + * object. In large programs, these two things also often happen in + * different functions. + * + * To avoid this, it would be nice if we could put both the declaration + * and the retrieval into the same place. This can be done via actions, + * and the function would then look like this: + * @code + * void MyAlgorithm::run () + * { + * ParameterHandler prm; + * prm.declare_entry ("Number of iterations", // name of parameter + * "10", // default value + * Patterns::Integer(1,100),// allowed values: 1...100 + * "The number of ..."); // some documentation, to be completed + * prm.add_action ("Number of iterations", + * [&](const std::string &value) { + * this->n_iterations = Utilities::string_to_int(value); + * }); + * + * // next read the parameter from an input file... + * prm.parse_input ("my_algorithm.prm"); + * + * ... actual code doing something useful follows here... + * @endcode + * Here, the action consists of a lambda function that takes the value + * for this parameter as a string, and then converts it to an integer + * to store in the variable where it belongs. This action is + * executed inside the call to prm.parse_input(), and so + * there is now no longer a need to extract the parameter's value + * at a later time. Furthermore, the code that sets the member variable + * is located right next to the place where the parameter is actually + * declared, so we no longer need to have two separate parts of the code + * base that deal with input parameters. + * + * Of course, it is possible to execute far more involved actions than + * just setting a member variable as shown above, even though that is + * a typical case. + * + * *

Style guide for data retrieval

* * We propose that every class which gets data out of a ParameterHandler @@ -1470,7 +1574,8 @@ namespace Patterns * We can think of the parameters so arranged as a file system in which every * parameter is a directory. The name of this directory is the name of the * parameter, and in this directory lie files that describe the parameter. - * These files are: + * These files are at the time of writing this documentation (other fields, + * such as those indicating "actions" may also exist in each directory): * * - value: The content of this file is the current value of this * parameter; initially, the content of the file equals the default value of @@ -1551,7 +1656,7 @@ namespace Patterns * * * @ingroup input - * @author Wolfgang Bangerth, October 1997, revised February 1998, 2010, 2011 + * @author Wolfgang Bangerth, October 1997, revised February 1998, 2010, 2011, 2017 * @author Alberto Sartori, 2015 * @author David Wells, 2016 */ @@ -1775,6 +1880,43 @@ public: const Patterns::PatternBase &pattern = Patterns::Anything(), const std::string &documentation = std::string()); + /** + * Attach an action to the parameter with name @p entry in the current + * section. The action needs to be a function-like object that takes the + * value of the parameter as a (string) argument. See the general documentation + * of this class for a longer description of actions, as well as examples. + * + * The action is executed in three different circumstances: + * - With the default value of the parameter with name @p name, at + * the end of the current function. This is useful because it allows + * for the action to execute whatever it needs to do at least once + * for each parameter, even those that are not actually specified in + * the input file (and thus remain at their default values). + * - Within the ParameterHandler::set() functions that explicitly + * set a value for a parameter. + * - Within the parse_input() function and similar functions such + * as parse_input_from_string(). Here, the action is executed + * whenever the parameter with which it is associated is read + * from the input, after it has been established that the value + * so read matches the pattern that corresponds to this parameter, + * and before the value is actually saved. + * + * It is valid to add multiple actions to the same parameter. They will + * in that case be executed in the same order in which they were added. + * + * @note Actions may modify all sorts of variables in their scope. The + * only thing an action should not modify is the ParameterHandler object + * it is attached to. In other words, it is not allowed to enter or + * leave sections of the current ParameterHandler object. It is, in + * principle, acceptable to call ParameterHandler::get() and related + * functions on other parameters in the current section, but since + * there is no guarantee about the order in which they will be read + * from an input file, you will not want to rely on the values these + * functions would return. + */ + void add_action (const std::string &entry, + const std::function &action); + /** * Create an alias for an existing entry. This provides a way to refer to a * parameter in the input file using an alternate name. The alias will be in @@ -2161,10 +2303,19 @@ private: /** * A list of patterns that are used to describe the parameters of this - * object. The are indexed by nodes in the property tree. + * object. Every nodes in the property tree corresponding to a parameter + * stores an index into this array. */ std::vector > patterns; + /** + * A list of actions that are associated with parameters. These + * are added by the add_action() function. Nodes in the property + * tree corresponding to individual parameters + * store indices into this array in order to reference specific actions. + */ + std::vector > actions; + /** * Mangle a string so that it doesn't contain any special characters or * spaces. diff --git a/source/base/parameter_handler.cc b/source/base/parameter_handler.cc index a688c1d0c1..ee5928e835 100644 --- a/source/base/parameter_handler.cc +++ b/source/base/parameter_handler.cc @@ -2024,6 +2024,43 @@ ParameterHandler::declare_entry (const std::string &entry, + +void ParameterHandler::add_action(const std::string &entry, + const std::function &action) +{ + actions.push_back (action); + + // get the current list of actions, if any + boost::optional + current_actions + = + entries->get_optional (get_current_full_path(entry) + path_separator + "actions"); + + // if there were actions already associated with this parameter, add + // the current one to it; otherwise, create a one-item list and use + // that + if (current_actions) + { + const std::string all_actions = current_actions.get() + + "," + + Utilities::int_to_string (actions.size()-1); + entries->put (get_current_full_path(entry) + path_separator + "actions", + all_actions); + } + else + entries->put (get_current_full_path(entry) + path_separator + "actions", + Utilities::int_to_string(actions.size()-1)); + + + // as documented, run the action on the default value at the very end + const std::string default_value = entries->get(get_current_full_path(entry) + + path_separator + + "default_value"); + action (default_value); +} + + + void ParameterHandler::declare_alias(const std::string &existing_entry_name, const std::string &alias_name, @@ -2193,9 +2230,12 @@ ParameterHandler::set (const std::string &entry_string, if (entries->get_optional(path + path_separator + "alias")) path = get_current_full_path(entries->get(path + path_separator + "alias")); - // assert that the entry is indeed declared + // get the node for the entry. if it doesn't exist, then we end up + // in the else-branch below, which asserts that the entry is indeed + // declared if (entries->get_optional(path + path_separator + "value")) { + // verify that the new value satisfies the provided pattern const unsigned int pattern_index = entries->get (path + path_separator + "pattern"); AssertThrow (patterns[pattern_index]->match(new_value), @@ -2205,6 +2245,20 @@ ParameterHandler::set (const std::string &entry_string, path_separator + "pattern_description"))); + // then also execute the actions associated with this + // parameter (if any have been provided) + const boost::optional action_indices_as_string + = entries->get_optional (path + path_separator + "actions"); + if (action_indices_as_string) + { + std::vector action_indices + = Utilities::string_to_int (Utilities::split_string_list (action_indices_as_string.get())); + for (unsigned int index : action_indices) + if (actions.size() >= index+1) + actions[index](new_value); + } + + // finally write the new value into the database entries->put (path + path_separator + "value", new_value); } @@ -2998,7 +3052,9 @@ ParameterHandler::scan_line (std::string line, path = get_current_full_path(entries->get(path + path_separator + "alias")); } - // assert that the entry is indeed declared + // get the node for the entry. if it doesn't exist, then we end up + // in the else-branch below, which asserts that the entry is indeed + // declared if (entries->get_optional (path + path_separator + "value")) { // if entry was declared: @@ -3009,6 +3065,7 @@ ParameterHandler::scan_line (std::string line, // entry, then ignore content if (entry_value.find ('{') == std::string::npos) { + // verify that the new value satisfies the provided pattern const unsigned int pattern_index = entries->get (path + path_separator + "pattern"); AssertThrow (patterns[pattern_index]->match (entry_value), @@ -3017,8 +3074,22 @@ ParameterHandler::scan_line (std::string line, entry_value, entry_name, patterns[pattern_index]->description())); + + // then also execute the actions associated with this + // parameter (if any have been provided) + const boost::optional action_indices_as_string + = entries->get_optional (path + path_separator + "actions"); + if (action_indices_as_string) + { + std::vector action_indices + = Utilities::string_to_int (Utilities::split_string_list (action_indices_as_string.get())); + for (unsigned int index : action_indices) + if (actions.size() >= index+1) + actions[index](entry_value); + } } + // finally write the new value into the database entries->put (path + path_separator + "value", entry_value); } -- 2.39.5