Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0
1.1
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
}
20 changes: 9 additions & 11 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,29 +34,28 @@ 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"""
def load_from_txt(self, *args: str) -> "Grid":
"""Load a grid from text"""

txt_nodes, txt_branches = self.read_txt(path)
text_lines = [line for arg in args for line in arg.strip().split("\n")]

txt_nodes, txt_branches = self.read_txt(text_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:
from_node_str, to_node_str, *comments = text_line.strip().split(" ")
from_node_str, to_node_str, *comments = text_line.strip().split()
except ValueError as err:
raise ValueError(f"Text line '{text_line}' is invalid. Skipping...") from err
comments = comments[0].split(",") if comments else []
Expand Down Expand Up @@ -116,4 +114,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)
20 changes: 18 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,21 @@ 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, *args: str):
"""Build a grid from a list of strings

See the documentation for the expected format of the txt_lines

Args:
*args (str): The lines of the grid

Examples:
>>> Grid.from_txt("1 2", "2 3", "3 4 transformer", "4 5", "S1 6")
alternative: Grid.from_txt("1 2\n2 3\n3 4 transformer\n4 5\nS1 6")
"""
return TextSource(grid_class=cls).load_from_txt(*args)

@classmethod
# pylint: disable=arguments-differ
def from_txt_file(cls, txt_file_path: Path):
Expand All @@ -416,8 +431,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
120 changes: 74 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,84 @@ 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):
grid = Grid.from_txt(
"S1 2",
"S1 3 open",
"2 7",
"3 5",
"3 6 transformer",
"5 7",
"7 8",
"8 9",
)
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_string(self):
txt_string = "S1 2\nS1 3 open\n2 7\n3 5\n3 6 transformer\n5 7\n7 8\n8 9"
assert Grid.from_txt(txt_string)

def test_from_txt_string_with_spaces(self):
txt_string = "S1 2 \nS1 3 open\n2 7\n3 5\n 3 6 transformer\n5 7\n7 8\n8 9"
assert Grid.from_txt(txt_string)

def test_from_docstring(self):
assert Grid.from_txt("""
S1 2
S1 3 open
2 7
3 5
3 6 transformer
5 7
7 8
8 9
""")

def test_from_txt_with_branch_ids(self):
grid = Grid.from_txt(
"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"
)
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):
with pytest.raises(ValueError):
Grid.from_txt("S1 2", "1 3")

def test_from_txt_with_invalid_line(self):
with pytest.raises(ValueError):
Grid.from_txt("S1")

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

def test_from_txt_with_unordered_branch_ids(self):
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")
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)