xmipy

Package for the eXtendend Model Interface. It provides abstract classes, as well as the implementation XmiWrapper

 1"""
 2Package for the eXtendend Model Interface.
 3It provides abstract classes, as well as the implementation `XmiWrapper`
 4"""
 5
 6# exports
 7from bmipy.bmi import Bmi
 8
 9from xmipy.xmi import Xmi
10from xmipy.xmiwrapper import XmiWrapper
11
12__all__ = ["Bmi", "Xmi", "XmiWrapper"]
13
14__version__ = "1.5.0"
class Bmi(abc.ABC):
 13class Bmi(ABC):
 14    """Base class for implementations of a Python-based BMI."""
 15
 16    @abstractmethod
 17    def initialize(self, config_file: str) -> None:
 18        """Perform startup tasks for the model.
 19
 20        Perform all tasks that take place before entering the model's time
 21        loop, including opening files and initializing the model state. Model
 22        inputs are read from a text-based configuration file, specified by
 23        `config_file`.
 24
 25        Parameters
 26        ----------
 27        config_file : str, optional
 28            The path to the model configuration file.
 29
 30        Notes
 31        -----
 32        Models should be refactored, if necessary, to use a
 33        configuration file. CSDMS does not impose any constraint on
 34        how configuration files are formatted, although YAML is
 35        recommended. A template of a model's configuration file
 36        with placeholder values is used by the BMI.
 37        """
 38        ...
 39
 40    @abstractmethod
 41    def update(self) -> None:
 42        """Advance model state by one time step.
 43
 44        Perform all tasks that take place within one pass through the model's
 45        time loop. This typically includes incrementing all of the model's
 46        state variables. If the model's state variables don't change in time,
 47        then they can be computed by the :func:`initialize` method and this
 48        method can return with no action.
 49        """
 50        ...
 51
 52    @abstractmethod
 53    def update_until(self, time: float) -> None:
 54        """Advance model state until the given time.
 55
 56        Parameters
 57        ----------
 58        time : float
 59            A model time later than the current model time.
 60        """
 61        ...
 62
 63    @abstractmethod
 64    def finalize(self) -> None:
 65        """Perform tear-down tasks for the model.
 66
 67        Perform all tasks that take place after exiting the model's time
 68        loop. This typically includes deallocating memory, closing files and
 69        printing reports.
 70        """
 71        ...
 72
 73    @abstractmethod
 74    def get_component_name(self) -> str:
 75        """Name of the component.
 76
 77        Returns
 78        -------
 79        str
 80            The name of the component.
 81        """
 82        ...
 83
 84    @abstractmethod
 85    def get_input_item_count(self) -> int:
 86        """Count of a model's input variables.
 87
 88        Returns
 89        -------
 90        int
 91          The number of input variables.
 92        """
 93        ...
 94
 95    @abstractmethod
 96    def get_output_item_count(self) -> int:
 97        """Count of a model's output variables.
 98
 99        Returns
100        -------
101        int
102          The number of output variables.
103        """
104        ...
105
106    @abstractmethod
107    def get_input_var_names(self) -> tuple[str]:
108        """List of a model's input variables.
109
110        Input variable names must be CSDMS Standard Names, also known
111        as *long variable names*.
112
113        Returns
114        -------
115        list of str
116            The input variables for the model.
117
118        Notes
119        -----
120        Standard Names enable the CSDMS framework to determine whether
121        an input variable in one model is equivalent to, or compatible
122        with, an output variable in another model. This allows the
123        framework to automatically connect components.
124
125        Standard Names do not have to be used within the model.
126        """
127        ...
128
129    @abstractmethod
130    def get_output_var_names(self) -> tuple[str]:
131        """List of a model's output variables.
132
133        Output variable names must be CSDMS Standard Names, also known
134        as *long variable names*.
135
136        Returns
137        -------
138        list of str
139            The output variables for the model.
140        """
141        ...
142
143    @abstractmethod
144    def get_var_grid(self, name: str) -> int:
145        """Get grid identifier for the given variable.
146
147        Parameters
148        ----------
149        name : str
150            An input or output variable name, a CSDMS Standard Name.
151
152        Returns
153        -------
154        int
155          The grid identifier.
156        """
157        ...
158
159    @abstractmethod
160    def get_var_type(self, name: str) -> str:
161        """Get data type of the given variable.
162
163        Parameters
164        ----------
165        name : str
166            An input or output variable name, a CSDMS Standard Name.
167
168        Returns
169        -------
170        str
171            The Python variable type; e.g., ``str``, ``int``, ``float``.
172        """
173        ...
174
175    @abstractmethod
176    def get_var_units(self, name: str) -> str:
177        """Get units of the given variable.
178
179        Standard unit names, in lower case, should be used, such as
180        ``meters`` or ``seconds``. Standard abbreviations, like ``m`` for
181        meters, are also supported. For variables with compound units,
182        each unit name is separated by a single space, with exponents
183        other than 1 placed immediately after the name, as in ``m s-1``
184        for velocity, ``W m-2`` for an energy flux, or ``km2`` for an
185        area.
186
187        Parameters
188        ----------
189        name : str
190            An input or output variable name, a CSDMS Standard Name.
191
192        Returns
193        -------
194        str
195            The variable units.
196
197        Notes
198        -----
199        CSDMS uses the `UDUNITS`_ standard from Unidata.
200
201        .. _UDUNITS: http://www.unidata.ucar.edu/software/udunits
202        """
203        ...
204
205    @abstractmethod
206    def get_var_itemsize(self, name: str) -> int:
207        """Get memory use for each array element in bytes.
208
209        Parameters
210        ----------
211        name : str
212            An input or output variable name, a CSDMS Standard Name.
213
214        Returns
215        -------
216        int
217            Item size in bytes.
218        """
219        ...
220
221    @abstractmethod
222    def get_var_nbytes(self, name: str) -> int:
223        """Get size, in bytes, of the given variable.
224
225        Parameters
226        ----------
227        name : str
228            An input or output variable name, a CSDMS Standard Name.
229
230        Returns
231        -------
232        int
233            The size of the variable, counted in bytes.
234        """
235        ...
236
237    @abstractmethod
238    def get_var_location(self, name: str) -> str:
239        """Get the grid element type that the a given variable is defined on.
240
241        The grid topology can be composed of *nodes*, *edges*, and *faces*.
242
243        *node*
244            A point that has a coordinate pair or triplet: the most
245            basic element of the topology.
246
247        *edge*
248            A line or curve bounded by two *nodes*.
249
250        *face*
251            A plane or surface enclosed by a set of edges. In a 2D
252            horizontal application one may consider the word “polygon”,
253            but in the hierarchy of elements the word “face” is most common.
254
255        Parameters
256        ----------
257        name : str
258            An input or output variable name, a CSDMS Standard Name.
259
260        Returns
261        -------
262        str
263            The grid location on which the variable is defined. Must be one of
264            `"node"`, `"edge"`, or `"face"`.
265
266        Notes
267        -----
268        CSDMS uses the `ugrid conventions`_ to define unstructured grids.
269
270        .. _ugrid conventions: http://ugrid-conventions.github.io/ugrid-conventions
271        """
272        ...
273
274    @abstractmethod
275    def get_current_time(self) -> float:
276        """Return the current time of the model.
277
278        Returns
279        -------
280        float
281            The current model time.
282        """
283        ...
284
285    @abstractmethod
286    def get_start_time(self) -> float:
287        """Start time of the model.
288
289        Model times should be of type float.
290
291        Returns
292        -------
293        float
294            The model start time.
295        """
296        ...
297
298    @abstractmethod
299    def get_end_time(self) -> float:
300        """End time of the model.
301
302        Returns
303        -------
304        float
305            The maximum model time.
306        """
307        ...
308
309    @abstractmethod
310    def get_time_units(self) -> str:
311        """Time units of the model.
312
313        Returns
314        -------
315        str
316            The model time unit; e.g., `days` or `s`.
317
318        Notes
319        -----
320        CSDMS uses the UDUNITS standard from Unidata.
321        """
322        ...
323
324    @abstractmethod
325    def get_time_step(self) -> float:
326        """Return the current time step of the model.
327
328        The model time step should be of type float.
329
330        Returns
331        -------
332        float
333            The time step used in model.
334        """
335        ...
336
337    @abstractmethod
338    def get_value(self, name: str, dest: np.ndarray) -> np.ndarray:
339        """Get a copy of values of the given variable.
340
341        This is a getter for the model, used to access the model's
342        current state. It returns a *copy* of a model variable, with
343        the return type, size and rank dependent on the variable.
344
345        Parameters
346        ----------
347        name : str
348            An input or output variable name, a CSDMS Standard Name.
349        dest : ndarray
350            A numpy array into which to place the values.
351
352        Returns
353        -------
354        ndarray
355            The same numpy array that was passed as an input buffer.
356        """
357        ...
358
359    @abstractmethod
360    def get_value_ptr(self, name: str) -> np.ndarray:
361        """Get a reference to values of the given variable.
362
363        This is a getter for the model, used to access the model's
364        current state. It returns a reference to a model variable,
365        with the return type, size and rank dependent on the variable.
366
367        Parameters
368        ----------
369        name : str
370            An input or output variable name, a CSDMS Standard Name.
371
372        Returns
373        -------
374        array_like
375            A reference to a model variable.
376        """
377        ...
378
379    @abstractmethod
380    def get_value_at_indices(
381        self, name: str, dest: np.ndarray, inds: np.ndarray
382    ) -> np.ndarray:
383        """Get values at particular indices.
384
385        Parameters
386        ----------
387        name : str
388            An input or output variable name, a CSDMS Standard Name.
389        dest : ndarray
390            A numpy array into which to place the values.
391        inds : array_like
392            The indices into the variable array.
393
394        Returns
395        -------
396        array_like
397            Value of the model variable at the given location.
398        """
399        ...
400
401    @abstractmethod
402    def set_value(self, name: str, src: np.ndarray) -> None:
403        """Specify a new value for a model variable.
404
405        This is the setter for the model, used to change the model's
406        current state. It accepts, through *src*, a new value for a
407        model variable, with the type, size and rank of *src*
408        dependent on the variable.
409
410        Parameters
411        ----------
412        name : str
413            An input or output variable name, a CSDMS Standard Name.
414        src : array_like
415            The new value for the specified variable.
416        """
417        ...
418
419    @abstractmethod
420    def set_value_at_indices(
421        self, name: str, inds: np.ndarray, src: np.ndarray
422    ) -> None:
423        """Specify a new value for a model variable at particular indices.
424
425        Parameters
426        ----------
427        name : str
428            An input or output variable name, a CSDMS Standard Name.
429        inds : array_like
430            The indices into the variable array.
431        src : array_like
432            The new value for the specified variable.
433        """
434        ...
435
436    # Grid information
437    @abstractmethod
438    def get_grid_rank(self, grid: int) -> int:
439        """Get number of dimensions of the computational grid.
440
441        Parameters
442        ----------
443        grid : int
444            A grid identifier.
445
446        Returns
447        -------
448        int
449            Rank of the grid.
450        """
451        ...
452
453    @abstractmethod
454    def get_grid_size(self, grid: int) -> int:
455        """Get the total number of elements in the computational grid.
456
457        Parameters
458        ----------
459        grid : int
460            A grid identifier.
461
462        Returns
463        -------
464        int
465            Size of the grid.
466        """
467        ...
468
469    @abstractmethod
470    def get_grid_type(self, grid: int) -> str:
471        """Get the grid type as a string.
472
473        Parameters
474        ----------
475        grid : int
476            A grid identifier.
477
478        Returns
479        -------
480        str
481            Type of grid as a string.
482        """
483        ...
484
485    # Uniform rectilinear
486    @abstractmethod
487    def get_grid_shape(self, grid: int, shape: np.ndarray) -> np.ndarray:
488        """Get dimensions of the computational grid.
489
490        Parameters
491        ----------
492        grid : int
493            A grid identifier.
494        shape : ndarray of int, shape *(ndim,)*
495            A numpy array into which to place the shape of the grid.
496
497        Returns
498        -------
499        ndarray of int
500            The input numpy array that holds the grid's shape.
501        """
502        ...
503
504    @abstractmethod
505    def get_grid_spacing(self, grid: int, spacing: np.ndarray) -> np.ndarray:
506        """Get distance between nodes of the computational grid.
507
508        Parameters
509        ----------
510        grid : int
511            A grid identifier.
512        spacing : ndarray of float, shape *(ndim,)*
513            A numpy array to hold the spacing between grid rows and columns.
514
515        Returns
516        -------
517        ndarray of float
518            The input numpy array that holds the grid's spacing.
519        """
520        ...
521
522    @abstractmethod
523    def get_grid_origin(self, grid: int, origin: np.ndarray) -> np.ndarray:
524        """Get coordinates for the lower-left corner of the computational grid.
525
526        Parameters
527        ----------
528        grid : int
529            A grid identifier.
530        origin : ndarray of float, shape *(ndim,)*
531            A numpy array to hold the coordinates of the lower-left corner of
532            the grid.
533
534        Returns
535        -------
536        ndarray of float
537            The input numpy array that holds the coordinates of the grid's
538            lower-left corner.
539        """
540        ...
541
542    # Non-uniform rectilinear, curvilinear
543    @abstractmethod
544    def get_grid_x(self, grid: int, x: np.ndarray) -> np.ndarray:
545        """Get coordinates of grid nodes in the x direction.
546
547        Parameters
548        ----------
549        grid : int
550            A grid identifier.
551        x : ndarray of float, shape *(nrows,)*
552            A numpy array to hold the x-coordinates of the grid node columns.
553
554        Returns
555        -------
556        ndarray of float
557            The input numpy array that holds the grid's column x-coordinates.
558        """
559        ...
560
561    @abstractmethod
562    def get_grid_y(self, grid: int, y: np.ndarray) -> np.ndarray:
563        """Get coordinates of grid nodes in the y direction.
564
565        Parameters
566        ----------
567        grid : int
568            A grid identifier.
569        y : ndarray of float, shape *(ncols,)*
570            A numpy array to hold the y-coordinates of the grid node rows.
571
572        Returns
573        -------
574        ndarray of float
575            The input numpy array that holds the grid's row y-coordinates.
576        """
577        ...
578
579    @abstractmethod
580    def get_grid_z(self, grid: int, z: np.ndarray) -> np.ndarray:
581        """Get coordinates of grid nodes in the z direction.
582
583        Parameters
584        ----------
585        grid : int
586            A grid identifier.
587        z : ndarray of float, shape *(nlayers,)*
588            A numpy array to hold the z-coordinates of the grid nodes layers.
589
590        Returns
591        -------
592        ndarray of float
593            The input numpy array that holds the grid's layer z-coordinates.
594        """
595        ...
596
597    @abstractmethod
598    def get_grid_node_count(self, grid: int) -> int:
599        """Get the number of nodes in the grid.
600
601        Parameters
602        ----------
603        grid : int
604            A grid identifier.
605
606        Returns
607        -------
608        int
609            The total number of grid nodes.
610        """
611        ...
612
613    @abstractmethod
614    def get_grid_edge_count(self, grid: int) -> int:
615        """Get the number of edges in the grid.
616
617        Parameters
618        ----------
619        grid : int
620            A grid identifier.
621
622        Returns
623        -------
624        int
625            The total number of grid edges.
626        """
627        ...
628
629    @abstractmethod
630    def get_grid_face_count(self, grid: int) -> int:
631        """Get the number of faces in the grid.
632
633        Parameters
634        ----------
635        grid : int
636            A grid identifier.
637
638        Returns
639        -------
640        int
641            The total number of grid faces.
642        """
643        ...
644
645    @abstractmethod
646    def get_grid_edge_nodes(self, grid: int, edge_nodes: np.ndarray) -> np.ndarray:
647        """Get the edge-node connectivity.
648
649        Parameters
650        ----------
651        grid : int
652            A grid identifier.
653        edge_nodes : ndarray of int, shape *(2 x nnodes,)*
654            A numpy array to place the edge-node connectivity. For each edge,
655            connectivity is given as node at edge tail, followed by node at
656            edge head.
657
658        Returns
659        -------
660        ndarray of int
661            The input numpy array that holds the edge-node connectivity.
662        """
663        ...
664
665    @abstractmethod
666    def get_grid_face_edges(self, grid: int, face_edges: np.ndarray) -> np.ndarray:
667        """Get the face-edge connectivity.
668
669        Parameters
670        ----------
671        grid : int
672            A grid identifier.
673        face_edges : ndarray of int
674            A numpy array to place the face-edge connectivity.
675
676        Returns
677        -------
678        ndarray of int
679            The input numpy array that holds the face-edge connectivity.
680        """
681        ...
682
683    @abstractmethod
684    def get_grid_face_nodes(self, grid: int, face_nodes: np.ndarray) -> np.ndarray:
685        """Get the face-node connectivity.
686
687        Parameters
688        ----------
689        grid : int
690            A grid identifier.
691        face_nodes : ndarray of int
692            A numpy array to place the face-node connectivity. For each face,
693            the nodes (listed in a counter-clockwise direction) that form the
694            boundary of the face.
695
696        Returns
697        -------
698        ndarray of int
699            The input numpy array that holds the face-node connectivity.
700        """
701        ...
702
703    @abstractmethod
704    def get_grid_nodes_per_face(
705        self, grid: int, nodes_per_face: np.ndarray
706    ) -> np.ndarray:
707        """Get the number of nodes for each face.
708
709        Parameters
710        ----------
711        grid : int
712            A grid identifier.
713        nodes_per_face : ndarray of int, shape *(nfaces,)*
714            A numpy array to place the number of nodes per face.
715
716        Returns
717        -------
718        ndarray of int
719            The input numpy array that holds the number of nodes per face.
720        """
721        ...

Base class for implementations of a Python-based BMI.

@abstractmethod
def initialize(self, config_file: str) -> None:
16    @abstractmethod
17    def initialize(self, config_file: str) -> None:
18        """Perform startup tasks for the model.
19
20        Perform all tasks that take place before entering the model's time
21        loop, including opening files and initializing the model state. Model
22        inputs are read from a text-based configuration file, specified by
23        `config_file`.
24
25        Parameters
26        ----------
27        config_file : str, optional
28            The path to the model configuration file.
29
30        Notes
31        -----
32        Models should be refactored, if necessary, to use a
33        configuration file. CSDMS does not impose any constraint on
34        how configuration files are formatted, although YAML is
35        recommended. A template of a model's configuration file
36        with placeholder values is used by the BMI.
37        """
38        ...

Perform startup tasks for the model.

Perform all tasks that take place before entering the model's time loop, including opening files and initializing the model state. Model inputs are read from a text-based configuration file, specified by config_file.

Parameters

config_file : str, optional The path to the model configuration file.

Notes

Models should be refactored, if necessary, to use a configuration file. CSDMS does not impose any constraint on how configuration files are formatted, although YAML is recommended. A template of a model's configuration file with placeholder values is used by the BMI.

@abstractmethod
def update(self) -> None:
40    @abstractmethod
41    def update(self) -> None:
42        """Advance model state by one time step.
43
44        Perform all tasks that take place within one pass through the model's
45        time loop. This typically includes incrementing all of the model's
46        state variables. If the model's state variables don't change in time,
47        then they can be computed by the :func:`initialize` method and this
48        method can return with no action.
49        """
50        ...

Advance model state by one time step.

Perform all tasks that take place within one pass through the model's time loop. This typically includes incrementing all of the model's state variables. If the model's state variables don't change in time, then they can be computed by the initialize() method and this method can return with no action.

@abstractmethod
def update_until(self, time: float) -> None:
52    @abstractmethod
53    def update_until(self, time: float) -> None:
54        """Advance model state until the given time.
55
56        Parameters
57        ----------
58        time : float
59            A model time later than the current model time.
60        """
61        ...

Advance model state until the given time.

Parameters

time : float A model time later than the current model time.

@abstractmethod
def finalize(self) -> None:
63    @abstractmethod
64    def finalize(self) -> None:
65        """Perform tear-down tasks for the model.
66
67        Perform all tasks that take place after exiting the model's time
68        loop. This typically includes deallocating memory, closing files and
69        printing reports.
70        """
71        ...

Perform tear-down tasks for the model.

Perform all tasks that take place after exiting the model's time loop. This typically includes deallocating memory, closing files and printing reports.

@abstractmethod
def get_component_name(self) -> str:
73    @abstractmethod
74    def get_component_name(self) -> str:
75        """Name of the component.
76
77        Returns
78        -------
79        str
80            The name of the component.
81        """
82        ...

Name of the component.

Returns

str The name of the component.

@abstractmethod
def get_input_item_count(self) -> int:
84    @abstractmethod
85    def get_input_item_count(self) -> int:
86        """Count of a model's input variables.
87
88        Returns
89        -------
90        int
91          The number of input variables.
92        """
93        ...

Count of a model's input variables.

Returns

int The number of input variables.

@abstractmethod
def get_output_item_count(self) -> int:
 95    @abstractmethod
 96    def get_output_item_count(self) -> int:
 97        """Count of a model's output variables.
 98
 99        Returns
100        -------
101        int
102          The number of output variables.
103        """
104        ...

Count of a model's output variables.

Returns

int The number of output variables.

@abstractmethod
def get_input_var_names(self) -> tuple[str]:
106    @abstractmethod
107    def get_input_var_names(self) -> tuple[str]:
108        """List of a model's input variables.
109
110        Input variable names must be CSDMS Standard Names, also known
111        as *long variable names*.
112
113        Returns
114        -------
115        list of str
116            The input variables for the model.
117
118        Notes
119        -----
120        Standard Names enable the CSDMS framework to determine whether
121        an input variable in one model is equivalent to, or compatible
122        with, an output variable in another model. This allows the
123        framework to automatically connect components.
124
125        Standard Names do not have to be used within the model.
126        """
127        ...

List of a model's input variables.

Input variable names must be CSDMS Standard Names, also known as long variable names.

Returns

list of str The input variables for the model.

Notes

Standard Names enable the CSDMS framework to determine whether an input variable in one model is equivalent to, or compatible with, an output variable in another model. This allows the framework to automatically connect components.

Standard Names do not have to be used within the model.

@abstractmethod
def get_output_var_names(self) -> tuple[str]:
129    @abstractmethod
130    def get_output_var_names(self) -> tuple[str]:
131        """List of a model's output variables.
132
133        Output variable names must be CSDMS Standard Names, also known
134        as *long variable names*.
135
136        Returns
137        -------
138        list of str
139            The output variables for the model.
140        """
141        ...

List of a model's output variables.

Output variable names must be CSDMS Standard Names, also known as long variable names.

Returns

list of str The output variables for the model.

@abstractmethod
def get_var_grid(self, name: str) -> int:
143    @abstractmethod
144    def get_var_grid(self, name: str) -> int:
145        """Get grid identifier for the given variable.
146
147        Parameters
148        ----------
149        name : str
150            An input or output variable name, a CSDMS Standard Name.
151
152        Returns
153        -------
154        int
155          The grid identifier.
156        """
157        ...

Get grid identifier for the given variable.

Parameters

name : str An input or output variable name, a CSDMS Standard Name.

Returns

int The grid identifier.

@abstractmethod
def get_var_type(self, name: str) -> str:
159    @abstractmethod
160    def get_var_type(self, name: str) -> str:
161        """Get data type of the given variable.
162
163        Parameters
164        ----------
165        name : str
166            An input or output variable name, a CSDMS Standard Name.
167
168        Returns
169        -------
170        str
171            The Python variable type; e.g., ``str``, ``int``, ``float``.
172        """
173        ...

Get data type of the given variable.

Parameters

name : str An input or output variable name, a CSDMS Standard Name.

Returns

str The Python variable type; e.g., str, int, float.

@abstractmethod
def get_var_units(self, name: str) -> str:
175    @abstractmethod
176    def get_var_units(self, name: str) -> str:
177        """Get units of the given variable.
178
179        Standard unit names, in lower case, should be used, such as
180        ``meters`` or ``seconds``. Standard abbreviations, like ``m`` for
181        meters, are also supported. For variables with compound units,
182        each unit name is separated by a single space, with exponents
183        other than 1 placed immediately after the name, as in ``m s-1``
184        for velocity, ``W m-2`` for an energy flux, or ``km2`` for an
185        area.
186
187        Parameters
188        ----------
189        name : str
190            An input or output variable name, a CSDMS Standard Name.
191
192        Returns
193        -------
194        str
195            The variable units.
196
197        Notes
198        -----
199        CSDMS uses the `UDUNITS`_ standard from Unidata.
200
201        .. _UDUNITS: http://www.unidata.ucar.edu/software/udunits
202        """
203        ...

Get units of the given variable.

Standard unit names, in lower case, should be used, such as meters or seconds. Standard abbreviations, like m for meters, are also supported. For variables with compound units, each unit name is separated by a single space, with exponents other than 1 placed immediately after the name, as in m s-1 for velocity, W m-2 for an energy flux, or km2 for an area.

Parameters

name : str An input or output variable name, a CSDMS Standard Name.

Returns

str The variable units.

Notes

CSDMS uses the UDUNITS standard from Unidata.

@abstractmethod
def get_var_itemsize(self, name: str) -> int:
205    @abstractmethod
206    def get_var_itemsize(self, name: str) -> int:
207        """Get memory use for each array element in bytes.
208
209        Parameters
210        ----------
211        name : str
212            An input or output variable name, a CSDMS Standard Name.
213
214        Returns
215        -------
216        int
217            Item size in bytes.
218        """
219        ...

Get memory use for each array element in bytes.

Parameters

name : str An input or output variable name, a CSDMS Standard Name.

Returns

int Item size in bytes.

@abstractmethod
def get_var_nbytes(self, name: str) -> int:
221    @abstractmethod
222    def get_var_nbytes(self, name: str) -> int:
223        """Get size, in bytes, of the given variable.
224
225        Parameters
226        ----------
227        name : str
228            An input or output variable name, a CSDMS Standard Name.
229
230        Returns
231        -------
232        int
233            The size of the variable, counted in bytes.
234        """
235        ...

Get size, in bytes, of the given variable.

Parameters

name : str An input or output variable name, a CSDMS Standard Name.

Returns

int The size of the variable, counted in bytes.

@abstractmethod
def get_var_location(self, name: str) -> str:
237    @abstractmethod
238    def get_var_location(self, name: str) -> str:
239        """Get the grid element type that the a given variable is defined on.
240
241        The grid topology can be composed of *nodes*, *edges*, and *faces*.
242
243        *node*
244            A point that has a coordinate pair or triplet: the most
245            basic element of the topology.
246
247        *edge*
248            A line or curve bounded by two *nodes*.
249
250        *face*
251            A plane or surface enclosed by a set of edges. In a 2D
252            horizontal application one may consider the word “polygon”,
253            but in the hierarchy of elements the word “face” is most common.
254
255        Parameters
256        ----------
257        name : str
258            An input or output variable name, a CSDMS Standard Name.
259
260        Returns
261        -------
262        str
263            The grid location on which the variable is defined. Must be one of
264            `"node"`, `"edge"`, or `"face"`.
265
266        Notes
267        -----
268        CSDMS uses the `ugrid conventions`_ to define unstructured grids.
269
270        .. _ugrid conventions: http://ugrid-conventions.github.io/ugrid-conventions
271        """
272        ...

Get the grid element type that the a given variable is defined on.

The grid topology can be composed of nodes, edges, and faces.

node A point that has a coordinate pair or triplet: the most basic element of the topology.

edge A line or curve bounded by two nodes.

face A plane or surface enclosed by a set of edges. In a 2D horizontal application one may consider the word “polygon”, but in the hierarchy of elements the word “face” is most common.

Parameters

name : str An input or output variable name, a CSDMS Standard Name.

Returns

str The grid location on which the variable is defined. Must be one of "node", "edge", or "face".

Notes

CSDMS uses the ugrid conventions to define unstructured grids.

@abstractmethod
def get_current_time(self) -> float:
274    @abstractmethod
275    def get_current_time(self) -> float:
276        """Return the current time of the model.
277
278        Returns
279        -------
280        float
281            The current model time.
282        """
283        ...

Return the current time of the model.

Returns

float The current model time.

@abstractmethod
def get_start_time(self) -> float:
285    @abstractmethod
286    def get_start_time(self) -> float:
287        """Start time of the model.
288
289        Model times should be of type float.
290
291        Returns
292        -------
293        float
294            The model start time.
295        """
296        ...

Start time of the model.

Model times should be of type float.

Returns

float The model start time.

@abstractmethod
def get_end_time(self) -> float:
298    @abstractmethod
299    def get_end_time(self) -> float:
300        """End time of the model.
301
302        Returns
303        -------
304        float
305            The maximum model time.
306        """
307        ...

End time of the model.

Returns

float The maximum model time.

@abstractmethod
def get_time_units(self) -> str:
309    @abstractmethod
310    def get_time_units(self) -> str:
311        """Time units of the model.
312
313        Returns
314        -------
315        str
316            The model time unit; e.g., `days` or `s`.
317
318        Notes
319        -----
320        CSDMS uses the UDUNITS standard from Unidata.
321        """
322        ...

Time units of the model.

Returns

str The model time unit; e.g., days or s.

Notes

CSDMS uses the UDUNITS standard from Unidata.

@abstractmethod
def get_time_step(self) -> float:
324    @abstractmethod
325    def get_time_step(self) -> float:
326        """Return the current time step of the model.
327
328        The model time step should be of type float.
329
330        Returns
331        -------
332        float
333            The time step used in model.
334        """
335        ...

Return the current time step of the model.

The model time step should be of type float.

Returns

float The time step used in model.

@abstractmethod
def get_value(self, name: str, dest: numpy.ndarray) -> numpy.ndarray:
337    @abstractmethod
338    def get_value(self, name: str, dest: np.ndarray) -> np.ndarray:
339        """Get a copy of values of the given variable.
340
341        This is a getter for the model, used to access the model's
342        current state. It returns a *copy* of a model variable, with
343        the return type, size and rank dependent on the variable.
344
345        Parameters
346        ----------
347        name : str
348            An input or output variable name, a CSDMS Standard Name.
349        dest : ndarray
350            A numpy array into which to place the values.
351
352        Returns
353        -------
354        ndarray
355            The same numpy array that was passed as an input buffer.
356        """
357        ...

Get a copy of values of the given variable.

This is a getter for the model, used to access the model's current state. It returns a copy of a model variable, with the return type, size and rank dependent on the variable.

Parameters

name : str An input or output variable name, a CSDMS Standard Name. dest : ndarray A numpy array into which to place the values.

Returns

ndarray The same numpy array that was passed as an input buffer.

@abstractmethod
def get_value_ptr(self, name: str) -> numpy.ndarray:
359    @abstractmethod
360    def get_value_ptr(self, name: str) -> np.ndarray:
361        """Get a reference to values of the given variable.
362
363        This is a getter for the model, used to access the model's
364        current state. It returns a reference to a model variable,
365        with the return type, size and rank dependent on the variable.
366
367        Parameters
368        ----------
369        name : str
370            An input or output variable name, a CSDMS Standard Name.
371
372        Returns
373        -------
374        array_like
375            A reference to a model variable.
376        """
377        ...

Get a reference to values of the given variable.

This is a getter for the model, used to access the model's current state. It returns a reference to a model variable, with the return type, size and rank dependent on the variable.

Parameters

name : str An input or output variable name, a CSDMS Standard Name.

Returns

array_like A reference to a model variable.

@abstractmethod
def get_value_at_indices( self, name: str, dest: numpy.ndarray, inds: numpy.ndarray) -> numpy.ndarray:
379    @abstractmethod
380    def get_value_at_indices(
381        self, name: str, dest: np.ndarray, inds: np.ndarray
382    ) -> np.ndarray:
383        """Get values at particular indices.
384
385        Parameters
386        ----------
387        name : str
388            An input or output variable name, a CSDMS Standard Name.
389        dest : ndarray
390            A numpy array into which to place the values.
391        inds : array_like
392            The indices into the variable array.
393
394        Returns
395        -------
396        array_like
397            Value of the model variable at the given location.
398        """
399        ...

Get values at particular indices.

Parameters

name : str An input or output variable name, a CSDMS Standard Name. dest : ndarray A numpy array into which to place the values. inds : array_like The indices into the variable array.

Returns

array_like Value of the model variable at the given location.

@abstractmethod
def set_value(self, name: str, src: numpy.ndarray) -> None:
401    @abstractmethod
402    def set_value(self, name: str, src: np.ndarray) -> None:
403        """Specify a new value for a model variable.
404
405        This is the setter for the model, used to change the model's
406        current state. It accepts, through *src*, a new value for a
407        model variable, with the type, size and rank of *src*
408        dependent on the variable.
409
410        Parameters
411        ----------
412        name : str
413            An input or output variable name, a CSDMS Standard Name.
414        src : array_like
415            The new value for the specified variable.
416        """
417        ...

Specify a new value for a model variable.

This is the setter for the model, used to change the model's current state. It accepts, through src, a new value for a model variable, with the type, size and rank of src dependent on the variable.

Parameters

name : str An input or output variable name, a CSDMS Standard Name. src : array_like The new value for the specified variable.

@abstractmethod
def set_value_at_indices(self, name: str, inds: numpy.ndarray, src: numpy.ndarray) -> None:
419    @abstractmethod
420    def set_value_at_indices(
421        self, name: str, inds: np.ndarray, src: np.ndarray
422    ) -> None:
423        """Specify a new value for a model variable at particular indices.
424
425        Parameters
426        ----------
427        name : str
428            An input or output variable name, a CSDMS Standard Name.
429        inds : array_like
430            The indices into the variable array.
431        src : array_like
432            The new value for the specified variable.
433        """
434        ...

Specify a new value for a model variable at particular indices.

Parameters

name : str An input or output variable name, a CSDMS Standard Name. inds : array_like The indices into the variable array. src : array_like The new value for the specified variable.

@abstractmethod
def get_grid_rank(self, grid: int) -> int:
437    @abstractmethod
438    def get_grid_rank(self, grid: int) -> int:
439        """Get number of dimensions of the computational grid.
440
441        Parameters
442        ----------
443        grid : int
444            A grid identifier.
445
446        Returns
447        -------
448        int
449            Rank of the grid.
450        """
451        ...

Get number of dimensions of the computational grid.

Parameters

grid : int A grid identifier.

Returns

int Rank of the grid.

@abstractmethod
def get_grid_size(self, grid: int) -> int:
453    @abstractmethod
454    def get_grid_size(self, grid: int) -> int:
455        """Get the total number of elements in the computational grid.
456
457        Parameters
458        ----------
459        grid : int
460            A grid identifier.
461
462        Returns
463        -------
464        int
465            Size of the grid.
466        """
467        ...

Get the total number of elements in the computational grid.

Parameters

grid : int A grid identifier.

Returns

int Size of the grid.

@abstractmethod
def get_grid_type(self, grid: int) -> str:
469    @abstractmethod
470    def get_grid_type(self, grid: int) -> str:
471        """Get the grid type as a string.
472
473        Parameters
474        ----------
475        grid : int
476            A grid identifier.
477
478        Returns
479        -------
480        str
481            Type of grid as a string.
482        """
483        ...

Get the grid type as a string.

Parameters

grid : int A grid identifier.

Returns

str Type of grid as a string.

@abstractmethod
def get_grid_shape(self, grid: int, shape: numpy.ndarray) -> numpy.ndarray:
486    @abstractmethod
487    def get_grid_shape(self, grid: int, shape: np.ndarray) -> np.ndarray:
488        """Get dimensions of the computational grid.
489
490        Parameters
491        ----------
492        grid : int
493            A grid identifier.
494        shape : ndarray of int, shape *(ndim,)*
495            A numpy array into which to place the shape of the grid.
496
497        Returns
498        -------
499        ndarray of int
500            The input numpy array that holds the grid's shape.
501        """
502        ...

Get dimensions of the computational grid.

Parameters

grid : int A grid identifier. shape : ndarray of int, shape (ndim,) A numpy array into which to place the shape of the grid.

Returns

ndarray of int The input numpy array that holds the grid's shape.

@abstractmethod
def get_grid_spacing(self, grid: int, spacing: numpy.ndarray) -> numpy.ndarray:
504    @abstractmethod
505    def get_grid_spacing(self, grid: int, spacing: np.ndarray) -> np.ndarray:
506        """Get distance between nodes of the computational grid.
507
508        Parameters
509        ----------
510        grid : int
511            A grid identifier.
512        spacing : ndarray of float, shape *(ndim,)*
513            A numpy array to hold the spacing between grid rows and columns.
514
515        Returns
516        -------
517        ndarray of float
518            The input numpy array that holds the grid's spacing.
519        """
520        ...

Get distance between nodes of the computational grid.

Parameters

grid : int A grid identifier. spacing : ndarray of float, shape (ndim,) A numpy array to hold the spacing between grid rows and columns.

Returns

ndarray of float The input numpy array that holds the grid's spacing.

@abstractmethod
def get_grid_origin(self, grid: int, origin: numpy.ndarray) -> numpy.ndarray:
522    @abstractmethod
523    def get_grid_origin(self, grid: int, origin: np.ndarray) -> np.ndarray:
524        """Get coordinates for the lower-left corner of the computational grid.
525
526        Parameters
527        ----------
528        grid : int
529            A grid identifier.
530        origin : ndarray of float, shape *(ndim,)*
531            A numpy array to hold the coordinates of the lower-left corner of
532            the grid.
533
534        Returns
535        -------
536        ndarray of float
537            The input numpy array that holds the coordinates of the grid's
538            lower-left corner.
539        """
540        ...

Get coordinates for the lower-left corner of the computational grid.

Parameters

grid : int A grid identifier. origin : ndarray of float, shape (ndim,) A numpy array to hold the coordinates of the lower-left corner of the grid.

Returns

ndarray of float The input numpy array that holds the coordinates of the grid's lower-left corner.

@abstractmethod
def get_grid_x(self, grid: int, x: numpy.ndarray) -> numpy.ndarray:
543    @abstractmethod
544    def get_grid_x(self, grid: int, x: np.ndarray) -> np.ndarray:
545        """Get coordinates of grid nodes in the x direction.
546
547        Parameters
548        ----------
549        grid : int
550            A grid identifier.
551        x : ndarray of float, shape *(nrows,)*
552            A numpy array to hold the x-coordinates of the grid node columns.
553
554        Returns
555        -------
556        ndarray of float
557            The input numpy array that holds the grid's column x-coordinates.
558        """
559        ...

Get coordinates of grid nodes in the x direction.

Parameters

grid : int A grid identifier. x : ndarray of float, shape (nrows,) A numpy array to hold the x-coordinates of the grid node columns.

Returns

ndarray of float The input numpy array that holds the grid's column x-coordinates.

@abstractmethod
def get_grid_y(self, grid: int, y: numpy.ndarray) -> numpy.ndarray:
561    @abstractmethod
562    def get_grid_y(self, grid: int, y: np.ndarray) -> np.ndarray:
563        """Get coordinates of grid nodes in the y direction.
564
565        Parameters
566        ----------
567        grid : int
568            A grid identifier.
569        y : ndarray of float, shape *(ncols,)*
570            A numpy array to hold the y-coordinates of the grid node rows.
571
572        Returns
573        -------
574        ndarray of float
575            The input numpy array that holds the grid's row y-coordinates.
576        """
577        ...

Get coordinates of grid nodes in the y direction.

Parameters

grid : int A grid identifier. y : ndarray of float, shape (ncols,) A numpy array to hold the y-coordinates of the grid node rows.

Returns

ndarray of float The input numpy array that holds the grid's row y-coordinates.

@abstractmethod
def get_grid_z(self, grid: int, z: numpy.ndarray) -> numpy.ndarray:
579    @abstractmethod
580    def get_grid_z(self, grid: int, z: np.ndarray) -> np.ndarray:
581        """Get coordinates of grid nodes in the z direction.
582
583        Parameters
584        ----------
585        grid : int
586            A grid identifier.
587        z : ndarray of float, shape *(nlayers,)*
588            A numpy array to hold the z-coordinates of the grid nodes layers.
589
590        Returns
591        -------
592        ndarray of float
593            The input numpy array that holds the grid's layer z-coordinates.
594        """
595        ...

Get coordinates of grid nodes in the z direction.

Parameters

grid : int A grid identifier. z : ndarray of float, shape (nlayers,) A numpy array to hold the z-coordinates of the grid nodes layers.

Returns

ndarray of float The input numpy array that holds the grid's layer z-coordinates.

@abstractmethod
def get_grid_node_count(self, grid: int) -> int:
597    @abstractmethod
598    def get_grid_node_count(self, grid: int) -> int:
599        """Get the number of nodes in the grid.
600
601        Parameters
602        ----------
603        grid : int
604            A grid identifier.
605
606        Returns
607        -------
608        int
609            The total number of grid nodes.
610        """
611        ...

Get the number of nodes in the grid.

Parameters

grid : int A grid identifier.

Returns

int The total number of grid nodes.

@abstractmethod
def get_grid_edge_count(self, grid: int) -> int:
613    @abstractmethod
614    def get_grid_edge_count(self, grid: int) -> int:
615        """Get the number of edges in the grid.
616
617        Parameters
618        ----------
619        grid : int
620            A grid identifier.
621
622        Returns
623        -------
624        int
625            The total number of grid edges.
626        """
627        ...

Get the number of edges in the grid.

Parameters

grid : int A grid identifier.

Returns

int The total number of grid edges.

@abstractmethod
def get_grid_face_count(self, grid: int) -> int:
629    @abstractmethod
630    def get_grid_face_count(self, grid: int) -> int:
631        """Get the number of faces in the grid.
632
633        Parameters
634        ----------
635        grid : int
636            A grid identifier.
637
638        Returns
639        -------
640        int
641            The total number of grid faces.
642        """
643        ...

Get the number of faces in the grid.

Parameters

grid : int A grid identifier.

Returns

int The total number of grid faces.

@abstractmethod
def get_grid_edge_nodes(self, grid: int, edge_nodes: numpy.ndarray) -> numpy.ndarray:
645    @abstractmethod
646    def get_grid_edge_nodes(self, grid: int, edge_nodes: np.ndarray) -> np.ndarray:
647        """Get the edge-node connectivity.
648
649        Parameters
650        ----------
651        grid : int
652            A grid identifier.
653        edge_nodes : ndarray of int, shape *(2 x nnodes,)*
654            A numpy array to place the edge-node connectivity. For each edge,
655            connectivity is given as node at edge tail, followed by node at
656            edge head.
657
658        Returns
659        -------
660        ndarray of int
661            The input numpy array that holds the edge-node connectivity.
662        """
663        ...

Get the edge-node connectivity.

Parameters

grid : int A grid identifier. edge_nodes : ndarray of int, shape (2 x nnodes,) A numpy array to place the edge-node connectivity. For each edge, connectivity is given as node at edge tail, followed by node at edge head.

Returns

ndarray of int The input numpy array that holds the edge-node connectivity.

@abstractmethod
def get_grid_face_edges(self, grid: int, face_edges: numpy.ndarray) -> numpy.ndarray:
665    @abstractmethod
666    def get_grid_face_edges(self, grid: int, face_edges: np.ndarray) -> np.ndarray:
667        """Get the face-edge connectivity.
668
669        Parameters
670        ----------
671        grid : int
672            A grid identifier.
673        face_edges : ndarray of int
674            A numpy array to place the face-edge connectivity.
675
676        Returns
677        -------
678        ndarray of int
679            The input numpy array that holds the face-edge connectivity.
680        """
681        ...

Get the face-edge connectivity.

Parameters

grid : int A grid identifier. face_edges : ndarray of int A numpy array to place the face-edge connectivity.

Returns

ndarray of int The input numpy array that holds the face-edge connectivity.

@abstractmethod
def get_grid_face_nodes(self, grid: int, face_nodes: numpy.ndarray) -> numpy.ndarray:
683    @abstractmethod
684    def get_grid_face_nodes(self, grid: int, face_nodes: np.ndarray) -> np.ndarray:
685        """Get the face-node connectivity.
686
687        Parameters
688        ----------
689        grid : int
690            A grid identifier.
691        face_nodes : ndarray of int
692            A numpy array to place the face-node connectivity. For each face,
693            the nodes (listed in a counter-clockwise direction) that form the
694            boundary of the face.
695
696        Returns
697        -------
698        ndarray of int
699            The input numpy array that holds the face-node connectivity.
700        """
701        ...

Get the face-node connectivity.

Parameters

grid : int A grid identifier. face_nodes : ndarray of int A numpy array to place the face-node connectivity. For each face, the nodes (listed in a counter-clockwise direction) that form the boundary of the face.

Returns

ndarray of int The input numpy array that holds the face-node connectivity.

@abstractmethod
def get_grid_nodes_per_face(self, grid: int, nodes_per_face: numpy.ndarray) -> numpy.ndarray:
703    @abstractmethod
704    def get_grid_nodes_per_face(
705        self, grid: int, nodes_per_face: np.ndarray
706    ) -> np.ndarray:
707        """Get the number of nodes for each face.
708
709        Parameters
710        ----------
711        grid : int
712            A grid identifier.
713        nodes_per_face : ndarray of int, shape *(nfaces,)*
714            A numpy array to place the number of nodes per face.
715
716        Returns
717        -------
718        ndarray of int
719            The input numpy array that holds the number of nodes per face.
720        """
721        ...

Get the number of nodes for each face.

Parameters

grid : int A grid identifier. nodes_per_face : ndarray of int, shape (nfaces,) A numpy array to place the number of nodes per face.

Returns

ndarray of int The input numpy array that holds the number of nodes per face.

class Xmi(xmipy.Bmi):
  7class Xmi(Bmi):
  8    """
  9    This class extends the CSDMS Basic Model Interface
 10
 11    The extension to the BMI is twofold:
 12
 13    - the model's outer convergence loop is exposed to facilitate coupling at
 14      this level
 15
 16    - a model can have sub-components which share the time stepping but have
 17      their own convergence loop
 18
 19    Since it only extends the BMI, models implementing
 20    the XMI are compatible with BMI
 21
 22    """
 23
 24    @abstractmethod
 25    def prepare_time_step(self, dt: float) -> None:
 26        """Prepare a single time step.
 27
 28        Read data from input files and calculate the current time step length
 29        and the simulation time at the end of the current time step.
 30
 31        Parameters
 32        ----------
 33        dt : float
 34            Model time step length for the current time step.
 35        """
 36        ...
 37
 38    @abstractmethod
 39    def do_time_step(self) -> None:
 40        """Perform a single time step.
 41
 42        Build and solve a time step to completion. This method encapsulates
 43        the prepare_solve, solve, and finalize_solve methods.
 44        """
 45        ...
 46
 47    @abstractmethod
 48    def finalize_time_step(self) -> None:
 49        """Finalize the time step.
 50
 51        Write messages and output after model convergence has
 52        been achieved.
 53        """
 54        ...
 55
 56    @abstractmethod
 57    def get_subcomponent_count(self) -> int:
 58        """Get the number of components in the simulation.
 59
 60        For most applications, this number will be equal to 1.
 61
 62        Returns
 63        -------
 64        int
 65          The number of components in the simulation.
 66        """
 67        ...
 68
 69    @abstractmethod
 70    def prepare_solve(self, component_id: int) -> None:
 71        """Prepare for solving the system of equations.
 72
 73        This preparation mostly consists of advancing model states.
 74
 75        Parameters
 76        ----------
 77        component_id : int
 78            Component id number.
 79        """
 80        ...
 81
 82    @abstractmethod
 83    def solve(self, component_id: int) -> bool:
 84        """Build and solve the linear system of equations.
 85
 86        Formulate the system of equations for this outer (Picard) iteration and
 87        solve. New data used when formulating the system of equations can be
 88        injected prior to solve().  Before calling this, a matching call to
 89        prepare_solve() should be performed.
 90
 91        Parameters
 92        ----------
 93        component_id : int
 94            Component id number.
 95
 96        Returns
 97        -------
 98        bool
 99          Boolean indicating if convergence has been achieved.
100
101        """
102        ...
103
104    @abstractmethod
105    def finalize_solve(self, component_id: int) -> None:
106        """Finalize the model solve for the current time step.
107
108        Finalize model variables prior to calling finalize_time_step(). This
109        method should always be called after calls to prepare_solve() and
110        solve()
111
112        Parameters
113        ----------
114        component_id : int
115            Component id number.
116
117        """
118        ...
119
120    @abstractmethod
121    def get_version(self) -> str:
122        """Get the version of the kernel."""
123        ...
124
125    @abstractmethod
126    def report_timing_totals(self) -> float:
127        """Logs and returns total time spent
128
129        Returns
130        -------
131        float
132            Total time spent
133
134        Raises
135        ------
136        TimerError
137            Raised if timing is not activated
138        """
139        ...
140
141    @abstractmethod
142    def get_constant_int(self, name: str) -> int:
143        """Get a constant integer
144
145        Parameters
146        ----------
147        name : str
148            Name of the constant
149
150        Returns
151        -------
152        int
153            Constant to be returned
154        """
155        ...
156
157    @abstractmethod
158    def set_int(self, name: str, value: int) -> None:
159        """Set integer
160
161        Parameters
162        ----------
163        name : str
164            Integer to be set
165        value : int
166            Value to set the integer to
167        """
168        ...
169
170    @abstractmethod
171    def get_var_address(
172        self, var_name: str, component_name: str, subcomponent_name: str = ""
173    ) -> str:
174        """Get the address of a given variable
175
176        Parameters
177        ----------
178        var_name : str
179            The variable name
180        component_name : str
181            The name of the component
182        subcomponent_name : str, optional
183            If applicable the name of the subcomponent, by default ""
184
185        Returns
186        -------
187        str
188            The variable address
189        """
190        ...

This class extends the CSDMS Basic Model Interface

The extension to the BMI is twofold:

  • the model's outer convergence loop is exposed to facilitate coupling at this level

  • a model can have sub-components which share the time stepping but have their own convergence loop

Since it only extends the BMI, models implementing the XMI are compatible with BMI

@abstractmethod
def prepare_time_step(self, dt: float) -> None:
24    @abstractmethod
25    def prepare_time_step(self, dt: float) -> None:
26        """Prepare a single time step.
27
28        Read data from input files and calculate the current time step length
29        and the simulation time at the end of the current time step.
30
31        Parameters
32        ----------
33        dt : float
34            Model time step length for the current time step.
35        """
36        ...

Prepare a single time step.

Read data from input files and calculate the current time step length and the simulation time at the end of the current time step.

Parameters

dt : float Model time step length for the current time step.

@abstractmethod
def do_time_step(self) -> None:
38    @abstractmethod
39    def do_time_step(self) -> None:
40        """Perform a single time step.
41
42        Build and solve a time step to completion. This method encapsulates
43        the prepare_solve, solve, and finalize_solve methods.
44        """
45        ...

Perform a single time step.

Build and solve a time step to completion. This method encapsulates the prepare_solve, solve, and finalize_solve methods.

@abstractmethod
def finalize_time_step(self) -> None:
47    @abstractmethod
48    def finalize_time_step(self) -> None:
49        """Finalize the time step.
50
51        Write messages and output after model convergence has
52        been achieved.
53        """
54        ...

Finalize the time step.

Write messages and output after model convergence has been achieved.

@abstractmethod
def get_subcomponent_count(self) -> int:
56    @abstractmethod
57    def get_subcomponent_count(self) -> int:
58        """Get the number of components in the simulation.
59
60        For most applications, this number will be equal to 1.
61
62        Returns
63        -------
64        int
65          The number of components in the simulation.
66        """
67        ...

Get the number of components in the simulation.

For most applications, this number will be equal to 1.

Returns

int The number of components in the simulation.

@abstractmethod
def prepare_solve(self, component_id: int) -> None:
69    @abstractmethod
70    def prepare_solve(self, component_id: int) -> None:
71        """Prepare for solving the system of equations.
72
73        This preparation mostly consists of advancing model states.
74
75        Parameters
76        ----------
77        component_id : int
78            Component id number.
79        """
80        ...

Prepare for solving the system of equations.

This preparation mostly consists of advancing model states.

Parameters

component_id : int Component id number.

@abstractmethod
def solve(self, component_id: int) -> bool:
 82    @abstractmethod
 83    def solve(self, component_id: int) -> bool:
 84        """Build and solve the linear system of equations.
 85
 86        Formulate the system of equations for this outer (Picard) iteration and
 87        solve. New data used when formulating the system of equations can be
 88        injected prior to solve().  Before calling this, a matching call to
 89        prepare_solve() should be performed.
 90
 91        Parameters
 92        ----------
 93        component_id : int
 94            Component id number.
 95
 96        Returns
 97        -------
 98        bool
 99          Boolean indicating if convergence has been achieved.
100
101        """
102        ...

Build and solve the linear system of equations.

Formulate the system of equations for this outer (Picard) iteration and solve. New data used when formulating the system of equations can be injected prior to solve(). Before calling this, a matching call to prepare_solve() should be performed.

Parameters

component_id : int Component id number.

Returns

bool Boolean indicating if convergence has been achieved.

@abstractmethod
def finalize_solve(self, component_id: int) -> None:
104    @abstractmethod
105    def finalize_solve(self, component_id: int) -> None:
106        """Finalize the model solve for the current time step.
107
108        Finalize model variables prior to calling finalize_time_step(). This
109        method should always be called after calls to prepare_solve() and
110        solve()
111
112        Parameters
113        ----------
114        component_id : int
115            Component id number.
116
117        """
118        ...

Finalize the model solve for the current time step.

Finalize model variables prior to calling finalize_time_step(). This method should always be called after calls to prepare_solve() and solve()

Parameters

component_id : int Component id number.

@abstractmethod
def get_version(self) -> str:
120    @abstractmethod
121    def get_version(self) -> str:
122        """Get the version of the kernel."""
123        ...

Get the version of the kernel.

@abstractmethod
def report_timing_totals(self) -> float:
125    @abstractmethod
126    def report_timing_totals(self) -> float:
127        """Logs and returns total time spent
128
129        Returns
130        -------
131        float
132            Total time spent
133
134        Raises
135        ------
136        TimerError
137            Raised if timing is not activated
138        """
139        ...

Logs and returns total time spent

Returns

float Total time spent

Raises

TimerError Raised if timing is not activated

@abstractmethod
def get_constant_int(self, name: str) -> int:
141    @abstractmethod
142    def get_constant_int(self, name: str) -> int:
143        """Get a constant integer
144
145        Parameters
146        ----------
147        name : str
148            Name of the constant
149
150        Returns
151        -------
152        int
153            Constant to be returned
154        """
155        ...

Get a constant integer

Parameters

name : str Name of the constant

Returns

int Constant to be returned

@abstractmethod
def set_int(self, name: str, value: int) -> None:
157    @abstractmethod
158    def set_int(self, name: str, value: int) -> None:
159        """Set integer
160
161        Parameters
162        ----------
163        name : str
164            Integer to be set
165        value : int
166            Value to set the integer to
167        """
168        ...

Set integer

Parameters

name : str Integer to be set value : int Value to set the integer to

@abstractmethod
def get_var_address( self, var_name: str, component_name: str, subcomponent_name: str = '') -> str:
170    @abstractmethod
171    def get_var_address(
172        self, var_name: str, component_name: str, subcomponent_name: str = ""
173    ) -> str:
174        """Get the address of a given variable
175
176        Parameters
177        ----------
178        var_name : str
179            The variable name
180        component_name : str
181            The name of the component
182        subcomponent_name : str, optional
183            If applicable the name of the subcomponent, by default ""
184
185        Returns
186        -------
187        str
188            The variable address
189        """
190        ...

Get the address of a given variable

Parameters

var_name : str The variable name component_name : str The name of the component subcomponent_name : str, optional If applicable the name of the subcomponent, by default ""

Returns

str The variable address

class XmiWrapper(xmipy.Xmi):
 43class XmiWrapper(Xmi):
 44    """The implementation of the XMI"""
 45
 46    def __init__(
 47        self,
 48        lib_path: Union[str, PathLike[Any]],
 49        lib_dependency: Union[str, PathLike[Any], None] = None,
 50        working_directory: Union[str, PathLike[Any], None] = None,
 51        timing: bool = False,
 52        logger_level: Union[str, int] = 0,
 53    ):
 54        """
 55        Constructor of `XmiWrapper`
 56
 57        Next to wrapping C functions of a library exposing XMI,
 58        it also adds timing and logging functionality.
 59        An example for logging can be seen below.
 60
 61        ```
 62        In [1]: from xmipy import XmiWrapper
 63
 64        In [2]: mf6 = XmiWrapper("/path/to/libmf6.so", working_directory="/path/to/sim", logger_level="DEBUG")
 65
 66        In [3]: mf6.initialize()
 67        DEBUG:libmf6.so: execute function: initialize(b'') returned 0
 68
 69        In [4]: mf6.get_start_time()
 70        DEBUG:libmf6.so: execute function: get_start_time(&c_double(0.0)) returned 0
 71        Out[4]: 0.0
 72
 73        In [5]: mf6.get_end_time()
 74        DEBUG:libmf6.so: execute function: get_end_time(&c_double(504.0)) returned 0
 75        Out[5]: 504.0
 76
 77        In [6]: mf6.get_grid_rank(1)
 78        DEBUG:libmf6.so: execute function: get_grid_rank(&c_int(1), &c_int(2)) returned 0
 79        Out[6]: 2
 80
 81        In [7]: mf6.get_value('SLN_1/MXITER')
 82        DEBUG:libmf6.so: execute function: get_var_rank(c_char_p(b'SLN_1/MXITER'), &c_int(0)) returned 0
 83        DEBUG:libmf6.so: execute function: get_var_type(c_char_p(b'SLN_1/MXITER'), &c_char_Array_51(b'INTEGER')) returned 0
 84        DEBUG:libmf6.so: execute function: get_var_type(c_char_p(b'SLN_1/MXITER'), &c_char_Array_51(b'INTEGER')) returned 0
 85        DEBUG:libmf6.so: execute function: get_value_ptr_int(c_char_p(b'SLN_1/MXITER'), &ndpointer_<i4_1d_1_C) returned 0
 86        DEBUG:libmf6.so: execute function: get_var_type(c_char_p(b'SLN_1/MXITER'), &c_char_Array_51(b'INTEGER')) returned 0
 87        DEBUG:libmf6.so: execute function: get_value_ptr_int(c_char_p(b'SLN_1/MXITER'), &ndpointer_<i4_1d_1_C) returned 0
 88        Out[7]: array([25], dtype=int32)
 89        ```
 90
 91        Parameters
 92        ----------
 93        lib_path : Union[str, PathLike]
 94            Path to the shared library
 95
 96        lib_dependency : Union[str, PathLike, None], optional
 97            Path to the dependencies of the shared library, by default None
 98
 99        working_directory : Union[str, PathLike, None], optional
100            The working directory the shared library expects when being called,
101            by default None
102
103        timing : bool, optional
104            Whether timing should be activated, by default False
105
106        logger_level : str, int, optional
107            Logger level, default 0 ("NOTSET"). Accepted values are
108            "DEBUG" (10), "INFO" (20), "WARNING" (30), "ERROR" (40) or
109            "CRITICAL" (50).
110        """
111
112        self._state = State.UNINITIALIZED
113        self.libname = Path(lib_path).name
114        self.logger = get_logger(self.libname, logger_level)
115
116        if lib_dependency:
117            self._add_lib_dependency(lib_dependency)
118        # LoadLibraryEx flag (py38+): LOAD_WITH_ALTERED_SEARCH_PATH 0x08
119        # -> uses the altered search path for resolving dll dependencies
120        # `winmode` has no effect while running on Linux or macOS
121        # Note: this could make xmipy less secure (dll-injection)
122        # Can we get it to work without this flag?
123        self.lib = CDLL(str(lib_path), winmode=0x08)
124
125        if working_directory:
126            self.working_directory = Path(working_directory)
127        else:
128            self.working_directory = Path().cwd()
129        self.timing = timing
130
131        if self.timing:
132            self.timer = Timer(
133                name=self.libname,
134                text="Elapsed time for {name}.{fn_name}: {seconds:0.4f} seconds",
135            )
136
137    def __del__(self) -> None:
138        if self._state == State.INITIALIZED:
139            self.finalize()
140
141    @staticmethod
142    def _add_lib_dependency(lib_dependency: Union[str, PathLike[Any]]) -> None:
143        lib_dependency = str(Path(lib_dependency).absolute())
144        if platform.system() == "Windows":
145            os.environ["PATH"] = lib_dependency + os.pathsep + os.environ["PATH"]
146        else:
147            # Assume a Unix-like system
148            if "LD_LIBRARY_PATH" in os.environ:
149                os.environ["LD_LIBRARY_PATH"] = (
150                    lib_dependency + os.pathsep + os.environ["LD_LIBRARY_PATH"]
151                )
152            else:
153                os.environ["LD_LIBRARY_PATH"] = lib_dependency
154
155    def report_timing_totals(self) -> float:
156        if self.timing:
157            total = self.timer.report_totals()
158            with show_logger_message(self.logger):
159                self.logger.info(
160                    "Total elapsed time for %s: %0.4f seconds",
161                    self.libname,
162                    total,
163                )
164            return total
165        else:
166            raise TimerError("Timing not activated")
167
168    def get_constant_int(self, name: str) -> int:
169        c_var = c_int.in_dll(self.lib, name)
170        return c_var.value
171
172    def set_int(self, name: str, value: int) -> None:
173        c_var = c_int.in_dll(self.lib, name)
174        c_var.value = value
175
176    def initialize(self, config_file: Union[str, PathLike[Any]] = "") -> None:
177        if self._state == State.UNINITIALIZED:
178            with cd(self.working_directory):
179                self._execute_function(self.lib.initialize, os.fsencode(config_file))
180                self._state = State.INITIALIZED
181        else:
182            raise InputError("The library is already initialized")
183
184    def initialize_mpi(self, value: int) -> None:
185        if self._state == State.UNINITIALIZED:
186            with cd(self.working_directory):
187                comm = c_int(value)
188                self._execute_function(self.lib.initialize_mpi, byref(comm))
189                self._state = State.INITIALIZED
190        else:
191            raise InputError("The library is already initialized")
192
193    def update(self) -> None:
194        with cd(self.working_directory):
195            self._execute_function(self.lib.update)
196
197    def update_until(self, time: float) -> None:
198        with cd(self.working_directory):
199            self._execute_function(self.lib.update_until, c_double(time))
200
201    def finalize(self) -> None:
202        if self._state == State.INITIALIZED:
203            with cd(self.working_directory):
204                self._execute_function(self.lib.finalize)
205                self._state = State.UNINITIALIZED
206        else:
207            raise InputError("The library is not initialized yet")
208
209    def get_current_time(self) -> float:
210        current_time = c_double(0.0)
211        self._execute_function(self.lib.get_current_time, byref(current_time))
212        return current_time.value
213
214    def get_start_time(self) -> float:
215        start_time = c_double(0.0)
216        self._execute_function(self.lib.get_start_time, byref(start_time))
217        return start_time.value
218
219    def get_end_time(self) -> float:
220        end_time = c_double(0.0)
221        self._execute_function(self.lib.get_end_time, byref(end_time))
222        return end_time.value
223
224    def get_time_step(self) -> float:
225        dt = c_double(0.0)
226        self._execute_function(self.lib.get_time_step, byref(dt))
227        return dt.value
228
229    def get_component_name(self) -> str:
230        len_name = self.get_constant_int("BMI_LENCOMPONENTNAME")
231        component_name = create_string_buffer(len_name)
232        self._execute_function(self.lib.get_component_name, byref(component_name))
233        return component_name.value.decode("ascii")
234
235    def get_version(self) -> str:
236        len_version = self.get_constant_int("BMI_LENVERSION")
237        version = create_string_buffer(len_version)
238        self._execute_function(self.lib.get_version, byref(version))
239        return version.value.decode("ascii")
240
241    def get_input_item_count(self) -> int:
242        count = c_int(0)
243        self._execute_function(self.lib.get_input_item_count, byref(count))
244        return count.value
245
246    def get_output_item_count(self) -> int:
247        count = c_int(0)
248        self._execute_function(self.lib.get_output_item_count, byref(count))
249        return count.value
250
251    def get_input_var_names(self) -> Tuple[str]:
252        len_address = self.get_constant_int("BMI_LENVARADDRESS")
253        nr_input_vars = self.get_input_item_count()
254        len_names = nr_input_vars * len_address
255        names = create_string_buffer(len_names)
256
257        # get a (1-dim) char array (char*) containing the input variable
258        # names as \x00 terminated sub-strings
259        self._execute_function(self.lib.get_input_var_names, byref(names))
260
261        # decode
262        input_vars: Tuple[str] = tuple(
263            names[i * len_address : (i + 1) * len_address]  # type: ignore
264            .split(b"\0", 1)[0]
265            .decode("ascii")
266            for i in range(nr_input_vars)
267        )
268        return input_vars
269
270    def get_output_var_names(self) -> Tuple[str]:
271        len_address = self.get_constant_int("BMI_LENVARADDRESS")
272        nr_output_vars = self.get_output_item_count()
273        len_names = nr_output_vars * len_address
274        names = create_string_buffer(len_names)
275
276        # get a (1-dim) char array (char*) containing the output variable
277        # names as \x00 terminated sub-strings
278        self._execute_function(self.lib.get_output_var_names, byref(names))
279
280        # decode
281        output_vars: Tuple[str] = tuple(
282            names[i * len_address : (i + 1) * len_address]  # type: ignore
283            .split(b"\0", 1)[0]
284            .decode("ascii")
285            for i in range(nr_output_vars)
286        )
287        return output_vars
288
289    def get_var_grid(self, name: str) -> int:
290        grid_id = c_int(0)
291        self._execute_function(
292            self.lib.get_var_grid,
293            c_char_p(name.encode()),
294            byref(grid_id),
295        )
296        return grid_id.value
297
298    def get_var_type(self, name: str) -> str:
299        len_var_type = self.get_constant_int("BMI_LENVARTYPE")
300        var_type = create_string_buffer(len_var_type)
301        self._execute_function(
302            self.lib.get_var_type,
303            c_char_p(name.encode()),
304            byref(var_type),
305        )
306        return var_type.value.decode()
307
308    # strictly speaking not BMI...
309    def get_var_shape(self, name: str) -> NDArray[np.int32]:
310        rank = self.get_var_rank(name)
311        array = np.zeros(rank, dtype=np.int32)
312        self._execute_function(
313            self.lib.get_var_shape,
314            c_char_p(name.encode()),
315            c_void_p(array.ctypes.data),
316        )
317        return array
318
319    def get_var_rank(self, name: str) -> int:
320        rank = c_int(0)
321        self._execute_function(
322            self.lib.get_var_rank,
323            c_char_p(name.encode()),
324            byref(rank),
325        )
326        return rank.value
327
328    def get_var_units(self, name: str) -> str:
329        raise NotImplementedError
330
331    def get_var_itemsize(self, name: str) -> int:
332        item_size = c_int(0)
333        self._execute_function(
334            self.lib.get_var_itemsize,
335            c_char_p(name.encode()),
336            byref(item_size),
337        )
338        return item_size.value
339
340    def get_var_nbytes(self, name: str) -> int:
341        nbytes = c_int(0)
342        self._execute_function(
343            self.lib.get_var_nbytes,
344            c_char_p(name.encode()),
345            byref(nbytes),
346        )
347        return nbytes.value
348
349    def get_var_location(self, name: str) -> str:
350        raise NotImplementedError
351
352    def get_time_units(self) -> str:
353        raise NotImplementedError
354
355    def get_value(
356        self, name: str, dest: Union[NDArray[Any], None] = None
357    ) -> NDArray[Any]:
358        # make sure that optional array is of correct layout:
359        if dest is not None and not dest.flags["C"]:
360            raise InputError("Array should have C layout")
361
362        # first deal with scalars
363        rank = self.get_var_rank(name)
364        var_type = self.get_var_type(name)
365        var_type_lower = var_type.lower()
366
367        if rank == 0:
368            if var_type_lower.startswith("string"):
369                ilen = self.get_var_nbytes(name)
370                strtype = "<S" + str(ilen + 1)
371                if dest is None:
372                    dest = np.empty(1, dtype=strtype, order="C")
373                self._execute_function(
374                    self.lib.get_value,
375                    c_char_p(name.encode()),
376                    byref(dest.ctypes.data_as(POINTER(c_char))),
377                )
378                dest[0] = dest[0].decode("ascii").strip()
379                return dest.astype(str)
380            else:
381                src = self.get_value_ptr_scalar(name)
382                if dest is None:
383                    return self.get_value_ptr_scalar(name).copy()
384                else:
385                    dest[0] = src[0]
386                    return dest
387
388        var_shape = self.get_var_shape(name)
389
390        if var_type_lower.startswith("double"):
391            if dest is None:
392                dest = np.empty(shape=var_shape, dtype=np.float64, order="C")
393            self._execute_function(
394                self.lib.get_value,
395                c_char_p(name.encode()),
396                byref(dest.ctypes.data_as(POINTER(c_double))),
397            )
398        elif var_type_lower.startswith("int"):
399            if dest is None:
400                dest = np.empty(shape=var_shape, dtype=np.int32, order="C")
401            self._execute_function(
402                self.lib.get_value,
403                c_char_p(name.encode()),
404                byref(dest.ctypes.data_as(POINTER(c_int))),
405            )
406        elif var_type_lower.startswith("string"):
407            if dest is None:
408                if var_shape[0] == 0:
409                    return np.empty((0,), "U1")
410                ilen = self.get_var_nbytes(name) // var_shape[0]
411                strtype = "<S" + str(ilen + 1)
412                dest = np.empty(var_shape[0], dtype=strtype, order="C")
413            self._execute_function(
414                self.lib.get_value,
415                c_char_p(name.encode()),
416                byref(dest.ctypes.data_as(POINTER(c_char))),
417            )
418            for i, x in enumerate(dest):
419                dest[i] = x.decode("ascii").strip()
420            return dest.astype(str)
421        else:
422            raise InputError(f"Unsupported value type {var_type!r}")
423
424        return dest
425
426    def get_value_ptr(self, name: str) -> NDArray[Any]:
427        # first scalars
428        rank = self.get_var_rank(name)
429        if rank == 0:
430            return self.get_value_ptr_scalar(name)
431
432        var_type = self.get_var_type(name)
433        var_type_lower = var_type.lower()
434        shape_array = self.get_var_shape(name)
435
436        # convert shape array to python tuple
437        shape_tuple = tuple(np.trim_zeros(shape_array))
438        ndim = len(shape_tuple)
439
440        if var_type_lower.startswith("double"):
441            arraytype = np.ctypeslib.ndpointer(
442                dtype=np.float64, ndim=ndim, shape=shape_tuple, flags="C"
443            )
444        elif var_type_lower.startswith("float"):
445            arraytype = np.ctypeslib.ndpointer(
446                dtype=np.float32, ndim=ndim, shape=shape_tuple, flags="C"
447            )
448        elif var_type_lower.startswith("int"):
449            arraytype = np.ctypeslib.ndpointer(
450                dtype=np.int32, ndim=ndim, shape=shape_tuple, flags="C"
451            )
452        else:
453            raise InputError(f"Unsupported value type {var_type!r}")
454        values = arraytype()
455        self._execute_function(
456            self.lib.get_value_ptr,
457            c_char_p(name.encode()),
458            byref(values),
459            detail="for variable " + name,
460        )
461        return values.contents
462
463    def get_value_ptr_scalar(self, name: str) -> NDArray[Any]:
464        var_type = self.get_var_type(name)
465        var_type_lower = var_type.lower()
466        if var_type_lower.startswith("double"):
467            arraytype = np.ctypeslib.ndpointer(
468                dtype=np.float64, ndim=1, shape=(1,), flags="C"
469            )
470        elif var_type_lower.startswith("float"):
471            arraytype = np.ctypeslib.ndpointer(
472                dtype=np.float32, ndim=1, shape=(1,), flags="C"
473            )
474        elif var_type_lower.startswith("int"):
475            arraytype = np.ctypeslib.ndpointer(
476                dtype=np.int32, ndim=1, shape=(1,), flags="C"
477            )
478        else:
479            raise InputError(f"Unsupported value type {var_type!r}")
480        values = arraytype()
481        self._execute_function(
482            self.lib.get_value_ptr,
483            c_char_p(name.encode()),
484            byref(values),
485            detail="for variable " + name,
486        )
487        return values.contents
488
489    def get_value_at_indices(
490        self, name: str, dest: NDArray[Any], inds: NDArray[np.int32]
491    ) -> NDArray[Any]:
492        raise NotImplementedError
493
494    def set_value(self, name: str, values: NDArray[Any]) -> None:
495        if not values.flags["C"]:
496            raise InputError("Array should have C layout")
497        var_type = self.get_var_type(name)
498        var_type_lower = var_type.lower()
499        if var_type_lower.startswith("double"):
500            if values.dtype != np.float64:
501                raise InputError("Array should have float64 elements")
502            self._execute_function(
503                self.lib.set_value,
504                c_char_p(name.encode()),
505                byref(values.ctypes.data_as(POINTER(c_double))),
506            )
507        elif var_type_lower.startswith("int"):
508            if values.dtype != np.int32:
509                raise InputError("Array should have int32 elements")
510            self._execute_function(
511                self.lib.set_value,
512                c_char_p(name.encode()),
513                byref(values.ctypes.data_as(POINTER(c_int))),
514            )
515        else:
516            raise InputError("Unsupported value type")
517
518    def set_value_at_indices(
519        self, name: str, inds: NDArray[Any], src: NDArray[Any]
520    ) -> None:
521        raise NotImplementedError
522
523    def get_grid_rank(self, grid: int) -> int:
524        grid_rank = c_int(0)
525        c_grid = c_int(grid)
526        self._execute_function(
527            self.lib.get_grid_rank,
528            byref(c_grid),
529            byref(grid_rank),
530        )
531        return grid_rank.value
532
533    def get_grid_size(self, grid: int) -> int:
534        grid_size = c_int(0)
535        c_grid = c_int(grid)
536        self._execute_function(
537            self.lib.get_grid_size,
538            byref(c_grid),
539            byref(grid_size),
540        )
541        return grid_size.value
542
543    def get_grid_type(self, grid: int) -> str:
544        len_grid_type = self.get_constant_int("BMI_LENGRIDTYPE")
545        grid_type = create_string_buffer(len_grid_type)
546        c_grid = c_int(grid)
547        self._execute_function(
548            self.lib.get_grid_type,
549            byref(c_grid),
550            byref(grid_type),
551        )
552        return grid_type.value.decode()
553
554    def get_grid_shape(self, grid: int, shape: NDArray[np.int32]) -> NDArray[np.int32]:
555        c_grid = c_int(grid)
556        self._execute_function(
557            self.lib.get_grid_shape,
558            byref(c_grid),
559            c_void_p(shape.ctypes.data),
560        )
561        return shape
562
563    def get_grid_spacing(
564        self, grid: int, spacing: NDArray[np.int32]
565    ) -> NDArray[np.int32]:
566        raise NotImplementedError
567
568    def get_grid_origin(
569        self, grid: int, origin: NDArray[np.int32]
570    ) -> NDArray[np.int32]:
571        raise NotImplementedError
572
573    def get_grid_x(self, grid: int, x: NDArray[np.float64]) -> NDArray[np.float64]:
574        c_grid = c_int(grid)
575        self._execute_function(
576            self.lib.get_grid_x,
577            byref(c_grid),
578            c_void_p(x.ctypes.data),
579        )
580        return x
581
582    def get_grid_y(self, grid: int, y: NDArray[np.float64]) -> NDArray[np.float64]:
583        c_grid = c_int(grid)
584        self._execute_function(
585            self.lib.get_grid_y,
586            byref(c_grid),
587            c_void_p(y.ctypes.data),
588        )
589        return y
590
591    def get_grid_z(self, grid: int, z: NDArray[np.float64]) -> NDArray[np.float64]:
592        c_grid = c_int(grid)
593        self._execute_function(
594            self.lib.get_grid_z,
595            byref(c_grid),
596            c_void_p(z.ctypes.data),
597        )
598        return z
599
600    def get_grid_node_count(self, grid: int) -> int:
601        grid_node_count = c_int(0)
602        c_grid = c_int(grid)
603        self._execute_function(
604            self.lib.get_grid_node_count,
605            byref(c_grid),
606            byref(grid_node_count),
607        )
608        return grid_node_count.value
609
610    def get_grid_edge_count(self, grid: int) -> int:
611        raise NotImplementedError
612
613    def get_grid_face_count(self, grid: int) -> int:
614        grid_face_count = c_int(0)
615        c_grid = c_int(grid)
616        self._execute_function(
617            self.lib.get_grid_face_count,
618            byref(c_grid),
619            byref(grid_face_count),
620        )
621        return grid_face_count.value
622
623    def get_grid_edge_nodes(
624        self, grid: int, edge_nodes: NDArray[np.int32]
625    ) -> NDArray[np.int32]:
626        raise NotImplementedError
627
628    def get_grid_face_edges(
629        self, grid: int, face_edges: NDArray[np.int32]
630    ) -> NDArray[np.int32]:
631        raise NotImplementedError
632
633    def get_grid_face_nodes(
634        self, grid: int, face_nodes: NDArray[np.int32]
635    ) -> NDArray[np.int32]:
636        c_grid = c_int(grid)
637        self._execute_function(
638            self.lib.get_grid_face_nodes,
639            byref(c_grid),
640            c_void_p(face_nodes.ctypes.data),
641        )
642        return face_nodes
643
644    def get_grid_nodes_per_face(
645        self, grid: int, nodes_per_face: NDArray[np.int32]
646    ) -> NDArray[np.int32]:
647        c_grid = c_int(grid)
648        self._execute_function(
649            self.lib.get_grid_nodes_per_face,
650            byref(c_grid),
651            c_void_p(nodes_per_face.ctypes.data),
652        )
653        return nodes_per_face
654
655    # ===========================
656    # here starts the XMI
657    # ===========================
658    def prepare_time_step(self, dt: float) -> None:
659        with cd(self.working_directory):
660            c_dt = c_double(dt)
661            self._execute_function(self.lib.prepare_time_step, byref(c_dt))
662
663    def do_time_step(self) -> None:
664        with cd(self.working_directory):
665            self._execute_function(self.lib.do_time_step)
666
667    def finalize_time_step(self) -> None:
668        with cd(self.working_directory):
669            self._execute_function(self.lib.finalize_time_step)
670
671    def get_subcomponent_count(self) -> int:
672        count = c_int(0)
673        self._execute_function(self.lib.get_subcomponent_count, byref(count))
674        return count.value
675
676    def prepare_solve(self, component_id: int = 1) -> None:
677        cid = c_int(component_id)
678        with cd(self.working_directory):
679            self._execute_function(self.lib.prepare_solve, byref(cid))
680
681    def solve(self, component_id: int = 1) -> bool:
682        cid = c_int(component_id)
683        has_converged = c_int(0)
684        with cd(self.working_directory):
685            self._execute_function(self.lib.solve, byref(cid), byref(has_converged))
686        return has_converged.value == 1
687
688    def finalize_solve(self, component_id: int = 1) -> None:
689        cid = c_int(component_id)
690
691        with cd(self.working_directory):
692            self._execute_function(self.lib.finalize_solve, byref(cid))
693
694    def get_var_address(
695        self, var_name: str, component_name: str, subcomponent_name: str = ""
696    ) -> str:
697        len_var_address = self.get_constant_int("BMI_LENVARADDRESS")
698        var_address = create_string_buffer(len_var_address)
699        self._execute_function(
700            self.lib.get_var_address,
701            c_char_p(component_name.upper().encode()),
702            c_char_p(subcomponent_name.upper().encode()),
703            c_char_p(var_name.upper().encode()),
704            byref(var_address),
705        )
706
707        return var_address.value.decode()
708
709    def _execute_function(
710        self, function: Callable[[Any], int], *args: Any, **kwargs: Any
711    ) -> None:
712        """
713        Utility function to execute a BMI function in the kernel and checks its status
714        """
715
716        if self.timing:
717            self.timer.start(function.__name__)
718
719        try:
720            # Execute library function
721            result = function(*args)
722
723            if self.logger.isEnabledFor(logging.DEBUG):
724                self.logger.debug(
725                    "execute function: %s returned %s",
726                    repr_function_call(function.__name__, *args),
727                    result,
728                )
729
730            if result != Status.SUCCESS:
731                msg = "BMI exception in "
732                msg += repr_function_call(function.__name__, *args)
733
734                # try to get detailed error msg, beware:
735                # directly call CDLL methods to avoid recursion
736                try:
737                    len_err_msg = self.get_constant_int("BMI_LENERRMESSAGE")
738                    err_msg = create_string_buffer(len_err_msg)
739                    self.lib.get_last_bmi_error(byref(err_msg))
740
741                    len_name = self.get_constant_int("BMI_LENCOMPONENTNAME")
742                    component_name = create_string_buffer(len_name)
743                    self.lib.get_component_name(byref(component_name))
744
745                    if "detail" in kwargs:
746                        detail = f", details : '{kwargs['detail']}'"
747                    else:
748                        detail = ""
749                    msg += (
750                        f": Message from {component_name.value.decode()} "
751                        + f"'{err_msg.value.decode()}'"
752                        + detail
753                    )
754                except AttributeError:
755                    self.logger.error("Couldn't extract error message")
756
757                raise XMIError(msg)
758
759        finally:
760            if self.timing:
761                self.timer.stop(function.__name__)

The implementation of the XMI

XmiWrapper( lib_path: Union[str, os.PathLike[Any]], lib_dependency: Union[str, os.PathLike[Any], NoneType] = None, working_directory: Union[str, os.PathLike[Any], NoneType] = None, timing: bool = False, logger_level: Union[str, int] = 0)
 46    def __init__(
 47        self,
 48        lib_path: Union[str, PathLike[Any]],
 49        lib_dependency: Union[str, PathLike[Any], None] = None,
 50        working_directory: Union[str, PathLike[Any], None] = None,
 51        timing: bool = False,
 52        logger_level: Union[str, int] = 0,
 53    ):
 54        """
 55        Constructor of `XmiWrapper`
 56
 57        Next to wrapping C functions of a library exposing XMI,
 58        it also adds timing and logging functionality.
 59        An example for logging can be seen below.
 60
 61        ```
 62        In [1]: from xmipy import XmiWrapper
 63
 64        In [2]: mf6 = XmiWrapper("/path/to/libmf6.so", working_directory="/path/to/sim", logger_level="DEBUG")
 65
 66        In [3]: mf6.initialize()
 67        DEBUG:libmf6.so: execute function: initialize(b'') returned 0
 68
 69        In [4]: mf6.get_start_time()
 70        DEBUG:libmf6.so: execute function: get_start_time(&c_double(0.0)) returned 0
 71        Out[4]: 0.0
 72
 73        In [5]: mf6.get_end_time()
 74        DEBUG:libmf6.so: execute function: get_end_time(&c_double(504.0)) returned 0
 75        Out[5]: 504.0
 76
 77        In [6]: mf6.get_grid_rank(1)
 78        DEBUG:libmf6.so: execute function: get_grid_rank(&c_int(1), &c_int(2)) returned 0
 79        Out[6]: 2
 80
 81        In [7]: mf6.get_value('SLN_1/MXITER')
 82        DEBUG:libmf6.so: execute function: get_var_rank(c_char_p(b'SLN_1/MXITER'), &c_int(0)) returned 0
 83        DEBUG:libmf6.so: execute function: get_var_type(c_char_p(b'SLN_1/MXITER'), &c_char_Array_51(b'INTEGER')) returned 0
 84        DEBUG:libmf6.so: execute function: get_var_type(c_char_p(b'SLN_1/MXITER'), &c_char_Array_51(b'INTEGER')) returned 0
 85        DEBUG:libmf6.so: execute function: get_value_ptr_int(c_char_p(b'SLN_1/MXITER'), &ndpointer_<i4_1d_1_C) returned 0
 86        DEBUG:libmf6.so: execute function: get_var_type(c_char_p(b'SLN_1/MXITER'), &c_char_Array_51(b'INTEGER')) returned 0
 87        DEBUG:libmf6.so: execute function: get_value_ptr_int(c_char_p(b'SLN_1/MXITER'), &ndpointer_<i4_1d_1_C) returned 0
 88        Out[7]: array([25], dtype=int32)
 89        ```
 90
 91        Parameters
 92        ----------
 93        lib_path : Union[str, PathLike]
 94            Path to the shared library
 95
 96        lib_dependency : Union[str, PathLike, None], optional
 97            Path to the dependencies of the shared library, by default None
 98
 99        working_directory : Union[str, PathLike, None], optional
100            The working directory the shared library expects when being called,
101            by default None
102
103        timing : bool, optional
104            Whether timing should be activated, by default False
105
106        logger_level : str, int, optional
107            Logger level, default 0 ("NOTSET"). Accepted values are
108            "DEBUG" (10), "INFO" (20), "WARNING" (30), "ERROR" (40) or
109            "CRITICAL" (50).
110        """
111
112        self._state = State.UNINITIALIZED
113        self.libname = Path(lib_path).name
114        self.logger = get_logger(self.libname, logger_level)
115
116        if lib_dependency:
117            self._add_lib_dependency(lib_dependency)
118        # LoadLibraryEx flag (py38+): LOAD_WITH_ALTERED_SEARCH_PATH 0x08
119        # -> uses the altered search path for resolving dll dependencies
120        # `winmode` has no effect while running on Linux or macOS
121        # Note: this could make xmipy less secure (dll-injection)
122        # Can we get it to work without this flag?
123        self.lib = CDLL(str(lib_path), winmode=0x08)
124
125        if working_directory:
126            self.working_directory = Path(working_directory)
127        else:
128            self.working_directory = Path().cwd()
129        self.timing = timing
130
131        if self.timing:
132            self.timer = Timer(
133                name=self.libname,
134                text="Elapsed time for {name}.{fn_name}: {seconds:0.4f} seconds",
135            )

Constructor of XmiWrapper

Next to wrapping C functions of a library exposing XMI, it also adds timing and logging functionality. An example for logging can be seen below.

In [1]: from xmipy import XmiWrapper

In [2]: mf6 = XmiWrapper("/path/to/libmf6.so", working_directory="/path/to/sim", logger_level="DEBUG")

In [3]: mf6.initialize()
DEBUG:libmf6.so: execute function: initialize(b'') returned 0

In [4]: mf6.get_start_time()
DEBUG:libmf6.so: execute function: get_start_time(&c_double(0.0)) returned 0
Out[4]: 0.0

In [5]: mf6.get_end_time()
DEBUG:libmf6.so: execute function: get_end_time(&c_double(504.0)) returned 0
Out[5]: 504.0

In [6]: mf6.get_grid_rank(1)
DEBUG:libmf6.so: execute function: get_grid_rank(&c_int(1), &c_int(2)) returned 0
Out[6]: 2

In [7]: mf6.get_value('SLN_1/MXITER')
DEBUG:libmf6.so: execute function: get_var_rank(c_char_p(b'SLN_1/MXITER'), &c_int(0)) returned 0
DEBUG:libmf6.so: execute function: get_var_type(c_char_p(b'SLN_1/MXITER'), &c_char_Array_51(b'INTEGER')) returned 0
DEBUG:libmf6.so: execute function: get_var_type(c_char_p(b'SLN_1/MXITER'), &c_char_Array_51(b'INTEGER')) returned 0
DEBUG:libmf6.so: execute function: get_value_ptr_int(c_char_p(b'SLN_1/MXITER'), &ndpointer_<i4_1d_1_C) returned 0
DEBUG:libmf6.so: execute function: get_var_type(c_char_p(b'SLN_1/MXITER'), &c_char_Array_51(b'INTEGER')) returned 0
DEBUG:libmf6.so: execute function: get_value_ptr_int(c_char_p(b'SLN_1/MXITER'), &ndpointer_<i4_1d_1_C) returned 0
Out[7]: array([25], dtype=int32)

Parameters

lib_path : Union[str, PathLike] Path to the shared library

lib_dependency : Union[str, PathLike, None], optional Path to the dependencies of the shared library, by default None

working_directory : Union[str, PathLike, None], optional The working directory the shared library expects when being called, by default None

timing : bool, optional Whether timing should be activated, by default False

logger_level : str, int, optional Logger level, default 0 ("NOTSET"). Accepted values are "DEBUG" (10), "INFO" (20), "WARNING" (30), "ERROR" (40) or "CRITICAL" (50).

libname
logger
lib
timing
def report_timing_totals(self) -> float:
155    def report_timing_totals(self) -> float:
156        if self.timing:
157            total = self.timer.report_totals()
158            with show_logger_message(self.logger):
159                self.logger.info(
160                    "Total elapsed time for %s: %0.4f seconds",
161                    self.libname,
162                    total,
163                )
164            return total
165        else:
166            raise TimerError("Timing not activated")

Logs and returns total time spent

Returns

float Total time spent

Raises

TimerError Raised if timing is not activated

def get_constant_int(self, name: str) -> int:
168    def get_constant_int(self, name: str) -> int:
169        c_var = c_int.in_dll(self.lib, name)
170        return c_var.value

Get a constant integer

Parameters

name : str Name of the constant

Returns

int Constant to be returned

def set_int(self, name: str, value: int) -> None:
172    def set_int(self, name: str, value: int) -> None:
173        c_var = c_int.in_dll(self.lib, name)
174        c_var.value = value

Set integer

Parameters

name : str Integer to be set value : int Value to set the integer to

def initialize(self, config_file: Union[str, os.PathLike[Any]] = '') -> None:
176    def initialize(self, config_file: Union[str, PathLike[Any]] = "") -> None:
177        if self._state == State.UNINITIALIZED:
178            with cd(self.working_directory):
179                self._execute_function(self.lib.initialize, os.fsencode(config_file))
180                self._state = State.INITIALIZED
181        else:
182            raise InputError("The library is already initialized")

Perform startup tasks for the model.

Perform all tasks that take place before entering the model's time loop, including opening files and initializing the model state. Model inputs are read from a text-based configuration file, specified by config_file.

Parameters

config_file : str, optional The path to the model configuration file.

Notes

Models should be refactored, if necessary, to use a configuration file. CSDMS does not impose any constraint on how configuration files are formatted, although YAML is recommended. A template of a model's configuration file with placeholder values is used by the BMI.

def initialize_mpi(self, value: int) -> None:
184    def initialize_mpi(self, value: int) -> None:
185        if self._state == State.UNINITIALIZED:
186            with cd(self.working_directory):
187                comm = c_int(value)
188                self._execute_function(self.lib.initialize_mpi, byref(comm))
189                self._state = State.INITIALIZED
190        else:
191            raise InputError("The library is already initialized")
def update(self) -> None:
193    def update(self) -> None:
194        with cd(self.working_directory):
195            self._execute_function(self.lib.update)

Advance model state by one time step.

Perform all tasks that take place within one pass through the model's time loop. This typically includes incrementing all of the model's state variables. If the model's state variables don't change in time, then they can be computed by the initialize() method and this method can return with no action.

def update_until(self, time: float) -> None:
197    def update_until(self, time: float) -> None:
198        with cd(self.working_directory):
199            self._execute_function(self.lib.update_until, c_double(time))

Advance model state until the given time.

Parameters

time : float A model time later than the current model time.

def finalize(self) -> None:
201    def finalize(self) -> None:
202        if self._state == State.INITIALIZED:
203            with cd(self.working_directory):
204                self._execute_function(self.lib.finalize)
205                self._state = State.UNINITIALIZED
206        else:
207            raise InputError("The library is not initialized yet")

Perform tear-down tasks for the model.

Perform all tasks that take place after exiting the model's time loop. This typically includes deallocating memory, closing files and printing reports.

def get_current_time(self) -> float:
209    def get_current_time(self) -> float:
210        current_time = c_double(0.0)
211        self._execute_function(self.lib.get_current_time, byref(current_time))
212        return current_time.value

Return the current time of the model.

Returns

float The current model time.

def get_start_time(self) -> float:
214    def get_start_time(self) -> float:
215        start_time = c_double(0.0)
216        self._execute_function(self.lib.get_start_time, byref(start_time))
217        return start_time.value

Start time of the model.

Model times should be of type float.

Returns

float The model start time.

def get_end_time(self) -> float:
219    def get_end_time(self) -> float:
220        end_time = c_double(0.0)
221        self._execute_function(self.lib.get_end_time, byref(end_time))
222        return end_time.value

End time of the model.

Returns

float The maximum model time.

def get_time_step(self) -> float:
224    def get_time_step(self) -> float:
225        dt = c_double(0.0)
226        self._execute_function(self.lib.get_time_step, byref(dt))
227        return dt.value

Return the current time step of the model.

The model time step should be of type float.

Returns

float The time step used in model.

def get_component_name(self) -> str:
229    def get_component_name(self) -> str:
230        len_name = self.get_constant_int("BMI_LENCOMPONENTNAME")
231        component_name = create_string_buffer(len_name)
232        self._execute_function(self.lib.get_component_name, byref(component_name))
233        return component_name.value.decode("ascii")

Name of the component.

Returns

str The name of the component.

def get_version(self) -> str:
235    def get_version(self) -> str:
236        len_version = self.get_constant_int("BMI_LENVERSION")
237        version = create_string_buffer(len_version)
238        self._execute_function(self.lib.get_version, byref(version))
239        return version.value.decode("ascii")

Get the version of the kernel.

def get_input_item_count(self) -> int:
241    def get_input_item_count(self) -> int:
242        count = c_int(0)
243        self._execute_function(self.lib.get_input_item_count, byref(count))
244        return count.value

Count of a model's input variables.

Returns

int The number of input variables.

def get_output_item_count(self) -> int:
246    def get_output_item_count(self) -> int:
247        count = c_int(0)
248        self._execute_function(self.lib.get_output_item_count, byref(count))
249        return count.value

Count of a model's output variables.

Returns

int The number of output variables.

def get_input_var_names(self) -> Tuple[str]:
251    def get_input_var_names(self) -> Tuple[str]:
252        len_address = self.get_constant_int("BMI_LENVARADDRESS")
253        nr_input_vars = self.get_input_item_count()
254        len_names = nr_input_vars * len_address
255        names = create_string_buffer(len_names)
256
257        # get a (1-dim) char array (char*) containing the input variable
258        # names as \x00 terminated sub-strings
259        self._execute_function(self.lib.get_input_var_names, byref(names))
260
261        # decode
262        input_vars: Tuple[str] = tuple(
263            names[i * len_address : (i + 1) * len_address]  # type: ignore
264            .split(b"\0", 1)[0]
265            .decode("ascii")
266            for i in range(nr_input_vars)
267        )
268        return input_vars

List of a model's input variables.

Input variable names must be CSDMS Standard Names, also known as long variable names.

Returns

list of str The input variables for the model.

Notes

Standard Names enable the CSDMS framework to determine whether an input variable in one model is equivalent to, or compatible with, an output variable in another model. This allows the framework to automatically connect components.

Standard Names do not have to be used within the model.

def get_output_var_names(self) -> Tuple[str]:
270    def get_output_var_names(self) -> Tuple[str]:
271        len_address = self.get_constant_int("BMI_LENVARADDRESS")
272        nr_output_vars = self.get_output_item_count()
273        len_names = nr_output_vars * len_address
274        names = create_string_buffer(len_names)
275
276        # get a (1-dim) char array (char*) containing the output variable
277        # names as \x00 terminated sub-strings
278        self._execute_function(self.lib.get_output_var_names, byref(names))
279
280        # decode
281        output_vars: Tuple[str] = tuple(
282            names[i * len_address : (i + 1) * len_address]  # type: ignore
283            .split(b"\0", 1)[0]
284            .decode("ascii")
285            for i in range(nr_output_vars)
286        )
287        return output_vars

List of a model's output variables.

Output variable names must be CSDMS Standard Names, also known as long variable names.

Returns

list of str The output variables for the model.

def get_var_grid(self, name: str) -> int:
289    def get_var_grid(self, name: str) -> int:
290        grid_id = c_int(0)
291        self._execute_function(
292            self.lib.get_var_grid,
293            c_char_p(name.encode()),
294            byref(grid_id),
295        )
296        return grid_id.value

Get grid identifier for the given variable.

Parameters

name : str An input or output variable name, a CSDMS Standard Name.

Returns

int The grid identifier.

def get_var_type(self, name: str) -> str:
298    def get_var_type(self, name: str) -> str:
299        len_var_type = self.get_constant_int("BMI_LENVARTYPE")
300        var_type = create_string_buffer(len_var_type)
301        self._execute_function(
302            self.lib.get_var_type,
303            c_char_p(name.encode()),
304            byref(var_type),
305        )
306        return var_type.value.decode()

Get data type of the given variable.

Parameters

name : str An input or output variable name, a CSDMS Standard Name.

Returns

str The Python variable type; e.g., str, int, float.

def get_var_shape(self, name: str) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.int32]]:
309    def get_var_shape(self, name: str) -> NDArray[np.int32]:
310        rank = self.get_var_rank(name)
311        array = np.zeros(rank, dtype=np.int32)
312        self._execute_function(
313            self.lib.get_var_shape,
314            c_char_p(name.encode()),
315            c_void_p(array.ctypes.data),
316        )
317        return array
def get_var_rank(self, name: str) -> int:
319    def get_var_rank(self, name: str) -> int:
320        rank = c_int(0)
321        self._execute_function(
322            self.lib.get_var_rank,
323            c_char_p(name.encode()),
324            byref(rank),
325        )
326        return rank.value
def get_var_units(self, name: str) -> str:
328    def get_var_units(self, name: str) -> str:
329        raise NotImplementedError

Get units of the given variable.

Standard unit names, in lower case, should be used, such as meters or seconds. Standard abbreviations, like m for meters, are also supported. For variables with compound units, each unit name is separated by a single space, with exponents other than 1 placed immediately after the name, as in m s-1 for velocity, W m-2 for an energy flux, or km2 for an area.

Parameters

name : str An input or output variable name, a CSDMS Standard Name.

Returns

str The variable units.

Notes

CSDMS uses the UDUNITS standard from Unidata.

def get_var_itemsize(self, name: str) -> int:
331    def get_var_itemsize(self, name: str) -> int:
332        item_size = c_int(0)
333        self._execute_function(
334            self.lib.get_var_itemsize,
335            c_char_p(name.encode()),
336            byref(item_size),
337        )
338        return item_size.value

Get memory use for each array element in bytes.

Parameters

name : str An input or output variable name, a CSDMS Standard Name.

Returns

int Item size in bytes.

def get_var_nbytes(self, name: str) -> int:
340    def get_var_nbytes(self, name: str) -> int:
341        nbytes = c_int(0)
342        self._execute_function(
343            self.lib.get_var_nbytes,
344            c_char_p(name.encode()),
345            byref(nbytes),
346        )
347        return nbytes.value

Get size, in bytes, of the given variable.

Parameters

name : str An input or output variable name, a CSDMS Standard Name.

Returns

int The size of the variable, counted in bytes.

def get_var_location(self, name: str) -> str:
349    def get_var_location(self, name: str) -> str:
350        raise NotImplementedError

Get the grid element type that the a given variable is defined on.

The grid topology can be composed of nodes, edges, and faces.

node A point that has a coordinate pair or triplet: the most basic element of the topology.

edge A line or curve bounded by two nodes.

face A plane or surface enclosed by a set of edges. In a 2D horizontal application one may consider the word “polygon”, but in the hierarchy of elements the word “face” is most common.

Parameters

name : str An input or output variable name, a CSDMS Standard Name.

Returns

str The grid location on which the variable is defined. Must be one of "node", "edge", or "face".

Notes

CSDMS uses the ugrid conventions to define unstructured grids.

def get_time_units(self) -> str:
352    def get_time_units(self) -> str:
353        raise NotImplementedError

Time units of the model.

Returns

str The model time unit; e.g., days or s.

Notes

CSDMS uses the UDUNITS standard from Unidata.

def get_value( self, name: str, dest: Optional[numpy.ndarray[Any, numpy.dtype[Any]]] = None) -> numpy.ndarray[typing.Any, numpy.dtype[typing.Any]]:
355    def get_value(
356        self, name: str, dest: Union[NDArray[Any], None] = None
357    ) -> NDArray[Any]:
358        # make sure that optional array is of correct layout:
359        if dest is not None and not dest.flags["C"]:
360            raise InputError("Array should have C layout")
361
362        # first deal with scalars
363        rank = self.get_var_rank(name)
364        var_type = self.get_var_type(name)
365        var_type_lower = var_type.lower()
366
367        if rank == 0:
368            if var_type_lower.startswith("string"):
369                ilen = self.get_var_nbytes(name)
370                strtype = "<S" + str(ilen + 1)
371                if dest is None:
372                    dest = np.empty(1, dtype=strtype, order="C")
373                self._execute_function(
374                    self.lib.get_value,
375                    c_char_p(name.encode()),
376                    byref(dest.ctypes.data_as(POINTER(c_char))),
377                )
378                dest[0] = dest[0].decode("ascii").strip()
379                return dest.astype(str)
380            else:
381                src = self.get_value_ptr_scalar(name)
382                if dest is None:
383                    return self.get_value_ptr_scalar(name).copy()
384                else:
385                    dest[0] = src[0]
386                    return dest
387
388        var_shape = self.get_var_shape(name)
389
390        if var_type_lower.startswith("double"):
391            if dest is None:
392                dest = np.empty(shape=var_shape, dtype=np.float64, order="C")
393            self._execute_function(
394                self.lib.get_value,
395                c_char_p(name.encode()),
396                byref(dest.ctypes.data_as(POINTER(c_double))),
397            )
398        elif var_type_lower.startswith("int"):
399            if dest is None:
400                dest = np.empty(shape=var_shape, dtype=np.int32, order="C")
401            self._execute_function(
402                self.lib.get_value,
403                c_char_p(name.encode()),
404                byref(dest.ctypes.data_as(POINTER(c_int))),
405            )
406        elif var_type_lower.startswith("string"):
407            if dest is None:
408                if var_shape[0] == 0:
409                    return np.empty((0,), "U1")
410                ilen = self.get_var_nbytes(name) // var_shape[0]
411                strtype = "<S" + str(ilen + 1)
412                dest = np.empty(var_shape[0], dtype=strtype, order="C")
413            self._execute_function(
414                self.lib.get_value,
415                c_char_p(name.encode()),
416                byref(dest.ctypes.data_as(POINTER(c_char))),
417            )
418            for i, x in enumerate(dest):
419                dest[i] = x.decode("ascii").strip()
420            return dest.astype(str)
421        else:
422            raise InputError(f"Unsupported value type {var_type!r}")
423
424        return dest

Get a copy of values of the given variable.

This is a getter for the model, used to access the model's current state. It returns a copy of a model variable, with the return type, size and rank dependent on the variable.

Parameters

name : str An input or output variable name, a CSDMS Standard Name. dest : ndarray A numpy array into which to place the values.

Returns

ndarray The same numpy array that was passed as an input buffer.

def get_value_ptr(self, name: str) -> numpy.ndarray[typing.Any, numpy.dtype[typing.Any]]:
426    def get_value_ptr(self, name: str) -> NDArray[Any]:
427        # first scalars
428        rank = self.get_var_rank(name)
429        if rank == 0:
430            return self.get_value_ptr_scalar(name)
431
432        var_type = self.get_var_type(name)
433        var_type_lower = var_type.lower()
434        shape_array = self.get_var_shape(name)
435
436        # convert shape array to python tuple
437        shape_tuple = tuple(np.trim_zeros(shape_array))
438        ndim = len(shape_tuple)
439
440        if var_type_lower.startswith("double"):
441            arraytype = np.ctypeslib.ndpointer(
442                dtype=np.float64, ndim=ndim, shape=shape_tuple, flags="C"
443            )
444        elif var_type_lower.startswith("float"):
445            arraytype = np.ctypeslib.ndpointer(
446                dtype=np.float32, ndim=ndim, shape=shape_tuple, flags="C"
447            )
448        elif var_type_lower.startswith("int"):
449            arraytype = np.ctypeslib.ndpointer(
450                dtype=np.int32, ndim=ndim, shape=shape_tuple, flags="C"
451            )
452        else:
453            raise InputError(f"Unsupported value type {var_type!r}")
454        values = arraytype()
455        self._execute_function(
456            self.lib.get_value_ptr,
457            c_char_p(name.encode()),
458            byref(values),
459            detail="for variable " + name,
460        )
461        return values.contents

Get a reference to values of the given variable.

This is a getter for the model, used to access the model's current state. It returns a reference to a model variable, with the return type, size and rank dependent on the variable.

Parameters

name : str An input or output variable name, a CSDMS Standard Name.

Returns

array_like A reference to a model variable.

def get_value_ptr_scalar(self, name: str) -> numpy.ndarray[typing.Any, numpy.dtype[typing.Any]]:
463    def get_value_ptr_scalar(self, name: str) -> NDArray[Any]:
464        var_type = self.get_var_type(name)
465        var_type_lower = var_type.lower()
466        if var_type_lower.startswith("double"):
467            arraytype = np.ctypeslib.ndpointer(
468                dtype=np.float64, ndim=1, shape=(1,), flags="C"
469            )
470        elif var_type_lower.startswith("float"):
471            arraytype = np.ctypeslib.ndpointer(
472                dtype=np.float32, ndim=1, shape=(1,), flags="C"
473            )
474        elif var_type_lower.startswith("int"):
475            arraytype = np.ctypeslib.ndpointer(
476                dtype=np.int32, ndim=1, shape=(1,), flags="C"
477            )
478        else:
479            raise InputError(f"Unsupported value type {var_type!r}")
480        values = arraytype()
481        self._execute_function(
482            self.lib.get_value_ptr,
483            c_char_p(name.encode()),
484            byref(values),
485            detail="for variable " + name,
486        )
487        return values.contents
def get_value_at_indices( self, name: str, dest: numpy.ndarray[typing.Any, numpy.dtype[typing.Any]], inds: numpy.ndarray[typing.Any, numpy.dtype[numpy.int32]]) -> numpy.ndarray[typing.Any, numpy.dtype[typing.Any]]:
489    def get_value_at_indices(
490        self, name: str, dest: NDArray[Any], inds: NDArray[np.int32]
491    ) -> NDArray[Any]:
492        raise NotImplementedError

Get values at particular indices.

Parameters

name : str An input or output variable name, a CSDMS Standard Name. dest : ndarray A numpy array into which to place the values. inds : array_like The indices into the variable array.

Returns

array_like Value of the model variable at the given location.

def set_value( self, name: str, values: numpy.ndarray[typing.Any, numpy.dtype[typing.Any]]) -> None:
494    def set_value(self, name: str, values: NDArray[Any]) -> None:
495        if not values.flags["C"]:
496            raise InputError("Array should have C layout")
497        var_type = self.get_var_type(name)
498        var_type_lower = var_type.lower()
499        if var_type_lower.startswith("double"):
500            if values.dtype != np.float64:
501                raise InputError("Array should have float64 elements")
502            self._execute_function(
503                self.lib.set_value,
504                c_char_p(name.encode()),
505                byref(values.ctypes.data_as(POINTER(c_double))),
506            )
507        elif var_type_lower.startswith("int"):
508            if values.dtype != np.int32:
509                raise InputError("Array should have int32 elements")
510            self._execute_function(
511                self.lib.set_value,
512                c_char_p(name.encode()),
513                byref(values.ctypes.data_as(POINTER(c_int))),
514            )
515        else:
516            raise InputError("Unsupported value type")

Specify a new value for a model variable.

This is the setter for the model, used to change the model's current state. It accepts, through src, a new value for a model variable, with the type, size and rank of src dependent on the variable.

Parameters

name : str An input or output variable name, a CSDMS Standard Name. src : array_like The new value for the specified variable.

def set_value_at_indices( self, name: str, inds: numpy.ndarray[typing.Any, numpy.dtype[typing.Any]], src: numpy.ndarray[typing.Any, numpy.dtype[typing.Any]]) -> None:
518    def set_value_at_indices(
519        self, name: str, inds: NDArray[Any], src: NDArray[Any]
520    ) -> None:
521        raise NotImplementedError

Specify a new value for a model variable at particular indices.

Parameters

name : str An input or output variable name, a CSDMS Standard Name. inds : array_like The indices into the variable array. src : array_like The new value for the specified variable.

def get_grid_rank(self, grid: int) -> int:
523    def get_grid_rank(self, grid: int) -> int:
524        grid_rank = c_int(0)
525        c_grid = c_int(grid)
526        self._execute_function(
527            self.lib.get_grid_rank,
528            byref(c_grid),
529            byref(grid_rank),
530        )
531        return grid_rank.value

Get number of dimensions of the computational grid.

Parameters

grid : int A grid identifier.

Returns

int Rank of the grid.

def get_grid_size(self, grid: int) -> int:
533    def get_grid_size(self, grid: int) -> int:
534        grid_size = c_int(0)
535        c_grid = c_int(grid)
536        self._execute_function(
537            self.lib.get_grid_size,
538            byref(c_grid),
539            byref(grid_size),
540        )
541        return grid_size.value

Get the total number of elements in the computational grid.

Parameters

grid : int A grid identifier.

Returns

int Size of the grid.

def get_grid_type(self, grid: int) -> str:
543    def get_grid_type(self, grid: int) -> str:
544        len_grid_type = self.get_constant_int("BMI_LENGRIDTYPE")
545        grid_type = create_string_buffer(len_grid_type)
546        c_grid = c_int(grid)
547        self._execute_function(
548            self.lib.get_grid_type,
549            byref(c_grid),
550            byref(grid_type),
551        )
552        return grid_type.value.decode()

Get the grid type as a string.

Parameters

grid : int A grid identifier.

Returns

str Type of grid as a string.

def get_grid_shape( self, grid: int, shape: numpy.ndarray[typing.Any, numpy.dtype[numpy.int32]]) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.int32]]:
554    def get_grid_shape(self, grid: int, shape: NDArray[np.int32]) -> NDArray[np.int32]:
555        c_grid = c_int(grid)
556        self._execute_function(
557            self.lib.get_grid_shape,
558            byref(c_grid),
559            c_void_p(shape.ctypes.data),
560        )
561        return shape

Get dimensions of the computational grid.

Parameters

grid : int A grid identifier. shape : ndarray of int, shape (ndim,) A numpy array into which to place the shape of the grid.

Returns

ndarray of int The input numpy array that holds the grid's shape.

def get_grid_spacing( self, grid: int, spacing: numpy.ndarray[typing.Any, numpy.dtype[numpy.int32]]) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.int32]]:
563    def get_grid_spacing(
564        self, grid: int, spacing: NDArray[np.int32]
565    ) -> NDArray[np.int32]:
566        raise NotImplementedError

Get distance between nodes of the computational grid.

Parameters

grid : int A grid identifier. spacing : ndarray of float, shape (ndim,) A numpy array to hold the spacing between grid rows and columns.

Returns

ndarray of float The input numpy array that holds the grid's spacing.

def get_grid_origin( self, grid: int, origin: numpy.ndarray[typing.Any, numpy.dtype[numpy.int32]]) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.int32]]:
568    def get_grid_origin(
569        self, grid: int, origin: NDArray[np.int32]
570    ) -> NDArray[np.int32]:
571        raise NotImplementedError

Get coordinates for the lower-left corner of the computational grid.

Parameters

grid : int A grid identifier. origin : ndarray of float, shape (ndim,) A numpy array to hold the coordinates of the lower-left corner of the grid.

Returns

ndarray of float The input numpy array that holds the coordinates of the grid's lower-left corner.

def get_grid_x( self, grid: int, x: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]:
573    def get_grid_x(self, grid: int, x: NDArray[np.float64]) -> NDArray[np.float64]:
574        c_grid = c_int(grid)
575        self._execute_function(
576            self.lib.get_grid_x,
577            byref(c_grid),
578            c_void_p(x.ctypes.data),
579        )
580        return x

Get coordinates of grid nodes in the x direction.

Parameters

grid : int A grid identifier. x : ndarray of float, shape (nrows,) A numpy array to hold the x-coordinates of the grid node columns.

Returns

ndarray of float The input numpy array that holds the grid's column x-coordinates.

def get_grid_y( self, grid: int, y: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]:
582    def get_grid_y(self, grid: int, y: NDArray[np.float64]) -> NDArray[np.float64]:
583        c_grid = c_int(grid)
584        self._execute_function(
585            self.lib.get_grid_y,
586            byref(c_grid),
587            c_void_p(y.ctypes.data),
588        )
589        return y

Get coordinates of grid nodes in the y direction.

Parameters

grid : int A grid identifier. y : ndarray of float, shape (ncols,) A numpy array to hold the y-coordinates of the grid node rows.

Returns

ndarray of float The input numpy array that holds the grid's row y-coordinates.

def get_grid_z( self, grid: int, z: numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]:
591    def get_grid_z(self, grid: int, z: NDArray[np.float64]) -> NDArray[np.float64]:
592        c_grid = c_int(grid)
593        self._execute_function(
594            self.lib.get_grid_z,
595            byref(c_grid),
596            c_void_p(z.ctypes.data),
597        )
598        return z

Get coordinates of grid nodes in the z direction.

Parameters

grid : int A grid identifier. z : ndarray of float, shape (nlayers,) A numpy array to hold the z-coordinates of the grid nodes layers.

Returns

ndarray of float The input numpy array that holds the grid's layer z-coordinates.

def get_grid_node_count(self, grid: int) -> int:
600    def get_grid_node_count(self, grid: int) -> int:
601        grid_node_count = c_int(0)
602        c_grid = c_int(grid)
603        self._execute_function(
604            self.lib.get_grid_node_count,
605            byref(c_grid),
606            byref(grid_node_count),
607        )
608        return grid_node_count.value

Get the number of nodes in the grid.

Parameters

grid : int A grid identifier.

Returns

int The total number of grid nodes.

def get_grid_edge_count(self, grid: int) -> int:
610    def get_grid_edge_count(self, grid: int) -> int:
611        raise NotImplementedError

Get the number of edges in the grid.

Parameters

grid : int A grid identifier.

Returns

int The total number of grid edges.

def get_grid_face_count(self, grid: int) -> int:
613    def get_grid_face_count(self, grid: int) -> int:
614        grid_face_count = c_int(0)
615        c_grid = c_int(grid)
616        self._execute_function(
617            self.lib.get_grid_face_count,
618            byref(c_grid),
619            byref(grid_face_count),
620        )
621        return grid_face_count.value

Get the number of faces in the grid.

Parameters

grid : int A grid identifier.

Returns

int The total number of grid faces.

def get_grid_edge_nodes( self, grid: int, edge_nodes: numpy.ndarray[typing.Any, numpy.dtype[numpy.int32]]) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.int32]]:
623    def get_grid_edge_nodes(
624        self, grid: int, edge_nodes: NDArray[np.int32]
625    ) -> NDArray[np.int32]:
626        raise NotImplementedError

Get the edge-node connectivity.

Parameters

grid : int A grid identifier. edge_nodes : ndarray of int, shape (2 x nnodes,) A numpy array to place the edge-node connectivity. For each edge, connectivity is given as node at edge tail, followed by node at edge head.

Returns

ndarray of int The input numpy array that holds the edge-node connectivity.

def get_grid_face_edges( self, grid: int, face_edges: numpy.ndarray[typing.Any, numpy.dtype[numpy.int32]]) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.int32]]:
628    def get_grid_face_edges(
629        self, grid: int, face_edges: NDArray[np.int32]
630    ) -> NDArray[np.int32]:
631        raise NotImplementedError

Get the face-edge connectivity.

Parameters

grid : int A grid identifier. face_edges : ndarray of int A numpy array to place the face-edge connectivity.

Returns

ndarray of int The input numpy array that holds the face-edge connectivity.

def get_grid_face_nodes( self, grid: int, face_nodes: numpy.ndarray[typing.Any, numpy.dtype[numpy.int32]]) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.int32]]:
633    def get_grid_face_nodes(
634        self, grid: int, face_nodes: NDArray[np.int32]
635    ) -> NDArray[np.int32]:
636        c_grid = c_int(grid)
637        self._execute_function(
638            self.lib.get_grid_face_nodes,
639            byref(c_grid),
640            c_void_p(face_nodes.ctypes.data),
641        )
642        return face_nodes

Get the face-node connectivity.

Parameters

grid : int A grid identifier. face_nodes : ndarray of int A numpy array to place the face-node connectivity. For each face, the nodes (listed in a counter-clockwise direction) that form the boundary of the face.

Returns

ndarray of int The input numpy array that holds the face-node connectivity.

def get_grid_nodes_per_face( self, grid: int, nodes_per_face: numpy.ndarray[typing.Any, numpy.dtype[numpy.int32]]) -> numpy.ndarray[typing.Any, numpy.dtype[numpy.int32]]:
644    def get_grid_nodes_per_face(
645        self, grid: int, nodes_per_face: NDArray[np.int32]
646    ) -> NDArray[np.int32]:
647        c_grid = c_int(grid)
648        self._execute_function(
649            self.lib.get_grid_nodes_per_face,
650            byref(c_grid),
651            c_void_p(nodes_per_face.ctypes.data),
652        )
653        return nodes_per_face

Get the number of nodes for each face.

Parameters

grid : int A grid identifier. nodes_per_face : ndarray of int, shape (nfaces,) A numpy array to place the number of nodes per face.

Returns

ndarray of int The input numpy array that holds the number of nodes per face.

def prepare_time_step(self, dt: float) -> None:
658    def prepare_time_step(self, dt: float) -> None:
659        with cd(self.working_directory):
660            c_dt = c_double(dt)
661            self._execute_function(self.lib.prepare_time_step, byref(c_dt))

Prepare a single time step.

Read data from input files and calculate the current time step length and the simulation time at the end of the current time step.

Parameters

dt : float Model time step length for the current time step.

def do_time_step(self) -> None:
663    def do_time_step(self) -> None:
664        with cd(self.working_directory):
665            self._execute_function(self.lib.do_time_step)

Perform a single time step.

Build and solve a time step to completion. This method encapsulates the prepare_solve, solve, and finalize_solve methods.

def finalize_time_step(self) -> None:
667    def finalize_time_step(self) -> None:
668        with cd(self.working_directory):
669            self._execute_function(self.lib.finalize_time_step)

Finalize the time step.

Write messages and output after model convergence has been achieved.

def get_subcomponent_count(self) -> int:
671    def get_subcomponent_count(self) -> int:
672        count = c_int(0)
673        self._execute_function(self.lib.get_subcomponent_count, byref(count))
674        return count.value

Get the number of components in the simulation.

For most applications, this number will be equal to 1.

Returns

int The number of components in the simulation.

def prepare_solve(self, component_id: int = 1) -> None:
676    def prepare_solve(self, component_id: int = 1) -> None:
677        cid = c_int(component_id)
678        with cd(self.working_directory):
679            self._execute_function(self.lib.prepare_solve, byref(cid))

Prepare for solving the system of equations.

This preparation mostly consists of advancing model states.

Parameters

component_id : int Component id number.

def solve(self, component_id: int = 1) -> bool:
681    def solve(self, component_id: int = 1) -> bool:
682        cid = c_int(component_id)
683        has_converged = c_int(0)
684        with cd(self.working_directory):
685            self._execute_function(self.lib.solve, byref(cid), byref(has_converged))
686        return has_converged.value == 1

Build and solve the linear system of equations.

Formulate the system of equations for this outer (Picard) iteration and solve. New data used when formulating the system of equations can be injected prior to solve(). Before calling this, a matching call to prepare_solve() should be performed.

Parameters

component_id : int Component id number.

Returns

bool Boolean indicating if convergence has been achieved.

def finalize_solve(self, component_id: int = 1) -> None:
688    def finalize_solve(self, component_id: int = 1) -> None:
689        cid = c_int(component_id)
690
691        with cd(self.working_directory):
692            self._execute_function(self.lib.finalize_solve, byref(cid))

Finalize the model solve for the current time step.

Finalize model variables prior to calling finalize_time_step(). This method should always be called after calls to prepare_solve() and solve()

Parameters

component_id : int Component id number.

def get_var_address( self, var_name: str, component_name: str, subcomponent_name: str = '') -> str:
694    def get_var_address(
695        self, var_name: str, component_name: str, subcomponent_name: str = ""
696    ) -> str:
697        len_var_address = self.get_constant_int("BMI_LENVARADDRESS")
698        var_address = create_string_buffer(len_var_address)
699        self._execute_function(
700            self.lib.get_var_address,
701            c_char_p(component_name.upper().encode()),
702            c_char_p(subcomponent_name.upper().encode()),
703            c_char_p(var_name.upper().encode()),
704            byref(var_address),
705        )
706
707        return var_address.value.decode()

Get the address of a given variable

Parameters

var_name : str The variable name component_name : str The name of the component subcomponent_name : str, optional If applicable the name of the subcomponent, by default ""

Returns

str The variable address