def init(self): if self.pf == 'pf': pp.runpp(self.network) elif self.pf == 'dcpf': pp.rundcpp(self.network) elif self.pf == 'opf': pp.runopp(self.network) else: pp.rundcopp(self.network)
def test_dcopf_poly(simple_opf_test_net): net = simple_opf_test_net pp.create_poly_cost(net, 0, "gen", cp1_eur_per_mw=100) # run OPF pp.rundcopp(net, ) # check and assert result logger.debug("test_simplest_voltage") logger.debug("res_gen:\n%s" % net.res_gen) logger.debug("res_ext_grid:\n%s" % net.res_ext_grid) logger.debug("res_bus.vm_pu: \n%s" % net.res_bus.vm_pu) assert abs(100 * net.res_gen.p_mw.values - net.res_cost) < 1e-3
def test_dcopf_poly(simple_opf_test_net): net = simple_opf_test_net pp.create_polynomial_cost(net, 0, "gen", np.array([100, 0])) # run OPF pp.rundcopp(net, verbose=False) # check and assert result logger.debug("test_simplest_voltage") logger.debug("res_gen:\n%s" % net.res_gen) logger.debug("res_ext_grid:\n%s" % net.res_ext_grid) logger.debug("res_bus.vm_pu: \n%s" % net.res_bus.vm_pu) assert abs(100 * net.res_gen.p_kw.values - net.res_cost) < 1e-3
def test_dcopf_pwl(simple_opf_test_net): # create net net = simple_opf_test_net pp.create_pwl_cost(net, 0, "gen" , [[0, 100, 100], [100, 200, 100]]) pp.create_pwl_cost(net, 0, "ext_grid", [[0, 100, 0], [100, 200, 0]]) # run OPF pp.rundcopp(net, ) assert net["OPF_converged"] # check and assert result logger.debug("test_simplest_voltage") logger.debug("res_gen:\n%s" % net.res_gen) logger.debug("res_ext_grid:\n%s" % net.res_ext_grid) logger.debug("res_bus.vm_pu: \n%s" % net.res_bus.vm_pu) assert abs(100 * net.res_gen.p_mw.values - net.res_cost) < 1e-3
def test_dcopf_pwl(simple_opf_test_net): # create net net = simple_opf_test_net # pp.create_polynomial_cost(net, 0, "gen", np.array([-100, 0])) pp.create_piecewise_linear_cost( net, 0, "gen", np.array([[-200, -20000], [-100, -10000], [0, 0]])) # run OPF pp.rundcopp(net, verbose=False) assert net["OPF_converged"] # check and assert result logger.debug("test_simplest_voltage") logger.debug("res_gen:\n%s" % net.res_gen) logger.debug("res_ext_grid:\n%s" % net.res_ext_grid) logger.debug("res_bus.vm_pu: \n%s" % net.res_bus.vm_pu) assert abs(100 * net.res_gen.p_kw.values - net.res_cost) < 1e-3
def step(self, *args, **kwargs): if self.pf == 'pf': a = pp.runpp(self.network) elif self.pf == 'dcpf': a = pp.rundcpp(self.network) elif self.pf == 'opf': a = pp.runopp(self.network) else: a = pp.rundcopp(self.network) return a
def solve_backend(self, verbose=False): for gen_id in self.gen.index.values: pp.create_poly_cost( self.grid_backend, gen_id, "gen", cp1_eur_per_mw=self.convert_per_unit_to_mw( self.gen["cost_pu"][gen_id]), ) try: pp.rundcopp( self.grid_backend, verbose=verbose, suppress_warnings=True, delta=self.params.tol, ) valid = True except pp.optimal_powerflow.OPFNotConverged as e: valid = False print(e) self._solve_save_backend() generators_p = self.grid_backend.res_gen["p_mw"].sum() ext_grids_p = self.grid_backend.res_ext_grid["p_mw"].sum() loads_p = self.grid_backend.load["p_mw"].sum() res_loads_p = self.grid_backend.res_load["p_mw"].sum() valid = (valid and np.abs(generators_p + ext_grids_p - loads_p) < 1e-2 and np.abs(res_loads_p - loads_p) < 1e-2) result = { "res_cost": self.grid_backend.res_cost, "res_bus": self.grid_backend.res_bus, "res_line": self.grid_backend.res_line, "res_gen": self.grid_backend.res_gen, "res_load": self.grid_backend.res_load, "res_ext_grid": self.grid_backend.res_ext_grid, "res_trafo": self.grid_backend.res_trafo, "valid": valid, } return result
def test_dcopf_pwl(): # create net net = pp.create_empty_network() pp.create_bus(net, vn_kv=10.) pp.create_bus(net, vn_kv=.4) pp.create_gen(net, 1, p_kw=-100, controllable=True, max_p_kw=-5, min_p_kw=-150, max_q_kvar=50, min_q_kvar=-50) pp.create_ext_grid(net, 0) pp.create_load(net, 1, p_kw=20, controllable=False) pp.create_line_from_parameters(net, 0, 1, 50, name="line2", r_ohm_per_km=0.876, c_nf_per_km=260.0, max_i_ka=0.123, x_ohm_per_km=0.1159876, max_loading_percent=100) # pp.create_polynomial_cost(net, 0, "gen", array([-100, 0])) pp.create_piecewise_linear_cost( net, 0, "gen", array([[-200, 20000], [-100, 10000], [0, 0]])) # run OPF pp.rundcopp(net, verbose=False) assert net["OPF_converged"] # check and assert result logger.debug("test_simplest_voltage") logger.debug("res_gen:\n%s" % net.res_gen) logger.debug("res_ext_grid:\n%s" % net.res_ext_grid) logger.debug("res_bus.vm_pu: \n%s" % net.res_bus.vm_pu) assert abs(100 * net.res_gen.p_kw.values - net.res_cost) < 1e-3
def OPA_model(net, f_bus, f_gen, f_load, f_line, f_trafo): # This variable allows to stop the function when a total blackout situation is reached total_blackout = 0 # Set failed elements as out of service. # In the case of failed buses, all the elements connected to the bus are also removed f_bus = list(f_bus) pp.set_element_status(net, f_bus, in_service=False) f_gen = list(f_gen) net.gen.loc[f_gen, 'in_service'] = False f_load = list(f_load) net.load.loc[f_load, 'in_service'] = False f_line = list(f_line) net.line.loc[f_line, 'in_service'] = False f_trafo = list(f_trafo) net.trafo.loc[f_trafo, 'in_service'] = False # Variable for entering in the while loop cascade = True # Store the initial power level of each generator intermediate_gen_power = dict(net.gen.loc[:, 'p_mw']) order_dict_gen = collections.OrderedDict( sorted(intermediate_gen_power.items())) intermediate_gen_power = list(order_dict_gen.values()) intermediate_load_power = dict(net.load.loc[:, 'p_mw']) order_dict_load = collections.OrderedDict( sorted(intermediate_load_power.items())) intermediate_load_power = list(order_dict_load.values()) while cascade == True: # Create a networkx graph of the power network G = pp.topology.create_nxgraph(net) # Create a list of sets containing the islands of the network list_islands = list(nx.connected_components(G)) # Create a list of buses with generators in service list_bus_with_gen_in_service = list( net.gen.bus[net.gen.in_service == True]) list_bus_with_gen_in_service = list(set(list_bus_with_gen_in_service)) list_bus_with_gen_in_service.sort() list_bus_with_slack_gen_in_service = list( net.gen.bus[net.gen.in_service == True][net.gen.slack == True]) # Check the configuration of each island in the power network for island in list_islands: #print(island) island = list(island) # Check if the island has an external grid. If yes, it is ok and you can proceed to the next island. # If no, check if there is an availabl generator in the island. If yes, turn it into an external grid. # If no, go to the next island. if set(list_bus_with_slack_gen_in_service).isdisjoint(island): if set(list_bus_with_gen_in_service).isdisjoint(island): pass else: set_island = set(island) bus_with_gen_in_island = list( set_island.intersection(list_bus_with_gen_in_service)) slack_gen = list(net.gen.index[net.gen.bus == bus_with_gen_in_island[0]]) net.gen.loc[slack_gen[0], 'slack'] = True else: pass # Set the isolated islands (without ext. grid/slack gen.) out of service pp.set_isolated_areas_out_of_service(net) # Try to run a DC OPF try: pp.rundcopp(net, verbose=False) # If it converges, save a the power level at each generators and ext grids intermediate_gen_power = dict(net.res_gen.loc[:, 'p_mw']) order_dict_gen = collections.OrderedDict( sorted(intermediate_gen_power.items())) intermediate_gen_power = list(order_dict_gen.values()) intermediate_load_power = dict(net.res_load.loc[:, 'p_mw']) order_dict_load = collections.OrderedDict( sorted(intermediate_load_power.items())) intermediate_load_power = list(order_dict_load.values()) # If the DC OPF does not converge, increase the loads costs # A while loop which decreases the loads costs until the DC OPF converges or the costs reach a value of 0 except pp.OPFNotConverged: #print(f_line, f_bus, f_trafo) print('CONVERGENCE PROBLEMS') cost = 1 load_cost = -100 while cost == 1: load_cost += 2 #print(load_cost) net.poly_cost.cp1_eur_per_mw[net.poly_cost.et == 'load'] = load_cost try: pp.rundcopp(net) #pp.rundcopp(net, SCPDIPM_RED_IT=100, PDIPM_COSTTOL = 1e-3, PDIPM_GRADTOL = 1e-3) intermediate_gen_power = dict(net.res_gen.loc[:, 'p_mw']) order_dict_gen = collections.OrderedDict( sorted(intermediate_gen_power.items())) intermediate_gen_power = list(order_dict_gen.values()) intermediate_load_power = dict(net.res_load.loc[:, 'p_mw']) order_dict_load = collections.OrderedDict( sorted(intermediate_load_power.items())) intermediate_load_power = list(order_dict_load.values()) cost = 0 except pp.OPFNotConverged: pass # If the loads costs are set to 0, run a DC power flow with the last available generators power levels if load_cost == 0: net.gen.loc[:, 'p_mw'] = intermediate_gen_power net.load.loc[:, 'p_mw'] = intermediate_load_power pp.rundcpp(net) break # This is in case to generators is available (everything is failed basically) except UserWarning: total_blackout = 1 break # If there is a total blackout situation, break the loop if total_blackout == 1: break # Create a list of lines still in service list_line_in_service = list( net.line.loc[net.line.in_service == True].index) list_line_in_service.sort() # This variable is to check if there are new failures due to overloads new_failure = 0 for line in list_line_in_service: level_loading = net.res_line.loc[line, 'loading_percent'] # If a line a loading >= the 99% of its maximum capacity, it is considered overloaded if level_loading >= 99: print('OVERLOADS') new_failure = 1 net.line.loc[line, 'in_service'] = False # Same for transformer lines list_trafo_in_service = list( net.trafo.loc[net.trafo.in_service == True].index) list_trafo_in_service.sort() for trafo in list_trafo_in_service: level_loading = net.res_trafo.loc[trafo, 'loading_percent'] if level_loading >= 99: print('OVERLOADS') new_failure = 1 net.trafo.loc[trafo, 'in_service'] = False # If there are no overloads, break the while loop if new_failure == 0: break # Save the final power levels of each generator and load if total_blackout == 0: final_state_load = dict(net.res_load.loc[:, 'p_mw']) order_dict_load = collections.OrderedDict( sorted(final_state_load.items())) list_load_power = list(order_dict_load.values()) final_state_gen = dict(net.res_gen.loc[:, 'p_mw']) order_dict_gen = collections.OrderedDict( sorted(final_state_gen.items())) list_gen_power = list(order_dict_gen.values()) list_gen_power = np.array(list_gen_power) list_load_power = np.array(list_load_power) for i, j in enumerate(list_gen_power): if math.isnan(j): list_gen_power[i] = 0 for i, j in enumerate(list_load_power): if math.isnan(j): list_load_power[i] = 0 elif total_blackout == 1: list_gen_power = np.zeros(len(net.gen.index)) list_load_power = np.zeros(len(net.load.index)) return (list_gen_power, list_load_power)
def optimal_dc(self): pp.rundcopp(self.net) if not self.opf_converged(): raise pp.OPFNotConverged return self._results()
def optimal_dc(self): try: pp.rundcopp(self.net) except pp.OPFNotConverged: return None return self._results()
def validate_from_ppc( ppc_net, net, pf_type="runpp", max_diff_values={ "bus_vm_pu": 1e-6, "bus_va_degree": 1e-5, "branch_p_mw": 1e-6, "branch_q_mvar": 1e-6, "gen_p_mw": 1e-6, "gen_q_mvar": 1e-6 }, run=True): """ This function validates the pypower case files to pandapower net structure conversion via a \ comparison of loadflow calculation results. (Hence the opf cost conversion is not validated.) INPUT: **ppc_net** - The pypower case file, which must already contain the pypower powerflow results or pypower must be importable. **net** - The pandapower network. OPTIONAL: **pf_type** ("runpp", string) - Type of validated power flow. Possible are ("runpp", "rundcpp", "runopp", "rundcopp") **max_diff_values** - Dict of maximal allowed difference values. The keys must be 'vm_pu', 'va_degree', 'p_branch_mw', 'q_branch_mvar', 'p_gen_mw' and 'q_gen_mvar' and the values floats. **run** (True, bool or list of two bools) - changing the value to False avoids trying to run (optimal) loadflows. Giving a list of two bools addresses first pypower and second pandapower. OUTPUT: **conversion_success** - conversion_success is returned as False if pypower or pandapower cannot calculate a powerflow or if the maximum difference values (max_diff_values ) cannot be hold. EXAMPLE: import pandapower.converter as pc net = cv.from_ppc(ppc_net, f_hz=50) conversion_success = cv.validate_from_ppc(ppc_net, net) NOTE: The user has to take care that the loadflow results already are included in the provided \ ppc_net or pypower is importable. """ # check in case of optimal powerflow comparison whether cost information exist if "opp" in pf_type: if not (len(net.polynomial_cost) | len(net.piecewise_linear_cost)): if "gencost" in ppc_net: if not len(ppc_net["gencost"]): logger.debug( 'ppc and pandapower net do not include cost information.' ) return True else: logger.error( 'The pandapower net does not include cost information.' ) return False else: logger.debug( 'ppc and pandapower net do not include cost information.') return True # guarantee run parameter as list, for pypower and pandapower (optimal) powerflow run run = [run, run] if isinstance(run, bool) else run # --- check pypower powerflow success, if possible if pypower_import and run[0]: try: if pf_type == "runpp": ppc_net = runpf.runpf(ppc_net, ppopt)[0] elif pf_type == "rundcpp": ppc_net = rundcpf.rundcpf(ppc_net, ppopt)[0] elif pf_type == "runopp": ppc_net = runopf.runopf(ppc_net, ppopt) elif pf_type == "rundcopp": ppc_net = rundcopf.rundcopf(ppc_net, ppopt) else: raise ValueError("The pf_type %s is unknown" % pf_type) except: logger.debug("The pypower run did not work.") ppc_success = True if 'success' in ppc_net.keys(): if ppc_net['success'] != 1: ppc_success = False logger.error( "The given ppc data indicates an unsuccessful pypower powerflow: " + "'ppc_net['success'] != 1'") if (ppc_net['branch'].shape[1] < 17): ppc_success = False logger.error( "The shape of given ppc data indicates missing pypower powerflow results." ) # --- try to run a pandapower powerflow if run[1]: if pf_type == "runpp": try: pp.runpp(net, init="dc", calculate_voltage_angles=True, trafo_model="pi") except pp.LoadflowNotConverged: try: pp.runpp(net, calculate_voltage_angles=True, init="flat", trafo_model="pi") except pp.LoadflowNotConverged: try: pp.runpp(net, trafo_model="pi", calculate_voltage_angles=False) if "bus_va_degree" in max_diff_values.keys(): max_diff_values[ "bus_va_degree"] = 1e2 if max_diff_values[ "bus_va_degree"] < 1e2 else max_diff_values[ "bus_va_degree"] logger.info("voltage_angles could be calculated.") except pp.LoadflowNotConverged: logger.error( 'The pandapower powerflow does not converge.') elif pf_type == "rundcpp": try: pp.rundcpp(net, trafo_model="pi") except pp.LoadflowNotConverged: logger.error('The pandapower dc powerflow does not converge.') elif pf_type == "runopp": try: pp.runopp(net, init="flat", calculate_voltage_angles=True) except pp.OPFNotConverged: try: pp.runopp(net, init="pf", calculate_voltage_angles=True) except (pp.OPFNotConverged, pp.LoadflowNotConverged, KeyError): try: pp.runopp(net, init="flat", calculate_voltage_angles=False) logger.info("voltage_angles could be calculated.") if "bus_va_degree" in max_diff_values.keys(): max_diff_values[ "bus_va_degree"] = 1e2 if max_diff_values[ "bus_va_degree"] < 1e2 else max_diff_values[ "bus_va_degree"] except pp.OPFNotConverged: try: pp.runopp(net, init="pf", calculate_voltage_angles=False) if "bus_va_degree" in max_diff_values.keys(): max_diff_values[ "bus_va_degree"] = 1e2 if max_diff_values[ "bus_va_degree"] < 1e2 else max_diff_values[ "bus_va_degree"] logger.info("voltage_angles could be calculated.") except (pp.OPFNotConverged, pp.LoadflowNotConverged, KeyError): logger.error( 'The pandapower optimal powerflow does not converge.' ) elif pf_type == "rundcopp": try: pp.rundcopp(net) except pp.LoadflowNotConverged: logger.error( 'The pandapower dc optimal powerflow does not converge.') else: raise ValueError("The pf_type %s is unknown" % pf_type) # --- prepare powerflow result comparison by reordering pp results as they are in ppc results if not ppc_success: return False if "opp" in pf_type: if not net.OPF_converged: return elif not net.converged: return False # --- store pypower powerflow results ppc_res = dict.fromkeys(ppc_elms) ppc_res["branch"] = ppc_net['branch'][:, 13:17] ppc_res["bus"] = ppc_net['bus'][:, 7:9] ppc_res["gen"] = ppc_net['gen'][:, 1:3] # --- pandapower bus result table pp_res = dict.fromkeys(ppc_elms) pp_res["bus"] = array(net.res_bus.sort_index()[['vm_pu', 'va_degree']]) # --- pandapower gen result table pp_res["gen"] = zeros([1, 2]) # consideration of parallel generators via storing how much generators have been considered # each node # if in ppc is only one gen -> numpy initially uses one dim array -> change to two dim array if len(ppc_net["gen"].shape) == 1: ppc_net["gen"] = array(ppc_net["gen"], ndmin=2) GENS = DataFrame(ppc_net['gen'][:, [0]].astype(int)) GEN_uniq = GENS.drop_duplicates() already_used_gen = Series(zeros(GEN_uniq.shape[0]).astype(int), index=[int(v) for v in GEN_uniq.values]) change_q_compare = [] for i, j in GENS.iterrows(): current_bus_type, current_bus_idx, same_bus_gen_idx, first_same_bus_in_service_gen_idx, \ last_same_bus_in_service_gen_idx = _gen_bus_info(ppc_net, i) if current_bus_type == 3 and i == first_same_bus_in_service_gen_idx: pp_res["gen"] = append( pp_res["gen"], array(net.res_ext_grid[net.ext_grid.bus == current_bus_idx][[ 'p_mw', 'q_mvar' ]]).reshape((1, 2)), 0) elif current_bus_type == 2 and i == first_same_bus_in_service_gen_idx: pp_res["gen"] = append( pp_res["gen"], array(net.res_gen[net.gen.bus == current_bus_idx][[ 'p_mw', 'q_mvar' ]]).reshape((1, 2)), 0) else: pp_res["gen"] = append( pp_res["gen"], array(net.res_sgen[net.sgen.bus == current_bus_idx][[ 'p_mw', 'q_mvar' ]])[already_used_gen.at[int(j)]].reshape((1, 2)), 0) already_used_gen.at[int(j)] += 1 change_q_compare += [int(j)] pp_res["gen"] = pp_res["gen"][1:, :] # delete initial zero row # --- pandapower branch result table pp_res["branch"] = zeros([1, 4]) # consideration of parallel branches via storing how often branches were considered # each node-to-node-connection try: init1 = concat([net.line.from_bus, net.line.to_bus], axis=1, sort=True).drop_duplicates() init2 = concat([net.trafo.hv_bus, net.trafo.lv_bus], axis=1, sort=True).drop_duplicates() except TypeError: # legacy pandas < 0.21 init1 = concat([net.line.from_bus, net.line.to_bus], axis=1).drop_duplicates() init2 = concat([net.trafo.hv_bus, net.trafo.lv_bus], axis=1).drop_duplicates() init1['hv_bus'] = nan init1['lv_bus'] = nan init2['from_bus'] = nan init2['to_bus'] = nan try: already_used_branches = concat([init1, init2], axis=0, sort=True) except TypeError: # pandas < 0.21 legacy already_used_branches = concat([init1, init2], axis=0) already_used_branches['number'] = zeros( [already_used_branches.shape[0], 1]).astype(int) BRANCHES = DataFrame(ppc_net['branch'][:, [0, 1, 8, 9]]) for i in BRANCHES.index: from_bus = pp.get_element_index(net, 'bus', name=int(ppc_net['branch'][i, 0])) to_bus = pp.get_element_index(net, 'bus', name=int(ppc_net['branch'][i, 1])) from_vn_kv = ppc_net['bus'][from_bus, 9] to_vn_kv = ppc_net['bus'][to_bus, 9] ratio = BRANCHES[2].at[i] angle = BRANCHES[3].at[i] # from line results if (from_vn_kv == to_vn_kv) & ((ratio == 0) | (ratio == 1)) & (angle == 0): pp_res["branch"] = append( pp_res["branch"], array(net.res_line[(net.line.from_bus == from_bus) & (net.line.to_bus == to_bus)][[ 'p_from_mw', 'q_from_mvar', 'p_to_mw', 'q_to_mvar' ]]) [int(already_used_branches.number.loc[ (already_used_branches.from_bus == from_bus) & (already_used_branches.to_bus == to_bus)].values)].reshape( 1, 4), 0) already_used_branches.number.loc[ (already_used_branches.from_bus == from_bus) & (already_used_branches.to_bus == to_bus)] += 1 # from trafo results else: if from_vn_kv >= to_vn_kv: pp_res["branch"] = append( pp_res["branch"], array(net.res_trafo[(net.trafo.hv_bus == from_bus) & (net.trafo.lv_bus == to_bus)] [['p_hv_mw', 'q_hv_mvar', 'p_lv_mw', 'q_lv_mvar' ]])[int(already_used_branches.number.loc[ (already_used_branches.hv_bus == from_bus) & (already_used_branches.lv_bus == to_bus)]. values)].reshape(1, 4), 0) already_used_branches.number.loc[ (already_used_branches.hv_bus == from_bus) & (already_used_branches.lv_bus == to_bus)] += 1 else: # switch hv-lv-connection of pypower connection buses pp_res["branch"] = append( pp_res["branch"], array(net.res_trafo[(net.trafo.hv_bus == to_bus) & (net.trafo.lv_bus == from_bus)] [['p_lv_mw', 'q_lv_mvar', 'p_hv_mw', 'q_hv_mvar' ]])[int(already_used_branches.number.loc[ (already_used_branches.hv_bus == to_bus) & (already_used_branches.lv_bus == from_bus)]. values)].reshape(1, 4), 0) already_used_branches.number.loc[ (already_used_branches.hv_bus == to_bus) & (already_used_branches.lv_bus == from_bus)] += 1 pp_res["branch"] = pp_res["branch"][1:, :] # delete initial zero row # --- do the powerflow result comparison diff_res = dict.fromkeys(ppc_elms) diff_res["bus"] = ppc_res["bus"] - pp_res["bus"] diff_res["bus"][:, 1] -= diff_res["bus"][0, 1] # remove va_degree offset diff_res["branch"] = ppc_res["branch"] - pp_res["branch"] diff_res["gen"] = ppc_res["gen"] - pp_res["gen"] # comparison of buses with several generator units only as q sum for i in GEN_uniq.loc[GEN_uniq[0].isin(change_q_compare)].index: next_is = GEN_uniq.index[GEN_uniq.index > i] if len(next_is) > 0: next_i = next_is[0] else: next_i = GENS.index[-1] + 1 if (next_i - i) > 1: diff_res["gen"][i:next_i, 1] = sum(diff_res["gen"][i:next_i, 1]) # logger info logger.debug( "Maximum voltage magnitude difference between pypower and pandapower: " "%.2e pu" % max_(abs(diff_res["bus"][:, 0]))) logger.debug( "Maximum voltage angle difference between pypower and pandapower: " "%.2e degree" % max_(abs(diff_res["bus"][:, 1]))) logger.debug( "Maximum branch flow active power difference between pypower and pandapower: " "%.2e MW" % max_(abs(diff_res["branch"][:, [0, 2]]))) logger.debug( "Maximum branch flow reactive power difference between pypower and " "pandapower: %.2e MVAr" % max_(abs(diff_res["branch"][:, [1, 3]]))) logger.debug( "Maximum active power generation difference between pypower and pandapower: " "%.2e MW" % max_(abs(diff_res["gen"][:, 0]))) logger.debug( "Maximum reactive power generation difference between pypower and pandapower: " "%.2e MVAr" % max_(abs(diff_res["gen"][:, 1]))) if _validate_diff_res(diff_res, {"bus_vm_pu": 1e-3, "bus_va_degree": 1e-3, "branch_p_mw": 1e-6, "branch_q_mvar": 1e-6}) and \ (max_(abs(diff_res["gen"])) > 1e-1).any(): logger.debug( "The active/reactive power generation difference possibly results " "because of a pypower error. Please validate " "the results via pypower loadflow." ) # this occurs e.g. at ppc case9 # give a return if isinstance(max_diff_values, dict): return _validate_diff_res(diff_res, max_diff_values) else: logger.debug("'max_diff_values' must be a dict.")
def runner_opf_topology_optimization( self, model, verbose=False, ): np.random.seed(0) model.gen["cost_pu"] = np.random.uniform(1.0, 5.0, (model.grid.gen.shape[0], )) model.build_model() if verbose: model.print_model() if model.params.solver_name == "glpk": print("Solver does not support bilinear or quadratic terms.") self.assertTrue(True) return result = model.solve(verbose=verbose) result_x = result["res_x"] result_objective = result["res_cost"] n_gen = model.grid.gen.shape[0] n_load = model.grid.load.shape[0] n_line = model.grid.line.shape[0] """ BACKEND BRUTE FORCE. """ results_backend = [] for idx, x_topology in enumerate( itertools.product([0, 1], repeat=n_gen + n_load + 4 * n_line)): # x_topology = [x_gen, x_load, x_line_or_1, x_line_or_2, x_line_ex_1, x_line_ex_2] x_topology = np.array(x_topology, dtype=np.int) # Initialization of variables ( x_gen, x_load, x_line_or_1, x_line_or_2, x_line_ex_1, x_line_ex_2, ) = self.topology_to_parts(x_topology, n_gen, n_load, n_line) # Check valid topology if self.is_valid_topology(x_topology, n_gen, n_load, n_line): # Generator bus gen_sub_bus = np.ones_like(x_gen, dtype=np.int) gen_sub_bus[x_gen.astype(np.bool)] = 2 gen_bus = [ model.grid.sub["bus"][sub_id][sub_bus - 1] for sub_bus, sub_id in zip(gen_sub_bus, model.grid.gen["sub"]) ] # Load bus load_sub_bus = np.ones_like(x_load, dtype=np.int) load_sub_bus[x_load.astype(np.bool)] = 2 load_bus = [ model.grid.sub["bus"][sub_id][sub_bus - 1] for sub_bus, sub_id in zip(load_sub_bus, model.grid.load["sub"]) ] # Power line status line_status = np.logical_and( np.logical_or(x_line_or_1, x_line_or_2), np.logical_or(x_line_ex_1, x_line_ex_2), ) # Power line - Origin bus line_or_sub_bus = -np.ones_like(x_line_or_1, dtype=np.int) line_or_sub_bus[x_line_or_1.astype(np.bool)] = 1 line_or_sub_bus[x_line_or_2.astype(np.bool)] = 2 line_or_bus = np.array([ model.grid.sub["bus"][sub_id][sub_bus - 1] if sub_bus != -1 else model.grid.sub["bus"][sub_id][0] for sub_bus, sub_id in zip(line_or_sub_bus, model.grid.line["sub_or"]) ]) # Power line - Extremity bus line_ex_sub_bus = -np.ones_like(x_line_ex_1, dtype=np.int) line_ex_sub_bus[x_line_ex_1.astype(np.bool)] = 1 line_ex_sub_bus[x_line_ex_2.astype(np.bool)] = 2 line_ex_bus = np.array([ model.grid.sub["bus"][sub_id][sub_bus - 1] if sub_bus != -1 else model.grid.sub["bus"][sub_id][0] for sub_bus, sub_id in zip(line_ex_sub_bus, model.grid.line["sub_ex"]) ]) # Construct grid for backend grid_tmp = model.grid_backend.deepcopy() grid_tmp.gen["bus"] = gen_bus grid_tmp.load["bus"] = load_bus grid_tmp.line["in_service"] = line_status[~model.grid.line. trafo] grid_tmp.line["from_bus"] = line_or_bus[~model.grid.line.trafo] grid_tmp.line["to_bus"] = line_ex_bus[~model.grid.line.trafo] grid_tmp.trafo["in_service"] = line_status[ model.grid.line.trafo] grid_tmp.trafo["hv_bus"] = line_or_bus[model.grid.line.trafo] grid_tmp.trafo["lv_bus"] = line_ex_bus[model.grid.line.trafo] for gen_id in grid_tmp.gen.index.values: pp.create_poly_cost( grid_tmp, gen_id, "gen", cp1_eur_per_mw=model.convert_per_unit_to_mw( model.grid.gen["cost_pu"][gen_id]), ) print(f"{len(results_backend) + 1}/{idx}: Running DC-OPF ...") try: pp.rundcopp(grid_tmp) valid = True except (pp.optimal_powerflow.OPFNotConverged, IndexError) as e: grid_tmp.res_cost = 0.0 print(e) continue load_p = model.convert_mw_to_per_unit( grid_tmp.load["p_mw"].sum()) gen_p = model.convert_mw_to_per_unit( grid_tmp.res_gen["p_mw"].sum()) valid = valid and np.abs(gen_p - load_p) < 1e-6 if (model.params.obj_gen_cost and model.params.obj_reward_quad and model.params.solver_name != "glpk"): objective = (grid_tmp.res_cost + np.square( model.convert_mw_to_per_unit( grid_tmp.res_line["p_from_mw"]) / model.grid.line["max_p_pu"]).sum()) elif (model.params.obj_reward_quad and model.params.solver_name != "glpk"): objective = +np.square( model.convert_mw_to_per_unit( grid_tmp.res_line["p_from_mw"]) / model.grid.line["max_p_pu"]).sum() else: objective = grid_tmp.res_cost results_backend.append({ "x": np.concatenate(( x_gen, x_load, x_line_or_1, x_line_or_2, x_line_ex_1, x_line_ex_2, )), "gen_bus": gen_bus, "load_bus": load_bus, "line_or_bus": line_or_bus, "line_ex_bus": line_ex_bus, "line_status": line_status.astype(int), "valid": valid, "objective": np.round(objective, 3), "load_p": load_p, "gen_p": np.round(gen_p, 3), }) results_backend = pd.DataFrame(results_backend) # Check with brute force solution objective_brute = results_backend["objective"][ results_backend["valid"]].min() hot_brute = np.abs(results_backend["objective"].values - objective_brute) < 0.05 indices_brute = hot_to_indices(hot_brute) status_brute = results_backend["x"][indices_brute] match_idx = [ idx for idx, line_status in zip(indices_brute, status_brute) if np.equal(line_status, result_x).all() ] # Compare results_backend["candidates"] = hot_brute results_backend["result_objective"] = np.nan results_backend["result_objective"][match_idx] = np.round( result_objective, 3) print(f"\n{model.name}\n") print(f"Solver: {result_objective}") print(results_backend[[ "gen_bus", "load_bus", "line_or_bus", "line_ex_bus", "line_status", "load_p", "gen_p", "valid", "candidates", "objective", "result_objective", ]][results_backend["candidates"] & results_backend["valid"]].to_string()) time.sleep(0.1) self.assertTrue(bool(match_idx))