You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Update the user guide and notebook for the boundary-edge model
Document the zero-length boundary edges and the two remaining NetworkNode
properties, and warn that length-weighted networkx algorithms treat a 0.0
edge as a free hop rather than erroring. Drop the boundary property from
the subclassing examples and the notebook.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/user-guide/network.qmd
+16-14Lines changed: 16 additions & 14 deletions
Original file line number
Diff line number
Diff line change
@@ -27,7 +27,6 @@ uv add modelskill[networks]
27
27
28
28
import pandas as pd
29
29
import numpy as np
30
-
from typing import Any
31
30
from modelskill.network import Network, NetworkNode, NetworkReach, ReachBreakPoint
32
31
33
32
@@ -46,10 +45,6 @@ class ExampleNode(NetworkNode):
46
45
def data(self) -> pd.DataFrame:
47
46
return self._data
48
47
49
-
@property
50
-
def boundary(self) -> dict[str, Any]:
51
-
return {}
52
-
53
48
54
49
class ExampleReach(NetworkReach):
55
50
"""Reach connecting two nodes with a given length."""
@@ -242,9 +237,21 @@ For the same reason, `resx=` merges node quantities only. Its reach-level quanti
242
237
Node timeseries, `to_dataframe()`, `to_dataset()`, `find(node=...)` and `recall()` are unaffected.
243
238
:::
244
239
245
-
A MIKE 1D network contains multiple levels that are unified into a generic network structure as depicted in the image below. The image introduces concepts like _find_, _recall_ and _boundary_ which are explained in the following sections.
240
+
A MIKE 1D network contains multiple levels that are unified into a generic network structure as depicted in the image below. The image introduces the _find_ and _recall_ concepts explained in the following sections.
241
+
242
+

243
+
244
+
::: {.callout-note}
245
+
## Zero-length boundary edges
246
+
247
+
A reach's first and last gridpoint sit at the same location as its start/end node, so each becomes a breakpoint connected to that node by an edge of length `0.0`. That edge carries `boundary=True` in `network.graph`, distinguishing it from an ordinary segment:
246
248
247
-

249
+
```{python}
250
+
[d for *_, d in network.graph.edges(data=True) if d["boundary"]][:2]
251
+
```
252
+
253
+
If you run a length-weighted `networkx` algorithm on `network.graph` (e.g. shortest path), it will silently treat these as free hops rather than erroring — unlike an edge with `length=None` (see the EPANET callout above), a `0.0` weight is valid input and networkx has no reason to reject it. Filter on `boundary` first if you need distances that only count real reach segments.
254
+
:::
248
255
249
256
#### Selective loading
250
257
@@ -470,7 +477,7 @@ Use `ReachObservation` when your measured quantity is representative of the whol
470
477
471
478
In case you have your network data in a format that is not included in [Building a Network](#building-a-network), you can assemble a `Network` object by subclassing the abstract base classes `NetworkNode` and `NetworkReach`.
472
479
473
-
`NetworkNode` requires three properties: `id`, `data`, and `boundary`.
480
+
`NetworkNode` requires two properties: `id`and `data`.
474
481
`NetworkReach` requires four: `id`, `start`, `end`, and `breakpoints`.
475
482
476
483
`NetworkReach.length` is optional and defaults to `None`. Reach length matters in some domains (rivers, sewer networks) and not in others (link-node water distribution models), so override it only where a length exists. Where it is left undefined, the reach contributes an edge with `length=None` to `network.graph`, which keeps length-weighted graph algorithms from quietly treating the reach as free. Nothing else in modelskill reads the length — matching and extraction work from break point distances alone.
@@ -481,7 +488,6 @@ The following is a simple implementation example:
481
488
```python
482
489
import pandas as pd
483
490
import numpy as np
484
-
from typing import Any
485
491
from modelskill.network import NetworkNode, NetworkReach, Network
486
492
487
493
@@ -500,10 +506,6 @@ class ExampleNode(NetworkNode):
500
506
defdata(self) -> pd.DataFrame:
501
507
returnself._data
502
508
503
-
@property
504
-
defboundary(self) -> dict[str, Any]:
505
-
return {}
506
-
507
509
508
510
classExampleReach(NetworkReach):
509
511
"""Reach connecting two nodes with a given length."""
@@ -540,7 +542,7 @@ class ExampleReach(NetworkReach):
540
542
```
541
543
542
544
::: {.callout-tip}
543
-
The three abstract properties that **every**`NetworkNode` subclass must implement are `id`, `data`and `boundary`. If `boundary` is not relevant for your use case, define the property to return an empty dictionary, as in the example above. Similarly, a`NetworkReach` with no intermediate points can return an empty `breakpoints` list, and one with no meaningful length can leave the `length` property out altogether.
545
+
The two abstract properties that **every**`NetworkNode` subclass must implement are `id`and `data`. A`NetworkReach` with no intermediate points can return an empty `breakpoints` list, and one with no meaningful length can leave the `length` property out altogether.
0 commit comments