def read_admittance_data(self, element_type, element): # Reads data on specified element and returns admittances (series and shunt) # and indices in the admittance matrix where the admittances should be added. # Currently only for lines and transformers (other elements are treated directly # when building admittance matrices. buses = self.buses if element_type == 'line': line = element idx_from = dps_uf.lookup_strings(line['from_bus'], buses['name']) idx_to = dps_uf.lookup_strings(line['to_bus'], buses['name']) if line['unit'] in ['p.u.', 'pu', 'pu/km']: if 'S_n' in line.dtype.names and 'V_n' in line.dtype.names and line[ 'S_n'] != 0 and line['V_n'] != 0: # If impedance given in p.u./km impedance = (line['R'] + 1j * line['X']) * line['length'] * line['V_n'] ** 2 / line['S_n'] / \ self.z_n[idx_from] shunt = 1j * line['B'] * line['length'] * 1 / ( line['V_n']**2 / line['S_n'] / self.z_n[idx_from]) else: # Per unit of system base and bus nominal voltage impedance = (line['R'] + 1j * line['X']) * line['length'] shunt = 1j * line['B'] * line['length'] elif line['unit'] in ['PF', 'pf', 'PowerFactory', 'powerfactory']: # Given in ohm/km, but with capacitance in micro-Siemens impedance = (line['R'] + 1j * line['X']) * line['length'] / self.z_n[idx_from] shunt = 1j * line['B'] * line['length'] * self.z_n[ idx_from] * 1e-6 elif line['unit'] in ['Ohm', 'ohm']: # Given in Ohm/km impedance = (line['R'] + 1j * line['X']) * line['length'] / self.z_n[idx_from] shunt = 1j * line['B'] * line['length'] * self.z_n[idx_from] admittance = 1 / impedance return idx_from, idx_to, admittance, shunt elif element_type == 'transformer': trafo = element idx_from = dps_uf.lookup_strings(trafo['from_bus'], buses['name']) idx_to = dps_uf.lookup_strings(trafo['to_bus'], buses['name']) ratio_from = ( trafo['ratio_from'] if not np.isnan(trafo['ratio_from']) else 1) if 'ratio_from' in trafo.dtype.names else 1 ratio_to = (trafo['ratio_to'] if not np.isnan(trafo['ratio_to']) else 1) if 'ratio_to' in trafo.dtype.names else 1 V_n_from = trafo['V_n_from'] if trafo['V_n_from'] else self.v_n[ idx_from] Z_base_trafo = V_n_from**2 / trafo[ 'S_n'] # <= Could also have used _to instead of _from impedance = (trafo['R'] + 1j * trafo['X']) * Z_base_trafo / self.z_n[idx_from] n_par = trafo['N_par'] if 'N_par' in trafo.dtype.names else 1 admittance = n_par / impedance return idx_from, idx_to, admittance, ratio_from, ratio_to
def network_event(self, event_type, name, action, dS=0): # Simulate disconnection/connection of element by modifying admittance matrix if action == 'deactivate' or action == 'disconnect': sign = -1 elif action == 'activate' or action == 'connect': sign = 1 if event_type == 'line': df = getattr(self, 'lines') obj = df[dps_uf.lookup_strings(name, df['name'])] print('Line {} event'.format(name)) idx_from, idx_to, admittance, shunt = self.read_admittance_data( 'line', obj) rows = np.array([idx_from, idx_to, idx_from, idx_to]) cols = np.array([idx_from, idx_to, idx_to, idx_from]) data = np.array([ admittance + shunt / 2, admittance + shunt / 2, -admittance, -admittance ]) y_line = lil_matrix((self.n_bus, ) * 2, dtype=complex) y_line[rows, cols] = data self.y_bus_red += sign * y_line elif event_type == 'sc': print('Short circuit event at bus {}'.format(name)) idx = dps_uf.lookup_strings(name, self.buses['name']) self.y_bus_red[idx, idx] += 1j * sign * 1e15 elif event_type == 'load_change': print('Step change in load demand {}'.format(name)) y_b_old = self.build_y_bus() # Add the additional value and recalculate y_buses p0 = self.loads['P'] q0 = self.loads['Q'] boolean = (self.loads['name'] == name) p0[boolean] += np.real(dS) q0[boolean] += np.imag(dS) self.loads['P'] = p0 self.loads['Q'] = q0 busname = self.loads['bus'][boolean] idx = dps_uf.lookup_strings(busname, self.buses['name'])[0] y_b_new = self.build_y_bus() self.y_bus_red[idx, idx] += y_b_new[idx, idx] - y_b_old[idx, idx]
def build_y_bus_red(self, keep_extra_buses=['name']): # Builds the admittance matrix of the reduced system by applying Kron reduction. By default, all buses other # generator buses are eliminated. Additional buses to include in the reduced system can be specified # in "keep_extra_buses" (list of bus names). # If extra buses are specified before , store these. To ensure that the reduced admittance matrix has the same # dimension if rebuilt (by i.e. by network_event()-function. if len(keep_extra_buses) > 0: keep_extra_buses = self.buses['name'] # Override kron reduction keep_extra_buses_idx = dps_uf.lookup_strings( keep_extra_buses, self.buses['name']) self.reduced_bus_idx = np.concatenate( [self.gen_bus_idx, np.array(keep_extra_buses_idx, dtype=int)]) # Remove duplicate buses _, idx = np.unique(self.reduced_bus_idx, return_index=True) self.reduced_bus_idx = np.sort(self.reduced_bus_idx[np.sort(idx)]) self.n_bus_red = len(self.reduced_bus_idx) self.y_bus_red_full = self.kron_reduction( self.y_bus, self.reduced_bus_idx) # np.empty((self.n_gen, self.n_gen)) self.gen_bus_idx_red = self.get_bus_idx_red( self.buses[self.gen_bus_idx]['name']) self.y_bus_red = sp.csr_matrix(self.y_bus_red_full) self.y_bus_red_mod = self.y_bus_red * 0 self.build_y_branch()
def store_vars(self, type, varnames, vardesc, resultdict): if type == 'GEN': var_outs = sorted(set(varnames) & set(self.gen_mdls['GEN'].output_list), key=varnames.index) var_ins = sorted(set(varnames) & set(self.gen_mdls['GEN'].input_list), key=varnames.index) store_vars_out = self.gen_mdls['GEN'].output[var_outs] store_vars_out = [ i[k] for k in range(len(store_vars_out[0])) for i in store_vars_out ] store_vars_in = self.gen_mdls['GEN'].input[var_ins] store_vars_in = [ i[k] for k in range(len(store_vars_in[0])) for i in store_vars_in ] store_vars_out.extend(store_vars_in) [ resultdict[tuple(desc)].append(out) for desc, out in zip(vardesc, store_vars_out) ] elif type == 'load': if 'v' in varnames: [ resultdict[tuple(desc)].append(out) for desc, out in zip(vardesc, self.v_red) ] if any(x in ['P_l', 'Q_l', 'S_l'] for x in varnames): store_vars = [] for load in self.loads: name = load[0] load_idx = dps_uf.lookup_strings(name, self.loads['name']) z = self.loads['Z'][load_idx] bus_idx = self.loads['bus_idx'][ load_idx] # This works when kron reduction is bypassed! # if bus_idx in self.reduced_bus_idx: s = abs(self.v_red[bus_idx])**2 / np.conj(z) if 'P_l' in varnames: store_vars.append(np.real(s)) if 'Q_l' in varnames: store_vars.append(np.imag(s)) if 'S_l' in varnames: store_vars.append(s) [ resultdict[tuple(desc)].append(out) for desc, out in zip(vardesc, store_vars) ]
def compute_result(self, type, name, res): if type == 'load': if res in ['p', 'q', 's', 'P', 'Q', 'S']: load_idx = dps_uf.lookup_strings(name, self.loads['name']) z = self.loads['Z'][load_idx] bus_idx = self.loads['bus_idx'][ load_idx] # This works when kron reduction is bypassed! # if bus_idx in self.reduced_bus_idx: s = abs(self.v_red[bus_idx])**2 / np.conj(z) # Lower case letters (p, q, s) give result in system p.u. base # Capital letters (P, Q, S) give result in ohm if res.isupper(): pu_mod = self.s_n else: pu_mod = 1 if res in ['s', 'S']: return s * pu_mod if res in ['p', 'P']: return s.real * pu_mod if res in ['q', 'Q']: return s.imag * pu_mod
def init_dyn_sim(self): # Build reduced system self.y_bus = self.build_y_bus() self.build_y_bus_red() self.v_red = self.v_0[self.reduced_bus_idx] # State variables: self.state_desc = np.empty((0, 2)) # Containers for dynamic models self.gen_mdls = dict() self.gov_mdls = dict() self.avr_mdls = dict() self.pss_mdls = dict() # ADDED self.ace_mdls = dict() for i, (input_data, container, library) in enumerate( zip([self.gen, self.gov, self.pss, self.avr, self.ace], [ self.gen_mdls, self.gov_mdls, self.pss_mdls, self.avr_mdls, self.ace_mdls ], [gen_lib, gov_lib, pss_lib, avr_lib, ace_lib])): for key in input_data.keys(): data = input_data[key].copy() start_idx = len(self.state_desc) mdl = getattr(library, key)() state_list = mdl.state_list names = data['name'] n_units = len(data) n_states = len(state_list) # state_desc_mdl = np.vstack([np.tile(names, n_states), np.repeat(state_list, n_units)]).T state_desc_mdl = np.vstack( [np.repeat(names, n_states), np.tile(state_list, n_units)]).T mdl.n_units = n_units # Global indices of model states in global state vector. # (x_global[mdl.idx] = x_local) mdl.idx = slice(start_idx, start_idx + len(state_desc_mdl) ) # Indices of all states belonging to model # dtypes-attribute is defined to allow view to be created easily (with named fields) # Allows syntax x['speed'] instead of x[state_idx['speed']] # within differential equation functions of dyn. models. mdl.dtypes = [(state, np.float) for state in state_list] mdl.shape = (n_states, n_units) mdl.par = data mdl.int_par = np.zeros(n_units, [(par, np.float) for par in mdl.int_par_list]) mdl.input = np.zeros(n_units, [(field, np.float) for field in mdl.input_list]) mdl.output = np.zeros(n_units, [(field, np.float) for field in mdl.output_list]) # JIT-compilation using Numba compile_these = ['_update', '_current_injections'] if self.use_numba: from numba import jit # Is this OK? for fun in compile_these: if hasattr(mdl, fun): setattr(mdl, fun[1:], jit()(getattr(mdl, fun))) else: for fun in compile_these: if hasattr(mdl, fun): setattr(mdl, fun[1:], getattr(mdl, fun)) if i == 0: # Do this for generators only mdl.bus_idx = dps_uf.lookup_strings( data['bus'], self.buses['name']) mdl.bus_idx_red = self.get_bus_idx_red(data['bus']) mdl.int_par['f'] = self.f elif i == 4: # Do this for ACE only mdl.active = np.ones(len(data), dtype=bool) # Appended for knowing which generator are equipped with secondary control mdl.gen_idx = dict() for gen_key in self.gen.keys(): lookup, mask = dps_uf.lookup_strings( data['gen'], self.gen[gen_key]['name'], return_mask=True) if len(lookup) > 0: mdl.gen_idx[gen_key] = [mask, lookup] # Getting correct bus indexes for enabling efficient calculation of power flow buses_1 = [] buses_2 = [] buses_to_keep_1 = data['bus1'] buses_to_keep_2 = data['bus2'] for bus1, bus2 in zip(buses_to_keep_1, buses_to_keep_2): for bus in self.buses['name']: if bus == bus1: buses_1.append(bus) if bus == bus2: buses_2.append(bus) mdl.bus_idx_1 = dps_uf.lookup_strings( buses_1, self.buses['name']) mdl.bus_idx_2 = dps_uf.lookup_strings( buses_2, self.buses['name']) mdl.bus_idx_red_1 = self.get_bus_idx_red(buses_1) mdl.bus_idx_red_2 = self.get_bus_idx_red(buses_2) # Easifying syntax for the following lines idx1 = mdl.bus_idx_red_1 idx2 = mdl.bus_idx_red_2 # Extracting the relevant entries in y_bus_red, and converting to a list for vector multiplication y_bus_tmp = np.squeeze( np.asarray(self.y_bus_red[idx1, idx2])) i_line = y_bus_tmp * (self.v_red[idx1] - self.v_red[idx2]) # Calculating base line flow p_line = -(self.v_red[idx1] * np.conj(i_line)).real # Setting the constant parameters mdl.int_par['f'] = self.f mdl.int_par['Ptie0'] = p_line else: # Do this for control models only mdl.active = np.ones(len(data), dtype=bool) # Determine which generators are controlled (model and indices). # This information is stored as a dict with keys corresponding to generator types, # and values with lists: [mask, idx], where: # mask (np.array, bool): Boolean mask to select controls that act on the particular generator type # idx (np.array, int): Points to which generators of this type are controlled mdl.gen_idx = dict() for gen_key in self.gen.keys(): lookup, mask = dps_uf.lookup_strings( data['gen'], self.gen[gen_key]['name'], return_mask=True) if len(lookup) > 0: mdl.gen_idx[gen_key] = [mask, lookup] # Local indices of states (starting on zero, ending on number of states of model) mdl.state_idx = np.zeros( (n_units, ), dtype=[(state, int) for state in state_list]) for state in state_list: mdl.state_idx[state] = np.where( state_desc_mdl[:, 1] == state)[0] container[key] = mdl self.state_desc = np.vstack([self.state_desc, state_desc_mdl]) self.n_states = self.state_desc.shape[0] self.state_desc_der = self.state_desc.copy() self.state_desc_der[:, 1] = np.char.add(np.array(self.n_states * ['d_']), self.state_desc[:, 1]) # Choose first generator at slack bus as slack generator for gen_key, gen_mdl in self.gen_mdls.items(): lookup = dps_uf.lookup_strings(self.slack_bus, gen_mdl.par['bus']) if not np.isnan(lookup) > 0: self.slack_generator_id = (gen_key, lookup) break self.slack_generator = np.where( (self.gen_pf_map_idx == self.slack_generator_id).all(axis=1))[0][0] self.p_g_setp = self.P_g_setp / self.s_n # System base sum_load_sl = sum(self.loads[self.loads['bus'] == self.slack_bus] ['P']) / self.s_n if len( self.loads) > 0 else 0 # Sum loads at slack bus sl_gen_idx = np.array(self.gen_bus_names == self.slack_bus) # The case with multiple generators at the slack bus where one or more are n_par in parallel has not been tested. sum_gen_sl = sum( (self.p_g_setp[sl_gen_idx] * self.n_par[sl_gen_idx] )[1:]) # Sum other generation at slack bus (not slack gen) self.p_g_setp[self.slack_generator] = self.s_0[self.get_bus_idx( [self.slack_bus])].real - sum_gen_sl + sum_load_sl self.p_g_setp[self.slack_generator] /= self.n_par[self.slack_generator] # Distribute reactive power equally among generators on the same bus self.n_gen_per_bus = np.array([ sum(self.gen_bus_idx == idx) for idx in np.arange(self.n_bus) ]) # Not counting identical generators (given by N_par-parameter)! self.q_g = (self.s_0.imag + self.q_sum_loads_bus )[self.gen_bus_idx] / self.n_gen_per_bus[self.gen_bus_idx] self.q_g /= self.n_par # From Load Flow self.v_g = self.v_0[self.gen_bus_idx] self.s_g = (self.p_g_setp + 1j * self.q_g) # Initialize global state vector self.x0 = np.zeros(self.n_states) # Generators for key, dm in self.gen_mdls.items(): if hasattr(dm, 'initialize'): # Get data from power flow solution dm_pf_map_idx = self.gen_pf_map_idx[:, 0] == key v_g = self.v_g[dm_pf_map_idx] s_g = self.s_g[dm_pf_map_idx] * self.s_n / dm.par['S_n'] # Convert complex quantities to float dm.input['V_t_abs'] = np.abs(v_g) dm.input['V_t_angle'] = np.angle(v_g) dm.output['P_e'] = s_g.real dm.output['Q'] = s_g.imag dm.initialize(self.x0[dm.idx].view(dtype=dm.dtypes), dm.input, dm.output, dm.par, dm.int_par) # AVR for key, dm in self.avr_mdls.items(): if hasattr(dm, 'initialize'): for gen_key, (mask, idx) in dm.gen_idx.items(): gen_mdl = self.gen_mdls[gen_key] dm.output['E_f'][mask] = gen_mdl.input['E_f'][idx] # for gen_key in dm.gen_mdl_list: # e_q_0[dm.gen_mdl_3 == gen_key] = self.gen_mdls[gen_key].e_q[dm.gen_idx_2[gen_key]] dm.initialize(self.x0[dm.idx].view(dtype=dm.dtypes), dm.input, dm.output, dm.par, dm.int_par) # GOV for key, dm in self.gov_mdls.items(): if hasattr(dm, 'initialize'): for gen_key, (mask, idx) in dm.gen_idx.items(): # mask: Boolean mask to map controls to generators # idx: Points to which generators are controlled gen_mdl = self.gen_mdls[gen_key] dm.output['P_m'][mask] = gen_mdl.input['P_m'][idx] # for gen_key in dm.gen_mdl_list: # P_m_0[dm.gen_mdl_3 == gen_key] = self.gen_mdls[gen_key].P_m[dm.gen_idx_2[gen_key]] dm.initialize(self.x0[dm.idx].view(dtype=dm.dtypes), dm.input, dm.output, dm.par, dm.int_par) # ACE for key, dm in self.ace_mdls.items(): if hasattr(dm, 'initialize'): pass # PSS for key, dm in self.pss_mdls.items(): if hasattr(dm, 'initialize'): pass
def build_y_bus(self, type='dyn', y_ext=np.empty((0, 0))): # Build bus admittance matrix. # If type=='dyn', generator admittances and load admittances are included in the admittance matrix. # Used for dynamic simulation. # If not type=='dyn', generator admittances and load admittances are not included. # Used for power flow calculation. n_bus = self.n_bus y_branch = np.zeros((n_bus, n_bus), dtype=complex) # Branches = Trafos + Lines buses = self.buses y_lines = np.zeros((n_bus, n_bus), dtype=complex) for i, line in enumerate(self.lines): idx_from, idx_to, admittance, shunt = self.read_admittance_data( 'line', line) rows = np.array([idx_from, idx_to, idx_from, idx_to]) cols = np.array([idx_from, idx_to, idx_to, idx_from]) data = np.array([ admittance + shunt / 2, admittance + shunt / 2, -admittance, -admittance ]) y_lines[rows, cols] += data y_branch += y_lines y_trafo = np.zeros((n_bus, n_bus), dtype=complex) for i, trafo in enumerate(self.transformers): idx_from, idx_to, admittance, ratio_from, ratio_to = self.read_admittance_data( 'transformer', trafo) rows = np.array([idx_from, idx_to, idx_from, idx_to]) cols = np.array([idx_from, idx_to, idx_to, idx_from]) data = np.array([ ratio_from * np.conj(ratio_from) * admittance, ratio_to * np.conj(ratio_to) * admittance, -ratio_from * np.conj(ratio_to) * admittance, -np.conj(ratio_from) * ratio_to * admittance ]) y_trafo[rows, cols] += data y_branch += y_trafo y_gen = np.zeros((n_bus, n_bus), dtype=complex) for key, data in self.gen.items(): for gen in data: idx_bus = dps_uf.lookup_strings(gen['bus'], buses['name']) V_n = gen['V_n'] impedance = 1j * gen['X_d_st'] * V_n**2 / gen[ 'S_n'] / self.z_n[idx_bus] y_gen[idx_bus, idx_bus] += gen['N_par'] / impedance y_load = np.zeros((n_bus, n_bus), dtype=complex) if type == 'dyn': # Remove Z-field if it already exists since y_bus is to be modified during load increase if 'Z' in self.loads.dtype.names: self.loads = self.loads[['name', 'bus', 'P', 'Q', 'model']] # Add impedance field to load input parameters (to be able to compute actual power consumed by load) # Impedance is given in system p.u. base if len(self.loads) > 0: field_z = np.ones(len(self.loads), dtype=[('Z', complex)]) field_bus = np.ones(len(self.loads), dtype=[('bus_idx', int)]) for i, load in enumerate(self.loads): s_load = (load['P'] + 1j * load['Q']) / self.s_n if load['model'] == 'Z' and abs(s_load) > 0: # idx_bus = buses[buses['name'] == load['bus']].index idx_bus = dps_uf.lookup_strings(load['bus'], buses['name']) z = np.conj(abs(self.v_0[idx_bus])**2 / s_load) y_load[idx_bus, idx_bus] += 1 / z field_z[i] = z field_bus[i] = idx_bus if len(self.loads) > 0: self.loads = dps_uf.combine_recarrays(self.loads, field_z) self.loads = dps_uf.combine_recarrays(self.loads, field_bus) y_shunt = np.zeros((n_bus, n_bus), dtype=complex) for i, shunt in enumerate(self.shunts): # if shunt['model'] == 'Z': # idx_bus = buses[buses['name'] == shunt['bus']].index idx_bus = dps_uf.lookup_strings(shunt['bus'], buses['name']) s_shunt = -1j * shunt['Q'] / self.s_n z = np.conj(abs(1)**2 / s_shunt) y_shunt[idx_bus, idx_bus] += 1 / z self.y_gen = y_gen self.y_branch = y_branch self.y_load = y_load self.y_shunt = y_shunt self.y_ext = y_ext Y = y_branch.copy() Y += y_shunt if type == 'dyn': Y += y_gen + y_load if y_ext.shape[0] > 0: Y += y_ext return Y
def __init__(self, model): self.use_numba = False # Load flow parameters self.pf_max_it = 10 self.tol = 1e-8 # Get static data: Convert lists to np.arrays for td in ['buses', 'lines', 'loads', 'transformers', 'shunts']: if td in model and len(model[td]) > 0: header = model[td][0] # Get dtype for each column data = model[td][1:] data_T = list(map(list, zip(*data))) col_dtypes = [np.array(col).dtype for col in data_T] entries = [tuple(entry) for entry in model[td][1:]] dtypes = [(name_, dtype_) for name_, dtype_ in zip(header, col_dtypes)] setattr(self, td, np.array(entries, dtype=dtypes)) else: setattr(self, td, np.empty(0)) # Get dynamic data: Convert dicts with lists to dicts with np.arrays for td in ['gov', 'avr', 'pss', 'generators', 'ace']: # ADDED ACE setattr(self, td, dict()) if td in model and len(model[td]) > 0: for key in model[td].keys(): # Get dtype for each column header = model[td][key][0] data = model[td][key][1:] data_T = list(map(list, zip(*data))) col_dtypes = [np.array(col).dtype for col in data_T] entries = [tuple(entry) for entry in data] dtypes = [(name_, dtype_) for name_, dtype_ in zip(header, col_dtypes)] print(key) getattr(self, td)[key] = np.array(entries, dtype=dtypes) # Add some potentially missing fields for key in self.generators.keys(): for req_attr, default in zip(['PF_n', 'N_par'], [1, 1]): if not req_attr in self.generators[key].dtype.names: new_field = np.ones(len(self.generators[key]), dtype=[(req_attr, float)]) new_field[req_attr] *= default self.generators[key] = dps_uf.combine_recarrays( self.generators[key], new_field) # Base for pu-system self.f = model['f'] self.s_n = model['base_mva'] # For all buses self.v_n = np.array(self.buses['V_n']) self.z_n = self.v_n**2 / self.s_n self.i_n = self.s_n / (np.sqrt(3) * self.v_n) if 'slack_bus' in model: self.slack_bus = model['slack_bus'] else: self.slack_bus = None self.n_bus = len(self.buses) # If nominal gen. voltage or apparent power is zero, change to system base # (Needs to be done after nominal bus voltages have been loaded (self.v_n) for key in self.generators.keys(): fix_idx = self.generators[key]['V_n'] == 0 gen_bus_idx = dps_uf.lookup_strings(self.generators[key]['bus'], self.buses['name']) self.generators[key]['V_n'][fix_idx] = self.buses['V_n'][ gen_bus_idx][fix_idx] fix_idx = self.generators[key]['S_n'] == 0 self.generators[key]['S_n'][fix_idx] = self.s_n # This is for backwards compatibilty only. Should have only self.gen (dict) in the future. self.gen = self.generators self.generators = self.gen['GEN'] # Load flow data (concatenate data from all generator types) self.n_gen = sum([len(gen) for gen in self.gen.values()]) self.v_g_setp = np.concatenate([gen['V'] for gen in self.gen.values()]) self.P_g_setp = np.concatenate([gen['P'] for gen in self.gen.values()]) self.n_par = np.concatenate( [gen['N_par'] for gen in self.gen.values()]) self.gen_bus_names = np.concatenate( [gen['bus'] for gen in self.gen.values()]) self.gen_pf_map_idx = [] for key, gen in self.gen.items(): [self.gen_pf_map_idx.append((key, i)) for i in range(len(gen))] self.gen_pf_map_idx = np.array(self.gen_pf_map_idx) self.gen_bus_idx = dps_uf.lookup_strings(self.gen_bus_names, self.buses['name']) # Remove duplicate buses to get number of unique buses with generators _, idx = np.unique(self.gen_bus_idx, return_index=True) self.gen_bus_idx_unique = self.gen_bus_idx[np.sort(idx)] self.n_gen_bus = len(self.gen_bus_idx_unique) self.reduced_bus_idx = self.gen_bus_idx_unique # Initialize bus admittance matrices self.y_bus = np.empty((self.n_bus, self.n_bus), dtype=complex) self.y_bus_lf = np.empty((self.n_bus, self.n_bus), dtype=complex) self.y_bus_lf[:] = np.nan self.y_bus_red_full = np.empty((self.n_gen, self.n_gen), dtype=complex) # self.y_bus_red_inv = np.empty((self.n_gen, self.n_gen)) # Initialize dynamic variables self.v_g = np.empty(self.n_gen, dtype=complex) self.i_inj = np.empty(self.n_gen, dtype=complex) self.time = 0.0
def __init__(self, rts, update_freq=50, *args, **kwargs): super().__init__(*args, **kwargs) self.rts = rts self.ps = rts.ps self.dt = self.rts.dt G = nx.MultiGraph() G.add_nodes_from(ps.buses['name']) G.add_edges_from(ps.lines[['from_bus', 'to_bus']]) G.add_edges_from(ps.transformers[['from_bus', 'to_bus']]) # nx.draw(G) pos = nx.spring_layout(G) x = np.zeros(ps.n_bus) y = np.zeros(ps.n_bus) for i, key in enumerate(pos.keys()): x[i], y[i] = pos[key] #plt.scatter(x, y) n_edges = len(ps.lines) + len(ps.transformers) x_edge = np.zeros(2 * n_edges) y_edge = np.zeros(2 * n_edges) connect = np.zeros(2 * n_edges, dtype=bool) i = 0 for branches in [ps.lines, ps.transformers]: for line in branches: x_edge[2 * i] = x[dps_uf.lookup_strings( line['from_bus'], ps.buses['name'])] y_edge[2 * i] = y[dps_uf.lookup_strings( line['from_bus'], ps.buses['name'])] x_edge[2 * i + 1] = x[dps_uf.lookup_strings( line['to_bus'], ps.buses['name'])] y_edge[2 * i + 1] = y[dps_uf.lookup_strings( line['to_bus'], ps.buses['name'])] connect[2 * i] = True i += 1 #for load in ps.loads: dt = self.dt n_samples = 500 dt = 20e-3 self.colors = lambda i: pg.intColor(i, hues=9, values=1, maxValue=255, minValue=150, maxHue=360, minHue=0, sat=255, alpha=255) # Phasor diagram self.graphWidget = pg.GraphicsLayoutWidget(show=True, title="Phasors") # self.setCentralWidget(self.graphWidget) #self.phasor_0 = np.array([0, 1, 0.9, 1, 0.9, 1]) + 1j * np.array([0, 0, -0.1, 0, 0.1, 0]) plot_win = self.graphWidget.addPlot(title='Phasors') plot_win.setAspectLocked(True) plot_win.plot(x_edge, y_edge, connect=connect) plot_win.plot(x, y, pen=None, symbol='o', symbolBrush='b', symbolSize=15) plot_win.plot(x[ps.gen_bus_idx], y[ps.gen_bus_idx], pen=None, symbol='o', symbolBrush='r', symbolSize=15) #plot_win.plot(x[ps.gen_bus_idx], y[ps.gen_bus_idx], pen=None, symbol='o', symbolBrush='y', symbolSize=15) #self.colors = lambda i: pg.intColor(i, hues=9, values=1, maxValue=255, minValue=150, maxHue=360, minHue=0, sat=255, alpha=255) #pen = pg.mkPen(color=self.colors(0), width=2) #self.graphWidget.plot(self.ts_keeper.time, np.zeros(n_samples), pen=pen) #scatter = pg.ScatterPlotItem(size=ps.n_bus, brush=pg.mkBrush(255, 255, 255, 120)) #angle = self.rts.x[self.ps.gen_mdls['GEN'].state_idx['angle']] #magnitude = self.ps.e_q #phasors = magnitude*np.exp(1j*angle) #self.pl_ph = [] #for i, phasor in enumerate(phasors[:, None]*self.phasor_0): # pen = pg.mkPen(color=self.colors(i), width=2) # self.pl_ph.append(plot_win_ph.plot(phasor.real, phasor.imag, pen=pen)) #plot_win_ph.enableAutoRange('xy', False) self.graphWidget.show() self.timer = QtCore.QTimer() self.timer.timeout.connect(self.update) self.timer.start(1000 / update_freq)
def __init__(self, rts, update_freq=50, z_ax='abs_pu', use_colors=False, rotating=False, *args, **kwargs): super().__init__(*args, **kwargs) self.rts = rts self.ps = rts.ps self.dt = self.rts.dt ps = self.ps self.z_ax = z_ax # nx.draw(G) self.scale = 10 if self.z_ax == 'angle': self.scale_z = 10*np.ones(ps.n_bus) self.offset_z = 3 # *np.ones(ps.n_bus) elif self.z_ax == 'abs': self.scale_z = 10 * ps.v_n / max(ps.v_n) * 0.3 self.offset_z = 0 # np.zeros(ps.n_bus) elif self.z_ax == 'abs_pu': self.scale_z = 10 * 0.3*(ps.v_n**0) self.offset_z = 0 # np.zeros(ps.n_bus) # elif self.z_ax == 'both': # self.scale_z = 10*np.ones(ps.n_bus) # self.offset_z = 12*np.ones(ps.n_bus) line_admittances = np.zeros(len(ps.lines), dtype=[('Y', float)]) for i, line in enumerate(ps.lines): line_admittances[i] = abs(ps.read_admittance_data('line', line)[2]) trafo_admittances = np.zeros(len(ps.transformers), dtype=[('Y', float)]) for i, trafo in enumerate(ps.transformers): trafo_admittances[i] = abs(ps.read_admittance_data('transformer', trafo)[2]) self.G = nx.MultiGraph() self.G.add_nodes_from(ps.buses['name']) # G.add_edges_from(ps.lines[['from_bus', 'to_bus']]) # G.add_edges_from(ps.transformers[['from_bus', 'to_bus']]) self.G.add_weighted_edges_from( dps_uf.combine_recarrays(ps.lines, line_admittances)[['from_bus', 'to_bus', 'Y']]) self.G.add_weighted_edges_from( dps_uf.combine_recarrays(ps.transformers, trafo_admittances)[['from_bus', 'to_bus', 'Y']]) self.grid_layout() self.n_edges = len(ps.lines) + len(ps.transformers) self.edge_from_bus = np.concatenate([dps_uf.lookup_strings(type_['from_bus'], ps.buses['name']) for type_ in [ps.lines, ps.transformers]]) self.edge_to_bus = np.concatenate([dps_uf.lookup_strings(type_['to_bus'], ps.buses['name']) for type_ in [ps.lines, ps.transformers]]) self.edge_x = np.vstack([self.x[self.edge_from_bus], self.x[self.edge_to_bus]]).T self.edge_y = np.vstack([self.y[self.edge_from_bus], self.y[self.edge_to_bus]]).T self.edge_z = np.vstack([self.z[self.edge_from_bus], self.z[self.edge_to_bus]]).T self.colors = lambda i: pg.intColor(i, hues=9, values=1, maxValue=255, minValue=150, maxHue=360, minHue=0, sat=255, alpha=255) self.window = gl.GLViewWidget() # self.window.setBackgroundColor('w') self.window.setWindowTitle('Grid') self.window.setGeometry(0, 110, 1920, 1080) self.window.setCameraPosition(distance=30, elevation=12) self.window.show() self.rotating = rotating self.gz = gl.GLGridItem() self.gz.translate(dx=0, dy=0, dz=-self.offset_z) self.window.addItem(self.gz) color = np.ones((ps.n_bus, 4)) color[:, -1] = 0.5 if use_colors: color[ps.gen_bus_idx, :] = np.array([self.colors(i).getRgb() for i in range(self.ps.n_gen_bus)]) / 255 else: color[ps.gen_bus_idx, :] = np.array([1 / 3, 2 / 3, 1, 0.5])[None, :] self.points = gl.GLScatterPlotItem( pos=np.vstack([self.x, self.y, self.z]).T, color=color, size=15 ) self.edge_x_mod = np.append(self.edge_x, np.nan*np.ones((self.n_edges, 1)), axis=1) self.edge_y_mod = np.append(self.edge_y, np.nan*np.ones((self.n_edges, 1)), axis=1) self.edge_z_mod = np.append(self.edge_z, np.nan*np.ones((self.n_edges, 1)), axis=1) # line_x_mod = line_x # line_y_mod = line_y edge_pos = np.vstack([self.edge_x_mod.flatten(), self.edge_y_mod.flatten(), self.edge_z_mod.flatten()]).T line_color = np.ones((self.n_edges, 4)) line_color[:, -1] = 0.5 line_color[len(ps.lines):, :] = np.array([1 / 3, 2 / 3, 1, 0.5])[None, :] # self.scale_branch_flows = 4 line_widths = np.ones((self.n_edges, 4)) self.lines = gl.GLLinePlotItem(pos=edge_pos, color=np.repeat(line_color, 3, axis=0), antialias=True, width=2) self.window.addItem(self.points) self.window.addItem(self.lines) # self.axis = gl.GLAxisItem() # self.graphWidget.show() self.timer = QtCore.QTimer() self.timer.timeout.connect(self.update) self.timer.start(1000//update_freq) self.t_prev = time.time()
model = model_data.load() #if not model['gov_on']: model.pop('gov', None) #if not model['avr_on']: model.pop('avr', None) #if not model['pss_on']: model.pop('pss', None) fig, ax = plt.subplots(1) ps = dps.PowerSystemModel(model) ps.power_flow() ps.init_dyn_sim() # Index to access relevant models, e.g. generators, avrs, govs etc. # Without index, the change is applied to all instances of specified model, e.g. all 'GEN' generators, or all 'TGR' avrs # index = dps_uf.lookup_strings('G1', ps.gen_mdls['GEN'].par['name']) # Index for G1 index = dps_uf.lookup_strings( 'HVDC_CTRL1', ps.hvdc_ctrl_mdls['HVDC_WASH_LEAD_LAG'].par['name']) #index = dps_uf.lookup_strings('BAT_CTRL_WASH_1', ps.bat_ctrl_mdls['WASH_LEAD_LAG'].par['name']) # index = dps_uf.lookup_strings('AVR1', ps.avr_mdls['TGR'].par['name']) # Index for AVR1 # index = dps_uf.lookup_strings(['G1','IB'], ps.gen_mdls['GEN'].par['name']) # Indices for G1 and IB for i in range(10): ps.init_dyn_sim() print(index) ps.hvdc_ctrl_mdls['HVDC_WASH_LEAD_LAG'].par['K'][index] = 0 + i * 1 #ps.gen_mdls['GEN'].par['H'][index] = 2 + i*0.1 # Perform system linearization ps_lin = dps_mdl.PowerSystemModelLinearization(ps) ps_lin.linearize() #pf, rev_Abs, rev_ang = ps_lin.pf_table()
import dynpssimpy.utility_functions as dps_uf model = model_data.load() if not model['gov_on']: model.pop('gov', None) if not model['avr_on']: model.pop('avr', None) if not model['pss_on']: model.pop('pss', None) fig, ax = plt.subplots(1) ps = dps.PowerSystemModel(model) ps.power_flow() ps.init_dyn_sim() # Index to access relevant models, e.g. generators, avrs, govs etc. # Without index, the change is applied to all instances of specified model, e.g. all 'GEN' generators, or all 'TGR' avrs index = dps_uf.lookup_strings('G1', ps.gen_mdls['GEN'].par['name']) # Index for G1 # index = dps_uf.lookup_strings('AVR1', ps.avr_mdls['TGR'].par['name']) # Index for AVR1 # index = dps_uf.lookup_strings(['G1','IB'], ps.gen_mdls['GEN'].par['name']) # Indices for G1 and IB for i in range(30): ps.init_dyn_sim() print(index) ps.avr_mdls['TGR'].par['Ka'][index] = 10 + i * 30 #ps.gen_mdls['GEN'].par['H'][index] = 2 + i*0.1 # Perform system linearization ps_lin = dps_mdl.PowerSystemModelLinearization(ps) ps_lin.linearize() #pf, rev_Abs, rev_ang = ps_lin.pf_table()