From e3b2866236998031349544d9fd504b61b3702a58 Mon Sep 17 00:00:00 2001 From: Phil Miller Date: Mon, 25 Aug 2025 11:36:28 -0700 Subject: [PATCH 1/8] Bump version in interface to 3.0 to reflect API breakage and intention for major version --- bmi.sidl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bmi.sidl b/bmi.sidl index 9c3b0fb..30766a8 100644 --- a/bmi.sidl +++ b/bmi.sidl @@ -1,8 +1,8 @@ // // 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); From 1f2c28fd1db20f6100b8161771f6368579c08b38 Mon Sep 17 00:00:00 2001 From: Phil Miller Date: Mon, 25 Aug 2025 11:36:38 -0700 Subject: [PATCH 2/8] First draft of extensions core specification --- bmi.sidl | 2 +- docs/source/bmi.control_funcs.md | 75 ++++++++++++++++++++++++++++++-- 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/bmi.sidl b/bmi.sidl index 30766a8..b9da31e 100644 --- a/bmi.sidl +++ b/bmi.sidl @@ -8,7 +8,7 @@ package csdms version 3.0-alpha1 { int get_bmi_version(out string version); // Model control: initialize, run, finalize (IRF) - int initialize(in string config_file); + int initialize(in string config_file, in array requested_extensions, out array supported_extensions); int update(); int update_until(in double time); int finalize(); diff --git a/docs/source/bmi.control_funcs.md b/docs/source/bmi.control_funcs.md index cfba9bb..77e7ac9 100644 --- a/docs/source/bmi.control_funcs.md +++ b/docs/source/bmi.control_funcs.md @@ -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 requested_extensions, out array supported_extensions); ``` ::: :::{tab-item} Python :sync: python ```python -def initialize(self, config_file: str) -> None: +def initialize(self, config_file: str, requested_extensions: list[str]) -> list[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 @@ -58,6 +74,59 @@ 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 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-item} c +:sync: c +```c +int get_extension(void *self, char const *name, void **extension_object); +``` +::: +:::: + +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** + +- 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. (update)= From 6c146bb33a0b2e3881cbb98528e8155b6c8f5768 Mon Sep 17 00:00:00 2001 From: Phil Miller Date: Mon, 3 Nov 2025 20:44:29 -0800 Subject: [PATCH 3/8] Abstract Python interface types --- docs/source/bmi.control_funcs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/bmi.control_funcs.md b/docs/source/bmi.control_funcs.md index 77e7ac9..5c78a43 100644 --- a/docs/source/bmi.control_funcs.md +++ b/docs/source/bmi.control_funcs.md @@ -26,7 +26,7 @@ int initialize(in string config_file, in array requested_extensions, :::{tab-item} Python :sync: python ```python -def initialize(self, config_file: str, requested_extensions: list[str]) -> list[str]: +def initialize(self, config_file: str, requested_extensions: Collection[str]) -> Collection[str]: ``` ::: From e71f81df2d1f6a8ab155a2f565234df04c4b4c27 Mon Sep 17 00:00:00 2001 From: Phil Miller Date: Mon, 3 Nov 2025 22:38:41 -0800 Subject: [PATCH 4/8] Add text describing get_extension implementation --- bmi.sidl | 3 +- docs/source/bmi.control_funcs.md | 100 ++++++++++++++++++++++++++++++- 2 files changed, 99 insertions(+), 4 deletions(-) diff --git a/bmi.sidl b/bmi.sidl index b9da31e..36a95a5 100644 --- a/bmi.sidl +++ b/bmi.sidl @@ -7,8 +7,9 @@ package csdms version 3.0-alpha1 { // Model and BMI metadata int get_bmi_version(out string version); - // Model control: initialize, run, finalize (IRF) + // Model control: initialize, extend, run, finalize (IRF) int initialize(in string config_file, in array requested_extensions, out array supported_extensions); + int get_extension(in string extension_name, out pointer extension_object); int update(); int update_until(in double time); int finalize(); diff --git a/docs/source/bmi.control_funcs.md b/docs/source/bmi.control_funcs.md index 5c78a43..c8f2593 100644 --- a/docs/source/bmi.control_funcs.md +++ b/docs/source/bmi.control_funcs.md @@ -101,32 +101,126 @@ should fail accordingly. 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 an MPI communicator) may mean that the - model will fail if that setup is not done before other BMI functions - are called. + 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); ``` ::: + +:::{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(this); } + if (name == "ExtensionB") { return static_cast(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)= From 1d998c95bf4f3f9775061baa8515146f44b0775b Mon Sep 17 00:00:00 2001 From: Phil Miller Date: Tue, 4 Nov 2025 07:00:11 -0800 Subject: [PATCH 5/8] Some text about extensions --- docs/source/bmi.extensions.md | 55 +++++++++++++++++++++++++++++++++++ docs/source/bmi.spec.md | 5 ++++ 2 files changed, 60 insertions(+) create mode 100644 docs/source/bmi.extensions.md diff --git a/docs/source/bmi.extensions.md b/docs/source/bmi.extensions.md new file mode 100644 index 0000000..33e8b5d --- /dev/null +++ b/docs/source/bmi.extensions.md @@ -0,0 +1,55 @@ +(extensions)= + +# BMI Extensions + +An extension of BMI is identified by a unique name string. It could +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. + + +## 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 +are encouraged to use types and calling conventions that follow the +practices of core BMI. However, this standard also anticipates that +extensions may be used to explicitly obtain functionality that is +specific to a programming language or platform. + +## Published Variable Sets + + +## Shared Conventions and Protocols + + +## 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. \ No newline at end of file diff --git a/docs/source/bmi.spec.md b/docs/source/bmi.spec.md index c94eace..fbe7764 100644 --- a/docs/source/bmi.spec.md +++ b/docs/source/bmi.spec.md @@ -17,6 +17,11 @@ Getters and setters Grid ``` +Additionally, the BMI council has defined a number of +{ref}`standardized 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, From 27576b5db3ce886878127907303df729914b0267 Mon Sep 17 00:00:00 2001 From: Phil Miller Date: Tue, 4 Nov 2025 07:03:13 -0800 Subject: [PATCH 6/8] Some text for additional var sets --- docs/source/bmi.extensions.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/source/bmi.extensions.md b/docs/source/bmi.extensions.md index 33e8b5d..1978e06 100644 --- a/docs/source/bmi.extensions.md +++ b/docs/source/bmi.extensions.md @@ -33,6 +33,11 @@ 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 From 86fb07fd99960df67dc15ae56ba5bcc3f31efcfe Mon Sep 17 00:00:00 2001 From: Phil Miller Date: Tue, 4 Nov 2025 07:07:34 -0800 Subject: [PATCH 7/8] Some text for conventions and protocols --- docs/source/bmi.extensions.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/source/bmi.extensions.md b/docs/source/bmi.extensions.md index 1978e06..ac5c730 100644 --- a/docs/source/bmi.extensions.md +++ b/docs/source/bmi.extensions.md @@ -41,6 +41,9 @@ semantics of these sets as they please. ## Shared Conventions and Protocols +Extensions may describe added constraints or enrished 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 From 095526df2db8bf9b24054585de1d7886e2e27a53 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 15:07:50 +0000 Subject: [PATCH 8/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/source/bmi.extensions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/bmi.extensions.md b/docs/source/bmi.extensions.md index ac5c730..dc35e5b 100644 --- a/docs/source/bmi.extensions.md +++ b/docs/source/bmi.extensions.md @@ -60,4 +60,4 @@ 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. \ No newline at end of file +an informal peer-reviewed venue.