Skip to content

HMS-to-RAS Boundary Condition Matching

This notebook demonstrates correlation-based matching between HEC-HMS DSS hydrograph outputs and HEC-RAS boundary condition locations using the BaldEagleCrkMulti2D example project.

Problem: HEC-RAS models use DSS-linked boundary conditions that reference HMS subbasin outputs. When models are modified or handed off, you need to verify that each RAS boundary condition is linked to the correct HMS hydrograph.

Solution: 1. Extract DSS-linked boundary conditions from RAS unsteady flow files 2. Read the corresponding hydrographs from the HMS DSS file 3. Read the full DSS catalog and match RAS boundary paths to catalog entries 4. Compare hydrographs using correlation and NRMSE to validate linkage

Example Project: BaldEagleCrkMulti2D - 10 unsteady flow files with DSS-linked boundary conditions - Bald_Eagle_Creek.dss (30 MB, ~1270 records) with HMS hydrographs - DSS boundaries: BALD EAGLE 40, FISHING CREEK, RESERVOIR LOCAL, LOCAL DOWNSTREAM OF DAM, MARSH CREEK

Key Functions: - RasDss.get_catalog() - Read DSS file catalog - RasDss.read_timeseries() - Extract time series from DSS - RasDss.extract_boundary_timeseries() - Extract all DSS boundary data - RasHydroCompare.compare_hydrographs() - Correlation and NRMSE comparison - RasHydroCompare.classify_match() - Classify match quality

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
from ras_commander import (
    init_ras_project, RasExamples, RasUnsteady,
    RasDss, RasHydroCompare
)

# Additional imports
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from pathlib import Path

# Verify which version loaded
import ras_commander
print(f'Loaded: {ras_commander.__file__}')
Text Only
LOCAL SOURCE MODE: Loading from <workspace>/ras_commander


Loaded: <workspace>\ras_commander\__init__.py

Parameters

Configure project settings for this notebook.

Python
# =============================================================================
# PARAMETERS - Edit these to customize the notebook
# =============================================================================

# Project Configuration
PROJECT_NAME = "BaldEagleCrkMulti2D"  # Example project with DSS boundary conditions
RAS_VERSION = "7.0"                    # HEC-RAS version
SUFFIX = "313"                         # Suffix for run folder

# DSS Configuration
DSS_FILENAME = "Bald_Eagle_Creek.dss"  # HMS DSS file with hydrographs
DSS_FLOW_FILTER = "/FLOW/"             # Filter for FLOW pathnames
DSS_RUN_FILTER = "PMF-EVENT"           # Filter for specific run (or None for all)

# Plan to analyze (plan with DSS-linked boundaries)
PLAN_NUMBER = "07"                     # Plan number with DSS boundaries

# JAVA_HOME for pyjnius/DSS access
import os
os.environ['JAVA_HOME'] = os.environ.get('JAVA_HOME', 'C:/tools/jdk17')

Step 1: Extract Example Project

Extract the Bald Eagle Creek Multi-2D example project which has DSS-linked boundary conditions and a rich DSS file with HMS hydrographs.

Python
# Extract example project
project_path = RasExamples.extract_project(PROJECT_NAME, suffix=SUFFIX)
print(f'Project extracted to: {project_path}')
Text Only
2026-06-11 16:48:20 - ras_commander.RasExamples - INFO - Successfully extracted project 'BaldEagleCrkMulti2D' to <workspace>\examples\example_projects\BaldEagleCrkMulti2D_313


Project extracted to: <workspace>\examples\example_projects\BaldEagleCrkMulti2D_313

Step 2: Initialize Project and Inspect Boundaries

Initialize the project and examine the boundaries_df to understand the boundary condition structure.

Python
# Initialize project
ras = init_ras_project(project_path, RAS_VERSION)

print(f'Project: {ras.project_name}')
print(f'Plans: {len(ras.plan_df)}')
print(f'Unsteady files: {len(ras.unsteady_df)}')
print(f'Total Boundaries: {len(ras.boundaries_df)}')

# Show boundary type breakdown
print(f'\nBoundary Types:')
for bc_type, count in ras.boundaries_df['bc_type'].value_counts().items():
    print(f'  {bc_type}: {count}')
Text Only
2026-06-11 16:48:20 - ras_commander.RasUtils - INFO - Discovered HEC-RAS 7.0 at <hec_ras_install>\7.0\Ras.exe via filesystem (x86)


2026-06-11 16:48:20 - ras_commander.RasUtils - INFO - Discovered HEC-RAS 6.7 Beta 5 at <hec_ras_install>\6.7 Beta 5\Ras.exe via filesystem (x86)


2026-06-11 16:48:20 - ras_commander.RasUtils - INFO - Discovered HEC-RAS 6.6 at <hec_ras_install>\6.6\Ras.exe via filesystem (x86)


2026-06-11 16:48:20 - ras_commander.RasUtils - INFO - Discovered HEC-RAS 6.5 at <hec_ras_install>\6.5\Ras.exe via filesystem (x86)


2026-06-11 16:48:20 - ras_commander.RasUtils - INFO - Discovered HEC-RAS 6.4.1 at <hec_ras_install>\6.4.1\Ras.exe via filesystem (x86)


2026-06-11 16:48:20 - ras_commander.RasUtils - INFO - Discovered HEC-RAS 6.3.1 at <hec_ras_install>\6.3.1\Ras.exe via filesystem (x86)


2026-06-11 16:48:20 - ras_commander.RasUtils - INFO - Discovered HEC-RAS 6.3 at <hec_ras_install>\6.3\Ras.exe via filesystem (x86)


2026-06-11 16:48:20 - ras_commander.RasUtils - INFO - Discovered HEC-RAS 6.2 at <hec_ras_install>\6.2\Ras.exe via filesystem (x86)


2026-06-11 16:48:20 - ras_commander.RasUtils - INFO - Discovered HEC-RAS 6.1 at <hec_ras_install>\6.1\Ras.exe via filesystem (x86)


2026-06-11 16:48:20 - ras_commander.RasUtils - INFO - Discovered HEC-RAS 6.0 at <hec_ras_install>\6.0\Ras.exe via filesystem (x86)


2026-06-11 16:48:20 - ras_commander.RasUtils - INFO - Discovered HEC-RAS 5.0.7 at <hec_ras_install>\5.0.7\Ras.exe via filesystem (x86)


2026-06-11 16:48:20 - ras_commander.RasUtils - INFO - Discovered HEC-RAS 5.0.6 at <hec_ras_install>\5.0.6\Ras.exe via filesystem (x86)


2026-06-11 16:48:20 - ras_commander.RasUtils - INFO - Discovered HEC-RAS 5.0.3 at <hec_ras_install>\5.0.3\Ras.exe via filesystem (x86)


2026-06-11 16:48:20 - ras_commander.RasUtils - INFO - Discovered HEC-RAS 4.1.0 at <hec_ras_install>\4.1.0\Ras.exe via filesystem (x86)


2026-06-11 16:48:20 - ras_commander.RasUtils - INFO - Discovered HEC-RAS 4.0 at <hec_ras_install>\4.0\Ras.exe via filesystem (x86)


2026-06-11 16:48:20 - ras_commander.RasUtils - INFO - Discovered HEC-RAS 6.7 Beta 4a at <hec_ras_install>\6.7 Beta 4a\Ras.exe via filesystem (x86)


2026-06-11 16:48:20 - ras_commander.RasUtils - INFO - Discovered 16 installed HEC-RAS version(s)


2026-06-11 16:48:20 - ras_commander.RasPrj - INFO - HEC-RAS 7.0 found via version discovery: <hec_ras_install>\7.0\Ras.exe


2026-06-11 16:48:20 - ras_commander.RasMap - INFO - Successfully parsed RASMapper file: <workspace>\examples\example_projects\BaldEagleCrkMulti2D_313\BaldEagleDamBrk.rasmap


2026-06-11 16:48:20 - ras_commander.RasPrj - INFO - ras-commander v0.98.0 | An open-source project of CLB Engineering Corporation (https://clbengineering.com/) | Docs: https://rascommander.info | GitHub: https://github.com/gpt-cmdr/ras-commander


2026-06-11 16:48:20 - ras_commander.RasPrj - INFO - Project initialized: BaldEagleDamBrk | Folder: <workspace>\examples\example_projects\BaldEagleCrkMulti2D_313


2026-06-11 16:48:20 - ras_commander.RasPrj - INFO - Using HEC-RAS executable: <hec_ras_install>\7.0\Ras.exe


2026-06-11 16:48:20 - ras_commander.RasPrj - INFO - 
═══════════════════════════════════════════════════════════════════════
ras-commander | HEC-RAS Automation Library
Docs: https://rascommander.info/
Repo: https://github.com/gpt-cmdr/ras-commander
═══════════════════════════════════════════════════════════════════════

PROJECT DATAFRAMES (single source of truth — use these, not file globbing):
  ras.plan_df        Plans, HDF paths, geometry/flow associations
  ras.geom_df        Geometry files and HDF preprocessor paths
  ras.flow_df        Steady flow files
  ras.unsteady_df    Unsteady flow files and configurations
  ras.boundaries_df  Boundary conditions (type, name, location)
  ras.results_df     Lightweight HDF results summaries
  ras.rasmap_df      RASMapper layers, terrain, land cover paths

KEY APIS (static classes — call directly, never instantiate):
  Execution:    RasCmdr.compute_plan() / compute_parallel() / compute_test_mode()
  Plan Files:   RasPlan.clone_plan() / clone_geom() / set_geom()
  Unsteady:     RasUnsteady — IC/BC management, gate openings, precipitation
  Geometry:     GeomCrossSection, GeomBridge, GeomStorage, GeomLateral, GeomMesh
  HDF Results:  HdfResultsPlan.get_wse() / get_compute_messages()
                HdfResultsMesh.get_mesh_max_ws() / get_mesh_cells_timeseries()
                HdfMesh.get_mesh_cell_points()
  QA/QC:        RasCheck.run_check() / RasFixit (geometry repair)
  DSS:          RasDss.get_timeseries() / check_pathname()
  USGS:         UsgsGaugeSpatial, GaugeMatcher, RasUsgsBoundaryGeneration
  Precipitation: StormGenerator, Atlas14Storm, PrecipAorc, Atlas14Variance
  Terrain:      RasTerrain.create_terrain_hdf() / RasTerrainMod

MULTI-PROJECT: Pass ras_object= to all API calls when using local RasPrj instances.

EXAMPLES: 100+ notebooks in examples/ (100s=execution, 200s=geometry, 300s=unsteady,
  400s=HDF results, 500s=remote, 800s=QA/QC, 900s=data integration).
  Review relevant notebooks before assembling new workflows.

PLATFORM: Most HEC-RAS operations require Windows. Linux/Wine support for
  headless execution, data access, geometry modification, and preprocessing
  is available via RasProcess (HEC-RAS 6.6+). See ras_commander/RasProcess.py.
  Remote distributed execution: ras_commander/remote/ (PsExec, Docker, SSH, cloud).
═══════════════════════════════════════════════════════════════════════


Project: BaldEagleDamBrk
Plans: 11
Unsteady files: 10
Total Boundaries: 51

Boundary Types:
  Normal Depth: 18
  Lateral Inflow Hydrograph: 11
  Gate Opening: 9
  Flow Hydrograph: 8
  Uniform Lateral Inflow Hydrograph: 5

Step 3: Identify DSS-Linked Boundaries

Filter boundaries_df for DSS-linked boundary conditions. These are the boundaries we need to match against the HMS DSS file.

Python
# Get all DSS-linked boundaries
all_dss_boundaries = ras.boundaries_df[ras.boundaries_df['Use DSS'] == 'True'].copy()

print(f'Total DSS-linked boundaries across all plans: {len(all_dss_boundaries)}')

# Show unique DSS A-parts (HMS subbasin names)
if 'dss_part_a' in all_dss_boundaries.columns:
    unique_subbasins = all_dss_boundaries['dss_part_a'].dropna().unique()
    print(f'\nUnique HMS Subbasins (from DSS A-part): {len(unique_subbasins)}')
    for name in sorted(unique_subbasins):
        count = (all_dss_boundaries['dss_part_a'] == name).sum()
        print(f'  {name} ({count} boundaries)')

# Focus on the target plan
plan_boundaries = ras.boundaries_df[ras.boundaries_df['unsteady_number'] == PLAN_NUMBER].copy()
dss_boundaries = plan_boundaries[plan_boundaries['Use DSS'] == 'True']

print(f'\nPlan {PLAN_NUMBER}:')
print(f'  Total boundaries: {len(plan_boundaries)}')
print(f'  DSS-linked boundaries: {len(dss_boundaries)}')

# Display DSS path components
if len(dss_boundaries) > 0:
    component_cols = ['bc_type', 'DSS Path', 'dss_part_a', 'dss_part_b', 'dss_part_d', 'dss_part_e']
    available_cols = [c for c in component_cols if c in dss_boundaries.columns]
    display(dss_boundaries[available_cols])
Text Only
Total DSS-linked boundaries across all plans: 13

Unique HMS Subbasins (from DSS A-part): 7
  BALD EAGLE 40 (1 boundaries)
  BALD EAGLE LOCAL (2 boundaries)
  BEECH CREEK FLOW (2 boundaries)
  FISHING CREEK (3 boundaries)
  LOCAL DOWNSTREAM OF DAM (2 boundaries)
  MARSH CREEK (2 boundaries)
  RESERVOIR LOCAL (1 boundaries)

Plan 07:
  Total boundaries: 10
  DSS-linked boundaries: 7
bc_type DSS Path dss_part_a dss_part_b dss_part_d dss_part_e
0 Flow Hydrograph //BALD EAGLE 40/FLOW/01JAN1999/15MIN/RUN:PMF-E... BALD EAGLE 40 FLOW 15MIN RUN:PMF-EVENT
2 Lateral Inflow Hydrograph //FISHING CREEK/FLOW/01JAN1999/15MIN/RUN:PMF-E... FISHING CREEK FLOW 15MIN RUN:PMF-EVENT
4 Uniform Lateral Inflow Hydrograph //RESERVOIR LOCAL/FLOW/01JAN1999/15MIN/RUN:PMF... RESERVOIR LOCAL FLOW 15MIN RUN:PMF-EVENT
5 Uniform Lateral Inflow Hydrograph //LOCAL DOWNSTREAM OF DAM/FLOW/01JAN1999/15MIN... LOCAL DOWNSTREAM OF DAM FLOW 15MIN RUN:PMF-EVENT
6 Lateral Inflow Hydrograph //MARSH CREEK/FLOW/01JAN1999/15MIN/RUN:PMF-EVENT/ MARSH CREEK FLOW 15MIN RUN:PMF-EVENT
7 Lateral Inflow Hydrograph //BEECH CREEK FLOW/FLOW/01JAN1999/15MIN/RUN:PM... BEECH CREEK FLOW FLOW 15MIN RUN:PMF-EVENT
8 Uniform Lateral Inflow Hydrograph //BALD EAGLE LOCAL/FLOW/01JAN1999/15MIN/RUN:PM... BALD EAGLE LOCAL FLOW 15MIN RUN:PMF-EVENT

Step 4: Read the HMS DSS Catalog

Read the full catalog from Bald_Eagle_Creek.dss and filter for FLOW pathnames. The DSS pathname structure is:

Text Only
//A-part/B-part/C-part/D-part/E-part/F-part/
//[HMS_ELEMENT]/FLOW/[DATE]/[INTERVAL]/RUN:[EVENT_ID]/

Part A (HMS element name) is the key for matching to RAS boundaries.

Python
# Get DSS file path
dss_file = project_path / DSS_FILENAME

if not dss_file.exists():
    raise FileNotFoundError(f'DSS file not found: {dss_file}')

print(f'DSS File: {dss_file.name}')
print(f'Size: {dss_file.stat().st_size / (1024*1024):.1f} MB')

# Read catalog
catalog_df = RasDss.get_catalog(dss_file)
print(f'\nTotal DSS pathnames: {len(catalog_df)}')

# Filter for FLOW pathnames
flow_catalog = catalog_df[catalog_df['pathname'].str.contains(DSS_FLOW_FILTER, na=False)]
print(f'FLOW pathnames: {len(flow_catalog)}')

# Further filter by run if specified
if DSS_RUN_FILTER:
    filtered_catalog = flow_catalog[flow_catalog['pathname'].str.contains(DSS_RUN_FILTER, na=False)]
    print(f'FLOW pathnames for \'{DSS_RUN_FILTER}\': {len(filtered_catalog)}')
else:
    filtered_catalog = flow_catalog

# Parse catalog pathnames into components
catalog_parsed = []
for pathname in filtered_catalog['pathname']:
    parts = pathname.strip('/').split('/')
    catalog_parsed.append({
        'pathname': pathname,
        'a_part': parts[0] if len(parts) > 0 else '',
        'b_part': parts[1] if len(parts) > 1 else '',
        'c_part': parts[2] if len(parts) > 2 else '',
        'd_part': parts[3] if len(parts) > 3 else '',
        'e_part': parts[4] if len(parts) > 4 else '',
    })

catalog_parsed_df = pd.DataFrame(catalog_parsed)
print(f'\nUnique HMS elements in DSS (A-part):')
for elem in sorted(catalog_parsed_df['a_part'].unique()):
    print(f'  {elem}')
Text Only
DSS File: Bald_Eagle_Creek.dss
Size: 29.3 MB
Configuring Java VM for DSS operations...
[OK] Java VM configured



Total DSS pathnames: 1270
FLOW pathnames: 313
FLOW pathnames for 'PMF-EVENT': 52

Unique HMS elements in DSS (A-part):
  BALD EAGLE 10
  BALD EAGLE 20
  BALD EAGLE 30
  BALD EAGLE 40
  BALD EAGLE AT MILESBURG
  BALD EAGLE BL BEECH
  BALD EAGLE HW
  BALD EAGLE LOCAL
  BEECH CREEK
  BEECH CREEK FLOW
  BEECH CREEK GAGE
  BEECH CREEK LOWER
  BEECH CREEK UPPER
  FISHING CREEK
  FISHING CREEK CONFLUENCE
  JUNCTION-1
  LOCAL DOWNSTREAM OF DAM
  MARSH CREEK
  MARSH CREEK GAGE
  OUTLET
  POOL HW
  RESERVOIR LOCAL
  SAYERS DAM
  SAYERS DAM-SPILL-1
  SPRING CREEK
  SPRING CREEK GAGE

Step 5: Match RAS Boundaries to DSS Catalog

Match each RAS DSS-linked boundary condition to its corresponding entry in the DSS catalog. The match is based on comparing the DSS path stored in the RAS unsteady file to the pathnames available in the DSS file.

Python
# Match RAS boundaries to DSS catalog entries
match_results = []

for idx, bc_row in dss_boundaries.iterrows():
    dss_path = bc_row.get('DSS Path', '')
    a_part = bc_row.get('dss_part_a', '')
    bc_type = bc_row.get('bc_type', '')

    # Look for exact pathname match in catalog
    exact_match = catalog_df[catalog_df['pathname'] == dss_path]

    # Look for A-part match (HMS subbasin name) in FLOW catalog
    a_part_matches = catalog_parsed_df[catalog_parsed_df['a_part'] == a_part] if a_part else pd.DataFrame()

    match_results.append({
        'bc_type': bc_type,
        'dss_a_part': a_part,
        'dss_path': dss_path,
        'exact_match_found': len(exact_match) > 0,
        'a_part_flow_records': len(a_part_matches),
        'catalog_pathnames': list(a_part_matches['pathname']) if len(a_part_matches) > 0 else [],
    })

match_df = pd.DataFrame(match_results)

print('=== RAS Boundary to DSS Catalog Matching ===')
print(f'Total DSS boundaries: {len(match_df)}')
print(f'Exact path matches found: {match_df["exact_match_found"].sum()}')
print(f'A-part matches found: {(match_df["a_part_flow_records"] > 0).sum()}')
print()

for _, row in match_df.iterrows():
    status = 'FOUND' if row['exact_match_found'] else ('A-PART FOUND' if row['a_part_flow_records'] > 0 else 'NOT FOUND')
    print(f'  [{status}] {row["dss_a_part"]} ({row["bc_type"]})')
    print(f'    RAS DSS Path: {row["dss_path"]}')
    if row['a_part_flow_records'] > 0:
        print(f'    Catalog entries: {row["a_part_flow_records"]}')
Text Only
=== RAS Boundary to DSS Catalog Matching ===
Total DSS boundaries: 7
Exact path matches found: 7
A-part matches found: 7

  [FOUND] BALD EAGLE 40 (Flow Hydrograph)
    RAS DSS Path: //BALD EAGLE 40/FLOW/01JAN1999/15MIN/RUN:PMF-EVENT/
    Catalog entries: 2
  [FOUND] FISHING CREEK (Lateral Inflow Hydrograph)
    RAS DSS Path: //FISHING CREEK/FLOW/01JAN1999/15MIN/RUN:PMF-EVENT/
    Catalog entries: 2
  [FOUND] RESERVOIR LOCAL (Uniform Lateral Inflow Hydrograph)
    RAS DSS Path: //RESERVOIR LOCAL/FLOW/01JAN1999/15MIN/RUN:PMF-EVENT/
    Catalog entries: 2
  [FOUND] LOCAL DOWNSTREAM OF DAM (Uniform Lateral Inflow Hydrograph)
    RAS DSS Path: //LOCAL DOWNSTREAM OF DAM/FLOW/01JAN1999/15MIN/RUN:PMF-EVENT/
    Catalog entries: 2
  [FOUND] MARSH CREEK (Lateral Inflow Hydrograph)
    RAS DSS Path: //MARSH CREEK/FLOW/01JAN1999/15MIN/RUN:PMF-EVENT/
    Catalog entries: 2
  [FOUND] BEECH CREEK FLOW (Lateral Inflow Hydrograph)
    RAS DSS Path: //BEECH CREEK FLOW/FLOW/01JAN1999/15MIN/RUN:PMF-EVENT/
    Catalog entries: 2
  [FOUND] BALD EAGLE LOCAL (Uniform Lateral Inflow Hydrograph)
    RAS DSS Path: //BALD EAGLE LOCAL/FLOW/01JAN1999/15MIN/RUN:PMF-EVENT/
    Catalog entries: 2

Step 6: Extract DSS Time Series for Boundaries

Read the actual hydrograph data from the DSS file for each DSS-linked boundary condition using RasDss.extract_boundary_timeseries().

Python
# Extract all DSS boundary time series for the target plan
enhanced_boundaries = RasDss.extract_boundary_timeseries(
    plan_boundaries,
    ras_object=ras
)

# Count successful extractions
dss_enhanced = enhanced_boundaries[enhanced_boundaries['Use DSS'] == 'True']
successful = dss_enhanced[dss_enhanced['dss_timeseries'].notna()]

print(f'=== DSS Time Series Extraction ===')
print(f'DSS boundaries in plan {PLAN_NUMBER}: {len(dss_enhanced)}')
print(f'Successfully extracted: {len(successful)}')
print()

# Show extraction summary
for idx, row in dss_enhanced.iterrows():
    ts = row.get('dss_timeseries')
    a_part = row.get('dss_part_a', 'Unknown')
    bc_type = row.get('bc_type', '')

    if ts is not None and len(ts) > 0:
        peak = ts['value'].max()
        n_points = len(ts)
        print(f'  {a_part} ({bc_type}): {n_points} points, peak = {peak:.1f}')
    else:
        print(f'  {a_part} ({bc_type}): NO DATA EXTRACTED')
Text Only
2026-06-11 16:48:20 - ras_commander.dss.RasDss - INFO - Found 7 DSS-defined boundaries


2026-06-11 16:48:21 - ras_commander.dss.RasDss - INFO - Extraction complete: 7 success, 0 failed


=== DSS Time Series Extraction ===
DSS boundaries in plan 07: 7
Successfully extracted: 7

  BALD EAGLE 40 (Flow Hydrograph): 673 points, peak = 193738.2
  FISHING CREEK (Lateral Inflow Hydrograph): 673 points, peak = 28510.1
  RESERVOIR LOCAL (Uniform Lateral Inflow Hydrograph): 673 points, peak = 75262.3
  LOCAL DOWNSTREAM OF DAM (Uniform Lateral Inflow Hydrograph): 673 points, peak = 5568.2
  MARSH CREEK (Lateral Inflow Hydrograph): 673 points, peak = 37820.3
  BEECH CREEK FLOW (Lateral Inflow Hydrograph): 673 points, peak = 32872.2
  BALD EAGLE LOCAL (Uniform Lateral Inflow Hydrograph): 673 points, peak = 27428.2

Step 7: Read DSS Catalog Time Series for Cross-Matching

Read FLOW time series from the DSS catalog for HMS elements that may not be directly linked to RAS boundaries. This allows cross-validation of all DSS entries against RAS boundary conditions.

Python
# Read a selection of FLOW time series from the DSS catalog
# Focus on pathnames matching our run filter
dss_timeseries_catalog = {}
dss_metadata = []

sample_paths = filtered_catalog['pathname'].tolist()
print(f'Reading {len(sample_paths)} FLOW time series from DSS catalog...')

for pathname in sample_paths:
    try:
        ts_df = RasDss.read_timeseries(dss_file, pathname)

        # Parse pathname
        parts = pathname.strip('/').split('/')
        hms_element = parts[0] if len(parts) > 0 else 'Unknown'

        dss_timeseries_catalog[pathname] = ts_df['value'].values
        dss_metadata.append({
            'pathname': pathname,
            'hms_element': hms_element,
            'n_points': len(ts_df),
            'peak_flow': ts_df['value'].max(),
        })
    except Exception as e:
        print(f'  Warning: Could not read {pathname}: {e}')

dss_meta_df = pd.DataFrame(dss_metadata)
print(f'\nSuccessfully read: {len(dss_meta_df)} time series')
print(f'Unique HMS elements: {dss_meta_df["hms_element"].nunique()}')

# Show summary
print(f'\nHMS Element Summary:')
for elem in sorted(dss_meta_df['hms_element'].unique()):
    elem_data = dss_meta_df[dss_meta_df['hms_element'] == elem]
    print(f'  {elem}: {len(elem_data)} records, max peak = {elem_data["peak_flow"].max():.1f}')
Text Only
Reading 52 FLOW time series from DSS catalog...



Successfully read: 52 time series
Unique HMS elements: 26

HMS Element Summary:
  BALD EAGLE 10: 2 records, max peak = 252903.0
  BALD EAGLE 20: 2 records, max peak = 234780.4
  BALD EAGLE 30: 2 records, max peak = 217744.6
  BALD EAGLE 40: 2 records, max peak = 193738.2
  BALD EAGLE AT MILESBURG: 2 records, max peak = 194325.9
  BALD EAGLE BL BEECH: 2 records, max peak = 249910.6
  BALD EAGLE HW: 2 records, max peak = 106592.7
  BALD EAGLE LOCAL: 2 records, max peak = 27428.2
  BEECH CREEK: 2 records, max peak = 30765.7
  BEECH CREEK FLOW: 2 records, max peak = 32872.2
  BEECH CREEK GAGE: 2 records, max peak = 32458.3
  BEECH CREEK LOWER: 2 records, max peak = 14217.8
  BEECH CREEK UPPER: 2 records, max peak = 32458.3
  FISHING CREEK: 2 records, max peak = 28510.1
  FISHING CREEK CONFLUENCE: 2 records, max peak = 257734.4
  JUNCTION-1: 2 records, max peak = 222289.3
  LOCAL DOWNSTREAM OF DAM: 2 records, max peak = 5568.2
  MARSH CREEK: 2 records, max peak = 37820.3
  MARSH CREEK GAGE: 2 records, max peak = 37820.3
  OUTLET: 2 records, max peak = 256680.1
  POOL HW: 2 records, max peak = 250809.0
  RESERVOIR LOCAL: 2 records, max peak = 75262.3
  SAYERS DAM: 2 records, max peak = 203465.4
  SAYERS DAM-SPILL-1: 2 records, max peak = 203465.4
  SPRING CREEK: 2 records, max peak = 92924.3
  SPRING CREEK GAGE: 2 records, max peak = 92924.3

Step 8: Correlation-Based Matching

For each extracted RAS boundary hydrograph, compare against ALL DSS catalog hydrographs using RasHydroCompare.compare_hydrographs(). This validates that the DSS links point to the correct HMS output.

Match Quality Classification (RasHydroCompare.classify_match()): - EXACT: r > 0.9999, NRMSE < 0.01% (identical data) - CLOSE: r > 0.99, NRMSE < 1.0% (minor rounding differences) - POSSIBLE: r > 0.95, NRMSE < 5.0% (similar shape, different magnitude) - MISMATCH: Below thresholds

Python
# Compare each RAS boundary hydrograph to DSS catalog entries
comparison_results = []

for idx, bc_row in successful.iterrows():
    ts_data = bc_row['dss_timeseries']
    if ts_data is None or len(ts_data) == 0:
        continue

    bc_flow = ts_data['value'].values
    a_part = bc_row.get('dss_part_a', 'Unknown')
    bc_type = bc_row.get('bc_type', '')
    dss_path_ras = bc_row.get('DSS Path', '')

    best_match = None
    best_corr = -1

    for pathname, dss_flow in dss_timeseries_catalog.items():
        # Compare hydrographs
        result = RasHydroCompare.compare_hydrographs(
            bc_flow, dss_flow, truncate_to_shorter=True
        )

        # Track best match
        if result['correlation'] > best_corr:
            best_corr = result['correlation']
            best_match = {
                'ras_a_part': a_part,
                'ras_bc_type': bc_type,
                'ras_dss_path': dss_path_ras,
                'ras_peak': float(np.max(bc_flow)),
                'best_dss_pathname': pathname,
                'best_hms_element': pathname.strip('/').split('/')[0],
                'correlation': result['correlation'],
                'nrmse_pct': result['nrmse_pct'],
                'peak_diff': result['peak_diff'],
                'peak_ratio': result['peak_ratio'],
                'quality': RasHydroCompare.classify_match(
                    result['correlation'], result['nrmse_pct']
                ),
            }

    if best_match:
        comparison_results.append(best_match)

comparison_df = pd.DataFrame(comparison_results)

print('=== Correlation-Based Matching Results ===')
print(f'Boundaries compared: {len(comparison_df)}')
print()

# Show quality distribution
if len(comparison_df) > 0:
    print('Match Quality Distribution:')
    for quality, count in comparison_df['quality'].value_counts().items():
        pct = count / len(comparison_df) * 100
        print(f'  {quality}: {count} ({pct:.0f}%)')

    print(f'\nDetailed Results:')
    print('-' * 100)
    for _, row in comparison_df.iterrows():
        linked_match = row['ras_a_part'] == row['best_hms_element']
        link_status = 'CONFIRMED' if linked_match and row['quality'] in ('EXACT', 'CLOSE') else 'CHECK'
        print(f'  [{row["quality"]:8s}] {row["ras_a_part"]}')
        print(f'    Linked DSS path: {row["ras_dss_path"]}')
        print(f'    Best match:      {row["best_dss_pathname"]}')
        print(f'    Correlation: {row["correlation"]:.6f}, NRMSE: {row["nrmse_pct"]:.4f}%')
        print(f'    Peak ratio: {row["peak_ratio"]:.4f}, Link status: {link_status}')
        print()
Text Only
=== Correlation-Based Matching Results ===
Boundaries compared: 7

Match Quality Distribution:
  EXACT: 7 (100%)

Detailed Results:
----------------------------------------------------------------------------------------------------
  [EXACT   ] BALD EAGLE 40
    Linked DSS path: //BALD EAGLE 40/FLOW/01JAN1999/15MIN/RUN:PMF-EVENT/
    Best match:      //BALD EAGLE 40/FLOW/01JAN1999/15MIN/RUN:PMF-EVENT/
    Correlation: 1.000000, NRMSE: 0.0000%
    Peak ratio: 1.0000, Link status: CONFIRMED

  [EXACT   ] FISHING CREEK
    Linked DSS path: //FISHING CREEK/FLOW/01JAN1999/15MIN/RUN:PMF-EVENT/
    Best match:      //FISHING CREEK/FLOW/01JAN1999/15MIN/RUN:PMF-EVENT/
    Correlation: 1.000000, NRMSE: 0.0000%
    Peak ratio: 1.0000, Link status: CONFIRMED

  [EXACT   ] RESERVOIR LOCAL
    Linked DSS path: //RESERVOIR LOCAL/FLOW/01JAN1999/15MIN/RUN:PMF-EVENT/
    Best match:      //RESERVOIR LOCAL/FLOW/01JAN1999/15MIN/RUN:PMF-EVENT/
    Correlation: 1.000000, NRMSE: 0.0000%
    Peak ratio: 1.0000, Link status: CONFIRMED

  [EXACT   ] LOCAL DOWNSTREAM OF DAM
    Linked DSS path: //LOCAL DOWNSTREAM OF DAM/FLOW/01JAN1999/15MIN/RUN:PMF-EVENT/
    Best match:      //LOCAL DOWNSTREAM OF DAM/FLOW/01JAN1999/15MIN/RUN:PMF-EVENT/
    Correlation: 1.000000, NRMSE: 0.0000%
    Peak ratio: 1.0000, Link status: CONFIRMED

  [EXACT   ] MARSH CREEK
    Linked DSS path: //MARSH CREEK/FLOW/01JAN1999/15MIN/RUN:PMF-EVENT/
    Best match:      //MARSH CREEK/FLOW/01JAN1999/15MIN/RUN:PMF-EVENT/
    Correlation: 1.000000, NRMSE: 0.0000%
    Peak ratio: 1.0000, Link status: CONFIRMED

  [EXACT   ] BEECH CREEK FLOW
    Linked DSS path: //BEECH CREEK FLOW/FLOW/01JAN1999/15MIN/RUN:PMF-EVENT/
    Best match:      //BEECH CREEK FLOW/FLOW/01JAN1999/15MIN/RUN:PMF-EVENT/
    Correlation: 1.000000, NRMSE: 0.0000%
    Peak ratio: 1.0000, Link status: CONFIRMED

  [EXACT   ] BALD EAGLE LOCAL
    Linked DSS path: //BALD EAGLE LOCAL/FLOW/01JAN1999/15MIN/RUN:PMF-EVENT/
    Best match:      //BALD EAGLE LOCAL/FLOW/01JAN1999/15MIN/RUN:PMF-EVENT/
    Correlation: 1.000000, NRMSE: 0.0000%
    Peak ratio: 1.0000, Link status: CONFIRMED

For DSS-linked boundaries, the primary validation is confirming that the DSS path in the RAS unsteady file points to the correct HMS element. An EXACT or CLOSE match between the boundary's own DSS time series and its best catalog match confirms correct linkage.

Python
# Validate that each boundary's DSS link is correct
print('=== DSS Link Validation Summary ===\n')

confirmed = 0
mismatched = 0
unverified = 0

for _, row in comparison_df.iterrows():
    is_self_match = row['ras_a_part'] == row['best_hms_element']
    quality = row['quality']

    if is_self_match and quality in ('EXACT', 'CLOSE'):
        confirmed += 1
        status = 'CONFIRMED'
    elif is_self_match and quality == 'POSSIBLE':
        unverified += 1
        status = 'NEEDS REVIEW'
    else:
        mismatched += 1
        status = 'MISMATCH'

    print(f'  {row["ras_a_part"]:30s} -> {row["best_hms_element"]:30s}  [{status}]')
    if not is_self_match:
        print(f'    WARNING: Best match is a different HMS element!')
        print(f'    Expected: {row["ras_a_part"]}, Got: {row["best_hms_element"]}')
        print(f'    Correlation: {row["correlation"]:.6f}')

print(f'\n--- Summary ---')
print(f'  Confirmed correct links: {confirmed}')
print(f'  Needs review:            {unverified}')
print(f'  Potential mismatches:     {mismatched}')
print(f'  Total:                    {len(comparison_df)}')
Text Only
=== DSS Link Validation Summary ===

  BALD EAGLE 40                  -> BALD EAGLE 40                   [CONFIRMED]
  FISHING CREEK                  -> FISHING CREEK                   [CONFIRMED]
  RESERVOIR LOCAL                -> RESERVOIR LOCAL                 [CONFIRMED]
  LOCAL DOWNSTREAM OF DAM        -> LOCAL DOWNSTREAM OF DAM         [CONFIRMED]
  MARSH CREEK                    -> MARSH CREEK                     [CONFIRMED]
  BEECH CREEK FLOW               -> BEECH CREEK FLOW                [CONFIRMED]
  BALD EAGLE LOCAL               -> BALD EAGLE LOCAL                [CONFIRMED]

--- Summary ---
  Confirmed correct links: 7
  Needs review:            0
  Potential mismatches:     0
  Total:                    7

Step 10: Visualize Matched Hydrographs

Create comparison plots showing the RAS boundary hydrograph overlaid with its best-matching DSS catalog entry.

Python
# Plot hydrograph comparisons
n_plots = min(len(successful), 6)
if n_plots == 0:
    print('No successful DSS extractions to plot.')
else:
    n_cols = min(n_plots, 2)
    n_rows = (n_plots + n_cols - 1) // n_cols

    fig, axes = plt.subplots(n_rows, n_cols, figsize=(14, 4 * n_rows))
    if n_plots == 1:
        axes = np.array([axes])
    axes = np.array(axes).flatten()

    plot_idx = 0
    for idx, bc_row in successful.iterrows():
        if plot_idx >= n_plots:
            break

        ts_data = bc_row['dss_timeseries']
        if ts_data is None or len(ts_data) == 0:
            continue

        ax = axes[plot_idx]
        bc_flow = ts_data['value'].values
        a_part = bc_row.get('dss_part_a', 'Unknown')
        bc_type = bc_row.get('bc_type', '')

        # Find best match from comparison results
        match_row = comparison_df[comparison_df['ras_a_part'] == a_part]

        # Plot RAS boundary hydrograph
        time_hrs = np.arange(len(bc_flow)) * 15 / 60  # Assume 15-min intervals
        ax.plot(time_hrs, bc_flow, 'b-', linewidth=1.5, label=f'RAS BC ({a_part})', alpha=0.8)

        # Overlay best DSS match if available
        if len(match_row) > 0:
            best_pathname = match_row.iloc[0]['best_dss_pathname']
            if best_pathname in dss_timeseries_catalog:
                dss_flow = dss_timeseries_catalog[best_pathname]
                min_len = min(len(bc_flow), len(dss_flow))
                dss_time = np.arange(min_len) * 15 / 60
                ax.plot(dss_time, dss_flow[:min_len], 'r--', linewidth=1.0,
                       label=f'DSS ({match_row.iloc[0]["best_hms_element"]})', alpha=0.7)

            quality = match_row.iloc[0]['quality']
            corr = match_row.iloc[0]['correlation']
            ax.set_title(f'{a_part} ({bc_type})\n[{quality}] r={corr:.6f}', fontsize=10)
        else:
            ax.set_title(f'{a_part} ({bc_type})', fontsize=10)

        ax.set_xlabel('Time (hours)')
        ax.set_ylabel('Flow')
        ax.legend(fontsize=8)
        ax.grid(True, alpha=0.3)

        plot_idx += 1

    # Hide unused axes
    for i in range(plot_idx, len(axes)):
        axes[i].set_visible(False)

    plt.tight_layout()
    plt.savefig(project_path / 'boundary_matching_plots.png', dpi=150, bbox_inches='tight')
    plt.show()
    print(f'Plots saved to: {project_path / "boundary_matching_plots.png"}')

png

Text Only
Plots saved to: <workspace>\examples\example_projects\BaldEagleCrkMulti2D_313\boundary_matching_plots.png

Step 11: Cross-Plan Boundary Comparison

Compare boundary conditions across multiple unsteady flow files to identify which plans share the same HMS inputs.

Python
# Analyze boundaries across all plans
print('=== Cross-Plan Boundary Analysis ===\n')

# Group by unsteady file
for unsteady_num in sorted(ras.boundaries_df['unsteady_number'].unique()):
    plan_bcs = ras.boundaries_df[ras.boundaries_df['unsteady_number'] == unsteady_num]
    dss_bcs = plan_bcs[plan_bcs['Use DSS'] == 'True']
    manual_bcs = plan_bcs[plan_bcs['Use DSS'] != 'True']

    # Get inline hydrograph count
    inline_count = 0
    unsteady_files = ras.unsteady_df[ras.unsteady_df['unsteady_number'] == unsteady_num]
    if len(unsteady_files) > 0:
        u_path = unsteady_files.iloc[0]['full_path']
        try:
            inline_bcs = RasUnsteady.get_inline_hydrograph_boundaries(u_path)
            inline_count = len(inline_bcs)
        except Exception:
            pass

    print(f'Unsteady {unsteady_num}: {len(plan_bcs)} total BCs')
    print(f'  DSS-linked: {len(dss_bcs)}')
    print(f'  Manual/other: {len(manual_bcs)}')
    if inline_count > 0:
        print(f'  Inline hydrographs: {inline_count}')

    if len(dss_bcs) > 0 and 'dss_part_a' in dss_bcs.columns:
        subbasins = dss_bcs['dss_part_a'].dropna().unique()
        print(f'  HMS subbasins: {", ".join(sorted(subbasins))}')
    print()
Text Only
2026-06-11 16:48:22 - ras_commander.RasUnsteady - INFO - Found 1 inline hydrograph boundaries in BaldEagleDamBrk.u01


2026-06-11 16:48:22 - ras_commander.RasUnsteady - INFO - Found 1 inline hydrograph boundaries in BaldEagleDamBrk.u02


2026-06-11 16:48:22 - ras_commander.RasUnsteady - INFO - Found 1 inline hydrograph boundaries in BaldEagleDamBrk.u03


2026-06-11 16:48:22 - ras_commander.RasUnsteady - INFO - Found 1 inline hydrograph boundaries in BaldEagleDamBrk.u07


2026-06-11 16:48:22 - ras_commander.RasUnsteady - INFO - Found 2 inline hydrograph boundaries in BaldEagleDamBrk.u08


2026-06-11 16:48:22 - ras_commander.RasUnsteady - INFO - Found 1 inline hydrograph boundaries in BaldEagleDamBrk.u09


2026-06-11 16:48:22 - ras_commander.RasUnsteady - INFO - Found 1 inline hydrograph boundaries in BaldEagleDamBrk.u10


2026-06-11 16:48:22 - ras_commander.RasUnsteady - INFO - Found 1 inline hydrograph boundaries in BaldEagleDamBrk.u11


2026-06-11 16:48:22 - ras_commander.RasUnsteady - INFO - Found 1 inline hydrograph boundaries in BaldEagleDamBrk.u12


2026-06-11 16:48:22 - ras_commander.RasUnsteady - INFO - Found 1 inline hydrograph boundaries in BaldEagleDamBrk.u13


=== Cross-Plan Boundary Analysis ===

Unsteady 01: 4 total BCs
  DSS-linked: 0
  Manual/other: 4
  Inline hydrographs: 1

Unsteady 02: 5 total BCs
  DSS-linked: 0
  Manual/other: 5
  Inline hydrographs: 1

Unsteady 03: 4 total BCs
  DSS-linked: 0
  Manual/other: 4
  Inline hydrographs: 1

Unsteady 07: 10 total BCs
  DSS-linked: 7
  Manual/other: 3
  Inline hydrographs: 1
  HMS subbasins: BALD EAGLE 40, BALD EAGLE LOCAL, BEECH CREEK FLOW, FISHING CREEK, LOCAL DOWNSTREAM OF DAM, MARSH CREEK, RESERVOIR LOCAL

Unsteady 08: 9 total BCs
  DSS-linked: 5
  Manual/other: 4
  Inline hydrographs: 2
  HMS subbasins: BALD EAGLE LOCAL, BEECH CREEK FLOW, FISHING CREEK, LOCAL DOWNSTREAM OF DAM, MARSH CREEK

Unsteady 09: 3 total BCs
  DSS-linked: 1
  Manual/other: 2
  Inline hydrographs: 1
  HMS subbasins: FISHING CREEK

Unsteady 10: 4 total BCs
  DSS-linked: 0
  Manual/other: 4
  Inline hydrographs: 1

Unsteady 11: 4 total BCs
  DSS-linked: 0
  Manual/other: 4
  Inline hydrographs: 1

Unsteady 12: 4 total BCs
  DSS-linked: 0
  Manual/other: 4
  Inline hydrographs: 1

Unsteady 13: 4 total BCs
  DSS-linked: 0
  Manual/other: 4
  Inline hydrographs: 1

Step 12: Inline Hydrograph Detection

Some unsteady files contain inline (manually entered) hydrographs instead of DSS links. These are extracted with RasUnsteady.get_inline_hydrograph_boundaries() and can be matched against DSS catalog entries.

Python
# Check all unsteady files for inline hydrographs
print('=== Inline Hydrograph Detection ===\n')

all_inline = []
for _, u_row in ras.unsteady_df.iterrows():
    u_path = u_row['full_path']
    u_num = u_row['unsteady_number']

    try:
        inline_bcs = RasUnsteady.get_inline_hydrograph_boundaries(u_path)
        if len(inline_bcs) > 0:
            inline_bcs['unsteady_number'] = u_num
            all_inline.append(inline_bcs)
            print(f'Unsteady {u_num} ({Path(u_path).name}): {len(inline_bcs)} inline hydrographs')
            for _, bc in inline_bcs.iterrows():
                print(f'  {bc["river"]}/{bc["reach"]}/{bc["station"]}: '
                      f'Peak={bc["peak_value"]:.0f}, Type={bc["bc_type"]}')
    except Exception as e:
        pass  # File may not have inline hydrographs

if all_inline:
    all_inline_df = pd.concat(all_inline, ignore_index=True)
    print(f'\nTotal inline hydrographs found: {len(all_inline_df)}')
else:
    print('No inline hydrographs found in any unsteady file.')
    print('All boundaries use DSS links - which is typical for this project.')
Text Only
2026-06-11 16:48:22 - ras_commander.RasUnsteady - INFO - Found 1 inline hydrograph boundaries in BaldEagleDamBrk.u07


2026-06-11 16:48:22 - ras_commander.RasUnsteady - INFO - Found 2 inline hydrograph boundaries in BaldEagleDamBrk.u08


2026-06-11 16:48:22 - ras_commander.RasUnsteady - INFO - Found 1 inline hydrograph boundaries in BaldEagleDamBrk.u09


2026-06-11 16:48:22 - ras_commander.RasUnsteady - INFO - Found 1 inline hydrograph boundaries in BaldEagleDamBrk.u10


2026-06-11 16:48:22 - ras_commander.RasUnsteady - INFO - Found 1 inline hydrograph boundaries in BaldEagleDamBrk.u11


2026-06-11 16:48:22 - ras_commander.RasUnsteady - INFO - Found 1 inline hydrograph boundaries in BaldEagleDamBrk.u12


2026-06-11 16:48:22 - ras_commander.RasUnsteady - INFO - Found 1 inline hydrograph boundaries in BaldEagleDamBrk.u13


2026-06-11 16:48:22 - ras_commander.RasUnsteady - INFO - Found 1 inline hydrograph boundaries in BaldEagleDamBrk.u01


2026-06-11 16:48:22 - ras_commander.RasUnsteady - INFO - Found 1 inline hydrograph boundaries in BaldEagleDamBrk.u02


2026-06-11 16:48:22 - ras_commander.RasUnsteady - INFO - Found 1 inline hydrograph boundaries in BaldEagleDamBrk.u03


=== Inline Hydrograph Detection ===

Unsteady 07 (BaldEagleDamBrk.u07): 1 inline hydrographs
  Bald Eagle Cr./Lock Haven/1: Peak=5000, Type=Lateral Inflow Hydrograph
Unsteady 08 (BaldEagleDamBrk.u08): 2 inline hydrographs
  Bald Eagle Cr./Lock Haven/1: Peak=5000, Type=Lateral Inflow Hydrograph
  //: Peak=135617, Type=Flow Hydrograph
Unsteady 09 (BaldEagleDamBrk.u09): 1 inline hydrographs
  //: Peak=135617, Type=Flow Hydrograph
Unsteady 10 (BaldEagleDamBrk.u10): 1 inline hydrographs
  //: Peak=100000, Type=Flow Hydrograph
Unsteady 11 (BaldEagleDamBrk.u11): 1 inline hydrographs
  //: Peak=100000, Type=Lateral Inflow Hydrograph
Unsteady 12 (BaldEagleDamBrk.u12): 1 inline hydrographs
  Bald Eagle Cr./Lock Haven/137520: Peak=193738, Type=Flow Hydrograph
Unsteady 13 (BaldEagleDamBrk.u13): 1 inline hydrographs
  //: Peak=100000, Type=Flow Hydrograph
Unsteady 01 (BaldEagleDamBrk.u01): 1 inline hydrographs
  //: Peak=100000, Type=Lateral Inflow Hydrograph
Unsteady 02 (BaldEagleDamBrk.u02): 1 inline hydrographs
  //: Peak=100000, Type=Flow Hydrograph
Unsteady 03 (BaldEagleDamBrk.u03): 1 inline hydrographs
  //: Peak=30000, Type=Flow Hydrograph

Total inline hydrographs found: 11

Step 13: Match Inline Hydrographs to DSS Catalog (if any)

If inline hydrographs were found, compare them against the DSS catalog to identify which HMS outputs they correspond to. This is the core of the HMS-to-RAS boundary matching workflow for non-DSS-linked boundaries.

Python
# Compare inline hydrographs to DSS catalog (if any found)
if all_inline:
    inline_matches = []

    for _, bc_row in all_inline_df.iterrows():
        bc_flow = bc_row['values']
        if bc_flow is None or len(bc_flow) == 0:
            continue

        best_match = None
        best_corr = -1

        for pathname, dss_flow in dss_timeseries_catalog.items():
            result = RasHydroCompare.compare_hydrographs(
                bc_flow, dss_flow, truncate_to_shorter=True
            )

            if result['correlation'] > best_corr:
                best_corr = result['correlation']
                hms_element = pathname.strip('/').split('/')[0]
                best_match = {
                    'river': bc_row['river'],
                    'reach': bc_row['reach'],
                    'station': bc_row['station'],
                    'bc_type': bc_row['bc_type'],
                    'peak_value': bc_row['peak_value'],
                    'unsteady_number': bc_row['unsteady_number'],
                    'best_hms_element': hms_element,
                    'best_dss_pathname': pathname,
                    'correlation': result['correlation'],
                    'nrmse_pct': result['nrmse_pct'],
                    'peak_ratio': result['peak_ratio'],
                    'quality': RasHydroCompare.classify_match(
                        result['correlation'], result['nrmse_pct']
                    ),
                }

        if best_match:
            inline_matches.append(best_match)

    if inline_matches:
        inline_match_df = pd.DataFrame(inline_matches)
        print('=== Inline Hydrograph Matching Results ===')
        print(f'Inline hydrographs matched: {len(inline_match_df)}')
        print()

        for quality, count in inline_match_df['quality'].value_counts().items():
            print(f'  {quality}: {count}')

        print()
        for _, row in inline_match_df.iterrows():
            print(f'  [{row["quality"]:8s}] {row["river"]}/{row["reach"]}/{row["station"]}')
            print(f'    Best HMS match: {row["best_hms_element"]}')
            print(f'    Correlation: {row["correlation"]:.6f}, NRMSE: {row["nrmse_pct"]:.4f}%')
            print(f'    Peak ratio: {row["peak_ratio"]:.4f}')
            print()
    else:
        print('No inline hydrographs to match.')
else:
    print('No inline hydrographs found - all boundaries use DSS links.')
    print('The DSS link validation in Steps 8-9 confirms correct HMS-RAS linkage.')
Text Only
=== Inline Hydrograph Matching Results ===
Inline hydrographs matched: 11

  MISMATCH: 11

  [MISMATCH] Bald Eagle Cr./Lock Haven/1
    Best HMS match: MARSH CREEK
    Correlation: 0.000000, NRMSE: 100.0000%
    Peak ratio: 0.1322

  [MISMATCH] Bald Eagle Cr./Lock Haven/1
    Best HMS match: MARSH CREEK
    Correlation: 0.000000, NRMSE: 100.0000%
    Peak ratio: 0.1322

  [MISMATCH] //
    Best HMS match: LOCAL DOWNSTREAM OF DAM
    Correlation: -0.174073, NRMSE: 41.2502%
    Peak ratio: 24.3558

  [MISMATCH] //
    Best HMS match: BALD EAGLE 10
    Correlation: -0.352899, NRMSE: 55.0453%
    Peak ratio: 0.7187

  [MISMATCH] //
    Best HMS match: BEECH CREEK
    Correlation: 0.455116, NRMSE: 55.3967%
    Peak ratio: 218.5219

  [MISMATCH] //
    Best HMS match: BEECH CREEK
    Correlation: 0.455116, NRMSE: 55.3967%
    Peak ratio: 218.5219

  [MISMATCH] Bald Eagle Cr./Lock Haven/137520
    Best HMS match: BALD EAGLE LOCAL
    Correlation: 0.540469, NRMSE: 32.9686%
    Peak ratio: 7.0635

  [MISMATCH] //
    Best HMS match: BALD EAGLE 10
    Correlation: -0.363866, NRMSE: 56.5483%
    Peak ratio: 0.6084

  [MISMATCH] //
    Best HMS match: BEECH CREEK
    Correlation: 0.455116, NRMSE: 55.3967%
    Peak ratio: 218.5219

  [MISMATCH] //
    Best HMS match: BALD EAGLE 10
    Correlation: -0.363866, NRMSE: 56.5483%
    Peak ratio: 0.6084

  [MISMATCH] //
    Best HMS match: RESERVOIR LOCAL
    Correlation: -0.045301, NRMSE: 51.9358%
    Peak ratio: 0.3986

Step 14: Export Results

Save matching results for documentation and further analysis.

Python
# Export comparison results
output_dir = project_path
output_file = output_dir / 'hms_ras_matching_results.csv'

if len(comparison_df) > 0:
    comparison_df.to_csv(output_file, index=False)
    print(f'DSS link validation results exported to: {output_file}')

# Export inline matching results if available
if all_inline and 'inline_match_df' in dir() and len(inline_match_df) > 0:
    inline_output = output_dir / 'inline_matching_results.csv'
    inline_match_df.to_csv(inline_output, index=False)
    print(f'Inline matching results exported to: {inline_output}')

# Create summary report
n_confirmed = comparison_df[comparison_df['quality'].isin(['EXACT', 'CLOSE'])].shape[0] if len(comparison_df) > 0 else 0
n_total = len(comparison_df)

summary = f'''HMS-to-RAS Boundary Condition Matching Results
===============================================

Project: {project_path.name}
Plan: {PLAN_NUMBER}
DSS File: {DSS_FILENAME}

DSS Link Validation:
  Total DSS boundaries:   {n_total}
  Confirmed (EXACT/CLOSE): {n_confirmed}
  Other:                   {n_total - n_confirmed}

Inline Hydrographs: {'Found ' + str(len(all_inline_df)) if all_inline else 'None (all DSS-linked)'}
'''

print(summary)

# Save summary
summary_file = output_dir / 'matching_summary.txt'
with open(summary_file, 'w') as f:
    f.write(summary)
print(f'Summary saved to: {summary_file}')
Text Only
DSS link validation results exported to: <workspace>\examples\example_projects\BaldEagleCrkMulti2D_313\hms_ras_matching_results.csv




Inline matching results exported to: <workspace>\examples\example_projects\BaldEagleCrkMulti2D_313\inline_matching_results.csv
HMS-to-RAS Boundary Condition Matching Results
===============================================

Project: BaldEagleCrkMulti2D_313
Plan: 07
DSS File: Bald_Eagle_Creek.dss

DSS Link Validation:
  Total DSS boundaries:   7
  Confirmed (EXACT/CLOSE): 7
  Other:                   0

Inline Hydrographs: Found 11

Summary saved to: <workspace>\examples\example_projects\BaldEagleCrkMulti2D_313\matching_summary.txt

Key Takeaways

Two Workflow Patterns for HMS-RAS Matching

Pattern 1 - DSS Link Validation (this project): When RAS boundaries already use DSS links, validation confirms correct linkage: 1. Read boundaries_df to get DSS paths and parsed components (dss_part_a = HMS subbasin) 2. Read DSS catalog with RasDss.get_catalog() 3. Extract time series with RasDss.extract_boundary_timeseries() 4. Cross-match using RasHydroCompare.compare_hydrographs() 5. Classify with RasHydroCompare.classify_match() (EXACT/CLOSE/POSSIBLE/MISMATCH)

Pattern 2 - Inline Hydrograph Matching: When RAS boundaries have inline (manually entered) hydrographs: 1. Extract inline data with RasUnsteady.get_inline_hydrograph_boundaries() 2. Read HMS DSS catalog and time series 3. Compare each inline hydrograph against all DSS entries 4. Best-match identifies which HMS subbasin feeds each RAS boundary

Key Functions

Function Purpose
RasDss.get_catalog() List all pathnames in a DSS file
RasDss.read_timeseries() Read a single DSS time series
RasDss.extract_boundary_timeseries() Batch extract all DSS boundary data
RasHydroCompare.compare_hydrographs() Calculate correlation and NRMSE
RasHydroCompare.classify_match() Classify match quality
RasUnsteady.get_inline_hydrograph_boundaries() Extract inline hydrograph data