Skip to content

Commit 6593684

Browse files
committed
Merge branch 'feat/add-from-txt' into chore/refactor-graph-tests
2 parents 6960082 + 0018e2f commit 6593684

File tree

6 files changed

+148
-70
lines changed

6 files changed

+148
-70
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,6 @@ PYPI_VERSION
5555
build/
5656
wheelhouse/
5757
_build/
58+
59+
# Jupyter Notebook
60+
.ipynb_checkpoints

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.0
1+
1.1

docs/examples/utils/grid_from_txt_examples.ipynb

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,22 @@
2828
"- A _transformer_ is defined as `<from_node> <to_node> transformer`\n",
2929
" - e.g.: `8 9 transformer`\n",
3030
"- A _grid opening_ is defined by adding `open`\n",
31-
" - e.g.: `4 5 open` for _lines_ or `6 7 transformer,open` for _transformers_\n",
32-
"\n",
31+
" - e.g.: `4 5 open` for _lines_ or `6 7 transformer,open` for _transformers_\n"
32+
]
33+
},
34+
{
35+
"cell_type": "markdown",
36+
"metadata": {},
37+
"source": [
3338
"### Loading a drawn grid into pgm-ds\n",
3439
"\n",
35-
"Once you've created a grid, copy the _Graph Data_ of your grid to a text file (e.g. `my_grid.txt`).\n",
40+
"There are two ways of loading a text grid into a pgm-ds:\n",
41+
"- load grid from a .txt file\n",
42+
"- load grid from a list of strings\n",
3643
"\n",
37-
"For example, your file could contain the following data:\n",
44+
"#### Load a grid from a .txt file\n",
45+
"Copy the _Graph Data_ of your grid to a text file (e.g. `my_grid.txt`).\n",
46+
"For the example above, the file should contain the following data:\n",
3847
"\n",
3948
"```text\n",
4049
"S1 2\n",
@@ -52,7 +61,7 @@
5261
},
5362
{
5463
"cell_type": "code",
55-
"execution_count": 1,
64+
"execution_count": 9,
5665
"metadata": {},
5766
"outputs": [],
5867
"source": [
@@ -68,12 +77,36 @@
6877
"cell_type": "markdown",
6978
"metadata": {},
7079
"source": [
71-
"**You should now have a grid loaded from your drawn graph data!**\n"
80+
"\n",
81+
"#### Load grid from a list of strings\n",
82+
"You can also load a grid directly from a list of strings"
83+
]
84+
},
85+
{
86+
"cell_type": "code",
87+
"execution_count": 7,
88+
"metadata": {},
89+
"outputs": [],
90+
"source": [
91+
"from power_grid_model_ds import Grid\n",
92+
"\n",
93+
"grid = Grid.from_txt(\n",
94+
" [\n",
95+
" \"S1 2\",\n",
96+
" \"S1 3 open\",\n",
97+
" \"2 7\",\n",
98+
" \"3 5\",\n",
99+
" \"3 6 transformer\",\n",
100+
" \"5 7\",\n",
101+
" \"7 8\",\n",
102+
" \"8 9\",\n",
103+
" ]\n",
104+
")"
72105
]
73106
},
74107
{
75108
"cell_type": "code",
76-
"execution_count": 2,
109+
"execution_count": 10,
77110
"metadata": {},
78111
"outputs": [
79112
{
@@ -99,7 +132,7 @@
99132
],
100133
"metadata": {
101134
"kernelspec": {
102-
"display_name": ".venv",
135+
"display_name": "Python 3 (ipykernel)",
103136
"language": "python",
104137
"name": "python3"
105138
},
@@ -113,9 +146,9 @@
113146
"name": "python",
114147
"nbconvert_exporter": "python",
115148
"pygments_lexer": "ipython3",
116-
"version": "3.12.3"
149+
"version": "3.13.1"
117150
}
118151
},
119152
"nbformat": 4,
120-
"nbformat_minor": 2
153+
"nbformat_minor": 4
121154
}

src/power_grid_model_ds/_core/model/grids/_text_sources.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
"""Create a grid from text a text file"""
66

77
import logging
8-
from pathlib import Path
98
from typing import TYPE_CHECKING
109

1110
from power_grid_model_ds._core.model.enums.nodes import NodeType
@@ -35,29 +34,28 @@ class TextSource:
3534
def __init__(self, grid_class: type["Grid"]):
3635
self.grid = grid_class.empty()
3736

38-
def load_grid_from_path(self, path: Path):
39-
"""Load assets from text file & sort them by id so that
40-
they are ready to be appended to the grid"""
37+
def load_from_txt(self, *args: str) -> "Grid":
38+
"""Load a grid from text"""
4139

42-
txt_nodes, txt_branches = self.read_txt(path)
40+
text_lines = [line for arg in args for line in arg.strip().split("\n")]
41+
42+
txt_nodes, txt_branches = self.read_txt(text_lines)
4343
self.add_nodes(txt_nodes)
4444
self.add_branches(txt_branches)
4545
self.grid.set_feeder_ids()
4646
return self.grid
4747

4848
@staticmethod
49-
def read_txt(path: Path) -> tuple[set, dict]:
49+
def read_txt(txt_lines: list[str]) -> tuple[set, dict]:
5050
"""Extract assets from text"""
51-
with open(path, "r", encoding="utf-8") as f:
52-
txt_rows = f.readlines()
5351

5452
txt_nodes = set()
5553
txt_branches = {}
56-
for text_line in txt_rows:
54+
for text_line in txt_lines:
5755
if not text_line.strip() or text_line.startswith("#"):
5856
continue # skip empty lines and comments
5957
try:
60-
from_node_str, to_node_str, *comments = text_line.strip().split(" ")
58+
from_node_str, to_node_str, *comments = text_line.strip().split()
6159
except ValueError as err:
6260
raise ValueError(f"Text line '{text_line}' is invalid. Skipping...") from err
6361
comments = comments[0].split(",") if comments else []
@@ -116,4 +114,4 @@ def add_branch(self, branch: tuple[str, str], comments: list[str]):
116114
new_branch.to_status = 0
117115
else:
118116
new_branch.to_status = 1
119-
self.grid.append(new_branch)
117+
self.grid.append(new_branch, check_max_id=False)

src/power_grid_model_ds/_core/model/grids/base.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,21 @@ def _from_pickle(cls, pickle_path: Path):
408408
raise TypeError(f"{pickle_path.name} is not a valid {cls.__name__} cache.")
409409
return grid
410410

411+
@classmethod
412+
def from_txt(cls, *args: str):
413+
"""Build a grid from a list of strings
414+
415+
See the documentation for the expected format of the txt_lines
416+
417+
Args:
418+
*args (str): The lines of the grid
419+
420+
Examples:
421+
>>> Grid.from_txt("1 2", "2 3", "3 4 transformer", "4 5", "S1 6")
422+
alternative: Grid.from_txt("1 2\n2 3\n3 4 transformer\n4 5\nS1 6")
423+
"""
424+
return TextSource(grid_class=cls).load_from_txt(*args)
425+
411426
@classmethod
412427
# pylint: disable=arguments-differ
413428
def from_txt_file(cls, txt_file_path: Path):
@@ -416,8 +431,9 @@ def from_txt_file(cls, txt_file_path: Path):
416431
Args:
417432
txt_file_path (Path): The path to the txt file
418433
"""
419-
text_source = TextSource(grid_class=cls)
420-
return text_source.load_grid_from_path(txt_file_path)
434+
with open(txt_file_path, "r", encoding="utf-8") as f:
435+
txt_lines = f.readlines()
436+
return TextSource(grid_class=cls).load_from_txt(*txt_lines)
421437

422438
def set_feeder_ids(self):
423439
"""Sets feeder and substation id properties in the grids arrays"""

tests/unit/model/grids/test_grid_base.py

Lines changed: 74 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -266,56 +266,84 @@ def test_grid_make_inactive_to_side(basic_grid):
266266
assert 0 == target_line_after.to_status
267267

268268

269-
def test_from_txt_file(tmp_path):
270-
"""Test that Grid can be created from txt file"""
271-
txt_file = tmp_path / "tmp_grid"
272-
txt_file.write_text("S1 2\nS1 3 open\n2 7\n3 5\n3 6 transformer\n5 7\n7 8\n8 9", encoding="utf-8")
273-
grid = Grid.from_txt_file(txt_file)
274-
txt_file.unlink()
275-
276-
assert 8 == grid.node.size
277-
assert 1 == grid.branches.filter(to_status=0).size
278-
assert 1 == grid.transformer.size
279-
np.testing.assert_array_equal([14, 10, 11, 12, 13, 15, 16, 17], grid.branches.id)
280-
281-
282-
def test_from_txt_file_with_branch_ids(tmp_path):
283-
txt_file = tmp_path / "tmp_grid"
284-
txt_file.write_text(
285-
"S1 2 91\nS1 3 92,open\n2 7 93\n3 5 94\n3 6 transformer,95\n5 7 96\n7 8 97\n8 9 98", encoding="utf-8"
286-
)
287-
grid = Grid.from_txt_file(txt_file)
288-
txt_file.unlink()
289-
290-
assert 8 == grid.node.size
291-
assert 1 == grid.branches.filter(to_status=0).size
292-
assert 1 == grid.transformer.size
293-
np.testing.assert_array_equal([95, 91, 92, 93, 94, 96, 97, 98], grid.branches.id)
294-
295-
296-
def test_from_txt_file_conflicting_ids(tmp_path):
297-
txt_file = tmp_path / "tmp_grid"
298-
txt_file.write_text("S1 2\n1 3", encoding="utf-8")
299-
300-
with pytest.raises(ValueError):
301-
Grid.from_txt_file(txt_file)
302-
303-
txt_file.unlink()
304-
305-
306-
def test_from_txt_file_with_unordered_node_ids(tmp_path):
307-
txt_file = tmp_path / "tmp_grid"
308-
txt_file.write_text("S1 2\nS1 10\n10 11\n2 5\n5 6\n3 4\n3 7", encoding="utf-8")
309-
grid = Grid.from_txt_file(txt_file)
310-
txt_file.unlink()
311-
312-
assert 9 == grid.node.size
313-
314-
315269
def test_grid_as_str(basic_grid):
316270
grid = basic_grid
317271

318272
grid_as_string = str(grid)
319273

320274
assert "102 106 301,transformer" in grid_as_string
321275
assert "103 104 203,open" in grid_as_string
276+
277+
278+
class TestFromTxt:
279+
def test_from_txt_lines(self):
280+
grid = Grid.from_txt(
281+
"S1 2",
282+
"S1 3 open",
283+
"2 7",
284+
"3 5",
285+
"3 6 transformer",
286+
"5 7",
287+
"7 8",
288+
"8 9",
289+
)
290+
assert 8 == grid.node.size
291+
assert 1 == grid.branches.filter(to_status=0).size
292+
assert 1 == grid.transformer.size
293+
np.testing.assert_array_equal([14, 10, 11, 12, 13, 15, 16, 17], grid.branches.id)
294+
295+
def test_from_txt_string(self):
296+
txt_string = "S1 2\nS1 3 open\n2 7\n3 5\n3 6 transformer\n5 7\n7 8\n8 9"
297+
assert Grid.from_txt(txt_string)
298+
299+
def test_from_txt_string_with_spaces(self):
300+
txt_string = "S1 2 \nS1 3 open\n2 7\n3 5\n 3 6 transformer\n5 7\n7 8\n8 9"
301+
assert Grid.from_txt(txt_string)
302+
303+
def test_from_docstring(self):
304+
assert Grid.from_txt("""
305+
S1 2
306+
S1 3 open
307+
2 7
308+
3 5
309+
3 6 transformer
310+
5 7
311+
7 8
312+
8 9
313+
""")
314+
315+
def test_from_txt_with_branch_ids(self):
316+
grid = Grid.from_txt(
317+
"S1 2 91", "S1 3 92,open", "2 7 93", "3 5 94", "3 6 transformer,95", "5 7 96", "7 8 97", "8 9 98"
318+
)
319+
assert 8 == grid.node.size
320+
assert 1 == grid.branches.filter(to_status=0).size
321+
assert 1 == grid.transformer.size
322+
np.testing.assert_array_equal([95, 91, 92, 93, 94, 96, 97, 98], grid.branches.id)
323+
324+
def test_from_txt_with_conflicting_ids(self):
325+
with pytest.raises(ValueError):
326+
Grid.from_txt("S1 2", "1 3")
327+
328+
def test_from_txt_with_invalid_line(self):
329+
with pytest.raises(ValueError):
330+
Grid.from_txt("S1")
331+
332+
def test_from_txt_with_unordered_node_ids(self):
333+
grid = Grid.from_txt("S1 2", "S1 10", "10 11", "2 5", "5 6", "3 4", "3 7")
334+
assert 9 == grid.node.size
335+
336+
def test_from_txt_with_unordered_branch_ids(self):
337+
grid = Grid.from_txt("5 6 16", "3 4 17", "3 7 18", "S1 2 12", "S1 10 13", "10 11 14", "2 5 15")
338+
assert 9 == grid.node.size
339+
340+
def test_from_txt_file(self, tmp_path):
341+
txt_file = tmp_path / "tmp_grid"
342+
txt_file.write_text("S1 2\nS1 3 open\n2 7\n3 5\n3 6 transformer\n5 7\n7 8\n8 9", encoding="utf-8")
343+
grid = Grid.from_txt_file(txt_file)
344+
txt_file.unlink()
345+
346+
assert 8 == grid.node.size
347+
assert 1 == grid.branches.filter(to_status=0).size
348+
assert 1 == grid.transformer.size
349+
np.testing.assert_array_equal([14, 10, 11, 12, 13, 15, 16, 17], grid.branches.id)

0 commit comments

Comments
 (0)