Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
112 changes: 44 additions & 68 deletions src/power_grid_model_io/converters/pandapower_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,13 +342,13 @@ def _create_output_data(self):
self._pp_buses_output()
self._pp_lines_output()
self._pp_ext_grids_output()
self._pp_loads_output()
self._pp_load_elements_output(element="load", symmetric=True)
self._pp_load_elements_output(element="ward", symmetric=True)
self._pp_load_elements_output(element="motor", symmetric=True)
self._pp_shunts_output()
self._pp_trafos_output()
self._pp_sgens_output()
self._pp_trafos3w_output()
self._pp_ward_output()
self._pp_motor_output()
self._pp_asym_gens_output()
self._pp_asym_loads_output()
# Switches derive results from branches pp_output_data and pgm_output_data of links. Hence, placed in the end.
Expand All @@ -360,13 +360,14 @@ def _create_output_data_3ph(self):
Furthermore, creates a global node lookup table, which stores nodes' voltage magnitude per unit and the voltage
angle in degrees
"""
# TODO create output_data_3ph for remaining components
# Although Pandapower itself did not implmenet res_shunt_3ph
# Since results are avaiable in PGM output, these should be converted.
# TODO create output_data_3ph for trafos3w, switches
self._pp_buses_output_3ph()
self._pp_lines_output_3ph()
self._pp_ext_grids_output_3ph()
self._pp_loads_output_3ph()
self._pp_load_elements_output(element="load", symmetric=False)
self._pp_load_elements_output(element="ward", symmetric=False)
self._pp_load_elements_output(element="motor", symmetric=False)
self._pp_shunts_output_3ph()
self._pp_trafos_output_3ph()
self._pp_sgens_output_3ph()
self._pp_asym_gens_output_3ph()
Expand Down Expand Up @@ -1560,64 +1561,35 @@ def _pp_asym_gens_output(self):

self.pp_output_data["res_asymmetric_sgen"] = pp_output_asym_gens

def _pp_loads_output(self):
def _pp_load_elements_output(self, element, symmetric):
"""
This function converts a power-grid-model Symmetrical Load output array to a Load Dataframe of PandaPower.

Returns:
a PandaPower Dataframe for the Load component
Utility function to convert output of elements represented as load
in power grid model.
element: "load", "motor" or "ward"
symmetric: True or False
"""
load_id_names = ["const_power", "const_impedance", "const_current"]
assert "res_load" not in self.pp_output_data

if (
ComponentType.sym_load not in self.pgm_output_data
or self.pgm_output_data[ComponentType.sym_load].size == 0
or ("load", load_id_names[0]) not in self.idx
):
return

# Store the results, while assuring that we are not overwriting any data
assert "res_load" not in self.pp_output_data
self.pp_output_data["res_load"] = self._pp_load_result_accumulate(
pp_component_name="load", load_id_names=load_id_names
)

def _pp_ward_output(self):
load_id_names = ["ward_const_power_load", "ward_const_impedance_load"]
assert "res_ward" not in self.pp_output_data

if (
ComponentType.sym_load not in self.pgm_output_data
or self.pgm_output_data[ComponentType.sym_load].size == 0
or ("ward", load_id_names[0]) not in self.idx
):
return

accumulated_loads = self._pp_load_result_accumulate(pp_component_name="ward", load_id_names=load_id_names)
# TODO Find a better way for mapping vm_pu from bus
# accumulated_loads["vm_pu"] = np.nan

# Store the results, while assuring that we are not overwriting any data
assert "res_ward" not in self.pp_output_data
self.pp_output_data["res_ward"] = accumulated_loads

def _pp_motor_output(self):
load_id_names = ["motor_load"]
if symmetric:
res_table = "res_" + element
else:
res_table = "res_" + element + "_3ph"

assert "res_motor" not in self.pp_output_data
if element == "load":
load_id_names = ["const_power", "const_impedance", "const_current"]
elif element == "ward":
load_id_names = ["ward_const_power_load", "ward_const_impedance_load"]
elif element == "motor":
load_id_names = ["motor_load"]

if (
ComponentType.sym_load not in self.pgm_output_data
or self.pgm_output_data[ComponentType.sym_load].size == 0
or ("motor", load_id_names[0]) not in self.idx
or (element, load_id_names[0]) not in self.idx
):
return

# Store the results, while assuring that we are not overwriting any data
assert "res_motor" not in self.pp_output_data
self.pp_output_data["res_motor"] = self._pp_load_result_accumulate(
pp_component_name="motor", load_id_names=load_id_names
assert res_table not in self.pp_output_data
self.pp_output_data[res_table] = self._pp_load_result_accumulate(
pp_component_name=element, load_id_names=load_id_names
)

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

def _pp_loads_output_3ph(self):
def _pp_shunts_output_3ph(self):
"""
This function converts a power-grid-model Symmetrical Load output array to a Load Dataframe of PandaPower.
This function converts a power-grid-model Shunt output array to a Shunt Dataframe of PandaPower.

Returns:
a PandaPower Dataframe for the Load component
a PandaPower Dataframe for the Shunt component
"""
load_id_names = ["const_power", "const_impedance", "const_current"]
if (
ComponentType.sym_load not in self.pgm_output_data
or self.pgm_output_data[ComponentType.sym_load].size == 0
or ("load", load_id_names[0]) not in self.idx
):
# TODO: create unit tests for the function
assert "res_shunt_3ph" not in self.pp_output_data

if ComponentType.shunt not in self.pgm_output_data or self.pgm_output_data[ComponentType.shunt].size == 0:
return

# Store the results, while assuring that we are not overwriting any data
assert "res_load_3ph" not in self.pp_output_data
self.pp_output_data["res_load_3ph"] = self._pp_load_result_accumulate(
pp_component_name="load", load_id_names=load_id_names
pgm_output_shunts = self.pgm_output_data[ComponentType.shunt]

pp_output_shunts = pd.DataFrame(
columns=["p_mw", "q_mvar", "vm_pu"],
index=self._get_pp_ids("shunt", pgm_output_shunts["id"]),
)
pp_output_shunts["p_mw"] = pgm_output_shunts["p"].sum() * 1e-6
pp_output_shunts["q_mvar"] = pgm_output_shunts["q"].sum() * 1e-6
# TODO Find a better way for mapping vm_pu from bus
# pp_output_shunts["vm_pu"] = np.nan
self.pp_output_data["res_shunt_3ph"] = pp_output_shunts

def _pp_asym_loads_output_3ph(self):
"""
Expand Down
107 changes: 68 additions & 39 deletions tests/unit/converters/test_pandapower_converter_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
#
# SPDX-License-Identifier: MPL-2.0

from typing import Callable, List
from unittest.mock import ANY, MagicMock, patch
from typing import Any, Callable, Dict, List
from unittest.mock import ANY, MagicMock, call, patch

import numpy as np
import pandas as pd
Expand Down Expand Up @@ -38,11 +38,14 @@ def test_create_output_data():
converter._pp_sgens_output.assert_called_once_with()
converter._pp_trafos_output.assert_called_once_with()
converter._pp_trafos3w_output.assert_called_once_with()
converter._pp_loads_output.assert_called_once_with()
expected_calls = [
call(element="load", symmetric=True),
call(element="ward", symmetric=True),
call(element="motor", symmetric=True),
]
converter._pp_load_elements_output.assert_has_calls(expected_calls)
converter._pp_asym_loads_output.assert_called_once_with()
converter._pp_asym_gens_output.assert_called_once_with()
converter._pp_motor_output.assert_called_once_with()
converter._pp_ward_output.assert_called_once_with()
converter._pp_switches_output.assert_called_once_with()


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

# Assert
assert len(converter.method_calls) == 8
assert len(converter.method_calls) == 11
converter._pp_buses_output_3ph.assert_called_once_with()
converter._pp_lines_output_3ph.assert_called_once_with()
converter._pp_ext_grids_output_3ph.assert_called_once_with()
converter._pp_sgens_output_3ph.assert_called_once_with()
converter._pp_trafos_output_3ph.assert_called_once_with()
converter._pp_loads_output_3ph.assert_called_once_with()
expected_calls = [
call(element="load", symmetric=False),
call(element="ward", symmetric=False),
call(element="motor", symmetric=False),
]
converter._pp_load_elements_output.assert_has_calls(expected_calls)
converter._pp_asym_loads_output_3ph.assert_called_once_with()
converter._pp_asym_gens_output_3ph.assert_called_once_with()


@pytest.mark.parametrize(
("create_fn", "table"),
("create_fn", "table", "create_fn_kwargs"),
[
(PandaPowerConverter._pp_buses_output, "node"),
(PandaPowerConverter._pp_lines_output, "line"),
(PandaPowerConverter._pp_ext_grids_output, "source"),
(PandaPowerConverter._pp_shunts_output, "shunt"),
(PandaPowerConverter._pp_sgens_output, "sym_gen"),
(PandaPowerConverter._pp_trafos_output, "transformer"),
(PandaPowerConverter._pp_trafos3w_output, "three_winding_transformer"),
(PandaPowerConverter._pp_loads_output, "sym_load"),
(PandaPowerConverter._pp_asym_loads_output, "asym_load"),
(PandaPowerConverter._pp_asym_gens_output, "asym_gen"),
(PandaPowerConverter._pp_ward_output, "ward"),
(PandaPowerConverter._pp_motor_output, "motor"),
(PandaPowerConverter._pp_switches_output, "link"),
(PandaPowerConverter._pp_buses_output_3ph, "node"),
(PandaPowerConverter._pp_lines_output_3ph, "line"),
(PandaPowerConverter._pp_ext_grids_output_3ph, "source"),
(PandaPowerConverter._pp_sgens_output_3ph, "sym_gen"),
(PandaPowerConverter._pp_trafos_output_3ph, "transformer"),
(PandaPowerConverter._pp_loads_output_3ph, "sym_load"),
(PandaPowerConverter._pp_asym_loads_output_3ph, "asym_load"),
(PandaPowerConverter._pp_asym_gens_output_3ph, "asym_gen"),
(PandaPowerConverter._pp_buses_output, "node", {}),
(PandaPowerConverter._pp_lines_output, "line", {}),
(PandaPowerConverter._pp_ext_grids_output, "source", {}),
(PandaPowerConverter._pp_shunts_output, "shunt", {}),
(PandaPowerConverter._pp_sgens_output, "sym_gen", {}),
(PandaPowerConverter._pp_trafos_output, "transformer", {}),
(PandaPowerConverter._pp_trafos3w_output, "three_winding_transformer", {}),
(PandaPowerConverter._pp_load_elements_output, "load", {"symmetric": True, "element": "sym_load"}),
(PandaPowerConverter._pp_load_elements_output, "ward", {"symmetric": True, "element": "ward"}),
(PandaPowerConverter._pp_load_elements_output, "motor", {"symmetric": True, "element": "motor"}),
(PandaPowerConverter._pp_asym_loads_output, "asym_load", {}),
(PandaPowerConverter._pp_asym_gens_output, "asym_gen", {}),
(PandaPowerConverter._pp_switches_output, "link", {}),
(PandaPowerConverter._pp_buses_output_3ph, "node", {}),
(PandaPowerConverter._pp_lines_output_3ph, "line", {}),
(PandaPowerConverter._pp_ext_grids_output_3ph, "source", {}),
(PandaPowerConverter._pp_sgens_output_3ph, "sym_gen", {}),
(PandaPowerConverter._pp_trafos_output_3ph, "transformer", {}),
(PandaPowerConverter._pp_load_elements_output, "load", {"symmetric": False, "element": "sym_load"}),
(PandaPowerConverter._pp_load_elements_output, "ward", {"symmetric": False, "element": "ward"}),
(PandaPowerConverter._pp_load_elements_output, "motor", {"symmetric": False, "element": "motor"}),
(PandaPowerConverter._pp_asym_loads_output_3ph, "asym_load", {}),
(PandaPowerConverter._pp_asym_gens_output_3ph, "asym_gen", {}),
],
)
def test_create_pp_output_object__empty(create_fn: Callable[[PandaPowerConverter], None], table: str):
def test_create_pp_output_object__empty(create_fn: Callable[..., None], table: str, create_fn_kwargs: Dict[str, Any]):
# Arrange: No table
converter = PandaPowerConverter()

# Act / Assert
with patch("power_grid_model_io.converters.pandapower_converter.pd.DataFrame") as mock_df:
create_fn(converter)
create_fn(converter, **create_fn_kwargs)
mock_df.assert_not_called()

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

# Act / Assert
with patch("power_grid_model_io.converters.pandapower_converter.pd.DataFrame") as mock_df:
create_fn(converter)
create_fn(converter, **create_fn_kwargs)
mock_df.assert_not_called()


Expand Down Expand Up @@ -532,15 +542,34 @@ def test_pp_load_result_accumulate__asym():


@pytest.mark.parametrize(
("output_fn", "table", "load_id_names", "result_suffix"),
("output_fn", "element", "symmetric", "table", "load_id_names", "result_suffix"),
[
(PandaPowerConverter._pp_loads_output, "load", ["const_power", "const_impedance", "const_current"], ""),
(PandaPowerConverter._pp_motor_output, "motor", ["motor_load"], ""),
(PandaPowerConverter._pp_loads_output_3ph, "load", ["const_power", "const_impedance", "const_current"], "_3ph"),
(
PandaPowerConverter._pp_load_elements_output,
"load",
True,
"load",
["const_power", "const_impedance", "const_current"],
"",
),
(PandaPowerConverter._pp_load_elements_output, "motor", True, "motor", ["motor_load"], ""),
(
PandaPowerConverter._pp_load_elements_output,
"load",
False,
"load",
["const_power", "const_impedance", "const_current"],
"_3ph",
),
],
)
def test_output_load_types(
output_fn: Callable[[PandaPowerConverter], None], table: str, load_id_names: List[str], result_suffix: str
output_fn: Callable[[PandaPowerConverter, str, bool], None],
element: str,
symmetric: bool,
table: str,
load_id_names: List[str],
result_suffix: str,
):
# Arrange
converter = PandaPowerConverter()
Expand All @@ -551,7 +580,7 @@ def test_output_load_types(
converter._pp_load_result_accumulate = MagicMock() # type: ignore

# Act
output_fn(converter)
output_fn(converter, element, symmetric)

# Assert
converter._pp_load_result_accumulate.assert_called_once_with(pp_component_name=table, load_id_names=load_id_names)
Expand All @@ -570,7 +599,7 @@ def test_output_load_ward():
converter._pp_load_result_accumulate = MagicMock()

# Act
converter._pp_ward_output()
converter._pp_load_elements_output(element="ward", symmetric=True)

# Assert
converter._pp_load_result_accumulate.assert_called_once_with(pp_component_name="ward", load_id_names=load_id_names)
Expand Down
Loading
Loading