Skip to content

HMS-RAS Coupled Forecast Execution

This notebook demonstrates the pattern for coupling HEC-HMS and HEC-RAS in a forecast workflow. HEC-HMS (Hydrologic Modeling System) computes rainfall-runoff to produce flow hydrographs, which are then fed as upstream boundary conditions into a HEC-RAS hydraulic model.

Requirements: - ras-commander — HEC-RAS automation (this library) - hms-commander — HEC-HMS automation (separate package, optional)

When hms-commander is not installed, this notebook shows the API call patterns so you can implement the coupling once you have both packages available.

Typical use cases: - Real-time flood forecasting - Design storm inundation mapping - Scenario analysis (existing vs. future conditions) - Climate change impact assessment

Python
# =============================================================================
# DEVELOPMENT MODE TOGGLE
# =============================================================================
USE_LOCAL_SOURCE = True  # <-- TOGGLE THIS

if USE_LOCAL_SOURCE:
    import sys
    from pathlib import Path
    local_path = str(Path.cwd().parent)
    if local_path not in sys.path:
        sys.path.insert(0, local_path)
    print(f"LOCAL SOURCE MODE: Loading from {local_path}/ras_commander")
else:
    print("PIP PACKAGE MODE: Loading installed ras-commander")

import ras_commander
print(f"Loaded: {ras_commander.__file__}")
Python
from pathlib import Path
import pandas as pd
import numpy as np
from datetime import datetime, timedelta

# ras-commander imports
from ras_commander import init_ras_project, RasCmdr, RasPlan

# hms-commander imports (optional - for coupled workflows)
try:
    from hms_commander import init_hms_project, HmsCmdr, HmsControl, HmsResults
    HMS_AVAILABLE = True
    print("hms-commander available - coupled workflow enabled")
except ImportError:
    HMS_AVAILABLE = False
    print("hms-commander not installed - showing pattern only")
    print("Install with: pip install hms-commander")

What You'll Learn

  • Configure an HMS forecast run using HmsControl.set_time_window()
  • Execute HMS headless via HmsCmdr.compute_run()
  • Extract outlet hydrographs from HMS DSS output using HmsResults.get_outflow_timeseries()
  • Feed HMS flow output as a RAS upstream boundary condition
  • Align time windows between HMS and RAS
  • Execute the RAS hydraulic model with updated boundary conditions

The HMS-RAS Coupling Pattern

HMS and RAS are separate programs with separate project files. The coupling happens in Python:

  1. HMS receives precipitation and computes runoff (rainfall-runoff transformation)
  2. HMS writes flow hydrographs to a DSS file
  3. Python reads the HMS DSS output
  4. Python writes the flow data as a RAS boundary condition (DSS or inline)
  5. RAS executes the hydraulic simulation using the HMS-derived flows

There is no direct file linkage between HMS and RAS — the connection is orchestrated in user scripts.

Python
print("HMS-RAS Coupled Forecast Pipeline:")
print("=" * 65)
print()
print("  [Weather Data / Precipitation Forecast]")
print("       |")
print("       v")
print("  +-------------------+")
print("  | HEC-HMS           |  <- hms-commander")
print("  | (Rainfall-Runoff) |")
print("  +-------------------+")
print("       |")
print("       | Flow hydrograph at watershed outlet (DSS)")
print("       v")
print("  [Python Orchestration Script]")
print("       |  - Read HMS DSS output")
print("       |  - Write to RAS boundary condition")
print("       |  - Align simulation time windows")
print("       v")
print("  +-------------------+")
print("  | HEC-RAS           |  <- ras-commander")
print("  | (Hydraulic Model) |")
print("  +-------------------+")
print("       |")
print("       v")
print("  [Flood Results: WSE, Depth, Velocity, Inundation Extent]")
print()
print("Key handoff: HMS outlet flow -> RAS upstream boundary condition")

Example 1: Simple HMS-RAS Handoff

A single HMS watershed outlet feeds a single RAS upstream boundary condition. This is the most common configuration for a simple stream reach with one contributing watershed.

Step 1: Configure HMS Forecast Dates

Python
# Define forecast window
forecast_start = datetime(2024, 7, 15, 0, 0)
forecast_end = forecast_start + timedelta(hours=72)

print(f"Forecast Period: {forecast_start} to {forecast_end}")
print(f"Duration: {(forecast_end - forecast_start).total_seconds() / 3600:.0f} hours")
print()

if HMS_AVAILABLE:
    # Initialize HMS project
    # hms = init_hms_project("/path/to/hms_project")

    # Update control spec dates
    # HmsControl.set_time_window(
    #     start_date=forecast_start.strftime("%d%B%Y %H:%M"),
    #     end_date=forecast_end.strftime("%d%B%Y %H:%M")
    # )
    print("HMS time window would be updated via HmsControl.set_time_window()")
else:
    print("Pattern (hms-commander):")
    print("  hms = init_hms_project('/path/to/hms_project')")
    print(f"  HmsControl.set_time_window(")
    print(f"      start_date='{forecast_start.strftime('%d%B%Y %H:%M')}',")
    print(f"      end_date='{forecast_end.strftime('%d%B%Y %H:%M')}'")
    print(f"  )")

Step 2: Execute HMS Forecast

Python
if HMS_AVAILABLE:
    # Execute HMS forecast run
    # HmsCmdr.compute_run("Run 1", max_memory="8G")
    print("HMS execution would run via HmsCmdr.compute_run()")
else:
    print("Pattern (hms-commander):")
    print("  HmsCmdr.compute_run('Forecast Run', max_memory='8G')")
    print()
    print("HMS execution details:")
    print("  - Runs HEC-HMS headless via Jython CLI")
    print("  - No GUI required for automated workflows")
    print("  - Configurable JVM memory allocation")
    print("  - Returns execution status and log messages")
    print("  - DSS output written to project results folder")

Step 3: Extract HMS Outlet Hydrograph

Python
if HMS_AVAILABLE:
    # Extract flow at HMS outlet
    # dss_file = Path("hms_project/results.dss")
    # flow_df = HmsResults.get_outflow_timeseries(dss_file, "Outlet")
    print("HMS results would be extracted via HmsResults.get_outflow_timeseries()")
else:
    # Create synthetic flow data for demonstration
    np.random.seed(42)
    times = pd.date_range(forecast_start, forecast_end, freq='1h')

    # Simulate a typical flood hydrograph
    n = len(times)
    peak_idx = n // 3  # Peak at 1/3 of duration (rising limb steeper than recession)
    t_rise = np.linspace(0, np.pi / 2, peak_idx)
    t_fall = np.linspace(np.pi / 2, np.pi, n - peak_idx)

    base_flow = 150.0
    peak_flow = 5200.0
    rising = base_flow + (peak_flow - base_flow) * np.sin(t_rise)
    falling = base_flow + (peak_flow - base_flow) * np.sin(t_fall)
    flows = np.concatenate([rising, falling])

    # Add minor random variation to simulate real HMS output
    flows = flows + np.random.normal(0, 15, n)
    flows = np.maximum(flows, base_flow)  # No negative flows

    flow_df = pd.DataFrame({
        'datetime': times,
        'flow_cfs': flows
    })

    peak_row = flow_df.loc[flow_df['flow_cfs'].idxmax()]

    print("Synthetic HMS outlet hydrograph (for demonstration):")
    print(f"  Time steps: {len(flow_df)}")
    print(f"  Start flow: {flow_df['flow_cfs'].iloc[0]:.0f} cfs")
    print(f"  Peak flow:  {peak_row['flow_cfs']:.0f} cfs at {peak_row['datetime']}")
    print(f"  End flow:   {flow_df['flow_cfs'].iloc[-1]:.0f} cfs")
    print()
    print(flow_df.head(10).to_string(index=False))
    print("  ...")

Step 4: Write Hydrograph to RAS Unsteady File

The HMS outlet flow becomes the upstream boundary condition in HEC-RAS. There are two approaches: 1. Write directly to the RAS unsteady flow file (inline table) 2. Write to a DSS file and reference it from the RAS unsteady file

Python
print("Approach A: Inline flow table in RAS unsteady file")
print("-" * 50)
print("  from ras_commander import RasUnsteady")
print()
print("  # hydrograph_df must have columns: 'hour' (float, hours since simulation start)")
print("  # and 'value' (flow rate in the model's flow units, e.g., cfs or cms)")
print("  hms_df = flow_df.copy()")
print("  hms_df['hour'] = (hms_df['datetime'] - forecast_start).dt.total_seconds() / 3600")
print("  hms_df = hms_df.rename(columns={'flow_cfs': 'value'})")
print()
print("  RasUnsteady.set_boundary_inline_hydrograph(")
print("      unsteady_file='project.u01',")
print("      hydrograph_df=hms_df[['hour', 'value']],")
print("      bc_type='Flow Hydrograph',   # default")
print("      river='MyRiver',             # optional: filter by river name")
print("      reach='MyReach',             # optional: filter by reach name")
print("      station='12345',             # optional: filter by station")
print("  )")
print()
print("Approach B: Write to DSS, reference from RAS unsteady file")
print("-" * 50)
print("  from ras_commander.dss import RasDss")
print()
print("  RasDss.write_timeseries(")
print("      dss_file='boundary.dss',")
print("      pathname='//BASIN/UPSTREAM/FLOW/01JUL2024/1HOUR/HMS/',")
print("      times=flow_df['datetime'].tolist(),")
print("      values=flow_df['flow_cfs'].tolist(),")
print("      units='CFS',")
print("      data_type='INST-VAL'")
print("  )")
print()
print("Recommendation:")
print("  Use DSS (Approach B) for operational forecasting — provides")
print("  audit trail of each forecast cycle's input boundary conditions.")

Step 5: Update RAS Plan Dates and Execute

Python
# Update RAS plan simulation dates to match HMS forecast window
print("Update RAS plan simulation dates:")
print(f"  from datetime import datetime")
print(f"  RasPlan.update_simulation_date(")
print(f"      '01',")
print(f"      start_date=datetime({forecast_start.year}, {forecast_start.month}, {forecast_start.day}, {forecast_start.hour}, {forecast_start.minute}),")
print(f"      end_date=datetime({forecast_end.year}, {forecast_end.month}, {forecast_end.day}, {forecast_end.hour}, {forecast_end.minute})")
print(f"  )")
print()
print("Execute RAS hydraulic simulation:")
print("  init_ras_project('/path/to/ras_project', '6.6')")
print("  RasCmdr.compute_plan('01', force_rerun=True, num_cores=4)")
print()
print("After execution, extract results:")
print("  from ras_commander.hdf import HdfResultsMesh")
print("  max_wse = HdfResultsMesh.get_mesh_max_ws('01', ras_object=ras)")
print("  max_depth = HdfResultsMesh.get_mesh_max_depth('01', ras_object=ras)")

Example 2: Multi-Element HMS with Multiple RAS Boundaries

For complex watersheds, HMS models multiple sub-basins with separate outlet junctions. Each junction contributes flow at a different location in the RAS model — lateral inflows, tributary junctions, and the main upstream boundary.

Python
# For complex watersheds, HMS may have multiple outlets
# Each becomes a separate RAS boundary condition

print("Multi-Element HMS -> Multi-Boundary RAS:")
print("=" * 55)
print()

# Simulated HMS elements with their corresponding RAS BC locations
hms_elements = {
    "Junction-1": {"bc_type": "upstream",   "bc_location": "Upstream_Main",  "peak_cfs": 3500},
    "Junction-2": {"bc_type": "lateral",     "bc_location": "Lateral_Trib1",  "peak_cfs": 1200},
    "Junction-3": {"bc_type": "lateral",     "bc_location": "Lateral_Trib2",  "peak_cfs": 800},
}

total_peak = sum(v["peak_cfs"] for v in hms_elements.values())

print(f"  {'HMS Element':<20} {'BC Type':<12} {'RAS BC Location':<25} {'Peak (cfs)'}")
print(f"  {'-'*20} {'-'*12} {'-'*25} {'-'*10}")
for element, config in hms_elements.items():
    print(f"  {element:<20} {config['bc_type']:<12} {config['bc_location']:<25} {config['peak_cfs']:>10,}")
print(f"  {'':20} {'':12} {'Total peak flow':<25} {total_peak:>10,}")
print()
print("Pattern for multi-element coupling:")
print()
print("  for element, config in hms_elements.items():")
print("      # Extract hydrograph from HMS DSS output")
print("      flow = HmsResults.get_outflow_timeseries(dss_file, element)")
print("      # Prepare hydrograph_df with required 'hour' and 'value' columns")
print("      hms_df = flow.rename(columns={'flow_cfs': 'value'})")
print("      hms_df['hour'] = (hms_df['datetime'] - forecast_start).dt.total_seconds() / 3600")
print("      # Write to RAS unsteady file — river/reach/station filter is optional")
print("      RasUnsteady.set_boundary_inline_hydrograph(")
print("          unsteady_file=unsteady_file,")
print("          hydrograph_df=hms_df[['hour', 'value']],")
print("          bc_type='Flow Hydrograph',")
print("          river=config.get('river'),    # optional: filter by river name")
print("          reach=config.get('reach'),    # optional: filter by reach name")
print("          station=config.get('station') # optional: filter by station")
print("      )")

Time Step Alignment

HMS and RAS may use different computation time steps. The key is ensuring the RAS boundary condition time series interval is compatible with the RAS simulation interval.

Python
print("Time Step Alignment Considerations:")
print("=" * 60)
print()
print(f"{'HMS Output Interval':<25} {'RAS BC Interval':<20} {'Behavior'}")
print("-" * 70)
print(f"{'5-minute':<25} {'5-minute':<20} {'Direct match - ideal'}")
print(f"{'15-minute':<25} {'5-minute':<20} {'RAS interpolates BC values'}")
print(f"{'1-hour':<25} {'1-minute':<20} {'RAS interpolates BC values'}")
print(f"{'1-hour':<25} {'1-hour':<20} {'Direct match'}")
print(f"{'1-hour':<25} {'6-hour':<20} {'BC coarser than RAS - avoid'}")
print()
print("Best practices:")
print("  1. Match HMS output interval to RAS BC interval")
print("  2. HMS warm-up: Start HMS 6-24 hours before RAS start")
print("     (watershed routing introduces lag time)")
print("  3. HMS end time: Run HMS through RAS end + travel time")
print("     (ensure flow at outlet covers full RAS simulation)")
print("  4. For 2D models: 1-hour HMS output + 30-sec RAS works")
print("     (RAS interpolates between hourly BC values)")
print()
print("Typical HMS warm-up adjustment:")
print("  hms_start = forecast_start - timedelta(hours=12)  # 12-hr warm-up")
print("  ras_start = forecast_start  # RAS starts at actual forecast time")

Key Takeaways

Summary

The HMS-RAS coupled workflow requires coordination between two separate tools:

  • hms-commander for HMS execution and results extraction
  • ras-commander for RAS execution and boundary management
  • User orchestration script to connect the two (not library code)

Critical Integration Points

Step Tool Method
Update HMS dates hms-commander HmsControl.set_time_window()
Execute HMS hms-commander HmsCmdr.compute_run()
Extract HMS flow hms-commander HmsResults.get_outflow_timeseries()
Write RAS BC ras-commander RasDss.write_timeseries()
Update RAS dates ras-commander RasPlan.update_simulation_date()
Execute RAS ras-commander RasCmdr.compute_plan()

HMS DSS Pathname Convention

HMS DSS output pathnames follow this structure:

Text Only
//BASIN_NAME/ELEMENT_NAME/FLOW/START_DATE/INTERVAL/RUN_NAME/

RAS typically expects:

Text Only
//PROJECT/LOCATION/FLOW/START_DATE/INTERVAL/VERSION/

You may need to remap pathnames when transferring data from HMS to RAS DSS files.

Common Pitfalls

  1. Time window mismatch — Ensure HMS outputs cover the full RAS simulation window including any travel time from watershed outlet to the RAS model domain.
  2. Units — HMS uses CFS or CMS; confirm RAS boundary condition expects the same units.
  3. DSS pathname errors — RAS is strict about DSS pathname format; validate with RasDss.check_pathname() before execution.
  4. Initial conditions — RAS initial conditions (IC) at simulation start should reflect baseflow, not zero flow, to avoid numerical instability.
  • 310_dss_boundary_extraction.ipynb — Reading and writing DSS boundary conditions
  • 110_single_plan_execution.ipynb — Basic RAS plan execution
  • 720_precipitation_methods_comprehensive.ipynb — Generating design storm precipitation for HMS input
Python
import shutil

# Clean up any extracted example projects created during this notebook
# (None were extracted in this demo — add paths here if you extracted projects locally)
cleanup_dirs = []

for d in cleanup_dirs:
    p = Path(d) if not isinstance(d, Path) else d
    if p.exists():
        shutil.rmtree(p, ignore_errors=True)
        print(f"Cleaned up: {d}")

print("Cleanup complete.")
print("Note: This notebook uses synthetic/pattern data only — no example projects were extracted.")
CLB Engineering Corporation  ·  LLM Forward Engineering
RAS Commander is a free and open-source project maintained by CLB Engineering Corporation. For agencies and firms seeking to modernize H&H workflows with LLM Forward approaches, contact CLB to partner with the engineers who wrote the automation.