Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
e0fe4f2
test commit
wwadman Jan 8, 2026
8e121f9
fixing too long line
wwadman Jan 8, 2026
ae4d72b
after visionary talk with Thijs
wwadman Jan 8, 2026
8b5e693
iets doet t :)
wwadman Jan 12, 2026
402f835
grids seem ok when eyeballing in Graph Editor
wwadman Jan 12, 2026
7e79786
Second test grid ready to be tested
wwadman Jan 13, 2026
78c4788
Another test
wwadman Jan 13, 2026
4b2cf47
Testing removal of duplicate line ids
wwadman Jan 13, 2026
2ddf464
SOme formatting
wwadman Jan 13, 2026
60068b4
poe format
wwadman Jan 13, 2026
e7fbf65
Cleaner code
wwadman Jan 13, 2026
7c5a798
Poe all and questions removed
wwadman Jan 13, 2026
7df3173
DCO Remediation Commit for wander.wadman <[email protected]>
wwadman Jan 13, 2026
db61e42
add mode argument
wwadman Jan 14, 2026
16e837a
Counter empty columns
wwadman Jan 15, 2026
e9d7c36
Merge remote-tracking branch 'origin/main' into feat/merge-grids
wwadman Jan 15, 2026
de0241d
Merge branch 'main' into feat/merge-grids
Thijss Jan 21, 2026
ffb59ef
Resolving test failing
wwadman Jan 21, 2026
d9f5c75
Resolving mypy src tests
wwadman Jan 21, 2026
2e03fe5
Resolving last mypy src test
wwadman Jan 21, 2026
88d86eb
Removing comment
wwadman Jan 21, 2026
1add36c
Moved tests to test_helpers
wwadman Jan 21, 2026
b7cfa78
poe all formatting
wwadman Jan 21, 2026
ee598d6
License added
wwadman Jan 21, 2026
5af0b51
feat: add check ids
jaapschoutenalliander Jan 21, 2026
cab74ed
chore: format
jaapschoutenalliander Jan 21, 2026
c86efff
Only deepcopy arrays of the second grid instead of the entire second …
wwadman Jan 22, 2026
f2babcb
Make mode a Literal
wwadman Jan 22, 2026
1ccc534
Update src/power_grid_model_ds/_core/model/grids/base.py
wwadman Jan 26, 2026
0995388
Make mode a Literal in merge_grids
wwadman Jan 26, 2026
1804fa1
switch other_grid type to Self
wwadman Jan 26, 2026
6a1c7f0
Update src/power_grid_model_ds/_core/model/grids/_helpers.py
wwadman Jan 26, 2026
4975265
Update tests/unit/model/grids/test_helpers.py
wwadman Jan 26, 2026
8e59c9a
Hardcode nodes in first test
wwadman Jan 26, 2026
a0db913
Hardcode second test
wwadman Jan 26, 2026
43aee11
add test with extendedgrid
wwadman Jan 26, 2026
3f74247
add test of incorrect mode
wwadman Jan 26, 2026
f8b7ca5
poe cleanup
wwadman Jan 26, 2026
fbee2bd
Update tests/unit/model/grids/test_helpers.py
wwadman Jan 26, 2026
36c84fd
Improve branches test
wwadman Jan 26, 2026
83365b8
Hardcode node ids check
wwadman Jan 26, 2026
b81c2f0
Forgot remove another assertion around a check_ids() call
wwadman Jan 26, 2026
7d38c6f
add fixtures
wwadman Jan 26, 2026
1a13adb
Renaming extended grid variable name
wwadman Jan 26, 2026
e3768c3
poe cleanup
wwadman Jan 26, 2026
b4df652
Fix fixtures
wwadman Jan 26, 2026
88231ca
Cleanup
wwadman Jan 26, 2026
987a328
switch from testing array types to grid types
wwadman Jan 27, 2026
78fd69d
switch from testing array types to grid types
wwadman Jan 27, 2026
eef7c41
update doc string
wwadman Jan 27, 2026
b54506a
poe cleanup
wwadman Jan 27, 2026
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
67 changes: 66 additions & 1 deletion src/power_grid_model_ds/_core/model/grids/_helpers.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
# SPDX-FileCopyrightText: Contributors to the Power Grid Model project <[email protected]>
#
# SPDX-License-Identifier: MPL-2.0
import copy
import dataclasses
import logging
from typing import TYPE_CHECKING, Type, TypeVar

from power_grid_model_ds._core.model.arrays import (
AsymVoltageSensorArray,
Branch3Array,
BranchArray,
IdArray,
NodeArray,
SourceArray,
SymGenArray,
SymLoadArray,
SymPowerSensorArray,
SymVoltageSensorArray,
TransformerTapRegulatorArray,
)
from power_grid_model_ds._core.model.arrays.base.array import FancyArray
from power_grid_model_ds._core.model.graphs.container import GraphContainer

Expand All @@ -14,8 +28,8 @@
if TYPE_CHECKING:
from .base import Grid

G = TypeVar("G", bound="Grid")

G = TypeVar("G", bound="Grid")

logger = logging.getLogger(__name__)

Expand All @@ -42,3 +56,54 @@ def create_empty_grid(grid_class: Type[G], graph_model: type[BaseGraphModel] = R
empty_fields = grid_class._get_empty_fields() # noqa # pylint: disable=protected-access
empty_fields["graphs"] = GraphContainer.empty(graph_model=graph_model)
return grid_class(**empty_fields)


def merge_grids(grid: G, other_grid: G, mode: str) -> G:
"""See Grid.merge()"""

match mode:
case "recalculate_ids":
other_grid = copy.deepcopy(other_grid)
offset = grid.id_counter # Possible improvement: grid.id_counter - other_grid.min_id() + 1
_increment_grid_ids_by_offset(other_grid, offset)
case "keep_ids":
pass
case _:
raise NotImplementedError(f"Merge mode {mode} is not implemented")

# Append all arrays from the first grid to the second
for array in other_grid.all_arrays():
grid.append(array, check_max_id=False)

return grid


def _increment_grid_ids_by_offset(grid: G, offset: int) -> None:
for array in grid.all_arrays():
if isinstance(array, IdArray):
_update_id_column(array, "id", offset)

columns = []
match array:
case SymPowerSensorArray() | SymVoltageSensorArray() | AsymVoltageSensorArray():
columns = []
case NodeArray():
columns = ["feeder_node_id", "feeder_branch_id"]
case TransformerTapRegulatorArray():
columns = ["regulated_object"]
case BranchArray():
columns = ["from_node", "to_node", "feeder_node_id", "feeder_branch_id"]
case Branch3Array():
columns = ["node_1", "node_2", "node_3"]
case SymGenArray() | SymLoadArray() | SourceArray():
columns = ["node"]
case _:
raise NotImplementedError(f"The array of type {type(array)} is not implemented for appending")

for column in columns:
_update_id_column(array, column, offset)


def _update_id_column(array: IdArray, column: str, offset: int) -> None:
mask = array.is_empty(column)
array[column][~mask] += offset
15 changes: 15 additions & 0 deletions src/power_grid_model_ds/_core/model/grids/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from power_grid_model_ds._core.model.grids._helpers import (
create_empty_grid,
create_grid_from_extended_grid,
merge_grids,
)
from power_grid_model_ds._core.model.grids._modify import (
add_array_to_grid,
Expand Down Expand Up @@ -346,3 +347,17 @@ def cache(self, cache_dir: Path, cache_name: str, compress: bool = True):
compress (bool, optional): Whether to compress the cache. Defaults to True.
"""
return save_grid_to_pickle(self, cache_dir=cache_dir, cache_name=cache_name, compress=compress)

def merge(self, other_grid: G, mode: str) -> G:
"""Merge another grid into this grid. When ids overlap, ids of other_grid are offset to avoid conflicts.

Args:
other_grid (G): The grid to merge into this grid.
mode (str): The merge mode:
- "recalculate_ids": Recalculate ids of other_grid to avoid conflicts.
- "keep_ids": Keep ids of other_grid. Raises an error if grids contain overlapping indices.
"""

merged_grid = merge_grids(self, other_grid, mode)

return merged_grid
40 changes: 40 additions & 0 deletions tests/unit/model/grids/test_grid_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
LineArray,
LinkArray,
NodeArray,
SourceArray,
ThreeWindingTransformerArray,
TransformerArray,
TransformerTapRegulatorArray,
Expand Down Expand Up @@ -401,3 +402,42 @@ def test_from_txt_file(self, tmp_path: Path):
assert 1 == grid.branches.filter(to_status=0).size
assert 1 == grid.transformer.size
np.testing.assert_array_equal([14, 10, 11, 12, 13, 15, 16, 17], grid.branches.id)


class TestMergeGrids:
def test_merge_two_grids(self):
grid1 = Grid.from_txt("S1 2", "S1 3 link", "3 4 transformer")
grid2 = Grid.from_txt("S11 12", "S11 13 link", "13 14 transformer")
grid1_size = grid1.node.size
grid2_size = grid2.node.size

merged_grid = grid1.merge(grid2, mode="keep_ids")
merged_grid_size = merged_grid.node.size

assert merged_grid_size == grid1_size + grid2_size, "Merged grid size should be the sum of both grids' sizes"

def test_merge_two_grids_with_overlapping_node_ids(self):
grid1 = Grid.from_txt("S1 2", "S1 3 link", "3 4 transformer")
grid2 = Grid.from_txt("S1 2", "S1 13 link", "13 14 transformer")
source = SourceArray(id=[501], node=[1], status=[1], u_ref=[0.0])
grid1.append(source)
grid2.append(source)

merged_grid = grid1.merge(grid2, mode="recalculate_ids")
assert merged_grid.check_ids() is None, "Asset ids are not unique after merging!"

# Check if from and to nodes are updated by checking that their values form the entire set of node ids:
assert set(merged_grid.branches.from_node).union(merged_grid.branches.to_node) == set(merged_grid.node.id), (
"All from and to nodes should form the entire set of node ids in the merged grid!"
)

# assert node in grid.source is updated by checking if the node column contains values that are all node ids:
assert set(merged_grid.source.node).issubset(merged_grid.node.id), "All source nodes should be valid node ids!"

def test_merge_two_grids_with_overlapping_line(self):
# Now both grids have 14 as highest node id, so both will have branch ids 15, 16 and 17:
grid1 = Grid.from_txt("S1 2", "S1 3 link", "3 14 transformer")
grid2 = Grid.from_txt("S1 2", "S1 13 link", "13 14 transformer")

merged_grid = grid1.merge(grid2, mode="recalculate_ids")
assert merged_grid.check_ids() is None, "Asset ids are not unique after merging!"