diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 55be5742e..45d9d8bcb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -32,6 +32,7 @@ Change Log - [ADDED] matplotlib v3.8.0 support (fixed :code:`plotting_colormaps.ipynb`) - [CHANGED] PowerFactory converter - name :code:`for_name` as :code:`equipment` for all elements; also add to line - [ADDED] option to use a second tap changer for the trafo element +- [CHANGED] parameters of function merge_internal_net_and_equivalent_external_net() - [FIXED] :code:`convert_format.py`: update the attributes of the characteristic objects to match the new characteristic diff --git a/pandapower/grid_equivalents/get_equivalent.py b/pandapower/grid_equivalents/get_equivalent.py index db3395602..67faac8c6 100644 --- a/pandapower/grid_equivalents/get_equivalent.py +++ b/pandapower/grid_equivalents/get_equivalent.py @@ -260,7 +260,7 @@ def get_equivalent(net, eq_type, boundary_buses, internal_buses, if return_internal: logger.debug("Merging of internal and equivalent network begins.") net_eq = merge_internal_net_and_equivalent_external_net( - net_eq, net_internal, eq_type, show_computing_time, + net_eq, net_internal, show_computing_time=show_computing_time, calc_volt_angles=calculate_voltage_angles) if len(orig_slack_gens): net_eq.gen.slack.loc[net_eq.gen.index.intersection(orig_slack_gens)] = True @@ -327,8 +327,7 @@ def get_equivalent(net, eq_type, boundary_buses, internal_buses, def merge_internal_net_and_equivalent_external_net( - net_eq, net_internal, eq_type, show_computing_time=False, - calc_volt_angles=False, **kwargs): + net_eq, net_internal, fuse_bus_column="auto", show_computing_time=False, **kwargs): """ Merges the internal network and the equivalent external network. It is expected that the boundaries occur in both, equivalent net and @@ -340,9 +339,11 @@ def merge_internal_net_and_equivalent_external_net( **net_internal** - internal area - **eq_type** (str) - equivalent type, such as "rei", "ward" or "xward" - OPTIONAL: + **fuse_bus_column** (str, "auto) - the function expects boundary buses to be in net_eq and + in net_internal. These duplicate buses get fused. To identify these buses, the given column is used. Option "auto" provides backward compatibility which is: use "name_equivalent" if + existing and "name" otherwise + **show_computing_time** (bool, False) ****kwargs** - key word arguments for pp.merge_nets() @@ -368,17 +369,24 @@ def merge_internal_net_and_equivalent_external_net( merged_net = pp.merge_nets( net_internal, net_eq, validate=kwargs.pop("validate", False), net2_reindex_log_level=kwargs.pop("net2_reindex_log_level", "debug"), **kwargs) - try: - merged_net.gen.max_p_mw[-len(net_eq.gen.max_p_mw):] = net_eq.gen.max_p_mw.values - merged_net.gen.min_p_mw[-len(net_eq.gen.max_p_mw):] = net_eq.gen.min_p_mw.values - except: - pass # --- fuse or combine the boundary buses in external and internal nets - busname_col = "name_equivalent" if "name_equivalent" in merged_net.bus.columns.tolist() else "name" + if fuse_bus_column == "auto": + if fuse_bus_column in merged_net.bus.columns: + raise ValueError( + f"{fuse_bus_column=} is ambiguous since the column 'auto' exists in net.bus") + if "name_equivalent" in merged_net.bus.columns: + fuse_bus_column = "name_equivalent" + else: + fuse_bus_column = "name" for bus in boundary_buses_inclusive_bswitch: - name = merged_net.bus[busname_col].loc[bus] - target_buses = merged_net.bus.index[merged_net.bus[busname_col] == name] + try: + name = merged_net.bus[fuse_bus_column].loc[bus] + except: + print(fuse_bus_column) + print(merged_net.bus.columns) + print() + target_buses = merged_net.bus.index[merged_net.bus[fuse_bus_column] == name] if len(target_buses) != 2: raise ValueError( "The code expects all boundary buses to occur double. One because " diff --git a/pandapower/grid_equivalents/rei_generation.py b/pandapower/grid_equivalents/rei_generation.py index 999fe190c..53ccc1fd4 100644 --- a/pandapower/grid_equivalents/rei_generation.py +++ b/pandapower/grid_equivalents/rei_generation.py @@ -673,11 +673,15 @@ def _replace_ext_area_by_impedances_and_shunts( "p_mw": shunt_params.parameter.values.real * net_eq.sn_mva }, index=range(max_idx+1, max_idx+1+shunt_params.shape[0])) new_shunts["name"] = "eq_shunt" - new_shunts["vn_kv"] = net_eq.bus.vn_kv.loc[new_shunts.bus.values].values + isin_sh = new_shunts.bus.isin(net_eq.bus.index) + new_shunts.loc[isin_sh, "vn_kv"] = net_eq.bus.vn_kv.loc[new_shunts.bus.loc[isin_sh]].values new_shunts["step"] = 1 new_shunts["max_step"] = 1 new_shunts["in_service"] = True net_eq["shunt"] = pd.concat([net_eq["shunt"], new_shunts]) + if n_disconnected_new_eq_shunts := sum(~isin_sh): + msg = f"{n_disconnected_new_eq_shunts=}, missing buses: {new_shunts.bus.loc[~isin_sh]}" + raise ValueError(msg) runpp_fct(net_eq, calculate_voltage_angles=calc_volt_angles, tolerance_mva=1e-6, max_iteration=100) diff --git a/pandapower/test/grid_equivalents/test_get_equivalent.py b/pandapower/test/grid_equivalents/test_get_equivalent.py index 43a9df637..f17b27d02 100644 --- a/pandapower/test/grid_equivalents/test_get_equivalent.py +++ b/pandapower/test/grid_equivalents/test_get_equivalent.py @@ -90,7 +90,7 @@ def run_basic_usecases(eq_type, net=None): # UC3: merge eq_net3 with subnet_rest eq_net3 = pp.grid_equivalents.merge_internal_net_and_equivalent_external_net( - eq_net3a, subnet_rest, eq_type) + eq_net3a, subnet_rest) pp.runpp(eq_net3, calculate_voltage_angles=True) assert pandapower.toolbox.nets_equal(net, create_test_net()) return eq_net1, eq_net2, eq_net3 @@ -315,7 +315,8 @@ def test_case9_with_slack_generator_in_external_net(): "impedance": 6}, check_all_pp_elements=True) # merge eq_net with internal net to get a power flow runable net to check the results eq_net3.gen.slack = True - eq_net4 = pp.grid_equivalents.merge_internal_net_and_equivalent_external_net(eq_net3, ib_net, eq_type) + eq_net4 = pp.grid_equivalents.merge_internal_net_and_equivalent_external_net( + eq_net3, ib_net) pp.runpp(eq_net4) check_res_bus(net, eq_net4) elif "ward" in eq_type: @@ -457,7 +458,7 @@ def test_retain_original_internal_indices(): net_eq = pp.grid_equivalents.get_equivalent(net, eq_type, boundary_buses, internal_buses, calculate_voltage_angles=True, retain_original_internal_indices=True) - + assert net_eq.sgen.index.tolist()[:3] == sgen_idxs[:3] assert set(net_eq.line.index.tolist()) - set(line_idxs) == set() assert set(net_eq.bus.index.tolist()[:-2]) - set(bus_idxs) == set() @@ -471,14 +472,14 @@ def test_switch_sgens(): pp.create_switch(net, 9, 1, "b") pp.create_sgen(net, 9, 10, 10) pp.runpp(net) - net_eq = pp.grid_equivalents.get_equivalent(net, "rei", [4, 8], [0]) + net_eq = pp.grid_equivalents.get_equivalent(net, "rei", [4, 8], [0]) assert max(net.res_bus.vm_pu[[0, 3, 4, 8]].values - net_eq.res_bus.vm_pu[[0, 3, 4, 8]].values) < 1e-6 assert max(net.res_bus.va_degree[[0, 3, 4, 8]].values - net_eq.res_bus.va_degree[[0, 3, 4, 8]].values) < 1e-6 def test_characteristic(): net = pp.networks.example_multivoltage() - pp.control.create_trafo_characteristics(net, "trafo", [1], 'vk_percent', + pp.control.create_trafo_characteristics(net, "trafo", [1], 'vk_percent', [[-2,-1,0,1,2]], [[2,3,4,5,6]]) pp.runpp(net) net_eq = pp.grid_equivalents.get_equivalent(net, "rei", [41], [0]) @@ -490,12 +491,12 @@ def test_controller(): pp.replace_gen_by_sgen(net) pp.create_load(net, 5, 10, 10) pp.create_sgen(net, 3, 1, 1) - + net.sgen.loc[:, "type"] = "wind" net.load.loc[:, "type"] = "residential" net.sgen.name = ["sgen0", "sgen1", "sgen3"] net.load.name = ["load0", "load1", "load2", "load3"] - + # load time series json_path = os.path.join(pp_dir, "test", "opf", "cigre_timeseries_15min.json") time_series = pd.read_json(json_path) @@ -515,12 +516,12 @@ def test_controller(): ConstControl(net, element="sgen", variable="p_mw", element_index=net.sgen.index.tolist(), profile_name=net.sgen.index.tolist(), data_source=DFData(sgen_ts)) - + pp.runpp(net) # getting equivalent net_eq = pp.grid_equivalents.get_equivalent(net, "rei", [4, 8], [0]) - + assert net_eq.controller.object[0].__dict__["element_index"] == [0, 2] assert net_eq.controller.object[0].__dict__["matching_params"]["element_index"] == [0, 2] for i in net.controller.index: @@ -529,7 +530,7 @@ def test_controller(): assert set(net_eq.controller.object[i].__dict__["profile_name"]) - \ set(net.controller.object[i].__dict__["profile_name"]) == set([]) - net_eq = pp.grid_equivalents.get_equivalent(net, "rei", [4, 8], [0], + net_eq = pp.grid_equivalents.get_equivalent(net, "rei", [4, 8], [0], retain_original_internal_indices=True) assert net_eq.controller.object[0].__dict__["element_index"] == [0, 2] assert net_eq.controller.object[0].__dict__["matching_params"]["element_index"] == [0, 2] @@ -540,7 +541,7 @@ def test_controller(): ConstControl(net, element='load', variable='p_mw', element_index=[li], data_source=DFData(load_ts), profile_name=[li]) assert len(net.controller) == 4 - net_eq = pp.grid_equivalents.get_equivalent(net, "rei", [4, 8], [0], + net_eq = pp.grid_equivalents.get_equivalent(net, "rei", [4, 8], [0], retain_original_internal_indices=True) assert net_eq.controller.index.tolist() == [0, 2] @@ -556,14 +557,14 @@ def test_motor(): pp.runpp(net) values1 = net.res_bus.vm_pu.values.copy() - for eq in ["rei", "ward", "xward"]: - net_eq = pp.grid_equivalents.get_equivalent(net, eq, [4, 8], [0], + for eq in ["rei", "ward", "xward"]: + net_eq = pp.grid_equivalents.get_equivalent(net, eq, [4, 8], [0], retain_original_internal_indices=True, show_computing_time=True) - + assert max(net_eq.res_bus.vm_pu[[0,3,4,8]].values - net.res_bus.vm_pu[[0,3,4,8]].values) < 1e-8 assert net_eq.motor.bus.values.tolist() == [3, 4] - + replace_motor_by_load(net, net.bus.index.tolist()) assert len(net.motor) == 0 assert len(net.res_motor) == 0 @@ -572,7 +573,7 @@ def test_motor(): assert net.res_load.loc[4].values.tolist() == [0, 0] pp.runpp(net) values2 = net.res_bus.vm_pu.values.copy() - assert max(values1 - values2) < 1e-10 + assert max(values1 - values2) < 1e-10 def test_sgen_bswitch(): @@ -582,11 +583,11 @@ def test_sgen_bswitch(): pp.create_sgen(net, 1, 5, in_service=False) pp.runpp(net) net.sgen.name = ["aa", "bb", "cc", "dd"] - net_eq = pp.grid_equivalents.get_equivalent(net, "rei", [4, 8], [0], + net_eq = pp.grid_equivalents.get_equivalent(net, "rei", [4, 8], [0], retain_original_internal_indices=True) assert net_eq.sgen.name[0] == 'aa//cc//dd-sgen_separate_rei_1' assert net_eq.sgen.p_mw[0] == 173 - + net = pp.networks.case9() pp.replace_gen_by_sgen(net) pp.create_bus(net, 345) @@ -597,12 +598,12 @@ def test_sgen_bswitch(): pp.create_switch(net, 1, 10, "b") net.sgen.name = ["aa", "bb", "cc", "dd"] pp.runpp(net) - net_eq = pp.grid_equivalents.get_equivalent(net, "rei", [4, 8], [0], + net_eq = pp.grid_equivalents.get_equivalent(net, "rei", [4, 8], [0], retain_original_internal_indices=True) - + assert net_eq.sgen.name[0] == 'aa//cc-sgen_separate_rei_1' assert net_eq.sgen.p_mw[0] == 173 - + # add some columns for test net.bus["voltLvl"]=1 net.sgen["col_mixed"] = ["1", 2, None, True] @@ -610,11 +611,11 @@ def test_sgen_bswitch(): net.sgen["col_different_str"] = ["str_1", "str_2", "str_3", "str_4"] net.sgen["bool"] = [False, True, False, False] net.sgen["voltLvl"] = [1, 1, 1, 1] - net_eq = pp.grid_equivalents.get_equivalent(net, "rei", [4, 8], [0]) + net_eq = pp.grid_equivalents.get_equivalent(net, "rei", [4, 8], [0]) assert net_eq.sgen["col_mixed"][0] == "mixed data type" assert net_eq.sgen["col_same_str"][0] == "str_test" assert net_eq.sgen["col_different_str"][0] == "str_3//str_1" - assert net_eq.sgen["col_different_str"][1] == "str_2" + assert net_eq.sgen["col_different_str"][1] == "str_2" assert net_eq.sgen["bool"][0] == False assert net_eq.sgen["bool"][1] == True assert net_eq.sgen["voltLvl"].values.tolist() == [1, 1] @@ -624,12 +625,12 @@ def test_ward_admittance(): net = pp.networks.case9() pp.runpp(net) res_bus = net.res_bus.copy() - create_passive_external_net_for_ward_admittance(net, [1, 2, 5, 6, 7], + create_passive_external_net_for_ward_admittance(net, [1, 2, 5, 6, 7], [4,8], True, _runpp_except_voltage_angles) assert len(net.shunt)==3 assert np.allclose(net.res_bus.vm_pu.values, res_bus.vm_pu.values) - - + + if __name__ == "__main__": pytest.main(['-x', __file__]) \ No newline at end of file diff --git a/pandapower/toolbox/grid_modification.py b/pandapower/toolbox/grid_modification.py index 872d8d2cb..c6fc5f911 100644 --- a/pandapower/toolbox/grid_modification.py +++ b/pandapower/toolbox/grid_modification.py @@ -141,7 +141,7 @@ def select_subnet(net, buses, include_switch_buses=False, include_results=False, def merge_nets(net1, net2, validate=True, merge_results=True, tol=1e-9, **kwargs): """Function to concatenate two nets into one data structure. The elements keep their indices - unless both nets have the same indices. In that case, net2 elements get reindex. The reindex + unless both nets have the same indices. In that case, net2 elements get reindexed. The reindex lookup of net2 elements can be retrieved by passing return_net2_reindex_lookup=True. Parameters