Single Plan Execution¶
# =============================================================================
# DEVELOPMENT MODE TOGGLE
# =============================================================================
USE_LOCAL_SOURCE = False # <-- 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 HdfResultsPlan, RasCmdr, RasExamples, RasPlan, RasPrj, init_ras_project, ras
# Additional imports
import os
import numpy as np
import pandas as pd
from IPython import display
import matplotlib.pyplot as plt
import psutil # For getting system CPU info
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
import subprocess
import shutil
# Verify which version loaded
import ras_commander
print(f"✓ Loaded: {ras_commander.__file__}")
📦 PIP PACKAGE MODE: Loading installed ras-commander
✓ Loaded: <workspace>\ras_commander\__init__.py
Prerequisites¶
Before running this notebook, ensure you have:
- ras-commander installed:
pip install ras-commander - Python 3.10+: Check with
python --version - HEC-RAS 6.3+: REQUIRED for plan execution
- Disk Space: ~2 GB (project + computation results)
- CPU Cores: 2+ recommended for multi-core testing
What You'll Learn¶
This notebook demonstrates single plan execution using RasCmdr.compute_plan():
- Basic Execution: Run a plan with default settings
- Destination Folders: Copy project before execution (preserve original)
- Core Count Control: Set number of CPU cores for computation
- Result Verification: Check HDF output and computation messages
Related Notebooks¶
- 111_executing_plan_sets.ipynb - Execute multiple plans sequentially
- 112_sequential_plan_execution.ipynb - Test mode for debugging
- 113_parallel_execution.ipynb - Parallel execution for speed
- 101_project_initialization.ipynb - Project setup
Key Concept: The compute_plan() Method¶
RasCmdr.compute_plan() is the fundamental execution method in ras-commander:
RasCmdr.compute_plan(
plan_number, # Required: "01", "02", etc.
dest_folder=None, # Optional: Copy to folder before execution
num_cores=None, # Optional: CPU cores (default: HEC-RAS decides)
clear_geompre=False, # Optional: Force geometry reprocessing
overwrite_dest=False # Optional: Overwrite existing dest_folder
)
See .claude/rules/hec-ras/execution.md for complete parameter reference.
Parameters¶
Configure these values to customize the notebook for your project.
# =============================================================================
# PARAMETERS - Edit these to customize the notebook
# =============================================================================
from pathlib import Path
# Project Configuration
PROJECT_NAME = "Muncie" # Example project to extract
RAS_VERSION = "7.0" # HEC-RAS version (6.3, 6.5, 6.6, etc.)
# Execution Settings
PLAN = "01" # Plan number to execute
NUM_CORES = 4 # CPU cores for 2D computation
RUN_SUFFIX = "run" # Suffix for run folder (e.g., Muncie_run)
# Extract the Bald Eagle Creek example project
# The extract_project method downloads the project from GitHub if not already present,
# and extracts it to the example_projects folder
bald_eagle_path = RasExamples.extract_project("Balde Eagle Creek", suffix="110")
print(f"Extracted project to: {bald_eagle_path}")
# Verify the path exists
print(f"Bald Eagle Creek project exists: {bald_eagle_path.exists()}")
Extracted project to: <workspace>\examples\example_projects\Balde Eagle Creek_110
Bald Eagle Creek project exists: True
Setting Up Our Working Environment¶
Let's set up our working directory and paths to example projects. We'll also check the number of available CPU cores on this system.
# Define paths to example projects
examples_dir = bald_eagle_path.parent
# Define computation output paths
compute_dest_folder = examples_dir / "compute_test_110"
# Remove stale compute directory before proceeding
if compute_dest_folder.exists():
shutil.rmtree(compute_dest_folder, ignore_errors=True)
print(f"Cleaned up stale directory: {compute_dest_folder}")
# Check system resources
cpu_count = psutil.cpu_count(logical=True)
physical_cpu_count = psutil.cpu_count(logical=False)
print(f"System has {physical_cpu_count} physical CPU cores ({cpu_count} logical cores)")
print(f"For HEC-RAS computation, it's often most efficient to use 2-8 cores")
System has 8 physical CPU cores (8 logical cores)
For HEC-RAS computation, it's often most efficient to use 2-8 cores
Understanding the RasCmdr.compute_plan Method¶
Before we dive into execution, let's understand the compute_plan method from the RasCmdr class, which is the core function for running HEC-RAS simulations.
Key Parameters¶
plan_number(str, Path): The plan number to execute or the full path to the plan filedest_folder(str, Path, optional): Destination folder for computationras_object(RasPrj, optional): Specific RAS object to use (defaults to globalras)clear_geompre(bool, optional): Whether to clear geometry preprocessor files (default: False)num_cores(int, optional): Number of processor cores to use (default: None, uses plan settings)overwrite_dest(bool, optional): Whether to overwrite the destination folder if it exists (default: False)
Returns¶
bool: True if the execution was successful, False otherwise
Key Concepts¶
-
Destination Folder: By default, the simulation runs in the original project folder. Specifying a destination folder creates a copy of the project in that location for execution, leaving the original project untouched.
-
Number of Cores: HEC-RAS can use multiple processor cores to speed up computation. The optimal number depends on the model complexity and your computer's specifications. Generally:
- 1-2 cores: Good for small models, highest efficiency per core
- 3-8 cores: Good balance for most models
-
8 cores: Diminishing returns, may actually be slower due to overhead
-
Geometry Preprocessor Files: These files store precomputed hydraulic properties. Clearing them forces HEC-RAS to recompute these properties, which is useful after making geometry changes.
-
Overwrite Destination: Controls whether an existing destination folder should be overwritten. This is a safety feature to prevent accidental deletion of important results.
Step 1: Project Initialization¶
Let's initialize the HEC-RAS project using the init_ras_project() function.
# Initialize the HEC-RAS project
init_ras_project(bald_eagle_path, RAS_VERSION)
print(f"Initialized HEC-RAS project: {ras.project_name}")
Initialized HEC-RAS project: BaldEagle
Step 2: Explore Available Plans¶
Let's examine the available plans in the project to understand what we're working with.
# Display the available plans in the project
print("Available plans in the project:")
display.display(ras.plan_df)
# Let's check the current setting for number of cores in the plans
print("\nCurrent core settings for plans:")
for plan_num in ras.plan_df['plan_number']:
# Check all three core parameters
d1_cores = RasPlan.get_plan_value(plan_num, "UNET D1 Cores")
d2_cores = RasPlan.get_plan_value(plan_num, "UNET D2 Cores")
ps_cores = RasPlan.get_plan_value(plan_num, "PS Cores")
print(f"Plan {plan_num}'s Existing Settings:")
print(f" 1D Cores: {d1_cores}")
print(f" 2D Cores: {d2_cores}")
print(f" Pump Station Cores: {ps_cores}")
Available plans in the project:
| plan_number | unsteady_number | geometry_number | Plan Title | Program Version | Short Identifier | Simulation Date | Computation Interval | Mapping Interval | Run HTab | ... | PS Cores | DSS File | Friction Slope Method | HDF_Results_Path | Geom File | Geom Path | Flow File | Flow Path | full_path | flow_type | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 01 | 02 | 01 | Unsteady with Bridges and Dam | 5.00 | UnsteadyFlow | 18FEB1999,0000,24FEB1999,0500 | 2MIN | 1HOUR | 1 | ... | None | dss | 2 | None | 01 | 02 | Unsteady | |||
| 1 | 02 | NaN | 01 | Steady Flow Run | NaN | SteadyRun | 02/18/1999,0000,02/24/1999,0500 | 2MIN | NaN | 1 | ... | None | dss | 1 | None | 01 | 02 | Steady |
2 rows × 32 columns
Current core settings for plans:
Plan 01's Existing Settings:
1D Cores: 0
2D Cores: 0
Pump Station Cores: None
Plan 02's Existing Settings:
1D Cores: None
2D Cores: None
Pump Station Cores: None
Step 3: Create a Destination Folder Structure¶
Now, let's prepare a destination folder for our computation. This allows us to run simulations without modifying the original project files.
# Create a destination folder path
dest_folder = examples_dir / "compute_test_cores_110"
# Remove stale compute directory before proceeding
if dest_folder.exists():
shutil.rmtree(dest_folder, ignore_errors=True)
print(f"Cleaned up stale directory: {dest_folder}")
# Check if the destination folder already exists
if dest_folder.exists():
print(f"Destination folder already exists: {dest_folder}")
print("We'll use overwrite_dest=True to replace it")
else:
print(f"Destination folder will be created: {dest_folder}")
Cleaned up stale directory: <workspace>\examples\example_projects\compute_test_cores_110
Destination folder will be created: <workspace>\examples\example_projects\compute_test_cores_110
Step 4: Execute a Plan with a Specified Number of Cores¶
Now we're ready to execute a plan with a specified number of cores, overwriting the destination folder if it exists. This is the core functionality demonstrated in Example 5 of the original script.
# Select a plan and number of cores
plan_number = "01"
num_cores = 2 # Specify the number of cores to use
print(f"Executing plan {plan_number} with {num_cores} cores...")
print(f"Destination folder: {dest_folder}")
# Record the start time
start_time = time.time()
# Execute the plan with specified parameters
success = RasCmdr.compute_plan(
plan_number, # The plan to execute
dest_folder=dest_folder, # Where to run the simulation
num_cores=num_cores, # Number of processor cores to use
overwrite_dest=True # Overwrite destination folder if it exists
)
# Record the end time and calculate duration
end_time = time.time()
duration = end_time - start_time
# Report results
if success:
print(f"✅ Plan {plan_number} executed successfully using {num_cores} cores")
print(f"Execution time: {duration:.2f} seconds")
else:
print(f"❌ Plan {plan_number} execution failed")
print(f"Time elapsed: {duration:.2f} seconds")
Executing plan 01 with 2 cores...
Destination folder: <workspace>\examples\example_projects\compute_test_cores_110
✅ Plan 01 executed successfully using 2 cores
Execution time: 95.77 seconds
Flexible Plan Number Formats¶
RasCmdr.compute_plan() accepts plan numbers in multiple formats — integer, string, zero-padded, or prefixed. All are normalized internally via RasUtils.normalize_ras_number() to the canonical two-digit form (e.g., "01").
With smart skip enabled (default), re-running an already-executed plan simply confirms the results are current without re-executing HEC-RAS.
from ras_commander import RasUtils
# All of these formats refer to the same plan "01"
flexible_inputs = [1, "1", "01", "p01"]
for raw in flexible_inputs:
normalized = RasUtils.normalize_ras_number(raw)
print(f" {str(raw):>5} -> {normalized!r}")
print()
# Re-run with each format — smart skip confirms results are current
for raw in flexible_inputs:
result = RasCmdr.compute_plan(raw, dest_folder=dest_folder, overwrite_dest=True)
print(f" compute_plan({str(raw):>5}) returned {result}")
1 -> '01'
1 -> '01'
01 -> '01'
p01 -> '01'
compute_plan( 1) returned ComputeResult(SUCCESS, results_df_row=None)
compute_plan( 1) returned ComputeResult(SUCCESS, results_df_row=None)
compute_plan( 01) returned ComputeResult(SUCCESS, results_df_row=None)
compute_plan( p01) returned ComputeResult(SUCCESS, results_df_row=None)
Verification: Successful Execution¶
Critical Success Criteria:
1. No exceptions raised during execution
2. HDF file created: {dest_folder}/{project_name}.p{plan_number}.hdf
3. Computation completed message in logs
4. HDF file size reasonable (> 1 KB, typically MB range)
Verification Code:
# Check HDF file exists and is valid
from pathlib import Path
from ras_commander.hdf import HdfResultsPlan
hdf_file = dest_folder / f"{project_name}.p{plan_number}.hdf"
assert hdf_file.exists(), f"HDF file not created: {hdf_file}"
assert hdf_file.stat().st_size > 1024, f"HDF file too small (likely corrupted)"
# Check computation messages
hdf = HdfResultsPlan(hdf_file)
messages = hdf.get_compute_messages()
assert messages is not None, "No computation messages found"
assert "Run completed" in messages or "Run was successful" in messages, "Execution may have failed"
print(f"[OK] Execution successful: {hdf_file}")
print(f" HDF size: {hdf_file.stat().st_size / 1e6:.1f} MB")
Visual Inspection:
1. Open dest_folder project in HEC-RAS GUI
2. Load the executed plan
3. View results in RAS Mapper
4. Check for warning/error messages in HEC-RAS GUI
Performance Metrics:
- Execution time logged automatically via @log_call decorator
- Check console output for timing information
- Compare execution times with different num_cores settings
What Can Go Wrong?¶
Common Issues:
1. FileNotFoundError: Project not initialized - run init_ras_project() first
2. HDF not created: Plan execution failed - check computation messages
3. Slow execution: Use num_cores parameter to parallelize 2D models
4. Geometry errors: Set clear_geompre=True to force reprocessing
Step 5: Verify Results¶
After execution, let's verify the results by checking the results paths and examining the destination folder.
# Verify that the destination folder exists and contains the expected files
if dest_folder.exists():
print(f"Destination folder exists: {dest_folder}")
# List the key files in the destination folder
print("\nKey files in destination folder:")
project_files = list(dest_folder.glob(f"{ras.project_name}.*"))
for file in project_files[:10]: # Show first 10 files
file_size = file.stat().st_size / 1024 # Size in KB
print(f" {file.name}: {file_size:.1f} KB")
if len(project_files) > 10:
print(f" ... and {len(project_files) - 10} more files")
# Check for HDF result files
print("\nHDF result files:")
hdf_files = list(dest_folder.glob(f"*.hdf"))
for file in hdf_files:
file_size = file.stat().st_size / (1024 * 1024) # Size in MB
print(f" {file.name}: {file_size:.1f} MB")
else:
print(f"Destination folder does not exist: {dest_folder}")
Destination folder exists: <workspace>\examples\example_projects\compute_test_cores_110
Key files in destination folder:
BaldEagle.b01: 9.2 KB
BaldEagle.bco01: 47814.4 KB
BaldEagle.c01: 522.1 KB
BaldEagle.dss: 3601.5 KB
BaldEagle.f01: 1209.0 KB
BaldEagle.f02: 1.5 KB
BaldEagle.g01: 513.6 KB
BaldEagle.g01.gmz: 372.6 KB
BaldEagle.g01.hdf: 4977.9 KB
BaldEagle.gis: 127.8 KB
... and 13 more files
HDF result files:
BaldEagle.g01.hdf: 4.9 MB
BaldEagle.p01.hdf: 8.4 MB
BaldEagle.u02.hdf: 0.0 MB
# Since we are now working in the dest_folder, init_ras_project in that folder
init_ras_project(dest_folder)
<ras_commander.RasPrj.RasPrj at 0x1d8fa86c9e0>
Viewing Execution Summary with results_df¶
The results_df DataFrame provides a lightweight summary of plan execution status, timing, and key metrics. It's automatically populated when you initialize a project.
| plan_number | plan_title | flow_type | hdf_path | hdf_exists | hdf_file_modified | ras_version | completed | has_errors | has_warnings | ... | runtime_unsteady_compute_hours | runtime_complete_process_speed | runtime_source | vol_error | vol_accounting_units | vol_error_percent | vol_flux_in | vol_flux_out | vol_starting | vol_ending | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 01 | Unsteady with Bridges and Dam | Unsteady | True | 2026-06-11 13:46:46.064302 | 5.00 | True | False | False | ... | 0.000781 | 5682.865588 | hdf | -29.546846 | Acre Feet | 0.014071 | 200682.453125 | 112574.828125 | 9306.797852 | 97384.882812 | |
| 1 | 02 | Steady Flow Run | Steady | False | NaT | NaN | False | False | False | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
2 rows × 26 columns
Step 6: Extract Computation Messages¶
After successful plan execution, we can extract detailed computation messages that provide insights into: - Computation time and performance metrics - Warning messages and errors (if any) - Convergence information - Process timing breakdown
The HdfResultsPlan.get_compute_messages() function automatically extracts messages from the HDF file, with fallback to .txt files for older HEC-RAS versions.
# Extract and display computation messages
from ras_commander import HdfResultsPlan
print("="*80)
print("EXTRACTING COMPUTATION MESSAGES")
print("="*80)
# Extract messages using plan number
compute_msgs = HdfResultsPlan.get_compute_messages(plan_number)
if compute_msgs:
print(f"\nSuccessfully extracted computation messages ({len(compute_msgs)} characters)\n")
# Display first 1000 characters
print("First 1000 characters of computation messages:")
print("-" * 80)
print(compute_msgs[:1000])
if len(compute_msgs) > 1000:
print("\n... (message truncated for display) ...")
print(f"\nTotal message length: {len(compute_msgs)} characters")
# Check for warnings or errors
print("\n" + "="*80)
print("CHECKING FOR WARNINGS/ERRORS")
print("="*80)
lines = compute_msgs.split('\n')
warnings_errors = [line for line in lines if 'warning' in line.lower() or 'error' in line.lower()]
if warnings_errors:
print(f"Found {len(warnings_errors)} warning/error messages:")
for msg in warnings_errors[:10]: # Show first 10
print(f" - {msg.strip()}")
else:
print("✓ No warnings or errors found in computation messages")
else:
print("⚠ No computation messages available")
print("This may indicate the plan was not computed or messages are not available.")
print("\n" + "="*80)
================================================================================
EXTRACTING COMPUTATION MESSAGES
================================================================================
Successfully extracted computation messages (1743 characters)
First 1000 characters of computation messages:
--------------------------------------------------------------------------------
Plan: 'Unsteady with Bridges and Dam' (BaldEagle.p01)
Simulation started at: 11Jun2026 01:45:11 PM
Writing Plan GIS Data...
Completed Writing Plan GIS Data
Writing Geometry...
Computing Bank Lines
Bank lines generated in 105 ms
Computing Edge Lines
Edge Lines generated in 61 ms
Computing XS Interpolation Surface
XS Interpolation Surface generated in 135 ms
Completed Writing Geometry
Writing Event Conditions ...
Completed Writing Event Condition Data
Geometric Preprocessor HEC-RAS 7.0 April 2026
Finished Processing Geometry
Performing Unsteady Flow Simulation HEC-RAS 7.0 April 2026
Unsteady Input Summary:
1D Unsteady Finite Difference Numerical Solution
Number of cross sections: 219
Overall Volume Accounting Error in Acre Feet: -29.55
Overall Volume Accounting Error as percentage: 0.01407
Please review "Computational Log File" output for volume accounting details
Writing Results to DSS
... (message truncated for display) ...
Total message length: 1743 characters
================================================================================
CHECKING FOR WARNINGS/ERRORS
================================================================================
Found 2 warning/error messages:
- Overall Volume Accounting Error in Acre Feet: -29.55
- Overall Volume Accounting Error as percentage: 0.01407
================================================================================
# Check the results path using the RasPlan.get_results_path method
# First, initialize a RAS object using the destination folder
try:
dest_ras = RasPrj()
init_ras_project(dest_folder, RAS_VERSION, ras_object=dest_ras)
# Get the results path for the plan we just executed
results_path = RasPlan.get_results_path(plan_number, ras_object=dest_ras)
if results_path:
print(f"Results for plan {plan_number} are located at: {results_path}")
# Check if the file exists and get its size
results_file = Path(results_path)
if results_file.exists():
size_mb = results_file.stat().st_size / (1024 * 1024)
print(f"Results file size: {size_mb:.2f} MB")
else:
print(f"No results found for plan {plan_number} in the destination folder")
except Exception as e:
print(f"Error checking results: {e}")
Results for plan 01 are located at: <workspace>\examples\example_projects\compute_test_cores_110\BaldEagle.p01.hdf
Results file size: 8.45 MB
# Demonstrate force_geompre parameter
print("="*80)
print("DEMONSTRATING FORCE_GEOMPRE")
print("="*80)
# Check what geometry preprocessing files exist before force_geompre
geom_hdf = dest_folder / f"{ras.project_name}.g01.hdf"
c_file = dest_folder / f"{ras.project_name}.c01"
print("\nBefore force_geompre:")
print(f" Geometry HDF exists: {geom_hdf.exists()} ({geom_hdf.name})")
print(f" Binary preprocessor exists: {c_file.exists()} ({c_file.name})")
print("\nForcing complete geometry reprocessing...")
print("This will delete BOTH .g##.hdf and .c## files\n")
start_time = time.time()
success = RasCmdr.compute_plan(
plan_number,
dest_folder=dest_folder,
num_cores=num_cores,
overwrite_dest=True,
force_geompre=True # Force complete geometry reprocessing
)
elapsed = time.time() - start_time
if success:
print(f"\n✓ Plan executed with force_geompre (took {elapsed:.2f} seconds)")
print(f" Complete geometry reprocessing was performed")
# Check files were regenerated
print(f"\nAfter force_geompre:")
print(f" Geometry HDF exists: {geom_hdf.exists()} (regenerated)")
print(f" Binary preprocessor exists: {c_file.exists()} (regenerated)")
else:
print(f"✗ Execution failed")
print("\n" + "="*80)
================================================================================
DEMONSTRATING FORCE_GEOMPRE
================================================================================
Before force_geompre:
Geometry HDF exists: True (BaldEagle.g01.hdf)
Binary preprocessor exists: True (BaldEagle.c01)
Forcing complete geometry reprocessing...
This will delete BOTH .g##.hdf and .c## files
✓ Plan executed with force_geompre (took 0.05 seconds)
Complete geometry reprocessing was performed
After force_geompre:
Geometry HDF exists: True (regenerated)
Binary preprocessor exists: True (regenerated)
================================================================================
Summary of Single Plan Execution Options¶
The RasCmdr.compute_plan() method provides a flexible way to execute HEC-RAS plans with various options. Here's a summary of all parameters:
Basic Parameters¶
-
Basic Execution: Simply provide a plan number
-
Destination Folder: Run in a separate folder to preserve the original project
-
Number of Cores: Control the CPU resources used
-
Overwrite Destination: Replace existing computation folders
Geometry Preprocessing Parameters¶
-
Clear Geometry Preprocessor (.c## files only):
-
Force Geometry Reprocessing (.g##.hdf AND .c## files) - NEW in v0.88.0:
Smart Skip Parameters (NEW in v0.88.0)¶
-
Smart Skip (default behavior): Automatically skips if results are current
-
Force Re-run: Override smart skip and always execute
-
Simple Existence Check: Use older skip logic (HDF exists check only)
Combined Options¶
- Complete Control: Combine multiple parameters
Decision Matrix: When to Use Which Parameters¶
| Scenario | Parameters to Use |
|---|---|
| First run, default settings | compute_plan("01") |
| Preserve original project | dest_folder="run1" |
| Re-run after geometry edit | force_geompre=True |
| Re-run same inputs (testing) | force_rerun=True |
| Resume interrupted batch | Default (smart skip handles this) |
| Geometry slightly modified | clear_geompre=True (lighter) |
| Geometry heavily modified | force_geompre=True (complete) |
Next Steps¶
To further enhance your HEC-RAS automation, consider exploring:
- Parallel Execution: Use
RasCmdr.compute_parallel()to run multiple plans simultaneously - Test Mode: Use
RasCmdr.compute_test_mode()for testing purposes - Pre-Processing: Modify plans, geometries, and unsteady flows before execution
- Post-Processing: Analyze results after computation
- Batch Processing: Create scripts for parameter sweeps or scenario analysis
These advanced topics are covered in other examples and documentation for the RAS Commander library.