def unpack(self, df=False): """ Unpack stored data in `_xy` and `_z` into arrays `t`, `xy`, and `z`. Parameters ---------- df : bool True to construct DataFrames `self.df` and `self.df_z` (time-consuming). """ if df is True: self.df = pd.DataFrame.from_dict(self._xy, orient='index', columns=self.dae.xy_name) self.t = self.df.index.to_numpy() self.xy = self.df.to_numpy() self.df_z = pd.DataFrame.from_dict(self._z, orient='index', columns=self.dae.z_name) self.z = self.df_z.to_numpy() else: n_steps = len(self._xy) self.t = np.array(list(self._xy.keys())) self.xy = np.zeros((n_steps, self.dae.m + self.dae.n)) self.z = np.zeros((n_steps, self.dae.o)) for idx, xy in enumerate(self._xy.values()): self.xy[idx, :] = xy for idx, z in enumerate(self._z.values()): self.z[idx, :] = z
def get(self, src: str, idx, attr: str = 'v', allow_none=False, default=0.0): """ Based on the indexer, get the `attr` field of the `src` parameter or variable. Parameters ---------- src : str param or var name idx : array-like device idx attr The attribute of the param or var to retrieve allow_none : bool True to allow None values in the indexer default : float If `allow_none` is true, the default value to use for None indexer. Returns ------- The requested param or variable attribute """ self._check_src(src) self._check_idx(idx) n = len(idx) if n == 0: return np.zeros(0) ret = [''] * n _type_set = False models = self.idx2model(idx, allow_none=allow_none) for i, idx in enumerate(idx): if models[i] is not None: uid = models[i].idx2uid(idx) instance = models[i].__dict__[src] val = instance.__dict__[attr][uid] else: val = default # deduce the type for ret if not _type_set: if isinstance(val, str): ret = [''] * n else: ret = np.zeros(n) _type_set = True ret[i] = val return ret
def list2array(self, n): """ Allocate memory for storage arrays. """ super().list2array(n) if self.mode == 'step': self._v_mem = np.zeros((n, self.delay + 1)) self.t = np.zeros(self.delay + 1) else: self._v_mem = np.zeros((n, 1))
def link_external(self, ext_model): """ Update variable addresses provided by external models This method sets attributes including `parent_model`, `parent_instance`, `uid`, `a`, `n`, `e_code` and `v_code`. It initializes the `e` and `v` to zero. Returns ------- None Parameters ---------- ext_model : Model Instance of the parent model """ self.parent = ext_model if isinstance(ext_model, GroupBase): if self.indexer.n > 0 and isinstance(self.indexer.v[0], (list, np.ndarray)): self._n = [len(i) for i in self.indexer.v ] # number of elements in each sublist self._idx = np.concatenate( [np.array(i) for i in self.indexer.v]) else: self._n = [len(self.indexer.v)] self._idx = self.indexer.v self.a = ext_model.get(src=self.src, idx=self._idx, attr='a').astype(int) self.n = len(self.a) self.v = np.zeros(self.n) self.e = np.zeros(self.n) else: original_var = ext_model.__dict__[self.src] if self.indexer is not None: uid = ext_model.idx2uid(self.indexer.v) else: uid = np.arange(ext_model.n, dtype=int) self._n = [len(uid)] if len(uid) > 0: self.a = original_var.a[uid] else: self.a = np.array([], dtype=int) # set initial v and e values to zero self.n = len(self.a) self.v = np.zeros(self.n) self.e = np.zeros(self.n)
def _set_arrays_alloc(self): """ Allocate for internal v and e arrays that cannot share memory with dae arrays. """ if not self.v_inplace: self.v = np.zeros(self.n) if not self.e_inplace: self.e = np.zeros(self.n)
def set_address(self, addr): """ Set the address of this variables Parameters ---------- addr : array-like The assigned address for this variable """ self.a = addr self.n = len(self.a) self.v = np.zeros(self.n) self.e = np.zeros(self.n)
def store_sparse_pattern(self, models: Optional[Union[str, List, OrderedDict]] = None): models = self._get_models(models) self._call_models_method('store_sparse_pattern', models) # add variable jacobian values for j_name in self.dae.jac_name: ii, jj, vv = list(), list(), list() # for `gy` matrix, always make sure the diagonal is reserved # It is a safeguard if the modeling user omitted the diagonal # term in the equations if j_name == 'gy': ii.extend(np.arange(self.dae.m)) jj.extend(np.arange(self.dae.m)) vv.extend(np.zeros(self.dae.m)) # logger.debug(f'Jac <{j_name}>, row={ii}') for mdl in models.values(): row_idx = mdl.row_of(f'{j_name}') col_idx = mdl.col_of(f'{j_name}') # logger.debug(f'Model <{name}>, row={row_idx}') ii.extend(row_idx) jj.extend(col_idx) vv.extend(np.zeros(len(np.array(row_idx)))) # add the constant jacobian values for row, col, val in mdl.zip_ijv(f'{j_name}c'): ii.extend(row) jj.extend(col) if isinstance(val, (float, int)): vv.extend(val * np.ones(len(row))) elif isinstance(val, (list, np.ndarray)): vv.extend(val) else: raise TypeError( f'Unknown type {type(val)} in constant jacobian {j_name}' ) if len(ii) > 0: ii = np.array(ii).astype(int) jj = np.array(jj).astype(int) vv = np.array(vv).astype(float) self.dae.store_sparse_ijv(j_name, ii, jj, vv) self.dae.build_pattern(j_name)
def resize_array(self): """ Resize arrays to the new `m` and `n` Returns ------- """ self.x = np.append(self.x, np.zeros(self.n - len(self.x))) self.y = np.append(self.y, np.zeros(self.m - len(self.y))) self.z = np.append(self.z, np.zeros(self.o - len(self.z))) self.f = np.append(self.f, np.zeros(self.n - len(self.f))) self.g = np.append(self.g, np.zeros(self.m - len(self.g)))
def __init__(self, u, mode='step', delay=0, name=None, tex_name=None, info=None): Discrete.__init__(self, name=name, tex_name=tex_name, info=info) if mode not in ('step', 'time'): raise ValueError( f'mode {mode} is invalid. Must be in "step" or "time"') self.u = u self.mode = mode self.delay = delay self.export_flags = ['v'] self.export_flags_tex = ['v'] self.t = np.array([0]) self.v = np.array([0]) self._v_mem = np.zeros((0, 1)) self.rewind = False self.has_check_var = True
def setUp(self) -> None: self.n = 5 # number of input values to delay self.step = 2 # steps of delay self.time = 1.0 # delay period in second self.data = DummyValue(0) self.data.v = np.zeros(self.n) self.dstep = Delay(u=self.data, mode='step', delay=self.step) self.dstep.list2array(self.n) self.dtime = Delay(u=self.data, mode='time', delay=self.time) self.dtime.list2array(self.n) self.avg = Average(u=self.data, mode='step', delay=1) self.avg.list2array(self.n) self.v = self.dstep.v self.vt = self.dtime.v self.va = self.avg.v self.n_forward = 5 self.tstep = 0.2 self.dae_t = 0 self.k = 0.2
def init(self): """ Initialize the status, storage and values for TDS. Returns ------- array-like The initial values of xy. """ t0, _ = elapsed() system = self.system if self.initialized: return system.dae.xy self._reset() self._load_pert() # Note: # calling `set_address` on `system.exist.pflow_tds` will point all variables # to the new array after extending `dae.y` system.set_address(models=system.exist.pflow_tds) system.set_dae_names(models=system.exist.tds) system.dae.clear_ts() system.store_sparse_pattern(models=system.exist.pflow_tds) system.store_adder_setter(models=system.exist.pflow_tds) system.vars_to_models() # Initialize `system.exist.tds` only to avoid Bus overwriting power flow solutions system.init(system.exist.tds) system.store_switch_times(system.exist.tds) # Build mass matrix into `self.Teye` self.Teye = spdiag(system.dae.Tf.tolist()) self.qg = np.zeros(system.dae.n + system.dae.m) self.initialized = self.test_init() # if `dae.n == 1`, `calc_h_first` depends on new `dae.gy` self.calc_h() _, s1 = elapsed(t0) if self.initialized is True: logger.info(f"Initialization was successful in {s1}.") else: logger.error(f"Initialization failed in {s1}.") if system.dae.n == 0: tqdm.write('No dynamic component loaded.') return system.dae.xy
def set_arrays(self, dae): """ Set the equation and values arrays. It slicing into DAE (when contiguous) or allocating new memory (when not contiguous). Parameters ---------- dae : DAE Reference to System.dae """ slice_idx = slice(self.a[0], self.a[-1] + 1) if self.v_inplace: self.v = dae.__dict__[self.v_code][slice_idx] else: self.v = np.zeros(self.n) if self.e_inplace: self.e = dae.__dict__[self.e_code][slice_idx] else: self.e = np.zeros(self.n)
def get(self, src: str, idx, attr: str = 'v'): """ Based on the indexer, get the `attr` field of the `src` parameter or variable. Parameters ---------- src : str param or var name idx : array-like attr The attribute of the param or var to retrieve Returns ------- The requested param or variable attribute """ self._check_src(src) self._check_idx(idx) n = len(idx) if n == 0: return np.zeros(0) ret = None models = self.idx2model(idx) for i, idx in enumerate(idx): uid = models[i].idx2uid(idx) instance = models[i].__dict__[src] val = instance.__dict__[attr][uid] # deduce the type for ret if ret is None: if isinstance(val, str): ret = [''] * n else: ret = np.zeros(n) ret[i] = val return ret
def setUp(self) -> None: self.n = 5 self.data = DummyValue(0) self.data.v = np.zeros(self.n) self.der = Derivative(u=self.data) self.der.list2array(self.n) self.v = self.der.v self.n_forward = 10 self.k = 0.2 self.t_step = 0.1 self.dae_t = 0
def link_external(self, ext_model): """ Method to be called by ``System`` for getting values from the external model or group. Parameters ---------- ext_model An instance of a model or group provided by System """ # set initial v values to zero self.v = np.zeros(self.n) if self.n == 0: return # the same `get` api for Group and Model self.v = ext_model.get(src=self.src, idx=self.indexer.v, attr='v')
def v(self): """ Return the reduced values from the reduction function in an array Returns ------- The array ``self._v`` storing the reduced values """ if self._v is None: self._v = np.zeros(len(self.ref.v)) idx = 0 for i, v in enumerate(self.ref.v): self._v[i] = self.fun(self.u.v[idx:idx + len(v)]) idx += len(v) return self._v else: return self._v
def v(self): """ Return the values of the repeated values in a sequantial 1-D array Returns ------- The array, ``self._v`` storing the repeated values """ if self._v is None: self._v = np.zeros(len(list_flatten(self.ref.v))) idx = 0 for i, v in enumerate(self.ref.v): self._v[idx:idx + len(v)] = self.u.v[i] idx += len(v) return self._v else: return self._v
def init(self): """ Initialize the status, storage and values for TDS. Returns ------- array-like The initial values of xy. """ t0, _ = elapsed() system = self.system if self.initialized: return system.dae.xy self._reset() self._load_pert() system.set_address(models=system.exist.tds) system.set_dae_names(models=system.exist.tds) system.dae.clear_ts() system.store_sparse_pattern(models=system.exist.pflow_tds) system.store_adder_setter(models=system.exist.pflow_tds) system.vars_to_models() system.init(system.exist.tds) system.store_switch_times(system.exist.tds) self.eye = spdiag([1] * system.dae.n) self.Teye = spdiag(system.dae.Tf.tolist()) * self.eye self.qg = np.zeros(system.dae.n + system.dae.m) self.calc_h() self.initialized = self.test_init() _, s1 = elapsed(t0) if self.initialized is True: logger.info(f"Initialization was successful in {s1}.") else: logger.error(f"Initialization failed in {s1}.") if system.dae.n == 0: tqdm.write('No dynamic component loaded.') return system.dae.xy
def store_sparse_pattern(self, models: Optional[Union[str, List, OrderedDict]] = None): """ Collect and store the sparsity pattern of Jacobian matrices. This is a runtime function specific to cases. Notes ----- For `gy` matrix, always make sure the diagonal is reserved. It is a safeguard if the modeling user omitted the diagonal term in the equations. """ models = self._get_models(models) self._call_models_method('store_sparse_pattern', models) # add variable jacobian values for jname in jac_names: ii, jj, vv = list(), list(), list() if jname == 'gy': ii.extend(np.arange(self.dae.m)) jj.extend(np.arange(self.dae.m)) vv.extend(np.zeros(self.dae.m)) for mdl in models.values(): for row, col, val in mdl.triplets.zip_ijv(jname): ii.extend(row) jj.extend(col) vv.extend(np.zeros_like(row)) for row, col, val in mdl.triplets.zip_ijv(jname + 'c'): # process constant Jacobians separately ii.extend(row) jj.extend(col) vv.extend(val * np.ones_like(row)) if len(ii) > 0: ii = np.array(ii, dtype=int) jj = np.array(jj, dtype=int) vv = np.array(vv, dtype=float) self.dae.store_sparse_ijv(jname, ii, jj, vv) self.dae.build_pattern(jname)
def link_external(self, ext_model): """ Update variable addresses provided by external models This method sets attributes including `parent_model`, `parent_instance`, `uid`, `a`, `n`, `e_code` and `v_code`. It initializes the `e` and `v` to zero. Returns ------- None Parameters ---------- ext_model : Model Instance of the parent model Warnings -------- `link_external` does not check if the ExtVar type is the same as the original variable to reduce performance overhead. It will be a silent error (a dimension too small error from `dae.build_pattern`) if a model uses `ExtAlgeb` to access a `State`, or vice versa. """ self.parent = ext_model if isinstance(ext_model, GroupBase): # determine the number of elements based on `indexer.v` if self.indexer.n > 0 and isinstance(self.indexer.v[0], (list, np.ndarray)): self._n = [len(i) for i in self.indexer.v ] # number of elements in each sublist self._idx = np.concatenate( [np.array(i) for i in self.indexer.v]) else: self._n = [len(self.indexer.v)] self._idx = self.indexer.v # use `0` for non-existent addresses (corr. to None in indexer) self.a = ext_model.get( src=self.src, idx=self._idx, attr='a', allow_none=self.allow_none, default=0, ).astype(int) # check if source var type is the same as this ExtVar vcodes = np.array( ext_model.get_field(src=self.src, idx=self._idx, field='v_code')) vcodes = vcodes[vcodes != np.array(None)].astype(str) if not all(vcodes == np.array(self.v_code)): raise TypeError( "ExtVar <%s.%s> is of type <%s>, but source Vars <%s.%s> may not." % (self.owner.class_name, self.name, self.v_code, ext_model.class_name, self.src)) self.n = len(self.a) else: original_var = ext_model.__dict__[self.src] if self.allow_none: raise NotImplementedError( f"{self.name}: allow_none not implemented for Model") if original_var.v_code != self.v_code: raise TypeError("Linking %s of %s to %s of %s is not allowed" % (self.name, self.class_name, original_var.name, original_var.class_name)) if self.indexer is not None: uid = ext_model.idx2uid(self.indexer.v) else: uid = np.arange(ext_model.n, dtype=int) self._n = [len(uid)] if len(uid) > 0: self.a = original_var.a[uid] else: self.a = np.array([], dtype=int) # set initial v and e values to zero self.n = len(self.a) self.v = np.zeros(self.n)
def clear_xy(self): """Reset variable arrays to empty """ self.x = np.zeros(self.n) self.y = np.zeros(self.m)
def list2array(self, n): super().list2array(n) self._last_v = np.zeros(n)
def assign_memory(self, n): """Assign memory for ``self.v`` and set the array to zero.""" self.v = np.zeros(n, dtype=self.vtype)
def clear_z(self): """ Reset status arrays to empty """ self.z = np.zeros(self.n)
def init(self): """ Initialize the status, storage and values for TDS. Returns ------- array-like The initial values of xy. """ t0, _ = elapsed() system = self.system if self.initialized: return system.dae.xy self.reset() self._load_pert() # restore power flow solutions system.dae.x[:len(system.PFlow.x_sol)] = system.PFlow.x_sol system.dae.y[:len(system.PFlow.y_sol)] = system.PFlow.y_sol # Note: # calling `set_address` on `system.exist.pflow_tds` will point all variables # to the new array after extending `dae.y` system.set_address(models=system.exist.pflow_tds) system.set_dae_names(models=system.exist.tds) system.dae.clear_ts() system.store_sparse_pattern(models=system.exist.pflow_tds) system.store_adder_setter(models=system.exist.pflow_tds) system.vars_to_models() system.init(system.exist.tds, routine='tds') system.store_switch_times(system.exist.tds) # Build mass matrix into `self.Teye` self.Teye = spdiag(system.dae.Tf.tolist()) self.qg = np.zeros(system.dae.n + system.dae.m) self.initialized = self.test_init() # connect to dime server if system.config.dime_enabled: if system.streaming.dimec is None: system.streaming.connect() # send out system data using DiME self.streaming_init() self.streaming_step() # if `dae.n == 1`, `calc_h_first` depends on new `dae.gy` self.calc_h() _, s1 = elapsed(t0) if self.initialized is True: logger.info(f"Initialization for dynamics was successful in {s1}.") else: logger.error(f"Initialization for dynamics failed in {s1}.") if system.dae.n == 0: tqdm.write('No dynamic component loaded.') return system.dae.xy
def init(self): """ Initialize the status, storage and values for TDS. Returns ------- array-like The initial values of xy. """ t0, _ = elapsed() system = self.system if self.initialized: return system.dae.xy self.reset() self._load_pert() # restore power flow solutions system.dae.x[:len(system.PFlow.x_sol)] = system.PFlow.x_sol system.dae.y[:len(system.PFlow.y_sol)] = system.PFlow.y_sol # Note: # calling `set_address` on `system.exist.pflow_tds` will point all variables # to the new array after extending `dae.y`. system.set_address(models=system.exist.pflow_tds) system.set_dae_names(models=system.exist.tds) system.dae.clear_ts() system.store_sparse_pattern(models=system.exist.pflow_tds) system.store_adder_setter(models=system.exist.pflow_tds) system.store_no_check_init(models=system.exist.pflow_tds) system.vars_to_models() system.init(system.exist.tds, routine='tds') # only store switch times when not replaying CSV data if self.data_csv is None: system.store_switch_times(system.exist.tds) # Build mass matrix into `self.Teye` self.Teye = spdiag(system.dae.Tf.tolist()) self.qg = np.zeros(system.dae.n + system.dae.m) self.initialized = True # test if residuals are close enough to zero if self.config.test_init: self.test_ok = self.test_init() # discard initialized values and use that from CSV if provided if self.data_csv is not None: system.dae.x[:] = self.data_csv[0, 1:system.dae.n + 1] system.dae.y[:] = self.data_csv[0, system.dae.n + 1:system.dae.n + system.dae.m + 1] system.vars_to_models() # connect to data streaming server if system.streaming.dimec is None: system.streaming.connect() if system.config.dime_enabled: # send out system data using DiME self.streaming_init() self.streaming_step() # if `dae.n == 1`, `calc_h_first` depends on new `dae.gy` self.calc_h() # allocate for internal variables self.x0 = np.zeros_like(system.dae.x) self.y0 = np.zeros_like(system.dae.y) self.f0 = np.zeros_like(system.dae.f) _, s1 = elapsed(t0) logger.info("Initialization for dynamics completed in %s.", s1) if self.test_ok is True: logger.info("Initialization was successful.") elif self.test_ok is False: logger.error("Initialization failed!!") else: logger.warning("Initialization results were not verified.") if system.dae.n == 0: tqdm.write('No differential equation detected.') return system.dae.xy
def clear_fg(self): """Resets equation arrays to empty """ self.f = np.zeros(self.n) self.g = np.zeros(self.m)