RAS-Commander (ras_commander) is a Python library designed to automate and streamline operations with HEC-RAS projects. It provides a suite of tools for managing projects, executing simulations, and handling results. This guide offers a comprehensive overview of the library's key concepts, modules, best practices, and advanced usage patterns. RAS-Commander is designed to be flexible, robust, and AI-accessible, making it an ideal tool for both manual and automated HEC-RAS workflows.
RAS-Commander can be installed with the following commands:
# Install dependencies first for potentially smoother installation
pip install h5py numpy pandas requests tqdm scipy xarray geopandas matplotlib psutil shapely fiona pathlib rtree
# Install or update ras-commander
pip install --upgrade ras-commander-
RAS Objects (
RasPrj):- Represent HEC-RAS projects containing information about plans, geometries, and flow files.
- Support both a global
rasobject (imported fromras_commander) and custom namedRasPrjinstances to handle multiple projects. - Include
rasmap_dfattribute providing access to spatial datasets referenced in RASMapper.
-
Project Initialization:
- Use
init_ras_project()to initialize projects and set upRasPrjobjects. - Handles project file (
.prj) discovery, HEC-RAS executable path determination, and data structure setup.
- Use
-
File Handling:
- Utilizes
pathlib.Pathfor consistent, platform-independent file paths. - Adheres to HEC-RAS file naming conventions (
.prj,.p01,.g01,.f01,.u01,.hdf).
- Utilizes
-
Data Management:
- Employs Pandas DataFrames (accessible via
ras_object.plan_df,geom_df,flow_df,unsteady_df,boundaries_df) to manage structured data about project components. - Provides methods for accessing and updating project files, which subsequently refresh these DataFrames.
- Employs Pandas DataFrames (accessible via
-
Execution Modes (
RasCmdr):- Single Plan Execution: Run individual plans with
RasCmdr.compute_plan(). - Sequential Execution: Run multiple plans in sequence in a test folder with
RasCmdr.compute_test_mode(). - Parallel Execution: Run multiple independent plans concurrently using worker folders with
RasCmdr.compute_parallel().
- Single Plan Execution: Run individual plans with
-
Example Projects (
RasExamples):- The
RasExamplesclass offers functionality to download and manage official HEC-RAS example projects for testing and learning.
- The
-
Utility Functions (
RasUtils):- Provides common utility functions for file operations (cloning, updating, removing), path handling, data conversion, error metrics calculation, and spatial queries.
-
AI-Accessibility:
- Structured, consistent codebase with clear documentation (like this guide and docstrings) intended to facilitate easier learning and usage by AI models and developers. Code aims for predictability.
-
Boundary Conditions (
RasPrj.boundaries_df):- Represent input conditions (flow/stage hydrographs, normal depth, etc.).
- The
RasPrjclass extracts and manages boundary condition data parsed from unsteady flow files (.u*).
-
Flexibility and Modularity:
- Functions operating on projects accept an optional
ras_objectparameter, allowing use with the globalrasor customRasPrjinstances. - Clear separation of concerns: project management (
RasPrj), execution (RasCmdr), file operations (RasPlan,RasGeo,RasUnsteady), HDF data retrieval (Hdf*classes), utilities (RasUtils), and examples (RasExamples).
- Functions operating on projects accept an optional
-
Error Handling and Logging:
- Emphasis on robust error checking (e.g.,
check_initialized) and informative logging. - Uses the
LoggingConfigmodule for consistent setup (console and optional file output). @log_calldecorator applied to most public functions for automatic call tracing at the DEBUG level.
- Emphasis on robust error checking (e.g.,
-
HDF Support (
Hdf*classes):- Comprehensive HDF file handling through specialized classes (
HdfBase,HdfMesh,HdfPlan,HdfResultsMesh,HdfResultsPlan,HdfResultsXsec,HdfStruc,HdfPipe,HdfPump,HdfFluvialPluvial,HdfInfiltration,HdfBndry,HdfXsec,HdfPlot,HdfResultsPlot). - Support for mesh, plan, cross-section geometry and results.
- Advanced data extraction, analysis, and plotting capabilities.
- Comprehensive HDF file handling through specialized classes (
-
Infrastructure Support (
HdfPipe,HdfPump,HdfStruc):- Tools for accessing geometry and results data related to pipe networks, pump stations, and other hydraulic structures from HDF files.
- Project Management: Initialize, load, and inspect HEC-RAS projects (
RasPrj,init_ras_project). - Plan Execution: Run single or multiple HEC-RAS plans sequentially or in parallel (
RasCmdr). - File Operations: Clone, modify, and manage HEC-RAS file types (plans, geometries, flows) programmatically (
RasPlan,RasGeo,RasUnsteady,RasUtils). - Data Extraction: Retrieve and process simulation results and geometry from HDF files (
Hdf*classes). - Boundary Condition Management: Extract and analyze boundary conditions from unsteady flow files (
RasPrj.get_boundary_conditions). - Parallel Processing: Optimize simulation time for multiple independent plans (
RasCmdr.compute_parallel). - Example Project Handling: Download and manage HEC-RAS example projects (
RasExamples). - Utility Functions: Perform common tasks like file manipulation, path resolution, error calculation, and spatial queries (
RasUtils). - HDF File Handling: Specialized classes for structured access to HEC-RAS HDF data.
- Infrastructure Analysis: Tools for analyzing pipe networks, pump stations, and structures via HDF data (
HdfPipe,HdfPump,HdfStruc). - Visualization: Basic plotting capabilities for HDF results (
HdfPlot,HdfResultsPlot).
- RasPrj: Manages HEC-RAS project state, including file discovery and dataframes for plans, geoms, flows, unsteady files, and boundary conditions.
- RasCmdr: Handles execution of HEC-RAS simulations via command line (single, sequential, parallel).
- RasPlan: Provides functions for plan file (
.p*) operations (cloning, modifying parameters like geometry, flow files, cores, intervals, description). - RasGeo: Manages geometry-related operations, primarily clearing preprocessor files (
.c*). - RasUnsteady: Handles unsteady flow file (
.u*) operations (updating title, restart settings, extracting boundary tables). - RasUtils: Offers general utility functions (file handling, path finding, data conversion, error metrics, spatial queries).
- RasExamples: Manages downloading and extracting official HEC-RAS example projects.
- RasMap: Parses HEC-RAS mapper configuration files (.rasmap) to extract paths to terrain, soil layer, land cover data, and other spatial datasets. Provides access to projection information and RASMapper settings.
- HdfBase: Provides base functionality for HDF file operations (time parsing, attribute access, projection).
- HdfBndry: Handles boundary geometry features (BC lines, breaklines, etc.) from geometry HDF files.
- HdfMesh: Manages mesh geometry data (cell polygons, points, faces, attributes) from HDF files.
- HdfPlan: Handles plan-level information (simulation times, parameters) from plan HDF files.
- HdfResultsMesh: Processes mesh results (WSE, velocity, depth timeseries, summaries) from plan HDF files.
- HdfResultsPlan: Handles plan-level results (volume accounting, runtime stats) from plan HDF files.
- HdfResultsXsec: Processes 1D cross-section results (WSE, flow, velocity timeseries) from plan HDF files.
- HdfStruc: Manages structure geometry data (centerlines, profiles) from geometry HDF files.
- HdfUtils: Provides utility functions specifically for HDF data handling (data type conversions, spatial queries).
- HdfXsec: Handles 1D cross-section and river geometry (cut lines, centerlines, banks) from geometry HDF files.
- HdfPipe: Handles pipe network geometry and results data from HDF files.
- HdfPump: Manages pump station geometry and results data from HDF files.
- HdfFluvialPluvial: Analyzes fluvial vs. pluvial boundaries based on results timing in plan HDF files.
- HdfInfiltration: Handles infiltration layer data (parameters, maps) from geometry or
.tif.hdffiles. - HdfPlot & HdfResultsPlot: Basic visualization utilities for HDF data and results.
- Decorators: Contains
@log_calland@standardize_input. - LoggingConfig: Sets up and provides access to the library's logging system.
-
Single Project Scripts:
- Use the global
rasobject for simplicity after initializing it.
from ras_commander import ras, init_ras_project # Initialize the global 'ras' object init_ras_project("/path/to/project", "6.5") # Use ras object for operations print(ras.plan_df)
- Use the global
-
Multiple Projects:
- Create separate
RasPrjinstances for each project usinginit_ras_projectwith theras_objectparameter set to a new instance or a unique identifier string. Always pass the specificras_objectto functions that need project context.
from ras_commander import RasPrj, init_ras_project, RasCmdr # Initialize two separate projects using new instances project1 = init_ras_project("/path/to/project1", "6.5", ras_object=RasPrj()) project2 = init_ras_project("/path/to/project2", "6.5", ras_object=RasPrj()) # Pass the specific object when calling functions RasCmdr.compute_plan("01", ras_object=project1) RasCmdr.compute_plan("02", ras_object=project2)
- Create separate
-
Consistency:
- Avoid mixing global
rasusage and customRasPrjinstances within the same logical part of your script to prevent confusion. When using multiple projects, always pass theras_objectparameter.
- Avoid mixing global
-
Use plan numbers as strings (e.g.,
"01","02") for consistency with file naming conventions when calling functions likecompute_plan.RasCmdr.compute_plan("01")
-
Check available plans before specifying plan numbers using the
plan_dfattribute.print(ras.plan_df) # Displays available plans and their details available_plans = ras.plan_df['plan_number'].tolist() print(f"Available plans: {available_plans}")
- Clear geometry preprocessor files (
.c*) usingRasGeo.clear_geompre_files()before running a plan if the underlying geometry (.g*) file has been significantly modified. This forces HEC-RAS to recalculate hydraulic tables.from ras_commander import RasPlan, RasGeo plan_path = RasPlan.get_plan_path("01") # Get path if needed RasGeo.clear_geompre_files(plan_path) # Clear for a specific plan # Or clear for all plans: RasGeo.clear_geompre_files()
- Alternatively, use
clear_geompre=TrueinRasCmdrfunctions for a clean computation environment, though callingRasGeo.clear_geompre_filesdirectly offers more control.RasCmdr.compute_plan("01", clear_geompre=True)
-
Adjust
max_workers(number of parallel HEC-RAS instances) based on available physical CPU cores and RAM. Each worker needs significant RAM (2-4GB+). -
Set
num_cores(cores per worker) to balance single-plan speed vs. overall throughput. Common values are 2-4 cores per worker.import psutil physical_cores = psutil.cpu_count(logical=False) cores_per_worker = 2 max_workers_cpu = max(1, physical_cores // cores_per_worker) # Consider RAM constraints as well - may need fewer workers than CPU allows max_workers = min(max_workers_cpu, 4) # Example: Limit to 4 workers regardless of CPU RasCmdr.compute_parallel(max_workers=max_workers, num_cores=cores_per_worker)
-
Use
dest_folderto organize outputs from parallel runs and prevent conflicts with the original project or other runs. Useoverwrite_dest=Truecautiously.results_dir = Path("./parallel_run_output") RasCmdr.compute_parallel(dest_folder=results_dir, overwrite_dest=True)
Proper error handling and logging are crucial for robust ras_commander scripts.
-
Logging Setup: The library automatically sets up basic console logging at the INFO level. You can change the level or add file logging.
import logging from ras_commander import setup_logging, get_logger # Change console log level to DEBUG logging.getLogger('ras_commander').setLevel(logging.DEBUG) # Or, setup file logging as well # setup_logging(log_file='my_ras_script.log', log_level=logging.DEBUG) # Get a logger for your script script_logger = get_logger(__name__) script_logger.info("Script starting...")
-
Using the
@log_callDecorator: Most public library functions already use@log_call, automatically logging function entry/exit at the DEBUG level. -
Custom Logging: Use the logger obtained via
get_logger()for more detailed script-specific logging.logger = get_logger(__name__) def my_custom_ras_step(): logger.info("Starting custom step...") try: # Perform operations using ras_commander functions result = RasCmdr.compute_plan("01") if not result: logger.warning("Plan 01 computation reported failure.") # Process result... logger.info("Custom step completed.") except Exception as e: logger.error(f"Custom step failed: {str(e)}", exc_info=True) # Log exception details # Handle error appropriately
-
Error Handling Best Practices:
- Use
try...exceptblocks around library calls that might fail (e.g., file operations, computations). - Catch specific exceptions (
FileNotFoundError,ValueError,subprocess.CalledProcessError) where possible. - Check return values from functions like
compute_planwhich returnTrue/False. - Provide informative error messages in your logs or exceptions.
try: plan_path = RasPlan.get_plan_path("99") # Plan likely doesn't exist if plan_path is None: raise ValueError("Plan 99 does not exist in the project.") # ... more operations except ValueError as ve: script_logger.error(f"Configuration error: {ve}") except FileNotFoundError as fnf: script_logger.error(f"File access error: {fnf}") except Exception as ex: script_logger.critical(f"An unexpected error occurred: {ex}", exc_info=True)
- Use
- Use
pathlib.Pathobjects for robust file and directory operations within your scripts. Library functions generally acceptstrorPath.from pathlib import Path project_dir = Path("/path/to/my/project") results_dir = project_dir.parent / "results" # Pass Path objects to library functions init_ras_project(project_dir, "6.5") RasCmdr.compute_plan("01", dest_folder=results_dir)
- Apply type hints in your own functions that use
ras_commanderto improve code readability, maintainability, and IDE support.from ras_commander import RasPrj, RasCmdr from typing import Dict, List def run_specific_plans(project: RasPrj, plans_to_run: List[str]) -> Dict[str, bool]: """Runs a list of plans for the given project.""" results = RasCmdr.compute_parallel( plan_number=plans_to_run, ras_object=project, max_workers=2, num_cores=2 ) return results
from ras_commander import init_ras_project, ras
# Initializes the global 'ras' object
init_ras_project("/path/to/project", "6.5")
print(f"Working with project: {ras.project_name}")
print(f"Available plans:\n{ras.plan_df}")from ras_commander import RasPlan, ras, init_ras_project
init_ras_project("/path/to/project", "6.5")
# Clone plan "01", automatically assigns next number (e.g., "02")
new_plan_number = RasPlan.clone_plan("01", new_plan_shortid="Cloned Plan Test")
print(f"Created new plan: {new_plan_number}")
# The 'ras' object is automatically refreshed after cloning
print(f"Updated plan list:\n{ras.plan_df}")-
Single Plan Execution:
from ras_commander import RasCmdr, init_ras_project init_ras_project("/path/to/project", "6.5") success = RasCmdr.compute_plan("01", num_cores=2) print(f"Plan '01' execution {'successful' if success else 'failed'}")
-
Parallel Execution of Multiple Plans:
from ras_commander import RasCmdr, init_ras_project from pathlib import Path init_ras_project("/path/to/project", "6.5") results_folder = Path("./parallel_results") results = RasCmdr.compute_parallel( plan_number=["01", "02", "03"], max_workers=3, num_cores=2, # 2 cores per worker dest_folder=results_folder, overwrite_dest=True, clear_geompre=False ) print("Parallel execution results:") for plan, success in results.items(): print(f" Plan {plan}: {'Successful' if success else 'Failed'}")
-
Sequential Execution in Test Folder:
from ras_commander import RasCmdr, init_ras_project init_ras_project("/path/to/project", "6.5") results = RasCmdr.compute_test_mode( plan_number=["01", "02"], dest_folder_suffix="[SequentialTest]", num_cores=4, overwrite_dest=True ) print("Sequential execution results:") for plan, success in results.items(): print(f" Plan {plan}: {'Successful' if success else 'Failed'}")
from ras_commander import RasPrj, init_ras_project, RasCmdr
from pathlib import Path
# Initialize two separate projects using new instances
project1 = init_ras_project("/path/to/project1", "6.5", ras_object=RasPrj())
project2 = init_ras_project("/path/to/project2", "6.5", ras_object=RasPrj())
results_folder1 = Path("./results_proj1")
results_folder2 = Path("./results_proj2")
# Perform operations on each project, passing the correct object
print(f"Running plan 01 for {project1.project_name}")
RasCmdr.compute_plan("01", ras_object=project1, dest_folder=results_folder1, overwrite_dest=True)
print(f"Running plan 02 for {project2.project_name}")
RasCmdr.compute_plan("02", ras_object=project2, dest_folder=results_folder2, overwrite_dest=True)
# Compare results (example: number of plans)
print(f"{project1.project_name} has {len(project1.plan_df)} plans.")
print(f"{project2.project_name} has {len(project2.plan_df)} plans.")
# Access HDF results (assuming computations were successful)
# Note: Need to re-initialize RasPrj for the output folders to access results easily,
# or construct HDF paths manually.
# Example: Re-initialize to read results from project 1's output
results_proj1 = init_ras_project(results_folder1, "6.5", ras_object=RasPrj())
hdf_df1 = results_proj1.get_hdf_entries()
print(f"HDF results found for Project 1 run:\n{hdf_df1}")ras_commander provides extensive support for reading HEC-RAS HDF files through specialized Hdf* classes. These methods typically accept a path identifier (string, Path, number) which is standardized by the @standardize_input decorator.
-
Exploring HDF Structure: Use
HdfBase.get_dataset_info()to print the internal structure of an HDF file.from ras_commander import HdfBase, init_ras_project, RasPlan init_ras_project("/path/to/project", "6.5") # Assume plan 01 has been computed and has results hdf_results_path = RasPlan.get_results_path("01") if hdf_results_path: print(f"Exploring HDF file: {hdf_results_path}") HdfBase.get_dataset_info(hdf_results_path, group_path="/Results/Unsteady/Output/Output Blocks/Base Output/Summary Output") else: print("No HDF results found for plan 01.")
-
Extracting Mesh Results (
HdfResultsMesh): UseHdfResultsMeshmethods to get timeseries or summary results for 2D areas.from ras_commander import HdfResultsMesh, init_ras_project, RasPlan import xarray as xr init_ras_project("/path/to/project", "6.5") hdf_results_path = RasPlan.get_results_path("01") # Path to .p01.hdf if hdf_results_path: try: # Get Water Surface time series for the first mesh area mesh_names = HdfMesh.get_mesh_area_names(hdf_results_path) if mesh_names: first_mesh = mesh_names[0] ws_timeseries: xr.DataArray = HdfResultsMesh.get_mesh_timeseries( hdf_results_path, first_mesh, "Water Surface" ) print(f"Water Surface timeseries for mesh '{first_mesh}':\n{ws_timeseries}") # Get Max Water Surface summary for all meshes max_ws_summary = HdfResultsMesh.get_mesh_max_ws(hdf_results_path) print(f"\nMax Water Surface Summary:\n{max_ws_summary}") else: print("No mesh areas found in HDF.") except Exception as e: print(f"Error reading mesh results: {e}") else: print("No HDF results found for plan 01.")
-
Working with Plan Results (
HdfResultsPlan): UseHdfResultsPlanfor plan-level results like runtime or volume accounting.from ras_commander import HdfResultsPlan, init_ras_project, RasPlan init_ras_project("/path/to/project", "6.5") hdf_results_path = RasPlan.get_results_path("01") if hdf_results_path: runtime_data = HdfResultsPlan.get_runtime_data(hdf_results_path) print(f"Runtime Data:\n{runtime_data}") volume_accounting = HdfResultsPlan.get_volume_accounting(hdf_results_path) print(f"\nVolume Accounting:\n{volume_accounting}") else: print("No HDF results found for plan 01.")
-
Cross-Section Results (
HdfResultsXsec): Extract 1D cross-section time series results usingHdfResultsXsec.from ras_commander import HdfResultsXsec, init_ras_project, RasPlan import xarray as xr init_ras_project("/path/to/project", "6.5") hdf_results_path = RasPlan.get_results_path("01") if hdf_results_path: xsec_results: xr.Dataset = HdfResultsXsec.get_xsec_timeseries(hdf_results_path) print(f"Cross Section Results Dataset:\n{xsec_results}") # Example: Access Water Surface for the first cross section # first_xs_name = xsec_results['cross_section'][0].item() # ws_first_xs = xsec_results['Water_Surface'].sel(cross_section=first_xs_name) # print(f"\nWater Surface for first cross section ({first_xs_name}):\n{ws_first_xs}") else: print("No HDF results found for plan 01.")
ras_commander provides specialized classes (HdfPipe, HdfPump) for handling pipe network and pump station data from HEC-RAS HDF files.
-
Pipe Network Operations (
HdfPipe): Extract geometry and results for pipe networks.from ras_commander import HdfPipe, init_ras_project, RasPlan, HdfMesh from pathlib import Path init_ras_project("/path/to/project_with_pipes", "6.5") # Assume plan 01 computed results for a project with pipes hdf_plan_path = RasPlan.get_results_path("01") # Geometry HDF is needed for geometric data hdf_geom_path = HdfMesh.get_mesh_cell_polygons(hdf_plan_path) # Infer geom HDF from plan if hdf_plan_path and hdf_geom_path: # Extract pipe conduit geometry pipe_conduits_gdf = HdfPipe.get_pipe_conduits(hdf_geom_path) print(f"Pipe Conduits:\n{pipe_conduits_gdf}") # Extract pipe node geometry pipe_nodes_gdf = HdfPipe.get_pipe_nodes(hdf_geom_path) print(f"\nPipe Nodes:\n{pipe_nodes_gdf}") # Get pipe network timeseries results (e.g., Node Depth) node_depth_ts = HdfPipe.get_pipe_network_timeseries(hdf_plan_path, "Nodes/Depth") print(f"\nNode Depth Timeseries:\n{node_depth_ts}") # Get pipe network summary results summary_df = HdfPipe.get_pipe_network_summary(hdf_plan_path) print(f"\nPipe Network Summary:\n{summary_df}") # Get profile for a specific conduit (e.g., conduit index 0) # profile_df = HdfPipe.get_pipe_profile(hdf_geom_path, conduit_id=0) # print(f"\nProfile for Conduit 0:\n{profile_df}") else: print("Could not find HDF plan or geometry files.")
-
Pump Station Operations (
HdfPump): Work with pump station geometry and results data.from ras_commander import HdfPump, init_ras_project, RasPlan, HdfMesh init_ras_project("/path/to/project_with_pumps", "6.5") # Assume plan 01 computed results hdf_plan_path = RasPlan.get_results_path("01") hdf_geom_path = HdfMesh.get_mesh_cell_polygons(hdf_plan_path) # Infer geom HDF if hdf_plan_path and hdf_geom_path: # Extract pump station locations pump_stations_gdf = HdfPump.get_pump_stations(hdf_geom_path) print(f"Pump Stations:\n{pump_stations_gdf}") # Get pump group details (like efficiency curves) pump_groups_df = HdfPump.get_pump_groups(hdf_geom_path) print(f"\nPump Groups:\n{pump_groups_df}") # Get pump station timeseries results (replace "Pump Station 1" with actual name) try: pump_ts = HdfPump.get_pump_station_timeseries(hdf_plan_path, "Pump Station 1") print(f"\nTimeseries for Pump Station 1:\n{pump_ts}") except ValueError as e: print(f"\nError getting timeseries: {e}") # Get pump station summary results summary_df = HdfPump.get_pump_station_summary(hdf_plan_path) print(f"\nPump Station Summary:\n{summary_df}") # Get pump operation timeseries (replace "Pump Station 1" with actual name) try: operation_df = HdfPump.get_pump_operation_timeseries(hdf_plan_path, "Pump Station 1") print(f"\nOperation Timeseries for Pump Station 1:\n{operation_df}") except ValueError as e: print(f"\nError getting operation timeseries: {e}") else: print("Could not find HDF plan or geometry files.")
Manage multiple projects by creating separate RasPrj instances.
from ras_commander import RasPrj, init_ras_project, RasCmdr, RasPlan
import pandas as pd
# Initialize multiple project instances
# Use ras_object=RasPrj() to ensure distinct instances
project1 = init_ras_project("/path/to/project1", "6.5", ras_object=RasPrj())
project2 = init_ras_project("/path/to/project2", "6.6", ras_object=RasPrj())
# This allows RasPrj Instances to be accessed independently
print(f"Project 1: {project1.project_name} ({len(project1.plan_df)} plans)")
print(f"Project 2: {project2.project_name} ({len(project2.plan_df)} plans)")
# --- Best Practices ---
# 1. Clear Naming: Use descriptive variable names (e.g., bald_eagle_proj, muncie_proj).
# 2. Pass Objects: Always pass the correct ras_object to functions.
# 3. Avoid Global 'ras': Don't rely on the global 'ras' when managing multiple projects.
# 4. Separate Outputs: Use distinct dest_folder paths for computations.
# 5. Resource Awareness: Monitor CPU/RAM when running computations for multiple projects, especially in parallel.
# --- Example Workflow ---
# Function to compare basic project structures
def compare_project_structures(ras_obj1: RasPrj, name1: str, ras_obj2: RasPrj, name2: str) -> pd.DataFrame:
"""Compare the structures of two HEC-RAS projects."""
comparison = {
'Project Name': [ras_obj1.project_name, ras_obj2.project_name],
'Plan Count': [len(ras_obj1.plan_df), len(ras_obj2.plan_df)],
'Geometry Count': [len(ras_obj1.geom_df), len(ras_obj2.geom_df)],
'Flow Count': [len(ras_obj1.flow_df), len(ras_obj2.flow_df)],
'Unsteady Count': [len(ras_obj1.unsteady_df), len(ras_obj2.unsteady_df)]
}
return pd.DataFrame(comparison, index=[name1, name2])
# Perform operations on each project
RasCmdr.compute_plan("01", ras_object=project1, dest_folder="./proj1_run")
RasCmdr.compute_plan("01", ras_object=project2, dest_folder="./proj2_run")
# Compare structures
comparison_df = compare_project_structures(project1, "Project 1", project2, "Project 2")
print("\nProject Structure Comparison:")
print(comparison_df)
# --- Application Examples ---
# 1. Model Comparison: Run same scenarios on different river models.
# 2. Basin-wide Analysis: Process connected or related models.
# 3. Parameter Sweep: Test parameter variations across multiple baseline models.
# 4. Batch Processing: Automate runs for a large inventory of models.ras_commander offers three modes for running HEC-RAS plans:
Runs one plan, optionally in a separate destination folder. Best for targeted runs or when immediate results are needed.
from ras_commander import RasCmdr, init_ras_project
init_ras_project("/path/to/project", "6.5")
success = RasCmdr.compute_plan(
plan_number="01", # Plan to execute
dest_folder="/path/to/single_run_results", # Optional: Where to run
num_cores=4, # Optional: Cores for this run
clear_geompre=True, # Optional: Force geometry preprocess
overwrite_dest=True # Optional: Overwrite dest if exists
)
print(f"Plan 01 execution status: {success}")Runs multiple plans one after another in a dedicated test folder (copy of the project). Best for plans with dependencies or for controlled resource usage.
from ras_commander import RasCmdr, init_ras_project
init_ras_project("/path/to/project", "6.5")
results = RasCmdr.compute_test_mode(
plan_number=["01", "02", "03"], # Plans to run in order
dest_folder_suffix="[SequentialRun]", # Suffix for test folder name
clear_geompre=True, # Optional: Clear before each plan
num_cores=4, # Optional: Cores for each plan
overwrite_dest=True # Optional: Overwrite test folder
)
print("Sequential results:", results)Runs multiple independent plans concurrently using temporary worker folders, consolidating results afterward. Best for maximizing speed on multi-core systems with independent plans.
from ras_commander import RasCmdr, init_ras_project
init_ras_project("/path/to/project", "6.5")
results = RasCmdr.compute_parallel(
plan_number=["01", "02", "03"], # Plans to run in parallel
max_workers=3, # Max concurrent HEC-RAS instances
num_cores=2, # Cores assigned to each worker
dest_folder="/path/to/parallel_results", # Final results location
clear_geompre=False, # Optional: Clear in worker folders
overwrite_dest=True # Optional: Overwrite final results folder
)
print("Parallel results:", results)- Dependency: Sequential (
compute_test_mode) for dependent plans; Parallel (compute_parallel) for independent plans. - Resources: Single (
compute_plan) or Sequential for limited hardware; Parallel for multi-core systems. - Speed: Parallel is usually fastest overall for multiple plans; Single is fastest for one specific plan.
- Debugging: Single or Sequential are often easier to debug.
- Isolation:
compute_test_modeandcompute_parallel(withdest_folder) provide isolated run environments.
compute_plan(): Returnsbool(success/failure).compute_test_mode(): ReturnsDict[str, bool]mapping plan number to success status.compute_parallel(): ReturnsDict[str, bool]mapping plan number to success status.
# Example checking results from parallel run
results = RasCmdr.compute_parallel(plan_number=["01", "02", "03"])
for plan_num, success in results.items():
print(f"Plan {plan_num}: {'Success' if success else 'Failed'}")
if not success:
print(f" Check logs related to plan {plan_num} execution.")Modify plan file (.p*) settings programmatically without the HEC-RAS GUI.
Read specific parameters directly from a plan file.
from ras_commander import RasPlan, init_ras_project
init_ras_project("/path/to/project", "6.5")
# Get the computation interval for plan 01
interval = RasPlan.get_plan_value("01", "Computation Interval")
print(f"Plan 01 Computation Interval: {interval}")
# Get the number of cores assigned (0 means 'all available')
cores = RasPlan.get_plan_value("01", "UNET D2 Cores") # For 2D cores
print(f"Plan 01 UNET D2 Cores setting: {cores}")
# Get the associated geometry file number
geom_file_str = RasPlan.get_plan_value("01", "Geom File") # Returns e.g., "g01"
geom_num = geom_file_str[1:] if geom_file_str else "N/A"
print(f"Plan 01 uses Geometry: {geom_num}")Common keys include: Computation Interval, Short Identifier, Simulation Date, UNET D1 Cores, UNET D2 Cores, Plan Title, Geom File, Flow File (or Unsteady File), Friction Slope Method, Run HTab, UNET Use Existing IB Tables.
Control which simulation components HEC-RAS executes.
from ras_commander import RasPlan, init_ras_project
init_ras_project("/path/to/project", "6.5")
# Example: Enable geometry preprocessing and unsteady sim, disable others
RasPlan.update_run_flags(
"01",
geometry_preprocessor=True, # Run HTab = 1
unsteady_flow_simulation=True, # Run UNet = 1
post_processor=False, # Run PostProcess = 0
floodplain_mapping=False # Run RASMapper = -1 (Note: False maps to -1 for RASMapper)
)
print("Updated run flags for plan 01.")Modify simulation time steps and output frequencies.
from ras_commander import RasPlan, init_ras_project
init_ras_project("/path/to/project", "6.5")
# Set computation to 10 sec, output to 1 min, mapping to 15 min
RasPlan.update_plan_intervals(
"01",
computation_interval="10SEC",
output_interval="1MIN",
mapping_interval="15MIN"
# instantaneous_interval="1HOUR" # Also available
)
print("Updated time intervals for plan 01.")Valid interval values (must match HEC-RAS exactly): 1SEC..30SEC, 1MIN..30MIN, 1HOUR..12HOUR, 1DAY.
Change the simulation window (start and end times).
from ras_commander import RasPlan, init_ras_project
from datetime import datetime
init_ras_project("/path/to/project", "6.5")
start_dt = datetime(2024, 1, 1, 12, 0) # Jan 1, 2024, 12:00 PM
end_dt = datetime(2024, 1, 5, 0, 0) # Jan 5, 2024, 00:00 AM
RasPlan.update_simulation_date("01", start_date=start_dt, end_date=end_dt)
print(f"Updated simulation dates for plan 01 to {start_dt} - {end_dt}.")Ensure the simulation window covers your boundary condition data and includes appropriate warm-up/cool-down periods.
Read or modify the multi-line description block in the plan file.
from ras_commander import RasPlan, init_ras_project
init_ras_project("/path/to/project", "6.5")
# Read current description
current_desc = RasPlan.read_plan_description("01")
print(f"Current description for plan 01:\n{current_desc}\n-----------------")
# Update the description
new_desc = f"""
Run Date: {datetime.now().strftime('%Y-%m-%d')}
Scenario: Test with updated Manning's values.
Source Geometry: g02
Source Flow: u03
Notes: Increased roughness in floodplain by 15%.
"""
RasPlan.update_plan_description("01", new_desc)
print("Updated description for plan 01.")Configure the number of processor cores a plan should use (sets UNET D1 Cores, UNET D2 Cores, PS Cores simultaneously).
from ras_commander import RasPlan, init_ras_project
init_ras_project("/path/to/project", "6.5")
# Set plan 01 to use 4 cores
RasPlan.set_num_cores("01", 4)
print("Set plan 01 to use 4 cores.")
# Set plan 02 to use all available cores (value 0)
RasPlan.set_num_cores("02", 0)
print("Set plan 02 to use all available cores.")Consider system resources. 2-8 cores are typically effective. Using too many can decrease performance.
Strategies to improve execution speed and resource usage.
-
Parallel Execution (
RasCmdr.compute_parallel):- Run independent plans simultaneously.
- Balance
max_workersandnum_coresbased on CPU/RAM.
# Example: 8 physical cores, run 4 plans in parallel, 2 cores each RasCmdr.compute_parallel(plan_number=["01", "02", "03", "04"], max_workers=4, num_cores=2)
-
Optimized Geometry Preprocessing:
- Avoid redundant calculations if multiple plans use the same geometry. Preprocess once, then run simulations.
from ras_commander import RasPlan, RasCmdr, init_ras_project init_ras_project("/path/to/project", "6.5") plan_to_preprocess = "01" # Plan using the geometry to preprocess geom_file_to_use = RasPlan.get_plan_value(plan_to_preprocess, "Geom File") # e.g., "g01" # Step 1: Force geometry preprocessing for the target geometry via one plan print(f"Preprocessing geometry {geom_file_to_use} using plan {plan_to_preprocess}...") RasPlan.update_run_flags( plan_to_preprocess, geometry_preprocessor=True, # Run HTab = 1 unsteady_flow_simulation=False # Run UNet = 0 (or -1 if available) # Set other flags as needed (e.g., post_processor=False) ) RasCmdr.compute_plan(plan_to_preprocess) # This run primarily generates .c* files # Step 2: Run actual simulations using the preprocessed geometry plans_using_geom = ["01", "03", "05"] # Plans that use the same geometry for plan_num in plans_using_geom: print(f"Running simulation for plan {plan_num} using preprocessed geometry...") RasPlan.update_run_flags( plan_num, geometry_preprocessor=False, # Run HTab = 0 (use existing tables) unsteady_flow_simulation=True # Run UNet = 1 # Set other flags as needed ) # Run the plan (can be single, parallel, or test mode) RasCmdr.compute_plan(plan_num)
-
Memory Management (Large Datasets):
- When reading large HDF results (especially 2D time series), process data in chunks if memory becomes an issue.
xarray(returned byget_mesh_timeseries) supports lazy loading and chunking.
import xarray as xr # ws_timeseries = HdfResultsMesh.get_mesh_timeseries(...) # If ws_timeseries is too large: # ws_chunked = ws_timeseries.chunk({'time': 100, 'cell_id': 10000}) # Example chunking # result = ws_chunked.mean(dim='cell_id').compute() # Perform computation
- When reading large HDF results (especially 2D time series), process data in chunks if memory becomes an issue.
-
I/O Optimization:
- Minimize repeated file opening/closing when reading multiple datasets from the same HDF file. Open it once with
h5py.File.
import h5py from ras_commander import HdfBase, HdfResultsMesh # ... and other Hdf classes hdf_path = "/path/to/results.p01.hdf" try: with h5py.File(hdf_path, 'r') as hdf_file: # Perform multiple reads within this block start_time = HdfBase.get_simulation_start_time(hdf_file) timestamps = HdfBase.get_unsteady_timestamps(hdf_file) # summary_ws = HdfResultsMesh.get_mesh_summary_output(hdf_file, "Maximum Water Surface") # Requires adapting func to take h5py.File print(f"Start time: {start_time}, Found {len(timestamps)} timestamps.") # ... more operations using the open hdf_file except Exception as e: print(f"Error accessing HDF file {hdf_path}: {e}")
(Note: Most
Hdf*methods currently use@standardize_inputwhich handles file opening/closing internally. Adapting them to accept openh5py.Fileobjects might be needed for extreme I/O optimization). - Minimize repeated file opening/closing when reading multiple datasets from the same HDF file. Open it once with
-
Profiling:
- Use Python's
cProfileto identify bottlenecks in your scripts.
import cProfile from ras_commander import RasCmdr, init_ras_project init_ras_project("/path/to/project", "6.5") # Profile a specific function call # cProfile.run('RasCmdr.compute_plan("01", num_cores=2)')
- Use Python's
Access and analyze boundary conditions extracted from unsteady flow files.
-
Accessing Boundary Conditions: The
boundaries_dfattribute holds the parsed data.from ras_commander import init_ras_project, ras init_ras_project("/path/to/project", "6.5") boundary_conditions = ras.boundaries_df if boundary_conditions is not None and not boundary_conditions.empty: print(f"Found {len(boundary_conditions)} boundary conditions:") print(boundary_conditions.head()) else: print("No boundary conditions found or project not initialized correctly.")
-
Filtering Boundary Conditions: Use standard pandas filtering.
if boundary_conditions is not None and not boundary_conditions.empty: # Get all flow hydrographs flow_hydrographs = boundary_conditions[boundary_conditions['bc_type'] == 'Flow Hydrograph'] print(f"\nFlow Hydrographs:\n{flow_hydrographs[['river_reach_name', 'river_station', 'hydrograph_num_values']]}") # Get boundary conditions for a specific river/reach # main_river_boundaries = boundary_conditions[boundary_conditions['river_reach_name'] == 'Main River']
-
Analyzing Boundary Condition Data: Access columns for details. Hydrograph values are often stored as strings or lists.
if 'flow_hydrographs' in locals() and not flow_hydrographs.empty: for index, bc in flow_hydrographs.iterrows(): print(f"\n--- BC {index} ---") print(f" Location: {bc['river_reach_name']} @ RS {bc['river_station']}") print(f" Num Values: {bc['hydrograph_num_values']}") # Note: 'hydrograph_values' might be a list of strings or numbers depending on parsing # print(f" Values (first 5): {bc.get('hydrograph_values', [])[:5]}")
-
Modifying Boundary Conditions: Direct modification via the library is generally NOT supported. The
boundaries_dfis read-only representation. Modifying boundary conditions typically requires:- Using
RasUnsteadyfunctions (extract_tables,write_table_to_file) to modify numeric tables. - Manually editing the
.u*files for structural changes. - Creating custom functions to parse/rewrite specific parts of the
.u*file.
- Using
-
Visualizing Boundary Conditions: Use pandas and matplotlib with the extracted table data.
from ras_commander import RasUnsteady # Needed for table extraction import matplotlib.pyplot as plt if 'flow_hydrographs' in locals() and not flow_hydrographs.empty: # Example for the first flow hydrograph found first_flow_bc = flow_hydrographs.iloc[0] unsteady_file_path = first_flow_bc['full_path'] # Get path from merged df # Extract tables for this unsteady file tables = RasUnsteady.extract_tables(unsteady_file_path) # Find the correct table (assuming one flow hydrograph per location) flow_table_name = 'Flow Hydrograph=' if flow_table_name in tables: flow_data = tables[flow_table_name] # Need time interval to create time axis (get from boundary_df or parse file) interval_str = first_flow_bc.get('Interval', '1HOUR') # Example: Get interval # Convert interval_str to timedelta (requires parsing logic, simplified here) # time_delta = pd.Timedelta(interval_str) # Simplistic example # time_axis = [i * time_delta for i in range(len(flow_data))] plt.figure(figsize=(10, 5)) plt.plot(flow_data['Value']) # Plot against index if time axis is complex plt.title(f"Flow Hydrograph: {first_flow_bc['river_reach_name']} RS {first_flow_bc['river_station']}") plt.xlabel("Time Step Index") plt.ylabel("Flow") plt.grid(True) plt.show() else: print(f"Could not find table '{flow_table_name}' in {unsteady_file_path}")
RasUtils provides tools beyond basic file operations.
-
Data Conversion (
convert_to_dataframe): Load data from various file types into pandas DataFrames.from ras_commander import RasUtils from pathlib import Path try: csv_df = RasUtils.convert_to_dataframe(Path("results.csv")) excel_df = RasUtils.convert_to_dataframe(Path("data.xlsx"), sheet_name="Sheet1") print("DataFrames loaded successfully.") except FileNotFoundError: print("Input file not found.") except NotImplementedError as e: print(f"Error: {e}")
-
Statistical Analysis (Error Metrics): Calculate common metrics for model calibration/validation.
from ras_commander import RasUtils import numpy as np observed = np.array([100, 120, 140, 160, 180]) predicted = np.array([105, 125, 135, 165, 175]) rmse = RasUtils.calculate_rmse(observed, predicted, normalized=False) percent_bias = RasUtils.calculate_percent_bias(observed, predicted, as_percentage=True) metrics = RasUtils.calculate_error_metrics(observed, predicted) print(f"RMSE: {rmse:.2f}") print(f"Percent Bias: {percent_bias:.2f}%") print(f"All Metrics: {metrics}")
-
Spatial Operations (KDTree): Perform nearest neighbor searches efficiently.
from ras_commander import RasUtils import numpy as np # Find nearest point in 'points' for each point in 'query_points' within 5 units points = np.array([[0, 0], [1, 1], [2, 2], [10, 10]]) query_points = np.array([[0.5, 0.5], [5, 5], [9, 9]]) nearest_indices = RasUtils.perform_kdtree_query(points, query_points, max_distance=5.0) # Returns indices from 'points': e.g., [1, -1, 3] (-1 if no point within max_distance) print(f"Nearest point indices: {nearest_indices}") # Find nearest neighbor within the 'points' dataset itself (excluding self) # neighbors_indices = RasUtils.find_nearest_neighbors(points, max_distance=3.0) # print(f"Nearest neighbor indices within dataset: {neighbors_indices}")
-
Data Consolidation (
consolidate_dataframe): Group and aggregate DataFrame rows, often merging values into lists.from ras_commander import RasUtils import pandas as pd df = pd.DataFrame({'Group': ['A', 'A', 'B', 'B', 'A'], 'Value': [10, 20, 30, 40, 50], 'Type': ['X', 'Y', 'X', 'Y', 'X']}) # Consolidate by 'Group', merging 'Value' and 'Type' into lists consolidated = RasUtils.consolidate_dataframe(df, group_by='Group', aggregation_method='list') print("Consolidated DataFrame:") print(consolidated) # Output might look like: # Value Type # Group # A [10, 20, 50] [X, Y, X] # B [30, 40] [X, Y]
Fine-tune parallel runs based on goals and resources.
Use fewer cores per worker to run more workers simultaneously (if RAM allows). Good for many small/medium independent plans.
from ras_commander import RasCmdr
import psutil
physical_cores = psutil.cpu_count(logical=False)
cores_per_worker = 2 # Minimal cores per HEC-RAS instance
max_workers = max(1, physical_cores // cores_per_worker)
# Check available RAM and potentially reduce max_workers if needed
print(f"Efficiency Mode: Running up to {max_workers} workers with {cores_per_worker} cores each.")
RasCmdr.compute_parallel(
plan_number=["01", "02", "03", "04", "05", "06"], # Example plans
max_workers=max_workers,
num_cores=cores_per_worker
)Assign more cores per worker, reducing the number of concurrent workers. Good if individual plan speed is critical or for few, large plans.
from ras_commander import RasCmdr
import psutil
physical_cores = psutil.cpu_count(logical=False)
cores_per_worker = max(4, min(8, physical_cores // 2)) # Use 4-8 cores, but not more than half the system
max_workers = max(1, physical_cores // cores_per_worker)
print(f"Performance Mode: Running up to {max_workers} workers with {cores_per_worker} cores each.")
RasCmdr.compute_parallel(
plan_number=["LargePlan01", "LargePlan02"], # Example large plans
max_workers=max_workers,
num_cores=cores_per_worker
)Limit total core usage to leave resources free for other tasks.
from ras_commander import RasCmdr
import psutil
physical_cores = psutil.cpu_count(logical=False)
max_total_cores = int(physical_cores * 0.75) # Use up to 75% of physical cores
cores_per_worker = 2
max_workers = max(1, max_total_cores // cores_per_worker)
print(f"Background Mode: Running up to {max_workers} workers, using max {max_total_cores} total cores.")
RasCmdr.compute_parallel(
max_workers=max_workers,
num_cores=cores_per_worker
)If many plans share the same geometry, preprocess it once before the parallel run, then run the parallel simulations without geometry preprocessing.
-
Preprocess Geometry (Single Run):
from ras_commander import RasPlan, RasCmdr, init_ras_project init_ras_project("/path/to/project", "6.5") plan_for_preprocessing = "01" # Choose one plan that uses the target geometry print("Preprocessing geometry...") RasPlan.update_run_flags( plan_for_preprocessing, geometry_preprocessor=True, unsteady_flow_simulation=False # Don't run the full simulation yet ) RasCmdr.compute_plan(plan_for_preprocessing) print("Geometry preprocessing complete.")
-
Run Parallel Simulations (Without Preprocessing):
plans_to_run = ["01", "03", "05"] # Plans sharing the preprocessed geometry print("Running parallel simulations without geometry preprocessing...") RasCmdr.compute_parallel( plan_number=plans_to_run, max_workers=3, num_cores=2, clear_geompre=False # Important: Don't clear the files we just created # Ensure update_run_flags is set correctly *inside* compute_parallel # if needed, or ensure plans are pre-configured correctly. # Best practice: Ensure plans are saved with Run HTab = 0 before parallel run. ) print("Parallel simulations complete.")
Self-Correction:
compute_paralleldoesn't directly takeupdate_run_flags. The flags should be set before callingcompute_parallel. It might be better to modify the template plans to haveRun HTab=0before the parallel run, or modify them in the worker folders (more complex). The easiest is often to ensure the.p*files are saved correctly beforehand.
RAS Commander now provides access to RASMapper configuration data through the rasmap_df attribute of the RasPrj class, which is initialized automatically when a project is loaded. This enables integration with spatial datasets referenced in RASMapper.
When you run init_ras_project, rasmap_df is populated with data from the project's .rasmap file
The rasmap_df contains paths to:
- Terrain data (DEM)
- Soil layers (Hydrologic Soil Groups)
- Land cover datasets
- Infiltration data
- Profile lines and other features
- Project settings and current visualization state
This allows programmatic access to the same spatial data being used in RASMapper visualizations.
RAS Commander provides functions for reading and writing Manning's n values in geometry files through the RasGeo class. This allows automation of roughness coefficient adjustments for calibration and sensitivity analysis.
from ras_commander import RasGeo, RasPlan, init_ras_project
init_ras_project("/path/to/project", "6.5")
# Get the geometry file path
geom_path = RasPlan.get_geom_path("01")
# Read base Manning's n values
mannings_df = RasGeo.get_mannings_baseoverrides(geom_path)
print(f"Current Manning's n values:\n{mannings_df}")
# Read region-specific Manning's n overrides
region_df = RasGeo.get_mannings_regionoverrides(geom_path)
print(f"Regional Manning's n overrides:\n{region_df}")
# Modify Manning's n values (example: increase all values by 20%)
mannings_df['Base Manning\'s n Value'] *= 1.2
RasGeo.set_mannings_baseoverrides(geom_path, mannings_df)
print("Updated Manning's n values in geometry file")
# Clear preprocessor files to ensure geometry changes take effect
RasGeo.clear_geompre_files()Common applications include:
- Automated calibration workflows
- Sensitivity analysis by batch-modifying roughness values
- Scenario analysis using different roughness sets
- Seasonal roughness adjustments
The enhanced HdfInfiltration class provides comprehensive tools for working with soil and infiltration data in HEC-RAS projects.
from ras_commander import HdfInfiltration, init_ras_project
init_ras_project("/path/to/project", "6.5")
# Get the infiltration layer HDF path from the RASMapper configuration
infiltration_path = ras.rasmap_df['infiltration_hdf_path'][0][0]
# Read current infiltration parameters
infil_df = HdfInfiltration.get_infiltration_baseoverrides(infiltration_path)
print(f"Current infiltration parameters:\n{infil_df}")
# Scale infiltration parameters
scale_factors = {
'Maximum Deficit': 1.2, # Increase by 20%
'Initial Deficit': 1.0, # No change
'Potential Percolation Rate': 0.8 # Decrease by 20%
}
updated_df = HdfInfiltration.scale_infiltration_data(
infiltration_path, infil_df, scale_factors
)
print("Updated infiltration parameters")
# Get infiltration map (raster value to mukey mapping)
infil_map = HdfInfiltration.get_infiltration_map()
# Calculate weighted parameters based on soil coverage
significant_soils = HdfInfiltration.get_significant_mukeys(soil_stats, threshold=1.0)
weighted_params = HdfInfiltration.calculate_weighted_parameters(
significant_soils, infiltration_params
)
print(f"Weighted infiltration parameters:\n{weighted_params}")Key capabilities include:
- Retrieving and modifying infiltration parameters
- Scaling parameter values for sensitivity analysis
- Calculating soil statistics from raster data
- Computing area-weighted infiltration parameters
- Extracting and analyzing soil map unit data
RAS Commander interacts primarily with HEC-RAS project definition files (ASCII text: .prj, .p*, .g*, .u*, .f*) and HDF output files (.hdf), aiming for accessibility and automation without needing the complexities of the HEC-RAS GUI or DSS manipulation.
-
Data Sources in HEC-RAS Projects:
- ASCII input files (plans, unsteady flows, geometry definitions, project structure).
- DSS (Data Storage System) files (often used for time-series inputs like hydrographs, observed data).
- HDF (Hierarchical Data Format) files (contain detailed geometry tables and simulation results).
-
RAS Commander's Focus:
- Reading/Writing ASCII: Parses
.prjfor structure. Reads/writes parameters in.p*,.u*,.g*files usingRasPlan,RasUnsteady,RasGeo. - Reading HDF: Extensive capabilities to read geometry and results from
.g*.hdfand.p*.hdffiles usingHdf*classes. - Avoiding Direct DSS Manipulation: The library generally avoids reading from or writing to DSS files directly due to their binary format complexity and reliance on HEC libraries.
- Reading/Writing ASCII: Parses
-
Strategy for Handling DSS Inputs:
- Option 1 (Recommended): Define Time Series in ASCII: Instead of referencing DSS paths in your unsteady flow file (
.u*), define hydrographs directly within the file using the fixed-width table format. You can useRasUnsteady.extract_tablesto read existing tables andRasUnsteady.write_table_to_fileto write modified/new ones. - Option 2 (Workaround): Read DSS Data via HDF: If a simulation using DSS inputs has already been run, the HDF results file often contains the time-series data that was originally sourced from DSS (e.g., boundary condition flows). You can extract this from the HDF using
ras_commander(e.g., viaHdfResultsXsec) and potentially use it to construct ASCII tables for future runs.
- Option 1 (Recommended): Define Time Series in ASCII: Instead of referencing DSS paths in your unsteady flow file (
-
Accessing Project Data:
- Project Structure:
ras.plan_df,ras.geom_df,ras.flow_df,ras.unsteady_dfprovide DataFrames parsed from the.prjfile and associated plan/unsteady files. - Plan/Unsteady Parameters: Use
RasPlan.get_plan_valueor read specific lines viaRasPlan/RasUnsteadyfunctions. Modify usingset_/update_functions. - Geometry Data: Detailed geometry (mesh, cross-sections, structures) is best accessed from the HDF geometry file (
.g*.hdf) usingHdfMesh,HdfXsec,HdfStruc,HdfPipe,HdfPump. - Results Data: Simulation outputs are read from the HDF results file (
.p*.hdf) usingHdfResultsMesh,HdfResultsPlan,HdfResultsXsec, etc. - Boundary Conditions: Parsed summary and table data available in
ras.boundaries_dfand viaRasUnsteady.extract_tables.
- Project Structure:
- Initialization: Start with
init_ras_project()to load the project structure into aRasPrjobject (usually the globalras). - Inspection: Use the
.dfattributes (ras.plan_df, etc.) andget_*_entries()methods to understand the project components. - Modification: Use
RasPlan,RasUnsteady,RasGeomethods to change parameters, clone components, or update file references. Remember these often refresh therasobject's DataFrames. - Execution: Use
RasCmdrmethods (compute_plan,compute_parallel,compute_test_mode) to run simulations. - Results Analysis: After successful computation, use
Hdf*classes to read geometry and results data from the relevant.hdffiles (geometry or plan results). UseRasPlan.get_results_path()to find the results HDF.
from ras_commander import (
init_ras_project, ras, RasPlan, RasUnsteady, RasCmdr, HdfResultsXsec
)
import pandas as pd
# 1. Initialize Project
init_ras_project("/path/to/project", "6.5")
# 2. Identify and Clone Components
template_plan = "01"
template_unsteady = ras.plan_df.loc[ras.plan_df['plan_number'] == template_plan, 'unsteady_number'].iloc[0]
new_plan_num = RasPlan.clone_plan(template_plan, new_plan_shortid="Scaled_Flow_Test")
new_unsteady_num = RasPlan.clone_unsteady(template_unsteady)
# 3. Link Cloned Components
RasPlan.set_unsteady(new_plan_num, new_unsteady_num) # Link new unsteady to new plan
print(f"Plan {new_plan_num} created, using Unsteady {new_unsteady_num}")
# 4. Modify Unsteady Flow Data
new_unsteady_path = RasPlan.get_unsteady_path(new_unsteady_num)
# Find the flow hydrograph table within the new unsteady file
try:
with open(new_unsteady_path, 'r') as f:
lines = f.readlines()
tables_info = RasUnsteady.identify_tables(lines)
flow_table_info = None
for name, start, end in tables_info:
if "Flow Hydrograph" in name:
flow_table_info = (name, start, end)
break
if not flow_table_info:
raise ValueError("Flow Hydrograph table not found in unsteady file.")
flow_table_name, flow_start_line, flow_end_line = flow_table_info
# Parse the table
flow_df = RasUnsteady.parse_fixed_width_table(lines, flow_start_line, flow_end_line)
# Modify the values (e.g., scale by 1.2)
flow_df['Value'] = flow_df['Value'] * 1.2
print(f"Scaled {len(flow_df)} flow values by 1.2")
# Write the modified table back
RasUnsteady.write_table_to_file(new_unsteady_path, flow_table_name, flow_df, flow_start_line)
print("Modified unsteady flow file saved.")
except Exception as e:
print(f"Error modifying unsteady file: {e}")
# Handle error exit
# 5. Execute the Modified Plan
print(f"Running plan {new_plan_num}...")
success = RasCmdr.compute_plan(new_plan_num, num_cores=2)
# 6. Analyze Results (if successful)
if success:
print("Computation successful. Analyzing results...")
hdf_path = RasPlan.get_results_path(new_plan_num)
if hdf_path:
xsec_results = HdfResultsXsec.get_xsec_timeseries(hdf_path)
max_flow = xsec_results['Maximum_Flow'].max().item() # Get overall max flow from results
print(f"Overall maximum cross-section flow in results: {max_flow:.2f}")
else:
print("Could not find HDF results file.")
else:
print("Computation failed.")- Understand Your Data: Know where key information resides (ASCII vs. HDF) and how HEC-RAS uses it.
- Leverage HDF: Use the
Hdf*classes for reading detailed geometry and results – it's often easier than parsing ASCII geometry or complex results formats. - Iterate: Start simple. Manually perform a step in HEC-RAS, understand the file changes, then automate that step using
ras_commander. Verify with the GUI. - Isolate Runs: Use
dest_folderinRasCmdrfunctions orcompute_test_modeto avoid modifying your original project during testing and development. - Log Extensively: Use
logger.info(),logger.debug(), etc., in your scripts to track progress and diagnose issues. Configure logging levels appropriately. - Use AI Assistance: Leverage the AI-friendly structure. Provide relevant code snippets, this guide, or specific examples to an AI assistant (like ChatGPT, Claude) to help generate code for your specific tasks using the
ras_commanderAPI.
By following these strategies, you can effectively use ras_commander to automate complex HEC-RAS workflows, even navigating around limitations like direct DSS interaction by focusing on ASCII parameter files and HDF data extraction.
After initializing a HEC-RAS project with init_ras_project(), ras_commander provides several pandas DataFrames accessible via the RasPrj object (e.g., ras.plan_df) to inspect the project's structure and components.
Basic info stored directly on the RasPrj object:
from ras_commander import ras, init_ras_project
init_ras_project(r"C:\path\to\your\Muncie_Project", "6.5") # Example path
print(f"Project Name: {ras.project_name}")
print(f"Project Folder: {ras.project_folder}")
print(f"PRJ File: {ras.prj_file}")
print(f"HEC-RAS Executable Path: {ras.ras_exe_path}")Example output:
Project Name: Muncie
Project Folder: C:\path\to\your\Muncie_Project
PRJ File: C:\path\to\your\Muncie_Project\Muncie.prj
HEC-RAS Executable Path: C:\Program Files (x86)\HEC\HEC-RAS\6.5\Ras.exe
Contains information parsed from the .prj file and individual .p* files about each plan.
print(f"\nPlan Files DataFrame ({len(ras.plan_df)} plans):")
display(ras.plan_df) # Use display() in notebooks for better formattingKey columns include:
plan_number: Plan identifier ("01", "02", ...).full_path: Path to the.p*file.Short Identifier: User-defined short name.Plan Title: User-defined full title.Geom File: Geometry file number used (gXX).Flow File: Flow file number used (fXXoruXX).unsteady_number: Unsteady flow number if used (uXX), elseNone.geometry_number: Geometry number used (gXX).Simulation Date: Start/end dates/times string.Computation Interval: Time step (e.g., "2MIN").Run HTab,Run UNet, etc.: Run flags (parsed value).UNET D1 Cores,UNET D2 Cores: Core settings (parsed integer).HDF_Results_Path: Path to.p*.hdfresults file, if it exists.Geom Path,Flow Path: Calculated paths to associated geometry and flow files.
(See original guide for example table structure)
Lists steady flow files (.f*) found in the .prj file.
print(f"\nSteady Flow Files DataFrame ({len(ras.flow_df)} files):")
display(ras.flow_df)Key columns:
flow_number: Flow file identifier ("01", "02", ...).full_path: Path to the.f*file.
(See original guide for example table structure)
Lists unsteady flow files (.u*) found in the .prj file, with metadata parsed from the files.
print(f"\nUnsteady Flow Files DataFrame ({len(ras.unsteady_df)} files):")
display(ras.unsteady_df)Key columns:
unsteady_number: Unsteady file identifier ("01", "02", ...).full_path: Path to the.u*file.Flow Title: Title parsed from the.u*file.Program Version: Version parsed from the.u*file.Use Restart: Restart flag parsed from the.u*file.- Other parsed metadata (Precipitation, Wind modes etc.).
(See original guide for example table structure)
Lists geometry files (.g*) found in the .prj file.
print(f"\nGeometry Files DataFrame ({len(ras.geom_df)} files):")
display(ras.geom_df)Key columns:
geom_number: Geometry file identifier ("01", "02", ...).full_path: Path to the.g*file.geom_file: Base name (gXX).hdf_path: Calculated path to the corresponding geometry HDF file (.g*.hdf).
(See original guide for example table structure)
Filters plan_df to show only plans where the HDF results file exists.
hdf_entries_df = ras.get_hdf_entries()
print(f"\nHDF Entries DataFrame ({len(hdf_entries_df)} plans with results):")
display(hdf_entries_df)Structure: Same columns as plan_df, but only includes rows where HDF_Results_Path points to an existing file.
(See original guide for example table structure)
Detailed information about boundary conditions parsed from all unsteady flow files (.u*) in the project.
print(f"\nBoundary Conditions DataFrame ({len(ras.boundaries_df)} conditions):")
display(ras.boundaries_df)Key columns include:
unsteady_number: Links to the.u*file.boundary_condition_number: Sequential ID within the unsteady file.river_reach_name,river_station: Location (for river boundaries).storage_area_name,pump_station_name: Location (for SA/Pump boundaries).bc_type: Type of boundary (e.g., "Flow Hydrograph", "Normal Depth", "Gate Opening").hydrograph_type: Specific type if it's a hydrograph.Interval: Time interval for hydrograph data.hydrograph_num_values: Number of points in the hydrograph table.hydrograph_values: List of hydrograph values (often as strings or numbers).- Columns inherited from
unsteady_dfvia merge (Flow Title,Program Version, etc.).
(See original guide for example table structure)
Use standard pandas operations to query and analyze this structured project data.
import pandas as pd
# Assuming 'ras' is initialized
# Find all plans using geometry "01"
g01_plans = ras.plan_df[ras.plan_df['geometry_number'] == '01']
print(f"\nPlans using Geometry 01: {g01_plans['plan_number'].tolist()}")
# Get details for a specific plan
plan_02_details = ras.plan_df[ras.plan_df['plan_number'] == '02'].iloc[0]
print(f"\nDetails for Plan 02 - Short ID: {plan_02_details['Short Identifier']}")
# Count boundary conditions by type across the whole project
if ras.boundaries_df is not None and not ras.boundaries_df.empty:
bc_counts = ras.boundaries_df['bc_type'].value_counts()
print("\nBoundary Condition Counts:")
print(bc_counts)
else:
print("\nNo boundary conditions found in project.")
# Find unsteady files using a restart file
restart_files = ras.unsteady_df[ras.unsteady_df['Use Restart'] == 'True'] # Check actual parsed value
print(f"\nUnsteady files using restart: {restart_files['unsteady_number'].tolist()}")These DataFrames provide a powerful way to programmatically understand and interact with the structure and components of your HEC-RAS projects.
The RAS-Commander (ras_commander) library provides a robust and flexible Python interface for automating HEC-RAS workflows. By leveraging its classes and functions for project management, execution, file operations, and HDF data extraction, users can significantly streamline their modeling processes. Adhering to the best practices outlined in this guide, particularly regarding RAS object management, file handling, and error checking, will lead to more efficient and reliable automation scripts.
Remember to consult the specific docstrings of functions for detailed parameter information and refer to the library's source code for the most up-to-date implementation details.
For further assistance, bug reports, or feature requests, please refer to the library's GitHub repository and issue tracker.
Happy Modeling!