]> https://gitweb.dealii.org/ - dealii.git/commitdiff
Add the possibility of executing an 'action' to ParameterHandler.
authorWolfgang Bangerth <bangerth@colostate.edu>
Wed, 12 Apr 2017 18:48:39 +0000 (12:48 -0600)
committerWolfgang Bangerth <bangerth@colostate.edu>
Tue, 18 Apr 2017 01:48:48 +0000 (19:48 -0600)
include/deal.II/base/parameter_handler.h
source/base/parameter_handler.cc

index 80230f5101d2ab914615550c8fce165996eafb91..61c2f254350f2faa54712551e3c12e4cdc4dfe97 100644 (file)
@@ -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.
  *
  *
+ *
+ * <h3>Adding Actions to Parameters</h3>
+ *
+ * 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 <i>convenient</i> 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
+ * <a href="http://en.cppreference.com/w/cpp/language/lambda">lambda
+ * function</a> 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 <i>all</i>
+ * 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 <code>prm.parse_input()</code>, 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.
+ *
+ *
  * <h3>Style guide for data retrieval</h3>
  *
  * 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):
  *
  * - <code>value</code>: 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<void (const std::string &value)> &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<std::shared_ptr<const Patterns::PatternBase> > 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<std::function<void (const std::string &)> > actions;
+
   /**
    * Mangle a string so that it doesn't contain any special characters or
    * spaces.
index a688c1d0c1360c48d9efe07fe435a837a8836530..ee5928e83553dfbd2c042f9c234ced9fb6573d3d 100644 (file)
@@ -2024,6 +2024,43 @@ ParameterHandler::declare_entry (const std::string           &entry,
 
 
 
+
+void ParameterHandler::add_action(const std::string &entry,
+                                  const std::function<void (const std::string &)> &action)
+{
+  actions.push_back (action);
+
+  // get the current list of actions, if any
+  boost::optional<std::string>
+  current_actions
+    =
+      entries->get_optional<std::string> (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<std::string>(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<std::string>(path + path_separator + "alias"))
     path = get_current_full_path(entries->get<std::string>(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<std::string>(path + path_separator + "value"))
     {
+      // verify that the new value satisfies the provided pattern
       const unsigned int pattern_index
         = entries->get<unsigned int> (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<std::string> action_indices_as_string
+        = entries->get_optional<std::string> (path + path_separator + "actions");
+      if (action_indices_as_string)
+        {
+          std::vector<int> 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<std::string>(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<std::string> (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<unsigned int> (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<std::string> action_indices_as_string
+                = entries->get_optional<std::string> (path + path_separator + "actions");
+              if (action_indices_as_string)
+                {
+                  std::vector<int> 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);
         }

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.