From a967548bdcc6b3c448d705b211727ae8e17162e3 Mon Sep 17 00:00:00 2001 From: Luca Heltai Date: Wed, 20 Sep 2017 11:27:19 +0200 Subject: [PATCH] ParameterAcceptor class. --- include/deal.II/base/parameter_acceptor.h | 517 ++++++++++++++++++ source/base/CMakeLists.txt | 1 + source/base/parameter_acceptor.cc | 237 ++++++++ .../parameter_acceptor_01.cc | 48 ++ .../parameter_acceptor_01.output | 3 + 5 files changed, 806 insertions(+) create mode 100644 include/deal.II/base/parameter_acceptor.h create mode 100644 source/base/parameter_acceptor.cc create mode 100644 tests/parameter_handler/parameter_acceptor_01.cc create mode 100644 tests/parameter_handler/parameter_acceptor_01.output diff --git a/include/deal.II/base/parameter_acceptor.h b/include/deal.II/base/parameter_acceptor.h new file mode 100644 index 0000000000..27fc2a894b --- /dev/null +++ b/include/deal.II/base/parameter_acceptor.h @@ -0,0 +1,517 @@ +//----------------------------------------------------------- +// +// Copyright (C) 2014 - 2016 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 at +// the top level of the deal.II distribution. +// +//----------------------------------------------------------- + +#ifndef dealii_base_parameter_acceptor_h +#define dealii_base_parameter_acceptor_h + +#include +#include +#include +#include +#include +#include +#include + +DEAL_II_NAMESPACE_OPEN + +/** + * A parameter acceptor base class. This class is used to define a public + * interface for classes wich want to use a single global ParameterHandler to + * handle parameters. This class declares one static ParameterHandler, and two + * static functions (declare_all_parameters() and parse_all_parameters()) that + * manage all of the derived classes. + * + * The basic interface provides two subscription mechanisms: a **global + * subscription mechanism** and a **local subscription mechanism**. + * + * The global subscription mechanism is such that whenever a class that was + * derived by ParameterAcceptor is constructed, a static registry + * (ParameterAcceptor::class_list) in the base class is updated with a pointer + * to the derived class, and a path in the parameter file. Such registry is + * traversed upon invocation of the single function + * ParameterAcceptor::initialize(file.prm) which in turn calls the method + * ParameterAcceptor::declare_parameters() for each of the registered classes, + * reads the file `file.prm` and subsequently calls the method + * ParameterAcceptor::parse_parameters(), again for each of the registered + * classes. The method log_info() can be used to extract informations about the + * classes that have been derived from ParameterAcceptor, and that will be + * parsed when calling ParameterAcceptor::initialize(). + * + * ParameterAcceptor can be used in two different ways: by overloading the + * ParameterAcceptor::declare_parameters() and + * ParameterAcceptor::parse_parameters() methods, or by calling its + * ParameterAcceptor::add_parameter() method for each parameter we want to + * have. This in turns makes sure that the given parameter is registered in the + * global parameter handler (by calling ParameterHandler::add_parameter()), at + * the correct path. If you define all your parameters using the + * ParameterAcceptor::add_parameter() method, then you don't need to overload + * any of the virtual methods of this class. + * + * If some post processing is required on the parsed values, the user can + * attach a signal to ParameterAcceptor::parse_parameters_call_back can be + * overridden, which is called just after the parse_parameters() function of + * each class. + * + * A typical usage of this class is the following: + * + * @code + * // This is your own class, derived from ParameterAcceptor + * class MyClass : public ParameterAcceptor { + * + * // The constructor of ParameterAcceptor requires a std::string, + * // which defines the section name where the parameters of MyClass + * // will be stored. + * + * MyClass(std::string name) : + * ParameterAcceptor(name) + * { + * add_parameter("A param", member_var); + * } + * + * private: + * std::vector member_var; + * ... + * }; + * + * int main() { + * // Make sure you build your class BEFORE calling + * // ParameterAcceptor::initialize() + * MyClass class; + * + * // With this call, all derived classes will have their + * // parameters initialized + * ParameterAcceptor::initialize("file.prm"); + * } + * @endcode + * + * An implementation that uses user defined declare and parse functions is given + * by the following example: + * + * @code + * // Again your own class, derived from ParameterAcceptor + * class MyClass : public ParameterAcceptor { + * + * MyClass(std::string name) : + * ParameterAcceptor(name) + * {} + * + * virtual void declare_parameters(ParameterHandler &prm) { + * ... + * } + * + * virtual void parse_parameters(ParameterHandler &prm) { + * ... + * } + * }; + * + * int main() { + * // Make sure you build your class BEFORE calling + * // ParameterAcceptor::initialize() + * MyClass class; + * ParameterAcceptor::initialize("file.prm"); + * class.run(); + * } + * @endcode + * + * + * Parameter files can be organised into section/subsection/subsubsection. + * To do so, the std::string passed to ParameterAcceptor within the + * constructor of the derived class needs to contain the separator "/". + * In fact, "first/second/third/My Class" will organize the parameters + * as follows + * + * @code + * subsection first + * subsection second + * subsection third + * subsection My Class + * ... # all the parameters + * end + * end + * end + * end + * @endcode + * + * In the following examles, we propose some use cases with increasing + * complexities. + * + * MyClass is derived from ParameterAcceptor and has a + * member object that is derived itself from ParameterAcceptor. + * @code + * class MyClass : public ParameterAcceptor + * { + * MyClass (std::string name); + * virtual void declare_parameters(ParameterHandler &prm); + * private: + * SomeParsedClass my_subclass; + * ... + * }; + * + * MyClass::MyClass(std::string name) + * : + * ParameterAcceptor(name), + * my_subclass("Forcing term") + * {} + * + * void MyClass::declare_parmeters(ParameterHandler &prm) + * { + * // many add_parameter(...); + * } + * + * ... + * + * int main() + * { + * MyClass mc("My Class"); + * + * ParameterAcceptor::initialize("file.prm"); + * } + * @endcode + * + * In this case, the structure of the parameters will be + * @code + * subsection Forcing term + * ... #parameters of SomeParsedClass + * end + * subsection My class + * ... #all the parameters of MyClass defined in declare_parameters + * end + * @endcode + * Note that the sections are alphabetically sorted. + * + * Now suppose that in the main file we need two or more objects of MyClass + * @code + * int main() + * { + * MyClass ca("Class A"); + * MyClass cb("Class B"); + * ParameterAcceptor::initialize("file.prm"); + * } + * @endcode + * + * What we will read in the parameter file looks like + * @code + * subsection Class A + * ... + * end + * subsection Class B + * ... + * end + * subsection Forcing term + * ... + * end + * @endcode + * Note that there is only one section "Forcing term", this is because + * both objects have defined the same name for the section of their + * SomeParsedClass. There are two strategies to manage this issue. The + * first one (not recommended) would be to change the name of the section + * of SomeParsedClass such that it contains also the string passed to + * the constructor of MyClass: + * @code + * MyClass::MyClass(std::string name) + * : + * ParameterAcceptor(name), + * my_subclass(name+" --- forcing term") + * {} + * @endcode + * + * The other way to proceed (recommended) is to use exploit the /section/subsection + * approach **in the main class**. + * @code + * int main() + * { + * MyClass ca("/Class A/Class"); + * MyClass cb("/Class B/Class"); + * ParameterAcceptor::initialize("file.prm"); + * } + * @endcode + * Now, in the parameter file we can find + * @code + * subsection Class A + * subsection Class + * ... + * end + * subsection Forcing term + * ... + * end + * end + * subsection Class B + * subsection Class + * ... + * end + * subsection Forcing term + * ... + * end + * end + * @endcode + * Note the "/" at the begin of the string name. This is interpreted by + * ParameterAcceptor like the root folder in Unix systems. This means + * that the sections "Class A" and "Class B" will not be nested under any + * section. On the other hand, if the string does not begin with a "/" + * as in the previous cases (and for the ParsedFunction also in this last + * example) the section will be created **under the current path**, which + * depends on the previously defined sections/subsections/subsubsections. + * Indeed, the section "Forcing term" is nested under "Class A" or "Class B". + * To make things more clear. let's consider the following two examples + * @code + * int main() + * { + * MyClass ca("/Class A/Class"); + * MyClass cb("Class B/Class"); + * ParameterAcceptor::initialize("file.prm"); + * } + * @endcode + * The parameter file will have the following structure + * @code + * subsection Class A + * subsection Class + * ... + * end + * subsection Forcing term + * ... + * end + * subsection Class B + * subsection Class + * ... + * end + * subsection Forcing term + * ... + * end + * end + * end + * @endcode + * + * If instead one of the paths ends with "/" instead of just + * a name of the class, subsequent classes will be declared + * under the full path, as if the class name should be interpreted + * as a directory: + * @code + * int main() + * { + * MyClass ca("/Class A/Class/"); + * MyClass cb("Class B/Class"); + * ParameterAcceptor::initialize("file.prm"); + * } + * @endcode + * The parameter file will have the following structure + * @code + * subsection Class A + * subsection Class + * ... + * subsection Forcing term + * ... + * end + * subsection Class B + * subsection Class + * ... + * end + * subsection Forcing term + * ... + * end + * end + * end + * end + * @endcode + * + * As a final remark, in order to allow a proper management of all the + * sections/subsections, the instantiation of objects and the call to + * ParameterAcceptor::initialize() **cannot be done in multithread**. + * + * If you pass an empty name, the boost::core::demangle() function is used to + * fill the section name with a human readable version of the class name + * itself. + * + * @author Luca Heltai, 2017. + */ +class ParameterAcceptor : public Subscriptor +{ +public: + /** + * The constructor adds derived classes to the list of acceptors. If + * a section name is specified, then this is used to scope the + * parameters in the given section, otherwise a pretty printed + * version of the derived class is used. + */ + ParameterAcceptor(const std::string section_name=""); + + /** + * The destructor sets to zero the pointer relative to this index, + * so that it is safe to destroy the mother class. + */ + virtual ~ParameterAcceptor(); + + /** + * Call declare_all_parameters(), read filename (if it is present as input + * parameter) and parse_all_parameters() on the static member prm. If + * outfilename is not the emtpy string, then write the content that was read + * in to the outfilename. The format of both input and output files are + * selected using the extensions of the files themselves. This can be either + * `prm` or `xml`. If the output format is `prm`, then the + * `output_style_for_prm_format` is used to decide wether we write the full + * documentation as well, or only the parameters. + * + * If the input file does not exist, a default one with the same name is created + * for you, and an exception is thrown. + * + * @param filename Input file name + * @param output_filename Output file name + * @param output_style_for_prm_format How to write the output file if format is `prm` + */ + static void initialize(const std::string filename, + const std::string output_filename="", + const ParameterHandler::OutputStyle output_style_for_prm_format=ParameterHandler::ShortText); + + /** + * Clear class list and global parameter file. + */ + static void clear(); + + /** + * Declare parameter entries of the derived class. + */ + virtual void declare_parameters(ParameterHandler &prm); + + /** + * Declare parameter call back. This function is called at the end of + * declare_all_parameters, to allow users to process their parameters right + * after they have been parsed. The default implementation is empty. + * + * You can use this function, for example, to create a quadrature + * rule after you have read how many quadrature points you wanted + * to use from the parameter file. + */ + boost::signals2::signal declare_parameters_call_back; + + /** + * Parse the (derived class) parameters. + */ + virtual void parse_parameters(ParameterHandler &prm); + + /** + * Parse parameter call back. This function is called at the end of + * parse_all_parameters, to allow users to process their parameters right + * after they have been parsed. The default implementation is empty. + * + * You can use this function, for example, to create a quadrature + * rule after you have read how many quadrature points you wanted + * to use from the parameter file. + */ + boost::signals2::signal parse_parameters_call_back; + + /** + * Parse the given ParameterHandler. This function enters the + * subsection returned by get_section_name() for each derived class, + * and parses all parameters that were added using add_parameter(). + */ + static void parse_all_parameters(ParameterHandler &prm=ParameterAcceptor::prm); + + + /** + * Print information to deallog about all stored classes. + */ + static void log_info(); + + + /** + * Initialize the global ParameterHandler with all derived classes + * parameters.This function enters the subsection returned by + * get_section_name() for each derived class, and declares all parameters + * that were added using add_parameter(). + */ + static void declare_all_parameters(ParameterHandler &prm=ParameterAcceptor::prm); + + + /** + * Return the section name of this class. If a name was provided + * at construction time, then that name is returned, otherwise it + * returns the name of this class, pretty printed. + */ + std::string get_section_name() const; + + /** + * Travers all registered classes, and figure out what + * subsections we need to enter. + */ + std::vector get_section_path() const; + + /** + * Add a parameter in the correct path. This method forwards all arguments to + * the ParameterHandler::add_parameter() method, after entering the correct + * section path. + * + * See the documentation of ParameterHandler::add_parameter() for more + * information. + */ + template + void add_parameter(const std::string &entry, + ParameterType ¶meter, + const std::string &documentation = std::string(), + const Patterns::PatternBase &pattern = + *Patterns::Tools::Convert::to_pattern()); + + /** + * The global parameter handler. + */ + static ParameterHandler prm; + +private: + /** + * Make sure we enter the right subsection of the global parameter file. + */ + void enter_my_subsection(ParameterHandler &prm); + + /** + * This function undoes what the enter_my_subsection() function did. It only + * makes sense if enter_my_subsection() is called before this one. + */ + void leave_my_subsection(ParameterHandler &prm); + + /** + * A list containing all constructed classes of type + * ParameterAcceptor. + */ + static std::vector > class_list; + + /** The index of this specific class within the class list. */ + const unsigned int acceptor_id; + + /** + * Separator between section and subsection. + */ + static const char sep = '/'; + +protected: + /** The subsection name for this class. */ + const std::string section_name; +}; + + +// Inline and template functions +template +void ParameterAcceptor::add_parameter(const std::string& entry, + ParameterType& parameter, + const std::string& documentation, + const Patterns::PatternBase& pattern) +{ + enter_my_subsection(prm); + prm.add_parameter(entry, parameter, documentation, pattern); + leave_my_subsection(prm); +} + + +DEAL_II_NAMESPACE_CLOSE + +#endif + diff --git a/source/base/CMakeLists.txt b/source/base/CMakeLists.txt index 172607dcaf..a525a152e6 100644 --- a/source/base/CMakeLists.txt +++ b/source/base/CMakeLists.txt @@ -47,6 +47,7 @@ SET(_unity_include_src named_selection.cc parallel.cc parameter_handler.cc + parameter_acceptor.cc parsed_function.cc partitioner.cc patterns.cc diff --git a/source/base/parameter_acceptor.cc b/source/base/parameter_acceptor.cc new file mode 100644 index 0000000000..e0213ad7a0 --- /dev/null +++ b/source/base/parameter_acceptor.cc @@ -0,0 +1,237 @@ +//----------------------------------------------------------- +// +// Copyright (C) 2015 - 2016 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 at +// the top level of the deal.II distribution. +// +//----------------------------------------------------------- + +#include +#include +#include +#include +#include +#include + +DEAL_II_NAMESPACE_OPEN + + +// Static empty class list +std::vector > ParameterAcceptor::class_list; +// Static parameter handler +ParameterHandler ParameterAcceptor::prm; + +ParameterAcceptor::ParameterAcceptor(const std::string name) : + acceptor_id(class_list.size()), + section_name(name) +{ + SmartPointer pt(this, boost::core::demangle(typeid(*this).name()).c_str()); + class_list.push_back(pt); +} + + +ParameterAcceptor::~ParameterAcceptor() +{ + class_list[acceptor_id] = 0; +} + +std::string ParameterAcceptor::get_section_name() const +{ + return (section_name != "" ? section_name : boost::core::demangle(typeid(*this).name())); +} + + +void +ParameterAcceptor::initialize(const std::string filename, + const std::string output_filename, + const ParameterHandler::OutputStyle output_style_for_prm_format) +{ + declare_all_parameters(); + // check the extension of input file + if (filename.substr(filename.find_last_of(".") + 1) == "prm") + { + try + { + prm.parse_input(filename); + } + catch (dealii::PathSearch::ExcFileNotFound) + { + std::ofstream out(filename); + Assert(out, ExcIO()); + prm.print_parameters(out, ParameterHandler::Text); + out.close(); + AssertThrow(false, ExcMessage("You specified "+filename+" as input "+ + "parameter file, but it does not exist. " + + "We created one for you.")); + } + } + else if (filename.substr(filename.find_last_of(".") + 1) == "xml") + { + std::ifstream is(filename); + if (!is) + { + std::ofstream out(filename); + Assert(out, ExcIO()); + prm.print_parameters(out, ParameterHandler::XML); + out.close(); + is.clear(); + AssertThrow(false, ExcMessage("You specified "+filename+" as input "+ + "parameter file, but it does not exist. " + + "We created one for you.")); + } + prm.parse_input_from_xml(is); + } + else + AssertThrow(false, ExcMessage("Invalid extension of parameter file. Please use .prm or .xml")); + + parse_all_parameters(); + if (output_filename != "") + { + std::ofstream outfile(output_filename.c_str()); + Assert(outfile, ExcIO()); + std::string extension = output_filename.substr(output_filename.find_last_of(".") + 1); + + if ( extension == "prm") + { + outfile << "# Parameter file generated with " << std::endl + << "# DEAL_II_GIT_BRANCH= " << DEAL_II_GIT_BRANCH << std::endl + << "# DEAL_II_GIT_SHORTREV= " << DEAL_II_GIT_SHORTREV << std::endl; + Assert(output_style_for_prm_format == ParameterHandler::Text || + output_style_for_prm_format == ParameterHandler::ShortText, + ExcMessage("Only Text or ShortText can be specified in output_style_for_prm_format.")) + prm.print_parameters(outfile, output_style_for_prm_format); + } + else if (extension == "xml") + prm.print_parameters(outfile, ParameterHandler::XML); + else if (extension == "latex" || extension == "tex") + prm.print_parameters(outfile, ParameterHandler::LaTeX); + else + AssertThrow(false,ExcNotImplemented()); + } +} + +void +ParameterAcceptor::clear() +{ + class_list.clear(); + prm.clear(); +} + + + +void ParameterAcceptor::declare_parameters(ParameterHandler &prm) +{} + + + +void ParameterAcceptor::parse_parameters(ParameterHandler &prm) +{} + + + +void +ParameterAcceptor::log_info() +{ + deallog.push("ParameterAcceptor"); + for (unsigned int i=0; iget_section_name() << std::endl; + else + deallog << " NULL" << std::endl; + } + deallog.pop(); +} + +void ParameterAcceptor::parse_all_parameters(ParameterHandler &prm) +{ + for (unsigned int i=0; i< class_list.size(); ++i) + if (class_list[i] != NULL) + { + class_list[i]->enter_my_subsection(prm); + class_list[i]->parse_parameters(prm); + class_list[i]->parse_parameters_call_back(); + class_list[i]->leave_my_subsection(prm); + } +} + +void ParameterAcceptor::declare_all_parameters(ParameterHandler &prm) +{ + for (unsigned int i=0; i< class_list.size(); ++i) + if (class_list[i] != NULL) + { + class_list[i]->enter_my_subsection(prm); + class_list[i]->declare_parameters(prm); + class_list[i]->declare_parameters_call_back(); + class_list[i]->leave_my_subsection(prm); + } +} + + +std::vector +ParameterAcceptor::get_section_path() const +{ + Assert(acceptor_id < class_list.size(), ExcInternalError()); + std::vector sections = + Utilities::split_string_list(class_list[acceptor_id]->get_section_name(), sep); + bool is_absolute = false; + if (sections.size() > 1) + { + // Handle the cases of a leading "/" + if (sections[0] == "") + { + is_absolute = true; + sections.erase(sections.begin()); + } + } + if (is_absolute == false) + { + // In all other cases, we scan for earlier classes, and prepend the + // first absolute path (in reverse order) we find to ours + for (int i=acceptor_id-1; i>=0; --i) + if (class_list[i] != NULL) + if (class_list[i]->get_section_name().front() == sep) + { + bool has_trailing = class_list[i]->get_section_name().back() == sep; + // Absolute path found + auto secs = Utilities::split_string_list(class_list[i]->get_section_name(), sep); + Assert(secs[0] == "", ExcInternalError()); + // Insert all sections except first and last + sections.insert(sections.begin(), secs.begin()+1, secs.end()-(has_trailing ? 0 : 1)); + // exit from for cycle + break; + } + } + return sections; +} + +void ParameterAcceptor::enter_my_subsection(ParameterHandler &prm=ParameterAcceptor::prm) +{ + std::vector sections = get_section_path(); + for (auto sec : sections) + { + prm.enter_subsection(sec); + } +} + +void ParameterAcceptor::leave_my_subsection(ParameterHandler &prm=ParameterAcceptor::prm) +{ + std::vector sections = get_section_path(); + for (auto sec : sections) + { + prm.leave_subsection(); + } +} + + + +DEAL_II_NAMESPACE_CLOSE + diff --git a/tests/parameter_handler/parameter_acceptor_01.cc b/tests/parameter_handler/parameter_acceptor_01.cc new file mode 100644 index 0000000000..2abeb978be --- /dev/null +++ b/tests/parameter_handler/parameter_acceptor_01.cc @@ -0,0 +1,48 @@ +//----------------------------------------------------------- +// +// Copyright (C) 2015 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 at +// the top level of the deal.II distribution. +// +//----------------------------------------------------------- + +// Classical way of working with parameters. + +#include "../tests.h" +#include + +template +class Test : public ParameterAcceptor +{ +public: + virtual void declare_parameters(ParameterHandler &prm) + { + prm.declare_entry("A double", "0.0", Patterns::Double(), + "Documentation"); + }; + + virtual void parse_parameters(ParameterHandler &prm) + { + deallog << "Double: " + << prm.get_double("A double") << std::endl; + }; +}; + + +int main () +{ + initlog(); + Test<2> a; + Test<1> b; + + ParameterHandler prm; + a.declare_all_parameters(prm); + prm.log_parameters(deallog); +} diff --git a/tests/parameter_handler/parameter_acceptor_01.output b/tests/parameter_handler/parameter_acceptor_01.output new file mode 100644 index 0000000000..e0f0e7fb96 --- /dev/null +++ b/tests/parameter_handler/parameter_acceptor_01.output @@ -0,0 +1,3 @@ + +DEAL:parameters:Test<1>::A double: 0.0 +DEAL:parameters:Test<2>::A double: 0.0 -- 2.39.5