Skip to content

Commit

Permalink
Merge branch 'master' of github.com:csdms/bmi into mdpiper/paper
Browse files Browse the repository at this point in the history
  • Loading branch information
mdpiper committed Jun 7, 2020
2 parents d6276ce + 656d78f commit faf21d7
Show file tree
Hide file tree
Showing 17 changed files with 413 additions and 103 deletions.
4 changes: 4 additions & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@ Contributors
* Michael Galloy
* Julian Hofer
* Eric Hutton
* Eric Morway
* Boyana Norris
* Scott Peckham
* Mark Piper
* Mike Taves
* Greg Tucker
* Ben van Werkhoven
* Martijn Visser

If you have contributed to the BMI package and your name is missing,
please send an email to the coordinators, or open a pull request
Expand Down
8 changes: 4 additions & 4 deletions bmi.sidl
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ package csdms version 2.0 {
int get_grid_node_count(in int grid, out int count);
int get_grid_edge_count(in int grid, out int count);
int get_grid_face_count(in int grid, out int count);
int get_grid_edge_nodes(in int grid, out array<int, 1> edge_nodes);
int get_grid_face_edges(in int grid, out array<int, 1> face_edges);
int get_grid_face_nodes(in int grid, out array<int, 1> face_nodes);
int get_grid_nodes_per_face(in int grid, out array<int, 1> nodes_per_face);
int get_grid_edge_nodes(in int grid, in array<int, 1> edge_nodes);
int get_grid_face_edges(in int grid, in array<int, 1> face_edges);
int get_grid_face_nodes(in int grid, in array<int, 1> face_nodes);
int get_grid_nodes_per_face(in int grid, in array<int, 1> nodes_per_face);
}
}
7 changes: 5 additions & 2 deletions docs/source/_templates/links.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<h3>Useful Links</h3>
<ul>
<li><a href="./bmi.getting_started.html">Getting Started Guide</a></li>
<li><a href="./bmi.best_practices.html">BMI Best Practices</a></li>
<li><a href="./glossary.html">Glossary</a></li>
<li><a href="https://github.com/csdms/bmi/">BMI @ GitHub</a></li>
<li><a href="https://github.com/csdms/bmi/issues">BMI issue tracker</a></li>
<li><a href="https://csdms.colorado.edu">CSDMS homepage</a></li>
<li><a href="https://github.com/csdms/bmi/issues">Issue Tracker</a></li>
<li><a href="https://csdms.colorado.edu">CSDMS Homepage</a></li>
</ul>

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.. _implementation:
.. _best_practices:

BMI best practices
==================
Expand All @@ -25,6 +25,18 @@ here are some tips to help when writing a BMI for a model.
the BMI specifies are the names of the functions, their arguments,
and their return values.

* :term:`Standard Names` are not required for naming a model's
:term:`exchange items <exchange item>`. However, the use of
standardized names makes it easier for a framework (or a human) to
match input and output variables between models.

* Don't change the variable names you currently use within your model
to :term:`Standard Names`. Standard Names are too long and
cumbersome to be used within a model code. Instead, you find a
`matching`_ Standard Name for each variable in your model and then
write your BMI functions to accept the Standard Names and map them
to your model's internal names.

* Constructs and features that are natural for the language should be
used when implementing a BMI. BMI strives to be developer-friendly.

Expand All @@ -34,6 +46,11 @@ here are some tips to help when writing a BMI for a model.
responsibility to ensure that array information is
flattened/redimensionalized in the correct order.

* Recall that models can have mulitple grids. This can be particularly
useful for defining :term:`exchange items <exchange item>` that
don't vary over the model domain; e.g., a diffusivity -- just define
the variable on a separate :ref:`scalar grid <unstructured_grids>`.

* Avoid using global variables, if possible. This isn't strictly a BMI
requirement, but if a model only uses local variables, its BMI will
be self-contained. This may allow multiple instances of the model to
Expand Down Expand Up @@ -73,3 +90,4 @@ here are some tips to help when writing a BMI for a model.
.. _C++: https://github.com/csdms/bmi-example-cxx
.. _Fortran: https://github.com/csdms/bmi-example-fortran
.. _Python: https://github.com/csdms/bmi-example-python
.. _matching: https://github.com/csdms/standard_names_registry
8 changes: 4 additions & 4 deletions docs/source/bmi.getter_setter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ state variable can be changed or check the new data for validity.
int get_value(in string name, in array<> dest);
The `get_value` function takes a variable name and copies values into a
provided buffer.
The type and size of the buffer depend on the variable,
provided array parameter.
The type and size of the array parameter depend on the variable,
and can be determined through
:ref:`get_var_type`, :ref:`get_var_nbytes`, etc.
Recall that arrays are always flattened in BMI,
Expand All @@ -39,7 +39,7 @@ even if the model uses dimensional variables.
* 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 buffer is a :term:`numpy` array.
* In Python, the array parameter is a :term:`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 :ref:`initialize`. Likewise, the
Expand All @@ -59,7 +59,7 @@ even if the model uses dimensional variables.
The `get_value_ptr` function takes a variable name and returns a reference
to a variable.
Unlike the buffer returned from :ref:`get_value`,
Unlike the array parameter returned from :ref:`get_value`,
the reference always points to the current values of the variable,
even if the model's state has changed.

Expand Down
67 changes: 67 additions & 0 deletions docs/source/bmi.getting_started.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
.. _getting_started:

How to get started
==================

If you want to add a Basic Model Interface (BMI) to your own model,
here are some tips on getting started.

1. Take a look at the list of BMI
:ref:`control functions and their descriptions <basic_model_interface>`
to get an idea of what these functions are meant to do
and how they provide a standard set of controls for your model.

2. On the theory that it's often easier to start with an example and
modify it, we recommend starting with a copy of
the :ref:`BMI example code<specs_and_examples>`---look for the version
written in the language of your model---to use as a template.
Each link points to a GitHub repository that includes an
example model called "heat", and a corresponding BMI in a file called
"bmi_heat" (so, for example, the Python version is "bmi_heat.py",
the C version is "bmi_heat.c" and "bmi_heat.h", etc.).
You can grab copies by cloning the repository to your local machine
and doing a file copy from there,
or by selecting, copying, and pasting the code directly
into your favorite editor.

3. Modify each of the BMI functions in the template file so that it works
on your model, rather than on the original "heat" example. Depending
on how your model code is structured, you may need to do some
:term:`refactoring <refactor>`.
For example, if your model's initialization and main
processing are lumped together in the same body of code, you will need
to divide them into separate functions. Our experience is that codes
that are already modular usually need little or no modification,
whereas codes that are more monolithic tend to require a lot more
refactoring (but that is probably worthwhile anyway for the sake of
the quality and sustainability of the code!).
Each case is a bit different.
Be sure to check out our :ref:`BMI best practices <best_practices>` document
for tips.
We encourage you to contact us with questions by posting an
issue on the `CSDMS Help Desk`_.

4. Test it out. Try writing a program or script that initializes your
model with a simple test case using the :ref:`initialize` function,
runs it with the :ref:`update` or :ref:`update_until` functions,
and exchanges data using :ref:`get_value` and :ref:`set_value`.
Run a test to verify that you get the same output from your BMI'd model
that you got from it prior to BMI'ing.
(Note: we recommend writing a :term:`unit test` for each of your
BMI functions; to learn more about unit tests,
check out our `webinar`_).

5. *(Optional but cool)* With a few additional steps, you can make your
model operate as a :ref:`pymt <pymt>` component,
so you can drive it directly from a Python shell,
and write Python scripts to couple it with other models.
(Learn more about *pymt* :ref:`here <pymt>`, and through its `documentation`_.)
The CSDMS Integration Facility team can provide help and guidance on this
process: just contact us through the `CSDMS Help Desk`_.


.. Links:
.. _CSDMS Help Desk: https://github.com/csdms/help-desk
.. _webinar: https://csdms.colorado.edu/wiki/Presenters-0478
.. _documentation: https://pymt.readthedocs.io
28 changes: 20 additions & 8 deletions docs/source/bmi.grid_funcs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ 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 :ref:`model_grids` and :ref:`implementation`.)
(See :ref:`model_grids` and :ref:`best_practices`.)


.. _get_grid_type:
Expand Down Expand Up @@ -49,7 +49,6 @@ is given in the :ref:`model_grids` section.

**Implementation notes**

* This function is needed for every :ref:`grid type <model_grids>`.
* In C++ and Python, the *type* argument is omitted and the grid
type name is returned from the function.

Expand All @@ -66,7 +65,8 @@ is given in the :ref:`model_grids` section.
/* SIDL */
int get_grid_rank(in int grid, out int rank);
Given a :term:`grid identifier`, get the :term:`rank` of that grid as an integer.
Given a :term:`grid identifier`, get the :term:`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.
Expand Down Expand Up @@ -366,12 +366,13 @@ Get the number of :term:`faces <face>` in the grid.
.. code-block:: java
/* SIDL */
int get_grid_edge_nodes(in int grid, out array<int, 1> edge_nodes);
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.
node at edge head. The total length of the array is
2 * :ref:`get_grid_edge_count`.

**Implementation notes**

Expand All @@ -391,10 +392,13 @@ node at edge head.
.. code-block:: java
/* SIDL */
int get_grid_face_edges(in int grid, out array<int, 1> face_edges);
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
:ref:`get_grid_nodes_per_face`.

**Implementation notes**

* This function is used for describing :ref:`unstructured
Expand All @@ -413,12 +417,17 @@ Get the face-edge connectivity.
.. code-block:: java
/* SIDL */
int get_grid_face_nodes(in int grid, out array<int, 1> face_nodes);
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 * :ref:`get_grid_face_count`.
More generally,
the length of the array is the sum of the values of
:ref:`get_grid_nodes_per_face`.

**Implementation notes**

Expand All @@ -438,10 +447,13 @@ that form the boundary of the face.
.. code-block:: java
/* SIDL */
int get_grid_nodes_per_face(in int grid, out array<int, 1> nodes_per_face);
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 :ref:`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 :ref:`unstructured
Expand Down
4 changes: 3 additions & 1 deletion docs/source/bmi.info_funcs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ but it should be unique to prevent conflicts with other components.
The number of variables the model can use from other models
implementing a BMI.
Also the number of variables that can be set with :ref:`set_value`.

**Implementation notes**

Expand All @@ -64,6 +65,7 @@ implementing a BMI.
The number of variables the model can provide other models
implementing a BMI.
Also the number of variables that can be retrieved with :ref:`get_value`.

**Implementation notes**

Expand Down Expand Up @@ -101,7 +103,7 @@ Standard Names do not have to be used within the model.
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 input variables.
* A model might have no input variables.

[:ref:`info_funcs` | :ref:`basic_model_interface`]

Expand Down
59 changes: 42 additions & 17 deletions docs/source/bmi.spec.rst
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
The *Basic Model Interface* (BMI) is a library specification
created by the `Community Surface Dynamics Modeling System`_ (CSDMS)
to facilitate the conversion of a model or dataset
into a reusable, plug-and-play component.
Recall that, in this context, an interface is a named set of functions
with prescribed arguments and return values.
The BMI functions make a model self-describing and fully controllable
by a modeling framework or application.
By design, the BMI functions are straightforward to implement in
any language, using only simple data types from standard language libraries.
Also by design, the BMI functions are noninvasive.
This means that a model's BMI does not make calls to other
components or tools and is not modified to use any
framework-specific data structures. A BMI, therefore, introduces no
dependencies into a model, so the model can still be used
in a stand-alone manner.
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:
Expand All @@ -22,6 +37,8 @@ The specification for each language is given in Table 1,
along with a corresponding example
in which the BMI is implemented.

.. _specs_and_examples:

.. table:: **Table 1:** BMI language specifications.
:align: center
:widths: 20, 25, 25, 30
Expand All @@ -35,6 +52,14 @@ in which the BMI is implemented.
Python `bmi.py`_ `bmi-python`_ `bmi-example-python`_
======== ============= ============== ======================

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

* :ref:`Getting Started Guide <getting_started>` --- a place to start
if you haven't written a BMI before
* :ref:`BMI Best Practices <best_practices>` --- our collected wisdom on
implementing a BMI

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


Expand All @@ -56,7 +81,7 @@ 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,
including the function prototype in :term:`SIDL`,
grouped by functional category.

.. table:: **Table 2:** Summary of BMI functions.
Expand Down
Loading

0 comments on commit faf21d7

Please sign in to comment.