Parameter Handling
Rationale
The Parameter, Parameters, and ParametersValidator classes provide a
type-safe and structured way to handle configuration parameters in mfem-mgis.
Parameteris a variant type that can hold various scalar and complex types (booleans, integers, reals, strings, vectors of parameters, dictionaries, and functions).Parametersis a map associating names toParametervalues.ParametersValidatoris a helper class to declare and validate parameters in a structured manner, ensuring type safety and consistency.
The use of ParametersValidator is the recommended way to validate parameters,
though its adoption is still limited in the codebase. The older checkParameters
functions are deprecated in favor of ParametersValidator.
Basic Usage
Declaring Parameters
Parameters are typically passed as dictionaries (Parameters):
auto params = Parameters{
{"IterativeMethod", "Newton"},
{"Theta", 0.5},
{"MaxIterations", 100}
};
A Parameter can hold various types:
Parameter p1 = 42; // integer
Parameter p2 = 3.14; // real
Parameter p3 = "value"; // string
Parameter p4 = true; // boolean
Parameter p5 = Parameters{}; // nested dictionary
Parameter p6 = std::vector<Parameter>{1, 2, 3}; // list
Accessing Parameters
Use the get functions to access parameter values. Two modes are available:
Non-throwing mode: Uses a
Contextto report errors.Throwing mode: Uses the
throwingattribute for constructors or functions where no context is available.
// Non-throwing mode (recommended)
auto ctx = Context{};
const auto value = get<int>(ctx, params, "MaxIterations");
if (isInvalid(value)) {
// Handle error
return ctx.registerErrorMessage("invalid parameter");
}
// Throwing mode (for constructors or functions with attributes::Throwing)
const auto value = get<int>(throwing, params, "MaxIterations");
The contains function checks if a parameter exists:
if (contains(params, "Theta")) {
// Parameter exists
}
Validating Parameters
The ParametersValidator class provides a fluent interface to declare and validate
parameters:
auto validator = ParametersValidator{}
.add<std::string>("Material", "Name of the material")
.add<int>("MaxIterations", {.required = true})
.add<real>("Theta", "Time integration parameter", {.required = true})
.addIncompatibleParametersList({"OptionA", "OptionB"});
// Validate parameters
auto ctx = Context{};
if (!validator.validate(ctx, params)) {
// Handle validation error
return ctx.registerErrorMessage("invalid parameters");
}
The add method can:
Declare allowed keys with optional descriptions.
Specify if a parameter is required.
Restrict the parameter to specific types using template arguments.
Add custom validators.
The addIncompatibleParametersList method declares mutually exclusive parameters.
Predefined Validators
The ParametersValidator class provides predefined validators:
addStrictlyPositiveIntegerCheck: Ensures a parameter is a strictly positive integer.
Custom validators can be added using the add method with a validator function:
auto validator = ParametersValidator{}
.add("CustomParameter", [](Context& ctx, const Parameter& p) noexcept -> bool {
if (!is<int>(p)) {
return ctx.registerErrorMessage("parameter must be an integer");
}
const auto value = get<int>(throwing, p);
if (value < 0 || value > 100) {
return ctx.registerErrorMessage("parameter must be between 0 and 100");
}
return true;
});
Error Handling Rules
Non-throwing mode: Functions that accept a
Contextmust never throw. Errors are reported by returningfalseor an invalid result (e.g.,std::nullopt,InvalidResult). TheContextaccumulates error messages.Throwing mode: Functions marked with
attributes::Throwingmay throw exceptions. This mode is reserved for constructors or functions where noContextis available.Error propagation: Use the
|operator to propagate errors from aContext:auto or_raise = ctx.getThrowingFailureHandler(); validator.validate(ctx, params) | or_raise;
Error messages: Provide clear and descriptive error messages. Include the parameter name and the expected type or constraints.
Best Practices
Use ``ParametersValidator``: Prefer
ParametersValidatorover the deprecatedcheckParametersfunctions for new code.Validate early: Validate parameters as early as possible, ideally at the beginning of functions or constructors.
Document parameters: Provide descriptions for parameters to improve error messages and documentation.
Type safety: Use template arguments to restrict parameter types when possible.
Required parameters: Mark required parameters explicitly using the
requiredoption inAddArguments.Incompatible parameters: Use
addIncompatibleParametersListto enforce mutual exclusivity between parameters.
Examples
Validating a set of solver parameters:
auto validator = ParametersValidator{}
.add<std::string>("Solver", "Name of the solver", {.required = true})
.add<int>("MaxIterations", "Maximum number of iterations")
.add<real>("Tolerance", "Convergence tolerance", {.required = true})
.addIncompatibleParametersList({"Verbose", "Silent"});
auto ctx = Context{};
if (!validator.validate(ctx, params)) {
return ctx.registerErrorMessage("invalid solver parameters");
}
Validating a strictly positive integer:
auto validator = ParametersValidator{}
.addStrictlyPositiveIntegerCheck("NumberOfSteps", {.required = true});
auto ctx = Context{};
if (!validator.validate(ctx, params)) {
return false;
}
Using custom validators:
auto validator = ParametersValidator{}
.add("CustomValue", [](Context& ctx, const Parameter& p) noexcept -> bool {
if (!is<real>(p)) {
return ParametersValidator::reportUnmatchedTypeError(ctx, "CustomValue");
}
const auto value = get<real>(throwing, p);
if (value <= 0 || value > 1) {
return ctx.registerErrorMessage("CustomValue must be in (0, 1]");
}
return true;
}, "A custom value between 0 and 1");