Source code for simobject.simulation

from collections import OrderedDict

from .quantity import Quantity
from .heartbeat_object import HeartbeatObject


[docs]class Simulation(HeartbeatObject): """Simulation object with updatable quantities This object ... - ... stores `Quantities` in _quantities that can be set with `addQuantity` - ... keeps track of the update order in systole, update, diastole - ... has itself a systole, and a diastole that can be set - ... overrides `update` which in this case calls all `systole`, `update`, and `diastoles` - ... provides a `data` dictionary to store other data (parameters, ...). the lists `systole_order`, `update_order`, and `diastole_order` can be set, but if they are empty they return the order in which the Quantities were added. """ __slots__ = ["_quantities", "_systole_order", "_update_order", "_diastole_order", "_data"] def __init__(self): # we call the method from super because we overwrite the own __setattr__ super().__setattr__("_quantities", OrderedDict()) super().__setattr__("_systole_order", []) super().__setattr__("_update_order", []) super().__setattr__("_diastole_order", []) super().__setattr__("_data", {}) # this is how one gets an attribute def __getattribute__(self, key): _quantities = super().__getattribute__("_quantities") if key in _quantities: return _quantities[key] else: return super().__getattribute__(key) # this is how to set an 'attribute' that internally we store in _quantities def __setattr__(self, key, value): _quantities = super().__getattribute__("_quantities") if isinstance(value, Quantity): self.addQuantity(key, value, info=value.info or key) elif key in _quantities: _quantities[key].setvalue(value) else: super().__setattr__(key, value)
[docs] def addQuantity(self, key, value, info=None, updater=None, systoler=None, diastoler=None, constant=None, copy=True): """ adds `value` as apparent attribute under the name `key`. `value` will be casted to type Quantity. If a numpy ndarray is passed, a new memory buffer will be created. If a quantity is passed, - ... a new Quantity object will be created with the same attributes. - ... and if `value` has already updater, systoler, or diastoler, those will be inherited - ... and if `updater`, `systoler`, and/or `diastoler` are given, those always override the ones that might already be set in `value`. - ... and `info` is None, then `key` is also set as the `info` attribute of the Quantity unless that one already had that attribute to begin with, else `key` is used instead. Parameters: ----------- copy : bool, optional, defaults to True by default a copy of the input value is created. If set to False, the same memory (for ndarrays) or object (for Quantities) are used. """ if isinstance(value, Quantity) and copy is False: q = value else: q = Quantity(value, owner=self, copy=copy) q.info = info or q.info or key if constant is not None: q._constant = constant # this actually calls the setter and getter to make sure # the updater is linked correctly q.updater = updater or q.updater q.systoler = systoler or q.systoler q.diastoler = diastoler or q.diastoler self._quantities[key] = q
[docs] def update(self): """updates everything: it calls: - the systole of the simulation object itself - the systole of all quantities in `self.systole_order`, then - the update of all quantities in `self.update_order`, then - the diastole of all quantities in `self.diastole_order`, then - the diastole of the simulation object itself """ self.systole() for key in self.systole_order: getattr(self, key).systole() for key in self.update_order: getattr(self, key).update() for key in self.diastole_order: getattr(self, key).diastole() self.diastole()
@property def update_order(self): "the order in which the quantity-updates are called" if len(self._update_order) == 0: return list(self._quantities.keys()) else: return self._update_order @update_order.setter def update_order(self, value): "value is a list of strings: names of the quantities to update" self._check_list(value) self._update_order = value @property def systole_order(self): "the order in which the quantity-systoles are called" if len(self._systole_order) == 0: return list(self._quantities.keys()) else: return self._systole_order @systole_order.setter def systole_order(self, value): "value is a list of strings: names of the quantities to update in the systole" self._check_list(value) self._systole_order = value @property def diastole_order(self): if len(self._diastole_order) == 0: return list(self._quantities.keys()) else: return self._diastole_order @diastole_order.setter def diastole_order(self, value): "value is a list of strings: names of the quantities to update in the diastole" self._check_list(value) self._diastole_order = value @property def data(self): return self._data @data.setter def data(self, value): raise AttributeError( 'cannot reassign data dictionary, use its methods like pop, update, ... instead') @staticmethod def _check_list(value): "checks if `value` is a list of strings" if not isinstance(value, list): raise TypeError('input must be a list') for val in value: if not isinstance(val, str): raise TypeError('not a str: ' + str(val)) # to make tab completion work def __dir__(self): return sorted(set(super().__dir__() + list(self._quantities.keys()))) def __repr__(self): s = "Simulation\n\n" for key in sorted(self._quantities.keys(), key=str.casefold): val = self._quantities[key] if key.startswith("_"): continue if len(key) > 12: name = key[:9] + "..." else: name = key if type(val) is Quantity: s += "{:11s}{:7s}: {:12s} {}\n".format(" Const. " if val._constant else "", "Quantity", name, "(" + val.info + ")" if val.info else "") else: s += "{:11s}{:7s}: {:12s}\n".format("", type(val).__name__, name) return s