Skip to content

Commit

Permalink
Implement blueprint approach for initializing cim data structure in c…
Browse files Browse the repository at this point in the history
…im converter (e2nIEE#2269)

* implemented blueprint approach for initializing cim data structure

* add support for reading cgmes v3.0 files

* add entries to changelog
  • Loading branch information
JakobKirschner committed Apr 17, 2024
1 parent a4f1d90 commit 7fd9930
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 30 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ Change Log
- [ADDED] switch results p and q
- [ADDED] PowerFactory converter: option to export lines with sections as a single line with averaged-out impedance, or export line sections as separate individual lines
- [ADDED] extend plotly function: add zoomlevel-parameter and hvdc lines
- [ADDED] added support for reading cgmes v3.0 files
- [CHANGED] plotting for voltage profile considers also gens that are slacks and only ext_grids and slack gens that are in service
- [CHANGED] switched from setup.py to pyproject.toml
- [CHANGED] updated upload_release.py to not call setup.py anymore (see https://packaging.python.org/en/latest/discussions/setup-py-deprecated/)
- [CHANGED] updated upload_release.py to install the uploadad package and print the version
- [CHANGED] updated MANIFEST.in to exclude the ci files from the wheel distribution
- [CHANGED] cim data structure method in cim converter changed to blueprint approach
- [FIXED] massive performance drag in large grids due to initializing Ybus for FACTS with np.zeros instead of using sparse matrix initialization
- [FIXED] further futurewarnings and deprecation warnings

Expand Down
83 changes: 53 additions & 30 deletions pandapower/converter/cim/cim_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import re
import tempfile
import zipfile
from types import MappingProxyType
from typing import Dict, List
import pandas as pd
import numpy as np
Expand All @@ -19,12 +20,14 @@

class CimParser:

def __init__(self, cim: Dict[str, Dict[str, pd.DataFrame]] = None):
def __init__(self, cim: Dict[str, Dict[str, pd.DataFrame]] = None, cgmes_version: str = '2.14.5'):
"""
This class parses CIM files and loads its content to a dictionary of
CIM profile (dict) -> CIM element type (str) -> CIM elements (DataFrame)
"""
self.logger = logging.getLogger(self.__class__.__name__)
self.cgmes_version = cgmes_version
self.__cim_blueprint = self._initialize_cim_data_structure(cgmes_version)
self.cim: Dict[str, Dict[str, pd.DataFrame]] = cim if cim is not None else self.get_cim_data_structure()
self.file_names: Dict[str, str] = dict()
self.report_container = ReportContainer()
Expand Down Expand Up @@ -59,7 +62,7 @@ def parse_files(self, file_list: List[str] or str = None, encoding: str = 'utf-8
self.set_cim_data_types()
self.logger.info("Finished parsing CIM files.")
self.report_container.add_log(Report(level=LogLevel.INFO, code=ReportCode.INFO_PARSING,
message="CIM parser finished parsing CIM files."))
message="CIM parser finished parsing CIM files."))
return self

def set_cim_data_types(self) -> CimParser:
Expand Down Expand Up @@ -96,7 +99,8 @@ def set_cim_data_types(self) -> CimParser:
self.logger.debug("Setting data type of %s from CIM element %s as type %s" %
(col, cim_element_type, data_type_col_str))
if col in default_values.keys(): # todo deprecated due to repair function?
self.cim[profile][cim_element_type][col] = self.cim[profile][cim_element_type][col].fillna(value=default_values[col])
self.cim[profile][cim_element_type][col] = self.cim[profile][cim_element_type][col].fillna(
value=default_values[col])
if data_type_col == bool_type:
self.cim[profile][cim_element_type][col] = \
self.cim[profile][cim_element_type][col].map(to_bool)
Expand Down Expand Up @@ -164,14 +168,14 @@ def prepare_cim_net(self) -> CimParser:
message="CIM parser finished preparing the CIM data."))
return self

def get_cim_data_structure(self) -> Dict[str, Dict[str, pd.DataFrame]]:
def _initialize_cim16_data_structure(self) -> Dict[str, Dict[str, pd.DataFrame]]:
"""
Get the cim data structure used by the converter.
Get the cim data structure used by the converter for cgmes version less than 3.
:return Dict[str, Dict[str, pd.DataFrame]]: The cim data structure used by the converter.
"""
self.logger.debug("Returning the CIM data structure.")
return dict({
'eq': {
return MappingProxyType({
'eq': MappingProxyType({
'ControlArea': pd.DataFrame(columns=['rdfId', 'name', 'type']),
'TieFlow': pd.DataFrame(columns=['rdfId', 'Terminal', 'ControlArea', 'positiveFlowIn']),
'ConnectivityNode': pd.DataFrame(columns=['rdfId', 'name', 'description', 'ConnectivityNodeContainer']),
Expand Down Expand Up @@ -286,16 +290,16 @@ def get_cim_data_structure(self) -> Dict[str, Dict[str, pd.DataFrame]]:
'AnalogValue': pd.DataFrame(columns=[
'rdfId', 'name', 'sensorAccuracy', 'MeasurementValueSource', 'Analog', 'value']),
'MeasurementValueSource': pd.DataFrame(columns=['rdfId', 'name'])
},
'eq_bd': {
}),
'eq_bd': MappingProxyType({
'ConnectivityNode': pd.DataFrame(columns=['rdfId', 'name', 'ConnectivityNodeContainer']),
'BaseVoltage': pd.DataFrame(columns=['rdfId', 'name', 'nominalVoltage']),
'Terminal': pd.DataFrame(
columns=['rdfId', 'ConnectivityNode', 'ConductingEquipment', 'sequenceNumber']),
'EnergySource': pd.DataFrame(columns=['rdfId', 'name', 'nominalVoltage', 'EnergySchedulingType']),
'EnergySchedulingType': pd.DataFrame(columns=['rdfId', 'name'])
},
'ssh': {
}),
'ssh': MappingProxyType({
'ControlArea': pd.DataFrame(columns=['rdfId', 'netInterchange']),
'ExternalNetworkInjection': pd.DataFrame(columns=[
'rdfId', 'p', 'q', 'referencePriority', 'controlEnabled']),
Expand Down Expand Up @@ -331,36 +335,36 @@ def get_cim_data_structure(self) -> Dict[str, Dict[str, pd.DataFrame]]:
'LinearShuntCompensator': pd.DataFrame(columns=['rdfId', 'controlEnabled', 'sections']),
'NonlinearShuntCompensator': pd.DataFrame(columns=['rdfId', 'controlEnabled', 'sections']),
'EquivalentInjection': pd.DataFrame(columns=['rdfId', 'regulationTarget', 'regulationStatus', 'p', 'q'])
},
'sv': {
}),
'sv': MappingProxyType({
'SvVoltage': pd.DataFrame(columns=['rdfId', 'TopologicalNode', 'v', 'angle']),
'SvPowerFlow': pd.DataFrame(columns=['rdfId', 'Terminal', 'p', 'q']),
'SvShuntCompensatorSections': pd.DataFrame(columns=['rdfId', 'ShuntCompensator', 'sections']),
'SvTapStep': pd.DataFrame(columns=['rdfId', 'TapChanger', 'position'])
},
'tp': {
}),
'tp': MappingProxyType({
'TopologicalNode': pd.DataFrame(columns=[
'rdfId', 'name', 'description', 'ConnectivityNodeContainer', 'BaseVoltage']),
'DCTopologicalNode': pd.DataFrame(columns=['rdfId', 'name', 'DCEquipmentContainer']),
'ConnectivityNode': pd.DataFrame(columns=['rdfId', 'TopologicalNode']),
'Terminal': pd.DataFrame(columns=['rdfId', 'TopologicalNode']),
'DCTerminal': pd.DataFrame(columns=['rdfId', 'DCTopologicalNode']),
'ACDCConverterDCTerminal': pd.DataFrame(columns=['rdfId', 'DCTopologicalNode'])
},
'tp_bd': {
}),
'tp_bd': MappingProxyType({
'TopologicalNode': pd.DataFrame(columns=['rdfId', 'name', 'ConnectivityNodeContainer', 'BaseVoltage']),
'ConnectivityNode': pd.DataFrame(columns=['rdfId', 'TopologicalNode'])
},
'dl': {
}),
'dl': MappingProxyType({
'Diagram': pd.DataFrame(columns=['rdfId', 'name']),
'DiagramObject': pd.DataFrame(columns=['rdfId', 'IdentifiedObject', 'Diagram', 'name']),
'DiagramObjectPoint': pd.DataFrame(columns=[
'rdfId', 'sequenceNumber', 'xPosition', 'yPosition', 'DiagramObject'])},
'gl': {
'rdfId', 'sequenceNumber', 'xPosition', 'yPosition', 'DiagramObject'])}),
'gl': MappingProxyType({
'CoordinateSystem': pd.DataFrame(columns=['rdfId', 'name', 'crsUrn']),
'Location': pd.DataFrame(columns=['rdfId', 'PowerSystemResources', 'CoordinateSystem']),
'PositionPoint': pd.DataFrame(columns=['rdfId', 'Location', 'sequenceNumber', 'xPosition', 'yPosition'])
}})
})})

def _parse_element(self, element, parsed=None):
if parsed is None:
Expand Down Expand Up @@ -424,19 +428,20 @@ def _get_cgmes_profile_from_xml(self, root: xml.etree.ElementTree.Element, ignor
profile_list = [profile_list]
for one_profile in profile_list:
if '/EquipmentCore/' in one_profile or '/EquipmentOperation/' in one_profile or \
'/EquipmentShortCircuit/' in one_profile:
'/EquipmentShortCircuit/' in one_profile or '/CoreEquipment-EU/' in one_profile:
return 'eq'
elif '/SteadyStateHypothesis/' in one_profile:
elif '/SteadyStateHypothesis/' in one_profile or '/SteadyStateHypothesis-EU/' in one_profile:
return 'ssh'
elif '/StateVariables/' in one_profile:
elif '/StateVariables/' in one_profile or '/StateVariables-EU/' in one_profile:
return 'sv'
elif '/Topology/' in one_profile:
elif '/Topology/' in one_profile or '/Topology-EU/' in one_profile:
return 'tp'
elif '/DiagramLayout/' in one_profile:
elif '/DiagramLayout/' in one_profile or '/DiagramLayout-EU/' in one_profile:
return 'dl'
elif '/GeographicalLocation/' in one_profile:
elif '/GeographicalLocation/' in one_profile or '/GeographicalLocation-EU/' in one_profile:
return 'gl'
elif '/EquipmentBoundary/' in one_profile or '/EquipmentBoundaryOperation/' in one_profile:
elif ('/EquipmentBoundary/' in one_profile or '/EquipmentBoundaryOperation/' in one_profile
or '/EquipmentBoundary-EU/' in one_profile):
return 'eq_bd'
elif '/TopologyBoundary/' in one_profile:
return 'tp_bd'
Expand Down Expand Up @@ -512,7 +517,7 @@ def _parse_source_file(self, file: str, output: dict, encoding: str, profile_nam
prf_content[element_type_c][col].values[prf_content[element_type_c][col].str.len().idxmax()]
# remove the namespace from the literal
prf_content[element_type_c][col] = \
prf_content[element_type_c][col].str[name_space.rfind('.')+1:]
prf_content[element_type_c][col].str[name_space.rfind('.') + 1:]
elif col_new.endswith('-about'):
col_new = 'rdfId'
prf_content[element_type_c][col] = prf_content[element_type_c][col].str[1:]
Expand Down Expand Up @@ -550,3 +555,21 @@ def get_file_names(self) -> Dict[str, str]:

def get_report_container(self) -> ReportContainer:
return self.report_container

def _initialize_cim_data_structure(self, cgmes_version: str) -> Dict[str, Dict[str, pd.DataFrame]]:
if cgmes_version == '2.14.5':
return self._initialize_cim16_data_structure()
if cgmes_version == '3.0':
return self._initialize_cim100_data_structure()
raise NotImplementedError(f"CGMES version {cgmes_version} is not supported.")

def _initialize_cim100_data_structure(self) -> Dict[str, Dict[str, pd.DataFrame]]:
raise NotImplementedError(f"CGMES version {3.0} is not supported yet.")

def get_cim_data_structure(self) -> Dict[str, Dict[str, pd.DataFrame]]:
cim_data_structure = {}
for one_profile, one_profile_dict in self.__cim_blueprint.items():
cim_data_structure[one_profile] = {}
for one_class, one_class_df in one_profile_dict.items():
cim_data_structure[one_profile][one_class] = one_class_df.copy(deep=True)
return cim_data_structure

0 comments on commit 7fd9930

Please sign in to comment.