Architecture¶
Overview of RAS Commander's design and implementation.
The Big Picture¶
Before diving into details, understand the core workflow:
flowchart LR
subgraph "1. Setup"
A[init_ras_project] --> B[ras object]
end
subgraph "2. Configure"
B --> C[Modify Plans/Geometry]
end
subgraph "3. Execute"
C --> D[RasCmdr.compute_plan]
end
subgraph "4. Extract"
D --> E[HDF Results]
end
style A fill:#4CAF50,color:#fff
style D fill:#2196F3,color:#fff
style E fill:#FF9800,color:#fff
The fundamental pattern: Initialize β Configure β Execute β Extract Results
The Two Worlds: Config vs Results¶
This is THE key insight for understanding ras-commander:
flowchart TB
subgraph CONFIG["π BEFORE Execution (Configuration)"]
direction TB
P[".p## Plan Files"]
G[".g## Geometry Files"]
U[".u## Unsteady Files"]
RP[RasPlan]
RG["Geom* Classes"]
RU[RasUnsteady]
P <--> RP
G <--> RG
U <--> RU
end
subgraph EXEC["βοΈ Execution"]
HEC["HEC-RAS.exe"]
end
subgraph RESULTS["π AFTER Execution (Results)"]
direction TB
HDF[".p##.hdf Result Files"]
HRM[HdfResultsMesh]
HRX[HdfResultsXsec]
HRP[HdfResultsPlan]
HDF --> HRM
HDF --> HRX
HDF --> HRP
end
CONFIG --> EXEC
EXEC --> RESULTS
style CONFIG fill:#E3F2FD,stroke:#1976D2
style RESULTS fill:#FFF3E0,stroke:#F57C00
style EXEC fill:#E8F5E9,stroke:#388E3C
Rule of thumb:
- Need to change settings? β Use Ras* or Geom* classes (plain text)
- Need to read results? β Use Hdf* classes (binary HDF)
Design Philosophy¶
1. Static Class Pattern¶
Most operations don't require state, so classes use static methods:
Benefits: - Simple, functional-style API - No object lifecycle management - Easy to test individual functions
2. Project State via RAS Objects¶
Project state is managed through RasPrj instances:
# Global singleton for single-project scripts
from ras_commander import ras
init_ras_project("/path", "6.5")
print(ras.plan_df)
# Named instances for multi-project
project1 = RasPrj()
init_ras_project("/path1", "6.5", ras_object=project1)
3. Plain Text vs HDF Separation¶
Different classes handle different file types:
| File Type | Classes | Operations |
|---|---|---|
.p##, .g##, .u## |
Ras* | Read/write parameters |
.p##.hdf, .g##.hdf |
Hdf* | Read results |
4. Lazy Loading¶
Heavy dependencies load only when needed:
# DSS module not loaded until first use
from ras_commander import RasDss # Still fast
# Only now does Java bridge initialize
RasDss.get_catalog("file.dss")
5. Input Standardization¶
The @standardize_input decorator accepts multiple input types:
@standardize_input("plan_path")
def get_results(plan_path: Union[str, int, Path]) -> dict:
# plan_path is always resolved to full path
pass
# All valid:
get_results("01") # Plan number
get_results(1) # Integer
get_results(Path("x.hdf")) # Path object
Module Organization¶
ras_commander/
β
βββ Core Modules (flat)
β βββ __init__.py # Exports, lazy imports
β βββ RasPrj.py # Project management
β βββ RasCmdr.py # Plan execution
β βββ RasPlan.py # Plan file operations
β βββ RasGeo.py # 2D geometry operations
β βββ RasUnsteady.py # Unsteady flow files
β βββ RasUtils.py # Utilities
β βββ RasExamples.py # Example management
β βββ RasMap.py # RASMapper parsing
β βββ RasControl.py # Legacy COM interface
β βββ RasBreach.py # Breach parameters
β βββ Decorators.py # @log_call, @standardize_input
β βββ LoggingConfig.py # Centralized logging
β
βββ hdf/ # HDF submodule
β βββ __init__.py
β βββ HdfBase.py # Core HDF operations
β βββ HdfPlan.py # Plan info
β βββ HdfMesh.py # Mesh geometry
β βββ HdfResultsMesh.py # Mesh results
β βββ HdfResultsPlan.py # Plan results
β βββ HdfResultsXsec.py # Cross section results
β βββ HdfStruc.py # Structures
β βββ HdfResultsBreach.py
β βββ HdfHydraulicTables.py
β βββ HdfPipe.py
β βββ HdfPump.py
β βββ HdfFluvialPluvial.py
β βββ HdfBndry.py
β βββ HdfPlot.py
β
βββ geom/ # Geometry parsing submodule
β βββ __init__.py
β βββ RasGeometry.py # Cross sections, storage
β βββ RasGeometryUtils.py
β βββ RasStruct.py # Inline structures
β
βββ dss/ # DSS operations submodule
β βββ __init__.py
β βββ RasDss.py
β
βββ remote/ # Remote execution submodule
βββ __init__.py
βββ Execution.py # compute_parallel_remote
βββ LocalWorker.py
βββ PsexecWorker.py
βββ DockerWorker.py
Understanding Class Names¶
Class names are systematic - learn the pattern, not 30 individual classes:
flowchart LR
subgraph PREFIX["Prefix = Data Source"]
H[Hdf] --> H1["HDF binary files"]
G[Geom] --> G1["Geometry text files"]
R[Ras] --> R1["General text files"]
end
subgraph MIDDLE["Middle = Scope"]
M[Mesh] --> M1["2D mesh data"]
X[Xsec] --> X1["1D cross sections"]
P[Plan] --> P1["Plan-level data"]
end
subgraph SUFFIX["'Results' = Output Data"]
RS[Results] --> RS1["Post-execution output"]
end
Decode any class:
| Class Name | Breakdown | Meaning |
|---|---|---|
HdfResultsMesh |
Hdf + Results + Mesh | 2D output from HDF |
HdfResultsXsec |
Hdf + Results + Xsec | 1D output from HDF |
GeomCrossSection |
Geom + CrossSection | Cross section from geometry file |
RasPlan |
Ras + Plan | Plan text file operations |
Choosing How to Execute¶
flowchart TD
START[How many plans?] --> ONE{One plan?}
ONE -->|Yes| CP["RasCmdr.compute_plan()"]
ONE -->|No| MACHINE{How many machines?}
MACHINE -->|One machine| PARA["RasCmdr.compute_parallel()"]
MACHINE -->|Multiple machines| REMOTE["compute_parallel_remote()"]
CP --> DONE[Results in .hdf]
PARA --> DONE
REMOTE --> DONE
style CP fill:#4CAF50,color:#fff
style PARA fill:#2196F3,color:#fff
style REMOTE fill:#9C27B0,color:#fff
| Scenario | Function | When to Use |
|---|---|---|
| Single plan | compute_plan("01") |
Testing, simple runs |
| Multiple plans, one PC | compute_parallel(["01","02","03"]) |
Sensitivity analysis |
| Multiple plans, multiple PCs | compute_parallel_remote(plans, workers) |
Large batches |
Submodule Guide¶
Choose the right submodule based on your task:
flowchart TD
TASK[What do you need?] --> Q1{Read simulation results?}
Q1 -->|Yes| HDF["hdf/ submodule"]
Q1 -->|No| Q2{Parse/modify geometry?}
Q2 -->|Yes| GEOM["geom/ submodule"]
Q2 -->|No| Q3{Read DSS boundary data?}
Q3 -->|Yes| DSS["dss/ submodule"]
Q3 -->|No| Q4{Distribute to multiple machines?}
Q4 -->|Yes| REMOTE["remote/ submodule"]
Q4 -->|No| CORE["Core classes (RasPlan, RasCmdr, etc.)"]
HDF --> HDF_EX["HdfResultsMesh.get_mesh_max_ws()"]
GEOM --> GEOM_EX["GeomCrossSection.get_station_elevation()"]
DSS --> DSS_EX["RasDss.read_timeseries()"]
REMOTE --> REMOTE_EX["compute_parallel_remote()"]
CORE --> CORE_EX["RasCmdr.compute_plan()"]
style HDF fill:#FF9800,color:#fff
style GEOM fill:#4CAF50,color:#fff
style DSS fill:#9C27B0,color:#fff
style REMOTE fill:#2196F3,color:#fff
style CORE fill:#607D8B,color:#fff
| Submodule | Purpose | Key Classes |
|---|---|---|
hdf/ |
Read HDF results | HdfResultsMesh, HdfResultsXsec, HdfResultsPlan |
geom/ |
Parse/modify geometry | GeomCrossSection, GeomBridge, GeomStorage |
dss/ |
Read DSS files | RasDss |
remote/ |
Distributed execution | LocalWorker, PsexecWorker, DockerWorker |
| (core) | Project mgmt, execution | RasPrj, RasCmdr, RasPlan |
Data Flow¶
βββββββββββββββββββ
β init_ras_projectβ
ββββββββββ¬βββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββ
β RasPrj β
β βββββββββββ βββββββββββ βββββββββββββββββββββ β
β β plan_df β β geom_df β β boundaries_df β β
β βββββββββββ βββββββββββ βββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββΌβββββββββββββββββββ
β β β
βΌ βΌ βΌ
ββββββββββββ ββββββββββββ ββββββββββββ
β RasPlan β β RasCmdr β β RasGeo β
β modify β β execute β β modify β
ββββββββββββ ββββββ¬ββββββ ββββββββββββ
β
βΌ
ββββββββββββββββ
β HEC-RAS.exe β
ββββββββ¬ββββββββ
β
βΌ
ββββββββββββββββ
β .p##.hdf β
ββββββββ¬ββββββββ
β
ββββββββββββββββββΌβββββββββββββββββ
β β β
βΌ βΌ βΌ
βββββββββββββ βββββββββββββ βββββββββββββ
βHdfResults β βHdfResults β βHdfResults β
β Mesh β β Xsec β β Plan β
βββββββββββββ βββββββββββββ βββββββββββββ
Key Patterns¶
Decorator Stack¶
class RasCmdr:
@staticmethod
@log_call # Automatic logging
@standardize_input("plan") # Input normalization
def compute_plan(plan, ...) -> bool:
pass
DataFrame Pattern¶
Project data stored in pandas DataFrames:
# plan_df columns:
# - plan_number: "01", "02", etc.
# - plan_path: Path to .p## file
# - plan_title: Plan name
# - geom_number: Linked geometry
# - flow_number: Linked flow file
# - hdf_path: Path to results HDF (if exists)
Worker Abstraction¶
Remote execution uses worker interface:
class BaseWorker:
def validate(self) -> bool: ...
def execute_plan(self, plan, dest) -> bool: ...
def cleanup(self) -> None: ...
class LocalWorker(BaseWorker): ...
class PsexecWorker(BaseWorker): ...
class DockerWorker(BaseWorker): ...
Error Propagation¶
def outer_function():
try:
result = inner_function()
except ValueError:
logger.error("Invalid input")
return None
except FileNotFoundError:
logger.error("File missing")
raise # Re-raise critical errors
Performance Considerations¶
- HDF Access: Use specific methods (
get_mesh_max_ws()) over general ones - Parallel Execution: Balance workers vs cores per worker
- Memory: Large meshes may need chunked processing
- DSS: First call downloads libraries (~17 MB)
Extension Points¶
Adding New HDF Extraction¶
- Add method to appropriate
HdfResults*class - Use
@standardize_inputdecorator - Return DataFrame or GeoDataFrame
- Add to
__init__.pyexports
Adding New Worker Type¶
- Create
NewWorker.pyinremote/ - Inherit from
BaseWorker - Implement
validate(),execute_plan(),cleanup() - Register in
init_ras_worker()factory