Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c5e2b54
fix/ PandaPower Transformer Loading
furqan463 Aug 24, 2025
e44385c
Merge pull request #1 from furqan463/furqan463-patch-1
furqan463 Aug 24, 2025
9902db6
Apply suggestion from @mgovers
mgovers Aug 25, 2025
daa8a57
Apply suggestion from @mgovers
mgovers Aug 25, 2025
a1c585a
reformat
mgovers Aug 25, 2025
97a55a9
fix typos
mgovers Aug 25, 2025
9929f27
fix line lengths
mgovers Aug 25, 2025
e43a8f6
Removed tiling, using array broadcast instead.
furqan463 Aug 25, 2025
ebb154a
Total Loading is max of phase wise loading
furqan463 Aug 25, 2025
1954ccd
Highlight a bug in Pandapower_Converter validation test
furqan463 Aug 25, 2025
e82714c
PandaPower_Converter: Fix Transformer Loading Calculation by current …
furqan463 Aug 26, 2025
b0fa174
PandaPower_Converter: Fix Transformer Loading Calculation by current …
furqan463 Aug 26, 2025
72d4578
Merge branch 'main' into main
mgovers Sep 2, 2025
fe83bf2
add new validation test + cleanup
mgovers Sep 2, 2025
087a32c
Update src/power_grid_model_io/converters/pandapower_converter.py
mgovers Sep 2, 2025
2fae6d0
ruff format
mgovers Sep 2, 2025
c328ae8
Pandapower_Converter: corrected total 3ph_line loading with max of lo…
furqan463 Sep 2, 2025
aaf2e66
format
mgovers Sep 3, 2025
ba52c39
update tests to cover both cases of trafo_loading options, added sepe…
furqan463 Sep 3, 2025
a398aa0
Validation tests for input_pgm_line
furqan463 Sep 4, 2025
7b82673
Remove resolved comments.
furqan463 Sep 4, 2025
1cecec4
Formatting.
furqan463 Sep 4, 2025
40d3a73
resolved test_pandapower_converter_ouput to completely test trafo and…
furqan463 Sep 4, 2025
6ce871a
fix test
mgovers Sep 4, 2025
4c23843
format
mgovers Sep 4, 2025
ec9ebeb
catch warnings instead of filter warnings
mgovers Sep 5, 2025
42cf4bc
actually resolve all warning messages
mgovers Sep 5, 2025
8ffafd1
Upload data file
furqan463 Sep 5, 2025
7e5aa8b
TODOs
furqan463 Sep 5, 2025
6788b51
Add license file, run pre-commit
furqan463 Sep 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 39 additions & 39 deletions src/power_grid_model_io/converters/pandapower_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,11 +422,12 @@ def _create_pgm_input_lines(self):
pgm_lines["x1"] = self._get_pp_attr("line", "x_ohm_per_km", expected_type="f8") * multiplier
pgm_lines["c1"] = c_nf_per_km * length_km * parallel * 1e-9
# The formula for tan1 = R_1 / Xc_1 = (g * 1e-6) / (2 * pi * f * c * 1e-9) = g / (2 * pi * f * c * 1e-3)
pgm_lines["tan1"] = (
self._get_pp_attr("line", "g_us_per_km", expected_type="f8", default=0)
/ c_nf_per_km
/ (2 * np.pi * self.system_frequency * 1e-3)
pgm_lines["tan1"] = np.divide(
self._get_pp_attr("line", "g_us_per_km", expected_type="f8", default=0),
c_nf_per_km * (2 * np.pi * self.system_frequency * 1e-3),
where=c_nf_per_km != 0.0,
)
pgm_lines["tan1"][np.equal(c_nf_per_km, 0.0)] = 0.0
pgm_lines["i_n"] = (
(self._get_pp_attr("line", "max_i_ka", expected_type="f8", default=np.nan) * 1e3)
* self._get_pp_attr("line", "df", expected_type="f8", default=1)
Expand All @@ -435,11 +436,12 @@ def _create_pgm_input_lines(self):
pgm_lines["r0"] = self._get_pp_attr("line", "r0_ohm_per_km", expected_type="f8", default=np.nan) * multiplier
pgm_lines["x0"] = self._get_pp_attr("line", "x0_ohm_per_km", expected_type="f8", default=np.nan) * multiplier
pgm_lines["c0"] = c0_nf_per_km * length_km * parallel * 1e-9
pgm_lines["tan0"] = (
self._get_pp_attr("line", "g0_us_per_km", expected_type="f8", default=0)
/ c0_nf_per_km
/ (2 * np.pi * self.system_frequency * 1e-3)
pgm_lines["tan0"] = np.divide(
self._get_pp_attr("line", "g0_us_per_km", expected_type="f8", default=0),
c0_nf_per_km * (2 * np.pi * self.system_frequency * 1e-3),
where=c0_nf_per_km != 0.0,
)
pgm_lines["tan0"][np.equal(c0_nf_per_km, 0.0)] = 0.0
assert ComponentType.line not in self.pgm_input_data
self.pgm_input_data[ComponentType.line] = pgm_lines

Expand Down Expand Up @@ -1382,7 +1384,9 @@ def _pp_trafos_output(self):
if self.trafo_loading == "current":
ui_from = pgm_output_transformers["i_from"] * pgm_input_transformers["u1"]
ui_to = pgm_output_transformers["i_to"] * pgm_input_transformers["u2"]
loading = np.maximum(ui_from, ui_to) / pgm_input_transformers["sn"] * loading_multiplier * 1e2
loading = (
(np.sqrt(3) * np.maximum(ui_from, ui_to) / pgm_input_transformers["sn"]) * loading_multiplier * 1e2
)
elif self.trafo_loading == "power":
loading = pgm_output_transformers["loading"] * loading_multiplier * 1e2
else:
Expand Down Expand Up @@ -1704,14 +1708,16 @@ def join_currents(table: str, bus_name: str, i_name: str) -> pd.DataFrame:
)
pp_switches_output = pp_switches_output[["i_ka"]]
pp_switches_output.set_index(pp_switches_output_index, inplace=True)
pp_switches_output["loading_percent"] = np.nan

# For et=b, ie bus to bus switches, links are created. get result from them
if not links_absent:
links = self.pgm_output_data[ComponentType.link]
# For links, i_from = i_to = i_ka / 1e3
link_ids = self._get_pp_ids("switch", links["id"], "b2b_switches")
pp_switches_output.loc[link_ids, "i_ka"] = links["i_from"] * 1e-3
in_ka = self.pp_input_data["switch"]["in_ka"].values
pp_switches_output["loading_percent"] = np.nan
pp_switches_output["loading_percent"] = np.divide(pp_switches_output["i_ka"], in_ka, where=in_ka != 0)

assert "res_switch" not in self.pp_output_data
self.pp_output_data["res_switch"] = pp_switches_output
Expand Down Expand Up @@ -1942,7 +1948,10 @@ def _pp_lines_output_3ph(self):
pp_output_lines_3ph["loading_c_percent"] = (
np.maximum(pp_output_lines_3ph["i_c_from_ka"], pp_output_lines_3ph["i_c_to_ka"]) / pgm_input_lines["i_n"]
) * 1e5
pp_output_lines_3ph["loading_percent"] = pgm_output_lines["loading"] * 1e2
pp_output_lines_3ph["loading_percent"] = np.maximum(
np.maximum(pp_output_lines_3ph["loading_a_percent"], pp_output_lines_3ph["loading_b_percent"]),
pp_output_lines_3ph["loading_c_percent"],
)

assert "res_line_3ph" not in self.pp_output_data
self.pp_output_data["res_line_3ph"] = pp_output_lines_3ph
Expand Down Expand Up @@ -2017,38 +2026,29 @@ def _pp_trafos_output_3ph(self): # pylint: disable=too-many-statements
# Only derating factor used here. Sn is already being multiplied by parallel
loading_multiplier = pp_input_transformers["df"] * 1e2
if self.trafo_loading == "current":
ui_from = pgm_output_transformers["i_from"] * pgm_input_transformers["u1"]
ui_to = pgm_output_transformers["i_to"] * pgm_input_transformers["u2"]
loading_a_percent = np.maximum(ui_from[:, 0], ui_to[:, 0]) / pgm_input_transformers["sn"]
loading_b_percent = np.maximum(ui_from[:, 1], ui_to[:, 1]) / pgm_input_transformers["sn"]
loading_c_percent = np.maximum(ui_from[:, 2], ui_to[:, 2]) / pgm_input_transformers["sn"]
loading = np.maximum(np.sum(ui_from, axis=1), np.sum(ui_to, axis=1)) / pgm_input_transformers["sn"]
ui_from = pgm_output_transformers["i_from"] * pgm_input_transformers["u1"][:, None]
ui_to = pgm_output_transformers["i_to"] * pgm_input_transformers["u2"][:, None]
loading_a_percent = np.sqrt(3) * np.maximum(ui_from[:, 0], ui_to[:, 0]) / pgm_input_transformers["sn"]
loading_b_percent = np.sqrt(3) * np.maximum(ui_from[:, 1], ui_to[:, 1]) / pgm_input_transformers["sn"]
loading_c_percent = np.sqrt(3) * np.maximum(ui_from[:, 2], ui_to[:, 2]) / pgm_input_transformers["sn"]
elif self.trafo_loading == "power":
loading_a_percent = (
np.maximum(
pgm_output_transformers["s_from"][:, 0],
pgm_output_transformers["s_to"][:, 0],
)
/ pgm_output_transformers["s_n"]
)
loading_b_percent = (
np.maximum(
pgm_output_transformers["s_from"][:, 1],
pgm_output_transformers["s_to"][:, 1],
)
/ pgm_output_transformers["s_n"]
)
loading_c_percent = (
np.maximum(
pgm_output_transformers["s_from"][:, 2],
pgm_output_transformers["s_to"][:, 2],
)
/ pgm_output_transformers["s_n"]
)
loading = pgm_output_transformers["loading"]
loading_a_percent = np.maximum(
pgm_output_transformers["s_from"][:, 0],
pgm_output_transformers["s_to"][:, 0],
) / (pgm_input_transformers["sn"] / 3)
loading_b_percent = np.maximum(
pgm_output_transformers["s_from"][:, 1],
pgm_output_transformers["s_to"][:, 1],
) / (pgm_input_transformers["sn"] / 3)
loading_c_percent = np.maximum(
pgm_output_transformers["s_from"][:, 2],
pgm_output_transformers["s_to"][:, 2],
) / (pgm_input_transformers["sn"] / 3)
else:
raise ValueError(f"Invalid transformer loading type: {str(self.trafo_loading)}")

loading = np.maximum(np.maximum(loading_a_percent, loading_b_percent), loading_c_percent)

pp_output_trafos_3ph = pd.DataFrame(
columns=[
"p_a_hv_mw",
Expand Down
2 changes: 1 addition & 1 deletion tests/data/pandapower/pp_v2_net_output.json
Original file line number Diff line number Diff line change
Expand Up @@ -2708,7 +2708,7 @@
"res_switch": {
"_module": "pandas.core.frame",
"_class": "DataFrame",
"_object": "{\"columns\":[\"i_ka\",\"loading_percent\"],\"index\":[101,3021,321],\"data\":[[0.064048243266779,null],[null,null],[0.020545341606839,null]]}",
"_object": "{\"columns\":[\"i_ka\",\"loading_percent\"],\"index\":[101,3021,321],\"data\":[[0.064048243266779,null],[0.0,null],[0.020545341606839,null]]}",
"orient": "split",
"dtype": {
"i_ka": "float64",
Expand Down
49 changes: 47 additions & 2 deletions tests/data/pandapower/pp_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

@lru_cache
def pp_net() -> pp.pandapowerNet:
# (ext #1) shunt - [104] - 3w - [105] - sym_gen
# (ext #1) shunt - [104] - 3w - [105] - asym_gen, asym_load
# | |
# [101] ---OO- [102] ---------- [103]
# | |
Expand Down Expand Up @@ -109,7 +109,7 @@ def pp_net_3ph() -> pp.pandapowerNet:
"""
Creates a pandapower net used for validating 3 phase calculations

(ext #1) shunt - [104], sym_gen - [105], motor, ward, asym_load, asym_gen
(ext #1) sym_gen , asym_load, asym_gen
| |
[101] ---OO- [102] ---------- [103]
| |
Expand Down Expand Up @@ -192,3 +192,48 @@ def pp_net_3ph() -> pp.pandapowerNet:
# )

return net


def pp_net_3ph_minimal_trafo():
net = pp.create_empty_network("test", f_hz=50)
pp.create_bus(net, 11, "source")
pp.create_bus(net, 11, "TF_HT")
pp.create_bus(net, 0.4, "TF_LT")

pp.create_ext_grid(net, 0, 1.0, 0, s_sc_max_mva=100, rx_max=0.1, x0x_max=1, r0x0_max=0.1)
pp.create_line_from_parameters(
net,
0,
1,
2,
0.33,
0.34,
0.001,
600,
r0_ohm_per_km=0.66,
x0_ohm_per_km=0.65,
c0_nf_per_km=0.001,
g_us_per_km=0,
g0_us_per_km=0,
)
pp.create_transformer_from_parameters(
net,
1,
2,
1,
11,
0.415,
0,
4,
0,
0.01,
-30,
vector_group="Dyn",
vk0_percent=4,
vkr0_percent=0,
mag0_percent=100,
mag0_rx=0,
si0_hv_partial=0.9,
)
pp.create_asymmetric_load(net, 2, 0.2, 0.19, 0.21, 0.05, 0.049, 0.052, 0, type="wye")
return net
8 changes: 5 additions & 3 deletions tests/unit/converters/test_pandapower_converter_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -572,9 +572,11 @@ def test_create_pgm_input_lines(mock_init_array: MagicMock, two_pp_objs, convert
)
pgm.assert_any_call(
"tan1",
_get_pp_attr("line", "g_us_per_km", expected_type="f8", default=0)
/ _get_pp_attr("line", "c_nf_per_km", expected_type="f8")
/ (np.pi / 10),
np.divide(
_get_pp_attr("line", "g_us_per_km", expected_type="f8", default=0),
_get_pp_attr("line", "c_nf_per_km", expected_type="f8") * (np.pi / 10),
where=_get_pp_attr("line", "c_nf_per_km", expected_type="f8") != 0.0,
),
)
pgm.assert_any_call(
"i_n",
Expand Down
7 changes: 5 additions & 2 deletions tests/unit/converters/test_pandapower_converter_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,7 @@ def test_pp_switch_output():
"element": [10, 10, 10, 10, 10, 11, 11, 77, 88],
"et": ["t", "t", "t3", "t3", "t3", "l", "l", "b", "b"],
"closed": [True, True, True, True, True, True, True, True, True],
"in_ka": [np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan],
},
index=[40, 41, 42, 43, 44, 45, 46, 47, 48],
),
Expand Down Expand Up @@ -863,7 +864,6 @@ def test_output_line_3ph(converter):
mock_pgm_array.__getitem__.assert_any_call("q_to")
mock_pgm_array.__getitem__.assert_any_call("i_from")
mock_pgm_array.__getitem__.assert_any_call("i_to")
mock_pgm_array.__getitem__.assert_any_call("loading")

# assignment
mock_pp_df.return_value.__setitem__.assert_any_call("p_a_from_mw", ANY)
Expand Down Expand Up @@ -1034,7 +1034,10 @@ def test_output_trafos_3ph__power(converter):
converter._pp_trafos_output_3ph()

# retrieval
mock_pgm_array.__getitem__.assert_any_call("loading")
assert "loading" not in mock_pgm_array.__getitem__.call_args_list, (
"The convention for loading differs between PGM and PP and the PGM loading therefore should not be used "
"for conversion to PP."
)

# result
converter.pp_output_data.__setitem__.assert_called_once_with("res_trafo_3ph", mock_pp_df.return_value)
Expand Down
17 changes: 15 additions & 2 deletions tests/validation/converters/test_pandapower_converter_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@

import pandas as pd
import pytest
from power_grid_model import DatasetType
from power_grid_model import ComponentType, DatasetType
from power_grid_model.data_types import SingleDataset

from power_grid_model_io.converters import PandaPowerConverter
from power_grid_model_io.data_types import ExtraInfo
from power_grid_model_io.utils.json import JsonEncoder

from ...data.pandapower.pp_validation import pp_net
from ...data.pandapower.pp_validation import pp_net, pp_net_3ph_minimal_trafo
from ..utils import compare_extra_info, component_attributes, component_objects, load_json_single_dataset, select_values

VALIDATION_FILE = Path(__file__).parents[2] / "data" / "pandapower" / "pgm_input_data.json"
Expand Down Expand Up @@ -116,3 +116,16 @@ def test_extra_info__serializable(extra_info):

# Assert
json.dumps(actual, cls=JsonEncoder) # expect no exception


@pytest.mark.filterwarnings("error:.*invalid value encountered in divide.*:RuntimeWarning")
def test_pgm_input_lines__cnf_zero():
pp_network = pp_net_3ph_minimal_trafo()
pp_converter = PandaPowerConverter()
pp_network.line.c_nf_per_km = 0
data, _ = pp_converter.load_input_data(pp_network)
assert data[ComponentType.line]["tan1"] == 0
pp_network.line.c_nf_per_km = 0.001
pp_network.line.c0_nf_per_km = 0
data, _ = pp_converter.load_input_data(pp_network)
assert data[ComponentType.line]["tan0"] == 0
Loading
Loading