Exercise Runtime Parameters (DuMuX course)
Problem set-up
Here we will expand on what we've covered in the basics exercise (see dumux-course/exercises/exercise-basic/README.md
), and the problem set up will remain the same.
Preparing the exercise
Navigate to the directory dumux-course/exercises/exercise-runtimeparams/
Task 1: Understanding Input Parameters
For this task we will edit the following files:
- The shared problem file:
problem.hh
- And the shared input file:
params.input
Parameters can either be directly defined within your program, or specified via the input file. Within every main file, (*.cc
), the following function is called
// parse command line arguments and input file
Parameters::init(argc, argv);
This will read in the parameters from the input file.
The input file should either be named the same as the executable file, with a trailing *.input
, or be named prarams.input
as this is the standard in our CMake system.
Alternatively, arbitrarily named input files (e.g. exercise1.input
) can be explicitly written as the first shell argument after the executable file (here exercise_runtimeparams
) is called.
./exercise_runtimeparams
#(Calls the file params.input as the default input file.)
./exercise_runtimeparams exercise1.input
#(Calls the input file provided (exercise1.input) as the input file.)
In the input file params.input
you can find the following section
[SpatialParams]
PermeabilityAquitard = 1e-15 # m^2
Aquitard.BrooksCoreyPcEntry = 4.5e4 # Pa
PermeabilityAquifer = 1e-12 # m^2
Aquifer.BrooksCoreyPcEntry = 1e4 # Pa
When a parameter is defined directly within your program, you'll need to recompile your program every time you change the value. When a parameter is passed via the input file, this is not the case. If we decided to vary the entry pressure in our geologic units a few times via the parameters listed above, there would be no need to recompile between simulation runs.
-
Task 1: Change the aquitard's entry pressure in the input file to a lower value and compare the results with the previous solution. You do not need to recompile the executable.
Task 2: Setting a variable to collect a runtime parameter
Let's free one of the variables in this exercise. Within the class problem.hh
, in the first line of the Neumann boundary condition definition function neumannAtPos(...)
, the injection rate is defined as 1.0 \cdot 10^{-4} kg s^{-1} m^{-2}
.
// inject nitrogen. negative values mean injection
// units kg/(s*m^2)
values[Indices::conti0EqIdx + FluidSystem::N2Idx]= -1e-4/FluidSystem::molarMass(FluidSystem::N2Idx);
values[Indices::conti0EqIdx + FluidSystem::H2OIdx] = 0.0;
This parameter may need to change, and if we choose to always change this within the class, we will need to recompile every time.
Instead of hard defining this parameter within the function, we can set a variable to read into our parameter tree via the input file and use this in our function instead.
To do this, there are two functions defined in dumux/dumux/common/parameters.hh
, getParam()
and getParamFromGroup()
. They use the following format:
variable_ = getParam<TYPE>("GROUPNAME.PARAMNAME");
or
variable_ = getParamFromGroup<TYPE>("GROUPNAME", "PARAMNAME");
<TYPE>
,<GROUPNAME>
,<PARAMNAME>
should be appropriately defined for your variable:
-
<TYPE>
is the type of the parameter to read (e.g.Scalar
) -
<GROUPNAME>
is the group in the input file (e.g.Problem
) -
<PARAMNAME>
is the name of the parameter in the input file (e.g.AquiferDepth
)
An example of this is already performed in the problem constructor. The Injection Duration (injectionDuration_
) is defined via the input file and can then be used later in the problem header.
// depth of the aquifer, units: m
aquiferDepth_ = getParam<Scalar>("Problem.AquiferDepth");
// the duration of the injection, units: second
injectionDuration_ = getParamFromGroup<Scalar>("Problem","InjectionDuration");
The injection duration parameter is located in the [Problem]
group, is named InjectionDuration
, and has the type Scalar
.
This variable should then be defined as a Scalar
at the bottom of this problem class in the private section.
Scalar injectionDuration_;
In the input file, within the group [Problem]
, a value is set to the Parameter name. This is then called in the problem class.
[Problem]
InjectionDuration = 2.628e6 # in seconds, i.e. one month
-
Task 2: The goal is to replace the value
-1e-4
invalues[Indices::conti0EqIdx + FluidSystem::N2Idx]= -1e-4/FluidSystem::molarMass(FluidSystem::N2Idx);
with a runtime variable.
- (2a) Develop a new variable called
totalAreaSpecificInflow_
, - (2b) Assign a value to this variable from a path in the input file, and
- (2c) Incorporate this variable into the injection boundary condition.
When your problem file and the input file are edited, compile and run the exercise.
make exercise_runtimeparams && ./exercise_runtimeparams params.input
- (2a) Develop a new variable called
Task 3: Default Values for Runtime Parameters
In the case that no path to a required runtime parameter is provided in the input file, the program will no longer run. You can try this if you like by removing the TotalAreaSpecificInflow
from the input file. The resulting error message should look like this:
loggingparametertree.hh:316:
Key Problem.TotalAreaSpecificInflow not found in the parameter tree ---> Abort!
To avoid this, we can place a default value in the variable definition. Whenever the parameter is specified in the input file, this default value in your class will be overwritten. In the case that no value is provided in the input file, this default value will be used. In order to do this, follow the following template.
variable_ = getParam<TYPE>("GROUPNAME.PARAMNAME", DEFAULTVALUE);
or
variable_ = getParamFromGroup<TYPE>("GROUPNAME","PARAMNAME", DEFAULTVALUE);
-
Task 3: Set up the
totalAreaSpecificInflow_
variable to record a default value of-1e-4
and run this with and without a provided value of-1e-3
in the input file.
Task 4: Other Runtime Parameter Functions
Setting default values for variables defined with runtime parameters can also lead to problems. If one runtime parameter from the input file is set to multiple different variables, each with a different default value, changing the variable in one location can lead to unexpected changes elsewhere. On top of this, in DuMux, there are a few base variables that are set with default values for all DuMux simulations. These can be found in the header file dumux/common/parameters.hh
in the function globalDefaultParameters
.
One way to check this is to use either the hasParam()
or the hasParamInGroup()
function. These functions returning bool
s will check to see if a parameter is read in via the input file. These functions are also both defined in the dumux/dumux/common/parameters.hh
class, and follow a similar format to that of getParam()
and getParamFromGroup()
.
An example of this would look like this:
if (hasParam("GROUPNAME.PARAMNAME"))
std::cout << "Parameter value is read from the input file." << std::endl;
else
std::cout << "Using the default parameter value." << std::endl;
or,
if (hasParamInGroup("GROUPNAME","PARAMNAME"))
std::cout << "Parameter value is read from the input file." << std::endl;
else
std::cout << "Using the default parameter value." << std::endl;
Using these functions we can better check which parameter values are being included in our program.
-
Task 4: Using one of the bool
hasParam
functions, place an output in the problem file to alert the user where the parameter value comes from.