Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 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
53 changes: 25 additions & 28 deletions src/power_grid_model_io/converters/pandapower_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2017,38 +2017,35 @@ 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"]
# since "i_from" and "i_to" are (n, 3) arrays while "u1" and "u2" are (n,) arrays, ValueError is generated
# during broadcast
ui_from = pgm_output_transformers["i_from"] * pgm_input_transformers["u1"][:, None]
ui_to = pgm_output_transformers["i_to"] * pgm_input_transformers["u2"][:, None]
# for phase wise loading, sn_ph = sn / 3, v_n = v / sqrt(3), so (i * u / sqrt(3)) / (sn / 3)
# ==> sqrt(3) * i * u / sn
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)}")

# PGM returns the average loading over the cable, but PandaPower returns the maximum of the per-phase loading.
# To make it consistent with PandaPower, overall loading will be calculated as max of above 3.
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
45 changes: 45 additions & 0 deletions tests/data/pandapower/pp_validation.py
Original file line number Diff line number Diff line change
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.195, 0.05, 0.045, 0.035, 0, type="wye")
return net
5 changes: 4 additions & 1 deletion tests/unit/converters/test_pandapower_converter_output.py
Original file line number Diff line number Diff line change
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
80 changes: 76 additions & 4 deletions tests/validation/converters/test_pandapower_converter_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from power_grid_model_io.converters import PandaPowerConverter
from power_grid_model_io.converters.pandapower_converter import PandaPowerData

from ...data.pandapower.pp_validation import pp_net, pp_net_3ph
from ...data.pandapower.pp_validation import pp_net, pp_net_3ph, pp_net_3ph_minimal_trafo
from ..utils import component_attributes_df, load_json_single_dataset

pp = pytest.importorskip("pandapower", reason="pandapower is not installed")
Expand Down Expand Up @@ -51,9 +51,10 @@ def load_and_convert_pgm_data_3ph() -> PandaPowerData:
"""
Load and convert the power_grid_model results
"""
data, extra_info = load_json_single_dataset(PGM_ASYM_OUTPUT_FILE, data_type="asym_output")
converter = PandaPowerConverter()
return converter.convert(data=data, extra_info=extra_info)
data, _ = load_json_single_dataset(PGM_ASYM_OUTPUT_FILE, data_type="asym_output")
converter = PandaPowerConverter(trafo_loading="power")
converter.load_input_data(load_validation_data_3ph(), make_extra_info=False)
return converter.convert(data=data)


@lru_cache
Expand Down Expand Up @@ -126,6 +127,74 @@ def test_generate_output_3ph(): # TODO: REMOVE THIS FUNCTION
json_converter.save(data=output_data_asym, extra_info=extra_info)


def test_output_trafos_3ph__power__with_comparison():
import numpy as np

def check_result(net):
v_hv = net.trafo.vn_hv_kv
v_lv = net.trafo.vn_lv_kv
i_max_hv = np.divide(net.trafo.sn_mva, v_hv * np.sqrt(3)) * 1e3
i_max_lv = np.divide(net.trafo.sn_mva, v_lv * np.sqrt(3)) * 1e3

i_a_hv = net.res_trafo_3ph.loc[:, "i_a_hv_ka"] * 1000
i_b_hv = net.res_trafo_3ph.loc[:, "i_b_hv_ka"] * 1000
i_c_hv = net.res_trafo_3ph.loc[:, "i_c_hv_ka"] * 1000

i_a_lv = net.res_trafo_3ph.loc[:, "i_a_lv_ka"] * 1000
i_b_lv = net.res_trafo_3ph.loc[:, "i_b_lv_ka"] * 1000
i_c_lv = net.res_trafo_3ph.loc[:, "i_c_lv_ka"] * 1000

np.testing.assert_allclose(
np.maximum(i_a_hv / i_max_hv, i_a_lv / i_max_lv) * 100, net.res_trafo_3ph.loading_a_percent
)
np.testing.assert_allclose(
np.maximum(i_b_hv / i_max_hv, i_b_lv / i_max_lv) * 100, net.res_trafo_3ph.loading_b_percent
)
np.testing.assert_allclose(
np.maximum(i_c_hv / i_max_hv, i_c_lv / i_max_lv) * 100, net.res_trafo_3ph.loading_c_percent
)

def compare_result(actual, expected, *, rtol):
np.testing.assert_allclose(actual.trafo.vn_hv_kv, expected.trafo.vn_hv_kv, rtol=rtol)
np.testing.assert_allclose(actual.trafo.vn_lv_kv, expected.trafo.vn_lv_kv, rtol=rtol)
np.testing.assert_allclose(actual.trafo.sn_mva, expected.trafo.sn_mva, rtol=rtol)
np.testing.assert_allclose(
actual.res_trafo_3ph.loc[:, "i_a_hv_ka"], expected.res_trafo_3ph.loc[:, "i_a_hv_ka"], rtol=rtol
)
np.testing.assert_allclose(
actual.res_trafo_3ph.loc[:, "i_b_hv_ka"], expected.res_trafo_3ph.loc[:, "i_b_hv_ka"], rtol=rtol
)
np.testing.assert_allclose(
actual.res_trafo_3ph.loc[:, "i_c_hv_ka"], expected.res_trafo_3ph.loc[:, "i_c_hv_ka"], rtol=rtol
)
np.testing.assert_allclose(
actual.res_trafo_3ph.loc[:, "i_a_lv_ka"], expected.res_trafo_3ph.loc[:, "i_a_lv_ka"], rtol=rtol
)
np.testing.assert_allclose(
actual.res_trafo_3ph.loc[:, "i_b_lv_ka"], expected.res_trafo_3ph.loc[:, "i_b_lv_ka"], rtol=rtol
)
np.testing.assert_allclose(
actual.res_trafo_3ph.loc[:, "i_c_lv_ka"], expected.res_trafo_3ph.loc[:, "i_c_lv_ka"], rtol=rtol
)
np.testing.assert_allclose(
actual.res_trafo_3ph.loading_a_percent, expected.res_trafo_3ph.loading_a_percent, rtol=rtol
)
np.testing.assert_allclose(
actual.res_trafo_3ph.loading_b_percent, expected.res_trafo_3ph.loading_b_percent, rtol=rtol
)
np.testing.assert_allclose(
actual.res_trafo_3ph.loading_c_percent, expected.res_trafo_3ph.loading_c_percent, rtol=rtol
)

pgm_net = pp_net_3ph_minimal_trafo()
pp_net = pp_net_3ph_minimal_trafo()
pp.runpp_pgm(pgm_net, symmetric=False)
pp.runpp_3ph(pp_net)
check_result(pgm_net)
check_result(pp_net)
compare_result(pgm_net, pp_net, rtol=0.04)


def test_output_data(output_data: Tuple[PandaPowerData, PandaPowerData]):
"""
Unit test to preload the expected and actual data
Expand Down Expand Up @@ -164,6 +233,9 @@ def test_attributes(output_data: Tuple[PandaPowerData, PandaPowerData], componen
pd.testing.assert_series_equal(actual_values, expected_values, atol=5e-4, rtol=1e-4)


# The following test only works for those components where valid data is returned by
# load_and_convert_pgm_data_3ph. since this is failing for trafo_output_3ph (returning
# from first "if", this function's output is not being tested currently.
@pytest.mark.parametrize(("component", "attribute"), component_attributes_df(load_and_convert_pgm_data_3ph()))
def test_attributes_3ph(output_data_3ph: Tuple[PandaPowerData, PandaPowerData], component: str, attribute: str):
"""
Expand Down
Loading