Skip to content

Commit dffa47b

Browse files
authored
Merge pull request #328 from furqan463/pp_converter
[Improvement] Convert all supported elements' outputs with asymmetric load flow
2 parents 2cd812f + ab4b470 commit dffa47b

File tree

3 files changed

+276
-107
lines changed

3 files changed

+276
-107
lines changed

src/power_grid_model_io/converters/pandapower_converter.py

Lines changed: 44 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -342,13 +342,13 @@ def _create_output_data(self):
342342
self._pp_buses_output()
343343
self._pp_lines_output()
344344
self._pp_ext_grids_output()
345-
self._pp_loads_output()
345+
self._pp_load_elements_output(element="load", symmetric=True)
346+
self._pp_load_elements_output(element="ward", symmetric=True)
347+
self._pp_load_elements_output(element="motor", symmetric=True)
346348
self._pp_shunts_output()
347349
self._pp_trafos_output()
348350
self._pp_sgens_output()
349351
self._pp_trafos3w_output()
350-
self._pp_ward_output()
351-
self._pp_motor_output()
352352
self._pp_asym_gens_output()
353353
self._pp_asym_loads_output()
354354
# Switches derive results from branches pp_output_data and pgm_output_data of links. Hence, placed in the end.
@@ -360,13 +360,14 @@ def _create_output_data_3ph(self):
360360
Furthermore, creates a global node lookup table, which stores nodes' voltage magnitude per unit and the voltage
361361
angle in degrees
362362
"""
363-
# TODO create output_data_3ph for remaining components
364-
# Although Pandapower itself did not implmenet res_shunt_3ph
365-
# Since results are avaiable in PGM output, these should be converted.
363+
# TODO create output_data_3ph for trafos3w, switches
366364
self._pp_buses_output_3ph()
367365
self._pp_lines_output_3ph()
368366
self._pp_ext_grids_output_3ph()
369-
self._pp_loads_output_3ph()
367+
self._pp_load_elements_output(element="load", symmetric=False)
368+
self._pp_load_elements_output(element="ward", symmetric=False)
369+
self._pp_load_elements_output(element="motor", symmetric=False)
370+
self._pp_shunts_output_3ph()
370371
self._pp_trafos_output_3ph()
371372
self._pp_sgens_output_3ph()
372373
self._pp_asym_gens_output_3ph()
@@ -1560,64 +1561,35 @@ def _pp_asym_gens_output(self):
15601561

15611562
self.pp_output_data["res_asymmetric_sgen"] = pp_output_asym_gens
15621563

1563-
def _pp_loads_output(self):
1564+
def _pp_load_elements_output(self, element, symmetric):
15641565
"""
1565-
This function converts a power-grid-model Symmetrical Load output array to a Load Dataframe of PandaPower.
1566-
1567-
Returns:
1568-
a PandaPower Dataframe for the Load component
1566+
Utility function to convert output of elements represented as load
1567+
in power grid model.
1568+
element: "load", "motor" or "ward"
1569+
symmetric: True or False
15691570
"""
1570-
load_id_names = ["const_power", "const_impedance", "const_current"]
1571-
assert "res_load" not in self.pp_output_data
1572-
1573-
if (
1574-
ComponentType.sym_load not in self.pgm_output_data
1575-
or self.pgm_output_data[ComponentType.sym_load].size == 0
1576-
or ("load", load_id_names[0]) not in self.idx
1577-
):
1578-
return
1579-
1580-
# Store the results, while assuring that we are not overwriting any data
1581-
assert "res_load" not in self.pp_output_data
1582-
self.pp_output_data["res_load"] = self._pp_load_result_accumulate(
1583-
pp_component_name="load", load_id_names=load_id_names
1584-
)
1585-
1586-
def _pp_ward_output(self):
1587-
load_id_names = ["ward_const_power_load", "ward_const_impedance_load"]
1588-
assert "res_ward" not in self.pp_output_data
1589-
1590-
if (
1591-
ComponentType.sym_load not in self.pgm_output_data
1592-
or self.pgm_output_data[ComponentType.sym_load].size == 0
1593-
or ("ward", load_id_names[0]) not in self.idx
1594-
):
1595-
return
1596-
1597-
accumulated_loads = self._pp_load_result_accumulate(pp_component_name="ward", load_id_names=load_id_names)
1598-
# TODO Find a better way for mapping vm_pu from bus
1599-
# accumulated_loads["vm_pu"] = np.nan
1600-
1601-
# Store the results, while assuring that we are not overwriting any data
1602-
assert "res_ward" not in self.pp_output_data
1603-
self.pp_output_data["res_ward"] = accumulated_loads
1604-
1605-
def _pp_motor_output(self):
1606-
load_id_names = ["motor_load"]
1571+
if symmetric:
1572+
res_table = "res_" + element
1573+
else:
1574+
res_table = "res_" + element + "_3ph"
16071575

1608-
assert "res_motor" not in self.pp_output_data
1576+
if element == "load":
1577+
load_id_names = ["const_power", "const_impedance", "const_current"]
1578+
elif element == "ward":
1579+
load_id_names = ["ward_const_power_load", "ward_const_impedance_load"]
1580+
elif element == "motor":
1581+
load_id_names = ["motor_load"]
16091582

16101583
if (
16111584
ComponentType.sym_load not in self.pgm_output_data
16121585
or self.pgm_output_data[ComponentType.sym_load].size == 0
1613-
or ("motor", load_id_names[0]) not in self.idx
1586+
or (element, load_id_names[0]) not in self.idx
16141587
):
16151588
return
1616-
16171589
# Store the results, while assuring that we are not overwriting any data
1618-
assert "res_motor" not in self.pp_output_data
1619-
self.pp_output_data["res_motor"] = self._pp_load_result_accumulate(
1620-
pp_component_name="motor", load_id_names=load_id_names
1590+
assert res_table not in self.pp_output_data
1591+
self.pp_output_data[res_table] = self._pp_load_result_accumulate(
1592+
pp_component_name=element, load_id_names=load_id_names
16211593
)
16221594

16231595
def _pp_load_result_accumulate(self, pp_component_name: str, load_id_names: List[str]) -> pd.DataFrame:
@@ -2131,26 +2103,30 @@ def _pp_trafos_output_3ph(self): # pylint: disable=too-many-statements
21312103
assert "res_trafo_3ph" not in self.pp_output_data
21322104
self.pp_output_data["res_trafo_3ph"] = pp_output_trafos_3ph
21332105

2134-
def _pp_loads_output_3ph(self):
2106+
def _pp_shunts_output_3ph(self):
21352107
"""
2136-
This function converts a power-grid-model Symmetrical Load output array to a Load Dataframe of PandaPower.
2108+
This function converts a power-grid-model Shunt output array to a Shunt Dataframe of PandaPower.
21372109
21382110
Returns:
2139-
a PandaPower Dataframe for the Load component
2111+
a PandaPower Dataframe for the Shunt component
21402112
"""
2141-
load_id_names = ["const_power", "const_impedance", "const_current"]
2142-
if (
2143-
ComponentType.sym_load not in self.pgm_output_data
2144-
or self.pgm_output_data[ComponentType.sym_load].size == 0
2145-
or ("load", load_id_names[0]) not in self.idx
2146-
):
2113+
# TODO: create unit tests for the function
2114+
assert "res_shunt_3ph" not in self.pp_output_data
2115+
2116+
if ComponentType.shunt not in self.pgm_output_data or self.pgm_output_data[ComponentType.shunt].size == 0:
21472117
return
21482118

2149-
# Store the results, while assuring that we are not overwriting any data
2150-
assert "res_load_3ph" not in self.pp_output_data
2151-
self.pp_output_data["res_load_3ph"] = self._pp_load_result_accumulate(
2152-
pp_component_name="load", load_id_names=load_id_names
2119+
pgm_output_shunts = self.pgm_output_data[ComponentType.shunt]
2120+
2121+
pp_output_shunts = pd.DataFrame(
2122+
columns=["p_mw", "q_mvar", "vm_pu"],
2123+
index=self._get_pp_ids("shunt", pgm_output_shunts["id"]),
21532124
)
2125+
pp_output_shunts["p_mw"] = pgm_output_shunts["p"].sum() * 1e-6
2126+
pp_output_shunts["q_mvar"] = pgm_output_shunts["q"].sum() * 1e-6
2127+
# TODO Find a better way for mapping vm_pu from bus
2128+
# pp_output_shunts["vm_pu"] = np.nan
2129+
self.pp_output_data["res_shunt_3ph"] = pp_output_shunts
21542130

21552131
def _pp_asym_loads_output_3ph(self):
21562132
"""

tests/unit/converters/test_pandapower_converter_output.py

Lines changed: 68 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
#
33
# SPDX-License-Identifier: MPL-2.0
44

5-
from typing import Callable, List
6-
from unittest.mock import ANY, MagicMock, patch
5+
from typing import Any, Callable, Dict, List
6+
from unittest.mock import ANY, MagicMock, call, patch
77

88
import numpy as np
99
import pandas as pd
@@ -38,11 +38,14 @@ def test_create_output_data():
3838
converter._pp_sgens_output.assert_called_once_with()
3939
converter._pp_trafos_output.assert_called_once_with()
4040
converter._pp_trafos3w_output.assert_called_once_with()
41-
converter._pp_loads_output.assert_called_once_with()
41+
expected_calls = [
42+
call(element="load", symmetric=True),
43+
call(element="ward", symmetric=True),
44+
call(element="motor", symmetric=True),
45+
]
46+
converter._pp_load_elements_output.assert_has_calls(expected_calls)
4247
converter._pp_asym_loads_output.assert_called_once_with()
4348
converter._pp_asym_gens_output.assert_called_once_with()
44-
converter._pp_motor_output.assert_called_once_with()
45-
converter._pp_ward_output.assert_called_once_with()
4649
converter._pp_switches_output.assert_called_once_with()
4750

4851

@@ -54,58 +57,65 @@ def test_create_output_data_3ph():
5457
PandaPowerConverter._create_output_data_3ph(self=converter) # type: ignore
5558

5659
# Assert
57-
assert len(converter.method_calls) == 8
60+
assert len(converter.method_calls) == 11
5861
converter._pp_buses_output_3ph.assert_called_once_with()
5962
converter._pp_lines_output_3ph.assert_called_once_with()
6063
converter._pp_ext_grids_output_3ph.assert_called_once_with()
6164
converter._pp_sgens_output_3ph.assert_called_once_with()
6265
converter._pp_trafos_output_3ph.assert_called_once_with()
63-
converter._pp_loads_output_3ph.assert_called_once_with()
66+
expected_calls = [
67+
call(element="load", symmetric=False),
68+
call(element="ward", symmetric=False),
69+
call(element="motor", symmetric=False),
70+
]
71+
converter._pp_load_elements_output.assert_has_calls(expected_calls)
6472
converter._pp_asym_loads_output_3ph.assert_called_once_with()
6573
converter._pp_asym_gens_output_3ph.assert_called_once_with()
6674

6775

6876
@pytest.mark.parametrize(
69-
("create_fn", "table"),
77+
("create_fn", "table", "create_fn_kwargs"),
7078
[
71-
(PandaPowerConverter._pp_buses_output, "node"),
72-
(PandaPowerConverter._pp_lines_output, "line"),
73-
(PandaPowerConverter._pp_ext_grids_output, "source"),
74-
(PandaPowerConverter._pp_shunts_output, "shunt"),
75-
(PandaPowerConverter._pp_sgens_output, "sym_gen"),
76-
(PandaPowerConverter._pp_trafos_output, "transformer"),
77-
(PandaPowerConverter._pp_trafos3w_output, "three_winding_transformer"),
78-
(PandaPowerConverter._pp_loads_output, "sym_load"),
79-
(PandaPowerConverter._pp_asym_loads_output, "asym_load"),
80-
(PandaPowerConverter._pp_asym_gens_output, "asym_gen"),
81-
(PandaPowerConverter._pp_ward_output, "ward"),
82-
(PandaPowerConverter._pp_motor_output, "motor"),
83-
(PandaPowerConverter._pp_switches_output, "link"),
84-
(PandaPowerConverter._pp_buses_output_3ph, "node"),
85-
(PandaPowerConverter._pp_lines_output_3ph, "line"),
86-
(PandaPowerConverter._pp_ext_grids_output_3ph, "source"),
87-
(PandaPowerConverter._pp_sgens_output_3ph, "sym_gen"),
88-
(PandaPowerConverter._pp_trafos_output_3ph, "transformer"),
89-
(PandaPowerConverter._pp_loads_output_3ph, "sym_load"),
90-
(PandaPowerConverter._pp_asym_loads_output_3ph, "asym_load"),
91-
(PandaPowerConverter._pp_asym_gens_output_3ph, "asym_gen"),
79+
(PandaPowerConverter._pp_buses_output, "node", {}),
80+
(PandaPowerConverter._pp_lines_output, "line", {}),
81+
(PandaPowerConverter._pp_ext_grids_output, "source", {}),
82+
(PandaPowerConverter._pp_shunts_output, "shunt", {}),
83+
(PandaPowerConverter._pp_sgens_output, "sym_gen", {}),
84+
(PandaPowerConverter._pp_trafos_output, "transformer", {}),
85+
(PandaPowerConverter._pp_trafos3w_output, "three_winding_transformer", {}),
86+
(PandaPowerConverter._pp_load_elements_output, "load", {"symmetric": True, "element": "sym_load"}),
87+
(PandaPowerConverter._pp_load_elements_output, "ward", {"symmetric": True, "element": "ward"}),
88+
(PandaPowerConverter._pp_load_elements_output, "motor", {"symmetric": True, "element": "motor"}),
89+
(PandaPowerConverter._pp_asym_loads_output, "asym_load", {}),
90+
(PandaPowerConverter._pp_asym_gens_output, "asym_gen", {}),
91+
(PandaPowerConverter._pp_switches_output, "link", {}),
92+
(PandaPowerConverter._pp_buses_output_3ph, "node", {}),
93+
(PandaPowerConverter._pp_lines_output_3ph, "line", {}),
94+
(PandaPowerConverter._pp_ext_grids_output_3ph, "source", {}),
95+
(PandaPowerConverter._pp_sgens_output_3ph, "sym_gen", {}),
96+
(PandaPowerConverter._pp_trafos_output_3ph, "transformer", {}),
97+
(PandaPowerConverter._pp_load_elements_output, "load", {"symmetric": False, "element": "sym_load"}),
98+
(PandaPowerConverter._pp_load_elements_output, "ward", {"symmetric": False, "element": "ward"}),
99+
(PandaPowerConverter._pp_load_elements_output, "motor", {"symmetric": False, "element": "motor"}),
100+
(PandaPowerConverter._pp_asym_loads_output_3ph, "asym_load", {}),
101+
(PandaPowerConverter._pp_asym_gens_output_3ph, "asym_gen", {}),
92102
],
93103
)
94-
def test_create_pp_output_object__empty(create_fn: Callable[[PandaPowerConverter], None], table: str):
104+
def test_create_pp_output_object__empty(create_fn: Callable[..., None], table: str, create_fn_kwargs: Dict[str, Any]):
95105
# Arrange: No table
96106
converter = PandaPowerConverter()
97107

98108
# Act / Assert
99109
with patch("power_grid_model_io.converters.pandapower_converter.pd.DataFrame") as mock_df:
100-
create_fn(converter)
110+
create_fn(converter, **create_fn_kwargs)
101111
mock_df.assert_not_called()
102112

103113
# Arrange: Empty table
104114
converter.pgm_output_data[table] = np.array([]) # type: ignore
105115

106116
# Act / Assert
107117
with patch("power_grid_model_io.converters.pandapower_converter.pd.DataFrame") as mock_df:
108-
create_fn(converter)
118+
create_fn(converter, **create_fn_kwargs)
109119
mock_df.assert_not_called()
110120

111121

@@ -532,15 +542,34 @@ def test_pp_load_result_accumulate__asym():
532542

533543

534544
@pytest.mark.parametrize(
535-
("output_fn", "table", "load_id_names", "result_suffix"),
545+
("output_fn", "element", "symmetric", "table", "load_id_names", "result_suffix"),
536546
[
537-
(PandaPowerConverter._pp_loads_output, "load", ["const_power", "const_impedance", "const_current"], ""),
538-
(PandaPowerConverter._pp_motor_output, "motor", ["motor_load"], ""),
539-
(PandaPowerConverter._pp_loads_output_3ph, "load", ["const_power", "const_impedance", "const_current"], "_3ph"),
547+
(
548+
PandaPowerConverter._pp_load_elements_output,
549+
"load",
550+
True,
551+
"load",
552+
["const_power", "const_impedance", "const_current"],
553+
"",
554+
),
555+
(PandaPowerConverter._pp_load_elements_output, "motor", True, "motor", ["motor_load"], ""),
556+
(
557+
PandaPowerConverter._pp_load_elements_output,
558+
"load",
559+
False,
560+
"load",
561+
["const_power", "const_impedance", "const_current"],
562+
"_3ph",
563+
),
540564
],
541565
)
542566
def test_output_load_types(
543-
output_fn: Callable[[PandaPowerConverter], None], table: str, load_id_names: List[str], result_suffix: str
567+
output_fn: Callable[[PandaPowerConverter, str, bool], None],
568+
element: str,
569+
symmetric: bool,
570+
table: str,
571+
load_id_names: List[str],
572+
result_suffix: str,
544573
):
545574
# Arrange
546575
converter = PandaPowerConverter()
@@ -551,7 +580,7 @@ def test_output_load_types(
551580
converter._pp_load_result_accumulate = MagicMock() # type: ignore
552581

553582
# Act
554-
output_fn(converter)
583+
output_fn(converter, element, symmetric)
555584

556585
# Assert
557586
converter._pp_load_result_accumulate.assert_called_once_with(pp_component_name=table, load_id_names=load_id_names)
@@ -570,7 +599,7 @@ def test_output_load_ward():
570599
converter._pp_load_result_accumulate = MagicMock()
571600

572601
# Act
573-
converter._pp_ward_output()
602+
converter._pp_load_elements_output(element="ward", symmetric=True)
574603

575604
# Assert
576605
converter._pp_load_result_accumulate.assert_called_once_with(pp_component_name="ward", load_id_names=load_id_names)

0 commit comments

Comments
 (0)