Model editing (deletion + type conversion)#

Note

Engine: OpenSWMM 6 — refactored. Documents openswmm.engine.ModelEditor. This is unique to the v6 engine — there is no SWMM 5 equivalent.

The ModelEditor mutates an already-parsed model in place: delete objects, convert object types, and analyse the cascading impact those operations would have on the rest of the network.

Use it for:

  • Cleaning up auto-generated networks (drop orphan nodes, dangling conduits).

  • Converting nodes between types (junction → storage, junction → outfall).

  • Pre-flighting a destructive change (“show me what would break if I delete this conduit”).

Reference: openswmm_edit.h.


Class signature#

class ModelEditor:
    def __init__(self, engine: object) -> None: ...
  • engine — pass the live Solver (or its underlying engine handle).

The editor must be applied to a Solver in the OPENED state, so that the model is parsed and addressable but routing has not yet started.


Helper return types#

  • ImpactEntry — one row in the report of “what this change would affect”: kind, id, description.

  • ConversionResult — outcome of a type conversion: success, warnings, fields that had to be defaulted.

Both are plain data classes with __repr__ so they display well during interactive exploration.


Key methods#

Counts (read-only properties — no parentheses)#

Property

Returns

node_count / link_count / subcatch_count / gage_count / table_count

Number of objects of each kind currently in the model. Access as attributes — e.g. editor.node_count, not editor.node_count().

Time control (typed datetime properties)#

Property

Value

start_datetime

Simulation start as datetime.datetime (read/write).

end_datetime

Simulation end as datetime.datetime (read/write).

report_start_datetime

Report start as datetime.datetime (read/write).

Impact analysis (read-only — no mutation)#

Method

Returns

analyze_node_impact(id_or_idx)()

List of ImpactEntry rows that would be affected by deletion.

analyze_link_impact(id_or_idx)()

Same, for a link.

analyze_subcatch_impact(id_or_idx)()

Same, for a subcatchment.

analyze_gage_impact(id_or_idx)()

Same, for a rain gage.

analyze_table_impact(id_or_idx)()

Same, for a table / curve.

analyze_transect_impact(idx)()

Same, for a transect.

Deletion#

Method

Returns

delete_node(id_or_idx)()

List of ImpactEntry for what was actually deleted / reconnected.

delete_link(id_or_idx)()

Same, for a link.

delete_subcatch(id_or_idx)()

Same, for a subcatchment.

delete_gage(id_or_idx)()

Same, for a rain gage.

delete_table(id_or_idx)()

Same, for a table / curve.

delete_transect(idx)()

Same, for a transect.

Type conversion#

Method

Returns

convert_node(id_or_idx, new_type)()

ConversionResult summarising the conversion.

convert_link(id_or_idx, new_type)()

Same, for a link.


End-to-end example#

from openswmm.engine import Solver, ModelEditor, NodeType

s = Solver("model.inp", "edited.rpt", "edited.out")
s.create()
s.open()                                  # state == OPENED

editor = ModelEditor(s)
print(f"before: {editor.node_count} nodes, {editor.link_count} links")

# Pre-flight: what would happen if we deleted node X?
impacts = editor.analyze_node_impact("X")
for entry in impacts:
    print(f"  would affect {entry}")

# Actually delete:
actual = editor.delete_node("X")
print(f"deleted {len(actual)} dependent items")

# Convert J5 from a junction to a storage node:
result = editor.convert_node("J5", NodeType.STORAGE)
print("conversion:", result)

from openswmm.engine import EngineState

s.initialize()
s.start()
while s.state == EngineState.RUNNING:
    if s.step() != 0:
        break
s.end()
s.report()
s.close()
s.destroy()

Common recipes#

Pre-flight a deletion before committing#

impacts = editor.analyze_node_impact("STORM_INLET_42")
if any(e.kind == "outlet" for e in impacts):
    print("Refusing to delete — node is the outlet for a subcatchment")
else:
    editor.delete_node("STORM_INLET_42")

Bulk-delete every dangling node#

from openswmm.engine import Nodes, Links

nodes = Nodes(s)
links = Links(s)
referenced = set()
for i in range(links.count()):
    referenced.add(links.get_from_node(i))
    referenced.add(links.get_to_node(i))

dangling = [
    nodes.get_id(i) for i in range(nodes.count())
    if i not in referenced
]
for nid in dangling:
    editor.delete_node(nid)
print(f"deleted {len(dangling)} dangling nodes")

Convert a junction to a storage node#

from openswmm.engine import NodeType

result = editor.convert_node("J5", NodeType.STORAGE)
if result.warnings:
    print("warnings:", result.warnings)
# Storage parameters default to placeholder values — set them now:
nodes = Nodes(s)
nodes.set_storage_functional("J5", a=10.0, b=0.0, c=0.0)

Save the edited model#

# ModelEditor mutates the in-memory model; the on-disk .inp is
# unchanged.  Persist via Solver.model_write:
s.model_write("model_edited.inp")

EngineState requirements & exceptions#

Method group

Required state

Notes

all editor methods

solver in OPENED

The editor refuses to mutate a running solver.

analyze_*

any state

Read-only; safe to call at any time.

Common EngineError codes:

  • NOT_FOUND — object id not in the model.

  • INVALID_INDEX — integer index out of range.

  • INVALID_TYPEconvert_node() to / from an unsupported combination (e.g. converting a divider to an outfall when the topology forbids it).


See also#