Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions bmi.sidl
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
//
// The Basic Model Interface (BMI)
//
package csdms version 2.1-dev.0 {
interface bmi {
package csdms version 3.0-alpha1 {
interface bmi3 {

// Model and BMI metadata
int get_bmi_version(out string version);

// Model control: initialize, run, finalize (IRF)
int initialize(in string config_file);
// Model control: initialize, extend, run, finalize (IRF)
int initialize(in string config_file, in array<string, 1> requested_extensions, out array<string, 1> supported_extensions);
int get_extension(in string extension_name, out pointer extension_object);
int update();
int update_until(in double time);
int finalize();
Expand Down
169 changes: 166 additions & 3 deletions docs/source/bmi.control_funcs.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,34 +19,50 @@ updating.
:::{tab-item} SIDL
:sync: sidl
```java
int initialize(in string config_file);
int initialize(in string config_file, in array<string, 1> requested_extensions, out array<string, 1> supported_extensions);
```
:::

:::{tab-item} Python
:sync: python
```python
def initialize(self, config_file: str) -> None:
def initialize(self, config_file: str, requested_extensions: Collection[str]) -> Collection[str]:
```
:::

:::{tab-item} c
:sync: c
```c
int initialize(void *self, char *config_file);
int initialize(void *self, char *config_file, char const **requested_extensions, char const **supported_extensions);
```
:::
::::

The `initialize` function accepts a string argument that gives the
path to its {term}`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.

It also takes an array of strings `requested_extensions` describing
the extensions that the caller would like to use if the the model
supports it. The model should determine which extensions it can
support, given its code and potentially the contents of the
configuration file. The supported extensions should be listed in
elements of the output array `supported_extensions`.

Use of extensions is completely optional within the scope of the core
BMI specification. Individual callers or models may require particular
extensions to provide their functionality. In the case where such an
extension is missing from the requested or supported extensions arrays
in or after the `initialize` call (respectively), the model or caller
should fail accordingly.

**Implementation notes**

- Models should be refactored, if necessary, to use a configuration
Expand All @@ -58,6 +74,153 @@ formatted.
a string -- a basic type in these languages.
- In C and Fortran, an integer status code indicating success (zero) or failure (nonzero)
is returned. In C++, Java, and Python, an exception is raised on failure.
- In C, the length of `supported_extensions` is upper-bounded by
`requested_extensions`, and so should be allocated
accordingly. Models should copy pointers from `requested_extensions`
to `supported_extensions` as appropriate. This allows the caller to
retain ownership.

**Extensions Rationale**

- The set of extensions that a caller can support should
be known in advance, since they will have their own semantics beyond
those of this BMI specification. Thus, it does not make sense in
this setting for models to advertise any extension that the caller
does not support.
- The set of extensions that a model supports may be
determined by the particular configuration with which it's
initialized. Thus, this cannot be queried before the `initialize()`
function.
- Extensions may require additional initialization steps. Thus, they
are requested in the `initialize()` function to indicate which ones
will be used if available. If they require added information or
setup behavior from the caller, as described in their own
specification, the caller is responsible for conforming to that
specification.
- If a caller requests a particular extension and a model indicates
support for it, the model may ultimately *require* that the caller
use the extension as it is specified. This implies, for instance,
that an extension requiring additional setup before the model enters
its time loop (e.g. setting values of calibration parameters or an
MPI communicator) may mean that the model will fail if that setup is
not done before other BMI functions are called.


(get-extension)=

## *get_extension*


::::{tab-set}
:sync-group: lang

:::{tab-item} SIDL
:sync: sidl
```java
int get_extension(in string extension_name, out pointer extension_object);
```
:::

:::{tab-item} Python
:sync: python
```python
def get_extension(self, extension_name: str) -> object:
```
:::

:::{tab-item} c
:sync: c
```c
int get_extension(void *self, char const *name, void **extension_object);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per Austin, I need to specify that this is a pointer-to-pointer to a single output object, and not some sort of array.

Copy link
Member Author

@PhilMiller PhilMiller Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type erased pointer, callers can reify them through {static,dynamic,reinterpret}_cast in C++, casating syntax in C, Rust std::transmute, or Fortran type(c_ptr). Python doesn't actually need anything special, since it's dynamically typed and dispatched anyway, but functions to get specific extensions could carry type hints to specify return of a particular extension object type.

```
:::

:::{tab-item} c++
:sync: c++
```c++
void* get_extension(std::string name);
```
:::
::::




For extensions specified to provide additional functions, these should
be accessed by the caller obtaining an associated extension object
with those functions as members.

**Implementation Notes**

- For staticly typed languages, including C, C++, and Fortran, the
output `extension_object` will be represented as an anonymous
(type-erased) pointer - `void *` or `type(c_ptr)`. The caller is
responsible for knowing the type of the pointed-to object, and
casting the pointer appropriately.
- In dynamically typed languages like Python, the return value will be
an object reference. The caller is expected to know what methods are
valid to call on the referenced object. If type checking is desired,
it may be implemented by wrapping the `get_extension` call in a
function with a suitable type hint on its return value.
- In C and C++, the `extension_object` instance is owned by the model
object, and should be suitably handled by a call to `finalize(self)`
or `model->Finalize()`, respectively.
- Depending on the model implementation language, the pointed-to
object need not be wholly distinct or disjoint in memory from the
model itself (i.e. `self` or `this`):

```python
def get_extension(self, extension_name: str):
if extension_name not in enabled_extensions:
raise UnimplementedException
return self
```

```c++
class MyModel : public bmi::Bmi, public ExtensionA, public ExtensionB
{
// ...
void* get_extension(std::string name) override {
// Casts below offset `this` to reference the corresponding vtable
if (name == "ExtensionA") { return static_cast<ExtensionA*>(this); }
if (name == "ExtensionB") { return static_cast<ExtensionB*>(this); }
throw std::runtime_error("Unimplemented extension requested");
}

void ExtensionA_Method1(int param1, void* param2) override;
void ExtensionA_Method2(int param1, void* param2) override;

void ExtensionB_Method1(int param1, void* param2) override;
void ExtensionB_Method2(int param1, void* param2) override;
};
```

```c
int MyModel_ExtensionA_Method1(struct Bmi *self, int param1, void* param2);
int MyModel_ExtensionA_Method2(struct Bmi *self, int param1, void* param2);

struct MyModel
{
// ...

struct ExtensionA extension_a = {
.method1 = &MyModel_ExtensionA_Method1;
.method2 = &MyModel_ExtensionA_Method2;
};
};

// ...
int MyModel_get_extension(struct Bmi *self, const char *extension_name, void** extension_object) {
struct MyModel *my_model = self->data;

if (strcmp(extension_name, "ExtensionA") == 0) {
*extension_object = &my_model->extension_a;
return BMI_SUCCESS;
}

return BMI_FAILURE;
}
```

(update)=

Expand Down
63 changes: 63 additions & 0 deletions docs/source/bmi.extensions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
(extensions)=

# BMI Extensions

An extension of BMI is identified by a unique name string. It could
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Describe reserved names and namespacing conventions below

encompass one or several of the following:

- Added functions exposed through extension objects that models export
via `get-extension-object`
- Variable sets that callers can expect models to publish
- Shared conventions of calling sequences or protocols around the use
of existing functions in core BMI or other extensions

Models implementing BMI are not required to support these extensions,
nor any others.


Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Describe versioning practices or conventions in some fashion

## Added Functions

Added functions in an extension must be expressed as members of an
{term}`extension object`. Models expose these extension objects via
`get-extension-object`. The name string should match the name of the
extension itself. If an extension needs to expose multiple distinct
objects, the name used for each should be an elaboration of the
extension's name.

The extension must specify the type signature of each extension object
it defines, with all of its members. For interoperability, develoeprs
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
it defines, with all of its members. For interoperability, develoeprs
it defines, with all of its members. For interoperability, developers

are encouraged to use types and calling conventions that follow the
practices of core BMI. However, this standard also anticipates that
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indexed/numbered variables could help alleviate string ABI interoperability in implementing extensions in various languages.

extensions may be used to explicitly obtain functionality that is
specific to a programming language or platform.

## Published Variable Sets

Extensions may define a variable set or sets that models and callers
can use to enrich their interactions. If a single name, it should
match that of the extension. Multiple names should each be an
elaboration of the extension's name. Extensions are free to define the
semantics of these sets as they please.

## Shared Conventions and Protocols

Extensions may describe added constraints or enrished semantics for
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Extensions may describe added constraints or enrished semantics for
Extensions may describe added constraints or enriched semantics for

how models and callers will interact using existing function in core
BMI or extensions they otherwise depend on.

## Relationship of Standardized Extensions to developers and the BMI specification process

The extensions described here have been designed and adopted by the
BMI council. They are standardized to support a common, interoperable
ecosystem of models and callers. We encourage development of other
extensions, and recommend that they follow the practices of extensions
described here. The BMI council only intends to standardize key
extensions that it expects will be broadly applicable. Extensions that
build on BMI for niche use cases are encouraged, and do not require
the BMI council's endorsement.

Nevertheless, the council appreciates being told of substantial
extensions, to better understand BMI usage and inform subsequent
development. The council can potentially provide design guidance and
share related experience. Presentations about extensions can act as
an informal peer-reviewed venue.
5 changes: 5 additions & 0 deletions docs/source/bmi.spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ Getters and setters <bmi.getter_setter>
Grid <bmi.grid_funcs>
```

Additionally, the BMI council has defined a number of
{ref}`standardized extensions <bmi.extensions>` that provide
additional functionality or more specific expectations of how models
and callers can interact.

Table 3 lists the individual BMI functions
along with a brief description.
Following the table is a detailed description of each function,
Expand Down