Quickstart

Here we have an example of how Sympl might be used to construct a model run script, with explanations of what’s going on. Here is the full model script we will be looking at (we break it into smaller pieces below):

from model_package import (
    get_initial_state, Radiation, BoundaryLayer, DeepConvection,
    ImplicitDynamics)
from sympl import (
    AdamsBashforth, PlotFunctionMonitor, UpdateFrequencyWrapper,
    datetime, timedelta)

def my_plot_function(fig, state):
    ax = fig.add_subplot(1, 1, 1)
    ax.set_xlabel('longitude')
    ax.set_ylabel('latitude')
    ax.set_title('Lowest model level air temperature (K)')
    im = ax.pcolormesh(
        state['air_temperature'].to_units('degK').values[0, :, :],
        vmin=260.,
        vmax=310.)
    cbar = fig.colorbar(im)

plot_monitor = PlotFunctionMonitor(my_plot_function)

state = get_initial_state(nx=256, ny=128, nz=64)
state['time'] = datetime(2000, 1, 1)

physics_stepper = AdamsBashforth([
    UpdateFrequencyWrapper(Radiation(), timedelta(hours=2)),
    BoundaryLayer(),
    DeepConvection(),
])
implicit_dynamics = ImplicitDynamics()

timestep = timedelta(minutes=30)
while state['time'] < datetime(2010, 1, 1):
    physics_diagnostics, state_after_physics = physics_stepper(state, timestep)
    dynamics_diagnostics, next_state = implicit_dynamics(state_after_physics, timestep)
    state.update(physics_diagnostics)
    state.update(dynamics_diagnostics)
    plot_monitor.store(state)
    next_state['time'] = state['time'] + timestep
    state = next_state

Importing Packages

At the beginning of the script we have import statements:

from model_package import (
    get_initial_state, Radiation, BoundaryLayer, DeepConvection,
    ImplicitDynamics)
from sympl import (
    AdamsBashforth, PlotFunctionMonitor, UpdateFrequencyWrapper,
    datetime, timedelta)

These grant access to the objects that will be used to construct the model, and are dependent on the model package you are using. Here, the names model_package, get_initial_state, Radiation, BoundaryLayer, DeepConvection, and ImplicitDynamics are placeholders, and do not refer to an actual existing package.

Defining a PlotFunctionMonitor

Here we define a plotting function, and use it to create a Monitor using PlotFunctionMonitor:

def my_plot_function(fig, state):
    ax = fig.add_subplot(1, 1, 1)
    ax.set_xlabel('longitude')
    ax.set_ylabel('latitude')
    ax.set_title('Lowest model level air temperature (K)')
    im = ax.pcolormesh(
        state['air_temperature'].to_units('degK').values[0, :, :],
        vmin=260.,
        vmax=310.)
    cbar = fig.colorbar(im)

plot_monitor = PlotFunctionMonitor(my_plot_function)

That Monitor will be used to produce an animated plot of the lowest model level air temperature as the model runs. Here we assume that the first axis is the vertical axis, and that the lowest level is at the lowest index, but this depends entirely on your model. The [0, :, :] part might be different for your model.

Initialize the Model State

To initialize the model, we need to create a dictionary which contains the model state. The way this is done is model-dependent. Here we assume there is a function that was defined by the model_package package which handles this for us:

state = get_initial_state(nx=256, ny=128, nz=64)
state['time'] = datetime(2000, 1, 1)

An initialized state is a dictionary whose keys are strings (like ‘air_temperature’) and values are DataArray objects, which store not only the data but also metadata like units. The one exception is the “time” quantity which is either a datetime-like or timedelta-like object. Here we are calling sympl.datetime() to initialize time, rather than directly creating a Python datetime. This is because sympl.datetime() can support a number of calendars using the netcdftime package, if installed, unlike the built-in datetime which only supports the Proleptic Gregorian calendar.

You can read more about the state, including sympl.datetime() in Model State.

Initialize Components

Now we need the objects that will process the state to move it forward in time. Those are the “components”:

physics_stepper = AdamsBashforth([
    UpdateFrequencyWrapper(Radiation(), timedelta(hours=2)),
    BoundaryLayer(),
    DeepConvection(),
])
implicit_dynamics = ImplicitDynamics()

AdamsBashforth is a TendencyStepper, which is created with a set of TendencyComponent components. The TendencyComponent components we have here are Radiation, BoundaryLayer, and DeepConvection. Each of these carries information about what it takes as inputs and provides as outputs, and can be called with a model state to return tendencies for a set of quantities. The TendencyStepper uses this information to step the model state forward in time.

The UpdateFrequencyWrapper applied to the Radiation object is an object that acts like a TendencyComponent but only computes its output if at least a certain amount of model time has passed since the last time the output was computed. Otherwise, it returns the last computed output. This is commonly used in atmospheric models to avoid doing radiation calculations (which are very expensive) every timestep, but it can be applied to any TendencyComponent.

The ImplicitDynamics class is a Stepper object, which steps the model state forward in time in the same way that a TendencyStepper would, but doesn’t use TendencyComponent objects in doing so.

The Main Loop

With everything initialized, we have the part of the code where the real computation is done – the main loop:

timestep = timedelta(minutes=30)
while state['time'] < datetime(2010, 1, 1):
    physics_diagnostics, state_after_physics = physics_stepper(state, timestep)
    dynamics_diagnostics, next_state = implicit_dynamics(state_after_physics, timestep)
    state.update(physics_diagnostics)
    state.update(dynamics_diagnostics)
    plot_monitor.store(state)
    next_state['time'] = state['time'] + timestep
    state = next_state

In the main loop, a series of component calls update the state, and the figure presented by plot_monitor is updated. The code is meant to be as self-explanatory as possible. It is necessary to manually set the time of the next state at the end of the loop. This is not done automatically by TendencyStepper and Stepper objects, because in many models you may want to update the state with multiple such objects in a sequence over the course of a single time step.