-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a geospatial extension #31
base: master
Are you sure you want to change the base?
Conversation
For eWaterCycle, we have adopted a "plugin" system for tying specific models into our main python package (work of @BSchilperoort). Maybe this can serve as inspiration? |
We used python's entry points for adding these plugins. However, to me the extension system here seems a bit complex (at least for the Python implementation). My main question is: should the extensions be optional? Which situation are we discussing here:
The first case is a bit more difficult for developers, as you'd need to write the code in such a way you don't need to import Perhaps for Python a structure like this can be good for case 1: class BmiHeat(Bmi):
_geo = None
def __init__(self) -> None:
pass
def get_extensions(self) -> tuple[str, ...]:
return ("geo")
def initialize_extension(self, extension: str) -> None:
if extension == "geo":
self._geo: = BmiHeatGeo(self) # raises an ImportError if bmi_geospatial is not installed
else:
raise ValueError
@property
def geo(self): # give users a nice error when they forgot to initialize the extension
if self._geo is not None:
return self._geo
else:
raise NotInitializedError which is used like: >>> heat = BmiHeat()
>>> heat.initialize_extension("geo")
>>> heat.geo.get_grid_coordinate_names(0) For case 2 multiple inheritance seems most straightforward to me: class BmiHeat(Bmi, BmiGeo): Where the BmiGeo methods have the prefix I'm curious what you think of this @mcflugen! |
hmm, my 2 cents on this is that I prefer option 2. (because in eWaterCycle, a larger separation between user and model developer exists. If a model supports both with and without a certain extension, I'd argue that the developer presents the user with two versions of the model, in the case of @BSchilperoort his example:
|
@BSchilperoort, @RolfHut Thanks for your comments. This is exactly the sort of feedback I was looking for! My feeling is that BMI extensions should be optional. The primary rationale for making extensions optional is the potential for a large and dynamic set of extensions. Requiring implementers to support every function across all extensions, and to continually update their libraries with the addition and removal of extensions, would be overly demanding, error prone, and possibly limit reusability. I intentionally didn't use inheritance as I wanted to decouple the extension from the core BMI. The core BMI, through the I think we can leave it up to a framework or wrappers to add attributes to an instance of a BMI to make a cleaner way to access extensions (i.e. I view user-friendliness as a secondary goal of the BMI. It can be left up to a framework to make a BMI more user-friendly. For example, the sensible-bmi wraps a BMI to make it more Pythonic and user-friendly. |
I think you touch on an important point in the design philosophy of BMI in general that may not have been written down explicitly, but that I do observe in your (and our own) work:
Concluding: there should be a clear distinction what is "core BMI" that optimizes interoperability and "extensions and platforms" that optimize user friendliness. |
@RolfHut A side note that @mcflugen listed a set of BMI design principles in csdms/bmi#105 (which we should merge and include in the documentation). We should add your observations on BMI design, as well. |
This pull request is meant to explore one possible implementation of BMI extensions (or maybe plugins, rather than extensions? Would that be a better word?).
The accompanying SIDL file that describes the interface for this example extension is something like the following (this extension is similar to what @mdpiper outlined in csdms/bmi#99),
A couple notes on this,
This requires a new function,
get_extensions
be added to the core BMI. This function puts together a list of all the extensions a component implements. Each element of the list is a string of the form<extension-id>@<library-name>:<entry-point>
(the precise form of this string doesn't matter, this is just something I came up with that seemed to work).<extension-id>
is a unique identifier for the extension,<library-name>
is the name of the library that contains the extension (for Python, this would be a module name, for C it would be the name of a shared library), and<entry-point>
is the name of the class that implements the extension. The library would be separate from and sit alongside of the core bmi library.heat/bmi_geo.py
defines the abstract base class for the geospatial extension.heat/bmi_heat_geo.py
implements the extension for the heat model.Some example code that demonstrates how a framework might use the extension,