コード例 #1
0
class WFL(GBmi):
    """
    csdms BMI implementation of the WFLOW BMI adaptor for GLOFRIM.
    """
    _name = 'WFL'
    _long_name = 'wflow'
    _version = ''
    _var_units = {}
    _input_var_names = []
    _output_var_names = []
    _area_var_name = 'csize'
    _timeunit = 'seconds'

    def __init__(self, loglevel=logging.INFO, logger=None):
        # import original PCR bmi
        import wflow.wflow_bmi as _bmi
        self._bmi = _bmi.wflowbmi_csdms()
        if logger:
            self.logger = logger.getChild(self._name)
        else:
            self.logger = setlogger(None, self._name, thelevel=loglevel)
        self._loglevel = loglevel
        self.initialized = False
        self.grid = None

    """
    Model Control Functions
    """

    def initialize_config(self, config_fn):
        # config settings
        if self.initialized:
            raise Warning(
                "model already initialized, it's therefore not longer possible to initialize the config"
            )
        self._config_fn = abspath(config_fn)
        self.logger.info('Read ini at {}'.format(self._config_fn))
        self._bmi.initialize_config(str(self._config_fn),
                                    loglevel=self._loglevel)
        self._config = self._bmi.config
        self._datefmt = "%Y-%m-%d %H:%M:%S"
        if self.get_attribute_value('run:runlengthdetermination') == 'steps':
            raise Warning(
                'set WFLOW run:runlengthdetermination to "intervals" in order to use the same time convention as in GLOFRIM'
            )
        # model time
        self._dt = self.get_time_step()
        self._startTime = self.get_start_time()
        self._endTime = self.get_end_time()
        self._t = self._startTime
        # model files
        self.logger.info('Config initialized')

        namesroles = self._bmi.dynModel.wf_supplyVariableNamesAndRoles()
        for n, r, u in namesroles:
            if r == 0 or r == 3:  #input  or parameter
                self._input_var_names.append(n)
            elif r == 1 or r == 2:  # output or state
                self._output_var_names.append(n)
            self._var_units[n] = u

    def initialize_model(self, **kwargs):
        if not hasattr(self, '_config_fn'):
            raise Warning('run initialize_config before initialize_model')
        self._bmi.initialize_model()
        self.initialized = True
        self.logger.info('Model initialized')
        # reset model time to make sure it is consistent with the model
        self._dt = self.get_time_step()
        self._startTime = self.get_start_time()
        self._endTime = self.get_end_time()
        self._t = self._startTime

    def initialize(self, config_fn):
        self.initialize_config(config_fn)
        self.initialize_model()

    def update(self, dt=None):
        # dt in seconds. if not given model timestep is used
        if self._t >= self._endTime:
            raise Exception("endTime already reached, model not updated")
        if (dt is not None) and (dt != self._dt.total_seconds()):
            dt = timedelta(seconds=dt)
            if not glib.check_dts_divmod(dt, self._dt):
                msg = "Invalid value for dt in comparison to model dt. Make sure a whole number of model timesteps ({}) fit in the given dt ({})"
                raise ValueError(msg.format(self._dt, dt))
            tt = timedelta(0)
            while tt < dt:  # loop required ad self._bmi.update(dt) does not work
                self._bmi.update()
                tt += self._dt
        else:
            self._bmi.update()
        self._t = self.get_current_time()
        self.logger.info('updated model to datetime {}'.format(
            self._t.strftime("%Y-%m-%d %H:%M")))

    def update_until(self, t, dt=None):
        if (t < self._t) or t > self._endTime:
            raise Exception(
                "wrong time input: smaller than model time or larger than endTime"
            )
        while self._t < t:
            self.update(dt=dt)

    def spinup(self):
        """PCR specific spinup function"""
        raise NotImplementedError()

    def finalize(self):
        self.logger.info('finalize bmi. Close logger.')
        self._bmi.finalize()
        closelogger(self.logger)

    """
    Variable Information Functions
    """

    def get_start_time(self):
        # if self.initialized:
        #     startTime = datetime.utcfromtimestamp(self._bmi.get_start_time())
        # else:
        #     startTime = self.get_attribute_value('run:starttime')
        #     startTime = datetime.strptime(startTime, self._datefmt)
        startTime = datetime.utcfromtimestamp(self._bmi.get_start_time())
        self._startTime = startTime
        return self._startTime

    def get_current_time(self):
        if self.initialized:
            #
            return datetime.utcfromtimestamp(self._bmi.get_current_time())
        else:
            return self.get_start_time()

    def get_end_time(self):
        # if self.initialized:
        #     endTime = datetime.utcfromtimestamp(self._bmi.get_end_time())
        # else:
        #     endTime = self.get_attribute_value('run:endtime')
        #     endTime = datetime.strptime(endTime, self._datefmt)
        endTime = datetime.utcfromtimestamp(self._bmi.get_end_time())
        self._endTime = endTime
        return self._endTime

    def get_time_step(self):
        if not hasattr(self, '_dt'):
            self._dt = timedelta(
                **{self.get_time_units(): self._bmi.get_time_step()})
        return self._dt

    def get_time_units(self):
        return self._timeunit

    """
    Variable Getter and Setter Functions
    """

    def get_value(self, long_var_name, fill_value=-999, **kwargs):
        # additional fill_value argument required to translate pcr maps to numpy arrays
        # NOTE that wflow flips the arrays upside down in the 'wf_supplyMapAsNumpy' function that is called by the bmi.
        # TODO: check if we can improve the wflow_bmi and change this
        var = np.asarray(self._bmi.get_value(
            long_var_name))[::-1, :]  # flip array back to N->S
        var = np.where(var == fill_value, np.nan, var)
        return var

    def get_value_at_indices(self,
                             long_var_name,
                             inds,
                             fill_value=-999,
                             **kwargs):
        return self.get_value(long_var_name, fill_value=fill_value,
                              **kwargs).flat[inds]

    def set_value(self, long_var_name, src, fill_value=-999):
        src = np.where(np.isnan(src), fill_value,
                       src).astype(self.get_var_type(long_var_name))
        # we need to flip the data again! see note in get_value command
        self._bmi.set_value(long_var_name, src[::-1, :])

    def set_value_at_indices(self, long_var_name, inds, src, fill_value=-999):
        val = self.get_value(long_var_name, fill_value=fill_value)
        val.flat[inds] = src
        self.set_value(long_var_name, val)

    """
    Grid Information Functions
    """

    def get_grid(self, mapdir=None):
        if not hasattr(self, 'grid') or (self.grid is None):
            if mapdir is None:
                mapdir = join(dirname(self._config_fn), 'staticmaps')
            # ldd is used for coupling to routing / flood model
            _ldd_fn = join(mapdir, 'wflow_ldd.map')
            if not isfile(_ldd_fn): raise IOError('ldd file not found')
            # landmask used for masking out coordinates outside mask
            _lm_fn = join(mapdir, 'wflow_subcatch.map')
            if not isfile(_lm_fn): raise IOError('subcatch file not found')
            self.logger.info('Getting rgrid info based on {}'.format(
                basename(_lm_fn)))
            with rasterio.open(_lm_fn, 'r') as ds:
                # TODO: change transform instead of flipping the grid every time ..
                self.grid = RGrid(ds.transform,
                                  ds.height,
                                  ds.width,
                                  crs=ds.crs,
                                  mask=ds.read(1) >= 1)
            # river is used for the 1D cells
            _riv_fn = join(mapdir, 'wflow_river.map')
            if isfile(_riv_fn):
                with rasterio.open(_riv_fn, 'r') as ds:
                    row, col = np.where(ds.read(1) == 1)
                    x, y = self.grid.xy(row=row, col=col)
                    inds = self.grid.ravel_multi_index(row, col)
                self.grid.set_1d(nodes=np.array(zip(x, y)),
                                 links=None,
                                 inds=inds)
            # read file with pcr readmap
            self.logger.info('Getting drainage direction from {}'.format(
                basename(_ldd_fn)))
            self.grid.set_dd(_ldd_fn, ddtype='ldd')
        return self.grid

    """
    set and get attribute / config 
    """

    def set_start_time(self, start_time):
        if isinstance(start_time, str):
            start_time = datetime.strptime(start_time, self._datefmt)
        t = (start_time - datetime.utcfromtimestamp(0))
        self._startTime = start_time
        self._t = start_time
        self._bmi.set_start_time(t.days * 86400 + t.seconds)

    def set_end_time(self, end_time):
        if isinstance(end_time, str):
            end_time = datetime.strptime(end_time, self._datefmt)
        t = (end_time - datetime.utcfromtimestamp(0))
        self._endTime = end_time
        self._bmi.set_end_time(t.days * 86400 + t.seconds)

    def set_out_dir(self, out_dir):
        # NOTE this needs te be set somewhere pre initialize config
        # the file setup is hardcoded in wflow
        pass

    def get_attribute_names(self):
        glib.configcheck(self, self.logger)
        return glib.configattr(self._config)

    def get_attribute_value(self, attribute_name):
        glib.configcheck(self, self.logger)
        self.logger.debug("get_attribute_value: " + attribute_name)
        return self._bmi.get_attribute_value(attribute_name)

    def set_attribute_value(self, attribute_name, attribute_value):
        glib.configcheck(self, self.logger)
        self.logger.info("set_attribute_value {} -> {} ".format(
            attribute_name, attribute_value))
        # update config
        glib.configset(self._config, attribute_name, attribute_value)
        # set wfl config
        return self._bmi.set_attribute_value(attribute_name, attribute_value)

    def write_config(self):
        """write adapted config to file. just before initializing
        only for models which do not allow for direct access to model config via bmi"""
        glib.write_config(self, self._config, self._config_fn, self.logger)
コード例 #2
0
class LFP(GBmi):
    """
    Glofrim implementation of the LFP BMI adaptor.
    """
    _name = 'LFP'
    _long_name = 'LISFlood-FP'
    _version = '5.9'
    _var_units = {'SGCQin': 'm3/s', 'dA': 'm2', 'H': 'm'}
    _input_var_names = ['SGCQin', 'dA']
    _output_var_names = ['SGCQin', 'H']
    _area_var_name = 'dA'
    _timeunit = 'seconds'

    def __init__(self, engine, loglevel=logging.INFO, logger=None):
        self._bmi = _bmi(engine=engine)
        if logger:
            self.logger = logger.getChild(self._name)
        else:
            self.logger = setlogger(None, self._name, thelevel=loglevel)
        self.initialized = False
        self.grid = None

    """
    Model Control Functions
    """

    def initialize_config(self, config_fn):
        if self.initialized:
            raise Warning(
                "model already initialized, it's therefore not longer possible to initialize the config"
            )
        # config settings
        self._config_fn = abspath(config_fn)
        self._config = glib.configread(self._config_fn,
                                       encoding='utf-8',
                                       cf=ParConfigParser())
        self._datefmt = "%Y-%m-%d"
        # model time
        self._dt = self.get_time_step()
        self._startTime = self.get_start_time()
        self._endTime = self.get_end_time()
        self._t = self._startTime
        # model files
        _root = dirname(self._config_fn)
        # mapdir where nextxy data is found
        self._mapdir = dirname(
            glib.getabspath(str(self.get_attribute_value('DEMfile')), _root))
        self._outdir = glib.getabspath(
            str(self.get_attribute_value('dirroot')), _root)
        self.logger.info('Config initialized')

    def initialize_model(self, **kwargs):
        if not hasattr(self, '_config_fn'):
            raise Warning('Run initialize_config before initialize_model')
        self.write_config(
        )  # write updated config to file as bmi does not allow direct access
        self._bmi.initialize(self._config_fn)
        self.initialized = True
        self.logger.info('Model initialized')
        # reset model time to make sure it is consistent with the model
        self._dt = self.get_time_step()
        self._startTime = self.get_start_time()
        self._endTime = self.get_end_time()
        self._t = self._startTime

    def initialize(self, config_fn):
        if not hasattr(self, '_config'):
            self.initialize_config(config_fn)
        self.initialize_model()

    def update(self, dt=None):
        # dt in seconds. if not given model timestep is used
        if self._t >= self._endTime:
            raise Exception("endTime already reached, model not updated")
        if (dt is not None) and (dt != self._dt.total_seconds()):
            dt = timedelta(seconds=dt)
            # because of the adaptive timestep scheme do not check the dt value
            # if not glib.check_dts_divmod(dt, self._dt):
            #     msg = "Invalid value for dt in comparison to model dt. Make sure a whole number of model timesteps ({}) fit in the given dt ({})"
            #     raise ValueError(msg.format(self._dt, dt))
        else:
            dt = self._dt
        t_next = self.get_current_time() + dt
        i = 0
        while self._t < t_next:
            self._bmi.update()
            self._t = self.get_current_time()
            i += 1
        self.logger.info(
            'updated model to datetime {} in {:d} iterations'.format(
                self._t.strftime("%Y-%m-%d %H:%M:%S"), i))

    def update_until(self, t, dt=None):
        if (t < self._t) or t > self._endTime:
            raise Exception(
                "wrong time input: smaller than model time or larger than endTime"
            )
        while self._t < t:
            self.update(dt=dt)

    # not defined in CMF
    def spinup(self):
        """PCR specific spinup function"""
        raise NotImplementedError()

    def finalize(self):
        self.logger.info('finalize bmi. Close logger.')
        self._bmi.finalize()
        closelogger(self.logger)

    """
    Variable Information Functions
    """

    def get_start_time(self):
        refdate = self.get_attribute_value('refdate')
        refdate = datetime.strptime(refdate, self._datefmt)
        if self.initialized:
            TStart = self._bmi.get_start_time()
        else:
            TStart = 0.
        startTime = refdate + timedelta(**{self.get_time_units(): TStart})
        self._startTime = startTime
        return self._startTime

    def get_current_time(self):
        if self.initialized:
            curtime = timedelta(
                **{self.get_time_units(): self._bmi.get_current_time()})
            return self._startTime + curtime
        else:
            return self.get_start_time()

    def get_end_time(self):
        if self.initialized:
            # TODO end time after initialization is not correct
            TStop = self._bmi.get_end_time()
            # pass
        else:
            TStop = float(self.get_attribute_value('sim_time'))
        endTime = self.get_start_time() + timedelta(
            **{self.get_time_units(): TStop})
        self._endTime = endTime
        return self._endTime

    def get_time_step(self):
        if self.initialized:
            dt = self._bmi.get_time_step()
        else:
            dt = float(self.get_attribute_value('initial_tstep'))
        self._dt = timedelta(**{self.get_time_units(): dt})
        return self._dt

    def get_time_units(self):
        return self._timeunit

    """
    Variable Getter and Setter Functions
    """

    def get_value(self, long_var_name, **kwargs):
        return np.asarray(self._bmi.get_var(long_var_name)).copy()

    def get_value_at_indices(self, long_var_name, inds, **kwargs):
        return self.get_value(long_var_name).flat[inds]

    def set_value(self, long_var_name, src, **kwargs):
        # LFP does not have a set_var function, but used the get_var function with an extra argument
        self._bmi.get_var(long_var_name)[:] = src.astype(
            self.get_var_type(long_var_name))

    def set_value_at_indices(self, long_var_name, inds, src, **kwargs):
        val = self.get_value(long_var_name)
        val.flat[inds] = src
        self.set_value(long_var_name, val)

    """
    Grid Information Functions
    """

    def get_grid(self):
        if not hasattr(self, 'grid') or (self.grid is None):
            # dem file used for rgrid and mask of 2D domain
            _dem_fn = glib.getabspath(str(self.get_attribute_value('DEMfile')),
                                      self._mapdir)
            if not isfile(_dem_fn): raise IOError('DEMfile file not found')
            self.logger.info('Getting rgrid info based on {}'.format(
                basename(_dem_fn)))
            with rasterio.open(_dem_fn, 'r') as ds:
                self.grid = RGrid(ds.transform,
                                  ds.height,
                                  ds.width,
                                  crs=ds.crs,
                                  mask=ds.read(1) != ds.nodata)
            # riv width file used for "1D coords"
            _width_fn = glib.getabspath(
                str(self.get_attribute_value('SGCwidth')), self._mapdir)
            if not isfile(_width_fn): raise IOError('SGCwidth file not found')
            with rasterio.open(_width_fn, 'r') as ds:
                row, col = np.where(ds.read(1) > 0)
                x, y = self.grid.xy(row=row, col=col)
                inds = self.grid.ravel_multi_index(row, col)
            self.grid.set_1d(nodes=np.array(zip(x, y)), links=None, inds=inds)
        return self.grid

    """
    set and get attribute / config 
    """

    def set_start_time(self, start_time):
        if isinstance(start_time, datetime):
            refdate = start_time.strftime(self._datefmt)
        elif isinstance(start_time, str):
            try:
                refdate = start_time  # str
                start_time = datetime.strptime(start_time,
                                               self._datefmt)  # check format
            except ValueError:
                raise ValueError('wrong date format, use "yyyy-mm-dd"')
        else:
            raise ValueError('wrong start_date datatype')
        self._startTime = start_time
        self._t = start_time
        self.set_attribute_value('refdate', refdate)

    def set_end_time(self, end_time):
        if isinstance(end_time, str):
            try:
                end_time = datetime.strptime(end_time, self._datefmt)
            except ValueError:
                raise ValueError('wrong end_date format, use "yyyy-mm-dd"')
        if not isinstance(end_time, datetime):
            raise ValueError('wrong end_date datatype')
        refdate = self.get_start_time()
        assert end_time > refdate
        TStop = (end_time -
                 refdate).seconds + (end_time - refdate).days * 86400
        TStop = '{:.0f}'.format(TStop)
        self._endTime = end_time
        self.set_attribute_value('sim_time', TStop)

    def set_out_dir(self, out_dir):
        self.set_attribute_value('dirroot',
                                 relpath(out_dir, dirname(self._config_fn)))
        self._outdir = abspath(out_dir)

    def get_attribute_names(self):
        glib.configcheck(self, self.logger)
        return glib.configattr(self._config)

    def get_attribute_value(self, attribute_name):
        glib.configcheck(self, self.logger)
        # always use "general" as config header; as file has no config header this is hard-coded
        if ':' not in attribute_name:
            attribute_name = 'general:{}'.format(attribute_name)
        else:
            attribute_name = 'general:{}'.format(attribute_name.split(':')[1])
        self.logger.debug("get_attribute_value: {}".format(attribute_name))
        return glib.configget(self._config, attribute_name)

    def set_attribute_value(self, attribute_name, attribute_value):
        glib.configcheck(self, self.logger)
        # always use "general" as config header; as file has no config header this is hard-coded
        if ':' not in attribute_name:
            attribute_name = 'general:{}'.format(attribute_name)
        else:
            attribute_name = 'general:{}'.format(attribute_name.split(':')[1])
        self.logger.debug("set_attribute_value: {} -> {}".format(
            attribute_name, attribute_value))
        return glib.configset(self._config, attribute_name,
                              str(attribute_value))

    def write_config(self):
        """write adapted config to file. just before initializing
        only for models which do not allow for direct access to model config via bmi"""
        self._config_fn = glib.write_config(self, self._config,
                                            self._config_fn, self.logger)