# Architecture¶

The backbone of the climlab architecture are the `Process`

and `TimeDependentProcess`

classes.
All model components in climlab are instances of `Process`

.
Conceptually, a `Process`

object represents any
physical mechanism that can be described in terms of one or more **state variables** and
processes that modify those variables.

As all relevant procedures and events that can be modelled with climlab are expressed in Processes, they build the basic structure of the package.

For example, if you want to model the incoming solar radiation on Earth,
climlab implements it as a Process, namely in the Diagnostic Process `_Insolation`

(or one of its specific daughter classes).

Another example: the emitted energy of a surface can be computed through the
`Boltzmann`

class which is also a climlab
Process and implements the Stefan Boltzmann Law for a grey body.
Like that, all events and procedures that climlab can model are organized in Processes.

Note

The implementation of a whole model, for example an Energy Balance Model (`EBM`

), is also an instance of the `Process`

class in climlab.

For more information about models, see the climlab Models chapter.

A `Process`

object contains
a subprocess dictionary, which itself can contain an arbitraily complex
collection of other `Process`

objects.

A Process that represents a whole model will typically have some subprocesses which represent specific physical components of the model, for example the albedo or the insolation component. More details about subprocesses can be found below.

The state variables of a Process are always defined on a **Domain**
which itself is based on **Axes** or a single **Axis**. The following section will give a basic introduction about their role in the package, their dependencies and their implementation.

## Process¶

A Process is an instance of the class `Process`

. Most processes are time-dependent and therefore an instance of the daughter class `TimeDependentProcess`

.

### Basic Dictionaries¶

A climlab.Process object has several iterable dictionaries (`dict`

) of named, gridded variables [1]:

`process.state`

contains the process’ state variables, which are usually time-dependent and which are major quantities that identify the condition and status of the process. This can be the (surface) temperature of a model for instance.

`process.input`

contains boundary conditions and other gridded quantities independent of the process. This dictionary is often set by a parent process.

`process.param`

contains parameter of the Process or model. Basically, this is the same as

`process.input`

but with scalar entries.

`process.tendencies`

is an iterable dictionary of time tendencies \((d/dt)\) for each state variable defined in

`process.state`

.Note

A non TimeDependentProcess (but instance of

`Process`

) does not have this dictionary.

`process.diagnostics`

contains any quantity derived from the current state. In an Energy Balance Model this dictionary can have entries like

`'ASR'`

,`'OLR'`

,`'icelat'`

,`'net_radiation'`

,`'albedo'`

or`'insolation'`

.

`process.subprocess`

holds subprocesses of the process. More about subprocesses is described below.

The process is fully described by contents of state, input and param dictionaries. tendencies and diagnostics are always computable from the current state.

### Subprocesses¶

Subprocesses are representing and modeling certain components of the parent process. A model consists of many subprocesses which are usually defined on the same state variables, domains and axes as the parent process, at least partially.

- Example:
The subprocess tree of an EBM may look like this:

model_EBM #<head process> diffusion #<subprocess> LW #<subprocess> albedo #<subprocess> iceline #<sub-subprocess> cold_albedo #<sub-subprocess> warm_albedo #<sub-subprocess> insolation #<subprocess>

It can be seen that subprocesses can have subprocesses themselves, like `albedo`

in this case.

A `subprocess`

is similar to its `parent process`

an instance of the `Process`

class. That means a `subprocess`

has dictionaries and attributes with the same names as its `parent process`

. Not necessary all will be the same or have the same entries, but a `subprocess`

has at least the basic dictionaries and attributes created during initialization of the `Process`

instance.

Every subprocess should work independently of its parent process given appropriate input.

- Example:
Investigating an individual process (possibly with its own subprocesses) isolated from its parent can be done through:

newproc = climlab.process_like(procname.subprocess['subprocname']) newproc.compute()Thereby anything in the input dictionary of

`'subprocname'`

will remain fixed.

### Process Integration over time¶

A `TimeDependentProcess`

can be integrated over time to see how the state variables and other diagnostic variables vary in time.

#### Time Dependency of a State Variable¶

For a state variable \(S\) which is dependendet on processes \(P_A\), \(P_B\), … the time dependency can be written as

When the state variable \(S\) is discretized over time like

the state tendency can be calculated through

and the new state of \(S\) after one timestep \(\Delta t\) is then:

Therefore, the new state of \(S\) is calculated by multiplying the process tendencies of \(S\) with the timestep and adding them up to the previous state of \(S\).

#### Time Dependency of an Energy Budget¶

The time dependency of an EBM energy budget is very similar to the above noted equations, just differing in a heat capacity factor \(C\). The state variable is temperature \(T\) in this case, which is altered by subprocesses \(SP_A\), \(SP_B\), …

Therefore, the new state of \(T\) after one timestep \(\Delta t\) can be written as:

The integration procedure is implemented in multiple nested function calls. The top functions for model integration are explained here, for details about computation of subprocess tendencies see Classification of Subprocess Types below.

`compute()`

is a method that computes tendencies \(d/dt\) for all state variablesit returns a dictionary of tendencies for all state variables

Temperature tendencies are \(\frac{SP_A(T)}{C}\), \(\frac{SP_B(T)}{C}\), … in this case, which are summed up like:

\[\textrm{tendencies}(T) = \frac{SP_A(T)}{C} + \frac{SP_B(T)}{C} + ...\]the keys for this dictionary are the same as keys of state dictionary

As temperature \(T\) is the only state variable in this energy budget, the tendencies dictionary also just has the one key, representing the state variable \(T\).

the tendency dictionary holds the total tendencies for each state including all subprocesses

In case subprocess \(SP_A\) itself has subprocesses, their \(T\) tendencies get included in tendency computation by

`compute()`

.the method only computes \(d/dt\) but

**does not apply changes**(which is done by`step_forward()`

)therefore, the method is relatively independent of the numerical scheme

method

**will update**variables in`proc.diagnostic`

dictionary. Therefore, it will also**gather all diagnostics**from the subprocesses

`step_forward()`

updates the state variablesit calls

`compute()`

to get current tendenciesthe method multiplies state tendencies with the timestep and adds them up to the state variables

`integrate_years()`

etc will automate time-stepping by calling the`step_forward`

method multiple times. It also does the computation of time-average diagnostics.`integrate_converge()`

calls`integrate_years()`

as long as the state variables keep changing over time.

- Example:
Integration of a climlab EBM model over time can look like this:

import climlab model = climlab.EBM() # integrate the model for one year model.integrate_years(1)

#### Classification of Subprocess Types¶

Processes can be classified in types: explicit, implicit, diagnostic and adjustment. This makes sense as subprocesses may have different impact on state variable tendencies (diagnostic processes don’t have a direct influence for instance) or the way their tendencies are computed differ (explixit and implicit).

Therefore, the `compute()`

method handles them seperately as well as in specific order. It calls private `_compute()`

methods that are specified in daugther classes of `Process`

namely `DiagnosticProcess`

,
`EnergyBudget`

(which are explicit processes) or
`ImplicitProcess`

.

The description of `compute()`

reveals the details how the different process types are handeled:

The function first computes all diagnostic processes. They don’t produce any tendencies directly but they may effect the other processes (such as change in solar distribution). Subsequently, all tendencies and diagnostics for all explicit processes are computed.

Tendencies due to implicit and adjustment processes need to be calculated from a state that is already adjusted after explicit alteration. For that reason the explicit tendencies are applied to the states temporarily. Now all tendencies from implicit processes are calculated by matrix inversions and similar to the explicit tendencies, the implicit ones are applied to the states temporarily. Subsequently, all instantaneous adjustments are computed.

Then the changes that were made to the states from explicit and implicit processes are removed again as this

`compute()`

function is supposed to calculate only tendencies and not apply them to the states.Finally, all calculated tendencies from all processes are collected for each state, summed up and stored in the dictionary

`self.tendencies`

, which is an attribute of the time-dependent-process object, for which the`compute()`

method has been called.

## Domain¶

A Domain defines an area or spatial base for a climlab `Process`

object. It consists of axes which
are `Axis`

objects that define the dimensions of the Domain.

In a Domain the heat capacity of grid points, bounds or cells/boxes is specified.

There are daughter classes `Atmosphere`

and `Ocean`

of the private `_Domain`

class implemented which themselves have daughter classes `SlabAtmosphere`

and `SlabOcean`

.

Every `Process`

needs to be defined on a Domain. If none is given during initialization but latitude `lat`

is specified, a default Domain is created.

Several methods are implemented that create Domains with special specifications. These are

## Axis¶

An `Axis`

is an object where information of a `_Domain`

’s spacial dimension are specified.

These include the type of the axis, the number of points, location of points and bounds on the spatial dimension, magnitude of bounds differences delta as well as their unit.

The axes of a `_Domain`

are stored in the dictionary axes, so they can be accessed through `dom.axes`

if `dom`

is an instance of `_Domain`

.

## Accessibility¶

For convenience with interactive work, each subprocess `'name'`

should be accessible
as `proc.subprocess.name`

as well as the regular way through the subprocess dictionary `proc.subprocess['name']`

. Note that `proc`

is an instance of the `Process`

class here.

- Example:
import climlab model = climlab.EBM() # quick access longwave_subp = model.subprocess.LW # regular path longwave_subp = model.subprocess['LW']

climlab will remain (as much as possible) agnostic about the data formats. Variables within the dictionaries will behave as `numpy.ndarray`

objects.

Grid information and other domain details are accessible as attributes of each process.
These attributes are `lat`

, `lat_bounds`

, `lon`

, `lon_bounds`

, `lev`

, `lev_bounds`

, `depth`

and `depth_bounds`

.

- Example:
the latitude points of a process object that is describing an EBM model

import climlab model = climlab.EBM() # quick access lat_points = model.lat # regular path lat_points = model.domains['Ts'].axes['lat'].points

Shortcuts like `proc.lat`

will work where these are unambiguous, which means there is only a single axis of that type in the process.

Many variables will be accessible as process attributes `proc.name`

. This restricts to unique field names in the above dictionaries.

Warning

There may be other dictionaries that do have name conflicts: e.g. dictionary of tendencies `proc.tendencies`

, with same keys as `proc.state`

.

These will **not be accessible** as `proc.name`

, but **will be accessible** as `proc.dict_name.name`

(as well as regular dictionary interface `proc.dict_name['name']`

).