Configuration code generation#
Applications that use the extended Rose metadata can run the LFRic Configurator as part of the application build process. The Configurator generates all the code required to read namelist configuration files. The code also makes the configuration information available to application coders in a user-friendly format.
This section describes how to load an application configuration into the application, and how code can use the various types of application configuration.
Loading the configuration#
The Configurator generates a procedure, read_configuration, to
read a namelist configuration file. Each namelist configuration is
stored in a namelist_type object. All the namelist_type
objects are stored in a namelist_collection_type object.
use configuration_mod, only: read_configuration
type(namelist_collection_type) :: configuration
<snip>
call read_configuration( namelist_file, configuration )
The LFRic infrastructure provides a driver configuration component that orchestrates both reading of the namelist configuration file and cross-checking the contents to ensure all required namelists are present. The driver configuration component can be used instead of directly calling the above procedure.
Using the Configuration Object#
The term “configuration object” refers to an object of type
namelist_collection_type. It holds a number of namelist_type
objects each of which holds the configuration choices for one of the
namelists. To access a namelist object, call the get_namelist
function on the namelist name:
use namelist_mod, only: namelist_type
use namelist_collection_mod, only : namelist_collection_type
type(namelist_collection_type) :: configuration
type(namelist_type), pointer :: base_mesh_nml
base_mesh_nml => configuration%get_namelist('base_mesh')
Then use the get_value function of the namelist_type object to
get the configuration value of a variable:
character(str_def) :: mesh_name
call base_mesh_nml%get_value('mesh_name', mesh_name)
Enumerations#
An enumeration is a variable that can take one of a small number of fixed values. In the namelist the permitted values are strings, but within the code, the option and each of the permitted values are converted into integers.
To get, and to use, an enumeration, one has to get the value
representing the choice, but also one or more of the enumeration list
to check against. Enumerations are stored as i_def integers. The
enumeration options are parameters that can be obtained directly from
Configurator-generated _config_mod modules.
To illustrate, Rose metadata can configure the value of the
geometry variable in the namelist so that it can be either the
string “spherical” or the string “planar”. In the following code, is
checked against two allowed choices of geometry: spherical and
planar, referenced by the two integer parameters in the
base_mesh_config_mod module. The names of the parameters are
prefixed with the name of the variable to ensure there is no
duplication of parameter names with other enumeration variables:
use base_mesh_config_mod, only: geometry_spherical, geometry_planar
integer(i_def) :: geometry_choice
real(r_def) :: domain_bottom
base_mesh_nml => configuration%get_namelist('base_mesh')
call base_mesh_nml%get_value('geometry', geometry_choice)
select case (geometry_choice)
case (geometry_planar)
domain_bottom = 0.0_r_def
case (geometry_spherical)
domain_bottom = earth_radius
case default
call log_event("Invalid geometry", LOG_LEVEL_ERROR)
end select
Hidden values
Use of enumerations can be better than using numerical options or string variables.
A parameter name is more meaningful and memorable than a numerical option, making code more readable. There is also a clearer link between the name and the metadata, as the metadata can be easily searched to find information about the option.
Code that compares integer options and parameters is safer than code that compares string options and parameters. If there are spelling errors in the names in the code, the former will fail at compile time whereas problems with the latter only arise at run-time.
Duplicating namelists#
Where namelists are duplicated, the possible values of the instance
variable can be used to distinguish between them. For example, for a
namelist partitioning with an instance key of mesh_choice,
the relevant parts of the Rose metadata may look as follows:
[namelist:partitioning]
duplicate=true
instance_key_member=mesh_choice
[namelist:partitioning=mesh_choice]
!enumeration=true
values='source', 'destination'
As the instance key is mesh_choice, there may be two copies of the
namelist each with a different mesh_choice:
&partitioning
mesh_choice = 'destination',
partitioner = 'cubedsphere',
panel_decomposition = 'auto',
/
&partitioning
mesh_choice = 'source',
partitioner = 'planar',
panel_decomposition = 'auto',
/
The different namelist options can be extracted with the following
code (noting that the possible mesh_choice strings must be known
in the code):
! Get namelist objects for the source and destination partitioning
source_partitioning_nml => &
configuration%get_namelist('partitioning', &
'source')
destination_partitioning_nml => &
configuration%get_namelist('partitioning', &
'destination')
! Extract information from the two different namelist objects
call source_partitioning_nml%get_value('partitioner', &
source_partitioner)
call destination_partitioning_nml%get_value('partitioner', &
destination_partitioner)
Using config_mod files#
In the examples above, the config_mod files were used only to
obtain the parameters that represent the options of an enumeration
variable.
It is normally possible to obtain any variables direct from the
config_mod files rather than going through the configuration
object functions. However, this method cannot work where namelists are
duplicated. Namelists can be duplicated by metadata definition as
described above, in which case values are distinguished by a key
variable.
But applications can also be required to read two separate namelist
configurations where the same namelist appears in both. In these
cases, the application can load each configuration into two separate
configuration objects. This means that different parts of the
application can be passed different configuration objects, and the
data in the configuration object will be specific for that part of the
application. While the parameter values that define enumerator options
will be the same for both parts of the application, the values for the
first namelist in the config_mod file will be overwritten by the
second namelist to be read in.
In the following example, the same requirement as the example above is
met by directly using the value of the geometry option from the
config_mod file:
use base_mesh_config_mod, only: geometry_spherical, &
geometry_planar, &
geometry
real(r_def) :: domain_bottom
select case (geometry)
case (geometry_planar)
domain_bottom = 0.0_r_def
case (geometry_spherical)
domain_bottom = earth_radius
case default
call log_event("Invalid geometry", LOG_LEVEL_ERROR)
end select
The default case would cause an error if geometry has not been
set or if it has been set to another valid value that is not supported
by this part of the code.