diff --git a/bmi.sidl b/bmi.sidl index 9c3b0fb..36a95a5 100644 --- a/bmi.sidl +++ b/bmi.sidl @@ -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 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 cfba9bb..c8f2593 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: 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 @@ -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); +``` +::: + +:::{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)= diff --git a/docs/source/bmi.extensions.md b/docs/source/bmi.extensions.md new file mode 100644 index 0000000..dc35e5b --- /dev/null +++ b/docs/source/bmi.extensions.md @@ -0,0 +1,63 @@ +(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 + +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 +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. 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,