modelutil: Programming Structure and Logic of Models
This package provides the utilities needed to define models and submodels that are interchangeable yet simple. There are three design principles used to accomplish this.
First, model variables (e.g. zonal wind, precipitation, sensible
heat flux) are managed using the
StateSet classes, including enough metadata to enable
one to figure out what exactly is being stored and passed.
State object is a collection of
objects, and a
StateSet object is a collection of
State objects. See this
descriptive overview of these three classes
or the class doctrings for details.
Second, each model package and subpackage has a module
interface_meta which defines a class
InterfaceMeta. This class gives the metadata
Field variables that are consistently defined
among all modules at that level and subpackages
immediately below that level that pass information in and out.
As an example consider the package
seaice which defines
a series of sea-ice models.
InterfaceMeta defined in
seaice.interface_meta apply to modules
seaice and any variables that get passed
in or out of the
(as defined in the file seaice/semtner/__init__.py).
classes each contain a
meta_ok_to_interface method that accepts a single
InterfaceMeta as an argument which tests whether
the object contains
Field variables that meet the
InterfaceMeta metadata definition.
More information on
InterfaceMeta can be found in the overview
Lastly, all models defined by the framework specified
by this package are objects structured the same way.
They are all subclasses of the
which defines all methods and attributes common to all models.
Objects of the
GenericModel class have the following
StateSetclass. All the methods of the
StateSetclass are available to model objects.
StateSetobject. All the attributes (public and private, found in
__dict__) of the
StateSetobject are copied (by reference) as attributes of the model.
and have the following additional public methods:
Adds args "tendency"
Fieldobjects to the model data container (args can be empty). If keyword
"replace", args replaces any previously existing values, if there is a
Statein the model data container. If keyword
"sum_with_previous", args are summed with any previously existing values, if there is a
Statein the model data container. Though a user-written overloaded
tend1method can call this method any number of times, it must call it at least once.
Returns the model object as a
StateSetclass object by reference. This is often used to pass the model output state to another model.
Additional tasks to perform on object instantiation. The
GenericModeldoes two things: it sets (by reference) all the attributes of the input
StateSetvariable to attributes of the model object, and it executes
setupenables subclasses of
GenericModelto add additional functionality to the instantiation method without overloading
Calculates the state at the next timestep. This is expressed as a
"tp1"which consists of adding the
Calculates the state at the next timestep, shifts all
Stateobjects one timestep back, and deletes the earliest timestep. Thus, after this method is executed, the
"t0"in the model is the newly calculated timestep.
Calculates the "tendency" of model prognostic variables (actually the tendency multiplied by one timestep, and thus the value to add to the current state to increment one timestep forward; has
"tend1"). This method is the meat of the model, and is described in further detail in the subsection below.
In other words, all model objects are
StateSet objects with additional methods.
Remember, in a
GenericModel model) object all
State objects have a unique
which is the reference key in the
New models are created by defining a new class that
is a subclass of
GenericModel. In that new class the
tend1 method is overloaded; this is the only method
or attribute that must be written in this new subclass.
Optionally, if the new model has to do additional tasks on
object instantiation, the
setup method can be
overloaded with those tasks
(don't overload the
usually these tests would be error checks and data initialization.
StateSet objects are passed as input
and returned as output for all models, they can be nested
in any order. Thus, there is no rigid distinction between
"upper-level" and "lower-level" models. As long as the
Field objects making up the
interface return true for the
method, those fields are interchangeable as input/output for
all models in this package. More often than not, however,
the model will be used "one-way";
will often return false in those cases.
If you're only interested in the final results of model integration,
you will probably not need to use the
tend1 method, outside
of its automatic inclusion in the
tend1 method is useful if you're only interested
in the "tendencies" or if you wish to calculate a number of "tendencies"
in the same time step with the same initial conditions before completing
The structure of the code in this method should include the following and be in the following order:
Fieldobjects should have attribute
Fieldobjects to the model data container as a
add_tend1method. Usually this method call is the final line in the
add_tend1 method automatically creates
State object in
the model's data container, if it doesn't already exist. If the
State object already exists, the
add_tend1 method adds the newly calculated "tendencies"
Field objects share the
ids, overwriting if the
"replace" and summing with any previous value if the
mode argument equals
An example of a case to use
mode equal to
would be if that model should be the only place that calculates that variable.
An example of a case to use
mode equal to
"sum_with_previous" would be if a number of models
each contribute a part of the overall "tendency" in that
variable (and the total "tendency" is the sum of the contributions).
Most of the time the list of
Field objects in
add_tend1 arguments list will be
local variables calculated in the
tend1 method. For models
where all the tendencies in the
tend1 method are calculated
by calling the
tend1 methods of submodels, there will likely
be no locally calculated "tendencies" to add. In those cases,
there's nothing to add to the
be called with no arguments.
pydoc documentation for the
GenericModel class for additional information.
Here is some pseudo-code defining a "leaf" model class
(i.e. a model that does not call any submodels)
GenericModel, integrating it 100 timesteps,
and returning the result as a new
class LeafModel(GenericModel): def setup(self): [... add optional extra initialization code ...] def tend1(self): [... add lines to calculate "tendency" tend_Field ...] self.add_tend1( tend_Field ) [... define Field variables ...] [... define State variables s1, s2 ...] set = StateSet(s1, s2) model = LeafModel(set) for i in range(100): model.step1_return() newset = model.asStateSet()
Here we define a "branch" model class (i.e. a model that does call
submodels) that calls the
to calculate "tendencies".
We integrate the model 100 timesteps and returning the result as a new
StateSet. Note how the use of the "branch" model is
identical as the "leaf":
class BranchModel(GenericModel): def setup(self): self._leafmodel = LeafModel( self.asStateSet() ) def tend1(self): self._leafmodel.tend1() self.add_tend1() [... define Field variables ...] [... define State variables s1, s2 ...] set = StateSet(s1, s2) model = BranchModel(set) for i in range(100): model.step1_return() newset = model.asStateSet()
Finally, here we illustrate how the use of
makes it easy to change the calling order of models, helping blur the
lines (at least in terms of initialization syntax) between
"leaf" and "branch" models. Because
BranchModel is initialized as a reference to
will also reference
[... define Field variables ...] [... define State variables s1, s2 ...] set = StateSet(s1, s2) branch_model = BranchModel(set) leaf_model = LeafModel(branch_model.asStateSet()) for i in range(100): leaf_model.step1_return() newset = leaf_model.asStateSet()
Acknowledgements: Thanks to Michael Tobis for the idea of structuring models in a recursive manner.