diff --git a/src/power_grid_model_io/config/excel/gaia_en.yaml b/src/power_grid_model_io/config/excel/gaia_en.yaml new file mode 100644 index 00000000..ed3c91b0 --- /dev/null +++ b/src/power_grid_model_io/config/excel/gaia_en.yaml @@ -0,0 +1,682 @@ +# SPDX-FileCopyrightText: Contributors to the Power Grid Model project +# +# SPDX-License-Identifier: MPL-2.0 +--- +id_reference: + nodes_table: Nodes + number: Number + node_number: Node.Number + sub_number: Subnumber +grid: + Nodes: + node: + id: + auto_id: + key: Number + u_rated: Unom_LL + extra: + - ID + - Name + Cables: + line: + id: + auto_id: + key: Number + from_node: + auto_id: + table: Nodes + key: + Number: From.Number + from_status: From.Switch state + to_node: + auto_id: + table: Nodes + key: + Number: To.Number + to_status: To.Switch state + r1: R + x1: X + c1: C + tan1: 0 +# r0: +# power_grid_model_io.functions.both_zeros_to_nan: +# value: R0 +# other_value: X0 +# x0: +# power_grid_model_io.functions.both_zeros_to_nan: +# value: X0 +# other_value: R0 +# c0: C0 + r0: R0 + x0: X0 + c0: C0 + tan0: 0 + i_n: Inom' + extra: + - ID + - Name + Lines: + line: + id: + auto_id: + key: Number + from_node: + auto_id: + table: Nodes + key: + Number: From.Number + from_status: From.Switch state + to_node: + auto_id: + table: Nodes + key: + Number: To.Number + to_status: To.Switch state +# r1: R +# x1: X +# c1: C + tan1: 0 +# r0: +# power_grid_model_io.functions.both_zeros_to_nan: +# value: R0 +# other_value: X0 +# x0: +# power_grid_model_io.functions.both_zeros_to_nan: +# value: X0 +# other_value: R0 +# c0: C0 + r0: R0 + x0: X0 + c0: C0 + tan0: 0 + i_n: Inom' + extra: + - ID + - Name +# Links: +# link: +# id: +# auto_id: +# key: Number +# from_node: +# auto_id: +# table: Nodes +# key: +# Number: From.Number +# from_status: From.Switch state +# to_node: +# auto_id: +# table: Nodes +# key: +# Number: To.Number +# to_status: To.Switch state +# extra: +# - ID +# - Name + Reactance coils: + line: + id: + auto_id: + key: Number + from_node: + auto_id: + table: Nodes + key: + Number: From.Number + from_status: From.Switch state + to_node: + auto_id: + table: Nodes + key: + Number: To.Number + to_status: To.Switch state + r1: R + x1: X + c1: 0 + tan1: 0 + r0: + power_grid_model_io.functions.both_zeros_to_nan: + value: R0 + other_value: X0 + x0: + power_grid_model_io.functions.both_zeros_to_nan: + value: X0 + other_value: R0 + c0: 0 + tan0: 0 + i_n: Inom + extra: + - ID + - Name + Transformers: + transformer: + id: + auto_id: + key: Number + from_node: + auto_id: + table: Nodes + key: + Number: From.Number + from_status: From.Switch state + to_node: + auto_id: + table: Nodes + key: + Number: To.Number + to_status: To.Switch state + u1: Unom1 + u2: Unom2 + sn: Snom + uk: + max: + - divide: + - Pk + - Snom + - power_grid_model_io.functions.value_or_default: + value: uk + default: 0.001 + - 0.001 + pk: Pk + i0: + power_grid_model_io.functions.phase_to_phase.relative_no_load_current: + i_0: Inul + p_0: Pnul + s_nom: Snom + u_nom: Unom2 + p0: Pnul + winding_from: + power_grid_model_io.functions.phase_to_phase.get_winding_from: + conn_str: Connection + neutral_grounding: N1 + winding_to: + power_grid_model_io.functions.phase_to_phase.get_winding_to: + conn_str: Connection + neutral_grounding: N2 + clock: + power_grid_model_io.functions.phase_to_phase.get_clock: + conn_str: Connection + tap_side: Tapside + tap_pos: Tap + tap_min: Tapmin + tap_max: Tapmax + tap_nom: Tapnom + tap_size: Tapsize + uk_min: + max: + - divide: + - Pk + - Snom + - power_grid_model_io.functions.value_or_default: + value: uk + default: 0.001 + - 0.001 + uk_max: + max: + - divide: + - Pk + - Snom + - power_grid_model_io.functions.value_or_default: + value: uk + default: 0.001 + - 0.001 + pk_min: Pk + pk_max: Pk + extra: + - ID + - Name + Special transformers: + transformer: + id: + auto_id: + key: Number + from_node: + auto_id: + table: Nodes + key: + Number: From.Number + from_status: From.Switch state + to_node: + auto_id: + table: Nodes + key: + Number: To.Number + to_status: To.Switch state + u1: Unom1 + u2: Unom2 + sn: Snom + uk: + max: + - divide: + - Pknom + - Snom + - power_grid_model_io.functions.value_or_default: + value: uknom + default: 0.001 + - 0.001 + pk: Pknom + i0: + power_grid_model_io.functions.phase_to_phase.relative_no_load_current: + i_0: Io + p_0: Po + s_nom: Snom + u_nom: Unom2 + p0: Po + winding_from: 0 + winding_to: 0 + clock: 0 + tap_side: Tap side + tap_pos: Tap + tap_min: Tap min + tap_max: Tap max + tap_nom: Tap nom + tap_size: Tap size + uk_min: + max: + - divide: + - Pkmin + - Snom + - power_grid_model_io.functions.value_or_default: + value: ukmin + default: 0.001 + - 0.001 + uk_max: + max: + - divide: + - Pkmax + - Snom + - power_grid_model_io.functions.value_or_default: + value: ukmax + default: 0.001 + - 0.001 + pk_min: Pkmin + pk_max: Pkmax + r_grounding_from: 0 + x_grounding_from: 0 + r_grounding_to: 0 + x_grounding_to: 0 + extra: + - ID + - Name + Transformer loads: + transformer: + id: + auto_id: + name: transformer + key: + - Node.Number + - Subnumber + from_node: + auto_id: + table: Nodes + key: + Number: Node.Number + to_node: + auto_id: + name: internal_node + key: + - Node.Number + - Subnumber + from_status: Switch state + to_status: 1 + u1: Unom1 + u2: Unom2 + sn: Snom + uk: + max: + - divide: + - Pk + - Snom + - power_grid_model_io.functions.value_or_default: + value: uk + default: 0.001 + - 0.001 + pk: Pk + p0: Po + i0: + power_grid_model_io.functions.phase_to_phase.relative_no_load_current: + i_0: 0 + p_0: Po + s_nom: Snom + u_nom: Unom2 + winding_from: + power_grid_model_io.functions.phase_to_phase.get_winding_from: + conn_str: Connection + winding_to: + power_grid_model_io.functions.phase_to_phase.get_winding_to: + conn_str: Connection + clock: + power_grid_model_io.functions.phase_to_phase.get_clock: + conn_str: Connection + tap_side: Tap side + tap_pos: Tap + tap_min: Tap min + tap_max: Tap max + tap_nom: Tap nom + tap_size: Tap size + uk_min: + max: + - divide: + - Pk + - Snom + - power_grid_model_io.functions.value_or_default: + value: uk + default: 0.001 + - 0.001 + uk_max: + max: + - divide: + - Pk + - Snom + - power_grid_model_io.functions.value_or_default: + value: uk + default: 0.001 + - 0.001 + pk_min: Pk + pk_max: Pk + r_grounding_from: 0 + x_grounding_from: 0 + r_grounding_to: 0 + x_grounding_to: 0 + extra: + - ID + - Name + node: + id: + auto_id: + name: internal_node + key: + - Node.Number + - Subnumber + u_rated: Unom2 + extra: + - ID + - Name + sym_load: + id: + auto_id: + name: load + key: + - Node.Number + - Subnumber + node: + auto_id: + name: internal_node + key: + - Node.Number + - Subnumber + status: Switch state + type: Behaviour + p_specified: + multiply: + - Load.P + - reference: + query_column: Node.Number + other_table: Nodes + key_column: Number + value_column: Simultaneity + q_specified: + multiply: + - Load.Q + - reference: + query_column: Node.Number + other_table: Nodes + key_column: Number + value_column: Simultaneity + extra: + - ID + - Name + sym_gen: + - id: + auto_id: + name: generation + key: + - Node.Number + - Subnumber + node: + auto_id: + name: internal_node + key: + - Node.Number + - Subnumber + status: Switch state + type: 0 + p_specified: Generation.P + q_specified: Generation.Q + extra: + - ID + - Name + - id: + auto_id: + name: pv_generation + key: + - Node.Number + - Subnumber + node: + auto_id: + name: internal_node + key: + - Node.Number + - Subnumber + status: Switch state + type: 0 + p_specified: PV.Pnom + q_specified: + power_grid_model_io.functions.phase_to_phase.reactive_power: + p: PV.Pnom + cos_phi: 1 + extra: + - ID + - Name + Sources: + source: + id: + auto_id: + key: + - Node.Number + - Subnumber + node: + auto_id: + table: Nodes + key: + Number: Node.Number + status: Switch state + u_ref: Uref + sk: Sk"nom + extra: + - ID + - Name + Synchronous generators: + sym_gen: + id: + auto_id: + key: + - Node.Number + - Subnumber + node: + auto_id: + table: Nodes + key: + Number: Node.Number + status: Switch state + type: 0 + p_specified: Pref + q_specified: + multiply: + - power_grid_model_io.functions.phase_to_phase.reactive_power: + p: Pref + cos_phi: cos phi + - Q + extra: + - ID + - Name + Loads: + asym_load: + id: + auto_id: + key: + - Node.Number + - Subnumber + node: + auto_id: + table: Nodes + key: + Number: Node.Number + status: Switch state + type: Behaviour + p_specified: + power_grid_model_io.functions.phase_to_phase.three_phase_array: + power_a: Pa + power_b: Pb + power_c: Pc + q_specified: + power_grid_model_io.functions.phase_to_phase.three_phase_array: + power_a: Qa + power_b: Qb + power_c: Qc + extra: + - ID + - Name + Zigzag transformers: + shunt: + id: + auto_id: + key: + - Node.Number + - Subnumber + node: + auto_id: + table: Nodes + key: + Number: Node.Number + status: Switch state + g1: 0 + b1: 0 + g0: + power_grid_model_io.functions.complex_inverse_real_part: + real: R0 + imag: X0 + b0: + power_grid_model_io.functions.complex_inverse_imaginary_part: + real: R0 + imag: X0 + extra: + - ID + - Name + Capacitors: + shunt: + id: + auto_id: + key: + - Node.Number + - Subnumber + node: + auto_id: + table: Nodes + key: + Number: Node.Number + status: Switch state + g1: 0 + b1: + power_grid_model_io.functions.phase_to_phase.reactive_power_to_susceptance: + q: Q + u_nom: Unom + g0: 0 + b0: 0 + extra: + - ID + - Name + Reactors: + shunt: + id: + auto_id: + key: + - Node.Number + - Subnumber + node: + auto_id: + table: Nodes + key: + Number: Node.Number + status: Switch state + g1: 0 + b1: + multiply: + - power_grid_model_io.functions.phase_to_phase.reactive_power_to_susceptance: + q: Q + u_nom: Unom + - -1 + g0: 0 + b0: 0 + extra: + - ID + - Name +units: + A: null + F: + µF: 0.000_001 + V: + kV: 1_000.0 + V: 1 + VA: + kVA: 1_000.0 + MVA: 1_000_000.0 + VAR: + kvar: 1_000.0 + Mvar: 1_000_000.0 + W: + kW: 1_000.0 + MW: 1_000_000.0 + Wp: + kWp: 1_000.0 + MWp: 1_000_000.0 + m/s: null + ohm: + Ohm: 1.0 + ohm/m: + ohm/km: 0.001 + one: + pu: 1.0 + "%": 0.01 + "‰": 0.001 + +substitutions: + ".*Switch state": + "off": 0 + "in": 1 + "on": 1 + "Switch state .*": + "off": 0 + "in": 1 + "on": 1 + N1: + 0: false + 1: true + none: false + own: true + N2: + 0: false + 1: true + none: false + own: true + N3: + 0: false + 1: true + none: false + own: true + Behaviour: + Constant admittance: 1 + Constant impedance: 1 + ~Constant current: 2 + Constant power: 0 + Default: 0 + Industry: 0 + Business: 0 + Residential: 0 + Living: 0 + Tapside: + 1: 0 + 2: 1 + Synchronous generators.Q: + absorb: -1 + supply: 1 diff --git a/src/power_grid_model_io/converters/__init__.py b/src/power_grid_model_io/converters/__init__.py index 02723cf0..95af4b9c 100644 --- a/src/power_grid_model_io/converters/__init__.py +++ b/src/power_grid_model_io/converters/__init__.py @@ -8,3 +8,4 @@ from power_grid_model_io.converters.pandapower_converter import PandaPowerConverter from power_grid_model_io.converters.pgm_json_converter import PgmJsonConverter from power_grid_model_io.converters.vision_excel_converter import VisionExcelConverter +from power_grid_model_io.converters.gaia_excel_converter import GaiaExcelConverter diff --git a/src/power_grid_model_io/converters/gaia_excel_converter.py b/src/power_grid_model_io/converters/gaia_excel_converter.py new file mode 100644 index 00000000..55fd5b4b --- /dev/null +++ b/src/power_grid_model_io/converters/gaia_excel_converter.py @@ -0,0 +1,84 @@ +# SPDX-FileCopyrightText: Contributors to the Power Grid Model project +# +# SPDX-License-Identifier: MPL-2.0 +""" +Vision Excel Converter: Load data from a Vision Excel export file and use a mapping file to convert the data to PGM +""" + +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Mapping, Optional, Union + +from power_grid_model_io.converters.tabular_converter import TabularConverter +from power_grid_model_io.data_stores.vision_excel_file_store import VisionExcelFileStore + +DEFAULT_MAPPING_FILE = Path(__file__).parent.parent / "config" / "excel" / "gaia_{language:s}.yaml" + + +@dataclass +class IdReferenceFields: + """ + Data class to store langage specific reference fields. + """ + + nodes_table: str + number: str + node_number: str + sub_number: str + + +class GaiaExcelConverter(TabularConverter): + """ + Vision Excel Converter: Load data from a Vision Excel export file and use a mapping file to convert the data to PGM + """ + + def __init__(self, source_file: Optional[Union[Path, str]] = None, language: str = "en"): + mapping_file = Path(str(DEFAULT_MAPPING_FILE).format(language=language)) + if not mapping_file.exists(): + raise FileNotFoundError(f"No Vision Excel mapping available for language '{language}'") + self._id_reference: Optional[IdReferenceFields] = None + source = VisionExcelFileStore(file_path=Path(source_file)) if source_file else None + super().__init__(mapping_file=mapping_file, source=source) + + def set_mapping(self, mapping: Mapping[str, Any]) -> None: + super().set_mapping(mapping) + + if "id_reference" in mapping: + self._id_reference = IdReferenceFields(**mapping["id_reference"]) + + def get_node_id(self, number: int) -> int: + """ + Get the automatically assigned id of a node + """ + if self._id_reference is None: + raise ValueError(f"Missing ID reference definition for {type(self).__name__}.get_node_id()") + table = self._id_reference.nodes_table + key = {self._id_reference.number: number} + return self.get_id(table=table, key=key) + + def get_branch_id(self, table: str, number: int) -> int: + """ + Get the automatically assigned id of a branch (line, transformer, etc.) + """ + if self._id_reference is None: + raise ValueError(f"Missing ID reference definition for {type(self).__name__}.get_branch_id()") + key = {self._id_reference.number: number} + return self.get_id(table=table, key=key) + + def get_appliance_id(self, table: str, node_number: int, sub_number: int) -> int: + """ + Get the automatically assigned id of an appliance (source, load, etc.) + """ + if self._id_reference is None: + raise ValueError(f"Missing ID reference definition for {type(self).__name__}.get_appliance_id()") + key = {self._id_reference.node_number: node_number, self._id_reference.sub_number: sub_number} + return self.get_id(table=table, key=key) + + def get_virtual_id(self, table: str, obj_name: str, node_number: int, sub_number: int) -> int: + """ + Get the automatically assigned id of a virtual object (e.g. the internal node of a 'TransformerLoad') + """ + if self._id_reference is None: + raise ValueError(f"Missing ID reference definition for {type(self).__name__}.get_virtual_id()") + key = {self._id_reference.node_number: node_number, self._id_reference.sub_number: sub_number} + return self.get_id(table=table, name=obj_name, key=key) diff --git a/src/power_grid_model_io/converters/tabular_converter.py b/src/power_grid_model_io/converters/tabular_converter.py index 4cda8416..d6029bfb 100644 --- a/src/power_grid_model_io/converters/tabular_converter.py +++ b/src/power_grid_model_io/converters/tabular_converter.py @@ -255,10 +255,14 @@ def _convert_col_def_to_attribute( attr_data = self._parse_col_def(data=data, table=table, col_def=col_def, extra_info=extra_info) - if len(attr_data.columns) != 1: + if len(attr_data.columns) == 1: + pgm_data[attr] = attr_data.iloc[:, 0] + elif component == 'asym_load': + pgm_data[attr] = attr_data.iloc[:, 0:3] + else: raise ValueError(f"DataFrame for {component}.{attr} should contain a single column ({attr_data.columns})") - pgm_data[attr] = attr_data.iloc[:, 0] + def _handle_extra_info( self, diff --git a/src/power_grid_model_io/functions/phase_to_phase.py b/src/power_grid_model_io/functions/phase_to_phase.py index 02110331..a0e75566 100644 --- a/src/power_grid_model_io/functions/phase_to_phase.py +++ b/src/power_grid_model_io/functions/phase_to_phase.py @@ -7,6 +7,7 @@ import math +import pandas as pd import structlog from power_grid_model import WindingType @@ -30,17 +31,17 @@ def reactive_power(p: float, cos_phi: float) -> float: """ Calculate the reactive power, based on p, cosine phi. """ - return p * math.sqrt(1 - cos_phi**2) / cos_phi + return p * math.sqrt(1 - cos_phi ** 2) / cos_phi def power_wind_speed( # pylint: disable=too-many-arguments - p_nom: float, - wind_speed: float, - cut_in_wind_speed: float = 3.0, - nominal_wind_speed: float = 14.0, - cutting_out_wind_speed: float = 25.0, - cut_out_wind_speed: float = 30.0, - axis_height: float = 30.0, + p_nom: float, + wind_speed: float, + cut_in_wind_speed: float = 3.0, + nominal_wind_speed: float = 14.0, + cutting_out_wind_speed: float = 25.0, + cut_out_wind_speed: float = 30.0, + axis_height: float = 30.0, ) -> float: """ Estimate p_ref based on p_nom and wind_speed. @@ -101,7 +102,6 @@ def _get_clock_impl(conn_str: str) -> int: get_winding_2 = _get_winding(parse_trafo3_connection, "winding_2") get_winding_3 = _get_winding(parse_trafo3_connection, "winding_3") - get_clock = _get_clock(parse_trafo_connection, "clock") get_clock_12 = _get_clock(parse_trafo3_connection, "clock_12") get_clock_13 = _get_clock(parse_trafo3_connection, "clock_13") @@ -130,3 +130,10 @@ def pvs_power_adjustment(p: float, efficiency_type: str) -> float: return p * 0.95 return p + + +def three_phase_array(power_a: float, power_b: float, power_c: float) -> pd.Series: + """ + Calculate the reactive power, based on p, cosine phi. + """ + return pd.Series([power_a, power_b, power_c])