Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,6 @@ PYPI_VERSION
build/
wheelhouse/
_build/

# Jupyter Notebook
.ipynb_checkpoints
53 changes: 43 additions & 10 deletions docs/examples/utils/grid_from_txt_examples.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,22 @@
"- A _transformer_ is defined as `<from_node> <to_node> transformer`\n",
" - e.g.: `8 9 transformer`\n",
"- A _grid opening_ is defined by adding `open`\n",
" - e.g.: `4 5 open` for _lines_ or `6 7 transformer,open` for _transformers_\n",
"\n",
" - e.g.: `4 5 open` for _lines_ or `6 7 transformer,open` for _transformers_\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Loading a drawn grid into pgm-ds\n",
"\n",
"Once you've created a grid, copy the _Graph Data_ of your grid to a text file (e.g. `my_grid.txt`).\n",
"There are two ways of loading a text grid into a pgm-ds:\n",
"- load grid from a .txt file\n",
"- load grid from a list of strings\n",
"\n",
"For example, your file could contain the following data:\n",
"#### Load a grid from a .txt file\n",
"Copy the _Graph Data_ of your grid to a text file (e.g. `my_grid.txt`).\n",
"For the example above, the file should contain the following data:\n",
"\n",
"```text\n",
"S1 2\n",
Expand All @@ -52,7 +61,7 @@
},
{
"cell_type": "code",
"execution_count": 1,
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -68,12 +77,36 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"**You should now have a grid loaded from your drawn graph data!**\n"
"\n",
"#### Load grid from a list of strings\n",
"You can also load a grid directly from a list of strings"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"from power_grid_model_ds import Grid\n",
"\n",
"grid = Grid.from_txt(\n",
" [\n",
" \"S1 2\",\n",
" \"S1 3 open\",\n",
" \"2 7\",\n",
" \"3 5\",\n",
" \"3 6 transformer\",\n",
" \"5 7\",\n",
" \"7 8\",\n",
" \"8 9\",\n",
" ]\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": 10,
"metadata": {},
"outputs": [
{
Expand All @@ -99,7 +132,7 @@
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
Expand All @@ -113,9 +146,9 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.3"
"version": "3.13.1"
}
},
"nbformat": 4,
"nbformat_minor": 2
"nbformat_minor": 4
}
18 changes: 6 additions & 12 deletions src/power_grid_model_ds/_core/model/grids/_text_sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"""Create a grid from text a text file"""

import logging
from pathlib import Path
from typing import TYPE_CHECKING

from power_grid_model_ds._core.model.enums.nodes import NodeType
Expand Down Expand Up @@ -35,25 +34,20 @@ class TextSource:
def __init__(self, grid_class: type["Grid"]):
self.grid = grid_class.empty()

def load_grid_from_path(self, path: Path):
"""Load assets from text file & sort them by id so that
they are ready to be appended to the grid"""

txt_nodes, txt_branches = self.read_txt(path)
def load_from_txt(self, txt_lines: list[str]) -> "Grid":
"""Load a grid from text"""
txt_nodes, txt_branches = self.read_txt(txt_lines)
self.add_nodes(txt_nodes)
self.add_branches(txt_branches)
self.grid.set_feeder_ids()
return self.grid

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

txt_nodes = set()
txt_branches = {}
for text_line in txt_rows:
for text_line in txt_lines:
if not text_line.strip() or text_line.startswith("#"):
continue # skip empty lines and comments
try:
Expand Down Expand Up @@ -116,4 +110,4 @@ def add_branch(self, branch: tuple[str, str], comments: list[str]):
new_branch.to_status = 0
else:
new_branch.to_status = 1
self.grid.append(new_branch)
self.grid.append(new_branch, check_max_id=False)
16 changes: 14 additions & 2 deletions src/power_grid_model_ds/_core/model/grids/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,17 @@ def _from_pickle(cls, pickle_path: Path):
raise TypeError(f"{pickle_path.name} is not a valid {cls.__name__} cache.")
return grid

@classmethod
def from_txt(cls, txt_lines: list[str]):
"""Build a grid from a list of strings

See the documentation for the expected format of the txt_lines

Example:
>>> Grid.from_txt(["1 2", "2 3", "3 4 transformer", "4 5", "S1 6"])
"""
return TextSource(grid_class=cls).load_from_txt(txt_lines)

@classmethod
# pylint: disable=arguments-differ
def from_txt_file(cls, txt_file_path: Path):
Expand All @@ -416,8 +427,9 @@ def from_txt_file(cls, txt_file_path: Path):
Args:
txt_file_path (Path): The path to the txt file
"""
text_source = TextSource(grid_class=cls)
return text_source.load_grid_from_path(txt_file_path)
with open(txt_file_path, "r", encoding="utf-8") as f:
txt_lines = f.readlines()
return TextSource(grid_class=cls).load_from_txt(txt_lines)

def set_feeder_ids(self):
"""Sets feeder and substation id properties in the grids arrays"""
Expand Down
129 changes: 83 additions & 46 deletions tests/unit/model/grids/test_grid_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,56 +266,93 @@ def test_grid_make_inactive_to_side(basic_grid):
assert 0 == target_line_after.to_status


def test_from_txt_file(tmp_path):
"""Test that Grid can be created from txt file"""
txt_file = tmp_path / "tmp_grid"
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")
grid = Grid.from_txt_file(txt_file)
txt_file.unlink()

assert 8 == grid.node.size
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)


def test_from_txt_file_with_branch_ids(tmp_path):
txt_file = tmp_path / "tmp_grid"
txt_file.write_text(
"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"
)
grid = Grid.from_txt_file(txt_file)
txt_file.unlink()

assert 8 == grid.node.size
assert 1 == grid.branches.filter(to_status=0).size
assert 1 == grid.transformer.size
np.testing.assert_array_equal([95, 91, 92, 93, 94, 96, 97, 98], grid.branches.id)


def test_from_txt_file_conflicting_ids(tmp_path):
txt_file = tmp_path / "tmp_grid"
txt_file.write_text("S1 2\n1 3", encoding="utf-8")

with pytest.raises(ValueError):
Grid.from_txt_file(txt_file)

txt_file.unlink()


def test_from_txt_file_with_unordered_node_ids(tmp_path):
txt_file = tmp_path / "tmp_grid"
txt_file.write_text("S1 2\nS1 10\n10 11\n2 5\n5 6\n3 4\n3 7", encoding="utf-8")
grid = Grid.from_txt_file(txt_file)
txt_file.unlink()

assert 9 == grid.node.size


def test_grid_as_str(basic_grid):
grid = basic_grid

grid_as_string = str(grid)

assert "102 106 301,transformer" in grid_as_string
assert "103 104 203,open" in grid_as_string


class TestFromTxt:
def test_from_txt_lines(self):
txt_lines = [
"S1 2",
"S1 3 open",
"2 7",
"3 5",
"3 6 transformer",
"5 7",
"7 8",
"8 9",
]
grid = Grid.from_txt(txt_lines)
assert 8 == grid.node.size
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)

def test_from_txt_with_branch_ids(self):
txt_lines = [
"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",
]

grid = Grid.from_txt(txt_lines)
assert 8 == grid.node.size
assert 1 == grid.branches.filter(to_status=0).size
assert 1 == grid.transformer.size
np.testing.assert_array_equal([95, 91, 92, 93, 94, 96, 97, 98], grid.branches.id)

def test_from_txt_with_conflicting_ids(self):
txt_lines = [
"S1 2",
"1 3",
]

with pytest.raises(ValueError):
Grid.from_txt(txt_lines)

def test_from_txt_with_unordered_node_ids(self):
txt_lines = [
"S1 2",
"S1 10",
"10 11",
"2 5",
"5 6",
"3 4",
"3 7",
]
grid = Grid.from_txt(txt_lines)
assert 9 == grid.node.size

def test_from_txt_with_unordered_branch_ids(self):
txt_lines = [
"5 6 16",
"3 4 17",
"3 7 18",
"S1 2 12",
"S1 10 13",
"10 11 14",
"2 5 15",
]
grid = Grid.from_txt(txt_lines)
assert 9 == grid.node.size

def test_from_txt_file(self, tmp_path):
txt_file = tmp_path / "tmp_grid"
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")
grid = Grid.from_txt_file(txt_file)
txt_file.unlink()

assert 8 == grid.node.size
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)