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__}")
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()}")
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")
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}")
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}")
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}")
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")
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}")
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}")
# Since we are now working in the dest_folder, init_ras_project in that folder
init_ras_project(dest_folder)
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.
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)
# 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}")
# 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)
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.