def parse_power_transformer(self, cim: CIMCircuit, circuit: MultiCircuit, busbar_dict): """ :param cim: :param circuit: :param busbar_dict: :return: """ if 'PowerTransformer' in cim.elements_by_type.keys(): for elm in cim.elements_by_type['PowerTransformer']: b1, b2 = elm.get_buses() B1, B2 = try_buses(b1, b2, busbar_dict) if B1 is not None and B2 is not None: R, X, G, B = elm.get_pu_values() rate = elm.get_rate() voltages = elm.get_voltages() voltages.sort() if len(voltages) == 2: lv, hv = voltages else: lv = 1 hv = 1 self.logger.add_error( 'Could not parse transformer nominal voltages', self.name) line = gcdev.Transformer2W(idtag=cimdev.rfid2uuid( elm.rfid), bus_from=B1, bus_to=B2, name=str(elm.name), r=R, x=X, g=G, b=B, rate=rate, tap=1.0, shift_angle=0, active=True, HV=hv, LV=lv) circuit.add_branch(line) else: self.logger.add_error('Bus not found', elm.rfid)
def test_demo_5_node(root_path=ROOT_PATH): np.core.arrayprint.set_printoptions(precision=4) grid = MultiCircuit() # Add buses bus1 = Bus('Bus 1', vnom=20) grid.add_bus(bus1) gen1 = Generator('Slack Generator', voltage_module=1.0) grid.add_generator(bus1, gen1) bus2 = Bus('Bus 2', vnom=20) grid.add_bus(bus2) grid.add_load(bus2, Load('load 2', P=40, Q=20)) bus3 = Bus('Bus 3', vnom=20) grid.add_bus(bus3) grid.add_load(bus3, Load('load 3', P=25, Q=15)) bus4 = Bus('Bus 4', vnom=20) grid.add_bus(bus4) grid.add_load(bus4, Load('load 4', P=40, Q=20)) bus5 = Bus('Bus 5', vnom=20) grid.add_bus(bus5) grid.add_load(bus5, Load('load 5', P=50, Q=20)) # add branches (Lines in this case) grid.add_branch(Branch(bus1, bus2, 'line 1-2', r=0.05, x=0.11, b=0.02)) grid.add_branch(Branch(bus1, bus3, 'line 1-3', r=0.05, x=0.11, b=0.02)) grid.add_branch(Branch(bus1, bus5, 'line 1-5', r=0.03, x=0.08, b=0.02)) grid.add_branch(Branch(bus2, bus3, 'line 2-3', r=0.04, x=0.09, b=0.02)) grid.add_branch(Branch(bus2, bus5, 'line 2-5', r=0.04, x=0.09, b=0.02)) grid.add_branch(Branch(bus3, bus4, 'line 3-4', r=0.06, x=0.13, b=0.03)) grid.add_branch(Branch(bus4, bus5, 'line 4-5', r=0.04, x=0.09, b=0.02)) # grid.plot_graph() print('\n\n', grid.name) options = PowerFlowOptions(SolverType.NR, verbose=False) power_flow = PowerFlowDriver(grid, options) power_flow.run() print_power_flow_results(power_flow=power_flow) v = np.array([1., 0.9553, 0.9548, 0.9334, 0.9534]) all_ok = np.isclose(np.abs(power_flow.results.voltage), v, atol=1e-3) return all_ok
def test_demo_5_node(root_path): np.core.arrayprint.set_printoptions(precision=4) grid = MultiCircuit() # Add buses bus_1 = Bus('Bus 1', vnom=20) # bus_1.is_slack = True grid.add_bus(bus_1) gen1 = Generator('Slack Generator', voltage_module=1.0) grid.add_generator(bus_1, gen1) bus_2 = Bus('Bus 2', vnom=20) grid.add_bus(bus_2) grid.add_load(bus_2, Load('load 2', P=40, Q=20)) bus_3 = Bus('Bus 3', vnom=20) grid.add_bus(bus_3) grid.add_load(bus_3, Load('load 3', P=25, Q=15)) bus_4 = Bus('Bus 4', vnom=20) grid.add_bus(bus_4) grid.add_load(bus_4, Load('load 4', P=40, Q=20)) bus_5 = Bus('Bus 5', vnom=20) grid.add_bus(bus_5) grid.add_load(bus_5, Load('load 5', P=50, Q=20)) # add branches (Lines in this case) grid.add_branch(Branch(bus_1, bus_2, 'line 1-2', r=0.05, x=0.11, b=0.02)) grid.add_branch(Branch(bus_1, bus_3, 'line 1-3', r=0.05, x=0.11, b=0.02)) grid.add_branch(Branch(bus_1, bus_5, 'line 1-5', r=0.03, x=0.08, b=0.02)) grid.add_branch(Branch(bus_2, bus_3, 'line 2-3', r=0.04, x=0.09, b=0.02)) grid.add_branch(Branch(bus_2, bus_5, 'line 2-5', r=0.04, x=0.09, b=0.02)) grid.add_branch(Branch(bus_3, bus_4, 'line 3-4', r=0.06, x=0.13, b=0.03)) grid.add_branch(Branch(bus_4, bus_5, 'line 4-5', r=0.04, x=0.09, b=0.02)) # grid.plot_graph() print('\n\n', grid.name) options = PowerFlowOptions(SolverType.NR, verbose=False) power_flow = PowerFlow(grid, options) power_flow.run() print_power_flow_results(power_flow=power_flow)
def main(): #################################################################################################################### # Define the circuit # # A circuit contains all the grid information regardless of the islands formed or the amount of devices #################################################################################################################### # create a circuit grid = MultiCircuit(name='lynn 5 bus') # let's create a master profile st = datetime.datetime(2020, 1, 1) dates = [st + datetime.timedelta(hours=i) for i in range(24)] time_array = pd.to_datetime(dates) x = np.linspace(-np.pi, np.pi, len(time_array)) y = np.abs(np.sin(x)) df_0 = pd.DataFrame(data=y, index=time_array) # complex values # set the grid master time profile grid.time_profile = df_0.index #################################################################################################################### # Define the buses #################################################################################################################### # I will define this bus with all the properties so you see bus1 = Bus(name='Bus1', vnom=10, # Nominal voltage in kV vmin=0.9, # Bus minimum voltage in per unit vmax=1.1, # Bus maximum voltage in per unit xpos=0, # Bus x position in pixels ypos=0, # Bus y position in pixels height=0, # Bus height in pixels width=0, # Bus width in pixels active=True, # Is the bus active? is_slack=False, # Is this bus a slack bus? area='Defualt', # Area (for grouping purposes only) zone='Default', # Zone (for grouping purposes only) substation='Default' # Substation (for grouping purposes only) ) # the rest of the buses are defined with the default parameters bus2 = Bus(name='Bus2') bus3 = Bus(name='Bus3') bus4 = Bus(name='Bus4') bus5 = Bus(name='Bus5') # add the bus objects to the circuit grid.add_bus(bus1) grid.add_bus(bus2) grid.add_bus(bus3) grid.add_bus(bus4) grid.add_bus(bus5) #################################################################################################################### # Add the loads #################################################################################################################### # In GridCal, the loads, generators ect are stored within each bus object: # we'll define the first load completely l2 = Load(name='Load', G=0, B=0, # admittance of the ZIP model in MVA at the nominal voltage Ir=0, Ii=0, # Current of the ZIP model in MVA at the nominal voltage P=40, Q=20, # Power of the ZIP model in MVA active=True, # Is active? mttf=0.0, # Mean time to failure mttr=0.0 # Mean time to recovery ) grid.add_load(bus2, l2) # Define the others with the default parameters grid.add_load(bus3, Load(P=25, Q=15)) grid.add_load(bus4, Load(P=40, Q=20)) grid.add_load(bus5, Load(P=50, Q=20)) #################################################################################################################### # Add the generators #################################################################################################################### g1 = Generator(name='gen', active_power=0.0, # Active power in MW, since this generator is used to set the slack , is 0 voltage_module=1.0, # Voltage set point to control Qmin=-9999, # minimum reactive power in MVAr Qmax=9999, # Maximum reactive power in MVAr Snom=9999, # Nominal power in MVA power_prof=None, # power profile vset_prof=None, # voltage set point profile active=True # Is active? ) grid.add_generator(bus1, g1) #################################################################################################################### # Add the lines #################################################################################################################### br1 = Branch(bus_from=bus1, bus_to=bus2, name='Line 1-2', r=0.05, # resistance of the pi model in per unit x=0.11, # reactance of the pi model in per unit g=1e-20, # conductance of the pi model in per unit b=0.02, # susceptance of the pi model in per unit rate=50, # Rate in MVA tap=1.0, # Tap value (value close to 1) shift_angle=0, # Tap angle in radians active=True, # is the branch active? mttf=0, # Mean time to failure mttr=0, # Mean time to recovery branch_type=BranchType.Line, # Branch type tag length=1, # Length in km (to be used with templates) template=BranchTemplate() # Branch template (The default one is void) ) grid.add_branch(br1) grid.add_branch(Branch(bus1, bus3, name='Line 1-3', r=0.05, x=0.11, b=0.02, rate=50)) grid.add_branch(Branch(bus1, bus5, name='Line 1-5', r=0.03, x=0.08, b=0.02, rate=80)) grid.add_branch(Branch(bus2, bus3, name='Line 2-3', r=0.04, x=0.09, b=0.02, rate=3)) grid.add_branch(Branch(bus2, bus5, name='Line 2-5', r=0.04, x=0.09, b=0.02, rate=10)) grid.add_branch(Branch(bus3, bus4, name='Line 3-4', r=0.06, x=0.13, b=0.03, rate=30)) grid.add_branch(Branch(bus4, bus5, name='Line 4-5', r=0.04, x=0.09, b=0.02, rate=30)) FileSave(grid, 'lynn5node.gridcal').save() #################################################################################################################### # Overwrite the default profiles with the custom ones #################################################################################################################### for load in grid.get_loads(): load.P_prof = load.P * df_0.values[:, 0] load.Q_prof = load.Q * df_0.values[:, 0] for gen in grid.get_static_generators(): gen.P_prof = gen.Q * df_0.values[:, 0] gen.Q_prof = gen.Q * df_0.values[:, 0] for gen in grid.get_generators(): gen.P_prof = gen.P * df_0.values[:, 0] #################################################################################################################### # Run a power flow simulation #################################################################################################################### # We need to specify power flow options pf_options = PowerFlowOptions(solver_type=SolverType.NR, # Base method to use verbose=False, # Verbose option where available tolerance=1e-6, # power error in p.u. max_iter=25, # maximum iteration number control_q=True # if to control the reactive power ) # Declare and execute the power flow simulation pf = PowerFlowDriver(grid, pf_options) pf.run() writer = pd.ExcelWriter('Results.xlsx') # now, let's compose a nice DataFrame with the voltage results headers = ['Vm (p.u.)', 'Va (Deg)', 'Vre', 'Vim'] Vm = np.abs(pf.results.voltage) Va = np.angle(pf.results.voltage, deg=True) Vre = pf.results.voltage.real Vim = pf.results.voltage.imag data = np.c_[Vm, Va, Vre, Vim] v_df = pd.DataFrame(data=data, columns=headers, index=grid.bus_names) # print('\n', v_df) v_df.to_excel(writer, sheet_name='V') # Let's do the same for the branch results headers = ['Loading (%)', 'Current(p.u.)', 'Power (MVA)'] loading = np.abs(pf.results.loading) * 100 current = np.abs(pf.results.If) power = np.abs(pf.results.Sf) data = np.c_[loading, current, power] br_df = pd.DataFrame(data=data, columns=headers, index=grid.branch_names) br_df.to_excel(writer, sheet_name='Br') # Finally the execution metrics print('\nError:', pf.results.error) print('Elapsed time (s):', pf.results.elapsed, '\n') # print(tabulate(v_df, tablefmt="pipe", headers=v_df.columns.values)) # print() # print(tabulate(br_df, tablefmt="pipe", headers=br_df.columns.values)) #################################################################################################################### # Run a time series power flow simulation #################################################################################################################### ts = TimeSeries(grid=grid, options=pf_options, opf_time_series_results=None, start_=0, end_=None) ts.run() print() print('-' * 200) print('Time series') print('-' * 200) print('Voltage time series') df_voltage = pd.DataFrame(data=np.abs(ts.results.voltage), columns=grid.bus_names, index=grid.time_profile) df_voltage.to_excel(writer, sheet_name='Vts') writer.close()
def data_to_grid_object(data, pos_dict, codification="utf-8") -> MultiCircuit: """ Turns the read data dictionary into a GridCal MultiCircuit object Args: data: Dictionary of data read from a DGS file pos_dict: Dictionary of objects and their positions read from a DGS file Returns: GridCal MultiCircuit object """ ############################################################################### # Refactor data into classes ############################################################################### # store tables for easy reference ''' ############################################################################### * Line * * ID: Unique identifier for DGS file * loc_name: Name * fold_id: In Folder * typ_id: Type in TypLne,TypTow,TypGeo,TypCabsys * chr_name: Characteristic Name * dline: Parameters: Length of Line in km * fline: Parameters: Derating Factor * outserv: Out of Service * pStoch: Failures: Element model in StoTyplne ''' if "ElmLne" in data.keys(): lines = data["ElmLne"] else: lines = np.zeros((0, 20)) ''' ############################################################################### * Line Type * * ID: Unique identifier for DGS file * loc_name: Name * fold_id: In Folder * chr_name: Characteristic Name * Ithr: Rated Short-Time (1s) Current (Conductor) in kA * aohl_: Cable / OHL * cline: Parameters per Length 1,2-Sequence: Capacitance C' in uF/km * cline0: Parameters per Length Zero Sequence: Capacitance C0' in uF/km * nlnph: Phases:1:2:3 * nneutral: Number of Neutrals:0:1 * rline: Parameters per Length 1,2-Sequence: AC-Resistance R'(20°C) in Ohm/km * rline0: Parameters per Length Zero Sequence: AC-Resistance R0' in Ohm/km * rtemp: Max. End Temperature in degC * sline: Rated Current in kA * uline: Rated Voltage in kV * xline: Parameters per Length 1,2-Sequence: Reactance X' in Ohm/km * xline0: Parameters per Length Zero Sequence: Reactance X0' in Ohm/km ''' if "TypLne" in data.keys(): lines_types = data["TypLne"] else: lines_types = np.zeros((0, 20)) ''' ############################################################################### * 2-Winding Transformer * * ID: Unique identifier for DGS file * loc_name: Name * fold_id: In Folder * typ_id: Type in TypTr2 * chr_name: Characteristic Name * sernum: Serial Number * constr: Year of Construction * cgnd_h: Internal Grounding Impedance, HV Side: Star Point:Connected:Not connected * cgnd_l: Internal Grounding Impedance, LV Side: Star Point:Connected:Not connected * i_auto: Auto Transformer * nntap: Tap Changer 1: Tap Position * ntrcn: Controller, Tap Changer 1: Automatic Tap Changing * outserv: Out of Service * ratfac: Rating Factor ''' if "ElmTr2" in data.keys(): transformers = data["ElmTr2"] else: transformers = np.zeros((0, 20)) ''' ############################################################################### * 2-Winding Transformer Type * * ID: Unique identifier for DGS file * loc_name: Name * fold_id: In Folder * chr_name: Characteristic Name * curmg: Magnetising Impedance: No Load Current in % * dutap: Tap Changer 1: Additional Voltage per Tap in % * frnom: Nominal Frequency in Hz * manuf: Manufacturer * nntap0: Tap Changer 1: Neutral Position * nt2ag: Vector Group: Phase Shift in *30deg * ntpmn: Tap Changer 1: Minimum Position * ntpmx: Tap Changer 1: Maximum Position * pcutr: Positive Sequence Impedance: Copper Losses in kW * pfe: Magnetising Impedance: No Load Losses in kW * phitr: Tap Changer 1: Phase of du in deg * strn: Rated Power in MVA * tap_side: Tap Changer 1: at Side:HV:LV * tr2cn_h: Vector Group: HV-Side:Y :YN:Z :ZN:D * tr2cn_l: Vector Group: LV-Side:Y :YN:Z :ZN:D * uk0tr: Zero Sequence Impedance: Short-Circuit Voltage uk0 in % * uktr: Positive Sequence Impedance: Short-Circuit Voltage uk in % * ur0tr: Zero Sequence Impedance: SHC-Voltage (Re(uk0)) uk0r in % * utrn_h: Rated Voltage: HV-Side in kV * utrn_l: Rated Voltage: LV-Side in kV * zx0hl_n: Zero Sequence Magnetising Impedance: Mag. Impedance/uk0 ''' if "TypTr2" in data.keys(): transformers_types = data["TypTr2"] else: transformers_types = np.zeros((0, 20)) ''' ############################################################################### * Terminal * * ID: Unique identifier for DGS file * loc_name: Name * fold_id: In Folder * typ_id: Type in TypBar * chr_name: Characteristic Name * iUsage: Usage:Busbar:Junction Node:Internal Node * outserv: Out of Service * phtech: Phase Technology:ABC:ABC-N:BI:BI-N:2PH:2PH-N:1PH:1PH-N:N * uknom: Nominal Voltage: Line-Line in kV ''' if "ElmTerm" in data.keys(): buses = data["ElmTerm"] else: buses = np.zeros((0, 20)) ''' ############################################################################### * Cubicle * * ID: Unique identifier for DGS file * loc_name: Name * fold_id: In Folder * chr_name: Characteristic Name * obj_bus: Bus Index * obj_id: Connected with in Elm* ''' if "StaCubic" in data.keys(): cubicles = data["StaCubic"] else: cubicles = np.zeros((0, 20)) ''' ############################################################################### * General Load * * ID: Unique identifier for DGS file * loc_name: Name * fold_id: In Folder * typ_id: Type in TypLod,TypLodind * chr_name: Characteristic Name * outserv: Out of Service * plini: Operating Point: Active Power in MW * qlini: Operating Point: Reactive Power in Mvar * scale0: Operating Point: Scaling Factor ''' if "ElmLod" in data.keys(): loads = data["ElmLod"] else: loads = np.zeros((0, 20)) ''' ############################################################################### * External Grid * * ID: Unique identifier for DGS file * loc_name: Name * fold_id: In Folder * chr_name: Characteristic Name * bustp: Bus Type:PQ:PV:SL * cgnd: Internal Grounding Impedance: Star Point:Connected:Not connected * iintgnd: Neutral Conductor: N-Connection:None:At terminal (ABC-N):Separate terminal * ikssmin: Min. Values: Short-Circuit Current Ik''min in kA * r0tx0: Max. Values Impedance Ratio: R0/X0 max. * r0tx0min: Min. Values Impedance Ratio: R0/X0 min. * rntxn: Max. Values: R/X Ratio (max.) * rntxnmin: Min. Values: R/X Ratio (min.) * snss: Max. Values: Short-Circuit Power Sk''max in MVA * snssmin: Min. Values: Short-Circuit Power Sk''min in MVA ''' if "ElmXnet" in data.keys(): external = data["ElmXnet"] else: external = np.zeros((0, 20)) ''' ############################################################################### * Grid * * ID: Unique identifier for DGS file * loc_name: Name * fold_id: In Folder * frnom: Nominal Frequency in Hz ''' if "ElmNet" in data.keys(): grid = data["ElmNet"] else: grid = np.zeros((0, 20)) ''' ############################################################################### ''' if "ElmGenstat" in data.keys(): static_generators = data["ElmGenstat"] else: static_generators = np.zeros((0, 20)) ''' ############################################################################### * Synchronous Machine * * ID: Unique identifier for DGS file * loc_name: Name * fold_id: In Folder * typ_id: Type in TypSym * chr_name: Characteristic Name * i_mot: Generator/Motor * iv_mode: Local Controller * ngnum: Number of: parallel Machines * outserv: Out of Service * pgini: Dispatch: Active Power in MW * q_max: Reactive Power Operational Limits: Max. in p.u. * q_min: Reactive Power Operational Limits: Min. in p.u. * qgini: Dispatch: Reactive Power in Mvar * usetp: Dispatch: Voltage in p.u. ''' if "ElmSym" in data.keys(): synchronous_machine = data["ElmSym"] else: synchronous_machine = np.zeros((0, 20)) ''' ############################################################################### * Synchronous Machine Type * * ID: Unique identifier for DGS file * loc_name: Name * fold_id: In Folder * cosn: Power Factor * rstr: Stator Resistance: rstr in p.u. * satur: For single fed short-circuit: Machine Type IEC909/IEC60909 * sgn: Nominal Apparent Power in MVA * ugn: Nominal Voltage in kV * xd: Synchronous Reactances: xd in p.u. * xdsat: For single fed short-circuit: Reciprocal of short-circuit ratio (xdsat) in p.u. * xdsss: Subtransient Reactance: saturated value xd''sat in p.u. * xq: Synchronous Reactances: xq in p.u. ''' if "TypSym" in data.keys(): synchronous_machine_type = data["TypSym"] else: synchronous_machine_type = np.zeros((0, 20)) ''' ############################################################################### * Asynchronous Machine * * ID: Unique identifier for DGS file * loc_name: Name * fold_id: In Folder * typ_id: Type in TypAsm*,TypAsmo*,TypAsm1* * chr_name: Characteristic Name * i_mot: Generator/Motor * ngnum: Number of: parallel Machines * outserv: Out of Service * pgini: Dispatch: Active Power in MW ''' if "ElmAsm" in data.keys(): asynchronous_machine = data["ElmAsm"] else: asynchronous_machine = np.zeros((0, 20)) ''' ############################################################################### * Synchronous Machine Type * * ID: Unique identifier for DGS file * loc_name: Name * fold_id: In Folder * i_mode: Input Mode * aiazn: Consider Transient Parameter: Locked Rotor Current (Ilr/In) in p.u. * amazn: Locked Rotor Torque in p.u. * amkzn: Torque at Stalling Point in p.u. * anend: Nominal Speed in rpm * cosn: Rated Power Factor * effic: Efficiency at nominal Operation in % * frequ: Nominal Frequency in Hz * i_cage: Rotor * nppol: No of Pole Pairs * pgn: Power Rating: Rated Mechanical Power in kW * ugn: Rated Voltage in kV * xmrtr: Rotor Leakage Reac. Xrm in p.u. * xstr: Stator Reactance Xs in p.u. ''' if "TypAsmo" in data.keys(): asynchronous_machine_type = data["TypAsmo"] else: asynchronous_machine_type = np.zeros((0, 20)) ''' ############################################################################### * Shunt/Filter * * ID: Unique identifier for DGS file * loc_name: Name * fold_id: In Folder * chr_name: Characteristic Name * ctech: Technology * fres: Design Parameter (per Step): Resonance Frequency in Hz * greaf0: Design Parameter (per Step): Quality Factor (at fr) * iswitch: Controller: Switchable * ncapa: Controller: Act.No. of Step * ncapx: Controller: Max. No. of Steps * outserv: Out of Service * qtotn: Design Parameter (per Step): Rated Reactive Power, L-C in Mvar * shtype: Shunt Type * ushnm: Nominal Voltage in kV ''' if "ElmShnt" in data.keys(): shunts = data["ElmShnt"] else: shunts = np.zeros((0, 20)) ''' ############################################################################### * Breaker/Switch * * ID: Unique identifier for DGS file * loc_name: Name * fold_id: In Folder * typ_id: Type in TypSwitch * chr_name: Characteristic Name * aUsage: Switch Type * nneutral: No. of Neutrals:0:1 * nphase: No. of Phases:1:2:3 * on_off: Closed ''' if "ElmCoup" in data.keys(): switches = data["ElmCoup"] else: switches = np.zeros((0, 20)) ############################################################################### # Post process the data ############################################################################### # put the tables that connect to a terminal in a list classes = [lines, transformers, loads, external, static_generators, shunts, synchronous_machine, asynchronous_machine] # construct the terminals dictionary ''' $$StaCubic;ID(a:40);loc_name(a:40);fold_id(p);chr_name(a:20);obj_bus(i);obj_id(p) ******************************************************************************** * Cubicle * * ID: Unique identifier for DGS file * loc_name: Name * fold_id: In Folder * chr_name: Characteristic Name * obj_bus: Bus Index * obj_id: Connected with in Elm* ******************************************************************************** ''' terminals_dict = dict() # dictionary to store the terminals ID associated with an object ID cub_obj_idx = cubicles['obj_id'].values cub_term_idx = cubicles['fold_id'].values # for i, elm_id in enumerate(cub_obj_idx): # bus_idx = cub_term_idx[i] # terminals_dict[elm_id] = bus_idx ID_idx = 0 for cla in classes: if cla.__len__() > 0: for ID in cla['ID'].values: idx = np.where(cubicles == ID)[0] terminals_dict[ID] = cub_term_idx[idx] ############################################################################### # Generate GridCal data ############################################################################### # general values baseMVA = 100 frequency = grid['frnom'][0] w = 2.0 * math.pi * frequency circuit = MultiCircuit() #################################################################################################################### # Terminals (nodes) #################################################################################################################### ''' ******************************************************************************** * Terminal * * ID: Unique identifier for DGS file * loc_name: Name * fold_id: In Folder * typ_id: Type in TypBar * iUsage: Usage:Busbar:Junction Node:Internal Node * uknom: Nominal Voltage: Line-Line in kV * chr_name: Characteristic Name * outserv: Out of Service ******************************************************************************** ''' # print('Parsing terminals') buses_dict = dict() for i in range(len(buses)): ID = buses['ID'][i] x, y = pos_dict[ID] buses_dict[ID] = i bus_name = buses['loc_name'][i].decode(codification) # BUS_Name vnom = buses['uknom'][i] bus = Bus(name=bus_name, vnom=vnom, vmin=0.9, vmax=1.1, xpos=x, ypos=-y, active=True) circuit.add_bus(bus) #################################################################################################################### # External grids (slacks) #################################################################################################################### ''' ############################################################################### ******************************************************************************** * External Grid * * ID: Unique identifier for DGS file * loc_name: Name * fold_id: In Folder * outserv: Out of Service * snss: Max. Values: Short-Circuit Power Sk''max in MVA * rntxn: Max. Values: R/X Ratio (max.) * z2tz1: Max. Values Impedance Ratio: Z2/Z1 max. * snssmin: Min. Values: Short-Circuit Power Sk''min in MVA * rntxnmin: Min. Values: R/X Ratio (min.) * z2tz1min: Min. Values Impedance Ratio: Z2/Z1 min. * chr_name: Characteristic Name * bustp: Bus Type:PQ:PV:SL * pgini: Operation Point: Active Power in MW * qgini: Operation Point: Reactive Power in Mvar * phiini: Operation Point: Angle in deg * usetp: Operation Point: Voltage Setpoint in p.u. ******************************************************************************** ''' for i in range(len(external)): ID = external['ID'][i] if 'phiini' in external.columns.values: va = external['phiini'][i] vm = external['usetp'][i] else: va = 0 vm = 1 buses = terminals_dict[ID] # array with the ID of the connection Buses bus1 = buses_dict[buses[0]] # index of the bus bus_obj = circuit.buses[bus1] # apply the slack values to the buses structure if the element is marked as slack if external['bustp'].values[i] == b'SL': # create the slack entry on buses bus_obj.is_slack = True # BUSES[bus1, bd.BUS_TYPE] = 3 # BUSES[bus1, bd.VA] = va # BUSES[bus1, bd.VM] = vm # # # create the slack entry on generators (add the slack generator) # gen_ = gen_line.copy() # gen_[gd.GEN_BUS] = bus1 # gen_[gd.MBASE] = baseMVA # gen_[gd.VG] = vm # gen_[gd.GEN_STATUS] = 1 # gen_[gd.PG] += external['pgini'].values[i] # # GEN.append(gen_) # GEN_NAMES.append(external['loc_name'][i]) elif external['bustp'].values[i] == b'PV': if 'pgini' in external.columns.values: p = external['pgini'].values[i] else: p = 0 # add a generator to the bus gen = Generator(name=external['loc_name'][i].decode(codification), active_power=p, voltage_module=vm, Qmin=-9999, Qmax=9999, Snom=9999, power_prof=None, vset_prof=None) circuit.add_generator(bus_obj, gen) # # mark the bus as pv # BUSES[bus1, bd.BUS_TYPE] = 2 # BUSES[bus1, bd.VA] = 0.0 # BUSES[bus1, bd.VM] = vm # # add the PV entry on generators # gen_ = gen_line.copy() # gen_[gd.GEN_BUS] = bus1 # gen_[gd.MBASE] = baseMVA # gen_[gd.VG] = vm # gen_[gd.GEN_STATUS] = 1 # gen_[gd.PG] += external['pgini'].values[i] # # GEN.append(gen_) # GEN_NAMES.append(external['loc_name'][i]) elif external['bustp'].values[i] == b'PQ': # Add a load to the bus load = Load(name=external['loc_name'][i].decode(codification), P=external['pgini'].values[i], Q=external['qgini'].values[i]) circuit.add_load(bus_obj, load) # BUSES[bus1, bd.BUS_TYPE] = 1 # BUSES[bus1, bd.VA] = va # BUSES[bus1, bd.VM] = vm # BUSES[bus1, bd.PD] += external['pgini'].values[i] # BUSES[bus1, bd.QD] += external['qgini'].values[i] #################################################################################################################### # Lines (branches) #################################################################################################################### # print('Parsing lines') if lines_types.__len__() > 0: lines_ID = lines['ID'].values lines_type_id = lines['typ_id'].values line_types_ID = lines_types['ID'].values lines_lenght = lines['dline'].values if 'outserv' in lines.keys(): lines_enables = lines['outserv'] else: lines_enables = np.ones(len(lines_ID)) lines_R = lines_types['rline'].values lines_L = lines_types['xline'].values lines_C = lines_types['cline'].values lines_rate = lines_types['sline'].values lines_voltage = lines_types['uline'].values for i in range(len(lines)): # line_ = branch_line.copy() ID = lines_ID[i] ID_Type = lines_type_id[i] type_idx = np.where(line_types_ID == ID_Type)[0][0] buses = terminals_dict[ID] # array with the ID of the connection Buses bus1 = buses_dict[buses[0]] bus2 = buses_dict[buses[1]] bus_from = circuit.buses[bus1] bus_to = circuit.buses[bus2] status = lines_enables[i] # impedances lenght = np.double(lines_lenght[i]) R = np.double(lines_R[type_idx]) * lenght # Ohm L = np.double(lines_L[type_idx]) * lenght # Ohm C = np.double(lines_C[type_idx]) * lenght * w * 1e-6 # S (siemens) # pass impedance to per unit vbase = np.double(lines_voltage[type_idx]) # kV zbase = vbase**2 / baseMVA # Ohm ybase = 1.0 / zbase # S r = R / zbase # pu l = L / zbase # pu b = C / ybase # pu # rated power Irated = np.double(lines_rate[type_idx]) # kA Smax = Irated * vbase # MVA line = Branch(bus_from=bus_from, bus_to=bus_to, name=lines['loc_name'][i].decode(codification), r=r, x=l, g=1e-20, b=b, rate=Smax, tap=1, shift_angle=0, active=status, mttf=0, mttr=0) circuit.add_branch(line) # # put all in the correct column # line_[brd.F_BUS] = bus1 # line_[brd.T_BUS] = bus2 # line_[brd.BR_R] = r # line_[brd.BR_X] = l # line_[brd.BR_B] = c # line_[brd.RATE_A] = Smax # line_[brd.BR_STATUS] = status # BRANCHES.append(line_) # # name_ = lines['loc_name'][i] # line_Name # BRANCH_NAMES.append(name_) # # # add edge to graph # g.add_edge(bus1, bus2) else: warn('Line types are empty') #################################################################################################################### # Transformers (Branches) #################################################################################################################### # print('Parsing transformers') ''' ******************************************************************************** * 2-Winding Transformer * * ID: Unique identifier for DGS file * loc_name: Name * fold_id: In Folder * typ_id: Type in TypTr2 * outserv: Out of Service * nntap: Tap Changer 1: Tap Position * sernum: Serial Number * constr: Year of Construction * chr_name: Characteristic Name ******************************************************************************** ''' if len(transformers_types) > 0: ''' ******************************************************************************** * 2-Winding Transformer Type * * ID: Unique identifier for DGS file * loc_name: Name * fold_id: In Folder * strn: Rated Power in MVA * frnom: Nominal Frequency in Hz * utrn_h: Rated Voltage: HV-Side in kV * utrn_l: Rated Voltage: LV-Side in kV * uktr: Positive Sequence Impedance: Short-Circuit Voltage uk in % * pcutr: Positive Sequence Impedance: Copper Losses in kW * uk0tr: Zero Sequence Impedance: Short-Circuit Voltage uk0 in % * ur0tr: Zero Sequence Impedance: SHC-Voltage (Re(uk0)) uk0r in % * tr2cn_h: Vector Group: HV-Side:Y :YN:Z :ZN:D * tr2cn_l: Vector Group: LV-Side:Y :YN:Z :ZN:D * nt2ag: Vector Group: Phase Shift in *30deg * curmg: Magnetizing Impedance: No Load Current in % * pfe: Magnetizing Impedance: No Load Losses in kW * zx0hl_n: Zero Sequence Magnetizing Impedance: Mag. Impedance/uk0 * tap_side: Tap Changer 1: at Side:HV:LV * dutap: Tap Changer 1: Additional Voltage per Tap in % * phitr: Tap Changer 1: Phase of du in deg * nntap0: Tap Changer 1: Neutral Position * ntpmn: Tap Changer 1: Minimum Position * ntpmx: Tap Changer 1: Maximum Position * manuf: Manufacturer * chr_name: Characteristic Name ******************************************************************************** ''' type_ID = transformers_types['ID'].values HV_nominal_voltage = transformers_types['utrn_h'].values LV_nominal_voltage = transformers_types['utrn_l'].values Nominal_power = transformers_types['strn'].values Copper_losses = transformers_types['pcutr'].values Iron_losses = transformers_types['pfe'].values No_load_current = transformers_types['curmg'].values Short_circuit_voltage = transformers_types['uktr'].values # GR_hv1 = transformers_types['ID'] # GX_hv1 = transformers_types['ID'] for i in range(len(transformers)): # line_ = branch_line.copy() ID = transformers['ID'][i] ID_Type = transformers['typ_id'][i] if ID_Type in type_ID: type_idx = np.where(type_ID == ID_Type)[0][0] buses = terminals_dict[ID] # array with the ID of the connection Buses bus1 = buses_dict[buses[0]] bus2 = buses_dict[buses[1]] bus_from = circuit.buses[bus1] bus_to = circuit.buses[bus2] Smax = Nominal_power[type_idx] # Uhv, Ulv, Sn, Pcu, Pfe, I0, Usc tpe = TransformerType(hv_nominal_voltage=HV_nominal_voltage[type_idx], lv_nominal_voltage=LV_nominal_voltage[type_idx], nominal_power=Smax, copper_losses=Copper_losses[type_idx], iron_losses=Iron_losses[type_idx], no_load_current=No_load_current[type_idx], short_circuit_voltage=Short_circuit_voltage[type_idx], gr_hv1=0.5, gx_hv1=0.5) Zs, Zsh = tpe.get_impedances() if Zsh != 0: Ysh = 1.0 / Zsh else: Ysh = 0j status = 1 - transformers['outserv'][i] trafo = Branch(bus_from=bus_from, bus_to=bus_to, name=transformers['loc_name'][i].decode(codification), r=Zs.real, x=Zs.imag, g=Ysh.real, b=Ysh.imag, rate=Smax, tap=1.0, shift_angle=0.0, active=status, mttf=0, mttr=0, branch_type=BranchType.Transformer) circuit.add_branch(trafo) else: warn('Transformer type not found!') else: warn('Transformer types are empty') #################################################################################################################### # Loads (nodes) #################################################################################################################### ''' ******************************************************************************** * General Load * * ID: Unique identifier for DGS file * loc_name: Name * fold_id: In Folder * typ_id: Type in TypLod,TypLodind * chr_name: Characteristic Name * plini: Operating Point: Active Power in MW * qlini: Operating Point: Reactive Power in Mvar * scale0: Operating Point: Scaling Factor ******************************************************************************** ''' # print('Parsing Loads') if len(loads) > 0: loads_ID = loads['ID'] loads_P = loads['plini'] loads_Q = loads['qlini'] scale = loads['scale0'] for i in range(len(loads)): ID = loads_ID[i] bus_idx = buses_dict[(terminals_dict[ID][0])] bus_obj = circuit.buses[bus_idx] p = loads_P[i] * scale[i] # in MW q = loads_Q[i] * scale[i] # in MVA load = Load(name=loads['loc_name'][i].decode(codification), P=p, Q=q) circuit.add_load(bus_obj, load) # BUSES[bus_idx, 2] += p # BUSES[bus_idx, 3] += q else: warn('There are no loads') #################################################################################################################### # Shunts #################################################################################################################### ''' ******************************************************************************** * Shunt/Filter * * ID: Unique identifier for DGS file * loc_name: Name * fold_id: In Folder * chr_name: Characteristic Name * shtype: Shunt Type * ushnm: Nominal Voltage in kV * qcapn: Design Parameter (per Step): Rated Reactive Power, C in Mvar * ncapx: Controller: Max. No. of Steps * ncapa: Controller: Act.No. of Step * outserv: Out of Service ******************************************************************************** ''' for i in range(len(shunts)): ID = shunts['ID'][i] buses = terminals_dict[ID] # array with the ID of the connection Buses bus1 = buses_dict[buses[0]] bus_obj = circuit.buses[bus1] name = shunts['loc_name'][i].decode(codification) if 'qcapn' in shunts.columns.values: b = shunts['ushnm'][i] / shunts['qcapn'][i] elif 'qtotn' in shunts.columns.values: b = shunts['ushnm'][i] / shunts['qtotn'][i] else: b = 1e-20 shunt = Shunt(name=name, B=b) circuit.add_shunt(bus_obj, shunt) #################################################################################################################### # Static generators (Gen) #################################################################################################################### ''' ******************************************************************************** * Static Generator * * ID: Unique identifier for DGS file * loc_name: Name * fold_id: In Folder * bus1: Terminal in StaCubic * outserv: Out of Service * sgn: Ratings: Nominal Apparent Power in MVA * cosn: Ratings: Power Factor * ngnum: Number of: parallel Machines * pgini: Dispatch: Active Power in MW * qgini: Dispatch: Reactive Power in Mvar * av_mode: Local Controller * ip_ctrl: Reference Machine ******************************************************************************** ''' for i in range(len(static_generators)): ID = static_generators['ID'][i] buses = terminals_dict[ID] # array with the ID of the connection Buses bus1 = buses_dict[buses[0]] bus_obj = circuit.buses[bus1] mode = static_generators['av_mode'][i] num_machines = static_generators['ngnum'][i] gen = StaticGenerator(name=static_generators['loc_name'][i].decode(codification), P=static_generators['pgini'][i] * num_machines, Q=static_generators['qgini'][i] * num_machines) circuit.add_static_generator(bus_obj, gen) #################################################################################################################### # Synchronous Machine (Gen) #################################################################################################################### ''' ******************************************************************************** * Synchronous Machine * * ID: Unique identifier for DGS file * loc_name: Name * fold_id: In Folder * typ_id: Type in TypSym * ngnum: Number of: parallel Machines * i_mot: Generator/Motor * chr_name: Characteristic Name * outserv: Out of Service * pgini: Dispatch: Active Power in MW * qgini: Dispatch: Reactive Power in Mvar * usetp: Dispatch: Voltage in p.u. * iv_mode: Mode of Local Voltage Controller * q_min: Reactive Power Operational Limits: Min. in p.u. * q_max: Reactive Power Operational Limits: Max. in p.u. ******************************************************************************** ''' for i in range(len(synchronous_machine)): ID = synchronous_machine['ID'][i] buses = terminals_dict[ID] # array with the ID of the connection Buses bus1 = buses_dict[buses[0]] bus_obj = circuit.buses[bus1] num_machines = synchronous_machine['ngnum'][i] # Get the type element ''' ******************************************************************************** * Synchronous Machine Type * * ID: Unique identifier for DGS file * loc_name: Name * fold_id: In Folder * sgn: Nominal Apparent Power in MVA * ugn: Nominal Voltage in kV * cosn: Power Factor * xd: Synchronous Reactances: xd in p.u. * xq: Synchronous Reactances: xq in p.u. * xdsss: Subtransient Reactance: saturated value xd''sat in p.u. * rstr: Stator Resistance: rstr in p.u. * xdsat: For single fed short-circuit: Reciprocal of short-circuit ratio (xdsat) in p.u. * satur: For single fed short-circuit: Machine Type IEC909/IEC60909 ******************************************************************************** ''' typ = synchronous_machine_type[synchronous_machine_type.ID == synchronous_machine['typ_id'][i]] snom = typ['sgn'].values[0] vnom = synchronous_machine['usetp'][i] name = synchronous_machine['loc_name'][i].decode(codification) gen = Generator(name=name, active_power=synchronous_machine['pgini'][i] * num_machines, voltage_module=vnom, Qmin=synchronous_machine['q_min'][i] * num_machines * snom, Qmax=synchronous_machine['q_max'][i] * num_machines * snom, Snom=snom, power_prof=None, vset_prof=None) circuit.add_generator(bus_obj, gen) # if synchronous_machine['pgini'][i] != 0: # # gen = StaticGenerator(name=name, power=complex(0, synchronous_machine['pgini'][i])) # gen = Generator(name=name, active_power=synchronous_machine['pgini'][i]) # circuit.add_static_generator(bus_obj, gen) return circuit
def interpret_excel_v3(circuit: MultiCircuit, data): """ Interpret the file version 3 In this file version there are no complex numbers saved :param circuit: :param data: Dictionary with the excel file sheet labels and the corresponding DataFrame :return: Nothing, just applies the loaded data to this MultiCircuit instance """ # print('Interpreting V2 data...') # clear all the data circuit.clear() circuit.name = data['name'] # set the base magnitudes circuit.Sbase = data['baseMVA'] # dictionary of branch types [name] -> type object branch_types = dict() # Set comments circuit.comments = data['Comments'] if 'Comments' in data.keys() else '' circuit.logger = Logger() # common function def set_object_attributes(obj_, attr_list, values): for a, attr in enumerate(attr_list): # Hack to change the enabled by active... if attr == 'is_enabled': attr = 'active' if attr == 'type_obj': attr = 'template' if attr == 'wire_name': attr = 'name' if hasattr(obj_, attr): conv = obj_.editable_headers[ attr].tpe # get the type converter if conv is None: setattr(obj_, attr, values[a]) elif conv is BranchType: cbr = BranchTypeConverter(None) setattr(obj_, attr, cbr(values[a])) else: setattr(obj_, attr, conv(values[a])) else: warn(str(obj_) + ' has no ' + attr + ' property.') # time profile ################################################################################################# if 'time' in data.keys(): time_df = data['time'] circuit.time_profile = pd.to_datetime(time_df.values[:, 0]) else: circuit.time_profile = None # Add the buses ################################################################################################ bus_dict = dict() if 'bus' in data.keys(): df = data['bus'] hdr = df.columns.values vals = df.values for i in range(len(df)): obj = Bus() set_object_attributes(obj, hdr, vals[i, :]) bus_dict[obj.name] = obj circuit.add_bus(obj) else: circuit.logger.append('No buses in the file!') # add the loads ################################################################################################ if 'load' in data.keys(): df = data['load'] bus_from = df['bus'].values hdr = df.columns.values hdr = np.delete(hdr, np.argwhere(hdr == 'bus')) vals = df[hdr].values profles_attr = { 'load_P_prof': 'P_prof', 'load_Q_prof': 'Q_prof', 'load_Ir_prof': 'Ir_prof', 'load_Ii_prof': 'Ii_prof', 'load_G_prof': 'G_prof', 'load_B_prof': 'B_prof', 'load_active_prof': 'active_prof', 'load_Cost_prof': 'Cost_prof' } for i in range(df.shape[0]): obj = Load() set_object_attributes(obj, hdr, vals[i, :]) # parse profiles: for sheet_name, load_attr in profles_attr.items(): if sheet_name in data.keys(): val = data[sheet_name].values[:, i] idx = data[sheet_name].index # setattr(obj, load_attr, pd.DataFrame(data=val, index=idx)) setattr(obj, load_attr, val) if circuit.time_profile is None or len( circuit.time_profile) < len(idx): circuit.time_profile = idx try: bus = bus_dict[str(bus_from[i])] except KeyError as ex: raise Exception( str(i) + ': Load bus is not in the buses list.\n' + str(ex)) if obj.name == 'Load': obj.name += str(len(bus.loads) + 1) + '@' + bus.name obj.bus = bus bus.loads.append(obj) obj.ensure_profiles_exist(circuit.time_profile) else: circuit.logger.append('No loads in the file!') # add the controlled generators ################################################################################ if 'generator' in data.keys(): df = data['generator'] bus_from = df['bus'].values hdr = df.columns.values hdr = np.delete(hdr, np.argwhere(hdr == 'bus')) vals = df[hdr].values for i in range(df.shape[0]): obj = Generator() set_object_attributes(obj, hdr, vals[i, :]) if 'generator_P_prof' in data.keys(): val = data['generator_P_prof'].values[:, i] idx = data['generator_P_prof'].index obj.create_profile(magnitude='P', index=idx, arr=val) # also create the Pf array because there might not be values in the file obj.create_profile(magnitude='Pf', index=idx, arr=None) if circuit.time_profile is None or len( circuit.time_profile) < len(idx): circuit.time_profile = idx if 'generator_Pf_prof' in data.keys(): val = data['generator_Pf_prof'].values[:, i] idx = data['generator_Pf_prof'].index obj.create_profile(magnitude='Pf', index=idx, arr=val) if 'generator_Vset_prof' in data.keys(): val = data['generator_Vset_prof'].values[:, i] idx = data['generator_Vset_prof'].index obj.create_profile(magnitude='Vset', index=idx, arr=val) if 'generator_active_prof' in data.keys(): val = data['generator_active_prof'].values[:, i] idx = data['generator_active_prof'].index obj.create_profile(magnitude='active', index=idx, arr=val) if 'generator_Cost_prof' in data.keys(): val = data['generator_Cost_prof'].values[:, i] idx = data['generator_Cost_prof'].index obj.create_profile(magnitude='Cost', index=idx, arr=val) try: bus = bus_dict[str(bus_from[i])] except KeyError as ex: raise Exception( str(i) + ': Controlled generator bus is not in the buses list.\n' + str(ex)) if obj.name == 'gen': obj.name += str(len(bus.controlled_generators) + 1) + '@' + bus.name obj.bus = bus bus.controlled_generators.append(obj) obj.ensure_profiles_exist(circuit.time_profile) else: circuit.logger.append('No controlled generator in the file!') # add the batteries ############################################################################################ if 'battery' in data.keys(): df = data['battery'] bus_from = df['bus'].values hdr = df.columns.values hdr = np.delete(hdr, np.argwhere(hdr == 'bus')) vals = df[hdr].values for i in range(df.shape[0]): obj = Battery() set_object_attributes(obj, hdr, vals[i, :]) if 'battery_P_prof' in data.keys(): val = data['battery_P_prof'].values[:, i] idx = data['battery_P_prof'].index obj.create_profile(magnitude='P', index=idx, arr=val) # also create the Pf array because there might not be values in the file obj.create_profile(magnitude='Pf', index=idx, arr=None) if 'battery_Vset_prof' in data.keys(): val = data['battery_Vset_prof'].values[:, i] idx = data['battery_Vset_prof'].index obj.create_profile(magnitude='Vset', index=idx, arr=val) if 'battery_active_prof' in data.keys(): val = data['battery_active_prof'].values[:, i] idx = data['battery_active_prof'].index obj.create_profile(magnitude='active', index=idx, arr=val) if 'battery_Cost_prof' in data.keys(): val = data['battery_Cost_prof'].values[:, i] idx = data['battery_Cost_prof'].index obj.create_profile(magnitude='Cost', index=idx, arr=val) try: bus = bus_dict[str(bus_from[i])] except KeyError as ex: raise Exception( str(i) + ': Battery bus is not in the buses list.\n' + str(ex)) if obj.name == 'batt': obj.name += str(len(bus.batteries) + 1) + '@' + bus.name obj.bus = bus bus.batteries.append(obj) obj.ensure_profiles_exist(circuit.time_profile) else: circuit.logger.append('No battery in the file!') # add the static generators #################################################################################### if 'static_generator' in data.keys(): df = data['static_generator'] bus_from = df['bus'].values hdr = df.columns.values hdr = np.delete(hdr, np.argwhere(hdr == 'bus')) vals = df[hdr].values for i in range(df.shape[0]): obj = StaticGenerator() set_object_attributes(obj, hdr, vals[i, :]) if 'static_generator_Sprof' in data.keys(): val = data['static_generator_Sprof'].values[:, i] idx = data['static_generator_Sprof'].index obj.create_profile(magnitude='P', index=idx, arr=val.real) obj.create_profile(magnitude='Q', index=idx, arr=val.imag) if 'static_generator_P_prof' in data.keys(): val = data['static_generator_P_prof'].values[:, i] idx = data['static_generator_P_prof'].index obj.create_profile(magnitude='P', index=idx, arr=val) if 'static_generator_Q_prof' in data.keys(): val = data['static_generator_Q_prof'].values[:, i] idx = data['static_generator_Q_prof'].index obj.create_profile(magnitude='Q', index=idx, arr=val) if 'static_generator_active_prof' in data.keys(): val = data['static_generator_active_prof'].values[:, i] idx = data['static_generator_active_prof'].index obj.create_profile(magnitude='active', index=idx, arr=val) if 'static_generator_Cost_prof' in data.keys(): val = data['static_generator_Cost_prof'].values[:, i] idx = data['static_generator_Cost_prof'].index obj.create_profile(magnitude='Cost', index=idx, arr=val) try: bus = bus_dict[str(bus_from[i])] except KeyError as ex: raise Exception( str(i) + ': Static generator bus is not in the buses list.\n' + str(ex)) if obj.name == 'StaticGen': obj.name += str(len(bus.static_generators) + 1) + '@' + bus.name obj.bus = bus bus.static_generators.append(obj) obj.ensure_profiles_exist(circuit.time_profile) else: circuit.logger.append('No static generator in the file!') # add the shunts ############################################################################################### if 'shunt' in data.keys(): df = data['shunt'] bus_from = df['bus'].values hdr = df.columns.values hdr = np.delete(hdr, np.argwhere(hdr == 'bus')) vals = df[hdr].values for i in range(df.shape[0]): obj = Shunt() set_object_attributes(obj, hdr, vals[i, :]) if 'shunt_Y_profiles' in data.keys(): val = data['shunt_Y_profiles'].values[:, i] idx = data['shunt_Y_profiles'].index obj.create_profile(magnitude='G', index=idx, arr=val.real) obj.create_profile(magnitude='B', index=idx, arr=val.imag) if 'shunt_G_prof' in data.keys(): val = data['shunt_G_prof'].values[:, i] idx = data['shunt_G_prof'].index obj.create_profile(magnitude='G', index=idx, arr=val) if 'shunt_B_prof' in data.keys(): val = data['shunt_B_prof'].values[:, i] idx = data['shunt_B_prof'].index obj.create_profile(magnitude='B', index=idx, arr=val) if 'shunt_active_prof' in data.keys(): val = data['shunt_active_prof'].values[:, i] idx = data['shunt_active_prof'].index obj.create_profile(magnitude='active', index=idx, arr=val) if 'shunt_Cost_prof' in data.keys(): val = data['shunt_Cost_prof'].values[:, i] idx = data['shunt_Cost_prof'].index obj.create_profile(magnitude='Cost', index=idx, arr=val) try: bus = bus_dict[str(bus_from[i])] except KeyError as ex: raise Exception( str(i) + ': Shunt bus is not in the buses list.\n' + str(ex)) if obj.name == 'shunt': obj.name += str(len(bus.shunts) + 1) + '@' + bus.name obj.bus = bus bus.shunts.append(obj) obj.ensure_profiles_exist(circuit.time_profile) else: circuit.logger.append('No shunt in the file!') # Add the wires ################################################################################################ if 'wires' in data.keys(): df = data['wires'] hdr = df.columns.values vals = df.values for i in range(len(df)): obj = Wire() set_object_attributes(obj, hdr, vals[i, :]) circuit.add_wire(obj) else: circuit.logger.append('No wires in the file!') # Add the overhead_line_types ################################################################################## if 'overhead_line_types' in data.keys(): df = data['overhead_line_types'] if data['overhead_line_types'].values.shape[0] > 0: for tower_name in df['tower_name'].unique(): obj = Tower() dft = df[df['tower_name'] == tower_name] vals = dft.values wire_prop = df.columns.values[len(obj.editable_headers):] # set the tower values set_object_attributes(obj, obj.editable_headers.keys(), vals[0, :]) # add the wires if len(wire_prop) == 7: for i in range(vals.shape[0]): # ['wire_name' 'xpos' 'ypos' 'phase' 'r' 'x' 'gmr'] name = dft['wire_name'].values[i] gmr = dft['gmr'].values[i] r = dft['r'].values[i] x = dft['x'].values[i] xpos = dft['xpos'].values[i] ypos = dft['ypos'].values[i] phase = dft['phase'].values[i] wire = Wire(name=name, gmr=gmr, r=r, x=x) w = WireInTower(wire=wire, xpos=xpos, ypos=ypos, phase=phase) obj.wires_in_tower.append(w) circuit.add_overhead_line(obj) branch_types[str(obj)] = obj else: pass else: circuit.logger.append('No overhead_line_types in the file!') # Add the wires ################################################################################################ if 'underground_cable_types' in data.keys(): df = data['underground_cable_types'] hdr = df.columns.values vals = df.values # for i in range(len(lst)): # obj = UndergroundLineType() # set_object_attributes(obj, hdr, vals[i, :]) # circuit.underground_cable_types.append(obj) # branch_types[str(obj)] = obj else: circuit.logger.append('No underground_cable_types in the file!') # Add the sequence line types ################################################################################## if 'sequence_line_types' in data.keys(): df = data['sequence_line_types'] hdr = df.columns.values vals = df.values for i in range(len(df)): obj = SequenceLineType() set_object_attributes(obj, hdr, vals[i, :]) circuit.add_sequence_line(obj) branch_types[str(obj)] = obj else: circuit.logger.append('No sequence_line_types in the file!') # Add the transformer types #################################################################################### if 'transformer_types' in data.keys(): df = data['transformer_types'] hdr = df.columns.values vals = df.values for i in range(len(df)): obj = TransformerType() set_object_attributes(obj, hdr, vals[i, :]) circuit.add_transformer_type(obj) branch_types[str(obj)] = obj else: circuit.logger.append('No transformer_types in the file!') # Add the branches ############################################################################################# if 'branch' in data.keys(): df = data['branch'] # fix the old 'is_transformer' property if 'is_transformer' in df.columns.values: df['is_transformer'] = df['is_transformer'].map({ True: 'transformer', False: 'line' }) df.rename(columns={'is_transformer': 'branch_type'}, inplace=True) bus_from = df['bus_from'].values bus_to = df['bus_to'].values hdr = df.columns.values hdr = np.delete(hdr, np.argwhere(hdr == 'bus_from')) hdr = np.delete(hdr, np.argwhere(hdr == 'bus_to')) vals = df[hdr].values for i in range(df.shape[0]): try: obj = Branch(bus_from=bus_dict[str(bus_from[i])], bus_to=bus_dict[str(bus_to[i])]) except KeyError as ex: raise Exception( str(i) + ': Branch bus is not in the buses list.\n' + str(ex)) set_object_attributes(obj, hdr, vals[i, :]) # set the branch circuit.add_branch(obj) obj.ensure_profiles_exist(circuit.time_profile) if 'branch_active_prof' in data.keys(): val = data['branch_active_prof'].values[:, i] idx = data['branch_active_prof'].index obj.create_profile(magnitude='active', index=idx, arr=val) if 'branch_Cost_prof' in data.keys(): val = data['branch_Cost_prof'].values[:, i] idx = data['branch_Cost_prof'].index obj.create_profile(magnitude='Cost', index=idx, arr=val) if 'branch_temp_oper_prof' in data.keys(): val = data['branch_temp_oper_prof'].values[:, i] idx = data['branch_temp_oper_prof'].index obj.create_profile(magnitude='temp_oper', index=idx, arr=val) # correct the branch template object template_name = str(obj.template) if template_name in branch_types.keys(): obj.template = branch_types[template_name] print(template_name, 'updtaed!') else: circuit.logger.append('No branches in the file!') # Other actions ################################################################################################ circuit.logger += circuit.apply_all_branch_types()
def interprete_excel_v2(circuit: MultiCircuit, data): """ Interpret the file version 2 :param circuit: :param data: Dictionary with the excel file sheet labels and the corresponding DataFrame :return: Nothing, just applies the loaded data to this MultiCircuit instance """ # print('Interpreting V2 data...') # clear all the data circuit.clear() circuit.name = data['name'] # set the base magnitudes circuit.Sbase = data['baseMVA'] # dictionary of branch types [name] -> type object branch_types = dict() # Set comments circuit.comments = data['Comments'] if 'Comments' in data.keys() else '' circuit.time_profile = None circuit.logger = Logger() # common function def set_object_attributes(obj_, attr_list, values): for a, attr in enumerate(attr_list): # Hack to change the enabled by active... if attr == 'is_enabled': attr = 'active' if attr == 'type_obj': attr = 'template' if hasattr(obj_, attr): conv = obj_.editable_headers[ attr].tpe # get the type converter if conv is None: setattr(obj_, attr, values[a]) elif conv is BranchType: cbr = BranchTypeConverter(None) setattr(obj_, attr, cbr(values[a])) else: setattr(obj_, attr, conv(values[a])) else: if attr in [ 'Y', 'Z', 'I', 'S', 'seq_resistance', 'seq_admittance', 'Zf' ]: if attr == 'Z': val = complex(values[a]) re = 1 / val.real if val.real != 0.0 else 0 im = 1 / val.imag if val.imag != 0.0 else 0 setattr(obj_, 'G', re) setattr(obj_, 'B', im) if attr == 'Zf': val = complex(values[a]) re = 1 / val.real if val.real != 0.0 else 0 im = 1 / val.imag if val.imag != 0.0 else 0 setattr(obj_, 'r_fault', re) setattr(obj_, 'x_fault', im) if attr == 'Y': val = complex(values[a]) re = val.real im = val.imag setattr(obj_, 'G', re) setattr(obj_, 'B', im) elif attr == 'I': val = complex(values[a]) setattr(obj_, 'Ir', val.real) setattr(obj_, 'Ii', val.imag) elif attr == 'S': val = complex(values[a]) setattr(obj_, 'P', val.real) setattr(obj_, 'Q', val.imag) elif attr == 'seq_resistance': val = complex(values[a]) setattr(obj_, 'R1', val.real) setattr(obj_, 'X1', val.imag) elif attr == 'seq_admittance': val = complex(values[a]) setattr(obj_, 'Gsh1', val.real) setattr(obj_, 'Bsh1', val.imag) else: warn(str(obj_) + ' has no ' + attr + ' property.') # Add the buses ################################################################################################ bus_dict = dict() if 'bus' in data.keys(): lst = data['bus'] hdr = lst.columns.values vals = lst.values for i in range(len(lst)): obj = Bus() set_object_attributes(obj, hdr, vals[i, :]) bus_dict[obj.name] = obj circuit.add_bus(obj) else: circuit.logger.append('No buses in the file!') # add the loads ################################################################################################ if 'load' in data.keys(): lst = data['load'] bus_from = lst['bus'].values hdr = lst.columns.values hdr = np.delete(hdr, np.argwhere(hdr == 'bus')) vals = lst[hdr].values for i in range(len(lst)): obj = Load() set_object_attributes(obj, hdr, vals[i, :]) if 'load_Sprof' in data.keys(): idx = data['load_Sprof'].index # create all the profiles obj.create_profiles(index=idx) # create the power profiles val = np.array( [complex(v) for v in data['load_Sprof'].values[:, i]]) obj.create_profile(magnitude='P', index=idx, arr=val.real) obj.create_profile(magnitude='Q', index=idx, arr=val.imag) if circuit.time_profile is None or len( circuit.time_profile) < len(idx): circuit.time_profile = idx if 'load_Iprof' in data.keys(): val = np.array( [complex(v) for v in data['load_Iprof'].values[:, i]]) idx = data['load_Iprof'].index obj.create_profile(magnitude='Ir', index=idx, arr=val.real) obj.create_profile(magnitude='Ii', index=idx, arr=val.imag) if circuit.time_profile is None or len( circuit.time_profile) < len(idx): circuit.time_profile = idx if 'load_Zprof' in data.keys(): val = np.array( [complex(v) for v in data['load_Zprof'].values[:, i]]) idx = data['load_Zprof'].index obj.create_profile(magnitude='G', index=idx, arr=val.real) obj.create_profile(magnitude='B', index=idx, arr=val.imag) if circuit.time_profile is None or len( circuit.time_profile) < len(idx): circuit.time_profile = idx try: bus = bus_dict[str(bus_from[i])] except KeyError as ex: raise Exception( str(i) + ': Load bus is not in the buses list.\n' + str(ex)) if obj.name == 'Load': obj.name += str(len(bus.loads) + 1) + '@' + bus.name obj.bus = bus bus.loads.append(obj) obj.ensure_profiles_exist(circuit.time_profile) else: circuit.logger.append('No loads in the file!') # add the controlled generators ################################################################################ if 'controlled_generator' in data.keys(): lst = data['controlled_generator'] bus_from = lst['bus'].values hdr = lst.columns.values hdr = np.delete(hdr, np.argwhere(hdr == 'bus')) vals = lst[hdr].values for i in range(len(lst)): obj = Generator() set_object_attributes(obj, hdr, vals[i, :]) if 'CtrlGen_P_profiles' in data.keys(): val = data['CtrlGen_P_profiles'].values[:, i] idx = data['CtrlGen_P_profiles'].index obj.create_profile(magnitude='P', index=idx, arr=val) # also create the Pf array because there might not be values in the file obj.create_profile(magnitude='Pf', index=idx) if 'CtrlGen_Pf_profiles' in data.keys(): val = data['CtrlGen_Pf_profiles'].values[:, i] idx = data['CtrlGen_Pf_profiles'].index obj.create_profile(magnitude='Pf', index=idx, arr=val) if 'CtrlGen_Vset_profiles' in data.keys(): val = data['CtrlGen_Vset_profiles'].values[:, i] idx = data['CtrlGen_Vset_profiles'].index obj.create_profile(magnitude='Vset', index=idx, arr=val) try: bus = bus_dict[str(bus_from[i])] except KeyError as ex: raise Exception( str(i) + ': Controlled generator bus is not in the buses list.\n' + str(ex)) if obj.name == 'gen': obj.name += str(len(bus.controlled_generators) + 1) + '@' + bus.name obj.bus = bus bus.controlled_generators.append(obj) obj.ensure_profiles_exist(circuit.time_profile) else: circuit.logger.append('No controlled generator in the file!') # add the batteries ############################################################################################ if 'battery' in data.keys(): lst = data['battery'] bus_from = lst['bus'].values hdr = lst.columns.values hdr = np.delete(hdr, np.argwhere(hdr == 'bus')) vals = lst[hdr].values for i in range(len(lst)): obj = Battery() set_object_attributes(obj, hdr, vals[i, :]) if 'battery_P_profiles' in data.keys(): val = data['battery_P_profiles'].values[:, i] idx = data['battery_P_profiles'].index obj.create_profile(magnitude='P', index=idx, arr=val) obj.create_profile(magnitude='Pf', index=idx) if 'battery_Pf_profiles' in data.keys(): val = data['battery_Pf_profiles'].values[:, i] idx = data['battery_Pf_profiles'].index obj.create_profile(magnitude='Pf', index=idx, arr=val) if 'battery_Vset_profiles' in data.keys(): val = data['battery_Vset_profiles'].values[:, i] idx = data['battery_Vset_profiles'].index obj.create_profile(magnitude='Vset', index=idx, arr=val) try: bus = bus_dict[str(bus_from[i])] except KeyError as ex: raise Exception( str(i) + ': Battery bus is not in the buses list.\n' + str(ex)) if obj.name == 'batt': obj.name += str(len(bus.batteries) + 1) + '@' + bus.name obj.bus = bus bus.batteries.append(obj) obj.ensure_profiles_exist(circuit.time_profile) else: circuit.logger.append('No battery in the file!') # add the static generators #################################################################################### if 'static_generator' in data.keys(): lst = data['static_generator'] bus_from = lst['bus'].values hdr = lst.columns.values hdr = np.delete(hdr, np.argwhere(hdr == 'bus')) vals = lst[hdr].values for i in range(len(lst)): obj = StaticGenerator() set_object_attributes(obj, hdr, vals[i, :]) if 'static_generator_Sprof' in data.keys(): val = data['static_generator_Sprof'].values[:, i] idx = data['static_generator_Sprof'].index obj.create_profile(magnitude='P', index=idx, arr=val.real) obj.create_profile(magnitude='Q', index=idx, arr=val.imag) if 'static_generator_P_prof' in data.keys(): val = data['static_generator_P_prof'].values[:, i] idx = data['static_generator_P_prof'].index obj.create_profile(magnitude='P', index=idx, arr=val) if 'static_generator_Q_prof' in data.keys(): val = data['static_generator_Q_prof'].values[:, i] idx = data['static_generator_Q_prof'].index obj.create_profile(magnitude='Q', index=idx, arr=val) try: bus = bus_dict[str(bus_from[i])] except KeyError as ex: raise Exception( str(i) + ': Static generator bus is not in the buses list.\n' + str(ex)) if obj.name == 'StaticGen': obj.name += str(len(bus.static_generators) + 1) + '@' + bus.name obj.bus = bus bus.static_generators.append(obj) obj.ensure_profiles_exist(circuit.time_profile) else: circuit.logger.append('No static generator in the file!') # add the shunts ############################################################################################### if 'shunt' in data.keys(): lst = data['shunt'] bus_from = lst['bus'].values hdr = lst.columns.values hdr = np.delete(hdr, np.argwhere(hdr == 'bus')) vals = lst[hdr].values for i in range(len(lst)): obj = Shunt() set_object_attributes(obj, hdr, vals[i, :]) if 'shunt_Y_profiles' in data.keys(): val = data['shunt_Y_profiles'].values[:, i] idx = data['shunt_Y_profiles'].index obj.create_profile(magnitude='G', index=idx, arr=val.real) obj.create_profile(magnitude='B', index=idx, arr=val.imag) try: bus = bus_dict[str(bus_from[i])] except KeyError as ex: raise Exception( str(i) + ': Shunt bus is not in the buses list.\n' + str(ex)) if obj.name == 'shunt': obj.name += str(len(bus.shunts) + 1) + '@' + bus.name obj.bus = bus bus.shunts.append(obj) obj.ensure_profiles_exist(circuit.time_profile) else: circuit.logger.append('No shunt in the file!') # Add the wires ################################################################################################ if 'wires' in data.keys(): lst = data['wires'] hdr = lst.columns.values vals = lst.values for i in range(len(lst)): obj = Wire() set_object_attributes(obj, hdr, vals[i, :]) circuit.add_wire(obj) else: circuit.logger.append('No wires in the file!') # Add the overhead_line_types ################################################################################## if 'overhead_line_types' in data.keys(): lst = data['overhead_line_types'] if data['overhead_line_types'].values.shape[0] > 0: for tower_name in lst['tower_name'].unique(): obj = Tower() vals = lst[lst['tower_name'] == tower_name].values # set the tower values set_object_attributes(obj, obj.editable_headers.keys(), vals[0, :]) # add the wires for i in range(vals.shape[0]): wire = Wire() set_object_attributes(wire, obj.get_wire_properties(), vals[i, len(obj.editable_headers):]) obj.wires_in_tower.append(wire) circuit.add_overhead_line(obj) branch_types[str(obj)] = obj else: pass else: circuit.logger.append('No overhead_line_types in the file!') # Add the wires ################################################################################################ if 'underground_cable_types' in data.keys(): lst = data['underground_cable_types'] hdr = lst.columns.values vals = lst.values # for i in range(len(lst)): # obj = UndergroundLineType() # set_object_attributes(obj, hdr, vals[i, :]) # circuit.underground_cable_types.append(obj) # branch_types[str(obj)] = obj else: circuit.logger.append('No underground_cable_types in the file!') # Add the sequence line types ################################################################################## if 'sequence_line_types' in data.keys(): lst = data['sequence_line_types'] hdr = lst.columns.values vals = lst.values for i in range(len(lst)): obj = SequenceLineType() set_object_attributes(obj, hdr, vals[i, :]) circuit.add_sequence_line(obj) branch_types[str(obj)] = obj else: circuit.logger.append('No sequence_line_types in the file!') # Add the transformer types #################################################################################### if 'transformer_types' in data.keys(): lst = data['transformer_types'] hdr = lst.columns.values vals = lst.values for i in range(len(lst)): obj = TransformerType() set_object_attributes(obj, hdr, vals[i, :]) circuit.add_transformer_type(obj) branch_types[str(obj)] = obj else: circuit.logger.append('No transformer_types in the file!') # Add the branches ############################################################################################# if 'branch' in data.keys(): lst = data['branch'] # fix the old 'is_transformer' property if 'is_transformer' in lst.columns.values: lst['is_transformer'] = lst['is_transformer'].map({ True: 'transformer', False: 'line' }) lst.rename(columns={'is_transformer': 'branch_type'}, inplace=True) bus_from = lst['bus_from'].values bus_to = lst['bus_to'].values hdr = lst.columns.values hdr = np.delete(hdr, np.argwhere(hdr == 'bus_from')) hdr = np.delete(hdr, np.argwhere(hdr == 'bus_to')) vals = lst[hdr].values for i in range(len(lst)): try: obj = Branch(bus_from=bus_dict[str(bus_from[i])], bus_to=bus_dict[str(bus_to[i])]) except KeyError as ex: raise Exception( str(i) + ': Branch bus is not in the buses list.\n' + str(ex)) set_object_attributes(obj, hdr, vals[i, :]) # correct the branch template object template_name = str(obj.template) if template_name in branch_types.keys(): obj.template = branch_types[template_name] print(template_name, 'updated!') # set the branch circuit.add_branch(obj) obj.ensure_profiles_exist(circuit.time_profile) else: circuit.logger.append('No branches in the file!') # Other actions ################################################################################################ circuit.logger += circuit.apply_all_branch_types()
def data_frames_to_circuit(data: Dict): """ Interpret data dictionary :param data: dictionary of data frames :return: MultiCircuit instance """ # create circuit circuit = MultiCircuit() if 'name' in data.keys(): circuit.name = str(data['name']) if circuit.name == 'nan': circuit.name = '' # set the base magnitudes if 'baseMVA' in data.keys(): circuit.Sbase = data['baseMVA'] # Set comments if 'Comments' in data.keys(): circuit.comments = str(data['Comments']) if circuit.comments == 'nan': circuit.comments = '' if 'ModelVersion' in data.keys(): circuit.model_version = int(data['ModelVersion']) if 'UserName' in data.keys(): circuit.user_name = data['UserName'] # dictionary of objects to iterate object_types = get_objects_dictionary() circuit.logger = Logger() # time profile ----------------------------------------------------------------------------------------------------- if 'time' in data.keys(): time_df = data['time'] circuit.time_profile = pd.to_datetime(time_df.values[:, 0], dayfirst=True) else: circuit.time_profile = None # dictionary of dictionaries by element type # elements_dict[DataType][element_name] = actual object elements_dict = dict() elements_dict_by_name = dict() # ------------------------------------------------------------------------------------------------------------------ # for each element type... for key, template_elm in object_types.items(): if key in data.keys(): # get the DataFrame df = data[key] # create the objects ... devices = list() devices_dict = dict() if 'idtag' in df.columns.values: for i in range(df.shape[0]): elm = type(template_elm)() idtag = df['idtag'].values[i] # create the buses dictionary, this works because the bus is the first key in "object_types" devices_dict[idtag] = elm # add the device to the elements devices.append(elm) else: for i in range(df.shape[0]): elm = type(template_elm)() idtag = df['name'].values[i] # create the buses dictionary, this works because the bus is the first key in "object_types" devices_dict[idtag] = elm # add the device to the elements devices.append(elm) elements_dict[template_elm.device_type] = devices_dict # fill in the objects if df.shape[0] > 0: # for each property ... for object_property_name, gc_prop in template_elm.editable_headers.items(): # if the object property exists in the data file, set all the object's property if object_property_name in df.columns.values: # get the type converter dtype = gc_prop.tpe # for each object, set the property for i in range(df.shape[0]): # convert and assign the data if dtype is None: val = df[object_property_name].values[i] setattr(devices[i], object_property_name, val) elif dtype in [DeviceType.SubstationDevice, DeviceType.AreaDevice, DeviceType.ZoneDevice, DeviceType.CountryDevice]: """ This piece is to assign the objects matching the Area, Substation, Zone and Country The cases may be: a) there is a matching id tag -> ok, assign it b) the value is a string -> create the relevant object, make sure it is not repeated by name inset the object in its matching object dictionary """ # search for the Substation, Area, Zone or Country matching object and assign the object # this is the stored string (either idtag or name...) val = str(df[object_property_name].values[i]) if dtype not in elements_dict.keys(): elements_dict[dtype] = dict() if dtype not in elements_dict_by_name.keys(): elements_dict_by_name[dtype] = dict() if val in elements_dict[dtype].keys(): # the grouping exists as object, use it grouping = elements_dict[dtype][val] else: # create the grouping if val in elements_dict_by_name[dtype].keys(): grouping = elements_dict_by_name[dtype][val] else: grouping = type(object_types[dtype.value.lower()])(name=val) elements_dict[dtype][grouping.idtag] = grouping # store also by name elements_dict_by_name[dtype][grouping.name] = grouping # set the object setattr(devices[i], object_property_name, grouping) elif dtype == DeviceType.BusDevice: # check if the bus is in the dictionary... if df[object_property_name].values[i] in elements_dict[DeviceType.BusDevice].keys(): parent_bus: dev.Bus = elements_dict[DeviceType.BusDevice][df[object_property_name].values[i]] setattr(devices[i], object_property_name, parent_bus) # add the device to the bus if template_elm.device_type in [DeviceType.LoadDevice, DeviceType.GeneratorDevice, DeviceType.BatteryDevice, DeviceType.StaticGeneratorDevice, DeviceType.ShuntDevice, DeviceType.ExternalGridDevice]: parent_bus.add_device(devices[i]) else: circuit.logger.add_error('Bus not found', str(df[object_property_name].values[i])) elif dtype in [DeviceType.TransformerTypeDevice, # template types mostly DeviceType.SequenceLineDevice, DeviceType.TowerDevice]: if df[object_property_name].values[i] in elements_dict[dtype].keys(): # get the actual template and set it val = elements_dict[dtype][df[object_property_name].values[i]] setattr(devices[i], object_property_name, val) else: circuit.logger.add_error(dtype.value + ' type not found', str(df[object_property_name].values[i])) elif dtype == bool: # regular types (int, str, float, etc...) val = df[object_property_name].values[i] if val == 'False': setattr(devices[i], object_property_name, False) elif val == 'True': setattr(devices[i], object_property_name, True) else: setattr(devices[i], object_property_name, bool(val)) else: # regular types (int, str, float, etc...) val = dtype(df[object_property_name].values[i]) setattr(devices[i], object_property_name, val) # search the profiles in the data and assign them if object_property_name in template_elm.properties_with_profile.keys(): # get the profile property prop_prof = template_elm.properties_with_profile[object_property_name] # build the profile property file-name to get it from the data profile_name = key + '_' + prop_prof if profile_name in data.keys(): # get the profile DataFrame dfp = data[profile_name] # for each object, set the profile for i in range(dfp.shape[1]): profile = dfp.values[:, i] setattr(devices[i], prop_prof, profile.astype(dtype)) else: circuit.logger.add_error('Profile was not found in the data', object_property_name) else: circuit.logger.add_error(object_property_name + ' of object type ' + str(template_elm.device_type) + ' not found in the input data') else: # no objects of this type pass # ensure profiles existence if circuit.time_profile is not None: for i in range(df.shape[0]): devices[i].ensure_profiles_exist(circuit.time_profile) # add the objects to the circuit (buses, branches ot template types) if template_elm.device_type == DeviceType.BusDevice: circuit.buses = devices # elif template_elm.device_type == DeviceType.SubstationDevice: # circuit.substations = devices # # elif template_elm.device_type == DeviceType.AreaDevice: # circuit.areas = devices # # elif template_elm.device_type == DeviceType.ZoneDevice: # circuit.zones = devices # # elif template_elm.device_type == DeviceType.CountryDevice: # circuit.countries = devices elif template_elm.device_type == DeviceType.BranchDevice: for d in devices: circuit.add_branch(d) # each branch needs to be converted accordingly elif template_elm.device_type == DeviceType.LineDevice: circuit.lines = devices elif template_elm.device_type == DeviceType.DCLineDevice: circuit.dc_lines = devices elif template_elm.device_type == DeviceType.Transformer2WDevice: circuit.transformers2w = devices elif template_elm.device_type == DeviceType.HVDCLineDevice: circuit.hvdc_lines = devices elif template_elm.device_type == DeviceType.UpfcDevice: circuit.upfc_devices = devices elif template_elm.device_type == DeviceType.VscDevice: for dev in devices: dev.correct_buses_connection() circuit.vsc_devices = devices elif template_elm.device_type == DeviceType.TowerDevice: circuit.overhead_line_types = devices elif template_elm.device_type == DeviceType.TransformerTypeDevice: circuit.transformer_types = devices elif template_elm.device_type == DeviceType.UnderGroundLineDevice: circuit.underground_cable_types = devices elif template_elm.device_type == DeviceType.SequenceLineDevice: circuit.sequence_line_types = devices elif template_elm.device_type == DeviceType.WireDevice: circuit.wire_types = devices else: circuit.logger.add_error('The data does not contain information about the type', str(key)) # fill in wires into towers ---------------------------------------------------------------------------------------- if 'tower_wires' in data.keys(): df = data['tower_wires'] for i in range(df.shape[0]): tower_name = df['tower_name'].values[i] wire_name = df['wire_name'].values[i] if (tower_name in elements_dict[DeviceType.TowerDevice].keys()) and \ (wire_name in elements_dict[DeviceType.WireDevice].keys()): tower = elements_dict[DeviceType.TowerDevice][tower_name] wire = elements_dict[DeviceType.WireDevice][wire_name] xpos = df['xpos'].values[i] ypos = df['ypos'].values[i] phase = df['phase'].values[i] w = dev.WireInTower(wire=wire, xpos=xpos, ypos=ypos, phase=phase) tower.wires_in_tower.append(w) # Other actions ---------------------------------------------------------------------------------------------------- circuit.logger += circuit.apply_all_branch_types() # Add the groups --------------------------------------------------------------------------------------------------- if DeviceType.SubstationDevice in elements_dict.keys(): circuit.substations = list(elements_dict[DeviceType.SubstationDevice].values()) if DeviceType.AreaDevice in elements_dict.keys(): circuit.areas = list(elements_dict[DeviceType.AreaDevice].values()) if DeviceType.ZoneDevice in elements_dict.keys(): circuit.zones = list(elements_dict[DeviceType.ZoneDevice].values()) if DeviceType.CountryDevice in elements_dict.keys(): circuit.countries = list(elements_dict[DeviceType.CountryDevice].values()) return circuit
def load_dpx(file_name, contraction_factor=1000) -> MultiCircuit: """ Read DPX file :param file_name: file name :param contraction_factor: contraction factor :return: MultiCircuit """ circuit = MultiCircuit() Sbase = 100 circuit.Sbase = Sbase SQRT3 = np.sqrt(3) # read the raw data into a structured dictionary print('Reading file...') structures_dict, logger = read_dpx_data(file_name=file_name) # format the read data print('Packing data...') data_structures, logger = repack(data_structures=structures_dict, logger=logger) buses_id_dict = dict() # create nodes for tpe in data_structures['Nodes']: # Airline support post # __headers__['Nodes']['APOIO'] = ['CLASS', 'ID', 'NAME', 'VBASE', 'GX', 'GY', 'SX', 'SY', 'EXIST'] # __headers__['Nodes']['ARM'] = ['CLASS', 'ID', 'NAME', 'VBASE', 'GX', 'GY', 'SX', 'SY', 'EXIST', 'YEAR'] # __headers__['Nodes']['CX'] = ['CLASS', 'ID', 'NAME', 'VBASE', 'GX', 'GY', 'SX', 'SY', 'EXIST'] # __headers__['Nodes']['CXN'] = ['CLASS', 'ID', 'NAME', 'VBASE', 'GX', 'GY', 'SX', 'SY', 'EXIST'] # __headers__['Nodes']['LOAD'] = ['CLASS', 'ID', 'NAME', 'VBASE', 'GX', 'GY', 'SX', 'SY', 'EXIST', 'VMIN', 'VMAX', 'NCMPLAN'] # fill to fit... if tpe in ['APOIO', 'ARM', 'CX', 'CXN', 'LOAD']: df = data_structures['Nodes'][tpe] for i in range(df.shape[0]): name = 'B' + str(len(circuit.buses) + 1) + '_' + str( df['NAME'].values[i]) Vnom = float(df['VBASE'].values[i]) x = float(df['GX'].values[i]) / contraction_factor y = float(df['GY'].values[i]) / contraction_factor id_ = df['ID'].values[i] bus = Bus(name=name, vnom=Vnom, xpos=x, ypos=y, height=40, width=60) circuit.add_bus(bus) buses_id_dict[id_] = bus # Network Equivalent # __headers__['Nodes']['EQUIV'] = ['CLASS', 'ID', 'NAME', 'VBASE', 'GX', 'GY', 'SX', 'SY', 'VMIN', 'VMAX', 'ZONE', # 'SEPNET', 'AUTOUP', 'P', 'Q', 'ELAST', 'SIMUL', 'HTYP', 'HARM5', 'HARM7', # 'HARM11', # 'HARM13', 'NOGRW', 'RS', 'XS', 'R1', 'X1', 'R2', 'X2', 'RH', 'XH', 'COM'] elif tpe == 'EQUIV': df = data_structures['Nodes'][tpe] for i in range(df.shape[0]): name = 'B' + str(len(circuit.buses) + 1) + '_' + str( df['NAME'].values[i]) Vnom = float(df['VBASE'].values[i]) x = float(df['GX'].values[i]) / contraction_factor y = float(df['GY'].values[i]) / contraction_factor id_ = df['ID'].values[i] bus = Bus(name=name, vnom=Vnom, xpos=x, ypos=y, height=40, width=60, is_slack=True) circuit.add_bus(bus) buses_id_dict[id_] = bus name = 'LD' + str(len(circuit.buses)) + '_' + str( df['NAME'].values[i]) p = float(df['P'].values[i]) * Sbase q = float(df['Q'].values[i]) * Sbase load = Load(name=name, P=p, Q=q) circuit.add_load(bus, load) # Generator # __headers__['Nodes']['GEN'] = ['CLASS', 'ID', 'NAME', 'VBASE', 'GX', 'GY', 'SX', 'SY', 'EXIST', 'MODEL', 'VMIN', # 'VMAX', # 'V', 'ENAB', 'P', 'Q', 'QMIN', 'QMAX', 'ELAST', 'HTYP', 'HARM5', 'HARM7', # 'HARM11', # 'HARM13', 'VNOM', 'RAT', 'TGEN', 'COST', 'YEAR'] elif tpe == 'GEN': df = data_structures['Nodes'][tpe] for i in range(df.shape[0]): name = 'B' + str(len(circuit.buses) + 1) + '_' + str( df['NAME'].values[i]) Vnom = float(df['VBASE'].values[i]) x = float(df['GX'].values[i]) / contraction_factor y = float(df['GY'].values[i]) / contraction_factor id_ = df['ID'].values[i] bus = Bus(name=name, vnom=Vnom, xpos=x, ypos=y, height=40, width=60) circuit.add_bus(bus) buses_id_dict[id_] = bus mode = int(df['MODEL'].values[i]) if mode == 1: name = 'GEN' + str(len(circuit.buses)) + '_' + str( df['NAME'].values[i]) p = float(df['P'].values[i]) * Sbase q = float(df['Q'].values[i]) * Sbase v = float(df['V'].values[i]) # p.u. gen = Generator(name=name, active_power=p, voltage_module=v) circuit.add_generator(bus, gen) else: name = 'GENSTAT' + str(len(circuit.buses)) + '_' + str( df['NAME'].values[i]) p = float(df['P'].values[i]) * Sbase q = float(df['Q'].values[i]) * Sbase gen = StaticGenerator(name=name, P=p, Q=q) circuit.add_static_generator(bus, gen) # Transformation station # __headers__['Nodes']['PT'] = ['CLASS', 'ID', 'NAME', 'VBASE', 'GX', 'GY', 'SX', 'SY', 'EXIST', 'VMIN', 'VMAX', # 'ZONE', # 'ENAB', 'P', 'Q', 'ELAST', 'SIMUL', 'HTYP', 'HARM5', 'HARM7', 'HARM11', 'HARM13', # 'NOGRW', # 'EQEXIST', 'EQPOSS1', 'MCOST1', 'ICOST1', 'EQPOSS2', 'MCOST2', 'ICOST2', # 'EQPOSS3', 'MCOST3', # 'ICOST3', 'NCLI', 'EQTYPE', 'YEAR', 'COM', 'INFOCOM', 'ID_AUX'] elif tpe in ['PT', 'PTC']: df = data_structures['Nodes'][tpe] for i in range(df.shape[0]): name = 'B' + str(len(circuit.buses) + 1) + '_' + str( df['NAME'].values[i]) Vnom = float(df['VBASE'].values[i]) x = float(df['GX'].values[i]) / contraction_factor y = float(df['GY'].values[i]) / contraction_factor id_ = df['ID'].values[i] bus = Bus(name=name, vnom=Vnom, xpos=x, ypos=y, height=40, width=60) name = 'LD' + str(len(circuit.buses) + 1) + '_' + str( df['NAME'].values[i]) p = float(df['P'].values[i]) * Sbase q = float(df['Q'].values[i]) * Sbase load = Load(name=name, P=p, Q=q) circuit.add_bus(bus) circuit.add_load(bus, load) buses_id_dict[id_] = bus # Reference node # __headers__['Nodes']['REF'] = ['CLASS', 'ID', 'NAME', 'VBASE', 'GX', 'GY', 'SX', 'SY', 'VREF', 'RAT', # 'COST', 'TGEN', 'YEAR'] elif tpe == 'REF': df = data_structures['Nodes'][tpe] for i in range(df.shape[0]): name = 'B' + str(len(circuit.buses) + 1) + '_' + str( df['NAME'].values[i]) Vnom = float(df['VBASE'].values[i]) x = float(df['GX'].values[i]) / contraction_factor y = float(df['GY'].values[i]) / contraction_factor id_ = df['ID'].values[i] bus = Bus(name=name, vnom=Vnom, xpos=x, ypos=y, height=40, width=60, is_slack=True) circuit.add_bus(bus) buses_id_dict[id_] = bus # Voltage Transformer # __headers__['Nodes']['TT'] = ['CLASS', 'ID', 'NAME', 'VBASE', 'GX', 'GY', 'SX', 'SY', 'EXIST', 'VMIN', 'VMAX', # 'DISABLE', 'HARM5', 'HARM7', 'HARM11', 'HARM13', 'EQEXIST', 'TAP', 'YEAR', # 'ID_AUX'] elif tpe == 'TT': df = data_structures['Nodes'][tpe] for i in range(df.shape[0]): name = 'B' + str(len(circuit.buses) + 1) + '_' + str( df['NAME'].values[i]) Vnom = float(df['VBASE'].values[i]) x = float(df['GX'].values[i]) / contraction_factor y = float(df['GY'].values[i]) / contraction_factor id_ = df['ID'].values[i] bus = Bus(name=name, vnom=Vnom, xpos=x, ypos=y, height=40, width=60) circuit.add_bus(bus) buses_id_dict[id_] = bus else: logger.add_error('Not recognised under Nodes', tpe) # create branches for tpe in data_structures['Branches']: # Condenser series or shunt # __headers__['Branches']['CAP'] = ['CLASS', 'ID', 'NAME', 'ID1', 'ID2', 'EXIST', 'STAT', 'PERM', 'EQ', 'YEAR'] if tpe in ['CAP', 'IND']: df = data_structures['Branches'][tpe] for i in range(df.shape[0]): name = df['NAME'].values[i] id1 = df['ID1'].values[i] id2 = df['ID2'].values[i] b1 = buses_id_dict[id1] b2 = buses_id_dict[id2] # get equipment reference in the catalogue eq_id = df['EQ'].values[i] df_cat = data_structures['CatalogBranch'][tpe] cat_elm = df_cat[df_cat['EQ'] == eq_id] try: x = float(cat_elm['REAC'].values[0]) * Sbase except: x = 1e-20 br = Branch(bus_from=b1, bus_to=b2, name=name, x=x, branch_type=BranchType.Branch) circuit.add_branch(br) # Estimator # __headers__['Branches']['ESTIM'] = ['CLASS', 'ID', 'NAME', 'ID1', 'ID2', 'INDEP', 'I', 'SIMULT'] if tpe in ['ESTIM']: df = data_structures['Branches'][tpe] for i in range(df.shape[0]): name = df['NAME'].values[i] id1 = df['ID1'].values[i] id2 = df['ID2'].values[i] b1 = buses_id_dict[id1] b2 = buses_id_dict[id2] br = Branch(bus_from=b1, bus_to=b2, name=name, branch_type=BranchType.Branch) circuit.add_branch(br) # Breaker # __headers__['Branches']['DISJ'] = ['CLASS', 'ID', 'NAME', 'ID1', 'ID2', 'EXIST', 'STAT', 'PERM', 'FAILRT', # 'TISOL', 'TRECONF', 'TREPAIR', 'EQ', 'YEAR', 'CONTROL'] # Fuse # __headers__['Branches']['FUS'] = ['CLASS', 'ID', 'NAME', 'ID1', 'ID2', 'EXIST', 'STAT', 'PERM', 'FAILRT', # 'TISOL','TRECONF', 'TREPAIR', 'EQ', 'YEAR'] # Switch # __headers__['Branches']['INTR'] = ['CLASS', 'ID', 'NAME', 'ID1', 'ID2', 'EXIST', 'STAT', 'PERM', 'FAILRT', # 'TISOL', 'TRECONF', 'TREPAIR', 'EQ', 'YEAR', 'DRIVE', 'CONTROL'] # Disconnector # __headers__['Branches']['SECC'] = ['CLASS', 'ID', 'NAME', 'ID1', 'ID2', 'EXIST', 'STAT', 'PERM', 'FAILRT', # 'TISOL', 'TRECONF', 'TREPAIR', 'EQ', 'YEAR', 'DRIVE', 'CONTROL'] if tpe in ['DISJ', 'FUS', 'INTR', 'SECC']: df = data_structures['Branches'][tpe] for i in range(df.shape[0]): name = df['NAME'].values[i] id1 = df['ID1'].values[i] id2 = df['ID2'].values[i] state = bool(int(df['STAT'].values[i])) b1 = buses_id_dict[id1] b2 = buses_id_dict[id2] br = Branch(bus_from=b1, bus_to=b2, name=name, active=state, branch_type=BranchType.Switch) circuit.add_branch(br) # Lines, cables and bars # fill until it fits or truncate the data # __headers__['Branches']['LINE'] = ['CLASS', 'ID', 'NAME', 'ID1', 'ID2', 'EXIST', 'COLOR', 'GEOLEN', 'LEN', # 'STAT', # 'PERM', 'FAILRT', 'TISOL', 'TRECONF', 'TREPAIR', 'RERAT', 'EQEXIST', 'NPOSS', # 'CHOOSEQ', 'INSRTCOST', 'EQPOSS1', 'MATCOST1', 'EQPOSS2', 'MATCOST2', # 'EQPOSS3', # 'MATCOST3', 'NCOOG', 'GX1', 'GY1', 'GX2', 'GY2'] if tpe in ['LINE']: df = data_structures['Branches'][tpe] for i in range(df.shape[0]): name = df['NAME'].values[i] id1 = df['ID1'].values[i] id2 = df['ID2'].values[i] b1 = buses_id_dict[id1] b2 = buses_id_dict[id2] length = float(df['LEN'].values[i]) # get equipment reference in the catalogue eq_id = df['EQEXIST'].values[i] df_cat = data_structures['CatalogBranch'][tpe] cat_elm = df_cat[df_cat['EQ'] == eq_id] try: r = float(cat_elm['R'].values[0]) * length / 1000 except: r = 1e-20 try: x = float(cat_elm['X'].values[0]) * length / 1000 except: x = 1e-20 try: b = float(cat_elm['B'].values[0]) * length / 1000 except: b = 1e-20 Imax = float( cat_elm['RATTYP'].values[0]) / 1000.0 # pass from A to kA Vnom = float(cat_elm['VNOM'].values[0]) # kV Smax = Imax * Vnom * SQRT3 # MVA # correct for zero values which are problematic r = r if r > 0.0 else 1e-20 x = x if x > 0.0 else 1e-20 b = b if b > 0.0 else 1e-20 br = Branch(bus_from=b1, bus_to=b2, name=name, r=r, x=x, b=b, rate=Smax, length=length, branch_type=BranchType.Line) circuit.add_branch(br) # Intensity Transformer # __headers__['Branches']['TI'] = ['CLASS', 'ID', 'NAME', 'ID1', 'ID2', 'INDEP', 'I', 'SIMULT', 'EXIST', 'STAT', # 'PERM', 'FAILRT', 'TISOL', 'TRECONF', 'TREPAIR', 'EQ', 'TAP1', 'TAP2', 'YEAR'] if tpe in ['TI']: df = data_structures['Branches'][tpe] for i in range(df.shape[0]): name = df['NAME'].values[i] id1 = df['ID1'].values[i] id2 = df['ID2'].values[i] b1 = buses_id_dict[id1] b2 = buses_id_dict[id2] # get equipment reference in the catalogue eq_id = df['EQ'].values[i] df_cat = data_structures['CatalogBranch'][tpe] cat_elm = df_cat[df_cat['EQ'] == eq_id] br = Branch(bus_from=b1, bus_to=b2, name=name, branch_type=BranchType.Transformer) circuit.add_branch(br) # Self-transformer # __headers__['Branches']['XFORM1'] = ['CLASS', 'ID', 'NAME', 'ID1', 'ID2', 'ID3', 'ID1N', 'ID2N', 'ID3N', # 'EXIST', # 'STAT', 'FAILRT', 'TISOL', 'TRECONF', 'TREPAIR', 'RERAT', 'CON1', 'RE1', # 'XE1', # 'CON2', 'RE2', 'XE2', 'CON3', 'RE3', 'XE3', 'LOSS', 'TPERM', 'SETVSEL', # 'SETV', # 'EQ', 'TAP1', 'TAP2', 'TAP3', 'YEAR', 'NUM'] if tpe in ['XFORM1', 'XFORM2']: df = data_structures['Branches'][tpe] for i in range(df.shape[0]): name = df['NAME'].values[i] id1 = df['ID1'].values[i] id2 = df['ID2'].values[i] b1 = buses_id_dict[id1] b2 = buses_id_dict[id2] # get equipment reference in the catalogue # eq_id = df['EQ'].values[i] eq_id = df['XE3'].values[ i] # to correct the bad data formatting these file has... df_cat = data_structures['CatalogBranch'][tpe] cat_elm = df_cat[df_cat['EQ'] == eq_id] if cat_elm.shape[0] > 0: r1 = float(cat_elm['RD1'].values[0]) r2 = float(cat_elm['RD2'].values[0]) x1 = float(cat_elm['XD1'].values[0]) x2 = float(cat_elm['XD2'].values[0]) s1 = float(cat_elm['SNOMTYP1'].values[0] ) / 1000.0 # from kVA to MVA s2 = float(cat_elm['SNOMTYP2'].values[0] ) / 1000.0 # from kVA to MVA r = r1 + r2 x = x1 + x2 s = s1 + s2 r = r if r > 0.0 else 1e-20 x = x if x > 0.0 else 1e-20 s = s if s > 0.0 else 1e-20 else: r = 1e-20 x = 1e-20 s = 1e-20 logger.add_error('Not found.', tpe + ':' + eq_id) br = Branch(bus_from=b1, bus_to=b2, name=name, r=r, x=x, rate=s, branch_type=BranchType.Transformer) circuit.add_branch(br) # 3-winding transformer # __headers__['Branches']['XFORM3'] = ['CLASS', 'ID', 'NAME', 'ID1', 'ID2', 'ID3', 'ID1N', 'ID2N', 'ID3N', # 'EXIST', # 'STAT', 'FAILRT', 'TISOL', 'TRECONF', 'TREPAIR', 'RERAT', 'CON1', 'RE1', # 'XE1', # 'CON2', 'RE2', 'XE2', 'CON3', 'RE3', 'XE3', 'LOSS', 'TPERM', 'SETVSEL', # 'SETV', # 'EQ', 'TAP1', 'TAP2', 'TAP3', 'YEAR', 'NUM'] if tpe in ['XFORM3']: df = data_structures['Branches'][tpe] for i in range(df.shape[0]): name = df['NAME'].values[i] id1 = df['ID1'].values[i] id2 = df['ID2'].values[i] id3 = df['ID3'].values[i] b1 = buses_id_dict[id1] b2 = buses_id_dict[id2] b3 = buses_id_dict[id3] # get equipment reference in the catalogue eq_id = df['EQ'].values[i] df_cat = data_structures['CatalogBranch'][tpe] cat_elm = df_cat[df_cat['EQ'] == eq_id] r1 = float(cat_elm['RD1'].values[0]) r2 = float(cat_elm['RD2'].values[0]) r3 = float(cat_elm['RD3'].values[0]) x1 = float(cat_elm['XD1'].values[0]) x2 = float(cat_elm['XD2'].values[0]) x3 = float(cat_elm['XD3'].values[0]) s1 = float( cat_elm['SNOMTYP1'].values[0]) / 1000.0 # from kVA to MVA s2 = float( cat_elm['SNOMTYP2'].values[0]) / 1000.0 # from kVA to MVA s3 = float( cat_elm['SNOMTYP3'].values[0]) / 1000.0 # from kVA to MVA r12 = r1 + r2 x12 = x1 + x2 s12 = s1 + s2 r13 = r1 + r3 x13 = x1 + x3 s13 = s1 + s3 r23 = r2 + r3 x23 = x2 + x3 s23 = s2 + s3 r12 = r12 if r12 > 0.0 else 1e-20 x12 = x12 if x12 > 0.0 else 1e-20 s12 = s12 if s12 > 0.0 else 1e-20 r13 = r13 if r13 > 0.0 else 1e-20 x13 = x13 if x13 > 0.0 else 1e-20 s13 = s13 if s13 > 0.0 else 1e-20 r23 = r23 if r23 > 0.0 else 1e-20 x23 = x23 if x23 > 0.0 else 1e-20 s23 = s23 if s23 > 0.0 else 1e-20 br = Branch(bus_from=b1, bus_to=b2, name=name, r=r12, x=x12, rate=s12, branch_type=BranchType.Transformer) circuit.add_branch(br) br = Branch(bus_from=b1, bus_to=b3, name=name, r=r13, x=x13, rate=s13, branch_type=BranchType.Transformer) circuit.add_branch(br) br = Branch(bus_from=b2, bus_to=b3, name=name, r=r23, x=x23, rate=s23, branch_type=BranchType.Transformer) circuit.add_branch(br) # Neutral impedance # __headers__['Branches']['ZN'] = ['CLASS', 'ID', 'NAME', 'ID1', 'ID2', 'EXIST', 'STAT', 'PERM', 'FAILRT', # 'TISOL','TRECONF', 'TREPAIR', 'EQ', 'YEAR'] if tpe in ['ZN']: df = data_structures['Branches'][tpe] for i in range(df.shape[0]): name = df['NAME'].values[i] id1 = df['ID1'].values[i] id2 = df['ID2'].values[i] b1 = buses_id_dict[id1] b2 = buses_id_dict[id2] br = Branch(bus_from=b1, bus_to=b2, name=name, branch_type=BranchType.Branch) circuit.add_branch(br) # return the circuit and the logs return circuit, logger
def test_corr_line_losses(): test_name = "test_corr_line_losses" grid = MultiCircuit(name=test_name) grid.Sbase = Sbase grid.time_profile = None grid.logger = list() # Create buses Bus0 = Bus(name="Bus0", vnom=10, is_slack=True) bus_1 = Bus(name="bus_1", vnom=10) grid.add_bus(Bus0) grid.add_bus(bus_1) # Create load grid.add_load(bus_1, Load(name="Load0", P=1.0, Q=0.4)) # Create slack bus grid.add_generator(Bus0, Generator(name="Utility")) # Create cable cable = Branch(bus_from=Bus0, bus_to=bus_1, name="Cable0", r=0.784, x=0.174, temp_base=20, # °C temp_oper=90, # °C alpha=0.00323) # Copper grid.add_branch(cable) options = PowerFlowOptions(verbose=True, apply_temperature_correction=True) power_flow = PowerFlow(grid, options) power_flow.run() # Check solution approx_losses = round(power_flow.results.losses[0], 3) solution = complex(0.011, 0.002) # Expected solution from GridCal # Tested on ETAP 16.1.0 print("\n=================================================================") print(f"Test: {test_name}") print("=================================================================\n") print(f"Results: {approx_losses}") print(f"Solution: {solution}") print() print("Buses:") for i, b in enumerate(grid.buses): print(f" - bus[{i}]: {b}") print() print("Branches:") for b in grid.branches: print(f" - {b}:") print(f" R = {round(b.R, 4)} pu") print(f" X = {round(b.X, 4)} pu") print(f" X/R = {round(b.X/b.R, 2)}") print() print("Voltages:") for i in range(len(grid.buses)): print(f" - {grid.buses[i]}: voltage={round(power_flow.results.voltage[i], 3)} pu") print() print("Losses:") for i in range(len(grid.branches)): print(f" - {grid.branches[i]}: losses={round(power_flow.results.losses[i], 3)} MVA") print() print("Loadings (power):") for i in range(len(grid.branches)): print(f" - {grid.branches[i]}: loading={round(power_flow.results.Sbranch[i], 3)} MVA") print() print("Loadings (current):") for i in range(len(grid.branches)): print(f" - {grid.branches[i]}: loading={round(power_flow.results.Ibranch[i], 3)} pu") print() assert approx_losses == solution
def test_gridcal_regulator(): """ GridCal test for the new implementation of transformer voltage regulators. """ test_name = "test_gridcal_regulator" grid = MultiCircuit(name=test_name) grid.Sbase = 100.0 # MVA grid.time_profile = None grid.logger = Logger() # Create buses POI = Bus( name="POI", vnom=100, # kV is_slack=True) grid.add_bus(POI) B_C3 = Bus(name="B_C3", vnom=10) # kV grid.add_bus(B_C3) B_MV_M32 = Bus(name="B_MV_M32", vnom=10) # kV grid.add_bus(B_MV_M32) B_LV_M32 = Bus(name="B_LV_M32", vnom=0.6) # kV grid.add_bus(B_LV_M32) # Create voltage controlled generators (or slack, a.k.a. swing) UT = Generator(name="Utility") UT.bus = POI grid.add_generator(POI, UT) # Create static generators (with fixed power factor) M32 = StaticGenerator(name="M32", P=4.2, Q=0.0) # MVA (complex) M32.bus = B_LV_M32 grid.add_static_generator(B_LV_M32, M32) # Create transformer types s = 100 # MVA z = 8 # % xr = 40 SS = TransformerType( name="SS", hv_nominal_voltage=100, # kV lv_nominal_voltage=10, # kV nominal_power=s, # MVA copper_losses=complex_impedance(z, xr).real * s * 1000.0 / grid.Sbase, # kW iron_losses=125, # kW no_load_current=0.5, # % short_circuit_voltage=z) # % grid.add_transformer_type(SS) s = 5 # MVA z = 6 # % xr = 20 PM = TransformerType( name="PM", hv_nominal_voltage=10, # kV lv_nominal_voltage=0.6, # kV nominal_power=s, # MVA copper_losses=complex_impedance(z, xr).real * s * 1000.0 / grid.Sbase, # kW iron_losses=6.25, # kW no_load_current=0.5, # % short_circuit_voltage=z) # % grid.add_transformer_type(PM) # Create branches X_C3 = Branch(bus_from=POI, bus_to=B_C3, name="X_C3", branch_type=BranchType.Transformer, template=SS, bus_to_regulated=True, vset=1.05) X_C3.tap_changer = TapChanger(taps_up=16, taps_down=16, max_reg=1.1, min_reg=0.9) X_C3.tap_changer.set_tap(X_C3.tap_module) grid.add_branch(X_C3) C_M32 = Branch(bus_from=B_C3, bus_to=B_MV_M32, name="C_M32", r=7.84, x=1.74) grid.add_branch(C_M32) X_M32 = Branch(bus_from=B_MV_M32, bus_to=B_LV_M32, name="X_M32", branch_type=BranchType.Transformer, template=PM) grid.add_branch(X_M32) # Apply templates (device types) grid.apply_all_branch_types() print("Buses:") for i, b in enumerate(grid.buses): print(f" - bus[{i}]: {b}") print() options = PowerFlowOptions(SolverType.NR, verbose=True, initialize_with_existing_solution=True, multi_core=True, control_q=ReactivePowerControlMode.Direct, control_taps=TapsControlMode.Direct, tolerance=1e-6, max_iter=99) power_flow = PowerFlowDriver(grid, options) power_flow.run() approx_volt = [round(100 * abs(v), 1) for v in power_flow.results.voltage] solution = [100.0, 105.2, 130.0, 130.1] # Expected solution from GridCal print() print(f"Test: {test_name}") print(f"Results: {approx_volt}") print(f"Solution: {solution}") print() print("Generators:") for g in grid.get_generators(): print(f" - Generator {g}: q_min={g.Qmin}pu, q_max={g.Qmax}pu") print() print("Branches:") branches = grid.get_branches() for b in grid.transformers2w: print( f" - {b}: R={round(b.R, 4)}pu, X={round(b.X, 4)}pu, X/R={round(b.X/b.R, 1)}, vset={b.vset}" ) print() print("Transformer types:") for t in grid.transformer_types: print( f" - {t}: Copper losses={int(t.Pcu)}kW, Iron losses={int(t.Pfe)}kW, SC voltage={t.Vsc}%" ) print() print("Losses:") for i in range(len(branches)): print( f" - {branches[i]}: losses={round(power_flow.results.losses[i], 3)} MVA" ) print() tr_vset = [tr.vset for tr in grid.transformers2w] print(f"Voltage settings: {tr_vset}") equal = np.isclose(approx_volt, solution, atol=1e-3).all() assert equal
def test_pv_3(): """ Voltage controlled generator test, also useful for a basic tutorial. In this case the generator M32 regulates the voltage at a setpoint of 1.025 pu, and the slack bus (POI) regulates it at 1.0 pu. The transformers' magnetizing branch losses are considered, as well as the main power transformer's voltage regulator (X_C3) which regulates bus B_MV_M32 at 1.005 pu. In addition, the iterative PV control method is used instead of the usual (faster) method. """ test_name = "test_pv_3" grid = MultiCircuit(name=test_name) Sbase = 100 # MVA grid.Sbase = Sbase grid.time_profile = None grid.logger = Logger() # Create buses POI = Bus( name="POI", vnom=100, # kV is_slack=True) grid.add_bus(POI) B_MV_M32 = Bus(name="B_MV_M32", vnom=10) # kV grid.add_bus(B_MV_M32) B_LV_M32 = Bus(name="B_LV_M32", vnom=0.6) # kV grid.add_bus(B_LV_M32) # Create voltage controlled generators (or slack, a.k.a. swing) UT = Generator(name="Utility") UT.bus = POI grid.add_generator(POI, UT) M32 = Generator(name="M32", active_power=4.2, voltage_module=1.025, Qmin=-2.5, Qmax=2.5) M32.bus = B_LV_M32 grid.add_generator(B_LV_M32, M32) # Create transformer types s = 100 # MVA z = 8 # % xr = 40 SS = TransformerType( name="SS", hv_nominal_voltage=100, # kV lv_nominal_voltage=10, # kV nominal_power=s, copper_losses=complex_impedance(z, xr).real * s * 1000 / Sbase, iron_losses=125, # kW no_load_current=0.5, # % short_circuit_voltage=z) grid.add_transformer_type(SS) s = 5 # MVA z = 6 # % xr = 20 PM = TransformerType( name="PM", hv_nominal_voltage=10, # kV lv_nominal_voltage=0.6, # kV nominal_power=s, copper_losses=complex_impedance(z, xr).real * s * 1000 / Sbase, iron_losses=6.25, # kW no_load_current=0.5, # % short_circuit_voltage=z) grid.add_transformer_type(PM) # Create branches X_C3 = Branch(bus_from=POI, bus_to=B_MV_M32, name="X_C3", branch_type=BranchType.Transformer, template=SS, bus_to_regulated=True, vset=1.005) X_C3.tap_changer = TapChanger(taps_up=16, taps_down=16, max_reg=1.1, min_reg=0.9) X_C3.tap_changer.set_tap(X_C3.tap_module) grid.add_branch(X_C3) X_M32 = Branch(bus_from=B_MV_M32, bus_to=B_LV_M32, name="X_M32", branch_type=BranchType.Transformer, template=PM) grid.add_branch(X_M32) # Apply templates (device types) grid.apply_all_branch_types() print("Buses:") for i, b in enumerate(grid.buses): print(f" - bus[{i}]: {b}") print() options = PowerFlowOptions(SolverType.LM, verbose=True, initialize_with_existing_solution=True, multi_core=True, control_q=ReactivePowerControlMode.Iterative, control_taps=TapsControlMode.Direct, tolerance=1e-6, max_iter=99) power_flow = PowerFlowDriver(grid, options) power_flow.run() approx_volt = [round(100 * abs(v), 1) for v in power_flow.results.voltage] solution = [100.0, 100.7, 102.5] # Expected solution from GridCal print() print(f"Test: {test_name}") print(f"Results: {approx_volt}") print(f"Solution: {solution}") print() print("Generators:") for g in grid.get_generators(): print(f" - Generator {g}: q_min={g.Qmin} MVAR, q_max={g.Qmax} MVAR") print() print("Branches:") for b in grid.branches: print(f" - {b}:") print(f" R = {round(b.R, 4)} pu") print(f" X = {round(b.X, 4)} pu") print(f" X/R = {round(b.X / b.R, 1)}") print(f" G = {round(b.G, 4)} pu") print(f" B = {round(b.B, 4)} pu") print() print("Transformer types:") for t in grid.transformer_types: print( f" - {t}: Copper losses={int(t.Pcu)}kW, Iron losses={int(t.Pfe)}kW, SC voltage={t.Vsc}%" ) print() print("Losses:") for i in range(len(grid.branches)): print( f" - {grid.branches[i]}: losses={1000 * round(power_flow.results.losses[i], 3)} kVA" ) print() equal = True for i in range(len(approx_volt)): if approx_volt[i] != solution[i]: equal = False assert equal
def main(): #################################################################################################################### # Define the circuit # # A circuit contains all the grid information regardless of the islands formed or the amount of devices #################################################################################################################### grid = MultiCircuit(name='lynn 5 bus') #################################################################################################################### # Define the buses #################################################################################################################### # I will define this bus with all the properties so you see bus1 = Bus(name='Bus1', vnom=10, # Nominal voltage in kV vmin=0.9, # Bus minimum voltage in per unit vmax=1.1, # Bus maximum voltage in per unit xpos=0, # Bus x position in pixels ypos=0, # Bus y position in pixels height=0, # Bus height in pixels width=0, # Bus width in pixels active=True, # Is the bus active? is_slack=False, # Is this bus a slack bus? area='Defualt', # Area (for grouping purposes only) zone='Default', # Zone (for grouping purposes only) substation='Default' # Substation (for grouping purposes only) ) # the rest of the buses are defined with the default parameters bus2 = Bus(name='Bus2') bus3 = Bus(name='Bus3') bus4 = Bus(name='Bus4') bus5 = Bus(name='Bus5') # add the bus objects to the circuit grid.add_bus(bus1) grid.add_bus(bus2) grid.add_bus(bus3) grid.add_bus(bus4) grid.add_bus(bus5) #################################################################################################################### # Add the loads #################################################################################################################### # In GridCal, the loads, generators ect are stored within each bus object: # we'll define the first load completely l2 = Load(name='Load', G=0, # Impedance of the ZIP model in MVA at the nominal voltage B=0, Ir=0, Ii=0, # Current of the ZIP model in MVA at the nominal voltage P=40, Q=20, # Power of the ZIP model in MVA P_prof=None, # Impedance profile Q_prof=None, # Current profile Ir_prof=None, # Power profile Ii_prof=None, G_prof=None, B_prof=None, active=True, # Is active? mttf=0.0, # Mean time to failure mttr=0.0 # Mean time to recovery ) grid.add_load(bus2, l2) # Define the others with the default parameters grid.add_load(bus3, Load(P=25, Q=15)) grid.add_load(bus4, Load(P=40, Q=20)) grid.add_load(bus5, Load(P=50, Q=20)) #################################################################################################################### # Add the generators #################################################################################################################### g1 = Generator(name='gen', active_power=0.0, # Active power in MW, since this generator is used to set the slack , is 0 voltage_module=1.0, # Voltage set point to control Qmin=-9999, # minimum reactive power in MVAr Qmax=9999, # Maximum reactive power in MVAr Snom=9999, # Nominal power in MVA power_prof=None, # power profile vset_prof=None, # voltage set point profile active=True # Is active? ) grid.add_generator(bus1, g1) #################################################################################################################### # Add the lines #################################################################################################################### br1 = Branch(bus_from=bus1, bus_to=bus2, name='Line 1-2', r=0.05, # resistance of the pi model in per unit x=0.11, # reactance of the pi model in per unit g=1e-20, # conductance of the pi model in per unit b=0.02, # susceptance of the pi model in per unit rate=50, # Rate in MVA tap=1.0, # Tap value (value close to 1) shift_angle=0, # Tap angle in radians active=True, # is the branch active? mttf=0, # Mean time to failure mttr=0, # Mean time to recovery branch_type=BranchType.Line, # Branch type tag length=1, # Length in km (to be used with templates) template=BranchTemplate() # Branch template (The default one is void) ) grid.add_branch(br1) grid.add_branch(Branch(bus1, bus3, name='Line 1-3', r=0.05, x=0.11, b=0.02, rate=50)) grid.add_branch(Branch(bus1, bus5, name='Line 1-5', r=0.03, x=0.08, b=0.02, rate=80)) grid.add_branch(Branch(bus2, bus3, name='Line 2-3', r=0.04, x=0.09, b=0.02, rate=3)) grid.add_branch(Branch(bus2, bus5, name='Line 2-5', r=0.04, x=0.09, b=0.02, rate=10)) grid.add_branch(Branch(bus3, bus4, name='Line 3-4', r=0.06, x=0.13, b=0.03, rate=30)) grid.add_branch(Branch(bus4, bus5, name='Line 4-5', r=0.04, x=0.09, b=0.02, rate=30)) #################################################################################################################### # Run a power flow simulation #################################################################################################################### # We need to specify power flow options pf_options = PowerFlowOptions(solver_type=SolverType.NR, # Base method to use verbose=False, # Verbose option where available tolerance=1e-6, # power error in p.u. max_iter=25, # maximum iteration number control_q=True # if to control the reactive power ) # Declare and execute the power flow simulation pf = PowerFlowDriver(grid, pf_options) pf.run() # now, let's compose a nice DataFrame with the voltage results headers = ['Vm (p.u.)', 'Va (Deg)', 'Vre', 'Vim'] Vm = np.abs(pf.results.voltage) Va = np.angle(pf.results.voltage, deg=True) Vre = pf.results.voltage.real Vim = pf.results.voltage.imag data = np.c_[Vm, Va, Vre, Vim] v_df = pd.DataFrame(data=data, columns=headers, index=grid.bus_names) print('\n', v_df) # Let's do the same for the branch results headers = ['Loading (%)', 'Current(p.u.)', 'Power (MVA)'] loading = np.abs(pf.results.loading) * 100 current = np.abs(pf.results.Ibranch) power = np.abs(pf.results.Sbranch) data = np.c_[loading, current, power] br_df = pd.DataFrame(data=data, columns=headers, index=grid.branch_names) print('\n', br_df) # Finally the execution metrics print('\nError:', pf.results.error) print('Elapsed time (s):', pf.results.elapsed, '\n') print(v_df) print() print(br_df)
def get_grid_lynn_5_bus_wiki(): grid = MultiCircuit(name='lynn 5 bus') bus_1 = Bus( name='bus_1', vnom=10, # Nominal voltage in kV vmin=0.9, # Bus minimum voltage in per unit vmax=1.1, # Bus maximum voltage in per unit xpos=0, # Bus x position in pixels ypos=0, # Bus y position in pixels height=0, # Bus height in pixels width=0, # Bus width in pixels active=True, # Is the bus active? is_slack=False, # Is this bus a slack bus? area='Default', # Area (for grouping purposes only) zone='Default', # Zone (for grouping purposes only) substation='Default' # Substation (for grouping purposes only) ) bus_2 = Bus(name='bus_2') bus_3 = Bus(name='bus_3') bus_4 = Bus(name='bus_4') bus_5 = Bus(name='bus_5') grid.add_bus(bus_1) grid.add_bus(bus_2) grid.add_bus(bus_3) grid.add_bus(bus_4) grid.add_bus(bus_5) load_2 = Load( name='Load', # impedance=complex(0, 0), # Impedance of the ZIP model in MVA at the nominal voltage # current=complex(0, 0), # Current of the ZIP model in MVA at the nominal voltage # power=complex(40, 20), # Power of the ZIP model in MVA # impedance_prof=None, # Impedance profile # current_prof=None, # Current profile # power_prof=None, # Power profile active=True, # Is active? mttf=0.0, # Mean time to failure mttr=0.0 # Mean time to recovery ) grid.add_load(bus_2, load_2) grid.add_load( bus_3, Load( # power=complex(25, 15) )) grid.add_load( bus_4, Load( # power=complex(40, 20) )) grid.add_load( bus_5, Load( # power=complex(50, 20) )) generator_1 = Generator( name='gen', active_power=0.0, # Active power in MW, since this generator is used to set the slack , is 0 voltage_module=1.0, # Voltage set point to control Qmin=-9999, # minimum reactive power in MVAr Qmax=9999, # Maximum reactive power in MVAr Snom=9999, # Nominal power in MVA power_prof=None, # power profile vset_prof=None, # voltage set point profile active=True # Is active? ) grid.add_generator(bus_1, generator_1) branch_1 = Branch( bus_from=bus_1, bus_to=bus_2, name='Line 1-2', r=0.05, # resistance of the pi model in per unit x=0.11, # reactance of the pi model in per unit g=1e-20, # conductance of the pi model in per unit b=0.02, # susceptance of the pi model in per unit rate=50, # Rate in MVA tap=1.0, # Tap value (value close to 1) shift_angle=0, # Tap angle in radians active=True, # is the branch active? mttf=0, # Mean time to failure mttr=0, # Mean time to recovery branch_type=BranchType.Line, # Branch type tag length=1, # Length in km (to be used with templates) # type_obj=BranchTemplate() # Branch template (The default one is void) ) grid.add_branch(branch_1) grid.add_branch( Branch(bus_1, bus_3, name='Line 1-3', r=0.05, x=0.11, b=0.02, rate=50)) grid.add_branch( Branch(bus_1, bus_5, name='Line 1-5', r=0.03, x=0.08, b=0.02, rate=80)) grid.add_branch( Branch(bus_2, bus_3, name='Line 2-3', r=0.04, x=0.09, b=0.02, rate=3)) grid.add_branch( Branch(bus_2, bus_5, name='Line 2-5', r=0.04, x=0.09, b=0.02, rate=10)) grid.add_branch( Branch(bus_3, bus_4, name='Line 3-4', r=0.06, x=0.13, b=0.03, rate=30)) grid.add_branch( Branch(bus_4, bus_5, name='Line 4-5', r=0.04, x=0.09, b=0.02, rate=30)) grid.compile() return grid
def data_frames_to_circuit(data: Dict): """ Interpret data dictionary :param data: dictionary of data frames :return: MultiCircuit instance """ # create circuit circuit = MultiCircuit() if 'name' in data.keys(): circuit.name = data['name'] # set the base magnitudes if 'baseMVA' in data.keys(): circuit.Sbase = data['baseMVA'] # Set comments if 'Comments' in data.keys(): circuit.comments = data['Comments'] if 'ModelVersion' in data.keys(): circuit.model_version = int(data['ModelVersion']) if 'UserName' in data.keys(): circuit.user_name = data['UserName'] # dictionary of objects to iterate object_types = get_objects_dictionary() circuit.logger = Logger() # time profile ----------------------------------------------------------------------------------------------------- if 'time' in data.keys(): time_df = data['time'] circuit.time_profile = pd.to_datetime(time_df.values[:, 0], dayfirst=True) else: circuit.time_profile = None # dictionary of dictionaries by element type # elements_dict[DataType][element_name] = actual object elements_dict = dict() # ------------------------------------------------------------------------------------------------------------------ # for each element type... for key, template_elm in object_types.items(): if key in data.keys(): # get the DataFrame df = data[key] # create the objects ... devices = list() devices_dict = dict() if 'idtag' in df.columns.values: for i in range(df.shape[0]): elm = type(template_elm)() idtag = df['idtag'].values[i] # create the buses dictionary, this works because the bus is the first key in "object_types" devices_dict[idtag] = elm # add the device to the elements devices.append(elm) else: for i in range(df.shape[0]): elm = type(template_elm)() idtag = df['name'].values[i] # create the buses dictionary, this works because the bus is the first key in "object_types" devices_dict[idtag] = elm # add the device to the elements devices.append(elm) elements_dict[template_elm.device_type] = devices_dict # fill in the objects if df.shape[0] > 0: # for each property ... for prop, gc_prop in template_elm.editable_headers.items(): # if the object property exists in the data file, set all the object's property if prop in df.columns.values: # get the type converter dtype = gc_prop.tpe # for each object, set the property for i in range(df.shape[0]): # convert and assign the data if dtype is None: val = df[prop].values[i] setattr(devices[i], prop, val) elif dtype == DeviceType.BusDevice: # check if the bus is in the dictionary... if df[prop].values[i] in elements_dict[DeviceType.BusDevice].keys(): parent_bus = elements_dict[DeviceType.BusDevice][df[prop].values[i]] setattr(devices[i], prop, parent_bus) # add the device to the bus if template_elm.device_type in [DeviceType.LoadDevice, DeviceType.GeneratorDevice, DeviceType.BatteryDevice, DeviceType.StaticGeneratorDevice, DeviceType.ShuntDevice, DeviceType.ExternalGridDevice]: parent_bus.add_device(devices[i]) else: circuit.logger.append('Bus not found: ' + str(df[prop].values[i])) elif dtype in [DeviceType.TransformerTypeDevice, # template types mostly DeviceType.SequenceLineDevice, DeviceType.TowerDevice]: if df[prop].values[i] in elements_dict[dtype].keys(): # get the actual template and set it val = elements_dict[dtype][df[prop].values[i]] setattr(devices[i], prop, val) else: circuit.logger.append(dtype.value + ' type not found: ' + str(df[prop].values[i])) else: # regular types (int, str, float, etc...) val = dtype(df[prop].values[i]) setattr(devices[i], prop, val) # search the profiles in the data and assign them if prop in template_elm.properties_with_profile.keys(): # get the profile property prop_prof = template_elm.properties_with_profile[prop] # build the profile property file-name to get it from the data profile_name = key + '_' + prop_prof if profile_name in data.keys(): # get the profile DataFrame dfp = data[profile_name] # for each object, set the profile for i in range(dfp.shape[1]): profile = dfp.values[:, i] setattr(devices[i], prop_prof, profile.astype(dtype)) else: circuit.logger.append(prop + ' profile was not found in the data') else: circuit.logger.append(prop + ' of object type ' + str(template_elm.device_type) + ' not found in the input data') else: # no objects of this type pass # ensure profiles existence if circuit.time_profile is not None: for i in range(df.shape[0]): devices[i].ensure_profiles_exist(circuit.time_profile) # add the objects to the circuit (buses, branches ot template types) if template_elm.device_type == DeviceType.BusDevice: circuit.buses = devices elif template_elm.device_type == DeviceType.BranchDevice: for d in devices: circuit.add_branch(d) # each branch needs to be converted accordingly elif template_elm.device_type == DeviceType.LineDevice: circuit.lines = devices elif template_elm.device_type == DeviceType.DCLineDevice: circuit.dc_lines = devices elif template_elm.device_type == DeviceType.Transformer2WDevice: circuit.transformers2w = devices elif template_elm.device_type == DeviceType.HVDCLineDevice: circuit.hvdc_lines = devices elif template_elm.device_type == DeviceType.VscDevice: circuit.vsc_converters = devices elif template_elm.device_type == DeviceType.TowerDevice: circuit.overhead_line_types = devices elif template_elm.device_type == DeviceType.TransformerTypeDevice: circuit.transformer_types = devices elif template_elm.device_type == DeviceType.UnderGroundLineDevice: circuit.underground_cable_types = devices elif template_elm.device_type == DeviceType.SequenceLineDevice: circuit.sequence_line_types = devices elif template_elm.device_type == DeviceType.WireDevice: circuit.wire_types = devices else: circuit.logger.append('The data does not contain information about the objects of type ' + str(key)) # fill in wires into towers ---------------------------------------------------------------------------------------- if 'tower_wires' in data.keys(): df = data['tower_wires'] for i in range(df.shape[0]): tower_name = df['tower_name'].values[i] wire_name = df['wire_name'].values[i] if (tower_name in elements_dict[DeviceType.TowerDevice].keys()) and \ (wire_name in elements_dict[DeviceType.WireDevice].keys()): tower = elements_dict[DeviceType.TowerDevice][tower_name] wire = elements_dict[DeviceType.WireDevice][wire_name] xpos = df['xpos'].values[i] ypos = df['ypos'].values[i] phase = df['phase'].values[i] w = WireInTower(wire=wire, xpos=xpos, ypos=ypos, phase=phase) tower.wires_in_tower.append(w) # Other actions ---------------------------------------------------------------------------------------------------- circuit.logger += circuit.apply_all_branch_types() return circuit
def parse_json_data(data) -> MultiCircuit: """ Parse JSON structure into GridCal MultiCircuit :param data: JSON structure (list of dictionaries) :return: GridCal MultiCircuit """ circuit = MultiCircuit() bus_id = dict() for element in data: if element["phases"] == 'ps': if element["type"] == "circuit": circuit = MultiCircuit() circuit.name = element["name"] circuit.Sbase = element["Sbase"] circuit.comments = element['comments'] elif element["type"] == "bus": # create the bus and add some properties elm = Bus(name=element["name"], vnom=element["Vnom"], vmin=0.9, vmax=1.1, xpos=element['x'], ypos=element['y'], height=element['h'], width=element['w'], active=True) if element["is_slack"]: elm.type = BusMode.Slack if element["vmax"] > 0: elm.Vmax = element["vmax"] if element["vmin"] > 0: elm.Vmin = element["vmin"] elm.Zf = complex(element['rf'], element['xf']) circuit.add_bus(elm) # add the bus to the dictionary bus_id[element["id"]] = elm elif element["type"] == "load": # get the matching bus object pointer bus = bus_id[element["bus"]] # create a load in the bus elm = Load(name=element['name'], G=element["G"], B=element["B"], Ir=element["Ir"], Ii=element["Ii"], P=element["P"], Q=element["Q"], active=element['active']) bus.loads.append(elm) elif element["type"] == "controlled_gen": # get the matching bus object pointer bus = bus_id[element["bus"]] # create a load in the bus elm = Generator(name=element['name'], active_power=element["P"], voltage_module=element["vset"], Qmin=element['qmin'], Qmax=element['qmax'], Snom=element['Snom'], power_prof=None, vset_prof=None, active=element['active'], p_min=0.0, p_max=element['Snom'], op_cost=1.0) bus.controlled_generators.append(elm) elif element["type"] == "static_gen": # get the matching bus object pointer bus = bus_id[element["bus"]] # create a load in the bus elm = StaticGenerator(name=element['name'], P=element['P'], Q=element['Q'], active=element['active']) bus.static_generators.append(elm) elif element["type"] == "battery": # get the matching bus object pointer bus = bus_id[element["bus"]] # create a load in the bus elm = Battery(name=element['name'], active_power=element["P"], voltage_module=element["vset"], Qmin=element['qmin'], Qmax=element['qmax'], Snom=element['Snom'], Enom=element['Enom'], power_prof=None, vset_prof=None, active=element['active']) bus.batteries.append(elm) elif element["type"] == "shunt": # get the matching bus object pointer bus = bus_id[element["bus"]] # create a load in the bus elm = Shunt(name=element['name'], G=element["g"], B=element["b"], active=element['active']) bus.shunts.append(elm) elif element["type"] == "branch": # get the matching bus object pointer bus1 = bus_id[element["from"]] bus2 = bus_id[element["to"]] # create a load in the bus elm = Branch(bus_from=bus1, bus_to=bus2, name=element["name"], r=element["r"], x=element["x"], g=element["g"], b=element["b"], rate=element["rate"], tap=element["tap_module"], shift_angle=element["tap_angle"], active=element["active"], mttf=0, mttr=0, branch_type=element["branch_type"]) circuit.add_branch(elm) else: warn('ID: ' + element["id"] + ' error: GridCal only takes positive sequence elements.') return circuit
def load_iPA(file_name): circuit = MultiCircuit() with open(file_name) as json_file: data = json.load(json_file) # elements dictionaries xfrm_dict = {entry['IdEnRed']: entry for entry in data['Transformadores']} # nodes_dict = {entry['id']: entry for entry in data['Nudos']} nodes_dict = dict() buses_dict = dict() for entry in data['Nudos']: nodes_dict[entry['id']] = entry bus = Bus(name=str(entry['id'])) buses_dict[entry['id']] = bus if entry['id'] > 0: # omit the node 0 because it is the "earth node"... circuit.add_bus(bus) gen_dict = {entry['IdEnRed']: entry for entry in data['Generadores']} load_dict = {entry['IdEnRed']: entry for entry in data['Consumos']} sw_dict = {entry['IdEnRed']: entry for entry in data['Interruptores']} # main grid vector_red = data['Red'] ''' {'id': 0, 'Tipo': 1, 'E': 0, 'EFase': 0, 'Tomas': 0, 'R1': 1e-05, 'X1': 1e-05, 'R0': 1e-05, 'X0': 1e-05, 'RN': 1e-05, 'XN': 1e-05, 'P': 0, 'Q': 0, 'Nudo1': 2410, 'Nudo2': 2403, 'Carga_Max': -1, 'ClassID': 1090, 'ClassMEMBER': 98076366, 'Conf': 'abc', 'LineaMT': '2030:98075347', 'Unom': 15.0} ''' for entry in vector_red: # pick the general attributes identifier = entry['id'] tpe = entry['Tipo'] n1_id = entry['Nudo1'] n2_id = entry['Nudo2'] # get the Bus objects associated to the bus indices if n1_id in buses_dict.keys(): bus1 = buses_dict[n1_id] if n2_id in buses_dict.keys(): bus2 = buses_dict[n2_id] if tpe == 0: # Fuente de Tensión(elemento Ptheta) # pick the bus that is not the earth bus... if n1_id == 0: bus = bus2 else: bus = bus1 bus.is_slack = True elm = Generator(name='Slack') circuit.add_generator(bus, elm) elif tpe == 1: # Elemento impedancia(lineas) V = entry['Unom'] Zbase = V * V / circuit.Sbase if identifier in load_dict.keys(): # load!!! print('Load found in lines: WTF?') else: # line!!! r = entry['R1'] / Zbase x = entry['X1'] / Zbase if r > 1e-5: branch_type = BranchType.Line else: # mark as "generic branch" the branches with very low resistance branch_type = BranchType.Branch elm = Branch(bus_from=bus1, bus_to=bus2, name=str(identifier), r=r, x=x, branch_type=branch_type) circuit.add_branch(elm) elif tpe == 2: # Elemento PQ # pick the bus that is not the earth bus... if n1_id == 0: bus = bus2 else: bus = bus1 p = entry['P'] # power in MW q = entry['Q'] elm = Load(name=str(identifier), P=p * 1e-3, Q=q * 1e-3) circuit.add_load(bus, elm) elif tpe == 3: # Elemento PV pass elif tpe == 4: # Reg de tensión V = entry['Unom'] Zbase = V * V / circuit.Sbase r = entry['R1'] / Zbase x = entry['X1'] / Zbase elm = Branch(bus_from=bus1, bus_to=bus2, name=str(identifier), r=r, x=x, branch_type=BranchType.Transformer) circuit.add_branch(elm) elif tpe == 5: # Transformador V = entry['Unom'] Zbase = V * V / circuit.Sbase r = entry['R1'] / Zbase x = entry['X1'] / Zbase elm = Branch(bus_from=bus1, bus_to=bus2, name=str(identifier), r=r, x=x, branch_type=BranchType.Transformer) circuit.add_branch(elm) # return the circuit return circuit
def test_xfo_static_tap_3(): """ Basic test with the main transformer's HV tap (X_C3) set at -2.5% (0.975 pu), which raises the LV by the same amount (+2.5%). """ test_name = "test_xfo_static_tap_3" grid = MultiCircuit(name=test_name) grid.Sbase = Sbase grid.time_profile = None grid.logger = Logger() # Create buses POI = Bus( name="POI", vnom=100, # kV is_slack=True) grid.add_bus(POI) B_C3 = Bus(name="B_C3", vnom=10) # kV grid.add_bus(B_C3) B_MV_M32 = Bus(name="B_MV_M32", vnom=10) # kV grid.add_bus(B_MV_M32) B_LV_M32 = Bus(name="B_LV_M32", vnom=0.6) # kV grid.add_bus(B_LV_M32) # Create voltage controlled generators (or slack, a.k.a. swing) UT = Generator(name="Utility") UT.bus = POI grid.add_generator(POI, UT) # Create static generators (with fixed power factor) M32 = StaticGenerator(name="M32", P=4.2, Q=0.0) # MVA (complex) M32.bus = B_LV_M32 grid.add_static_generator(B_LV_M32, M32) # Create transformer types s = 5 # MVA z = 8 # % xr = 40 SS = TransformerType( name="SS", hv_nominal_voltage=100, # kV lv_nominal_voltage=10, # kV nominal_power=s, copper_losses=complex_impedance(z, xr).real * s * 1000 / Sbase, iron_losses=6.25, # kW no_load_current=0.5, # % short_circuit_voltage=z) grid.add_transformer_type(SS) s = 5 # MVA z = 6 # % xr = 20 PM = TransformerType( name="PM", hv_nominal_voltage=10, # kV lv_nominal_voltage=0.6, # kV nominal_power=s, copper_losses=complex_impedance(z, xr).real * s * 1000 / Sbase, iron_losses=6.25, # kW no_load_current=0.5, # % short_circuit_voltage=z) grid.add_transformer_type(PM) # Create branches X_C3 = Branch(bus_from=POI, bus_to=B_C3, name="X_C3", branch_type=BranchType.Transformer, template=SS, tap=0.975) # update to a more precise tap changer X_C3.apply_tap_changer( TapChanger(taps_up=20, taps_down=20, max_reg=1.1, min_reg=0.9)) grid.add_branch(X_C3) C_M32 = Branch(bus_from=B_C3, bus_to=B_MV_M32, name="C_M32", r=0.784, x=0.174) grid.add_branch(C_M32) X_M32 = Branch(bus_from=B_MV_M32, bus_to=B_LV_M32, name="X_M32", branch_type=BranchType.Transformer, template=PM) grid.add_branch(X_M32) # Apply templates (device types) grid.apply_all_branch_types() print("Buses:") for i, b in enumerate(grid.buses): print(f" - bus[{i}]: {b}") print() options = PowerFlowOptions(SolverType.NR, verbose=True, initialize_with_existing_solution=True, multi_core=True, control_q=ReactivePowerControlMode.Direct, tolerance=1e-6, max_iter=15) power_flow = PowerFlowDriver(grid, options) power_flow.run() print() print(f"Test: {test_name}") print() print("Generators:") for g in grid.get_generators(): print(f" - Generator {g}: q_min={g.Qmin} MVAR, q_max={g.Qmax} MVAR") print() print("Branches:") for b in grid.branches: print(f" - {b}:") print(f" R = {round(b.R, 4)} pu") print(f" X = {round(b.X, 4)} pu") print(f" X/R = {round(b.X/b.R, 1)}") print(f" G = {round(b.G, 4)} pu") print(f" B = {round(b.B, 4)} pu") print() print("Transformer types:") for t in grid.transformer_types: print(f" - {t}: Copper losses={int(t.Pcu)}kW, " f"Iron losses={int(t.Pfe)}kW, SC voltage={t.Vsc}%") print() print("Losses:") for i in range(len(grid.branches)): print( f" - {grid.branches[i]}: losses={1000*round(power_flow.results.losses[i], 3)} kVA" ) print() equal = False for i, branch in enumerate(grid.branches): if branch.name == "X_C3": equal = power_flow.results.tap_module[i] == branch.tap_module if not equal: grid.export_pf(f"{test_name}_results.xlsx", power_flow.results) grid.save_excel(f"{test_name}_grid.xlsx") assert equal
def test_basic(): """ Basic GridCal test, also useful for a basic tutorial. In this case the magnetizing branch of the transformers is neglected by inputting 1e-20 excitation current and iron core losses. The results are identical to ETAP's, which always uses this assumption in balanced load flow calculations. """ test_name = "test_basic" grid = MultiCircuit(name=test_name) S_base = 100 # MVA grid.Sbase = S_base grid.time_profile = None grid.logger = list() # Create buses POI = Bus( name="POI", vnom=100, #kV is_slack=True) grid.add_bus(POI) B_C3 = Bus(name="B_C3", vnom=10) #kV grid.add_bus(B_C3) B_MV_M32 = Bus(name="B_MV_M32", vnom=10) #kV grid.add_bus(B_MV_M32) B_LV_M32 = Bus(name="B_LV_M32", vnom=0.6) #kV grid.add_bus(B_LV_M32) # Create voltage controlled generators (or slack, a.k.a. swing) UT = Generator(name="Utility") UT.bus = POI grid.add_generator(POI, UT) # Create static generators (with fixed power factor) M32 = StaticGenerator( name="M32", P=4.2, # MW Q=0.0j) # MVAR M32.bus = B_LV_M32 grid.add_static_generator(B_LV_M32, M32) # Create transformer types s = 5 # MVA z = 8 # % xr = 40 SS = TransformerType( name="SS", hv_nominal_voltage=100, # kV lv_nominal_voltage=10, # kV nominal_power=s, copper_losses=complex_impedance(z, xr).real * s * 1000 / S_base, iron_losses=1e-20, no_load_current=1e-20, short_circuit_voltage=z) grid.add_transformer_type(SS) s = 5 # MVA z = 6 # % xr = 20 PM = TransformerType( name="PM", hv_nominal_voltage=10, # kV lv_nominal_voltage=0.6, # kV nominal_power=s, copper_losses=complex_impedance(z, xr).real * s * 1000 / S_base, iron_losses=1e-20, no_load_current=1e-20, short_circuit_voltage=z) grid.add_transformer_type(PM) # Create branches X_C3 = Branch(bus_from=POI, bus_to=B_C3, name="X_C3", branch_type=BranchType.Transformer, template=SS) grid.add_branch(X_C3) C_M32 = Branch(bus_from=B_C3, bus_to=B_MV_M32, name="C_M32", r=0.784, x=0.174) grid.add_branch(C_M32) X_M32 = Branch(bus_from=B_MV_M32, bus_to=B_LV_M32, name="X_M32", branch_type=BranchType.Transformer, template=PM) grid.add_branch(X_M32) # Apply templates (device types) grid.apply_all_branch_types() print("Buses:") for i, b in enumerate(grid.buses): print(f" - bus[{i}]: {b}") print() options = PowerFlowOptions(SolverType.LM, verbose=True, initialize_with_existing_solution=True, multi_core=True, control_q=ReactivePowerControlMode.Direct, tolerance=1e-6, max_iter=99) power_flow = PowerFlow(grid, options) power_flow.run() approx_volt = [round(100 * abs(v), 1) for v in power_flow.results.voltage] solution = [ 100.0, 99.6, 102.7, 102.9 ] # Expected solution from GridCal and ETAP 16.1.0, for reference print() print(f"Test: {test_name}") print(f"Results: {approx_volt}") print(f"Solution: {solution}") print() print("Generators:") for g in grid.get_generators(): print(f" - Generator {g}: q_min={g.Qmin}pu, q_max={g.Qmax}pu") print() print("Branches:") for b in grid.branches: print(f" - {b}:") print(f" R = {round(b.R, 4)} pu") print(f" X = {round(b.X, 4)} pu") print(f" X/R = {round(b.X/b.R, 1)}") print(f" G = {round(b.G, 4)} pu") print(f" B = {round(b.B, 4)} pu") print() print("Transformer types:") for t in grid.transformer_types: print( f" - {t}: Copper losses={int(t.Pcu)}kW, Iron losses={int(t.Pfe)}kW, SC voltage={t.Vsc}%" ) print() print("Losses:") for i in range(len(grid.branches)): print( f" - {grid.branches[i]}: losses={1000*round(power_flow.results.losses[i], 3)} kVA" ) print() equal = True for i in range(len(approx_volt)): if approx_volt[i] != solution[i]: equal = False assert equal
def test_xfo_static_tap_1(): """ Basic test with the main transformer's HV tap (X_C3) set at +5% (1.05 pu), which lowers the LV by the same amount (-5%). """ test_name = "test_xfo_static_tap_1" grid = MultiCircuit(name=test_name) grid.Sbase = Sbase grid.time_profile = None grid.logger = Logger() # Create buses POI = Bus( name="POI", vnom=100, #kV is_slack=True) grid.add_bus(POI) B_C3 = Bus(name="B_C3", vnom=10) #kV grid.add_bus(B_C3) B_MV_M32 = Bus(name="B_MV_M32", vnom=10) #kV grid.add_bus(B_MV_M32) B_LV_M32 = Bus(name="B_LV_M32", vnom=0.6) #kV grid.add_bus(B_LV_M32) # Create voltage controlled generators (or slack, a.k.a. swing) UT = Generator(name="Utility") UT.bus = POI grid.add_generator(POI, UT) # Create static generators (with fixed power factor) M32 = StaticGenerator(name="M32", P=4.2, Q=0.0) # MVA (complex) M32.bus = B_LV_M32 grid.add_static_generator(B_LV_M32, M32) # Create transformer types s = 5 # MVA z = 8 # % xr = 40 SS = TransformerType( name="SS", hv_nominal_voltage=100, # kV lv_nominal_voltage=10, # kV nominal_power=s, copper_losses=complex_impedance(z, xr).real * s * 1000 / Sbase, iron_losses=6.25, # kW no_load_current=0.5, # % short_circuit_voltage=z) grid.add_transformer_type(SS) s = 5 # MVA z = 6 # % xr = 20 PM = TransformerType( name="PM", hv_nominal_voltage=10, # kV lv_nominal_voltage=0.6, # kV nominal_power=s, copper_losses=complex_impedance(z, xr).real * s * 1000 / Sbase, iron_losses=6.25, # kW no_load_current=0.5, # % short_circuit_voltage=z) grid.add_transformer_type(PM) # Create branches X_C3 = Branch(bus_from=POI, bus_to=B_C3, name="X_C3", branch_type=BranchType.Transformer, template=SS, tap=1.05) grid.add_branch(X_C3) C_M32 = Branch(bus_from=B_C3, bus_to=B_MV_M32, name="C_M32", r=0.784, x=0.174) grid.add_branch(C_M32) X_M32 = Branch(bus_from=B_MV_M32, bus_to=B_LV_M32, name="X_M32", branch_type=BranchType.Transformer, template=PM) grid.add_branch(X_M32) # Apply templates (device types) grid.apply_all_branch_types() print("Buses:") for i, b in enumerate(grid.buses): print(f" - bus[{i}]: {b}") print() options = PowerFlowOptions(SolverType.NR, verbose=True, initialize_with_existing_solution=True, multi_core=True, control_q=ReactivePowerControlMode.Direct, tolerance=1e-6, max_iter=99) power_flow = PowerFlowDriver(grid, options) power_flow.run() approx_volt = [round(100 * abs(v), 1) for v in power_flow.results.voltage] solution = [100.0, 94.7, 98.0, 98.1] # Expected solution from GridCal print() print(f"Test: {test_name}") print(f"Results: {approx_volt}") print(f"Solution: {solution}") print() print("Generators:") for g in grid.get_generators(): print(f" - Generator {g}: q_min={g.Qmin} MVAR, q_max={g.Qmax} MVAR") print() print("Branches:") for b in grid.branches: print(f" - {b}:") print(f" R = {round(b.R, 4)} pu") print(f" X = {round(b.X, 4)} pu") print(f" X/R = {round(b.X/b.R, 1)}") print(f" G = {round(b.G, 4)} pu") print(f" B = {round(b.B, 4)} pu") print() print("Transformer types:") for t in grid.transformer_types: print( f" - {t}: Copper losses={int(t.Pcu)}kW, Iron losses={int(t.Pfe)}kW, SC voltage={t.Vsc}%" ) print() print("Losses:") for i in range(len(grid.branches)): print( f" - {grid.branches[i]}: losses={1000*round(power_flow.results.losses[i], 3)} kVA" ) print() equal = True for i in range(len(approx_volt)): if approx_volt[i] != solution[i]: equal = False assert equal
def test_line_losses_1(): """ Basic line losses test. """ test_name = "test_line_losses_1" grid = MultiCircuit(name=test_name) Sbase = 100 # MVA grid.Sbase = Sbase grid.time_profile = None grid.logger = Logger() # Create buses Bus0 = Bus(name="Bus0", vnom=25, is_slack=True) Bus1 = Bus(name="Bus1", vnom=25) grid.add_bus(Bus0) grid.add_bus(Bus1) # Create load grid.add_load(Bus1, Load(name="Load0", P=1.0, Q=0.4)) # Create slack bus grid.add_generator(Bus0, Generator(name="Utility")) # Create cable (r and x should be in pu) grid.add_branch( Line(bus_from=Bus0, bus_to=Bus1, name="Cable1", r=0.01, x=0.05)) # Run non-linear load flow options = PowerFlowOptions(verbose=True) power_flow = PowerFlowDriver(grid, options) power_flow.run() # Check solution approx_losses = round(1000 * power_flow.results.losses[0], 3) solution = complex(0.116, 0.58) # Expected solution from GridCal # Tested on ETAP 16.1.0 and pandapower print( "\n=================================================================") print(f"Test: {test_name}") print( "=================================================================\n") print(f"Results: {approx_losses}") print(f"Solution: {solution}") print() print("Buses:") for i, b in enumerate(grid.buses): print(f" - bus[{i}]: {b}") print() print("Branches:") branches = grid.get_branches() for b in branches: print(f" - {b}:") print(f" R = {round(b.R, 4)} pu") print(f" X = {round(b.X, 4)} pu") print(f" X/R = {round(b.X/b.R, 2)}") print() print("Voltages:") for i in range(len(grid.buses)): print( f" - {grid.buses[i]}: voltage={round(power_flow.results.voltage[i], 3)} pu" ) print() print("Losses:") for i in range(len(branches)): print( f" - {branches[i]}: losses={round(power_flow.results.losses[i], 3)} MVA" ) print() print("Loadings (power):") for i in range(len(branches)): print( f" - {branches[i]}: loading={round(power_flow.results.Sf[i], 3)} MVA" ) print() print("Loadings (current):") for i in range(len(branches)): print( f" - {branches[i]}: loading={round(power_flow.results.If[i], 3)} pu" ) print() assert approx_losses == solution
def test_tolerance_lf_higher(): test_name = "test_tolerance_lf_higher" grid = MultiCircuit(name=test_name) grid.Sbase = Sbase grid.time_profile = None grid.logger = list() # Create buses Bus0 = Bus(name="Bus0", vnom=25, is_slack=True) bus_1 = Bus(name="bus_1", vnom=25) grid.add_bus(Bus0) grid.add_bus(bus_1) # Create load grid.add_load(bus_1, Load(name="Load0", P=1.0, Q=0.4)) # Create slack bus grid.add_generator(Bus0, Generator(name="Utility")) # Create cable (r and x should be in pu) grid.add_branch( Branch(bus_from=Bus0, bus_to=bus_1, name="Cable1", r=0.01, x=0.05, tolerance=10)) # Run non-linear power flow options = PowerFlowOptions( verbose=True, branch_impedance_tolerance_mode=BranchImpedanceMode.Upper) power_flow = PowerFlow(grid, options) power_flow.run() # Check solution approx_losses = round(1000 * power_flow.results.losses[0], 3) solution = complex(0.128, 0.58) # Expected solution from GridCal # Tested on ETAP 16.1.0 and pandapower print( "\n=================================================================") print(f"Test: {test_name}") print( "=================================================================\n") print(f"Results: {approx_losses}") print(f"Solution: {solution}") print() print("Buses:") for i, b in enumerate(grid.buses): print(f" - bus[{i}]: {b}") print() print("Branches:") for b in grid.branches: print(f" - {b}:") print(f" R = {round(b.R, 4)} pu") print(f" X = {round(b.X, 4)} pu") print(f" X/R = {round(b.X/b.R, 2)}") print() print("Voltages:") for i in range(len(grid.buses)): print( f" - {grid.buses[i]}: voltage={round(power_flow.results.voltage[i], 3)} pu" ) print() print("Losses:") for i in range(len(grid.branches)): print( f" - {grid.branches[i]}: losses={round(power_flow.results.losses[i], 3)} MVA" ) print() print("Loadings (power):") for i in range(len(grid.branches)): print( f" - {grid.branches[i]}: loading={round(power_flow.results.Sbranch[i], 3)} MVA" ) print() print("Loadings (current):") for i in range(len(grid.branches)): print( f" - {grid.branches[i]}: loading={round(power_flow.results.Ibranch[i], 3)} pu" ) print() assert approx_losses == solution