Skip to content

Support SWMM by reading topology from the companion .inp file #689

Description

@jpalm3r

SWMM results are refused today because the reach connectivity is missing from the .out file. It is not a mikeio1d gap — the data is not in the file, and it is in the companion .inp input file, which sits next to every SWMM result. Everything below was verified against the committed tests/testdata/swmm.out and the swmm.inp in DHI/mikeio1d's testdata.

The topology is in the .inp, and it pairs exactly

swmm.inp is in the same upstream directory our four existing fixtures came from (MIT, commit d937466, recorded in tests/testdata/README.md), so it can be vendored the same way.

swmm.out swmm.inp match
reaches / links 13 13 [CONDUITS] exact
nodes 14 13 [JUNCTIONS] + 1 [OUTFALLS] (18) exact

[CONDUITS] gives Name From Node To Node Length, so it supplies both connectivity and real reach lengths:

[CONDUITS]
;;Name           From Node        To Node          Length     Roughness  ...
;;-------------- ---------------- ---------------- ---------- ----------
1                9                10               400        0.01
4                19               20               200        0.01
...

There is no fallback if the .inp is absent. In the .out alone, every reach reports StartNodeIndex == -1, node xcoord/ycoord are nan, geometry is NodePoint(x=nan, y=nan), chainages is a single -1e-30 sentinel, length is 0, and structures is empty. So no geometric reconstruction is possible — the .inp is required, not optional.

Node and reach timeseries load fine, so only the topology is missing.

Blocker: _simplify_colnames raises on every SWMM node

This has to be fixed before any of the above matters. mikeio1d reports each pollutant as SWMM_NODE_QUAL with no pollutant identity attached, so a node with two pollutants yields two identically named columns:

>>> Res1D("swmm.out").nodes["9"].to_dataframe().columns
[..., 'SWMM_NODE_QUAL:9', 'SWMM_NODE_QUAL:9']

_simplify_colnames in src/modelskill/model/adapters/_res1d.py requires exactly one column per quantity, so it raises:

ValueError: There must be exactly one column per quantity, found [('SWMM_NODE_QUAL:9', 'SWMM_NODE_QUAL'), ('SWMM_NODE_QUAL:9', 'SWMM_NODE_QUAL')]

The same happens for SWMM_LINK_QUAL on reaches.

The only source of the pollutant names is the .inp [POLLUTANTS] section, which from_swmm would already be reading. In the fixture it lists TSS then Lead, and Lead declares a co-pollutant fraction of 0.2 against TSS. The two columns hold 15.720075 and 3.144015 — exactly 0.2×, which confirms column order follows [POLLUTANTS] order in this file.

What is not established is whether that ordering is guaranteed across SWMM versions and models. That decides the naming:

  • SWMM_NODE_QUAL_TSS / SWMM_NODE_QUAL_Lead — useful, but confidently wrong if the order ever differs
  • SWMM_NODE_QUAL_1 / _2 — always correct, tells the user nothing

Worth checking the SWMM binary output format spec before choosing.

Sketch

  • Network.from_swmm(out, inp, *, nodes=None, reaches=None), with inp required.
  • Extend src/modelskill/model/adapters/_inp.py (added for EPANET in Per-product Network constructors, with EPANET companion files #687) with a [CONDUITS] reader. The section reader already handles [SECTION] headers, ;-prefixed comments and whitespace-delimited rows.
  • The reach adapter needs connectivity from the parsed rows rather than from reach.start_node. Res1DReach already accepts the start/end Res1DNode objects and only uses reach.start_node to validate them, and it gained a length override in Per-product Network constructors, with EPANET companion files #687 — so this is a narrower change than it looks.
  • Other SWMM link types ([PUMPS], [ORIFICES], [WEIRS], [OUTLETS]) all put Name From-Node To-Node in their first three columns and carry no length. The fixture has none of them, so supporting them would be unverified; decide whether to read them or refuse a model that uses them.
  • .out moves out of _UNSUPPORTED_EXTENSIONS into its own extension set, and _EXTENSION_CONSTRUCTORS gains the from_swmm mapping. test_every_mikeio1d_extension_is_accounted_for keeps the tables honest.

Related limits

Supersedes #688, which claimed this was blocked on a fixture that turned out to exist.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions