diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index cd57ed263..e854ab5b1 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -36,7 +36,7 @@ jobs: version: "1.8.27" - name: Install dependencies - run: uv sync --group dev --group docs --group networks + run: uv sync --group dev --group docs --group network - name: Build documentation run: just docs diff --git a/.github/workflows/full_test.yml b/.github/workflows/full_test.yml index 341923a71..bd2621ead 100644 --- a/.github/workflows/full_test.yml +++ b/.github/workflows/full_test.yml @@ -38,7 +38,7 @@ jobs: enable-cache: true - name: Install dependencies - run: uv sync --group test --group networks --no-dev + run: uv sync --group test --group network --no-dev - name: Install pandas 2.x if: matrix.pandas-version == 'pandas2' @@ -57,7 +57,7 @@ jobs: - name: Test run: just test - build-no-networks: + build-no-network: runs-on: ubuntu-latest steps: @@ -71,7 +71,7 @@ jobs: python-version: "3.12" enable-cache: true - - name: Install dependencies (without networks) + - name: Install dependencies (without network) run: uv sync --group test --no-dev - name: Test diff --git a/.github/workflows/notebooks_test.yml b/.github/workflows/notebooks_test.yml index ef39a8eb8..aaa4d5ba8 100644 --- a/.github/workflows/notebooks_test.yml +++ b/.github/workflows/notebooks_test.yml @@ -24,7 +24,7 @@ jobs: python-version: "3.14" enable-cache: true - name: Install dependencies - run: uv sync --group test --group notebooks --group networks --no-dev + run: uv sync --group test --group notebooks --group network --no-dev - name: Test notebooks run: | uv run pytest tests/notebooks/ diff --git a/adr/010-optional-domain-dependencies.md b/adr/010-optional-domain-dependencies.md index c67cab5b9..fc5e9dc89 100644 --- a/adr/010-optional-domain-dependencies.md +++ b/adr/010-optional-domain-dependencies.md @@ -56,7 +56,10 @@ Installation: `pip install modelskill modelskill-network` **Open Questions:** - Should `modelskill[all]` install all optional model types? -- How to handle version constraints for optional dependencies? +- How to handle version constraints for optional dependencies? Answered for network + support by [ADR-013](013-network-topology-in-mikeio1d.md): the `network` extra names a + minimum mikeio1d, because the topology layer ships there. Network support requires + whatever Python that release requires. - Should optional dependencies be tested in CI for every commit or separately? ## Status Notes diff --git a/adr/012-network-format-constructors.md b/adr/012-network-format-constructors.md index 324959247..8a863068a 100644 --- a/adr/012-network-format-constructors.md +++ b/adr/012-network-format-constructors.md @@ -1,9 +1,19 @@ # ADR-012: One Network Constructor per Modelling Product -**Status**: Draft +**Status**: Accepted, narrowed by [ADR-013](013-network-topology-in-mikeio1d.md) **Date**: 2026-08 +## Narrowed by ADR-013 + +The constructors, the companion arguments, the extension tables, the coverage test and the +`.inp` reader are mikeio1d's. It replaced `from_mike` and `from_epanet` with one +`Network.open` that reads the extension. Naming a constructor after the product that wrote +the file is still the rule, and mikeio1d applies it. + +`NetworkModelResult` hands a path to mikeio1d. The refusal messages for `.out`, `.resx` +and the formats without a fixture are written there. + ## Context `Network` is built from result files read through mikeio1d, whose single `Res1D` class opens nine extensions across five products — MIKE 1D (`.res1d`), MIKE 11 (`.res11`), MOUSE (`.prf`, `.crf`, `.xrf`), EPANET (`.res`), SWMM (`.out`), Water Hammer (`.whr`), and `.resx`, which is shared by the last three. There is no per-format reader and no per-format constructor argument, so from mikeio1d's side all nine look alike. modelskill's constructor was named `from_res1d`, and its extension guard was briefly widened to accept everything mikeio1d could read — making the name promise one format while reading nine. diff --git a/adr/013-network-topology-in-mikeio1d.md b/adr/013-network-topology-in-mikeio1d.md new file mode 100644 index 000000000..a6ea74b5f --- /dev/null +++ b/adr/013-network-topology-in-mikeio1d.md @@ -0,0 +1,59 @@ +# ADR-013: The Network Topology Layer Belongs to mikeio1d + +**Status**: Accepted + +**Date**: 2026-08 + +## Context + +`modelskill.network` had grown to roughly 630 lines of topology: the abstract node/reach/breakpoint types, a `Res1D` adapter, one constructor per modelling product, the `.resx` and `.inp` companions, +tables of which extensions we refuse and why, a networkx graph carrying reach lengths and boundary edges, an alias map, and `find`/`recall`/`to_dataset` on top. `NetworkModelResult` uses five members +of `Network`, two of them private, and never traverses the graph. mikeio1d's `experimental.to_networkx` converts the same files in 25 lines and ignores gridpoints. + +That leaves us on the far side of the line ADR-001 drew for mikeio, where we call `mikeio.read()` and stop, modelling no dfsu geometry and policing no format list. `Res1D` reads nine extensions across +five products. Our tables decide which of the nine we accept, and a test fails our CI when a mikeio1d release adds a tenth. The fixtures those tables are checked against are copies of mikeio1d's own: +`network.res1d`, `network_cali.res11`, `epanet.res/.resx/.inp`. + +## Decision + +mikeio1d gains an optional network module that builds and owns `Network`. modelskill requires it and consumes what it produces. + +| Owner | Pieces | +|---|---| +| mikeio1d | abstract types and `BasicNode`/`BasicReach`, the `Res1D` adapter, `Network.open`, the `.resx` and `.inp` companions, the extension policy tables, graph construction with its length and boundary semantics, the alias map, `find`, `recall`, `to_dataframe`, `to_dataset` | +| modelskill | `NetworkModelResult`, `NodeModelResult`, `NodeObservation`, `ReachObservation`, matching, the MIKE+ station resolver | + +`NetworkModelResult` takes a `Network` the upstream module built, or a path it hands to that module. The module is an extra there, carrying networkx and xarray, so `to_dataset()` ships with the class. +modelskill's `network` extra requires a mikeio1d release new enough to contain it. + +Original IDs become the only identifier a user handles: `NodeObservation.at` takes a node name or a `(reach, distance)` pair, and no longer an integer. The alias integers stay an internal index, +because the ID space mixes names and break points and a tuple cannot be an xarray coordinate value. A saved comparer records the original ID with the integer beside it as `node_index`, so reloading +does not depend on the numbering the installed mikeio1d handed out. + +The loader's output over six fixture loads was recorded before anything moved — graph edges with their lengths and boundary flags, the alias map, the dataframe, and every answer `find` and `recall` +give. Those snapshots are the upstream module's acceptance test. Phase 1 landed as mikeio1d [#247](https://github.com/DHI/mikeio1d/pull/247), merged 2026-08-19. The snapshots pass there unchanged +twice: against the code moved verbatim, and again after the two product constructors collapsed into `Network.open`. + +modelskill 1.4.0 waits for the mikeio1d release carrying the module, which is not out yet. + +## Alternatives Considered + +**Keep the layer here.** Defensible while the API is private. Costs a format matrix, an EPANET `.inp` parser and a graph contract for traversals we never perform. + +**Move only the constructors and companions**, leaving the graph and the abstract types here. Splits the format knowledge from the topology it produces, and leaves `Res1DReach` here as the single +adapter for a plug point with no second implementation. + +**A separate `modelskill-network` package.** Rejected in ADR-010 for fragmenting the install. It would still own format knowledge that belongs with mikeio1d. + +**Ask mikeio1d to guarantee stable node numbering** instead of dropping integers from our API. Puts a promise on someone else's release process, to protect a number users should not be handling. + +## Consequences + +- ADR-012 is narrowed: the constructors, the companion arguments, the extension tables and the coverage test become mikeio1d's. Naming a constructor after the product that wrote the file is still the + rule, and mikeio1d applies it. +- ADR-010's open question about version constraints for optional dependencies is answered for this feature: the `network` extra pins a minimum mikeio1d, and network support requires whatever Python + that release requires. +- A hand-built network needs mikeio1d installed, since `BasicNode`/`BasicReach` move too. That costs a .NET dependency for users who touch no MIKE file, which only matters for tests and for a backend + nobody has written. +- Dropping `at=` breaks a signature that shipped in the 1.4.0a3 alpha only, while the network module is opt-in and absent from the API reference. Removing it after 1.4.0 would cost more. +- Releases become coupled in one direction: a fix to network file reading ships on mikeio1d's schedule. A format mikeio1d adds no longer breaks our CI. diff --git a/adr/README.md b/adr/README.md index 59b6d3bf2..fe0639ba0 100644 --- a/adr/README.md +++ b/adr/README.md @@ -30,7 +30,8 @@ Each ADR follows this structure: - [ADR-009](009-factory-pattern.md) - Factory pattern for type detection - [ADR-010](010-optional-domain-dependencies.md) - Optional dependencies for domain-specific model types (Draft) - [ADR-011](011-vertical-pre-extracted-columns.md) - VerticalModelResult ingests pre-extracted columns -- [ADR-012](012-network-format-constructors.md) - One Network constructor per modelling product (Draft) +- [ADR-012](012-network-format-constructors.md) - One Network constructor per modelling product (narrowed by ADR-013) +- [ADR-013](013-network-topology-in-mikeio1d.md) - The network topology layer belongs to mikeio1d ## Contributing diff --git a/docs/images/res1d_network_mapping.png b/docs/images/res1d_network_mapping.png deleted file mode 100644 index 2930e9914..000000000 Binary files a/docs/images/res1d_network_mapping.png and /dev/null differ diff --git a/docs/user-guide/network.qmd b/docs/user-guide/network.qmd index a769d9c16..751a3eafa 100644 --- a/docs/user-guide/network.qmd +++ b/docs/user-guide/network.qmd @@ -7,17 +7,18 @@ jupyter: python3 ::: {.callout-warning collapse="true"} ## Extra dependencies required -Network support depends on libraries that are **not** installed -by default, e.g. `networkx`. You can install them alongside `modelskill` using the _networks_ extra: +Network support depends on `mikeio1d`, which reads the result files and builds +the network, and which is **not** installed by default. Install it alongside +`modelskill` with the _network_ extra: ```bash -uv pip install modelskill[networks] +uv pip install modelskill[network] ``` or ```bash -uv add modelskill[networks] +uv add modelskill[network] ``` ::: @@ -25,382 +26,71 @@ uv add modelskill[networks] ```{python} # | echo: false -import pandas as pd -import numpy as np -from typing import Any -from modelskill.network import Network, NetworkNode, NetworkReach, ReachBreakPoint - - -class ExampleNode(NetworkNode): - """Node backed by an in-memory DataFrame, e.g. model output.""" - - def __init__(self, node_id: str, data: pd.DataFrame): - self._id = node_id - self._data = data - - @property - def id(self) -> str: - return self._id - - @property - def data(self) -> pd.DataFrame: - return self._data - - @property - def boundary(self) -> dict[str, Any]: - return {} - - -class ExampleReach(NetworkReach): - """Reach connecting two nodes with a given length.""" - - def __init__( - self, - reach_id: str, - start: NetworkNode, - end: NetworkNode, - length: float, - breakpoints: list | None = None, - ): - self._id = reach_id - self._start = start - self._end = end - self._length = length - self._breakpoints = breakpoints or [] - - @property - def id(self) -> str: - return self._id - - @property - def start(self) -> NetworkNode: - return self._start - - @property - def end(self) -> NetworkNode: - return self._end - - @property - def length(self) -> float: - return self._length - - @property - def breakpoints(self) -> list: - return self._breakpoints - - -class ExampleBreakPoint(ReachBreakPoint): - def __init__(self, reach_id: str, distance: float, data: pd.DataFrame): - self._id = (reach_id, distance) - self._data = data - - @property - def id(self): - return self._id - - @property - def data(self): - return self._data - - -# Synthetic model output covering the observation period -model_time = pd.date_range("1994-08-07 16:00", periods=180, freq="1min") -t = np.linspace(0, 2 * np.pi, len(model_time)) - -df1 = pd.DataFrame({"WaterLevel": 194.0 + np.sin(t)}, index=model_time) -df2 = pd.DataFrame({"WaterLevel": 193.8 + 0.8 * np.sin(t)}, index=model_time) -df3 = pd.DataFrame({"WaterLevel": 193.6 + 0.6 * np.sin(t)}, index=model_time) -df4 = pd.DataFrame({"WaterLevel": 193.9 + 0.9 * np.sin(t)}, index=model_time) - -node_s1 = ExampleNode("sensor_1", df1) -node_s2 = ExampleNode("sensor_2", df2) -node_s3 = ExampleNode("sensor_3", df3) - -bp = ExampleBreakPoint("r1", 200.0, df4) -reach1 = ExampleReach("r1", node_s1, node_s2, length=500.0, breakpoints=[bp]) -reach2 = ExampleReach("r2", node_s2, node_s3, length=300.0) -network = Network(reaches=[reach1, reach2]) -``` - -A **Network** represents a 1D pipe or river network as a directed graph: nodes hold timeseries data (e.g. water level at a junction) and reaches carry the topology and reach length between them. Break points along a reach (e.g. cross-section chainages) are supported as observation locations too. - -The typical workflow is: - -``` -Network → NetworkModelResult → match() → Comparer -``` - -## Building a Network - -You can build a `Network` object by loading it from a supported network result file. Reading these files relies on [mikeio1d](https://github.com/DHI/mikeio1d), so install the `networks` dependency group first. - -There is one constructor per product that writes the file: - -| Constructor | Extensions | Product | -|---|---|---| -| `Network.from_mike` | `.res1d`, `.res11` | MIKE 1D, MIKE 11 | -| `Network.from_epanet` | `.res`, plus optional `.resx` and `.inp` | EPANET | - -The remaining formats mikeio1d can open cannot be turned into a `Network`, and say so when you try: - -| Extension | Why not | -|---|---| -| `.out` (SWMM) | The reach connectivity is not in the `.out` at all — it lives in the companion `.inp` input file, which modelskill does not read yet ([#689](https://github.com/DHI/modelskill/issues/689)). | -| `.resx` | Not a network on its own. It holds extra results for the network defined in the sibling `.res`, so pass it as `from_epanet(res, resx=...)` instead. | -| `.prf`, `.crf`, `.xrf` (MOUSE), `.whr` (Water Hammer) | No test fixture exists for these formats, so support cannot be verified. [Open an issue](https://github.com/DHI/modelskill/issues) if you need one. | - -### From a network result file - -The quickest way to get a `Network` is from the path to a result file: - -```{python} -# | echo: false - path_to_res1d = "../../tests/testdata/network.res1d" -path_to_res11 = "../../tests/testdata/network_cali.res11" -path_to_epanet = "../../tests/testdata/epanet.res" path_to_sensor_data_1 = "../../tests/testdata/network_sensor_1.csv" path_to_sensor_data_2 = "../../tests/testdata/network_sensor_2.csv" ``` -```{python} -from modelskill.network import Network - -network = Network.from_mike(path_to_res1d) -network -``` - -or a `mikeio1d.Res1D` that has already been opened: - -```python -from mikeio1d import Res1D - -res = Res1D(path_to_res1d) -network = Network.from_mike(res) -``` - -MIKE 11 files work the same way. Note that MIKE 11 keeps its timeseries on reach gridpoints rather than on nodes, so the nodes of such a network carry no data of their own: - -```{python} -Network.from_mike(path_to_res11) -``` - -EPANET results use `from_epanet`: - -```{python} -Network.from_epanet(path_to_epanet) -``` - -#### EPANET companion files - -An EPANET run writes more than one file, and the `.res` is not the whole picture: - -| File | What it adds | -|---|---| -| `.res` | The network and its main timeseries. Required. | -| `.resx` | Extra results — tank volume and pump energy. Merged onto matching nodes. | -| `.inp` | The model input. The only one of the three carrying reach lengths. | - -Pass the companions alongside the result file to get a fuller network: - -```{python} -# | echo: false -path_to_epanet_resx = "../../tests/testdata/epanet.resx" -path_to_epanet_inp = "../../tests/testdata/epanet.inp" -``` - -```{python} -network_epanet = Network.from_epanet( - path_to_epanet, - resx=path_to_epanet_resx, - inp=path_to_epanet_inp, -) -network_epanet -``` - -`Volume` and `Volume Percentage` come from the `.resx`, and the reach lengths from the `.inp`: - -```{python} -sorted( - d["length"] - for *_, d in network_epanet.graph.edges(data=True) - if d["length"] is not None -) -``` - -::: {.callout-warning} -## EPANET reach geometry is limited - -EPANET is a link-node model, and mikeio1d reports no length and a single synthetic gridpoint for each reach. So for an EPANET network: - -* without `inp=`, every edge of `network.graph` has `length=None`. A length-weighted `networkx` call then fails rather than returning a meaningless number — shortest-path treats the edge as unreachable, and anything that sums the weights raises `TypeError`. The attribute is always present, since `networkx` defaults a missing weight to `1`. With `inp=`, only pumps and valves stay `None`, since `[PIPES]` is the one section carrying lengths -* reaches have no breakpoints, so a `ReachObservation` cannot be matched — use `NodeObservation` instead -* `find(reach=..., distance=)` never resolves; only `distance="start"` and `distance="end"` work - -For the same reason, `resx=` merges node quantities only. Its reach-level quantities — pump energy, efficiency and costs — have no breakpoint to live on, which is tracked in [#680](https://github.com/DHI/modelskill/issues/680). - -Node timeseries, `to_dataframe()`, `to_dataset()`, `find(node=...)` and `recall()` are unaffected. -::: - -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. - -![How a Res1D file maps to a Network object. Reaches and nodes are re-indexed as integers; boundary nodes expose `find()`/`recall()` round-trip lookups.](../images/res1d_network_mapping.png) - -#### Selective loading - -Large result files can contain thousands of nodes and gridpoints. Loading all of that data into memory is slow and may cause memory issues — especially when you only need the timeseries at a handful of nodes where observations exist. - -Both constructors accept the same two optional arguments to restrict what gets loaded: - -| Argument | Type | Effect | -|---|---|---| -| `nodes` | `None` \| `str` \| `list[str]` | Control which nodes have timeseries data loaded. `None` (default) loads all nodes; `[]` skips all node data; a name or list loads only those nodes. | -| `reaches` | `None` \| `str` \| `list[str]` | Control which reaches have intermediate gridpoint data populated. `None` (default) loads everything; `[]` skips all gridpoints; a name or list of names loads only those reaches. | - -::: {.callout-note} -Selective loading only controls **which timeseries are held in memory**. The full network topology (nodes, reaches, lengths) is always constructed so that `find()`, `recall()`, and graph algorithms still work on the complete network. -::: - -The most memory-efficient setup — useful when you only care about specific junction nodes — is to pass the node IDs you need and skip all intermediate gridpoints with `reaches=[]`: - -```{python} -network_subset = Network.from_mike( - path_to_res1d, - nodes=["78", "46"], - reaches=[], -) -network_subset -``` - -If you also need gridpoint data along a particular reach, pass its name (or a list of names): - -```{python} -network_subset = Network.from_mike( - path_to_res1d, - nodes=["78", "46"], - reaches=["94l1"], -) -network_subset -``` - -When only some nodes are loaded, `to_dataframe()` and `to_dataset()` only contain columns for those nodes — the rest are graph-connected but data-free: - -```{python} -network_subset.to_dataframe(sel="WaterLevel").head() -``` - -## Inspecting the Network - -### Available quantities - -```{python} -network.quantities -``` - -### Underlying graph +A **network** is a 1D pipe or river network read as a graph: nodes hold timeseries +data (water level at a junction, say), reaches carry the topology and the length +between them, and break points along a reach are locations in their own right. -The network exposes a `networkx.Graph` so you can use any NetworkX algorithm or -plotting function directly: +The workflow is the usual four steps, with the network standing in for a grid or a +mesh: -```{python} -# | echo: false - -plot_kwargs = { - "font_size": 6, - "node_size": 130, - "node_color": "white", - "edgecolors": "black", - "with_labels": True, -} ``` - -```{python} -import networkx as nx -import matplotlib.pyplot as plt - -fig, ax = plt.subplots(figsize=(10, 9), layout="tight") -nx.draw(network.graph, ax=ax, **plot_kwargs) -plt.show() +result file → NetworkModelResult → match() → Comparer ``` -### Timeseries data +## Where the network comes from -```{python} -# Multi-index DataFrame: columns are (node, quantity) -network.to_dataframe().head() -``` - -```{python} -# Select a single quantity -network.to_dataframe(sel="WaterLevel").head() -``` - -## Looking up node IDs +Reading the file and building the graph is [mikeio1d](https://github.com/DHI/mikeio1d)'s +job, not modelskill's. `Network.open` reads MIKE 1D (`.res1d`), MIKE 11 (`.res11`) +and EPANET (`.res`) results, finds the EPANET companion files, and says so when a +format cannot be turned into a network. See its documentation for the companions, +for loading only part of a large file, and for `find`/`recall` between the names the +model uses and the graph's own integers. -After construction, nodes are re-labelled as integers. Use `find()` to go from original coordinates to the integer ID and `recall()` to go back. - -::: {.callout-tip} -When creating `NodeObservation` objects for skill assessment you generally do **not** need to call `find()`. You can pass the original string ID as `node=`, and `NetworkModelResult` will resolve it for you during matching. For breakpoints, use `at=(reach, distance)` rather than `node=`. See [Skill assessment workflow](#skill-assessment-workflow) for details. -::: +modelskill takes the file path directly, and opens it for you: ```{python} -# Look up a named node by its original id -node_id = network.find(node="117") -print(f"Node '117' → integer id {node_id}") +import modelskill as ms -# Recover the original label -print(network.recall(node_id)) +mr = ms.NetworkModelResult(path_to_res1d, name="MyModel", item="WaterLevel") +mr ``` -```{python} -# Look up a break point by reach + chainage -bp_id = network.find(reach="94l1", distance=21.285) -print(f"Break point (94l1, 21.285) → integer id {bp_id}") -print(network.recall(bp_id)) -``` +Open the network yourself when you need to name EPANET companion files, or to keep +memory down on a large model by reading only the locations you will score: ```{python} -# Node batch lookup -ids = network.find(node=["20", "113", "38"]) -print(ids) -``` +from mikeio1d.network import Network -```{python} -# Reach lookup -ids = network.find(reach="58l1", distance="start") -print(ids) -ids = network.find(reach="58l1", distance=[51.456, 77.185]) -print(ids) -ids = network.find(reach="58l1", distance=["start", 77.185]) -print(ids) +network = Network.open(path_to_res1d, nodes=["78", "46"], reaches=["94l1"]) +ms.NetworkModelResult(network, name="MyModel", item="WaterLevel") ``` ## Skill assessment workflow -### 1. Wrap the Network in a NetworkModelResult +### 1. The model result -```{python} -import modelskill as ms -from modelskill.model.network import NetworkModelResult - -mr = NetworkModelResult(network, name="MyModel", item="WaterLevel") -mr -``` +`mr` above is the model side of the comparison, holding one quantity over every +location the file was read for. ### 2. Create NodeObservations and compute skill `NodeObservation` accepts a file path directly; the observation name is taken from the filename. -The `at=` argument can be specified in three ways, depending on what information you have at hand. +The `at=` argument takes either of two forms, depending on what you have at hand. ::: {.callout-note} ## MIKE 1D vocabulary vs the modelskill API -In MIKE 1D, "nodes" are the named connection points in the 1D network (manholes, junctions, outfalls) while "reaches" are the pipe or channel segments that connect them. The modelskill API uses `NodeObservation` more broadly: the `at=` argument accepts either a node ID (int or string) **or** a `(reach_id, distance)` breakpoint tuple that identifies a specific chainage along a reach. If you only need a reach-level quantity (uniform across the reach), use `ReachObservation` with `reach=` instead. +In MIKE 1D, "nodes" are the named connection points in the 1D network (manholes, junctions, outfalls) while "reaches" are the pipe or channel segments that connect them. The modelskill API uses `NodeObservation` more broadly: the `at=` argument accepts either the node's name **or** a `(reach_id, distance)` break point tuple that identifies a specific chainage along a reach. If you only need a reach-level quantity (uniform across the reach), use `ReachObservation` with `reach=` instead. ::: -#### Option A — original string alias +#### Option A — the node's name -Pass the original node identifier from the source format (e.g. the Res1D node name) as a plain string. The `NetworkModelResult` resolves it to the correct integer ID at match time, so you do not need to call `network.find()` yourself: +Pass the node's name in the model, e.g. the Res1D node name. It is resolved against the network at match time: ```{python} obs_1 = ms.NodeObservation(path_to_sensor_data_1, at="78") @@ -411,26 +101,26 @@ cc.skill() ``` ::: {.callout-note} -Resolution happens inside `ms.match()`. If the string is not found in the network's alias map a `ValueError` is raised with a clear message indicating which alias could not be resolved. +Resolution happens inside `ms.match()`. A name the network does not hold raises a `ValueError` that names the near misses. ::: #### Option B — breakpoint by `(reach, distance)` tuple When your observation sits at a chainage along a reach rather than at a named junction node, you can use the `at` argument and pass a `(reach_id, distance)` tuple. The `NetworkModelResult` looks up the corresponding breakpoint at match time: -```python -obs_bp = ms.NodeObservation(path_to_sensor_data_1, at=("94l1", 21.285)) +```{python} +obs_bp = ms.NodeObservation(path_to_sensor_data_1, at=("94l1", 42.57)) cc = ms.match(obs=obs_bp, mod=mr) cc.skill() ``` -The tuple form is equivalent to calling `network.find(reach="94l1", distance=21.285)` beforehand and is resolved during matching. +The tuple form is equivalent to calling `network.find(reach="94l1", distance=42.57)` beforehand, and is resolved during matching. ::: {.callout-note} ## Chainage tolerance -Breakpoint distances are matched with a tolerance of **1 × 10⁻³** (i.e. ±0.001 in whatever distance units the network uses). This means that small floating-point discrepancies between the distance you type and the value stored in the network are handled gracefully. If no breakpoint falls within that tolerance a `ValueError` is raised. +Break point distances are matched within the tolerance mikeio1d uses to decide two chainages are the same place, so small floating-point discrepancies between the distance you type and the value stored in the network are handled gracefully. If no break point falls within it, a `ValueError` is raised, as it is when the break point exists but carries no data for the quantity you are scoring - MIKE 1D stores water level and discharge at alternating grid points along a reach. ::: ### 3. Using ReachObservation for reach-uniform quantities @@ -447,7 +137,7 @@ obs_q Pass the observation to `ms.match()` exactly as you would a `NodeObservation`. modelskill resolves which breakpoint to use automatically: ```{python} -mr_q = NetworkModelResult(network, name="MyModel", item="Discharge") +mr_q = ms.NetworkModelResult(network, name="MyModel", item="Discharge") cc_q = ms.match(obs=obs_q, mod=mr_q) cc_q.skill() ``` @@ -456,130 +146,45 @@ cc_q.skill() Use `ReachObservation` when your measured quantity is representative of the whole reach (e.g. discharge, which is constant along a reach in steady flow). If you need to compare a quantity that varies spatially along the reach (e.g. water level at a specific chainage), use a `NodeObservation` with a `(reach, distance)` tuple instead (see [Option B](#option-b-breakpoint-by-reach-distance-tuple) above). ::: -## Development - -### Custom network formats +## Locating observations with a MIKE+ database -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`. +The examples above assume you already know where each sensor sits in the network. A MIKE+ project normally records that itself, in the sqlite database shipped alongside the result files: `m_Measurement` says which file and item each measured timeseries lives in, and `m_Station` says where in the network it belongs. -`NetworkNode` requires three properties: `id`, `data`, and `boundary`. -`NetworkReach` requires four: `id`, `start`, `end`, and `breakpoints`. - -`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. - - -The following is a simple implementation example: +Pass that database as `db` and modelskill does the lookup for you: ```python -import pandas as pd -import numpy as np -from typing import Any -from modelskill.network import NetworkNode, NetworkReach, Network - - -class ExampleNode(NetworkNode): - """Node backed by an in-memory DataFrame, e.g. model output.""" - - def __init__(self, node_id: str, data: pd.DataFrame): - self._id = node_id - self._data = data - - @property - def id(self) -> str: - return self._id - - @property - def data(self) -> pd.DataFrame: - return self._data - - @property - def boundary(self) -> dict[str, Any]: - return {} - +quantity = "Pressure" -class ExampleReach(NetworkReach): - """Reach connecting two nodes with a given length.""" +network = Network.open("model.res", quantities=quantity) +network_model = ms.NetworkModelResult(network, item=quantity) - def __init__( - self, reach_id: str, start: NetworkNode, end: NetworkNode, length: float, - breakpoints: list | None = None, - ): - self._id = reach_id - self._start = start - self._end = end - self._length = length - self._breakpoints = breakpoints or [] - - @property - def id(self) -> str: - return self._id - - @property - def start(self) -> NetworkNode: - return self._start - - @property - def end(self) -> NetworkNode: - return self._end - - @property - def length(self) -> float: - return self._length - - @property - def breakpoints(self) -> list: - return self._breakpoints -``` - -::: {.callout-tip} -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. -::: - - -```{python} -from modelskill.network import Network - -# df1, df2 and df3 are DataFrame objects that are loaded in memory -node_s1 = ExampleNode("sensor_1", df1) -node_s2 = ExampleNode("sensor_2", df2) -node_s3 = ExampleNode("sensor_3", df3) - -reach1 = ExampleReach("r1", node_s1, node_s2, length=500.0) -reach2 = ExampleReach("r2", node_s2, node_s3, length=300.0) +obs = ms.NodeObservation.from_multiple( + data="calibration.dfs0", + db="model.sqlite", + quantity=quantity, +) -network = Network(reaches=[reach1, reach2]) -network +cc = ms.match(obs, network_model) ``` -### Adding break points along a reach +One observation is created per item of the data source, named after the station's asset name. Because the mapping runs item by item rather than location by location, several sensors at the same node — a pair either side of a check valve, say — all become separate observations. -Break points represent intermediate chainage locations on a reach (e.g. cross-sections). Subclass `ReachBreakPoint` the same way — implement `id` (a `(reach_id, distance)` tuple) and `data`: +Reach-uniform quantities work the same way through the sibling method: ```python -from modelskill.network import ReachBreakPoint - - -class ExampleBreakPoint(ReachBreakPoint): - def __init__(self, reach_id: str, distance: float, data: pd.DataFrame): - self._id = (reach_id, distance) - self._data = data - - @property - def id(self): - return self._id - - @property - def data(self): - return self._data +obs_q = ms.ReachObservation.from_multiple( + data="calibration.dfs0", db="model.sqlite", quantity="Flow" +) +``` +Which class to use is decided by the database, not by you: stations recorded on a junction or a tank are node observations, and stations recorded on a link are reach observations. Asking `NodeObservation` for a quantity that the database places on links raises an error naming `ReachObservation`, and the other way around. -# df4 is a DataFrame object that has been loaded in memory -bp = ExampleBreakPoint("r1", 200.0, df4) -reach1 = ExampleReach("r1", node_s1, node_s2, length=500.0, breakpoints=[bp]) -reach2 = ExampleReach("r2", node_s2, node_s3, length=300.0) -network = Network(reaches=[reach1, reach2]) -``` +A few details worth knowing: +* **`quantity` is optional.** Omit it and the quantity is inferred, as long as the data holds only one for the class you asked for. A calibration file mixing pressure and flow raises an error listing what it found. +* **The quantity name comes from the database, the unit from the data.** Calibration files often carry no usable EUM information, so the database is the only reliable source for the name. +* **`source` picks between files.** It defaults to `data` when that is a path. Pass it explicitly if you hand over an already-read `mikeio.Dataset` or a `DataFrame`, since neither remembers where it came from. +* **Items the database cannot place raise by default**, separating the two causes: a station that exists but has no measurement registered for this file, and an item that is not in the database at all. Pass `on_missing="skip"` to build observations from the rest. ## See also diff --git a/justfile b/justfile index d027887a5..e33c86c5c 100644 --- a/justfile +++ b/justfile @@ -23,9 +23,9 @@ test: typecheck: uv run mypy src/ --config-file pyproject.toml -# Run doctests in metrics.py +# Run doctests in metrics.py and types.py doctest: - uv run pytest src/modelskill/metrics.py --doctest-modules + uv run pytest src/modelskill/metrics.py src/modelskill/types.py --doctest-modules # Generate HTML coverage report coverage: diff --git a/notebooks/Collection_systems_network.ipynb b/notebooks/Collection_systems_network.ipynb index 89e9962e0..5291a0e84 100644 --- a/notebooks/Collection_systems_network.ipynb +++ b/notebooks/Collection_systems_network.ipynb @@ -1,1629 +1,1894 @@ { - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "fdb0d0b9", - "metadata": {}, - "outputs": [], - "source": [ - "import modelskill as ms\n", - "import pandas as pd\n", - "import numpy as np\n", - "\n", - "import networkx as nx\n", - "import matplotlib.pyplot as plt\n", - "\n", - "from modelskill.network import Network" - ] - }, - { - "cell_type": "markdown", - "id": "b643e568", - "metadata": {}, - "source": [ - "# 1D network workflow\n", - "\n", - "This notebook shows how to use `modelskill` to evaluate model results from 1D network simulations, such as collection systems or river networks. The workflow follows the same four-step pattern used elsewhere in `modelskill`: define model results → define observations → match → compare.\n", - "\n", - "## Loading network results\n", - "\n", - "The `Network` class organises data from a network simulation (e.g. a sewer system or a river) into a form that `modelskill` can work with. It stores time-series data for every node and break point in the network, and exposes the topology via a `networkx` graph.\n", - "\n", - "### Loading from a supported format\n", - "\n", - "The easiest way to create a `Network` is to load it directly from a supported file format. Currently **Res1D** (MIKE 1D) is supported:\n", - "\n", - "```python\n", - "from modelskill.network import Network\n", - "\n", - "network = Network.from_mike(\"path/to/results.res1d\")\n", - "``` \n", - "\n", - "### Custom network format\n", - "\n", - "For other simulation tools you can build a `Network` from your own data by subclassing the abstract base classes `NetworkNode` and `NetworkEdge`. Notice that this approach requires that you build the logic to generate a list of `NetworkEdge` to pass it to the network.\n", - "\n", - "```python\n", - "from modelskill.network import Network, NetworkNode, NetworkEdge\n", - "\n", - "class MyNode(NetworkNode): ...\n", - "\n", - "class MyEdge(NetworkEdge): ...\n", - "\n", - "\n", - "def generate_list_of_edges(a_network: CustomNetwork) -> list[MyEdge]: ...\n", - "\n", - "\n", - "edges = generate_list_of_edges(custom_network)\n", - "\n", - "network = Network(edges)\n", - "``` \n", - "\n", - "#### Break points\n", - "\n", - "Edges can optionally contain **break points** — intermediate locations along a reach (e.g. cross-section chainages) that carry their own time-series data. You can include them with subclass `EdgeBreakPoint`.\n", - "\n", - "### Example" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "cd363bae", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "\n", - "Reaches: 118\n", - "Nodes: 259\n", - "Quantities: ['WaterLevel', 'Discharge']\n", - "Time: 1994-08-07 16:35:00 - 1994-08-07 18:35:00" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "network = Network.from_mike(\"../tests/testdata/network.res1d\")\n", - "network" - ] - }, - { - "cell_type": "markdown", - "id": "33a451d3", - "metadata": {}, - "source": [ - "All time-series data stored in the network can be accessed as an `xarray.Dataset` with `to_dataset()`. The dataset has one variable per physical quantity and uses the network's integer node IDs as the `node` coordinate:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "2a2d7414", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
<xarray.Dataset> Size: 231kB\n",
-              "Dimensions:     (time: 110, node: 259)\n",
-              "Coordinates:\n",
-              "  * time        (time) datetime64[ns] 880B 1994-08-07T16:35:00 ... 1994-08-07...\n",
-              "  * node        (node) int64 2kB 0 1 2 3 4 5 6 7 ... 252 253 254 255 256 257 258\n",
-              "Data variables:\n",
-              "    WaterLevel  (time, node) float32 114kB 195.4 194.7 nan ... 188.5 nan nan\n",
-              "    Discharge   (time, node) float32 114kB nan nan 5.72e-06 ... nan 0.01692 0.0
" - ], - "text/plain": [ - " Size: 231kB\n", - "Dimensions: (time: 110, node: 259)\n", - "Coordinates:\n", - " * time (time) datetime64[ns] 880B 1994-08-07T16:35:00 ... 1994-08-07...\n", - " * node (node) int64 2kB 0 1 2 3 4 5 6 7 ... 252 253 254 255 256 257 258\n", - "Data variables:\n", - " WaterLevel (time, node) float32 114kB 195.4 194.7 nan ... 188.5 nan nan\n", - " Discharge (time, node) float32 114kB nan nan 5.72e-06 ... nan 0.01692 0.0" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "network.to_dataset()" - ] - }, - { - "cell_type": "markdown", - "id": "a972271a", - "metadata": {}, - "source": [ - "`Network` also exposes the underlying `networkx.Graph` via the `graph` property. This graph contains the full network topology — each graph node stores the node's data and boundary metadata — making it straightforward to run graph-based analyses (shortest path, connectivity checks, etc.):" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "06e8c2cb", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "network.graph" - ] - }, - { - "cell_type": "markdown", - "id": "885523e1", - "metadata": {}, - "source": [ - "Querying the underlying `networkx` graph lets you run topology-based analyses (e.g. shortest path, connected components) directly on the network. The visualisation below plots the graph, highlighting boundary/junction nodes with a thicker outline:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "1d068e44", - "metadata": {}, - "outputs": [], - "source": [ - "def plot_network(g: nx.Graph):\n", - "\n", - " g = g.copy()\n", - " lengths = nx.get_edge_attributes(g, \"length\")\n", - " max_len = max(lengths.values()) if lengths else 1.0\n", - " nx.set_edge_attributes(\n", - " g,\n", - " {e: v / max_len for e, v in lengths.items()},\n", - " \"norm_length\",\n", - " )\n", - "\n", - " widthmap = [2 if 'boundary' in g.nodes[node] else 1 for node in g.nodes()]\n", - " plot_kwargs = {\n", - " \"font_size\": 6,\n", - " \"node_size\": 130,\n", - " \"node_color\": \"white\",\n", - " \"edgecolors\": \"black\",\n", - " \"linewidths\": widthmap,\n", - " \"with_labels\": True,\n", - " }\n", - " fig, ax = plt.subplots(1, 1, sharey=True, layout=\"tight\", figsize=(10, 9))\n", - "\n", - " n = g.number_of_nodes()\n", - " k = 10 / np.sqrt(n) # increase multiplier (5, 10, ...) until nodes stop overlapping\n", - "\n", - " pos = nx.kamada_kawai_layout(g, weight=\"norm_length\", scale=10)\n", - " nx.draw(g, ax=ax, pos=pos, **plot_kwargs)\n", - "\n", - " # Set limits explicitly AFTER draw, otherwise matplotlib auto-scales them away\n", - " xs, ys = zip(*pos.values())\n", - " pad = 0.5\n", - " ax.set_xlim(min(xs) - pad, max(xs) + pad)\n", - " ax.set_ylim(min(ys) - pad, max(ys) + pad)\n", - " plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "53ab2b9c", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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RYTw2cLVjc65JUc2ep24lIgvHEYf4u0fCEsiA5KDQQO2h2kRnOSf+8EC1j9lyhRbNssjhSS58HrEkBef4QeiGGEfvX3+O4XoA5xScoNxMvmfv31UQiLC1T/YB1pw+YFC3FciFwjtDwtmBg3JTU9peTjU5D97uF4Q7uQph48zy0cohs+Zd8xZf7TI94MD06ZNO6adWhz26NByBlYYuGAgiOsg3LaSEHiPj3hJqR2YEOkFiW4hhDiGCDtmCO5EwhERGV5wU9SIAmsS2uJYh9BXCj2RfkGubKJV+xG1iG/cSt9KjWJptErCUfXtuWJBoSnfZzu5B1EoRK0kAs41bq13eNkeioD5ZSFuEO+JtJvCcaWXMt8TyW0fkTaIK4QVRbl89W7/wFUnn5pIBvLOCaUOt4oKPxDmiGsEezjkmfDp6667zsKYY4U8M+iCW83gR/QyCXOmLVly6QoM1HDOE/ku5JxyjXBOyA1PLu/6WAIRjXPvCfd4Z3CEKJKw6CaKgygHivv5QROEe0Z0+sWxx3H844QQQmR6+LovVaqU+/777+3vr7/+2p166qnu0ksvtdfh1Vdfdc2bN7fne/fudX///bdbsmSJu+OOO9yYMWPs9REjRriWLVum4Z4Ikbk+l8cdd1zC0/OZnDZtmnvhhRfcwoUL3T///GOvn3jiia527drunnvucVdddZXLkiXLYW/HokWL3MqVK93u3btdrly5XJkyZdwll1xy2PvE/K+99pobPXq0++GHH+xvvmuKFStm3x2NGjVyJ598cmT6gwcPum3btrldu3a5nDlzurx587rjjz8+yTJ/+eUXN3bsWLd69Wr3xx9/uBw5crjChQu72267zRUqVMj9F3bu3Om2b99uxyxPnjy2reLoU6tWLbsO3nrrLTv3119/vRs+fLjLmjWrGzBggHvppZfst+eBBx5wF154oVu8eLHr0qWLO+2009xPP/3kPv/8c1tO48aN3euvv35YnyMhUhuJbiGEOEbgBr1atWr2vGrVqm7BggVu3bp1dkMzefLkQ6bnhnn9+vWuV69e7osvvnAVKlSw18uXL29/CyHSFsTq77//bgIVoXi4QluItARh3bNnT/sdatGihbv//vttwGjKlClu0KBB7pRTTnHffPONO//8893cuXNdtmzZbD4GaJhu+fLlkUGnN99809188806oSLdom9nIYQ4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EOXKlcu98sorrn379lalP7olnhBCCCGEEOmZNBHdiEgP4iya/v37uwceeCAiVrwgo43XlClTrL91eLowr732mgknxByttoD2WtHTIYoAkQ1M79cTb3viEW57RTssBHw0VatWdVdffbWrU6eO/U3P7e7du9tymb5cuXIxlx09H8KfdmW0Dvv000/dqaeeekgbslhi/e2337Z2atWrVzdx7GEZDFI0a9bMBhJSWo7nnXfesZZfCCeEK8scPXq0tQfzQnnWrFkpinj45JNPkvQepyVbeIBm2rRpbuDAgdZvPBFoEUe7sy+++CIyqBGGwQAGC7yAvuiii9z48ePdTz/9ZP3R2Q9anHG82E7fTo5jxf4cOHAg5noR+Exz22232SAR13aePHnsGqtXr54dZ3/dHevwmURojxo1Kmav+2j4fDIPg0MDBgxIlW0UQgghhBAiw4rucE9s3MAwa9eutf9xqr1o3rFjh/3PTTduKW4oQs1P50GkI5qaNGlif//1118R8RieDrww3rZtm/2PGPLribc98cAx9iDkYokynF3EcteuXe1vBPfIkSNdnz59bN2I2FhEz4fg6N27tzmEuLrsB+56SmIdhxynG5ea5x6ca3qL814sopfjwZ1kGxC4MHTo0IjLDS1btkzIufe92tknD/vroSfz6aef7mrXru0qVKhg105yzjnLQUT369fPRC8ufjSdO3eOnGt6oXMt0YvdD8B42Df6muOcsw3w3nvvuXHjxh2yTKIhcOErVapkQr9Hjx42uMAx4ZytW7fO+spzzhYvXuyOZfjMM8jBoM2VV15p1xjHnwEbok8YGOLBsQaOJ+f4iiuucPnz53fTp0+3AREhhBBCCCEyAmkiugmP9rz11ltJ3lu2bJmFniJQ5s6da47h1KlT3b59++x9XDFEJg6on46b87vvvttCkHlwM897vI5YQjiHp0OMI6h4nbBV71LiRCJA421PeP4w4X3Yv3+/hV57WK53fxGMfsCB13H6CNGGcJhzcvMVLlzYlo+Yw/3jOITD3uOJ9dy5c7saNWrYcy8gAQcZ4rm30csBjhEh4LjzfhufeOIJ9+WXX0amCTvXyYl4lk/kAqLfD7LgOH/22We2b2PHjnV33HGHbTMDE4k452wLgwsPP/ywhdWHzynnHAceOPZETnCNJccll1ziJkyYEPk7Wsh///33JrZ/++03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47gkl3bxb2Efhg0bZmHxhND7fG0GKqiNwICKj2bgumDQ4tZbb7UoC17neUoDM0IIIYQQQqRHjuOftO4VLo4N/vrrL/fHH3+4HDlyuJNPPvlfL2f16tXu5ZdfdkuXLnW7du1yp556qitRooS766673MUXX5zQMgYPHuw6deoU+fv44493N954o7v66qtd9uzZ3a+//urGjRvnli1bFpnm9NNPdwsXLnSlSpWKvNa7d2/39NNPu1WrVrn8+fMntO7du3e7Cy+80FWrVs2NGTPGXnvmmWfcI488Yu/FYubMme711193p5xyinvppZfcHXfc4R544AFbDlx//fX2/4wZM+z/yy+/3J100klu/vz5LlH++ecfO47ly5d348ePjznNn3/+6d588033zTff2LHnPBYsWNA1atQo7v4zT9u2bW1f27VrZ/vKtoXZvn27Gz16tHvjjTfs2PO1lDdvXjsnLVu2dGeffXbC+yGEEEIIIUR6QqJbHLOMGjXK3X333W7//v0pTlukSBH3zjvvuOLFiyd5fcuWLa5s2bLuzDPPdHPnzjVhntLAw8033+wWLVrkPvvsM1eyZEl7fdKkSa5x48Zu7dq1rnDhwvYaAxQMKMBDDz1k0w4fPtyEN6L3/PPPt3WeeOKJrlChQm7Tpk2ucuXK7n//+5/tG+KYda1bt85deumlkcEChC3TnXPOOfb3ww8/bIMNMGjQINe1a1f3008/ubPOOiuy3evXr3dDhgxxr7zyituxY8ch+5U1a1Z30003uY4dO7oqVapEXv/hhx9c/fr13cqVK92wYcNc8+bNUzzWQgghhBBCZCrS2moXIi356aefLK+bcPdYOeeEQlPcLLkCbuR00/qKNlhUeI+XP/3ll19a+Duh1HPmzEnyHn3Faf3VpUuXyGuxCrx5wuHlTMe2UpCNwmsU0yP83k8fqyAbRdxiQQ414dwUywtvR6wCffEeVBfnGBDGzz7REs7nvQshhBBCCHGsIadbCOfM7SYU+5dffnF79+51uXLlcqVLl7ZHIhBe3rBhQ/f111+bI92qVStznAlbxzXGef78888tFHvChAmuUqVKhyzjvvvuc2PHjrXpDyf8ntDyjRs3usWLF7vjjjvO3XbbbeYw46YDTjfrY3uuuOIK17dvX9tGnGzc7qFDhyZx6NlGlvH4449bWHvdunXdwYMH7T1cdRz5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uG7durlPPvnELV261BUqVMjEOKK7SpUqNh3F1p544gn33XffuTZt2lhYOuK7bt26kdBzIYQQQgghMjMS3UKIf8Xdd9/t3njjDffpp59GwshTAgFOJfPFixe7smXL6sgLIYQQQohMj8LLhRD/in79+lnl8lq1armVK1emOP2QIUNMcPfs2VOCWwghhBBCHDNIdAsh/hWnn366mzNnjuVnV6hQwT344IMWOh7m4MGDbsaMGe6aa65xHTt2tAfh50IIIYQQQhwrKLxciKNEEATuo48+cqNGjTIxunv3bnfqqae6YsWKuRYtWphQPe644zL88d+xY4fr2rWre/XVV91ff/3lypUrZ/ne+/btc19//bUVW6O9GAKcdmGIc3LBwxXNhRBCCCGEyKxIdAtxFMT22LFj3TPPPGOiMx7kND/wwAOuSZMmGVp8L1u2zJxs3woMENS5cuUyFxzB/dNPPyWZ59Zbb3VjxoxxWbNmTYMtFkIIIYQQIvWQ1STEEeTvv/92rVq1cnfcccchgjtaWH/11VcmPgm5xgXOiKxfv95de+21EcF97rnnuqefftpt2bLFbd++3Xp5Mw3F1m677baIuz1+/HjXvn17G6AQQgghhBAiMyOnW4gjBAKyZcuWFk7uoWXWPffc4+rVq+dOOeUU61M9ZcoU98ILL7gvvvgiMh3TUGgsvTrev//+u/v555/d3r17LUT8vPPOcyeccIK7/vrr3TvvvGPTXHbZZfY8T548cZfz9ttvuwYNGtjgBLz77rtWiE0IIYQQQojMikS3EEeIV155xUQ3nHjiiRZi3rhx45jTbtu2zb388svukUceibjcr7/+uoWap6dBhEWLFtkAwZtvvhkRynD22WebeB46dKj9TQ43YebJCW4P/blbt25tz2+44QYT4kIIIYQQQmRWJLqFOEIC9cILL3Tfffed/T1x4kTXqFGjJNMcOHDAKnk///zzbu7cuUneI++5QIECbvny5S5HjhxH7ZwQ9v3hhx+a6Ie8efO6atWq2f9htm7daqJ64cKF1oO7bdu25mSTo03YOG49Odk43+w7205BNcLIOQ6Eks+aNcv16tXLwsjh/ffft5Zh7OuqVatsHTj7P/zwgznnQgghhBBCZEZUxUiIIwDi1AvuKlWqmOAmJPvqq6+213G977//frdu3TqrYH7++edbETEqfhO2PXr0aAvN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tlEIIYQQQogjjUS3EOKos3XrVlegQAHXs2dP16NHj4TnGzlypDniK1ascMWLFz+q2yiEEEIIIcTRQC3DhBBHnVdeecVagjVt2tRVqFDB5ciRwy1fvtz98ccfrkaNGq5q1ar2P5XM4Y477nCXXnqpGzNmjOV0v/TSSzpLQgghhBAiQyLRLYQ46gwfPtw1btzY3O533nnHNWjQwF4/4YQT3Lhx49zChQtd165d3YABAyLzjBo1yl6n8BrPyfUWQgghhBAioyHRLYQ4qhw8eNCtXbvW3GxE9hlnnBF5j0Jq+fPnt+cnnniiy5Ll/76ScMVbt27trr76anfOOee433//3ULUhRBCCCGEyGhkTesNEEJkbvbu3Wv/J9f6a//+/a53795uxIgR9vfTTz9tLcXI5b7pppvsNVqKCSGEEEIIkdGQ0y2EOKqccsop5lzv3Lkz7jR33XWXa9eunStatKj9jeCGEiVKuH/++cee08tbCCGEEEKIjIZEtxDi6H7JZMniypcv76ZPnx7z/UcffdQVLlzYcr49u3btsv+3bNnitm3b5goWLJgkLF0IIYQQQoiMgsLLhRBHnbZt27qWLVu6H374wbVv394tXbrUrVy50vp19+nTx1WpUsXNnz/fVapUyT3xxBOuWbNmbvv27W7fvn1W4fyBBx6I5HsLIYQQQgiRkVCfbiFEquR1U7n85ptvjuRtJwI9vZ966in3888/uzPPPPOobqMQQgghhBBHA1lHQoijDkXUnnzySTdy5EhzsoMgSKi3d9++fd3DDz8swS2EEEIIITIsCi8XQqQKFEvbuHGj6969u1Ul79atmytZsuQh0/34449u0KBBbsiQIa5NmzbukUce0RkSQgghhBAZFoWXCyFSFcLLEd703b7yyitd7dq1Xa5cudzu3bvdvHnz3KxZs6xSOdM8+OCDVvlcCCGEEEKIjIpEtxAi1aFA2pQpU9xLL71kRdUolnb88ce7s846y6qZN2nSxFqNCSGEEEIIkdGR6BZCpDnkeF977bWW+z116tS03hwhhBBCCCGOGCqkJoRIcwghL1GihOV6CyGEEEIIkZlQITUhRLoA0f3CCy+4v//+251wwgkus/H999+7GTNmWC77wYMH3WmnneaqV6/uLr/8cuWtCyGEEEJkYiS6hRDpRnQfOHDArV271p5nBv755x/35ptv2mDCBx98EHOaCy+80LVr187dfvvtFl4vhBBCCCEyF8rpFkKkCzZv3uzy5ctnOd033nijy+hQjZ2CcO+8805C0yO+Z86c6c4999yjvm1CCCGEECL1UE63ECJdQOVyWocRhp3R+euvv6wVWlhwFytWzD3zzDPmeH/44YfulVdecZUrV468v3z5cgs1/+WXX9Joq4UQQgghxNFATrcQIt1QsWJFV7x4cTdmzBiX0di2bZsJ6TfeeMOtWrXK/fnnn1aVnVZohJffeeedMXO3Fy9ebI74mjVr7O+yZcu6zz77zGXNquwfIYQQQojMgES3ECLdgDD97rvvTHRmFHbt2uU6d+7sXnvtNfv75ptvdkWKFDGB/dNPP7nJkyebAL/pppvc0KFDzdGP5tdff3WVKlVyP/74o/09bdo0V69evVTfFyGEEEIIceSR6BZCpBv69+/v+vXr53bu3JkhKnpv2bLFXX311W79+vWue/furkWLFi5v3rxJpnn//fddy5Yt3c8//+xOPPFEc7ZPOeUUK572xx9/uKpVq7pHH33U8rmvu+46m6dWrVru3XffTaO9EkIIIYQQRxLFLwoh0gWEYtMuDOe4TZs21lLrzDPPdHXr1nVFixZ16Q3c6xtuuMFc6o8++siVKlUq5nRUYv/222+tVdgll1xibcJwtV988UV3zjnnRKa79tpr3fnnn29u95w5c9wPP/zgChcunIp7JIQQQgghjgYqpCaESPMq388995wrWbKk69mzp702fPhw99RTT7kHHnjACpBdc8017u233zZhnl4qrTds2NBca4qlxRPcQEX2k08+2RUsWNA1a9bM/f777xY+f//997saNWq4jz/+2KbLkiWLa968eWS+JUuWpMq+CCGEEEKIo4tEtxAizaAnN4XD7r33Xrdy5cq40+H8kuNMwTEqgx8O+/fvN5FMKDhO+n+BAmm33HKLtfWaPXu2tTYrV66c+/zzz829JlSc91nP888/7ypUqGAPenUTgv7FF1/Y+xs3bnR9+vRx48ePt333hJ1vQuyFEEIIIUTGR+HlQog0gfBpWmQRnu0h9Jq8aMKsDxw4YGL2pZdeihQYmzRpkonRGTNmuBNOOCHusv/55x/LiaZqOLnS/A1UBEe8k0995ZVXHlbeOK2+ENk41Z727dvb/4jw+fPnm6P90EMPubfeesvWvWzZMhP9tAbLmTOnGz16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EEKIq0WV7iQMlWwHY7GCg8wQzsePH7c+Vmf/RqxyO9j+vXz5cgsuy5Url6U8796928QtFWxn00a8IJLptQZmbBNYhjAGQsm2bdsW7ftlf0uXLvUeeOABr0iRIjZPm30QnAYEXvEYPdcIHTcrG2s5CwQEsfF4v379bB43iwjY6N18bXrFqazzPrHGI765TqWd16KffcmSJeYKkOAObVxOQaALxOGS6zlfCA90C0G0YxDuxznmoL3iamEhicWv/fv3mzsjuI1DCCGEEEKECT6RZGnatCk+abuULFnS9/HHH9v9Fy9e9JUuXdr3559/+vLly+fLkiWL/zk//PCDr379+lfc7z///OMrWrSo79FHH41035w5c3wpU6b0v+bo0aP9jx88eNB333332f1Vq1b1zZs3z/fjjz/6zp075zt69KhvwoQJvsKFC9vjNWrU8P3+++/+527cuNF30003+fd7xx13+HLmzOk7ceJErI7Dt99+67v11lt9N954o38f7nL77bf7unbt6vvuu+/+1bEVSZsyZcr4z4G//vrL17JlS9+OHTv8j1eoUMGXIUMG38qVK+02vx/HSy+95D+XOGf/LUeOHPG9/PLLvkyZMkU6F3m9Pn36+A4dOhRHn1IIIYQQQiQFVOlOwtSrV89/nQpzVEFm9DuTzDx9+nQLLmMON33VVPMC52YHQmI5fc7t2rWzXte3337bK1mypNewYUOzagMjuwhvc1CxxqpOKjgVa/q+M2TIYAnoWM47dOjgZc+e3RKhqXYHpoSzb3qrXRAalXTGejFTm/70K0GVm89JNZxeXDeWjD51quVU9fk89KaL8CEwMI1k+2Do16a6TduBc42434ObAQ/O5REbeA6J+ZzntGjUrl3bfovY13GD8JugdYJWEIL+yE4QQgghhBBhQEKrfnH1UNHOnDmzVdFuuOGGSJW8wAp169atfcmTJ/dNnTr1ivtj2+HDh19WLQ6+tGjRwqroV2Lnzp2+hQsX+mbMmOH74IMPrBIeE3Pnzo1USafamCxZMl/NmjV9K1as8F24cMHe49mzZ33vvfeeVSvZrly5cr5Tp07Ze2rWrJnd161bt1gcQRGqcI6682jBggX+SjfnD78bOH36tK98+fJ2vUuXLr7Vq1f7Ll265MufP38kBwnnVkxQTX/88cftdzh48GDfr7/+GuV2v/32my8iIsLO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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "plot_network(network.graph)" - ] - }, - { - "cell_type": "markdown", - "id": "5d3030f5", - "metadata": {}, - "source": [ - "Notice that in the visualisation above, some graph nodes have a thicker outline — these are the *nodes* (junctions and boundaries), while the others are *break points* along each reach.\n", - "\n", - "#### Mapping original IDs to integer IDs\n", - "\n", - "Internally, the `Network` relabels all nodes and break points with consecutive integers for efficient indexing. The original IDs used by the simulation tool are preserved and can be looked up with `find()`:\n", - "\n", - "In this Res1D example the original node IDs are strings. `find(node=...)` returns the corresponding integer ID:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "d9d23a8b", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "252" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "network.find(node=\"98\")" - ] - }, - { - "cell_type": "markdown", - "id": "ae495c5d", - "metadata": {}, - "source": [ - "Break points are identified in the original network by the edge (reach) they belong to and their distance from the start node.\n", - "\n", - "> **Note:** The current `Network` implementation assumes a directed edge, so distance is always measured from the start node." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "30c88717", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "131" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "network.find(reach=\"44l1\", distance=44.841)" - ] - }, + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "fdb0d0b9", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T14:20:00.197602Z", + "iopub.status.busy": "2026-08-25T14:20:00.197216Z", + "iopub.status.idle": "2026-08-25T14:20:02.473745Z", + "shell.execute_reply": "2026-08-25T14:20:02.471396Z" + } + }, + "outputs": [], + "source": [ + "import modelskill as ms\n", + "import pandas as pd\n", + "import numpy as np\n", + "\n", + "import networkx as nx\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from mikeio1d.network import Network" + ] + }, + { + "cell_type": "markdown", + "id": "b643e568", + "metadata": {}, + "source": [ + "# 1D network workflow\n", + "\n", + "This notebook shows how to use `modelskill` to evaluate model results from 1D network simulations, such as collection systems or river networks. The workflow follows the same four-step pattern used elsewhere in `modelskill`: define model results → define observations → match → compare.\n", + "\n", + "## Loading network results\n", + "\n", + "The `Network` class organises data from a network simulation (e.g. a sewer system or a river) into a form that `modelskill` can work with. It stores time-series data for every node and break point in the network, and exposes the topology via a `networkx` graph.\n", + "\n", + "It comes from [mikeio1d](https://github.com/DHI/mikeio1d), which reads the result files, and it arrives with `modelskill[network]`.\n", + "\n", + "### Loading from a supported format\n", + "\n", + "`Network.open` reads MIKE 1D (`.res1d`), MIKE 11 (`.res11`) and EPANET (`.res`) results:\n", + "\n", + "```python\n", + "from mikeio1d.network import Network\n", + "\n", + "network = Network.open(\"path/to/results.res1d\")\n", + "```\n", + "\n", + "It also takes the arguments for reading only part of a large file, and for naming the EPANET companion files. See mikeio1d's documentation for those, and for building a network from a format it does not read.\n", + "\n", + "### Break points\n", + "\n", + "A reach can carry **break points** — intermediate locations along it (e.g. cross-section chainages) with their own time-series data. They are locations you can compare against, addressed by their reach and their distance along it.\n", + "\n", + "### Example" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "cd363bae", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T14:20:02.477942Z", + "iopub.status.busy": "2026-08-25T14:20:02.477210Z", + "iopub.status.idle": "2026-08-25T14:20:03.381839Z", + "shell.execute_reply": "2026-08-25T14:20:03.377494Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "a7e41a31", - "metadata": {}, - "source": [ - "Multiple IDs can be looked up in a single call. For break points, each distance value corresponds to the edge at the same position in the `edge` list (one-to-one pairing):" + "data": { + "text/plain": [ + "\n", + "Reaches: 118\n", + "Nodes: 495\n", + "Quantities: ['WaterLevel', 'Discharge']\n", + "Time: 1994-08-07 16:35:00 - 1994-08-07 18:35:00" ] - }, + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "network = Network.open(\"../tests/testdata/network.res1d\")\n", + "network" + ] + }, + { + "cell_type": "markdown", + "id": "33a451d3", + "metadata": {}, + "source": [ + "All time-series data stored in the network can be accessed as an `xarray.Dataset` with `to_dataset()`. The dataset has one variable per physical quantity, and the `node` coordinate is the graph's own integer index. Alongside it, `name`, `reach` and `distance` carry the names the model gave each location, so a column can be read without holding on to the network:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "2a2d7414", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T14:20:03.387636Z", + "iopub.status.busy": "2026-08-25T14:20:03.387172Z", + "iopub.status.idle": "2026-08-25T14:20:03.730978Z", + "shell.execute_reply": "2026-08-25T14:20:03.729115Z" + } + }, + "outputs": [ { - "cell_type": "code", - "execution_count": 10, - "id": "e25a4ba8", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "([241, 3, 40], [131, 133])" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
<xarray.Dataset> Size: 498kB\n",
+       "Dimensions:     (time: 110, node: 495)\n",
+       "Coordinates:\n",
+       "  * time        (time) datetime64[ns] 880B 1994-08-07T16:35:00 ... 1994-08-07...\n",
+       "  * node        (node) int64 4kB 0 1 2 3 4 5 6 7 ... 488 489 490 491 492 493 494\n",
+       "    name        (node) <U17 34kB '100' '99' '' '' '' '101' ... '' '' '' '' '' ''\n",
+       "    reach       (node) <U10 20kB '' '' '100l1' ... 'Pump:115p1' 'Pump:115p1'\n",
+       "    distance    (node) float64 4kB nan nan 0.0 23.84 ... 1.0 0.0 41.21 82.43\n",
+       "Data variables:\n",
+       "    WaterLevel  (time, node) float32 218kB 195.4 194.7 195.4 ... 193.8 nan 195.0\n",
+       "    Discharge   (time, node) float32 218kB nan nan nan 5.72e-06 ... nan 0.0 nan
" ], - "source": [ - "network.find(node=[\"92\", \"101\", \"113\"]), network.find(reach=[\"44l1\", \"45l1\"], distance=[44.841, 37.206])" + "text/plain": [ + " Size: 498kB\n", + "Dimensions: (time: 110, node: 495)\n", + "Coordinates:\n", + " * time (time) datetime64[ns] 880B 1994-08-07T16:35:00 ... 1994-08-07...\n", + " * node (node) int64 4kB 0 1 2 3 4 5 6 7 ... 488 489 490 491 492 493 494\n", + " name (node) " ] - }, + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "network.graph" + ] + }, + { + "cell_type": "markdown", + "id": "885523e1", + "metadata": {}, + "source": [ + "Querying the underlying `networkx` graph lets you run topology-based analyses (e.g. shortest path, connected components) directly on the network. The visualisation below plots the graph, highlighting boundary/junction nodes with a thicker outline:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "1d068e44", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T14:20:03.742500Z", + "iopub.status.busy": "2026-08-25T14:20:03.742219Z", + "iopub.status.idle": "2026-08-25T14:20:03.748104Z", + "shell.execute_reply": "2026-08-25T14:20:03.746842Z" + } + }, + "outputs": [], + "source": [ + "def network_layout(network):\n", + " \"\"\"Place every graph node: named nodes from the reach skeleton, break points\n", + " interpolated along the reach that carries them.\n", + "\n", + " The graph joins a named node to the break point at the same chainage with a\n", + " zero-length edge, so a layout that reads edge lengths as distances cannot be\n", + " run over the graph itself. The skeleton of named nodes carries the reach\n", + " lengths without those edges, and every break point has a chainage that says\n", + " where along its reach it belongs.\n", + " \"\"\"\n", + " skeleton = nx.Graph()\n", + " for reach in network.reaches.values():\n", + " skeleton.add_edge(reach.start.id, reach.end.id, length=reach.length or 1.0)\n", + " named = nx.kamada_kawai_layout(skeleton, weight=\"length\")\n", + "\n", + " graph_id = {a: n for n, a in nx.get_node_attributes(network.graph, \"alias\").items()}\n", + " pos = {graph_id[name]: np.asarray(p) for name, p in named.items()}\n", + " for reach_id, reach in network.reaches.items():\n", + " start, end = np.asarray(named[reach.start.id]), np.asarray(named[reach.end.id])\n", + " span = reach.end_distance - reach.start_distance\n", + " for bp in reach.breakpoints:\n", + " if bp.distance is None: # a reach end whose chainage is unknown\n", + " fraction = 1.0\n", + " elif span == 0:\n", + " fraction = 0.0\n", + " else:\n", + " fraction = (bp.distance - reach.start_distance) / span\n", + " pos[graph_id[(reach_id, bp.distance)]] = start + fraction * (end - start)\n", + " return pos\n", + "\n", + "\n", + "def plot_network(network, ax=None):\n", + " \"\"\"Draw the network: named nodes as large circles, break points as small ones.\"\"\"\n", + " graph = network.graph\n", + " pos = network_layout(network)\n", + "\n", + " # recall() says what each integer stands for: a named node, or a break point\n", + " # given as a reach and a distance along it.\n", + " kinds = network.recall(list(graph))\n", + " nodes = [n for n, kind in zip(graph, kinds) if \"node\" in kind]\n", + " breakpoints = [n for n, kind in zip(graph, kinds) if \"node\" not in kind]\n", + "\n", + " if ax is None:\n", + " _, ax = plt.subplots(figsize=(10, 9), layout=\"tight\")\n", + " nx.draw_networkx_edges(graph, pos, ax=ax, edge_color=\"0.55\", width=0.8)\n", + " # break points first, so a break point coincident with a node hides beneath it\n", + " nx.draw_networkx_nodes(graph, pos, ax=ax, nodelist=breakpoints, node_size=14,\n", + " node_color=\"white\", edgecolors=\"0.45\", linewidths=0.6)\n", + " nx.draw_networkx_nodes(graph, pos, ax=ax, nodelist=nodes, node_size=55,\n", + " node_color=\"white\", edgecolors=\"black\", linewidths=1.4)\n", + " ax.set_aspect(\"equal\")\n", + " ax.set_axis_off()\n", + " return ax" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "53ab2b9c", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T14:20:03.752735Z", + "iopub.status.busy": "2026-08-25T14:20:03.752107Z", + "iopub.status.idle": "2026-08-25T14:20:04.992453Z", + "shell.execute_reply": "2026-08-25T14:20:04.991033Z" + } + }, + "outputs": [ { - "cell_type": "code", - "execution_count": 11, - "id": "cb1ae550", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "({'node': '98'},\n", - " {'reach': '45l1', 'distance': 37.20599457458005},\n", - " [{'node': '98'}, {'reach': '45l1', 'distance': 37.20599457458005}])" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "network.recall(252), network.recall(133), network.recall([252, 133]) " + "data": { + "image/png": "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", + "text/plain": [ + "
" ] - }, + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_network(network);" + ] + }, + { + "cell_type": "markdown", + "id": "5d3030f5", + "metadata": {}, + "source": [ + "Notice that in the visualisation above, the large, heavy circles are the *nodes* (junctions and boundaries), while the small ones strung along each reach are its *break points*. A break point coincident with a node — the first and last gridpoint of a reach — sits beneath that node's circle, which is where it physically is.\n", + "\n", + "#### Mapping original IDs to integer IDs\n", + "\n", + "Internally, the `Network` relabels all nodes and break points with consecutive integers for efficient indexing. The original IDs used by the simulation tool are preserved and can be looked up with `find()`:\n", + "\n", + "In this Res1D example the original node IDs are strings. `find(node=...)` returns the corresponding integer ID:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "d9d23a8b", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T14:20:04.995517Z", + "iopub.status.busy": "2026-08-25T14:20:04.995321Z", + "iopub.status.idle": "2026-08-25T14:20:05.000066Z", + "shell.execute_reply": "2026-08-25T14:20:04.998667Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "41ca197f", - "metadata": {}, - "source": [ - "## Integration with `modelskill`\n", - "\n", - "Wrap a `Network` in a `NetworkModelResult` to make it compatible with the standard `modelskill` comparison workflow. The `item` argument selects which quantity to use when more than one is available in the network:" + "data": { + "text/plain": [ + "478" ] - }, + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "network.find(node=\"98\")" + ] + }, + { + "cell_type": "markdown", + "id": "ae495c5d", + "metadata": {}, + "source": [ + "Break points are identified in the original network by the edge (reach) they belong to and their distance from the start node.\n", + "\n", + "> **Note:** The current `Network` implementation assumes a directed edge, so distance is always measured from the start node." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "30c88717", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T14:20:05.002631Z", + "iopub.status.busy": "2026-08-25T14:20:05.002358Z", + "iopub.status.idle": "2026-08-25T14:20:05.007373Z", + "shell.execute_reply": "2026-08-25T14:20:05.005888Z" + } + }, + "outputs": [ { - "cell_type": "code", - "execution_count": 13, - "id": "edec2e5a", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - ": WaterLevel" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "network_model = ms.NetworkModelResult(network, item=\"WaterLevel\")\n", - "network_model" + "data": { + "text/plain": [ + "240" ] - }, + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "network.find(reach=\"44l1\", distance=44.841)" + ] + }, + { + "cell_type": "markdown", + "id": "a7e41a31", + "metadata": {}, + "source": [ + "Multiple IDs can be looked up in a single call. For break points, each distance value corresponds to the edge at the same position in the `edge` list (one-to-one pairing):" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "e25a4ba8", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T14:20:05.009708Z", + "iopub.status.busy": "2026-08-25T14:20:05.009423Z", + "iopub.status.idle": "2026-08-25T14:20:05.015680Z", + "shell.execute_reply": "2026-08-25T14:20:05.014411Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "5905e267", - "metadata": {}, - "source": [ - "To evaluate the model we need observations at network nodes. These are represented by `NodeObservation`, which requires an integer node ID obtained via `Network.find()`.\n", - "\n", - "In this example we create synthetic sensor observations by extracting data from the network dataset and adding random noise to simulate real-world measurement error and timing jitter:" + "data": { + "text/plain": [ + "([455, 5, 68], [240, 244])" ] - }, + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "network.find(node=[\"92\", \"101\", \"113\"]), network.find(reach=[\"44l1\", \"45l1\"], distance=[44.841, 37.206])" + ] + }, + { + "cell_type": "markdown", + "id": "c4f36cfd", + "metadata": {}, + "source": [ + "Use `recall()` to translate integer IDs back to the original identifiers. This is useful when you want to know which original node or break point corresponds to a given integer ID:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "cb1ae550", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T14:20:05.018483Z", + "iopub.status.busy": "2026-08-25T14:20:05.018235Z", + "iopub.status.idle": "2026-08-25T14:20:05.024805Z", + "shell.execute_reply": "2026-08-25T14:20:05.022827Z" + } + }, + "outputs": [ { - "cell_type": "code", - "execution_count": 14, - "id": "817e1800", - "metadata": {}, - "outputs": [], - "source": [ - "ds = network.to_dataset()\n", - "\n", - "# Script to generate dummy sensor data\n", - "sensor_1 = ds[\"WaterLevel\"].sel(node=30).to_pandas().rename(\"water_level@sens1\")\n", - "sensor_2 = ds[\"WaterLevel\"].sel(node=54).to_pandas().rename(\"water_level@sens2\")\n", - "sensor_3 = ds[\"WaterLevel\"].sel(node=71).to_pandas().rename(\"water_level@sens3\")\n", - "\n", - "perfect_sensors = [sensor_1, sensor_2, sensor_3]\n", - "real_sensors = []\n", - "\n", - "for n, sensor in enumerate(perfect_sensors, start=1):\n", - " sensor += np.random.normal(0, 0.1, len(sensor))\n", - " sensor.index = [sensor.index[i] + pd.Timedelta(s, unit=\"s\") for i, s in enumerate(np.random.uniform(-10, 10, len(sensor)))]\n", - " sensor.sort_index(inplace=True)\n", - " if n == 2:\n", - " sensor = sensor.iloc[30:]\n", - " if n == 3:\n", - " sensor = pd.concat([sensor.iloc[:50], sensor.iloc[70:]])\n", - "\n", - " real_sensors.append(sensor)\n", - " sensor.to_csv(f\"../tests/testdata/network_sensor_{n}.csv\")\n", - "\n", - "sensor_1 = real_sensors[0]\n", - "sensor_2 = real_sensors[1]\n", - "sensor_3 = real_sensors[2]" + "data": { + "text/plain": [ + "({'reach': '47l1', 'distance': 26.7092518454833},\n", + " {'reach': '1l1', 'distance': 0.0},\n", + " [{'reach': '47l1', 'distance': 26.7092518454833},\n", + " {'reach': '1l1', 'distance': 0.0}])" ] - }, + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "network.recall(252), network.recall(133), network.recall([252, 133]) " + ] + }, + { + "cell_type": "markdown", + "id": "41ca197f", + "metadata": {}, + "source": [ + "## Integration with `modelskill`\n", + "\n", + "Wrap a `Network` in a `NetworkModelResult` to make it compatible with the standard `modelskill` comparison workflow. The `item` argument selects which quantity to use when more than one is available in the network:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "edec2e5a", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T14:20:05.028134Z", + "iopub.status.busy": "2026-08-25T14:20:05.027832Z", + "iopub.status.idle": "2026-08-25T14:20:05.040168Z", + "shell.execute_reply": "2026-08-25T14:20:05.038998Z" + } + }, + "outputs": [ { - "cell_type": "code", - "execution_count": 15, - "id": "66d1b420", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
nbiasrmseurmsemaeccsir2
observation
water_level@sens2800.0072410.0972850.0970150.0777890.8507820.0005010.721882
\n", - "
" - ], - "text/plain": [ - " n bias rmse urmse mae cc \\\n", - "observation \n", - "water_level@sens2 80 0.007241 0.097285 0.097015 0.077789 0.850782 \n", - "\n", - " si r2 \n", - "observation \n", - "water_level@sens2 0.000501 0.721882 " - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# The name is taken from the name of the series\n", - "node_id = network.find(reach=\"117l1\", distance=48.7)\n", - "single_obs = ms.NodeObservation(sensor_2, at=node_id)\n", - "cmp = ms.match(single_obs, network_model)\n", - "cmp.skill()" + "data": { + "text/plain": [ + ": WaterLevel" ] - }, + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "network_model = ms.NetworkModelResult(network, item=\"WaterLevel\")\n", + "network_model" + ] + }, + { + "cell_type": "markdown", + "id": "5905e267", + "metadata": {}, + "source": [ + "To evaluate the model we need observations at network nodes. These are represented by `NodeObservation`, which is addressed the way the model names the location: a node's name, or a break point as a `(reach, distance)` pair. The integers the graph uses are an internal index, and observations never mention them.\n", + "\n", + "In this example we create synthetic sensor observations by extracting data from the network dataset and adding random noise to simulate real-world measurement error and timing jitter:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "817e1800", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T14:20:05.043090Z", + "iopub.status.busy": "2026-08-25T14:20:05.042858Z", + "iopub.status.idle": "2026-08-25T14:20:05.064739Z", + "shell.execute_reply": "2026-08-25T14:20:05.063884Z" + } + }, + "outputs": [], + "source": [ + "ds = network.to_dataset()\n", + "\n", + "# Script to generate dummy sensor data. Two sensors sit at named nodes and one\n", + "# at a break point along a reach, which is the pair of forms an observation takes.\n", + "sensor_locations = [\"98\", (\"117l1\", 48.7), \"101\"]\n", + "\n", + "\n", + "def graph_id(at):\n", + " if isinstance(at, str):\n", + " return network.find(node=at)\n", + " reach, distance = at\n", + " return network.find(reach=reach, distance=distance)\n", + "\n", + "\n", + "sensor_1, sensor_2, sensor_3 = (\n", + " ds[\"WaterLevel\"].sel(node=graph_id(at)).to_pandas().rename(f\"water_level@sens{n}\")\n", + " for n, at in enumerate(sensor_locations, start=1)\n", + ")\n", + "\n", + "perfect_sensors = [sensor_1, sensor_2, sensor_3]\n", + "real_sensors = []\n", + "\n", + "for n, sensor in enumerate(perfect_sensors, start=1):\n", + " sensor += np.random.normal(0, 0.1, len(sensor))\n", + " sensor.index = [sensor.index[i] + pd.Timedelta(s, unit=\"s\") for i, s in enumerate(np.random.uniform(-10, 10, len(sensor)))]\n", + " sensor.sort_index(inplace=True)\n", + " if n == 2:\n", + " sensor = sensor.iloc[30:]\n", + " if n == 3:\n", + " sensor = pd.concat([sensor.iloc[:50], sensor.iloc[70:]])\n", + "\n", + " real_sensors.append(sensor)\n", + " sensor.to_csv(f\"../tests/testdata/network_sensor_{n}.csv\")\n", + "\n", + "sensor_1 = real_sensors[0]\n", + "sensor_2 = real_sensors[1]\n", + "sensor_3 = real_sensors[2]" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "66d1b420", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T14:20:05.067278Z", + "iopub.status.busy": "2026-08-25T14:20:05.067089Z", + "iopub.status.idle": "2026-08-25T14:20:05.115372Z", + "shell.execute_reply": "2026-08-25T14:20:05.114441Z" + } + }, + "outputs": [ { - "cell_type": "code", - "execution_count": 16, - "id": "ed1f9094", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
nbiasrmseurmsemaeccsir2
observation
network_sensor_2800.0072410.0972850.0970150.0777890.8507820.0005010.721882
\n", - "
" - ], - "text/plain": [ - " n bias rmse urmse mae cc \\\n", - "observation \n", - "network_sensor_2 80 0.007241 0.097285 0.097015 0.077789 0.850782 \n", - "\n", - " si r2 \n", - "observation \n", - "network_sensor_2 0.000501 0.721882 " - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
nbiasrmseurmsemaeccsir2
observation
water_level@sens2790.0112470.1060830.1054850.0825450.851650.0005450.720208
\n", + "
" ], - "source": [ - "# The name is taken from the name of the file\n", - "path_to_sensor2 = \"../tests/testdata/network_sensor_2.csv\"\n", - "single_obs = ms.NodeObservation(path_to_sensor2, at=node_id)\n", - "cmp = ms.match(single_obs, network_model)\n", - "cmp.skill()" + "text/plain": [ + " n bias rmse urmse mae cc \\\n", + "observation \n", + "water_level@sens2 79 0.011247 0.106083 0.105485 0.082545 0.85165 \n", + "\n", + " si r2 \n", + "observation \n", + "water_level@sens2 0.000545 0.720208 " ] - }, + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# The name is taken from the name of the series\n", + "# sensor 2 sits at a break point, addressed by its reach and distance along it\n", + "at = (\"117l1\", 48.7)\n", + "single_obs = ms.NodeObservation(sensor_2, at=at)\n", + "cmp = ms.match(single_obs, network_model)\n", + "cmp.skill()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "ed1f9094", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T14:20:05.117280Z", + "iopub.status.busy": "2026-08-25T14:20:05.117046Z", + "iopub.status.idle": "2026-08-25T14:20:05.150538Z", + "shell.execute_reply": "2026-08-25T14:20:05.149228Z" + } + }, + "outputs": [ { - "cell_type": "code", - "execution_count": 17, - "id": "de621fec", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
nbiasrmseurmsemaeccsir2
observation
Sensor 2800.0072410.0972850.0970150.0777890.8507820.0005010.721882
\n", - "
" - ], - "text/plain": [ - " n bias rmse urmse mae cc si \\\n", - "observation \n", - "Sensor 2 80 0.007241 0.097285 0.097015 0.077789 0.850782 0.000501 \n", - "\n", - " r2 \n", - "observation \n", - "Sensor 2 0.721882 " - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
nbiasrmseurmsemaeccsir2
observation
network_sensor_2790.0112470.1060830.1054850.0825450.851650.0005450.720208
\n", + "
" ], - "source": [ - "# The name is passed\n", - "single_obs = ms.NodeObservation(path_to_sensor2, at=node_id, name=\"Sensor 2\")\n", - "cmp = ms.match(single_obs, network_model)\n", - "cmp.skill()" + "text/plain": [ + " n bias rmse urmse mae cc \\\n", + "observation \n", + "network_sensor_2 79 0.011247 0.106083 0.105485 0.082545 0.85165 \n", + "\n", + " si r2 \n", + "observation \n", + "network_sensor_2 0.000545 0.720208 " ] - }, - { - "cell_type": "markdown", - "id": "2357349d", - "metadata": {}, - "source": [ - "### Plotting" - ] - }, + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# The name is taken from the name of the file\n", + "path_to_sensor2 = \"../tests/testdata/network_sensor_2.csv\"\n", + "single_obs = ms.NodeObservation(path_to_sensor2, at=at)\n", + "cmp = ms.match(single_obs, network_model)\n", + "cmp.skill()" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "de621fec", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T14:20:05.152192Z", + "iopub.status.busy": "2026-08-25T14:20:05.152048Z", + "iopub.status.idle": "2026-08-25T14:20:05.176449Z", + "shell.execute_reply": "2026-08-25T14:20:05.175587Z" + } + }, + "outputs": [ { - "cell_type": "code", - "execution_count": 18, - "id": "923a1d93", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAsEAAAK9CAYAAADFUbHOAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjksIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvJkbTWQAAAAlwSFlzAAAPYQAAD2EBqD+naQAAvQ9JREFUeJzs3Qd4U1X/B/BvWzopbUFahpa9BAUUEFwUlTJUFBeKtICCoFRx/eUV90BBRMXRgmhlOABFBKsiKihliIrSFxFQpogyymoLpenK//mda/qmpSNpxr03+X6eJ8ab5tyc3Cblm5PfPSfAarVaQURERETkRwL17gARERERkbcxBBMRERGR32EIJiIiIiK/wxBMRERERH6HIZiIiIiI/A5DMBERERH5HYZgIiIiIvI7DMFERERE5HcYgomIiIjI7zAEExH5gT59+qgLERFpGIKJSFcffvghAgIC8Mknn5z2sy5duqifffvtt6f9rFmzZrjooouceqy0tDTMmTMHnrBnzx7V12nTpnlk/77ixIkTePLJJ3HOOeegbt26OOOMM9C1a1fce++9+Oeff+CLSktL1evummuuQXx8vHre8vwnTZqEgoICvbtH5LcYgolIV5dccom6XrNmTbnbc3NzsXnzZtSpUwdr164t97O//vpLXWxtjRCCqWZFRUXo3bs3XnzxRVx66aV4+eWX8cgjj+D888/HBx98gD/++MMnD2N+fj5uu+02ZGdn484778T06dNxwQUXqA8DAwcOhNVq1buLRH6pjt4dICL/1rRpU7Rs2fK0EPz999+rcHDTTTed9jPbtrMh2BOKi4vVSB/VbMmSJdi4cSPef/993HrrreV+JiOihYWFpj2M8hqQ/oeFhZ32s5CQEPVBzv6bizvuuAMtWrRQQXjFihXo27evl3tMRBwJJiLdSZiVcHTq1Kmy2yQ0dOrUSY2UrV+/vlzQlJ9J6cHFF1+stmfPno3LL78ccXFxCA0NRceOHTFjxoxyjyGB47fffsOqVatUW7nY18geP34c9913n/q6WvbRpk0bvPDCC+Ue177kQUbzWrdure67ZcsWh5+rxWJRwUf2L23l8SZMmKBut5Gvyi+77LLT2kpfzjzzTNx4443lbpO+yLGSANaoUSOMHTsWx44dc7hPtXncBQsWoFu3bqhXrx6ioqJw7rnn4tVXX612/zt37lTXtt+bPem77Mfetm3b1GM2aNBA/bx79+749NNPy91HRvbldyKviQceeACxsbGq3OC6665TI6/2NmzYgP79+6Nhw4YIDw9XH75uv/32cvc5efIkHnzwwbLXQfv27dXvu+JorTzm3XffrQK9HHu575dfflnp85YQXFnpjvRRbN26tdrjRkSewZFgIjJECH733Xfxww8/lAVT28iZXHJyclRpROfOnct+1qFDB1VPKiTwShCRmkspn8jIyMC4ceNUeEtJSVH3kaB4zz33IDIyEo8++qi6TQKj7evqhIQE/P333ypASr3xunXrMHHiROzfv1+1tSehW0Yux4wZo8KPhDRHRoPlPtJHGcmWtmeffTZ+/fVXvPLKK6oUQEZKxc0334ynnnoKBw4cQOPGjcvaSzupm73lllvKbpP+ShCUr9vHjx+P3bt344033lAfKuQ4BQcHO/x7cPRxv/76awwdOhRXXHGF+qBgC3LyeFLbW5XmzZur63nz5uGxxx5TQbIq8oFFwrKE74cfflgFW6kfHzx4MD7++OOyAGkjv9v69eurDxjyYUV+ZxJSFy5cqH5+6NAh9OvXT4Vk2V9MTIy63+LFi8v2IUFXfj9Sgz5q1ChVq7x8+XI89NBD6rUhvyd7K1euVH2Sx5FgLR+0nCHHWUhbItKBlYhIZ7/99psMs1mfffZZtV1UVGStW7eude7cuWq7UaNG1tTUVPX/ubm51qCgIOsdd9xR1j4/P/+0ffbv39/aqlWrcrd16tTJmpCQcNp95XHl8f74449ytz/88MPqsfbu3au2d+/erfoZFRVlPXToULn72n724osvVvk83333XWtgYKB19erV5W6fOXOmart27Vq1/fvvv6vt119/vdz9xo0bZ42MjCx7vrIfud/7779f7n5ffvnlabfL867sudtz9HHvvfdedQyKi4utzpD27du3V4/RvHlz68iRI63p6enWgwcPnnbfK664wnruuedaCwoKym4rLS21XnTRRda2bduW3TZ79my1v759+6qf29x///3qd3f8+HG1/cknn6j7/fTTT1X2b8mSJeo+kyZNKnf7jTfeaA0ICLDu2LGj7Da5n/wu5bVbW9JnOY7Hjh2r9T6IqPZYDkFEupMRURnVtdX6/ve//1VfS9u+QpZr28lxUitcUlJSrh5Yvtq2kVHjw4cPq5HdXbt2qe2afPTRR+pE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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
nbiasrmseurmsemaeccsir2
observation
Sensor 2790.0112470.1060830.1054850.0825450.851650.0005450.720208
\n", + "
" ], - "source": [ - "cmp = ms.match(single_obs, network_model)\n", - "cmp.plot()\n", - "cmp.plot.timeseries();" + "text/plain": [ + " n bias rmse urmse mae cc si \\\n", + "observation \n", + "Sensor 2 79 0.011247 0.106083 0.105485 0.082545 0.85165 0.000545 \n", + "\n", + " r2 \n", + "observation \n", + "Sensor 2 0.720208 " ] - }, + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# The name is passed\n", + "single_obs = ms.NodeObservation(path_to_sensor2, at=at, name=\"Sensor 2\")\n", + "cmp = ms.match(single_obs, network_model)\n", + "cmp.skill()" + ] + }, + { + "cell_type": "markdown", + "id": "2357349d", + "metadata": {}, + "source": [ + "### Plotting" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "923a1d93", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T14:20:05.178104Z", + "iopub.status.busy": "2026-08-25T14:20:05.177945Z", + "iopub.status.idle": "2026-08-25T14:20:05.761740Z", + "shell.execute_reply": "2026-08-25T14:20:05.760245Z" + } + }, + "outputs": [ { - "cell_type": "markdown", - "id": "02e77cf0", - "metadata": {}, - "source": [ - "## Multiple sensors\n", - "\n", - "When you have observations at several nodes you can use `NodeObservation.from_multiple()` to create a list of `NodeObservation` objects. Pass `nodes` as a `dict` mapping each node ID to either a column name/index within a shared `data` source, or to a separate data source entirely:" + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAsEAAAK9CAYAAADFUbHOAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjksIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvJkbTWQAAAAlwSFlzAAAPYQAAD2EBqD+naQAAo6FJREFUeJzs3XlcVPX+x/HXgDMgKLhBZIEaaeaClLZcK7WuaFa23O6tLLesa4tmu2nlbb1pZWUL2maaXjPLm5W31MwWyrJ+lERllqKmlghugCAMMOf3x4mJQcABhpkz8H4+HjzqzJw55zMzqO/5zvd8PzbDMAxERERERJqRkEAXICIiIiLibwrBIiIiItLsKASLiIiISLOjECwiIiIizY5CsIiIiIg0OwrBIiIiItLsKASLiIiISLOjECwiIiIizY5CsIiIiIg0OwrBIiLNwKBBgxg0aFCgyxARsQyFYBEJqDfeeAObzcayZcsOu69Pnz7YbDY+/vjjw+5LSEigf//+dTrX7NmzmT9/fn1LrdW2bduw2WzMnDmzUY7fVBw8eJD77ruPXr16ERkZSfv27UlOTubmm2/m999/D3R5jcLlcjF//nwuvPBC4uPjiYyMpFevXjz88MMUFxcHujyRZkshWEQC6swzzwTg888/97g9Pz+fH374gRYtWrB27VqP+3bs2MGOHTvcj/VWY4ZgObLS0lIGDBjA448/zllnncWTTz7J3Xffzcknn8xrr73GL7/8EugSG0VRURFXX301ubm5XH/99cyaNYtTTz2V++67j2HDhmEYRqBLFGmWWgS6ABFp3jp27EiXLl0OC8FffvklhmHwj3/847D7KrbrGoIbQ1lZGS6XK9BlBIW3336b9evXs2jRIq688kqP+4qLi3E6nQGqrOFcLhdOp5Pw8PDD7nM4HKxdu9bjm4t//vOfdO7cmfvuu481a9YwePBgf5YrImgkWEQs4Mwzz2T9+vUcOnTIfdvatWvp2bMnw4YNY926dR5Bc+3atdhsNs444wwA5s2bxznnnENsbCxhYWH06NGDOXPmeJyjc+fO/Pjjj3z66afYbDZsNpvHHNkDBw5wyy23EB8fT1hYGMcffzyPPvqox3krT3mYNWsWiYmJhIWFsWHDBq+fa0lJCffddx/HH388YWFhxMfHM3nyZEpKStz79OrVi7PPPvuwx7pcLo455hj+/ve/e9w2a9YsevbsSXh4OEcddRTXXXcd+/fv97qm+pz39ddfp2/fvrRu3ZqoqCh69+7N008/Xevxs7KyANzvW2Xh4eFERUV53LZx40b+/ve/065dO8LDw+nXrx/vvvuuxz7z58/HZrOxdu1abrvtNmJiYoiMjOSSSy4hNzfXY9/09HSGDh1Khw4daNmyJV26dGHcuHEe+xQWFnL77be7fw9OOOEEZs6cedhorc1mY+LEiSxatIiePXsSFhbGypUrq33eDoej2qk7l1xyCQA//fRTtY8TkcalkWARCbgzzzyThQsX8tVXX7mDacXIWf/+/cnLy+OHH34gKSnJfV/37t1p3749AHPmzKFnz55ceOGFtGjRguXLl3PjjTficrmYMGECALNmzeKmm26iVatW3HPPPQAcddRRgPl19cCBA/ntt9+47rrrSEhI4IsvvmDq1Kns2rWLWbNmedQ7b948iouLGT9+PGFhYbRr186r0WCXy8WFF17I559/zvjx4znxxBP5/vvveeqpp/jll194++23Abj88su5//77yc7OJi4uzv34zz//nN9//50rrrjCfdt1113H/Pnzufrqq5k0aRJbt27lueeeY/369axduxa73e71++DteVevXs2IESP461//yqOPPgqYQW7t2rXcfPPNNR6/U6dOACxYsIB7770Xm81W474//vgjZ5xxBscccwxTpkwhMjKSN954g4svvpj//ve/7gBZ4aabbqJt27bcd999bNu2jVmzZjFx4kSWLFkCQE5ODkOGDCEmJoYpU6bQpk0btm3bxltvveU+hmEYXHjhhXz88cdcc801JCcns2rVKu68805+++03nnrqKY9zfvTRR7zxxhtMnDiRDh060LlzZy9e5T9lZ2cD0KFDhzo9TkR8xBARCbAff/zRAIyHHnrIMAzDKC0tNSIjI41XX33VMAzDOOqoo4zU1FTDMAwjPz/fCA0NNf75z3+6H19UVHTYMYcOHWocd9xxHrf17NnTGDhw4GH7PvTQQ0ZkZKTxyy+/eNw+ZcoUIzQ01Ni+fbthGIaxdetWAzCioqKMnJwcj30r7nv88cdrfJ4LFy40QkJCjM8++8zj9ueff94AjLVr1xqGYRg///yzARjPPvusx3433nij0apVK/fz/eyzzwzAWLRokcd+K1euPOz2gQMHVvvcK/P2vDfffLMRFRVllJWV1Xq8qoqKiowTTjjBAIxOnToZY8eONebOnWvs3r37sH3/+te/Gr179zaKi4vdt7lcLqN///5G165d3bfNmzfPAIzBgwcbLpfLffutt95qhIaGGgcOHDAMwzCWLVtmAMb//d//1Vjf22+/bQDGww8/7HH73//+d8NmsxmbN2923wYYISEhxo8//lin16CywYMHG1FRUcb+/fvrfQwRqT9NhxCRgDvxxBNp3769e67vd999R2Fhofsr5P79+7svjvvyyy8pLy/3mA/csmVL9//n5eWxZ88eBg4cyJYtW8jLyzvi+d98803OOuss2rZty549e9w/gwcPpry8nLS0NI/9L730UmJiYur8PN98801OPPFEunfv7nGec845B8C9Cka3bt1ITk52j2IClJeXs3TpUoYPH+5+vm+++SbR0dGkpKR4HK9v3760atWq2lU1auPtedu0aUNhYSGrV6+u0/FbtmzJV199xZ133gmYUxmuueYajj76aG666Sb3lJB9+/bx0Ucfcdlll1FQUOB+Xnv37mXo0KFs2rSJ3377zePY48eP9xhZPuussygvL+fXX3911wzwv//9j9LS0mrre//99wkNDWXSpEket99+++0YhsGKFSs8bh84cCA9evSo02tQ4ZFHHuHDDz9kxowZ7tpExL8UgkUk4Gw2G/3793fP/V27di2xsbEcf/zxgGcIrvhv5RC8du1aBg8eTGRkJG3atCEmJoa7774bwKsQvGnTJlauXElMTIzHT8XFSjk5OR77d+nSpV7Pc9OmTfz444+Hnadbt26Hnefyyy9n7dq17rD3ySefkJOTw+WXX+5xvLy8PGJjYw875sGDBw+r2xvenPfGG2+kW7duDBs2jGOPPZZx48bVOB+2qujoaB577DG2bdvGtm3bmDt3LieccALPPfccDz30EACbN2/GMAymTZt22PO67777DnutwFwyr7K2bdsCuOdGDxw4kEsvvZQHHniADh06cNFFFzFv3jyPudi//vorHTt2pHXr1h7HOvHEE933V1bf34MlS5Zw7733cs0113DDDTfU6xgi0nCaEywilnDmmWeyfPlyvv/++8OupO/fv797Xubnn39Ox44dOe644wDzYqu//vWvdO/enSeffJL4+HgcDgfvv/8+Tz31lNdzdVNSUpg8eXK191eE1AqVR57rwuVy0bt3b5588slq74+Pj3f//+WXX87UqVN58803ueWWW3jjjTeIjo7m3HPP9ThebGwsixYtqvZ49Rmt9ua8sbGxZGRksGrVKlasWMGKFSuYN28eo0eP5tVXX/X6XJ06dWLcuHFccsklHHfccSxatIiHH37Y/Z7dcccdDB06tNrHVnxAqhAaGlrtfsYfF7TZbDaWLl3KunXrWL58OatWrWLcuHE88cQTrFu3jlatWnldd4X6/B6sXr2a0aNHc/755/P888/X+fEi4jsKwSJiCZXXC167di233HKL+76+ffsSFhbGJ598wldffcV5553nvm/58uWUlJTw7rvveowGVjcVoKYLsRITEzl48GCjL1OVmJjId999x1//+tdaLwoDc5Tx1FNPZcmSJUycOJG33nqLiy++mLCwMI/jffjhh5xxxhn1Dub1OS+YKx4MHz6c4cOH43K5uPHGG3nhhReYNm3aYQH1SNq2bUtiYiI//PADgPsDjt1u9/l7cvrpp3P66afz73//m9dee42rrrqK119/nWuvvZZOnTrx4YcfUlBQ4DEavHHjRuDPC/vq66uvvuKSSy6hX79+vPHGG7RooX+CRQJJ0yFExBL69etHeHg4ixYt4rfffvMYCQ4LC+Pkk08mNTWVwsJCj6kQFSOARqUlrPLy8pg3b95h54iMjOTAgQOH3X7ZZZfx5ZdfsmrVqsPuO3DgAGVlZQ15ah7n+e2333jppZcOu+/QoUMUFhZ63Hb55Zezbt06XnnlFfbs2eMxJaHieOXl5e5pBJWVlZVV+1y9caTz7t2712M7JCTEvXJH5ekFVX333Xfs2bPnsNt//fVXNmzYwAknnACYI82DBg3ihRdeYNeuXYftX3XpM2/s37//sGXOkpOTPWo+77zzKC8v57nnnvPY76mnnsJmszFs2LA6n7fCTz/9xPnnn0/nzp353//+57MPLSJSf/oYKiKW4HA4OOWUU/jss88ICwujb9++Hvf379+fJ554AvCcDzxkyBD3qOR1113HwYMHeemll4iNjT0sQPXt25c5c+bw8MMPc/zxxxMbG8s555zDnXfeybvvvssFF1zA2LFj6du3L4WFhXz//fcsXbqUbdu2eb2M1Zo1a6pthXvxxRczatQo3njjDa6//no+/vhjzjjjDMrLy9m4cSNvvPEGq1atol+/fu7HXHbZZdxxxx3ccccdtGvX7rBR0YEDB3Ldddcxffp0MjIyGDJkCHa7nU2bNvHmm2/y9NNPe6zt660jnffaa69l3759nHPOORx77LH8+uuvPPvssyQnJ7vnz1Zn9erV3HfffVx44YWcfvrptGrVii1btvDKK69QUlLC/fff7943NTWVM888k969e/PPf/6T4447jt27d/Pll1+yc+dOvvvuuzo9p1dffZXZs2dzySWXkJiYSEFBAS+99BJRUVHubxaGDx/O2WefzT333MO2bdvo06cPH3zwAe+88w633HILiYmJdTpnhYKCAoYOHcr+/fu58847ee+99zzuT0xM5C9/+Uu9ji0iDRDQtSlERCqZOnWqARj9+/c/7L633nrLAIzWrVsftjTXu+++ayQlJRnh4eFG586djUcffdR45ZVXDMDYunWre7/s7Gzj/PPPN1q3bm0AHkuGFRQUGFOnTjWOP/54w+FwGB06dDD69+9vzJw503A6nYZh1L4MWsV9Nf0sXLjQMAzDcDqdxqOPPmr07NnTCAsLM9q2bWv07dvXeOCBB4y8vLzDjnvGGWcYgHHttdfW+Lq9+OKLRt++fY2WLVsarVu3Nnr37m1MnjzZ+P333937eLNEmrfnXbp0qTFkyBAjNjbWcDgcRkJCgnHdddcZu3btqvWYW7ZsMf71r38Zp59+uhEbG2u0aNHCiImJMc4//3zjo48+Omz/rKwsY/To0UZcXJxht9uNY445xrjggguMpUuXuvepWCKt6tJnH3/8sQEYH3/8sWEYhvHtt98aI0aMMBISEoywsDAjNjbWuOCCC4z09HSPxxUUFBi33nqr0bFjR8Nutxtdu3Y1Hn/8cY/l1wzDXCJtwoQJtT7fCkf63RgzZoxXxxER37IZhpqWi4iIiEjzojnBIiIiItLsKASLiIiISLOjECwiIiIizY5CsIiIiIg0OwrBIiIiItLsKASLiIiISLOjZhn15HK5+P3332nduvUR25+KiIiISOMzDIOCggI6duxISEjtY71aJ7iedu7cSXx8fKDLEBERERHOAf4HtARs7Nixg2OPPbbWR2gkuJ5at24NwI4dO4iKivLLOXNzcwGIiYnxy/msXIcValAdqiMY6rBCDapDdQRDHVaoQXXUr45PPgnl8ssjKC62MXjwIT788M+cVhuF4HqqmAIRFRXltxBcXFzsPmcgWaEOK9SgOlRHMNRhhRpUh+oIhjqs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pJQwd24e8fHB9XocKZ9VppHgIFNUVMTi7dv5NCmZYVlb+DQpmcXbt1NUVOTXOvLy8vj999/p06dPrfslJSWxc+fOWr+e8OWxREREpO6qrgLRlEeAKygEB5nIyEhm9OtHmx3bOXB8V9rsMEeEIyMj/VpHRRBt3bp1rftV3F9bcPXlsURERKRumvIyaLVRCA4yhYWFTElP50B8Am02b+JAfAJT0tPdc4T9xdtAWlBQgM1mo0OHDuzbt4/s7Gz3T94fs+zrcywRERFpuOYagEEhOOhEREQwIiGBgZkZrEg8joGZGYxISCAiIsKvdURHR9OxY0cyMzNr3S8zM5Njjz0Wh8PB3/72N44++mj3z80331zvY4mIiEjDNOcADFoiLejYbDbGDx3CyKIiIiMjee+EbkRERPjtorjKhg8fzgsvvMDnn3/OmWeeedj9n332Gdu2beO2224D4IknnmD//v3u+zt27FjvY4mIiEj9NfcADBoJDko2m809BzgyMjIgARjgjjvuICIiguuuu469e/d63Ldv3z6uv/56oqKimDhxIgB9+/Zl8ODB7p8ePXrU+1giIiJSPwrAJo0ES70df/zxLFiwgBEjRtC7d2+uueYaunTpwrZt25g7dy779+/n9ddfP2KjDF8fS0RERKqnAPwnhWBpkEsvvZRvv/2W6dOn8/LLL5OTk4PL5SI8PJxvvvnGY7TXn8cSERERTwrAnjQdQhqsV69eLFq0iF27dlFeXs6rr75KSUkJM2bMCOixRERExKQAfDiNBIvPjR49ml27djFlyhSOPfZYHnnkEUscS0REpDlSAK6eQrA0irvuuou77rrLcscSERFpThSAa6bpECIiIiJNkAJw7RSCRURERJoYBeAjUwgWERERaUIUgL2jECwiIiLSRCgAe08hWERERKQJUACuG4VgERERkSCnAFx3CsEiIiIiQUwBuH4UgkVERESCVHMNwIZhUFhYCEBhYSGGYdT5GGqWISIiImJhLpeLzMxMsrOziYuLIykpiZCQkGYdgF9c9QGLt29nRr9+TElPZ0RCAuOHDqnTcRSCpVFs27aNLl26MG/ePMaOHRvockRERIJWZmYmy5cvx+l04nA4ANi7N7lZBmCAoqIiFm/fzqdJyQzL2sKBpGTIzGBkUVGdjqPpEFIvCxYswGazVfszZcqUah/z/vvvc//99/u30HpwuVw89thjdOnShfDwcJKSkli8eHGdjvHhhx9yzjnnEB0dTevWrenbty9Llizx2OfgwYPccsstHHvssYSFhXHiiScyZ84cXz4Vr7lcLubMmUNycjItW7akffv2nHPOOXz33XeH7Vff12b+/Pk1/s5kZ2d77LtkyRJGjhxJ165dsdlsDBo0yFdPVUQk6GRnZ+N0OklMTMTpdLJihbPZBmCAyMhIZvTrR5sd2zlwfFfa7DBHhCMjI+t0HI0ES4M8+OCDdOnSxeO2Xr160alTJw4dOoTdbnff/v7775Oammr5IHzPPfcwY8YM/vnPf3LKKafwzjvvcOWVV2Kz2bjiiiuO+Ph58+ZxzTXXkJKSwiOPPEJoaCg///wzO3bscO9TXl7O0KFDSU9PZ8KECXTt2pVVq1Zx4403sn//fu6+++7GfIqHGTduHIsWLWL06NFMnDiRwsJC1q9fT05Ojsd+DX1toPrfmTZt2nhsz5kzh2+++YZTTjmFvXv3Nui5iYgEu7i4OBwOB1lZWezY0Y3XX+9HSUnzDMBgzgGekp7OgaRk2mzexIH4BKakp/PeCd3qdiBD6iUvL88AjLy8vGrvP3TokLFhwwbj0KFDPjun0+k0nE6nz47XkDpefvllAzD+7//+z+vHTZgwwfDVr1xjvRY7d+407Ha7MWHCBPdtLpfLOOuss4xjjz3WKCsrq7WOrVu3Gi1btjQmTZpU63neeOMNAzDmzp3rcfull15qhIeHG7t3765T3TW9HoAxb968Wh+7ZMkSAzDeeuutWvfz5rWp7X2ZN2+e178z27dvN8rLyw3DMIyePXsaAwcOPOJjDOPPP3c7d+6s82vYGHbv3h3wOqxQg+pQHcFQhxVqqKmO8vJyY/369cYjj3xlhIWVG2AYF1xgGMXF/q0jEKqrw+VyGc+vWGkMfOFF48tvvjUGvvCi8fyKlYbL5TpiPqtM0yGkUWzbtg2bzcb8+fMBGDt2LKmpqQAeX4NX2LVrFxs3bqS0tLTGYxqGQefOnbnooosOu6+4uJjo6Giuu+66BtX9zjvvUFpayo033ui+zWazccMNN7Bz506+/PLLWh///PPPU15ezoMPPgiYUx6Maq5Y/eyzzwAOGz294oorKC4u5p133gEgJyeHmJgYBg0a5HGczZs3ExkZyeWXX16/J1rJk08+yamnnsoll1yCy+VyX21bVUNfm8oKCgooLy+v8f74+HhCQvTXk4gIQEhICHv3JvPgg6dSUhLSbEeAK9hsNsYPHcJ7V13J6SefxHtXXcn4oUM8coU39K+MNEheXh579uzx+KnOddddR0pKCgALFy50/1SYOnUqJ554Ir/99luN57LZbIwcOZIVK1awb98+j/uWL19Ofn4+I0eOdN9Wta6afkpKStyPWb9+PZGRkZx44okexz/11FPd99fmww8/pHv37rz//vsce+yxtG7dmvbt2zNt2jRcLpd7v5KSEkJDQ90XOFSIiIgA4JtvvgEgNjaWOXPm8Omnn/Lss88C5rzcsWPH0rp1a2bPnl1rPUeSn5/P119/zSmnnMLdd99NdHQ0rVq14rjjjuONN97w2Lehr02Fs88+m6ioKCIiIrjwwgvZtGlTg56DiFiDy+UiIyODlStXkpGR4fF3njRMc10FojY2m809BzgyMrLOARg0J9j/DAPqePWiW8UoaaV5tg0WEQH1+MWpMHjw4MNuq27k8y9/+QvdunVj9erVHkG1rkaPHs2///1vli5dyvjx4923/+c//6Fz586ceeaZ7ttiYmK8OmblFSx27drFUUcdddgfpqOPPhqA33//vdZjbdq0idDQUK6++momT55Mnz59eOutt3j44YcpKytj+vTpAJxwwgmUl5ezbt06j5orRogrfxj4+9//zogRI5g6dSrDhg3jnXfeYe3atbz99tu0b9/eq+dYk6ysLAzD4PXXX6dFixY89thjREdH8/TTT3PFFVcQFRXFueee65PXJiIigrFjx7pD8DfffMOTTz5J//79+fbbb4mPj2/QcxGRwKpuBYPk5OTAFtUEKAA3HoVgfysqglat6vVQH0bfPx08CHW8mrKy1NRUunWr40T0asyfP989daI23bp147TTTmPx4sXuELxv3z5WrFjB5MmTPQLa6tWrvTp3z5493f9/6NAhwqr52yU8PNx9f20OHjyIy+VixowZ3HXXXQBceuml7Nu3j6effpq7776b1q1bc+WVV/Lggw8ybtw4UlNT6dq1Kx988IF7ZLfqeZ577jk++eQT/v73v/PLL78watSow6aFFBUVVTud5ODBgx4j9KGhobRt29Z9H8DevXtZt24dp512GgAXXnghXbp04eGHH3aH4Ia+NpdddhmXXXaZe/viiy9m6NChDBgwgH//+988//zztT5eRKyt8goGWVlZh636InWnANy4AjodIi0tjeHDh9OxY0dsNhtvv/22x/27d+9m7NixdOzYkYiICM4999wavzo1DINhw4ZVe5zq9v3Xv/7F0UcfTcuWLRk8eLC+kq2nU089lcGDB3v8NLbRo0fzxRdf8OuvvwLw5ptvUlpayqhRozz2q1pXTT8VI5kALVu29JgeUaG4uNh9f20q7h8xYoTH7SNGjODQoUPuKQNxcXG8++67lJSUMGTIELp06cKdd97pnvLQqsoHpXbt2vHMM8+QmZlJdHQ0zzzzzGHnnjlzJjExMR4/ADfddJPHbSeddNJh9Xbp0sUdgCvOP3z4cL7++mvKysp88tpU58wzz+S0007jww8/rPNjRcRaKq9g4HA4iIuLC3RJQU0BuPEFdCS4sLCQPn36MG7cOP72t7953GcYBhdffDF2u5133nmHqKgonnzySQYPHsyGDRsOWwtu1qxZXs8Heeyxx3jmmWd49dVX6dKlC9OmTWPo0KFs2LDBParVaCIizNHXeqgY5bP7ejpEkLniiiu49dZbWbx4MdOmTeM///kP/fr144QTTvDYz9tRiOjoaHeAO/roo/n4448xDOOwC/cAOnbsWOuxOnbsyKZNmzjqqKM8bo+NjQVg//797tsGDBjAli1b+P77791/FiqmFFQ3ur5q1Sr3MXbu3HnYsmIjR45k4MCBHrelpKRw5513MmTIn110KofViudTtd6KmktLSyksLCQ6OrrBr01N4uPj+fnnn+v1WBGxjqSkJACPrmZSP2lpdkaNUgBubAENwcOGDWPYsGHV3rdp0ybWrVvHDz/84P66es6cOcTFxbF48WKuvfZa974ZGRk88cQTpKene4zqVccwDGbNmsW9997r/jp5wYIFHHXUUbz99tter3VabzZb/acfNMacYD+qz6T16rRr145hw4axePFiRo8ezdq1a5k1a9Zh+x3pd6FC5TnBycnJvPzyy/z000/06NHDvc9XX33lvr82ffv2ZdOmTfz2228cd9xx7tsrwm3VecqhoaEex6wYEa06or5y5UpefvllJk+ezKJFixgzZgxfffUVLVr8+Uf4uOOOO+yDAECPHj1qHKHv2LEjcXFx1V6Q+PvvvxMeHk7r1q2Bhr82NdmyZYvX87dFxLpCQkI0B9gHzADcRgHYDyw7J7jia9fKI7MhISGEhYXx+eefu0NwUVERV155JampqV599bJ161ays7M9QkF0dDSnnXYaX375ZY0huKSkxOOr4Pz8fAByc3PdXwdXVlpaSnl5OaWlpYSGhnrxjI+s4mvpQCsrK3Mvb1VWVlbtPNSK2ypeA/jzvczNzT1sFHPXrl3k5eWRmJjo1Uj3FVdcwYgRI7j99tsJDQ3l0ksvPayOFStWePV8evTo4X7seeedh91u57nnnuPpp58GzA9Oc+bM4ZhjjuGUU05x77tr1y727dvnEXYvvfRSXn/9dV588UUeeughwLxi+pVXXqFdu3YkJSXVuAxcbm4ujz76KL1792bgwIHu/Q4cOMC1117LKaecwgMPPMBZZ53F8OHDeeihh7j33nuB2n83Kr8H1fnHP/7Bs88+y4oVK9x/Lvbs2cM777zD2WefTXl5OeXl5V69NhV1VPd+5ubmHhZ2V6xYwTfffMPEiRNrrNEwDAzDqPU5VKj4c7dv3z5LLLGWm5sb6BIsUQOojqpUhycr1GGFGtLS7IwcGU1JiY2UlBJmz84jLy8wtVjh9YC611FQUOD1vpYNwd27dychIYGpU6fywgsvEBkZyVNPPcXOnTvdX78C3HrrrfTv37/atWOrU/EVedWvf4866qhavz6fPn06DzzwQD2eiVQ4+eSTAfM9S0lJITQ01L3O7b333svChQv55Zdf6Ny58xGPNWzYMNq3b89///tfzj33XPd0g8r++te/1rnGY489lptuuoknn3yS0tJS+vXrx7vvvsvnn3/Oq6++6vGBpqLmn376icTERMC8oOycc87hscceY+/evSQlJfHuu++ydu1aZs+e7XFh2V//+ldOO+00jj/+eLKzs5k7dy4HDx5k2bJlHgHutttuY+/evaxYsYLQ0FCGDh3KuHHjmD59OsOHD6dPnz51fp6VTZ48maVLl3L55Zdz8803Ex0dzYsvvkhpaal7vWNvX5uKEFzd+zlw4ED69OlD3759iY6OZv369cyfP5/4+Hj3RYQVPvvsM/dKGXv27KGoqIhHHnkEgLPOOouzzjqrQc9ZRMRqKkaAS0psDBxYwNy5hzQCXFeGQfh//lOX/a0BMJYtW+ZxW3p6utGnTx8DMEJDQ42hQ4caw4YNM84991zDMAzjnXfeMY4//nijoKCg1uNUtnbtWgMwfv/9d4/b//GPfxiXXXZZjY8rLi428vLy3D87duxQx7haun9t3br1sG5lZWVlxk033WTExMQYNpvNo3vcmDFjDMDYunWr1zVcf/31BmC89tprDXk6hykvLzceeeQRo1OnTobD4TB69uxp/Oc//zlsv4qaf/nlF4/bCwoKjJtvvtmIi4szHA6H0bt372off+uttxrHHXecERYWZsTExBhXXnmlkZWV5bHPO++8YwDGE0884XF7fn6+0alTJ6NPnz7u34v6dowzDMPIysoyLrnkEiMqKspo2bKlcc455xhff/11nV+bijqqez/vueceIzk52YiOjjbsdruRkJBg3HDDDUZ2dvZh57nvvvsMoNqf++67r8bnoY5x1qxBdaiOYKgjkDV8+KFhhIcbBhhGSkqxsX27dd6Tim51K1asMNavX+/u5unvOo5o507DGDLEyPvj3wpvOsbZDKOaRV0DwGazsWzZMi6++OLD7svLy8PpdBITE8Npp51Gv379SE1N5ZZbbuGZZ57xGDUrLy8nJCSEs846i08++eSwY23ZsoXExETWr1/vMXdp4MCBJCcnu7/mPZL8/Hyio6PJy8sjKirqsPuLi4vZunUrXbp08dnFdo1yYVyQ1lFaWsodd9zBvHnzyM7OdjeZCEQdoPfEKnVU/LmLiorCbrdX+w2BP+Xk5AAEtA4r1KA6VEcw1BGoGqquAjF7dg5hYdZ5T37//XeP9Z+HDx/u17nfR3xfDAMWL4YJE+DAAfLDwoguKakxn1UW+ElzXoiOjiYmJoZNmzaRnp7unvowZcoUMjMzycjIcP8APPXUU8ybN6/aY3Xp0oW4uDjWrFnjvi0/P5+vvvqKv/zlL43+XMQ3iouLee2117j00ksDFoBFREQaIhiWQau8/rPT6bTW+s9798Lll8NVV8GBA9CvH3z+udcPD+ic4IMHD7J582b39tatW8nIyKBdu3YkJCTw5ptvEhMTQ0JCAt9//z0333wzF198sXu5p7i4uGovhktISKBLly7u7e7duzN9+nQuueQSbDYbt9xyCw8//DBdu3Z1L5HWsWPHakehxVpycnL48MMPeeONN9i7dy8333xzoEsSERGps2AIwGDh9Z/few+uvRaysyE0FP71L5g6FY7QuKmygIbg9PR0zj77bPf2bbfdBsCYMWOYP38+u3bt4rbbbmP37t0cffTRjB49mmnTptX5PD///DN5lS6vnDx5MoWFhYwfP54DBw5w5pln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4MABdu3axcqVK9m7dy/HHXecx37R0dF07dqVAQMGsHTpUjZu3MiyZcuqPeZnn31GTk4OCQkJtGjRghYtWvDrr79y++2309kKAfPAAXOI8e9/h717zbbH6elw550QGhro6pq9qqtAKACLiIgEJ8tOh6gsOjoaMC+WS09P56GHHqpxX8MwMAzD4yK2ykaNGsXgwYM9bhs6dCijRo3i6quv9l3R9fHhh3D11bBzp9n2eMoUuO8+cyRYAq66ZdACcRGciIiINFxAQ/DBgwfZvHmze3vr1q1kZGTQrl07EhISePPNN4mJiSEhIYHvv/+em2++mYsvvpghQ4YAsGXLFpYsWcKQIUOIiYlh586dzJgxg5YtW3Leeee5j9u9e3emT5/OJZdcQvv27Wnfvr1HHXa7nbi4OE444QT/PPGqiorMwPvss+a22h5bjtYBFhERaVoCGoLT09M5++yz3dsVF56NGTOG+fPns2vXLm677TZ2797N0UcfzejRo5k2bZp7//DwcD777DNmzZrF/v37OeqooxgwYABffPGFxwTqn3/+mTyrDtlVbXt8ww3w+OPq+mYhCsAiIiJNT0BD8KBBgzAMo8b7J02aVOsKDh07duT9998/4nlqOwfAtm3bjngMn3M64aGH4JFHzK5vHTvCK6+o65vFKACLiIg0TUExJ7jJ+fFHc/T322/N7SuvhOeeU9c3i1EAFhERabosvTpEk1NeDk88YbY9/vZbtT22MAVgERGRpk0jwf6ybRuMGQNpaeb2sGHw8svmNAixFAVgERGRpk8jwY3NMMy5vr17mwE4MhJefBHee08B2IIUgEVERJoHjQQ3pt27zbbHy5eb22ecAa++ComJga1LqqUALCIi0nxoJLixVLQ9Xr7cbHbx6KPw6acKwBalACwiItK8aCTY1w4cgEmTYOFCczspyfz/pKSAliU1UwAWERFpfjQS7EsffmjO/V240Gx7PHUqfP21ArCFKQCLiIg0TxoJ9oWqbY8TE822x/37B7YuqZUCsIiISPOlkeCGSk+Hk076MwDfcANkZCgAW5wCsIiISPOmkeCGSklR2+MgowAsIiIiGgluKJcLRoyA779XAA4CCsAiIiICGgluuFdegauvDnQV4gUFYBEREamgkeCGuvTSQFcgXlAAFhERkcoUgqXJUwAWERGRqhSCpUlTABYREZHqKARLk6UA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TqeysrJUWlqq48ePa+PGjcrMzFRCQoJeeOEFt/137NihV155RRUVFSooKNBbb72lvLw8Sdc/3aGwsFAHDx7U0aNH9eabb8putyslJUWpqanKzs7WlClT9M477+jYsWN6//339dJLL2nDhg23zJmdna1//vOfeuGFF/S9731PERERrud69+6tzZs36x//+IfKy8v1xBNP6PTp026v7969u3bv3q2PP/5Y//nPfxpduZ09e7a2bt2qBQsWqKKiQr///e/12muvac6cOa25tADgM5RgAGih3r17a8+ePbr99ts1fvx49ezZU48//rgeeOAB7dy5U126dHHbf/bs2dqzZ4/uvvtuPf/881q8eLGysrIkXf+c29dff11Dhw7VnXfeqS1btmj9+vXq2rWrpOu/DGLKlCmaPXu2+vTpo3HjxumDDz5QcnLyLXP26tVL9913n/bv3+92K4QkPf300xo4cKCysrJ0//33Ky4uTuPGjXPbZ86cOQoNDVVaWppiYmIavV944MCB+tOf/qTVq1fr61//up555hk999xzmjp1aguu6PVfzNHS1wBAW9hMS97dAQCAF6SkpOgXv/hFi4qwzWZTcXFxg/IuXS/Vd911V7v6ldUAPIuVYACAX3344YdyOByaMmVKi187ceJEJSYmuh6vXLlSnTt31t/+9jdPRgTQDrESDAAISIcPH5YkhYaGqkePHpKu/3a+G/c2R0dH66tf/arf8gGwNkowAAAAgg63QwAAACDoUIIBAAAQdCjBAAAACDqUYAAAAAQdSjAAAACCDiUYAAAAQYcSDAAAgKBDCQYAAEDQ+T/0xovxi9dvUwAAAABJRU5ErkJggg==", + "text/plain": [ + "
" ] + }, + "metadata": {}, + "output_type": "display_data" }, { - "cell_type": "code", - "execution_count": 19, - "id": "752261bf", - "metadata": {}, - "outputs": [], - "source": [ - "sensor_df = pd.concat(real_sensors, axis=1)" + "data": { + "image/png": "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", + "text/plain": [ + "
" ] - }, + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "cmp = ms.match(single_obs, network_model)\n", + "cmp.plot()\n", + "cmp.plot.timeseries();" + ] + }, + { + "cell_type": "markdown", + "id": "02e77cf0", + "metadata": {}, + "source": [ + "## Multiple sensors\n", + "\n", + "When you have observations at several nodes you can use `NodeObservation.from_multiple()` to create a list of `NodeObservation` objects. Pass `nodes` as a `dict` mapping each node ID to either a column name/index within a shared `data` source, or to a separate data source entirely:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "752261bf", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T14:20:05.765128Z", + "iopub.status.busy": "2026-08-25T14:20:05.764941Z", + "iopub.status.idle": "2026-08-25T14:20:05.775259Z", + "shell.execute_reply": "2026-08-25T14:20:05.772228Z" + } + }, + "outputs": [], + "source": [ + "sensor_df = pd.concat(real_sensors, axis=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "9acfaf6f", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T14:20:05.781391Z", + "iopub.status.busy": "2026-08-25T14:20:05.781041Z", + "iopub.status.idle": "2026-08-25T14:20:06.015467Z", + "shell.execute_reply": "2026-08-25T14:20:06.014152Z" + } + }, + "outputs": [ { - "cell_type": "code", - "execution_count": 20, - "id": "9acfaf6f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
nbiasrmseurmsemaeccsir2
observation
water_level@sens11100.0008440.0989450.0989410.0760150.9749860.0005090.950559
water_level@sens2800.0072410.0972850.0970150.0777890.8507820.0005010.721882
water_level@sens389-0.0208130.1046320.1025410.0821040.9360940.0005290.871119
\n", - "
" - ], - "text/plain": [ - " n bias rmse urmse mae cc \\\n", - "observation \n", - "water_level@sens1 110 0.000844 0.098945 0.098941 0.076015 0.974986 \n", - "water_level@sens2 80 0.007241 0.097285 0.097015 0.077789 0.850782 \n", - "water_level@sens3 89 -0.020813 0.104632 0.102541 0.082104 0.936094 \n", - "\n", - " si r2 \n", - "observation \n", - "water_level@sens1 0.000509 0.950559 \n", - "water_level@sens2 0.000501 0.721882 \n", - "water_level@sens3 0.000529 0.871119 " - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
nbiasrmseurmsemaeccsir2
observation
water_level@sens11100.0058920.1035770.1034090.0807410.9774310.0005310.955162
water_level@sens2790.0112470.1060830.1054850.0825450.8516500.0005450.720208
water_level@sens389-0.0238980.1026050.0997830.0864510.1610080.000509-0.056709
\n", + "
" ], - "source": [ - "multi_obs = ms.NodeObservation.from_multiple(data=sensor_df, nodes={30: \"water_level@sens1\", 54: \"water_level@sens2\", 71: \"water_level@sens3\"})\n", - "ms.match(multi_obs, network_model).skill()" + "text/plain": [ + " n bias rmse urmse mae cc \\\n", + "observation \n", + "water_level@sens1 110 0.005892 0.103577 0.103409 0.080741 0.977431 \n", + "water_level@sens2 79 0.011247 0.106083 0.105485 0.082545 0.851650 \n", + "water_level@sens3 89 -0.023898 0.102605 0.099783 0.086451 0.161008 \n", + "\n", + " si r2 \n", + "observation \n", + "water_level@sens1 0.000531 0.955162 \n", + "water_level@sens2 0.000545 0.720208 \n", + "water_level@sens3 0.000509 -0.056709 " ] - }, + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "multi_obs = ms.NodeObservation.from_multiple(data=sensor_df, nodes=dict(zip(sensor_locations, sensor_df.columns)))\n", + "ms.match(multi_obs, network_model).skill()" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "d7a0acf1", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T14:20:06.018707Z", + "iopub.status.busy": "2026-08-25T14:20:06.018293Z", + "iopub.status.idle": "2026-08-25T14:20:06.126797Z", + "shell.execute_reply": "2026-08-25T14:20:06.124201Z" + } + }, + "outputs": [ { - "cell_type": "code", - "execution_count": 21, - "id": "d7a0acf1", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
nbiasrmseurmsemaeccsir2
observation
water_level@sens11100.0008440.0989450.0989410.0760150.9749860.0005090.950559
network_sensor_2800.0072410.0972850.0970150.0777890.8507820.0005010.721882
water_level@sens389-0.0208130.1046320.1025410.0821040.9360940.0005290.871119
\n", - "
" - ], - "text/plain": [ - " n bias rmse urmse mae cc \\\n", - "observation \n", - "water_level@sens1 110 0.000844 0.098945 0.098941 0.076015 0.974986 \n", - "network_sensor_2 80 0.007241 0.097285 0.097015 0.077789 0.850782 \n", - "water_level@sens3 89 -0.020813 0.104632 0.102541 0.082104 0.936094 \n", - "\n", - " si r2 \n", - "observation \n", - "water_level@sens1 0.000509 0.950559 \n", - "network_sensor_2 0.000501 0.721882 \n", - "water_level@sens3 0.000529 0.871119 " - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
nbiasrmseurmsemaeccsir2
observation
water_level@sens11100.0058920.1035770.1034090.0807410.9774310.0005310.955162
network_sensor_2790.0112470.1060830.1054850.0825450.8516500.0005450.720208
water_level@sens389-0.0238980.1026050.0997830.0864510.1610080.000509-0.056709
\n", + "
" ], - "source": [ - "multi_obs = ms.NodeObservation.from_multiple(nodes={30: sensor_1, 54: path_to_sensor2, 71: sensor_3})\n", - "ms.match(multi_obs, network_model).skill()" + "text/plain": [ + " n bias rmse urmse mae cc \\\n", + "observation \n", + "water_level@sens1 110 0.005892 0.103577 0.103409 0.080741 0.977431 \n", + "network_sensor_2 79 0.011247 0.106083 0.105485 0.082545 0.851650 \n", + "water_level@sens3 89 -0.023898 0.102605 0.099783 0.086451 0.161008 \n", + "\n", + " si r2 \n", + "observation \n", + "water_level@sens1 0.000531 0.955162 \n", + "network_sensor_2 0.000545 0.720208 \n", + "water_level@sens3 0.000509 -0.056709 " ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" } - ], - "metadata": { - "kernelspec": { - "display_name": "modelskill (3.13.13)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.13" - } + ], + "source": [ + "multi_obs = ms.NodeObservation.from_multiple(nodes=dict(zip(sensor_locations, [sensor_1, path_to_sensor2, sensor_3])))\n", + "ms.match(multi_obs, network_model).skill()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "modelskill (3.13.13)", + "language": "python", + "name": "python3" }, - "nbformat": 4, - "nbformat_minor": 5 + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 } diff --git a/pyproject.toml b/pyproject.toml index 890fc7574..e33d442ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,9 @@ classifiers = [ ] [project.optional-dependencies] -networks = ["mikeio1d", "networkx"] +# networkx and xarray arrive through mikeio1d's own network extra, which +# carries the topology layer this package builds on (ADR-013). +network = ["mikeio1d[network]"] [dependency-groups] dev = ["pytest", "plotly >= 4.5", "ruff==0.6.2", "netCDF4", "dask"] @@ -62,7 +64,12 @@ test = [ notebooks = ["nbformat", "nbconvert", "jupyter", "plotly", "shapely", "seaborn"] -networks = ["mikeio1d>=1.2.1", "networkx"] +network = ["mikeio1d[network]"] + +[tool.uv.sources] +# TODO: swap for a version floor in both "network" entries above once mikeio1d +# releases the network module. ADR-013 holds modelskill 1.4.0 until it does. +mikeio1d = { git = "https://github.com/DHI/mikeio1d", branch = "main" } [project.urls] "Homepage" = "https://github.com/DHI/modelskill" diff --git a/roadmap/features/network-models.md b/roadmap/features/network-models.md index 2190befb9..246c8ad75 100644 --- a/roadmap/features/network-models.md +++ b/roadmap/features/network-models.md @@ -26,3 +26,5 @@ In active development. MIKE 1D, MIKE 11 and EPANET result files can be read toda MOUSE and Water Hammer results are not read yet: no shareable result file exists for either format, so support cannot be verified. SWMM results are not read yet: the reach connectivity lives in the companion '.inp' input file, which modelskill does not read yet. +The layer that reads those files and builds the network lives in mikeio1d, where the formats, the fixtures and a first graph conversion already were (ADR-013). ModelSkill keeps the model result, the observations and the matching, and requires `mikeio1d[network]`. The release is coordinated: this feature ships once mikeio1d has released the module it depends on. + diff --git a/src/modelskill/comparison/_comparison.py b/src/modelskill/comparison/_comparison.py index 8838184e5..4e3c6fe05 100644 --- a/src/modelskill/comparison/_comparison.py +++ b/src/modelskill/comparison/_comparison.py @@ -26,9 +26,22 @@ from .. import metrics as mtr from .. import Quantity from ..types import GeometryType -from ..obs import PointObservation, TrackObservation, NodeObservation +from ..obs import ( + PointObservation, + TrackObservation, + NodeObservation, + ReachObservation, +) from ..model import PointModelResult, TrackModelResult, VerticalModelResult -from ..timeseries._timeseries import _normalize_time_to_ns, _validate_data_var_name +from ..timeseries._coords import ( + NETWORK_LOCATION_COORDS, + _coordinate_values, + network_location, +) +from ..timeseries._timeseries import ( + _normalize_time_to_ns, + _validate_data_var_name, +) from ._comparer_plotter import ComparerPlotter from ..metrics import _parse_metric @@ -54,7 +67,7 @@ def _drop_scalar_coords(data: xr.Dataset) -> xr.Dataset: """Drop scalar coordinate variables that shouldn't appear as columns in dataframes""" - coords_to_drop = ["x", "y", "z", "node"] + coords_to_drop = ["x", "y", "z", *NETWORK_LOCATION_COORDS] return data.drop_vars(coords_to_drop, errors="ignore") @@ -72,8 +85,8 @@ def _parse_dataset(data: xr.Dataset) -> xr.Dataset: # coordinates # Only add x, y, z coordinates if they don't exist and we don't have node coordinates - has_node_coords = "node" in data.coords - if not has_node_coords: + has_network_coords = GeometryType.from_network_coords(data) is not None + if not has_network_coords: if "x" not in data.coords: data.coords["x"] = np.nan if "y" not in data.coords: @@ -112,10 +125,9 @@ def _parse_dataset(data: xr.Dataset) -> xr.Dataset: # Validate attrs if "gtype" not in data.attrs: # Determine gtype based on available coordinates - if "node" in data.coords: - data.attrs["gtype"] = str(GeometryType.NODE) - else: - data.attrs["gtype"] = str(GeometryType.POINT) + data.attrs["gtype"] = str( + GeometryType.from_network_coords(data) or GeometryType.POINT + ) # assert "gtype" in data.attrs, "data must have a gtype attribute" # assert data.attrs["gtype"] in [ # str(GeometryType.POINT), @@ -642,15 +654,27 @@ def z(self) -> Any: @property def node(self) -> Any: - """node-coordinate""" + """Name of the node this comparer sits at""" return self._coordinate_values("node") - def _coordinate_values(self, coord: str) -> None | Any: + @property + def reach(self) -> Any: + """Name of the reach this comparer sits on""" + return self._coordinate_values("reach") + + @property + def distance(self) -> Any: + """along-reach distance of a breakpoint""" + return self._coordinate_values("distance") + + @property + def _at(self) -> Any: + """Where this comparer sits, in the form NodeObservation.at takes""" + return network_location(self.data) + + def _coordinate_values(self, coord: str) -> Any: """Get coordinate values if they exist, otherwise return None""" - if coord not in self.data.coords: - return None - vals = self.data[coord].values - return np.atleast_1d(vals)[0] if vals.ndim == 0 else vals + return _coordinate_values(self.data, coord) @property def n_models(self) -> int: @@ -773,7 +797,9 @@ def rename( return Comparer(matched_data=data, raw_mod_data=raw_mod_data) - def _to_observation(self) -> PointObservation | TrackObservation | NodeObservation: + def _to_observation( + self, + ) -> PointObservation | TrackObservation | NodeObservation | ReachObservation: """Convert to Observation""" if self.gtype == "point": df = _drop_scalar_coords(self.data)[self._obs_str].to_dataframe() @@ -804,7 +830,16 @@ def _to_observation(self) -> PointObservation | TrackObservation | NodeObservati return NodeObservation( data=df, name=self.name, - at=self.node, + at=self._at, + quantity=self.quantity, + # TODO: add attrs + ) + elif self.gtype == "reach": + df = _drop_scalar_coords(self.data)[self._obs_str].to_dataframe() + return ReachObservation( + data=df, + name=self.name, + reach=self.reach, quantity=self.quantity, # TODO: add attrs ) @@ -1012,9 +1047,9 @@ def _to_long_dataframe( """Return a copy of the data as a long-format pandas DataFrame (for groupby operations)""" if self.gtype == "vertical": - data = self.data.drop_vars("node", errors="ignore") + data = self.data.drop_vars(NETWORK_LOCATION_COORDS, errors="ignore") else: - data = self.data.drop_vars(["z", "node"], errors="ignore") + data = self.data.drop_vars(["z", *NETWORK_LOCATION_COORDS], errors="ignore") # this step is necessary since we keep arbitrary derived data in the dataset, but not z/node # i.e. using a hardcoded whitelist of variables to keep is less flexible @@ -1338,8 +1373,9 @@ def to_dataframe(self) -> pd.DataFrame: + ["z"] ) return df[cols] - elif self.gtype == str(GeometryType.NODE): - # For network data, drop node coordinate like other geometries drop their coordinates + elif self.gtype in (str(GeometryType.NODE), str(GeometryType.REACH)): + # For network data, drop the location coordinates like other geometries + # drop theirs return _drop_scalar_coords(self.data).to_dataframe() else: raise NotImplementedError(f"Unknown gtype: {self.gtype}") @@ -1361,7 +1397,7 @@ def save(self, filename: Union[str, Path]) -> None: # https://docs.xarray.dev/en/stable/user-guide/io.html#groups # There is no need to save raw data for track data, since it is identical to the matched data - if self.gtype in ("point", "node"): + if self.gtype in ("point", "node", "reach"): ds = self.data.copy() # copy needed to avoid modifying self.data for key, ts_mod in self.raw_mod_data.items(): @@ -1396,7 +1432,7 @@ def load(filename: Union[str, Path]) -> "Comparer": # FIXME: consider during Phase3 return Comparer(matched_data=data) - if data.gtype in ("point", "node"): + if data.gtype in ("point", "node", "reach"): raw_mod_data: Dict[ str, PointModelResult @@ -1413,10 +1449,8 @@ def load(filename: Union[str, Path]) -> "Comparer": {"_time_raw_" + new_key: "time", var_name: new_key} ) ts: PointModelResult | NodeModelResult - if data.gtype == "node": - ts = NodeModelResult( - data=ds, node=int(ds.coords["node"].item()), name=new_key - ) + if data.gtype in ("node", "reach"): + ts = NodeModelResult(data=ds, name=new_key) else: ts = PointModelResult(data=ds, name=new_key) diff --git a/src/modelskill/model/adapters/__init__.py b/src/modelskill/model/adapters/__init__.py deleted file mode 100644 index 33ad255b1..000000000 --- a/src/modelskill/model/adapters/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Network format adapters.""" diff --git a/src/modelskill/model/adapters/_inp.py b/src/modelskill/model/adapters/_inp.py deleted file mode 100644 index 329b2c92a..000000000 --- a/src/modelskill/model/adapters/_inp.py +++ /dev/null @@ -1,109 +0,0 @@ -"""Minimal reader for EPANET and SWMM ``.inp`` input files. - -mikeio1d reads only the binary result formats, so the ``.inp`` that accompanies a -result file has to be parsed here. Both products use the same layout: bracketed -section headers, ``;``-prefixed comments (including the ``;;Name Node1 ...`` -column headers the products write), whitespace-delimited data rows, and blank -lines to ignore. - -Only the sections modelskill needs are interpreted; everything else is kept as -raw fields for a caller to use, or ignored. -""" - -from __future__ import annotations - -from pathlib import Path - - -def read_sections(path: str | Path) -> dict[str, list[list[str]]]: - """Parse an ``.inp`` file into its sections. - - Parameters - ---------- - path : str or Path - Path to an EPANET or SWMM ``.inp`` file. - - Returns - ------- - dict[str, list[list[str]]] - Section name (upper case, without brackets) mapped to its data rows, - each row split into whitespace-delimited fields. Comment-only and blank - lines are dropped, as is any trailing comment on a data row. - - Examples - -------- - >>> sections = read_sections("model.inp") # doctest: +SKIP - >>> sections["PIPES"][0] # doctest: +SKIP - ['10', '10', '11', '3209.544', '304.8', '100', '0', 'Open'] - """ - sections: dict[str, list[list[str]]] = {} - current: list[list[str]] | None = None - - with open(path, "r", encoding="utf-8", errors="replace") as f: - for line in f: - # A comment can trail a data row, so strip it before anything else. - line = line.split(";", 1)[0].strip() - if not line: - continue - - if line.startswith("["): - name = line.strip("[]").strip().upper() - current = sections.setdefault(name, []) - continue - - if current is not None: - current.append(line.split()) - - return sections - - -def read_pipe_lengths(path: str | Path) -> dict[str, float]: - """Read reach lengths from the ``[PIPES]`` section of an EPANET ``.inp``. - - Parameters - ---------- - path : str or Path - Path to an EPANET ``.inp`` file. - - Returns - ------- - dict[str, float] - Pipe ID mapped to its length. Pumps and valves are links too, but carry - no length, so they are absent from the result rather than present with a - placeholder. - - Raises - ------ - ValueError - If the file has no ``[PIPES]`` section, or a row there has too few - fields to read a length from. - - Notes - ----- - ``[PIPES]`` rows are ``ID Node1 Node2 Length Diameter Roughness ...``, so the - length is the fourth field. The units are whatever the model declares in - ``[OPTIONS]``; no conversion is applied. - """ - sections = read_sections(path) - - try: - rows = sections["PIPES"] - except KeyError: - raise ValueError( - f"'{path}' has no [PIPES] section, so it does not look like an " - "EPANET input file. Available sections: " - f"{sorted(sections)}." - ) - - _ID, _LENGTH = 0, 3 - lengths: dict[str, float] = {} - for row in rows: - if len(row) <= _LENGTH: - raise ValueError( - f"Cannot read a pipe length from [PIPES] row {' '.join(row)!r} " - f"in '{path}': expected at least {_LENGTH + 1} fields " - f"(ID, Node1, Node2, Length), got {len(row)}." - ) - lengths[row[_ID]] = float(row[_LENGTH]) - - return lengths diff --git a/src/modelskill/model/adapters/_res1d.py b/src/modelskill/model/adapters/_res1d.py deleted file mode 100644 index 567f0d498..000000000 --- a/src/modelskill/model/adapters/_res1d.py +++ /dev/null @@ -1,189 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING - -import pandas as pd - -if TYPE_CHECKING: - from mikeio1d.result_network import ResultNode, ResultGridPoint, ResultReach - -from modelskill.network import NetworkNode, ReachBreakPoint, NetworkReach - - -def _simplify_colnames(node: ResultNode | ResultGridPoint) -> pd.DataFrame: - # We remove suffixes and indexes so the columns contain only the quantity names - - # Some formats keep no timeseries at all on some locations - MIKE 11, for instance, - # stores everything on reach gridpoints, leaving the nodes empty. Asking mikeio1d - # for a dataframe there raises, so return an empty one instead. - if not node.quantities: - return pd.DataFrame() - - # The columns in a Res1D dataframe follow the convention "Quantity:Location:Sublocation" - # where Location refers to the node id or the reach id followed by the chainage. - RES1D_NAME_SEP = ":" - df = node.to_dataframe() - renamer_dict = {} - for quantity in node.quantities: - column_pairs = [ - (col, quantity) - for col in df.columns - if quantity in col.split(RES1D_NAME_SEP) - ] - if len(column_pairs) != 1: - raise ValueError( - f"There must be exactly one column per quantity, found {column_pairs}." - ) - old_name, new_name = column_pairs[0] - renamer_dict[old_name] = new_name - return df.rename(columns=renamer_dict).copy() - - -def _merge_extra_quantities( - base: pd.DataFrame, extra: pd.DataFrame, *, node_id: str -) -> pd.DataFrame: - """Append a companion file's quantities to a node's frame as extra columns. - - Parameters - ---------- - base : pd.DataFrame - The node's frame from the main result file. - extra : pd.DataFrame - The same node's frame from the companion file, sharing its time index. - node_id : str - Node ID, used in error messages. - - Returns - ------- - pd.DataFrame - - Raises - ------ - ValueError - If a quantity appears in both frames. Concatenating would give the node - two columns of the same name, which is the state ``_simplify_colnames`` - already refuses. - """ - if extra.empty: - return base - - overlapping = base.columns.intersection(extra.columns) - if len(overlapping) > 0: - raise ValueError( - f"Node {node_id!r} already has {sorted(overlapping)} in the main " - "result file, so the companion file's copy cannot be merged in." - ) - - return pd.concat([base, extra], axis=1) - - -class Res1DNode(NetworkNode): - def __init__( - self, - id: str, - *, - data: pd.DataFrame | None = None, - boundary: dict[str, pd.DataFrame] | None = None, - ): - self._id = id - self._data = pd.DataFrame() if data is None else data - self._boundary = {} if boundary is None else boundary - - @property - def id(self) -> str: - return self._id - - @property - def data(self) -> pd.DataFrame: - return self._data - - @property - def boundary(self) -> dict[str, pd.DataFrame]: - return self._boundary - - -class GridPoint(ReachBreakPoint): - def __init__( - self, reach_id: str, chainage: float, data: pd.DataFrame | None = None - ): - self._id = (reach_id, chainage) - self._data = pd.DataFrame() if data is None else data - - @property - def id(self) -> tuple[str, float]: - return self._id - - @property - def data(self) -> pd.DataFrame: - return self._data - - -class Res1DReach(NetworkReach): - """NetworkReach adapter for a mikeio1d ResultReach.""" - - def __init__( - self, - reach: ResultReach, - start_node: Res1DNode, - end_node: Res1DNode, - *, - populate_gridpoints: bool = True, - length: float | None = None, - ): - self._id = reach.name - - # Must be checked separately: some formats (.resx) report None for both the - # reach and the node, which the identity checks below would let through. - if reach.start_node is None or reach.end_node is None: - raise ValueError( - f"mikeio1d reported no start/end node for reach {reach.name!r}; " - "this result format's topology cannot be represented as a Network." - ) - - if start_node.id != reach.start_node: - raise ValueError("Incorrect starting node.") - if end_node.id != reach.end_node: - raise ValueError("Incorrect ending node.") - - intermediate_gridpoints = ( - reach.gridpoints[1:-1] if len(reach.gridpoints) > 2 else [] - ) - - self._start = start_node - self._end = end_node - - # A length read from a companion input file wins, since mikeio1d has none - # to offer for the formats that need one. Otherwise: mikeio1d returns 0 - # when it cannot read a reach length - link-node models such as EPANET - # report this for every reach. Report it as undefined rather than as a - # zero-length reach, which would make length-weighted graph algorithms - # treat the reach as free. The two cases cannot be told apart upstream. - self._length = length if length is not None else (reach.length or None) - self._breakpoints: list[ReachBreakPoint] = [ - GridPoint( - gridpoint.reach_name, - gridpoint.chainage, - _simplify_colnames(gridpoint) if populate_gridpoints else None, - ) - for gridpoint in intermediate_gridpoints - ] - - @property - def id(self) -> str: - return self._id - - @property - def start(self) -> Res1DNode: - return self._start - - @property - def end(self) -> Res1DNode: - return self._end - - @property - def length(self) -> float | None: - return self._length - - @property - def breakpoints(self) -> list[ReachBreakPoint]: - return self._breakpoints diff --git a/src/modelskill/model/network.py b/src/modelskill/model/network.py index 328c1cdea..97c2d05ee 100644 --- a/src/modelskill/model/network.py +++ b/src/modelskill/model/network.py @@ -1,20 +1,38 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Sequence +from pathlib import Path +from typing import TYPE_CHECKING, Any, Sequence import numpy as np -import numpy.typing as npt import pandas as pd import xarray as xr -from modelskill.timeseries import TimeSeries, _parse_network_node_input +from modelskill.timeseries import ( + TimeSeries, + _parse_network_breakpoint_input, + _parse_network_node_input, +) from ._base import SelectedItems from ..obs import NodeObservation, ReachObservation +from ..timeseries._coords import network_location from ..quantity import Quantity -from ..types import PointType +from ..types import GeometryType, PointType if TYPE_CHECKING: - from modelskill.network import Network + from mikeio1d.network import Network + + +def _network_class() -> type[Network]: + # Imported here, not at module scope, so this module stays importable + # without the optional network dependencies (ADR-010). + try: + from mikeio1d.network import Network + except ImportError as err: + raise ImportError( + "NetworkModelResult needs the network topology layer from mikeio1d, " + "which the 'network' extra installs: pip install modelskill[network]" + ) from err + return Network class NodeModelResult(TimeSeries): @@ -30,8 +48,13 @@ class NodeModelResult(TimeSeries): name : str, optional The name of the model result, by default None (will be set to file name or item name) - node : int, optional - node ID (integer), by default None + node : str or tuple[str, float], optional + Where the data sits: a node name, or a break point as + ``(reach_id, distance)``. By default None, which requires data that + already carries a ``node`` or ``reach`` coordinate. + node_index : int, optional + The integer the network used for this location, recorded as provenance. + Nothing reads it back, by default None item : str | int | None, optional If multiple items/arrays are present in the input an item must be given (as either an index or a string), by default None @@ -43,62 +66,98 @@ class NodeModelResult(TimeSeries): Examples -------- >>> import modelskill as ms - >>> mr = ms.NodeModelResult(data, node=123, name="Node_123") - >>> mr2 = ms.NodeModelResult(df, item="Water Level", node=456) + >>> mr = ms.NodeModelResult(data, node="123", name="Node_123") + >>> mr2 = ms.NodeModelResult(df, item="Water Level", node=("r1", 24.5)) """ def __init__( self, data: PointType, - node: int, + node: str | tuple[str, float | None] | None = None, *, + node_index: int | None = None, name: str | None = None, item: str | int | None = None, quantity: Quantity | None = None, aux_items: Sequence[int | str] | None = None, ): if not self._is_input_validated(data): - data = _parse_network_node_input( - data, - name=name, - item=item, - quantity=quantity, - node=node, - aux_items=aux_items, - ) + if isinstance(node, tuple): + reach, distance = node + data = _parse_network_breakpoint_input( + data, + name=name, + item=item, + quantity=quantity, + aux_items=aux_items, + reach=reach, + distance=distance, + ) + elif node is not None: + data = _parse_network_node_input( + data, + name=name, + item=item, + quantity=quantity, + node=node, + aux_items=aux_items, + ) + else: + raise ValueError( + "'NodeModelResult' needs a node name or a (reach, distance) " + "pair when the data does not already carry its location" + ) if not isinstance(data, xr.Dataset): raise ValueError("'NodeModelResult' requires xarray.Dataset") - if data.coords.get("node") is None: - raise ValueError("'node' coordinate not found in data") + if GeometryType.from_network_coords(data) is None: + raise ValueError( + "'NodeModelResult' needs a node name, a (reach, distance) pair, or " + "data that already carries a 'node' or 'reach' coordinate" + ) + if node_index is not None: + data = data.assign_coords(node_index=int(node_index)) data_var = str(list(data.data_vars)[0]) data[data_var].attrs["kind"] = "model" super().__init__(data=data) @property - def node(self) -> int: - """Node ID of model result""" - node_val = self.data.coords["node"] - return int(node_val.item()) + def node(self) -> Any: + """Where this result was extracted, as its network named it.""" + return network_location(self.data) + + def _location_repr(self) -> str | None: + return f"Location: {self.node}" + + @property + def node_index(self) -> int | None: + """Graph integer this location had in the network it came from, if recorded. + + Provenance only. Nothing reads it back: the numbering belongs to one + network built by one version, so a saved result is identified by + :attr:`node` instead. + """ + if "node_index" not in self.data.coords: + return None + return int(np.atleast_1d(self.data.coords["node_index"].values)[0]) def _create_new_instance(self, data: xr.Dataset) -> NodeModelResult: - """Extract node from data and create new instance""" - node = int(data.coords["node"].item()) - return self.__class__(data, node=node) + """Create a new instance; the location already travels in the coords.""" + return self.__class__(data) class NetworkModelResult: """Model result for network data with time and node dimensions. - Construct a NetworkModelResult from a Network object containing - timeseries data for each node. Users must provide exact node IDs - (integers obtained via ``Network.find()``) when creating observations — - no spatial interpolation is performed. + Construct one from a result file, or from a :class:`mikeio1d.network.Network` + already built. Observations name the location they sit at, and no spatial + interpolation is performed. Parameters ---------- - data : Network - Network-like object with a ``to_dataset()`` method (e.g. :class:`modelskill.network.Network`). + data : Network, str or Path + Path to a ``.res1d``, ``.res11`` or ``.res`` result file, or a + :class:`mikeio1d.network.Network`. name : str, optional The name of the model result, by default None (will be set to first data variable name) @@ -113,23 +172,46 @@ class NetworkModelResult: Examples -------- >>> import modelskill as ms - >>> from modelskill.network import Network - >>> network = Network(reaches) # reaches is a list[NetworkReach] - >>> mr = ms.NetworkModelResult(network, name="MyModel") - >>> obs = ms.NodeObservation(data, node=network.find(node="node_A")) + >>> mr = ms.NetworkModelResult("model.res1d", item="WaterLevel") + >>> obs = ms.NodeObservation(data, at="node_A") >>> extracted = mr.extract(obs) + + Open the network yourself to name EPANET companion files, or to keep memory + down on a large model by reading only the locations you will score: + + >>> from mikeio1d.network import Network + >>> network = Network.open("model.res1d", nodes=["node_A", "node_B"]) + >>> mr = ms.NetworkModelResult(network, name="MyModel") + + Notes + ----- + The network is used as given, not copied, so ``mr.network`` is the caller's + object. + + See Also + -------- + mikeio1d.network.Network.open : Read a network from a result file. """ def __init__( self, - data: Network, + data: Network | str | Path, *, name: str | None = None, item: str | int | None = None, quantity: Quantity | None = None, aux_items: Sequence[int | str] | None = None, ): - self.network = data.copy() + network_class = _network_class() + if isinstance(data, (str, Path)): + self.network = network_class.open(data) + elif isinstance(data, network_class): + self.network = data + else: + raise TypeError( + "NetworkModelResult takes a mikeio1d.network.Network or a path to a " + f"result file, got {type(data).__name__}" + ) ds = self.network.to_dataset() sel_items = SelectedItems.parse( @@ -144,6 +226,12 @@ def __init__( if quantity is None: da = self.data[sel_items.values] quantity = Quantity.from_cf_attrs(da.attrs) + if quantity == Quantity.undefined(): + # A result file names its quantity but carries no unit, and + # Quantity.from_cf_attrs needs both. Fall back to the name alone + # rather than reporting nothing at all. + name = da.attrs.get("long_name") or str(sel_items.values) + quantity = Quantity(name=name, unit="") self.quantity = quantity # Mark data variables as model data @@ -152,18 +240,16 @@ def __init__( def __repr__(self) -> str: return f"<{self.__class__.__name__}>: {self.name}" - _CHAINAGE_TOLERANCE = 1e-3 # Tolerance in source-network distance units (e.g., meters if chainage is in meters). + #: Coordinates mikeio1d puts on to_dataset() to say what each column is. They + #: are re-applied through NodeModelResult, which knows modelskill's names for + #: them, so they never reach a comparer under these. + _UPSTREAM_IDENTITY_COORDS = ("name", "reach", "distance") @property def time(self) -> pd.DatetimeIndex: """Return the time coordinate as a pandas.DatetimeIndex.""" return pd.DatetimeIndex(self.data.time.to_index()) - @property - def nodes(self) -> npt.NDArray[np.intp]: - """Return the node IDs as a numpy array of integers.""" - return self.data.node.values - def extract( self, observation: NodeObservation | ReachObservation, @@ -173,7 +259,7 @@ def extract( Parameters ---------- observation : NodeObservation or ReachObservation - observation with node ID or reach ID + observation naming a node, a breakpoint, or a reach Returns ------- @@ -192,134 +278,108 @@ def extract( def _extract_node(self, observation: NodeObservation) -> NodeModelResult: node_id = self._resolve_alias(observation.at) - available_nodes = set(self.data.node.values) - if node_id not in available_nodes: + if node_id not in self.data.indexes["node"]: raise ValueError( - f"Node {node_id} exists in the network topology but its timeseries was not loaded. " - f"Re-create the NetworkModelResult with the relevant nodes populated, " - f"e.g. Network.from_mike(path, nodes=[...])." + f"{observation.at!r} exists in the network topology but its " + "timeseries was not loaded. Re-create the NetworkModelResult with " + "the relevant nodes populated, e.g. " + "NetworkModelResult(Network.open(path, nodes=[...]))." ) - return NodeModelResult( - data=self.data.sel(node=node_id).drop_vars("node"), - node=node_id, - name=self.name, - item=self.sel_items.values, - quantity=self.quantity, - aux_items=self.sel_items.aux, - ) - - def _extract_reach(self, observation: ReachObservation) -> NodeModelResult: - # Extract model result from an arbitrary breakpoint belonging to the reach. + # A location that carries no data for this quantity is all-NaN here, just + # as it is on the reach path: MIKE 1D stores quantities at different grid + # points, so a breakpoint carrying Discharge may carry no WaterLevel. + item = self.sel_items.values + if not bool(self.data[item].sel(node=node_id).notnull().any()): + raise ValueError( + f"{observation.at!r} was found in the network but has no data for " + f"quantity '{item}'. Choose a location that has this quantity, or a " + "model result for a quantity this location has." + ) - # Searches the alias map for breakpoints whose reach component matches - # ``observation.reach``, then returns the first one that has data in the - # dataset. Raises if no breakpoint with data is found or if the quantity - # is not present for any breakpoint of that reach. + return self._as_node_result(node_id) + def _extract_reach(self, observation: ReachObservation) -> NodeModelResult: + # A reach observation matches any breakpoint along the reach, so long as + # they agree. Which breakpoints those are is read off the dataset's own + # coordinates; the network is consulted only to explain a failure. item = self.sel_items.values reach_id = observation.reach - try: - reach = self.network._reaches[reach_id] - except KeyError: + if reach_id not in self.network.reaches: raise ValueError(f"Reach {reach_id} not found in network.") - # This only searches intermediate breakpoints since reach-level data is not - # expected in nodes. - - available_nodes = {int(node_id) for node_id in self.data.node.values} - found_ds = None - found_int_id: int | None = None - missing_node_data = False - for breakpoint in reach.breakpoints: - if breakpoint.data is None: - continue - if item not in breakpoint.data.columns: - continue - - int_id = self.network.find( - reach=breakpoint.id[0], distance=breakpoint.distance - ) - if int_id not in available_nodes: - missing_node_data = True - continue - - ds = self.data.sel(node=int_id).drop_vars("node") - if found_ds is not None: - da1, da2 = xr.align(ds[item], found_ds[item], join="inner") - if not np.allclose(da1.values, da2.values, equal_nan=True): - raise ValueError( - "Not all data in breakpoints are equivalent. " - "Select a specific node instead of the reach." - ) - else: - found_ds = ds - found_int_id = int_id - - if found_ds is not None and found_int_id is not None: - return NodeModelResult( - data=found_ds, - node=found_int_id, - name=self.name, - item=item, - quantity=self.quantity, - aux_items=self.sel_items.aux, - ) - if missing_node_data: + on_reach = np.flatnonzero(self.data["reach"].values == reach_id) + # A location that carries no data for this quantity is all-NaN here, + # since quantities with different coverage are aligned on the way in. + with_data = self.data[item].isel(node=on_reach).notnull().any("time").values + candidates = self.data.isel(node=on_reach[np.flatnonzero(with_data)]) + + if candidates.sizes["node"] == 0: + raise ValueError(self._explain_no_reach_data(reach_id, item)) + + values = candidates[item].transpose("time", "node").values + if not np.allclose(values, values[:, :1], equal_nan=True): raise ValueError( + "Not all data in breakpoints are equivalent. " + "Select a specific node instead of the reach." + ) + + # Lowest distance first, unknown distances last, so the breakpoint chosen + # does not depend on the numbering mikeio1d happened to hand out. + distance = np.nan_to_num(candidates["distance"].values, nan=np.inf) + order = np.lexsort((candidates["node"].values, distance)) + return self._as_node_result(int(candidates["node"].values[order[0]])) + + def _explain_no_reach_data(self, reach_id: str, item: str) -> str: + # Whether the reach has no such data at all, or has it at breakpoints this + # model result did not load, is a distinction only the network can make. + has_source_data = any( + breakpoint.data is not None and item in breakpoint.data.columns + for breakpoint in self.network.reaches[reach_id].breakpoints + ) + if has_source_data: + return ( f"Reach '{reach_id}' has breakpoint data for quantity " f"'{item}', but matching breakpoint nodes are " "missing from the model dataset. Re-create the NetworkModelResult " "with the relevant reaches populated." ) - - raise ValueError( + return ( f"Reach '{reach_id}' was found in the network but none of its " - f"breakpoints have data loaded for quantity '{self.sel_items.values}'. " + f"breakpoints have data loaded for quantity '{item}'. " f"Re-create the NetworkModelResult with the relevant reaches populated." ) - def _resolve_alias(self, alias: int | str | tuple[str, float]) -> int: - # Resolve a node alias to an internal node ID. - - # Breakpoint tuple aliases are matched first by exact key lookup and then - # by reach ID and distance within ``_CHAINAGE_TOLERANCE``. If multiple - # candidates are within tolerance, the closest distance is selected; ties - # are broken by choosing the smallest node ID. Distance units are the - # same as the network chainage units. - - if isinstance(alias, int): - if alias not in self.data.node: - raise ValueError( - f"Node {alias} not found. Available: {list(self.nodes[:5])}..." - ) - return alias - else: - if alias in self.network._alias_map: - return self.network._alias_map[alias] + def _as_node_result(self, node_id: int) -> NodeModelResult: + # The location is taken from the network rather than from the observation, + # so a distance given as 24.5001 is recorded as the network's own 24.5. + where = self.network.recall(int(node_id)) + location = ( + where["node"] if "node" in where else (where["reach"], where["distance"]) + ) + data = self.data.sel(node=node_id).drop_vars( + ("node", *self._UPSTREAM_IDENTITY_COORDS), errors="ignore" + ) + return NodeModelResult( + data=data, + node=location, + node_index=int(node_id), + name=self.name, + item=self.sel_items.values, + quantity=self.quantity, + aux_items=self.sel_items.aux, + ) + def _resolve_alias(self, alias: str | tuple[str, float]) -> int: + # Delegated to Network.find rather than matched against the dataset's own + # name/reach/distance coords: find() searches the whole topology, so a hit + # that is missing from the dataset is a location whose timeseries was not + # loaded, which is a different mistake from one that does not exist. + try: if isinstance(alias, tuple): - # Handle tolerances reach_id, distance = alias - candidates: list[tuple[float, int]] = [] - for key, node_id in self.network._alias_map.items(): - if isinstance(key, tuple) and key[0] == reach_id: - diff = abs(key[1] - distance) - if diff <= self._CHAINAGE_TOLERANCE: - candidates.append((diff, node_id)) - if candidates: - return min( - candidates, key=lambda candidate: (candidate[0], candidate[1]) - )[1] - - available = list(self.network._alias_map.keys())[:5] - if isinstance(alias, tuple): - raise ValueError( - f"Breakpoint {alias} not found in network. " - f"Available aliases (first 5): {available}" - ) - raise ValueError( - f"Node alias '{alias}' not found in network. " - f"Available aliases (first 5): {available}" - ) + return int(self.network.find(reach=str(reach_id), distance=distance)) + return int(self.network.find(node=str(alias))) + except KeyError as err: + raise ValueError(f"Location {alias!r} not found. {err.args[0]}") from err diff --git a/src/modelskill/network.py b/src/modelskill/network.py deleted file mode 100644 index 4487c5809..000000000 --- a/src/modelskill/network.py +++ /dev/null @@ -1,1200 +0,0 @@ -"""Opt-in network module for network model results (e.g. MIKE 1D / res1d). - -Requires the ``networks`` dependency group (networkx, mikeio1d). -Install with:: - - uv sync --group networks - -Import this module explicitly to use network functionality:: - - from modelskill.network import Network - -""" - -from __future__ import annotations - -import sys - -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 -import xarray as xr - -if TYPE_CHECKING: - from mikeio1d import Res1D - from mikeio1d.result_network import ResultReach - from .model.adapters._res1d import Res1DReach - - -_MIKE_EXTENSIONS = frozenset({".res1d", ".res11"}) -_EPANET_EXTENSIONS = frozenset({".res"}) - -_NO_FIXTURE = ( - "{product} results are not supported yet: modelskill has no test fixture for " - "this format, so support cannot be verified. Please open an issue if you need it." -) -# A result file that holds timeseries but no topology of its own. The connectivity -# is in a companion file we do not parse yet. -_TOPOLOGY_IN_COMPANION_FILE = ( - "SWMM '.out' files carry no reach connectivity of their own - it lives in the " - "companion '.inp' input file, which modelskill does not read yet. Tracked in " - "https://github.com/DHI/modelskill/issues/689." -) -# A companion result file: readable, but it describes a network defined elsewhere. -_COMPANION_RESULT_FILE = ( - "'.resx' holds extra EPANET results (tank volume, pump energy) for a network " - "defined in the sibling '.res' file, so it has no topology of its own. Read the " - "'.res' file and pass this one alongside it: " - "Network.from_epanet(res, resx=...)." -) - -# extension -> why modelskill will not read it, even though mikeio1d can -_UNSUPPORTED_EXTENSIONS: dict[str, str] = { - ".out": _TOPOLOGY_IN_COMPANION_FILE, - ".resx": _COMPANION_RESULT_FILE, - ".prf": _NO_FIXTURE.format(product="MOUSE"), - ".crf": _NO_FIXTURE.format(product="MOUSE"), - ".xrf": _NO_FIXTURE.format(product="MOUSE"), - ".whr": _NO_FIXTURE.format(product="Water Hammer"), -} - -# extension -> the constructor that reads it, for "use X instead" errors -_EXTENSION_CONSTRUCTORS: dict[str, str] = { - **{extension: "from_mike" for extension in _MIKE_EXTENSIONS}, - **{extension: "from_epanet" for extension in _EPANET_EXTENSIONS}, -} - - -def _check_file_path_is_str(res: Res1D) -> None: - """Reject a Res1D opened with a path object rather than a string. - - mikeio1d resolves reach topology with ``str.endswith`` on - ``Res1D.file_path``, which raises ``AttributeError`` from deep inside the - load when that attribute is a ``Path``. Fail here instead, where the cause - can be named. - """ - file_path = getattr(res, "file_path", None) - if file_path is not None and not isinstance(file_path, str): - raise TypeError( - f"This Res1D was opened with a {type(file_path).__name__} file_path, " - "which mikeio1d cannot resolve reach topology from. Re-open it as " - "Res1D(str(path)), or pass the path to the constructor directly." - ) - - -class NetworkNode(ABC): - """Abstract base class for a node in a network. - - A node represents a discrete location in the network (e.g. a junction, - reservoir, or boundary point) that carries time-series data for one or - more physical quantities. - - Three properties must be implemented: - - * :attr:`id` - a unique string identifier for the node. - * :attr:`data` - a time-indexed :class:`pandas.DataFrame` whose columns - are quantity names. - * :attr:`boundary` - a dict of boundary-condition metadata (may be empty). - - The concrete helper :class:`BasicNode` is provided for the common case - where the data is already available as a DataFrame. - - See Also - -------- - BasicNode : Ready-to-use concrete implementation. - NetworkReach : Connects two NetworkNode instances. - Network : Container that assembles nodes and reaches into a graph. - """ - - @property - @abstractmethod - def id(self) -> str: - """Unique string identifier for this node.""" - pass - - @property - @abstractmethod - def data(self) -> pd.DataFrame: - """Time-indexed DataFrame with one column per quantity.""" - pass - - @property - @abstractmethod - def boundary(self) -> dict[str, Any]: - """Boundary-condition metadata dict (may be empty).""" - pass - - @property - def quantities(self) -> list[str]: - """List of quantity names available at this node.""" - return list(self.data.columns) - - -class ReachBreakPoint(ABC): - """Abstract base class for an intermediate break point along a network reach. - - Break points represent locations between the start and end nodes of a - reach (e.g. cross-section chainage points along a river reach) that carry - their own time-series data. - - Two properties must be implemented: - - * :attr:`id` - a ``(reach_id, distance)`` tuple that uniquely locates the - break point within the network. - * :attr:`data` - a time-indexed :class:`pandas.DataFrame` whose columns - are quantity names. - - The :attr:`distance` convenience property returns ``id[1]`` (the - along-reach distance in the units used by the parent network). - - Examples - -------- - Minimal subclass: - - >>> class MyBreakPoint(ReachBreakPoint): - ... def __init__(self, reach_id, chainage, df): - ... self._id = (reach_id, chainage) - ... self._data = df - ... @property - ... def id(self): return self._id - ... @property - ... def data(self): return self._data - - See Also - -------- - NetworkReach : Owns a list of ReachBreakPoint instances. - NetworkNode : Represents a start/end node of a reach. - Network : Assembles reaches (and their break points) into a graph. - """ - - @property - @abstractmethod - def id(self) -> tuple[str, float]: - """``(reach_id, distance)`` tuple uniquely identifying this break point.""" - pass - - @property - @abstractmethod - def data(self) -> pd.DataFrame: - """Time-indexed DataFrame with one column per quantity.""" - pass - - @property - def distance(self) -> float: - """Along-reach distance of this break point, measured from the start node.""" - return self.id[1] - - @property - def quantities(self) -> list[str]: - """List of quantity names available at this break point.""" - return list(self.data.columns) - - -class NetworkReach(ABC): - """Abstract base class for a reach in a network. - - A reach represents a directed connection between two :class:`NetworkNode` - instances (e.g. a river reach between two junctions). It may also carry - a list of :class:`ReachBreakPoint` objects for intermediate chainage - locations. - - Subclass this to integrate your own network topology. Four properties - must be implemented: - - * :attr:`id` - a unique string identifier for the reach. - * :attr:`start` - the upstream/start :class:`NetworkNode`. - * :attr:`end` - the downstream/end :class:`NetworkNode`. - * :attr:`breakpoints` - list of :class:`ReachBreakPoint` instances ordered - by increasing distance from the start node (empty list if none). - - :attr:`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. - - The concrete helper :class:`BasicReach` is provided for the common case - where all data is already available in memory. - - Examples - -------- - Minimal subclass, without a length: - - >>> class MyReach(NetworkReach): - ... def __init__(self, rid, start_node, end_node): - ... self._id = rid - ... self._start = start_node - ... self._end = end_node - ... @property - ... def id(self): return self._id - ... @property - ... def start(self): return self._start - ... @property - ... def end(self): return self._end - ... @property - ... def breakpoints(self): return [] - - Add a :attr:`length` property on top of that when the domain has one: - - >>> class MyMeasuredReach(MyReach): - ... def __init__(self, rid, start_node, end_node, length): - ... super().__init__(rid, start_node, end_node) - ... self._length = length - ... @property - ... def length(self): return self._length - - See Also - -------- - BasicReach : Ready-to-use concrete implementation. - NetworkNode : Represents the start/end of this reach. - ReachBreakPoint : Intermediate data points along this reach. - Network : Assembles a list of NetworkReach objects into a graph. - """ - - @property - @abstractmethod - def id(self) -> str: - """Unique string identifier for this reach.""" - pass - - @property - @abstractmethod - def start(self) -> NetworkNode: - """Start (upstream) node of this reach.""" - pass - - @property - @abstractmethod - def end(self) -> NetworkNode: - """End (downstream) node of this reach.""" - pass - - @property - def length(self) -> float | None: - """Total length of this reach in network units, or ``None`` if undefined.""" - return None - - @property - @abstractmethod - def breakpoints(self) -> list[ReachBreakPoint]: - """Ordered list of intermediate :class:`ReachBreakPoint` objects (may be empty).""" - pass - - @property - def n_breakpoints(self) -> int: - """Number of break points in the reach.""" - return len(self.breakpoints) - - -class BasicNode(NetworkNode): - """Concrete :class:`NetworkNode` for programmatic network construction. - - Parameters - ---------- - id : str - Unique node identifier. - data : pd.DataFrame - Time-indexed DataFrame with one column per quantity. - boundary : dict, optional - Boundary condition metadata, by default empty. - - Examples - -------- - >>> import pandas as pd - >>> time = pd.date_range("2020", periods=3, freq="h") - >>> node = BasicNode("junction_1", pd.DataFrame({"WaterLevel": [1.0, 1.1, 1.2]}, index=time)) - """ - - def __init__( - self, - id: str, - data: pd.DataFrame, - boundary: dict[str, Any] | None = None, - ) -> None: - self._id = id - self._data = data - self._boundary: dict[str, Any] = boundary or {} - - @property - def id(self) -> str: - return self._id - - @property - def data(self) -> pd.DataFrame: - return self._data - - @property - def boundary(self) -> dict[str, Any]: - return self._boundary - - -class BasicReach(NetworkReach): - """Concrete :class:`NetworkReach` for programmatic network construction. - - Parameters - ---------- - id : str - Unique reach identifier. - start : NetworkNode - Start node. - end : NetworkNode - End node. - length : float, optional - Reach length, by default None (undefined). - breakpoints : list[ReachBreakPoint], optional - Intermediate break points, by default empty. - - Examples - -------- - >>> reach = BasicReach("reach_1", node_a, node_b, length=250.0) - - Where the domain has no reach length, leave it out: - - >>> reach = BasicReach("pipe_1", node_a, node_b) - """ - - def __init__( - self, - id: str, - start: NetworkNode, - end: NetworkNode, - length: float | None = None, - breakpoints: list[ReachBreakPoint] | None = None, - ) -> None: - self._id = id - self._start = start - self._end = end - self._length = length - self._breakpoints: list[ReachBreakPoint] = breakpoints or [] - - @property - def id(self) -> str: - return self._id - - @property - def start(self) -> NetworkNode: - return self._start - - @property - def end(self) -> NetworkNode: - return self._end - - @property - def length(self) -> float | None: - return self._length - - @property - def breakpoints(self) -> list[ReachBreakPoint]: - return self._breakpoints - - -class Network: - """Network built from a set of reaches, with coordinate lookup and data access.""" - - def __init__(self, reaches: Sequence[NetworkReach]): - graph = self._generate_graph(reaches) - self._initialize_network_attributes(graph) - self._reaches = self._generate_reaches_dict(reaches) - - 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 - time_window = "N/A - N/A" if len(time) == 0 else f"{time[0]} - {time[-1]}" - out = [ - "", - f"Reaches: {len(self._reaches)}", - f"Nodes: {self._graph.number_of_nodes()}", - f"Quantities: {self.quantities}", - f"Time: {time_window}", - ] - return "\n".join(out) - - @classmethod - def from_mike( - cls, - res: str | Path | Res1D, - *, - nodes: str | list[str] | None = None, - reaches: str | list[str] | None = None, - ) -> Network: - """Create a Network from a MIKE 1D or MIKE 11 result file. - - Parameters - ---------- - res : str, Path or Res1D - Path to a ``.res1d`` or ``.res11`` file, or an already-opened - :class:`mikeio1d.Res1D` object. - nodes : str, list of str, or None, optional - Controls which nodes have their timeseries data loaded into memory. - - * ``None`` *(default)* — data is loaded for every node. - * A single node ID or a list of node IDs — only those nodes get - data; others are topology-only. - * ``[]`` (empty list) — no node data is loaded at all. - - The full network topology is always constructed regardless of this - setting, so ``find()`` and ``recall()`` still work on all nodes. - reaches : str, list of str, or None, optional - Controls which reaches have their intermediate gridpoint data - populated. - - * ``None`` *(default)* — gridpoints are populated for every reach. - * A single reach name or a list of reach names — only those reaches - get gridpoint data; others are topology-only. - * ``[]`` (empty list) — no gridpoint data is loaded at all. - - Returns - ------- - Network - - Raises - ------ - NotImplementedError - If the file extension is not one modelskill can read. - ValueError - If the extension belongs to another constructor, such as EPANET. - - Examples - -------- - Load everything (default behaviour): - - >>> from modelskill.network import Network - >>> network = Network.from_mike("model.res1d") - - Load data only for the two nodes where observations exist, and skip - all intermediate gridpoint data to keep memory usage low: - - >>> network = Network.from_mike( - ... "model.res1d", - ... nodes=["node_a", "node_b"], - ... reaches=[], - ... ) - - Load data for selected nodes and gridpoints for one specific reach: - - >>> network = Network.from_mike( - ... "model.res1d", - ... nodes=["node_a", "node_b"], - ... reaches=["reach_1"], - ... ) - - Notes - ----- - MIKE 11 keeps its timeseries on reach gridpoints rather than on nodes, - so the nodes of a ``.res11`` network carry no data of their own. Pass - ``reaches`` rather than ``nodes`` to control what gets loaded. - - See Also - -------- - from_epanet : Read an EPANET result file. - """ - return cls._from_mikeio1d( - res, - nodes=nodes, - reaches=reaches, - allowed=_MIKE_EXTENSIONS, - caller="from_mike", - ) - - @classmethod - def from_epanet( - cls, - res: str | Path | Res1D, - *, - resx: str | Path | Res1D | None = None, - inp: str | Path | None = None, - nodes: str | list[str] | None = None, - reaches: str | list[str] | None = None, - ) -> Network: - """Create a Network from an EPANET result file and its companions. - - An EPANET run writes up to three files that modelskill can use. The - ``.res`` holds the network and its main timeseries; the optional - ``.resx`` holds extra results; and the optional ``.inp`` is the input - file, which is the only one of the three carrying reach lengths. - - Parameters - ---------- - res : str, Path or Res1D - Path to a ``.res`` file, or an already-opened - :class:`mikeio1d.Res1D` object. - resx : str, Path, Res1D or None, optional - Companion ``.resx`` file from the same run. Its extra node - quantities (tank ``Volume`` and ``Volume Percentage``) are merged - onto the matching nodes. By default None, and those quantities are - simply absent. - inp : str, Path or None, optional - EPANET ``.inp`` input file for the same model, read for its - ``[PIPES]`` lengths. By default None, and reach lengths are - undefined. - nodes : str, list of str, or None, optional - Which nodes get their timeseries loaded. See :meth:`from_mike`. - reaches : str, list of str, or None, optional - Which reaches get their gridpoint data loaded. See - :meth:`from_mike`. EPANET results have no intermediate gridpoints, - so this argument has no effect. - - Returns - ------- - Network - - Raises - ------ - NotImplementedError - If the file extension is not one modelskill can read. - ValueError - If the extension belongs to another constructor, such as MIKE, if a - companion file has the wrong extension, or if ``resx`` does not come - from the same run as ``res``. - - Examples - -------- - >>> from modelskill.network import Network - >>> network = Network.from_epanet("model.res") - - With both companions, for real edge lengths and the extra quantities: - - >>> network = Network.from_epanet( - ... "model.res", - ... resx="model.resx", - ... inp="model.inp", - ... ) - - Notes - ----- - EPANET is a link-node model, and mikeio1d reports no length and a - single synthetic gridpoint for each of its reaches. As a result: - - * without ``inp``, every edge of :attr:`graph` has ``length=None``, so a - length-weighted graph algorithm fails rather than returning a - meaningless number. Pumps and valves keep ``length=None`` even with - ``inp``, since ``[PIPES]`` is the only section carrying lengths - * reaches have no breakpoints, so - :class:`~modelskill.obs.ReachObservation` cannot be matched against - an EPANET network — use :class:`~modelskill.obs.NodeObservation` - * ``find(reach=..., distance=)`` never resolves; only - ``distance="start"`` and ``distance="end"`` work - - For the same reason, ``resx`` merges node quantities only. Its - reach-level quantities (pump energy, efficiency and costs) have no - breakpoint to live on, which is tracked in issue #680. - - Node timeseries, :meth:`to_dataframe`, :meth:`to_dataset`, - ``find(node=...)`` and :meth:`recall` are unaffected. - - See Also - -------- - from_mike : Read a MIKE 1D or MIKE 11 result file. - """ - return cls._from_mikeio1d( - res, - nodes=nodes, - reaches=reaches, - allowed=_EPANET_EXTENSIONS, - caller="from_epanet", - resx=resx, - inp=inp, - ) - - @classmethod - def _from_mikeio1d( - cls, - res: str | Path | Res1D, - *, - nodes: str | list[str] | None, - reaches: str | list[str] | None, - allowed: frozenset[str], - caller: str, - resx: str | Path | Res1D | None = None, - inp: str | Path | None = None, - ) -> Network: - """Shared implementation behind the public ``from_*`` constructors. - - Parameters - ---------- - allowed : frozenset of str - Extensions this constructor accepts. - caller : str - Name of the public method, used in error messages. - resx : str, Path, Res1D or None, optional - Companion result file whose node quantities are merged in. - inp : str, Path or None, optional - Companion input file read for reach lengths. - """ - if sys.version_info >= (3, 14): - raise NotImplementedError( - f"Current version of 'mikeio1d' requires python < 3.14 and {sys.version} is being used." - ) - - from mikeio1d import Res1D as _Res1D - - if isinstance(res, (str, Path)): - path = Path(res) - cls._validate_extension(path.suffix, allowed=allowed, caller=caller) - res = _Res1D(str(path)) - elif isinstance(res, _Res1D): - _check_file_path_is_str(res) - suffix = Path(res.file_path).suffix - cls._validate_extension(suffix, allowed=allowed, caller=caller) - else: - raise TypeError( - f"Expected a str, Path or Res1D object, got {type(res).__name__!r}" - ) - - if nodes is None: - nodes_list: list[str] = list(res.nodes.keys()) - elif isinstance(nodes, str): - nodes_list = [nodes] - else: - nodes_list = list(nodes) - - if reaches is None: - reaches_list: list[str] = list(res.reaches.keys()) - elif isinstance(reaches, str): - reaches_list = [reaches] - else: - reaches_list = list(reaches) - - extra = None if resx is None else cls._open_companion_result(res, resx) - lengths = None if inp is None else cls._read_companion_lengths(inp) - - list_of_reaches = cls._load_res1d_network( - res, nodes_list, reaches_list, extra=extra, lengths=lengths - ) - return cls(list_of_reaches) - - @staticmethod - def _read_companion_lengths(inp: str | Path) -> dict[str, float]: - """Read reach lengths from a companion ``.inp`` input file.""" - from modelskill.model.adapters._inp import read_pipe_lengths - - path = Path(inp) - if path.suffix.lower() != ".inp": - raise ValueError( - f"Expected an EPANET '.inp' input file, got '{path.suffix}'. " - "This argument reads reach lengths from the model input, not " - "from a result file." - ) - return read_pipe_lengths(path) - - @staticmethod - def _open_companion_result(res: Res1D, resx: str | Path | Res1D) -> Res1D: - """Open and validate a companion ``.resx`` result file. - - Raises - ------ - ValueError - If the extension is not ``.resx``, or if the file does not come from - the same run as ``res``. - """ - from mikeio1d import Res1D as _Res1D - - if isinstance(resx, (str, Path)): - path = Path(resx) - if path.suffix.lower() != ".resx": - raise ValueError( - f"Expected an EPANET '.resx' companion file, got '{path.suffix}'." - ) - extra = _Res1D(str(path)) - elif isinstance(resx, _Res1D): - _check_file_path_is_str(resx) - if Path(resx.file_path).suffix.lower() != ".resx": - raise ValueError( - "Expected an EPANET '.resx' companion file, got " - f"'{Path(resx.file_path).suffix}'." - ) - extra = resx - else: - raise TypeError( - f"Expected a str, Path or Res1D object, got {type(resx).__name__!r}" - ) - - # Merging two different runs would line up silently and produce a network - # that is wrong in a way no later error would reveal. - if not res.time_index.equals(extra.time_index): - raise ValueError( - "The '.resx' companion does not share a time axis with the " - "'.res' file, so the two are not from the same run. Got " - f"{len(extra.time_index)} steps ending {extra.end_time} against " - f"{len(res.time_index)} ending {res.end_time}." - ) - - unknown_nodes = set(extra.nodes) - set(res.nodes) - if unknown_nodes: - raise ValueError( - f"The '.resx' companion holds nodes {sorted(unknown_nodes)} that are " - "absent from the '.res' network, so the two files do not describe " - "the same model." - ) - - unknown_reaches = set(extra.reaches) - set(res.reaches) - if unknown_reaches: - raise ValueError( - f"The '.resx' companion holds reaches {sorted(unknown_reaches)} that are " - "absent from the '.res' network, so the two files do not describe " - "the same model." - ) - - return extra - - @staticmethod - def _validate_extension( - suffix: str, *, allowed: frozenset[str], caller: str - ) -> None: - """Check a file extension against mikeio1d and against one constructor. - - Raises - ------ - NotImplementedError - If modelskill cannot read the extension, either because mikeio1d - does not support it or because modelskill does not. - ValueError - If another constructor is the one that reads this extension. - """ - from mikeio1d import Res1D as _Res1D - - extension = suffix.lower() - - # Checked before the supported set below, since these all *are* readable - # by mikeio1d - it is modelskill that cannot use the result. - reason = _UNSUPPORTED_EXTENSIONS.get(extension) - if reason is not None: - raise NotImplementedError(f"Cannot read '{suffix}' files. {reason}") - - supported = _Res1D.get_supported_file_extensions() - if extension not in supported: - readable = sorted(supported - set(_UNSUPPORTED_EXTENSIONS)) - raise NotImplementedError( - f"Unsupported file extension '{suffix}'. " - f"Supported extensions are {readable}." - ) - - if extension not in allowed: - constructor = _EXTENSION_CONSTRUCTORS.get(extension) - if constructor is None: - raise NotImplementedError( - f"File extension '{suffix}' is supported by mikeio1d but is not mapped " - "to a Network constructor in this version of modelskill. " - "Please upgrade modelskill or open an issue." - ) - raise ValueError( - f"Network.{caller}() reads {sorted(allowed)} files, got '{suffix}'. " - f"Use Network.{constructor}() instead." - ) - - @staticmethod - def _load_res1d_network( - res: Res1D, - nodes: list[str], - reaches: list[str], - *, - extra: Res1D | None = None, - lengths: dict[str, float] | None = None, - ) -> list[Res1DReach]: - from modelskill.model.adapters._res1d import ( - Res1DReach, - Res1DNode, - _merge_extra_quantities, - _simplify_colnames, - ) - - nodes_set = set(nodes) - reaches_set = set(reaches) - lengths = lengths or {} - - # In order to work with bigger files, we might want to select a subset of nodes and avoid - # potential memory issues. For this reason, we create this intermediate step that populates - # only the data in the passed nodes - - def _init_node(reach: ResultReach, is_end: bool) -> Res1DNode: - id = reach.end_node if is_end else reach.start_node - gpt_idx = -1 if is_end else 0 - if id in nodes_set: - node = res.nodes[id] - df = _simplify_colnames(node) - # Merged here rather than up front so selective loading still - # decides what is held in memory. - if extra is not None and id in extra.nodes: - df = _merge_extra_quantities( - df, _simplify_colnames(extra.nodes[id]), node_id=id - ) - overlapping_gridpoint = reach.gridpoints[gpt_idx] - boundary = _simplify_colnames(overlapping_gridpoint) - return Res1DNode(id, data=df, boundary={reach.name: boundary}) - else: - return Res1DNode(id) - - return [ - Res1DReach( - reach, - _init_node(reach, False), - _init_node(reach, True), - populate_gridpoints=reach.name in reaches_set, - length=lengths.get(reach.name), - ) - for reach in res.reaches.values() - ] - - @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_reaches_dict( - reaches: Sequence[NetworkReach], - ) -> dict[str, NetworkReach]: - return {r.id: r for r in reaches} - - @staticmethod - def _build_dataframe(g: nx.Graph) -> pd.DataFrame: - data_in_nodes = { - k: v["data"] - for k, v in g.nodes.items() - if v["data"] is not None and not v["data"].empty - } - if len(data_in_nodes) == 0: - columns = pd.MultiIndex.from_arrays([[], []], names=["node", "quantity"]) - return pd.DataFrame(index=pd.Index([], name="time"), columns=columns) - df = pd.concat(data_in_nodes, axis=1) - df.columns = df.columns.set_names(["node", "quantity"]) - df.index.name = "time" - return df.copy() - - def to_dataframe(self, sel: str | None = None) -> pd.DataFrame: - """Dataframe using node ids as column names. - - It will be multiindex unless 'sel' is passed. - - Parameters - ---------- - sel : Optional[str], optional - Quantity to select, by default None - - Returns - ------- - pd.DataFrame - Timeseries contained in graph nodes - """ - df = self._df.copy() - if sel is None: - return df - else: - df.attrs["quantity"] = sel - return df.reorder_levels(["quantity", "node"], axis=1).loc[:, sel] - - def to_dataset(self) -> xr.Dataset: - """Dataset using node ids as coords. - - Returns - ------- - xr.Dataset - Timeseries contained in graph nodes - """ - df_raw = self.to_dataframe() - if len(df_raw.columns) == 0: - return xr.Dataset() - df = df_raw.reorder_levels(["quantity", "node"], axis=1) - quantities = df.columns.get_level_values("quantity").unique() - return xr.Dataset( - {q: xr.DataArray(df[q], dims=["time", "node"]) for q in quantities} - ) - - @property - def graph(self) -> nx.Graph: - """Graph of the network.""" - return self._graph - - @property - def quantities(self) -> list[str]: - """Quantities present in data. - - Returns - ------- - List[str] - List of quantities - """ - return list(self.to_dataframe().columns.get_level_values(1).unique()) - - @staticmethod - def _generate_graph(reaches: Sequence[NetworkReach]) -> nx.Graph: - g0 = nx.Graph() - for reach in reaches: - # 1) Add start and end nodes - for node in [reach.start, reach.end]: - node_key = node.id - if node_key in g0.nodes: - g0.nodes[node_key]["boundary"].update(node.boundary) - else: - g0.add_node(node_key, data=node.data, boundary=node.boundary) - - # 2) Add edges connecting start/end nodes to their adjacent breakpoints - start_key = reach.start.id - end_key = reach.end.id - if reach.n_breakpoints == 0: - g0.add_edge(start_key, end_key, length=reach.length) - else: - bp_keys = [bp.id for bp in reach.breakpoints] - for bp, bp_key in zip(reach.breakpoints, bp_keys): - g0.add_node(bp_key, data=bp.data) - - g0.add_edge(start_key, bp_keys[0], length=reach.breakpoints[0].distance) - - # Only the final segment needs the total length. Break point - # distances are known even when the total is not, so a reach - # without a length still gets real lengths on every edge but - # this one. - tail_length = ( - None - if reach.length is None - else reach.length - reach.breakpoints[-1].distance - ) - g0.add_edge(bp_keys[-1], end_key, length=tail_length) - - # 3) Connect consecutive intermediate breakpoints - for i in range(reach.n_breakpoints - 1): - current_ = reach.breakpoints[i] - next_ = reach.breakpoints[i + 1] - length = next_.distance - current_.distance - g0.add_edge( - current_.id, - next_.id, - length=length, - ) - - return nx.convert_node_labels_to_integers(g0, label_attribute="alias") - - @overload - def find( - self, - *, - node: str, - reach: None = None, - distance: None = None, - ) -> int: - pass - - @overload - def find( - self, - *, - node: list[str], - reach: None = None, - distance: None = None, - ) -> list[int]: - pass - - @overload - def find( - self, - *, - node: None = None, - reach: str | list[str], - distance: str | float, - ) -> int: - pass - - @overload - def find( - self, - *, - node: None = None, - reach: str | list[str], - distance: list[str | float], - ) -> list[int]: - pass - - def find( - self, - node: str | list[str] | None = None, - reach: str | list[str] | None = None, - distance: str | float | list[str | float] | None = None, - ) -> int | list[int]: - """Find node or breakpoint id in the Network object based on former coordinates. - - Parameters - ---------- - node : str | List[str], optional - Node id(s) in the original network, by default None - reach : str | List[str], optional - Reach id(s) for breakpoint lookup or reach endpoint lookup, by default None - distance : str | float | List[str | float], optional - Distance(s) along reach for breakpoint lookup, or "start"/"end" - for reach endpoints, by default None - - Returns - ------- - int | List[int] - Node or breakpoint id(s) in the generic network - - Raises - ------ - ValueError - If invalid combination of parameters is provided - KeyError - If requested node/breakpoint is not found in the network - """ - by_node = node is not None - by_breakpoint = reach is not None or distance is not None - - if by_node and by_breakpoint: - raise ValueError( - "Cannot specify both 'node' and 'reach'/'distance' parameters simultaneously" - ) - - if not by_node and not by_breakpoint: - raise ValueError( - "Must specify either 'node' or both 'reach' and 'distance' parameters" - ) - - ids: list[str | tuple[str, float]] - - if by_node: - assert node is not None - if not isinstance(node, list): - node = [node] - ids = list(node) - - else: - if reach is None or distance is None: - raise ValueError( - "Both 'reach' and 'distance' parameters are required for breakpoint/endpoint lookup" - ) - - if not isinstance(reach, list): - reach = [reach] - - if not isinstance(distance, list): - distance = [distance] - - if len(reach) == 1: - reach = reach * len(distance) - - if len(reach) != len(distance): - raise ValueError( - "Incompatible lengths of 'reach' and 'distance' arguments. One 'reach' admits multiple distances, otherwise they must be the same length." - ) - - ids = [] - for reach_i, distance_i in zip(reach, distance): - if distance_i in ["start", "end"]: - if reach_i not in self._reaches: - raise KeyError(f"Reach '{reach_i}' not found in the network.") - - network_reach = self._reaches[reach_i] - if distance_i == "start": - ids.append(network_reach.start.id) - else: - ids.append(network_reach.end.id) - else: - if not isinstance(distance_i, (int, float)): - raise ValueError( - "Invalid 'distance' value for breakpoint lookup: " - f"{distance_i!r}. Expected a numeric value or 'start'/'end'." - ) - ids.append((reach_i, distance_i)) - - _CHAINAGE_TOLERANCE = 1e-3 - - def _resolve_id(id): - if id in self._alias_map: - return self._alias_map[id] - if isinstance(id, tuple): - reach_id, distance = id - for key, val in self._alias_map.items(): - if ( - isinstance(key, tuple) - and key[0] == reach_id - and abs(key[1] - distance) <= _CHAINAGE_TOLERANCE - ): - return val - return None - - resolved = [_resolve_id(id) for id in ids] - missing_ids = [ids[i] for i, v in enumerate(resolved) if v is None] - if missing_ids: - raise KeyError( - f"Node/breakpoint(s) {missing_ids} not found in the network. Available nodes are {set(self._alias_map.keys())}" - ) - if len(resolved) == 1: - return resolved[0] - return resolved - - @overload - def recall(self, id: int) -> dict[str, Any]: - pass - - @overload - def recall(self, id: list[int]) -> list[dict[str, Any]]: - pass - - def recall(self, id: int | list[int]) -> dict[str, Any] | list[dict[str, Any]]: - """Recover the original coordinates of an element given the node id(s) in the Network object. - - Parameters - ---------- - id : int | List[int] - Node id(s) in the generic network - - Returns - ------- - Dict[str, Any] | List[Dict[str, Any]] - Original coordinates. For single input returns dict, for multiple inputs returns list of dicts. - Dict contains coordinates: - - For nodes: 'node' key with node id - - For breakpoints: 'reach' and 'distance' keys with reach id and distance - - Raises - ------ - KeyError - If node id is not found in the network - ValueError - If node id string format is invalid - """ - if not isinstance(id, list): - id = [id] - - reverse_alias_map = {v: k for k, v in self._alias_map.items()} - - results: list[dict[str, Any]] = [] - for node_id in id: - if node_id not in reverse_alias_map: - raise KeyError(f"Node ID {node_id} not found in the network.") - - key = reverse_alias_map[node_id] - if isinstance(key, str): - results.append({"node": key}) - else: - results.append({"reach": key[0], "distance": key[1]}) - - if len(results) == 1: - 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"): - nodes = [ - BasicNode(nid, pd.DataFrame({quantity: data[:, i]}, index=time)) - for i, nid in enumerate(node_ids) - ] - reaches = [ - BasicReach(f"r{i}", nodes[i], nodes[i + 1], length=100.0) - for i in range(len(nodes) - 1) - ] - return Network(reaches) diff --git a/src/modelskill/obs.py b/src/modelskill/obs.py index fa2cc01ca..d5f492286 100644 --- a/src/modelskill/obs.py +++ b/src/modelskill/obs.py @@ -1,21 +1,33 @@ """ # Observations -ModelSkill supports four types of observations: +ModelSkill supports five types of observations: * [`PointObservation`](`modelskill.PointObservation`) - a point timeseries from a dfs0/nc file or a DataFrame * [`TrackObservation`](`modelskill.TrackObservation`) - a track (moving point) timeseries from a dfs0/nc file or a DataFrame * [`VerticalObservation`](`modelskill.VerticalObservation`) - a vertical profile from a dfs0/nc file or a DataFrame -* [`NodeObservation`](`modelskill.NodeObservation`) - a network node timeseries for specific node IDs. +* [`NodeObservation`](`modelskill.NodeObservation`) - a network node timeseries for a named node or break point. +* [`ReachObservation`](`modelskill.ReachObservation`) - a network reach timeseries for a quantity uniform along the reach. An observation can be created by explicitly invoking one of the above classes or using the [`observation()`](`modelskill.observation`) function which will return the appropriate type based on the input data (if possible). """ from __future__ import annotations -from typing import Literal, Any, Union, overload +import sqlite3 +from pathlib import Path +from typing import ( + Any, + Iterable, + Literal, + NamedTuple, + Sequence, + Union, + overload, +) from typing_extensions import Self import warnings +import numpy as np import pandas as pd import xarray as xr @@ -29,11 +41,16 @@ _parse_network_node_input, _parse_network_breakpoint_input, ) +from .timeseries._coords import network_location # NetCDF attributes can only be str, int, float https://unidata.github.io/netcdf4-python/#attributes-in-a-netcdf-file Serializable = Union[str, int, float] +# Where a node observation sits: the name the network gave the node, or a +# breakpoint given as (reach_id, distance) along a reach. +NodeLocation = Union[str, tuple[str, float]] + def observation( data: DataInputType, @@ -78,7 +95,7 @@ def observation( >>> import modelskill as ms >>> o_pt = ms.observation(df, item=0, x=366844, y=6154291, name="Klagshamn") >>> o_tr = ms.observation("lon_after_lat.dfs0", item="wl", x_item=1, y_item=0) - >>> o_node = ms.observation(df, item="Water Level", at=123, name="123") + >>> o_node = ms.observation(df, item="Water Level", at="123", name="123") >>> o_reach = ms.observation(df, item="Discharge", reach="reach_1", name="reach_1_Q") """ if gtype is None: @@ -113,6 +130,379 @@ def _guess_gtype(**kwargs) -> GeometryType: return GeometryType.POINT +def _item_names(data: Any) -> list[str]: + """Names of the individual timeseries held by an already-opened data source.""" + if isinstance(data, pd.DataFrame): + return [str(c) for c in data.columns] + if isinstance(data, xr.Dataset): + return [str(v) for v in data.data_vars] + if hasattr(data, "names"): # mikeio.Dataset + return [str(n) for n in data.names] + if hasattr(data, "name"): # pd.Series, mikeio.DataArray, xr.DataArray + return [str(data.name)] + raise ValueError( + f"Cannot determine item names from data of type {type(data).__name__}" + ) + + +class _Station(NamedTuple): + """One measured timeseries, resolved to the network location it belongs to.""" + + item_name: str #: name of the item in the data source + name: str #: display name for the observation + location: str | tuple[str, float] #: node name, or (reach, chainage) + kind: Literal["node", "reach"] #: which observation class fits + quantity: str #: modelled quantity name + + +class _MikePlusStationResolver: + """Resolves data source items to network locations, via a MIKE+ database. + + A MIKE+ project ships a sqlite database alongside its result files. Two of + its tables say where the measured timeseries belong in the network: + + * ``m_Measurement`` - one row per measured timeseries, naming the file + (``tsfilename``) and the item within it (``tsitemname``), plus the + modelled quantity (``resitemname``). + * ``m_Station`` - the location, as ``locationid`` plus a ``locationtype`` + saying whether that identifier names a node or a link. + + Everything MIKE+ specific is contained here - the table names, the join, the + ``locationtype`` codes, the encoding of ``resitemname`` - so a change to the + database layout is a change to this class alone. Callers see only + :class:`_Station`. + """ + + _TABLES: dict[str, set[str]] = { + "m_Station": { + "muid", + "locationid", + "locationtype", + "chainagevalue", + "assetname", + }, + "m_Measurement": { + "measurementstationid", + "tsfilename", + "tsitemname", + "resitemname", + }, + } + + # m_Station.locationtype codes. 8 is a junction and 12 a tank or reservoir; + # both are graph nodes. 9 is a link, which becomes a breakpoint when the + # station carries a chainage and a whole reach when it does not. A station + # with any other code is not a place in the network, so it is left out + # rather than guessed at, and reported when it was what the caller asked + # for. + _NODE_TYPES = frozenset({8, 12}) + _LINK_TYPES = frozenset({9}) + + _QUERY = """ + SELECT m.tsitemname AS item_name, + m.tsfilename AS tsfilename, + m.resitemname AS resitemname, + s.assetname AS assetname, + s.locationid AS locationid, + s.locationtype AS locationtype, + s.chainagevalue AS chainagevalue + FROM m_Measurement m + JOIN m_Station s ON s.muid = m.measurementstationid + """ + + def __init__( + self, + db: str | Path | sqlite3.Connection, + *, + source: str | None = None, + ) -> None: + """Read the join, and the station names needed to explain a failure. + + ``db`` is a path or an already-open connection; an open one is left open. + ``source`` restricts the measurements to one result file, matched on file + name alone, so a full path is fine. Without it, measurements from every file + are considered and an item registered against two of them raises. + """ + self._source = source + + if isinstance(db, sqlite3.Connection): + conn, opened = db, None + else: + conn = opened = sqlite3.connect(str(db)) + try: + self._validate(conn) + + rows = pd.read_sql_query(self._QUERY, conn) + + # Read the station names now rather than on demand: the only other + # use is naming stations that carry no measurement, on the failure + # path, and reading them here is what lets the connection close. + self._assets = set( + pd.read_sql_query("SELECT assetname FROM m_Station", conn)["assetname"] + .dropna() + .tolist() + ) + finally: + if opened is not None: + opened.close() + + if source is not None: + wanted = self._file_name(source).casefold() + names = rows["tsfilename"].fillna("").map(self._file_name) + rows = rows[names.str.casefold() == wanted] + + rows["quantity"] = rows["resitemname"].str.split(";").str[0].str.strip() + self._rows = rows + + def resolve( + self, + item_names: Iterable[str], + *, + quantity: str | None = None, + kind: Literal["node", "reach"] | None = None, + on_missing: Literal["raise", "skip"] = "raise", + ) -> list[_Station]: + """Resolve item names, e.g. the columns of a dfs0, to their locations. + + ``quantity`` selects one of several measured quantities; left None it is + inferred, and raises when the selection holds more than one. ``kind`` + restricts the result to nodes or to reaches. ``on_missing="skip"`` drops + items the database does not register, which otherwise raise. Stations the + database does not place in the network are always left out. + """ + requested = list(dict.fromkeys(item_names)) + rows = self._rows[self._rows["item_name"].isin(requested)].copy() + + missing = [item for item in requested if item not in set(rows["item_name"])] + if missing and on_missing == "raise": + raise ValueError( + f"{len(missing)} of {len(requested)} items could not be resolved " + f"against the MIKE+ database.\n" + + self._unresolved_message(missing) + + '\n Pass on_missing="skip" to ignore these.' + ) + + if ambiguous := sorted( + rows.loc[rows.duplicated("item_name", keep=False), "item_name"].unique() + ): + raise ValueError( + f"Item(s) {ambiguous} are registered against more than one file. " + "Pass 'source' to say which file the data comes from." + ) + + if rows.empty: + raise ValueError("No items could be resolved against the MIKE+ database.") + + located = rows.apply(self._location, axis=1) + rows["kind"] = [k for k, _ in located] + rows["location"] = [location for _, location in located] + + if quantity is None: + placed = rows[rows["kind"].notna()] + pool = placed if kind is None else placed[placed["kind"] == kind] + available = sorted(pool["quantity"].unique()) + if len(available) == 0: + raise ValueError( + f"No {kind} locations found. Quantities present: " + f"{rows['quantity'].value_counts().to_dict()}." + + self._unsupported_message(rows) + ) + if len(available) > 1: + raise ValueError( + "Several quantities present, so 'quantity' cannot be inferred: " + f"{pool['quantity'].value_counts().to_dict()}. " + f"Pass one of {available}." + ) + quantity = available[0] + + selection = rows[rows["quantity"] == quantity] + if selection.empty: + raise ValueError( + f"Quantity {quantity!r} not found. Available: " + f"{rows['quantity'].value_counts().to_dict()}." + ) + + if kind is not None: + of_kind = selection[selection["kind"] == kind] + if of_kind.empty: + other = sorted(selection["kind"].dropna().unique()) + if not other: + raise ValueError( + f"No {quantity!r} station could be placed in the network." + + self._unsupported_message(selection) + ) + raise ValueError( + f"All {len(selection)} {quantity!r} station(s) are of kind " + f"{other}, not {kind!r}." + self._unsupported_message(selection) + ) + selection = of_kind + + selection = selection[selection["kind"].notna()] + if selection.empty: + raise ValueError( + f"No {quantity!r} station could be placed in the network." + + self._unsupported_message(rows) + ) + + names = self._display_names(selection) + return [ + _Station( + item_name=str(row.item_name), + name=str(name), + location=row.location, + kind=row.kind, + quantity=str(row.quantity), + ) + for name, row in zip(names, selection.itertuples()) + ] + + def _validate(self, conn: sqlite3.Connection) -> None: + tables = { + row[0] + for row in conn.execute("SELECT name FROM sqlite_master WHERE type='table'") + } + if missing := sorted(set(self._TABLES) - tables): + raise ValueError( + f"Database is missing table(s) {missing}. " + "A MIKE+ database with 'm_Station' and 'm_Measurement' is required." + ) + for table, required in self._TABLES.items(): + columns = {row[1] for row in conn.execute(f"PRAGMA table_info([{table}])")} + if missing_cols := sorted(required - columns): + raise ValueError( + f"Table '{table}' is missing column(s) {missing_cols}. " + "The database layout is not the one modelskill expects." + ) + + def _location( + self, row: pd.Series + ) -> tuple[str | None, str | tuple[str, float] | None]: + """The kind and location of one station, or (None, None) if it is neither.""" + # A link station with a chainage names a point along a reach, which is a + # node observation at a breakpoint. Without a chainage it names the reach + # as a whole. + try: + location_type = int(row["locationtype"]) + except (TypeError, ValueError): + location_type = -1 + + location_id = str(row["locationid"]) + if location_type in self._NODE_TYPES: + return "node", location_id + if location_type in self._LINK_TYPES: + chainage = row["chainagevalue"] + if pd.isna(chainage): + return "reach", location_id + return "node", (location_id, float(chainage)) + + return None, None + + def _unsupported_message(self, rows: pd.DataFrame) -> str: + """Names the stations of ``rows`` that are not places in the network.""" + unsupported = rows[rows["kind"].isna()] + if unsupported.empty: + return "" + + listed = ", ".join( + f"'{row.locationid}' ({row.locationtype!r})" + for row in unsupported.itertuples() + ) + return ( + " Station(s) with an unsupported locationtype were left out: " + f"{listed}. Known codes are " + f"{sorted(self._NODE_TYPES | self._LINK_TYPES)}." + ) + + def _unresolved_message(self, missing: Sequence[str]) -> str: + known = [item for item in missing if item in self._assets] + unknown = [item for item in missing if item not in self._assets] + + lines = [] + if known: + where = f" for '{self._file_name(self._source)}'" if self._source else "" + lines.append( + f" Known station, no measurement registered{where} ({len(known)}):\n" + + "\n".join(f" {item}" for item in known) + ) + if unknown: + lines.append( + f" Not found in the database ({len(unknown)}):\n" + + "\n".join(f" {item}" for item in unknown) + ) + return "\n".join(lines) + + @staticmethod + def _file_name(path: object) -> str: + """The file name in a path, whose separator is Windows' in the database.""" + return str(path).replace("\\", "/").rsplit("/", 1)[-1] + + @staticmethod + def _display_names(selection: pd.DataFrame) -> pd.Series: + # assetname is far shorter than the raw item name and is normally unique, + # but it is only safe as a display name when it distinguishes every row. + assets = selection["assetname"] + if assets.notna().all() and assets.nunique() == len(selection): + return assets.astype(str) + return selection["item_name"].astype(str) + + +def _observations_from_mikeplus( + cls: type, + *, + data: PointType, + db: Any, + kind: Literal["node", "reach"], + location_arg: str, + quantity: Quantity | str | None, + source: str | None, + on_missing: Literal["raise", "skip"], + aux_items: list[int | str] | None, + attrs: dict | None, +) -> list[Any]: + """Build observations from a data source and a MIKE+ database.""" + from .timeseries._point import _open_and_name + + if source is None and isinstance(data, (str, Path)): + source = str(data) + + # Open once rather than per observation; a path would otherwise be re-read + # for every station in the database. + opened, _ = _open_and_name(data, None) + + # A Quantity is metadata and does not select: its name is the caller's own, + # not the database's. Only a string names a quantity in the database. + given_quantity = quantity if isinstance(quantity, Quantity) else None + wanted = quantity if isinstance(quantity, str) else None + + stations = _MikePlusStationResolver(db, source=source).resolve( + _item_names(opened), + quantity=wanted, + kind=kind, + on_missing=on_missing, + ) + + observations = [] + for station in stations: + obs = cls( + opened, + item=station.item_name, + name=station.name, + quantity=given_quantity, + aux_items=aux_items, + attrs=attrs, + **{location_arg: station.location}, + ) + if given_quantity is None: + # The database names the quantity; the data source knows its unit. + obs.quantity = Quantity( + name=station.quantity, + unit=obs.quantity.unit, + is_directional=obs.quantity.is_directional, + ) + observations.append(obs) + return observations + + def _validate_attrs(data_attrs: dict, attrs: dict | None) -> None: # See similar method in xarray https://github.com/pydata/xarray/blob/main/xarray/backends/api.py#L165 @@ -477,18 +867,30 @@ def z(self): return self._coordinate_values("z") +def _at_from_coords(ds: xr.Dataset) -> str | tuple[str, float]: + """The location in the form ``NodeObservation`` takes it. + + Unlike :func:`~modelskill.timeseries._coords.network_location`, which + reports the location as recorded, this coerces to the types the ``at`` + argument is declared with. + """ + location = network_location(ds) + if isinstance(location, tuple): + return (str(location[0]), float(location[1])) + return str(location) + + class NodeObservation(Observation): """Class for observations at network nodes. Create a NodeObservation from a DataFrame or other data source. - The ``at`` parameter accepts three forms: + The ``at`` parameter accepts two forms: - * **int** — internal network ID, used directly. - * **str** — original node alias (e.g. Res1D node name), resolved to an - integer ID automatically when matched against a - :class:`~modelskill.model.network.NetworkModelResult`. - * **tuple[str, float]** — breakpoint location as ``(reach_id, distance)`` - along a reach, resolved via the alias map at match time. + * **str** — the node's name in the model (e.g. a Res1D node name). + * **tuple[str, float]** — a breakpoint, as ``(reach_id, distance)`` along a + reach. + + Both are resolved against the network when the observation is matched. .. note:: "Node" in this API follows the broad graph sense: it covers both @@ -505,12 +907,11 @@ class NodeObservation(Observation): ---------- data : str, Path, mikeio.Dataset, mikeio.DataArray, pd.DataFrame, pd.Series, xr.Dataset or xr.DataArray data source with time series for the node - at : int, str, or tuple[str, float] + at : str or tuple[str, float] Observation location. Accepted forms: - * **int** — internal network ID. - * **str** — original node alias (e.g. Res1D node name). - * **tuple[str, float]** — breakpoint as ``(reach_id, distance)``. + * **str** — the node's name in the model (e.g. a Res1D node name). + * **tuple[str, float]** — a breakpoint, as ``(reach_id, distance)``. item : (int, str), optional index or name of the wanted item/column, by default None if data contains more than one item, item must be given @@ -528,8 +929,8 @@ class NodeObservation(Observation): Examples -------- >>> import modelskill as ms - >>> o1 = ms.NodeObservation(data, at=123, name="123") - >>> o2 = ms.NodeObservation(df, item="Water Level", at=456) + >>> o1 = ms.NodeObservation(data, at="123", name="123") + >>> o2 = ms.NodeObservation(df, item="Water Level", at="456") >>> >>> # String alias resolved at match time >>> o3 = ms.NodeObservation(data, at="node_A") @@ -538,14 +939,14 @@ class NodeObservation(Observation): >>> o4 = ms.NodeObservation(data, at=("reach_1", 24.5)) >>> >>> # Multiple node observations from separate data sources - >>> obs = ms.NodeObservation.from_multiple(nodes={123: df1, 456: df2}) + >>> obs = ms.NodeObservation.from_multiple(nodes={"123": df1, "456": df2}) """ def __init__( self, data: PointType, *, - at: int | str | tuple[str, float], + at: str | tuple[str, float], item: int | str | None = None, name: str | None = None, weight: float = 1.0, @@ -553,6 +954,12 @@ def __init__( aux_items: list[int | str] | None = None, attrs: dict | None = None, ) -> None: + if isinstance(at, (int, np.integer)) and not isinstance(at, bool): + raise TypeError( + "'at' takes a node name or a (reach, distance) pair, not an integer. " + "The integers a Network hands out are an internal index; " + "network.recall() gives the name back." + ) if isinstance(at, tuple): reach, distance = str(at[0]), float(at[1]) if not self._is_input_validated(data): @@ -579,26 +986,21 @@ def __init__( super().__init__(data=data, weight=weight, attrs=attrs) @property - def at(self) -> int | str | tuple[str, float]: - """Observation location: node ID (int/str) or breakpoint ``(reach_id, distance)`` tuple.""" - if "reach" in self.data.coords: - return ( - str(self.data.coords["reach"].item()), - float(self.data.coords["distance"].item()), - ) - return self.data.coords["node"].item() # int or str + def at(self) -> str | tuple[str, float]: + """Observation location: a node name, or a ``(reach_id, distance)`` breakpoint.""" + return _at_from_coords(self.data) + + @property + def node(self) -> Any: + """Name of the node this observation sits at, or None for a break point.""" + return self._coordinate_values("node") + + def _location_repr(self) -> str | None: + return f"Location: {self.at}" def _create_new_instance(self, data: xr.Dataset) -> Self: """Reconstruct instance from a dataset slice.""" - if "reach" in data.coords: - return self.__class__( - data, - at=( - str(data.coords["reach"].item()), - float(data.coords["distance"].item()), - ), - ) - return self.__class__(data, at=data.coords["node"].item()) + return self.__class__(data, at=_at_from_coords(data)) @overload @classmethod @@ -606,7 +1008,7 @@ def from_multiple( cls, *, data: PointType, - nodes: dict[int, str | int], + nodes: dict[NodeLocation, str | int], quantity: Quantity | None = None, aux_items: list[int | str] | None = None, attrs: dict | None = None, @@ -617,20 +1019,38 @@ def from_multiple( def from_multiple( cls, *, - nodes: dict[int, PointType], + nodes: dict[NodeLocation, PointType], quantity: Quantity | None = None, aux_items: list[int | str] | None = None, attrs: dict | None = None, ) -> list[NodeObservation]: pass + @overload + @classmethod + def from_multiple( + cls, + *, + data: PointType, + db: str | Path | Any, + quantity: Quantity | str | None = None, + source: str | None = None, + on_missing: Literal["raise", "skip"] = "raise", + aux_items: list[int | str] | None = None, + attrs: dict | None = None, + ) -> list[NodeObservation]: + pass + @classmethod def from_multiple( cls, *, data: PointType | None = None, - nodes: dict[int, Any] | None = None, - quantity: Quantity | None = None, + nodes: dict[NodeLocation, Any] | None = None, + db: str | Path | Any | None = None, + quantity: Quantity | str | None = None, + source: str | None = None, + on_missing: Literal["raise", "skip"] = "raise", aux_items: list[int | str] | None = None, attrs: dict | None = None, ) -> list[NodeObservation]: @@ -641,22 +1061,52 @@ def from_multiple( 1. **Separate data sources** — pass only ``nodes`` as a dict mapping each node ID to its own data source (file path, DataFrame, etc.):: - obs = NodeObservation.from_multiple(nodes={123: df1, 456: "sensor.csv"}) + obs = NodeObservation.from_multiple(nodes={"123": df1, "456": "sensor.csv"}) 2. **Shared data source** — pass a single ``data`` object together with ``nodes`` as a dict mapping each node ID to the column name or index to select from ``data``:: - obs = NodeObservation.from_multiple(data=df, nodes={123: "col_a", 456: "col_b"}) + obs = NodeObservation.from_multiple(data=df, nodes={"123": "col_a", "456": "col_b"}) + + 3. **MIKE+ database** — pass a single ``data`` object together with + ``db``, and the locations are looked up in the database:: + + obs = NodeObservation.from_multiple(data="calib.dfs0", db="model.sqlite") + + One observation is created per item of ``data`` that the database + places on a node, so several sensors at the same node are all kept. Parameters ---------- data : PointType, optional - Shared data source (required when ``nodes`` values are column selectors). - nodes : dict[int, PointType | str | int] - Mapping of node_id -> data source or column selector. - quantity : Quantity | None, optional - Physical quantity metadata, by default None. + Shared data source (required when ``nodes`` values are column + selectors, and when ``db`` is given). + nodes : dict[str | tuple[str, float], PointType | str | int] + Mapping of location -> data source or column selector. A location + takes either of the forms accepted by ``at``: a node name, or a + ``(reach_id, distance)`` breakpoint. + + Note that a location can appear only once, so this form cannot + express several observations at the same node. Use ``db`` when the + data has several sensors at one location. + db : str, Path or sqlite3.Connection, optional + MIKE+ database locating the items of ``data`` in the network. + Mutually exclusive with ``nodes``. + quantity : Quantity or str, optional + Physical quantity metadata, by default None. With ``db``, a string + selects which quantity to build observations for and the metadata + comes from the database; omit it and the quantity is inferred when + the data holds only one. A ``Quantity`` supplies the metadata and + does not select, so the database must hold only one quantity for + this kind of location - to do both, pass the string and set + ``obs.quantity`` on the observations afterwards. + source : str, optional + With ``db``, the file the items come from. Taken from ``data`` when + that is a path, by default None. + on_missing : {"raise", "skip"}, optional + With ``db``, what to do with items the database cannot place, by + default "raise". aux_items : list[int | str] | None, optional Auxiliary items, by default None. attrs : dict | None, optional @@ -666,7 +1116,39 @@ def from_multiple( ------- list[NodeObservation] List of NodeObservation objects. + + Raises + ------ + ValueError + If both ``nodes`` and ``db`` are given, if neither is, or if the + database cannot resolve the requested items. """ + if db is not None: + if nodes is not None: + raise ValueError( + "'nodes' and 'db' are mutually exclusive: the database " + "supplies the locations." + ) + if data is None: + raise ValueError("'data' is required when 'db' is given") + return _observations_from_mikeplus( + cls, + data=data, + db=db, + kind="node", + location_arg="at", + quantity=quantity, + source=source, + on_missing=on_missing, + aux_items=aux_items, + attrs=attrs, + ) + + if isinstance(quantity, str): + raise TypeError( + "'quantity' must be a Quantity unless 'db' is given, got str" + ) + if nodes is None: raise ValueError("'nodes' argument is required") if not isinstance(nodes, dict): @@ -771,10 +1253,195 @@ def reach(self) -> str: """Reach ID of this observation.""" return str(self.data.coords["reach"].item()) + def _location_repr(self) -> str | None: + return f"Location: {self.reach}" + def _create_new_instance(self, data: xr.Dataset) -> Self: """Reconstruct instance from a dataset slice.""" return self.__class__(data, reach=str(data.coords["reach"].item())) + @overload + @classmethod + def from_multiple( + cls, + *, + data: PointType, + reaches: dict[str, str | int], + quantity: Quantity | None = None, + aux_items: list[int | str] | None = None, + attrs: dict | None = None, + ) -> list[ReachObservation]: + pass + + @overload + @classmethod + def from_multiple( + cls, + *, + reaches: dict[str, PointType], + quantity: Quantity | None = None, + aux_items: list[int | str] | None = None, + attrs: dict | None = None, + ) -> list[ReachObservation]: + pass + + @overload + @classmethod + def from_multiple( + cls, + *, + data: PointType, + db: str | Path | Any, + quantity: Quantity | str | None = None, + source: str | None = None, + on_missing: Literal["raise", "skip"] = "raise", + aux_items: list[int | str] | None = None, + attrs: dict | None = None, + ) -> list[ReachObservation]: + pass + + @classmethod + def from_multiple( + cls, + *, + data: PointType | None = None, + reaches: dict[str, Any] | None = None, + db: str | Path | Any | None = None, + quantity: Quantity | str | None = None, + source: str | None = None, + on_missing: Literal["raise", "skip"] = "raise", + aux_items: list[int | str] | None = None, + attrs: dict | None = None, + ) -> list[ReachObservation]: + """Create multiple ReachObservation objects. + + Two calling conventions are supported: + + 1. **Separate data sources** — pass only ``reaches`` as a dict mapping + each reach ID to its own data source (file path, DataFrame, etc.):: + + obs = ReachObservation.from_multiple(reaches={"r1": df1, "r2": "sensor.csv"}) + + 2. **Shared data source** — pass a single ``data`` object together with + ``reaches`` as a dict mapping each reach ID to the column name or + index to select from ``data``:: + + obs = ReachObservation.from_multiple(data=df, reaches={"r1": "col_a", "r2": "col_b"}) + + 3. **MIKE+ database** — pass a single ``data`` object together with + ``db``, and the reaches are looked up in the database:: + + obs = ReachObservation.from_multiple(data="calib.dfs0", db="model.sqlite") + + One observation is created per item of ``data`` that the database + places on a link without a chainage. + + Parameters + ---------- + data : PointType, optional + Shared data source (required when ``reaches`` values are column + selectors, and when ``db`` is given). + reaches : dict[str, PointType | str | int] + Mapping of reach_id -> data source or column selector. + + Note that a reach can appear only once, so this form cannot express + several observations on the same reach. Use ``db`` when the data has + several sensors on one reach. + db : str, Path or sqlite3.Connection, optional + MIKE+ database locating the items of ``data`` in the network. + Mutually exclusive with ``reaches``. + quantity : Quantity or str, optional + Physical quantity metadata, by default None. With ``db``, a string + selects which quantity to build observations for and the metadata + comes from the database; omit it and the quantity is inferred when + the data holds only one. A ``Quantity`` supplies the metadata and + does not select, so the database must hold only one quantity for + this kind of location - to do both, pass the string and set + ``obs.quantity`` on the observations afterwards. + source : str, optional + With ``db``, the file the items come from. Taken from ``data`` when + that is a path, by default None. + on_missing : {"raise", "skip"}, optional + With ``db``, what to do with items the database cannot place, by + default "raise". + aux_items : list[int | str] | None, optional + Auxiliary items, by default None. + attrs : dict | None, optional + Additional attributes, by default None. + + Returns + ------- + list[ReachObservation] + List of ReachObservation objects. + + Raises + ------ + ValueError + If both ``reaches`` and ``db`` are given, if neither is, or if the + database cannot resolve the requested items. + """ + if db is not None: + if reaches is not None: + raise ValueError( + "'reaches' and 'db' are mutually exclusive: the database " + "supplies the locations." + ) + if data is None: + raise ValueError("'data' is required when 'db' is given") + return _observations_from_mikeplus( + cls, + data=data, + db=db, + kind="reach", + location_arg="reach", + quantity=quantity, + source=source, + on_missing=on_missing, + aux_items=aux_items, + attrs=attrs, + ) + + if isinstance(quantity, str): + raise TypeError( + "'quantity' must be a Quantity unless 'db' is given, got str" + ) + + if reaches is None: + raise ValueError("'reaches' argument is required") + if not isinstance(reaches, dict): + raise TypeError( + f"'reaches' must be a dict mapping reach_id -> data_source, got {type(reaches).__name__}" + ) + + reach_ids = list(reaches.keys()) + + if data is None: + data_sources: list[PointType] = list(reaches.values()) + return [ + cls( + data_i, + reach=reach_i, + item=None, + quantity=quantity, + aux_items=aux_items, + attrs=attrs, + ) + for data_i, reach_i in zip(data_sources, reach_ids) + ] + else: + reach_items: list[int | str | None] = list(reaches.values()) + return [ + cls( + data, + reach=reach_i, + item=item_i, + quantity=quantity, + aux_items=aux_items, + attrs=attrs, + ) + for reach_i, item_i in zip(reach_ids, reach_items) + ] + def unit_display_name(name: str) -> str: """Display name diff --git a/src/modelskill/timeseries/__init__.py b/src/modelskill/timeseries/__init__.py index f52f17271..9869b51b2 100644 --- a/src/modelskill/timeseries/__init__.py +++ b/src/modelskill/timeseries/__init__.py @@ -1,6 +1,6 @@ from ._timeseries import TimeSeries -from ._point import ( - _parse_xyz_point_input, +from ._point import _parse_xyz_point_input +from ._network import ( _parse_network_node_input, _parse_network_breakpoint_input, ) diff --git a/src/modelskill/timeseries/_coords.py b/src/modelskill/timeseries/_coords.py index 98304befa..14b5559d2 100644 --- a/src/modelskill/timeseries/_coords.py +++ b/src/modelskill/timeseries/_coords.py @@ -1,4 +1,9 @@ +from __future__ import annotations + +from typing import Any + import numpy as np +import xarray as xr class XYZCoords: @@ -18,7 +23,7 @@ def as_dict(self) -> dict: class NodeCoords: - def __init__(self, node: int | str | None = None): + def __init__(self, node: str | None = None): self.node = node if node is not None else np.nan @property @@ -49,3 +54,44 @@ def as_dict(self) -> dict: if self.distance is not None: d["distance"] = self.distance return d + + +def _coordinate_values(ds: xr.Dataset, coord: str) -> Any: + """A dataset's values for one coordinate, or None when it has no such coordinate. + + A scalar coordinate is unwrapped to its single value; anything else is + handed back as the array it is. + """ + if coord not in ds.coords: + return None + vals = ds[coord].values + return np.atleast_1d(vals)[0] if vals.ndim == 0 else vals + + +#: Scalar coordinates that say where a network timeseries sits, rather than what +#: it holds. They are dropped on the way to a dataframe, where they would +#: otherwise become columns. +NETWORK_LOCATION_COORDS = ("node", "node_index", "reach", "distance") + + +def network_location(ds: xr.Dataset) -> Any: + """Where a network timeseries sits, as the network that produced it named it. + + Returns a node name for a node, a ``(reach, distance)`` pair for a + breakpoint, a reach name when no distance was given, and None for data that + carries no network location. The value is returned as recorded, so a comparer + saved by an older version gives back the integer it stored. + """ + if "node" in ds.coords: + return _network_scalar(ds, "node") + if "reach" in ds.coords: + reach = _network_scalar(ds, "reach") + if "distance" not in ds.coords: + return reach + return (reach, _network_scalar(ds, "distance")) + return None + + +def _network_scalar(ds: xr.Dataset, name: str) -> Any: + value = _coordinate_values(ds, name) + return value.item() if hasattr(value, "item") else value diff --git a/src/modelskill/timeseries/_network.py b/src/modelskill/timeseries/_network.py new file mode 100644 index 000000000..535b22c53 --- /dev/null +++ b/src/modelskill/timeseries/_network.py @@ -0,0 +1,45 @@ +from __future__ import annotations +from typing import Sequence + +import xarray as xr + +from ..quantity import Quantity +from ..types import PointType +from ._coords import NodeCoords, ReachCoords +from ._point import _parse_point_input + + +def _parse_network_node_input( + data: PointType, + name: str | None, + item: str | int | None, + quantity: Quantity | None, + node: str | None, + aux_items: Sequence[int | str] | None, +) -> xr.Dataset: + if node is None: + raise ValueError("'node' argument cannot be empty.") + coords = NodeCoords(node=node) + ds = _parse_point_input(data, name, item, quantity, aux_items, coords=coords) + return ds + + +def _parse_network_breakpoint_input( + data: PointType, + name: str | None, + item: str | int | None, + quantity: Quantity | None, + aux_items: Sequence[int | str] | None, + *, + reach: str, + distance: float | None = None, +) -> xr.Dataset: + """Parse input for a breakpoint (or reach-level) observation. + + When ``distance`` is ``None`` the observation is reach-level — no + ``distance`` coordinate is stored and the result can be matched to any + breakpoint on the reach. + """ + coords = ReachCoords(reach=reach, distance=distance) + ds = _parse_point_input(data, name, item, quantity, aux_items, coords=coords) + return ds diff --git a/src/modelskill/timeseries/_point.py b/src/modelskill/timeseries/_point.py index f0742f4dc..f0eb47b2d 100644 --- a/src/modelskill/timeseries/_point.py +++ b/src/modelskill/timeseries/_point.py @@ -168,12 +168,7 @@ def _include_attributes( ) -> xr.Dataset: ds = ds.copy() - if "node" in ds.coords or ("reach" in ds.coords and "distance" in ds.coords): - ds.attrs["gtype"] = str(GeometryType.NODE) - elif "reach" in ds.coords: - ds.attrs["gtype"] = str(GeometryType.REACH) - else: - ds.attrs["gtype"] = str(GeometryType.POINT) + ds.attrs["gtype"] = str(GeometryType.from_network_coords(ds) or GeometryType.POINT) ds[name].attrs["long_name"] = quantity.name ds[name].attrs["units"] = quantity.unit @@ -279,39 +274,3 @@ def _parse_xyz_point_input( coords = XYZCoords(x, y, z) ds = _parse_point_input(data, name, item, quantity, aux_items, coords=coords) return ds - - -def _parse_network_node_input( - data: PointType, - name: str | None, - item: str | int | None, - quantity: Quantity | None, - node: int | str | None, - aux_items: Sequence[int | str] | None, -) -> xr.Dataset: - if node is None: - raise ValueError("'node' argument cannot be empty.") - coords = NodeCoords(node=node) - ds = _parse_point_input(data, name, item, quantity, aux_items, coords=coords) - return ds - - -def _parse_network_breakpoint_input( - data: PointType, - name: str | None, - item: str | int | None, - quantity: Quantity | None, - aux_items: Sequence[int | str] | None, - *, - reach: str, - distance: float | None = None, -) -> xr.Dataset: - """Parse input for a breakpoint (or reach-level) observation. - - When ``distance`` is ``None`` the observation is reach-level — no - ``distance`` coordinate is stored and the result can be matched to any - breakpoint on the reach. - """ - coords = ReachCoords(reach=reach, distance=distance) - ds = _parse_point_input(data, name, item, quantity, aux_items, coords=coords) - return ds diff --git a/src/modelskill/timeseries/_timeseries.py b/src/modelskill/timeseries/_timeseries.py index bba5d78f7..55d8ae8af 100644 --- a/src/modelskill/timeseries/_timeseries.py +++ b/src/modelskill/timeseries/_timeseries.py @@ -10,11 +10,13 @@ from ..types import GeometryType from ..quantity import Quantity +from ._coords import NETWORK_LOCATION_COORDS, _coordinate_values from ._plotter import TimeSeriesPlotter, MatplotlibTimeSeriesPlotter from .. import __version__ T = TypeVar("T", bound="TimeSeries") + DEFAULT_COLORS = [ "#b30000", "#7c1158", @@ -101,16 +103,9 @@ def _validate_dataset(ds: xr.Dataset) -> xr.Dataset: # Validate coordinates: x,y spatial, node-based, or reach-based (with or without chainage) has_spatial_coords = "x" in ds.coords and "y" in ds.coords - has_node_coord = "node" in ds.coords - has_breakpoint_coords = "reach" in ds.coords and "distance" in ds.coords - has_reach_coord = "reach" in ds.coords and "distance" not in ds.coords - - if ( - not has_spatial_coords - and not has_node_coord - and not has_breakpoint_coords - and not has_reach_coord - ): + has_network_coords = GeometryType.from_network_coords(ds) is not None + + if not has_spatial_coords and not has_network_coords: raise ValueError( "data must have either x,y coordinates, a node coordinate, " "reach+distance coordinates, or a reach coordinate" @@ -263,16 +258,8 @@ def y(self) -> Any: def y(self, value: Any) -> None: self.data["y"] = value - @property - def node(self) -> Any: - """node-coordinate""" - return self._coordinate_values("node") - - def _coordinate_values(self, coord: str) -> None | float | np.ndarray: - if coord not in self.data.coords: - return None # Node-based data doesn't have y coordinate - vals = self.data[coord].values - return np.atleast_1d(vals)[0] if vals.ndim == 0 else vals + def _coordinate_values(self, coord: str) -> Any: + return _coordinate_values(self.data, coord) @property def _is_modelresult(self) -> bool: @@ -292,16 +279,18 @@ def _values_as_series(self) -> pd.Series: def _aux_vars(self): return list(self.data.filter_by_attrs(kind="aux").data_vars) + def _location_repr(self) -> str | None: + """The location line for ``__repr__``, or None when there is nothing to say.""" + if self.gtype == str(GeometryType.POINT): + if self.x is not None and self.y is not None: + return f"Location: {self.x}, {self.y}" + return None + def __repr__(self) -> str: res = [] res.append(f"<{self.__class__.__name__}>: {self.name}") - if self.gtype == str(GeometryType.POINT): - # Show location based on available coordinates - if "node" in self.data.coords: - node_id = self.data.coords["node"].item() - res.append(f"Node: {node_id}") - elif self.x is not None and self.y is not None: - res.append(f"Location: {self.x}, {self.y}") + if (location := self._location_repr()) is not None: + res.append(location) res.append(f"Time: {self.time[0]} - {self.time[-1]}") res.append(f"Quantity: {self.quantity}") if len(self._aux_vars) > 0: @@ -366,10 +355,12 @@ def to_dataframe(self) -> pd.DataFrame: return df[cols] elif self.gtype == str(GeometryType.VERTICAL): return self.data.drop_vars(["x", "y"]).to_dataframe() - elif self.gtype == str(GeometryType.NODE): - return self.data.drop_vars(["node"]).to_dataframe() - elif self.gtype == str(GeometryType.REACH): - return self.data.drop_vars(["reach"]).to_dataframe() + elif self.gtype in (str(GeometryType.NODE), str(GeometryType.REACH)): + # A breakpoint carries reach and distance rather than node, so drop + # whichever of them this one has. + return self.data.drop_vars( + NETWORK_LOCATION_COORDS, errors="ignore" + ).to_dataframe() else: raise NotImplementedError(f"Unknown gtype: {self.gtype}") diff --git a/src/modelskill/types.py b/src/modelskill/types.py index cf9e2a390..002ddb40f 100644 --- a/src/modelskill/types.py +++ b/src/modelskill/types.py @@ -56,6 +56,44 @@ def from_string(s: str) -> "GeometryType": f"GeometryType {s} not recognized. Available options: {[m.name for m in GeometryType]}" ) from e + @staticmethod + def from_network_coords(ds: xr.Dataset) -> "GeometryType | None": + """The network location a dataset's coordinates record, if any. + + Only network coordinates are read. Data located some other way, by x and + y for instance, records no network location and gives None rather than + the geometry it does have. + + Parameters + ---------- + ds : xr.Dataset + Dataset to inspect. + + Returns + ------- + GeometryType or None + NODE for a node or a break point, REACH for a whole reach, and None + for data that carries no network location. + + Examples + -------- + >>> import xarray as xr + >>> from modelskill.types import GeometryType + >>> GeometryType.from_network_coords(xr.Dataset(coords={"node": "123"})) + + >>> GeometryType.from_network_coords(xr.Dataset(coords={"reach": "r1", "distance": 24.5})) + + >>> GeometryType.from_network_coords(xr.Dataset(coords={"reach": "r1"})) + + >>> GeometryType.from_network_coords(xr.Dataset(coords={"x": 0.0, "y": 0.0})) is None + True + """ + if "node" in ds.coords or {"reach", "distance"} <= set(ds.coords): + return GeometryType.NODE + if "reach" in ds.coords: + return GeometryType.REACH + return None + DataInputType = Union[ str, diff --git a/tests/network_helpers.py b/tests/network_helpers.py new file mode 100644 index 000000000..1c4f6a0a0 --- /dev/null +++ b/tests/network_helpers.py @@ -0,0 +1,65 @@ +"""Helpers shared by the tests that build networks by hand. + +Importing this module needs mikeio1d, which is an optional dependency (ADR-010), +so guard the import with ``pytest.importorskip("mikeio1d.network")`` first. +""" + +from __future__ import annotations + +import pandas as pd +from mikeio1d.network import Network, BasicNode, BasicReach, ReachBreakPoint + + +class BreakPoint(ReachBreakPoint): + """A break point at a known distance along a reach.""" + + def __init__(self, reach, distance, data): + self._id = (reach, distance) + self._data = data + + @property + def id(self): + return self._id + + @property + def data(self): + return self._data + + +def make_network(node_ids, time, data, quantity="WaterLevel"): + """A chain of nodes, each carrying one quantity, joined by unit reaches.""" + nodes = [ + BasicNode(node_id, pd.DataFrame({quantity: data[:, i]}, index=time)) + for i, node_id in enumerate(node_ids) + ] + reaches = [ + BasicReach(f"r{i}", nodes[i], nodes[i + 1], length=100.0) + for i in range(len(nodes) - 1) + ] + return Network(reaches) + + +def make_breakpoint_network(reach_id, distance, data): + """A one-reach network whose data sits on a break point, not on its nodes.""" + empty = pd.DataFrame() + reach = BasicReach( + reach_id, + BasicNode("start", empty), + BasicNode("end", empty), + length=100.0, + breakpoints=[BreakPoint(reach_id, distance, data)], + ) + return Network([reach]) + + +def node_series(network, quantity="WaterLevel"): + """Each node's own series for `quantity`, keyed by node id, read off the reaches. + + The three nodes of `sample_network` carry three different series, so a + comparer built from one of them cannot be satisfied by any of the others. + """ + nodes = {} + for reach in network.reaches.values(): + for node in (reach.start, reach.end): + nodes[node.id] = node.data[quantity] + return pd.DataFrame(nodes) diff --git a/tests/test_comparercollection.py b/tests/test_comparercollection.py index bdf4aa807..7a1fb9459 100644 --- a/tests/test_comparercollection.py +++ b/tests/test_comparercollection.py @@ -454,10 +454,8 @@ def test_save_and_load_preserves_raw_model_data(cc, tmp_path): @pytest.fixture def node_comparer() -> modelskill.comparison.Comparer: """A comparer built by matching a NodeObservation against a NetworkModelResult (node gtype).""" - pytest.importorskip("networkx") - from modelskill.model.network import NetworkModelResult - from modelskill.network import Network, BasicNode, BasicReach - from modelskill.obs import NodeObservation + pytest.importorskip("mikeio1d.network") + from mikeio1d.network import Network, BasicNode, BasicReach time = pd.date_range("2019-01-01", periods=6, freq="D") node_a_data = pd.DataFrame( @@ -471,9 +469,25 @@ def node_comparer() -> modelskill.comparison.Comparer: ) network = Network([reach]) - nmr = NetworkModelResult(network, name="Network_Model") - node_id = network.find(node="123") - obs = NodeObservation(node_a_data, at=node_id, name="Node_123_Obs") + nmr = ms.NetworkModelResult(network, name="Network_Model") + obs = ms.NodeObservation(node_a_data, at="123", name="Node_123_Obs") + + return ms.match(obs, nmr) + + +@pytest.fixture +def reach_comparer() -> modelskill.comparison.Comparer: + """A comparer built by matching a ReachObservation (reach gtype).""" + pytest.importorskip("mikeio1d.network") + from tests.network_helpers import make_breakpoint_network + + time = pd.date_range("2019-01-01", periods=6, freq="D") + values = pd.DataFrame({"WaterLevel": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]}, index=time) + + nmr = ms.NetworkModelResult( + make_breakpoint_network("r1", 50.0, values), name="Network_Model" + ) + obs = ms.ReachObservation(values, reach="r1", name="Reach_r1_Obs") return ms.match(obs, nmr) @@ -490,6 +504,70 @@ def test_save_and_load_round_trips_node_gtype_raw_data(node_comparer, tmp_path): assert len(cc2[0].raw_mod_data["Network_Model"]) == len( node_comparer.raw_mod_data["Network_Model"] ) + # The node was addressed by name, and the name is what comes back: reloading + # must not depend on the integer the network happened to hand out. + assert cc2[0].node == "123" + assert cc2[0].raw_mod_data["Network_Model"].node == "123" + + +def test_a_comparer_saved_by_1_4_0a3_still_loads(): + """The alpha wrote the graph integer into the node coordinate. + + Nothing on the load path derives a location from it any more, so such a file + keeps working -- it just gives the integer back. Needs no mikeio1d: it is a + netcdf file, not a network. + """ + cmp = ms.load("tests/testdata/node_comparer_1.4.0a3.nc") + + assert cmp.gtype == "node" + assert cmp.node == 0 + assert cmp.skill().to_dataframe().shape[0] == 1 + + +def test_save_and_load_round_trips_reach_gtype_raw_data(reach_comparer, tmp_path): + """Reach-gtype comparers must survive a save()/load() round trip too.""" + cc = ms.ComparerCollection([reach_comparer]) + fn = tmp_path / "test_cc_reach.msk" + cc.save(fn) + + cc2 = ms.load(fn) + + assert cc2[0].gtype == "reach" + assert cc2[0].reach == "r1" + assert len(cc2[0].raw_mod_data["Network_Model"]) == len( + reach_comparer.raw_mod_data["Network_Model"] + ) + + +def test_to_dataframe_on_a_node_comparer(node_comparer): + df = node_comparer.to_dataframe() + + assert list(df.columns) == ["Observation", "Network_Model"] + assert df.index.name == "time" + + +def test_to_dataframe_on_a_reach_comparer(reach_comparer): + """The location coordinates are dropped, as they are for every other gtype.""" + df = reach_comparer.to_dataframe() + + assert list(df.columns) == ["Observation", "Network_Model"] + assert df.index.name == "time" + + +def test_skill_on_a_node_comparer(node_comparer): + sk = node_comparer.skill() + + assert sk.to_dataframe().shape[0] == 1 + assert "Node_123_Obs" in sk.index + + +def test_plot_a_node_comparer(node_comparer): + assert node_comparer.plot.timeseries() is not None + assert node_comparer.plot.scatter() is not None + + +def test_plot_a_reach_comparer(reach_comparer): + assert reach_comparer.plot.timeseries() is not None # ======================== plotting ======================== diff --git a/tests/test_match.py b/tests/test_match.py index 3324f4788..ec0b25278 100644 --- a/tests/test_match.py +++ b/tests/test_match.py @@ -7,10 +7,6 @@ import modelskill as ms from modelskill.comparison._comparison import ItemSelection from modelskill.model.dfsu import DfsuModelResult -try: - from modelskill.network import _make_basic_network -except ImportError: - pass @pytest.fixture @@ -341,20 +337,24 @@ def test_only_1_model_depth_overlap(self, simple_vo, simple_vm): def network(): """Network fixture with 3 nodes""" pytest.importorskip("networkx") + from tests.network_helpers import make_network + time = pd.date_range("2017-10-27", periods=20, freq="h") np.random.seed(42) data = np.random.normal(1.5, 0.3, (20, 3)) - return _make_basic_network(["100", "200", "300"], time, data) + return make_network(["100", "200", "300"], time, data) @pytest.fixture def network2(): """Second network fixture with offset data for multi-model tests""" pytest.importorskip("networkx") + from tests.network_helpers import make_network + time = pd.date_range("2017-10-27", periods=20, freq="h") np.random.seed(42) data = np.random.normal(1.5, 0.3, (20, 3)) + 0.1 - return _make_basic_network(["100", "200", "300"], time, data) + return make_network(["100", "200", "300"], time, data) @pytest.fixture @@ -366,7 +366,7 @@ def network_mr(network): @pytest.fixture def node_obs1(network): """NodeObservation for node '100'""" - node_id = network.find(node="100") + node_id = "100" time = pd.date_range("2017-10-27", periods=18, freq="h") # Add some noise to make it different from model np.random.seed(123) @@ -378,7 +378,7 @@ def node_obs1(network): @pytest.fixture def node_obs2(network): """NodeObservation for node '200'""" - node_id = network.find(node="200") + node_id = "200" time = pd.date_range("2017-10-27", periods=15, freq="h") np.random.seed(456) data = np.random.normal(1.6, 0.25, len(time)) @@ -392,7 +392,7 @@ def node_obs_invalid(network): time = pd.date_range("2017-10-27", periods=10, freq="h") data = np.random.normal(1.5, 0.2, len(time)) df = pd.DataFrame({"WaterLevel": data}, index=time) - return ms.NodeObservation(df, at=999, name="Node_999_Obs") + return ms.NodeObservation(df, at="999", name="Node_999_Obs") @pytest.fixture @@ -410,7 +410,7 @@ def network_mr2(network2): @pytest.fixture def node_obs_gaps(network): """NodeObservation with time gaps""" - node_id = network.find(node="100") + node_id = "100" time = pd.date_range("2017-10-27", periods=10, freq="2h") # Different frequency data = np.random.normal(1.5, 0.2, len(time)) df = pd.DataFrame({"WaterLevel": data}, index=time) @@ -1007,6 +1007,37 @@ def test_match_node_obs_with_network_model(node_obs1, network_mr): assert cmp.mod_names == ["Network_Model"] +def test_match_reach_obs_with_network_model(): + """A reach observation matches any breakpoint along the reach.""" + pytest.importorskip("mikeio1d.network") + from tests.network_helpers import make_breakpoint_network + + time = pd.date_range("2017-10-27", periods=20, freq="h") + np.random.seed(42) + model_data = pd.DataFrame( + {"WaterLevel": np.random.normal(1.5, 0.3, len(time))}, index=time + ) + network_mr = ms.NetworkModelResult( + make_breakpoint_network("r0", 50.0, model_data), name="Network_Model" + ) + + np.random.seed(123) + obs_time = time[:18] + df = pd.DataFrame( + {"WaterLevel": np.random.normal(1.4, 0.2, len(obs_time))}, index=obs_time + ) + obs = ms.ReachObservation(df, reach="r0", name="Reach_r0") + + cmp = ms.match(obs, network_mr) + + assert cmp.n_models == 1 + assert cmp.n_points == 18 + assert cmp.name == "Reach_r0" + assert cmp.gtype == "reach" + assert cmp.reach == "r0" + assert cmp.mod_names == ["Network_Model"] + + def test_match_multiple_node_obs_with_network(node_obs1, node_obs2, network_mr): cc = ms.match([node_obs1, node_obs2], network_mr) assert cc.n_models == 1 @@ -1027,7 +1058,7 @@ def test_match_node_obs_with_multiple_network_models( def test_match_network_invalid_node_error(node_obs_invalid, network_mr): - with pytest.raises(ValueError, match="Node 999 not found"): + with pytest.raises(ValueError, match="not found"): ms.match(node_obs_invalid, network_mr) @@ -1064,7 +1095,8 @@ def test_network_match_multi_obs_multi_model_comprehensive( def test_network_match_error_non_node_observation(network_mr, point_obs_error): """Test that non-NodeObservation raises appropriate error""" with pytest.raises( - TypeError, match="NetworkModelResult supports NodeObservation and ReachObservation" + TypeError, + match="NetworkModelResult supports NodeObservation and ReachObservation", ): ms.match(point_obs_error, network_mr) diff --git a/tests/test_mikeplus.py b/tests/test_mikeplus.py new file mode 100644 index 000000000..bce209243 --- /dev/null +++ b/tests/test_mikeplus.py @@ -0,0 +1,558 @@ +import sqlite3 + +import numpy as np +import pandas as pd +import pytest + +from modelskill import NodeObservation, Quantity, ReachObservation + +STATION_COLUMNS = [ + "muid", + "locationid", + "locationtype", + "chainagevalue", + "assetname", +] +MEASUREMENT_COLUMNS = [ + "measurementstationid", + "tsfilename", + "tsitemname", + "resitemname", +] + +JUNCTION = 8 +LINK = 9 +TANK = 12 + + +def station(muid, locationid, locationtype, assetname, chainagevalue=None): + return dict( + muid=muid, + locationid=locationid, + locationtype=locationtype, + chainagevalue=chainagevalue, + assetname=assetname, + ) + + +def measurement(station_muid, item, quantity, file="calib.dfs0"): + return dict( + measurementstationid=station_muid, + tsfilename=rf"..\Scripts\{file}", + tsitemname=item, + resitemname=f"{quantity};{quantity};100450", + ) + + +def build_db(path, stations, measurements, *, station_columns=STATION_COLUMNS): + conn = sqlite3.connect(str(path)) + pd.DataFrame(stations, columns=station_columns).to_sql( + "m_Station", conn, index=False + ) + pd.DataFrame(measurements, columns=MEASUREMENT_COLUMNS).to_sql( + "m_Measurement", conn, index=False + ) + conn.commit() + conn.close() + return str(path) + + +@pytest.fixture +def db(tmp_path): + """Two pressure sensors on nodes, one flow meter on a link.""" + stations = [ + station("s1", "wNode_1", JUNCTION, "PT.401"), + station("s2", "Tank_A", TANK, "LT.410"), + station("s3", "Pipe_7", LINK, "FT.403"), + ] + measurements = [ + measurement("s1", "item_pressure_1", "Pressure"), + measurement("s2", "item_pressure_2", "Pressure"), + measurement("s3", "item_flow_1", "Flow"), + ] + return build_db(tmp_path / "mikeplus.sqlite", stations, measurements) + + +def frame(*items): + """A data source holding one timeseries per named item.""" + time = pd.date_range("2024-01-01", periods=24, freq="h") + rng = np.random.default_rng(42) + return pd.DataFrame( + {item: rng.normal(35.0, 1.0, len(time)) for item in items}, index=time + ) + + +def test_junction_and_tank_resolve_to_nodes(db): + obs_list = NodeObservation.from_multiple( + data=frame("item_pressure_1", "item_pressure_2"), db=db, quantity="Pressure" + ) + + assert {obs.at for obs in obs_list} == {"wNode_1", "Tank_A"} + + +def test_link_without_chainage_resolves_to_a_reach(db): + (obs,) = ReachObservation.from_multiple( + data=frame("item_flow_1"), db=db, quantity="Flow" + ) + + assert obs.reach == "Pipe_7" + + +def test_link_with_chainage_resolves_to_a_breakpoint(tmp_path): + path = build_db( + tmp_path / "chainage.sqlite", + [station("s1", "Pipe_7", LINK, "FT.403", chainagevalue=24.5)], + [measurement("s1", "item_flow_1", "Flow")], + ) + + (obs,) = NodeObservation.from_multiple( + data=frame("item_flow_1"), db=path, quantity="Flow" + ) + + assert obs.at == ("Pipe_7", 24.5) + + +def test_several_items_at_one_location_all_survive(tmp_path): + path = build_db( + tmp_path / "shared.sqlite", + [ + station("s1", "wNode_1", JUNCTION, "PT.401"), + station("s2", "wNode_1", JUNCTION, "PT.402"), + ], + [ + measurement("s1", "before_valve", "Pressure"), + measurement("s2", "after_valve", "Pressure"), + ], + ) + + obs_list = NodeObservation.from_multiple( + data=frame("before_valve", "after_valve"), db=path, quantity="Pressure" + ) + + assert [obs.name for obs in obs_list] == ["PT.401", "PT.402"] + assert {obs.at for obs in obs_list} == {"wNode_1"} + + +def test_name_falls_back_to_item_name_when_assetnames_collide(tmp_path): + path = build_db( + tmp_path / "collide.sqlite", + [ + station("s1", "wNode_1", JUNCTION, "same"), + station("s2", "wNode_2", JUNCTION, "same"), + ], + [ + measurement("s1", "item_a", "Pressure"), + measurement("s2", "item_b", "Pressure"), + ], + ) + + obs_list = NodeObservation.from_multiple( + data=frame("item_a", "item_b"), db=path, quantity="Pressure" + ) + + assert [obs.name for obs in obs_list] == ["item_a", "item_b"] + + +def test_quantity_is_inferred_when_unambiguous(db): + obs_list = NodeObservation.from_multiple( + data=frame("item_pressure_1", "item_pressure_2"), db=db + ) + + assert {obs.quantity.name for obs in obs_list} == {"Pressure"} + + +def test_ambiguous_quantity_raises_and_lists_options(tmp_path): + path = build_db( + tmp_path / "two_quantities.sqlite", + [ + station("s1", "wNode_1", JUNCTION, "PT.401"), + station("s2", "wNode_2", JUNCTION, "LT.410"), + ], + [ + measurement("s1", "item_pressure", "Pressure"), + measurement("s2", "item_level", "Water Level"), + ], + ) + + with pytest.raises(ValueError, match="cannot be inferred") as excinfo: + NodeObservation.from_multiple( + data=frame("item_pressure", "item_level"), db=path + ) + + assert "Pressure" in str(excinfo.value) + assert "Water Level" in str(excinfo.value) + + +def test_the_observation_kind_narrows_the_pool_used_for_inference(db): + # The database holds pressure on two nodes and flow on a reach. Asking for + # node observations leaves only one quantity, so it needs no naming. + obs_list = NodeObservation.from_multiple( + data=frame("item_pressure_1", "item_pressure_2", "item_flow_1"), db=db + ) + + assert {obs.quantity.name for obs in obs_list} == {"Pressure"} + assert len(obs_list) == 2 + + +def test_unknown_quantity_raises(db): + with pytest.raises(ValueError, match="not found"): + NodeObservation.from_multiple( + data=frame("item_pressure_1"), db=db, quantity="Discharge" + ) + + +def test_asking_for_a_node_when_the_station_is_a_reach_raises(db): + with pytest.raises(ValueError, match="not 'node'"): + NodeObservation.from_multiple(data=frame("item_flow_1"), db=db, quantity="Flow") + + +def test_missing_items_raise_and_separate_the_two_causes(db): + with pytest.raises(ValueError) as excinfo: + NodeObservation.from_multiple( + data=frame("item_pressure_1", "PT.401", "never_heard_of_it"), + db=db, + quantity="Pressure", + ) + + message = str(excinfo.value) + assert "no measurement registered" in message + assert "PT.401" in message + assert "Not found in the database" in message + assert "never_heard_of_it" in message + + +def test_missing_items_can_be_skipped(db): + obs_list = NodeObservation.from_multiple( + data=frame("item_pressure_1", "never_heard_of_it"), + db=db, + quantity="Pressure", + on_missing="skip", + ) + + assert [obs.name for obs in obs_list] == ["PT.401"] + + +def test_source_selects_between_files(tmp_path): + path = build_db( + tmp_path / "files.sqlite", + [station("s1", "wNode_1", JUNCTION, "PT.401")], + [ + measurement("s1", "item_a", "Pressure", file="main.dfs0"), + measurement("s1", "item_a", "Pressure", file="other.dfs0"), + ], + ) + + obs_list = NodeObservation.from_multiple( + data=frame("item_a"), db=path, quantity="Pressure", source="main.dfs0" + ) + + assert len(obs_list) == 1 + + +def test_source_accepts_a_full_path(tmp_path): + path = build_db( + tmp_path / "fullpath.sqlite", + [station("s1", "wNode_1", JUNCTION, "PT.401")], + [measurement("s1", "item_a", "Pressure", file="main.dfs0")], + ) + + obs_list = NodeObservation.from_multiple( + data=frame("item_a"), + db=path, + quantity="Pressure", + source="/some/where/main.dfs0", + ) + + assert len(obs_list) == 1 + + +def test_source_matches_the_whole_file_name(tmp_path): + """A file name that is a substring of another must not match it as well.""" + path = build_db( + tmp_path / "substring.sqlite", + [station("s1", "wNode_1", JUNCTION, "PT.401")], + [ + measurement("s1", "item_a", "Pressure", file="calib.dfs0"), + measurement("s1", "item_a", "Pressure", file="my_calib.dfs0"), + ], + ) + + obs_list = NodeObservation.from_multiple( + data=frame("item_a"), db=path, quantity="Pressure", source="calib.dfs0" + ) + + assert len(obs_list) == 1 + + +def test_an_underscore_in_the_source_is_not_a_wildcard(tmp_path): + path = build_db( + tmp_path / "wildcard.sqlite", + [station("s1", "wNode_1", JUNCTION, "PT.401")], + [measurement("s1", "item_a", "Pressure", file="calibX1.dfs0")], + ) + + with pytest.raises(ValueError, match="could not be resolved"): + NodeObservation.from_multiple( + data=frame("item_a"), db=path, quantity="Pressure", source="calib_1.dfs0" + ) + + +def test_item_registered_against_several_files_raises_without_source(tmp_path): + path = build_db( + tmp_path / "ambiguous.sqlite", + [station("s1", "wNode_1", JUNCTION, "PT.401")], + [ + measurement("s1", "item_a", "Pressure", file="main.dfs0"), + measurement("s1", "item_a", "Pressure", file="other.dfs0"), + ], + ) + + with pytest.raises(ValueError, match="more than one file"): + NodeObservation.from_multiple( + data=frame("item_a"), db=path, quantity="Pressure" + ) + + +def test_a_station_that_is_no_place_in_the_network_is_left_out(tmp_path): + """A rain gauge registered in the same file must not fail the whole resolve.""" + path = build_db( + tmp_path / "gauge.sqlite", + [ + station("s1", "wNode_1", JUNCTION, "PT.401"), + station("s2", "Catch_1", 1, "RG.1"), + ], + [ + measurement("s1", "item_pressure", "Pressure"), + measurement("s2", "item_rain", "Rainfall"), + ], + ) + + obs_list = NodeObservation.from_multiple( + data=frame("item_pressure", "item_rain"), db=path, quantity="Pressure" + ) + + assert [obs.name for obs in obs_list] == ["PT.401"] + + +def test_asking_for_a_quantity_only_an_unplaceable_station_carries_raises(tmp_path): + path = build_db( + tmp_path / "gauge_only.sqlite", + [station("s2", "Catch_1", 1, "RG.1")], + [measurement("s2", "item_rain", "Rainfall")], + ) + + with pytest.raises(ValueError, match="unsupported locationtype"): + NodeObservation.from_multiple( + data=frame("item_rain"), db=path, quantity="Rainfall" + ) + + +def test_unknown_locationtype_raises(tmp_path): + path = build_db( + tmp_path / "weird.sqlite", + [station("s1", "wNode_1", 99, "PT.401")], + [measurement("s1", "item_a", "Pressure")], + ) + + with pytest.raises(ValueError, match="unsupported locationtype"): + NodeObservation.from_multiple( + data=frame("item_a"), db=path, quantity="Pressure" + ) + + +def test_accepts_an_open_connection(db): + conn = sqlite3.connect(db) + try: + (obs,) = ReachObservation.from_multiple( + data=frame("item_flow_1"), db=conn, quantity="Flow" + ) + finally: + conn.close() + + assert obs.reach == "Pipe_7" + + +def test_missing_table_raises(tmp_path): + path = str(tmp_path / "empty.sqlite") + conn = sqlite3.connect(path) + pd.DataFrame({"a": [1]}).to_sql("something_else", conn, index=False) + conn.close() + + with pytest.raises(ValueError, match="missing table"): + NodeObservation.from_multiple(data=frame("item_a"), db=path) + + +def test_missing_column_raises(tmp_path): + path = build_db( + tmp_path / "thin.sqlite", + [ + dict(muid="s1", locationid="wNode_1", locationtype=JUNCTION, assetname="a"), + ], + [measurement("s1", "item_a", "Pressure")], + station_columns=["muid", "locationid", "locationtype", "assetname"], + ) + + with pytest.raises(ValueError, match="missing column"): + NodeObservation.from_multiple(data=frame("item_a"), db=path) + + +@pytest.fixture +def calibration_data(): + """A data source holding both pressure and flow items.""" + time = pd.date_range("2024-01-01", periods=24, freq="h") + rng = np.random.default_rng(42) + return pd.DataFrame( + { + "item_pressure_1": rng.normal(35.0, 1.0, len(time)), + "item_pressure_2": rng.normal(36.0, 1.0, len(time)), + "item_flow_1": rng.normal(120.0, 5.0, len(time)), + }, + index=time, + ) + + +class TestNodeObservationFromDatabase: + def test_builds_one_observation_per_item(self, db, calibration_data): + obs_list = NodeObservation.from_multiple( + data=calibration_data, db=db, quantity="Pressure" + ) + + assert len(obs_list) == 2 + assert all(isinstance(obs, NodeObservation) for obs in obs_list) + assert [obs.at for obs in obs_list] == ["wNode_1", "Tank_A"] + + def test_names_come_from_the_database(self, db, calibration_data): + obs_list = NodeObservation.from_multiple( + data=calibration_data, db=db, quantity="Pressure" + ) + + assert [obs.name for obs in obs_list] == ["PT.401", "LT.410"] + + def test_quantity_comes_from_the_database(self, db, calibration_data): + obs_list = NodeObservation.from_multiple( + data=calibration_data, db=db, quantity="Pressure" + ) + + assert all(obs.quantity.name == "Pressure" for obs in obs_list) + + def test_data_is_selected_per_item(self, db, calibration_data): + obs_list = NodeObservation.from_multiple( + data=calibration_data, db=db, quantity="Pressure" + ) + + expected = calibration_data["item_pressure_1"].to_numpy() + assert obs_list[0].values == pytest.approx(expected) + + def test_quantity_is_inferred_when_only_nodes_are_wanted( + self, db, calibration_data + ): + obs_list = NodeObservation.from_multiple(data=calibration_data, db=db) + + assert len(obs_list) == 2 + assert all(obs.quantity.name == "Pressure" for obs in obs_list) + + def test_several_sensors_at_one_node_are_all_kept(self, tmp_path): + path = build_db( + tmp_path / "shared.sqlite", + [ + station("s1", "wNode_1", JUNCTION, "PT.401"), + station("s2", "wNode_1", JUNCTION, "PT.402"), + ], + [ + measurement("s1", "before_valve", "Pressure"), + measurement("s2", "after_valve", "Pressure"), + ], + ) + time = pd.date_range("2024-01-01", periods=5, freq="h") + data = pd.DataFrame( + {"before_valve": range(5), "after_valve": range(5, 10)}, index=time + ) + + obs_list = NodeObservation.from_multiple(data=data, db=path) + + assert [obs.at for obs in obs_list] == ["wNode_1", "wNode_1"] + assert [obs.name for obs in obs_list] == ["PT.401", "PT.402"] + + def test_flow_on_a_link_points_at_reach_observation(self, db, calibration_data): + with pytest.raises(ValueError, match="not 'node'"): + NodeObservation.from_multiple(data=calibration_data, db=db, quantity="Flow") + + def test_unresolvable_item_raises(self, db, calibration_data): + data = calibration_data.rename(columns={"item_pressure_1": "mystery_sensor"}) + + with pytest.raises(ValueError, match="could not be resolved"): + NodeObservation.from_multiple(data=data, db=db, quantity="Pressure") + + def test_unresolvable_item_can_be_skipped(self, db, calibration_data): + data = calibration_data.rename(columns={"item_pressure_1": "mystery_sensor"}) + + obs_list = NodeObservation.from_multiple( + data=data, db=db, quantity="Pressure", on_missing="skip" + ) + + assert [obs.name for obs in obs_list] == ["LT.410"] + + def test_db_and_nodes_are_mutually_exclusive(self, db, calibration_data): + with pytest.raises(ValueError, match="mutually exclusive"): + NodeObservation.from_multiple( + data=calibration_data, db=db, nodes={"wNode_1": "item_pressure_1"} + ) + + def test_db_without_data_raises(self, db): + with pytest.raises(ValueError, match="'data' is required"): + NodeObservation.from_multiple(db=db) + + def test_a_quantity_object_supplies_metadata_and_does_not_select(self, tmp_path): + """A Quantity's name is the caller's own, not a name in the database.""" + path = build_db( + tmp_path / "metadata.sqlite", + [station("s1", "wNode_1", JUNCTION, "PT.401")], + [measurement("s1", "item_a", "Pressure")], + ) + + obs_list = NodeObservation.from_multiple( + data=frame("item_a"), db=path, quantity=Quantity("Pressure_m", "m") + ) + + assert [obs.quantity.name for obs in obs_list] == ["Pressure_m"] + assert [obs.quantity.unit for obs in obs_list] == ["m"] + + def test_quantity_string_without_db_raises(self, calibration_data): + with pytest.raises(TypeError, match="must be a Quantity"): + NodeObservation.from_multiple( + data=calibration_data, + nodes={"wNode_1": "item_pressure_1"}, + quantity="Pressure", + ) + + +class TestReachObservationFromDatabase: + def test_builds_reach_observations(self, db, calibration_data): + obs_list = ReachObservation.from_multiple( + data=calibration_data, db=db, quantity="Flow" + ) + + assert len(obs_list) == 1 + assert isinstance(obs_list[0], ReachObservation) + assert obs_list[0].reach == "Pipe_7" + assert obs_list[0].name == "FT.403" + assert obs_list[0].quantity.name == "Flow" + + def test_quantity_is_inferred_when_only_reaches_are_wanted( + self, db, calibration_data + ): + obs_list = ReachObservation.from_multiple(data=calibration_data, db=db) + + assert [obs.reach for obs in obs_list] == ["Pipe_7"] + + def test_pressure_on_a_node_points_at_node_observation(self, db, calibration_data): + with pytest.raises(ValueError, match="not 'reach'"): + ReachObservation.from_multiple( + data=calibration_data, db=db, quantity="Pressure" + ) + + def test_db_and_reaches_are_mutually_exclusive(self, db, calibration_data): + with pytest.raises(ValueError, match="mutually exclusive"): + ReachObservation.from_multiple( + data=calibration_data, db=db, reaches={"r1": "item_flow_1"} + ) diff --git a/tests/test_network.py b/tests/test_network.py index a41fc997b..ba05934be 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -2,74 +2,24 @@ # ruff: noqa: E402 import sys -from pathlib import Path import pytest -pytest.importorskip("networkx") +pytest.importorskip("mikeio1d.network") import pandas as pd import xarray as xr import numpy as np +import mikeio1d import modelskill as ms -from modelskill.model.network import ( +from mikeio1d.network import Network, BasicNode, BasicReach +from modelskill import ( NetworkModelResult, - NodeModelResult, + NodeObservation, + Quantity, + ReachObservation, ) -from modelskill.model.adapters._inp import read_pipe_lengths, read_sections -from modelskill.model.adapters._res1d import ( - Res1DNode, - Res1DReach, - _simplify_colnames, -) -from modelskill.network import ( - Network, - BasicNode, - BasicReach, - NetworkReach, - ReachBreakPoint, - _EPANET_EXTENSIONS, - _MIKE_EXTENSIONS, - _UNSUPPORTED_EXTENSIONS, -) -from modelskill.obs import NodeObservation -from modelskill.quantity import Quantity - - -def _make_network(node_ids, time, data, quantity="WaterLevel"): - nodes = [ - BasicNode(nid, pd.DataFrame({quantity: data[:, i]}, index=time)) - for i, nid in enumerate(node_ids) - ] - reaches = [ - BasicReach(f"r{i}", nodes[i], nodes[i + 1], length=100.0) - for i in range(len(nodes) - 1) - ] - return Network(reaches) - - -@pytest.fixture -def sample_network_data(): - """Sample network data as xr.Dataset""" - time = pd.date_range("2010-01-01", periods=10, freq="h") - nodes = [123, 456, 789] - - # Create sample data - np.random.seed(42) # For reproducible tests - data = np.random.randn(len(time), len(nodes)) - - ds = xr.Dataset( - { - "WaterLevel": (["time", "node"], data), - }, - coords={ - "time": time, - "node": nodes, - }, - ) - ds["WaterLevel"].attrs["units"] = "m" - ds["WaterLevel"].attrs["long_name"] = "Water Level" - return ds +from tests.network_helpers import make_breakpoint_network, make_network, node_series @pytest.fixture @@ -78,7 +28,7 @@ def sample_network(): time = pd.date_range("2010-01-01", periods=10, freq="h") np.random.seed(42) data = np.random.randn(10, 3) - return _make_network(["123", "456", "789"], time, data) + return make_network(["123", "456", "789"], time, data) @pytest.fixture @@ -102,25 +52,12 @@ def sample_network_multivars(): @pytest.fixture -def dataset_without_node(): +def breakpoint_network(): + """A one-reach network whose data sits on a break point, not on the nodes.""" time = pd.date_range("2010-01-01", periods=10, freq="h") - - # Create sample data - np.random.seed(42) # For reproducible tests - data = np.random.randn(len(time)) - - ds = xr.Dataset( - { - "WaterLevel": (["time"], data), - }, - coords={ - "time": time, - }, - ) - ds["WaterLevel"].attrs["units"] = "m" - ds["WaterLevel"].attrs["long_name"] = "Water Level" - - return ds + np.random.seed(42) + values = pd.DataFrame({"WaterLevel": np.random.randn(10)}, index=time) + return make_breakpoint_network("r1", 50.0, values) @pytest.fixture @@ -152,22 +89,42 @@ def test_init_with_network(self, sample_network): assert len(nmr.time) == 10 assert isinstance(nmr.time, pd.DatetimeIndex) - assert len(nmr.nodes) == 3 + + def test_quantity_name_survives_to_the_model_result(self, sample_network): + """The network knows its quantity by name even without a unit.""" + nmr = NetworkModelResult(sample_network) + + assert nmr.quantity.name == "WaterLevel" + assert nmr.quantity != Quantity.undefined() + + def test_quantity_carries_into_extracted_node(self, sample_network): + nmr = NetworkModelResult(sample_network) + obs_data = pd.DataFrame({"sensor": np.zeros(len(nmr.time))}, index=nmr.time) + extracted = nmr.extract(NodeObservation(obs_data, at="123")) + + assert extracted.quantity.name == "WaterLevel" + + def test_explicit_quantity_wins(self, sample_network): + given = Quantity(name="Water Level", unit="meter") + nmr = NetworkModelResult(sample_network, quantity=given) + + assert nmr.quantity == given def test_init_with_name(self, sample_network): """Test initialization with explicit name""" nmr = NetworkModelResult(sample_network, name="Test_Network") assert nmr.name == "Test_Network" - def test_init_with_item_selection(self, sample_network_multivars): + def test_init_with_item_selection(self, sample_network_multivars, sample_node_data): """Test initialization with specific item selection""" nmr = NetworkModelResult( sample_network_multivars, item="WaterLevel", name="Network_WL" ) + extracted = nmr.extract(NodeObservation(sample_node_data, at="123")) assert nmr.name == "Network_WL" - assert "WaterLevel" in nmr.data.data_vars - assert "Discharge" not in nmr.data.data_vars + assert nmr.quantity.name == "WaterLevel" + assert extracted.quantity.name == "WaterLevel" def test_init_fails_with_unsupported_type(self): """Test that passing a non-Network object raises an error""" @@ -185,21 +142,20 @@ def test_repr(self, sample_network): def test_extract_valid_node(self, sample_network, sample_node_data): """Test extraction of a valid node""" nmr = NetworkModelResult(sample_network) - node_id = sample_network.find(node="123") + node_id = "123" obs = NodeObservation(sample_node_data, at=node_id, name="Node_123") extracted = nmr.extract(obs) - assert isinstance(extracted, NodeModelResult) assert extracted.node == node_id assert len(extracted.time) == 10 def test_extract_invalid_node(self, sample_network, sample_node_data): """Test extraction of a node not present in the network""" nmr = NetworkModelResult(sample_network) - obs = NodeObservation(sample_node_data, at=999, name="Node_999") + obs = NodeObservation(sample_node_data, at="999", name="Node_999") - with pytest.raises(ValueError, match="Node 999 not found"): + with pytest.raises(ValueError, match="not found"): nmr.extract(obs) def test_extract_wrong_observation_type(self, sample_network): @@ -236,26 +192,26 @@ def test_init_with_df(self, sample_node_data): """Test initialization with pandas DataFrame""" obs = NodeObservation( - sample_node_data, at=123, name="Sensor_1", item="WaterLevel" + sample_node_data, at="123", name="Sensor_1", item="WaterLevel" ) - assert obs.at == 123 + assert obs.at == "123" assert obs.name == "Sensor_1" assert len(obs.time) == 10 assert isinstance(obs.time, pd.DatetimeIndex) def test_init_with_series(self, sample_series): """Test initialization with pandas Series""" - obs = NodeObservation(sample_series, at=456, name="Node_456") + obs = NodeObservation(sample_series, at="456", name="Node_456") - assert obs.at == 456 + assert obs.at == "456" assert obs.name == "Node_456" assert len(obs.time) == 10 def test_node_attrs(self, sample_node_data): """Test attrs property""" attrs = {"source": "test", "version": "1.0"} - obs = NodeObservation(sample_node_data, at=123, attrs=attrs, weight=2.5) + obs = NodeObservation(sample_node_data, at="123", attrs=attrs, weight=2.5) assert obs.attrs["source"] == "test" assert obs.attrs["version"] == "1.0" @@ -266,7 +222,7 @@ def test_multiple_nodes_returns_list_of_observations(self, multi_data): """Test that from_multiple returns a list of NodeObservation objects""" obs_list = NodeObservation.from_multiple( data=multi_data, - nodes={123: "station_0", 456: "station_1", 789: "station_2"}, + nodes={"123": "station_0", "456": "station_1", "789": "station_2"}, ) assert len(obs_list) == 3 @@ -275,17 +231,17 @@ def test_multiple_nodes_returns_list_of_observations(self, multi_data): def test_node_ids_are_assigned_correctly(self, multi_data): obs_list = NodeObservation.from_multiple( data=multi_data, - nodes={123: "station_0", 456: "station_1", 789: "station_2"}, + nodes={"123": "station_0", "456": "station_1", "789": "station_2"}, ) - assert obs_list[0].node == 123 - assert obs_list[1].node == 456 - assert obs_list[2].node == 789 + assert obs_list[0].node == "123" + assert obs_list[1].node == "456" + assert obs_list[2].node == "789" def test_names_derived_from_column_names(self, multi_data): obs_list = NodeObservation.from_multiple( data=multi_data, - nodes={123: "station_0", 456: "station_1", 789: "station_2"}, + nodes={"123": "station_0", "456": "station_1", "789": "station_2"}, ) assert obs_list[0].name == "station_0" @@ -301,12 +257,12 @@ def test_from_xarray_dataset(self, sample_node_data): coords={"time": sample_node_data.index}, ) obs_list = NodeObservation.from_multiple( - data=ds, nodes={123: "station_0", 456: "station_1"} + data=ds, nodes={"123": "station_0", "456": "station_1"} ) assert len(obs_list) == 2 - assert obs_list[0].node == 123 - assert obs_list[1].node == 456 + assert obs_list[0].node == "123" + assert obs_list[1].node == "456" def test_nodes_must_be_dict(self, multi_data): with pytest.raises(TypeError, match="'nodes' must be a dict"): @@ -316,7 +272,7 @@ def test_attrs_propagated_to_all_observations(self, multi_data): attrs = {"source": "sensor_array", "version": 2} obs_list = NodeObservation.from_multiple( data=multi_data, - nodes={1: "station_0", 2: "station_1", 3: "station_2"}, + nodes={"1": "station_0", "2": "station_1", "3": "station_2"}, attrs=attrs, ) @@ -326,38 +282,38 @@ def test_attrs_propagated_to_all_observations(self, multi_data): def test_init_from_csv(self): obs = NodeObservation( - "tests/testdata/network_sensor_1.csv", at=1, item="water_level@sens1" + "tests/testdata/network_sensor_1.csv", at="1", item="water_level@sens1" ) - assert obs.at == 1 + assert obs.at == "1" assert len(obs.time) == 110 assert isinstance(obs.time, pd.DatetimeIndex) def test_from_multiple_csvs_via_dict(self): obs_list = NodeObservation.from_multiple( nodes={ - 1: "tests/testdata/network_sensor_1.csv", - 2: "tests/testdata/network_sensor_2.csv", - 3: "tests/testdata/network_sensor_3.csv", + "1": "tests/testdata/network_sensor_1.csv", + "2": "tests/testdata/network_sensor_2.csv", + "3": "tests/testdata/network_sensor_3.csv", } ) assert len(obs_list) == 3 assert all(isinstance(obs, NodeObservation) for obs in obs_list) - assert obs_list[0].node == 1 - assert obs_list[1].node == 2 - assert obs_list[2].node == 3 + assert obs_list[0].node == "1" + assert obs_list[1].node == "2" + assert obs_list[2].node == "3" for obs in obs_list: assert len(obs.time) > 0 def test_nodes_dict_maps_node_to_item(self, multi_data): obs_list = NodeObservation.from_multiple( - data=multi_data, nodes={123: "station_0", 456: "station_1"} + data=multi_data, nodes={"123": "station_0", "456": "station_1"} ) assert len(obs_list) == 2 - assert obs_list[0].node == 123 - assert obs_list[1].node == 456 + assert obs_list[0].node == "123" + assert obs_list[1].node == "456" assert obs_list[0].name == "station_0" assert obs_list[1].name == "station_1" @@ -367,26 +323,77 @@ def test_nodes_none_raises(self, multi_data): def test_single_node_dict(self, sample_node_data): obs_list = NodeObservation.from_multiple( - data=sample_node_data, nodes={123: "WaterLevel"} + data=sample_node_data, nodes={"123": "WaterLevel"} ) assert len(obs_list) == 1 assert isinstance(obs_list[0], NodeObservation) - assert obs_list[0].node == 123 + assert obs_list[0].node == "123" + def test_nodes_keys_accept_aliases(self, multi_data): + obs_list = NodeObservation.from_multiple( + data=multi_data, nodes={"node_A": "station_0", "node_B": "station_1"} + ) -class TestNodeModelResult: - """Test NodeModelResult class""" + assert [obs.at for obs in obs_list] == ["node_A", "node_B"] - @pytest.mark.parametrize("fixture_name", ["sample_node_data", "sample_series"]) - def test_init_(self, request, fixture_name): - """Test initialization with pandas DataFrame""" - data = request.getfixturevalue(fixture_name) - nmr = NodeModelResult(data, node=123, name="Node_123_Model") + def test_nodes_keys_accept_breakpoints(self, multi_data): + obs_list = NodeObservation.from_multiple( + data=multi_data, + nodes={("reach_1", 24.5): "station_0", ("reach_1", 50.0): "station_1"}, + ) - assert nmr.node == 123 - assert nmr.name == "Node_123_Model" - assert len(nmr.time) == 10 + assert [obs.at for obs in obs_list] == [("reach_1", 24.5), ("reach_1", 50.0)] + + +class TestReachObservationFromMultiple: + @pytest.fixture + def multi_data(self, sample_node_data): + return pd.DataFrame( + { + "station_0": sample_node_data["WaterLevel"].values, + "station_1": sample_node_data["WaterLevel"].values + 0.1, + }, + index=sample_node_data.index, + ) + + def test_returns_list_of_reach_observations(self, multi_data): + obs_list = ReachObservation.from_multiple( + data=multi_data, reaches={"reach_1": "station_0", "reach_2": "station_1"} + ) + + assert len(obs_list) == 2 + assert all(isinstance(obs, ReachObservation) for obs in obs_list) + assert [obs.reach for obs in obs_list] == ["reach_1", "reach_2"] + assert [obs.name for obs in obs_list] == ["station_0", "station_1"] + + def test_separate_data_sources(self): + obs_list = ReachObservation.from_multiple( + reaches={ + "reach_1": "tests/testdata/network_sensor_1.csv", + "reach_2": "tests/testdata/network_sensor_2.csv", + } + ) + + assert [obs.reach for obs in obs_list] == ["reach_1", "reach_2"] + assert all(len(obs.time) > 0 for obs in obs_list) + + def test_attrs_propagated(self, multi_data): + obs_list = ReachObservation.from_multiple( + data=multi_data, + reaches={"reach_1": "station_0"}, + attrs={"source": "sensor_array"}, + ) + + assert obs_list[0].attrs["source"] == "sensor_array" + + def test_reaches_none_raises(self, multi_data): + with pytest.raises(ValueError, match="'reaches' argument is required"): + ReachObservation.from_multiple(data=multi_data, reaches=None) + + def test_reaches_must_be_dict(self, multi_data): + with pytest.raises(TypeError, match="'reaches' must be a dict"): + ReachObservation.from_multiple(data=multi_data, reaches="reach_1") class TestNetworkIntegration: @@ -395,12 +402,11 @@ class TestNetworkIntegration: def test_network_to_node_extraction(self, sample_network, sample_node_data): """Test complete workflow from network model to node extraction""" nmr = NetworkModelResult(sample_network, name="Network_Model") - node_id = sample_network.find(node="123") + node_id = "123" obs = NodeObservation(sample_node_data, at=node_id, name="Node_123_Obs") extracted = nmr.extract(obs) - assert isinstance(extracted, NodeModelResult) assert extracted.node == node_id assert extracted.name == "Network_Model" assert len(extracted.time) == len(obs.time) @@ -408,7 +414,7 @@ def test_network_to_node_extraction(self, sample_network, sample_node_data): def test_matching_workflow(self, sample_network, sample_node_data): """Test matching workflow with network data""" nmr = NetworkModelResult(sample_network, name="Network_Model") - node_id = sample_network.find(node="123") + node_id = "123" obs = NodeObservation(sample_node_data, at=node_id, name="Node_123_Obs") comparer = ms.match(obs, nmr) @@ -429,9 +435,9 @@ def test_matching_workflow_multiple_nodes(self, sample_network, sample_node_data } ) - node_0 = sample_network.find(node="123") - node_1 = sample_network.find(node="456") - node_2 = sample_network.find(node="789") + node_0 = "123" + node_1 = "456" + node_2 = "789" # Create multiple NodeObservations using .from_multiple obs_list = NodeObservation.from_multiple( @@ -450,439 +456,283 @@ 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" -) -def test_open_res1d(): - path_to_file = "./tests/testdata/network.res1d" - network = Network.from_mike(path_to_file) - assert network.graph.number_of_nodes() == 259 +class TestValuesReachTheComparer: + """The series a comparer holds is the one the network keeps at that location. + The observation is built from the model's own data, so an exact match is the + expected outcome and any mix-up between locations, quantities or models shows + up as a non-zero score. + """ -@pytest.mark.skipif( - sys.version_info >= (3, 14), reason="mikeio1d requires Python < 3.14" -) -def test_extract_reach_observation_happy_path(sample_node_data): - path_to_file = "./tests/testdata/network.res1d" - network = Network.from_mike(path_to_file) - nmr = NetworkModelResult(network, item="Discharge", name="network_model") - obs_data = sample_node_data.rename(columns={"WaterLevel": "Discharge"}) - obs = ms.ReachObservation(obs_data, reach="100l1", item="Discharge") + def test_extract_returns_the_nodes_own_series(self, sample_network): + values = node_series(sample_network) + nmr = NetworkModelResult(sample_network, name="Network_Model") + obs = NodeObservation(values, at="456", item="456") - extracted = nmr.extract(obs) + extracted = nmr.extract(obs) - assert isinstance(extracted, NodeModelResult) - assert extracted.name == "network_model" - assert extracted.node in nmr.nodes + assert extracted.to_dataframe()["Network_Model"].to_numpy() == pytest.approx( + values["456"].to_numpy() + ) + def test_a_matched_node_scores_zero_against_its_own_series(self, sample_network): + values = node_series(sample_network) + nmr = NetworkModelResult(sample_network, name="Network_Model") + obs = NodeObservation(values, at="456", item="456", name="Node_456_Obs") -@pytest.mark.skipif( - sys.version_info >= (3, 14), reason="mikeio1d requires Python < 3.14" -) -def test_extract_reach_observation_non_equivalent_breakpoints_raises(sample_node_data): - path_to_file = "./tests/testdata/network.res1d" - network = Network.from_mike(path_to_file) - nmr = NetworkModelResult(network, item="Discharge") - obs_data = sample_node_data.rename(columns={"WaterLevel": "Discharge"}) - obs = ms.ReachObservation(obs_data, reach="113l1", item="Discharge") + cmp = ms.match(obs, nmr) - with pytest.raises(ValueError, match="Not all data in breakpoints are equivalent"): - nmr.extract(obs) + assert cmp.n_points == len(values) + assert cmp.score()["Network_Model"] == pytest.approx(0.0) + def test_every_node_of_a_collection_keeps_its_own_series(self, sample_network): + """One observation per node, each read from that node's own column.""" + values = node_series(sample_network) + nmr = NetworkModelResult(sample_network, name="Network_Model") + obs_list = NodeObservation.from_multiple( + data=values, nodes={nid: nid for nid in values.columns} + ) -@pytest.mark.skipif( - sys.version_info >= (3, 14), reason="mikeio1d requires Python < 3.14" -) -def test_extract_reach_observation_with_reaches_not_populated_raises_valueerror( - sample_node_data, -): - path_to_file = "./tests/testdata/network.res1d" - network = Network.from_mike(path_to_file, reaches=[]) - nmr = NetworkModelResult(network, item="WaterLevel") - obs = ms.ReachObservation(sample_node_data, reach="100l1", item="WaterLevel") + cc = ms.match(obs_list, nmr) - with pytest.raises(ValueError, match="none of its breakpoints have data loaded"): - nmr.extract(obs) + assert len(cc) == 3 + for cmp in cc: + assert cmp.score()["Network_Model"] == pytest.approx(0.0) + def test_two_networks_keep_their_series_apart(self, sample_network): + """The second network is the first shifted by 0.1. -@pytest.mark.skipif( - sys.version_info >= (3, 14), reason="mikeio1d requires Python < 3.14" -) -def test_extract_reach_observation_breakpoint_node_missing_raises_valueerror( - sample_node_data, -): - path_to_file = "./tests/testdata/network.res1d" - network = Network.from_mike(path_to_file) - nmr = NetworkModelResult(network, item="Discharge") - obs_data = sample_node_data.rename(columns={"WaterLevel": "Discharge"}) - baseline_obs = ms.ReachObservation(obs_data, reach="100l1", item="Discharge") - node_id = nmr.extract(baseline_obs).node - remaining_nodes = [] - for node in nmr.data.node.values: - node_int = int(node) - if node_int != node_id: - remaining_nodes.append(node_int) - nmr.data = nmr.data.sel(node=remaining_nodes) + A crossed model column would put that shift on the wrong name. + """ + values = node_series(sample_network) + shifted = make_network( + list(values.columns), values.index, values.to_numpy() + 0.1 + ) + obs = NodeObservation(values, at="456", item="456", name="Node_456_Obs") + + cmp = ms.match( + obs, + [ + NetworkModelResult(sample_network, name="Network_1"), + NetworkModelResult(shifted, name="Network_2"), + ], + ) - obs = ms.ReachObservation(obs_data, reach="100l1", item="Discharge") + bias = cmp.score(metric="bias") + assert bias["Network_1"] == pytest.approx(0.0) + assert bias["Network_2"] == pytest.approx(0.1) - with pytest.raises(ValueError, match="matching breakpoint nodes are missing"): - nmr.extract(obs) + def test_a_matched_breakpoint_scores_zero_against_its_own_series( + self, breakpoint_network + ): + """The break point's data sits on neither of the reach's nodes.""" + values = breakpoint_network.reaches["r1"].breakpoints[0].data + nmr = NetworkModelResult(breakpoint_network, name="Network_Model") + obs = NodeObservation(values, at=("r1", 50.0), name="BP_Obs") + cmp = ms.match(obs, nmr) -@pytest.mark.skipif( - sys.version_info >= (3, 14), reason="mikeio1d requires Python < 3.14" -) -def test_from_mike_nodes_filter_creates_full_network(): - """When nodes is specified, the full network topology is created.""" - path_to_file = "./tests/testdata/network.res1d" - full_network = Network.from_mike(path_to_file) + assert cmp.n_points == len(values) + assert cmp.score()["Network_Model"] == pytest.approx(0.0) - selected_nodes = ["1", "108"] - partial_network = Network.from_mike(path_to_file, nodes=selected_nodes) + def test_a_matched_reach_scores_zero_against_its_breakpoints_series( + self, breakpoint_network + ): + values = breakpoint_network.reaches["r1"].breakpoints[0].data + nmr = NetworkModelResult(breakpoint_network, name="Network_Model") + obs = ReachObservation(values, reach="r1", name="Reach_Obs") - # Full topology is preserved - assert ( - partial_network.graph.number_of_nodes() == full_network.graph.number_of_nodes() - ) + cmp = ms.match(obs, nmr) + assert cmp.n_points == len(values) + assert cmp.score()["Network_Model"] == pytest.approx(0.0) -@pytest.mark.skipif( - sys.version_info >= (3, 14), reason="mikeio1d requires Python < 3.14" -) -def test_from_mike_nodes_filter_only_selected_have_data(): - """When nodes is specified, only selected nodes contain non-empty data.""" - path_to_file = "./tests/testdata/network.res1d" + def test_the_selected_item_brings_its_own_values(self, sample_network_multivars): + """Discharge is ten times WaterLevel here, so the selected column cannot + pass for the one left behind.""" + discharge = node_series(sample_network_multivars, "Discharge") + nmr = NetworkModelResult( + sample_network_multivars, item="Discharge", name="Network_Model" + ) + obs = NodeObservation(discharge, at="123", item="123") + + extracted = nmr.extract(obs) + + assert extracted.to_dataframe()["Network_Model"].to_numpy() == pytest.approx( + discharge["123"].to_numpy() + ) - selected_nodes = ["1", "108"] - network = Network.from_mike(path_to_file, nodes=selected_nodes, reaches=[]) - g = network.graph.copy() + def test_a_matched_item_scores_zero_against_its_own_series( + self, sample_network_multivars + ): + discharge = node_series(sample_network_multivars, "Discharge") + nmr = NetworkModelResult( + sample_network_multivars, item="Discharge", name="Network_Model" + ) + obs = NodeObservation(discharge, at="123", item="123", name="Node_123_Obs") - n_nodes = network.graph.number_of_nodes() - assert sum([g.nodes[n]["data"].empty for n in g.nodes]) == n_nodes - 2 - for n in selected_nodes: - assert not g.nodes[network.find(n)]["data"].empty + cmp = ms.match(obs, nmr) + + assert cmp.score()["Network_Model"] == pytest.approx(0.0) + + def test_an_aux_item_brings_its_own_values(self, sample_network_multivars): + """The aux item rides alongside the scored one, and holds its own series.""" + water_level = node_series(sample_network_multivars) + discharge = node_series(sample_network_multivars, "Discharge") + nmr = NetworkModelResult( + sample_network_multivars, + item="WaterLevel", + aux_items=["Discharge"], + name="Network_Model", + ) + obs = NodeObservation(water_level, at="123", item="123", name="Node_123_Obs") + + cmp = ms.match(obs, nmr) + + assert cmp.data["Discharge"].attrs["kind"] == "aux" + assert cmp.data["Discharge"].to_numpy() == pytest.approx( + discharge["123"].to_numpy() + ) + assert cmp.score()["Network_Model"] == pytest.approx(0.0) @pytest.mark.skipif( sys.version_info >= (3, 14), reason="mikeio1d requires Python < 3.14" ) -def test_from_mike_nodes_single_string(): - """nodes argument accepts a single string (not just a list).""" - path_to_file = "./tests/testdata/network.res1d" - full_network = Network.from_mike(path_to_file) +def test_a_model_result_built_from_a_file_carries_the_files_own_values(): + """Checked against mikeio1d's own read of the file. - network = Network.from_mike(path_to_file, nodes="108", reaches=[]) - g = network.graph.copy() + That read goes straight to the result file, not through the Network the + model result is built on, so the two agreeing pins the whole way in. + """ + path = "./tests/testdata/network.res1d" + expected = mikeio1d.open(path).nodes.read()["WaterLevel:1"] + mr = NetworkModelResult(path, item="WaterLevel", name="Network_Model") + obs = NodeObservation(expected, at="1", name="Node_1_Obs") - assert g.number_of_nodes() == full_network.graph.number_of_nodes() + extracted = mr.extract(obs) - nodes_with_data = [n for n in g.nodes if not g.nodes[n]["data"].empty] - nodes_with_data = [network.recall(n)["node"] for n in nodes_with_data] - assert nodes_with_data == ["108"] + assert extracted.to_dataframe()["Network_Model"].to_numpy() == pytest.approx( + expected.to_numpy() + ) @pytest.mark.skipif( sys.version_info >= (3, 14), reason="mikeio1d requires Python < 3.14" ) -def test_dataframe_from_partial_network(): - """nodes argument accepts a single string (not just a list).""" - path_to_file = "./tests/testdata/network.res1d" - selected_nodes = ["108", "101"] - network = Network.from_mike(path_to_file, nodes=selected_nodes, reaches=[]) - nodes_in_df = network.to_dataframe().droplevel(axis=1, level=1).columns +def test_a_model_result_can_be_built_from_a_result_file(): + mr = NetworkModelResult("./tests/testdata/network.res1d", item="WaterLevel") - assert set(nodes_in_df) == set([network.find(n) for n in selected_nodes]) + assert mr.quantity.name == "WaterLevel" + assert len(mr.time) > 0 @pytest.mark.skipif( sys.version_info >= (3, 14), reason="mikeio1d requires Python < 3.14" ) -def test_nodes_filtered_network_keeps_datetime_index(): - """Topology-only nodes must not degrade the time index to object dtype. - - A nodes-filtered network keeps the full topology, storing empty data for - the unselected nodes. Concatenating those empty (RangeIndex) frames must - not corrupt the DatetimeIndex, otherwise ms.match() later fails with - "time must be datetime". - """ +def test_extract_reach_observation_happy_path(sample_node_data): path_to_file = "./tests/testdata/network.res1d" - network = Network.from_mike(path_to_file, nodes=["108", "101"], reaches=[]) + network = Network.open(path_to_file) + nmr = NetworkModelResult(network, item="Discharge", name="network_model") + obs_data = sample_node_data.rename(columns={"WaterLevel": "Discharge"}) + obs = ms.ReachObservation(obs_data, reach="100l1", item="Discharge") - assert isinstance(network._df.index, pd.DatetimeIndex) - assert network._df.index.dtype == "datetime64[ns]" - assert network.to_dataset()["time"].dtype == np.dtype("datetime64[ns]") + extracted = nmr.extract(obs) + + assert extracted.name == "network_model" + reach, _ = extracted.node + assert reach == "100l1" @pytest.mark.skipif( sys.version_info >= (3, 14), reason="mikeio1d requires Python < 3.14" ) -def test_from_res1d_empty_nodes_and_reaches_keeps_topology_and_empty_outputs(): +def test_extract_reach_observation_non_equivalent_breakpoints_raises(sample_node_data): path_to_file = "./tests/testdata/network.res1d" - network = Network.from_mike(path_to_file, nodes=["108", "101"], reaches=[]) + network = Network.open(path_to_file) + nmr = NetworkModelResult(network, item="Discharge") + obs_data = sample_node_data.rename(columns={"WaterLevel": "Discharge"}) + obs = ms.ReachObservation(obs_data, reach="113l1", item="Discharge") - assert isinstance(network._df.index, pd.DatetimeIndex) - assert network._df.index.dtype == "datetime64[ns]" - assert network.to_dataset()["time"].dtype == np.dtype("datetime64[ns]") + with pytest.raises(ValueError, match="Not all data in breakpoints are equivalent"): + nmr.extract(obs) @pytest.mark.skipif( sys.version_info >= (3, 14), reason="mikeio1d requires Python < 3.14" ) -def test_from_mike_empty_nodes_and_reaches_keeps_topology_and_empty_outputs(): +def test_extract_reach_observation_with_reaches_not_populated_raises_valueerror( + sample_node_data, +): path_to_file = "./tests/testdata/network.res1d" - full_network = Network.from_mike(path_to_file) - network = Network.from_mike(path_to_file, nodes=[], reaches=[]) - - assert network.graph.number_of_nodes() == full_network.graph.number_of_nodes() - - df = network.to_dataframe() - assert df.empty - assert isinstance(df.columns, pd.MultiIndex) - assert df.columns.names == ["node", "quantity"] - assert df.index.name == "time" - - ds = network.to_dataset() - assert isinstance(ds, xr.Dataset) - assert len(ds.data_vars) == 0 - - -# --------------------------------------------------------------------------- -# Optional reach length -# --------------------------------------------------------------------------- - - -class _StubBreakPoint(ReachBreakPoint): - """Minimal concrete ReachBreakPoint for building reaches by hand.""" - - def __init__(self, reach_id, distance, data=None): - self._id = (reach_id, distance) - self._data = pd.DataFrame() if data is None else data - - @property - def id(self): - return self._id - - @property - def data(self): - return self._data - - -def _two_node_pair(): - time = pd.date_range("2020", periods=3, freq="h") - df = pd.DataFrame({"WaterLevel": [1.0, 1.1, 1.2]}, index=time) - return BasicNode("a", df), BasicNode("b", df.copy()) - - -class TestOptionalReachLength: - """Reach length is undefined in some domains, so it must be omittable.""" - - def test_subclass_may_omit_length(self): - class LengthlessReach(NetworkReach): - def __init__(self, id, start, end): - self._id, self._start, self._end = id, start, end - - @property - def id(self): - return self._id - - @property - def start(self): - return self._start - - @property - def end(self): - return self._end - - @property - def breakpoints(self): - return [] - - a, b = _two_node_pair() - reach = LengthlessReach("r1", a, b) - - assert reach.length is None - assert Network([reach]).graph.number_of_nodes() == 2 - - def test_basic_reach_length_defaults_to_none(self): - a, b = _two_node_pair() - - assert BasicReach("r1", a, b).length is None - - def test_edge_length_is_none_when_undefined(self): - a, b = _two_node_pair() - - network = Network([BasicReach("r1", a, b)]) - - assert [d["length"] for *_, d in network.graph.edges(data=True)] == [None] - - def test_breakpoint_distances_survive_an_undefined_length(self): - """Only the final segment needs the total, so the rest keep real lengths.""" - a, b = _two_node_pair() - breakpoints = [_StubBreakPoint("r1", d) for d in (30.0, 70.0)] - - network = Network([BasicReach("r1", a, b, breakpoints=breakpoints)]) - - lengths = sorted( - (d["length"] for *_, d in network.graph.edges(data=True)), - key=lambda v: (v is None, v), - ) - assert lengths == [30.0, 40.0, None] - - def test_length_weighted_algorithms_fail_loudly(self): - """Storing None keeps networkx honest. - - Omitting the attribute instead would let networkx default the weight to - 1, so every call below would return a plausible but meaningless number. - With None, shortest-path treats the edge as hidden and the arithmetic - consumers raise. - """ - import networkx as nx - - a, b = _two_node_pair() - g = Network([BasicReach("r1", a, b)]).graph - - with pytest.raises(nx.NetworkXNoPath): - nx.shortest_path_length(g, 0, 1, weight="length") - - with pytest.raises(TypeError): - g.size(weight="length") - - def test_known_length_is_unchanged(self): - a, b = _two_node_pair() - breakpoints = [_StubBreakPoint("r1", 40.0)] - - network = Network([BasicReach("r1", a, b, 100.0, breakpoints)]) - - assert sorted(d["length"] for *_, d in network.graph.edges(data=True)) == [ - 40.0, - 60.0, - ] - + network = Network.open(path_to_file, reaches=[]) + nmr = NetworkModelResult(network, item="WaterLevel") + obs = ms.ReachObservation(sample_node_data, reach="100l1", item="WaterLevel") -# --------------------------------------------------------------------------- -# Which extensions each constructor accepts, and why the rest are refused -# --------------------------------------------------------------------------- + with pytest.raises(ValueError, match="none of its breakpoints have data loaded"): + nmr.extract(obs) @pytest.mark.skipif( sys.version_info >= (3, 14), reason="mikeio1d requires Python < 3.14" ) -class TestExtensionPolicy: - @pytest.mark.parametrize("suffix", [".res1d", ".res11", ".RES1D"]) - def test_from_mike_accepts_mike_extensions(self, tmp_path, suffix): - """The file does not exist, so mikeio1d - not the guard - is what complains.""" - with pytest.raises((FileExistsError, FileNotFoundError)): - Network.from_mike(tmp_path / f"network{suffix}") - - def test_error_lists_only_readable_extensions(self): - with pytest.raises(NotImplementedError) as excinfo: - Network.from_mike("network.nc") - - message = str(excinfo.value) - for extension in _MIKE_EXTENSIONS | _EPANET_EXTENSIONS: - assert extension in message - for extension in _UNSUPPORTED_EXTENSIONS: - assert extension not in message - - def test_swmm_refusal_names_the_companion_inp(self): - """A real file, so this fails the day SWMM support lands.""" - with pytest.raises(NotImplementedError, match=r"companion '\.inp'"): - Network.from_mike("./tests/testdata/swmm.out") - - def test_resx_refusal_points_at_the_resx_argument(self): - """'.resx' is a companion, so the message must name what to do instead.""" - with pytest.raises( - NotImplementedError, match=r"from_epanet\(res, resx=\.\.\.\)" - ): - Network.from_mike("./tests/testdata/epanet.resx") - - @pytest.mark.parametrize("suffix", [".prf", ".crf", ".xrf", ".whr"]) - def test_formats_without_a_fixture_are_refused(self, tmp_path, suffix): - with pytest.raises(NotImplementedError, match="no test fixture"): - Network.from_mike(tmp_path / f"network{suffix}") - - def test_every_mikeio1d_extension_is_accounted_for(self): - """A new mikeio1d format must be read or explicitly refused, never ignored.""" - from mikeio1d import Res1D - - accounted_for = ( - _MIKE_EXTENSIONS | _EPANET_EXTENSIONS | set(_UNSUPPORTED_EXTENSIONS) - ) - - assert accounted_for == Res1D.get_supported_file_extensions() - - def test_res1d_opened_with_a_path_is_refused(self): - """mikeio1d calls str.endswith on file_path, so a Path breaks it later on.""" - from mikeio1d import Res1D - - res = Res1D(Path("./tests/testdata/network.res1d")) - - with pytest.raises(TypeError, match="file_path"): - Network.from_mike(res) - +def test_extract_breakpoint_without_data_for_the_quantity_raises_valueerror( + sample_node_data, +): + """MIKE 1D stores WaterLevel and Discharge at alternating grid points, so a + breakpoint that exists can still hold nothing for the selected quantity.""" + network = Network.open("./tests/testdata/network.res1d") + nmr = NetworkModelResult(network, item="WaterLevel") + obs = ms.NodeObservation(sample_node_data, at=("94l1", 21.285), item="WaterLevel") -# --------------------------------------------------------------------------- -# NodeObservation — alias / breakpoint node forms -# --------------------------------------------------------------------------- + with pytest.raises(ValueError, match="no data for quantity 'WaterLevel'"): + nmr.extract(obs) class TestNodeObservationAliases: - """NodeObservation accepts int, str alias, and (reach, distance) tuple.""" + """NodeObservation accepts a node name or a (reach, distance) tuple.""" - def test_integer_node_unchanged(self, sample_node_data): - obs = NodeObservation(sample_node_data, at=42) - assert obs.at == 42 - assert isinstance(obs.at, int) - - def test_integer_node_coord(self, sample_node_data): - obs = NodeObservation(sample_node_data, at=42) - assert "node" in obs.data.coords - assert int(obs.data.coords["node"].item()) == 42 + @pytest.mark.parametrize("at", [42, np.int64(42)]) + def test_an_integer_is_refused(self, sample_node_data, at): + with pytest.raises(TypeError, match="not an integer"): + NodeObservation(sample_node_data, at=at) def test_string_alias_stored(self, sample_node_data): obs = NodeObservation(sample_node_data, at="node_A", name="test") assert obs.at == "node_A" assert isinstance(obs.at, str) + assert obs.gtype == "node" - def test_string_alias_has_node_coord(self, sample_node_data): - obs = NodeObservation(sample_node_data, at="node_A") - assert "node" in obs.data.coords - assert obs.data.coords["node"].item() == "node_A" - - def test_string_alias_gtype_is_node(self, sample_node_data): + def test_a_named_node_knows_its_node(self, sample_node_data): obs = NodeObservation(sample_node_data, at="node_A") - assert obs.data.attrs["gtype"] == "node" + assert obs.node == "node_A" def test_tuple_node_stored(self, sample_node_data): obs = NodeObservation(sample_node_data, at=("reach_1", 24.5)) assert obs.at == ("reach_1", 24.5) assert isinstance(obs.at, tuple) + assert obs.gtype == "node" - def test_tuple_node_gtype_is_node(self, sample_node_data): + def test_a_breakpoint_has_no_node_name(self, sample_node_data): + """`node` names a junction; a breakpoint is placed along a reach instead.""" obs = NodeObservation(sample_node_data, at=("reach_1", 24.5)) - assert obs.data.attrs["gtype"] == "node" + assert obs.node is None - def test_tuple_node_has_reach_distance_coords(self, sample_node_data): + def test_a_breakpoint_survives_trimming(self, sample_node_data): obs = NodeObservation(sample_node_data, at=("reach_1", 24.5)) - assert "reach" in obs.data.coords - assert "distance" in obs.data.coords - assert str(obs.data.coords["reach"].item()) == "reach_1" - assert float(obs.data.coords["distance"].item()) == pytest.approx(24.5) - def test_tuple_node_has_no_node_coord(self, sample_node_data): - obs = NodeObservation(sample_node_data, at=("reach_1", 24.5)) - assert "node" not in obs.data.coords + trimmed = obs.trim(start_time=obs.time[1], end_time=obs.time[-1]) - def test_tuple_node_roundtrip_via_create_new_instance(self, sample_node_data): - obs = NodeObservation(sample_node_data, at=("reach_1", 24.5)) - obs2 = obs._create_new_instance(obs.data) - assert obs2.at == ("reach_1", 24.5) + assert trimmed.at == ("reach_1", 24.5) + assert len(trimmed) == len(obs) - 1 - def test_string_roundtrip_via_create_new_instance(self, sample_node_data): + def test_a_named_node_observation_survives_trimming(self, sample_node_data): obs = NodeObservation(sample_node_data, at="node_A") - obs2 = obs._create_new_instance(obs.data) - assert obs2.at == "node_A" + + trimmed = obs.trim(start_time=obs.time[1], end_time=obs.time[-1]) + + assert trimmed.at == "node_A" + assert len(trimmed) == len(obs) - 1 # --------------------------------------------------------------------------- @@ -891,107 +741,73 @@ def test_string_roundtrip_via_create_new_instance(self, sample_node_data): class TestNetworkModelResultAliasResolution: - """NetworkModelResult.extract() resolves str and tuple aliases via alias_map.""" + """extract() resolves a node name or a (reach, distance) pair to a location.""" - def test_network_stored(self, sample_network): + def test_the_network_is_kept_as_given(self, sample_network): nmr = NetworkModelResult(sample_network) - assert hasattr(nmr, "network") - assert "123" in nmr.network._alias_map - assert "456" in nmr.network._alias_map - assert "789" in nmr.network._alias_map + + assert nmr.network is sample_network def test_extract_with_string_alias(self, sample_network, sample_node_data): nmr = NetworkModelResult(sample_network) obs = NodeObservation(sample_node_data, at="123", name="Node_123") + extracted = nmr.extract(obs) - expected_id = sample_network.find(node="123") - assert isinstance(extracted, NodeModelResult) - assert extracted.node == expected_id + + assert extracted.node == "123" def test_extract_string_alias_wrong_key_raises( self, sample_network, sample_node_data ): nmr = NetworkModelResult(sample_network) obs = NodeObservation(sample_node_data, at="nonexistent_node") + with pytest.raises(ValueError, match="not found"): nmr.extract(obs) - def test_extract_with_tuple_breakpoint(self, sample_network, sample_node_data): - """Tuple alias is resolved via _alias_map (mapping injected for this test).""" - nmr = NetworkModelResult(sample_network) - existing_int = int(sample_network.find(node="123")) - nmr.network._alias_map[("reach_test", 10.0)] = existing_int - obs = NodeObservation(sample_node_data, at=("reach_test", 10.0)) - extracted = nmr.extract(obs) - assert extracted.node == existing_int - - def test_extract_with_tuple_breakpoint_tolerance( + def test_a_failed_lookup_names_the_near_misses( self, sample_network, sample_node_data ): nmr = NetworkModelResult(sample_network) - existing_int = int(sample_network.find(node="123")) - base_distance = 10.0 - tol = NetworkModelResult._CHAINAGE_TOLERANCE - nmr.network._alias_map[("reach_test", base_distance)] = existing_int - obs = NodeObservation( - sample_node_data, at=("reach_test", base_distance + tol / 2) - ) - extracted = nmr.extract(obs) - assert extracted.node == existing_int + obs = NodeObservation(sample_node_data, at="124") - def test_extract_with_tuple_breakpoint_outside_tolerance_raises( - self, sample_network, sample_node_data - ): - nmr = NetworkModelResult(sample_network) - existing_int = int(sample_network.find(node="123")) - base_distance = 10.0 - tol = NetworkModelResult._CHAINAGE_TOLERANCE - nmr.network._alias_map[("reach_test", base_distance)] = existing_int - obs = NodeObservation( - sample_node_data, at=("reach_test", base_distance + tol + 1e-4) - ) - with pytest.raises(ValueError, match="not found"): + with pytest.raises(ValueError, match="123"): nmr.extract(obs) - def test_extract_with_tuple_breakpoint_uses_closest_within_tolerance( - self, sample_network, sample_node_data - ): - nmr = NetworkModelResult(sample_network) - base_distance = 10.0 - tol = NetworkModelResult._CHAINAGE_TOLERANCE - node_a = int(sample_network.find(node="123")) - node_b = int(sample_network.find(node="456")) - nmr.network._alias_map[("reach_test", base_distance + 2e-4)] = node_a - nmr.network._alias_map[("reach_test", base_distance + 8e-4)] = node_b + def test_extract_with_tuple_breakpoint(self, breakpoint_network, sample_node_data): + nmr = NetworkModelResult(breakpoint_network) + obs = NodeObservation(sample_node_data, at=("r1", 50.0)) - obs = NodeObservation( - sample_node_data, at=("reach_test", base_distance + tol * 0.6) - ) extracted = nmr.extract(obs) - assert extracted.node == node_b - def test_extract_with_tuple_breakpoint_tie_uses_smallest_node_id( - self, sample_network, sample_node_data + assert extracted.node == ("r1", 50.0) + + def test_extract_with_tuple_breakpoint_tolerance( + self, breakpoint_network, sample_node_data ): - nmr = NetworkModelResult(sample_network) - base_distance = 10.0 - tol = NetworkModelResult._CHAINAGE_TOLERANCE - node_a = int(sample_network.find(node="123")) - node_b = int(sample_network.find(node="456")) - nmr.network._alias_map[("reach_test", base_distance + 4e-4)] = node_a - nmr.network._alias_map[("reach_test", base_distance + 8e-4)] = node_b + nmr = NetworkModelResult(breakpoint_network) + obs = NodeObservation(sample_node_data, at=("r1", 50.0 + 5e-4)) - obs = NodeObservation( - sample_node_data, at=("reach_test", base_distance + tol * 0.6) - ) extracted = nmr.extract(obs) - assert extracted.node == min(node_a, node_b) + + # The distance recorded is the network's own, not the one typed. + assert extracted.node == ("r1", 50.0) + + def test_extract_with_tuple_breakpoint_outside_tolerance_raises( + self, breakpoint_network, sample_node_data + ): + nmr = NetworkModelResult(breakpoint_network) + obs = NodeObservation(sample_node_data, at=("r1", 50.0 + 2e-3)) + + with pytest.raises(ValueError, match="not found"): + nmr.extract(obs) def test_extract_tuple_alias_wrong_key_raises( self, sample_network, sample_node_data ): nmr = NetworkModelResult(sample_network) obs = NodeObservation(sample_node_data, at=("nonexistent_reach", 0.0)) + with pytest.raises(ValueError, match="not found"): nmr.extract(obs) @@ -999,416 +815,114 @@ def test_match_with_string_alias(self, sample_network, sample_node_data): """Full ms.match() workflow works end-to-end with a string alias.""" nmr = NetworkModelResult(sample_network, name="Network_Model") obs = NodeObservation(sample_node_data, at="123", name="Node_123") + comparer = ms.match(obs, nmr) + assert comparer.n_points > 0 assert "Network_Model" in comparer.mod_names -# --------------------------------------------------------------------------- -# Res1D adapter — no mikeio1d required, the adapter is duck-typed -# --------------------------------------------------------------------------- - - -class _StubLocation: - """Stands in for a mikeio1d ResultNode / ResultGridPoint.""" - - def __init__(self, quantities, df=None): - self.quantities = quantities - self._df = df - - def to_dataframe(self): - if self._df is None: - raise AssertionError("to_dataframe() should not be called") - return self._df - +# ======================== location identity ======================== -class TestSimplifyColnames: - def test_location_without_quantities_gives_empty_frame(self): - """MIKE 11 keeps its data on gridpoints, leaving nodes with no quantities.""" - df = _simplify_colnames(_StubLocation(quantities=[])) - assert df.empty - assert list(df.columns) == [] +class TestLocationIdentity: + """A network timeseries is identified by the name its network gave it.""" - def test_quantity_columns_are_stripped_of_location_suffix(self): - time = pd.date_range("2020", periods=2, freq="h") - raw = pd.DataFrame({"WaterLevel:node_1": [1.0, 2.0]}, index=time) + def test_breakpoint_observation_converts_to_a_dataframe(self, sample_node_data): + obs = ms.NodeObservation(sample_node_data, at=("r1", 24.5), item="WaterLevel") - df = _simplify_colnames(_StubLocation(quantities=["WaterLevel"], df=raw)) + df = obs.to_dataframe() assert list(df.columns) == ["WaterLevel"] + assert len(df) == len(sample_node_data) + def test_reach_observation_converts_to_a_dataframe(self, sample_node_data): + obs = ms.ReachObservation(sample_node_data, reach="r1", item="WaterLevel") -class _StubReach: - """Stands in for a mikeio1d ResultReach.""" - - def __init__(self, name="r1", start_node="a", end_node="b", length=100.0): - self.name = name - self.start_node = start_node - self.end_node = end_node - self.length = length - self.gridpoints = [] - - -class TestRes1DReachConnectivity: - """Formats that expose no reach connectivity must fail with a clear message.""" - - @pytest.mark.parametrize("missing", ["start_node", "end_node"]) - def test_missing_node_raises(self, missing): - reach = _StubReach(**{missing: None}) - - with pytest.raises(ValueError, match="no start/end node for reach 'r1'"): - Res1DReach(reach, Res1DNode("a"), Res1DNode("b")) + df = obs.to_dataframe() - def test_both_nodes_missing_raises(self): - """.resx reports None for both, which the identity checks alone would allow.""" - reach = _StubReach(start_node=None, end_node=None) - - with pytest.raises(ValueError, match="no start/end node"): - Res1DReach(reach, Res1DNode(None), Res1DNode(None)) # type: ignore[arg-type] - - def test_mismatched_start_node_still_raises(self): - with pytest.raises(ValueError, match="Incorrect starting node"): - Res1DReach(_StubReach(), Res1DNode("wrong"), Res1DNode("b")) - - -class TestRes1DReachLength: - """mikeio1d returns 0 when it cannot read a length; that is not a real zero.""" - - @pytest.mark.parametrize("reported", [0, 0.0]) - def test_zero_becomes_undefined(self, reported): - reach = Res1DReach(_StubReach(length=reported), Res1DNode("a"), Res1DNode("b")) - - assert reach.length is None - - def test_real_length_passes_through(self): - reach = Res1DReach(_StubReach(length=47.5), Res1DNode("a"), Res1DNode("b")) - - assert reach.length == 47.5 - - -# --------------------------------------------------------------------------- -# from_mike / from_epanet -# --------------------------------------------------------------------------- - -requires_mikeio1d = pytest.mark.skipif( - sys.version_info >= (3, 14), reason="mikeio1d requires Python < 3.14" -) - - -@requires_mikeio1d -class TestFromMike: - def test_res1d(self): - network = Network.from_mike("./tests/testdata/network.res1d") - - assert network.graph.number_of_nodes() == 259 - - def test_res11(self): - """MIKE 11 keeps its data on gridpoints, so its nodes are empty.""" - network = Network.from_mike("./tests/testdata/network_cali.res11") - - assert len(network._reaches) == 3 - assert network.graph.number_of_nodes() == 71 - assert set(network.quantities) == {"Discharge", "Water Level"} - assert [r.n_breakpoints for r in network._reaches.values()] == [23, 21, 23] - - def test_res11_reaches_have_real_lengths(self): - network = Network.from_mike("./tests/testdata/network_cali.res11") - - lengths = [d["length"] for *_, d in network.graph.edges(data=True)] - assert all(length > 0 for length in lengths) - - def test_open_res1d_object(self): - from mikeio1d import Res1D - - res = Res1D("./tests/testdata/network.res1d") - - network = Network.from_mike(res, nodes=[], reaches=[]) - - assert network.graph.number_of_nodes() == 259 - - def test_epanet_file_is_redirected(self): - with pytest.raises(ValueError, match=r"Use Network\.from_epanet\(\)"): - Network.from_mike("./tests/testdata/epanet.res") - - def test_unknown_extension(self): - with pytest.raises(NotImplementedError, match="Unsupported file extension"): - Network.from_mike("./tests/testdata/obs.dfs0") - - def test_unsupported_type(self): - with pytest.raises(TypeError, match="Expected a str, Path or Res1D object"): - Network.from_mike(42) # type: ignore[arg-type] - - -@requires_mikeio1d -class TestFromEpanet: - def test_epanet(self): - network = Network.from_epanet("./tests/testdata/epanet.res") - - assert network.graph.number_of_nodes() == 11 - assert len(network._reaches) == 13 - assert set(network.quantities) == { - "Demand", - "Head", - "Pressure", - "WaterQuality", - } - assert not network.to_dataframe().empty - - def test_link_node_reaches_have_no_length_or_breakpoints(self): - """Without inp=, mikeio1d reports neither - documented in the docstring.""" - network = Network.from_epanet("./tests/testdata/epanet.res") - - lengths = [d["length"] for *_, d in network.graph.edges(data=True)] - assert lengths and all(length is None for length in lengths) - assert all(r.n_breakpoints == 0 for r in network._reaches.values()) - - def test_reach_observation_cannot_be_matched(self, sample_node_data): - """Follows from having no breakpoints; also documented in the docstring.""" - network = Network.from_epanet("./tests/testdata/epanet.res") - nmr = NetworkModelResult(network, item="Pressure") - obs = ms.ReachObservation(sample_node_data, reach="10", item="WaterLevel") - - with pytest.raises(ValueError, match="breakpoints"): - nmr.extract(obs) - - def test_mike_file_is_redirected(self): - with pytest.raises(ValueError, match=r"Use Network\.from_mike\(\)"): - Network.from_epanet("./tests/testdata/network.res1d") - - def test_open_res1d_object_is_validated(self): - from mikeio1d import Res1D - - res = Res1D("./tests/testdata/network.res1d") - - with pytest.raises(ValueError, match=r"Use Network\.from_mike\(\)"): - Network.from_epanet(res) - - @pytest.mark.parametrize("suffix", [".res", ".RES"]) - def test_extension_is_case_insensitive(self, tmp_path, suffix): - with pytest.raises((FileExistsError, FileNotFoundError)): - Network.from_epanet(tmp_path / f"network{suffix}") - - -# --------------------------------------------------------------------------- -# EPANET companion files: .inp for reach lengths, .resx for extra quantities -# --------------------------------------------------------------------------- - -_EPANET_RES = "./tests/testdata/epanet.res" -_EPANET_RESX = "./tests/testdata/epanet.resx" -_EPANET_INP = "./tests/testdata/epanet.inp" - -# The 12 [PIPES] entries; reach "9" is the pump, which carries no length. -_PUMP_REACH = "9" - - -@requires_mikeio1d -class TestEpanetCompanionInp: - """`.inp` is the only one of the three files carrying reach lengths.""" - - def test_pipe_reaches_get_real_lengths(self): - network = Network.from_epanet(_EPANET_RES, inp=_EPANET_INP) - - lengths = {r.id: r.length for r in network._reaches.values()} - assert lengths["10"] == pytest.approx(3209.544) - assert lengths["110"] == pytest.approx(60.96) - - def test_pump_reach_stays_undefined(self): - """[PIPES] is the only section with lengths, so pumps keep None.""" - network = Network.from_epanet(_EPANET_RES, inp=_EPANET_INP) - - lengths = {r.id: r.length for r in network._reaches.values()} - assert lengths[_PUMP_REACH] is None - assert sum(v is None for v in lengths.values()) == 1 - - def test_graph_edges_carry_the_lengths(self): - network = Network.from_epanet(_EPANET_RES, inp=_EPANET_INP) - - lengths = [d["length"] for *_, d in network.graph.edges(data=True)] - assert sum(v is not None for v in lengths) == 12 - - def test_node_ids_overlapping_reach_ids_are_not_confused(self): - """Most IDs here name both a node and a reach, e.g. '9', '10', '21'.""" - network = Network.from_epanet(_EPANET_RES, inp=_EPANET_INP) - - assert set(network._reaches) & set(network._alias_map) # they do overlap - # Reach "10" is 3209.544 long; node "10" is untouched by the length map. - assert network._reaches["10"].length == pytest.approx(3209.544) - node_10 = network.find(node="10") - assert "Head" in network.to_dataframe()[node_10].columns - - def test_wrong_suffix_is_refused(self): - with pytest.raises(ValueError, match=r"Expected an EPANET '\.inp'"): - Network.from_epanet(_EPANET_RES, inp=_EPANET_RESX) - - def test_file_without_a_pipes_section_is_refused(self, tmp_path): - other = tmp_path / "not-epanet.inp" - other.write_text("[JUNCTIONS]\n;;Name\n9 1000\n") - - with pytest.raises(ValueError, match=r"no \[PIPES\] section"): - Network.from_epanet(_EPANET_RES, inp=other) - - -@requires_mikeio1d -class TestEpanetCompanionResx: - """`.resx` holds extra results for the network defined in the sibling `.res`.""" - - def test_extra_node_quantities_are_merged(self): - network = Network.from_epanet(_EPANET_RES, resx=_EPANET_RESX) - - assert set(network.quantities) == { - "Demand", - "Head", - "Pressure", - "WaterQuality", - "Volume", - "Volume Percentage", - } - - def test_only_the_nodes_present_in_the_resx_gain_them(self): - """The .resx covers the tank and the reservoir, not all eleven nodes.""" - network = Network.from_epanet(_EPANET_RES, resx=_EPANET_RESX) - df = network.to_dataframe() - - with_volume = { - node - for node in df.columns.get_level_values("node").unique() - if "Volume" in df[node].columns - } - # Node IDs are re-indexed to integers, so recall the original labels. - assert {network.recall(node)["node"] for node in with_volume} == {"2", "9"} - - def test_values_come_through(self): - network = Network.from_epanet(_EPANET_RES, resx=_EPANET_RESX) - - reservoir = network.find(node="9") - volume = network.to_dataframe()[(reservoir, "Volume Percentage")] - assert len(volume) == 25 - assert volume.notna().all() - - def test_selective_loading_still_governs_what_is_read(self): - network = Network.from_epanet(_EPANET_RES, resx=_EPANET_RESX, nodes=["2"]) - - df = network.to_dataframe() - tank = network.find(node="2") - assert set(df.columns.get_level_values("node").unique()) == {tank} - assert "Volume" in df[tank].columns - - def test_both_companions_together(self): - network = Network.from_epanet(_EPANET_RES, resx=_EPANET_RESX, inp=_EPANET_INP) - - assert "Volume" in network.quantities - assert network._reaches["10"].length == pytest.approx(3209.544) - - def test_an_open_res1d_object_is_accepted(self): - from mikeio1d import Res1D - - network = Network.from_epanet(_EPANET_RES, resx=Res1D(_EPANET_RESX)) - - assert "Volume" in network.quantities - - def test_wrong_suffix_is_refused(self): - with pytest.raises(ValueError, match=r"Expected an EPANET '\.resx'"): - Network.from_epanet(_EPANET_RES, resx=_EPANET_RES) - - def test_a_result_file_of_another_format_is_refused(self): - from mikeio1d import Res1D - - other = Res1D("./tests/testdata/network.res1d") - - with pytest.raises(ValueError, match=r"Expected an EPANET '\.resx'"): - Network.from_epanet(_EPANET_RES, resx=other) - - def test_a_companion_from_another_run_is_refused(self, monkeypatch): - """Merging two runs would line up silently and give a wrong network.""" - from mikeio1d import Res1D + assert list(df.columns) == ["WaterLevel"] - res = Res1D(_EPANET_RES) - resx = Res1D(_EPANET_RESX) - shifted = resx.time_index + pd.Timedelta("1D") + def test_a_named_node_survives_trimming(self, sample_network, sample_node_data): + nmr = NetworkModelResult(sample_network) + extracted = nmr.extract(ms.NodeObservation(sample_node_data, at="123")) - # Both objects share the Res1D class, so shift only this one instance. - original = type(resx).time_index.fget - monkeypatch.setattr( - type(resx), - "time_index", - property(lambda self: shifted if self is resx else original(self)), + trimmed = extracted.trim( + start_time=extracted.time[1], end_time=extracted.time[-1] ) - with pytest.raises(ValueError, match="does not share a time axis"): - Network.from_epanet(res, resx=resx) + assert trimmed.node == extracted.node + assert len(trimmed) == len(extracted) - 1 - def test_a_companion_naming_an_unknown_node_is_refused(self, monkeypatch): - """A node the .res has never heard of means these are different models.""" - from mikeio1d import Res1D - - res = Res1D(_EPANET_RES) - resx = Res1D(_EPANET_RESX) - strangers = dict(resx.nodes) | {"not_in_the_res": None} - - original = type(resx).nodes.fget - monkeypatch.setattr( - type(resx), - "nodes", - property(lambda self: strangers if self is resx else original(self)), - ) + def test_a_matched_breakpoint_records_its_chainage( + self, breakpoint_network, sample_node_data + ): + nmr = NetworkModelResult(breakpoint_network, name="Network_Model") + obs = ms.NodeObservation(sample_node_data, at=("r1", 50.0), name="BP") - with pytest.raises(ValueError, match="not_in_the_res"): - Network.from_epanet(res, resx=resx) + cmp = ms.match(obs, nmr) - def test_unsupported_type_is_refused(self): - with pytest.raises(TypeError, match="Expected a str, Path or Res1D object"): - Network.from_epanet(_EPANET_RES, resx=42) # type: ignore[arg-type] + assert cmp.gtype == "node" + assert cmp.node is None + assert cmp.reach == "r1" + assert cmp.distance == pytest.approx(50.0) + def test_a_matched_reach_reports_the_breakpoint_it_was_read_from( + self, breakpoint_network, sample_node_data + ): + """The observation is reach-level, so gtype stays 'reach'; distance says + which breakpoint the model data was taken from.""" + nmr = NetworkModelResult(breakpoint_network, name="Network_Model") + obs = ms.ReachObservation(sample_node_data, reach="r1", name="Reach") -class TestReadInp: - """Minimal .inp reader - see modelskill/model/adapters/_inp.py.""" + cmp = ms.match(obs, nmr) - def _write(self, tmp_path, text): - path = tmp_path / "model.inp" - path.write_text(text) - return path + assert cmp.gtype == "reach" + assert cmp.reach == "r1" + assert cmp.distance == pytest.approx(50.0) - def test_sections_are_keyed_without_brackets_and_upper_cased(self, tmp_path): - path = self._write(tmp_path, "[Pipes]\n1 a b 10\n[TANKS]\n2 5\n") - assert set(read_sections(path)) == {"PIPES", "TANKS"} +class TestObservationFactory: + """ms.observation() routes the network keywords to the right class.""" - def test_comment_and_blank_lines_are_dropped(self, tmp_path): - path = self._write( - tmp_path, - ";a leading banner\n\n[PIPES]\n" - ";;ID Node1 Node2 Length\n" - ";;-- ----- ----- ------\n" - "1 a b 10\n\n", - ) + def test_at_gives_a_node_observation(self, sample_node_data): + obs = ms.observation(sample_node_data, at="123", item="WaterLevel") - assert read_sections(path) == {"PIPES": [["1", "a", "b", "10"]]} + assert isinstance(obs, ms.NodeObservation) + assert obs.at == "123" - def test_trailing_comment_is_stripped_from_a_data_row(self, tmp_path): - path = self._write(tmp_path, "[PIPES]\n1 a b 10 ; the short one\n") + def test_a_breakpoint_tuple_gives_a_node_observation(self, sample_node_data): + obs = ms.observation(sample_node_data, at=("r1", 24.5), item="WaterLevel") - assert read_sections(path)["PIPES"] == [["1", "a", "b", "10"]] + assert isinstance(obs, ms.NodeObservation) + assert obs.at == ("r1", 24.5) - def test_rows_before_any_section_are_ignored(self, tmp_path): - path = self._write(tmp_path, "stray row\n[PIPES]\n1 a b 10\n") + def test_reach_gives_a_reach_observation(self, sample_node_data): + obs = ms.observation(sample_node_data, reach="r1", item="WaterLevel") - assert read_sections(path) == {"PIPES": [["1", "a", "b", "10"]]} + assert isinstance(obs, ms.ReachObservation) + assert obs.reach == "r1" - def test_lengths_are_read_from_the_fourth_field(self, tmp_path): - path = self._write(tmp_path, "[PIPES]\n1 a b 10.5 300 100\n") - - assert read_pipe_lengths(path) == {"1": 10.5} + @pytest.mark.parametrize( + "gtype,kwargs,expected", + [ + ("node", {"at": "123"}, ms.NodeObservation), + ("reach", {"reach": "r1"}, ms.ReachObservation), + ], + ) + def test_gtype_can_be_named_outright( + self, sample_node_data, gtype, kwargs, expected + ): + obs = ms.observation(sample_node_data, gtype=gtype, item="WaterLevel", **kwargs) - def test_a_short_row_raises_rather_than_dropping_a_length(self, tmp_path): - path = self._write(tmp_path, "[PIPES]\n1 a b\n") + assert isinstance(obs, expected) - with pytest.raises(ValueError, match="Cannot read a pipe length"): - read_pipe_lengths(path) - def test_a_repeated_section_header_accumulates(self, tmp_path): - path = self._write( - tmp_path, "[PIPES]\n1 a b 10\n[TANKS]\n2 5\n[PIPES]\n3 c d 20\n" - ) +def test_a_reach_observation_keeps_its_weight_and_attrs(sample_node_data): + obs = ReachObservation( + sample_node_data, reach="r1", weight=2.5, attrs={"source": "test"} + ) - assert read_pipe_lengths(path) == {"1": 10.0, "3": 20.0} + assert obs.weight == 2.5 + assert obs.attrs["source"] == "test" + assert obs.quantity == Quantity.undefined() diff --git a/tests/testdata/README.md b/tests/testdata/README.md index a6f4a71c6..63bb46db9 100644 --- a/tests/testdata/README.md +++ b/tests/testdata/README.md @@ -10,16 +10,20 @@ These network files come from | File | Format | Used for | |---|---|---| -| `network_cali.res11` | MIKE 11 | `Network.from_mike` coverage for `.res11` | -| `epanet.res` | EPANET | `Network.from_epanet` coverage | -| `epanet.resx` | EPANET (MIKE+) | the `resx=` companion — extra node quantities merged onto the `.res` network | -| `epanet.inp` | EPANET input | the `inp=` companion — real pipe lengths, which the `.res` does not carry | -| `swmm.out` | SWMM | asserting `.out` is refused — its reach connectivity lives in a companion `.inp` we do not read yet (#689) | +| `network_cali.res11` | MIKE 11 | nothing here any more — see below | +| `epanet.res` | EPANET | nothing here any more — see below | +| `epanet.resx` | EPANET (MIKE+) | extra node quantities, merged onto the `.res` network | +| `epanet.inp` | EPANET input | real pipe lengths, which the `.res` does not carry | +| `swmm.out` | SWMM | nothing here any more — see below | + +Reading these formats moved to mikeio1d with the rest of the topology layer +(ADR-013), and the tests that covered it moved with it. The files are kept because +mikeio1d has the same copies and modelskill may want EPANET-side coverage of its +own; nothing in this repository reads them today except `network.res1d`. `epanet.resx` and `epanet.inp` pair with `epanet.res`: same run, same IDs. The `.resx` node and reach IDs are a strict subset of the `.res` ones, and the `.inp` `[PIPES]` IDs cover every `.res` reach except the pump. -`swmm.out` is kept without its `.inp` on purpose. It pins the refusal, so the test -fails the day we add SWMM support or a future mikeio1d starts reporting reach -connectivity for it. +`swmm.out` is kept without its `.inp` on purpose: the refusal it used to pin is +mikeio1d's now, and the file is the fixture that refusal needs. diff --git a/tests/testdata/network_sensor_1.csv b/tests/testdata/network_sensor_1.csv index 904d9eb79..6f46c2869 100644 --- a/tests/testdata/network_sensor_1.csv +++ b/tests/testdata/network_sensor_1.csv @@ -1,111 +1,111 @@ ,water_level@sens1 -1994-08-07 16:35:06.721389014,193.7479319011718 -1994-08-07 16:36:11.808982110,193.9276622504125 -1994-08-07 16:36:58.463517098,193.73969537883863 -1994-08-07 16:38:50.136489724,193.5324026294447 -1994-08-07 16:39:54.184260240,193.75098664628783 -1994-08-07 16:41:02.301898383,193.9631823043365 -1994-08-07 16:41:48.047551850,193.88949067602914 -1994-08-07 16:43:03.765627271,193.73298338692013 -1994-08-07 16:43:59.271576674,193.518740505735 -1994-08-07 16:44:53.976575879,193.80052813920184 -1994-08-07 16:45:52.150380498,193.76709749688646 -1994-08-07 16:46:59.335689619,193.87266429057766 -1994-08-07 16:47:48.671800096,193.61027606890661 -1994-08-07 16:49:09.441634441,193.90646493021583 -1994-08-07 16:50:05.221428246,193.68537026321115 -1994-08-07 16:51:31.750429245,194.00527903620747 -1994-08-07 16:52:44.709396591,193.960610633433 -1994-08-07 16:54:15.295748944,193.87369664367992 -1994-08-07 16:56:09.081416788,193.74406275881358 -1994-08-07 16:57:14.043033644,193.71501017501876 -1994-08-07 16:58:18.210601321,193.83703675670696 -1994-08-07 16:59:03.257072206,194.06250124233108 -1994-08-07 17:00:06.081835904,193.97168058658363 -1994-08-07 17:01:06.426608705,193.6410669817052 -1994-08-07 17:02:22.837775497,193.7119913501634 -1994-08-07 17:03:18.768918912,193.8826429315821 -1994-08-07 17:04:17.424400499,193.76142364154504 -1994-08-07 17:05:06.371871595,193.82686832219773 -1994-08-07 17:06:17.991770538,193.7814248098547 -1994-08-07 17:07:14.600956935,193.747104244197 -1994-08-07 17:08:19.929202980,193.88962326399343 -1994-08-07 17:09:18.863933688,193.8894988894418 -1994-08-07 17:10:21.785300698,193.7678915506987 -1994-08-07 17:11:22.760047207,193.8350592978198 -1994-08-07 17:12:16.293904286,193.76937160755946 -1994-08-07 17:13:18.937531227,193.7072710276047 -1994-08-07 17:14:09.393470503,194.01678510826014 -1994-08-07 17:15:16.341863122,193.81790025709154 -1994-08-07 17:16:17.315241669,193.96796138622275 -1994-08-07 17:17:15.246401107,193.93776810871083 -1994-08-07 17:18:05.749523838,193.92373793354915 -1994-08-07 17:19:22.112742914,193.75041032904946 -1994-08-07 17:20:31.310425275,193.92249310948324 -1994-08-07 17:21:21.982712671,193.82335772625748 -1994-08-07 17:22:23.767907448,193.95466062588378 -1994-08-07 17:23:39.680152861,194.013618376674 -1994-08-07 17:24:38.147955485,194.11815372525726 -1994-08-07 17:25:38.420371252,194.50584562429492 -1994-08-07 17:26:24.408303070,194.54446646687336 -1994-08-07 17:27:31.105998416,194.46853129987517 -1994-08-07 17:28:34.880232331,194.55187457703428 -1994-08-07 17:29:37.175071072,194.86809034869842 -1994-08-07 17:30:35.271697419,194.70284194184958 -1994-08-07 17:31:31.204858260,194.98822903414495 -1994-08-07 17:32:24.847235710,195.24737706568624 -1994-08-07 17:33:36.679829931,195.2715904795984 -1994-08-07 17:34:37.932637257,195.1555004434547 -1994-08-07 17:35:25.487179907,195.11808508664626 -1994-08-07 17:36:22.047650391,195.35302938305526 -1994-08-07 17:37:22.665545301,195.058608262039 -1994-08-07 17:38:23.922713966,195.03890774364643 -1994-08-07 17:39:37.300808590,195.10925487796052 -1994-08-07 17:40:39.123378882,194.9197940042712 -1994-08-07 17:41:39.736323741,194.91541376013848 -1994-08-07 17:42:32.624612495,194.84419544155747 -1994-08-07 17:43:38.386604464,194.91238993528924 -1994-08-07 17:44:35.752954642,194.80146002502337 -1994-08-07 17:45:47.706770126,194.91559552756155 -1994-08-07 17:46:56.228029771,194.72765509600865 -1994-08-07 17:47:50.302639903,194.72477357208479 -1994-08-07 17:48:51.313688841,194.64138661647883 -1994-08-07 17:50:04.027931558,194.66788700646532 -1994-08-07 17:50:59.170111019,194.69887586125873 -1994-08-07 17:52:19.507804755,194.75091927194543 -1994-08-07 17:53:36.244289704,194.73563801241843 -1994-08-07 17:54:46.006725041,194.35800960937343 -1994-08-07 17:55:50.875697661,194.41711389439024 -1994-08-07 17:57:01.668197445,194.29313729304636 -1994-08-07 17:58:16.481604045,194.45199484204932 -1994-08-07 17:59:47.768779742,194.45872420226013 -1994-08-07 18:00:49.317493448,194.4314547834637 -1994-08-07 18:02:02.027423669,194.2515095971401 -1994-08-07 18:03:55.326990060,194.2601910363553 -1994-08-07 18:05:08.677524139,194.09049632407513 -1994-08-07 18:05:55.807125909,194.25764674606657 -1994-08-07 18:07:06.508171149,194.15893464151893 -1994-08-07 18:08:14.768585338,194.32278841101814 -1994-08-07 18:08:59.434113874,194.1892503649891 -1994-08-07 18:10:10.361033654,194.3360223971569 -1994-08-07 18:11:39.416513356,193.91053863482347 -1994-08-07 18:13:12.292137298,194.03847881603537 -1994-08-07 18:14:50.579821036,194.19255380323284 -1994-08-07 18:16:11.445107220,194.2372052362117 -1994-08-07 18:17:13.979038396,194.02910039993844 -1994-08-07 18:19:11.411173338,194.1543403568542 -1994-08-07 18:20:05.747471067,194.28718925009687 -1994-08-07 18:21:08.770390114,194.2290999628811 -1994-08-07 18:22:04.993112618,193.88244373099988 -1994-08-07 18:23:12.416131227,194.02896457246328 -1994-08-07 18:24:13.427829087,193.99948814393267 -1994-08-07 18:25:12.565706666,194.16456490763096 -1994-08-07 18:26:07.842114358,194.02641628418803 -1994-08-07 18:26:58.397587323,194.16132614507512 -1994-08-07 18:28:03.529864903,194.15134556849915 -1994-08-07 18:29:00.650668046,193.92727557471548 -1994-08-07 18:30:03.066222312,193.9766219050112 -1994-08-07 18:31:10.120818363,194.06760532977663 -1994-08-07 18:32:09.431674937,193.95571034292806 -1994-08-07 18:33:00.389496491,194.03147735060986 -1994-08-07 18:35:01.353919658,193.94705444753936 +1994-08-07 16:35:00.776781603,194.32011320654263 +1994-08-07 16:36:09.729399842,194.7470367441099 +1994-08-07 16:37:10.325784362,194.67395882505454 +1994-08-07 16:38:54.294488082,194.61153571139567 +1994-08-07 16:39:49.893755801,194.54321065864184 +1994-08-07 16:40:49.464208760,194.51251435556324 +1994-08-07 16:41:54.155002671,194.48729978930177 +1994-08-07 16:42:47.858648484,194.58252622043406 +1994-08-07 16:43:48.114422841,194.4304369580146 +1994-08-07 16:44:50.213843207,194.38249215797805 +1994-08-07 16:45:52.389905612,194.64429343992802 +1994-08-07 16:47:02.765925660,194.53358432734336 +1994-08-07 16:47:47.544625374,194.60385269292348 +1994-08-07 16:49:02.637927182,194.59824639141928 +1994-08-07 16:50:12.152163291,194.64549922015675 +1994-08-07 16:51:38.335652213,194.20982648777377 +1994-08-07 16:52:40.498338592,194.65174104529223 +1994-08-07 16:54:29.296070178,194.67321466962673 +1994-08-07 16:56:16.843462211,194.70496214061026 +1994-08-07 16:57:19.546830813,194.57169766844956 +1994-08-07 16:58:21.508555037,194.66168658625153 +1994-08-07 16:59:19.782410316,194.55758442776926 +1994-08-07 17:00:21.921517678,194.5576186499002 +1994-08-07 17:01:20.092192674,194.81469647880382 +1994-08-07 17:02:17.746355889,194.70522414915595 +1994-08-07 17:03:13.166714757,194.60789232032135 +1994-08-07 17:04:12.749365769,194.79521007424793 +1994-08-07 17:05:10.304197359,194.42783733284318 +1994-08-07 17:06:04.189757936,194.5555761260468 +1994-08-07 17:07:12.765391276,194.73618898202943 +1994-08-07 17:08:12.365563077,194.6375905289089 +1994-08-07 17:09:15.491917299,194.57025641473498 +1994-08-07 17:10:06.493562414,194.4688605015372 +1994-08-07 17:11:07.187363310,194.75550986631575 +1994-08-07 17:12:09.160016532,194.61054169889053 +1994-08-07 17:13:21.469083676,194.6253361248603 +1994-08-07 17:14:17.930689144,194.70277955607804 +1994-08-07 17:15:17.279332650,194.56556903513305 +1994-08-07 17:16:08.640601075,194.50005512303613 +1994-08-07 17:17:07.068658303,194.65868598122688 +1994-08-07 17:18:09.766449327,194.6653618297461 +1994-08-07 17:19:20.161218787,194.74595890778755 +1994-08-07 17:20:18.992883026,194.4980021093954 +1994-08-07 17:21:30.086313691,194.55154639192463 +1994-08-07 17:22:28.651457434,194.57671325756172 +1994-08-07 17:23:40.633424866,194.69867899539543 +1994-08-07 17:24:37.117548132,194.82545584348597 +1994-08-07 17:25:30.448984712,195.0279680264088 +1994-08-07 17:26:30.029453346,195.07988551475916 +1994-08-07 17:27:32.245829383,194.92487401587312 +1994-08-07 17:28:30.078420729,195.3240098122494 +1994-08-07 17:29:38.136013235,195.47706773119452 +1994-08-07 17:30:35.734525134,195.77572434729703 +1994-08-07 17:31:34.656548288,196.34019205600237 +1994-08-07 17:32:31.554119147,196.4872903805679 +1994-08-07 17:33:34.533280257,196.31028793328298 +1994-08-07 17:34:21.642437078,196.39518666193 +1994-08-07 17:35:37.901281041,196.23731771908797 +1994-08-07 17:36:31.067494778,196.2907368565569 +1994-08-07 17:37:26.536405793,196.05114435497984 +1994-08-07 17:38:26.179123415,196.23182104355894 +1994-08-07 17:39:34.480757192,195.7356264141028 +1994-08-07 17:40:32.367919827,195.76434390248752 +1994-08-07 17:41:29.068741892,195.387522485378 +1994-08-07 17:42:25.963454370,195.26662774076652 +1994-08-07 17:43:43.256506377,195.1620181140931 +1994-08-07 17:44:48.923258096,194.99956715610247 +1994-08-07 17:45:48.951022779,194.803275106843 +1994-08-07 17:46:51.832758663,194.62647855997503 +1994-08-07 17:47:53.011655455,194.840056508079 +1994-08-07 17:48:53.291279628,194.7191954018589 +1994-08-07 17:49:45.414193152,194.72130314272985 +1994-08-07 17:51:06.934395423,194.736784985074 +1994-08-07 17:52:14.283447737,194.57160881923406 +1994-08-07 17:53:31.345360278,194.83788750770336 +1994-08-07 17:54:45.878612606,194.6186418522351 +1994-08-07 17:55:54.526498381,194.60992402778382 +1994-08-07 17:57:07.804714354,194.70930496247433 +1994-08-07 17:58:29.732584061,194.631433387878 +1994-08-07 17:59:51.646966493,194.7453412546349 +1994-08-07 18:00:49.479370729,194.63355314090856 +1994-08-07 18:02:05.027988263,194.54741392208302 +1994-08-07 18:04:07.168692247,194.66260603569324 +1994-08-07 18:05:02.408720278,194.65222312074025 +1994-08-07 18:05:59.994427311,194.7324924261196 +1994-08-07 18:06:58.002591414,194.59584590330348 +1994-08-07 18:08:12.257364299,194.7322344755338 +1994-08-07 18:08:56.371471124,194.7565388217572 +1994-08-07 18:09:59.424399444,194.68480423777225 +1994-08-07 18:11:43.381217236,194.4815298273368 +1994-08-07 18:13:18.462469421,194.72667934520882 +1994-08-07 18:14:45.523662018,194.6906565875005 +1994-08-07 18:16:02.135996224,194.65676781090227 +1994-08-07 18:17:12.775638626,194.43981653379436 +1994-08-07 18:19:04.224160802,194.55663239846368 +1994-08-07 18:20:01.156801276,194.65240550437673 +1994-08-07 18:21:04.005495270,194.74085520785582 +1994-08-07 18:22:07.212758918,194.54846322157974 +1994-08-07 18:23:17.581805886,194.5943242421595 +1994-08-07 18:24:09.834797808,194.66609677586615 +1994-08-07 18:25:02.157922163,194.64754442371688 +1994-08-07 18:26:00.013570112,194.58158453962835 +1994-08-07 18:26:58.607951371,194.61501209771856 +1994-08-07 18:28:09.760211999,194.52247158113664 +1994-08-07 18:29:04.676695951,194.54233718320697 +1994-08-07 18:29:58.126126307,194.4578251058232 +1994-08-07 18:30:58.188406457,194.6101479534574 +1994-08-07 18:32:08.511690435,194.52181232461433 +1994-08-07 18:33:05.469289719,194.63804807700458 +1994-08-07 18:34:50.281906950,194.5500288327867 diff --git a/tests/testdata/network_sensor_2.csv b/tests/testdata/network_sensor_2.csv index 9eddb84df..403d0b069 100644 --- a/tests/testdata/network_sensor_2.csv +++ b/tests/testdata/network_sensor_2.csv @@ -1,81 +1,81 @@ ,water_level@sens2 -1994-08-07 17:08:19.453000537,193.69907521870385 -1994-08-07 17:09:11.667777579,193.53254848297152 -1994-08-07 17:10:17.713878006,193.37840712805215 -1994-08-07 17:11:14.377409723,193.36046774853432 -1994-08-07 17:12:19.607715440,193.35883525251685 -1994-08-07 17:13:16.506158153,193.54074175140104 -1994-08-07 17:14:22.442398906,193.5636244747378 -1994-08-07 17:15:07.096815541,193.3936963704199 -1994-08-07 17:16:10.480296727,193.48972566299682 -1994-08-07 17:17:22.450643797,193.4587060771225 -1994-08-07 17:18:21.490266204,193.35182993861991 -1994-08-07 17:19:19.872035872,193.45517089047667 -1994-08-07 17:20:18.551963043,193.39190620106453 -1994-08-07 17:21:37.487970642,193.65287311723839 -1994-08-07 17:22:38.893023517,193.51519324042974 -1994-08-07 17:23:37.808072814,193.72342214376903 -1994-08-07 17:24:30.088355655,193.42926966972004 -1994-08-07 17:25:22.809359233,193.373787100516 -1994-08-07 17:26:33.203907398,193.87313878617707 -1994-08-07 17:27:24.450142559,193.77977426912494 -1994-08-07 17:28:34.256134409,193.87776964481588 -1994-08-07 17:29:29.162052421,193.74593301162298 -1994-08-07 17:30:40.417250997,194.06335624478052 -1994-08-07 17:31:36.292748137,194.0891460493891 -1994-08-07 17:32:22.577927686,193.93598437841732 -1994-08-07 17:33:35.944430514,193.89206968675506 -1994-08-07 17:34:23.631420564,194.29720825961996 -1994-08-07 17:35:33.310491457,193.94006446417492 -1994-08-07 17:36:36.768648209,193.94301852816398 -1994-08-07 17:37:41.256034582,193.93359649072713 -1994-08-07 17:38:27.439954370,193.99934207664205 -1994-08-07 17:39:32.423879950,194.0276140940857 -1994-08-07 17:40:32.505523785,193.75309351356177 -1994-08-07 17:41:26.182311995,193.86393199710076 -1994-08-07 17:42:31.811747815,193.86698395764947 -1994-08-07 17:43:43.977586264,193.79824583031655 -1994-08-07 17:44:32.820391407,193.88413939303942 -1994-08-07 17:45:39.780028021,193.82682735759107 -1994-08-07 17:46:45.520148467,193.64527654802706 -1994-08-07 17:47:47.632936535,193.79625317554138 -1994-08-07 17:48:57.346727826,193.6655550615617 -1994-08-07 17:49:49.253930336,193.78374770218835 -1994-08-07 17:51:02.833869005,193.49014557011174 -1994-08-07 17:52:08.948334997,193.5521603282343 -1994-08-07 17:53:32.956569310,193.42249475180054 -1994-08-07 17:54:40.776321387,193.41421417275032 -1994-08-07 17:55:45.154182165,193.7623513346016 -1994-08-07 17:57:01.877564139,193.71602664387882 -1994-08-07 17:58:27.973645801,193.6626855905494 -1994-08-07 17:59:48.164323924,193.58427431473757 -1994-08-07 18:00:49.608896563,193.81199176289348 -1994-08-07 18:02:06.114975563,193.64886741921137 -1994-08-07 18:04:10.676323539,193.719005472149 -1994-08-07 18:05:07.173323943,193.96465013641256 -1994-08-07 18:06:11.346838731,193.68426045579662 -1994-08-07 18:07:15.138616621,193.88023114770002 -1994-08-07 18:08:12.352725832,193.73742809783437 -1994-08-07 18:09:09.040990372,193.95542280957721 -1994-08-07 18:10:05.444299339,193.72699587134503 -1994-08-07 18:11:43.548122207,193.64415106941962 -1994-08-07 18:13:19.975340480,193.61679887917686 -1994-08-07 18:15:00.099149664,193.7092729650918 -1994-08-07 18:15:58.266937956,193.51481360263026 -1994-08-07 18:17:11.010370099,193.88621915401467 -1994-08-07 18:19:00.194728453,193.70300139682695 -1994-08-07 18:20:01.268579900,193.74549285164244 -1994-08-07 18:20:59.085548638,193.61744713398235 -1994-08-07 18:22:04.935328179,193.73830581406855 -1994-08-07 18:23:02.479745169,193.64939163691596 -1994-08-07 18:24:03.446628434,193.70495758379232 -1994-08-07 18:25:06.549919135,193.8685145360975 -1994-08-07 18:26:03.183612415,193.76014328305922 -1994-08-07 18:27:13.078601240,193.970374402854 -1994-08-07 18:28:04.443206888,193.861958294037 -1994-08-07 18:29:11.413727234,193.7661905820629 -1994-08-07 18:30:08.277252518,193.82534783818286 -1994-08-07 18:31:15.483217962,193.74254129620306 -1994-08-07 18:32:16.040549977,193.6307058726291 -1994-08-07 18:33:10.182896344,193.70636292657173 -1994-08-07 18:34:50.194959733,193.5921011184813 +1994-08-07 17:08:16.746982777,193.46666085110937 +1994-08-07 17:09:15.911869348,193.5553573859252 +1994-08-07 17:10:12.195124338,193.68937807365793 +1994-08-07 17:11:04.448876310,193.45076123271946 +1994-08-07 17:12:21.097623039,193.35810147147876 +1994-08-07 17:13:10.410972001,193.2465448797826 +1994-08-07 17:14:18.997462489,193.3968538310765 +1994-08-07 17:15:03.516275677,193.4281083125626 +1994-08-07 17:16:19.363113442,193.41499628403477 +1994-08-07 17:17:21.535598304,193.5666798285079 +1994-08-07 17:18:06.703804784,193.39019715759977 +1994-08-07 17:19:07.113924200,193.38622798499898 +1994-08-07 17:20:33.249205987,193.44059499667398 +1994-08-07 17:21:31.188910307,193.39014297074812 +1994-08-07 17:22:25.449777484,193.5135983423928 +1994-08-07 17:23:34.351449340,193.44326026160877 +1994-08-07 17:24:36.704856539,193.64662660593314 +1994-08-07 17:25:23.749227916,193.68181603286484 +1994-08-07 17:26:25.413103928,193.65784040523326 +1994-08-07 17:27:34.119977569,193.6594899766634 +1994-08-07 17:28:38.077895472,193.56136289039708 +1994-08-07 17:29:28.892229227,193.88248108364874 +1994-08-07 17:30:37.381056452,193.879931506782 +1994-08-07 17:31:22.016878283,193.9764859878361 +1994-08-07 17:32:24.820104399,194.0092368597367 +1994-08-07 17:33:30.644397706,193.88064299765335 +1994-08-07 17:34:33.346912592,194.05445097743723 +1994-08-07 17:35:23.787289189,193.82172014137683 +1994-08-07 17:36:27.366039084,194.11063686379669 +1994-08-07 17:37:21.887545915,194.0337211037484 +1994-08-07 17:38:24.492378576,193.97610821824 +1994-08-07 17:39:30.357983784,193.8996749540457 +1994-08-07 17:40:32.873187909,193.92570259171478 +1994-08-07 17:41:34.362113670,194.00499314349867 +1994-08-07 17:42:37.953467214,193.97506638859474 +1994-08-07 17:43:42.534176765,193.8375936679057 +1994-08-07 17:44:46.286409008,193.8822436003635 +1994-08-07 17:45:38.814041258,193.92934125470285 +1994-08-07 17:46:46.299281342,193.8427036608706 +1994-08-07 17:47:54.794002989,193.67733464061652 +1994-08-07 17:48:47.411067897,193.7657885903237 +1994-08-07 17:49:54.806563276,193.79932081862353 +1994-08-07 17:51:07.203432962,193.6679871844179 +1994-08-07 17:52:24.175267637,193.66515188457322 +1994-08-07 17:53:38.221315864,193.47159887553875 +1994-08-07 17:54:47.955110981,193.3670697177164 +1994-08-07 17:55:57.151866554,193.2148360925973 +1994-08-07 17:57:08.560582293,193.62690790451714 +1994-08-07 17:58:23.735060106,193.72418492900746 +1994-08-07 17:59:55.978275973,193.6697424454863 +1994-08-07 18:00:56.860847094,193.862786559526 +1994-08-07 18:02:08.048762300,193.5319389365475 +1994-08-07 18:04:00.764336576,193.87660770279663 +1994-08-07 18:05:03.913437188,193.60076940202146 +1994-08-07 18:06:13.261131339,193.6366841968641 +1994-08-07 18:07:06.157111035,193.59798175417382 +1994-08-07 18:08:04.896493562,193.68986271298314 +1994-08-07 18:09:08.807194603,193.75154771138716 +1994-08-07 18:10:01.992525026,193.59058976854448 +1994-08-07 18:11:47.873996513,194.03607519614027 +1994-08-07 18:13:19.705641908,193.85331823073562 +1994-08-07 18:15:01.527177197,193.82125166357054 +1994-08-07 18:16:06.112479066,193.6735111347705 +1994-08-07 18:17:16.685286166,193.73108188615356 +1994-08-07 18:19:17.074371614,193.8626267277998 +1994-08-07 18:20:10.845364231,193.7936194327107 +1994-08-07 18:21:12.902532477,193.75779898411886 +1994-08-07 18:22:05.155728075,193.7031060506949 +1994-08-07 18:22:58.734020179,193.5646247175248 +1994-08-07 18:24:13.186786829,193.73911466320203 +1994-08-07 18:25:01.856294891,193.84057875255806 +1994-08-07 18:26:15.909490510,193.59172917653063 +1994-08-07 18:26:59.409353061,193.729350832721 +1994-08-07 18:28:14.140616186,193.6950562602535 +1994-08-07 18:29:12.131829961,193.56685946901322 +1994-08-07 18:30:15.997926093,193.7309558657659 +1994-08-07 18:31:00.070062248,193.77991569884506 +1994-08-07 18:31:58.657467012,193.65439446319758 +1994-08-07 18:33:12.430771829,193.7274017925486 +1994-08-07 18:35:06.217438898,193.7440013043444 diff --git a/tests/testdata/network_sensor_3.csv b/tests/testdata/network_sensor_3.csv index 540162613..b15854108 100644 --- a/tests/testdata/network_sensor_3.csv +++ b/tests/testdata/network_sensor_3.csv @@ -1,91 +1,91 @@ ,water_level@sens3 -1994-08-07 16:34:59.749185049,193.65573611633096 -1994-08-07 16:36:08.234735831,193.5492676961432 -1994-08-07 16:37:10.476205369,193.44270461528237 -1994-08-07 16:38:56.739070973,193.7316050738842 -1994-08-07 16:39:48.448422501,193.46705769407055 -1994-08-07 16:41:00.899011077,193.60328571871497 -1994-08-07 16:41:57.674627567,193.35125246300828 -1994-08-07 16:42:47.462417744,193.5185512837486 -1994-08-07 16:44:05.707731356,193.59592502393136 -1994-08-07 16:44:47.395900520,193.66394020062216 -1994-08-07 16:45:54.846537383,193.62178032405015 -1994-08-07 16:46:48.526040668,193.77009561152101 -1994-08-07 16:48:00.550698110,193.54903378032205 -1994-08-07 16:49:06.813081870,193.5248851167554 -1994-08-07 16:50:10.392793101,193.4807046599272 -1994-08-07 16:51:20.373948386,193.50658954793624 -1994-08-07 16:52:42.364153730,193.58489517430476 -1994-08-07 16:54:24.475220308,193.50643945831553 -1994-08-07 16:56:03.429894489,193.72466197230418 -1994-08-07 16:57:07.522109743,193.66315601778547 -1994-08-07 16:58:19.771112168,193.6526056046781 -1994-08-07 16:59:12.330382377,193.5774691241722 -1994-08-07 17:00:15.682992521,193.57194585146243 -1994-08-07 17:01:06.996793691,193.776140134919 -1994-08-07 17:02:03.506371148,193.68766157959166 -1994-08-07 17:03:10.614276582,193.49255884053937 -1994-08-07 17:04:12.713137359,193.62602129546661 -1994-08-07 17:05:11.105347604,193.6977653873034 -1994-08-07 17:06:08.694168494,193.76860864084824 -1994-08-07 17:07:10.279609860,193.50288362365427 -1994-08-07 17:08:21.003764660,193.55578257769696 -1994-08-07 17:09:06.412524613,193.56869342489048 -1994-08-07 17:10:15.705612895,193.40978399466348 -1994-08-07 17:11:09.451766070,193.60182994885278 -1994-08-07 17:12:08.055520793,193.51766966463958 -1994-08-07 17:13:10.657325255,193.7243146064135 -1994-08-07 17:14:21.633020060,193.51257532827768 -1994-08-07 17:15:09.075371450,193.55232084181247 -1994-08-07 17:16:17.914468302,193.62712317582472 -1994-08-07 17:17:16.975978261,193.5158279508316 -1994-08-07 17:18:08.275302169,193.52467436568807 -1994-08-07 17:19:15.119707735,193.8068739097595 -1994-08-07 17:20:17.654157988,193.58466106079132 -1994-08-07 17:21:35.874256489,193.6432198883477 -1994-08-07 17:22:26.201697028,193.7402389587467 -1994-08-07 17:23:40.614190845,193.8251455313248 -1994-08-07 17:24:35.163885201,194.0484457054399 -1994-08-07 17:25:33.831846465,194.2188581110414 -1994-08-07 17:26:29.463057842,194.14930206264734 -1994-08-07 17:27:32.301951282,194.26559908122894 -1994-08-07 17:48:51.253848509,194.47916510244187 -1994-08-07 17:49:47.468746981,194.53818636632545 -1994-08-07 17:51:17.048530468,194.42192673115431 -1994-08-07 17:52:06.196063531,194.3566111817392 -1994-08-07 17:53:33.571594531,194.49275563859712 -1994-08-07 17:54:40.295857745,194.24161289020807 -1994-08-07 17:55:51.426795930,194.32646211685966 -1994-08-07 17:57:07.010027886,194.3129625711656 -1994-08-07 17:58:14.788227728,194.21563049881922 -1994-08-07 17:59:55.817232777,193.97104689092097 -1994-08-07 18:01:00.899387468,194.01433322885484 -1994-08-07 18:02:11.092720572,194.06259026020842 -1994-08-07 18:04:08.113392108,194.0429608417371 -1994-08-07 18:05:13.679114222,193.96529261399613 -1994-08-07 18:05:59.122553354,194.15157054129583 -1994-08-07 18:06:57.704020361,194.05899018808853 -1994-08-07 18:08:00.962755607,193.87481824713652 -1994-08-07 18:09:12.803710813,193.94393360920478 -1994-08-07 18:09:55.783676414,193.90634365346295 -1994-08-07 18:11:38.994491765,194.00426585723756 -1994-08-07 18:13:23.817088485,194.21330958450045 -1994-08-07 18:15:02.660036684,193.89671511569932 -1994-08-07 18:16:07.012693812,193.78174880032458 -1994-08-07 18:17:20.710118446,193.86633562744598 -1994-08-07 18:19:15.130507240,194.0741744560302 -1994-08-07 18:20:15.800301822,194.00207588928566 -1994-08-07 18:21:05.594143504,193.98954078505082 -1994-08-07 18:22:10.672244133,193.96015892979668 -1994-08-07 18:22:59.116797345,193.8059099694073 -1994-08-07 18:24:17.782794557,193.83533534410043 -1994-08-07 18:25:04.518698881,193.9507667786435 -1994-08-07 18:26:01.010477471,193.74729023512035 -1994-08-07 18:26:58.886134140,194.11217785860217 -1994-08-07 18:28:09.564788837,193.8869974441292 -1994-08-07 18:29:08.171733686,193.8225247414004 -1994-08-07 18:30:08.862763988,193.94471797299423 -1994-08-07 18:30:58.204733811,193.73271910461622 -1994-08-07 18:32:15.465370257,193.6686396836888 -1994-08-07 18:33:11.959829548,193.89010296389915 -1994-08-07 18:35:04.487313817,193.81745709215483 +1994-08-07 16:34:53.680090784,195.93216076519226 +1994-08-07 16:36:03.016073867,195.9345555668001 +1994-08-07 16:37:03.615114632,195.87220444573137 +1994-08-07 16:39:04.169996900,196.07260811118314 +1994-08-07 16:40:05.088750158,196.01833164040877 +1994-08-07 16:40:58.934617468,195.7868431574667 +1994-08-07 16:41:46.633021730,196.07718129064628 +1994-08-07 16:42:54.313644581,195.9604742525487 +1994-08-07 16:44:01.994806709,195.82502054758228 +1994-08-07 16:45:05.716604054,195.8614853805758 +1994-08-07 16:46:05.078540769,195.94226830524835 +1994-08-07 16:47:04.659109174,196.13169890225316 +1994-08-07 16:48:04.071253636,196.0508294592809 +1994-08-07 16:48:55.856609530,195.8157297978531 +1994-08-07 16:50:20.836102217,195.78676980238802 +1994-08-07 16:51:27.259242998,196.0502612429115 +1994-08-07 16:52:46.613962431,195.90082751638676 +1994-08-07 16:54:14.361648370,195.82840913303832 +1994-08-07 16:56:09.602491818,195.86111093441735 +1994-08-07 16:57:07.509307885,196.08105725975832 +1994-08-07 16:58:04.607812159,195.91595807409047 +1994-08-07 16:59:06.162651347,196.046190892334 +1994-08-07 17:00:04.860410721,195.93309106576953 +1994-08-07 17:01:19.693130234,195.989758713676 +1994-08-07 17:02:17.013786542,196.03859678014445 +1994-08-07 17:03:21.921253632,195.9350327844052 +1994-08-07 17:04:21.306422808,196.0611642713905 +1994-08-07 17:05:19.649468214,195.99686776883638 +1994-08-07 17:06:03.336005403,195.7951648783128 +1994-08-07 17:07:08.175640300,196.03696226597071 +1994-08-07 17:08:13.223933084,196.04961642590735 +1994-08-07 17:09:20.896721614,195.86822876151624 +1994-08-07 17:10:12.692196265,195.93634924463356 +1994-08-07 17:11:21.281490027,195.95654321002246 +1994-08-07 17:12:09.750840411,195.88120744267295 +1994-08-07 17:13:19.174570528,195.69717438944858 +1994-08-07 17:14:11.448114976,196.1061718342362 +1994-08-07 17:15:07.570426731,196.09399840812893 +1994-08-07 17:16:06.885048483,195.85406340253886 +1994-08-07 17:17:22.662212163,196.0792883943752 +1994-08-07 17:18:22.246108903,196.12897187602258 +1994-08-07 17:19:12.398538344,195.96210045263325 +1994-08-07 17:20:32.008320960,196.01346569313253 +1994-08-07 17:21:31.501188032,195.97332807650434 +1994-08-07 17:22:35.058031998,196.0923133910623 +1994-08-07 17:23:24.124859305,195.93741310885218 +1994-08-07 17:24:38.659325565,196.00513593642532 +1994-08-07 17:25:35.358147024,196.06989936406853 +1994-08-07 17:26:22.438476497,195.95165645941802 +1994-08-07 17:27:37.111814779,196.1077539353862 +1994-08-07 17:48:59.845122779,196.06867177276968 +1994-08-07 17:49:59.732989933,196.0571296425243 +1994-08-07 17:51:15.496873472,195.8685146657441 +1994-08-07 17:52:14.096936412,196.14841260340083 +1994-08-07 17:53:40.763706788,195.93029445362168 +1994-08-07 17:54:39.549902410,196.01744835625516 +1994-08-07 17:55:52.701116337,195.96104178186428 +1994-08-07 17:56:59.300944272,195.80391343108298 +1994-08-07 17:58:15.648708976,196.04582044920252 +1994-08-07 17:59:55.609780366,195.73148703736967 +1994-08-07 18:00:48.491480170,196.0329238945659 +1994-08-07 18:02:05.832518720,195.94609041324372 +1994-08-07 18:04:01.981196118,196.0870217358456 +1994-08-07 18:05:06.385341376,195.82819669263213 +1994-08-07 18:06:02.100215510,195.97316503312695 +1994-08-07 18:07:09.809500401,196.04851254822992 +1994-08-07 18:08:02.507913856,195.93280039947714 +1994-08-07 18:09:09.478070425,196.07991903067744 +1994-08-07 18:10:12.897665524,195.8858685223849 +1994-08-07 18:11:42.297675514,195.85546581194953 +1994-08-07 18:13:09.578201802,196.03228099205927 +1994-08-07 18:14:58.536619975,195.9259991178699 +1994-08-07 18:16:06.565053613,195.8178981389149 +1994-08-07 18:17:14.826306388,195.88467512468196 +1994-08-07 18:19:09.806901698,195.8420830166265 +1994-08-07 18:19:58.851825129,195.88215275337 +1994-08-07 18:21:01.335159759,195.96773411751616 +1994-08-07 18:22:16.682911258,195.9911857524558 +1994-08-07 18:23:07.233670407,196.0512707039425 +1994-08-07 18:24:11.527824425,195.9524087710378 +1994-08-07 18:25:01.436698737,195.89337228514202 +1994-08-07 18:26:06.247244282,195.8784401317867 +1994-08-07 18:27:05.656973313,196.05157296880046 +1994-08-07 18:28:00.856102141,195.9754065310726 +1994-08-07 18:29:00.407347532,195.97144918429876 +1994-08-07 18:30:13.017417982,195.99000755334848 +1994-08-07 18:31:10.919329130,195.83631114492073 +1994-08-07 18:32:00.620127477,196.06952449706685 +1994-08-07 18:33:10.975642642,195.9252297985391 +1994-08-07 18:34:56.951901187,195.98766363899963 diff --git a/tests/testdata/node_comparer_1.4.0a3.nc b/tests/testdata/node_comparer_1.4.0a3.nc new file mode 100644 index 000000000..78c8a2751 Binary files /dev/null and b/tests/testdata/node_comparer_1.4.0a3.nc differ