Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
ea41a57
Including subset method draft
jpalm3r Mar 26, 2026
a80d3a1
Minor refactoring
jpalm3r Mar 26, 2026
bfc2f92
Include network subset in notebook
jpalm3r Mar 26, 2026
569d75e
Include copy of reduce method
jpalm3r Mar 26, 2026
74ccf56
Include basic test for reducing network
jpalm3r Mar 26, 2026
63bdc22
Including test for inplace
jpalm3r Mar 26, 2026
8b08099
Fixing mypy issues
jpalm3r Mar 26, 2026
d30789e
merging latest changes from main
jpalm3r Apr 8, 2026
15db0b3
Adding option to filter out node data
jpalm3r Apr 14, 2026
118052d
Fix populating gridpoints
jpalm3r Apr 15, 2026
b3c24b7
Fix tests
jpalm3r Apr 15, 2026
cbf9add
Simplify new tests
jpalm3r Apr 15, 2026
15ff8b1
Add df test
jpalm3r Apr 15, 2026
6d76486
Small fixes and expanding docs
jpalm3r Apr 15, 2026
76190e8
Including to_dataset cell
jpalm3r Apr 15, 2026
11b26db
removing cell
jpalm3r Apr 15, 2026
851ad7a
refining api for selecting node and reaches subset
jpalm3r Apr 15, 2026
6c6852c
small docstring change
jpalm3r Apr 15, 2026
9f65bc8
solution for lazy matching with networkmodelresult and nodeobservation
jpalm3r Apr 15, 2026
21c54d6
Including tests
jpalm3r Apr 15, 2026
9c33eed
Update docs
jpalm3r Apr 15, 2026
0179ece
update docs
jpalm3r Apr 15, 2026
7e385c4
Including at parameter in NodeObservation
jpalm3r Apr 20, 2026
a0344ef
Apply tuple breakpoint tolerance when resolving network aliases
Copilot Apr 20, 2026
7fd014b
Make tuple alias lookup deterministic and add tolerance edge-case tests
Copilot Apr 20, 2026
1f47f56
Document deterministic tie-break in tuple alias resolution
Copilot Apr 20, 2026
f16a751
Clarify tolerance units and make tolerance tests self-describing
Copilot Apr 20, 2026
8a99715
Add tie-break coverage for tolerance-based tuple alias matching
Copilot Apr 20, 2026
5068f60
Handle empty network data and optimize subset/alias resolution paths
Copilot Apr 21, 2026
edd87d7
Avoid duplicate mutation in reduce_around when copy is false
Copilot Apr 21, 2026
a120582
Removing alias_map property
jpalm3r Apr 21, 2026
fefe0e8
Remove reduce_around
jpalm3r Apr 21, 2026
5aa25da
Removing coords test
jpalm3r Apr 21, 2026
eaec45c
remove unused imports
jpalm3r Apr 21, 2026
c77e56b
Replace "all" for sentinel value None
jpalm3r Apr 22, 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
140 changes: 92 additions & 48 deletions notebooks/Collection_systems_network.ipynb

Large diffs are not rendered by default.

83 changes: 70 additions & 13 deletions src/modelskill/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Any, Sequence, overload, TYPE_CHECKING
from copy import deepcopy

import networkx as nx
import pandas as pd
Expand Down Expand Up @@ -324,10 +325,14 @@ class Network:
"""Network built from a set of edges, with coordinate lookup and data access."""

def __init__(self, edges: Sequence[NetworkEdge]):
self._edges: dict[str, NetworkEdge] = {e.id: e for e in edges}
self._graph = self._initialize_graph()
self._alias_map = self._initialize_alias_map()
self._df = self._build_dataframe()
graph = self._generate_graph(edges)
self._initialize_network_attributes(graph)
self._edges = self._generate_edges_dict(edges)

def _initialize_network_attributes(self, graph: nx.Graph):
self._alias_map = self._generate_alias_map(graph)
self._df = self._build_dataframe(graph)
self._graph = graph.copy()

def __repr__(self) -> str:
time = self._df.index
Expand Down Expand Up @@ -360,12 +365,13 @@ def from_res1d(cls, res: str | Path | Res1D) -> Network:
>>> network = Network.from_res1d(Res1D("model.res1d"))
"""
if sys.version_info >= (3, 14):
raise NotImplementedError(f"Current version of 'mikeio1d' requires python < 3.14 and {sys.version} is being used.")

raise NotImplementedError(
f"Current version of 'mikeio1d' requires python < 3.14 and {sys.version} is being used."
)

from mikeio1d import Res1D as _Res1D
from modelskill.model.adapters._res1d import Res1DReach


if isinstance(res, (str, Path)):
path = Path(res)
if path.suffix.lower() != ".res1d":
Expand All @@ -384,11 +390,17 @@ def from_res1d(cls, res: str | Path | Res1D) -> Network:
]
return cls(edges)

def _initialize_alias_map(self) -> dict[str | tuple[str, float], int]:
return {self.graph.nodes[id]["alias"]: id for id in self.graph.nodes()}
@staticmethod
def _generate_alias_map(g: nx.Graph) -> dict[str | tuple[str, float], int]:
return {g.nodes[id]["alias"]: id for id in g.nodes()}

@staticmethod
def _generate_edges_dict(edges: Sequence[NetworkEdge]) -> dict[str, NetworkEdge]:
return {e.id: e for e in edges}

def _build_dataframe(self) -> pd.DataFrame:
df = pd.concat({k: v["data"] for k, v in self._graph.nodes.items()}, axis=1)
@staticmethod
def _build_dataframe(g: nx.Graph) -> pd.DataFrame:
df = pd.concat({k: v["data"] for k, v in g.nodes.items()}, axis=1)
df.columns = df.columns.set_names(["node", "quantity"])
df.index.name = "time"
return df.copy()
Comment thread
jpalm3r marked this conversation as resolved.
Expand Down Expand Up @@ -445,9 +457,10 @@ def quantities(self) -> list[str]:
"""
return list(self.to_dataframe().columns.get_level_values(1).unique())

def _initialize_graph(self) -> nx.Graph:
@staticmethod
def _generate_graph(edges: Sequence[NetworkEdge]) -> nx.Graph:
g0 = nx.Graph()
for edge in self._edges.values():
for edge in edges:
# 1) Add start and end nodes
for node in [edge.start, edge.end]:
node_key = node.id
Expand Down Expand Up @@ -486,6 +499,39 @@ def _initialize_graph(self) -> nx.Graph:

return nx.convert_node_labels_to_integers(g0, label_attribute="alias")

def reduce_around(self, node: int, radius: int = 5, copy: bool = True) -> None | "Network":
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
"""Select subset of data around a node.

Parameters
----------
node : int
Id of node that represents the center of the graph subset
radius : int, default 5
Number of hops around the central node of the subset
copy : bool, default
Return a copy of the network or mutate the network in place
"""

network_copy = self.copy()
graph_subset: nx.Graph = nx.ego_graph(network_copy.graph, node, radius)

Comment thread
jpalm3r marked this conversation as resolved.
Outdated
subset_edges = []
for edge in self._edges.values():
new_start = self.find(node=edge.start.id)
new_end = self.find(node=edge.end.id)
if (new_start in graph_subset.nodes) and (new_end in graph_subset.nodes):
subset_edges.append(edge)

if copy:
network_copy._initialize_network_attributes(graph_subset)
network_copy._edges = network_copy._generate_edges_dict(subset_edges)
return network_copy
else:
self._initialize_network_attributes(graph_subset)
self._edges = self._generate_edges_dict(subset_edges)
return None


@overload
def find(
self,
Expand Down Expand Up @@ -693,6 +739,17 @@ def recall(self, id: int | list[int]) -> dict[str, Any] | list[dict[str, Any]]:
return results[0]
else:
return results

def copy(self) -> "Network":
"""Create a deep copy of the Network.

Returns
-------
Network
Deep copy of the Network object
"""
return deepcopy(self)



def _make_basic_network(node_ids, time, data, quantity="WaterLevel"):
Expand Down
28 changes: 26 additions & 2 deletions tests/test_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,8 +437,32 @@ def test_matching_workflow_multiple_nodes(self, sample_network, sample_node_data
assert comparer.n_points > 0


@pytest.mark.skipif(sys.version_info >= (3, 14), reason="mikeio1d requires Python < 3.14")
@pytest.mark.skipif(
sys.version_info >= (3, 14), reason="mikeio1d requires Python < 3.14"
)
def test_open_res1d():
path_to_file = "./tests/testdata/network.res1d"
network = Network.from_res1d(path_to_file)
assert network.graph.number_of_nodes() == 259
assert network.graph.number_of_nodes() == 259


@pytest.mark.skipif(
sys.version_info >= (3, 14), reason="mikeio1d requires Python < 3.14"
)
def test_network_subset_copy():
path_to_file = "./tests/testdata/network.res1d"
network = Network.from_res1d(path_to_file)
small_network = network.reduce_around(node=52)
Comment thread
jpalm3r marked this conversation as resolved.
Outdated
assert small_network.graph.number_of_nodes() == 22
assert 52 in small_network.graph.nodes()


@pytest.mark.skipif(
sys.version_info >= (3, 14), reason="mikeio1d requires Python < 3.14"
)
def test_network_subset_inplace():
path_to_file = "./tests/testdata/network.res1d"
network = Network.from_res1d(path_to_file)
network.reduce_around(node=52, copy=False)
assert network.graph.number_of_nodes() == 22
assert 52 in network.graph.nodes()
Loading