Parameter Retrieval
ClimaParams.jl provides a centralized system for managing climate model parameters. The core workflow is to create a parameter dictionary and then retrieve parameters from it.
Core Functions
Two functions cover most use:
create_toml_dictconstructs a parameter dictionary from TOML filesget_parameter_valuesretrieves parameters from the dictionary
Creating Parameter Dictionaries
To construct a parameter dictionary, pass in the desired floating point type. This sources parameter values from the global default list stored in src/parameters.toml.
import ClimaParams as CP
toml_dict = CP.create_toml_dict(Float64)ParamDict{Float64} with 696 parametersThe float type determines the precision of every "float" parameter, and is recoverable from the dictionary with float_type:
CP.float_type(toml_dict)Float64You can also specify custom override and default files:
# One override file
toml_dict = CP.create_toml_dict(
Float64;
override_file = "my_parameters.toml",
default_file = "default_parameters.toml",
)
# Several override files, merged left to right, then applied to the defaults
toml_dict = CP.create_toml_dict(Float64, "site.toml", "my_experiment.toml")See Override Files for how merging and precedence work.
Retrieving Parameters
To retrieve parameters, pass in the TOML dictionary and the parameter names that match those in the TOML file.
params = CP.get_parameter_values(
toml_dict,
["universal_gas_constant", "gravitational_acceleration"],
)
params.gravitational_acceleration9.81You can also use direct indexing to obtain values from the parameter dictionary:
toml_dict["gravitational_acceleration"]9.81Name Maps
Name maps map global parameter names to local variable names. This is useful when you want the short, conventional symbols of a parameterization in your code without shortening the globally unique name in the TOML file.
Using NamedTuples
name_map = (;
:gravitational_acceleration => :g,
:angular_velocity_planet_rotation => :omega,
)
params = CP.get_parameter_values(toml_dict, name_map)
params.g9.81Using Dictionaries
name_map = Dict(
"gravitational_acceleration" => "g",
"angular_velocity_planet_rotation" => "omega",
)
params = CP.get_parameter_values(toml_dict, name_map)
params.omega7.2921159e-5Using Varargs
params = CP.get_parameter_values(
toml_dict,
:gravitational_acceleration => :g,
:angular_velocity_planet_rotation => :omega,
)(omega = 7.2921159e-5, g = 9.81)A name map is converted to a Dict internally, so the fields of the returned NamedTuple come out in the Dict's iteration order, not the order of the pairs you supplied. Access the fields by name, or splat the result into keyword arguments as create_parameter_struct does. Never splat it into positional arguments.
Component Logging
Pass a component name when retrieving parameters. This records which parameters are read by which model component, which write_log_file then writes out for reproducibility, and which check_override_parameter_usage uses to catch misspelled override entries.
params = CP.get_parameter_values(toml_dict, ["gravitational_acceleration"], "Ocean")(gravitational_acceleration = 9.81,)In every method above, component is the third positional argument. In the varargs method it must be passed as a keyword, because the positional slots are taken by the pairs:
CP.get_parameter_values(
toml_dict,
:gravitational_acceleration => :g;
component = "Ocean",
)Tagged Parameters
Parameters can carry a tag so that a whole group can be retrieved at once. See Parameter tags for the file syntax and for the caveat that the bundled default file is currently untagged.
overrides = Dict(
"my_diffusivity" => Dict(
"value" => 1.0,
"type" => "float",
"description" => "Example horizontal diffusivity (m² s⁻¹).",
"tag" => ["MyComponent"],
),
"my_damping_timescale" => Dict(
"value" => 3600.0,
"type" => "float",
"description" => "Example damping timescale (s).",
"tag" => ["MyComponent", "Turbulence"],
),
)
tagged_dict = CP.create_toml_dict(Float64; override_file = overrides)
CP.get_tagged_parameter_values(tagged_dict, "mycomponent")(my_damping_timescale = 3600.0, my_diffusivity = 1.0)Tag matching is case-insensitive and ignores punctuation; see fuzzy_match. Passing a vector of tags returns the union:
CP.get_tagged_parameter_values(tagged_dict, ["MyComponent", "Turbulence"])(my_damping_timescale = 3600.0, my_diffusivity = 1.0)Parameter Structs
Model components should not read the parameter dictionary in their inner code. Instead, build an immutable struct once at setup, and pass it down. This keeps the component's parameter surface explicit and the struct isbits, so it can be moved to a GPU.
The constructor takes the ParamDict and a name map from global TOML names to struct field names:
import ClimaParams as CP
Base.@kwdef struct ThermodynamicsParameters{FT}
universal_gas_constant::FT
molar_mass_dry_air::FT
temperature_triple_point::FT
# Derived parameters
R_d::FT = universal_gas_constant / molar_mass_dry_air
end
# TOML dictionary constructor
function ThermodynamicsParameters(toml_dict::CP.ParamDict)
name_map = Dict(
"universal_gas_constant" => "universal_gas_constant",
"molar_mass_dry_air" => "molar_mass_dry_air",
"temperature_triple_point" => "temperature_triple_point",
)
parameters = CP.get_parameter_values(toml_dict, name_map, "Thermodynamics")
FT = CP.float_type(toml_dict)
return ThermodynamicsParameters{FT}(; parameters...)
end
# Float-type constructor, for convenience
ThermodynamicsParameters(::Type{FT}) where {FT <: AbstractFloat} =
ThermodynamicsParameters(CP.create_toml_dict(FT))
thermo_params = ThermodynamicsParameters(Float64)
thermo_params.R_d287.0024093890231create_parameter_struct does the same job in one call, and also accepts already-constructed nested structs:
name_map = Dict(
"universal_gas_constant" => "universal_gas_constant",
"molar_mass_dry_air" => "molar_mass_dry_air",
"temperature_triple_point" => "temperature_triple_point",
)
toml_dict = CP.create_toml_dict(Float64)
CP.create_parameter_struct(ThermodynamicsParameters, toml_dict, name_map)Main.ThermodynamicsParameters{Float64}(8.3144598, 0.02897, 273.16, 287.0024093890231)Hierarchical Parameter Sets
Models with several components nest the component structs inside a model-level struct, so that a single object can be passed to every part of the model:
thermodynamics_params = Thermodynamics.ThermodynamicsParameters(toml_dict)
microphysics_params = CloudMicrophysics.Parameters.CloudMicrophysicsParameters(
toml_dict,
thermodynamics_params,
)
parameter_set = ClimaAtmosParameters(
toml_dict,
thermodynamics_params,
microphysics_params,
)Capturing a whole nested parameter set inside a GPU broadcast pushes a large struct into kernel parameter memory. Pull out the piece you need first, e.g. thp = p.params.thermodynamics_params, then broadcast over thp.
Parameters-as-functions
Parameters can be accessed through functions for added flexibility:
K_therm(param_set) = param_set.K_thermThis is useful for derived parameters:
derived_param(param_set) = param_set.param1 * param_set.param2Or to forward parameters from nested parameter structs:
forwarded_param(ps::ParamSet) = ps.nested_params.forwarded_paramSuch functions can be autogenerated using @eval:
for fn in fieldnames(ParamSet)
@eval $(fn)(ps::ParamSet) = ps.$(fn)
endParameter Types
The type field of a parameter selects the Julia type its value is converted to:
type | Julia type |
|---|---|
"float" | the FT given to create_toml_dict |
"integer" | Int |
"string" | String |
"bool" | Bool |
"datetime" | Dates.DateTime |
Conversion is by construction, so a value that cannot be represented in the declared type raises an error rather than being silently truncated: an "integer" parameter written as 2.5 throws an InexactError.
Array-valued parameters use the same type as their scalar counterparts and return a Vector of that type.
[gravitational_acceleration]
value = 9.81
type = "float"
description = "Gravitational acceleration on the planet (m s⁻²)."
[epoch_time]
value = 2000-01-01T11:58:55.816
type = "datetime"
description = "J2000 epoch (Jan 1, 2000 11:58:55.816 UTC) as a DateTime"