Skip to content

Commit 0aaef37

Browse files
authored
Merge pull request #14 from SOPTIM/feature/convert-link-with-measurements
Feature/convert link with measurements #13
2 parents b150968 + 1d5dbd3 commit 0aaef37

File tree

8 files changed

+153
-2
lines changed

8 files changed

+153
-2
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "cgmes2pgm_converter"
3-
version = "0.2.3"
3+
version = "0.3.0"
44
description = "Library to convert Common Grid Model Exchange Standard (CGMES) datasets to PowerGridModel (PGM) format"
55
authors = [
66
{ name = "Lars Friedrich, Eduard Fried, Udo Schmitz", email = "powergridmodel@soptim.de" },

src/cgmes2pgm_converter/common/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
BranchMeasurements,
3333
DefaultSigma,
3434
IncompleteMeasurements,
35+
LinkAsShortLineOptions,
3536
MeasSub,
3637
MeasurementSubstitutionOptions,
3738
PassiveNodeOptions,

src/cgmes2pgm_converter/common/converter_options.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@
1515
from dataclasses import dataclass, field
1616
from enum import Enum
1717

18-
from .measurement_substitution import MeasurementSubstitutionOptions
18+
from .measurement_substitution import (
19+
LinkAsShortLineOptions,
20+
MeasurementSubstitutionOptions,
21+
)
1922
from .network_splitting import NetworkSplittingOptions
2023

2124

@@ -60,6 +63,9 @@ class ConverterOptions:
6063
network_splitting: NetworkSplittingOptions = field(
6164
default_factory=NetworkSplittingOptions
6265
)
66+
link_as_short_line: LinkAsShortLineOptions = field(
67+
default_factory=LinkAsShortLineOptions
68+
)
6369

6470
# Deprecated: support for disabling the use of generic branches has been removed
6571
# Generic branches are used for all branch types

src/cgmes2pgm_converter/common/measurement_substitution.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,23 @@ class SshSubstitutionOptions:
5353
sigma: float = 20 * 1e6
5454

5555

56+
@dataclass
57+
class LinkAsShortLineOptions:
58+
"""
59+
Generate measurements for generators and loads
60+
without an existing power measurement using SSH values.
61+
62+
Attributes:
63+
enable (bool): Whether to use SSH values for substitution.
64+
sigma (float): The default sigma value for the substituted measurements (MW).
65+
"""
66+
67+
enable: bool = False
68+
sigma_factor: float = 1.5
69+
r: float = 0.01
70+
x: float = 0.01
71+
72+
5673
@dataclass
5774
class UMeasurementSubstitutionOptions:
5875
"""

src/cgmes2pgm_converter/components/branch/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"""
1818

1919
from .equivalent_branch import EquivalentBranchBuilder
20+
from .gb_from_link import GenericBranchFromLinkBuilder
2021
from .line import LineBuilder
2122
from .link import LinkBuilder
2223
from .transformer import *
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Copyright [2025] [SOPTIM AG]
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import numpy as np
16+
from power_grid_model import ComponentType, initialize_array
17+
18+
from cgmes2pgm_converter.common import AbstractCgmesIdMapping, BranchType, CgmesDataset
19+
20+
from ..component import AbstractPgmComponentBuilder
21+
22+
23+
class GenericBranchFromLinkBuilder(AbstractPgmComponentBuilder):
24+
25+
def __init__(
26+
self,
27+
cgmes_source: CgmesDataset,
28+
id_mapping: AbstractCgmesIdMapping,
29+
data_type: str = "input",
30+
):
31+
super().__init__(cgmes_source, id_mapping, data_type)
32+
self._component_name = ComponentType.generic_branch
33+
34+
def is_active(self) -> bool:
35+
return self._converter_options.link_as_short_line.enable
36+
37+
def build_from_cgmes(self, input_data) -> tuple[np.ndarray, dict | None]:
38+
39+
filtered_sensors = [
40+
s
41+
for s in input_data[ComponentType.sym_power_sensor]
42+
if self.filter_sensor(s)
43+
]
44+
# link_with_sensors_indices = [i for i in input_data[ComponentType.sym_power_sensor]["measured_object"] if self.filter_link(i)]
45+
link_with_sensors_indices = [
46+
i
47+
for i in [m["measured_object"] for m in filtered_sensors]
48+
if self.filter_link(i)
49+
]
50+
mask = np.isin(input_data[ComponentType.link]["id"], link_with_sensors_indices)
51+
filtered_links = input_data[ComponentType.link][mask]
52+
53+
# Remove links
54+
input_data[ComponentType.link] = input_data[ComponentType.link][~mask]
55+
56+
# -> add GenericBranch
57+
arr = initialize_array(
58+
self._data_type, self.component_name(), filtered_links.shape[0]
59+
)
60+
61+
# arr["id"] = self._id_mapping.add_cgmes_iris(res["line"], res["name"])
62+
arr["id"] = filtered_links["id"]
63+
arr["from_node"] = filtered_links["from_node"]
64+
arr["to_node"] = filtered_links["to_node"]
65+
arr["from_status"] = filtered_links["from_status"]
66+
arr["to_status"] = filtered_links["to_status"]
67+
arr["r1"] = self._converter_options.link_as_short_line.r
68+
arr["x1"] = self._converter_options.link_as_short_line.x
69+
arr["g1"] = 0.0
70+
arr["b1"] = 0.0
71+
arr["k"] = 1.0
72+
arr["theta"] = 0.0
73+
74+
for s in filtered_sensors:
75+
s["p_sigma"] = (
76+
self._converter_options.link_as_short_line.sigma_factor * s["p_sigma"]
77+
)
78+
s["q_sigma"] = (
79+
self._converter_options.link_as_short_line.sigma_factor * s["q_sigma"]
80+
)
81+
s["power_sigma"] = (
82+
self._converter_options.link_as_short_line.sigma_factor
83+
* s["power_sigma"]
84+
)
85+
86+
## TODO: _type = kopieren aus extra_data von Link
87+
extra_info = {}
88+
return arr, extra_info
89+
90+
def filter_sensor(self, sensor) -> bool:
91+
meas = sensor["measured_object"]
92+
return self.filter_link(meas)
93+
94+
def filter_link(self, idx: int) -> bool:
95+
extra = self._extra_info[idx]
96+
if (
97+
extra["_type"] == "Breaker"
98+
or extra["_type"] == "Switch"
99+
or extra["_type"] == "Disconnector"
100+
):
101+
return True
102+
return False
103+
104+
def component_name(self) -> ComponentType:
105+
return self._component_name

src/cgmes2pgm_converter/components/measurement/power_sensor.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class SymPowerBuilder(AbstractPgmComponentBuilder):
3939
(SAMPLE(?_eq) as ?eq)
4040
(SAMPLE(?_tn) as ?tn)
4141
?term
42+
(SAMPLE(?_eq_type) as ?eq_type)
4243
(SAMPLE(?_p) as ?p)
4344
(SAMPLE(?_q) as ?q)
4445
(SAMPLE(?_acc_p) as ?acc_p)
@@ -62,6 +63,8 @@ class SymPowerBuilder(AbstractPgmComponentBuilder):
6263
}
6364
FILTER(?_type_p = "ThreePhaseActivePower")
6465
66+
?_eq rdf:type ?_eq_type.
67+
6568
?term cim:Terminal.TopologicalNode ?_tn.
6669
6770
$TOPO_ISLAND
@@ -509,6 +512,23 @@ def get_terminal_types(
509512
elif meas_node == node_to:
510513
terminal_types[i] = MeasuredTerminalType.branch_to
511514

515+
elif (
516+
eq_id in input_data[ComponentType.link]["id"]
517+
and self._converter_options.link_as_short_line.enable
518+
):
519+
520+
node_from = input_data[ComponentType.link]["from_node"][
521+
input_data[ComponentType.link]["id"] == eq_id
522+
]
523+
node_to = input_data[ComponentType.link]["to_node"][
524+
input_data[ComponentType.link]["id"] == eq_id
525+
]
526+
527+
if meas_node == node_from:
528+
terminal_types[i] = MeasuredTerminalType.branch_from
529+
elif meas_node == node_to:
530+
terminal_types[i] = MeasuredTerminalType.branch_to
531+
512532
elif eq_id in input_data[ComponentType.generic_branch]["id"]:
513533

514534
node_from = input_data[ComponentType.generic_branch]["from_node"][

src/cgmes2pgm_converter/converter.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ def _get_component_builders(
147147
c.SymPowerSensorIncompleteAppliance,
148148
c.SymLoadOrGenForPassiveNodeBuilder,
149149
c.SymPowerForPassiveNodeBuilder,
150+
c.GenericBranchFromLinkBuilder,
150151
]
151152
return [
152153
builder(self._datasource, self._id_mapping, self._options)

0 commit comments

Comments
 (0)