How To CEM Refactor
Sharing Numerical Models: Case Study
One of the goals of CSDMS is to increasing model sharing within Earth-surface modeling community. Unfortunately, many existing models are not in a state that allows them to be easily shared with others. Often models are written as standalone units by someone and maybe never used by more than a handful of users. As an example of what can be done to make a model more "sharable", I have taken an existing that was written by someone else and is outside of my area of expertise. In this case the model is a coastal evolution model written by Andrew Ashton (I'm not sure of the model's real name so I've code named it deltas).
The Goal
We would like a programmer to be able to use the functionality of someone else's model within their own application. For this example, we create an application programming interface (API) for the deltas model. We will get into the details of the API later; I think it is instructive to show what the finished interface will look like though. As an example, this is what someone's code might look like if they wished to use the deltas API (which we haven't actually written yet).
#include <stdlib.h>
#include <deltas_api.h>
int
main( void )
{
Deltas_state* s = deltas_init( NULL ); /* Create and initialize a new state structure. */
deltas_set_save_file( s, "fileout_0" ); /* Set a parameter in the state structure. */
deltas_run_until( s, 200. ); /* Run the model for 200 days. */
deltas_finalize( s ); /* Free resources used for the state structure. */
return EXIT_SUCCESS;
}
Here the programmer has used four functions from the deltas API: deltas_init, deltas_set_save_file, deltas_run_until, and deltas_finalize. The API also defines a new data type, Deltas_state. To begin with I'll give a brief description of the Deltas_state type, and then a brief description of each API functions.
The state structure: Deltas_state
The API provides a data type that contains all of the information needed to advance the model from one time step to the next. In this case the data type is called Deltas_state and is completely opaque to the user of the API. That is, the user is unable to directly access the members of the structure. To access and manipulate an instance of the model's state, the user only uses the methods that are defined within the API.
The state structure allows the application developer to create multiple instances of the model and run them concurrently or in series without having them interfere with one another. This also allows the model to be incrementally advanced from one time step to another. If the API developer were to write a functions that could read/write the structure to a file, one could easily implement a checkpoint/restart facility for the application.
Initialize: deltas_init
Creates a new state structure, and initializes it. Some things that might be done within this function are:
- allocate memory
- initialize arrays, and variables
- open files
- read input data
Run: deltas_run_until
Run the model until a given time. In our case we run the model for 200 days. If we wanted to run the model for an additional 100 days, we could call deltas_run_until again,
deltas_run_until( s, 300. ); /* Run the model for another 100 days. */
This should advance the model from day 200 to day 300. What it probably should not do is run the model from day 0 to day 300. Ultimately though, so long as the API provides an interface that is similar to this, the actual implementation of the API is up the programmer. However, if the model were run from zero every time the run_until function was called the API programmer may get a lot of complaints that his code runs slowly from someone who has written the following code:
for ( i=1; i<=300; i++ )
deltas_run_until( s, i );
In this case the model would have effectively been run for 45150 days rather than just 300 days.
Finalize: deltas_finalize
Free any resources that the state structure had been using, and destroy it. Some things that might be done here are:
- Free memory
- Close files
- Print output
Getters/Setters: deltas_set_save_file
As the state structure is completely opaque to the API user, the API must provide functions that get and set certain variables from the model state. In the above example, we wanted to set the name of the files that the model will write output to. Many of the variables that are contained in the state structure may not have get and/or set functions. The state structure may have some variables that it wouldn't make sense for some to change arbitrarily. For instance, it probably wouldn't make sense for someone to set the current time step willy-nilly since the model hasn't actually been advanced to that time.
For the deltas API, I've currently created four getters and four setters. The are:
- deltas_get_save_file/deltas_set_save_file: Get/set the name of the output file to save output to.
- deltas_get_read_file/deltas_set_read_file: Get/set the name of the input file to read input from.
- deltas_get_sed_rate/deltas_set_sed_rate: Get/set the sedimentation rate at the river mouth.
- deltas_get_depth/deltas_set_depth: Get/set the water depth of the deltas grid.
The Implementation
Now that we know where we are going, we look at how to get there. In this section I will describe how I refactored an existing coastal evolution model to provide the interface of the previous section.