RHEM NoDb Mod
Prepares and runs the Rangeland Hydrology and Erosion Model (RHEM) for each TOPAZ subcatchment in a WEPPcloud run.
See also: AGENTS.md for NoDb locking/caching expectations and debugging tips.
Overview
This mod is implemented as two NoDb singletons:
Rhem(rhem.nodb): builds per-hillslope RHEM inputs under<wd>/rhem/and runs the compiled RHEM binary in parallel.RhemPost(rhempost.nodb): reads RHEM*.sumoutputs and aggregates them into watershed-wide annual and return-period summaries used by reports and map queries.
At a glance, it:
- Combines watershed slope geometry, soil texture, rangeland cover fractions, and CLIGEN climate into RHEM
*.parand*.stminputs. - Writes one
*.runfile per hillslope and executeswepppy/rhem/bin/rhem_v23concurrently (per-hillslope logs go to*.err). - Produces
*.sumoutputs consumed byRhemPostand exposed through WEPPcloud report/query endpoints.
Workflow
Preconditions
Rhem.prep_hillslopes() expects these NoDb controllers to already be in a usable state:
Watershed: watershed abstracted and subcatchment summaries available.Soils:soils.domsoil_dpopulated and soil textures resolvable.RangelandCover:coversmapping available for each TOPAZ ID.Climate: per-subcatchment climate summaries and.clifiles present.
If required inputs are missing, Rhem raises RhemNoDbLockedException with a message describing what must be run first.
Stages
Rhem.clean()- Recreates
<wd>/rhem/runs/and<wd>/rhem/output/.
- Recreates
Rhem.prep_hillslopes()- For each TOPAZ subcatchment, creates:
hill_<id>.parviawepppy.rhem.make_parameter_file(...)hill_<id>.stmfrom the CLIGEN.clifile (Rust-accelerated whenwepppyo3is installed; otherwise uses the PythonClimateFile.make_storm_filefallback)hill_<id>.runviawepppy.rhem.make_hillslope_run(...)
- For each TOPAZ subcatchment, creates:
Rhem.run_hillslopes()- Runs
wepppy.rhem.run_hillslope(...)for each TOPAZ ID (threaded). - Calls
RhemPost.run_post()to populaterhempost.nodbaggregates.
- Runs
API / Usage
Python (direct NoDb usage)
from wepppy.nodb.mods.rhem import Rhem, RhemPost
wd = "/path/to/run"
rhem = Rhem.getInstance(wd)
rhem.clean()
rhem.prep_hillslopes()
rhem.run_hillslopes()
rhempost = RhemPost.getInstance(wd)
print(rhempost.watershed_annuals)
print(rhempost.query_sub_val("runoff")) # "runoff" | "sed_yield" | "soil_loss"
RQ-engine entrypoint (WEPPcloud)
The rq-engine exposes a FastAPI route that enqueues the RHEM job:
POST /runs/{runid}/{config}/run-rhem(JWT scoperq:enqueue)
The request body may include these booleans (all default to true when omitted):
| Field | Meaning |
|---|---|
clean / clean_hillslopes |
Reset rhem/runs and rhem/output before prep/run |
prep / prep_hillslopes |
Generate *.par, *.stm, and *.run files |
run / run_hillslopes |
Execute the RHEM binary and run RhemPost |
Outputs
Files on disk
| Path | Produced by | Notes |
|---|---|---|
<wd>/rhem/runs/hill_<id>.par |
Rhem.prep_hillslopes() |
Parameter file (cover/soil/slope derived) |
<wd>/rhem/runs/hill_<id>.stm |
Rhem.prep_hillslopes() |
Storm file derived from CLIGEN .cli |
<wd>/rhem/runs/hill_<id>.run |
Rhem.prep_hillslopes() |
Batch runner input to rhem_v23 -b |
<wd>/rhem/runs/hill_<id>.err |
Rhem.run_hillslopes() |
Captures stdout/stderr for that hillslope run |
<wd>/rhem/output/hill_<id>.sum |
RHEM binary | Parsed by RhemPost |
Aggregates in rhempost.nodb
After RhemPost.run_post():
hill_summaries[topaz_id]: per-hillslopeRhemSummaryobjects (may be missing annuals when a.sumis incomplete).watershed_annuals: watershed totals and normalized values (includesmm/yrconversions).watershed_ret_freqsandret_freq_periods: return-period series aggregated across hillslopes.missing_summaries_count: how many hillslopes did not parse into annuals.
Integration Points
- Depends on (inputs)
wepppy.nodb.core:Watershed,Soils,Climatewepppy.nodb.mods.rangeland_cover:RangelandCoverwepppy.topo.watershed_abstraction.SlopeFile: slope geometry (.slp)
- Model execution
wepppy.rhem: input file writers +run_hillslope(...)wrapper aroundwepppy/rhem/bin/rhem_v23- Optional accelerator:
wepppyo3.climate.make_rhem_storm_file(when installed)
- Used by (outputs)
- WEPPcloud reports and queries via
wepppy/weppcloud/routes/nodb_api/rhem_bp.py - Export flows via
wepppy/export/arc_export.py(readsRhemPostwhen present) - Preflight checklist via
services/preflight2(checks theRedisPreptimestamp forrun_rhem)
- WEPPcloud reports and queries via
Developer Notes
prep_hillslopes()is CPU-parallel; keep per-hillslope work self-contained (it is safe to extendprepare_single(...)when new inputs map cleanly to a TOPAZ ID).- Storm generation is optional-Rust: the code prefers
wepppyo3when available and otherwise falls back to Python CLIGEN parsing. Avoid adding “silent” extra fallbacks beyond this established boundary. RhemPost.run_post()assumeshill_<id>.sumexists for every TOPAZ ID and tracks missing/invalid annuals viamissing_summaries_count.
Operational Notes
- The RHEM binary is executed with a per-hillslope timeout (currently
200seconds). Timeouts and other subprocess errors propagate up and abort the batch. wepppy.rhem.run_hillslope(...)writes logs tohill_<id>.errbut does not currently check the subprocess exit code; treat missing/empty*.sumoutputs as a primary signal of failure.- If you suspect stale NoDb state during debugging, use the NoDb refresh guidance in AGENTS.md and re-run
Rhem.clean()before a fresh prep.
Further Reading
wepppy/nodb/mods/rhem/rhem.py(controller and orchestration)wepppy/nodb/mods/rhem/rhempost.py(post-processing and aggregates)wepppy/rhem/rhem.py(binary wrapper and input writers)wepppy/microservices/rq_engine/rhem_routes.py(rq-engine enqueue API)wepppy/weppcloud/routes/nodb_api/rhem_bp.py(report/query endpoints)