Source code for VeraGridEngine.IO.matpower.legacy.matpower_parser

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
# SPDX-License-Identifier: MPL-2.0

from typing import Dict, Tuple, List, Union
import numpy as np
import pandas as pd

from VeraGridEngine.basic_structures import Logger
from VeraGridEngine.enumerations import TapModuleControl, TapPhaseControl, ConverterControlType
from VeraGridEngine.Devices.multi_circuit import MultiCircuit
import VeraGridEngine.Devices as dev
import VeraGridEngine.IO.matpower.legacy.matpower_branch_definitions as matpower_branches
import VeraGridEngine.IO.matpower.legacy.matpower_bus_definitions as matpower_buses
import VeraGridEngine.IO.matpower.legacy.matpower_gen_definitions as matpower_gen
from VeraGridEngine.IO.matpower.veragrid_to_matpower import build_matpower_case_dict


[docs] def find_between(s: str, first: str, last: str) -> str: """ Find sting between two sub-strings Args: s: Main string first: first sub-string last: second sub-string Example find_between('[Hello]', '[', ']') -> returns 'Hello' Returns: String between the first and second sub-strings, if any was found otherwise returns an empty string """ try: start = s.index(first) + len(first) end = s.index(last, start) return s[start:end] except ValueError: return ""
[docs] def txt2mat(txt: str, line_splitter=';', to_float=True): """ :param txt: :param line_splitter: :param to_float: :return: """ lines = txt.strip().split('\n') # del lines[-1] # preprocess lines (delete the comments) lines2 = list() for i, line in enumerate(lines): if line.lstrip()[0] != '%': lines2.append(line) else: # print('skipping', line) pass # convert the lines to data nrows = len(lines2) arr = None for i, line in enumerate(lines2): if ';' in line: line2 = line.split(line_splitter)[0] else: line2 = line vec = line2.strip().split() # declare the container array based on the first line if arr is None: ncols = len(vec) if to_float: arr = np.zeros((nrows, ncols)) else: arr = np.zeros((nrows, ncols), dtype=object) # fill-in the data for j, val in enumerate(vec): if to_float: arr[i, j] = float(val) else: arr[i, j] = val.strip().replace("'", "") return np.array(arr)
[docs] def parse_areas_data(circuit: MultiCircuit, data: Dict[str, np.ndarray], logger: Logger): """ Parse Matpower / FUBM Matpower area data into VeraGrid :param circuit: MultiCircuit instance :param data: data dictionary :param logger: Logger :return: area index -> object dictionary """ area_idx_dict = dict() if 'areas' in data.keys(): table = data['areas'] if table.shape[0] > 0: # if there are areas declared, clean the default areas circuit.areas = list() for i in range(table.shape[0]): area_idx = int(table[i, 0]) area_ref_bus_idx = table[i, 1] a = dev.Area(name='Area ' + str(area_idx), code=str(area_idx)) area_idx_dict[area_idx] = (a, area_ref_bus_idx) circuit.add_area(a) if i == 0: # set the default area circuit.default_area = circuit.areas[0] return area_idx_dict
[docs] def parse_buses_data(circuit: MultiCircuit, data: Dict[str, np.ndarray], area_idx_dict, logger: Logger): """ Parse Matpower / FUBM Matpower bus data into VeraGrid :param circuit: MultiCircuit instance :param data: data dictionary :param area_idx_dict: area index -> object dictionary :param logger: Logger :return: bus index -> object dictionary """ # Buses table = data['bus'] n = table.shape[0] # load profiles if 'Lprof' in data.keys(): Pprof = data['Lprof'] Qprof = data['LprofQ'] are_load_prfiles = True print('There are load profiles') else: are_load_prfiles = False if 'bus_names' in data.keys(): names = data['bus_names'] else: names = ['bus ' + str(int(table[i, matpower_buses.BUS_I])) for i in range(n)] # Buses bus_idx_dict = dict() for i in range(n): # Create bus area_idx = int(table[i, matpower_buses.BUS_AREA]) bus_idx = int(table[i, matpower_buses.BUS_I]) is_slack = False if area_idx in area_idx_dict.keys(): area, ref_idx = area_idx_dict[area_idx] if ref_idx == bus_idx: is_slack = True else: area = None code = str(bus_idx) bus = dev.Bus(name=names[i], code=code, Vnom=table[i, matpower_buses.BASE_KV], vmax=table[i, matpower_buses.VMAX], vmin=table[i, matpower_buses.VMIN], area=area, is_slack=is_slack, Vm0=table[i, matpower_buses.VM], Va0=np.deg2rad(table[i, matpower_buses.VA])) # store the given bus index in relation to its real index in the table for later bus_idx_dict[table[i, matpower_buses.BUS_I]] = i # determine if the bus is set as slack manually bus.is_slack = table[i, matpower_buses.BUS_TYPE] == matpower_buses.REF # Add the bus to the circuit buses circuit.add_bus(bus) # Add the load if table[i, matpower_buses.PD] != 0 or table[i, matpower_buses.QD] != 0: load = dev.Load(P=table[i, matpower_buses.PD], Q=table[i, matpower_buses.QD]) circuit.add_load(bus=bus, api_obj=load) # Add the shunt if table[i, matpower_buses.GS] != 0 or table[i, matpower_buses.BS] != 0: shunt = dev.Shunt(G=table[i, matpower_buses.GS], B=table[i, matpower_buses.BS]) circuit.add_shunt(bus=bus, api_obj=shunt) return bus_idx_dict
[docs] def parse_generators(circuit: MultiCircuit, data: Dict[str, np.ndarray], bus_idx_dict, logger: Logger): """ Parse Matpower / FUBM Matpower generator data into VeraGrid :param circuit: MultiCircuit instance :param data: data dictionary :param bus_idx_dict: bus index -> object dictionary :param logger: Logger :return: """ # Generators table = data['gen'] n = table.shape[0] # load profiles if 'Gprof' in data.keys(): Gprof = data['Gprof'] are_gen_prfiles = True print('There are gen profiles') else: are_gen_prfiles = False if 'gen_names' in data.keys(): names = data['gen_names'] else: names = ['gen ' + str(i) for i in range(n)] gen_dict = dict() for i in range(n): bus_idx = bus_idx_dict[int(table[i, matpower_gen.GEN_BUS])] # TODO: Calculate pf based on reactive_power gen = dev.Generator(name=names[i], P=float(table[i, matpower_gen.PG]), vset=float(table[i, matpower_gen.VG]), Qmax=float(table[i, matpower_gen.QMAX]), Qmin=float(table[i, matpower_gen.QMIN]), Pmin=float(table[i, matpower_gen.PMIN]), Pmax=float(table[i, matpower_gen.PMAX]) ) gen_dict[i] = gen # Add the generator to the bus gen.bus = circuit.buses[bus_idx] circuit.add_generator(bus=circuit.buses[bus_idx], api_obj=gen) if 'gencost' in data: # parse the OPF data opf_table = data['gencost'] for i in gen_dict.keys(): curve_model = opf_table[i, 0] gen_dict[i].startup_cost = opf_table[i, 1] gen_dict[i].shutdown_cost = opf_table[i, 2] n_cost = opf_table[i, 3] points = opf_table[i, 4:] if curve_model == 2: if len(points) == 3: gen_dict[i].Cost0 = points[2] gen_dict[i].Cost = points[1] gen_dict[i].Cost2 = points[0] elif len(points) == 2: gen_dict[i].Cost = points[1] gen_dict[i].Cost0 = points[0] elif len(points) == 1: gen_dict[i].Cost = points[0] else: logger.add_warning("No curve points declared", gen_dict[i].name, curve_model) elif curve_model == 1: # fit a quadratic curve x = points[0::1] y = points[0::2] if len(x) == len(y): coeff = np.polyfit(x, y, 2) gen_dict[i].Cost = coeff[1] else: logger.add_warning("Curve x not the same length as y", gen_dict[i].name, curve_model) else: logger.add_warning("Unsupported curve model", gen_dict[i].name, curve_model)
# if gen_dict[i].Cost == 0.0: # gen_dict[i].enabled_dispatch = False
[docs] def parse_branches_data(circuit: MultiCircuit, data: Dict[str, np.ndarray], bus_idx_dict, logger: Logger): """ Parse Matpower / FUBM Matpower branch data into VeraGrid :param circuit: MultiCircuit instance :param data: data dictionary :param bus_idx_dict: bus index -> object dictionary :param logger: Logger :return: Nothing """ # Branches table = data['branch'] n = table.shape[0] if table.shape[1] == 37: # FUBM model logger.add_info('It is a FUBM model') if 'branch_names' in data.keys(): names = data['branch_names'] else: names = ['branch ' + str(i) for i in range(n)] for i in range(n): f_idx = int(table[i, matpower_branches.F_BUS]) t_idx = int(table[i, matpower_branches.T_BUS]) bus_f = circuit.buses[bus_idx_dict[f_idx]] bus_t = circuit.buses[bus_idx_dict[t_idx]] if table.shape[1] == 37: # FUBM model # converter type (I, II, III) matpower_converter_mode = table[i, matpower_branches.CONV_A] # determine the converter control mode Pfset = table[i, matpower_branches.PF] Ptset = table[i, matpower_branches.PT] Vt_set = table[i, matpower_branches.VT_SET] Vf_set = table[i, matpower_branches.VF_SET] # dc voltage Qfset = table[i, matpower_branches.QF] Qtset = table[i, matpower_branches.QT] m = table[i, matpower_branches.TAP] if table[i, matpower_branches.TAP] > 0 else 1.0 tap_phase = np.deg2rad(table[i, matpower_branches.SHIFT]) v_set = 1.0 Pset = 0.0 Qset = 0.0 control_bus = None is_transformer = (bus_f.Vnom != bus_t.Vnom or (table[i, matpower_branches.TAP] != 1.0 and table[i, matpower_branches.TAP] != 0) or table[i, matpower_branches.SHIFT] != 0.0 or Pfset != 0.0 or Ptset != 0.0 or Qtset != 0.0 or Qfset != 0.0 or Vf_set != 0.0 or Vt_set != 0.0) # tau based controls if Pfset != 0.0: tap_phase_control_mode = TapPhaseControl.Pf Pset = Pfset elif Ptset != 0.0: tap_phase_control_mode = TapPhaseControl.Pt Pset = Ptset else: tap_phase_control_mode = TapPhaseControl.fixed # m based controls if Qtset != 0.0: tap_module_control_mode = TapModuleControl.Qt Qset = Qtset elif Qfset != 0.0: tap_module_control_mode = TapModuleControl.Qf Qset = Qtset elif Vt_set != 0.0: tap_module_control_mode = TapModuleControl.Vm v_set = Vt_set control_bus = bus_t elif Vf_set != 0.0: tap_module_control_mode = TapModuleControl.Vm v_set = Vf_set control_bus = bus_f else: tap_module_control_mode = TapModuleControl.fixed if matpower_converter_mode > 0: # it is a converter """ FUBM control chart Type I are the ones making Qf = 0, therefore each DC grid must have at least one Type II control the voltage, and DC grids must have at least one Type III are the droop controlled ones, there may be one Control Mode Constraint1 Constraint2 VSC type 1 Pf vdc -> Vf I 2 Pf Qac -> Qt I 3 Pf vac -> Vt I 4 vdc -> Vf Qac -> Qt II 5 vdc -> Vf vac -> Vt II 6 vdc droop Qac -> Qt III 7 vdc droop vac -> Vt III """ control1 = None control2 = None control1val = 0.0 control2val = 0.0 # tau based controls if Pfset != 0.0: control1 = ConverterControlType.Pdc control1val = Pfset elif Ptset != 0.0: control1 = ConverterControlType.Pac control1val = Ptset else: control1 = ConverterControlType.Qac control1val = 0.0 # m based controls if Qtset != 0.0: control2 = ConverterControlType.Qac control2val = Qtset elif Qfset != 0.0: control2 = ConverterControlType.Qac control2val = 0.0 elif Vt_set != 0.0: control2 = ConverterControlType.Vm_ac control2val = Vt_set elif Vf_set != 0.0: control2 = ConverterControlType.Vm_dc control2val = Vf_set else: control2 = ConverterControlType.Qac control2val = 0.0 # set the from bus as a DC bus # this is by design of the matpower FUBM model, # if it is a converter, # the DC bus is always the "from" bus bus_f.is_dc = True if matpower_converter_mode == 1: # Type I: normal converter pass elif matpower_converter_mode == 2: # Type II: voltage controlling converter (slack converter) pass elif matpower_converter_mode == 3: # Type III: Power-voltage droop pass else: pass rate = max(table[i, [matpower_branches.RATE_A, matpower_branches.RATE_B, matpower_branches.RATE_C]]) if rate == 0.0: # in matpower rate=0 means not limited by rating rate = 10000 monitor_loading = False else: monitor_loading = True # TODO: Figure this one out branch = dev.VSC(bus_from=bus_f, bus_to=bus_t, code="{0}_{1}_1".format(f_idx, t_idx), name='VSC' + str(len(circuit.vsc_devices) + 1), active=bool(table[i, matpower_branches.BR_STATUS]), # r=table[i, matpower_branches.BR_R], # x=table[i, matpower_branches.BR_X], # tap_module=m, # tap_module_max=table[i, matpower_branches.MA_MAX], # tap_module_min=table[i, matpower_branches.MA_MIN], # tap_phase=tap_phase, # tap_phase_max=np.deg2rad(table[i, matpower_branches.SH_MAX]), # tap_phase_min=np.deg2rad(table[i, matpower_branches.SH_MIN]), # G0sw=table[i, matpower_branches.GSW], # Beq=table[i, matpower_branches.BEQ], # Beq_max=table[i, matpower_branches.BEQ_MAX], # Beq_min=table[i, matpower_branches.BEQ_MIN], rate=rate, kdp=table[i, matpower_branches.KDP], k=table[i, matpower_branches.K2], # tap_phase_control_mode=tap_phase_control_mode, # tap_module_control_mode=tap_module_control_mode, # Pset=Pset, # Qset=Qset, # vset=v_set, alpha1=table[i, matpower_branches.ALPHA1], alpha2=table[i, matpower_branches.ALPHA2], alpha3=table[i, matpower_branches.ALPHA3], monitor_loading=monitor_loading, control1=control1, control2=control2, control1_val=control1val, control2_val=control2val) branch.regulation_bus = control_bus circuit.add_vsc(obj=branch) logger.add_info('Branch as converter', 'Branch {}'.format(str(i + 1))) elif is_transformer: rate = table[i, matpower_branches.RATE_A] if rate == 0.0: # in matpower rate=0 means not limited by rating rate = 10000 monitor_loading = False else: monitor_loading = True branch = dev.Transformer2W(bus_from=bus_f, bus_to=bus_t, code="{0}_{1}_1".format(f_idx, t_idx), name=names[i], r=float(table[i, matpower_branches.BR_R]), x=float(table[i, matpower_branches.BR_X]), g=0.0, b=float(table[i, matpower_branches.BR_B]), rate=rate, active=bool(table[i, matpower_branches.BR_STATUS]), monitor_loading=monitor_loading, tap_module=m, tap_module_max=float(table[i, matpower_branches.MA_MAX]), tap_module_min=float(table[i, matpower_branches.MA_MIN]), tap_phase=tap_phase, tap_phase_max=np.deg2rad(table[i, matpower_branches.SH_MAX]), tap_phase_min=np.deg2rad(table[i, matpower_branches.SH_MIN]), tap_phase_control_mode=tap_phase_control_mode, tap_module_control_mode=tap_module_control_mode, Pset=Pset, Qset=Qset, vset=v_set) branch.regulation_bus = control_bus circuit.add_transformer2w(obj=branch) logger.add_info('Branch as 2w transformer', 'Branch {}'.format(str(i + 1))) else: rate = table[i, matpower_branches.RATE_A] if rate == 0.0: # in matpower rate=0 means not limited by rating rate = 10000 monitor_loading = False else: monitor_loading = True branch = dev.Line(bus_from=bus_f, bus_to=bus_t, code="{0}_{1}_1".format(f_idx, t_idx), name=names[i], r=table[i, matpower_branches.BR_R], x=table[i, matpower_branches.BR_X], b=table[i, matpower_branches.BR_B], rate=rate, monitor_loading=monitor_loading, active=bool(table[i, matpower_branches.BR_STATUS])) circuit.add_line(obj=branch, logger=logger) logger.add_info('Branch as line', 'Branch {}'.format(str(i + 1))) else: if (bus_f.Vnom != bus_t.Vnom or (table[i, matpower_branches.TAP] != 1.0 and table[i, matpower_branches.TAP] != 0) or table[i, matpower_branches.SHIFT] != 0.0): rate = table[i, matpower_branches.RATE_A] if rate == 0.0: # in matpower rate=0 means not limited by rating rate = 10000 monitor_loading = False else: monitor_loading = True branch = dev.Transformer2W(bus_from=bus_f, bus_to=bus_t, code="{0}_{1}_1".format(f_idx, t_idx), name=names[i], r=float(table[i, matpower_branches.BR_R]), x=float(table[i, matpower_branches.BR_X]), g=0.0, b=float(table[i, matpower_branches.BR_B]), rate=rate, monitor_loading=monitor_loading, tap_module=float(table[i, matpower_branches.TAP]), tap_phase=np.deg2rad(table[i, matpower_branches.SHIFT]), # * np.pi / 180, active=bool(table[i, matpower_branches.BR_STATUS])) circuit.add_transformer2w(obj=branch) logger.add_info('Branch as 2w transformer', 'Branch {}'.format(str(i + 1))) else: rate = table[i, matpower_branches.RATE_A] if rate == 0.0: # in matpower rate=0 means not limited by rating rate = 10000 monitor_loading = False else: monitor_loading = True branch = dev.Line(bus_from=bus_f, bus_to=bus_t, code="{0}_{1}_1".format(f_idx, t_idx), name=names[i], r=table[i, matpower_branches.BR_R], x=table[i, matpower_branches.BR_X], b=table[i, matpower_branches.BR_B], rate=rate, monitor_loading=monitor_loading, active=bool(table[i, matpower_branches.BR_STATUS])) circuit.add_line(obj=branch, logger=logger) logger.add_info('Branch as line', 'Branch {}'.format(str(i + 1))) # convert normal lines into DC-lines if needed for line in circuit.lines: if line.bus_to.is_dc and line.bus_from.is_dc: dc_line = dev.DcLine(bus_from=line.bus_from, bus_to=line.bus_to, code=line.code, name=line.name, active=line.active, rate=line.rate, r=line.R) dc_line.active_prof = line.active_prof dc_line.rate_prof = line.rate_prof # add device to the circuit circuit.add_dc_line(obj=dc_line) # delete_with_dialogue the line from the circuit circuit.delete_line(line) logger.add_info('Converted to DC line', line.name)
[docs] def interpret_data_v1(data, logger: Logger) -> MultiCircuit: """ Pass the loaded table-like data to the structures :param data: Data dictionary :param logger: Logger :return: """ circuit = MultiCircuit() # time profile if 'master_time' in data.keys(): master_time_array = data['master_time'] else: master_time_array = None # areas area_idx_dict = parse_areas_data(circuit, data, logger) # parse buses bus_idx_dict = parse_buses_data(circuit, data, area_idx_dict, logger) # parse generators parse_generators(circuit, data, bus_idx_dict, logger) # parse Branches parse_branches_data(circuit, data, bus_idx_dict, logger) # add the profiles if master_time_array is not None: circuit.format_profiles(pd.DatetimeIndex(master_time_array)) return circuit
[docs] def read_matpower_file(filename: str, logger: Logger) -> Dict[str, np.ndarray]: """ Read a Matpower case and return the structures :param filename: :param logger: :return: """ # open the file as text with open(filename, 'r') as myfile: text = myfile.read() # split the file into its case variables (the case variables always start with 'mpc.') chunks = text.split('mpc.') # declare circuit circuit = MultiCircuit() data = dict() matpower_Sbase = 100.0 # further process the loaded text for chunk in chunks: if ',' in chunk: chunk = chunk.replace(',', '') vals = chunk.split('=') key = vals[0].strip() if key == "baseMVA": v = find_between(chunk, '=', ';') matpower_Sbase = float(v) elif key == "bus": if chunk.startswith("bus_name"): v = txt2mat(find_between(chunk, '{', '}'), line_splitter=';', to_float=False) v = np.ndarray.flatten(v) data['bus_names'] = v else: data['bus'] = txt2mat(find_between(chunk, '[', ']'), line_splitter=';') elif key == "areas": data['areas'] = txt2mat(find_between(chunk, '[', ']'), line_splitter=';') elif key == "gencost": data['gencost'] = txt2mat(find_between(chunk, '[', ']'), line_splitter=';') elif key == "gen": data['gen'] = txt2mat(find_between(chunk, '[', ']'), line_splitter=';') elif key == "branch": data['branch'] = txt2mat(find_between(chunk, '[', ']'), line_splitter=';') if "Ohms to p.u." in text: # convert branch impedance to p.u. like matpower does... bus_data = data['bus'] branch_data = data['branch'] Vbase = bus_data[0, matpower_buses.BASE_KV] * 1e3 Sbase = matpower_Sbase * 1e6 branch_data[:, matpower_branches.BR_R] /= (Vbase * Vbase / Sbase) branch_data[:, matpower_branches.BR_X] /= (Vbase * Vbase / Sbase) logger.add_warning("Converted Ohms to p.u.") if "kW to MW" in text: bus_data = data['bus'] bus_data[:, matpower_buses.PD] /= 1e3 bus_data[:, matpower_buses.QD] /= 1e3 logger.add_warning("Converted kW to MW") if matpower_Sbase != 100.0: logger.add_warning("Sbase was not 100, in VeraGrid it always should be 100MVA", value=circuit.Sbase, expected_value=100.0) circuit.Sbase = 100.0 return data
[docs] def parse_matpower_file(filename, export=False) -> [MultiCircuit, Logger]: """ Args: filename: export: Returns: """ # declare circuit circuit = MultiCircuit() logger = Logger() data = read_matpower_file(filename, logger) if 'bus' in data.keys(): circuit = interpret_data_v1(circuit, data, logger) else: logger.add_error('No bus data') return circuit, logger
[docs] def arr_to_dict(hdr, arr): """ Match header-data pair into a dictionary :param hdr: array of header data :param arr: array of values :return: """ assert len(hdr) == len(arr) return {h: a for h, a in zip(hdr, arr)}
[docs] def get_matpower_case_data(filename, force_linear_cost=False) -> Dict: """ Parse matpower .m file and get the case data structure :param filename: Name of the file :param force_linear_cost: Force linear cost when costs are found? :return: Matpower case data dictionary """ logger = Logger() data = read_matpower_file(filename, logger) bus_data = list() bus_arr = data['bus'] headers = matpower_buses.bus_headers[:bus_arr.shape[1]] for i in range(bus_arr.shape[0]): bus_data.append(arr_to_dict(hdr=headers, arr=bus_arr[i, :])) gen_data = list() gen_arr = data['gen'] headers = matpower_gen.gen_headers[: gen_arr.shape[1]] for i in range(gen_arr.shape[0]): gen_data.append(arr_to_dict(hdr=headers, arr=gen_arr[i, :])) gen_cost_data = list() gen_cost_arr = data['gencost'] for i in range(gen_cost_arr.shape[0]): cost_type = gen_cost_arr[i, 0] startup = gen_cost_arr[i, 1] shutdown = gen_cost_arr[i, 2] n = int(gen_cost_arr[i, 3]) cost_vector = gen_cost_arr[i, 3:3 + n] if force_linear_cost: if len(cost_vector) == 3: cost_vector[2] = 0.0 gen_cost_data.append({'costtype': cost_type, 'startup': startup, 'shutdown': shutdown, 'n': n, 'costvector': cost_vector}) branch_data = list() bus_arr = data['branch'] headers = matpower_branches.branch_headers[: bus_arr.shape[1]] for i in range(bus_arr.shape[0]): branch_data.append(arr_to_dict(hdr=headers, arr=bus_arr[i, :])) return {'baseMVA': 100.0, 'bus': bus_data, 'branch': branch_data, 'gen': gen_data, 'gencost': gen_cost_data}
[docs] def get_buses(circuit: MultiCircuit) -> Tuple[List[Dict[str, float]], Dict[dev.Bus, int]]: """ Get matpower buses structure :param circuit: MultiCircuit :return: list of buses structure, buses dictionary {Bus: bus int} """ data = list() bus_dict = dict() inj_per_bus = circuit.get_injection_devices_grouped_by_bus() for i, elm in enumerate(circuit.buses): Pd = 0.0 Qd = 0.0 Gs = 0.0 Bs = 0.0 injection_devices = inj_per_bus.get(elm, None) for child in injection_devices: if isinstance(child, dev.Shunt): Gs += child.G Bs += child.B elif isinstance(child, (dev.Load, dev.StaticGenerator)): Pd += child.P Qd += child.Q data.append({ 'bus_i': i + 1, # matlab starts at 1 'type': elm.determine_bus_type().value, 'Pd': Pd, 'Qd': Qd, 'Gs': Gs, 'Bs': Bs, 'area': 0, 'Vm': elm.Vm0, 'Va': elm.Va0, 'baseKV': elm.Vnom, 'zone': 0, 'Vmax': elm.Vmax, 'Vmin': elm.Vmin }) bus_dict[circuit.buses[i]] = i + 1 return data, bus_dict
[docs] def get_generation( circuit: MultiCircuit, bus_dict: Dict[dev.Bus, int]) -> Tuple[List[Dict[str, float]], List[Dict[str, float]]]: """ Get generation and generation cost data :param circuit: :param bus_dict: :return: """ data = list() cost_data = list() elm_list = circuit.get_generators() + circuit.get_batteries() for k, elm in enumerate(elm_list): if elm.bus is not None: i = bus_dict[elm.bus] # already accounts for the +1 of Matlab data.append({'bus': i, 'Pg': elm.P, 'Qg': 0, 'Qmax': elm.Qmax, 'Qmin': elm.Qmin, 'Vg': elm.Vset, 'mBase': elm.Snom, 'status': int(elm.active), 'Pmax': elm.Pmax, 'Pmin': elm.Pmin, 'Pc1': 0, 'Pc2': 0, 'Qc1min': 0, 'Qc1max': 0, 'Qc2min': 0, 'Qc2max': 0, 'ramp_agc': 0, 'ramp_10': 0, 'ramp_30': 0, 'ramp_q': 0, 'apf': 0, }) cost_data.append({ 'costtype': 2, 'startup': elm.startup_cost, 'shutdown': elm.shutdown_cost, 'n': 3, 'costvector': [elm.Cost2, elm.Cost, elm.Cost0] }) return data, cost_data
[docs] def get_branches(circuit: MultiCircuit, bus_dict: Dict[dev.Bus, int]) -> List[Dict[str, float]]: """ :param circuit: :param bus_dict: :return: """ data = list() elm_list = circuit.get_branches(add_vsc=False, add_hvdc=False, add_switch=True) for k, elm in enumerate(elm_list): f = bus_dict[elm.bus_from] # already accounts for the +1 of Matlab t = bus_dict[elm.bus_to] # already accounts for the +1 of Matlab angle = 0.0 ratio = 1.0 angmin = -360.0 # deg angmax = 360.0 # deg if isinstance(elm, dev.Transformer2W): angle = elm.tap_phase * 57.2958 # deg ratio = elm.tap_module angmin = elm.tap_phase_min * 57.2958 # deg angmax = elm.tap_phase_max * 57.2958 # deg data.append({'fbus': f, 'tbus': t, 'r': elm.R, 'x': elm.X, 'b': elm.B, 'rateA': elm.rate, 'rateB': elm.rate * elm.contingency_factor, 'rateC': 0, 'ratio': ratio, 'angle': angle, 'status': int(elm.active), 'angmin': angmin, 'angmax': angmax, }) return data
[docs] def to_matpower(circuit: MultiCircuit, logger: Logger = Logger(), t_idx: int | None = None) -> Dict[str, Union[float, List]]: """ :param circuit: :param logger: :param t_idx: :return: """ case = build_matpower_case_dict(circuit=circuit, t_idx=t_idx, logger=logger) return case