def test_gridcal_regulator(): """ GridCal test for the new implementation of transformer voltage regulators. """ grid = MultiCircuit(name=test_name) grid.Sbase = Sbase 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 = ControlledGenerator(name="Utility") UT.bus = POI grid.add_controlled_generator(POI, UT) # Create static generators (with fixed power factor) M32 = StaticGenerator(name="M32", power=4.2 + 0.0j) # 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=get_complex(z, xr).real * s * 1000 / 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=get_complex(z, xr).real * s * 1000 / 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() grid.compile() options = PowerFlowOptions(SolverType.LM, verbose=True, robust=True, initialize_with_existing_solution=True, multi_core=True, control_q=True, control_taps=True, tolerance=1e-6, max_iter=99) power_flow = PowerFlowMP(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("Controlled generators:") for g in grid.get_controlled_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}: 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.Copper_losses)}kW, Iron losses={int(t.Iron_losses)}kW, SC voltage={t.Short_circuit_voltage}%" ) 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(f"Voltage settings: {grid.numerical_circuit.vset}") #print("GridCal logger:") #for l in grid.logger: #print(f" - {l}") equal = True for i in range(len(approx_volt)): if approx_volt[i] != solution[i]: equal = False assert equal
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%). """ grid = MultiCircuit(name=test_name) grid.Sbase = Sbase 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 = ControlledGenerator(name="Utility") UT.bus = POI grid.add_controlled_generator(POI, UT) # Create static generators (with fixed power factor) M32 = StaticGenerator(name="M32", power=4.2 + 0.0j) # 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=complexe(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=complexe(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() grid.compile() options = PowerFlowOptions(SolverType.NR, verbose=True, robust=True, initialize_with_existing_solution=True, multi_core=True, control_q=True, control_taps=True, tolerance=1e-6, max_iter=15) power_flow = PowerFlow(grid, options) power_flow.run() print() print(f"Test: {test_name}") print() print("Controlled generators:") for g in grid.get_controlled_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.Copper_losses)}kW, " f"Iron losses={int(t.Iron_losses)}kW, SC voltage={t.Short_circuit_voltage}%" ) 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_pv_1(): """ 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, but their voltage regulators aren't. """ grid = MultiCircuit(name=test_name) grid.Sbase = Sbase grid.time_profile = None grid.logger = list() # 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 = ControlledGenerator(name="Utility") UT.bus = POI grid.add_controlled_generator(POI, UT) M32 = ControlledGenerator(name="M32", active_power=4.2, voltage_module=1.025, Qmin=-2.5, Qmax=2.5) M32.bus = B_LV_M32 grid.add_controlled_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=complexe(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=complexe(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) 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() grid.compile() options = PowerFlowOptions(SolverType.LM, verbose=True, robust=True, initialize_with_existing_solution=True, multi_core=True, control_q=True, 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, 100.1, 102.5] # 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("Controlled generators:") for g in grid.get_controlled_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.Copper_losses)}kW, Iron losses={int(t.Iron_losses)}kW, SC voltage={t.Short_circuit_voltage}%") 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 load_dpx(file_name, contraction_factor=1000): """ Read DPX file :param file_name: file name :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, power=complex(p, 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 = ControlledGenerator(name=name, active_power=p, voltage_module=v) circuit.add_controlled_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, power=complex(p, 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, power=complex(p, 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.append(tpe + ' not recognised under Nodes') # 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.append('The ' + tpe + ' type ' + eq_id + ' was not found.') 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 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 = ControlledGenerator(name='Slack') circuit.add_controlled_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), power=complex(p, 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_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%). """ grid = MultiCircuit(name=test_name) grid.Sbase = Sbase 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 = ControlledGenerator(name="Utility") UT.bus = POI grid.add_controlled_generator(POI, UT) # Create static generators (with fixed power factor) M32 = StaticGenerator(name="M32", power=4.2 + 0.0j) # 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=complexe(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=complexe(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() grid.compile() options = PowerFlowOptions(SolverType.LM, verbose=True, robust=True, initialize_with_existing_solution=True, multi_core=True, control_q=True, 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, 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("Controlled generators:") for g in grid.get_controlled_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.Copper_losses)}kW, Iron losses={int(t.Iron_losses)}kW, SC voltage={t.Short_circuit_voltage}%" ) 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_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. """ grid = MultiCircuit(name=test_name) grid.Sbase = Sbase 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 = ControlledGenerator(name="Utility") UT.bus = POI grid.add_controlled_generator(POI, UT) # Create static generators (with fixed power factor) M32 = StaticGenerator(name="M32", power=4.2 + 0.0j) # 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=complexe(z, xr).real * s * 1000 / Sbase, 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=complexe(z, xr).real * s * 1000 / Sbase, 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() grid.compile() options = PowerFlowOptions(SolverType.LM, verbose=True, robust=True, initialize_with_existing_solution=True, multi_core=True, control_q=True, 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("Controlled generators:") for g in grid.get_controlled_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.Copper_losses)}kW, Iron losses={int(t.Iron_losses)}kW, SC voltage={t.Short_circuit_voltage}%" ) 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