def nr_step(self): """ Single stepping for Newton Raphson method Returns ------- """ system = self.system # evaluate discrete, differential, algebraic, and jacobians system.e_clear() system.l_update_var() system.f_update() system.g_update() system.l_check_eq() system.l_set_eq() system.fg_to_dae() system.j_update() # prepare and solve linear equations self.inc = -matrix([matrix(system.dae.f), matrix(system.dae.g)]) self.A = sparse([[system.dae.fx, system.dae.gx], [system.dae.fy, system.dae.gy]]) self.inc = self.solver.solve(self.A, self.inc) system.dae.x += np.ravel(np.array(self.inc[:system.dae.n])) system.dae.y += np.ravel(np.array(self.inc[system.dae.n:])) mis = np.max(np.abs(system.dae.fg)) self.mis.append(mis) system.vars_to_models() return mis
def reset(self): """ Reset internal states to pre-init condition. """ self.deltat = 0 self.deltatmin = 0 self.deltatmax = 0 self.h = 0 self.next_pc = 0.1 self.Teye = None self.qg = np.array([]) self.converged = False self.last_converged = False self.busted = False self.niter = 0 self._switch_idx = 0 # index into `System.switch_times` self._last_switch_t = -999 # the last event time self.custom_event = False self.mis = [1, 1] self.system.dae.t = np.array(0.0) self.pbar = None self.plotter = None self.plt = None # short name for `plotter` self.initialized = False
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 to_array(self): """ Converts field ``v`` to the NumPy array type. to enable array-based calculation. Must be called after adding all elements. Store a copy of original input values to field ``vin``. Set ``pu_coeff`` to all ones. Warnings -------- After this call, `add` will not be allowed to avoid unexpected issues. """ self.v = np.array(self.v, dtype=self.vtype) # data quality check # ---------------------------------------- # NOTE: temporarily disabled due to nested parameters # if np.sum(np.isnan(self.v)) > 0: # raise ValueError(f'Param <{self.name} contains NaN.') if self.v.dtype != np.object: self.v[self.v == np.inf] = 1e8 self.v[self.v == -np.inf] = -1e8 # ---------------------------------------- self.vin = np.array(self.v, dtype=self.vtype) self.pu_coeff = np.ones_like(self.v, dtype=float)
def __init__(self, u, lower, upper, enable=True, name=None, tex_name=None, info=None, no_upper=False, no_lower=False): super().__init__(name=name, tex_name=tex_name, info=info) self.u = u self.lower = lower self.upper = upper self.enable = enable self.no_upper = no_upper self.no_lower = no_lower self.zu = np.array([0]) self.zl = np.array([0]) self.zi = np.array([1]) self.export_flags = ['zi'] self.export_flags_tex = ['z_i'] self.has_check_var = True if not self.no_lower: self.export_flags.append('zl') self.export_flags_tex.append('z_l') self.warn_flags.append(('zl', 'lower')) if not self.no_upper: self.export_flags.append('zu') self.export_flags_tex.append('z_u') self.warn_flags.append(('zu', 'upper'))
def to_array(self): """ Convert ``v`` to np.ndarray after adding elements. Store a copy if the input in `vin`. Set ``pu_coeff`` to all ones. The conversion enables array-based calculation. Warnings -------- After this call, `add` will not be allowed, because data will not be copied over to ``vin``. """ # data quality check # ---------------------------------------- self.v = np.array(self.v, dtype=float) # NOTE: temporarily disabled due to nested parameters # if np.sum(np.isnan(self.v)) > 0: # raise ValueError(f'Param <{self.name} contains NaN.') self.v[self.v == np.inf] = 1e8 self.v[self.v == -np.inf] = -1e8 # ---------------------------------------- self.vin = np.array(self.v, dtype=float) self.pu_coeff = np.ones_like(self.v)
def __init__(self, u, bound, equal=False, enable=True, name=None, tex_name=None, info=None, cache=False, z0=0, z1=1): super().__init__(name=name, tex_name=tex_name, info=info) self.u = u self.bound = dummify(bound) self.equal: bool = equal self.enable: bool = enable self.cache: bool = cache self._eval: bool = False # if has been eval'ed and cached self.z0 = np.array([z0]) # negation of `self.z1` self.z1 = np.array( [z1]) # if the less-than condition (u < bound) is True self.export_flags = ['z0', 'z1'] self.export_flags_tex = ['z_0', 'z_1'] self.has_check_var = True
def set_address(self, addr: np.ndarray, contiguous=False): """ Set the address of internal variables. Parameters ---------- addr : np.ndarray The assigned address for this variable contiguous : bool, optional If the addresses are contiguous """ self.a = addr self.n = len(self.a) # NOT IN USE self.ae = np.array(self.a) self.av = np.array(self.a) # ----------- self._contiguous = contiguous if self._contiguous: if self.e_setter is False: self.e_inplace = True if self.v_setter is False: self.v_inplace = True
def __init__(self, system): self.system = system self.t = 0 self.ts = DAETimeSeries(self) self.m, self.n, self.o = 0, 0, 0 self.x, self.y, self.z = np.array([]), np.array([]), np.array([]) self.f, self.g = np.array([]), np.array([]) self.fx = None self.fy = None self.gx = None self.gy = None self.tx = None self.rx = None self.fx_pattern = None self.fy_pattern = None self.gx_pattern = None self.gy_pattern = None self.tx_pattern = None self.rx_pattern = None self.x_name, self.x_tex_name = [], [] self.y_name, self.y_tex_name = [], [] self.z_name, self.z_tex_name = [], [] # ----- indices of sparse matrices ----- self.ifx, self.jfx, self.vfx = list(), list(), list() self.ify, self.jfy, self.vfy = list(), list(), list() self.igx, self.jgx, self.vgx = list(), list(), list() self.igy, self.jgy, self.vgy = list(), list(), list() self.itx, self.jtx, self.vtx = list(), list(), list() self.irx, self.jrx, self.vrx = list(), list(), list()
def nr_step(self): """ Single step using Newton-Raphson method. Returns ------- float maximum absolute mismatch """ system = self.system # evaluate discrete, differential, algebraic, and Jacobians system.dae.clear_fg() system.l_update_var(self.models, niter=self.niter, err=self.mis[-1]) system.s_update_var(self.models) system.f_update(self.models) system.g_update(self.models) system.l_update_eq(self.models) system.fg_to_dae() if self.config.method == 'NR': system.j_update(models=self.models) elif self.config.method == 'dishonest': if self.niter < self.config.n_factorize: system.j_update(self.models) # prepare and solve linear equations self.inc = -matrix([matrix(system.dae.f), matrix(system.dae.g)]) self.A = sparse([[system.dae.fx, system.dae.gx], [system.dae.fy, system.dae.gy]]) if not self.config.linsolve: self.inc = self.solver.solve(self.A, self.inc) else: self.inc = self.solver.linsolve(self.A, self.inc) system.dae.x += np.ravel(np.array(self.inc[:system.dae.n])) system.dae.y += np.ravel(np.array(self.inc[system.dae.n:])) # find out variables associated with maximum mismatches fmax = 0 if system.dae.n > 0: fmax_idx = np.argmax(np.abs(system.dae.f)) fmax = system.dae.f[fmax_idx] logger.debug("Max. diff mismatch %.10g on %s", fmax, system.dae.x_name[fmax_idx]) gmax_idx = np.argmax(np.abs(system.dae.g)) gmax = system.dae.g[gmax_idx] logger.debug("Max. algeb mismatch %.10g on %s", gmax, system.dae.y_name[gmax_idx]) mis = max(abs(fmax), abs(gmax)) if self.niter == 0: self.mis[0] = mis else: self.mis.append(mis) system.vars_to_models() return mis
def setUp(self): self.lower = NumParam() self.upper = NumParam() self.u = Algeb() self.upper.v = np.array([2, 2, 2, 2, 2, 2, 2.8, 3.9]) self.u.v = np.array([-3, -1.1, -5, 0, 1, 2, 3, 10]) self.lower.v = np.array([-2, -1, 0.5, 0, 0.5, 1.5, 2, 3])
def linsolve(self, A, b): """ Solve linear equation set ``Ax = b`` and returns the solutions in a 1-D array. This function performs both symbolic and numeric factorizations every time, and can be slower than ``Solver.solve``. Parameters ---------- A Sparse matrix b RHS of the equation Returns ------- The solution in a 1-D np array. """ if self.sparselib == 'umfpack': try: umfpack.linsolve(A, b) except ArithmeticError: logger.error('Singular matrix. Case is not solvable') return np.ravel(b) elif self.sparselib == 'klu': try: klu.linsolve(A, b) except ArithmeticError: logger.error('Singular matrix. Case is not solvable') return np.ravel(b) elif self.sparselib in ('spsolve', 'cupy'): ccs = A.CCS size = A.size data = np.array(ccs[2]).reshape((-1,)) indices = np.array(ccs[1]).reshape((-1,)) indptr = np.array(ccs[0]).reshape((-1,)) A = csc_matrix((data, indices, indptr), shape=size) if self.sparselib == 'spsolve': x = spsolve(A, b) return np.ravel(x) elif self.sparselib == 'cupy': # delayed import for startup speed import cupy as cp # NOQA from cupyx.scipy.sparse import csc_matrix as csc_cu # NOQA from cupyx.scipy.sparse.linalg.solve import lsqr as cu_lsqr # NOQA cu_A = csc_cu(A) cu_b = cp.array(np.array(b).reshape((-1,))) x = cu_lsqr(cu_A, cu_b) return np.ravel(cp.asnumpy(x[0]))
def store_switch_times(self, models, eps=1e-4): """ Store event switching time in a sorted Numpy array in ``System.switch_times`` and an OrderedDict ``System.switch_dict``. ``System.switch_dict`` has keys as event times and values as the OrderedDict of model names and instances associated with the event. Parameters ---------- models : OrderedDict model name : model instance eps : float The small time step size to use immediately before and after the event Returns ------- array-like self.switch_times """ out = np.array([], dtype=np.float) if self.options.get('flat') is True: return out names = [] for instance in models.values(): times = np.array(instance.get_times()).ravel() out = np.append(out, times) out = np.append(out, times - eps) out = np.append(out, times + eps) names.extend([instance.class_name] * (3 * len(times))) # sort sort_idx = np.argsort(out).astype(int) out = out[sort_idx] names = [names[i] for i in sort_idx] # select t > current time ltzero_idx = np.where(out >= self.dae.t)[0] out = out[ltzero_idx] names = [names[i] for i in ltzero_idx] # make into an OrderedDict with unique keys and model names combined for i, j in zip(out, names): if i not in self.switch_dict: self.switch_dict[i] = {j: self.models[j]} else: self.switch_dict[i].update({j: self.models[j]}) self.switch_times = np.array(list(self.switch_dict.keys())) # self.switch_times = out self.n_switches = len(self.switch_times) return self.switch_times
def __init__(self, dae=None): self.dae = dae self._data = OrderedDict() self._z = OrderedDict() self.t = np.array([]) self.xy = np.array([]) self.z = np.array([]) self.df = None self.df_z = None
def __init__( self, name: Optional[str] = None, tex_name: Optional[str] = None, info: Optional[str] = None, unit: Optional[str] = None, v_str: Optional[Union[str, float]] = None, v_iter: Optional[str] = None, e_str: Optional[str] = None, discrete: Optional[Discrete] = None, v_setter: Optional[bool] = False, e_setter: Optional[bool] = False, addressable: Optional[bool] = True, export: Optional[bool] = True, diag_eps: Optional[float] = 0.0, ): self.name = name self.info = info self.unit = unit self.tex_name = tex_name if tex_name else name self.owner = None # instance of the owner Model self.id = None # variable internal index inside a model (assigned in run time) FIXME: not in use self.v_str = v_str # equation string (v = v_str) for variable initialization self.v_iter = v_iter # the implicit equation (0 = v_iter) for iterative initialization self.e_str = e_str # string for symbolic equation self.discrete = discrete self.v_setter = v_setter # True if this variable sets the variable value self.e_setter = e_setter # True if this var sets the equation value self.addressable = addressable # True if this var needs to be assigned an address FIXME: not in use self.export = export # True if this var's value needs to exported self.diag_eps = diag_eps # small diagonal value (1e-6) to be added to `dae.gy` # attributes assigned by `set_address` self.n = 0 self.a: ndarray = np.array([], dtype=int) # address array self.v: ndarray = np.array([], dtype=np.float) # variable value array self.e: ndarray = np.array([], dtype=np.float) # equation value array self.av: ndarray = np.array( [], dtype=int) # FIXME: future var. address array self.ae: ndarray = np.array( [], dtype=int) # FIXME: future equation address array # internal flags # NOTE: # contiguous is only True for internal variables of models with flag ``collate`` equal to ``False``. self._contiguous = False # True if if address is contiguous to allow slicing into arrays without copy. self.e_inplace = False # True if `self.e` is in-place access to `System.dae.__dict__[self.e_code]` self.v_inplace = False # True if `self.v` is in-place access to `System.dae.__dict__[self.v_code]` self.allow_none = False # True to allow None in address (NOT IN USE)
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 run(self, **kwargs): succeed = False system = self.system self.singular_idx = np.array([], dtype=int) if system.PFlow.converged is False: logger.warning( 'Power flow not solved. Eig analysis will not continue.') return succeed else: if system.TDS.initialized is False: system.TDS.init() system.TDS._itm_step() if system.dae.n == 0: logger.error('No dynamic model. Eig analysis will not continue.') else: if sum(system.dae.Tf != 0) != len(system.dae.Tf): self.singular_idx = np.argwhere(np.equal( system.dae.Tf, 0.0)).ravel().astype(int) logger.info( f"System contains {len(self.singular_idx)} zero time constants. " ) logger.debug([system.dae.x_name[i] for i in self.singular_idx]) self.x_name = np.array(system.dae.x_name) self.x_name = np.delete(self.x_name, self.singular_idx) self.summary() t1, s = elapsed() self.calc_state_matrix() self.remove_singular_rc() self.calc_part_factor() if not self.system.files.no_output: self.report() if system.options.get('state_matrix') is True: self.export_state_matrix() if self.config.plot: self.plot() _, s = elapsed(t1) logger.info('Eigenvalue analysis finished in {:s}.'.format(s)) succeed = True system.exit_code = 0 if succeed else 1 return succeed
def __init__(self, dae=None): self.dae = dae # internal dict storage self._xy = OrderedDict() self._z = OrderedDict() self.t = np.array([]) self.xy = np.array([]).reshape((-1, 1)) self.z = np.array([]).reshape((-1, 1)) # data frame members self.df = None self.df_z = None
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 nr_step(self): """ Single step using Newton-Raphson method. Returns ------- float maximum absolute mismatch """ system = self.system # evaluate discrete, differential, algebraic, and Jacobians system.dae.clear_fg() system.l_update_var(self.models, niter=self.niter, err=self.mis[-1]) system.s_update_var(self.models) system.f_update(self.models) system.g_update(self.models) system.l_update_eq(self.models) system.fg_to_dae() if self.config.method == 'NR': system.j_update(models=self.models) elif self.config.method == 'dishonest': if self.niter < self.config.n_factorize: system.j_update(self.models) # prepare and solve linear equations self.inc = -matrix([matrix(system.dae.f), matrix(system.dae.g)]) self.A = sparse([[system.dae.fx, system.dae.gx], [system.dae.fy, system.dae.gy]]) if not self.config.linsolve: self.inc = self.solver.solve(self.A, self.inc) else: self.inc = self.solver.linsolve(self.A, self.inc) system.dae.x += np.ravel(np.array(self.inc[:system.dae.n])) system.dae.y += np.ravel(np.array(self.inc[system.dae.n:])) mis = np.max(np.abs(system.dae.fg)) if self.niter == 0: self.mis[0] = mis else: self.mis.append(mis) system.vars_to_models() return mis
def __init__(self, system=None, config=None): super().__init__(system, config) self.config.add( OrderedDict(( ('tol', 1e-6), ('t0', 0.0), ('tf', 20.0), ('fixt', 1), ('shrinkt', 1), ('tstep', 1 / 30), ('max_iter', 15), ))) self.config.add_extra( "_help", tol="convergence tolerance", t0="simulation starting time", tf="simulation ending time", fixt="use fixed step size (1) or variable (0)", shrinkt='shrink step size for fixed method if not converged', tstep='the initial step step size', max_iter='maximum number of iterations', ) self.config.add_extra( "_alt", tol="float", t0=">=0", tf=">t0", fixt=(0, 1), shrinkt=(0, 1), tstep='float', max_iter='>=10', ) # overwrite `tf` from command line if system.options.get('tf') is not None: self.config.tf = system.options.get('tf') # to be computed self.deltat = 0 self.deltatmin = 0 self.deltatmax = 0 self.h = 0 self.next_pc = 0 self.eye = None self.Teye = None self.qg = np.array([]) self.tol_zero = self.config.tol / 100 # internal status self.converged = False self.busted = False self.err_msg = '' self.niter = 0 self._switch_idx = 0 # index into `System.switch_times` self._last_switch_t = -999 # the last critical time self.mis = 1 self.pbar = None self.callpert = None self.plotter = None self.plt = None self.initialized = False
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 __init__(self, u, center, lower, upper, enable=True): """ """ super().__init__(u, lower, upper, enable=enable) self.center = center # default state if not enabled self.zi = np.array([0.]) self.zl = np.array([0.]) self.zu = np.array([0.]) self.zur = np.array([0.]) self.zlr = np.array([0.]) self.export_flags = ['zl', 'zi', 'zu', 'zur', 'zlr'] self.export_flags_tex = ['z_l', 'z_i', 'z_u', 'z_ur', 'z_lr']
def __init__(self, u, lower, upper, enable=True, no_lower=False, no_upper=False, lower_cond=None, upper_cond=None, name=None, tex_name=None, info=None): Discrete.__init__(self, name=name, tex_name=tex_name, info=info) self.u = u self.rate_lower = dummify(lower) self.rate_upper = dummify(upper) # `lower_cond` and `upper_cond` are arrays of 0/1 indicating whether # the corresponding rate limit should be *enabled*. # 0 - disabled, 1 - enabled. # If is `None`, all rate limiters will be enabled. self.rate_lower_cond = dummify(lower_cond) self.rate_upper_cond = dummify(upper_cond) self.rate_no_lower = no_lower self.rate_no_upper = no_upper self.enable = enable self.zur = np.array([0]) self.zlr = np.array([0]) self.has_check_eq = True # Note: save ops by not calculating `zir` # self.zir = np.array([1]) # self.export_flags = ['zir'] # self.export_flags_tex = ['z_{ir}'] if not self.rate_no_lower: self.export_flags.append('zlr') self.export_flags_tex.append('z_{lr}') self.warn_flags.append(('zlr', 'lower')) if not self.rate_no_upper: self.export_flags.append('zur') self.export_flags_tex.append('z_{ur}') self.warn_flags.append(('zur', 'upper'))
class System(object): """ New power system class """ def __init__(self, case: Optional[str] = None, name: Optional[str] = None, config_path: Optional[str] = None, options: Optional[Dict] = None, **kwargs): self.name = name self.options = {} if options is None else options if kwargs: self.options.update(kwargs) self.calls = OrderedDict() self.models = OrderedDict() self.groups = OrderedDict() self.programs = OrderedDict() self.switch_times = np.array([]) # get and load default config file self.config = Config(self.__class__.__name__) self._config_path = get_config_path( ) if not config_path else config_path self._config_from_file = self.load_config(self._config_path) self.config.load(self._config_from_file) # custom configuration for system goes after this line self.config.add( OrderedDict(( ('freq', 60), ('mva', 100), ('store_z', 0), ))) self.files = FileMan() self.files.set(case=case, **self.options) self.dae = DAE(system=self) # dynamic imports: routine import need to query model flags self._group_import() self._model_import() self._routine_import() self._models_with_flag = { 'pflow': self.get_models_with_flag('pflow'), 'tds': self.get_models_with_flag('tds'), 'pflow_and_tds': self.get_models_with_flag(('tds', 'pflow')), } # ------------------------------ # FIXME: reduce clutter with defaultdict `adders` and `setters`, each with `x`, `y`, `f`, and `g` self.f_adders, self.f_setters = list(), list() self.g_adders, self.g_setters = list(), list() self.x_adders, self.x_setters = list(), list() self.y_adders, self.y_setters = list(), list() self.antiwindups = list()
def __init__(self, dae=None): self.dae = dae # internal dict storage self._data = OrderedDict() self._z = OrderedDict() self.t = np.array([]) self.xy = np.array([]) self.z = np.array([]) # data frame members self.df = None self.df_z = None # flags self._need_unpack = True
def __init__(self, system, config): ModelData.__init__(self) self.bus = IdxParam( model='Bus', info="linked bus idx", mandatory=True, ) self.tf = TimerParam( info='Fault start time for the bus', mandatory=True, callback=self.apply_fault, ) self.tc = TimerParam( info='Fault end time for the bus', callback=self.clear_fault, ) self.xf = NumParam( info='Fault to ground impedance', default=1e-4, tex_name='x_f', ) self.rf = NumParam( info='Fault to ground resistance', default=0, tex_name='x_f', ) Model.__init__(self, system, config) self.flags.update({'tds': True}) self.group = 'TimedEvent' self.gf = ConstService( tex_name='g_{f}', v_str='re(1/(rf + 1j * xf))', ) self.bf = ConstService( tex_name='b_{f}', v_str='im(1/(rf + 1j * xf))', ) self.uf = ConstService( tex_name='u_f', v_str='0', ) self.a = ExtAlgeb( model='Bus', src='a', indexer=self.bus, tex_name=r'\theta', e_str='u * uf * (v ** 2 * gf)', ) self.v = ExtAlgeb( model='Bus', src='v', indexer=self.bus, tex_name=r'V', e_str='u * uf * (v ** 2 * bf)', ) self._vstore = np.array([])
def __init__(self, v_str: Optional[str] = None, v_numeric: Optional[Callable] = None, vtype: Optional[type] = None, name: Optional[str] = None, tex_name=None, info=None): super().__init__(name=name, vtype=vtype, tex_name=tex_name, info=info) self.v_str = v_str self.v_numeric = v_numeric self.v: Union[float, int, ndarray] = np.array([0.])
def init(self, routine): """ Set values for the very first time step. """ Model.init(self, routine) self.apply_exact(np.array(self.system.TDS.config.t0)) logger.debug('<%s>: Initialization done', self.class_name)
def v(self): if self._v is None: self._v = [v1 if not np.isnan(v1) else v2 for v1, v2 in zip(self.optional.v, self.fallback.v)] self._v = np.array(self._v) return self._v