swmmx 0.0.10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. swmmx-0.0.10/LICENSE +22 -0
  2. swmmx-0.0.10/MANIFEST.in +7 -0
  3. swmmx-0.0.10/PKG-INFO +326 -0
  4. swmmx-0.0.10/README.md +299 -0
  5. swmmx-0.0.10/pyproject.toml +54 -0
  6. swmmx-0.0.10/setup.cfg +4 -0
  7. swmmx-0.0.10/src/swmmx/__init__.py +72 -0
  8. swmmx-0.0.10/src/swmmx/api.py +887 -0
  9. swmmx-0.0.10/src/swmmx/bin/linux/libswmm5.so +0 -0
  10. swmmx-0.0.10/src/swmmx/bin/macos/.gitkeep +0 -0
  11. swmmx-0.0.10/src/swmmx/bin/win64/swmm5.dll +0 -0
  12. swmmx-0.0.10/src/swmmx/bin/win64/vcomp140.dll +0 -0
  13. swmmx-0.0.10/src/swmmx/core/__init__.py +4 -0
  14. swmmx-0.0.10/src/swmmx/core/errors.py +124 -0
  15. swmmx-0.0.10/src/swmmx/counts.py +187 -0
  16. swmmx-0.0.10/src/swmmx/elements.py +1515 -0
  17. swmmx-0.0.10/src/swmmx/engine.py +206 -0
  18. swmmx-0.0.10/src/swmmx/errors.py +8 -0
  19. swmmx-0.0.10/src/swmmx/export/__init__.py +6 -0
  20. swmmx-0.0.10/src/swmmx/export/base.py +259 -0
  21. swmmx-0.0.10/src/swmmx/export/csv.py +58 -0
  22. swmmx-0.0.10/src/swmmx/export/excel.py +75 -0
  23. swmmx-0.0.10/src/swmmx/export/gis.py +369 -0
  24. swmmx-0.0.10/src/swmmx/export/utils.py +521 -0
  25. swmmx-0.0.10/src/swmmx/inp.py +416 -0
  26. swmmx-0.0.10/src/swmmx/models.py +81 -0
  27. swmmx-0.0.10/src/swmmx/parameters.py +576 -0
  28. swmmx-0.0.10/src/swmmx/plotting/__init__.py +8 -0
  29. swmmx-0.0.10/src/swmmx/plotting/layout.py +394 -0
  30. swmmx-0.0.10/src/swmmx/plotting/profile.py +514 -0
  31. swmmx-0.0.10/src/swmmx/plotting/styles.py +337 -0
  32. swmmx-0.0.10/src/swmmx/plotting/timeseries.py +368 -0
  33. swmmx-0.0.10/src/swmmx/plotting/utils.py +168 -0
  34. swmmx-0.0.10/src/swmmx/py.typed +1 -0
  35. swmmx-0.0.10/src/swmmx/results.py +202 -0
  36. swmmx-0.0.10/src/swmmx/schema.py +143 -0
  37. swmmx-0.0.10/src/swmmx/schemas/all.csv +349 -0
  38. swmmx-0.0.10/src/swmmx/schemas/parameters.csv +563 -0
  39. swmmx-0.0.10/src/swmmx/time.py +105 -0
  40. swmmx-0.0.10/src/swmmx/validation.py +249 -0
  41. swmmx-0.0.10/src/swmmx.egg-info/PKG-INFO +326 -0
  42. swmmx-0.0.10/src/swmmx.egg-info/SOURCES.txt +49 -0
  43. swmmx-0.0.10/src/swmmx.egg-info/dependency_links.txt +1 -0
  44. swmmx-0.0.10/src/swmmx.egg-info/requires.txt +4 -0
  45. swmmx-0.0.10/src/swmmx.egg-info/top_level.txt +1 -0
  46. swmmx-0.0.10/tests/test_add_remove_api.py +180 -0
  47. swmmx-0.0.10/tests/test_count_api.py +98 -0
  48. swmmx-0.0.10/tests/test_export_api.py +120 -0
  49. swmmx-0.0.10/tests/test_packaging_metadata.py +20 -0
  50. swmmx-0.0.10/tests/test_plotting_api.py +130 -0
  51. swmmx-0.0.10/tests/test_public_api.py +169 -0
swmmx-0.0.10/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 swmmx contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,7 @@
1
+ include README.md
2
+ include LICENSE
3
+ include parameters.csv
4
+ include all.csv
5
+ recursive-include example *.inp
6
+ recursive-include src/swmmx/bin *
7
+ recursive-include src/swmmx/schemas *.csv
swmmx-0.0.10/PKG-INFO ADDED
@@ -0,0 +1,326 @@
1
+ Metadata-Version: 2.4
2
+ Name: swmmx
3
+ Version: 0.0.10
4
+ Summary: A professional, cross-platform Python toolkit for EPA SWMM.
5
+ Author: Mohammadali Geranmehr
6
+ License: MIT
7
+ Keywords: swmm,stormwater,hydrology,hydraulics,epa
8
+ Classifier: Development Status :: 2 - Pre-Alpha
9
+ Classifier: Intended Audience :: Science/Research
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: Microsoft :: Windows
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Operating System :: MacOS
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Scientific/Engineering
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: numpy<2,>=1.26
23
+ Requires-Dist: pandas<3,>=2.1
24
+ Requires-Dist: matplotlib<3.9,>=3.8
25
+ Requires-Dist: networkx<3.4,>=3
26
+ Dynamic: license-file
27
+
28
+ # swmmx
29
+
30
+ `swmmx` A Python Toolkit for Building, Editing, Running, Visualizing, and Exporting EPA SWMM Models:
31
+
32
+ ```python
33
+ from swmmx import swmm
34
+
35
+ m = swmm("example/example.inp")
36
+ print(m.time.count())
37
+ result = m.run()
38
+ print(m.time.count_run())
39
+ ```
40
+
41
+ Version `0.0.10` currently provides:
42
+
43
+ - `swmm(path=None, new=None, flow_unit=None, custom_dll_path=None)`
44
+ - `m.time.vector()`, `m.time.count()`, `m.time.vector_run()`, `m.time.count_run()`
45
+ - structured parameter access through `m.get.<main_category>.<sub_category>()` and `m.set.<main_category>.<sub_category>()`
46
+ - discoverable object counts through `m.count.<main_category>()`, `m.count.model()`, `m.count.model_dict()`, and `m.count.model_df()`
47
+ - editable model construction through `m.add.<category>.<element_type>()` and `m.remove.<category>.<element_type>()`
48
+ - matplotlib plotting through `m.plot_layout()`, `m.plot_timeseries.<category>.<sub_category>()`, and `m.plot_profile.*`
49
+ - external-format export through `m.export.gis()`, `m.export.csv()`, and `m.export.excel()`
50
+ - `m.save()`, `m.run()`, `m.runs()`, `m.validate()`, `m.log()`, and `m.clone()`
51
+ - lazy native-engine loading for bundled Windows/Linux engines plus custom engine paths
52
+ - preserving `.inp` parsing/writing that keeps comments, unknown sections, and section order whenever possible
53
+
54
+ Constructor examples:
55
+
56
+ ```python
57
+ m = swmm("example/example.inp") # open an existing model
58
+ m = swmm() # new SI model, LPS by default
59
+ m = swmm(new="SI", flow_unit="CMS") # new SI model
60
+ m = swmm(new="US", flow_unit="GPM") # new US model
61
+ ```
62
+
63
+ ## Installation note
64
+
65
+ `swmmx` uses conservative desktop-friendly dependency ranges:
66
+
67
+ - `numpy>=1.26,<2`
68
+ - `pandas>=2.1,<3`
69
+ - `matplotlib>=3.8,<3.9`
70
+ - `networkx>=3,<3.4`
71
+
72
+ These bounds avoid pulling the newest scientific stack into an older desktop environment just because `swmmx` was installed there.
73
+
74
+ If an existing Spyder/Anaconda environment already shows a NumPy ABI message such as `A module that was compiled using NumPy 1.x cannot be run in NumPy 2`, close Spyder first, remove the user-site copies that are shadowing the base environment, then reinstall `swmmx`:
75
+
76
+ ```bash
77
+ python -m pip uninstall -y numpy pandas matplotlib networkx
78
+ python -m pip install path/to/swmmx-0.0.10-py3-none-any.whl
79
+ ```
80
+
81
+ Restart Spyder or its kernel after changing compiled packages.
82
+
83
+ ## Native engines
84
+
85
+ The package includes the supplied bundled engines:
86
+
87
+ - Windows 64-bit: `swmm5.dll`
88
+ - Linux 64-bit: `libswmm5.so`
89
+ - macOS: reserved path only for now; provide a custom engine path until a GitHub Actions build is added
90
+
91
+ ## Time semantics
92
+
93
+ `m.time.vector()` mirrors SWMM reporting behavior: it starts at the first report interval after `REPORT_START_*` and proceeds through `END_*`.
94
+
95
+ ```python
96
+ frame = m.time.vector() # pandas DataFrame with timestamp index
97
+ ```
98
+
99
+ Run-time vectors use the actual period count read from the `.out` file after the engine finishes.
100
+ `m.time.count()` remains the expected pre-run count; `m.time.count_run()` is the actual post-run count and requires results.
101
+
102
+ ## Parameter access
103
+
104
+ ```python
105
+ lengths = m.get.conduit.length()
106
+ one_length = m.get.conduit.length("P001")
107
+ flow = m.get.link.flow(ids=["P001", "P005"], format="df")
108
+
109
+ m.set.conduit.roughness(0.013)
110
+ m.set.conduit.roughness([0.013, 0.014], ids=["P001", "P005"])
111
+ ```
112
+
113
+ Supported getters default to NumPy output; `format="df"` gives pandas output. Writable parameters appear in `dir(m.set.<category>)`; attempting to set a derived or result parameter raises a read-only error.
114
+
115
+ ## Counts
116
+
117
+ ```python
118
+ m.count.conduit()
119
+ m.count.node()
120
+ m.count.subcatchment()
121
+
122
+ total = m.count.model()
123
+ by_type = m.count.model_dict()
124
+ summary = m.count.model_df()
125
+ ```
126
+
127
+ Count helpers take no `ids` or `format` arguments and always reflect the current in-memory model, including unsaved add/remove edits. `m.count.model()` totals detailed element types without double-counting composite rollups such as `node` and `link`; the dictionary and DataFrame summaries expose the detailed counts behind that total.
128
+
129
+ ## Add and remove elements
130
+
131
+ ```python
132
+ from swmmx import swmm
133
+
134
+ m = swmm(new="SI")
135
+
136
+ m.add.node.junction("J1", invert_elevation=10, max_depth=3)
137
+ m.add.node.outfall("OUT1", invert_elevation=9, type="FREE")
138
+
139
+ m.add.link.conduit(
140
+ "C1",
141
+ from_node="J1",
142
+ to_node="OUT1",
143
+ length=100,
144
+ roughness=0.013,
145
+ shape="CIRCULAR",
146
+ diameter=1.0,
147
+ )
148
+
149
+ # Referenced objects must exist before they are used.
150
+ m.add.time.time_series(
151
+ "Rain1",
152
+ data=[
153
+ ("2026-01-01 00:00", 0.0),
154
+ ("2026-01-01 00:05", 5.0),
155
+ ],
156
+ )
157
+ m.add.hydrology.rain_gage(
158
+ "RG1",
159
+ format="INTENSITY",
160
+ interval="00:05",
161
+ source_type="TIMESERIES",
162
+ time_series="Rain1",
163
+ )
164
+
165
+ m.save("new_model.inp")
166
+
167
+ m.remove.link.conduit("C1")
168
+ ```
169
+
170
+ The add API validates IDs, required fields, numeric values, enums, and references before it writes EPA SWMM records. The remove API validates dependencies before deletion; by default it refuses unsafe removals, while `force=True` performs only conservative cascades that are known to remain valid. For example, removing a node with `force=True` can remove dependent conduits, but unsupported cascades raise a clear error instead of leaving broken references.
171
+
172
+ When coordinates are omitted, `swmmx` uses practical display defaults:
173
+
174
+ - new nodes use `x = max(node x) + 100`, `y = max(node y)`, or `(0, 0)` when the model has no node coordinates;
175
+ - new rain gages use the maximum mapped `x` and `y`, or `(0, 0)` when the model has no map coordinates;
176
+ - new subcatchments use the minimum mapped `x` and `y`, or `(0, 0)` when the model has no map coordinates.
177
+
178
+ Every add or remove operation sets `m.modified` to `True`. If the model already had results, the edit also sets `m.results_stale` to `True` and invalidates the old result accessors until the model is run again.
179
+
180
+ Generic fallbacks are also available:
181
+
182
+ ```python
183
+ m.add_element("node", "junction", "J2", invert_elevation=11, max_depth=2)
184
+ m.remove_element("node", "junction", "J2")
185
+ ```
186
+
187
+ ## Plotting
188
+
189
+ `swmmx` uses matplotlib directly for network maps, result time series, and longitudinal profiles:
190
+
191
+ ```python
192
+ m.plot_layout()
193
+
194
+ m.plot_layout(
195
+ title="Drainage Network",
196
+ legend=True,
197
+ grid=True,
198
+ axis=True,
199
+ )
200
+
201
+ m.plot_layout(
202
+ links={
203
+ "color": {
204
+ "by": "parameter",
205
+ "category": "conduit",
206
+ "variable": "roughness",
207
+ "mode": "continuous",
208
+ "cmap": "viridis",
209
+ }
210
+ }
211
+ )
212
+
213
+ m.plot_timeseries.link.flow(["C1", "C2"])
214
+
215
+ m.plot_profile.nodes(
216
+ "J1",
217
+ "OUT1",
218
+ show_hgl=True,
219
+ aggregation="max",
220
+ )
221
+ ```
222
+
223
+ `m.plot_layout()` draws mapped subcatchments, links, nodes, and rain gages from the model coordinates. Layer dictionaries support ordinary static styling as well as data-driven styling from fixed input parameters, simulation results, or your own ID-to-value mappings. Result-driven styles require a completed run; user-data styles are useful for classes such as risk bands, inspection status, or scenario groups.
224
+
225
+ `m.plot_timeseries.<category>.<sub_category>()` routes through the result API and plots one or many timestamped series with matplotlib. `m.plot_profile.nodes()`, `.links()`, and `.longest()` build directed hydraulic paths and render geometry-first longitudinal profiles, with HGL/water overlays available after a run.
226
+
227
+ All plotting calls return `(fig, ax)`, accept `ax=` for composition, and support `save_path=` / `save_format=`. Common errors are explicit: layout plots need coordinates, result-based plots need `m.run()`, invalid IDs raise `UnknownIDError`, and disconnected profile requests raise `NoPathError`.
228
+
229
+ ## Export
230
+
231
+ ```python
232
+ m.export.gis()
233
+
234
+ m.export.gis(
235
+ path="exports/gis",
236
+ elements=["nodes", "links", "subcatchments"],
237
+ )
238
+
239
+ m.export.csv(
240
+ path="exports/csv",
241
+ elements="all",
242
+ time_step=-1,
243
+ )
244
+
245
+ m.export.excel(
246
+ path="exports",
247
+ file_name="model_export.xlsx",
248
+ )
249
+ ```
250
+
251
+ If `path` is omitted, exports go beside the model file when one exists, otherwise into the current working directory. `file_name` controls a single workbook name or the prefix used for multi-file CSV/GIS exports. `elements` accepts named tables, groups such as `hydrology` or `quality`, or `"all"`.
252
+
253
+ When results are available, `time_step=-1` attaches the last simulated snapshot to result-capable tables. Without results, exports continue with parameter-only tables and a warning by default; use `strict_results=True` when missing results should be treated as an error. CSV writes one UTF-8 file per table, while Excel writes one workbook with one sheet per selected table.
254
+
255
+ GIS export writes spatial layers for nodes, links, subcatchments, and rain gages. It requires the optional packages `geopandas` and `shapely` (`pip install geopandas shapely`). Existing outputs are protected by default; pass `overwrite=True` only when replacing files is intentional.
256
+
257
+ ## Public parameter catalog
258
+
259
+ The public dotted API is organized into the following categories and subcategories:
260
+
261
+ - **`option_general`**: `flow_units`, `infiltration_model`, `flow_routing`, `link_offsets`, `force_main_equation`, `allow_ponding`, `minimum_slope`, `skip_steady_state`, `system_flow_tolerance`, `lateral_flow_tolerance`
262
+ - **`option_process`**: `ignore_rainfall`, `ignore_snowmelt`, `ignore_groundwater`, `ignore_rdii`, `ignore_routing`, `ignore_quality`
263
+ - **`option_date_time`**: `start_date`, `start_time`, `end_date`, `end_time`, `report_start_date`, `report_start_time`, `report_step`, `wet_step`, `dry_step`, `routing_step`, `rule_step`, `sweep_start`, `sweep_end`, `dry_days`
264
+ - **`option_dynamic_wave`**: `inertial_damping`, `normal_flow_limited`, `surcharge_method`, `variable_step`, `minimum_step`, `lengthening_step`, `minimum_surface_area`, `head_tolerance`, `maximum_trials`, `threads`
265
+ - **`rain_gage`**: `id`, `count`, `format`, `interval`, `snow_catch_factor`, `source_type`, `time_series`, `filename`, `station`, `units`, `rainfall`
266
+ - **`subcatchment`**: `id`, `count`, `rain_gage`, `outlet`, `area`, `width`, `slope`, `impervious_percent`, `curb_length`, `snow_pack`, `tag`, `polygon`, `centroid`, `rainfall`, `snow_depth`, `evaporation`, `infiltration`, `runoff`, `groundwater_flow`, `groundwater_elevation`, `soil_moisture`, `pollutant_concentration`, `n_impervious`, `n_pervious`, `depression_storage_impervious`, `depression_storage_pervious`, `zero_depression_storage_impervious_percent`, `subarea_routing`, `percent_routed`
267
+ - **`infiltration_horton`**: `maximum_rate`, `minimum_rate`, `decay`, `dry_time`, `maximum_volume`
268
+ - **`infiltration_green_ampt`**: `suction_head`, `hydraulic_conductivity`, `initial_moisture_deficit`
269
+ - **`infiltration_curve_number`**: `curve_number`, `conductivity`, `dry_time`
270
+ - **`node`**: `id`, `count`, `type`, `invert_elevation`, `max_depth`, `initial_depth`, `surcharge_depth`, `ponded_area`, `tag`, `coordinate`, `external_inflow`, `dry_weather_flow`, `treatment`, `depth`, `head`, `volume`, `lateral_inflow`, `total_inflow`, `flooding`, `overflow`, `pollutant_concentration`
271
+ - **`junction`**: `id`, `count`, `invert_elevation`, `max_depth`, `initial_depth`, `surcharge_depth`, `ponded_area`
272
+ - **`outfall`**: `id`, `count`, `invert_elevation`, `type`, `fixed_stage`, `tidal_curve`, `time_series`, `tide_gate`, `route_to`
273
+ - **`flow_divider`**: `id`, `count`, `invert_elevation`, `max_depth`, `initial_depth`, `surcharge_depth`, `ponded_area`, `type`, `diverted_link`, `cutoff_flow`, `diversion_curve`, `weir_height`, `weir_coefficient`
274
+ - **`storage_unit`**: `id`, `count`, `invert_elevation`, `max_depth`, `initial_depth`, `storage_curve_type`, `storage_curve`, `area`, `area_coefficient`, `area_exponent`, `area_constant`, `evaporation_factor`, `seepage_loss`
275
+ - **`link`**: `id`, `count`, `type`, `from_node`, `to_node`, `inlet_offset`, `outlet_offset`, `initial_flow`, `maximum_flow`, `flap_gate`, `tag`, `vertices`, `flow`, `depth`, `velocity`, `volume`, `capacity`, `setting`, `pollutant_concentration`
276
+ - **`conduit`**: `id`, `count`, `from_node`, `to_node`, `length`, `roughness`, `inlet_offset`, `outlet_offset`, `initial_flow`, `maximum_flow`, `shape`, `geometry`, `barrels`, `culvert_code`, `entry_loss`, `exit_loss`, `average_loss`, `flap_gate`, `seepage_rate`, `slope`, `full_area`, `full_depth`, `hydraulic_radius`, `full_flow`, `normal_depth`, `critical_depth`, `flow`, `depth`, `velocity`, `capacity`
277
+ - **`pump`**: `id`, `count`, `from_node`, `to_node`, `curve`, `initial_status`, `startup_depth`, `shutoff_depth`, `flow`, `status`, `setting`, `energy`
278
+ - **`orifice`**: `id`, `count`, `from_node`, `to_node`, `type`, `shape`, `height`, `width`, `offset`, `discharge_coefficient`, `flap_gate`, `open_close_time`, `flow`, `setting`
279
+ - **`weir`**: `id`, `count`, `from_node`, `to_node`, `type`, `crest_height`, `length`, `side_slope`, `discharge_coefficient`, `flap_gate`, `end_contractions`, `end_coefficient`, `surcharge`, `road_width`, `road_surface`, `flow`, `setting`
280
+ - **`outlet`**: `id`, `count`, `from_node`, `to_node`, `offset`, `flap_gate`, `rating_type`, `curve`, `coefficient`, `exponent`, `flow`, `setting`
281
+ - **`cross_section`**: `link`, `shape`, `geometry_1`, `geometry_2`, `geometry_3`, `geometry_4`, `barrels`, `culvert_code`, `height`, `width`, `side_slope`, `shape_curve`
282
+ - **`transect`**: `id`, `count`, `roughness_left`, `roughness_right`, `roughness_channel`, `left_bank`, `right_bank`, `stations`, `elevations`, `modifiers`
283
+ - **`curve`**: `id`, `count`, `type`, `x`, `y`, `points`
284
+ - **`coordinate`**: `node_coordinates`, `subcatchment_coordinates`, `link_vertices`, `polygons`, `labels`, `map_dimensions`, `map_units`
285
+ - **`street`**: `id`, `count`, `crown_width`, `curb_height`, `cross_slope`, `roughness`, `depression_storage`, `gutter_width`, `gutter_slope`, `spread`
286
+ - **`inlet`**: `id`, `count`, `type`, `grate_length`, `grate_width`, `grate_type`, `curb_length`, `curb_height`, `slotted_length`, `slotted_width`, `captured_flow`
287
+ - **`inlet_usage`**: `node`, `inlet`, `conduit`, `number`, `clogging_factor`, `flow_restriction`
288
+ - **`lid_control`**: `id`, `count`, `type`
289
+ - **`lid_surface`**: `storage_depth`, `vegetation_fraction`, `roughness`, `slope`, `side_slope`
290
+ - **`lid_pavement`**: `thickness`, `void_ratio`, `impervious_surface_fraction`, `permeability`, `clogging_factor`
291
+ - **`lid_soil`**: `thickness`, `porosity`, `field_capacity`, `wilting_point`, `conductivity`, `conductivity_slope`, `suction_head`
292
+ - **`lid_storage`**: `height`, `void_ratio`, `seepage_rate`, `clogging_factor`
293
+ - **`lid_drain`**: `coefficient`, `exponent`, `offset_height`, `delay`, `open_level`, `closed_level`, `control_curve`
294
+ - **`lid_usage`**: `subcatchment`, `lid_control`, `number`, `area`, `width`, `initial_saturation`, `from_impervious_percent`, `from_pervious_percent`, `outlet`, `drain_to`, `inflow`, `evaporation`, `infiltration`, `surface_outflow`, `drain_outflow`, `storage`
295
+ - **`aquifer`**: `id`, `count`, `porosity`, `wilting_point`, `field_capacity`, `conductivity`, `conductivity_slope`, `tension_slope`, `upper_evaporation_fraction`, `lower_evaporation_depth`, `lower_groundwater_loss_rate`, `bottom_elevation`, `water_table_elevation`, `unsaturated_moisture`, `upper_evaporation_pattern`
296
+ - **`groundwater`**: `subcatchment`, `aquifer`, `node`, `surface_elevation`, `a1`, `b1`, `a2`, `b2`, `a3`, `fixed_depth`, `threshold_elevation`, `lateral_flow_equation`, `deep_flow_equation`
297
+ - **`snow_pack`**: `id`, `count`, `plowable_fraction`, `impervious_fraction`, `pervious_fraction`, `minimum_melt_coefficient`, `maximum_melt_coefficient`, `base_temperature`, `free_water_capacity_fraction`, `initial_snow_depth`, `initial_free_water`, `depth_at_100_percent_cover`, `removal_depth`, `fraction_to_impervious`, `fraction_to_pervious`, `fraction_to_immediate_melt`, `fraction_to_subcatchment`, `fraction_to_outflow`, `destination_subcatchment`
298
+ - **`climate`**: `temperature_time_series`, `evaporation_type`, `evaporation_constant`, `evaporation_monthly`, `evaporation_time_series`, `evaporation_recovery_pattern`, `evaporation_dry_only`, `wind_speed_type`, `wind_speed_monthly`, `snowmelt_parameters`, `areal_depletion_impervious`, `areal_depletion_pervious`
299
+ - **`climate_adjustment`**: `temperature`, `evaporation`, `rainfall`, `conductivity`
300
+ - **`pollutant`**: `id`, `count`, `units`, `rain_concentration`, `groundwater_concentration`, `rdii_concentration`, `decay_coefficient`, `snow_only`, `co_pollutant`, `co_pollutant_fraction`, `dry_weather_flow_concentration`, `initial_concentration`
301
+ - **`land_use`**: `id`, `count`, `sweeping_interval`, `sweeping_availability`, `last_swept`
302
+ - **`coverage`**: `subcatchment`, `land_use`, `percent`
303
+ - **`loading`**: `subcatchment`, `pollutant`, `initial_buildup`
304
+ - **`buildup`**: `land_use`, `pollutant`, `function`, `maximum_buildup`, `rate_constant`, `power`, `normalizer`
305
+ - **`washoff`**: `land_use`, `pollutant`, `function`, `coefficient`, `exponent`, `cleaning_efficiency`, `bmp_efficiency`
306
+ - **`treatment`**: `node`, `pollutant`, `expression`
307
+ - **`time_series`**: `id`, `count`, `datetime`, `values`, `filename`, `description`
308
+ - **`time_pattern`**: `id`, `count`, `type`, `multipliers`
309
+ - **`external_inflow`**: `node`, `constituent`, `time_series`, `type`, `units_factor`, `scale_factor`, `baseline`, `pattern`
310
+ - **`dry_weather_flow`**: `node`, `constituent`, `average_value`, `monthly_pattern`, `daily_pattern`, `hourly_pattern`, `weekend_pattern`
311
+ - **`rdii`**: `node`, `unit_hydrograph`, `sewer_area`
312
+ - **`unit_hydrograph`**: `id`, `count`, `rain_gage`, `month`, `short_term_r`, `short_term_t`, `short_term_k`, `medium_term_r`, `medium_term_t`, `medium_term_k`, `long_term_r`, `long_term_t`, `long_term_k`
313
+ - **`control_rule`**: `id`, `count`, `text`, `conditions`, `actions`, `priority`, `enabled`, `action_log`
314
+ - **`interface_file`**: `rainfall`, `runoff`, `hotstart`, `rdii`, `inflow`, `outflow`, `use_file`, `save_file`
315
+ - **`system_result`**: `air_temperature`, `rainfall`, `snow_depth`, `evaporation`, `infiltration`, `runoff`, `dry_weather_inflow`, `groundwater_inflow`, `rdii_inflow`, `direct_inflow`, `total_lateral_inflow`, `flooding`, `outfall_flow`, `storage_volume`, `pollutant_loading`
316
+ - **`summary`**: `model`, `counts`, `options`, `subcatchment_runoff`, `subcatchment_washoff`, `node_depth`, `node_inflow`, `node_flooding`, `node_surcharge`, `storage_volume`, `outfall_loading`, `link_flow`, `link_velocity`, `conduit_surcharge`, `pump_operation`, `lid_performance`, `runoff_continuity`, `flow_routing_continuity`, `quality_routing_continuity`, `validation_issues`
317
+
318
+ ## Validation
319
+
320
+ ```python
321
+ validation = m.validate()
322
+ print(validation.ok)
323
+ print(validation.to_frame())
324
+ ```
325
+
326
+ The first validator checks duplicate IDs, missing required options, invalid unit values, missing nodes/links, conduit endpoints, and several common cross-section/reference errors.
swmmx-0.0.10/README.md ADDED
@@ -0,0 +1,299 @@
1
+ # swmmx
2
+
3
+ `swmmx` A Python Toolkit for Building, Editing, Running, Visualizing, and Exporting EPA SWMM Models:
4
+
5
+ ```python
6
+ from swmmx import swmm
7
+
8
+ m = swmm("example/example.inp")
9
+ print(m.time.count())
10
+ result = m.run()
11
+ print(m.time.count_run())
12
+ ```
13
+
14
+ Version `0.0.10` currently provides:
15
+
16
+ - `swmm(path=None, new=None, flow_unit=None, custom_dll_path=None)`
17
+ - `m.time.vector()`, `m.time.count()`, `m.time.vector_run()`, `m.time.count_run()`
18
+ - structured parameter access through `m.get.<main_category>.<sub_category>()` and `m.set.<main_category>.<sub_category>()`
19
+ - discoverable object counts through `m.count.<main_category>()`, `m.count.model()`, `m.count.model_dict()`, and `m.count.model_df()`
20
+ - editable model construction through `m.add.<category>.<element_type>()` and `m.remove.<category>.<element_type>()`
21
+ - matplotlib plotting through `m.plot_layout()`, `m.plot_timeseries.<category>.<sub_category>()`, and `m.plot_profile.*`
22
+ - external-format export through `m.export.gis()`, `m.export.csv()`, and `m.export.excel()`
23
+ - `m.save()`, `m.run()`, `m.runs()`, `m.validate()`, `m.log()`, and `m.clone()`
24
+ - lazy native-engine loading for bundled Windows/Linux engines plus custom engine paths
25
+ - preserving `.inp` parsing/writing that keeps comments, unknown sections, and section order whenever possible
26
+
27
+ Constructor examples:
28
+
29
+ ```python
30
+ m = swmm("example/example.inp") # open an existing model
31
+ m = swmm() # new SI model, LPS by default
32
+ m = swmm(new="SI", flow_unit="CMS") # new SI model
33
+ m = swmm(new="US", flow_unit="GPM") # new US model
34
+ ```
35
+
36
+ ## Installation note
37
+
38
+ `swmmx` uses conservative desktop-friendly dependency ranges:
39
+
40
+ - `numpy>=1.26,<2`
41
+ - `pandas>=2.1,<3`
42
+ - `matplotlib>=3.8,<3.9`
43
+ - `networkx>=3,<3.4`
44
+
45
+ These bounds avoid pulling the newest scientific stack into an older desktop environment just because `swmmx` was installed there.
46
+
47
+ If an existing Spyder/Anaconda environment already shows a NumPy ABI message such as `A module that was compiled using NumPy 1.x cannot be run in NumPy 2`, close Spyder first, remove the user-site copies that are shadowing the base environment, then reinstall `swmmx`:
48
+
49
+ ```bash
50
+ python -m pip uninstall -y numpy pandas matplotlib networkx
51
+ python -m pip install path/to/swmmx-0.0.10-py3-none-any.whl
52
+ ```
53
+
54
+ Restart Spyder or its kernel after changing compiled packages.
55
+
56
+ ## Native engines
57
+
58
+ The package includes the supplied bundled engines:
59
+
60
+ - Windows 64-bit: `swmm5.dll`
61
+ - Linux 64-bit: `libswmm5.so`
62
+ - macOS: reserved path only for now; provide a custom engine path until a GitHub Actions build is added
63
+
64
+ ## Time semantics
65
+
66
+ `m.time.vector()` mirrors SWMM reporting behavior: it starts at the first report interval after `REPORT_START_*` and proceeds through `END_*`.
67
+
68
+ ```python
69
+ frame = m.time.vector() # pandas DataFrame with timestamp index
70
+ ```
71
+
72
+ Run-time vectors use the actual period count read from the `.out` file after the engine finishes.
73
+ `m.time.count()` remains the expected pre-run count; `m.time.count_run()` is the actual post-run count and requires results.
74
+
75
+ ## Parameter access
76
+
77
+ ```python
78
+ lengths = m.get.conduit.length()
79
+ one_length = m.get.conduit.length("P001")
80
+ flow = m.get.link.flow(ids=["P001", "P005"], format="df")
81
+
82
+ m.set.conduit.roughness(0.013)
83
+ m.set.conduit.roughness([0.013, 0.014], ids=["P001", "P005"])
84
+ ```
85
+
86
+ Supported getters default to NumPy output; `format="df"` gives pandas output. Writable parameters appear in `dir(m.set.<category>)`; attempting to set a derived or result parameter raises a read-only error.
87
+
88
+ ## Counts
89
+
90
+ ```python
91
+ m.count.conduit()
92
+ m.count.node()
93
+ m.count.subcatchment()
94
+
95
+ total = m.count.model()
96
+ by_type = m.count.model_dict()
97
+ summary = m.count.model_df()
98
+ ```
99
+
100
+ Count helpers take no `ids` or `format` arguments and always reflect the current in-memory model, including unsaved add/remove edits. `m.count.model()` totals detailed element types without double-counting composite rollups such as `node` and `link`; the dictionary and DataFrame summaries expose the detailed counts behind that total.
101
+
102
+ ## Add and remove elements
103
+
104
+ ```python
105
+ from swmmx import swmm
106
+
107
+ m = swmm(new="SI")
108
+
109
+ m.add.node.junction("J1", invert_elevation=10, max_depth=3)
110
+ m.add.node.outfall("OUT1", invert_elevation=9, type="FREE")
111
+
112
+ m.add.link.conduit(
113
+ "C1",
114
+ from_node="J1",
115
+ to_node="OUT1",
116
+ length=100,
117
+ roughness=0.013,
118
+ shape="CIRCULAR",
119
+ diameter=1.0,
120
+ )
121
+
122
+ # Referenced objects must exist before they are used.
123
+ m.add.time.time_series(
124
+ "Rain1",
125
+ data=[
126
+ ("2026-01-01 00:00", 0.0),
127
+ ("2026-01-01 00:05", 5.0),
128
+ ],
129
+ )
130
+ m.add.hydrology.rain_gage(
131
+ "RG1",
132
+ format="INTENSITY",
133
+ interval="00:05",
134
+ source_type="TIMESERIES",
135
+ time_series="Rain1",
136
+ )
137
+
138
+ m.save("new_model.inp")
139
+
140
+ m.remove.link.conduit("C1")
141
+ ```
142
+
143
+ The add API validates IDs, required fields, numeric values, enums, and references before it writes EPA SWMM records. The remove API validates dependencies before deletion; by default it refuses unsafe removals, while `force=True` performs only conservative cascades that are known to remain valid. For example, removing a node with `force=True` can remove dependent conduits, but unsupported cascades raise a clear error instead of leaving broken references.
144
+
145
+ When coordinates are omitted, `swmmx` uses practical display defaults:
146
+
147
+ - new nodes use `x = max(node x) + 100`, `y = max(node y)`, or `(0, 0)` when the model has no node coordinates;
148
+ - new rain gages use the maximum mapped `x` and `y`, or `(0, 0)` when the model has no map coordinates;
149
+ - new subcatchments use the minimum mapped `x` and `y`, or `(0, 0)` when the model has no map coordinates.
150
+
151
+ Every add or remove operation sets `m.modified` to `True`. If the model already had results, the edit also sets `m.results_stale` to `True` and invalidates the old result accessors until the model is run again.
152
+
153
+ Generic fallbacks are also available:
154
+
155
+ ```python
156
+ m.add_element("node", "junction", "J2", invert_elevation=11, max_depth=2)
157
+ m.remove_element("node", "junction", "J2")
158
+ ```
159
+
160
+ ## Plotting
161
+
162
+ `swmmx` uses matplotlib directly for network maps, result time series, and longitudinal profiles:
163
+
164
+ ```python
165
+ m.plot_layout()
166
+
167
+ m.plot_layout(
168
+ title="Drainage Network",
169
+ legend=True,
170
+ grid=True,
171
+ axis=True,
172
+ )
173
+
174
+ m.plot_layout(
175
+ links={
176
+ "color": {
177
+ "by": "parameter",
178
+ "category": "conduit",
179
+ "variable": "roughness",
180
+ "mode": "continuous",
181
+ "cmap": "viridis",
182
+ }
183
+ }
184
+ )
185
+
186
+ m.plot_timeseries.link.flow(["C1", "C2"])
187
+
188
+ m.plot_profile.nodes(
189
+ "J1",
190
+ "OUT1",
191
+ show_hgl=True,
192
+ aggregation="max",
193
+ )
194
+ ```
195
+
196
+ `m.plot_layout()` draws mapped subcatchments, links, nodes, and rain gages from the model coordinates. Layer dictionaries support ordinary static styling as well as data-driven styling from fixed input parameters, simulation results, or your own ID-to-value mappings. Result-driven styles require a completed run; user-data styles are useful for classes such as risk bands, inspection status, or scenario groups.
197
+
198
+ `m.plot_timeseries.<category>.<sub_category>()` routes through the result API and plots one or many timestamped series with matplotlib. `m.plot_profile.nodes()`, `.links()`, and `.longest()` build directed hydraulic paths and render geometry-first longitudinal profiles, with HGL/water overlays available after a run.
199
+
200
+ All plotting calls return `(fig, ax)`, accept `ax=` for composition, and support `save_path=` / `save_format=`. Common errors are explicit: layout plots need coordinates, result-based plots need `m.run()`, invalid IDs raise `UnknownIDError`, and disconnected profile requests raise `NoPathError`.
201
+
202
+ ## Export
203
+
204
+ ```python
205
+ m.export.gis()
206
+
207
+ m.export.gis(
208
+ path="exports/gis",
209
+ elements=["nodes", "links", "subcatchments"],
210
+ )
211
+
212
+ m.export.csv(
213
+ path="exports/csv",
214
+ elements="all",
215
+ time_step=-1,
216
+ )
217
+
218
+ m.export.excel(
219
+ path="exports",
220
+ file_name="model_export.xlsx",
221
+ )
222
+ ```
223
+
224
+ If `path` is omitted, exports go beside the model file when one exists, otherwise into the current working directory. `file_name` controls a single workbook name or the prefix used for multi-file CSV/GIS exports. `elements` accepts named tables, groups such as `hydrology` or `quality`, or `"all"`.
225
+
226
+ When results are available, `time_step=-1` attaches the last simulated snapshot to result-capable tables. Without results, exports continue with parameter-only tables and a warning by default; use `strict_results=True` when missing results should be treated as an error. CSV writes one UTF-8 file per table, while Excel writes one workbook with one sheet per selected table.
227
+
228
+ GIS export writes spatial layers for nodes, links, subcatchments, and rain gages. It requires the optional packages `geopandas` and `shapely` (`pip install geopandas shapely`). Existing outputs are protected by default; pass `overwrite=True` only when replacing files is intentional.
229
+
230
+ ## Public parameter catalog
231
+
232
+ The public dotted API is organized into the following categories and subcategories:
233
+
234
+ - **`option_general`**: `flow_units`, `infiltration_model`, `flow_routing`, `link_offsets`, `force_main_equation`, `allow_ponding`, `minimum_slope`, `skip_steady_state`, `system_flow_tolerance`, `lateral_flow_tolerance`
235
+ - **`option_process`**: `ignore_rainfall`, `ignore_snowmelt`, `ignore_groundwater`, `ignore_rdii`, `ignore_routing`, `ignore_quality`
236
+ - **`option_date_time`**: `start_date`, `start_time`, `end_date`, `end_time`, `report_start_date`, `report_start_time`, `report_step`, `wet_step`, `dry_step`, `routing_step`, `rule_step`, `sweep_start`, `sweep_end`, `dry_days`
237
+ - **`option_dynamic_wave`**: `inertial_damping`, `normal_flow_limited`, `surcharge_method`, `variable_step`, `minimum_step`, `lengthening_step`, `minimum_surface_area`, `head_tolerance`, `maximum_trials`, `threads`
238
+ - **`rain_gage`**: `id`, `count`, `format`, `interval`, `snow_catch_factor`, `source_type`, `time_series`, `filename`, `station`, `units`, `rainfall`
239
+ - **`subcatchment`**: `id`, `count`, `rain_gage`, `outlet`, `area`, `width`, `slope`, `impervious_percent`, `curb_length`, `snow_pack`, `tag`, `polygon`, `centroid`, `rainfall`, `snow_depth`, `evaporation`, `infiltration`, `runoff`, `groundwater_flow`, `groundwater_elevation`, `soil_moisture`, `pollutant_concentration`, `n_impervious`, `n_pervious`, `depression_storage_impervious`, `depression_storage_pervious`, `zero_depression_storage_impervious_percent`, `subarea_routing`, `percent_routed`
240
+ - **`infiltration_horton`**: `maximum_rate`, `minimum_rate`, `decay`, `dry_time`, `maximum_volume`
241
+ - **`infiltration_green_ampt`**: `suction_head`, `hydraulic_conductivity`, `initial_moisture_deficit`
242
+ - **`infiltration_curve_number`**: `curve_number`, `conductivity`, `dry_time`
243
+ - **`node`**: `id`, `count`, `type`, `invert_elevation`, `max_depth`, `initial_depth`, `surcharge_depth`, `ponded_area`, `tag`, `coordinate`, `external_inflow`, `dry_weather_flow`, `treatment`, `depth`, `head`, `volume`, `lateral_inflow`, `total_inflow`, `flooding`, `overflow`, `pollutant_concentration`
244
+ - **`junction`**: `id`, `count`, `invert_elevation`, `max_depth`, `initial_depth`, `surcharge_depth`, `ponded_area`
245
+ - **`outfall`**: `id`, `count`, `invert_elevation`, `type`, `fixed_stage`, `tidal_curve`, `time_series`, `tide_gate`, `route_to`
246
+ - **`flow_divider`**: `id`, `count`, `invert_elevation`, `max_depth`, `initial_depth`, `surcharge_depth`, `ponded_area`, `type`, `diverted_link`, `cutoff_flow`, `diversion_curve`, `weir_height`, `weir_coefficient`
247
+ - **`storage_unit`**: `id`, `count`, `invert_elevation`, `max_depth`, `initial_depth`, `storage_curve_type`, `storage_curve`, `area`, `area_coefficient`, `area_exponent`, `area_constant`, `evaporation_factor`, `seepage_loss`
248
+ - **`link`**: `id`, `count`, `type`, `from_node`, `to_node`, `inlet_offset`, `outlet_offset`, `initial_flow`, `maximum_flow`, `flap_gate`, `tag`, `vertices`, `flow`, `depth`, `velocity`, `volume`, `capacity`, `setting`, `pollutant_concentration`
249
+ - **`conduit`**: `id`, `count`, `from_node`, `to_node`, `length`, `roughness`, `inlet_offset`, `outlet_offset`, `initial_flow`, `maximum_flow`, `shape`, `geometry`, `barrels`, `culvert_code`, `entry_loss`, `exit_loss`, `average_loss`, `flap_gate`, `seepage_rate`, `slope`, `full_area`, `full_depth`, `hydraulic_radius`, `full_flow`, `normal_depth`, `critical_depth`, `flow`, `depth`, `velocity`, `capacity`
250
+ - **`pump`**: `id`, `count`, `from_node`, `to_node`, `curve`, `initial_status`, `startup_depth`, `shutoff_depth`, `flow`, `status`, `setting`, `energy`
251
+ - **`orifice`**: `id`, `count`, `from_node`, `to_node`, `type`, `shape`, `height`, `width`, `offset`, `discharge_coefficient`, `flap_gate`, `open_close_time`, `flow`, `setting`
252
+ - **`weir`**: `id`, `count`, `from_node`, `to_node`, `type`, `crest_height`, `length`, `side_slope`, `discharge_coefficient`, `flap_gate`, `end_contractions`, `end_coefficient`, `surcharge`, `road_width`, `road_surface`, `flow`, `setting`
253
+ - **`outlet`**: `id`, `count`, `from_node`, `to_node`, `offset`, `flap_gate`, `rating_type`, `curve`, `coefficient`, `exponent`, `flow`, `setting`
254
+ - **`cross_section`**: `link`, `shape`, `geometry_1`, `geometry_2`, `geometry_3`, `geometry_4`, `barrels`, `culvert_code`, `height`, `width`, `side_slope`, `shape_curve`
255
+ - **`transect`**: `id`, `count`, `roughness_left`, `roughness_right`, `roughness_channel`, `left_bank`, `right_bank`, `stations`, `elevations`, `modifiers`
256
+ - **`curve`**: `id`, `count`, `type`, `x`, `y`, `points`
257
+ - **`coordinate`**: `node_coordinates`, `subcatchment_coordinates`, `link_vertices`, `polygons`, `labels`, `map_dimensions`, `map_units`
258
+ - **`street`**: `id`, `count`, `crown_width`, `curb_height`, `cross_slope`, `roughness`, `depression_storage`, `gutter_width`, `gutter_slope`, `spread`
259
+ - **`inlet`**: `id`, `count`, `type`, `grate_length`, `grate_width`, `grate_type`, `curb_length`, `curb_height`, `slotted_length`, `slotted_width`, `captured_flow`
260
+ - **`inlet_usage`**: `node`, `inlet`, `conduit`, `number`, `clogging_factor`, `flow_restriction`
261
+ - **`lid_control`**: `id`, `count`, `type`
262
+ - **`lid_surface`**: `storage_depth`, `vegetation_fraction`, `roughness`, `slope`, `side_slope`
263
+ - **`lid_pavement`**: `thickness`, `void_ratio`, `impervious_surface_fraction`, `permeability`, `clogging_factor`
264
+ - **`lid_soil`**: `thickness`, `porosity`, `field_capacity`, `wilting_point`, `conductivity`, `conductivity_slope`, `suction_head`
265
+ - **`lid_storage`**: `height`, `void_ratio`, `seepage_rate`, `clogging_factor`
266
+ - **`lid_drain`**: `coefficient`, `exponent`, `offset_height`, `delay`, `open_level`, `closed_level`, `control_curve`
267
+ - **`lid_usage`**: `subcatchment`, `lid_control`, `number`, `area`, `width`, `initial_saturation`, `from_impervious_percent`, `from_pervious_percent`, `outlet`, `drain_to`, `inflow`, `evaporation`, `infiltration`, `surface_outflow`, `drain_outflow`, `storage`
268
+ - **`aquifer`**: `id`, `count`, `porosity`, `wilting_point`, `field_capacity`, `conductivity`, `conductivity_slope`, `tension_slope`, `upper_evaporation_fraction`, `lower_evaporation_depth`, `lower_groundwater_loss_rate`, `bottom_elevation`, `water_table_elevation`, `unsaturated_moisture`, `upper_evaporation_pattern`
269
+ - **`groundwater`**: `subcatchment`, `aquifer`, `node`, `surface_elevation`, `a1`, `b1`, `a2`, `b2`, `a3`, `fixed_depth`, `threshold_elevation`, `lateral_flow_equation`, `deep_flow_equation`
270
+ - **`snow_pack`**: `id`, `count`, `plowable_fraction`, `impervious_fraction`, `pervious_fraction`, `minimum_melt_coefficient`, `maximum_melt_coefficient`, `base_temperature`, `free_water_capacity_fraction`, `initial_snow_depth`, `initial_free_water`, `depth_at_100_percent_cover`, `removal_depth`, `fraction_to_impervious`, `fraction_to_pervious`, `fraction_to_immediate_melt`, `fraction_to_subcatchment`, `fraction_to_outflow`, `destination_subcatchment`
271
+ - **`climate`**: `temperature_time_series`, `evaporation_type`, `evaporation_constant`, `evaporation_monthly`, `evaporation_time_series`, `evaporation_recovery_pattern`, `evaporation_dry_only`, `wind_speed_type`, `wind_speed_monthly`, `snowmelt_parameters`, `areal_depletion_impervious`, `areal_depletion_pervious`
272
+ - **`climate_adjustment`**: `temperature`, `evaporation`, `rainfall`, `conductivity`
273
+ - **`pollutant`**: `id`, `count`, `units`, `rain_concentration`, `groundwater_concentration`, `rdii_concentration`, `decay_coefficient`, `snow_only`, `co_pollutant`, `co_pollutant_fraction`, `dry_weather_flow_concentration`, `initial_concentration`
274
+ - **`land_use`**: `id`, `count`, `sweeping_interval`, `sweeping_availability`, `last_swept`
275
+ - **`coverage`**: `subcatchment`, `land_use`, `percent`
276
+ - **`loading`**: `subcatchment`, `pollutant`, `initial_buildup`
277
+ - **`buildup`**: `land_use`, `pollutant`, `function`, `maximum_buildup`, `rate_constant`, `power`, `normalizer`
278
+ - **`washoff`**: `land_use`, `pollutant`, `function`, `coefficient`, `exponent`, `cleaning_efficiency`, `bmp_efficiency`
279
+ - **`treatment`**: `node`, `pollutant`, `expression`
280
+ - **`time_series`**: `id`, `count`, `datetime`, `values`, `filename`, `description`
281
+ - **`time_pattern`**: `id`, `count`, `type`, `multipliers`
282
+ - **`external_inflow`**: `node`, `constituent`, `time_series`, `type`, `units_factor`, `scale_factor`, `baseline`, `pattern`
283
+ - **`dry_weather_flow`**: `node`, `constituent`, `average_value`, `monthly_pattern`, `daily_pattern`, `hourly_pattern`, `weekend_pattern`
284
+ - **`rdii`**: `node`, `unit_hydrograph`, `sewer_area`
285
+ - **`unit_hydrograph`**: `id`, `count`, `rain_gage`, `month`, `short_term_r`, `short_term_t`, `short_term_k`, `medium_term_r`, `medium_term_t`, `medium_term_k`, `long_term_r`, `long_term_t`, `long_term_k`
286
+ - **`control_rule`**: `id`, `count`, `text`, `conditions`, `actions`, `priority`, `enabled`, `action_log`
287
+ - **`interface_file`**: `rainfall`, `runoff`, `hotstart`, `rdii`, `inflow`, `outflow`, `use_file`, `save_file`
288
+ - **`system_result`**: `air_temperature`, `rainfall`, `snow_depth`, `evaporation`, `infiltration`, `runoff`, `dry_weather_inflow`, `groundwater_inflow`, `rdii_inflow`, `direct_inflow`, `total_lateral_inflow`, `flooding`, `outfall_flow`, `storage_volume`, `pollutant_loading`
289
+ - **`summary`**: `model`, `counts`, `options`, `subcatchment_runoff`, `subcatchment_washoff`, `node_depth`, `node_inflow`, `node_flooding`, `node_surcharge`, `storage_volume`, `outfall_loading`, `link_flow`, `link_velocity`, `conduit_surcharge`, `pump_operation`, `lid_performance`, `runoff_continuity`, `flow_routing_continuity`, `quality_routing_continuity`, `validation_issues`
290
+
291
+ ## Validation
292
+
293
+ ```python
294
+ validation = m.validate()
295
+ print(validation.ok)
296
+ print(validation.to_frame())
297
+ ```
298
+
299
+ The first validator checks duplicate IDs, missing required options, invalid unit values, missing nodes/links, conduit endpoints, and several common cross-section/reference errors.