Source code for fridom.framework.modules.module

import fridom.framework as fr
from functools import wraps

[docs] def module_method(method): """ Decorator for the start, update and stop method of a module. Description ----------- Sets the log level of the module if the log level is set and time the duration of the method. """ @wraps(method) def wrapper(self, *args, **kwargs): if self.is_enabled(): # if the log level is set, change the log level for the module if self.log_level is not None: old_log_level = fr.log.level fr.config.set_log_level(self.log_level.value) fr.log.debug( f"Calling '{method.__name__}' of: {self.name}") # check if the model settings are already set if self.mset is None: result = method(self, *args, **kwargs) else: with self.mset.timer[self.name]: result = method(self, *args, **kwargs) # if the log level was set, change it back to the old log level if self.log_level is not None: fr.config.set_log_level(old_log_level) return result else: if method.__name__ == "update": return kwargs.get('mz') return wrapper
[docs] class Module: """ Base class for all modules. Description ----------- A module is a component of the model that is executed at each time step. It can for example be a tendency term, a parameterization, or a diagnostic as for example outputting the model state to a file. Required methods: 1. `__init__(self, ...) -> None`: The constructor only takes keyword argument which are stored as attributes. Always call the parent constructor with `super().__init__(name, **kwargs)`. The name of the module is stored in the timing module and should not be too long. 2. `update(self, mz: ModelState) -> None`: This method is called by the model at each time step. It can for example update the tendency state `mz.dz` based on the model state `mz`. Or write the model state to a file. Make sure to wrap the method with the `@update_module` decorator. Optional methods: 1. `start(self, mset: ModelSettingsBase) -> None`: This method is called by the model when the module is started. It can for example open an output file. Make sure to wrap the method with the `@start_module` decorator. 2. `stop(self) -> None`: This method is called by the model when the module is stopped. It can for example close an output file. Make sure to wrap the method with the `@stop_module` decorator. Parameters ---------- `name` : `str` The name of the module. `**kwargs` Keyword arguments that are stored as attributes of the module. Flags ----- `required_halo` : `int` The number of halo points required by the module. `mpi_available` : `bool` Whether the module can be run in parallel. `execute_at_start` : `bool` Whether the module should be executed before the first time step. Examples -------- .. code-block:: python import fridom.framework as fr class Increment(fr.modules.Module): def __init__(self): # sets the module name to "Increment", and the number to None super().__init__("Increment", number=None) @fr.modules.start_module def start(self): self.number = 0 # sets the number to 0 @fr.modules.update_module def update(self, mz: fr.ModelSettingsBase) -> None: self.number += 1 # increments the number by 1 @fr.modules.stop_module def stop(self): self.number = None # sets the number to None """ name = "Base Module" _is_mod_submodule = False
[docs] def __init__(self) -> None: # The module is enabled by default self.__enabled = True # The log level self.log_level: fr.config.LogLevel | None = None # Set the flags self._required_halo = None # The number of halo points required by the module self.mpi_available = True # Whether the module can be run in parallel self.execute_at_start = False # The grid should be set by the model when the module is started self.mset: 'fr.ModelSettingsBase | None' = None self.timer: 'fr.timing_module.TimingModule | None' = None # Differentiation and interpolation modules self._diff_module: 'fr.grid.DiffModule | None' = None self._interp_module: 'fr.grid.InterpolationModule | None' = None return
[docs] def setup(self, mset: 'fr.ModelSettingsBase') -> None: """ Start the module Description ----------- This method is called by the ModelSettings.setup() and sets the ModelSettings as well as the differentiation and interpolation modules. """ self.mset = mset if not self._is_mod_submodule: # setup the differentiation and interpolation modules self._setup_submodule("diff_module", mset) self._setup_submodule("interp_module", mset) return
def _setup_submodule(self, name, mset): submodule = getattr(self, name) if submodule is None: submodule = getattr(mset.grid, name) setattr(self, name, submodule) else: submodule.setup(mset=mset) return
[docs] def start(self) -> None: """ Start the module Description ----------- This method is called at the beginning of the model run. Child classes that require a start method (for example to start an output writer) should overwrite this method. Make sure to decorate the method with the `@module_method` decorator. """ return
[docs] def stop(self) -> None: """ Stop the module Description ----------- This method is called by the model at the end of the model run or when the model is reset. Child classes that require a stop method (for example to close an output file) should overwrite this method. Make sure to decorate the method with the `@module_method` decorator. """ return
[docs] def update(self, mz: 'fr.ModelState') -> 'fr.ModelState': """ Update the module Description ----------- This method is called by the model at each time step. Child classes should overwrite this method to update the module. Make sure to decorate the method with the `@module_method` decorator. Parameters ---------- `mz` : `fr.ModelState` The model state at the current time step. Returns ------- `fr.ModelState` The updated model state. """ return mz
[docs] def enable(self) -> None: """ Enabling the module means that it will be executed at each time step. Disabled modules are neither initialized nor updated. """ self.__enabled = True return
[docs] def disable(self) -> None: """ Enabling the module means that it will be executed at each time step. Disabled modules are neither initialized nor updated. """ self.__enabled = False return
[docs] def is_enabled(self) -> bool: """ Return whether the module is enabled or not. """ return self.__enabled
[docs] def reset(self): """ Stop and start the module. """ self.stop() self.start()
def __repr__(self) -> str: """ String representation of the time stepper. """ res = self.name if not self.__enabled: res += " (disabled)" for key, value in self.info.items(): res += "\n - {}: {}".format(key, value) return res # ================================================================ # Properties # ================================================================ @property def info(self) -> dict: """ Return a dictionary with information about the time stepper. Description ----------- This method should be overridden by the child class to return a dictionary with information about the time stepper. This information is used to print the time stepper in the `__repr__` method. """ info = {} # Check if the grid is set grid_is_set = self.mset is not None # ---------------------------------------------------------------- # Check if the differentiation module should be printed # ---------------------------------------------------------------- print_diff = False diff_is_set = self.diff_module is not None if diff_is_set and grid_is_set: if self.diff_module is not self.grid.diff_module: print_diff = True elif diff_is_set: print_diff = True if print_diff: info["Diff. Module"] = self.diff_module.name # ---------------------------------------------------------------- # Check if the differentiation module should be printed # ---------------------------------------------------------------- print_interp = False interp_is_set = self.interp_module is not None if interp_is_set and grid_is_set: if self.interp_module is not self.grid.interp_module: print_interp = True elif interp_is_set: print_interp = True if print_interp: info["Interp. Module"] = self.interp_module.name # ---------------------------------------------------------------- # Check if the required halo should be printed # ---------------------------------------------------------------- print_halo = self._required_halo is not None if print_halo: info["Required Halo"] = self.required_halo return info @property def mset(self) -> 'fr.ModelSettingsBase': """ The model settings """ return self._mset @mset.setter def mset(self, mset: 'fr.ModelSettingsBase') -> None: self._mset = mset return @property def grid(self) -> 'fr.grid.GridBase': """ The grid of the model settings """ return self.mset.grid @property def diff_module(self) -> 'fr.grid.DiffModule': """The differentiation module to be used by this module.""" return self._diff_module @diff_module.setter def diff_module(self, value): self._diff_module = value return @property def interp_module(self) -> 'fr.grid.InterpolationModule': """The interpolation module to be used by this module.""" return self._interp_module @interp_module.setter def interp_module(self, value): self._interp_module = value return @property def required_halo(self) -> int: # Return the required halo if it is set if self._required_halo is not None: return self._required_halo # If it is not set, check the differentiation and interpolation modules # If they are not set, return 0 req_halo = 0 if self.diff_module is not None: req_halo = self.diff_module.required_halo if self.interp_module is not None: req_halo = max(req_halo, self.interp_module.required_halo) return req_halo @required_halo.setter def required_halo(self, value: int): self._required_halo = value return