Basic Model Interface (BMI)

When you climb in the driver’s seat of an unfamiliar car, you are nonetheless presented with a familiar sight. Whatever the make or model may be, we take it for granted that the vehicle will provide a steering wheel, brake pedal, and speedometer, alongside the various other controls and readouts that are common to essentially all cars and trucks on the planet. Although we don’t usually think of it this way, drivers across the globe benefit from a standard interface: a set of control mechanisms and information displays that have essentially the same design regardless of whether the vehicle is a tiny electric two-seater or a giant stretch limousine. This standard interface makes the job of operating a vehicle much easier than it would be if each one presented a radically different interface. Imagine a world where switching from a sports car to a pickup truck required months of study and practice! Similarly, railroads benefit from a standard for coupling rail cars together. The result: trains can be assembled from combinations of all sorts of different rail cars, built by different companies, in different places, and with different purposes.

We believe that numerical models, and the sub-components that make up those models, should offer a similar kind of standardization. To this end, the Community Surface Dynamics Modeling System (CSDMS) has developed the Basic Model Interface (BMI): a set of standard control and query functions that, when added to a model code, make that model both easier to learn and easier to couple with other software elements.

While a BMI can be written for any language, CSDMS currently supports four languages: C, C++, Fortran, and Python. The specification for each language is given in Table 1, along with a corresponding example in which the BMI is implemented.

Table 1: BMI language specifications.
Language Specification Repository Example
C bmi.h bmi-c bmi-example-c
C++ bmi.hxx bmi-cxx bmi-example-cxx
Fortran bmi.f90 bmi-fortran bmi-example-fortran
Python bmi.py bmi-python bmi-example-python

Along with the examples, two documents may be particularly helpful when writing a BMI:

A complete description of the functions that make up the BMI is given next.

The Basic Model Interface

The functions that comprise the Basic Model Interface can be grouped into categories:

Table 2 lists the individual BMI functions along with a brief description. Following the table is a detailed description of each function, including the function prototype in SIDL, grouped by functional category.

Table 2: Summary of BMI functions.
Function Description
initialize Perform startup tasks for the model.
update Advance model state by one time step.
update_until Advance model state until the given time.
finalize Perform tear-down tasks for the model.
get_component_name Name of the model.
get_input_item_count Count of a model’s input variables.
get_output_item_count Count of a model’s output variables.
get_input_var_names List of a model’s input variables.
get_output_var_names List of a model’s output variables.
get_var_grid Get the grid identifier for a variable.
get_var_type Get the data type of a variable.
get_var_units Get the units of a variable.
get_var_itemsize Get the size (in bytes) of one element of a variable.
get_var_nbytes Get the total size (in bytes) of a variable.
get_var_location Get the grid element type of a variable.
get_current_time Current time of the model.
get_start_time Start time of the model.
get_end_time End time of the model.
get_time_units Time units used in the model.
get_time_step Time step used in the model.
get_value Get a copy of values of a given variable.
get_value_ptr Get a reference to the values of a given variable.
get_value_at_indices Get variable values at specific locations.
set_value Set the values of a given variable.
set_value_at_indices Set the values of a variable at specific locations.
get_grid_rank Get the number of dimensions of a computational grid.
get_grid_size Get the total number of elements of a computational grid.
get_grid_type Get the grid type as a string.
get_grid_shape Get the dimensions of a computational grid.
get_grid_spacing Get the spacing between grid nodes.
get_grid_origin Get the origin of a grid.
get_grid_x Get the locations of a grid’s nodes in dimension 1.
get_grid_y Get the locations of a grid’s nodes in dimension 2.
get_grid_z Get the locations of a grid’s nodes in dimension 3.
get_grid_node_count Get the number of nodes in the grid.
get_grid_edge_count Get the number of edges in the grid.
get_grid_face_count Get the number of faces in the grid.
get_grid_edge_nodes Get the edge-node connectivity.
get_grid_face_edges Get the face-edge connectivity.
get_grid_face_nodes Get the face-node connectivity.
get_grid_nodes_per_face Get the number of nodes for each face.

Model control functions

These BMI functions are critical to plug-and-play modeling because they allow a calling component to bypass a model’s own time loop. They also provide the caller with fine-grained control over the model – a calling application is able to, for instance, update a model one time step at a time, change its state, and then continue updating.

initialize

/* SIDL */
int initialize(in string config_file);

The initialize function accepts a string argument that gives the path to its configuration file. This function should perform all tasks that are to take place before entering the model’s time loop. Models should be refactored, if necessary, to read their inputs (which could include filenames for other input files) from a configuration file. BMI does not impose any constraint on how configuration files are formatted.

Implementation notes

  • Models should be refactored, if necessary, to use a configuration file.
  • While no constraints are placed on how configuration files are formatted, YAML is preferred.
  • In C and Fortran, the config_file argument is passed as a character array, whereas in C++ and Python, it’s passed as a string – a basic type in these languages.
  • In C and Fortran, an integer status code indicating success or failure is returned. In C++ and Python, an exception is raised on failure.

[Model control functions | The Basic Model Interface]

update

/* SIDL */
int update();

The update function advances the model by a single time step. This is the model’s own internal time step (as returned by the BMI get_time_step function), not the time step of a controlling application. This function should perform all tasks that take place during one pass through the model’s time loop. It does not contain the time loop. This typically involves incrementing all of the model’s state variables. If the model’s state variables don’t change in time, then they can be computed by the initialize function and this function can just return without doing anything.

Implementation notes

  • In C and Fortran, an integer status code indicating success or failure is returned. In C++ and Python, an exception is raised on failure.

[Model control functions | The Basic Model Interface]

update_until

/* SIDL */
int update_until(in double time);

The update_until function updates the model to a particular time, as provided by its time argument. If the model permits, the time argument can be a non-integral multiple of time steps, and even negative. Once called, the value returned by the BMI get_current_time function must return the provided time to reflect that the model was updated to the requested time.

Implementation notes

  • Time is always a double-precision value.
  • In C and Fortran, an integer status code indicating success or failure is returned. In C++ and Python, an exception is raised on failure.

[Model control functions | The Basic Model Interface]

finalize

/* SIDL */
int finalize();

The finalize function should perform all tasks that take place after exiting the model’s time loop. This typically includes deallocating memory, closing files and printing reports.

Implementation notes

  • In C and Fortran, an integer status code indicating success or failure is returned. In C++ and Python, an exception is raised on failure.

[Model control functions | The Basic Model Interface]

Model information functions

These functions supply the model name and the model’s exchange items – the variables that the model can use from and provide to other models that have a BMI.

get_component_name

/* SIDL */
int get_component_name(out string name);

This function supplies the name of the model component as a string. There are no restrictions on the name, but it should be unique to prevent conflicts with other components.

Implementation notes

  • In C and Fortran, the name argument is a a character array. In C++ and Python, this argument is omitted, and a string – a basic type in these languages – is returned from the function.

[Model information functions | The Basic Model Interface]

get_input_item_count

/* SIDL */
int get_input_item_count(out int count);

The number of variables the model can use from other models implementing a BMI. Also the number of variables that can be set with set_value.

Implementation notes

  • In C++ and Python, the argument is omitted and the count is returned from the function.

[Model information functions | The Basic Model Interface]

get_output_item_count

/* SIDL */
int get_output_item_count(out int count);

The number of variables the model can provide other models implementing a BMI. Also the number of variables that can be retrieved with get_value.

Implementation notes

  • In C++ and Python, the argument is omittedq and the count is returned from the function.

[Model information functions | The Basic Model Interface]

get_input_var_names

/* SIDL */
int get_input_var_names(out array<string, 1> names);

Gets an array of names for the variables the model can use from other models implementing a BMI. The length of the array is given by get_input_item_count. The names are preferably in the form of CSDMS Standard Names. Standard Names enable a modeling framework to determine whether an input variable in one model is equivalent to, or compatible with, an output variable in another model. This allows the framework to automatically connect components. Standard Names do not have to be used within the model.

Implementation notes

  • In C and Fortran, the names are passed back as an array of character pointers (because the variable names could have differing lengths).
  • In C++, the argument is omitted and the names are returned from the function in a vector, a standard container in the language.
  • In Python, the argument is omitted and the names are returned from the function in a tuple, a standard container in the language.
  • A model might have no input variables.

[Model information functions | The Basic Model Interface]

get_output_var_names

/* SIDL */
int get_output_var_names(out array<string, 1> names);

Gets an array of names for the variables the model can provide to other models implementing a BMI. The length of the array is given by get_output_item_count. The names are preferably in the form of CSDMS Standard Names. Standard Names enable a modeling framework to determine whether an input variable in one model is equivalent to, or compatible with, an output variable in another model. This allows the framework to automatically connect components. Standard Names do not have to be used within the model.

Implementation notes

  • In C and Fortran, the names are passed back as an array of character pointers (because the variable names could have differing lengths).
  • In C++, the argument is omitted and the names are returned from the function in a vector, a standard container in the language.
  • In Python, the argument is omitted and the names are returned from the function in a tuple, a standard container in the language.
  • A model may have no output variables.

[Model information functions | The Basic Model Interface]

Variable information functions

These BMI functions provide information about a particular input or output variable. They must accommodate any variable returned from the get_input_var_names or get_output_var_names functions – the variable name is used as an argument in each function. Based on the information returned, type or unit conversions can be applied when necessary.

get_var_grid

/* SIDL */
int get_var_grid(in string name, out int grid);

Each input and output variable is defined on a grid. (Available grid types are listed in the Model grid functions section.) The get_var_grid function provides the identifier (an integer) for this grid. The identifier can be passed to the BMI grid information functions to get the details of a particular grid; e.g., x- and y-coordinates, size, type, etc. A model can have one or more grids.

Implementation notes

  • Grid identifiers start at 0.
  • In C++ and Python, the grid argument is omitted and the grid identifier is returned from the function.

[Variable information functions | The Basic Model Interface]

get_var_type

/* SIDL */
int get_var_type(in string name, out string type);

The get_var_type function provides the data type of the variable as it’s stored in memory by the model. The data type is returned as a string. Use of native language type names is encouraged; e.g., in C, use int, float, and double, while in Fortran, use integer, real, and double precision.

Implementation notes

  • In C++ and Python, the type argument is omitted and the variable type name is returned from the function.

[Variable information functions | The Basic Model Interface]

get_var_units

/* SIDL */
int get_var_units(in string name, out string units);

Get the units of the given variable. Standard unit names, in lower case, should be used, such as meters or seconds. Standard abbreviations, such as m for meters, are also supported. For variables with compound units, each unit name is separated by a single space, with exponents other than 1 placed immediately after the name, as in m s-1 for velocity, W m-2 for an energy flux, or km2 for an area. The abbreviations used in the BMI are derived from Unidata’s UDUNITS package. See, for example, The Units Database for a full description of valid unit names and a list of supported units.

Implementation notes

  • In C++ and Python, the units argument is omitted and the variable units name is returned from the function.

[Variable information functions | The Basic Model Interface]

get_var_itemsize

/* SIDL */
int get_var_itemsize(in string name, out int size);

The get_var_itemsize function provides the size, in bytes, of a single element of the variable. For example, if data for a variable are stored as 64-bit integers, get_var_itemsize would return 8.

Implementation notes

  • In C++ and Python, the size argument is omitted and the item size is returned from the function.

[Variable information functions | The Basic Model Interface]

get_var_nbytes

/* SIDL */
int get_var_nbytes(in string name, out int nbytes);

The get_var_nbytes function provides the total amount of memory used to store a variable; i.e., the number of items multiplied by the size of each item.

Implementation notes

  • In C++ and Python, the nbytes argument is omitted and the total amount of memory used by the variable is returned from the function.

[Variable information functions | The Basic Model Interface]

get_var_location

/* SIDL */
int get_var_location(in string name, out string location);

The get_var_location function, given a variable name, returns a string that indicates on what grid element the variable is defined. Valid return values are:

  • node
  • edge
  • face

Implementation notes

  • In C++ and Python, the location argument is omitted and the location is returned from the function.
  • If the given variable is a scalar (i.e., defined on a scalar grid), the return from this function is ignored.

[Variable information functions | The Basic Model Interface]

Time functions

These simple diagnostic functions provide information on model time. Model time is always expressed as a floating point value.

get_current_time

/* SIDL */
int get_current_time(out double time);

The current model time.

Implementation notes

  • In C++ and Python, the argument is omitted and the time is returned from the function.

[Time functions | The Basic Model Interface]

get_start_time

/* SIDL */
int get_start_time(out double time);

The start time of the model.

Implementation notes

  • The start time in BMI is typically defined to be 0.0.
  • In C++ and Python, the argument is omitted and the time is returned from the function.

[Time functions | The Basic Model Interface]

get_end_time

/* SIDL */
int get_end_time(out double time);

The end time of the model.

Implementation notes

  • If the model doesn’t define an end time, a large number (e.g., the largest floating point number supported on a platform) is typically chosen.
  • In C++ and Python, the argument is omitted and the time is returned from the function.

[Time functions | The Basic Model Interface]

get_time_units

/* SIDL */
int get_time_units(out string units);

Get the units of time as reported by the model’s BMI (through get_current_time, get_end_time, etc.). It’s recommended to use time unit conventions from Unidata’s UDUNITS package; e.g., s, min, h, d.

Implementation notes

  • Avoid using years as a unit, if possible, since a year is difficult to define precisely. UDUNITS defines a year as 365.2422 days or 31556926 seconds.
  • In C++ and Python, the argument is omitted and the units are returned from the function.

[Time functions | The Basic Model Interface]

get_time_step

/* SIDL */
int get_time_step(out double time_step);

Get the time step used in the model. The time step is always expressed as a floating point value.

Implementation notes

  • A time step is typically a positive value. However, if the model permits it, a negative value can be used (running the model backward).
  • In C++ and Python, the argument is omitted and the time step is returned from the function.

[Time functions | The Basic Model Interface]

Variable getter and setter functions

These functions are used to access and modify the variables that a model exposes through its BMI (see get_input_var_names and get_output_var_names).

A getter is a function called to get a variable from your model’s state. A model’s state variables typically change with each time step, so getters are called to get current values.

A setter is a function called to change/overwrite a variable in your model’s state. A setter may impose restrictions on how a state variable can be changed or check the new data for validity.

get_value

/* SIDL */
int get_value(in string name, in array<> dest);

The get_value function takes a variable name and copies values into a provided array parameter. The type and size of the array parameter depend on the variable, and can be determined through get_var_type, get_var_nbytes, etc. Recall that arrays are always flattened in BMI, even if the model uses dimensional variables.

Implementation notes

  • The dest argument must be defined and allocated before calling get_value. Whatever values it contains are overwritten in the call to get_value.
  • In Python, the array parameter is a numpy array.
  • In C++, get_value is a void function.
  • Depending on how a model is written, a variable may not be accessible until after the call to initialize. Likewise, the variable may not be accessible after calling finalize.

[Variable getter and setter functions | The Basic Model Interface]

get_value_ptr

/* SIDL */
int get_value_ptr(in string name, out array<> dest_ptr);

The get_value_ptr function takes a variable name and returns a reference to a variable. Unlike the array parameter returned from get_value, the reference always points to the current values of the variable, even if the model’s state has changed.

Implementation notes

  • The reference points to a flattened array.
  • In C++, the dest_ptr argument is omitted, and the reference is returned through the function.
  • In Python, a numpy array is returned.

[Variable getter and setter functions | The Basic Model Interface]

get_value_at_indices

/* SIDL */
int get_value_at_indices(in string name, in array<> dest, in array<int, 1> inds);

Use the get_value_at_indices function to get a copy of a variable’s values at the locations specified by the one-dimensional array indices in the inds argument. The values are returned through the dest argument.

Implementation notes

All the notes from get_value apply. Additionally,

  • Both dest and inds are flattened arrays.
  • The inds argument is always of type integer.

[Variable getter and setter functions | The Basic Model Interface]

set_value

/* SIDL */
int set_value(in string name, in array<> src);

The set_value function takes a variable name and an array of values, src, and copies those values into the model’s internal array of values, overwriting the current contents. The type and size of src must match the model’s internal array, and can be determined through get_var_type, get_var_nbytes, etc. Recall that arrays are always flattened in BMI, even if the model uses dimensional variables.

Implementation notes

  • In Python, src is a numpy array.
  • In C++, set_value is a void function.
  • Depending on how a model is written, a variable may not be accessible until after the call to initialize. Likewise, the variable may not be accessible after calling finalize.

[Variable getter and setter functions | The Basic Model Interface]

set_value_at_indices

/* SIDL */
int set_value_at_indices(in string name, in array<int, 1> inds, in array<> src);

Use the set_value_at_indices function to set a variable’s values at the locations specified by the one-dimensional array indices in the inds argument.

Implementation notes

All the notes from set_value apply. Additionally,

  • Both src and inds are flattened arrays.
  • The inds argument is always of type integer.

[Variable getter and setter functions | The Basic Model Interface]

Model grid functions

The functions in this section describe model grids. In the BMI, every exchange item is defined on a grid, and is referenced by a grid identifier returned from the get_var_grid function. This identifier is a required input to the functions listed below.

A model can have multiple grids. For example, consider modeling the diffusion of temperature over a flat plate. One grid could be a uniform rectilinear grid on which temperature is defined. A second grid could be a scalar, on which a constant thermal diffusivity is defined.

Not all grid functions are used by each type of grid. However, all BMI grid functions must be implemented. (See Model grids and BMI best practices.)

get_grid_type

/* SIDL */
int get_grid_type(in int grid, out string type);

Given a grid identifier, get the type of that grid as a string. Valid grid types are:

  • scalar
  • points
  • vector
  • unstructured
  • structured_quadrilateral
  • rectilinear
  • uniform_rectilinear

A detailed description of the grid types supported in BMI is given in the Model grids section.

Implementation notes

  • In C++ and Python, the type argument is omitted and the grid type name is returned from the function.

[Model grid functions | The Basic Model Interface]

get_grid_rank

/* SIDL */
int get_grid_rank(in int grid, out int rank);

Given a grid identifier, get the rank (the number of dimensions) of that grid as an integer.

A grid’s rank determines the length of the return value of many of the following grid functions. For instance, get_grid_shape returns an array of length rank. Similarly, a grid’s rank determines which of get_grid_x, get_grid_y, etc. are implemented.

Implementation notes

  • This function is needed for every grid type.
  • In C++ and Python, the rank argument is omitted and the grid rank is returned from the function.

[Model grid functions | The Basic Model Interface]

get_grid_size

/* SIDL */
int get_grid_size(in int grid, out int size);

Given a grid identifier, get the total number of elements (or nodes) of that grid as an integer.

The grid size is used for, among other things, the length of arrays returned by get_grid_x and get_grid_y for unstructured and structured quad grids.

Implementation notes

  • This function is needed for every grid type.
  • In C++ and Python, the size argument is omitted and the grid size is returned from the function.

[Model grid functions | The Basic Model Interface]

get_grid_shape

/* SIDL */
int get_grid_shape(in int grid, in array<int, 1> shape);

Get the dimensions of the model grid.

Note that this function (as well as the other grid functions) returns information ordered with “ij” indexing (as opposed to “xy”). For example, consider a two-dimensional rectilinear grid with four columns (nx = 4) and three rows (ny = 3). The get_grid_shape function would return a shape of [ny, nx], or [3,4]. If there were a third dimension, the length of the z-dimension, nz, would be listed first.

Also note that the grid shape is the number of nodes in the coordinate directions and not the number of cells or elements. It is possible for grid values to be associated with the nodes or with the cells.

Implementation notes

  • This function is used for describing all structured grids.
  • In Python, the shape argument is a numpy array.
  • In C++, this is a void function.

[Model grid functions | The Basic Model Interface]

get_grid_spacing

/* SIDL */
int get_grid_spacing(in int grid, in array<double, 1> spacing);

Get the distance between the nodes of the model grid.

The get_grid_spacing function provides the width of each cell in the number of dimensions as returned by get_grid_rank. As with get_grid_shape, the spacing is given in “ij” indexing* order; e.g., for a two-dimensional grid, the spacing between rows is followed by spacing between columns, [dy, dx].

Implementation notes

  • This function is used for describing uniform rectilinear grids.
  • In Python, the spacing argument is a numpy array.
  • In C++, this is a void function.

[Model grid functions | The Basic Model Interface]

get_grid_origin

/* SIDL */
int get_grid_origin(in int grid, in array<double, 1> origin);

Get the coordinates of the lower-left corner of the model grid.

The origin parameter is a one-dimensional array of the size returned by get_grid_rank. As with get_grid_shape, the origin is given in “ij” indexing* order; e.g., for a two-dimensional grid, the origin is given in the column dimension, followed by the row dimension, [y0, x0].

Implementation notes

  • This function is used for describing uniform rectilinear grids.
  • In Python, the origin argument is a numpy array.
  • In C++, this is a void function.

[Model grid functions | The Basic Model Interface]

get_grid_x

/* SIDL */
int get_grid_x(in int grid, in array<double, 1> x);

Get the locations of the grid nodes in the first coordinate direction.

The length of the resulting one-dimensional array depends on the grid type. (It will have either get_grid_rank or get_grid_size elements.) See Model grids for more information.

Implementation notes

[Model grid functions | The Basic Model Interface]

get_grid_y

/* SIDL */
int get_grid_y(in int grid, in array<double, 1> y);

Get the locations of the grid nodes in the second coordinate direction.

The length of the resulting one-dimensional array depends on the grid type. (It will have either get_grid_rank or get_grid_size elements.) See Model grids for more information.

Implementation notes

[Model grid functions | The Basic Model Interface]

get_grid_z

/* SIDL */
int get_grid_z(in int grid, in array<double, 1> z);

Get the locations of the grid nodes in the third coordinate direction.

The length of the resulting one-dimensional array depends on the grid type. (It will have either get_grid_rank or get_grid_size elements.) See Model grids for more information.

Implementation notes

[Model grid functions | The Basic Model Interface]

get_grid_node_count

/* SIDL */
int get_grid_node_count(in int grid, out int count);

Get the number of nodes in the grid.

Implementation notes

  • This function is used for describing unstructured grids.
  • In C++ and Python, the count argument is omitted and the node count is returned from the function.

[Model grid functions | The Basic Model Interface]

get_grid_edge_count

/* SIDL */
int get_grid_edge_count(in int grid, out int count);

Get the number of edges in the grid.

Implementation notes

  • This function is used for describing unstructured grids.
  • In C++ and Python, the count argument is omitted and the edge count is returned from the function.

[Model grid functions | The Basic Model Interface]

get_grid_face_count

/* SIDL */
int get_grid_face_count(in int grid, out int count);

Get the number of faces in the grid.

Implementation notes

  • This function is used for describing unstructured grids.
  • In C++ and Python, the count argument is omitted and the face count is returned from the function.

[Model grid functions | The Basic Model Interface]

get_grid_edge_nodes

/* SIDL */
int get_grid_edge_nodes(in int grid, in array<int, 1> edge_nodes);

Get the edge-node connectivity.

For each edge, connectivity is given as node at edge tail, followed by node at edge head. The total length of the array is 2 * get_grid_edge_count.

Implementation notes

  • This function is used for describing unstructured grids.
  • In Python, the edge_nodes argument is a numpy array.
  • In C++, this is a void function.

[Model grid functions | The Basic Model Interface]

get_grid_face_edges

/* SIDL */
int get_grid_face_edges(in int grid, in array<int, 1> face_edges);

Get the face-edge connectivity.

The length of the array returned is the sum of the values of get_grid_nodes_per_face.

Implementation notes

  • This function is used for describing unstructured grids.
  • In Python, the face_edges argument is a numpy array.
  • In C++, this is a void function.

[Model grid functions | The Basic Model Interface]

get_grid_face_nodes

/* SIDL */
int get_grid_face_nodes(in int grid, in array<int, 1> face_nodes);

Get the face-node connectivity.

For each face, the nodes (listed in a counter-clockwise direction) that form the boundary of the face. For a grid of quadrilaterals, the total length of the array is 4 * get_grid_face_count. More generally, the length of the array is the sum of the values of get_grid_nodes_per_face.

Implementation notes

  • This function is used for describing unstructured grids.
  • In Python, the face_nodes argument is a numpy array.
  • In C++, this is a void function.

[Model grid functions | The Basic Model Interface]

get_grid_nodes_per_face

/* SIDL */
int get_grid_nodes_per_face(in int grid, in array<int, 1> nodes_per_face);

Get the number of nodes for each face.

The returned array has a length of get_grid_face_count. The number of edges per face is equal to the number of nodes per face.

Implementation notes

  • This function is used for describing unstructured grids.
  • In Python, the nodes_per_face argument is a numpy array.
  • In C++, this is a void function.

[Model grid functions | The Basic Model Interface]

Help

Adding a BMI to a model can be a daunting task. If you’d like assistance, CSDMS can help. Depending on your need, we can provide advice or consulting services. Feel free to contact us through the CSDMS Help Desk.