diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d7d1eb99c8..1ae422ac9a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,7 @@ Change Log [upcoming release] - 2026-..-.. ------------------------------- +- [ADDED] set_opf_controllable parameter in from_mpc to mark converted external grids and generators as controllable - [FIXED] OPF cleaned up in case of non convergence - [FIXED] ``create_continuous_bus_index`` (and other toolbox functions relying on ``element_bus_tuples``) now also update the ``bus`` column of the ``ssc`` element table; it was previously missing from the list of bus-referencing elements - [FIXED] speedup dump_to_geojson_node_branch @@ -202,7 +203,7 @@ Change Log - [FIXED] DC OPF bug if verbose = True - [CHANGED] cim2pp conversion of SVC parameter active power: p is set to 0, instead of using the p-value from the SV-profile - [ADDED] Parellel contingency analysis using multiple cores through a multiprocessing worker pool -- [Changed] DC-line mode to handle negative values in 2 different ways, inversing the line direction or considering powerflow always to correlate with the given from_bus +- [Changed] DC-line mode to handle negative values in 2 different ways, inversing the line direction or considering powerflow always to correlate with the given from_bus - [FIXED] type annotations in create methods - [CHANGED] drop_inactive_elements performance improvements: using pandas methods instead of looping - [ADDED] add a few more tests for the grid modification functions diff --git a/pandapower/converter/matpower/from_mpc.py b/pandapower/converter/matpower/from_mpc.py index d76d350370..4893c3b9f3 100644 --- a/pandapower/converter/matpower/from_mpc.py +++ b/pandapower/converter/matpower/from_mpc.py @@ -29,6 +29,7 @@ def from_mpc( casename_mpc_file='mpc', validate_conversion=False, load_case_engine=None, + set_opf_controllable=False, **kwargs, ): """ @@ -50,6 +51,7 @@ def from_mpc( load_case_engine (object, None): External engine used to call MATPOWER `loadcase` (e.g. Oct2Py() object from matpower.start_instance()). Defaults to None. If None, parse data using matpowercaseframes.reader.parse_file. + set_opf_controllable (bool, False): If True, marks converted ``ext_grid`` and ``gen`` elements as controllable in the pandapower network. This is needed for correct conversion for OPF purposes, as MATPOWER cannot imply controllability directly for gen types. Keyword Arguments: any: are passed to :func:`from_ppc` @@ -68,12 +70,16 @@ def from_mpc( elif ending == ".m": ppc = _m2ppc(mpc_file, load_case_engine=load_case_engine) net = from_ppc(ppc, f_hz=f_hz, validate_conversion=validate_conversion, **kwargs) + if set_opf_controllable: + if len(net.ext_grid) > 0: + net.ext_grid["controllable"] = True + if len(net.gen) > 0: + net.gen["controllable"] = True if "mpc_additional_data" in ppc: if "_options" not in net: net["_options"] = {} net._options.update(ppc["mpc_additional_data"]) logger.info('added fields %s in net._options' % list(ppc["mpc_additional_data"].keys())) - return net diff --git a/pandapower/converter/pypower/from_ppc.py b/pandapower/converter/pypower/from_ppc.py index 18e7e1dd6e..9703665ef4 100644 --- a/pandapower/converter/pypower/from_ppc.py +++ b/pandapower/converter/pypower/from_ppc.py @@ -67,7 +67,6 @@ def from_ppc(ppc, f_hz=50, validate_conversion=False, **kwargs) -> pandapowerNet logger.setLevel(logging.DEBUG) if not validate_from_ppc(ppc, net, **kwargs): logger.error("Validation failed.") - return net diff --git a/pandapower/test/converter/test_from_mpc.py b/pandapower/test/converter/test_from_mpc.py index da9f1935df..8346a150ad 100644 --- a/pandapower/test/converter/test_from_mpc.py +++ b/pandapower/test/converter/test_from_mpc.py @@ -55,6 +55,27 @@ def test_from_mpc_m(): assert case24_m.converged assert nets_equal(case24_mat, case24_m) +@pytest.mark.skipif(not matpowercaseframes_imported, + reason="matpowercaseframes is needed to convert .m files.") +def test_from_mpc_opf_controllable(): + this_folder = os.path.join(pp_dir, "test", "converter") + mat_case = os.path.join(this_folder, 'case24_ieee_rts.mat') + mat_net = from_mpc( + str(mat_case), + validate_conversion=False, + set_opf_controllable=True, + ) + assert mat_net.ext_grid["controllable"].all() + assert mat_net.gen["controllable"].all() + + m_case = os.path.join(this_folder, 'case24_ieee_rts.m') + m_net = from_mpc( + str(m_case), + validate_conversion=False, + set_opf_controllable=True, + ) + assert m_net.ext_grid["controllable"].all() + assert m_net.gen["controllable"].all() if __name__ == '__main__': pytest.main([__file__, "-xs"]) diff --git a/pandapower/test/converter/test_from_ppc_opf_controllable.py b/pandapower/test/converter/test_from_ppc_opf_controllable.py new file mode 100644 index 0000000000..1398f56354 --- /dev/null +++ b/pandapower/test/converter/test_from_ppc_opf_controllable.py @@ -0,0 +1,36 @@ +import numpy as np + +from pandapower.converter.pypower import from_ppc + +def test_from_ppc_opf_controllable(): + ppc = { + "version": "2", + "baseMVA": 100.0, + "bus": np.array( + [ + [0, 3, 0, 0, 0, 0, 1, 1.0, 0.0, 110.0, 1, 1.1, 0.9], + [1, 2, 0, 0, 0, 0, 1, 1.0, 0.0, 110.0, 1, 1.1, 0.9], + ], + dtype=float, + ), + "branch": np.array( + [ + [0, 1, 0.01, 0.05, 0, 100, 100, 100, 0, 0, 1, -30, 30], + ], + dtype=float, + ), + "gen": np.array( + [ + [0, 50, 0, 100, -100, 1.0, 100, 1, 100, 0], + [1, 20, 0, 100, -100, 1.0, 100, 1, 100, 0], + ], + dtype=float, + ), + } + + net = from_ppc(ppc, validate_conversion=False, set_opf_controllable=True) + + assert len(net.ext_grid) == 1 + assert len(net.gen) == 1 + assert net.ext_grid["controllable"].all() + assert net.gen["controllable"].all() \ No newline at end of file