You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
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:
_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
SWMM link quantities (SWMM_LINK_FLOW, DEPTH, VELOCITY, CAPACITY, …) sit on single-gridpoint reaches, which produce no breakpoints, so they are unreachable for the same reason EPANET's are — from_res1d cannot reach EPANET link quantities — no breakpoint for single-gridpoint reaches #680. Node quantities are unaffected, so a SWMM network would be usable via NodeObservation from day one.
[COORDINATES] gives node x/y, but NetworkNode carries no coordinates today, so that is out of scope.
SWMM results are refused today because the reach connectivity is missing from the
.outfile. It is not a mikeio1d gap — the data is not in the file, and it is in the companion.inpinput file, which sits next to every SWMM result. Everything below was verified against the committedtests/testdata/swmm.outand theswmm.inpin DHI/mikeio1d's testdata.The topology is in the
.inp, and it pairs exactlyswmm.inpis in the same upstream directory our four existing fixtures came from (MIT, commitd937466, recorded intests/testdata/README.md), so it can be vendored the same way.swmm.outswmm.inp[CONDUITS][JUNCTIONS]+ 1[OUTFALLS](18)[CONDUITS]givesName From Node To Node Length, so it supplies both connectivity and real reach lengths:There is no fallback if the
.inpis absent. In the.outalone, every reach reportsStartNodeIndex == -1, nodexcoord/ycoordarenan,geometryisNodePoint(x=nan, y=nan),chainagesis a single-1e-30sentinel,lengthis0, andstructuresis empty. So no geometric reconstruction is possible — the.inpis required, not optional.Node and reach timeseries load fine, so only the topology is missing.
Blocker:
_simplify_colnamesraises on every SWMM nodeThis has to be fixed before any of the above matters. mikeio1d reports each pollutant as
SWMM_NODE_QUALwith no pollutant identity attached, so a node with two pollutants yields two identically named columns:_simplify_colnamesinsrc/modelskill/model/adapters/_res1d.pyrequires exactly one column per quantity, so it raises:The same happens for
SWMM_LINK_QUALon reaches.The only source of the pollutant names is the
.inp[POLLUTANTS]section, whichfrom_swmmwould already be reading. In the fixture it listsTSSthenLead, andLeaddeclares a co-pollutant fraction of0.2against TSS. The two columns hold15.720075and3.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 differsSWMM_NODE_QUAL_1/_2— always correct, tells the user nothingWorth checking the SWMM binary output format spec before choosing.
Sketch
Network.from_swmm(out, inp, *, nodes=None, reaches=None), withinprequired.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.reach.start_node.Res1DReachalready accepts the start/endRes1DNodeobjects and only usesreach.start_nodeto validate them, and it gained alengthoverride in Per-product Network constructors, with EPANET companion files #687 — so this is a narrower change than it looks.[PUMPS],[ORIFICES],[WEIRS],[OUTLETS]) all putName From-Node To-Nodein 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..outmoves out of_UNSUPPORTED_EXTENSIONSinto its own extension set, and_EXTENSION_CONSTRUCTORSgains thefrom_swmmmapping.test_every_mikeio1d_extension_is_accounted_forkeeps the tables honest.Related limits
SWMM_LINK_FLOW,DEPTH,VELOCITY,CAPACITY, …) sit on single-gridpoint reaches, which produce no breakpoints, so they are unreachable for the same reason EPANET's are — from_res1d cannot reach EPANET link quantities — no breakpoint for single-gridpoint reaches #680. Node quantities are unaffected, so a SWMM network would be usable viaNodeObservationfrom day one.[COORDINATES]gives node x/y, butNetworkNodecarries no coordinates today, so that is out of scope.Supersedes #688, which claimed this was blocked on a fixture that turned out to exist.