Tutorial
Terminology
ConceptualClimateModels.jl follows a process-based modelling approach to make differential equation systems from processes. A process is simply a particular equation defining the dynamics of a climate variable, while also explicitly defining which variable the equation defines. A vector of processes is composed by the user, and given to the main function processes_to_coupledodes which bundles them into a system of equations that creates a dynamical system. The dynamical system can then be further analyzed in terms of stability properties, multistability, tipping, periodic (or not) behavior, chaos, and many more aspects, via the DynamicalSystems.jl library (see the examples).
Note the distinction: a process is not the climate variable (such as "clouds" or "insolation"); rather it is the exact equation that defines the behavior of the climate variable, and could itself utilize many other already existing climate variables, or introduce new ones. In terminology of climate science a process is a generalization of the term "parameterization". Many different processes may describe the behavior of a particular variable and typically one wants to analyze what the model does when using one versus the other process.
ConceptualClimateModels.jl uses ModelingToolkit.jl for building the equations representing the climate model via symbolic expressions. It then uses DynamicalSystems.jl to further analyze the models. Familiarity with either package is good to have (but not mandatory), and will allow you to faster and better understand the concepts discussed here.
Introductory example
Let's say that we want to create the most basic energy balance model,
\[c_T \frac{dT}{dt} = ASR - OLR + f\]
where $ASR$ is the absorbed solar radiation given by
\[ASR = S (1-\alpha)\]
with $\alpha$ the planetary albedo, $OLR$ is the outgoing longwave radiation given by the linearized expression
\[OLR = A + BT\]
and $f$ some radiative forcing at the top of the atmosphere, that is based on CO2 concentrations and given by
\[f = 3.7\log_2\left(\frac{CO_2}{400}\right)\]
with CO2 concentrations in ppm.
To create this model with ConceptualClimateModels.jl while providing the least information possible we can do:
using ConceptualClimateModels
using ConceptualClimateModels.GlobalMeanEBM # submodule for global mean models
processes = [
BasicRadiationBalance(),
LinearOLR(),
ParameterProcess(α),
CO2Forcing(), # for default CO2 valu this is zero forcing
]
ds = processes_to_coupledodes(processes, GlobalMeanEBM)
println(dynamical_system_summary(ds))┌ Warning: Variable CO2(t) was introduced in process of variable f(t).
│ However, a process for CO2(t) was not provided,
│ and there is no default process for it either.
│ Since it has a default value, we make it a parameter by adding a process:
│ `ParameterProcess(CO2)`.
└ @ ProcessBasedModelling ~/.julia/packages/ProcessBasedModelling/dbWJG/src/make.jl:122
┌ Warning: Variable S(t) was introduced in process of variable ASR(t).
│ However, a process for S(t) was not provided,
│ and there is no default process for it either.
│ Since it has a default value, we make it a parameter by adding a process:
│ `ParameterProcess(S)`.
└ @ ProcessBasedModelling ~/.julia/packages/ProcessBasedModelling/dbWJG/src/make.jl:122
┌ Warning: Initialization system is overdetermined. 4 equations for 0 unknowns. Initialization will default to using least squares. `SCCNonlinearProblem` can only be used for initialization of fully determined systems and hence will not be used here. To suppress this warning pass warn_initialize_determined = false. To make this warning into an error, pass fully_determined = true
└ @ ModelingToolkit ~/.julia/packages/ModelingToolkit/EpUEk/src/systems/diffeqs/abstractodesystem.jl:1522
1-dimensional CoupledODEs
deterministic: true
discrete time: false
in-place: false
with equations:
Differential(t)(T(t)) ~ (-0.0029390154298310064(ASR(t) + f(t) - OLR(t))) / (-τ_T)
OLR(t) ~ -277.0 + 1.8T(t)
S(t) ~ S_0
α(t) ~ α_0
CO2(t) ~ CO2_0
ASR(t) ~ 340.25S(t)*(1 - α(t))
f(t) ~ 3.7log2((1//400)*CO2(t))
and parameter values:
:α_0 => 0.3
:CO2_0 => 400.0
:S_0 => 1.0
:τ_T => 1.46951e6The output is a dynamical system from DynamicalSystems.jl that is generated via symbolic expressions based on ModelingToolkit.jl, utilizing the process-based approach of ProcessBasedModelling.jl.
As the dynamical system is made by symbolic expressions, these can be obtained back at any time:
using DynamicalSystems
# access the MTK model that stores the symbolic bindings
mtk = referrenced_sciml_model(ds)
# show the equations of the dynamic state variables of the dynamical system
equations(mtk)\[ \begin{align} \frac{\mathrm{d} T\left( t \right)}{\mathrm{d}t} &= \frac{ - 0.002939 \left( \mathtt{ASR}\left( t \right) + f\left( t \right) - \mathtt{OLR}\left( t \right) \right)}{ - \mathtt{\tau\_T}} \end{align} \]
# show state functions that are observable,
# i.e., they do not have a time derivative, they are not dynamic state variables
observed(mtk)\[ \begin{align} \mathtt{OLR}\left( t \right) &= -277 + 1.8 T\left( t \right) \\ S\left( t \right) &= \mathtt{S\_0} \\ \alpha\left( t \right) &= \mathtt{\alpha\_0} \\ \mathtt{CO2}\left( t \right) &= \mathtt{CO2\_0} \\ \mathtt{ASR}\left( t \right) &= 340.25 S\left( t \right) \left( 1 - \alpha\left( t \right) \right) \\ f\left( t \right) &= 3.7 \log_{2}\left( \frac{1}{400} \mathtt{CO2}\left( t \right) \right) \end{align} \]
# show parameters
parameters(mtk)4-element Vector{Any}:
τ_T
α_0
CO2_0
S_0The symbolic variables and parameters can also be used to query or alter the dynamical system. In the equations above we say that the symbolic variable CO2 was equated to the parameter CO2_0. There are multiple ways to obtain a symbolic index provided we know its name. First, we can re-create the symbolic parameter:
index = only(@parameters CO2_0)\[ \begin{equation} \mathtt{CO2\_0} \end{equation} \]
Second, we can use the retrieved MTK model and access its CO2_0 field, which will return the symbolic variable:
index = mtk.CO2_0\[ \begin{equation} \mathtt{CO2\_0} \end{equation} \]
Third, we can use a Symbol corresponding to the variable name. This is typically the simplest way.
index = :CO2_0:CO2_0we can query the value of this named parameter in the system,
current_parameter(ds, index)400.0or alter it:
# access symbolic parameter CO2_0 from the tracked symbolic list of the model
set_parameter!(ds, index, 800)Similarly, we can obtain or alter values corresponding to the dynamic variables, or observed functions of the state of the system, using their symbolic indices. For example we can obtain the value corresponding to symbolic variable $T$ at the current state of the dynamical system with:
observe_state(ds, T) # binding `T` already exists in scope290.0We didn't have to create T because when we did using ConceptualClimateModels.GlobalMeanEBM, T was brought into scope. Similarly, we can obtain the $OLR$ (outgoing longwave radiation), using the Symbol representation of the symbolic variable:
observe_state(ds, :OLR)245.0The advantage of using the symbolic variable OLR itself instead of its Symbol representation, is that symbolic variables can do arbitrary symbolic computations. For example let's say that when we created the equations of the system we didn't have defined a variable representing the expression $T^2/OLR$. That's not a problem, we can query the expression nevertheless:
observe_state(ds, T^2/OLR) # symbolic expression343.265306122449Let's unpack the steps that led to this level of automation.
Processes
A process is conceptually an equation the defines a climate variable or observable. All processes that define the system are then composed into a set of differential equations via processes_to_coupledodes (or processes_to_mtkmodel) that represent the climate model.
For example, the process
T_process = BasicRadiationBalance()ConceptualClimateModels.GlobalMeanEBM.BasicRadiationBalance(5.0e8, f(t), T(t), ASR(t), OLR(t))is the process defining the variable $T$, representing temperature. We can learn this by either reading the documentation string of BasicRadiationBalance, or querying it directly:
using ProcessBasedModelling: lhs, rhs
# This is the equation created by the process
lhs(T_process) ~ rhs(T_process)\[ \begin{equation} \frac{\mathrm{d} T\left( t \right)}{\mathrm{d}t} \mathtt{\tau\_T} = 0.002939 \left( \mathtt{ASR}\left( t \right) + f\left( t \right) - \mathtt{OLR}\left( t \right) \right) \end{equation} \]
Notice that this process does not further define e.g. outgoing longwave radiation OLR(t). That is why in the original example we also provided LinearOLR, which defines it:
OLR_process = LinearOLR()
lhs(OLR_process) ~ rhs(OLR_process)\[ \begin{equation} \mathtt{OLR}\left( t \right) = -277 + 1.8 T\left( t \right) \end{equation} \]
Each physical "observable" or variable that can be configured in the system has its own process. This allows very easily exchanging the way processes are represented by equations without having to alter many equations. For example, if instead of LinearOLR we have provided
OLR_process = EmissivityStefanBoltzmanOLR()
lhs(OLR_process) ~ rhs(OLR_process)\[ \begin{equation} \mathtt{OLR}\left( t \right) = 5.6704 \cdot 10^{-8} \left( T\left( t \right) \right)^{4} \varepsilon\left( t \right) \end{equation} \]
then we would have used a Stefan-Boltzmann grey-atmosphere representation for the outgoing longwave radiation. Notice how this introduced an additional variable $\varepsilon$.
Default processes
Hold on a minute though, because in the original processes we provided,
processes = [
BasicRadiationBalance(),
LinearOLR(),
ParameterProcess(α),
CO2Forcing(),
]there was no process that defined for example the absorbed solar radiation $ASR$!
Well, ConceptualClimateModels.jl allows the concept of default processes. The package exports some submodules, and each submodule targets a particular application of conceptual climate models. In this Tutorial we are using the GlobalMeanEBM submodule, which provides functionality to model global mean energy balance models.
Each submodule defines and exports its own list of predefined symbolic variables. When we wrote
using ConceptualClimateModels.GlobalMeanEBMwe brought into scope all the variables that this (sub)module defines and exports, such as T, α, OLR.
Each (sub)module and provides a list of predefined processes for its predefined symbolic variables. These predefined default processes are loaded automatically when we provide the (sub)module as a second argument to processes_to_coupledodes, which we did above. In this (sub)module, the default process for the $ASR$ is $ASR = S(1-\alpha)$ with $S$ the solar constant.
The function processes_to_coupledodes goes through all processes the user provided and identifies variables that themselves do not have a process. It then checks the list of default processes and attempts to assign one to these variables. If there are no default processes for some variables, it makes the variables themselves parameters with the same name but with a subscript 0 if the variables have a default value.
For example, let's assume that we completely remove default processes and we don't specify a process for the albedo $α$:
processes = [
BasicRadiationBalance(),
LinearOLR(),
CO2Forcing(), # for default CO2 value this is zero forcing
ASR ~ S*(1-α), # add the processes for ASR, but not for S or α
]4-element Vector{Any}:
ConceptualClimateModels.GlobalMeanEBM.BasicRadiationBalance(5.0e8, f(t), T(t), ASR(t), OLR(t))
OLR(t) ~ -277.0 + 1.8T(t)
ConceptualClimateModels.GlobalMeanEBM.CO2Forcing(f(t), CO2(t), 3.7)
ASR(t) ~ S(t)*(1 - α(t))which we'll give to processes_to_mtkmodel. Notice how there is no second argument given, which would normally be the (sub)module to obtain default processes from.
side note: we use `processtomtkmodelhere, as we don't care yet for making aDynamicalSystem`; just the symbolic model representation
mtk = processes_to_mtkmodel(processes)
# we access the equations directly from the model
equations(mtk)\[ \begin{align} \frac{\mathrm{d} T\left( t \right)}{\mathrm{d}t} \mathtt{\tau\_T} &= 0.002939 \left( \mathtt{ASR}\left( t \right) + f\left( t \right) - \mathtt{OLR}\left( t \right) \right) \\ \mathtt{OLR}\left( t \right) &= -277 + 1.8 T\left( t \right) \\ f\left( t \right) &= 3.7 \log_{2}\left( \frac{1}{400} \mathtt{CO2}\left( t \right) \right) \\ \mathtt{ASR}\left( t \right) &= S\left( t \right) \left( 1 - \alpha\left( t \right) \right) \\ \mathtt{CO2}\left( t \right) &= \mathtt{CO2\_0} \\ \alpha\left( t \right) &= \mathtt{\alpha\_0} \\ S\left( t \right) &= \mathtt{S\_0} \end{align} \]
You will notice the equation $α = α_0$ where $\alpha_0$ is now a parameter of the system (i.e., it can be altered after creating the system). The value of $\alpha_0$ is the default value of $\alpha$:
default_value(α)0.3# current value of the _parameter_ α_0 (not the variable!)
default_value(mtk.α_0)0.3When this automation occurs a warning is thrown:
┌ Warning: Variable α(t) was introduced in process of variable ASR(t).
│ However, a process for α(t) was not provided,
│ and there is no default process for it either.
│ Since it has a default value, we make it a parameter by adding a process:
└ `ParameterProcess(α)`.ParameterProcess is the most trivial process: it simply means that the corresponding variable does not have any actual process describing it and rather it is a system parameter.
This automation does not occur if there is no default value. For example, the $ASR$ variable does not have a default value. If we have not assigned a process for $ASR$, the system construction would error instead:
processes = [
BasicRadiationBalance(),
LinearOLR(),
CO2Forcing(),
]
mtk = processes_to_mtkmodel(processes)ERROR: ArgumentError: Variable ASR(t) was introduced in process of
variable T(t). However, a process for ASR(t) was not provided,
there is no default process for ASR(t), and ASR(t) doesn't have a default value.
Please provide a process for variable ASR(t).These warnings and errors are always "perfectly" informative. They tell us exactly which variable does not have a process, and exactly which other process introduced the process-less variable first. This drastically improves the modelling experience, especially when large and complex models are being created.
Adding your own processes
Each of the submodules of ConceptualClimateModels.jl provides an increasing list of predefined processes that you can use out of the box to compose climate models. The predefined processes all come from existing literature and cite their source via BiBTeX.
It is also very easy to make new processes on your own. The simplest way to make a process is to just provide an equation for it with the l.h.s. of the equation being the variable the process defines.
For example:
@variables x(t) = 0.5 # all variables must be functions of (t)
x_process = x ~ 0.5*T^2 # x is just a function of temperature\[ \begin{equation} x\left( t \right) = 0.5 \left( T\left( t \right) \right)^{2} \end{equation} \]
A more re-usable approach however is to create a function that generates a process or create a new process type as we describe in making new processes.
A note on symbolic variable instances
Recall that when we wrote
using ConceptualClimateModels.GlobalMeanEBMat the start of this tutorial, we brought into scope variables that this (sub)module defines and exports, such as T, α, OLR. They are listed on the (sub)module's documentation page, and are used in that module's default processes.
In some predefined processes documentation you will notice call signatures like
BasicRadiationBalance(; T, f, kwargs...)There are keywords that do not have an assignment like T, f above. This means that use the (sub)module's predefined variables.
Crucially, these default variables are symbolic variables. They are defined as
@variables begin
T(t) = 0.5 # ...
# ...
endwhich means that expressions that involve them result in symbolic expressions, for example
A2 = 0.5
B2 = 0.5
OLR2 = A2 + B2*T\[ \begin{equation} 0.5 + 0.5 T\left( t \right) \end{equation} \]
In contrast, if we did instead
T2 = 0.5 # _not_ symbolic!
OLR3 = A2 + B2*T20.75This OLR3 is not a symbolic expression and cannot be used to represent a process.
You can use your own variables in any predefined processes. You can define them by doing
@variables begin
(T1_tropics(t) = 290.0), [bounds = (200.0, 350.0), description = "temperature in tropical box 1, in Kelvin"]
end\[ \begin{equation} \left[ \begin{array}{c} \mathtt{T1\_tropics}\left( t \right) \\ \end{array} \right] \end{equation} \]
and then assign them to the corresponding keyword argument
process = BasicRadiationBalance(T = T1_tropics)ConceptualClimateModels.GlobalMeanEBM.BasicRadiationBalance(5.0e8, f(t), T1_tropics(t), ASR(t), OLR(t))Defining variables with the extra bounds, description annotations is useful for integrating with the rest of the functionality of the library, and therefore it is strongly recommended.
Above we assigned T1_tropics as the temperature variable. This means we also need to assign the same variable as the one setting the OLR variable by also providing the processes LinearOLR(T = T1_tropics) (for example).
Default values, limits, etc.
All predefined variables that could be dynamic variables (i.e., could have a time derivative applied to them) have a default value, a description, and plausible physical bounds.
To obtain the default value, use default_value(x). For the description, use getdescription(x). For the bounds, see plausible_limits.
Automatically named parameters
The majority of predefined processes create symbolic parameters that are automatically named based on the variables governing the processes. This default behaviour can be altered in two ways.
For example, IceAlbedoFeedback adds named parameters to the equations whose name is derived from the name of the variable representing ice albedo:
@variables my_ice_α(t) = 0.1 # don't forget the `(t)`!
ice_process = IceAlbedoFeedback(; α_ice = my_ice_α)
processes = [ice_process]
mtk = processes_to_mtkmodel(processes)
equations(mtk)\[ \begin{align} \mathtt{my\_ice\_\alpha}\left( t \right) &= \mathtt{max\_my\_ice\_\alpha} + 0.5 \left( - \mathtt{max\_my\_ice\_\alpha} + \mathtt{min\_my\_ice\_\alpha} \right) \left( 1 + \tanh\left( \frac{2 \left( - \mathtt{Tfreeze\_my\_ice\_\alpha} + \frac{1}{2} \mathtt{Tscale\_my\_ice\_\alpha} + T\left( t \right) \right)}{\mathtt{Tscale\_my\_ice\_\alpha}} \right) \right) \\ T\left( t \right) &= \mathtt{T\_0} \end{align} \]
parameters(mtk)5-element Vector{SymbolicUtils.BasicSymbolic{Real}}:
max_my_ice_α
Tfreeze_my_ice_α
Tscale_my_ice_α
min_my_ice_α
T_0We can alter this behaviour by either providing our own named parameters to one of the keywords of the process, or wrapping a value around LiteralParameter to replace the parameter by a literal constant, like so:
@parameters myfreeze = 260.0
ice_process = IceAlbedoFeedback(;
α_ice = my_ice_α,
Tfreeze = myfreeze, # my custom parameter
max = LiteralParameter(0.9) # don't make a parameter
)
mtk = processes_to_mtkmodel([ice_process])
equations(mtk)\[ \begin{align} \mathtt{my\_ice\_\alpha}\left( t \right) &= 0.9 + 0.5 \left( -0.9 + \mathtt{min\_my\_ice\_\alpha} \right) \left( 1 + \tanh\left( \frac{2 \left( \frac{1}{2} \mathtt{Tscale\_my\_ice\_\alpha} - \mathtt{myfreeze} + T\left( t \right) \right)}{\mathtt{Tscale\_my\_ice\_\alpha}} \right) \right) \\ T\left( t \right) &= \mathtt{T\_0} \end{align} \]
parameters(mtk)4-element Vector{SymbolicUtils.BasicSymbolic{Real}}:
myfreeze
Tscale_my_ice_α
min_my_ice_α
T_0Integration with DynamicalSystems.jl
ConceptualClimateModels.jl integrates with DynamicalSystems.jl. It provides some convenience functions that require ModelingToolkit.jl, such as dynamical_system_summary, plausible_limits or named_current_parameters, see below. Moreover, since all dynamical systems generated by ConceptualClimateModels.jl have symbolic bindings, one can use the symbolic variables in e.g., interactive GUI exploration or to access or set the parameters of the system.
ConceptualClimateModels.plausible_limits — Functionplausible_limits(x::Num)Return a tuple (min, max) of plausible limiting values for the variable x. If x is defined with the bounds metadata, this is returned as the limits. Else, if x has a default value, the limits are this value ± 20%. Else, if there is no default value, an error is thrown.
plausible_limits(ds::DynamicalSystem [, idxs])Return a vector of limits (min, max) for each dynamic state variable in ds. Optionally provide the idxs of the variables to use as a vector of Symbols for symbolic variables present in the referrenced MTK model of ds.
See also plausible_grid, plausible_ic_sampler.
ConceptualClimateModels.plausible_ic_sampler — Functionplausible_ic_sampler(ds::DynamicalSystem [, seed])Return a sampler that can be called as a 0-argument function sampler(), which yields random initial conditions within the hyperrectangle defined by the plausible_limits of ds. The sampler is useful to give to e.g., DynamicalSystems.basins_fractions.
ConceptualClimateModels.plausible_grid — Functionplausible_grid(ds::DynamicalSystem, n = 101)Return a grid that is a tuple of range objects that each spans the plausible_limits(ds). n can be an integer or a vector of integers (for each dimension). The resulting grid is useful to give to DynamicalSystems.AttractorsViaRecurrences.
ConceptualClimateModels.dynamical_system_summary — Functiondynamical_system_summary(ds::DynamicalSystem)Return a printable/writable string containing a summary of ds, which outlines its current status and lists all symbolic equations and parameters that constitute the system, if a referrence to a ModelingToolkit.jl model exists in ds.
ConceptualClimateModels.named_current_parameters — Functionnamed_current_parameters(ds::DynamicalSystem)Return a dictionary mapping parameters of ds (as Symbols) to their values.
ConceptualClimateModels.try_set_parameter! — Functiontry_set_parameter!(ds::DynamicalSystem, symbol, value)An extension of set_parameter! for as symbolic parameter, that first checks if the symbol exists in the referenced MTK model, and if not, it warns and returns nothing.
API Reference
ConceptualClimateModels.processes_to_coupledodes — Functionprocesses_to_coupledodes(processes [, default]; kw...)Convert a given Vector of processes to a DynamicalSystem, in particular CoupledODEs. All processes represent symbolic equations managed by ModelingToolkit.jl. default is a vector for default processes that "process-less" variables introduced in processes will obtain. Use processes_to_mtkmodel to obtain the MTK model before it is structurally simplified and converted to a DynamicalSystem. See also processes_to_mtkmodel for more details on what processes is, or see the online Tutorial.
Keyword arguments
diffeq: options passed to DifferentialEquations.jl ODE solving when constructing theCoupledODEs.inplace: whether the dynamical system will be in place or not. Defaults totrueif the system dimension is ≤ 5.split = false: whether to split parameters as per ModelingToolkit.jl. Note the default is not ModelingToolkit's default, i.e., no splitting occurs. This accelerates parameter access, assuming all parameters are of the same type.kw...: all other keywords are propagated toprocesses_to_mtkmodel.
ProcessBasedModelling.processes_to_mtkmodel — Functionprocesses_to_mtkmodel(processes::Vector [, default]; kw...)Construct a ModelingToolkit.jl model/system using the provided processes and default processes. The model/system is not structurally simplified. Use the function processes_to_mtkeqs to obtain the raw Vector{Equation} before it is passed to the MTK model/system like ODESystem.
During construction, the following automations improve user experience:
- Variable(s) introduced in
processesthat does not themselves have a process obtain a default process fromdefault. - If no default exists, but the variable(s) have a default numerical value, a
ParameterProcessis created for said variable(s) and a warning is thrown. - Else, an informative error is thrown.
- An error is also thrown if any variable has two or more processes assigned to it.
- An error is thrown if any of the given processes are not actually processes, but rather expressions without a LHF-variable.
processes is a Vector whose elements can be:
- Any instance of a subtype of
Process.Processis like a wrapper aroundEquationthat provides some conveniences, e.g., handling of timescales or not having limitations on the left-hand-side (LHS) form. - An
Equation. The LHS format of the equation is limited. Letxbe a@variableandpbe a@parameter. Then, the LHS can only be one of:x,Differential(t)(x),Differential(t)(x)*p, orp*Differential(t)(x). The versions withpmay fail unexpectedly. Anything else will error. - A
Vectorof the above two, which is then expanded. This allows the convenience of functions representing a physical process that may require many equations to be defined (because e.g., they may introduce more variables). - A ModelingToolkit.jl
XDESystem, in which case theequationsof the system are expanded as if they were given as a vector of equations like above. This allows the convenience of straightforwardly coupling with already existingXDESystems.
Default processes
processes_to_mtkmodel allows for specifying default processes by giving default. These default processes are assigned to variables introduced in the main input processes, but themselves do not have an assigned process in the main input.
default can be a Vector of individual processes (Equation or Process). Alternatively, default can be a Module. The recommended way to build field-specific modelling libraries based on ProcessBasedModelling.jl is to define modules/submodules that offer a pool of pre-defined variables and processes. Modules may register their own default processes via the function register_default_process!. These registered default processes are used when default is a Module.
Keyword arguments
type = ODESystem: the model type to make.name = nameof(type): the name of the model.independent = t: the independent variable (default:@variables t).tis also exported by ProcessBasedModelling.jl for convenience.warn_default::Bool = true: iftrue, throw a warning when a variable does not have an assigned process but it has a default value so that it becomes a parameter instead.check_rhs::Bool = true: iftrue, check that the RHS of all processes is NOT anEquationtype. Throw an informative error if there is one. This helps scenarios where the RHS is wrongly anEquationassigning the LHS itself (has happened to me many times!).
Making new processes
To make a new processes you can:
- Create a function that given some keyword arguments uses one of the existing generic processes to make and return a process instance. Or, it can return an equation directly, provided it satisfies the format of
processes_to_mtkmodel. For an example of this, see the source code ofSeparatedClearAllSkyAlbedoorEmissivityFeedbackTanh. - Create a new
Processsubtype. This is preferred, because it leads to much better printing/display of the list of processes. For an example of this, see the source code ofIceAlbedoFeedback. To create a newProcesssee the API of ProcessBasedModelling.jl or read the documentation string ofProcessbelow.
ProcessBasedModelling.Process — TypeProcessA new process must subtype Process and can be used in processes_to_mtkmodel. The type must extend the following functions from the module ProcessBasedModelling:
lhs_variable(p)which returns the variable the process describes (left-hand-side variable). There is a default implementationlhs_variable(p) = p.variableif the field exists.rhs(p)which is the right-hand-side expression, i.e., the "actual" process.- (optional)
timescale(p), which defaults toNoTimeDerivative. - (optional)
lhs(p)which returns the left-hand-side. Letτ = timescale(p). Then defaultlhs(p)behaviour depends onτas follows:- Just
lhs_variable(p)ifτ == NoTimeDerivative(). Differential(t)(p)ifτ == nothing, or multiplied with a number ifτ isa LiteralParameter.τ_var*Differential(t)(p)ifτ isa Union{Real, Num}. If real, a new named parameterτ_varis created that has the prefix:τ_and then the lhs-variable name and has default valueτ. Else ifNum,τ_var = τas given.- Explicitly extend
lhs_variableif the above do not suit you.
- Just
ProcessBasedModelling.register_default_process! — Functionregister_default_process!(process, m::Module; warn = true)Register a process (Equation or Process) as a default process for its LHS variable in the list of default processes tracked by the given module. If warn, throw a warning if a default process with same LHS variable already exists and will be overwritten.
You can use default_processes to obtain the list of tracked default processes.
If you are developing a new module/package that is based on ProcessBasedModelling.jl, and within it you also register default processes, then enclose your register_default_process! calls within the module's __init__() function. For example:
module MyProcesses
# ...
function __init__()
register_default_process!.(
[
process1,
process2,
# ...
],
Ref(MyProcesses)
)
end
end # moduleGeneric processes
Processes that do not depend on any particular physical concept and instead provide a simple way to create new processes for a given climate variable:
ProcessBasedModelling.ParameterProcess — TypeParameterProcess(variable, value = default_value(variable)) <: ProcessThe simplest process which equates a given variable to a constant value that is encapsulated in a parameter. If value isa Real, then a named parameter with the name of variable and _0 appended is created. Else, if valua isa Num then it is taken as the paremeter directly.
Example:
@variables T(t) = 0.5
proc = ParameterProcess(T)will create the equation T ~ T_0, where T_0 is a @parameter with default value 0.5.
ProcessBasedModelling.TimeDerivative — TypeTimeDerivative(variable, expression [, τ])The second simplest process that equates the time derivative of the variable to the given expression while providing some conveniences over manually constructing an Equation.
It creates the equation τ_$(variable) Differential(t)(variable) ~ expression by constructing a new @parameter with default value τ (if τ is already a @parameter, it is used as-is). If τ is not given, then 1 is used at its place and no parameter is created.
Note that if iszero(τ), then the process variable ~ expression is created.
ProcessBasedModelling.ExpRelaxation — TypeExpRelaxation(variable, expression [, τ]) <: ProcessA common process for creating an exponential relaxation of variable towards the given expression, with timescale τ. It creates the equation:
τn*Differential(t)(variable) ~ expression - variableWhere τn is a new named @parameter with the value of τ and name τ_($(variable)). If instead τ is nothing, then 1 is used in its place (this is the default behavior). If iszero(τ), then the equation variable ~ expression is created instead.
The convenience function
ExpRelaxation(process, τ)allows converting an existing process (or equation) into an exponential relaxation by using the rhs(process) as the expression in the equation above.
ProcessBasedModelling.AdditionProcess — TypeAdditionProcess(process, added...)A convenience process for adding processes added to the rhs of the given process. added can be a single symbolic expression. Otherwise, added can be a Process or Equation, or multitude of them, in which case it is checked that the lhs_variable across all added components matches the process.
ConceptualClimateModels.SigmoidProcess — TypeSigmoidProcess <: Process
SigmoidProcess(variable, driver; left, right, scale, reference)A common process for when a variable has a sigmoidal dependence on a driver variable. The rest of the input arguments should be real numbers or @parameter named parameters.
The process creates a sigmoidal relationship based on the tanh function:
variable ~ left + (right - left)*(1 + tanh(2(driver - reference)/scale))*0.5i.e., the variable goes from value left to value right as driver increases over a range of scale (centered at reference). Instead of reference you may provide start or finish keywords, which make reference = start + scale/2 or reference = finish - scale/2 respectively.
If the values given to the parameters of the expression are real numbers, they become named parameters prefixed with the name of variable, then the name of the driver, and then _sigmoid_left, _sigmoid_right, _sigmoid_rate and _sigmoid_ref respectively. Use LiteralParameter for parameters you do not wish to rename.