diff --git a/core/config/config_helper.cpp b/core/config/config_helper.cpp index 738c0a31539..db881ca12c7 100644 --- a/core/config/config_helper.cpp +++ b/core/config/config_helper.cpp @@ -20,7 +20,7 @@ namespace config { template <> deferred_factory_parameter -build_or_get_factory(const pnode& config, +parse_or_get_factory(const pnode& config, const registry& context, const type_descriptor& td) { diff --git a/core/config/config_helper.hpp b/core/config/config_helper.hpp index 28fd84c8a75..798d3623856 100644 --- a/core/config/config_helper.hpp +++ b/core/config/config_helper.hpp @@ -64,7 +64,7 @@ inline std::shared_ptr get_stored_obj(const pnode& config, * the registry by string. */ template -deferred_factory_parameter build_or_get_factory(const pnode& config, +deferred_factory_parameter parse_or_get_factory(const pnode& config, const registry& context, const type_descriptor& td); @@ -73,7 +73,7 @@ deferred_factory_parameter build_or_get_factory(const pnode& config, */ template <> deferred_factory_parameter -build_or_get_factory(const pnode& config, +parse_or_get_factory(const pnode& config, const registry& context, const type_descriptor& td); @@ -82,25 +82,25 @@ build_or_get_factory(const pnode& config, */ template <> deferred_factory_parameter -build_or_get_factory(const pnode& config, +parse_or_get_factory(const pnode& config, const registry& context, const type_descriptor& td); /** - * give a vector of factory by calling build_or_get_factory. + * give a vector of factory by calling parse_or_get_factory. */ template -inline std::vector> build_or_get_factory_vector( +inline std::vector> parse_or_get_factory_vector( const pnode& config, const registry& context, const type_descriptor& td) { std::vector> res; if (config.get_tag() == pnode::tag_t::array) { for (const auto& it : config.get_array()) { - res.push_back(build_or_get_factory(it, context, td)); + res.push_back(parse_or_get_factory(it, context, td)); } } else { // only one config can be passed without array - res.push_back(build_or_get_factory(config, context, td)); + res.push_back(parse_or_get_factory(config, context, td)); } return res; @@ -156,8 +156,18 @@ get_value(const pnode& config) if (config.get_tag() == pnode::tag_t::real) { return static_cast(get_value(config)); } else if (config.get_tag() == pnode::tag_t::array) { - return ValueType{get_value(config.get(0)), - get_value(config.get(1))}; + real_type real(0); + real_type imag(0); + if (config.get_array().size() >= 1) { + real = get_value(config.get(0)); + } + if (config.get_array().size() >= 2) { + imag = get_value(config.get(1)); + } + GKO_THROW_IF_INVALID( + config.get_array().size() <= 2, + "complex value array expression only accept up to two elements"); + return ValueType{real, imag}; } GKO_INVALID_STATE("Can not get complex value"); } diff --git a/core/config/stop_config.cpp b/core/config/stop_config.cpp index e3e2e7ad57b..c100cbb42f8 100644 --- a/core/config/stop_config.cpp +++ b/core/config/stop_config.cpp @@ -124,7 +124,7 @@ configure_implicit_residual(const pnode& config, const registry& context, template <> deferred_factory_parameter -build_or_get_factory(const pnode& config, +parse_or_get_factory(const pnode& config, const registry& context, const type_descriptor& td) { diff --git a/core/solver/cg.cpp b/core/solver/cg.cpp index 7c1e81c8971..71e5fcfbb3b 100644 --- a/core/solver/cg.cpp +++ b/core/solver/cg.cpp @@ -69,12 +69,12 @@ typename Cg::parameters_type Cg::parse( } if (auto& obj = config.get("criteria")) { params.with_criteria( - gko::config::build_or_get_factory_vector< + gko::config::parse_or_get_factory_vector< const stop::CriterionFactory>(obj, context, td_for_child)); } if (auto& obj = config.get("preconditioner")) { params.with_preconditioner( - gko::config::build_or_get_factory( + gko::config::parse_or_get_factory( obj, context, td_for_child)); } return params; diff --git a/include/ginkgo/core/config/config.hpp b/include/ginkgo/core/config/config.hpp index 9fe2539d95f..f2cbdd3f583 100644 --- a/include/ginkgo/core/config/config.hpp +++ b/include/ginkgo/core/config/config.hpp @@ -27,45 +27,74 @@ class pnode; /** - * parse is the main entry point to create an Ginkgo object based on + * parse is the main entry point to create an Ginkgo LinOpFactory based on * some file configuration. It reads a configuration stored as a property tree * and creates the desired type. * * General rules for configuration - * 1. all parameter and template usage are according to the class directly. It - * has the same behavior as the class like default setting without specifying - * anything. When the class factory parameters allows `with_(value)`, - * the file configuration will allow `"": value` - * 2. all key will use snake_case including template. For example, ValueType -> - * value_type - * 3. If the value is not bool, integer, or floating point, we will use string - * to represent everything. For example, we will use string to select the - * enum value. `"baseline": "absolute"` will select the absolute baseline in - * ResidualNorm critrion - * 4. `"type"` is the new key to select the class without template type. We also - * prepend the namespace except for gko. For example, we use "solver::Cg" for - * Cg solver. Note. the template type is given by the another entry or from - * the type_descriptor. - * 5. We have supports the following datatype with postfix to indicate their - * size: int32, int64, float32, float64, complex, complex. - * note: we have also allow `void` additionally in type_descriptor to specify - * file must contain the value/index type config. - * 6. We use [real, imag] to represent complex values. If it only contains one - * value or none [], we will treat it as a complex number with an imaginary - * part = 0. - * 7. In many cases, the parameter allows array input. If the array only - * contains one object, users can directly provide the object without putting - * it into an array. - * `"criteria": [{...}]` and `"criteria": {...}` are the same. + * 1. The configuration can be used to define factory parameters and class + * template parameters. Any factory parameter that is not defined in the + * configuration will fallback to their default value. Any template parameter + * that is not defined will fallback to the type_descriptor argument + * 2. The new `"type"` key determines which Ginkgo object to create. The value + * for this key is the desired class name with namespaces (except for + * `gko::`, `experimental::`). Any template parameters a class might have are + * left out. Only classes with a factory are supported. For example, the + * configuration `"type": "solver::Cg"` specifies that a Cg solver will be + * created. Note: template parameters can either be given in the + * configuration as separate key-value pairs, or in the type_descriptor. + * 3. Factory and class template parameters can be defined with key-value pairs + * that are derived from the class they are refering to. When a factory has a + * parameter with the function `with_(value)`, then the configuration + * allows `"": value` to define this parameter. When a class has a + * template parameter `template class`, then the + * configuration allows `"": value` to the template parameter. The + * supported values of the template parameter depend on the context. For + * index and value types, these are listed under 4. + * 4. Values for template parameters are represented with strings. The following + * datatypes, with postfix to indicate their size, are supported: int32, + * int64, float32, float64, complex, complex. + * 5. All keys use snake_case including template parameters. Factory parameter + * keys are already defined with snake_case in their factories, while class + * template arguments need to be translated, i.e. `ValueType -> value_type`. + * 6. The allowed values for factory parameters depend on the type the parameter + * is stored as. There are three distinct options: + * - POD types (bool, integer, floating point, or enum): Except for enum, + * the value has to be the POD type. For enums, a string value is used to + * represent them. The string has to be one of the possible enum values. + * An example of this type of parameter is the `krylov_dim` parameter for + * the Gmres solver. + * - LinOp (smart) pointers: The value has to be a string. The string is used + * to look up a LinOp object in the registry. + * An example is the `generated_preconditioner` parameter for iterative + * solvers such as Cg. + * - LinOpFactory (smart) pointers: The value can either be a string, or a + * nested configuration. The string has the same behavior as for LinOp + * pointers, i.e. an LinOpFactory object from the registry is taken. The + * nested configuration has to adhere to the general configuration rules + * again. See the examples below for some use-cases. + * An example is the `preconditioner` parameter for iterative solvers + * such as Cg. + * - CriterionFactory (smart) pointers: The value can either be a string, or + * a nested configuration. It has the same behavior as for LinOpFactory. + * - A vector of the types above: The value has to be an array with the + * inner values specified as above. + * 7. Complex values are represented as an 2-element array [real, imag]. If the + * array contains only one value, it will be considered as a complex number + * with an imaginary part = 0. An empy array will be treated as zero. + * 8. Keys that expect array of objects also accept single object which is + * interpreted as a 1-element array. This means the following configurations + * are equivalent if the key expects an array value: `"": [{object}]` + * and `"": {object}`. * - * The configuration needs to specify the resulting type by the field: + * All configurations need to specify the resulting type by the field: * ``` * "type": "some_supported_ginkgo_type" * ``` - * The result will be a deferred_factory_parameter, - * which can be thought of as an intermediate step before a LinOpFactory. - * Providing an Executor through the function `.on(exec)` will then create the - * factory with the parameters as defined in the configuration. + * The result will be a deferred_factory_parameter, which is an intermediate + * step before a LinOpFactory. Providing an Executor through the function + * `.on(exec)` will then create the factory with the parameters as defined in + * the configuration. * * Given a configuration that is defined as * ``` @@ -126,7 +155,10 @@ class pnode; * templated type, then the value and/or index type from * the descriptor will be used. Any definition of the * value and/or index type within the config will take - * precedence over the descriptor. + * precedence over the descriptor. If `void` is used for + * one or both of the types, then the corresponding type + * has to be defined in the config, otherwise the + * parsing will fail. * * @return a deferred_factory_parameter which creates an LinOpFactory after * `.on(exec)` is called on it. diff --git a/include/ginkgo/core/config/type_descriptor.hpp b/include/ginkgo/core/config/type_descriptor.hpp index 244457e989e..48475f7f469 100644 --- a/include/ginkgo/core/config/type_descriptor.hpp +++ b/include/ginkgo/core/config/type_descriptor.hpp @@ -16,15 +16,16 @@ namespace config { * This class describes the value and index types to be used when building a * Ginkgo type from a configuration file. * - * A type_descriptor is passed to the parse function defines which - * template parameters, in terms of value_type and/or index_type, the created - * object will have. For example, a CG solver created like this: + * A type_descriptor is passed in order to define the parse function defines + * which template parameters, in terms of value_type and/or index_type, the + * created object will have. For example, a CG solver created like this: * ``` * auto cg = parse(config, context, type_descriptor("float64", "int32")); * ``` * will have the value type `float64` and the index type `int32`. Any Ginkgo - * object that does not require one of these types will just ignore it. We used - * void type to specify no default type. + * object that does not require one of these types will just ignore it. The + * value `void` can be used to specify that no default type is provided. In this + * case, the configuration has to provide the necessary template types. * * If the configuration specifies one of the fields (or both): * ```