Index: wflow-py/wflow/bmi.py =================================================================== diff -u --- wflow-py/wflow/bmi.py (revision 0) +++ wflow-py/wflow/bmi.py (revision 8fc4e26c9cf424be10a5204df5d8f4b86a21c5bb) @@ -0,0 +1,395 @@ +#! /usr/bin/env python + +""" +2015-03-06 +Deltares +Arno Kockx + +To create this file the original version of the CSDMS BMI Python Language Binding (file bmi.py) from https://github.com/csdms/bmi-python/blob/master/bmi/bmi.py was extended so that it can be used in OpenDA. + +The following changes were made: +1. All grid information functions have been merged into the Bmi class, so that different variables within the same model can have different grids. +2. Added function get_grid_type to get the grid type for a given variable. +3. Added function save_state to ask the model to save its state to disk. +4. Added comments. Where the original version of the CSDMS BMI Python Language Binding was ambiguous, the information from http://csdms.colorado.edu/wiki/BMI_Description and common sense were used to fill in most of the gaps. +""" + +from abc import ABCMeta, abstractmethod + + +class BmiGridType(object): + """ + Enumeration with grid types. + """ + + UNKNOWN = 0 + UNIFORM = 1 + RECTILINEAR = 2 + STRUCTURED = 3 + UNSTRUCTURED = 4 + + +class Bmi(object): + """ + Interface (abstract base class) for a model that implements the CSDMS BMI (Basic Model Interface). + """ + + __metaclass__ = ABCMeta + + """ + Model Control Functions + """ + + @abstractmethod + def initialize(self, filename): + """ + Initialize the model. + + Input parameters: + File filename: path and name of the configuration file for the model. + """ + raise NotImplementedError + + @abstractmethod + def update(self): + """ + Update the model to the next time step. + """ + raise NotImplementedError + + @abstractmethod + def update_until(self, time): + """ + Update the model until the given time. + + Input parameters: + double time: time in the units and epoch returned by the function get_time_units. + """ + raise NotImplementedError + + @abstractmethod + def update_frac(self, time_frac): + """ + ??? + + Input parameters: + double time_frac: ??? + """ + raise NotImplementedError + + @abstractmethod + def save_state(self, destination_directory): + """ + Ask the model to write its complete internal current state to one or more state files in the given directory. + Afterwards the given directory should only contain the state files and nothing else. + + Input parameters: + File destination_directory: the directory in which the state files should be written. + """ + raise NotImplementedError + + @abstractmethod + def finalize(self): + """ + Finalize the model. + """ + raise NotImplementedError + + """ + Model Information Functions + """ + + @abstractmethod + def get_component_name(self): + """ + Return value: + String: identifier of the model. + """ + raise NotImplementedError + + @abstractmethod + def get_input_var_names(self): + """ + Return value: + List of String objects: identifiers of all input variables of the model. + """ + raise NotImplementedError + + @abstractmethod + def get_output_var_names(self): + """ + Return value: + List of String objects: identifiers of all output variables of the model. + """ + raise NotImplementedError + + """ + Variable Information Functions + """ + + @abstractmethod + def get_var_type(self, long_var_name): + """ + Input parameters: + String long_var_name: identifier of a variable in the model. + + Return value: + String: data type of the values of the given variable, e.g. Numpy datatype string. + """ + raise NotImplementedError + + @abstractmethod + def get_var_units(self, long_var_name): + """ + Input parameters: + String long_var_name: identifier of a variable in the model. + + Return value: + String: unit of the values of the given variable. Return a string formatted using the UDUNITS standard from Unidata. + """ + raise NotImplementedError + + @abstractmethod + def get_var_rank(self, long_var_name): + """ + Input parameters: + String long_var_name: identifier of a variable in the model. + + Return value: + Integer: number of dimensions of the given variable. + """ + raise NotImplementedError + + @abstractmethod + def get_var_size(self, long_var_name): + """ + Input parameters: + String long_var_name: identifier of a variable in the model. + + Return value: + Integer: total number of values contained in the given variable, e.g. gridCellCount. + """ + raise NotImplementedError + + @abstractmethod + def get_var_nbytes(self, long_var_name): + """ + Input parameters: + String long_var_name: identifier of a variable in the model. + + Return value: + ???: ??? + """ + raise NotImplementedError + + @abstractmethod + def get_start_time(self): + """ + Return value: + double: start time of the model in the units and epoch returned by the function get_time_units. + """ + raise NotImplementedError + + @abstractmethod + def get_current_time(self): + """ + Return value: + double: current time of the model in the units and epoch returned by the function get_time_units. + """ + raise NotImplementedError + + @abstractmethod + def get_end_time(self): + """ + Return value: + double: end time of the model in the units and epoch returned by the function get_time_units. + """ + raise NotImplementedError + + @abstractmethod + def get_time_step(self): + """ + Return value: + double: duration of one time step of the model in the units returned by the function get_time_units. + """ + raise NotImplementedError + + @abstractmethod + def get_time_units(self): + """ + Return value: + String: unit and epoch of time in the model. Return a string formatted using the UDUNITS standard from Unidata. + """ + raise NotImplementedError + + """ + Variable Getter and Setter Functions + """ + + @abstractmethod + def get_value(self, long_var_name): + """ + Input parameters: + String long_var_name: identifier of a variable in the model. + + Return value: + Numpy array of values in the data type returned by the function get_var_type: all values of the given variable. + """ + raise NotImplementedError + + @abstractmethod + def get_value_at_indices(self, long_var_name, inds): + """ + Input parameters: + String long_var_name: identifier of a variable in the model. + List of Lists of integers inds: each nested List contains one index for each dimension of the given variable, + i.e. each nested List indicates one element in the multi-dimensional variable array, + e.g. [[0, 0, 0], [0, 0, 1], [0, 15, 19], [0, 15, 20], [0, 15, 21]] indicates 5 elements in a 3D grid. + + Return value: + Numpy array of values in the data type returned by the function get_var_type: one value for each of the indicated elements. + """ + raise NotImplementedError + + @abstractmethod + def set_value(self, long_var_name, src): + """ + Input parameters: + String long_var_name: identifier of a variable in the model. + Numpy array of values src: all values to set for the given variable. + """ + raise NotImplementedError + + @abstractmethod + def set_value_at_indices(self, long_var_name, inds, src): + """ + Input parameters: + String long_var_name: identifier of a variable in the model. + List of Lists of integers inds: each nested List contains one index for each dimension of the given variable, + i.e. each nested List indicates one element in the multi-dimensional variable array, + e.g. [[0, 0], [0, 1], [15, 19], [15, 20], [15, 21]] indicates 5 elements in a 2D grid. + Numpy array of values src: one value to set for each of the indicated elements. + """ + raise NotImplementedError + + """ + Grid Information Functions + """ + + @abstractmethod + def get_grid_type(self, long_var_name): + """ + Input parameters: + String long_var_name: identifier of a variable in the model. + + Return value: + BmiGridType type of the grid geometry of the given variable. + """ + raise NotImplementedError + + @abstractmethod + def get_grid_shape(self, long_var_name): + """ + Only return something for variables with a uniform, rectilinear or structured grid. Otherwise raise ValueError. + + Input parameters: + String long_var_name: identifier of a variable in the model. + + Return value: + List of integers: the sizes of the dimensions of the given variable, e.g. [500, 400] for a 2D grid with 500x400 grid cells. + """ + raise NotImplementedError + + @abstractmethod + def get_grid_spacing(self, long_var_name): + """ + Only return something for variables with a uniform grid. Otherwise raise ValueError. + + Input parameters: + String long_var_name: identifier of a variable in the model. + + Return value: + List of doubles: the size of a grid cell for each of the dimensions of the given variable, e.g. [width, height] for a 2D grid cell. + """ + raise NotImplementedError + + @abstractmethod + def get_grid_origin(self, long_var_name): + """ + Only return something for variables with a uniform grid. Otherwise raise ValueError. + + Input parameters: + String long_var_name: identifier of a variable in the model. + + Return value: + List of doubles: the coordinate of the grid origin for each of the dimensions of the given variable. For a 2D grid this must be the lower left corner of the grid. + """ + raise NotImplementedError + + @abstractmethod + def get_grid_x(self, long_var_name): + """ + Only return something for variables with a rectilinear, structured or unstructured grid. Otherwise raise ValueError. + + Input parameters: + String long_var_name: identifier of a variable in the model. + + Return value: + Numpy array of doubles: x coordinate of grid cell center for each grid cell, in the same order as the values returned by function get_value. + For a rectilinear grid: x coordinate of column center for each column. + """ + raise NotImplementedError + + @abstractmethod + def get_grid_y(self, long_var_name): + """ + Only return something for variables with a rectilinear, structured or unstructured grid. Otherwise raise ValueError. + + Input parameters: + String long_var_name: identifier of a variable in the model. + + Return value: + Numpy array of doubles: y coordinate of grid cell center for each grid cell, in the same order as the values returned by function get_value. + For a rectilinear grid: y coordinate of row center for each row. + """ + raise NotImplementedError + + @abstractmethod + def get_grid_z(self, long_var_name): + """ + Only return something for variables with a rectilinear, structured or unstructured grid. Otherwise raise ValueError. + + Input parameters: + String long_var_name: identifier of a variable in the model. + + Return value: + Numpy array of doubles: z coordinate of grid cell center for each grid cell, in the same order as the values returned by function get_value. + For a rectilinear grid: z coordinate of layer center for each layer. + """ + raise NotImplementedError + + @abstractmethod + def get_grid_connectivity(self, long_var_name): + """ + Only return something for variables with an unstructured grid. Otherwise raise ValueError. + + Input parameters: + String long_var_name: identifier of a variable in the model. + + Return value: + ??? + """ + raise NotImplementedError + + @abstractmethod + def get_grid_offset(self, long_var_name): + """ + Only return something for variables with an unstructured grid. Otherwise raise ValueError. + + Input parameters: + String long_var_name: identifier of a variable in the model. + + Return value: + ??? + """ + raise NotImplementedError Index: wflow-py/wflow/wflow_bmi.py =================================================================== diff -u -r72e2f53fd1c07133b9c15f3353d95b7f5b75fa32 -r8fc4e26c9cf424be10a5204df5d8f4b86a21c5bb --- wflow-py/wflow/wflow_bmi.py (.../wflow_bmi.py) (revision 72e2f53fd1c07133b9c15f3353d95b7f5b75fa32) +++ wflow-py/wflow/wflow_bmi.py (.../wflow_bmi.py) (revision 8fc4e26c9cf424be10a5204df5d8f4b86a21c5bb) @@ -3,11 +3,13 @@ import os import logging +import wflow.bmi as bmi + #TODO: Set log level also ini to be able to make quiet or non-quiet runs #TODO: set re-init in the ini file to be able to make cold start runs #TODO: Rework framework to get rid of max timesteps shit -class wflowbmi(object): +class wflowbmi_ligth(object): def initialize(self, configfile=None,loglevel=logging.DEBUG): """ @@ -216,9 +218,9 @@ -class wflowbmi_csdms(object): +class wflowbmi_csdms(bmi.Bmi): - def initialize(self, configfile=None,loglevel=logging.DEBUG): + def initialize(self, filename,loglevel=logging.DEBUG): """ Assumptions for now: - the configfile wih be a full path @@ -227,8 +229,8 @@ retval = 0 self.currenttimestep = 1 wflow_cloneMap = 'wflow_subcatch.map' - datadir = os.path.dirname(configfile) - inifile = os.path.basename(configfile) + datadir = os.path.dirname(filename) + inifile = os.path.basename(filename) runid = "run_default" # The current pcraster framework needs a max number of timesteps :-( # This cannot be avoided at the moment (needs a rework) @@ -237,130 +239,307 @@ # Get name of module from ini file name maxNrSteps = 10000 - if "wflow_sbm.ini" in configfile: + if "wflow_sbm.ini" in filename: import wflow_sbm as wf - elif "wflow_hbv.ini" in configfile: + elif "wflow_hbv.ini" in filename: import wflow_sbm as wf - elif "wflow_routing.ini" in configfile: + elif "wflow_routing.ini" in filename: import wflow_routing as wf else: raise NotImplementedError - myModel = wf.WflowModel(wflow_cloneMap, datadir, runid, inifile) + self.myModel = wf.WflowModel(wflow_cloneMap, datadir, runid, inifile) - self.dynModel = wf.wf_DynamicFramework(myModel, maxNrSteps, firstTimestep = 1) - self.dynModel.createRunId(NoOverWrite=0,level=loglevel,model=os.path.basename(configfile)) + self.dynModel = wf.wf_DynamicFramework(self.myModel, maxNrSteps, firstTimestep = 1) + self.dynModel.createRunId(NoOverWrite=0,level=loglevel,model=os.path.basename(filename)) self.dynModel._runInitial() self.dynModel._runResume() return retval - def finalize(self): + def update(self): """ - Shutdown the library and clean up the model. + Propagate the model to the next timestep + """ + self.dynModel._runDynamic(self.currenttimestep, self.currenttimestep) + self.currenttimestep = self.currenttimestep + 1 + + + + def update_until(self, time): """ - self.dynModel._runSuspend() - self.dynModel._wf_shutdown() + Update the model until the given time. + :var double time: time in the units and epoch returned by the function get_time_units. + """ + timespan = time - self.get_current_time() + nrsteps = int(timespan/self.dynModel.timestepsecs) + self.dynModel._runDynamic(self.currenttimestep, self.currenttimestep + nrsteps -1) + self.currenttimestep = self.currenttimestep + nrsteps - def update(self, dt): + def update_frac(self, time_frac): """ - Return type string, compatible with numpy. - Propagate the model one timestep? + No idea what to do with this one..... + + Input parameters: + double time_frac: ??? """ - #curstep = self.dynModel.wf_ - if dt == -1: - self.dynModel._runDynamic(self.currenttimestep, self.currenttimestep) - self.currenttimestep = self.currenttimestep + 1 - else: - nrsteps = int(dt/self.dynModel.timestepsecs) - self.dynModel._runDynamic(self.currenttimestep, self.currenttimestep + nrsteps -1) - self.currenttimestep = self.currenttimestep + nrsteps + raise NotImplementedError + def save_state(self, destination_directory): + """ + Ask the model to write its complete internal current state to one or more state files in the given directory. + Afterwards the given directory should only contain the state files and nothing else. - def get_time_units(self): + :var destination_directory: the directory in which the state files should be written. """ - :return: time units as a CF convention string - (http://cfconventions.org/Data/cf-conventions/cf-conventions-1.7/build/cf-conventions.html#time-coordinate) + self.dynModel.wf_suspend(destination_directory) + def finalize(self): """ + Shutdown the library and clean up the model. + Uses the default (model configured) state location to also save states. - return self.dynModel.wf_supplyEpoch() + """ + self.dynModel._runSuspend() + self.dynModel._wf_shutdown() - def get_var_count(self): + def get_component_name(self): """ - Return number of variables + :return identifier of the model: """ - return self.dynModel.wf_supplyVariableCount() + return self.dynModel.name - def get_var_name(self, i): + + def get_input_var_names(self): """ - Return variable name + :return List of String objects: identifiers of all input variables of the model: """ + namesroles = self.dynModel.wf_supplyVariableNamesAndRoles() - names = self.dynModel.wf_supplyVariableNames() - return names[i] + # input variable are all forcing variables and all state variables + #0 = input (to the model) + #1 = is output (from the model) + #2 = input/output (state information) + #3 = model parameter + inames = [] + for varrol in namesroles: + if varrol[1] == 0 or varrol[1] == 2: + inames.append(varrol[0]) + return inames - def get_var_type(self, name): + def get_output_var_names(self): """ - Return type string, compatible with numpy. + :return List of String objects: identifiers of all output variables of the model: """ - npmap = self.dynModel.wf_supplyMapAsNumpy(name) + namesroles = self.dynModel.wf_supplyVariableNamesAndRoles() + # input variable are all forcing variables and all state variables + #0 = input (to the model) + #1 = is output (from the model) + #2 = input/output (state information) + #3 = model parameter + inames = [] + + for varrol in namesroles: + if varrol[1] == 1 or varrol[1] == 2: + inames.append(varrol[0]) + return inames + + + def get_var_type(self, long_var_name): + """ + :return type string, compatible with numpy: + """ + npmap = self.dynModel.wf_supplyMapAsNumpy(long_var_name) + return npmap.dtype - def get_var_rank(self, name): + def get_var_rank(self, long_var_name): """ - Return array rank or 0 for scalar. + :var String long_var_name: identifier of a variable in the model: + + :return array rank or 0 for scalar (number of dimensions): """ - npmap = self.dynModel.wf_supplyMapAsNumpy(name) + npmap = self.dynModel.wf_supplyMapAsNumpy(long_var_name) return len(npmap.shape) + def get_var_size(self, long_var_name): + """ + :var String long_var_name: identifier of a variable in the model: - def get_var_shape(self, name): + :return total number of values contained in the given variable (number of elements in map): """ - Return shape of the array. + npmap = self.dynModel.wf_supplyMapAsNumpy(long_var_name) + + return npmap.size + + def get_var_nbytes(self, long_var_name): """ - npmap = self.dynModel.wf_supplyMapAsNumpy(name) + :var String long_var_name: identifier of a variable in the model: - return npmap.shape + :return total number of bytes contained in the given variable (number of elements * bytes per element): + """ + npmap = self.dynModel.wf_supplyMapAsNumpy(long_var_name) + return npmap.size * npmap.itemsize + def get_start_time(self): """ - returns start time + :return start time in the units and epoch returned by the function get_time_units: """ return self.dynModel.wf_supplyStartTime() + def get_current_time(self): + """ + :return current time of simulation n the units and epoch returned by the function get_time_units: + """ + return self.dynModel.wf_supplyCurrentTime() + def get_end_time(self): """ - returns end time of simulation + :return end time of simulation n the units and epoch returned by the function get_time_units: """ return self.dynModel.wf_supplyEndTime() + def get_time_step(self): + """ + :return duration of one time step of the model in the units returned by the function get_time_units: + """ + return self.dynModel.timestepsecs - def get_current_time(self): + def get_time_units(self): """ - returns current time of simulation + :return: time units as a CF convention string + (http://cfconventions.org/Data/cf-conventions/cf-conventions-1.7/build/cf-conventions.html#time-coordinate) """ - return self.dynModel.wf_supplyCurrentTime() - def get_var(self, name): + return self.dynModel.wf_supplyEpoch() + + def get_value(self, long_var_name): """ - Return an nd array from model library + :var long_var_name name of the variable + :return an np array of long_var_name """ - return self.dynModel.wf_supplyMapAsNumpy(name) + return self.dynModel.wf_supplyMapAsNumpy(long_var_name) + def get_value_at_indices(self, long_var_name, inds): + """ + :var long_var_name: identifier of a variable in the model: + :var List of list each tuple contains one index for each dimension of the given variable, i.e. each tuple indicates one element in the multi-dimensional variable array: + + :return numpy array of values in the data type returned by the function get_var_type. + """ + npmap = self.dynModel.wf_supplyMapAsNumpy(long_var_name) + + return npmap[inds] + + def set_value_at_indices(self, long_var_name, inds, src): + """ + :var long_var_name: identifier of a variable in the model: + :var inds: List of Lists of integers inds each nested List contains one index for each dimension of the given variable, + i.e. each nested List indicates one element in the multi-dimensional variable array, + e.g. [[0, 0], [0, 1], [15, 19], [15, 20], [15, 21]] indicates 5 elements in a 2D grid.: + :var src: Numpy array of values. one value to set for each of the indicated elements: + """ + + npmap = self.dynModel.wf_supplyMapAsNumpy(long_var_name) + npmap[inds] = src + self.dynModel.wf_setValuesAsNumpy(long_var_name,npmap) + + + def get_grid_type(self, long_var_name): + """ + :var String long_var_name: identifier of a variable in the model. + + :return BmiGridType type of the grid geometry of the given variable: + """ + + ret=BmiGridType() + + return ret.UNIFORM + + + def get_grid_shape(self, long_var_name): + """ + Only return something for variables with a uniform, rectilinear or structured grid. Otherwise raise ValueError. + + :var long_var_name: identifier of a variable in the model. + + :return List of integers: the sizes of the dimensions of the given variable, e.g. [500, 400] for a 2D grid with 500x400 grid cells. + """ + + dim = self.dynModel.wf_supplyGridDim() + #[ Xul, Yul, xsize, ysize, rows, cols] + + return dim[4,5] + + def get_grid_origin(self, long_var_name): + """ + Only return something for variables with a uniform grid. Otherwise raise ValueError. + + Input parameters: + String long_var_name: identifier of a variable in the model. + + Return value: + List of doubles: the coordinate of the grid origin for each of the dimensions of the given variable. For a 2D grid this must be the lower left corner of the grid. + """ + raise NotImplementedError + + def get_grid_x(self, long_var_name): + """ + Only return something for variables with a rectilinear, structured or unstructured grid. Otherwise raise ValueError. + + Input parameters: + String long_var_name: identifier of a variable in the model. + + Return value: + Numpy array of doubles: x coordinate of grid cell center for each grid cell, in the same order as the values returned by function get_value. + For a rectilinear grid: x coordinate of column center for each column. + """ + return self.myModel.xcoordinate(1) + + +XXXXXXXXXXXXX HIer gebleven + def get_var_count(self): + """ + Return number of variables + """ + return self.dynModel.wf_supplyVariableCount() + + + def get_var_name(self, i): + """ + Return variable name + """ + + names = self.dynModel.wf_supplyVariableNames() + return names[i] + + + def get_var_shape(self, name): + """ + Return shape of the array. + """ + npmap = self.dynModel.wf_supplyMapAsNumpy(name) + + return npmap.shape + + + + + def set_var(self, name, var): """ Set the variable name with the values of var @@ -404,50 +583,12 @@ tmp.flat[index] = var self.set_var(name, name, tmp) - def get_component_name(self): - raise NotImplementedError - def get_input_var_names(self): - raise NotImplementedError - def get_output_var_names(self): - raise NotImplementedError - - def get_var_units(self, long_var_name): - raise NotImplementedError - - def get_var_size(self, long_var_name): - raise NotImplementedError - - def get_var_nbytes(self, long_var_name): - raise NotImplementedError - - def get_time_step(self): - raise NotImplementedError - - def get_value(self, long_var_name): - raise NotImplementedError - - def get_value_at_indices(self, long_var_name, inds): - raise NotImplementedError - - def set_value(self, long_var_name, src): - raise NotImplementedError - - def set_value_at_indices(self, long_var_name, inds, src): - raise NotImplementedError - class BmiGridType(object): UNKNOWN = 0 UNIFORM = 1 RECTILINEAR = 2 STRUCTURED = 3 UNSTRUCTURED = 4 -class BmiRaster(wflowbmi_csdms): - def get_grid_shape(self, long_var_name): - raise NotImplementedError - def get_grid_spacing(self, long_var_name): - raise NotImplementedError - def get_grid_origin(self, long_var_name): - raise NotImplementedError \ No newline at end of file