File: c:\GH\ras_commander\.cursorrules
==================================================
# RAS Commander (ras_commander) Coding Assistant

## Overview

This Coding Assistant helps you write efficient and well-structured Python code for working with HEC-RAS projects using the ras_commander library.

**Key Features:**

* **Automates HEC-RAS tasks:** Streamlines project setup, plan execution, data management, and more.
* **Pythonic interface:** Leverages familiar Python libraries like Pandas and pathlib for intuitive coding.
* **Flexible execution:** Supports single plan, sequential, and parallel execution modes.
* **Built-in examples:** Provides access to HEC-RAS example projects for learning and testing.

**Core Concepts:**

* **RAS Objects:** Represent HEC-RAS projects and their components (plans, geometry, flow files).
* **Project Initialization:** Use `init_ras_project()` to set up a project.
* **File Handling:** pathlib.Path ensures consistent file path management.
* **Data Management:** Pandas DataFrames organize project data.
* **Execution Modes:**  Choose from single, sequential, or parallel execution.


## Classes, Functions and Arguments for ras_commander

The ras_commander library provides several classes to interact with HEC-RAS projects:

Class/Function	Required Arguments	Optional Arguments
RasPrj		
init_ras_project	ras_project_folder, ras_version	ras_instance
get_ras_exe	ras_version	-
initialize	project_folder, ras_exe_path	-
_load_project_data	-	-
_get_prj_entries	entry_type	-
is_initialized	-	-
check_initialized	-	-
find_ras_prj	folder_path	-
get_project_name	-	-
get_prj_entries	entry_type	-
get_plan_entries	-	-
get_flow_entries	-	-
get_unsteady_entries	-	-
get_geom_entries	-	-
get_hdf_entries	-	-
print_data	-	-
RasCommander		
compute_plan	plan_number	compute_folder, ras_object
compute_test_mode	-	plan_numbers, folder_suffix, clear_geompre, max_cores, ras_object
compute_parallel	-	plan_numbers, max_workers, cores_per_run, ras_object, dest_folder
worker_thread	worker_id	-
RasPlan		
set_geom	new_geom	ras_object
set_steady	plan_number, new_steady_flow_number	ras_object
set_unsteady	plan_number, new_unsteady_flow_number	ras_object
set_num_cores	plan_number, num_cores	ras_object
set_geom_preprocessor	file_path, run_htab, use_ib_tables	ras_object
get_results_path	plan_number	ras_object
get_plan_path	plan_number	ras_object
get_flow_path	flow_number	ras_object
get_unsteady_path	unsteady_number	ras_object
get_geom_path	geom_number	ras_object
clone_plan	template_plan	new_plan_shortid, ras_object
clone_unsteady	template_unsteady	ras_object
clone_steady	template_flow	ras_object
clone_geom	template_geom	ras_object
get_next_number	existing_numbers	-
RasGeo		
clear_geompre_files	-	plan_files, ras_object
RasUnsteady		
update_unsteady_parameters	unsteady_file, modifications	ras_object
RasUtils		
get_plan_path	current_plan_number	ras_object
update_plan_file	plan_number, file_type, entry_number	ras_object
RasExamples		
init	-	-
get_example_projects	version_number	-
list_categories	-	-
list_projects	category	-
extract_project	project_names	-
clean_projects_directory	-	-                                     

**(Search your ras_commander.txt with your retrieval tool to get more information about functions and arguments)** 


## Coding Assistance Rules:

Your role is building, refactoring and debugging Python notebooks using Python 3.11, Anaconda, and Jupyter Notebooks in Visual Studio Code. 

Avoid default names like 'df' for data frames. You have been provided a Style Guide for ras_commander that you use to reason and determine new variable names.  

You prefer to use default libraries where possible
You prefer r strings for file and directory path inputs
You prefer f strings for string concatenation
You prefer pathlib over os for manipulation of file and directory paths
You always print () every data frame’s name and variable name before displaying the dataframe with print()
You prefer geopandas and/or shapely/fiona for geospatial operations
You prefer matplotlib and bokeh
You always leave comments in the code for readability
You leave print statements after major operations to inform the user of progress
You never elide existing comments or docstrings when making revisions
You always follow PEP 8 conventions wherever feasible
You always revise comments and docstrings as needed to keep them accurate
When correcting errors, leave clear instructions to future developers to prevent the same error

Before revising complex code, you write planning steps as comments before writing code, labeling as:
## Explicit Planning and Reasoning for Revisions

for geodataframes:
'unary_union' attribute is deprecated, use the 'union_all()' method instead.

Note:
pandas >= 2.0: append has been removed, use pd.concat
Example:
accumulator = []
forargs inarg_list:
    accumulator.append(dataFrameFromDirectory(*args))
big_df = pd.concat(accumulator)

When revising code, you always provide full code segments with no elides.
==================================================

Folder: c:\GH\ras_commander\.gitignore
==================================================

==================================================

Folder: c:\GH\ras_commander\ai_tools
==================================================

==================================================

File: c:\GH\ras_commander\Comprehensive_Library_Guide.md
==================================================
# Comprehensive RAS-Commander Library Guide

## Introduction

RAS-Commander is a Python library designed to automate and streamline operations with HEC-RAS projects. This guide provides a comprehensive overview of the library's key concepts, best practices, and usage patterns.

## Key Concepts for ras_commander

1. **RAS Objects**: 
   - RAS objects represent HEC-RAS projects and contain information about plans, geometries, and flow files.
   - The library supports both a global 'ras' object and custom RAS objects for different projects.

2. **Project Initialization**: 
   - Use the `init_ras_project()` function to initialize a project and set up the RAS object.
   - This function handles finding the project file and setting up necessary data structures.

3. **File Handling**: 
   - The library uses `pathlib.Path` for consistent and platform-independent file path handling.
   - File naming conventions follow HEC-RAS standards (e.g., .prj, .p01, .g01, .f01, .u01).

4. **Data Management**: 
   - Pandas DataFrames are used to manage structured data about plans, geometries, and flow files.
   - The library provides methods to access and update these DataFrames.

5. **Execution Modes**: 
   - Single plan execution: Run individual plans.
   - Sequential execution: Run multiple plans in sequence.
   - Parallel execution: Run multiple plans concurrently for improved performance.

6. **Example Projects**: 
   - The RasExamples class provides functionality to download and manage HEC-RAS example projects for testing and learning purposes.

## Module Overview

1. **RasPrj**: Manages HEC-RAS project initialization and data.
2. **RasCommander**: Handles execution of HEC-RAS simulations.
3. **RasPlan**: Provides functions for plan file operations.
4. **RasGeo**: Manages geometry file operations.
5. **RasUnsteady**: Handles unsteady flow file operations.
6. **RasUtils**: Offers utility functions for common tasks.
7. **RasExamples**: Manages example HEC-RAS projects.


## Best Practices

1. **RAS Object Usage**:
   - For simple scripts working with a single project, use the global 'ras' object:
     ```python
     from ras_commander import ras, init_ras_project
     init_ras_project("/path/to/project", "6.5")
     # Use ras object for operations
     ```
   - For complex scripts or when working with multiple projects, create and use separate RAS objects:
     ```python
     from ras_commander import RasPrj, init_ras_project
     project1 = init_ras_project("/path/to/project1", "6.5")
     project2 = init_ras_project("/path/to/project2", "6.5")
     ```
   - Be consistent: don't mix global and custom RAS object usage in the same script.

2. **Plan Specification**:
   - Use plan numbers as strings (e.g., "01", "02") for consistency:
     ```python
     RasCommander.compute_plan("01")
     ```
   - Always check available plans before specifying plan numbers:
     ```python
     print(ras.plan_df)  # Display available plans
     ```

3. **Geometry Preprocessor Files**:
   - Clear geometry preprocessor files before significant changes:
     ```python
     RasGeo.clear_geompre_files()
     ```
   - Use `clear_geompre=True` for clean computation environment:
     ```python
     RasCommander.compute_plan("01", clear_geompre=True)
     ```

4. **Parallel Execution**:
   - Consider available cores when setting `max_workers`:
     ```python
     RasCommander.compute_parallel(max_workers=4, cores_per_run=2)
     ```
   - Use `dest_folder` to keep project folder organized:
     ```python
     RasCommander.compute_parallel(dest_folder="/path/to/results")
     ```

5. **Error Handling**:
   - Use try-except blocks to handle potential errors:
     ```python
     try:
         RasCommander.compute_plan("01")
     except FileNotFoundError:
         print("Plan file not found")
     ```
   - Utilize logging for informative output:
     ```python
     import logging
     logging.basicConfig(level=logging.INFO)
     ```

6. **File Path Handling**:
   - Use pathlib.Path for file and directory operations:
     ```python
     from pathlib import Path
     project_path = Path("/path/to/project")
     ```

7. **Type Hinting**:
   - Use type hints to improve code readability and IDE support:
     ```python
     def compute_plan(plan_number: str, clear_geompre: bool = False) -> bool:
         ...
     ```

## Common Usage Patterns

1. **Initializing a Project**:
   ```python
   from ras_commander import init_ras_project, ras
   init_ras_project("/path/to/project", "6.5")
   print(f"Working with project: {ras.project_name}")
   ```

2. **Cloning a Plan**:
   ```python
   from ras_commander import RasPlan
   new_plan_number = RasPlan.clone_plan("01")
   print(f"Created new plan: {new_plan_number}")
   ```

3. **Updating Unsteady Flow Parameters**:
   ```python
   from ras_commander import RasUnsteady, RasPlan
   plan_path = RasPlan.get_plan_path("01")
   RasUnsteady.update_unsteady_parameters(plan_path, {"Computation Interval": "1MIN"})
   ```

4. **Executing a Single Plan**:
   ```python
   from ras_commander import RasCommander
   success = RasCommander.compute_plan("01")
   print(f"Plan execution {'successful' if success else 'failed'}")
   ```

5. **Parallel Execution of Multiple Plans**:
   ```python
   from ras_commander import RasCommander
   results = RasCommander.compute_parallel(plan_numbers=["01", "02"], max_workers=2, cores_per_run=2)
   for plan, success in results.items():
       print(f"Plan {plan}: {'Successful' if success else 'Failed'}")
   ```

6. **Working with Example Projects**:
   ```python
   from ras_commander import RasExamples
   ras_examples = RasExamples()
   project_paths = ras_examples.extract_project(["Balde Eagle Creek", "Muncie"])
   for path in project_paths:
       print(f"Extracted project to: {path}")
   ```

```markdown
## Advanced Usage

1. **Working with Multiple Projects**:
   ```python
   from ras_commander import init_ras_project, RasCommander, RasPlan

   project1 = init_ras_project("/path/to/project1", "6.5")
   project2 = init_ras_project("/path/to/project2", "6.5")

   # Clone plans in both projects
   new_plan1 = RasPlan.clone_plan("01", ras_object=project1)
   new_plan2 = RasPlan.clone_plan("01", ras_object=project2)

   # Execute plans in both projects
   RasCommander.compute_plan(new_plan1, ras_object=project1)
   RasCommander.compute_plan(new_plan2, ras_object=project2)
   ```

2. **Using ThreadPoolExecutor for Simultaneous Execution**:
   ```python
   from concurrent.futures import ThreadPoolExecutor
   from ras_commander import RasCommander

   def execute_plan(plan, project, compute_folder):
       return RasCommander.compute_plan(plan, ras_object=project, compute_folder=compute_folder)

   with ThreadPoolExecutor(max_workers=2) as executor:
       futures = [
           executor.submit(execute_plan, "01", project1, "compute_folder1"),
           executor.submit(execute_plan, "01", project2, "compute_folder2")
       ]
       for future in futures:
           print(f"Plan execution result: {future.result()}")
   ```

3. **Creating and Using Plan Sets**:
   ```python
   import pandas as pd
   from ras_commander import RasPlan, RasCommander

   def create_plan_set(base_plan, num_copies):
       plan_set = []
       for _ in range(num_copies):
           new_plan = RasPlan.clone_plan(base_plan)
           plan_set.append({'plan_number': new_plan})
       return pd.DataFrame(plan_set)

   plan_set = create_plan_set("01", 5)
   results = RasCommander.compute_parallel(plan_numbers=plan_set['plan_number'].tolist())
   ```

4. **Custom Error Handling and Logging**:
   ```python
   import logging
   from ras_commander import RasCommander

   logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
   logger = logging.getLogger(__name__)

   try:
       RasCommander.compute_plan("01")
   except FileNotFoundError as e:
       logger.error(f"Plan file not found: {e}")
   except Exception as e:
       logger.exception(f"An unexpected error occurred: {e}")
   ```

5. **Advanced DataFrame Operations for Result Analysis**:
   ```python
   import pandas as pd
   from ras_commander import ras

   # Assuming we have executed multiple plans and have results
   results_df = ras.get_hdf_entries()

   # Filter and analyze results
   successful_runs = results_df[results_df['HDF_Results_Path'].notna()]
   print(f"Number of successful runs: {len(successful_runs)}")

   # You could add more advanced analysis here, such as comparing results across different plans
   ```

## Troubleshooting

1. **Project Initialization Issues**:
   - Ensure the project path is correct and the .prj file exists.
   - Verify that the specified HEC-RAS version is installed on your system.

2. **Execution Failures**:
   - Check that the plan, geometry, and flow files referenced in the plan exist.
   - Ensure the HEC-RAS executable path is correct.
   - Review HEC-RAS log files for specific error messages.

3. **Parallel Execution Problems**:
   - Reduce the number of `max_workers` if you're experiencing memory issues.
   - Ensure each worker has sufficient resources (cores, memory) to run a plan.

4. **File Access Errors**:
   - Verify that you have read/write permissions for the project directory.
   - Close any open HEC-RAS instances that might be locking files.

5. **Inconsistent Results**:
   - Always clear geometry preprocessor files (`clear_geompre=True`) when making geometry changes.
   - Ensure that plan parameters are correctly set before execution.

## Conclusion

The RAS-Commander library provides a powerful set of tools for automating HEC-RAS operations. By following the best practices outlined in this guide and leveraging the library's features, you can efficiently manage and execute complex HEC-RAS projects programmatically.

Remember to always refer to the latest documentation and the library's source code for the most up-to-date information. As you become more familiar with RAS-Commander, you'll discover more ways to optimize your HEC-RAS workflows and increase your productivity.

For further assistance, bug reports, or feature requests, please refer to the library's GitHub repository and issue tracker. Happy modeling!
==================================================

Folder: c:\GH\ras_commander\examples
==================================================

==================================================

File: c:\GH\ras_commander\future_dev_roadmap.ipynb
==================================================
{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Future Development Roadmap: \n",
    "    \n",
    "    1. Load critical keys and values from the project files into the project config\n",
    "        Implemented:\n",
    "        - Project Name\n",
    "        - Project Folder\n",
    "        - Lists of plan, flow, unsteady, and geometry files\n",
    "        - HEC-RAS Executable Path\n",
    "        \n",
    "        Not Implemented:\n",
    "        - Units \n",
    "        - Coordinate System \n",
    "        - rasmap file path (replace prj file path extension with \".rasmap\" and add it to the project config)\n",
    "        - Current Plan\n",
    "        - Description (including checks to see if it is a valid string and within the default max length)\n",
    "        - DSS Start Date=01JAN1999 (note format is MMDDYYY)\n",
    "        - DSS Start Time=1200 (note format is HHMM)\n",
    "        - DSS End Date=04JAN1999 (note format is MMDDYYY)\n",
    "        - DSS End Time=1200 (note format is HHMM)\n",
    "        - DSS File=dss\n",
    "        - DSS File=Bald_Eagle_Creek.dss \n",
    "              \n",
    "    Other not implemented:\n",
    "    \n",
    "    2. Load critical keys and lists of string values from the plan files into the project config\n",
    "        - Plan Title\n",
    "        - Plan Shortid\n",
    "        - Simulation Date\n",
    "        - Geometry File\n",
    "        - Flow File (may not be present) - if present, the plan is a 1D steady plan\n",
    "        - Unsteady File (may not be present) - if present, the plan is a 1D or 2D unsteady plan\n",
    "        - UNET D2 Name (may not be present) - if present, the plan is a 2D plan\n",
    "        - Type (1D Steady, 1D Unsteady, 1D/2D, or 2D)\n",
    "        - UNET 1D Methodology\n",
    "       \n",
    "    3. Load critical keys and strings from the unsteady flow files into the project config\n",
    "        - Flow Title\n",
    "        - Pandas Dataframe for any Boundary Conditions present and whether they are defined in the file or whether they use a DSS file input\n",
    "           - One dataframe for all unsteady flow files, with each Boundary Location in each file having its own row\n",
    "           - For each unsteady flow filereturn an appended dataframe with each boundary condition and it's \"Boundary Name\", \"Interval\", \"DSS Path\", \"Flow Hydrograph Slope\", and whether Use DSS is True or False\n",
    "           - Need to incorporate knowledge from the excel methods we used for setting boundary conditions\n",
    "           \n",
    "    4. Load critical keys and strings from the steady flow files into the project config\n",
    "        - Flow Title\n",
    "        - Since steady models are not as commonly used, this is a low priority integration (external contributions are welcome)\n",
    "               \n",
    "    \n",
    "    5. Load critical keys and values from the rasmap file into the project config\n",
    "        - rasmap_projection_string\n",
    "        - Version   #Ex: <Version>2.0.0</Version> \n",
    "        - RASProjectionFilename Filename=\".\\Terrain\\Projection.prj\"\n",
    "        \n",
    "        - List of ras terrains as pandas dataframes\n",
    "            - for each, list of tiff files and order\n",
    "            - flag whether terrain mods exist \n",
    "            \n",
    "        - List of Infiltration hdf files as pandas dataframes\n",
    "            - Mapping of infiltration layers to geometries\n",
    "            \n",
    "        - List of land cover hdf files as pandas dataframes\n",
    "            - Mapping of land cover to geometries\n",
    "        \n",
    "        - List of all Mannings N layers, hdf files and mapping to geometries as pandas dataframes\n",
    "            \n",
    "    6. Create a list of all valid hdf plan files are present in the project folder, and flag whether they contain a completed simulation\n",
    "    \n",
    "    This roadmap for the project_init function will provide the basic information needed to support most basic hec-ras automation workflows.  \n",
    "    \n",
    "    Remember, this project init might be called multiple times.  Every time, it should clear any previously created datafrarmes and variables and replace them.  It is important that revisions can be made, init be re-run, and information is current and reflects the current state of the project. \n",
    "\n",
    "       \n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "NOTE: We will NOT need to extract all keys from the HEC-RAS plan files.  We only need to toggle relevant settings, and provide a low-level function that can handle setting a single key by passing a full path.  We will build on top of that function to make specialized functions for each key that is important enough to warrant automation.  Each should have a docstring explaining the keys and how to set them, so the end user does not need to determine those details.   "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# HEC-RAS Plan File Extraction Strategy (Version 6.5)\n",
    "\n",
    "## Key Changes Observed:\n",
    "\n",
    "1. The Program Version has been updated to 6.50.\n",
    "2. Several new keys have been added.\n",
    "3. Some keys have been removed.\n",
    "4. The order of some keys has changed.\n",
    "\n",
    "## Observed Keys in Version 6.5 (1D Unsteady Plan File)\n",
    "\n",
    "| Key | Example Value | Extraction Strategy |\n",
    "|-----|---------------|---------------------|\n",
    "| Plan Title | Unsteady with Bridges and Dam | Split by '=' and take second part |\n",
    "| Program Version | 6.50 | Split by '=' and take second part |\n",
    "| Short Identifier | UnsteadyFlow | Split by '=' and take second part |\n",
    "| Simulation Date | 18FEB1999,0000,24FEB1999,0500 | Split by '=' and ',' to extract start and end dates/times |\n",
    "| Geom File | g01 | Split by '=' and take second part |\n",
    "| Flow File | u02 | Find all occurrences, split by '=' and take second part |\n",
    "| Subcritical Flow | (No value, presence indicates subcritical flow) | Check for presence of line |\n",
    "| K Sum by GR | 0 | Split by '=' and take second part |\n",
    "| Std Step Tol | 0.01 | Split by '=' and take second part |\n",
    "| Critical Tol | 0.01 | Split by '=' and take second part |\n",
    "| Num of Std Step Trials | 20 | Split by '=' and take second part |\n",
    "| Max Error Tol | 0.3 | Split by '=' and take second part |\n",
    "| Flow Tol Ratio | 0.001 | Split by '=' and take second part |\n",
    "| Split Flow NTrial | 30 | Split by '=' and take second part |\n",
    "| Split Flow Tol | 0.02 | Split by '=' and take second part |\n",
    "| Split Flow Ratio | 0.02 | Split by '=' and take second part |\n",
    "| Log Output Level | 0 | Split by '=' and take second part |\n",
    "| Friction Slope Method | 2 | Split by '=' and take second part |\n",
    "| Unsteady Friction Slope Method | 2 | Split by '=' and take second part |\n",
    "| Unsteady Bridges Friction Slope Method | 1 | Split by '=' and take second part |\n",
    "| Parabolic Critical Depth | (No value, presence indicates use) | Check for presence of line |\n",
    "| Global Vel Dist | 0 , 0 , 0 | Split by '=' and then by ',' |\n",
    "| Global Log Level | 0 | Split by '=' and take second part |\n",
    "| CheckData | True | Split by '=' and take second part |\n",
    "| Encroach Param | -1 ,0,0, 0 | Split by '=' and then by ',' |\n",
    "| Flow Ratio Target | (Empty in example) | Split by '=' and take second part if present |\n",
    "| Flow Ratio Tolerance | 0.1 | Split by '=' and take second part |\n",
    "| Flow Ratio Initial Ratio | (Empty in example) | Split by '=' and take second part if present |\n",
    "| Flow Ratio Min Ratio | 0.5 | Split by '=' and take second part |\n",
    "| Flow Ratio Max Ratio | 4 | Split by '=' and take second part |\n",
    "| Flow Ratio Max Iterations | 10 | Split by '=' and take second part |\n",
    "| Flow Ratio Reference | (Empty in example) | Split by '=' and take second part if present |\n",
    "| Computation Interval | 2MIN | Split by '=' and parse value and unit |\n",
    "| Output Interval | 1HOUR | Split by '=' and parse value and unit |\n",
    "| Instantaneous Interval | 2HOUR | Split by '=' and parse value and unit |\n",
    "| Mapping Interval | 1HOUR | Split by '=' and parse value and unit |\n",
    "| Computation Time Step Use Courant | 0 | Split by '=' and take second part |\n",
    "| Computation Time Step Use Time Series | 0 | Split by '=' and take second part |\n",
    "| Computation Time Step Max Courant | (Empty in example) | Split by '=' and take second part if present |\n",
    "| Computation Time Step Min Courant | (Empty in example) | Split by '=' and take second part if present |\n",
    "| Computation Time Step Count To Double | 0 | Split by '=' and take second part |\n",
    "| Computation Time Step Max Doubling | 0 | Split by '=' and take second part |\n",
    "| Computation Time Step Max Halving | 0 | Split by '=' and take second part |\n",
    "| Computation Time Step Residence Courant | 0 | Split by '=' and take second part |\n",
    "| Run HTab | 1 | Split by '=' and take second part |\n",
    "| Run UNet | 1 | Split by '=' and take second part |\n",
    "| Run Sediment | 0 | Split by '=' and take second part |\n",
    "| Run PostProcess | 1 | Split by '=' and take second part |\n",
    "| Run WQNet | 0 | Split by '=' and take second part |\n",
    "| Run RASMapper | 0 | Split by '=' and take second part |\n",
    "| UNET Theta | 1 | Split by '=' and take second part |\n",
    "| UNET Theta Warmup | 1 | Split by '=' and take second part |\n",
    "| UNET ZTol | 0.01 | Split by '=' and take second part |\n",
    "| UNET ZSATol | 0.1 | Split by '=' and take second part |\n",
    "| UNET QTol | (Empty in example) | Split by '=' and take second part if present |\n",
    "| UNET MxIter | 20 | Split by '=' and take second part |\n",
    "| UNET Max Iter WO Improvement | 0 | Split by '=' and take second part |\n",
    "| UNET MaxInSteps | 0 | Split by '=' and take second part |\n",
    "| UNET DtIC | 0 | Split by '=' and take second part |\n",
    "| UNET DtMin | 0 | Split by '=' and take second part |\n",
    "| UNET MaxCRTS | 20 | Split by '=' and take second part |\n",
    "| UNET WFStab | 2 | Split by '=' and take second part |\n",
    "| UNET SFStab | 1 | Split by '=' and take second part |\n",
    "| UNET WFX | 1 | Split by '=' and take second part |\n",
    "| UNET SFX | 1 | Split by '=' and take second part |\n",
    "| UNET Gravity | 32.17405 | Split by '=' and take second part |\n",
    "| UNET 1D Methodology | Finite Difference | Split by '=' and take second part |\n",
    "| UNET DSS MLevel | 4 | Split by '=' and take second part |\n",
    "| UNET Pardiso | 0 | Split by '=' and take second part |\n",
    "| UNET DZMax Abort | 100 | Split by '=' and take second part |\n",
    "| UNET Use Existing IB Tables | -1 | Split by '=' and take second part |\n",
    "| UNET Froude Reduction | False | Split by '=' and take second part |\n",
    "| UNET Froude Limit | 0.8 | Split by '=' and take second part |\n",
    "| UNET Froude Power | 4 | Split by '=' and take second part |\n",
    "| UNET D1 Cores | 0 | Split by '=' and take second part |\n",
    "| UNET WindReference | Eulerian | Split by '=' and take second part |\n",
    "| UNET WindDragFormulation | Hsu (1988) | Split by '=' and take second part |\n",
    "| UNET D2 Coriolis | 0 | Split by '=' and take second part |\n",
    "| UNET D2 Cores | 0 | Split by '=' and take second part |\n",
    "| UNET D2 Theta | 1 | Split by '=' and take second part |\n",
    "| UNET D2 Theta Warmup | 1 | Split by '=' and take second part |\n",
    "| UNET D2 Z Tol | 0.01 | Split by '=' and take second part |\n",
    "| UNET D2 Volume Tol | 0.01 | Split by '=' and take second part |\n",
    "| UNET D2 Max Iterations | 20 | Split by '=' and take second part |\n",
    "| UNET D2 Equation | 0 | Split by '=' and take second part |\n",
    "| UNET D2 TotalICTime | (Empty in example) | Split by '=' and take second part if present |\n",
    "| UNET D2 RampUpFraction | 0.5 | Split by '=' and take second part |\n",
    "| UNET D2 TimeSlices | 1 | Split by '=' and take second part |\n",
    "| UNET D2 Turbulence Formulation | Non-Conservative (original) | Split by '=' and take second part |\n",
    "| UNET D2 Eddy Viscosity | (Empty in example) | Split by '=' and take second part if present |\n",
    "| UNET D2 Transverse Eddy Viscosity | (Empty in example) | Split by '=' and take second part if present |\n",
    "| UNET D2 Smagorinsky Mixing | 0 | Split by '=' and take second part |\n",
    "| UNET D2 BCVolumeCheck | 0 | Split by '=' and take second part |\n",
    "| UNET D2 Latitude | (Empty in example) | Split by '=' and take second part if present |\n",
    "| UNET D2 Cores | 0 | Split by '=' and take second part |\n",
    "| UNET D2 SolverType | PARDISO (Direct) | Split by '=' and take second part |\n",
    "| UNET D2 Minimum Iterations | 3 | Split by '=' and take second part |\n",
    "| UNET D2 Maximum Iterations | 30 | Split by '=' and take second part |\n",
    "| UNET D2 Restart Number | 10 | Split by '=' and take second part |\n",
    "| UNET D2 Relaxation Coeff | 1.3 | Split by '=' and take second part |\n",
    "| UNET D2 SOR Precondition Iterations | 10 | Split by '=' and take second part |\n",
    "| UNET D2 ILUT Maximum Fill | 8 | Split by '=' and take second part |\n",
    "| UNET D2 ILUT Tolerance | 1E-08 | Split by '=' and take second part |\n",
    "| UNET D2 Convergence Tolerance | 0.00001 | Split by '=' and take second part |\n",
    "| PS Theta | 1 | Split by '=' and take second part |\n",
    "| PS WS Tol | 0.01 | Split by '=' and take second part |\n",
    "| PS Volume Tol | 0.01 | Split by '=' and take second part |\n",
    "| PS Max Iterations | 20 | Split by '=' and take second part |\n",
    "| PS Time Slices | 1 | Split by '=' and take second part |\n",
    "| PS Iterate With 2D | 0 | Split by '=' and take second part |\n",
    "| PS Cores | 0 | Split by '=' and take second part |\n",
    "| UNET D1D2 MaxIter | 0 | Split by '=' and take second part |\n",
    "| UNET D1D2 ZTol | 0.01 | Split by '=' and take second part |\n",
    "| UNET D1D2 QTol | 0.1 | Split by '=' and take second part |\n",
    "| UNET D1D2 MinQTol | 1 | Split by '=' and take second part |\n",
    "| DSS File | dss | Split by '=' and take second part |\n",
    "| Write IC File | 0 | Split by '=' and take second part |\n",
    "| Write IC File at Fixed DateTime | 0 | Split by '=' and take second part |\n",
    "| IC Time | ,, | Split by '=' and then by ',' |\n",
    "| Write IC File Reoccurance | (Empty in example) | Split by '=' and take second part if present |\n",
    "| Write IC File at Sim End | 0 | Split by '=' and take second part |\n",
    "| Echo Input | False | Split by '=' and take second part |\n",
    "| Echo Parameters | False | Split by '=' and take second part |\n",
    "| Echo Output | False | Split by '=' and take second part |\n",
    "| Write Detailed | 0 | Split by '=' and take second part |\n",
    "| HDF Write Warmup | 0 | Split by '=' and take second part |\n",
    "| HDF Write Time Slices | 0 | Split by '=' and take second part |\n",
    "| HDF Flush | 0 | Split by '=' and take second part |\n",
    "| HDF Compression | 1 | Split by '=' and take second part |\n",
    "| HDF Chunk Size | 1 | Split by '=' and take second part |\n",
    "| HDF Spatial Parts | 1 | Split by '=' and take second part |\n",
    "| HDF Use Max Rows | 0 | Split by '=' and take second part |\n",
    "| HDF Fixed Rows | 1 | Split by '=' and take second part |\n",
    "| Stage Flow Hydrograph | Bald Eagle      ,Loc Hav         ,138154.4 | Find all occurrences, split by '=' and ',' |\n",
    "| Calibration Method | 0 | Split by '=' and take second part |\n",
    "| Calibration Iterations | 20 | Split by '=' and take second part |\n",
    "| Calibration Max Change | 0.05 | Split by '=' and take second part |\n",
    "| Calibration Tolerance | 0.2 | Split by '=' and take second part |\n",
    "| Calibration Maximum | 1.5 | Split by '=' and take second part |\n",
    "| Calibration Minimum | 0.5 | Split by '=' and take second part |\n",
    "| Calibration Optimization Method | 1 | Split by '=' and take second part |\n",
    "| Calibration Window | ,,, | Split by '=' and then by ',' |\n",
    "| WQ AD Non Conservative | (No value, presence indicates use) | Check for presence of line |\n",
    "| WQ ULTIMATE | -1 | Split by '=' and take second part |\n",
    "| WQ Max Comp Step | 1HOUR | Split by '=' and parse value and unit |\n",
    "| WQ Output Interval | 15MIN | Split by '=' and parse value and unit |\n",
    "| WQ Output Selected Increments | 0 | Split by '=' and take second part |\n",
    "| WQ Create Restart | 0 | Split by '=' and take second part |\n",
    "| WQ Fixed Restart | 0 | Split by '=' and take second part |\n",
    "| WQ Restart Simtime | (Empty in example) | Split by '=' and take second part if present |\n",
    "| WQ Restart Date | (Empty in example) | Split by '=' and take second part if present |\n",
    "| WQ Restart Hour | (Empty in example) | Split by '=' and take second part if present |\n",
    "| WQ System Summary | 0 | Split by '=' and take second part |\n",
    "| WQ Write To DSS | 0 | Split by '=' and take second part |\n",
    "| Sorting and Armoring Iterations | 10 | Split by '=' and take second part |\n",
    "| XS Update Threshold | 0.02 | Split by '=' and take second part |\n",
    "| Bed Roughness Predictor | 0 | Split by '=' and take second part |\n",
    "| Hydraulics Update Threshold | 0.02 | Split by '=' and take second part |\n",
    "| Energy Slope Method | 0 | Split by '=' and take second part |\n",
    "| Volume Change Method | 1 | Split by '=' and take second part |\n",
    "| Sediment Retention Method | 0 | Split by '=' and take second part |\n",
    "| Sediment TS Multiplier | 1 | Split by '=' and take second part |\n",
    "| Warm Up Method | 0 | Split by '=' and take second part |\n",
    "| Warm Up Duration | (Empty in example) | Split by '=' and take second part if present |\n",
    "| Warm Up Duration - Concentration | (Empty in example) | Split by '=' and take second part if present |\n",
    "| Warm Up Duration - Gradation | (Empty in example) | Split by '=' and take second part if present |\n",
    "| Warm Up Duration - Bathymetry | (Empty in example) | Split by '=' and take second part if present |\n",
    "| XS Weighting Method | 0 | Split by '=' and take second part |\n",
    "| Number of US Weighted Cross Sections | 1 | Split by '=' and take second part |\n",
    "| Number of DS Weighted Cross Sections | 1 | Split by '=' and take second part |\n",
    "| Upstream XS Weight | 0 | Split by '=' and take second part |\n",
    "| Main XS Weight | 1 | Split by '=' and take second part |\n",
    "| Downstream XS Weight | 0 | Split by '=' and take second part |\n",
    "| Number of DS XS's Weighted with US Boundary | 1 | Split by '=' and take second part |\n",
    "| Upstream Boundary Weight | 1 | Split by '=' and take second part |\n",
    "| Weight of XSs Associated with US Boundary | 0 | Split by '=' and take second part |\n",
    "| Number of US XS's Weighted with DS Boundary | 1 | Split by '=' and take second part |\n",
    "| Downstream Boundary Weight | 0.5 | Split by '=' and take second part |\n",
    "| Weight of XSs Associated with DS Boundary | 0.5 | Split by '=' and take second part |\n",
    "| Percentile Method | 0 | Split by '=' and take second part |\n",
    "| Sediment Output Level | 4 | Split by '=' and take second part |\n",
    "| Mass or Volume Output | 0 | Split by '=' and take second part |\n",
    "| Output Increment Type | 1 | Split by '=' and take second part |\n",
    "| Profile and TS Output Increment | 10 | Split by '=' and take second part |\n",
    "| Transport Output Increment | 1 | Split by '=' and take second part |\n",
    "| XS Output Flag | 0 | Split by '=' and take second part |\n",
    "| XS Output Increment | 10 | Split by '=' and take second part |\n",
    "| Read HDF5 Sediment Hotstart | 0 | Split by '=' and take second part |\n",
    "| Sediment Hotstart Type | 0 | Split by '=' and take second part |\n",
    "| Sediment Hotstart File | (Empty in example) | Split by '=' and take second part if present |\n",
    "| Sediment Hotstart Date | (Empty in example) | Split by '=' and take second part if present |\n",
    "| Sediment Hotstart Time | (Empty in example) | Split by '=' and take second part if present |\n",
    "| Write Gradation File | 0 | Split by '=' and take second part |\n",
    "| Read Gradation Hotstart | 0 | Split by '=' and take second part |\n",
    "| Gradation File Name | (Empty in example) | Split by '=' and take second part if present |\n",
    "| Write HDF5 File | 0 | Split by '=' and take second part |\n",
    "| Write Binary Output | 1 | Split by '=' and take second part |\n",
    "| Write DSS Sediment File | 0 | Split by '=' and take second part |\n",
    "| DSS Sediment Output Type | 1 | Split by '=' and take second part |\n",
    "| DSS Location | (Empty in example) | Split by '=' and take second part if present |\n",
    "| Summary Reach | (Empty in example) | Split by '=' and take second part if present |\n",
    "| SV Curve | 0 | Split by '=' and take second part |\n",
    "| Specific Gage Flag | 0 | Split by '=' and take second part |\n",
    "| Subcell Erosion Methods | 0 | Split by '=' and take second part |\n",
    "| Subcell Deposition Methods | 0 | Split by '=' and take second part |\n",
    "| Advection Scheme | 1 | Split by '=' and take second part |\n",
    "| Matrix Solver | 0 | Split by '=' and take second part |\n",
    "| Implicit Weighting Factor | 1 | Split by '=' and take second part |\n",
    "| Maximum Outer Loop Convergence Iterations | 5 | Split by '=' and take second part |\n",
    "| Convergence Maximum Absolute | 0.001 | Split by '=' and take second part |\n",
    "| Convergence RMSE | 0.0001 | Split by '=' and take second part |\n",
    "| Grain Fractions Max Abs Error | 0.001 | Split by '=' and take second part |\n",
    "| Max Subgrid Regions | 1 | Split by '=' and take second part |\n",
    "| Max Subgrid Length Scale | 3.402823E+38 | Split by '=' and take second part |\n",
    "| Initial Layer Thickness | 3 | Split by '=' and take second part |\n",
    "| Min Layer Thickness | 0.1 | Split by '=' and take second part |\n",
    "| Max Layer Thickness | 6 | Split by '=' and take second part |\n",
    "| Number of Layers | 5 | Split by '=' and take second part |\n",
    "\n",
    "## Key Changes and Implications:\n",
    "\n",
    "1. New sediment-related parameters: The updated file includes many new parameters related to sediment transport and modeling. This suggests that the sediment modeling capabilities have been expanded in Version 6.5.\n",
    "\n",
    "2. Computational parameters: New parameters for time step control and computational methods have been added, indicating more fine-grained control over the simulation process.\n",
    "\n",
    "3. 2D modeling parameters: Additional parameters for 2D modeling (e.g., UNET D2 parameters) suggest enhanced 2D modeling capabilities.\n",
    "\n",
    "4. Water quality parameters: Some water quality (WQ) parameters have been removed, while others remain. This might indicate a change in how water quality modeling is handled.\n",
    "\n",
    "5. Output control: New parameters for controlling output formats and intervals have been added, suggesting more flexible output options.\n",
    "\n",
    "## Revised Extraction Strategy:\n",
    "\n",
    "1. Maintain a dictionary of all of the relevant keys, including those from both versions.\n",
    "    - Have a separate function specifically to lookup keys in the plan file from a dictionary or by passing the key name. \n",
    "2. When parsing the file, check for the presence of each key and extract its value if present.\n",
    "3. For keys that may appear multiple times (e.g., 'Stage Flow Hydrograph'), collect all occurrences in a list.\n",
    "4. Use regular expressions for more complex parsing tasks, especially for keys with multiple values or specific formats.\n",
    "5. Implement version-specific parsing logic where necessary, based on the 'Program Version' value.\n",
    "6. Handle empty values consistently, either storing them as None or an empty string.\n",
    "7. For boolean flags (keys without values), store their presence as True in the dictionary.\n",
    "8. Implement error handling for unexpected formats or missing required keys.\n",
    "\n",
    "## Similarities Across Plan Types\n",
    "\n",
    "1. Basic Structure: All plan types (steady, unsteady, 1D, and 2D) maintain a similar overall structure with key-value pairs.\n",
    "2. Common Keys: Many keys are shared across all plan types, including basic project information, computational parameters, and output settings.\n",
    "3. Version Consistency: The key structure remains consistent across different HEC-RAS versions, with differences primarily in values rather than key names.\n",
    "\n",
    "## Key Differences in 2D Plans\n",
    "\n",
    "While 2D plans share many keys with 1D plans, they introduce several new keys and sections:\n",
    "\n",
    "1. 2D Specific Keys:\n",
    "   - UNET D2 Name\n",
    "   - UNET D2 Cores\n",
    "   - UNET D2 SolverType\n",
    "   - UNET D2 Turbulence Formulation\n",
    "   - UNET D2 Smagorinsky Mixing\n",
    "\n",
    "2. Breach Modeling Keys:\n",
    "   - Breach Loc (multiple entries)\n",
    "   - Breach Method\n",
    "   - Breach Geom\n",
    "   - Breach Start\n",
    "   - Breach Progression\n",
    "   - Simplified Physical Breach Downcutting\n",
    "   - Simplified Physical Breach Widening\n",
    "   - Mass Wasting Options\n",
    "   - Various DLBreach keys\n",
    "\n",
    "3. Adaptive Hydraulics (ADH) Keys:\n",
    "   - ADH Filename\n",
    "   - ADH Link\n",
    "\n",
    "## Updated Extraction Strategy\n",
    "\n",
    "1. Single Key-Value Pairs:\n",
    "   - Create a dictionary to store all single key-value pairs.\n",
    "   - Populate this dictionary for all plan types (steady, unsteady, 1D, 2D).\n",
    "   - Handle empty values consistently, storing them as None or an empty string.\n",
    "\n",
    "2. Multi-Key Pairs (e.g., Boundary Conditions, Breach Locations):\n",
    "   - Use pandas DataFrames to store multi-key pairs.\n",
    "   - Create separate DataFrames for different types of multi-key data (e.g., one for boundary conditions, another for breach locations).\n",
    "   - Ensure the DataFrame structure can accommodate data from all plan types.\n",
    "\n",
    "3. Plan Type Detection:\n",
    "   - Implement logic to detect the plan type (steady/unsteady, 1D/2D) based on the presence of specific keys.\n",
    "   - Use this detection to guide the extraction process, especially for 2D-specific keys.\n",
    "\n",
    "4. Consistent Extraction Process:\n",
    "   - Use a single extraction function that can handle all plan types.\n",
    "   - Within this function, use conditional logic to handle plan-type-specific keys and sections.\n",
    "\n",
    "5. Version Agnostic Approach:\n",
    "   - Design the extraction process to be version-agnostic, focusing on key names rather than specific values.\n",
    "   - Maintain a comprehensive list of all possible keys across versions and plan types.\n",
    "\n",
    "6. Handling 2D-Specific Data:\n",
    "   - For 2D-specific sections (like breach data), create dedicated DataFrames or nested dictionaries to capture the hierarchical structure.\n",
    "   - Ensure that the extraction process can handle the repetitive nature of certain 2D-specific sections (e.g., multiple breach locations).\n",
    "\n",
    "7. Error Handling and Logging:\n",
    "   - Implement robust error handling to manage unexpected key names or structures.\n",
    "   - Log any inconsistencies or unrecognized keys for further analysis.\n",
    "\n",
    "By following this updated strategy, we can create a flexible and comprehensive extraction process that handles all types of HEC-RAS plan files consistently, regardless of the simulation type or software version. The resulting data structure will provide easy access to both common and plan-specific parameters, facilitating further analysis and processing of HEC-RAS simulation data.\n",
    "\n",
    "\n",
    "\n",
    "This revised strategy accounts for the changes in Version 6.5 while maintaining backward compatibility with earlier versions. It allows for flexible parsing of the plan file, accommodating both existing and potential future changes in the file structure."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}

==================================================

File: c:\GH\ras_commander\LICENSE
==================================================
MIT License

Copyright (c) 2024 William M. Katzenmeyer

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.

==================================================

File: c:\GH\ras_commander\pyproject.toml
==================================================
[build-system]
requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2"]
build-backend = "setuptools.build_meta"

[project]
name = "ras_commander"
dynamic = ["version"]
description = "A library for automating HEC-RAS operations using python functions."
authors = [{name = "William Katzenmeyer, P.E., C.F.M.", email = "heccommander@gmail.com"}]
readme = "README.md"
requires-python = ">=3.9"
classifiers = [
    "Programming Language :: Python :: 3",
    "License :: OSI Approved :: MIT License",
    "Operating System :: OS Independent",
]

[project.urls]
Homepage = "https://github.com/yourusername/ras_commander"

[tool.setuptools_scm]
version_scheme = "release-branch-semver"  # This ensures clean, stable version numbers
local_scheme = "no-local-version"  # Avoid any local identifiers like +g684fd17
write_to = "ras_commander/_version.py"


[tool.setuptools]
packages = ["ras_commander"]

==================================================

File: c:\GH\ras_commander\python
==================================================

==================================================

Folder: c:\GH\ras_commander\ras_commander
==================================================

==================================================

File: c:\GH\ras_commander\README.md
==================================================
# RAS-Commander (ras_commander)

ras_commander is a Python library for automating HEC-RAS operations, providing a set of tools to interact with HEC-RAS project files, execute simulations, and manage project data. This library is an evolution of the RAS-Commander 1.0 Python Notebook Application previously released under the HEC-Commander tools.

## Features

- Automate HEC-RAS project management and simulations
- Support for both single and multiple project instances
- Parallel execution of HEC-RAS plans
- Utilities for managing geometry, plan, and unsteady flow files
- Example project management for testing and development
- Two primary operation modes: "Run Missing" and "Build from DSS"

## Installation

Install ras_commander using pip:

```bash
pip install ras-commander
```

## Requirements

- Python 3.9+
- HEC-RAS 6.2 or later (other versions may work, all testing was done with version 6.2 and above)

For a full list of dependencies, see the `requirements.txt` file.

## Quick Start

```python
from ras_commander import init_ras_project, RasCommander, RasPlan

# Initialize a project
init_ras_project("/path/to/project", "6.5")

# Execute a single plan
RasCommander.compute_plan("01")

# Execute plans in parallel
results = RasCommander.compute_parallel(
    plan_numbers=["01", "02"],
    max_workers=2,
    cores_per_run=2
)

# Modify a plan
RasPlan.set_geom("01", "02")
```

## Key Components

- `RasPrj`: Manages HEC-RAS projects
- `RasCommander`: Handles execution of HEC-RAS simulations
- `RasPlan`: Provides functions for modifying and updating plan files
- `RasGeo`: Handles operations related to geometry files
- `RasUnsteady`: Manages unsteady flow file operations
- `RasUtils`: Contains utility functions for file operations and data management
- `RasExamples`: Manages and loads HEC-RAS example projects

## Documentation

For detailed usage instructions and API documentation, please refer to the [Comprehensive Library Guide](Comprehensive_Library_Guide.md).

## Examples

Check out the `examples/` directory for sample scripts demonstrating various features of ras_commander.

## Development

### Setting up the development environment

1. Clone the repository:
   ```
   git clone https://github.com/yourusername/ras_commander.git
   ```
2. Create a virtual environment and activate it:
   ```
   python -m venv venv
   source venv/bin/activate  # On Windows, use `venv\Scripts\activate`
   ```
3. Install the development dependencies:
   ```
   pip install -r requirements.txt
   ```
Certainly! I'll provide an updated Project Organization Diagram based on the current structure of the ras_commander library. Here's the updated diagram:


## Project Organization Diagram

```
ras_commander
├── .github
│   └── workflows
│       └── python-package.yml
├── ras_commander
│   ├── __init__.py
│   ├── RasCommander.py
│   ├── RasExamples.py
│   ├── RasGeo.py
│   ├── RasPlan.py
│   ├── RasPrj.py
│   ├── RasUnsteady.py
│   └── RasUtils.py
├── examples
│   ├── 01_project_initialization.py
│   ├── 02_plan_operations.py
│   ├── 03_geometry_operations.py
│   ├── 04_unsteady_flow_operations.py
│   ├── 05_utility_functions.py
│   ├── 06_single_plan_execution.py
│   ├── 07_sequential_plan_execution.py
│   ├── 08_parallel_execution.py
│   ├── 09_specifying_plans.py
│   ├── 10_arguments_for_compute.py
│   ├── 11_Using_RasExamples.ipynb
│   ├── 12_plan_set_execution.py
│   └── 13_multiple_project_operations.py
├── tests
│   └── ... (test files)
├── .gitignore
├── LICENSE
├── README.md
├── STYLE_GUIDE.md
├── Comprehensive_Library_Guide.md
├── pyproject.toml
├── setup.cfg
├── setup.py
└── requirements.txt
```


## Inclusion of .cursorrules and ai_tools for AI-driven Coding Experience

Open the ras_commander folder in the Cursor IDE, and it will automatically include the .cursorrules file in your instructions.  Additionally, two other provided methods for interacting with the library though your current AI subscriptions: 

- ChatGPT:  ras_commander GPT Assistant (LINK HERE)
- Latest LLM summaries of the code base:
   - Entire code base: LINK HERE (TOKEN COUNT) (for Claude or Gemini)
   - Examples and Function Docstrings Only: LINK HERE (TOKEN COUNT) (for GPT-4o, o1 or Llama 3.1 405b)
- Cursor IDE through .cursorrules file
- 'rascommander_code_assistant.ipynb' notebook in the ras_commander folder, which allows for dynamic summarization of the code base and API chatting directly through the notebook. 

There are a series of scripts provided in the "llm_summaries" folder that provide summaries of the code base, and the docstrings of the functions.  They can be run in your local environment, or provided to ChatGPT's code interpreter for execution.  

## RAS-Commander GPT Assistant 

The ras_commander GPT assistant has access the entire code base, and can be a helpful tool for understanding the library and its capabilities.  However, it is subject to the same context window limitations and file retrieval limtations as I have covered in ADD BLOG LINK HERE.  For best results, use the llm summaries above to provide robust context to the model before asking to generate complex workflows. 

## Contributing

We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details on how to submit pull requests, report issues, and suggest improvements.

## Style Guide

This project follows a specific style guide to maintain consistency across the codebase. Please refer to the [Style Guide](STYLE_GUIDE.md) for details on coding conventions, documentation standards, and best practices.


## License

ras_commander is released under the MIT License. See the license file for details.

## Acknowledgments

ras_commander is based on the HEC-Commander project's "Command Line is All You Need" approach, leveraging the HEC-RAS command-line interface for automation. The initial development of this library was presented in the HEC-Commander Tools repository.  In a 2024 Australian Water School webinar, Bill demonstrated the derivation of basic HEC-RAS automation functions from plain language instructions. Leveraging the previously developed code and AI tools, the library was created. The primary tools used for this initial development were Anthropic's Claude, GPT-4o, Google's Gemini Experimental models,and the Cursor AI Coding IDE.



## Contact

For questions, suggestions, or support, please contact:
William Katzenmeyer, P.E., C.F.M. - billk@fenstermaker.com









NOTES: INCORPORATE INTO THE README.MD FILE ABOVE UNDER A NEW SECTION FOR CURRENT USES AND ROADMAP ITEMS, THEN DELETE THIS NOTE


Potential Uses of HEC-RAS Automation Functions
This set of functions provides a powerful foundation for automating various aspects of HEC-RAS modeling workflows. Here are some potential applications:
1. Calibration and Sensitivity Analysis:
Automated Parameter Variation: Users can create multiple simulation scenarios with varying parameters (e.g., Manning's n values, boundary conditions, initial conditions) to calibrate their model against observed data.
Sensitivity Testing: Evaluate the impact of different input parameters on model outputs by generating a range of scenarios and analyzing the results. This helps identify critical parameters that require more attention during calibration.
2. Real-time Forecasting:
Dynamic Model Updates: Integrate with external data sources (e.g., weather forecasts, streamflow observations) to automatically update boundary conditions and initial conditions in unsteady flow files before running the simulation.
Ensemble Forecasting: Generate multiple forecasts by incorporating uncertainty in input data and model parameters. This provides a more comprehensive understanding of potential future flow conditions.
3. Scenario Analysis:
Land Use Change Impacts: Evaluate the effects of land use changes on flood risk by modifying Manning's n values using extract_2d_mannings_tables, modify_2d_mannings_table, and write_2d_mannings_tables and running simulations with updated geometry files.
Climate Change Impacts: Analyze the potential impacts of projected climate changes on flood risk by adjusting precipitation patterns and other relevant parameters in unsteady flow files.
4. Batch Processing and High-Performance Computing:
Large-scale Model Runs: Utilize run_plans_parallel to execute multiple simulations concurrently on a multi-core system, significantly reducing processing time for large-scale models or complex scenarios.
Automated Report Generation: Integrate with Python libraries like matplotlib and bokeh to automatically generate customized reports summarizing simulation results, including tables, figures, and maps.
5. Model Development and Testing:
Rapid Prototyping: Quickly set up and run new model configurations using template files and automated workflows, facilitating rapid model development and testing.
Regression Testing: Ensure model integrity and consistency after code changes or updates by automatically running a predefined set of simulations and comparing results with expected outputs.
6. User-Friendly Interfaces:
GUI Development: Integrate with Python GUI libraries like Tkinter or PyQt to create user-friendly interfaces for automating HEC-RAS workflows, allowing non-programmers to access the power of automation.



==================================================

File: c:\GH\ras_commander\requirements.txt
==================================================
# Core dependencies
pandas>=1.0.0
numpy>=1.18.0
pathlib>=1.0.1
requests>=2.25.0

# Data handling and analysis
h5py>=3.1.0

# Plotting (if needed)
matplotlib>=3.3.0

# Development and testing
pytest>=6.2.0
flake8>=3.9.0
black>=21.5b1

# Documentation
sphinx>=3.5.0
sphinx-rtd-theme>=0.5.0

# Packaging and distribution
setuptools>=50.3.2
wheel>=0.35.1
twine>=3.3.0
==================================================

File: c:\GH\ras_commander\setup.cfg
==================================================
[bdist_wheel]
universal = 1

==================================================

File: c:\GH\ras_commander\setup.py
==================================================
from setuptools import setup

setup()

==================================================

File: c:\GH\ras_commander\STYLE_GUIDE.md
==================================================
# RAS Commander (ras_commander) Style Guide

## Table of Contents
1. [Naming Conventions](#1-naming-conventions)
2. [Code Structure and Organization](#2-code-structure-and-organization)
3. [Documentation and Comments](#3-documentation-and-comments)
4. [Code Style](#4-code-style)
5. [Error Handling](#5-error-handling)
6. [Testing](#6-testing)
7. [Version Control](#7-version-control)
8. [Type Hinting](#8-type-hinting)
9. [Project-Specific Conventions](#9-project-specific-conventions)

## 1. Naming Conventions

### 1.1 General Rules
- Use `snake_case` for all function and variable names
- Use `CamelCase` for class names
- Use `UPPER_CASE` for constants

### 1.2 Function Naming
- Start function names with a verb describing the action
- Use clear, descriptive names
- Common verbs and their uses:
  - `get_`: retrieve data
  - `set_`: set values or properties
  - `compute_`: execute or calculate
  - `clone_`: copy
  - `clear_`: remove or reset data
  - `find_`: search
  - `update_`: modify existing data

### 1.3 Abbreviations
Use the following abbreviations consistently throughout the codebase:

- ras: HEC-RAS
- prj: Project
- geom: Geometry
- pre: Preprocessor
- geompre: Geometry Preprocessor
- num: Number
- init: Initialize
- XS: Cross Section
- DSS: Data Storage System
- GIS: Geographic Information System
- BC: Boundary Condition
- IC: Initial Condition
- TW: Tailwater

Use these abbreviations in lowercase for function and variable names (e.g., `geom`, not `Geom` or `GEOM`).

### 1.4 Class Naming
- Use `CamelCase` for class names (e.g., `FileOperations`, `PlanOperations`)
- Class names should be nouns or noun phrases

### 1.5 Variable Naming
- Use descriptive names indicating purpose or content
- Prefix boolean variables with `is_`, `has_`, or similar

## 2. Code Structure and Organization

### 2.1 File Organization
- Group related functions into appropriate classes
- Keep each class in its own file, named after the class

### 2.2 Function Organization
- Order functions logically within a class
- Place common or important functions at the top of the class

### 2.3 Module Structure
- Use the following order for module contents:
  1. Module-level docstring
  2. Imports (grouped and ordered)
  3. Constants
  4. Classes
  5. Functions

## 3. Documentation and Comments

### 3.1 Docstrings
- Use docstrings for all modules, classes, methods, and functions
- Follow Google Python Style Guide format
- Include parameters, return values, and a brief description
- For complex functions, include examples in the docstring

### 3.2 Comments
- Use inline comments sparingly, only for complex logic
- Keep comments up-to-date with code changes
- Use TODO comments for future work, formatted as: `# TODO: description`

## 4. Code Style

### 4.1 Imports
- Order imports as follows:
  1. Standard library imports
  2. Third-party library imports
  3. Local application imports
- Use absolute imports
- Use `import ras_commander as ras` for shortening the library name in examples

### 4.2 Whitespace
- Follow PEP 8 guidelines
- Use 4 spaces for indentation (no tabs)
- Use blank lines to separate logical sections of code

### 4.3 Line Length
- Limit lines to 79 characters for code, 72 for comments and docstrings
- Use parentheses for line continuation in long expressions

## 5. Error Handling

- Use explicit exception handling with try/except blocks
- Raise custom exceptions when appropriate, with descriptive messages
- Use logging for error reporting and debugging information

## 6. Testing

- Write unit tests for all functions and methods
- Use the `unittest` framework
- Aim for high test coverage, especially for critical functionality
- Include tests for both single-project and multi-project scenarios

## 7. Version Control

- Use meaningful commit messages that clearly describe the changes made
- Create feature branches for new features or significant changes
- Submit pull requests for code review before merging into the main branch

## 8. Type Hinting

- Use type hints for function parameters and return values
- Use the `typing` module for complex types (e.g., `List`, `Dict`, `Optional`)
- Include type hints in function signatures and docstrings

## 9. Project-Specific Conventions

### 9.1 RAS Instance Handling
- Design functions to accept an optional `ras_object` parameter:
  ```python
  def some_function(param1, param2, ras_object=None):
      ras_obj = ras_object or ras
      ras_obj.check_initialized()
      # Function implementation
  ```

### 9.2 File Path Handling
- Use `pathlib.Path` for file and directory path manipulations
- Convert string paths to Path objects at the beginning of functions

### 9.3 DataFrame Handling
- Use pandas for data manipulation and storage where appropriate
- Prefer method chaining for pandas operations to improve readability

### 9.4 Parallel Execution
- Follow the guidelines in the "Benchmarking is All You Need" blog post for optimal core usage in parallel plan execution

### 9.5 Function Return Values
- Prefer returning meaningful values over modifying global state
- Use tuple returns for multiple values instead of modifying input parameters

Remember, consistency is key. When in doubt, prioritize readability and clarity in your code. Always consider the maintainability and extensibility of the codebase when making design decisions.

## 10. Inheritance

### 10.1 General Principles

- Prioritize composition over inheritance when appropriate.
- Design base classes for extension.
- Clearly document the public API and subclass API using docstrings.

### 10.2 Naming Conventions

- Public API: No leading underscores.
- Subclass API: Single leading underscore (e.g., `_prepare_for_execution`).
- Internal attributes and methods: Single leading underscore.
- Name mangling (double leading underscores): Use sparingly and document the decision clearly.

### 10.3 Template Method Pattern

Consider using the template method pattern in base classes to define a high-level algorithm structure. Subclasses can then override specific steps to customize behavior.

### 10.4 Dataframe Access Control

Use properties to control access and modification of dataframes, providing a controlled interface for subclasses.
==================================================

File: c:\GH\ras_commander\updated_pyproject.toml
==================================================
[build-system]
requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2"]
build-backend = "setuptools.build_meta"

[project]
name = "ras_commander"
dynamic = ["version"]
description = "A library for automating HEC-RAS operations using python functions."
authors = [{name = "William Katzenmeyer, P.E., C.F.M.", email = "heccommander@gmail.com"}]
readme = "README.md"
requires-python = ">=3.9"
classifiers = [
    "Programming Language :: Python :: 3",
    "License :: OSI Approved :: MIT License",
    "Operating System :: OS Independent",
]

[project.urls]
Homepage = "https://github.com/yourusername/ras_commander"

[tool.setuptools_scm]
write_to = "ras_commander/_version.py"

[tool.setuptools]
packages = ["ras_commander"]

==================================================

File: c:\GH\ras_commander\.gitignore\.gitignore
==================================================
# Ignore the example_projects folder and all its subfolders
example_projects/

# Ignore the Example_Projects_6_5.zip file
Example_Projects_6_5.zip

# Ignore the misc folder and all its subfolders
misc/

==================================================

File: c:\GH\ras_commander\ai_tools\llmsummarize 2. docs and code only.py
==================================================
from pathlib import Path

# Get the name of this script
this_script = Path(__file__).name
print(f"Script name: {this_script}")

# Define the subfolder to summarize
summarize_subfolder = Path(__file__).parent.parent
print(f"Subfolder to summarize: {summarize_subfolder}")

# Define the output file name based on the folder name
output_file_name = f"{summarize_subfolder.name}_code_only.txt"
output_file_path = Path(__file__).parent / "llm_summary" / output_file_name
print(f"Output file path: {output_file_path}")

# Ensure the output directory exists
output_file_path.parent.mkdir(parents=True, exist_ok=True)
print(f"Output directory ensured to exist: {output_file_path.parent}")

# Define folders to omit
omit_folders = ["Bald Eagle Creek", "__pycache__", ".git", ".github", "tests", "build", "dist", "ras_commander.egg-info", "venv", "example_projects", "llm_summary", "misc", "future", ".github"]
print(f"Folders to omit: {omit_folders}")

# Define files or extensions to omit
omit_files = [".pyc", ".pyo", ".pyd", ".dll", ".so", ".dylib", ".exe", ".bat", ".sh", ".log", ".tmp", ".bak", ".swp", ".DS_Store", "Thumbs.db", "example_projects.zip", "11_accessing_example_projects.ipynb", "Example_Projects_6_5.zip"]
print(f"Files or extensions to omit: {omit_files}")

# Open the output file
with open(output_file_path, 'w', encoding='utf-8') as outfile:
    print(f"Opened output file: {output_file_path}")
    # Iterate over all files and subfolders in the summarize_subfolder directory
    for filepath in summarize_subfolder.rglob('*'):
        # Check if the file is not this script, not in the omit_folders, and not in omit_files
        if (filepath.name != this_script and 
            not any(omit_folder in filepath.parts for omit_folder in omit_folders) and
            not any(filepath.suffix == ext or filepath.name == ext for ext in omit_files)):
            # Write the filename or folder name
            if filepath.is_file():
                outfile.write(f"File: {filepath}\n")
                print(f"Writing file: {filepath}")
            else:
                outfile.write(f"Folder: {filepath}\n")
                print(f"Writing folder: {filepath}")
            outfile.write("="*50 + "\n")  # Separator
            
            # If it's a file, open and read the contents of the file
            if filepath.is_file():
                try:
                    with open(filepath, 'r', encoding='utf-8') as infile:
                        content = infile.read()
                        print(f"Reading content of file: {filepath}")
                except UnicodeDecodeError:
                    with open(filepath, 'rb') as infile:
                        content = infile.read()
                        content = content.decode('utf-8', errors='ignore')
                        print(f"Reading and converting content of file: {filepath}")
                
                # Write the contents to the output file
                outfile.write(content)
                print(f"Written content of file: {filepath}")
            
            # Write a separator after the file contents or folder name
            outfile.write("\n" + "="*50 + "\n\n")
            print(f"Written separator for: {filepath}")

print(f"All files and folders have been combined into '{output_file_path}'")
==================================================

File: c:\GH\ras_commander\ai_tools\llmsummarize 3. documentation and docstrings.py
==================================================
from pathlib import Path
import ast

# Get the name of this script
this_script = Path(__file__).name
print(f"Script name: {this_script}")

# Define the subfolder to summarize
summarize_subfolder = Path(__file__).parent.parent
print(f"Subfolder to summarize: {summarize_subfolder}")

# Define the output file name based on the folder name
output_file_name = f"{summarize_subfolder.name}_code_structure.txt"
output_file_path = Path(__file__).parent / "llm_summary" / output_file_name
print(f"Output file path: {output_file_path}")

# Ensure the output directory exists
output_file_path.parent.mkdir(parents=True, exist_ok=True)
print(f"Output directory ensured to exist: {output_file_path.parent}")

# Define folders to omit
omit_folders = ["Bald Eagle Creek", "__pycache__", ".git", ".github", "tests", "build", "dist", "ras_commander.egg-info", "venv", "example_projects", "llm_summary", "misc", "future", ".github"]
print(f"Folders to omit: {omit_folders}")

# Define files or extensions to omit
omit_files = [".pyc", ".pyo", ".pyd", ".dll", ".so", ".dylib", ".exe", ".bat", ".sh", ".log", ".tmp", ".bak", ".swp", ".DS_Store", "Thumbs.db", "example_projects.zip", "11_accessing_example_projects.ipynb", "Example_Projects_6_5.zip"]
print(f"Files or extensions to omit: {omit_files}")

def extract_function_structure(node):
    if isinstance(node, ast.FunctionDef):
        docstring = ast.get_docstring(node)
        if docstring:
            return f"def {node.name}({', '.join(arg.arg for arg in node.args.args)}):\n    \"\"\"{docstring}\"\"\"\n"
        else:
            return f"def {node.name}({', '.join(arg.arg for arg in node.args.args)}):\n    pass\n"
    return ""

def process_file(file_path):
    with open(file_path, 'r', encoding='utf-8') as file:
        content = file.read()
    
    tree = ast.parse(content)
    function_structures = []
    for node in ast.walk(tree):
        function_structures.append(extract_function_structure(node))
    
    return ''.join(function_structures)

# Open the output file
with open(output_file_path, 'w', encoding='utf-8') as outfile:
    print(f"Opened output file: {output_file_path}")
    # Iterate over all files and subfolders in the summarize_subfolder directory
    for filepath in summarize_subfolder.rglob('*'):
        # Check if the file is not this script, not in the omit_folders, and not in omit_files
        if (filepath.name != this_script and 
            not any(omit_folder in filepath.parts for omit_folder in omit_folders) and
            not any(filepath.suffix == ext or filepath.name == ext for ext in omit_files)):
            # Write the filename or folder name
            if filepath.is_file():
                outfile.write(f"File: {filepath}\n")
                print(f"Writing file: {filepath}")
            else:
                outfile.write(f"Folder: {filepath}\n")
                print(f"Writing folder: {filepath}")
            outfile.write("="*50 + "\n")  # Separator
            
            # If it's a file, process and write the function structures
            if filepath.is_file() and filepath.suffix == '.py':
                try:
                    function_structures = process_file(filepath)
                    outfile.write(function_structures)
                    print(f"Written function structures of file: {filepath}")
                except Exception as e:
                    print(f"Error processing file {filepath}: {str(e)}")
            
            # Write a separator after the file contents or folder name
            outfile.write("\n" + "="*50 + "\n\n")
            print(f"Written separator for: {filepath}")

print(f"All files and folders have been processed into '{output_file_path}'")
==================================================

File: c:\GH\ras_commander\ai_tools\rascommander_code_assistant.ipynb
==================================================
{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# RAS-Commander Code Assistant\n",
    "\n",
    "Alpha, this only works with Claude 3.5 Sonnet for now\n",
    "\n",
    "Future devlopement will include multi-turn support and ability to select between different models\n",
    "\n",
    "Provide your own API key to make this work.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# User query\n",
    "user_query = \"\"\"   Make a table for each class file in the library with all functions, their arguments (with typing/expected input), and a short summary of the function's purpose.\n",
    "\n",
    "\"\"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define files, folders and extensions to omit\n",
    "omit_folders = [\n",
    "    \"Bald Eagle Creek\", \n",
    "    \"__pycache__\", \n",
    "    \".git\", \n",
    "    \".github\", \n",
    "    \"tests\", \n",
    "    \"build\", \n",
    "    \"dist\", \n",
    "    \"ras_commander.egg-info\", \n",
    "    \"venv\", \n",
    "    \"example_projects\", \n",
    "    \"llm_summary\", \"misc\", \"future\", \"ai_tools\"\n",
    "]\n",
    "\n",
    "# Define file extensions to omit\n",
    "omit_extensions = [\n",
    "    # Common image file extensions\n",
    "    '.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp', '.svg', '.ico',\n",
    "    # Other binary file extensions\n",
    "    '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',\n",
    "    '.zip', '.rar', '.7z', '.tar', '.gz',\n",
    "    '.exe', '.dll', '.so', '.dylib',\n",
    "    '.pyc', '.pyo', '.pyd',  # Python bytecode and compiled files\n",
    "    '.class',  # Java bytecode\n",
    "    '.log', '.tmp', '.bak', '.swp',  # Temporary and backup files\n",
    "    '.bat', '.sh',  # Script files\n",
    "]\n",
    "\n",
    "# Define files to omit based on keywords\n",
    "omit_files = [\n",
    "    'FunctionList.md',\n",
    "    'DS_Store',\n",
    "    'Thumbs.db',\n",
    "    'llmsummarize'\n",
    "    'example_projects.zip',\n",
    "    '11_accessing_example_projects.ipynb',\n",
    "    'Example_Projects_6_5.zip'\n",
    "    'github_code_assistant.ipynb',\n",
    "    'example_projects.ipynb',\n",
    "    '11_Using_RasExamples.ipynb',\n",
    "    'example_projects.csv',\n",
    "    'rascommander_code_assistant.ipynb',\n",
    "    'RasExamples.py'\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Successfully imported pandas\n",
      "Successfully imported anthropic\n",
      "Successfully imported tiktoken\n",
      "Successfully imported IPython.display\n",
      "Successfully imported astor\n",
      "All required packages have been installed and imported successfully.\n"
     ]
    }
   ],
   "source": [
    "# Install necessary packages\n",
    "def install_and_import(package_name, import_name=None):\n",
    "    import subprocess\n",
    "    import sys\n",
    "    if import_name is None:\n",
    "        import_name = package_name\n",
    "    try:\n",
    "        __import__(import_name)\n",
    "    except ImportError:\n",
    "        subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", package_name])\n",
    "        __import__(import_name)\n",
    "    print(f\"Successfully imported {import_name}\")\n",
    "\n",
    "install_and_import(\"pandas\")\n",
    "install_and_import(\"anthropic\")\n",
    "install_and_import(\"tiktoken\")\n",
    "install_and_import(\"IPython\", \"IPython.display\")\n",
    "install_and_import(\"astor\")\n",
    "\n",
    "import os\n",
    "from pathlib import Path\n",
    "import pandas as pd\n",
    "import anthropic\n",
    "import tiktoken\n",
    "import astor\n",
    "from IPython.display import display, clear_output\n",
    "\n",
    "print(\"All required packages have been installed and imported successfully.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Set API Key\n",
    "#os.environ[\"ANTHROPIC_API_KEY\"] = 'YOUR KEY HERE'\n",
    "\n",
    "\n",
    "# Alternately, you can set the key from the file Anthropic_API_Key.txt\n",
    "with open('C:\\SCRATCH\\Anthropic_API_Key.txt', 'r') as file:\n",
    "    os.environ[\"ANTHROPIC_API_KEY\"] = file.read().strip()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "System message loaded successfully.\n"
     ]
    }
   ],
   "source": [
    "# Define system message from the ras_commander .cursorrules file\n",
    "from pathlib import Path\n",
    "\n",
    "def read_system_message():\n",
    "    # Get the current notebook's directory\n",
    "    current_dir = Path.cwd()\n",
    "    \n",
    "    # Path to the .cursorrules file (assuming it's in the parent directory)\n",
    "    cursor_rules_path = current_dir.parent / '.cursorrules'\n",
    "\n",
    "    # Check if .cursorrules exists\n",
    "    if not cursor_rules_path.exists():\n",
    "        raise FileNotFoundError(\"This notebook expects to be in a directory within the ras_commander repo which has a .cursorrules file in its parent directory.\")\n",
    "\n",
    "    # Read the .cursorrules file as plain text\n",
    "    with open(cursor_rules_path, 'r') as f:\n",
    "        system_message = f.read().strip()\n",
    "\n",
    "    if not system_message:\n",
    "        raise ValueError(\"No system message found in .cursorrules file.\")\n",
    "\n",
    "    return system_message\n",
    "\n",
    "# Read the system message from .cursorrules\n",
    "system_message = read_system_message()\n",
    "\n",
    "print(\"System message loaded successfully.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Context folder set to: c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\n"
     ]
    }
   ],
   "source": [
    "# Define folder as the parent folder (since this notebook lives in the ai_tools folder)\n",
    "# Get the current notebook's directory\n",
    "current_dir = Path.cwd()\n",
    "\n",
    "# Set the context folder to the parent of the current directory\n",
    "context_folder = current_dir.parent\n",
    "\n",
    "print(f\"Context folder set to: {context_folder}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Notebook name: ai_tools\n",
      "Subfolder to summarize: c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\n",
      "Output file path: c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\llm_summary\\ras_commander workspace7_code_only.txt\n",
      "Output directory ensured to exist: c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\llm_summary\n",
      "Opened output file: c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\llm_summary\\ras_commander workspace7_code_only.txt\n",
      "All files and folders have been combined into 'c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\llm_summary\\ras_commander workspace7_code_only.txt'\n",
      "Notebook name: ai_tools\n",
      "Subfolder to summarize: c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\n",
      "Output file path: c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\llm_summary\\ras_commander workspace7_code_only_stripped.txt\n",
      "Output directory ensured to exist: c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\llm_summary\n",
      "Opened output file: c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\llm_summary\\ras_commander workspace7_code_only_stripped.txt\n",
      "All files and folders have been combined into 'c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\llm_summary\\ras_commander workspace7_code_only_stripped.txt'\n"
     ]
    }
   ],
   "source": [
    "# Function to compile codebase, omitting specified folders, extensions, and files\n",
    "\n",
    "import tiktoken\n",
    "from pathlib import Path\n",
    "\n",
    "def strip_code_from_functions(content):\n",
    "    \"\"\"\n",
    "    Strip the code from functions, leaving only function signatures and docstrings.\n",
    "    \n",
    "    Args:\n",
    "    content (str): The content of a Python file.\n",
    "    \n",
    "    Returns:\n",
    "    str: The content with function bodies removed.\n",
    "    \"\"\"\n",
    "    import ast\n",
    "    import astor\n",
    "\n",
    "    class FunctionStripper(ast.NodeTransformer):\n",
    "        def visit_FunctionDef(self, node):\n",
    "            # Keep the function signature and docstring (if present)\n",
    "            new_node = ast.FunctionDef(\n",
    "                name=node.name,\n",
    "                args=node.args,\n",
    "                body=[ast.Pass()],  # Replace the body with a pass statement\n",
    "                decorator_list=node.decorator_list,\n",
    "                returns=node.returns\n",
    "            )\n",
    "            # If there's a docstring, keep it\n",
    "            if (len(node.body) > 0 and isinstance(node.body[0], ast.Expr) and\n",
    "                isinstance(node.body[0].value, ast.Str)):\n",
    "                new_node.body = [node.body[0], ast.Pass()]\n",
    "            return new_node\n",
    "\n",
    "    try:\n",
    "        tree = ast.parse(content)\n",
    "        stripped_tree = FunctionStripper().visit(tree)\n",
    "        return astor.to_source(stripped_tree)\n",
    "    except SyntaxError:\n",
    "        # If parsing fails, return the original content\n",
    "        return content\n",
    "\n",
    "def combine_files(summarize_subfolder, omit_folders, omit_extensions, omit_files, strip_code=False):\n",
    "    combined_text = \"\"\n",
    "    file_token_counts = {}\n",
    "    \n",
    "    # Get the name of this notebook\n",
    "    this_notebook = Path.cwd().name\n",
    "    print(f\"Notebook name: {this_notebook}\")\n",
    "\n",
    "    # Ensure summarize_subfolder is a Path object\n",
    "    summarize_subfolder = Path(summarize_subfolder)\n",
    "    print(f\"Subfolder to summarize: {summarize_subfolder}\")\n",
    "\n",
    "    # Define the output file name based on the folder name\n",
    "    output_file_name = f\"{summarize_subfolder.name}_code_only{'_stripped' if strip_code else ''}.txt\"\n",
    "    output_file_path = Path.cwd().parent / \"llm_summary\" / output_file_name\n",
    "    print(f\"Output file path: {output_file_path}\")\n",
    "\n",
    "    # Ensure the output directory exists\n",
    "    output_file_path.parent.mkdir(parents=True, exist_ok=True)\n",
    "    print(f\"Output directory ensured to exist: {output_file_path.parent}\")\n",
    "\n",
    "    # Initialize tokenizer\n",
    "    enc = tiktoken.encoding_for_model(\"gpt-3.5-turbo\")\n",
    "\n",
    "    # Open the output file\n",
    "    with open(output_file_path, 'w', encoding='utf-8') as outfile:\n",
    "        print(f\"Opened output file: {output_file_path}\")\n",
    "        # Iterate over all files and subfolders in the summarize_subfolder directory\n",
    "        for filepath in summarize_subfolder.rglob('*'):\n",
    "            # Check if the file is not this notebook, not in the omit_folders, not in omit_extensions, and not in omit_files\n",
    "            if (filepath.name != this_notebook and \n",
    "                not any(omit_folder in filepath.parts for omit_folder in omit_folders) and\n",
    "                filepath.suffix.lower() not in omit_extensions and\n",
    "                not any(omit_file in filepath.name for omit_file in omit_files)):\n",
    "                # Write the filename or folder name\n",
    "                if filepath.is_file():\n",
    "                    outfile.write(f\"File: {filepath}\\n\")\n",
    "                else:\n",
    "                    outfile.write(f\"Folder: {filepath}\\n\")\n",
    "                outfile.write(\"=\"*50 + \"\\n\")  # Separator\n",
    "                \n",
    "                # If it's a file, open and read the contents of the file\n",
    "                if filepath.is_file():\n",
    "                    try:\n",
    "                        with open(filepath, 'r', encoding='utf-8') as infile:\n",
    "                            content = infile.read()\n",
    "                    except UnicodeDecodeError:\n",
    "                        with open(filepath, 'rb') as infile:\n",
    "                            content = infile.read()\n",
    "                            content = content.decode('utf-8', errors='ignore')\n",
    "                    \n",
    "                    # Strip code if the option is enabled and it's a Python file\n",
    "                    if strip_code and filepath.suffix.lower() == '.py':\n",
    "                        content = strip_code_from_functions(content)\n",
    "                    \n",
    "                    # Write the contents to the output file\n",
    "                    outfile.write(content)\n",
    "                    \n",
    "                    # Count tokens for this file\n",
    "                    file_tokens = len(enc.encode(content))\n",
    "                    file_token_counts[str(filepath)] = file_tokens\n",
    "                \n",
    "                # Write a separator after the file contents or folder name\n",
    "                outfile.write(\"\\n\" + \"=\"*50 + \"\\n\\n\")\n",
    "            else:\n",
    "                dummy = 0\n",
    "\n",
    "    print(f\"All files and folders have been combined into '{output_file_path}'\")\n",
    "\n",
    "    # Count total tokens\n",
    "    with open(output_file_path, 'r', encoding='utf-8') as f:\n",
    "        combined_text = f.read()\n",
    "    token_count = len(enc.encode(combined_text))\n",
    "    \n",
    "    return combined_text, token_count, file_token_counts\n",
    "\n",
    "\n",
    "# Combine files while keeping code\n",
    "combined_text, token_count, file_token_counts = combine_files(context_folder, omit_folders, omit_extensions, omit_files)\n",
    "\n",
    "# Combine files while stripping code\n",
    "combined_text, token_count, file_token_counts = combine_files(context_folder, omit_folders, omit_extensions, omit_files, strip_code=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Top 20 files by token count:\n",
      "c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\ras_commander\\RasPlan.py: 3796 tokens\n",
      "c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\README.md: 3099 tokens\n",
      "c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\Comprehensive_Library_Guide.md: 2391 tokens\n",
      "c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\ras_commander\\RasPrj.py: 1711 tokens\n",
      "c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\ras_commander\\RasUtils.py: 1507 tokens\n",
      "c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\STYLE_GUIDE.md: 1461 tokens\n",
      "c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\ras_commander\\RasCommander.py: 1392 tokens\n",
      "c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\ras_commander\\README.md: 1250 tokens\n",
      "c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\.cursorrules: 1107 tokens\n",
      "c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\examples\\02_plan_operations.py: 484 tokens\n",
      "c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\ras_commander\\RasGeo.py: 387 tokens\n",
      "c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\ras_commander\\__init__.py: 241 tokens\n",
      "c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\examples\\07_sequential_plan_execution.py: 239 tokens\n",
      "c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\examples\\09_specifying_plans.py: 239 tokens\n",
      "c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\updated_pyproject.toml: 198 tokens\n",
      "c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\ras_commander\\RasUnsteady.py: 196 tokens\n",
      "c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\examples\\13_multiple_project_operations.py: 194 tokens\n",
      "c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\examples\\12_plan_set_execution.py: 185 tokens\n",
      "c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\examples\\01_project_initialization.py: 180 tokens\n",
      "c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace7\\examples\\08_parallel_execution.py: 166 tokens\n"
     ]
    }
   ],
   "source": [
    "# Sort files by token count and get top 20\n",
    "top_20_files = sorted(file_token_counts.items(), key=lambda x: x[1], reverse=True)[:20]\n",
    "\n",
    "print(\"\\nTop 20 files by token count:\")\n",
    "for file, count in top_20_files:\n",
    "    print(f\"{file}: {count} tokens\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Combined text token count: 22963\n"
     ]
    }
   ],
   "source": [
    "# Check the total token count\n",
    "print(f\"Combined text token count: {token_count}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Pricing DataFrame:\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Model</th>\n",
       "      <th>Input ($/MTok)</th>\n",
       "      <th>Output ($/MTok)</th>\n",
       "      <th>Prompt Caching Write ($/MTok)</th>\n",
       "      <th>Prompt Caching Read ($/MTok)</th>\n",
       "      <th>Context Window</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>Claude 3.5 Sonnet</td>\n",
       "      <td>3</td>\n",
       "      <td>15</td>\n",
       "      <td>3.75</td>\n",
       "      <td>0.3</td>\n",
       "      <td>200000</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "               Model  Input ($/MTok)  Output ($/MTok)  \\\n",
       "0  Claude 3.5 Sonnet               3               15   \n",
       "\n",
       "   Prompt Caching Write ($/MTok)  Prompt Caching Read ($/MTok)  Context Window  \n",
       "0                           3.75                           0.3          200000  "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Estimated cost: $0.1919\n"
     ]
    }
   ],
   "source": [
    "# Set up Anthropic client\n",
    "def stream_response(client, full_prompt, max_tokens=4096):\n",
    "    response_text = \"\"\n",
    "    with client.messages.stream(\n",
    "        max_tokens=max_tokens,\n",
    "        messages=[\n",
    "            {\"role\": \"user\", \"content\": full_prompt}\n",
    "        ],\n",
    "        model=\"claude-3-sonnet-20240229\"\n",
    "    ) as stream:\n",
    "        for text in stream.text_stream:\n",
    "            response_text += text\n",
    "            clear_output(wait=True)\n",
    "            print(\"Claude's response:\")\n",
    "            print(response_text)\n",
    "            \n",
    "    return response_text\n",
    "\n",
    "def estimate_cost(input_tokens, output_tokens, pricing_df):\n",
    "    model = \"Claude 3.5 Sonnet\"\n",
    "    input_cost = (input_tokens / 1e6) * pricing_df.loc[pricing_df['Model'] == model, 'Input ($/MTok)'].values[0]\n",
    "    output_cost = (output_tokens / 1e6) * pricing_df.loc[pricing_df['Model'] == model, 'Output ($/MTok)'].values[0]\n",
    "    return input_cost + output_cost\n",
    "\n",
    "\n",
    "# Set up Anthropic client\n",
    "client = anthropic.Anthropic(api_key=os.environ.get(\"ANTHROPIC_API_KEY\"))\n",
    "\n",
    "# Create pricing dataframe\n",
    "pricing_data = {\n",
    "    \"Model\": [\"Claude 3.5 Sonnet\"],\n",
    "    \"Input ($/MTok)\": [3],\n",
    "    \"Output ($/MTok)\": [15],\n",
    "    \"Prompt Caching Write ($/MTok)\": [3.75],\n",
    "    \"Prompt Caching Read ($/MTok)\": [0.30],\n",
    "    \"Context Window\": [200000]\n",
    "}\n",
    "\n",
    "pricing_df = pd.DataFrame(pricing_data)\n",
    "print(\"Pricing DataFrame:\")\n",
    "display(pricing_df)\n",
    "\n",
    "# Combine system message, context, and user query\n",
    "full_prompt = f\"{system_message}\\n\\nContext:\\n{combined_text}\\n\\n\\n\\n\\nUser Query: {user_query}\"\n",
    "\n",
    "# Estimate cost\n",
    "enc = tiktoken.encoding_for_model(\"gpt-3.5-turbo\")\n",
    "input_tokens = token_count + len(enc.encode(user_query))\n",
    "output_tokens = 8192 # assuming full response\n",
    "estimated_cost = estimate_cost(input_tokens, output_tokens, pricing_df)\n",
    "\n",
    "print(f\"\\nEstimated cost: ${estimated_cost:.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### See the cost per message above, and add additional file/folder filters if desired"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "ename": "ValueError",
     "evalue": "This is a test error to prevent automatically querying model and incurring costs",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "Cell \u001b[1;32mIn[11], line 2\u001b[0m\n\u001b[0;32m      1\u001b[0m \u001b[38;5;66;03m# raise error to prevent automatically querying model and incurring costs\u001b[39;00m\n\u001b[1;32m----> 2\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mThis is a test error to prevent automatically querying model and incurring costs\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n",
      "\u001b[1;31mValueError\u001b[0m: This is a test error to prevent automatically querying model and incurring costs"
     ]
    }
   ],
   "source": [
    "# raise error to prevent automatically querying model and incurring costs\n",
    "raise ValueError(\"This is a test error to prevent automatically querying model and incurring costs\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Claude's response:\n",
      "Sure, here's a table for each class file in the ras_commander library, with all functions, their arguments (with typing/expected input), and a short summary of the function's purpose.\n",
      "\n",
      "1. `RasCommander.py`\n",
      "\n",
      "| Function | Arguments | Summary |\n",
      "|----------|-----------|---------|\n",
      "| `compute_plan` | `plan_number: str`, `compute_folder: Optional[Union[str, Path]] = None`, `ras_object: Optional[RasPrj] = None` | Execute a HEC-RAS plan. |\n",
      "| `compute_test_mode` | `plan_numbers: Optional[List[str]] = None`, `folder_suffix: str = '[Test]'`, `clear_geompre: bool = False`, `max_cores: Optional[int] = None`, `ras_object: Optional[RasPrj] = None` | Execute HEC-RAS plans in test mode. |\n",
      "| `compute_parallel` | `plan_numbers: Optional[List[str]] = None`, `max_workers: int = 2`, `cores_per_run: int = 2`, `ras_object: Optional[RasPrj] = None`, `dest_folder: Optional[Union[str, Path]] = None` | Execute HEC-RAS plans in parallel using multiple worker threads. |\n",
      "| `worker_thread` | `worker_id: int` | A worker thread function for parallel execution. |\n",
      "\n",
      "2. `RasGeo.py`\n",
      "\n",
      "| Function | Arguments | Summary |\n",
      "|----------|-----------|---------|\n",
      "| `clear_geompre_files` | `plan_files: Optional[Union[str, Path, List[Union[str, Path]]]] = None`, `ras_object: Optional[RasPrj] = None` | Clear HEC-RAS geometry preprocessor files. |\n",
      "\n",
      "3. `RasPlan.py`\n",
      "\n",
      "| Function | Arguments | Summary |\n",
      "|----------|-----------|---------|\n",
      "| `set_geom` | `plan_number: Union[str, int]`, `new_geom: Union[str, int]`, `ras_object: Optional[RasPrj] = None` | Set the geometry for the specified plan. |\n",
      "| `set_steady` | `plan_number: str`, `new_steady_flow_number: str`, `ras_object: Optional[RasPrj] = None` | Apply a steady flow file to a plan file. |\n",
      "| `set_unsteady` | `plan_number: str`, `new_unsteady_flow_number: str`, `ras_object: Optional[RasPrj] = None` | Apply an unsteady flow file to a plan file. |\n",
      "| `set_num_cores` | `plan_number: Union[str, Path]`, `num_cores: int`, `ras_object: Optional[RasPrj] = None` | Update the maximum number of cores to use in the HEC-RAS plan file. |\n",
      "| `set_geom_preprocessor` | `file_path: Union[str, Path]`, `run_htab: int`, `use_ib_tables: int`, `ras_object: Optional[RasPrj] = None` | Update the simulation plan file to modify the 'Run HTab' and 'UNET Use Existing IB Tables' settings. |\n",
      "| `get_results_path` | `plan_number: str`, `ras_object: Optional[RasPrj] = None` | Retrieve the results file path for a given HEC-RAS plan number. |\n",
      "| `get_plan_path` | `plan_number: str`, `ras_object: Optional[RasPrj] = None` | Return the full path for a given plan number. |\n",
      "| `get_flow_path` | `flow_number: str`, `ras_object: Optional[RasPrj] = None` | Return the full path for a given flow number. |\n",
      "| `get_unsteady_path` | `unsteady_number: str`, `ras_object: Optional[RasPrj] = None` | Return the full path for a given unsteady number. |\n",
      "| `get_geom_path` | `geom_number: str`, `ras_object: Optional[RasPrj] = None` | Return the full path for a given geometry number. |\n",
      "| `clone_plan` | `template_plan: str`, `new_plan_shortid: Optional[str] = None`, `ras_object: Optional[RasPrj] = None` | Create a new plan file based on a template and update the project file. |\n",
      "| `clone_unsteady` | `template_unsteady: str`, `ras_object: Optional[RasPrj] = None` | Copy unsteady flow files from a template, find the next unsteady number, and update the project file accordingly. |\n",
      "| `clone_steady` | `template_flow: str`, `ras_object: Optional[RasPrj] = None` | Copy steady flow files from a template, find the next flow number, and update the project file accordingly. |\n",
      "| `clone_geom` | `template_geom: str`, `ras_object: Optional[RasPrj] = None` | Copy geometry files from a template, find the next geometry number, and update the project file accordingly. |\n",
      "| `get_next_number` | `existing_numbers: List[str]` | Determine the next available number from a list of existing numbers. |\n",
      "\n",
      "4. `RasPrj.py`\n",
      "\n",
      "| Function | Arguments | Summary |\n",
      "|----------|-----------|---------|\n",
      "| `__init__` | - | Initialize a RasPrj instance. |\n",
      "| `initialize` | `project_folder: Union[str, Path]`, `ras_exe_path: Union[str, Path]` | Initialize a RasPrj instance with the given project folder and RAS executable path. |\n",
      "| `_load_project_data` | - | Load project data from the HEC-RAS project file. |\n",
      "| `_get_prj_entries` | `entry_type: str` | Extract entries of a specific type from the HEC-RAS project file. |\n",
      "| `is_initialized` | - | Check if the RasPrj instance has been initialized. |\n",
      "| `check_initialized` | - | Ensure that the RasPrj instance has been initialized. |\n",
      "| `find_ras_prj` | `folder_path: Union[str, Path]` | Find the appropriate HEC-RAS project file (.prj) in the given folder. |\n",
      "| `get_project_name` | - | Get the name of the HEC-RAS project. |\n",
      "| `get_prj_entries` | `entry_type: str` | Get entries of a specific type from the HEC-RAS project. |\n",
      "| `get_plan_entries` | - | Get all plan entries from the HEC-RAS project. |\n",
      "| `get_flow_entries` | - | Get all flow entries from the HEC-RAS project. |\n",
      "| `get_unsteady_entries` | - | Get all unsteady flow entries from the HEC-RAS project. |\n",
      "| `get_geom_entries` | - | Get all geometry entries from the HEC-RAS project. |\n",
      "| `get_hdf_entries` | - | Get HDF entries for plans that have results. |\n",
      "| `print_data` | - | Print all RAS Object data for this instance. |\n",
      "| `init_ras_project` | `ras_project_folder: str`, `ras_version: str`, `ras_instance: Optional[RasPrj] = None` | Initialize a RAS project. |\n",
      "| `get_ras_exe` | `ras_version: str` | Determine the HEC-RAS executable path based on the input. |\n",
      "\n",
      "5. `RasUnsteady.py`\n",
      "\n",
      "| Function | Arguments | Summary |\n",
      "|----------|-----------|---------|\n",
      "| `update_unsteady_parameters` | `unsteady_file: Union[str, Path]`, `modifications: Dict[str, Any]`, `ras_object: Optional[RasPrj] = None` | Modify parameters in an unsteady flow file. |\n",
      "\n",
      "6. `RasUtils.py`\n",
      "\n",
      "| Function | Arguments | Summary |\n",
      "|----------|-----------|---------|\n",
      "| `create_backup` | `file_path: Path`, `backup_suffix: str = '_backup'`, `ras_object: Optional[RasPrj] = None` | Create a backup of the specified file. |\n",
      "| `restore_from_backup` | `backup_path: Path`, `remove_backup: bool = True`, `ras_object: Optional[RasPrj] = None` | Restore a file from its backup. |\n",
      "| `create_directory` | `directory_path: Path`, `ras_object: Optional[RasPrj] = None` | Ensure that a directory exists, creating it if necessary. |\n",
      "| `find_files_by_extension` | `extension: str`, `ras_object: Optional[RasPrj] = None` | List all files in the project directory with a specific extension. |\n",
      "| `get_file_size` | `file_path: Path`, `ras_object: Optional[RasPrj] = None` | Get the size of a file in bytes. |\n",
      "| `get_file_modification_time` | `file_path: Path`, `ras_object: Optional[RasPrj] = None` | Get the last modification time of a file. |\n",
      "| `get_plan_path` | `current_plan_number_or_path: Union[str, Path]`, `ras_object: Optional[RasPrj] = None` | Get the path for a plan file with a given plan number or path. |\n",
      "| `remove_with_retry` | `path: Path`, `max_attempts: int = 5`, `initial_delay: float = 1.0`, `is_folder: bool = True`, `ras_object: Optional[RasPrj] = None` | Attempts to remove a file or folder with retry logic and exponential backoff. |\n",
      "| `update_plan_file` | `plan_number_or_path: Union[str, Path]`, `file_type: str`, `entry_number: int`, `ras_object: Optional[RasPrj] = None` | Update a plan file with a new file reference. |\n",
      "| `check_file_access` | `file_path: Union[str, Path]`, `mode: str = 'r'` | Check if a file can be accessed with the specified mode. |\n",
      "\n",
      "7. `RasExamples.py`\n",
      "\n",
      "| Function | Arguments | Summary |\n",
      "|----------|-----------|---------|\n",
      "| `__init__` | - | Initialize the RasExamples class. |\n",
      "| `get_example_projects` | `version_number: Optional[str] = None` | Get the available example projects for the specified version. |\n",
      "| `list_categories` | - | List the available categories of example projects. |\n",
      "| `list_projects` | `category: Optional[str] = None` | List the available example projects in a given category. |\n",
      "| `extract_project` | `project_names: Union[str, List[str]]` | Extract the specified example project(s) to the local file system. |\n",
      "| `clean_projects_directory` | - | Clean the directory where example projects are extracted. |\n",
      "\n",
      "Please note that these tables are based on the code provided, and the actual implementation details may vary.\n"
     ]
    }
   ],
   "source": [
    "# Stream Claude's response\n",
    "response_text = stream_response(client, full_prompt)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "-----"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Example Response"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Claude's response:\n",
    "Sure, I'll review the code and identify any duplicate or redundant sections that can be removed or consolidated to reduce the overall size of the library.\n",
    "\n",
    "1. `RasFolderInit.py`:\n",
    "   - The `get_plan_entries`, `get_flow_entries`, `get_unsteady_entries`, and `get_geom_entries` functions follow a similar pattern. They can be consolidated into a single function that takes a file type as an argument.\n",
    "   - The `find_ras_prj` function could potentially be merged with the `get_project_name` function, as they both operate on project file paths.\n",
    "\n",
    "2. `RasPlan.py`:\n",
    "   - The `get_plan_path`, `get_flow_path`, `get_unsteady_path`, and `get_geom_path` functions are very similar and could be combined into a single function that takes a file type and number as arguments.\n",
    "   - The `set_geom`, `set_flow`, and `set_unsteady` functions have a lot of overlapping code, particularly for updating plan files. These could be consolidated into a single function that takes the file type and number as arguments.\n",
    "\n",
    "3. `RasUtils.py`:\n",
    "   - The `create_backup` and `restore_from_backup` functions have some overlap in their logic and could potentially be combined into a single function with an additional argument to specify whether to create or restore a backup.\n",
    "   - The `remove_file` function could be merged with `remove_folder_with_retry` by adding an optional argument to handle file removal.\n",
    "\n",
    "4. `RasCommander.py`:\n",
    "   - The `compute_plan` and `compute_plan_from_folder` functions have a lot of overlapping code. These could be combined into a single function with an optional argument to specify the folder path.\n",
    "\n",
    "5. `RasPrj.py`:\n",
    "   - The `find_ras_prj` function is already present in `RasFolderInit.py`, so it could be removed from this module.\n",
    "   - The `load_project_data` function is not used anywhere else in the codebase, so it could be removed unless it has a planned future use.\n",
    "\n",
    "After consolidating these functions and removing any redundant code, the overall size of the library should be reduced. However, it's important to ensure that the functionality remains intact and that any changes are thoroughly tested.\n",
    "\n",
    "Estimated cost: $0.2435\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "freshcmdr_311",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}

==================================================

File: c:\GH\ras_commander\ai_tools\README.md
==================================================
# AI Tools and Scripts for the RAS Commander Repository

This folder contains AI tools and scripts for the RAS Commander repository.

==================================================

File: c:\GH\ras_commander\examples\01_project_initialization.py
==================================================
# 01_project_initialization.py

#### --- IMPORTS AND EXAMPLE PROJECT SETUP --- ####

import sys
from pathlib import Path

# Add the parent directory to the Python path
current_file = Path(__file__).resolve()
parent_directory = current_file.parent.parent
sys.path.append(str(parent_directory))

# Flexible imports to allow for development without installation
try:
    # Try to import from the installed package
    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras
except ImportError:
    # If the import fails, add the parent directory to the Python path
    current_file = Path(__file__).resolve()
    parent_directory = current_file.parent.parent
    sys.path.append(str(parent_directory))
    
    # Now try to import again
    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras

# Extract specific projects
ras_examples = RasExamples()
ras_examples.extract_project(["Balde Eagle Creek", "BaldEagleCrkMulti2D", "Muncie"])

#### --- START OF SCRIPT --- ####

# RAS-Commander Library Notes:
# 1. This example demonstrates both the default global 'ras' object and custom ras objects.
# 2. The global 'ras' object is suitable for simple scripts working with a single project.
# 3. Custom ras objects are recommended for complex scripts or when working with multiple projects.
# 4. The init_ras_project function initializes a project and sets up the ras object.
# 5. Each ras object contains information about its project, including plan, geometry, and flow files.

# Best Practices:
# 1. For simple scripts working with a single project, using the global 'ras' object is fine.
# 2. For complex scripts or when working with multiple projects, create and use separate ras objects.
# 3. Be consistent in your approach: don't mix global and non-global ras object usage in the same script.
# 4. Use descriptive names for custom ras objects to clearly identify different projects.

def main():
    # Get the current script's directory
    current_dir = Path(__file__).parent
    
    # Define paths to example projects
    bald_eagle_path = current_dir.parent / "examples" / "example_projects" / "Balde Eagle Creek"
    multi_2d_path = current_dir.parent / "examples" / "example_projects" / "BaldEagleCrkMulti2D"
    muncie_path = current_dir.parent / "examples" / "example_projects" / "Muncie"

    print("Example Set 1: Using the default global 'ras' object")
    print("-----------------------------------------------------")

    # Initialize using the global RAS instance
    print("Step 1: Initializing with global RAS instance")
    init_ras_project(bald_eagle_path, "6.5") # This will set the global 'ras' object
    ras.print_data()  # Using the class method

    # Demonstrate accessing specific data
    print("\nStep 2: Demonstrating accessing specific data")
    print("Global RAS instance (Bald Eagle Creek) first plan file:")
    print(ras.plan_df.iloc[0] if not ras.plan_df.empty else "No plan files")
    
    print("\nStep 3: Accessing All RAS Object Data")
    print(f"Project Name: {ras.get_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}")
    
    print("\nPlan Files DataFrame:")
    print(ras.plan_df)
    
    print("\nFlow Files DataFrame:")
    print(ras.flow_df)
    
    print("\nUnsteady Flow Files DataFrame:")
    print(ras.unsteady_df)
    
    print("\nGeometry Files DataFrame:")
    print(ras.geom_df)
    
    print("\nHDF Entries DataFrame:")
    print(ras.get_hdf_entries())

    print("\nExample Set 2: Using custom ras objects")
    print("-----------------------------------------------------")

    # Initialize multiple project instances
    print("Step 1: Initializing multiple project instances")
    multi_2d_project = init_ras_project(multi_2d_path, "6.5")
    muncie_project = init_ras_project(muncie_path, "6.5")

    print("\nMulti2D project data:")    
    multi_2d_project.print_data()
    print("\nMuncie project data:")
    muncie_project.print_data()

    # Demonstrate accessing specific data from custom ras objects
    print("\nStep 2: Accessing specific data from custom ras objects")
    print("Multi2D project first geometry file:")
    print(multi_2d_project.geom_df.iloc[0] if not multi_2d_project.geom_df.empty else "No geometry files")
    
    print("\nMuncie project first unsteady flow file:")
    print(muncie_project.unsteady_df.iloc[0] if not muncie_project.unsteady_df.empty else "No unsteady flow files")

    print("\nStep 3: Accessing All RAS Object Data for Multi2D Project")
    print(f"Project Name: {multi_2d_project.get_project_name()}")
    print(f"Project Folder: {multi_2d_project.project_folder}")
    print(f"PRJ File: {multi_2d_project.prj_file}")
    print(f"HEC-RAS Executable Path: {multi_2d_project.ras_exe_path}")
    
    print("\nPlan Files DataFrame:")
    print(multi_2d_project.plan_df)
    
    print("\nFlow Files DataFrame:")
    print(multi_2d_project.flow_df)
    
    print("\nUnsteady Flow Files DataFrame:")
    print(multi_2d_project.unsteady_df)
    
    print("\nGeometry Files DataFrame:")
    print(multi_2d_project.geom_df)
    
    print("\nHDF Entries DataFrame:")
    print(multi_2d_project.get_hdf_entries())

    print("\nExample of simplified import (not recommended for complex scripts)")
    print("-----------------------------------------------------")
    print("from ras_commander import *")
    print("# This allows you to use all functions and classes without prefixes")
    print("# For example: compute_plan() instead of RasCommander.compute_plan()")
    print("# Note: This approach can lead to naming conflicts and is generally not recommended for larger scripts")

if __name__ == "__main__":
    main()
==================================================

File: c:\GH\ras_commander\examples\02_plan_operations.py
==================================================
# 02_plan_operations.py

#### --- IMPORTS AND EXAMPLE PROJECT SETUP --- ####

import sys
from pathlib import Path

# Add the parent directory to the Python path
current_file = Path(__file__).resolve()
parent_directory = current_file.parent.parent
sys.path.append(str(parent_directory))

# Flexible imports to allow for development without installation
try:
    # Try to import from the installed package
    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras
except ImportError:
    # If the import fails, add the parent directory to the Python path
    current_file = Path(__file__).resolve()
    parent_directory = current_file.parent.parent
    sys.path.append(str(parent_directory))
    
    # Now try to import again
    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras

# Extract specific projects
ras_examples = RasExamples()
ras_examples.extract_project(["Balde Eagle Creek"])

#### --- START OF SCRIPT --- ####

"""
This script demonstrates the process of initializing a HEC-RAS project and performing various operations on plans, geometries, and unsteady flows using the functions within the RasPlan Class.

Process Flow:
1. Project Initialization: Initialize a HEC-RAS project by specifying the project path and version.
2. Plan Cloning: Clone an existing plan, creating a new plan entry.
3. Geometry Cloning: Clone a geometry associated with the original plan, generating a new geometry entry.
4. Unsteady Flow Cloning: Clone an unsteady flow, creating a new unsteady flow entry.
5. Plan Configuration:
   a. Set the cloned geometry for the new plan.
   b. Set the cloned unsteady flow for the new plan.
   c. Update the number of cores to be used for the new plan.
   d. Configure geometry preprocessor options for the new plan.
6. Plan Computation: Compute the new plan and verify successful execution.
7. Results Verification: Check the HDF entries to confirm that results were written.

Additional operations that could be demonstrated:
8. Plan Modification: Update specific parameters in the plan file (e.g., simulation time, output intervals).
9. Geometry Editing: Modify cross-sections, manning's n values, or other geometry data.
10. Unsteady Flow Modification: Adjust boundary conditions or initial conditions.
11. Batch Operations: Perform operations on multiple plans simultaneously.
12. Error Handling: Demonstrate how to handle and report errors during plan operations.
13. Results Analysis: Extract and analyze key output values from the computed plan.
"""

# RAS-Commander Library Notes:
# 1. This example uses the default global 'ras' object for simplicity.
# 2. If you need to work with multiple projects, use separate ras objects for each project.
# 3. Once you start using non-global ras objects, stick with that approach throughout your script.

# Best Practices:
# 1. For simple scripts working with a single project, using the global 'ras' object is fine.
# 2. For complex scripts or when working with multiple projects, create and use separate ras objects.
# 3. Be consistent in your approach: don't mix global and non-global ras object usage in the same script.

def main():
    # Initialize the project
    current_dir = Path(__file__).parent
    project_path = current_dir / "example_projects" / "Balde Eagle Creek"
    init_ras_project(project_path, "6.5")

    print("Initial plan files:")
    print(ras.plan_df)
    print()

    # Step 1: Clone a plan
    print("Step 1: Cloning a plan")
    new_plan_number = RasPlan.clone_plan("01")
    print(f"New plan created: {new_plan_number}")
    print("Updated plan files:")
    print(ras.plan_df)
    print()
    
    # Step 2: Clone a geometry
    print("Step 2: Cloning a geometry")
    new_geo_number = RasPlan.clone_geom("01")
    print(f"New geometry created: {new_geo_number}")
    print("Updated geometry files:")
    print(ras.geom_df)
    print()
    
    # Step 3: Clone an unsteady flow
    print("Step 3: Cloning an unsteady flow")
    new_unsteady_number = RasPlan.clone_unsteady("02")
    print(f"New unsteady flow created: {new_unsteady_number}")
    print("Updated unsteady flow files:")
    print(ras.unsteady_df)
    print()

    # Step 4: Set geometry for the cloned plan
    print("Step 4: Setting geometry for a plan")
    RasPlan.set_geom(new_plan_number, new_geo_number)
    plan_path = RasPlan.get_plan_path(new_plan_number)
    print(f"Updated geometry for plan {new_plan_number}")
    print(f"Plan file path: {plan_path}")
    print()

    # Step 5: Set unsteady flow for the cloned plan
    print("Step 5: Setting unsteady flow for a plan")
    RasPlan.set_unsteady(new_plan_number, new_unsteady_number)
    print(f"Updated unsteady flow for plan {new_plan_number}")
    print()

    # Step 6: Set the number of cores for the cloned plan
    print("Step 6: Setting the number of cores for a plan")
    RasPlan.set_num_cores(new_plan_number, 2)
    print(f"Updated number of cores for plan {new_plan_number}")
    print()

    # Step 7: Set geometry preprocessor options for the cloned plan
    print("Step 7: Setting geometry preprocessor options")
    RasPlan.set_geom_preprocessor(plan_path, run_htab=-1, use_ib_tables=-1)
    print(f"Updated geometry preprocessor options for plan {new_plan_number}")
    
    # Step 8: Compute the cloned plan
    print("Step 8: Computing the cloned plan")
    success = RasCommander.compute_plan(new_plan_number)
    print(f"Computing plan {new_plan_number}")
    if success:
        print(f"Plan {new_plan_number} computed successfully")
    else:
        print(f"Failed to compute plan {new_plan_number}")
    print()
    
    # Step 9: Get the HDF entries for the cloned plan to prove that the results were written
    print("Step 9: Retrieving HDF entries for the cloned plan")
    # Refresh the plan entries to ensure we have the latest data
    ras.plan_df = ras.get_plan_entries()
    hdf_entries = ras.get_hdf_entries()
    if not hdf_entries.empty:
        print("HDF entries for the cloned plan:")
        print(hdf_entries)
    else:
        print("No HDF entries found. This could mean the plan hasn't been computed successfully or the results haven't been written yet.")
    
    # Display the plan entries to see if the HDF path is populated
    print("\nCurrent plan entries:")
    print(ras.plan_df)
    
if __name__ == "__main__":
    main()
==================================================

File: c:\GH\ras_commander\examples\03_geometry_operations.py
==================================================
# 03_geometry_operations.py

#### --- IMPORTS AND EXAMPLE PROJECT SETUP --- ####

import sys
from pathlib import Path

# Add the parent directory to the Python path
current_file = Path(__file__).resolve()
parent_directory = current_file.parent.parent
sys.path.append(str(parent_directory))

# Flexible imports to allow for development without installation
try:
    # Try to import from the installed package
    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras
except ImportError:
    # If the import fails, add the parent directory to the Python path
    current_file = Path(__file__).resolve()
    parent_directory = current_file.parent.parent
    sys.path.append(str(parent_directory))
    
    # Now try to import again
    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras

# Extract specific projects
ras_examples = RasExamples()
ras_examples.extract_project(["Muncie"])

#### --- START OF SCRIPT --- ####

# RAS-Commander Library Notes:
# 1. This example uses the default global 'ras' object for simplicity.
# 2. If you need to work with multiple projects, use separate ras objects for each project.
# 3. Once you start using non-global ras objects, stick with that approach throughout your script.
# 4. The RasGeo class provides methods for working with geometry files and preprocessor operations.

# Best Practices:
# 1. For simple scripts working with a single project, using the global 'ras' object is fine.
# 2. For complex scripts or when working with multiple projects, create and use separate ras objects.
# 3. Be consistent in your approach: don't mix global and non-global ras object usage in the same script.
# 4. Always clear geometry preprocessor files before making significant changes to ensure clean results.

def main():
    # Initialize the project
    current_dir = Path(__file__).parent
    project_path = current_dir / "example_projects" / "Muncie"
    init_ras_project(project_path, "6.5")

    print("Initial plan files:")
    print(ras.plan_df)
    print()

    # Step 1: Clone a plan
    print("Step 1: Cloning a plan")
    new_plan_number = RasPlan.clone_plan("01")
    print(f"New plan created: {new_plan_number}")
    print("Updated plan files:")
    print(ras.plan_df)
    print()

    # Step 2: Clone a geometry file and assign it to the cloned plan
    print("Step 2: Cloning a geometry file and assigning it to the cloned plan")
    new_geom_number = RasPlan.clone_geom("01")
    print(f"New geometry created: {new_geom_number}")
    print(f"Now set the new geometry to the new plan")
    RasPlan.set_geom(new_plan_number, new_geom_number)
    print(f"New geometry {new_geom_number} assigned to plan {new_plan_number}")
    print("Updated geometry files:")
    print(ras.geom_df)
    print()

    # Step 3: Clear geometry preprocessor files for the cloned plan
    print("Step 3: Clearing geometry preprocessor files for the cloned plan")
    plan_path = RasPlan.get_plan_path(new_plan_number)
    RasGeo.clear_geompre_files(plan_path)
    print(f"Cleared geometry preprocessor files for plan {new_plan_number}")
    print()

    # Step 4: Clear geometry preprocessor files for all plans
    print("Step 4: Clearing geometry preprocessor files for all plans")
    RasGeo.clear_geompre_files()
    print("Cleared geometry preprocessor files for all plans")
    print()

    # Step 5: Print the updated plan information
    print("Step 5: Updated plan information")
    plan_df = ras.get_plan_entries()
    print(plan_df)
    print()

    # Step 6: Compute the cloned plan with new geometry and core count
    print("Step 6: Computing the cloned plan")
    success = RasCommander.compute_plan(new_plan_number)
    print(f"Computing plan {new_plan_number}")
    if success:
        print(f"Plan {new_plan_number} computed successfully")
    else:
        print(f"Failed to compute plan {new_plan_number}")
        
    # Step 7: Get and print results paths
    print("\nStep 7: Getting results paths")
    for plan_number in [new_plan_number, "01"]:  # Check both the new plan and the original plan
        results_path = RasPlan.get_results_path(plan_number)
        if results_path:
            print(f"Results for plan {plan_number} are located at: {results_path}")
        else:
            print(f"No results found for plan {plan_number}")
        

if __name__ == "__main__":
    main()
==================================================

File: c:\GH\ras_commander\examples\04_unsteady_flow_operations.py
==================================================
# 04_unsteady_flow_operations.py

#### --- IMPORTS AND EXAMPLE PROJECT SETUP --- ####

import sys
from pathlib import Path

# Add the parent directory to the Python path
current_file = Path(__file__).resolve()
parent_directory = current_file.parent.parent
sys.path.append(str(parent_directory))

# Flexible imports to allow for development without installation
try:
    # Try to import from the installed package
    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras
except ImportError:
    # If the import fails, add the parent directory to the Python path
    current_file = Path(__file__).resolve()
    parent_directory = current_file.parent.parent
    sys.path.append(str(parent_directory))
    
    # Now try to import again
    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras

# Extract specific projects
ras_examples = RasExamples()
ras_examples.extract_project(["Balde Eagle Creek"])

#### --- START OF SCRIPT --- ####

# RAS-Commander Library Notes:
# 1. This example uses the default global 'ras' object for simplicity.
# 2. If you need to work with multiple projects, use separate ras objects for each project.
# 3. Once you start using non-global ras objects, stick with that approach throughout your script.

# Best Practices:
# 1. For simple scripts working with a single project, using the global 'ras' object is fine.
# 2. For complex scripts or when working with multiple projects, create and use separate ras objects.
# 3. Be consistent in your approach: don't mix global and non-global ras object usage in the same script.

def main():
    # Initialize the project using the global 'ras' object
    current_dir = Path(__file__).parent
    project_path = current_dir / "example_projects" / "Balde Eagle Creek"
    init_ras_project(project_path, "6.5")

    print("Initial plan files:")
    print(ras.plan_df)
    print()

    # Step 1: Clone a plan
    print("Step 1: Cloning a plan")
    new_plan_number = RasPlan.clone_plan("01")
    print(f"New plan created: {new_plan_number}")
    print("Updated plan files:")
    print(ras.plan_df)
    print()

    # Step 2: Get the plan file path
    plan_path = RasPlan.get_plan_path(new_plan_number)

    # Step 3: Update unsteady flow parameters individually
    print("Step 3: Updating unsteady flow parameters individually")
    RasUnsteady.update_unsteady_parameters(plan_path, {"Simulation Date": "01JAN2023,0000,05JAN2023,2400"})
    RasUnsteady.update_unsteady_parameters(plan_path, {"Computation Interval": "1MIN"})
    RasUnsteady.update_unsteady_parameters(plan_path, {"Output Interval": "15MIN"})
    print("Updated parameters individually")
    print()

    # Step 4: Update unsteady flow parameters in batch
    print("Step 4: Updating unsteady flow parameters in batch")
    batch_modifications = {
        "Mapping Interval": "30MIN",
        "Hydrograph Output Interval": "1HOUR",
        "Detailed Output Interval": "1HOUR"
    }
    RasUnsteady.update_unsteady_parameters(plan_path, batch_modifications)
    print("Updated parameters in batch")
    print()

    # Step 5: Verify changes
    print("Step 5: Verifying changes")
    with open(plan_path, 'r') as f:
        content = f.read()
        for param in ["Simulation Date", "Computation Interval", "Output Interval", 
                      "Mapping Interval", "Hydrograph Output Interval", "Detailed Output Interval"]:
            for line in content.split('\n'):
                if line.startswith(param):
                    print(f"Updated {line}")
                    break
    print()

    # Step 6: Compute the updated plan
    print("Step 6: Computing the updated plan")
    success = RasCommander.compute_plan(new_plan_number)
    if success:
        print(f"Plan {new_plan_number} computed successfully")
    else:
        print(f"Failed to compute plan {new_plan_number}")

if __name__ == "__main__":
    main()
==================================================

File: c:\GH\ras_commander\examples\05_utility_functions.py
==================================================
# 05_utility_functions.py

#### --- IMPORTS AND EXAMPLE PROJECT SETUP --- ####

import sys
from pathlib import Path

# Add the parent directory to the Python path
current_file = Path(__file__).resolve()
parent_directory = current_file.parent.parent
sys.path.append(str(parent_directory))

# Flexible imports to allow for development without installation
try:
    # Try to import from the installed package
    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras
except ImportError:
    # If the import fails, add the parent directory to the Python path
    current_file = Path(__file__).resolve()
    parent_directory = current_file.parent.parent
    sys.path.append(str(parent_directory))
    
    # Now try to import again
    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras

# Extract specific projects
ras_examples = RasExamples()
ras_examples.extract_project(["Balde Eagle Creek"])

#### --- START OF SCRIPT --- ####

# RAS-Commander Library Notes:
# 1. This example uses the default global 'ras' object for simplicity.
# 2. If you need to work with multiple projects, use separate ras objects for each project.
# 3. Once you start using non-global ras objects, stick with that approach throughout your script.
# 4. The RasUtils class provides various utility functions for working with HEC-RAS projects.

# Best Practices:
# 1. For simple scripts working with a single project, using the global 'ras' object is fine.
# 2. For complex scripts or when working with multiple projects, create and use separate ras objects.
# 3. Be consistent in your approach: don't mix global and non-global ras object usage in the same script.

def main():
    # Initialize the project
    current_dir = Path(__file__).parent
    project_path = current_dir / "example_projects" / "Balde Eagle Creek"
    init_ras_project(project_path, "6.5")
    plan_number = "01"

    # Example 1: Get plan path using RasUtils
    print("Example 1: Getting plan path")
    plan_path = RasUtils.get_plan_path(plan_number)
    print(f"Path for plan {plan_number} is: {plan_path}")
    
    # Example 2: Get geometry path using RasPlan
    print("\nExample 2: Getting geometry path")
    geom_number = "01"
    geom_path = RasPlan.get_geom_path(geom_number)
    print(f"Path for geometry {geom_number} is: {geom_path}")
    
    # Example 3: Get unsteady flow path using RasPlan
    print("\nExample 3: Getting unsteady flow path")
    unsteady_number = "01"
    unsteady_path = RasPlan.get_unsteady_path(unsteady_number)
    print(f"Path for unsteady flow {unsteady_number} is: {unsteady_path}")
    
    # Example 4: Get project name
    print("\nExample 4: Getting project name")
    project_name = ras.get_project_name()
    print(f"Project name: {project_name}")


if __name__ == "__main__":
    main()
==================================================

File: c:\GH\ras_commander\examples\06_single_plan_execution.py
==================================================
#### --- IMPORTS AND EXAMPLE PROJECT SETUP --- ####

import sys
from pathlib import Path

# Add the parent directory to the Python path
current_file = Path(__file__).resolve()
parent_directory = current_file.parent.parent
sys.path.append(str(parent_directory))

# Flexible imports to allow for development without installation
try:
    # Try to import from the installed package
    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras
except ImportError:
    # If the import fails, add the parent directory to the Python path
    current_file = Path(__file__).resolve()
    parent_directory = current_file.parent.parent
    sys.path.append(str(parent_directory))
    
    # Now try to import again
    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras

# Extract specific projects
ras_examples = RasExamples()
ras_examples.extract_project(["Balde Eagle Creek"])

#### --- START OF SCRIPT --- ####

# RAS-Commander Library Notes:
# 1. This example uses the default global 'ras' object for simplicity.
# 2. If you need to work with multiple projects, use separate ras objects for each project.
# 3. Once you start using non-global ras objects, stick with that approach throughout your script.

# Best Practices:
# 1. For simple scripts working with a single project, using the global 'ras' object is fine.
# 2. For complex scripts or when working with multiple projects, create and use separate ras objects.
# 3. Be consistent in your approach: don't mix global and non-global ras object usage in the same script.

def main():
    # Initialize the project using the global 'ras' object
    current_dir = Path(__file__).parent
    project_path = current_dir / "example_projects" / "Balde Eagle Creek"
    init_ras_project(project_path, "6.5")

    print("Available plans:")
    print(ras.plan_df)
    print()

    # Example 1: Execute a single plan
    print("Example 1: Executing a single plan")
    plan_number = "01"
    success = RasCommander.compute_plan(plan_number)
    if success:
        print(f"Plan {plan_number} executed successfully")
    else:
        print(f"Plan {plan_number} execution failed")
    print()

    # Example 2: Execute a plan in a separate compute folder
    print("Example 2: Executing a plan in a separate compute folder")
    plan_number = "02"
    compute_folder = project_path.parent / "compute_test"
    success = RasCommander.compute_plan(plan_number, compute_folder=compute_folder)
    if success:
        print(f"Plan {plan_number} executed successfully in {compute_folder}")
    else:
        print(f"Plan {plan_number} execution failed in {compute_folder}")
    print()

    # Example 3: Get and print results path
    print("Example 3: Getting results path")
    results_path = RasPlan.get_results_path(plan_number)
    if results_path:
        print(f"Results for plan {plan_number} are located at: {results_path}")
    else:
        print(f"No results found for plan {plan_number}")

if __name__ == "__main__":
    main()
==================================================

File: c:\GH\ras_commander\examples\07_sequential_plan_execution.py
==================================================
#### --- IMPORTS AND EXAMPLE PROJECT SETUP --- ####

import sys
from pathlib import Path


# Add the parent directory to the Python path
current_file = Path(__file__).resolve()
parent_directory = current_file.parent.parent
sys.path.append(str(parent_directory))

# Flexible imports to allow for development without installation
try:
    # Try to import from the installed package
    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras
except ImportError:
    # If the import fails, add the parent directory to the Python path
    current_file = Path(__file__).resolve()
    parent_directory = current_file.parent.parent
    sys.path.append(str(parent_directory))
    
    # Now try to import again
    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras

# Housekeeping Note: 
# For all of the functions that do batched execution (sequential or parallel), they are careful not to overwrite existing folders
# So if you want your script to be repeatable, you need to make sure you delete the folders before running again.
# Otherwise an error will be raised to prevent overwriting any results from your previous runs.
# This will not be done by the example projects routines, which only overwrite the source folder for repeatability. 
    
import shutil
from pathlib import Path
# Define the keys to search for in folder names
# Delete example projects folder
current_file = Path(__file__).resolve()
current_dir = current_file.parent
delete_folder_path = current_dir / "example_projects"

if delete_folder_path.exists():
    print(f"Removing existing folder: {delete_folder_path}")
    shutil.rmtree(delete_folder_path)
else:
    print(f"Folder not found: {delete_folder_path}")
    
# re-

# Extract specific projects
ras_examples = RasExamples()
ras_examples.extract_project(["Balde Eagle Creek"])

#### --- START OF SCRIPT --- ####

# RAS-Commander Library Notes:
# 1. This example uses the default global 'ras' object for simplicity.
# 2. If you need to work with multiple projects, use separate ras objects for each project.
# 3. Once you start using non-global ras objects, stick with that approach throughout your script.

# Best Practices:
# 1. For simple scripts working with a single project, using the global 'ras' object is fine.
# 2. For complex scripts or when working with multiple projects, create and use separate ras objects.
# 3. Be consistent in your approach: don't mix global and non-global ras object usage in the same script.
# 4. For functions that do batched execution (sequential or parallel), they are careful not to overwrite existing folders.
# 5. If you want your script to be repeatable, make sure to delete the folders before running again.

def main():
    # Initialize the project using the global 'ras' object
    current_dir = Path(__file__).parent
    project_path = current_dir / "example_projects" / "Balde Eagle Creek"
    init_ras_project(project_path, "6.5")

    print("Available plans:")
    print(ras.plan_df)
    print()

    # Example 1: Sequential execution of all plans without clearing geompre files
    print("Example 1: Sequential execution of all plans without clearing geompre files")
    RasCommander.compute_test_mode(folder_suffix="[AllSequential]")
    print("Sequential execution of all plans completed without clearing geompre files")
    print()
    
    # Example 2: Sequential execution of specific plans with clearing geompre files
    print("Example 2: Sequential execution of specific plans with clearing geompre files")
    RasCommander.compute_test_mode(
        plan_numbers=["01", "02"],
        folder_suffix="[SpecificSequentialClearGeompre]",
        clear_geompre=True,
        max_cores=2
    )
    print("Sequential execution of specific plans completed with clearing geompre files")
    print()

    # Example 3: Demonstrate clearing geompre files for specific plans
    print("Example 3: Clearing geompre files for specific plans")
    plan_files = [RasPlan.get_plan_path("01"), RasPlan.get_plan_path("02")]
    RasGeo.clear_geompre_files(plan_files)
    print("Geometry preprocessor files cleared for specific plans")
    print()

    # Example 4: Demonstrate clearing all geompre files
    print("Example 4: Clearing all geompre files")
    RasGeo.clear_geompre_files()
    print("All geometry preprocessor files cleared")

if __name__ == "__main__":
    main()
==================================================

File: c:\GH\ras_commander\examples\08_parallel_execution.py
==================================================
#### --- IMPORTS AND EXAMPLE PROJECT SETUP --- ####

import sys
from pathlib import Path
import shutil

# Add the parent directory to the Python path
current_file = Path(__file__).resolve()
parent_directory = current_file.parent.parent
sys.path.append(str(parent_directory))

# Flexible imports to allow for development without installation
try:
    # Try to import from the installed package
    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras
except ImportError:
    # If the import fails, add the parent directory to the Python path
    current_file = Path(__file__).resolve()
    parent_directory = current_file.parent.parent
    sys.path.append(str(parent_directory))
    
    # Now try to import again
    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras

# Extract specific projects
ras_examples = RasExamples()
ras_examples.extract_project(["Balde Eagle Creek"])

#### --- START OF SCRIPT --- ####

# RAS-Commander Library Notes:
# 1. This example uses the default global 'ras' object for simplicity.
# 2. If you need to work with multiple projects, use separate ras objects for each project.
# 3. Once you start using non-global ras objects, stick with that approach throughout your script.
# 4. For functions that do batched execution (sequential or parallel), they are careful not to overwrite existing folders.
# 5. If you want your script to be repeatable, make sure to delete the folders before running again.

# Best Practices:
# 1. For simple scripts working with a single project, using the global 'ras' object is fine.
# 2. For complex scripts or when working with multiple projects, create and use separate ras objects.
# 3. Be consistent in your approach: don't mix global and non-global ras object usage in the same script.
# 4. When using parallel execution, consider the number of cores available on your machine.
# 5. Use the dest_folder argument to keep your project folder clean and organized.

def main():
    # Initialize the project using the global 'ras' object
    current_dir = Path(__file__).parent
    project_path = current_dir / "example_projects" / "Balde Eagle Creek"
    init_ras_project(project_path, "6.5")

    print("Available plans:")
    print(ras.plan_df)
    print()

    # Housekeeping: Remove existing compute folders if they exist
    compute_folder = project_path.parent / "compute_test_parallel"
    if compute_folder.exists():
        print(f"Removing existing folder: {compute_folder}")
        shutil.rmtree(compute_folder)
    
    # Example 1: Parallel execution of all plans
    print("Example 1: Parallel execution of all plans")
    results_all = RasCommander.compute_parallel(max_workers=3, cores_per_run=2, dest_folder=compute_folder)
    print("Parallel execution of all plans results:")
    for plan_number, success in results_all.items():
        print(f"Plan {plan_number}: {'Successful' if success else 'Failed'}")
    print()
    
    # Example 2: Parallel execution of specific plans
    print("Example 2: Parallel execution of specific plans")
    specific_plans = ["01", "02"]
    specific_compute_folder = compute_folder / "specific_plans"
    if specific_compute_folder.exists():
        print(f"Removing existing folder: {specific_compute_folder}")
        shutil.rmtree(specific_compute_folder)
    results_specific = RasCommander.compute_parallel(
        plan_numbers=specific_plans,
        max_workers=2,
        cores_per_run=2,
        dest_folder=specific_compute_folder
    )
    print("Parallel execution of specific plans results:")
    for plan_number, success in results_specific.items():
        print(f"Plan {plan_number}: {'Successful' if success else 'Failed'}")
    print()

    # Example 3: Get and print results paths
    print("Example 3: Getting results paths")
    for plan_number in specific_plans:
        results_path = RasPlan.get_results_path(plan_number)
        if results_path:
            print(f"Results for plan {plan_number} are located at: {results_path}")
        else:
            print(f"No results found for plan {plan_number}")

if __name__ == "__main__":
    main()
==================================================

File: c:\GH\ras_commander\examples\09_specifying_plans.py
==================================================
#### --- IMPORTS AND EXAMPLE PROJECT SETUP --- ####

import sys
from pathlib import Path


# Add the parent directory to the Python path
current_file = Path(__file__).resolve()
parent_directory = current_file.parent.parent
sys.path.append(str(parent_directory))

# Flexible imports to allow for development without installation
try:
    # Try to import from the installed package
    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras
except ImportError:
    # If the import fails, add the parent directory to the Python path
    current_file = Path(__file__).resolve()
    parent_directory = current_file.parent.parent
    sys.path.append(str(parent_directory))
    
    # Now try to import again
    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras

# Housekeeping Note: 
# For all of the functions that do batched execution (sequential or parallel), they are careful not to overwrite existing folders
# So if you want your script to be repeatable, you need to make sure you delete the folders before running again.
# Otherwise an error will be raised to prevent overwriting any results from your previous runs.
# This will not be done by the example projects routines, which only overwrite the source folder for repeatability. 
    
import shutil
from pathlib import Path
# Define the keys to search for in folder names
# Delete example projects folder
current_file = Path(__file__).resolve()
current_dir = current_file.parent
delete_folder_path = current_dir / "example_projects"

if delete_folder_path.exists():
    print(f"Removing existing folder: {delete_folder_path}")
    shutil.rmtree(delete_folder_path)
else:
    print(f"Folder not found: {delete_folder_path}")
    
# re-

# Extract specific projects
ras_examples = RasExamples()
ras_examples.extract_project(["Balde Eagle Creek"])

#### --- START OF SCRIPT --- ####

# RAS-Commander Library Notes:
# 1. This example uses the default global 'ras' object for simplicity.
# 2. If you need to work with multiple projects, use separate ras objects for each project.
# 3. Once you start using non-global ras objects, stick with that approach throughout your script.
# 4. The RasCommander class provides methods for executing plans in various ways.
# 5. You can specify individual plans or lists of plans for batch operations.

# Best Practices:
# 1. For simple scripts working with a single project, using the global 'ras' object is fine.
# 2. For complex scripts or when working with multiple projects, create and use separate ras objects.
# 3. Be consistent in your approach: don't mix global and non-global ras object usage in the same script.
# 4. When specifying plans, use plan numbers as strings (e.g., "01", "02") for consistency.
# 5. Always check the available plans in the project before specifying plan numbers for execution.

def main():
    # Initialize the project
    current_dir = Path(__file__).parent
    project_path = current_dir / "example_projects" / "Balde Eagle Creek"
    init_ras_project(project_path, "6.5")

    print("Available plans:")
    print(ras.plan_df)
    print()

    # Example 1: Sequential execution of specific plans
    print("Example 1: Sequential execution of specific plans (1 and 3)")
    RasCommander.compute_test_mode(plan_numbers=["01", "03"], folder_suffix="[SpecificSequential]", max_cores=6)
    print("Sequential execution of specific plans completed")
    print()

    # Example 2: Parallel execution of specific plans
    print("Example 2: Parallel execution of specific plans")
    results_specific = RasCommander.compute_parallel(
        plan_numbers=["01", "02"],
        max_workers=2,
        cores_per_run=2,
        dest_folder=project_path.parent / "parallel_specific_results"
    )
    print("Parallel execution of specific plans results:")
    for plan_number, success in results_specific.items():
        print(f"Plan {plan_number}: {'Successful' if success else 'Failed'}")
    print()

    # Example 3: Execute all plans
    print("Example 3: Execute all plans")
    all_plan_numbers = ras.plan_df['plan_number'].tolist()
    RasCommander.compute_test_mode(plan_numbers=all_plan_numbers, folder_suffix="[AllPlans]")
    print("Execution of all plans completed")
    print()

if __name__ == "__main__":
    main()





==================================================

File: c:\GH\ras_commander\examples\10_arguments_for_compute.py
==================================================
#### --- IMPORTS AND EXAMPLE PROJECT SETUP --- ####

import sys
from pathlib import Path

# Add the parent directory to the Python path
current_file = Path(__file__).resolve()
parent_directory = current_file.parent.parent
sys.path.append(str(parent_directory))

# Flexible imports to allow for development without installation
try:
    # Try to import from the installed package
    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras
except ImportError:
    # If the import fails, add the parent directory to the Python path
    current_file = Path(__file__).resolve()
    parent_directory = current_file.parent.parent
    sys.path.append(str(parent_directory))
    
    # Now try to import again
    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras

# Extract specific projects
ras_examples = RasExamples()
ras_examples.extract_project(["Balde Eagle Creek"])

#### --- START OF SCRIPT --- ####

# RAS-Commander Library Notes:
# 1. This example uses the default global 'ras' object for simplicity.
# 2. If you need to work with multiple projects, use separate ras objects for each project.
# 3. Once you start using non-global ras objects, stick with that approach throughout your script.
# 4. The RasCommander class provides various arguments for fine-tuning plan computation:
#    - plan_numbers: List of plan numbers to compute (e.g., ["01", "02"])
#    - folder_suffix: String to append to the computation folder name
#    - clear_geompre: Boolean to clear geometry preprocessor files before computation
#    - max_cores: Integer specifying the maximum number of cores to use
#    - max_workers: Integer specifying the maximum number of parallel workers (for parallel execution)
#    - cores_per_run: Integer specifying the number of cores to use per run (for parallel execution)
#    - dest_folder: Path object specifying the destination folder for computation results

# Best Practices:
# 1. For simple scripts working with a single project, using the global 'ras' object is fine.
# 2. For complex scripts or when working with multiple projects, create and use separate ras objects.
# 3. Be consistent in your approach: don't mix global and non-global ras object usage in the same script.
# 4. Utilize the various arguments in compute functions to customize plan execution.
# 5. Always consider your system's capabilities when setting max_cores and max_workers.
# 6. Use clear_geompre=True when you want to ensure a clean computation environment.
# 7. Specify dest_folder to keep your project folder organized and prevent overwriting previous results.

def main():
    # Initialize the project
    current_dir = Path(__file__).parent
    project_path = current_dir / "example_projects" / "Balde Eagle Creek"
    init_ras_project(project_path, "6.5")

    print("Available plans:")
    print(ras.plan_df)
    print()

    # Example 1: Sequential execution with various arguments
    print("Example 1: Sequential execution with various arguments")
    RasCommander.compute_test_mode(
        plan_numbers=["01", "02"],
        folder_suffix="[SequentialWithArgs]",
        clear_geompre=True,
        max_cores=2
    )
    print("Sequential execution completed")
    print()

    # Example 2: Parallel execution with various arguments
    print("Example 2: Parallel execution with various arguments")
    results = RasCommander.compute_parallel(
        plan_numbers=["01", "02"],
        max_workers=2,
        cores_per_run=2,
        dest_folder=project_path.parent / "parallel_results",
        clear_geompre=True
    )
    print("Parallel execution results:")
    for plan_number, success in results.items():
        print(f"Plan {plan_number}: {'Successful' if success else 'Failed'}")
    print()

    # Example 3: Single plan execution with specific arguments
    print("Example 3: Single plan execution with specific arguments")
    success = RasCommander.compute_plan(
        "03",
        compute_folder=project_path.parent / "single_plan_result",
        clear_geompre=True
    )
    print(f"Single plan execution: {'Successful' if success else 'Failed'}")

if __name__ == "__main__":
    main()






==================================================

File: c:\GH\ras_commander\examples\11_Using_RasExamples.ipynb
==================================================
{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ras_commander imported successfully\n"
     ]
    }
   ],
   "source": [
    "import sys\n",
    "from pathlib import Path\n",
    "\n",
    "# Flexible imports to allow for development without installation\n",
    "try:\n",
    "    # Try to import from the installed package\n",
    "    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras\n",
    "except ImportError:\n",
    "    # If the import fails, add the parent directory to the Python path\n",
    "    import os\n",
    "    current_file = Path(os.getcwd()).resolve()\n",
    "    parent_directory = current_file.parent\n",
    "    sys.path.append(str(parent_directory))\n",
    "    \n",
    "    # Now try to import again\n",
    "    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras\n",
    "\n",
    "print(\"ras_commander imported successfully\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Example projects folder: c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace6\\examples\\example_projects\n",
      "Found zip file: c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace6\\examples\\Example_Projects_6_5.zip\n",
      "Loading project data from CSV...\n",
      "Loaded 66 projects from CSV, use list_categories() and list_projects() to explore them\n",
      "----- RasExamples Extracting Project -----\n",
      "Extracting project 'Balde Eagle Creek'\n",
      "Project 'Balde Eagle Creek' already exists. Deleting existing folder...\n",
      "Existing folder for project 'Balde Eagle Creek' has been deleted.\n",
      "Successfully extracted project 'Balde Eagle Creek' to c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace6\\examples\\example_projects\\Balde Eagle Creek\n",
      "----- RasExamples Extracting Project -----\n",
      "Extracting project 'BaldEagleCrkMulti2D'\n",
      "Project 'BaldEagleCrkMulti2D' already exists. Deleting existing folder...\n",
      "Existing folder for project 'BaldEagleCrkMulti2D' has been deleted.\n",
      "Successfully extracted project 'BaldEagleCrkMulti2D' to c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace6\\examples\\example_projects\\BaldEagleCrkMulti2D\n",
      "----- RasExamples Extracting Project -----\n",
      "Extracting project 'Muncie'\n",
      "Project 'Muncie' already exists. Deleting existing folder...\n",
      "Existing folder for project 'Muncie' has been deleted.\n",
      "Successfully extracted project 'Muncie' to c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace6\\examples\\example_projects\\Muncie\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[WindowsPath('c:/Users/billk/Desktop/AWS Webinar AI for HEC-RAS/ras_commander/ras_commander workspace6/examples/example_projects/Balde Eagle Creek'),\n",
       " WindowsPath('c:/Users/billk/Desktop/AWS Webinar AI for HEC-RAS/ras_commander/ras_commander workspace6/examples/example_projects/BaldEagleCrkMulti2D'),\n",
       " WindowsPath('c:/Users/billk/Desktop/AWS Webinar AI for HEC-RAS/ras_commander/ras_commander workspace6/examples/example_projects/Muncie')]"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# The First Code Cell is All You Need\n",
    "\n",
    "# This is what this Class was intended to do: Help me make repeatable workflows around HEC-RAS Example Projects for testing and demonstration purposes. \n",
    "# Replace the Example_Projects_6_5.zip with your own zip file in the same format and you will be able to load them by folder name for repeatable workflows.\n",
    "# Just make sure all project folders have unique folder names. \n",
    "\n",
    "# Extract specific projects\n",
    "ras_examples = RasExamples()\n",
    "ras_examples.extract_project([\"Balde Eagle Creek\", \"BaldEagleCrkMulti2D\", \"Muncie\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Example projects folder: c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace6\\examples\\example_projects\n",
      "Found zip file: c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace6\\examples\\Example_Projects_6_5.zip\n",
      "Loading project data from CSV...\n",
      "Loaded 66 projects from CSV, use list_categories() and list_projects() to explore them\n",
      "Example projects are already downloaded.\n",
      "ras_examples.folder_df:\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Category</th>\n",
       "      <th>Project</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>1D Sediment Transport</td>\n",
       "      <td>BSTEM - Simple Example</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>1D Sediment Transport</td>\n",
       "      <td>Dredging Example</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>1D Sediment Transport</td>\n",
       "      <td>Reservoir Video Tutorial</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>1D Sediment Transport</td>\n",
       "      <td>SIAM Example</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>1D Sediment Transport</td>\n",
       "      <td>Simple Sediment Transport Example</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>61</th>\n",
       "      <td>Applications Guide</td>\n",
       "      <td>Example 6 - Floodway Determination</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>62</th>\n",
       "      <td>Applications Guide</td>\n",
       "      <td>Example 7 - Multiple Plans</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>63</th>\n",
       "      <td>Applications Guide</td>\n",
       "      <td>Example 8 - Looped Network</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>64</th>\n",
       "      <td>Applications Guide</td>\n",
       "      <td>Example 9 - Mixed Flow Analysis</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>65</th>\n",
       "      <td>Water Quality</td>\n",
       "      <td>Nutrient Example</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>66 rows × 2 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "                 Category                             Project\n",
       "0   1D Sediment Transport              BSTEM - Simple Example\n",
       "1   1D Sediment Transport                    Dredging Example\n",
       "2   1D Sediment Transport            Reservoir Video Tutorial\n",
       "3   1D Sediment Transport                        SIAM Example\n",
       "4   1D Sediment Transport   Simple Sediment Transport Example\n",
       "..                    ...                                 ...\n",
       "61     Applications Guide  Example 6 - Floodway Determination\n",
       "62     Applications Guide          Example 7 - Multiple Plans\n",
       "63     Applications Guide          Example 8 - Looped Network\n",
       "64     Applications Guide     Example 9 - Mixed Flow Analysis\n",
       "65          Water Quality                    Nutrient Example\n",
       "\n",
       "[66 rows x 2 columns]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Initialize RasExamples (it will use the current working directory by default)\n",
    "ras_examples = RasExamples()\n",
    "\n",
    "# Check if example projects are already downloaded\n",
    "if ras_examples.projects_dir.exists():\n",
    "    print(\"Example projects are already downloaded.\")\n",
    "    print(\"ras_examples.folder_df:\")\n",
    "    display(ras_examples.folder_df)\n",
    "else:\n",
    "    print(\"Downloading example projects...\")\n",
    "    ras_examples.get_example_projects()\n",
    "    print(\"ras_examples.folder_df:\")\n",
    "    display(ras_examples.folder_df)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Available categories: 1D Sediment Transport, 1D Steady Flow Hydraulics, 1D Unsteady Flow Hydraulics, 2D Sediment Transport, 2D Unsteady Flow Hydraulics, Applications Guide, Water Quality\n",
      "\n",
      "Available categories:\n",
      "- 1D Sediment Transport\n",
      "- 1D Steady Flow Hydraulics\n",
      "- 1D Unsteady Flow Hydraulics\n",
      "- 2D Sediment Transport\n",
      "- 2D Unsteady Flow Hydraulics\n",
      "- Applications Guide\n",
      "- Water Quality\n"
     ]
    }
   ],
   "source": [
    "# List all categories\n",
    "categories = ras_examples.list_categories()\n",
    "print(\"\\nAvailable categories:\")\n",
    "for category in categories:\n",
    "    print(f\"- {category}\")\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Projects in '1D Unsteady Flow Hydraulics':\n",
      "- Balde Eagle Creek\n",
      "- Bridge Hydraulics\n",
      "- ContractionExpansionMinorLosses\n",
      "- Culvert Hydraulics\n",
      "- Culverts with Flap Gates\n",
      "- Dam Breaching\n",
      "- Elevation Controled Gates\n",
      "- Inline Structure with Gated Spillways\n",
      "- Internal Stage and Flow Boundary Condition\n",
      "- JunctionHydraulics\n",
      "- Lateral Strcuture with Gates\n",
      "- Lateral Structure connected to a River Reach\n",
      "- Lateral Structure Overflow Weir\n",
      "- Lateral Structure with Culverts\n",
      "- Lateral Structure with Culverts and Gates\n",
      "- Levee Breaching\n",
      "- Mixed Flow Regime\n",
      "- Multiple Reaches with Hydraulic Structures\n",
      "- NavigationDam\n",
      "- Pumping Station\n",
      "- Pumping Station with Rules\n",
      "- Rule Operations\n",
      "- Simplified Physical Breaching\n",
      "- Storage Area Hydraulic Connection\n",
      "- UngagedAreaInflows\n",
      "- Unsteady Flow Encroachment Analysis\n"
     ]
    }
   ],
   "source": [
    "\n",
    "# List projects in a specific category\n",
    "category = \"1D Unsteady Flow Hydraulics\"\n",
    "projects = ras_examples.list_projects(category)\n",
    "print(f\"\\nProjects in '{category}':\")\n",
    "for project in projects:\n",
    "    print(f\"- {project}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "All available projects:\n",
      "- BSTEM - Simple Example\n",
      "- Dredging Example\n",
      "- Reservoir Video Tutorial\n",
      "- SIAM Example\n",
      "- Simple Sediment Transport Example\n",
      "- Unsteady Sediment with Concentration Rules\n",
      "- Video Tutorial (Sediment Intro)\n",
      "- Baxter RAS Mapper\n",
      "- Chapter 4 Example Data\n",
      "- ConSpan Culvert\n",
      "- Mixed Flow Regime Channel\n",
      "- Wailupe GeoRAS\n",
      "- Balde Eagle Creek\n",
      "- Bridge Hydraulics\n",
      "- ContractionExpansionMinorLosses\n",
      "- Culvert Hydraulics\n",
      "- Culverts with Flap Gates\n",
      "- Dam Breaching\n",
      "- Elevation Controled Gates\n",
      "- Inline Structure with Gated Spillways\n",
      "- Internal Stage and Flow Boundary Condition\n",
      "- JunctionHydraulics\n",
      "- Lateral Strcuture with Gates\n",
      "- Lateral Structure connected to a River Reach\n",
      "- Lateral Structure Overflow Weir\n",
      "- Lateral Structure with Culverts\n",
      "- Lateral Structure with Culverts and Gates\n",
      "- Levee Breaching\n",
      "- Mixed Flow Regime\n",
      "- Multiple Reaches with Hydraulic Structures\n",
      "- NavigationDam\n",
      "- Pumping Station\n",
      "- Pumping Station with Rules\n",
      "- Rule Operations\n",
      "- Simplified Physical Breaching\n",
      "- Storage Area Hydraulic Connection\n",
      "- UngagedAreaInflows\n",
      "- Unsteady Flow Encroachment Analysis\n",
      "- Chippewa_2D\n",
      "- BaldEagleCrkMulti2D\n",
      "- Muncie\n",
      "- Example 1 - Critical Creek\n",
      "- Example 10 - Stream Junction\n",
      "- Example 11 - Bridge Scour\n",
      "- Example 12 - Inline Structure\n",
      "- Example 13 - Singler Bridge (WSPRO)\n",
      "- Example 14 - Ice Covered River\n",
      "- Example 15 - Split Flow Junction with Lateral Weir\n",
      "- Example 16 - Channel Modification\n",
      "- Example 17 - Unsteady Flow Application\n",
      "- Example 18 - Advanced Inline Structure\n",
      "- Example 19 - Hydrologic Routing - ModPuls\n",
      "- Example 2 - Beaver Creek\n",
      "- Example 20 - HagerLatWeir\n",
      "- Example 21 - Overflow Gates\n",
      "- Example 22 - Groundwater Interflow\n",
      "- Example 23 - Urban Modeling\n",
      "- Example 24 - Mannings-n-Calibration\n",
      "- Example 3 - Single Culvert\n",
      "- Example 4 - Multiple Culverts\n",
      "- Example 5 - Multiple Openings\n",
      "- Example 6 - Floodway Determination\n",
      "- Example 7 - Multiple Plans\n",
      "- Example 8 - Looped Network\n",
      "- Example 9 - Mixed Flow Analysis\n",
      "- Nutrient Example\n"
     ]
    }
   ],
   "source": [
    "# List all projects\n",
    "all_projects = ras_examples.list_projects()\n",
    "print(\"\\nAll available projects:\")\n",
    "for project in all_projects:\n",
    "    print(f\"- {project}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "----- RasExamples Extracting Project -----\n",
      "Extracting project 'Balde Eagle Creek'\n",
      "Project 'Balde Eagle Creek' already exists. Deleting existing folder...\n",
      "Existing folder for project 'Balde Eagle Creek' has been deleted.\n",
      "Successfully extracted project 'Balde Eagle Creek' to c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace6\\examples\\example_projects\\Balde Eagle Creek\n",
      "----- RasExamples Extracting Project -----\n",
      "Extracting project 'BaldEagleCrkMulti2D'\n",
      "Project 'BaldEagleCrkMulti2D' already exists. Deleting existing folder...\n",
      "Existing folder for project 'BaldEagleCrkMulti2D' has been deleted.\n",
      "Successfully extracted project 'BaldEagleCrkMulti2D' to c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace6\\examples\\example_projects\\BaldEagleCrkMulti2D\n",
      "----- RasExamples Extracting Project -----\n",
      "Extracting project 'Muncie'\n",
      "Project 'Muncie' already exists. Deleting existing folder...\n",
      "Existing folder for project 'Muncie' has been deleted.\n",
      "Successfully extracted project 'Muncie' to c:\\Users\\billk\\Desktop\\AWS Webinar AI for HEC-RAS\\ras_commander\\ras_commander workspace6\\examples\\example_projects\\Muncie\n"
     ]
    }
   ],
   "source": [
    "# Extract specific projects\n",
    "projects_to_extract = [\"Balde Eagle Creek\", \"BaldEagleCrkMulti2D\", \"Muncie\"]\n",
    "extracted_paths = ras_examples.extract_project(projects_to_extract)\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "cmdr_workspace",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}

==================================================

File: c:\GH\ras_commander\examples\12_plan_set_execution.py
==================================================
#### --- IMPORTS AND EXAMPLE PROJECT SETUP --- ####

import sys
from pathlib import Path

# Add the parent directory to the Python path
current_file = Path(__file__).resolve()
parent_directory = current_file.parent.parent
sys.path.append(str(parent_directory))

# Flexible imports to allow for development without installation
try:
    # Try to import from the installed package
    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras
except ImportError:
    # If the import fails, add the parent directory to the Python path
    current_file = Path(__file__).resolve()
    parent_directory = current_file.parent.parent
    sys.path.append(str(parent_directory))
    
    # Now try to import again
    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras

# Extract specific projects
ras_examples = RasExamples()
ras_examples.extract_project(["Balde Eagle Creek"])

#### --- START OF SCRIPT --- ####

import pandas as pd


def create_plan_set(base_plan, base_geom, num_copies):
    plan_set = []
    for i in range(num_copies):
        new_plan = RasPlan.clone_plan(base_plan)
        new_geom = RasPlan.clone_geom(base_geom)
        RasPlan.set_geom(new_plan, new_geom)
        plan_set.append({
            'plan_number': new_plan,
            'geom_number': new_geom
        })
    return pd.DataFrame(plan_set)

def main():
    # Initialize the project
    current_dir = Path(__file__).parent
    project_path = current_dir / "example_projects" / "Balde Eagle Creek"
    init_ras_project(project_path, "6.5")

    print("Available plans:")
    print(ras.plan_df)
    print("\nAvailable geometries:")
    print(ras.geom_df)
    print()

    # Create a plan set
    base_plan = "01"
    base_geom = "01"
    num_copies = 5
    plan_set = create_plan_set(base_plan, base_geom, num_copies)
    
    print("Created plan set:")
    print(plan_set)
    print()

    # Placeholder for user to insert code that makes programmatic changes to the model
    # For example:
    # for index, row in plan_set.iterrows():
    #     plan_path = RasPlan.get_plan_path(row['plan_number'])
    #     geom_path = RasPlan.get_geom_path(row['geom_number'])
    #     # Make changes to the plan or geometry file here
    #     # For example, you could modify Manning's n values, cross-section data, etc.

    # Execute the plan set in parallel
    print("Executing plan set in parallel")
    results = RasCommander.compute_parallel(
        plan_numbers=plan_set['plan_number'].tolist(),
        max_workers=3,
        cores_per_run=2
    )

    # Add execution results to the plan_set DataFrame
    plan_set['execution_success'] = plan_set['plan_number'].map(results)

    print("\nPlan set execution results:")
    print(plan_set)

    # Here you could add code to analyze the results, such as:
    # - Extracting key output values from each simulation
    # - Comparing results across different plans
    # - Creating visualizations of the results

if __name__ == "__main__":
    main()
==================================================

File: c:\GH\ras_commander\examples\13_multiple_project_operations.py
==================================================
#### --- IMPORTS AND EXAMPLE PROJECT SETUP --- ####

import sys
import shutil
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor

# Add the parent directory to the Python path
current_file = Path(__file__).resolve()
parent_directory = current_file.parent.parent
sys.path.append(str(parent_directory))

# Flexible imports to allow for development without installation
try:
    # Try to import from the installed package
    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras
except ImportError:
    # If the import fails, add the parent directory to the Python path
    current_file = Path(__file__).resolve()
    parent_directory = current_file.parent.parent
    sys.path.append(str(parent_directory))
    
    # Now try to import again
    from ras_commander import init_ras_project, RasExamples, RasCommander, RasPlan, RasGeo, RasUnsteady, RasUtils, ras

# Extract specific projects
ras_examples = RasExamples()
ras_examples.extract_project(["Balde Eagle Creek", "Muncie"])

#### --- START OF SCRIPT --- ####

def execute_plan(plan_number, ras_object, compute_folder):
    # Set the number of cores to 2 before executing the plan
    RasPlan.set_num_cores(plan_number, 2, ras_object=ras_object)
    
    # Execute the plan in the compute folder
    success = RasCommander.compute_plan(plan_number, ras_object=ras_object, compute_folder=compute_folder)
    
    return plan_number, success

def main():
    # Initialize two projects
    current_dir = Path(__file__).parent
    bald_eagle_path = current_dir / "example_projects" / "Balde Eagle Creek"
    muncie_path = current_dir / "example_projects" / "Muncie"
    
    bald_eagle = init_ras_project(bald_eagle_path, "6.5")
    muncie = init_ras_project(muncie_path, "6.5")

    print("Available plans in Bald Eagle Creek project:")
    print(bald_eagle.plan_df)
    print("\nAvailable plans in Muncie project:")
    print(muncie.plan_df)
    print()

    # Example 1: Clone plans with custom short identifiers
    print("Example 1: Cloning plans with custom short identifiers")
    new_bald_eagle_plan = RasPlan.clone_plan("01", new_plan_shortid="BECustom", ras_object=bald_eagle)
    new_muncie_plan = RasPlan.clone_plan("01", new_plan_shortid="MunCustom", ras_object=muncie)
    print(f"Created new plan {new_bald_eagle_plan} in Bald Eagle Creek project")
    print(f"Created new plan {new_muncie_plan} in Muncie project")
    print()

    # Example 2: Set geometry for the new plans
    print("Example 2: Setting geometry for the new plans")
    RasPlan.set_geom(new_bald_eagle_plan, "01", ras_object=bald_eagle)
    RasPlan.set_geom(new_muncie_plan, "01", ras_object=muncie)
    print(f"Set geometry for plan {new_bald_eagle_plan} in Bald Eagle Creek project")
    print(f"Set geometry for plan {new_muncie_plan} in Muncie project")
    print()

    # Example 3: Update unsteady flow parameters for both projects
    print("Example 3: Updating unsteady flow parameters")
    bald_eagle_plan_file = RasPlan.get_plan_path(new_bald_eagle_plan, ras_object=bald_eagle)
    muncie_plan_file = RasPlan.get_plan_path(new_muncie_plan, ras_object=muncie)

    modifications = {
        "Computation Interval": "2MIN",
        "Output Interval": "30MIN",
        "Mapping Interval": "1HOUR"
    }

    RasUnsteady.update_unsteady_parameters(bald_eagle_plan_file, modifications, ras_object=bald_eagle)
    RasUnsteady.update_unsteady_parameters(muncie_plan_file, modifications, ras_object=muncie)
    print("Updated unsteady flow parameters for both projects")
    print()

    # Example 4: Execute plans for both projects simultaneously in separate compute folders
    print("Example 4: Executing plans for both projects simultaneously in separate compute folders")
    
    # Create compute folders
    bald_eagle_compute_folder = bald_eagle_path.parent / "compute_bald_eagle"
    muncie_compute_folder = muncie_path.parent / "compute_muncie"
    
    # Remove existing compute folders if they exist
    for folder in [bald_eagle_compute_folder, muncie_compute_folder]:
        if folder.exists():
            shutil.rmtree(folder)
        folder.mkdir(parents=True, exist_ok=True)
    
    with ThreadPoolExecutor(max_workers=2) as executor:
        futures = [
            executor.submit(execute_plan, new_bald_eagle_plan, bald_eagle, bald_eagle_compute_folder),
            executor.submit(execute_plan, new_muncie_plan, muncie, muncie_compute_folder)
        ]
        
        results = {}
        for future in futures:
            plan_number, success = future.result()
            results[plan_number] = success

    print("Execution results:")
    for plan_number, success in results.items():
        print(f"Plan {plan_number} execution: {'Successful' if success else 'Failed'}")
    print()

    # Example 5: Get and print results paths
    print("Example 5: Getting results paths")
    bald_eagle_results = RasPlan.get_results_path(new_bald_eagle_plan, ras_object=bald_eagle)
    muncie_results = RasPlan.get_results_path(new_muncie_plan, ras_object=muncie)

    if bald_eagle_results:
        print(f"Results for Bald Eagle Creek plan {new_bald_eagle_plan} are located at: {bald_eagle_results}")
    else:
        print(f"No results found for Bald Eagle Creek plan {new_bald_eagle_plan}")

    if muncie_results:
        print(f"Results for Muncie plan {new_muncie_plan} are located at: {muncie_results}")
    else:
        print(f"No results found for Muncie plan {new_muncie_plan}")

    print("\nNote: The original project folders can now be edited while the compute operations are running in separate folders.")

if __name__ == "__main__":
    main()
==================================================

File: c:\GH\ras_commander\examples\example_projects.csv
==================================================
Category,Project
1D Sediment Transport,BSTEM - Simple Example
1D Sediment Transport,Dredging Example
1D Sediment Transport,Reservoir Video Tutorial
1D Sediment Transport,SIAM Example
1D Sediment Transport,Simple Sediment Transport Example
1D Sediment Transport,Unsteady Sediment with Concentration Rules
1D Sediment Transport,Video Tutorial (Sediment Intro)
1D Steady Flow Hydraulics,Baxter RAS Mapper
1D Steady Flow Hydraulics,Chapter 4 Example Data
1D Steady Flow Hydraulics,ConSpan Culvert
1D Steady Flow Hydraulics,Mixed Flow Regime Channel
1D Steady Flow Hydraulics,Wailupe GeoRAS
1D Unsteady Flow Hydraulics,Balde Eagle Creek
1D Unsteady Flow Hydraulics,Bridge Hydraulics
1D Unsteady Flow Hydraulics,ContractionExpansionMinorLosses
1D Unsteady Flow Hydraulics,Culvert Hydraulics
1D Unsteady Flow Hydraulics,Culverts with Flap Gates
1D Unsteady Flow Hydraulics,Dam Breaching
1D Unsteady Flow Hydraulics,Elevation Controled Gates
1D Unsteady Flow Hydraulics,Inline Structure with Gated Spillways
1D Unsteady Flow Hydraulics,Internal Stage and Flow Boundary Condition
1D Unsteady Flow Hydraulics,JunctionHydraulics
1D Unsteady Flow Hydraulics,Lateral Strcuture with Gates
1D Unsteady Flow Hydraulics,Lateral Structure connected to a River Reach
1D Unsteady Flow Hydraulics,Lateral Structure Overflow Weir
1D Unsteady Flow Hydraulics,Lateral Structure with Culverts
1D Unsteady Flow Hydraulics,Lateral Structure with Culverts and Gates
1D Unsteady Flow Hydraulics,Levee Breaching
1D Unsteady Flow Hydraulics,Mixed Flow Regime
1D Unsteady Flow Hydraulics,Multiple Reaches with Hydraulic Structures
1D Unsteady Flow Hydraulics,NavigationDam
1D Unsteady Flow Hydraulics,Pumping Station
1D Unsteady Flow Hydraulics,Pumping Station with Rules
1D Unsteady Flow Hydraulics,Rule Operations
1D Unsteady Flow Hydraulics,Simplified Physical Breaching
1D Unsteady Flow Hydraulics,Storage Area Hydraulic Connection
1D Unsteady Flow Hydraulics,UngagedAreaInflows
1D Unsteady Flow Hydraulics,Unsteady Flow Encroachment Analysis
2D Sediment Transport,Chippewa_2D
2D Unsteady Flow Hydraulics,BaldEagleCrkMulti2D
2D Unsteady Flow Hydraulics,Muncie
Applications Guide,Example 1 - Critical Creek
Applications Guide,Example 10 - Stream Junction
Applications Guide,Example 11 - Bridge Scour
Applications Guide,Example 12 - Inline Structure
Applications Guide,Example 13 - Singler Bridge (WSPRO)
Applications Guide,Example 14 - Ice Covered River
Applications Guide,Example 15 - Split Flow Junction with Lateral Weir
Applications Guide,Example 16 - Channel Modification
Applications Guide,Example 17 - Unsteady Flow Application
Applications Guide,Example 18 - Advanced Inline Structure
Applications Guide,Example 19 - Hydrologic Routing - ModPuls
Applications Guide,Example 2 - Beaver Creek
Applications Guide,Example 20 - HagerLatWeir
Applications Guide,Example 21 - Overflow Gates
Applications Guide,Example 22 - Groundwater Interflow
Applications Guide,Example 23 - Urban Modeling
Applications Guide,Example 24 - Mannings-n-Calibration
Applications Guide,Example 3 - Single Culvert
Applications Guide,Example 4 - Multiple Culverts
Applications Guide,Example 5 - Multiple Openings
Applications Guide,Example 6 - Floodway Determination
Applications Guide,Example 7 - Multiple Plans
Applications Guide,Example 8 - Looped Network
Applications Guide,Example 9 - Mixed Flow Analysis
Water Quality,Nutrient Example

==================================================

File: c:\GH\ras_commander\ras_commander\RasCommander.py
==================================================
"""
Execution operations for running HEC-RAS simulations using subprocess.
Based on the HEC-Commander project's "Command Line is All You Need" approach, leveraging the -c compute flag to run HEC-RAS and orchestrating changes directly in the RAS input files to achieve automation outcomes. 
"""

import os
import subprocess
import shutil
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed
from .RasPrj import ras, RasPrj, init_ras_project, get_ras_exe
from .RasPlan import RasPlan
from .RasGeo import RasGeo
from .RasUtils import RasUtils
import subprocess
import os
import logging
import time
import pandas as pd
from threading import Thread, Lock
import queue
from pathlib import Path
import shutil
import queue
from threading import Thread, Lock
import time


class RasCommander:
    @staticmethod
    def compute_plan(
        plan_number,
        compute_folder=None, 
        ras_object=None
        ):
        """
        Execute a HEC-RAS plan.

        Args:
            plan_number (str, Path): The plan number to execute (e.g., "01", "02") or the full path to the plan file.
            compute_folder (str, Path, optional): Name of the folder or full path for computation.
                If a string is provided, it will be created in the same parent directory as the project folder.
                If a full path is provided, it will be used as is.
                If the compute_folder already exists, a ValueError will be raised to prevent overwriting.
            ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.

        Returns:
            bool: True if the execution was successful, False otherwise.

        Raises:
            ValueError: If the specified compute_folder already exists and is not empty.

        Example:
            # Execute plan "01" in the current project folder
            RasCommander.compute_plan("01")

            # Execute plan "02" in a new compute folder
            RasCommander.compute_plan("02", compute_folder="ComputeRun1")

            # Execute a specific plan file in a new compute folder
            RasCommander.compute_plan(r"C:\path\to\plan.p01.hdf", compute_folder="ComputeRun2")

        Notes:
            When using a compute_folder:
            - A new RasPrj object is created for the computation.
            - The entire project is copied to the new folder before execution.
            - Results will be stored in the new folder, preserving the original project.
        """
        # Initialize RasPrj object with the default "ras" object if no specific object is provided
        ras_obj = ras_object or ras
        ras_obj.check_initialized()
        
        # Determine the compute folder path and plan path
        if compute_folder is not None:
            compute_folder = Path(ras_obj.project_folder).parent / compute_folder if isinstance(compute_folder, str) else Path(compute_folder)
            
            # Check if the compute folder exists and is empty
            if compute_folder.exists() and any(compute_folder.iterdir()):
                raise ValueError(f"Compute folder '{compute_folder}' exists and is not empty. Please ensure the compute folder is empty before proceeding.")
            elif not compute_folder.exists():
                shutil.copytree(ras_obj.project_folder, compute_folder)
            
            # Initialize a new RAS project in the compute folder
            compute_ras = RasPrj()
            compute_ras.initialize(compute_folder, ras_obj.ras_exe_path)
            compute_prj_path = compute_ras.prj_file
        else:
            compute_ras = ras_obj
            compute_prj_path = ras_obj.prj_file

        # Determine the plan path
        compute_plan_path = Path(plan_number) if isinstance(plan_number, (str, Path)) and Path(plan_number).is_file() else RasPlan.get_plan_path(plan_number, compute_ras)

        if not compute_prj_path or not compute_plan_path:
            print(f"Error: Could not find project file or plan file for plan {plan_number}")
            return False

        # Prepare the command for HEC-RAS execution
        cmd = f'"{compute_ras.ras_exe_path}" -c "{compute_prj_path}" "{compute_plan_path}"'
        print("Running HEC-RAS from the Command Line:")
        print(f"Running command: {cmd}")

        # Execute the HEC-RAS command
        start_time = time.time()
        try:
            subprocess.run(cmd, check=True, shell=True, capture_output=True, text=True)
            end_time = time.time()
            run_time = end_time - start_time
            print(f"HEC-RAS execution completed for plan: {plan_number}")
            print(f"Total run time for plan {plan_number}: {run_time:.2f} seconds")
            return True
        except subprocess.CalledProcessError as e:
            end_time = time.time()
            run_time = end_time - start_time
            print(f"Error running plan: {plan_number}")
            print(f"Error message: {e.output}")
            print(f"Total run time for plan {plan_number}: {run_time:.2f} seconds")
            return False

        ras_obj = ras_object or ras
        ras_obj.plan_df = ras_obj.get_plan_entries()
        ras_obj.geom_df = ras_obj.get_geom_entries()
        ras_obj.flow_df = ras_obj.get_flow_entries()
        ras_obj.unsteady_df = ras_obj.get_unsteady_entries()


    def compute_test_mode(
        plan_numbers=None, 
        folder_suffix="[Test]", 
        clear_geompre=False, 
        max_cores=None, 
        ras_object=None
    ):
        """
        Execute HEC-RAS plans in test mode.

        This function creates a separate test folder, copies the project there, and executes the specified plans.
        It allows for isolated testing without affecting the original project files.

        Args:
            plan_numbers (list of str, optional): List of plan numbers to execute. 
                If None, all plans will be executed. Default is None.
            folder_suffix (str, optional): Suffix to append to the test folder name. 
                Defaults to "[Test]".
            clear_geompre (bool, optional): Whether to clear geometry preprocessor files.
                Defaults to False.
            max_cores (int, optional): Maximum number of cores to use for each plan.
                If None, the current setting is not changed. Default is None.
            ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.

        Returns:
            None

        Example:
            Run all plans: RasCommander.compute_test_mode()
            Run specific plans: RasCommander.compute_test_mode(plan_numbers=["01", "03", "05"])
            Run plans with a custom folder suffix: RasCommander.compute_test_mode(folder_suffix="[TestRun]")
            Run plans and clear geometry preprocessor files: RasCommander.compute_test_mode(clear_geompre=True)
            Run plans with a specific number of cores: RasCommander.compute_test_mode(max_cores=4)
            
        Notes:
            - This function executes plans in a separate folder for isolated testing.
            - If plan_numbers is not provided, all plans in the project will be executed.
            - The function does not change the geometry preprocessor and IB tables settings.  
                - To force recomputing of geometry preprocessor and IB tables, use the clear_geompre=True option.
            - Plans are executed sequentially.
        """
        
        # This line of code is used to initialize the RasPrj object with the default "ras" object if no specific object is provided.
        ras_obj = ras_object or ras
        # This line of code is used to check if the RasPrj object is initialized.
        ras_obj.check_initialized()
        
        print("Starting the compute_test_mode...")
           
        # Use the project folder from the ras object
        project_folder = ras_obj.project_folder

        # Check if the project folder exists
        if not project_folder.exists():
            print(f"Error: Project folder '{project_folder}' does not exist.")
            return

        # Create test folder with the specified suffix in the same directory as the project folder
        compute_folder = project_folder.parent / f"{project_folder.name} {folder_suffix}"
        print(f"Creating the test folder: {compute_folder}...")

        # Check if the compute folder exists and is empty
        if compute_folder.exists():
            if any(compute_folder.iterdir()):
                raise ValueError(
                    f"Compute folder '{compute_folder}' exists and is not empty. "
                    "Please ensure the compute folder is empty before proceeding."
                )
        else:
            try:
                shutil.copytree(project_folder, compute_folder)
            except FileNotFoundError:
                print(f"Error: Unable to copy project folder. Source folder '{project_folder}' not found.")
                return
            except PermissionError:
                print(f"Error: Permission denied when trying to create or copy to '{compute_folder}'.")
                return
            except Exception as e:
                print(f"Error occurred while copying project folder: {str(e)}")
                return

        # Initialize a new RAS project in the compute folder
        try:
            compute_ras = RasPrj()
            compute_ras.initialize(compute_folder, ras_obj.ras_exe_path)
            compute_prj_path = compute_ras.prj_file
        except Exception as e:
            print(f"Error initializing RAS project in compute folder: {str(e)}")
            return

        if not compute_prj_path:
            print("Project file not found.")
            return
        print(f"Project file found: {compute_prj_path}")

        # Get plan entries
        print("Getting plan entries...")
        try:
            ras_compute_plan_entries = compute_ras.plan_df
            print("Retrieved plan entries successfully.")
        except Exception as e:
            print(f"Error retrieving plan entries: {str(e)}")
            return

        # Filter plans if plan_numbers is provided
        if plan_numbers:
            ras_compute_plan_entries = ras_compute_plan_entries[
                ras_compute_plan_entries['plan_number'].isin(plan_numbers)
            ]
            print(f"Filtered plans to execute: {plan_numbers}")

        # Optimize by iterating once to clear geompre files and set max cores
        if clear_geompre or max_cores is not None:
            print("Processing geometry preprocessor files and core settings...")
            for plan_file in ras_compute_plan_entries['full_path']:
                if clear_geompre:
                    try:
                        RasGeo.clear_geompre_files(plan_file)
                        print(f"Cleared geometry preprocessor files for {plan_file}")
                    except Exception as e:
                        print(f"Error clearing geometry preprocessor files for {plan_file}: {str(e)}")
                if max_cores is not None:
                    try:
                        RasPlan.set_num_cores(plan_file, num_cores=max_cores)
                        print(f"Set max cores to {max_cores} for {plan_file}")
                    except Exception as e:
                        print(f"Error setting max cores for {plan_file}: {str(e)}")
            print("Geometry preprocessor files and core settings processed successfully.")

        # Run plans sequentially
        print("Running selected plans sequentially...")
        for _, plan in ras_compute_plan_entries.iterrows():
            plan_number = plan["plan_number"]
            start_time = time.time()
            try:
                RasCommander.compute_plan(plan_number, ras_object=compute_ras)
            except Exception as e:
                print(f"Error computing plan {plan_number}: {str(e)}")
            end_time = time.time()
            run_time = end_time - start_time
            print(f"Total run time for plan {plan_number}: {run_time:.2f} seconds")

        print("All selected plans have been executed.")
        print("compute_test_mode completed.")

        ras_obj = ras_object or ras
        ras_obj.plan_df = ras_obj.get_plan_entries()
        ras_obj.geom_df = ras_obj.get_geom_entries()
        ras_obj.flow_df = ras_obj.get_flow_entries()
        ras_obj.unsteady_df = ras_obj.get_unsteady_entries()

    @staticmethod
    def compute_parallel(
        plan_numbers: list[str] | None = None,
        max_workers: int = 2,
        cores_per_run: int = 2,
        ras_object: RasPrj | None = None,
        dest_folder: str | Path | None = None
    ) -> dict[str, bool]:
        """
        Execute HEC-RAS plans in parallel using multiple worker threads.

        This function creates separate worker folders, copies the project to each, and executes the specified plans
        in parallel. It allows for isolated and concurrent execution of multiple plans.

        Args:
            plan_numbers (list[str], optional): List of plan numbers to execute. 
                If None, all plans will be executed. Default is None.
            max_workers (int, optional): Maximum number of worker threads to use.
                Default is 2.
            cores_per_run (int, optional): Number of cores to use for each plan execution.
                Default is 2.
            ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
            dest_folder (str | Path, optional): Destination folder for the final computed results.
                If None, results will be stored in a "[Computed]" folder next to the original project.

        Returns:
            dict[str, bool]: A dictionary with plan numbers as keys and boolean values indicating success (True) or failure (False).

        Raises:
            ValueError: If the destination folder exists and is not empty.
            FileNotFoundError: If a plan file is not found.

        Notes:
            - This function creates separate folders for each worker to ensure isolated execution.
            - Each worker uses its own RAS object to prevent conflicts.
            - Plans are distributed among workers using a queue to ensure efficient parallel processing.
            - The function automatically handles cleanup and consolidation of results after execution.
        
        Revision Notes:
            - Removed redundant variable initializations.
            - Streamlined worker folder creation and RAS object initialization.
            - Optimized the consolidation of results from worker folders.
            - Removed debug print statements for cleaner execution logs.
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()

        project_folder = ras_obj.project_folder

        if dest_folder is not None:
            dest_folder_path = Path(dest_folder)
            if dest_folder_path.exists():
                if any(dest_folder_path.iterdir()):
                    raise ValueError(
                        f"\nError: Destination folder already exists: '{dest_folder_path}'\n"
                        f"To prevent accidental overwriting of results, this operation cannot proceed.\n"
                        f"Please take one of the following actions:\n"
                        f"1. Delete the folder manually and run the operation again.\n"
                        f"2. Use a different destination folder name.\n"
                        f"3. Programmatically delete the folder before calling compute_parallel, like this:\n"
                        f"   if Path('{dest_folder_path}').exists():\n"
                        f"       shutil.rmtree('{dest_folder_path}')\n"
                        f"This safety measure ensures that you don't inadvertently overwrite existing results."
                    )
            else:
                try:
                    dest_folder_path.mkdir(parents=True, exist_ok=True)
                except PermissionError:
                    raise PermissionError(f"Unable to create destination folder '{dest_folder_path}'. Permission denied.")
            try:
                shutil.copytree(project_folder, dest_folder_path, dirs_exist_ok=True)
            except shutil.Error as e:
                raise IOError(f"Error copying project to destination folder: {str(e)}")
            project_folder = dest_folder_path  # Update project_folder to the new destination

        if plan_numbers:
            if isinstance(plan_numbers, str):
                plan_numbers = [plan_numbers]
            ras_obj.plan_df = ras_obj.plan_df[ras_obj.plan_df['plan_number'].isin(plan_numbers)]

        num_plans = len(ras_obj.plan_df)
        max_workers = min(max_workers, num_plans) if num_plans > 0 else 1
        print(f"Adjusted max_workers to {max_workers} based on the number of plans: {num_plans}")

        # Clean up existing worker folders
        for worker_id in range(1, max_workers + 1):
            worker_folder = project_folder.parent / f"{project_folder.name} [Worker {worker_id}]"
            if worker_folder.exists():
                shutil.rmtree(worker_folder)
                print(f"Removed existing worker folder: {worker_folder}")

        # Create worker folders and initialize RAS objects
        worker_ras_objects = {}
        for worker_id in range(1, max_workers + 1):
            worker_folder = project_folder.parent / f"{project_folder.name} [Worker {worker_id}]"
            shutil.copytree(project_folder, worker_folder)
            
            worker_ras_instance = RasPrj()
            worker_ras_instance = init_ras_project(
                ras_project_folder=worker_folder,
                ras_version=ras_obj.ras_exe_path,
                ras_instance=worker_ras_instance
            )
            worker_ras_objects[worker_id] = worker_ras_instance

        # Prepare plan queue with plan numbers
        plan_queue = queue.Queue()
        for plan_number in ras_obj.plan_df['plan_number']:
            plan_queue.put(plan_number)

        # Initialize results dictionary and thread locks
        execution_results: dict[str, bool] = {}
        results_lock = Lock()
        queue_lock = Lock()

        def worker_thread(worker_id: int):
            worker_ras_obj = worker_ras_objects[worker_id]
            while True:
                with queue_lock:
                    if plan_queue.empty():
                        break
                    plan_number = plan_queue.get()
                
                try:
                    plan_path = RasPlan.get_plan_path(plan_number, ras_object=worker_ras_obj)
                    if not plan_path:
                        raise FileNotFoundError(f"Plan file not found: {plan_number}")

                    RasPlan.set_num_cores(plan_number, cores_per_run, ras_object=worker_ras_obj)

                    print(f"Worker {worker_id} executing plan {plan_number}")

                    success = RasCommander.compute_plan(plan_number, ras_object=worker_ras_obj)

                    with results_lock:
                        execution_results[plan_number] = success
                    print(f"Completed: Plan {plan_number} in worker {worker_id}")
                except Exception as e:
                    with results_lock:
                        execution_results[plan_number] = False
                    print(f"Failed: Plan {plan_number} in worker {worker_id}. Error: {str(e)}")

        # Start worker threads
        worker_threads = []
        for worker_id in range(1, max_workers + 1):
            worker_ras_instance = worker_ras_objects[worker_id]
            worker_ras_instance.plan_df = worker_ras_instance.get_plan_entries()
            worker_ras_instance.geom_df = worker_ras_instance.get_geom_entries()
            worker_ras_instance.flow_df = worker_ras_instance.get_flow_entries()
            worker_ras_instance.unsteady_df = worker_ras_instance.get_unsteady_entries()
            
            thread = Thread(target=worker_thread, args=(worker_id,))
            thread.start()
            worker_threads.append(thread)
            print(f"Started worker thread {worker_id}")

        # Wait for all threads to complete
        for worker_id, thread in enumerate(worker_threads, 1):
            thread.join()
            print(f"Worker thread {worker_id} has completed.")

        # Consolidate results
        final_dest_folder = dest_folder_path if dest_folder is not None else project_folder.parent / f"{project_folder.name} [Computed]"
        final_dest_folder.mkdir(exist_ok=True)
        print(f"Final destination for computed results: {final_dest_folder}")

        for worker_id, worker_ras in worker_ras_objects.items():
            worker_folder = worker_ras.project_folder
            try:
                for item in worker_folder.iterdir():
                    dest_path = final_dest_folder / item.name
                    if dest_path.exists():
                        if dest_path.is_dir():
                            shutil.rmtree(dest_path)
                        else:
                            dest_path.unlink()
                    shutil.move(str(item), final_dest_folder)
                shutil.rmtree(worker_folder)
                print(f"Moved results and removed worker folder: {worker_folder}")
            except Exception as e:
                print(f"Error moving results from {worker_folder} to {final_dest_folder}: {str(e)}")

        # Print execution results for each plan
        print("\nExecution Results:")
        for plan_number, success in execution_results.items():
            print(f"Plan {plan_number}: {'Successful' if success else 'Failed'}")

        return execution_results
==================================================

File: c:\GH\ras_commander\ras_commander\RasExamples.py
==================================================
import os
import requests
import zipfile
import pandas as pd
from pathlib import Path
import shutil
from typing import Union, List
import csv
from datetime import datetime

class RasExamples:
    """
    A class for quickly loading HEC-RAS example projects for testing and development of ras_commander.

    This class provides functionality to download, extract, and manage HEC-RAS example projects.
    It supports both default HEC-RAS example projects and custom projects from user-provided URLs.

    Expected folder structure:              Notes:
    ras_commander/
    ├── examples/                           # This is examples_dir
    │   ├── example_projects/               # This is projects_dir
    │   │   ├── Balde Eagle Creek/          # Individual Projects from Zip file
    │   │   ├── Muncie/                 
    │   │   └── ...
    │   ├── Example_Projects_6_5.zip        # HEC-RAS Example Projects zip file will be downloaded here
    │   ├── example_projects.csv            # CSV file containing cached project metadata
    │   └── 01_project_initialization.py    # ras_commander library examples are also at this level
    │   └── ...
    └── ras_commander/                      # Code for the ras_commander library

    Attributes:
        base_url (str): Base URL for downloading HEC-RAS example projects.
        valid_versions (list): List of valid HEC-RAS versions for example projects.
        base_dir (Path): Base directory for storing example projects.
        examples_dir (Path): Directory for example projects and related files. (assumed to be parent )
        projects_dir (Path): Directory where example projects are extracted.
        zip_file_path (Path): Path to the downloaded zip file.
        folder_df (pd.DataFrame): DataFrame containing folder structure information.
        csv_file_path (Path): Path to the CSV file for caching project metadata.

    
    Future Improvements:
    - Implement the ability for user-provided example projects (provided as a zip file) for their own repeatable examples. 
    - If the zip file is in the same folder structure as the HEC-RAS example projects, simple replace Example_Projects_6_5.zip and the folder structure will be automatically extracted from the zip file.
    - The actual RAS example projects haven't been updated much, but there is the structure here to handle future versions. Although this version of the code is probably fine for a few years, until HEC-RAS 2025 comes out. 
       
    """

    def __init__(self):
        """
        Initialize the RasExamples class.

        This constructor sets up the necessary attributes and paths for managing HEC-RAS example projects.
        It initializes the base URL for downloads, valid versions, directory paths, and other essential
        attributes. It also creates the projects directory if it doesn't exist and loads the project data.

        The method also prints the location of the example projects folder and calls _load_project_data()
        to initialize the project data.
        """
        self.base_url = 'https://github.com/HydrologicEngineeringCenter/hec-downloads/releases/download/'
        self.valid_versions = [
            "6.5", "6.4.1", "6.3.1", "6.3", "6.2", "6.1", "6.0",
            "5.0.7", "5.0.6", "5.0.5", "5.0.4", "5.0.3", "5.0.1", "5.0",
            "4.1", "4.0", "3.1.3", "3.1.2", "3.1.1", "3.0", "2.2"
        ]
        self.base_dir = Path.cwd()
        self.examples_dir = self.base_dir
        self.projects_dir = self.examples_dir / 'example_projects'
        self.zip_file_path = None
        self.folder_df = None
        self.csv_file_path = self.examples_dir / 'example_projects.csv'

        self.projects_dir.mkdir(parents=True, exist_ok=True)
        print(f"Example projects folder: {self.projects_dir}")
        self._load_project_data()

    def _load_project_data(self):
        """
        Load project data from CSV if up-to-date, otherwise extract from zip.

        Checks for existing CSV file and compares modification times with zip file.
        Extracts folder structure if necessary and saves to CSV.
        """
        self._find_zip_file()
        
        if not self.zip_file_path:
            print("No example projects zip file found. Downloading...")
            self.get_example_projects()
        
        zip_modified_time = os.path.getmtime(self.zip_file_path)
        
        if self.csv_file_path.exists():
            csv_modified_time = os.path.getmtime(self.csv_file_path)
            
            if csv_modified_time >= zip_modified_time:
                print("Loading project data from CSV...")
                self.folder_df = pd.read_csv(self.csv_file_path)
                print(f"Loaded {len(self.folder_df)} projects from CSV, use list_categories() and list_projects() to explore them")
                return

        print("Extracting folder structure from zip file...")
        self._extract_folder_structure()
        self._save_to_csv()

    def _find_zip_file(self):
        """Locate the example projects zip file in the examples directory."""
        for version in self.valid_versions:
            potential_zip = self.examples_dir / f"Example_Projects_{version.replace('.', '_')}.zip"
            if potential_zip.exists():
                self.zip_file_path = potential_zip
                print(f"Found zip file: {self.zip_file_path}")
                break

    def _extract_folder_structure(self):
        """
        Extract folder structure from the zip file.

        Populates folder_df with category and project information.
        """
        folder_data = []
        try:
            with zipfile.ZipFile(self.zip_file_path, 'r') as zip_ref:
                for file in zip_ref.namelist():
                    parts = Path(file).parts
                    if len(parts) > 2:
                        folder_data.append({
                            'Category': parts[1],
                            'Project': parts[2]
                        })
            
            self.folder_df = pd.DataFrame(folder_data).drop_duplicates()
            print(f"Extracted {len(self.folder_df)} projects")
            print("folder_df:")
            display(self.folder_df)
        except Exception as e:
            print(f"An error occurred while extracting the folder structure: {str(e)}")
            self.folder_df = pd.DataFrame(columns=['Category', 'Project'])

    def _save_to_csv(self):
        """Save the extracted folder structure to CSV file."""
        if self.folder_df is not None and not self.folder_df.empty:
            self.folder_df.to_csv(self.csv_file_path, index=False)
            print(f"Saved project data to {self.csv_file_path}")

    def get_example_projects(self, version_number='6.5'):
        """
        Download and extract HEC-RAS example projects for a specified version.

        Args:
            version_number (str): HEC-RAS version number. Defaults to '6.5'.

        Returns:
            Path: Path to the extracted example projects.

        Raises:
            ValueError: If an invalid version number is provided.
        """
        print(f"Getting example projects for version {version_number}")
        if version_number not in self.valid_versions:
            raise ValueError(f"Invalid version number. Valid versions are: {', '.join(self.valid_versions)}")

        zip_url = f"{self.base_url}1.0.31/Example_Projects_{version_number.replace('.', '_')}.zip"
        
        self.examples_dir.mkdir(parents=True, exist_ok=True)
        
        self.zip_file_path = self.examples_dir / f"Example_Projects_{version_number.replace('.', '_')}.zip"

        if not self.zip_file_path.exists():
            print(f"Downloading HEC-RAS Example Projects from {zip_url}. \n The file is over 400 MB, so it may take a few minutes to download....")
            response = requests.get(zip_url)
            with open(self.zip_file_path, 'wb') as file:
                file.write(response.content)
            print(f"Downloaded to {self.zip_file_path}")
        else:
            print("HEC-RAS Example Projects zip file already exists. Skipping download.")

        self._load_project_data()
        return self.projects_dir

    def list_categories(self):
        """
        List all categories of example projects.

        Returns:
            list: Available categories.
        """
        if self.folder_df is None or 'Category' not in self.folder_df.columns:
            print("No categories available. Make sure the zip file is properly loaded.")
            return []
        categories = self.folder_df['Category'].unique()
        print(f"Available categories: {', '.join(categories)}")
        return categories.tolist()

    def list_projects(self, category=None):
        """
        List all projects or projects in a specific category.

        Args:
            category (str, optional): Category to filter projects.

        Returns:
            list: List of project names.
        """
        if self.folder_df is None:
            print("No projects available. Make sure the zip file is properly loaded.")
            return []
        if category:
            projects = self.folder_df[self.folder_df['Category'] == category]['Project'].unique()
        else:
            projects = self.folder_df['Project'].unique()
        return projects.tolist()

    def extract_project(self, project_names: Union[str, List[str]]):
        """
        Extract one or more specific projects from the zip file.

        Args:
            project_names (str or List[str]): Name(s) of the project(s) to extract.

        Returns:
            List[Path]: List of paths to the extracted project(s).

        Raises:
            ValueError: If any project is not found.
        """
        if isinstance(project_names, str):
            project_names = [project_names]

        extracted_paths = []

        for project_name in project_names:
            print("----- RasExamples Extracting Project -----")
            print(f"Extracting project '{project_name}'")
            project_path = self.projects_dir / project_name

            if project_path.exists():
                print(f"Project '{project_name}' already exists. Deleting existing folder...")
                shutil.rmtree(project_path)
                print(f"Existing folder for project '{project_name}' has been deleted.")

            if self.folder_df is None or self.folder_df.empty:
                raise ValueError("No project information available. Make sure the zip file is properly loaded.")

            project_info = self.folder_df[self.folder_df['Project'] == project_name]
            if project_info.empty:
                raise ValueError(f"Project '{project_name}' not found in the zip file.")

            category = project_info['Category'].iloc[0]
            
            # Ensure the project directory exists
            project_path.mkdir(parents=True, exist_ok=True)

            try:
                with zipfile.ZipFile(self.zip_file_path, 'r') as zip_ref:
                    for file in zip_ref.namelist():
                        parts = Path(file).parts
                        if len(parts) > 2 and parts[2] == project_name:
                            # Remove the first two levels (category and project name)
                            relative_path = Path(*parts[3:])
                            extract_path = project_path / relative_path
                            if file.endswith('/'):
                                extract_path.mkdir(parents=True, exist_ok=True)
                            else:
                                extract_path.parent.mkdir(parents=True, exist_ok=True)
                                with zip_ref.open(file) as source, open(extract_path, "wb") as target:
                                    shutil.copyfileobj(source, target)

                print(f"Successfully extracted project '{project_name}' to {project_path}")
                extracted_paths.append(project_path)
            except zipfile.BadZipFile:
                print(f"Error: The file {self.zip_file_path} is not a valid zip file.")
            except FileNotFoundError:
                print(f"Error: The file {self.zip_file_path} was not found.")
            except Exception as e:
                print(f"An unexpected error occurred while extracting the project: {str(e)}")
            #print("----- RasExamples Extraction Complete -----")
        return extracted_paths

    def is_project_extracted(self, project_name):
        """
        Check if a specific project is already extracted.

        Args:
            project_name (str): Name of the project to check.

        Returns:
            bool: True if the project is extracted, False otherwise.
        """
        project_path = self.projects_dir / project_name
        return project_path.exists()

    def clean_projects_directory(self):
        """Remove all extracted projects from the example_projects directory."""
        print(f"Cleaning projects directory: {self.projects_dir}")
        if self.projects_dir.exists():
            shutil.rmtree(self.projects_dir)
        self.projects_dir.mkdir(parents=True, exist_ok=True)
        print("Projects directory cleaned.")

# Example usage:
# ras_examples = RasExamples()
# extracted_paths = ras_examples.extract_project(["Bald Eagle Creek", "BaldEagleCrkMulti2D", "Muncie"])
# for path in extracted_paths:
#     print(f"Extracted to: {path}")

==================================================

File: c:\GH\ras_commander\ras_commander\RasGeo.py
==================================================
"""
Operations for handling geometry files in HEC-RAS projects.
"""
from pathlib import Path
from typing import List, Union
from .RasPlan import RasPlan
from .RasPrj import ras
import re

class RasGeo:
    """
    A class for operations on HEC-RAS geometry files.
    """
    
    @staticmethod
    def clear_geompre_files(
        plan_files: Union[str, Path, List[Union[str, Path]]] = None,
        ras_object = None
    ) -> None:
        """
        Clear HEC-RAS geometry preprocessor files for specified plan files or all plan files in the project directory.
        
        Limitations/Future Work:
        - This function only deletes the geometry preprocessor file.
        - It does not clear the IB tables.
        - It also does not clear geometry preprocessor tables from the geometry HDF.
        - All of these features will need to be added to reliably remove geometry preprocessor files for 1D and 2D projects.
        
        Parameters:
            plan_files (Union[str, Path, List[Union[str, Path]]], optional): 
                Full path(s) to the HEC-RAS plan file(s) (.p*).
                If None, clears all plan files in the project directory.
            ras_object: An optional RAS object instance.
        
        Returns:
            None
        
        Examples:
            # Clear all geometry preprocessor files in the project directory
            RasGeo.clear_geompre_files()
            
            # Clear a single plan file
            RasGeo.clear_geompre_files(r'path/to/plan.p01')
            
            # Clear multiple plan files
            RasGeo.clear_geompre_files([r'path/to/plan1.p01', r'path/to/plan2.p02'])

        Note:
            This function updates the ras object's geometry dataframe after clearing the preprocessor files.
        """
        ## Explicit Function Steps
        # 1. Initialize the ras_object, defaulting to the global ras if not provided.
        # 2. Define a helper function to clear a single geometry preprocessor file.
        # 3. Determine the list of plan files to process based on the input.
        # 4. Iterate over each plan file and clear its geometry preprocessor file.
        ras_obj = ras_object or ras
        ras_obj.check_initialized()
        
        def clear_single_file(plan_file: Union[str, Path], ras_obj) -> None:
            plan_path = Path(plan_file)
            geom_preprocessor_suffix = '.c' + ''.join(plan_path.suffixes[1:]) if plan_path.suffixes else '.c'
            geom_preprocessor_file = plan_path.with_suffix(geom_preprocessor_suffix)
            if geom_preprocessor_file.exists():
                try:
                    print(f"Deleting geometry preprocessor file: {geom_preprocessor_file}")
                    geom_preprocessor_file.unlink()
                    print("File deletion completed successfully.")
                except PermissionError:
                    raise PermissionError(f"Unable to delete geometry preprocessor file: {geom_preprocessor_file}. Permission denied.")
                except OSError as e:
                    raise OSError(f"Error deleting geometry preprocessor file: {geom_preprocessor_file}. {str(e)}")
            else:
                print(f"No geometry preprocessor file found for: {plan_file}")
        
        if plan_files is None:
            print("Clearing all geometry preprocessor files in the project directory.")
            plan_files_to_clear = list(ras_obj.project_folder.glob(r'*.p*'))
        elif isinstance(plan_files, (str, Path)):
            plan_files_to_clear = [plan_files]
        elif isinstance(plan_files, list):
            plan_files_to_clear = plan_files
        else:
            raise ValueError("Invalid input. Please provide a string, Path, list of paths, or None.")
        
        for plan_file in plan_files_to_clear:
            clear_single_file(plan_file, ras_obj)
        ras_obj.geom_df = ras_obj.get_geom_entries()


==================================================

File: c:\GH\ras_commander\ras_commander\RasPlan.py
==================================================
"""
Operations for modifying and updating HEC-RAS plan files.

"""
import re
from pathlib import Path
import shutil
from typing import Union, Optional
import pandas as pd
from .RasPrj import RasPrj, ras
from .RasUtils import RasUtils

class RasPlan:
    """
    A class for operations on HEC-RAS plan files.
    """
    
    @staticmethod
    def set_geom(plan_number: Union[str, int], new_geom: Union[str, int], ras_object=None) -> pd.DataFrame:
        """
        Set the geometry for the specified plan.

        Parameters:
            plan_number (Union[str, int]): The plan number to update.
            new_geom (Union[str, int]): The new geometry number to set.
            ras_object: An optional RAS object instance.

        Returns:
            pd.DataFrame: The updated geometry DataFrame.

        Example:
            updated_geom_df = RasPlan.set_geom('02', '03')

        Note:
            This function updates the ras object's dataframes after modifying the project structure.
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()

        # Ensure plan_number and new_geom are strings
        plan_number = str(plan_number).zfill(2)
        new_geom = str(new_geom).zfill(2)

        # Before doing anything, make sure the plan, geom, flow, and unsteady dataframes are current
        ras_obj.plan_df = ras_obj.get_plan_entries()
        ras_obj.geom_df = ras_obj.get_geom_entries()
        ras_obj.flow_df = ras_obj.get_flow_entries()
        ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
        
        # List the geom_df for debugging
        print("Current geometry DataFrame within the function:")
        print(ras_obj.geom_df)
        
        if new_geom not in ras_obj.geom_df['geom_number'].values:
            raise ValueError(f"Geometry {new_geom} not found in project.")

        # Update the geometry for the specified plan
        ras_obj.plan_df.loc[ras_obj.plan_df['plan_number'] == plan_number, 'geom_number'] = new_geom

        print(f"Geometry for plan {plan_number} set to {new_geom}")
        print("Updated plan DataFrame:")
        display(ras_obj.plan_df)

        # Update the project file
        prj_file_path = ras_obj.prj_file
        with open(prj_file_path, 'r') as f:
            lines = f.readlines()

        plan_pattern = re.compile(rf"^Plan File=p{plan_number}", re.IGNORECASE)
        geom_pattern = re.compile(r"^Geom File=g\d+", re.IGNORECASE)
        
        for i, line in enumerate(lines):
            if plan_pattern.match(line):
                for j in range(i+1, len(lines)):
                    if geom_pattern.match(lines[j]):
                        lines[j] = f"Geom File=g{new_geom}\n"
                        break
                break

        with open(prj_file_path, 'w') as f:
            f.writelines(lines)

        print(f"Updated project file with new geometry for plan {plan_number}")

        # Re-initialize the ras object to reflect changes
        ras_obj.initialize(ras_obj.project_folder, ras_obj.ras_exe_path)

        return ras_obj.plan_df

    @staticmethod
    def set_steady(plan_number: str, new_steady_flow_number: str, ras_object=None):
        """
        Apply a steady flow file to a plan file.
        
        Parameters:
        plan_number (str): Plan number (e.g., '02')
        new_steady_flow_number (str): Steady flow number to apply (e.g., '01')
        ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
        
        Returns:
        None

        Raises:
        ValueError: If the specified steady flow number is not found in the project file
        FileNotFoundError: If the specified plan file is not found

        Example:
        >>> RasPlan.set_steady('02', '01')

        Note:
            This function updates the ras object's dataframes after modifying the project structure.
        """
        logging.info(f"Setting steady flow file to {new_steady_flow_number} in Plan {plan_number}")
        ras_obj = ras_object or ras
        ras_obj.check_initialized()
                        
        # Update the flow dataframe in the ras instance to ensure it is current
        ras_obj.flow_df = ras_obj.get_flow_entries()
        
        if new_steady_flow_number not in ras_obj.flow_df['flow_number'].values:
            raise ValueError(f"Steady flow number {new_steady_flow_number} not found in project file.")
        
        # Resolve the full path of the plan file
        plan_file_path = RasPlan.get_plan_path(plan_number, ras_obj)
        if not plan_file_path:
            raise FileNotFoundError(f"Plan file not found: {plan_number}")
        
        with open(plan_file_path, 'r') as f:
            lines = f.readlines()
        with open(plan_file_path, 'w') as f:
            for line in lines:
                if line.startswith("Flow File=f"):
                    f.write(f"Flow File=f{new_steady_flow_number}\n")
                    logging.info(f"Updated Flow File in {plan_file_path} to f{new_steady_flow_number}")
                else:
                    f.write(line)

        ras_obj.plan_df = ras_obj.get_plan_entries()
        ras_obj.geom_df = ras_obj.get_geom_entries()
        ras_obj.flow_df = ras_obj.get_flow_entries()
        ras_obj.unsteady_df = ras_obj.get_unsteady_entries()

    @staticmethod
    def set_unsteady(plan_number: str, new_unsteady_flow_number: str, ras_object=None):
        """
        Apply an unsteady flow file to a plan file.
        
        Parameters:
        plan_number (str): Plan number (e.g., '04')
        new_unsteady_flow_number (str): Unsteady flow number to apply (e.g., '01')
        ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
        
        Returns:
        None

        Raises:
        ValueError: If the specified unsteady number is not found in the project file
        FileNotFoundError: If the specified plan file is not found

        Example:
        >>> RasPlan.set_unsteady('04', '01')

        Note:
            This function updates the ras object's dataframes after modifying the project structure.
        """
        print(f"Setting unsteady flow file from {new_unsteady_flow_number} to {plan_number}")
        
        ras_obj = ras_object or ras
        ras_obj.check_initialized()
        
        # Update the unsteady dataframe in the ras instance to ensure it is current
        ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
        
        if new_unsteady_flow_number not in ras_obj.unsteady_df['unsteady_number'].values:
            raise ValueError(f"Unsteady number {new_unsteady_flow_number} not found in project file.")
        
        # Get the full path of the plan file
        plan_file_path = RasPlan.get_plan_path(plan_number, ras_obj)
        if not plan_file_path:
            raise FileNotFoundError(f"Plan file not found: {plan_number}")
        
        
        # DEV NOTE: THIS WORKS HERE, BUT IN OTHER FUNCTIONS WE DO THIS MANUALLY.  
        # UPDATE OTHER FUNCTIONS TO USE RasUtils.update_plan_file INSTEAD OF REPLICATING THIS CODE.
        
        RasUtils.update_plan_file(plan_file_path, 'Unsteady', new_unsteady_flow_number)
        print(f"Updated unsteady flow file in {plan_file_path} to u{new_unsteady_flow_number}")

        ras_obj.plan_df = ras_obj.get_plan_entries()
        ras_obj.geom_df = ras_obj.get_geom_entries()
        ras_obj.flow_df = ras_obj.get_flow_entries()
        ras_obj.unsteady_df = ras_obj.get_unsteady_entries()

    @staticmethod
    def set_num_cores(plan_number, num_cores, ras_object=None):
        """
        Update the maximum number of cores to use in the HEC-RAS plan file.
        
        Parameters:
        plan_number (str): Plan number (e.g., '02') or full path to the plan file
        num_cores (int): Maximum number of cores to use
        ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
        
        Returns:
        None

        Notes on setting num_cores in HEC-RAS:
        The recommended setting for num_cores is 2 (most efficient) to 8 (most performant)
        More details in the HEC-Commander Repository Blog "Benchmarking is All You Need"
        https://github.com/billk-FM/HEC-Commander/blob/main/Blog/7._Benchmarking_Is_All_You_Need.md
        
        Microsoft Windows has a maximum of 64 cores that can be allocated to a single Ras.exe process. 

        Example:
        >>> # Using plan number
        >>> RasPlan.set_num_cores('02', 4)
        >>> # Using full path to plan file
        >>> RasPlan.set_num_cores('/path/to/project.p02', 4)

        Note:
            This function updates the ras object's dataframes after modifying the project structure.
        """
        print(f"Setting num_cores to {num_cores} in Plan {plan_number}")
        
        ras_obj = ras_object or ras
        ras_obj.check_initialized()
        
        # Determine if plan_number is a path or a plan number
        if Path(plan_number).is_file():
            plan_file_path = Path(plan_number)
            if not plan_file_path.exists():
                raise FileNotFoundError(f"Plan file not found: {plan_file_path}. Please provide a valid plan number or path.")
        else:
            # Update the plan dataframe in the ras instance to ensure it is current
            ras_obj.plan_df = ras_obj.get_prj_entries('Plan')
            
            # Get the full path of the plan file
            plan_file_path = RasPlan.get_plan_path(plan_number, ras_obj)
            if not plan_file_path:
                raise FileNotFoundError(f"Plan file not found: {plan_number}. Please provide a valid plan number or path.")
        
        cores_pattern = re.compile(r"(UNET D1 Cores= )\d+")
        with open(plan_file_path, 'r') as file:
            content = file.read()
        new_content = cores_pattern.sub(rf"\g<1>{num_cores}", content)
        with open(plan_file_path, 'w') as file:
            file.write(new_content)
        print(f"Updated {plan_file_path} with {num_cores} cores.")
        
        ras_obj.plan_df = ras_obj.get_plan_entries()
        ras_obj.geom_df = ras_obj.get_geom_entries()
        ras_obj.flow_df = ras_obj.get_flow_entries()
        ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
        
        
    @staticmethod
    def set_geom_preprocessor(file_path, run_htab, use_ib_tables, ras_object=None):
        """
        Update the simulation plan file to modify the `Run HTab` and `UNET Use Existing IB Tables` settings.
        
        Parameters:
        file_path (str): Path to the simulation plan file (.p06 or similar) that you want to modify.
        run_htab (int): Value for the `Run HTab` setting:
            - `0` : Do not run the geometry preprocessor, use existing geometry tables.
            - `-1` : Run the geometry preprocessor, forcing a recomputation of the geometry tables.
        use_ib_tables (int): Value for the `UNET Use Existing IB Tables` setting:
            - `0` : Use existing interpolation/boundary (IB) tables without recomputing them.
            - `-1` : Do not use existing IB tables, force a recomputation.
        ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
        
        Returns:
        None

        Raises:
        ValueError: If `run_htab` or `use_ib_tables` are not integers or not within the accepted values (`0` or `-1`).
        FileNotFoundError: If the specified file does not exist.
        IOError: If there is an error reading or writing the file.

        Example:
        >>> RasPlan.set_geom_preprocessor('/path/to/project.p06', run_htab=-1, use_ib_tables=0)

        Note:
            This function updates the ras object's dataframes after modifying the project structure.
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()
        
        if run_htab not in [-1, 0]:
            raise ValueError("Invalid value for `Run HTab`. Expected `0` or `-1`.")
        if use_ib_tables not in [-1, 0]:
            raise ValueError("Invalid value for `UNET Use Existing IB Tables`. Expected `0` or `-1`.")
        try:
            print(f"Reading the file: {file_path}")
            with open(file_path, 'r') as file:
                lines = file.readlines()
            print("Updating the file with new settings...")
            updated_lines = []
            for line in lines:
                if line.lstrip().startswith("Run HTab="):
                    updated_line = f"Run HTab= {run_htab} \n"
                    updated_lines.append(updated_line)
                    print(f"Updated 'Run HTab' to {run_htab}")
                elif line.lstrip().startswith("UNET Use Existing IB Tables="):
                    updated_line = f"UNET Use Existing IB Tables= {use_ib_tables} \n"
                    updated_lines.append(updated_line)
                    print(f"Updated 'UNET Use Existing IB Tables' to {use_ib_tables}")
                else:
                    updated_lines.append(line)
            print(f"Writing the updated settings back to the file: {file_path}")
            with open(file_path, 'w') as file:
                file.writelines(updated_lines)
            print("File update completed successfully.")
        except FileNotFoundError:
            raise FileNotFoundError(f"The file '{file_path}' does not exist.")
        except IOError as e:
            raise IOError(f"An error occurred while reading or writing the file: {e}")

        ras_obj.plan_df = ras_obj.get_plan_entries()
        ras_obj.geom_df = ras_obj.get_geom_entries()
        ras_obj.flow_df = ras_obj.get_flow_entries()
        ras_obj.unsteady_df = ras_obj.get_unsteady_entries()

# Get Functions to retrieve file paths for plan, flow, unsteady, geometry and results files

    @staticmethod
    def get_results_path(plan_number: str, ras_object=None) -> Optional[str]:
        """
        Retrieve the results file path for a given HEC-RAS plan number.

        Args:
            plan_number (str): The HEC-RAS plan number for which to find the results path.
            ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.

        Returns:
            Optional[str]: The full path to the results file if found and the file exists, or None if not found.

        Raises:
            RuntimeError: If the project is not initialized.

        Example:
            >>> ras_plan = RasPlan()
            >>> results_path = ras_plan.get_results_path('01')
            >>> if results_path:
            ...     print(f"Results file found at: {results_path}")
            ... else:
            ...     print("Results file not found.")
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()
        
        # Update the plan dataframe in the ras instance to ensure it is current
        ras_obj.plan_df = ras_obj.get_plan_entries()
        
        # Ensure plan_number is a string
        plan_number = str(plan_number)
        
        # Ensure plan_number is formatted as '01', '02', etc.
        plan_number = plan_number.zfill(2)
        
        # print the ras_obj.plan_df dataframe
        print("Plan DataFrame:")
        display(ras_obj.plan_df)
        
        plan_entry = ras_obj.plan_df[ras_obj.plan_df['plan_number'] == plan_number]
        if not plan_entry.empty:
            results_path = plan_entry['HDF_Results_Path'].iloc[0]
            if results_path:
                print(f"Results file for Plan number {plan_number} exists at: {results_path}")
                return results_path
            else:
                print(f"Results file for Plan number {plan_number} does not exist.")
                return None
        else:
            print(f"Plan number {plan_number} not found in the entries.")
            return None
        
    @staticmethod
    def get_plan_path(plan_number: str, ras_object=None) -> Optional[str]:
        """
        Return the full path for a given plan number.
        
        This method ensures that the latest plan entries are included by refreshing
        the plan dataframe before searching for the requested plan number.
        
        Args:
        plan_number (str): The plan number to search for.
        ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
        
        Returns:
        Optional[str]: The full path of the plan file if found, None otherwise.
        
        Raises:
        RuntimeError: If the project is not initialized.

        Example:
        >>> ras_plan = RasPlan()
        >>> plan_path = ras_plan.get_plan_path('01')
        >>> if plan_path:
        ...     print(f"Plan file found at: {plan_path}")
        ... else:
        ...     print("Plan file not found.")
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()
        
        project_name = ras_obj.project_name
        
        # Use updated plan dataframe
        plan_df = ras_obj.get_plan_entries()
        
        plan_path = plan_df[plan_df['plan_number'] == plan_number]
        
        if not plan_path.empty:
            full_path = plan_path['full_path'].iloc[0]
            return full_path
        else:
            print(f"Plan number {plan_number} not found in the updated plan entries.")
            return None

    @staticmethod
    def get_flow_path(flow_number: str, ras_object=None) -> Optional[str]:
        """
        Return the full path for a given flow number.

        Args:
        flow_number (str): The flow number to search for.
        ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.

        Returns:
        Optional[str]: The full path of the flow file if found, None otherwise.

        Raises:
        RuntimeError: If the project is not initialized.

        Example:
        >>> ras_plan = RasPlan()
        >>> flow_path = ras_plan.get_flow_path('01')
        >>> if flow_path:
        ...     print(f"Flow file found at: {flow_path}")
        ... else:
        ...     print("Flow file not found.")
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()
        
        # Use updated flow dataframe
        ras_obj.flow_df = ras_obj.get_prj_entries('Flow')
        
        flow_path = ras_obj.flow_df[ras_obj.flow_df['flow_number'] == flow_number]
        if not flow_path.empty:
            full_path = flow_path['full_path'].iloc[0]
            return full_path
        else:
            print(f"Flow number {flow_number} not found in the updated flow entries.")
            return None

    @staticmethod
    def get_unsteady_path(unsteady_number: str, ras_object=None) -> Optional[str]:
        """
        Return the full path for a given unsteady number.

        Args:
        unsteady_number (str): The unsteady number to search for.
        ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.

        Returns:
        Optional[str]: The full path of the unsteady file if found, None otherwise.

        Raises:
        RuntimeError: If the project is not initialized.

        Example:
        >>> ras_plan = RasPlan()
        >>> unsteady_path = ras_plan.get_unsteady_path('01')
        >>> if unsteady_path:
        ...     print(f"Unsteady file found at: {unsteady_path}")
        ... else:
        ...     print("Unsteady file not found.")
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()
        
        # Use updated unsteady dataframe
        ras_obj.unsteady_df = ras_obj.get_prj_entries('Unsteady')
        
        unsteady_path = ras_obj.unsteady_df[ras_obj.unsteady_df['unsteady_number'] == unsteady_number]
        if not unsteady_path.empty:
            full_path = unsteady_path['full_path'].iloc[0]
            return full_path
        else:
            print(f"Unsteady number {unsteady_number} not found in the updated unsteady entries.")
            return None

    @staticmethod
    def get_geom_path(geom_number: str, ras_object=None) -> Optional[str]:
        """
        Return the full path for a given geometry number.

        Args:
        geom_number (str): The geometry number to search for.
        ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.

        Returns:
        Optional[str]: The full path of the geometry file if found, None otherwise.

        Raises:
        RuntimeError: If the project is not initialized.

        Example:
        >>> ras_plan = RasPlan()
        >>> geom_path = ras_plan.get_geom_path('01')
        >>> if geom_path:
        ...     print(f"Geometry file found at: {geom_path}")
        ... else:
        ...     print("Geometry file not found.")
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()
        
        # Use updated geom dataframe
        ras_obj.geom_df = ras_obj.get_prj_entries('Geom')
        
        geom_path = ras_obj.geom_df[ras_obj.geom_df['geom_number'] == geom_number]
        if not geom_path.empty:
            full_path = geom_path['full_path'].iloc[0]
            return full_path
        else:
            print(f"Geometry number {geom_number} not found in the updated geometry entries.")
            return None
#  Clone Functions to copy unsteady, flow, and geometry files from templates
     
    @staticmethod
    def clone_plan(template_plan, new_plan_shortid=None, ras_object=None):
        """
        Create a new plan file based on a template and update the project file.
        
        Parameters:
        template_plan (str): Plan number to use as template (e.g., '01')
        new_plan_shortid (str, optional): New short identifier for the plan file
        ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
        
        Returns:
        str: New plan number
        
        Example:
        >>> ras_plan = RasPlan()
        >>> new_plan_number = ras_plan.clone_plan('01', new_plan_shortid='New Plan')
        >>> print(f"New plan created with number: {new_plan_number}")

        Note:
            This function updates the ras object's dataframes after modifying the project structure.
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()

        # Update plan entries without reinitializing the entire project
        ras_obj.plan_df = ras_obj.get_prj_entries('Plan')

        new_plan_num = RasPlan.get_next_number(ras_obj.plan_df['plan_number'])
        template_plan_path = ras_obj.project_folder / f"{ras_obj.project_name}.p{template_plan}"
        new_plan_path = ras_obj.project_folder / f"{ras_obj.project_name}.p{new_plan_num}"
        
        if not template_plan_path.exists():
            raise FileNotFoundError(f"Template plan file '{template_plan_path}' does not exist.")

        shutil.copy(template_plan_path, new_plan_path)
        print(f"Copied {template_plan_path} to {new_plan_path}")

        with open(new_plan_path, 'r') as f:
            plan_lines = f.readlines()

        shortid_pattern = re.compile(r'^Short Identifier=(.*)$', re.IGNORECASE)
        for i, line in enumerate(plan_lines):
            match = shortid_pattern.match(line.strip())
            if match:
                current_shortid = match.group(1)
                if new_plan_shortid is None:
                    new_shortid = (current_shortid + "_copy")[:24]
                else:
                    new_shortid = new_plan_shortid[:24]
                plan_lines[i] = f"Short Identifier={new_shortid}\n"
                break

        with open(new_plan_path, 'w') as f:
            f.writelines(plan_lines)

        print(f"Updated short identifier in {new_plan_path}")

        with open(ras_obj.prj_file, 'r') as f:
            lines = f.readlines()

        # Prepare the new Plan File entry line
        new_plan_line = f"Plan File=p{new_plan_num}\n"

        # Find the correct insertion point for the new Plan File entry
        plan_file_pattern = re.compile(r'^Plan File=p(\d+)', re.IGNORECASE)
        insertion_index = None
        for i, line in enumerate(lines):
            match = plan_file_pattern.match(line.strip())
            if match:
                current_number = int(match.group(1))
                if current_number < int(new_plan_num):
                    continue
                else:
                    insertion_index = i
                    break

        if insertion_index is not None:
            lines.insert(insertion_index, new_plan_line)
        else:
            # Try to insert after the last Plan File entry
            plan_indices = [i for i, line in enumerate(lines) if plan_file_pattern.match(line.strip())]
            if plan_indices:
                last_plan_index = plan_indices[-1]
                lines.insert(last_plan_index + 1, new_plan_line)
            else:
                # Append at the end if no Plan File entries exist
                lines.append(new_plan_line)

        # Write the updated lines back to the project file
        with open(ras_obj.prj_file, 'w') as f:
            f.writelines(lines)

        print(f"Updated {ras_obj.prj_file} with new plan p{new_plan_num}")
        new_plan = new_plan_num
        
        # Store the project folder path
        project_folder = ras_obj.project_folder

        # Re-initialize the ras global object
        ras_obj.initialize(project_folder, ras_obj.ras_exe_path)

        ras_obj.plan_df = ras_obj.get_plan_entries()
        ras_obj.geom_df = ras_obj.get_geom_entries()
        ras_obj.flow_df = ras_obj.get_flow_entries()
        ras_obj.unsteady_df = ras_obj.get_unsteady_entries()

        return new_plan


    @staticmethod
    def clone_unsteady(template_unsteady, ras_object=None):
        """
        Copy unsteady flow files from a template, find the next unsteady number,
        and update the project file accordingly.

        Parameters:
        template_unsteady (str): Unsteady flow number to be used as a template (e.g., '01')
        ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.

        Returns:
        str: New unsteady flow number (e.g., '03')

        Example:
        >>> ras_plan = RasPlan()
        >>> new_unsteady_num = ras_plan.clone_unsteady('01')
        >>> print(f"New unsteady flow file created: u{new_unsteady_num}")

        Note:
            This function updates the ras object's dataframes after modifying the project structure.
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()

        # Update unsteady entries without reinitializing the entire project
        ras_obj.unsteady_df = ras_obj.get_prj_entries('Unsteady')

        new_unsteady_num = RasPlan.get_next_number(ras_obj.unsteady_df['unsteady_number'])
        template_unsteady_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{template_unsteady}"
        new_unsteady_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{new_unsteady_num}"

        if not template_unsteady_path.exists():
            raise FileNotFoundError(f"Template unsteady file '{template_unsteady_path}' does not exist.")

        shutil.copy(template_unsteady_path, new_unsteady_path)
        print(f"Copied {template_unsteady_path} to {new_unsteady_path}")

        # Copy the corresponding .hdf file if it exists
        template_hdf_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{template_unsteady}.hdf"
        new_hdf_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{new_unsteady_num}.hdf"
        if template_hdf_path.exists():
            shutil.copy(template_hdf_path, new_hdf_path)
            print(f"Copied {template_hdf_path} to {new_hdf_path}")
        else:
            print(f"No corresponding .hdf file found for '{template_unsteady_path}'. Skipping '.hdf' copy.")

        with open(ras_obj.prj_file, 'r') as f:
            lines = f.readlines()

        # Prepare the new Unsteady Flow File entry line
        new_unsteady_line = f"Unsteady File=u{new_unsteady_num}\n"

        # Find the correct insertion point for the new Unsteady Flow File entry
        unsteady_file_pattern = re.compile(r'^Unsteady File=u(\d+)', re.IGNORECASE)
        insertion_index = None
        for i, line in enumerate(lines):
            match = unsteady_file_pattern.match(line.strip())
            if match:
                current_number = int(match.group(1))
                if current_number < int(new_unsteady_num):
                    continue
                else:
                    insertion_index = i
                    break

        if insertion_index is not None:
            lines.insert(insertion_index, new_unsteady_line)
        else:
            # Try to insert after the last Unsteady Flow File entry
            unsteady_indices = [i for i, line in enumerate(lines) if unsteady_file_pattern.match(line.strip())]
            if unsteady_indices:
                last_unsteady_index = unsteady_indices[-1]
                lines.insert(last_unsteady_index + 1, new_unsteady_line)
            else:
                # Append at the end if no Unsteady Flow File entries exist
                lines.append(new_unsteady_line)

        # Write the updated lines back to the project file
        with open(ras_obj.prj_file, 'w') as f:
            f.writelines(lines)

        print(f"Updated {ras_obj.prj_file} with new unsteady flow file u{new_unsteady_num}")
        new_unsteady = new_unsteady_num
        
        # Store the project folder path
        project_folder = ras_obj.project_folder
        hecras_path = ras_obj.ras_exe_path

        # Re-initialize the ras global object
        ras_obj.initialize(project_folder, hecras_path)
        
        ras_obj.plan_df = ras_obj.get_plan_entries()
        ras_obj.geom_df = ras_obj.get_geom_entries()
        ras_obj.flow_df = ras_obj.get_flow_entries()
        ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
        
        return new_unsteady

    @staticmethod
    def clone_steady(template_flow, ras_object=None):
        """
        Copy steady flow files from a template, find the next flow number,
        and update the project file accordingly.
        
        Parameters:
        template_flow (str): Flow number to be used as a template (e.g., '01')
        ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
        
        Returns:
        str: New flow number (e.g., '03')

        Example:
        >>> ras_plan = RasPlan()
        >>> new_flow_num = ras_plan.clone_steady('01')
        >>> print(f"New steady flow file created: f{new_flow_num}")

        Note:
            This function updates the ras object's dataframes after modifying the project structure.
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()

        # Update flow entries without reinitializing the entire project
        ras_obj.flow_df = ras_obj.get_prj_entries('Flow')

        new_flow_num = RasPlan.get_next_number(ras_obj.flow_df['flow_number'])
        template_flow_path = ras_obj.project_folder / f"{ras_obj.project_name}.f{template_flow}"
        new_flow_path = ras_obj.project_folder / f"{ras_obj.project_name}.f{new_flow_num}"

        if not template_flow_path.exists():
            raise FileNotFoundError(f"Template steady flow file '{template_flow_path}' does not exist.")

        shutil.copy(template_flow_path, new_flow_path)
        print(f"Copied {template_flow_path} to {new_flow_path}")

        # Read the contents of the project file
        with open(ras_obj.prj_file, 'r') as f:
            lines = f.readlines()

        # Prepare the new Steady Flow File entry line
        new_flow_line = f"Flow File=f{new_flow_num}\n"

        # Find the correct insertion point for the new Steady Flow File entry
        flow_file_pattern = re.compile(r'^Flow File=f(\d+)', re.IGNORECASE)
        insertion_index = None
        for i, line in enumerate(lines):
            match = flow_file_pattern.match(line.strip())
            if match:
                current_number = int(match.group(1))
                if current_number < int(new_flow_num):
                    continue
                else:
                    insertion_index = i
                    break

        if insertion_index is not None:
            lines.insert(insertion_index, new_flow_line)
        else:
            # Try to insert after the last Steady Flow File entry
            flow_indices = [i for i, line in enumerate(lines) if flow_file_pattern.match(line.strip())]
            if flow_indices:
                last_flow_index = flow_indices[-1]
                lines.insert(last_flow_index + 1, new_flow_line)
            else:
                # Append at the end if no Steady Flow File entries exist
                lines.append(new_flow_line)

        # Write the updated lines back to the project file
        with open(ras_obj.prj_file, 'w') as f:
            f.writelines(lines)

        print(f"Updated {ras_obj.prj_file} with new steady flow file f{new_flow_num}")
        new_steady = new_flow_num
        
        # Store the project folder path
        project_folder = ras_obj.project_folder

        # Re-initialize the ras global object
        ras_obj.initialize(project_folder, ras_obj.ras_exe_path)
        
        ras_obj.plan_df = ras_obj.get_plan_entries()
        ras_obj.geom_df = ras_obj.get_geom_entries()
        ras_obj.flow_df = ras_obj.get_flow_entries()
        ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
        
        return new_steady


    @staticmethod
    def clone_geom(template_geom, ras_object=None):
        """
        Copy geometry files from a template, find the next geometry number,
        and update the project file accordingly.
        
        Parameters:
        template_geom (str): Geometry number to be used as a template (e.g., '01')
        ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
        
        Returns:
        str: New geometry number (e.g., '03')

        Note:
            This function updates the ras object's dataframes after modifying the project structure.
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()

        # Update geometry entries without reinitializing the entire project
        ras_obj.geom_df = ras_obj.get_prj_entries('Geom')  # Call the correct function to get updated geometry entries
        print(f"Updated geometry entries:\n{ras_obj.geom_df}")

#  Clone Functions to copy unsteady, flow, and geometry files from templates
     
    @staticmethod
    def clone_plan(template_plan, new_plan_shortid=None, ras_object=None):
        """
        Create a new plan file based on a template and update the project file.
        
        Parameters:
        template_plan (str): Plan number to use as template (e.g., '01')
        new_plan_shortid (str, optional): New short identifier for the plan file
        ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
        
        Returns:
        str: New plan number
        
        Revision Notes:
        - Updated to insert new plan entry in the correct position
        - Improved error handling and logging
        - Updated to use get_prj_entries('Plan') for the latest entries
        - Added print statements for progress tracking

        Example:
        >>> ras_plan = RasPlan()
        >>> new_plan_number = ras_plan.clone_plan('01', new_plan_shortid='New Plan')
        >>> print(f"New plan created with number: {new_plan_number}")
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()

        # Update plan entries without reinitializing the entire project
        ras_obj.plan_df = ras_obj.get_prj_entries('Plan')

        new_plan_num = RasPlan.get_next_number(ras_obj.plan_df['plan_number'])
        template_plan_path = ras_obj.project_folder / f"{ras_obj.project_name}.p{template_plan}"
        new_plan_path = ras_obj.project_folder / f"{ras_obj.project_name}.p{new_plan_num}"
        
        if not template_plan_path.exists():
            raise FileNotFoundError(f"Template plan file '{template_plan_path}' does not exist.")

        shutil.copy(template_plan_path, new_plan_path)
        print(f"Copied {template_plan_path} to {new_plan_path}")

        with open(new_plan_path, 'r') as f:
            plan_lines = f.readlines()

        shortid_pattern = re.compile(r'^Short Identifier=(.*)$', re.IGNORECASE)
        for i, line in enumerate(plan_lines):
            match = shortid_pattern.match(line.strip())
            if match:
                current_shortid = match.group(1)
                if new_plan_shortid is None:
                    new_shortid = (current_shortid + "_copy")[:24]
                else:
                    new_shortid = new_plan_shortid[:24]
                plan_lines[i] = f"Short Identifier={new_shortid}\n"
                break

        with open(new_plan_path, 'w') as f:
            f.writelines(plan_lines)

        print(f"Updated short identifier in {new_plan_path}")

        with open(ras_obj.prj_file, 'r') as f:
            lines = f.readlines()

        # Prepare the new Plan File entry line
        new_plan_line = f"Plan File=p{new_plan_num}\n"

        # Find the correct insertion point for the new Plan File entry
        plan_file_pattern = re.compile(r'^Plan File=p(\d+)', re.IGNORECASE)
        insertion_index = None
        for i, line in enumerate(lines):
            match = plan_file_pattern.match(line.strip())
            if match:
                current_number = int(match.group(1))
                if current_number < int(new_plan_num):
                    continue
                else:
                    insertion_index = i
                    break

        if insertion_index is not None:
            lines.insert(insertion_index, new_plan_line)
        else:
            # Try to insert after the last Plan File entry
            plan_indices = [i for i, line in enumerate(lines) if plan_file_pattern.match(line.strip())]
            if plan_indices:
                last_plan_index = plan_indices[-1]
                lines.insert(last_plan_index + 1, new_plan_line)
            else:
                # Append at the end if no Plan File entries exist
                lines.append(new_plan_line)

        # Write the updated lines back to the project file
        with open(ras_obj.prj_file, 'w') as f:
            f.writelines(lines)

        print(f"Updated {ras_obj.prj_file} with new plan p{new_plan_num}")
        new_plan = new_plan_num
        
        # Store the project folder path
        project_folder = ras_obj.project_folder

        # Re-initialize the ras global object
        ras_obj.initialize(project_folder, ras_obj.ras_exe_path)
        return new_plan


    @staticmethod
    def clone_unsteady(template_unsteady, ras_object=None):
        """
        Copy unsteady flow files from a template, find the next unsteady number,
        and update the project file accordingly.

        Parameters:
        template_unsteady (str): Unsteady flow number to be used as a template (e.g., '01')
        ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.

        Returns:
        str: New unsteady flow number (e.g., '03')

        Example:
        >>> ras_plan = RasPlan()
        >>> new_unsteady_num = ras_plan.clone_unsteady('01')
        >>> print(f"New unsteady flow file created: u{new_unsteady_num}")

        Revision Notes:
        - Updated to insert new unsteady flow entry in the correct position
        - Improved error handling and logging
        - Removed dst_folder parameter as it's not needed (using project folder)
        - Added handling for corresponding .hdf files
        - Updated to use get_prj_entries('Unsteady') for the latest entries
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()

        # Update unsteady entries without reinitializing the entire project
        ras_obj.unsteady_df = ras_obj.get_prj_entries('Unsteady')

        new_unsteady_num = RasPlan.get_next_number(ras_obj.unsteady_df['unsteady_number'])
        template_unsteady_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{template_unsteady}"
        new_unsteady_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{new_unsteady_num}"

        if not template_unsteady_path.exists():
            raise FileNotFoundError(f"Template unsteady file '{template_unsteady_path}' does not exist.")

        shutil.copy(template_unsteady_path, new_unsteady_path)
        print(f"Copied {template_unsteady_path} to {new_unsteady_path}")

        # Copy the corresponding .hdf file if it exists
        template_hdf_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{template_unsteady}.hdf"
        new_hdf_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{new_unsteady_num}.hdf"
        if template_hdf_path.exists():
            shutil.copy(template_hdf_path, new_hdf_path)
            print(f"Copied {template_hdf_path} to {new_hdf_path}")
        else:
            print(f"No corresponding .hdf file found for '{template_unsteady_path}'. Skipping '.hdf' copy.")

        with open(ras_obj.prj_file, 'r') as f:
            lines = f.readlines()

        # Prepare the new Unsteady Flow File entry line
        new_unsteady_line = f"Unsteady File=u{new_unsteady_num}\n"

        # Find the correct insertion point for the new Unsteady Flow File entry
        unsteady_file_pattern = re.compile(r'^Unsteady File=u(\d+)', re.IGNORECASE)
        insertion_index = None
        for i, line in enumerate(lines):
            match = unsteady_file_pattern.match(line.strip())
            if match:
                current_number = int(match.group(1))
                if current_number < int(new_unsteady_num):
                    continue
                else:
                    insertion_index = i
                    break

        if insertion_index is not None:
            lines.insert(insertion_index, new_unsteady_line)
        else:
            # Try to insert after the last Unsteady Flow File entry
            unsteady_indices = [i for i, line in enumerate(lines) if unsteady_file_pattern.match(line.strip())]
            if unsteady_indices:
                last_unsteady_index = unsteady_indices[-1]
                lines.insert(last_unsteady_index + 1, new_unsteady_line)
            else:
                # Append at the end if no Unsteady Flow File entries exist
                lines.append(new_unsteady_line)

        # Write the updated lines back to the project file
        with open(ras_obj.prj_file, 'w') as f:
            f.writelines(lines)

        print(f"Updated {ras_obj.prj_file} with new unsteady flow file u{new_unsteady_num}")
        new_unsteady = new_unsteady_num
        
        # Store the project folder path
        project_folder = ras_obj.project_folder
        hecras_path = ras_obj.ras_exe_path

        # Re-initialize the ras global object
        ras_obj.initialize(project_folder, hecras_path)
        
        return new_unsteady

    @staticmethod
    def clone_steady(template_flow, ras_object=None):
        """
        Copy steady flow files from a template, find the next flow number,
        and update the project file accordingly.
        
        Parameters:
        template_flow (str): Flow number to be used as a template (e.g., '01')
        ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
        
        Returns:
        str: New flow number (e.g., '03')

        Example:
        >>> ras_plan = RasPlan()
        >>> new_flow_num = ras_plan.clone_steady('01')
        >>> print(f"New steady flow file created: f{new_flow_num}")

        Revision Notes:
        - Updated to insert new steady flow entry in the correct position
        - Improved error handling and logging
        - Added handling for corresponding .hdf files
        - Updated to use get_prj_entries('Flow') for the latest entries
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()

        # Update flow entries without reinitializing the entire project
        ras_obj.flow_df = ras_obj.get_prj_entries('Flow')

        new_flow_num = RasPlan.get_next_number(ras_obj.flow_df['flow_number'])
        template_flow_path = ras_obj.project_folder / f"{ras_obj.project_name}.f{template_flow}"
        new_flow_path = ras_obj.project_folder / f"{ras_obj.project_name}.f{new_flow_num}"

        if not template_flow_path.exists():
            raise FileNotFoundError(f"Template steady flow file '{template_flow_path}' does not exist.")

        shutil.copy(template_flow_path, new_flow_path)
        print(f"Copied {template_flow_path} to {new_flow_path}")

        # Read the contents of the project file
        with open(ras_obj.prj_file, 'r') as f:
            lines = f.readlines()

        # Prepare the new Steady Flow File entry line
        new_flow_line = f"Flow File=f{new_flow_num}\n"

        # Find the correct insertion point for the new Steady Flow File entry
        flow_file_pattern = re.compile(r'^Flow File=f(\d+)', re.IGNORECASE)
        insertion_index = None
        for i, line in enumerate(lines):
            match = flow_file_pattern.match(line.strip())
            if match:
                current_number = int(match.group(1))
                if current_number < int(new_flow_num):
                    continue
                else:
                    insertion_index = i
                    break

        if insertion_index is not None:
            lines.insert(insertion_index, new_flow_line)
        else:
            # Try to insert after the last Steady Flow File entry
            flow_indices = [i for i, line in enumerate(lines) if flow_file_pattern.match(line.strip())]
            if flow_indices:
                last_flow_index = flow_indices[-1]
                lines.insert(last_flow_index + 1, new_flow_line)
            else:
                # Append at the end if no Steady Flow File entries exist
                lines.append(new_flow_line)

        # Write the updated lines back to the project file
        with open(ras_obj.prj_file, 'w') as f:
            f.writelines(lines)

        print(f"Updated {ras_obj.prj_file} with new steady flow file f{new_flow_num}")
        new_steady = new_flow_num
        
        # Store the project folder path
        project_folder = ras_obj.project_folder

        # Re-initialize the ras global object
        ras_obj.initialize(project_folder, ras_obj.ras_exe_path)
        
        return new_steady

    @staticmethod
    def clone_geom(template_geom, ras_object=None):
        """
        Copy geometry files from a template, find the next geometry number,
        and update the project file accordingly.
        
        Parameters:
        template_geom (str): Geometry number to be used as a template (e.g., '01')
        ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
        
        Returns:
        str: New geometry number (e.g., '03')

        Note:
            This function updates the ras object's dataframes after modifying the project structure.
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()

        # Update geometry entries without reinitializing the entire project
        ras_obj.geom_df = ras_obj.get_prj_entries('Geom')

        template_geom_filename = f"{ras_obj.project_name}.g{template_geom}"
        template_geom_path = ras_obj.project_folder / template_geom_filename

        if not template_geom_path.is_file():
            raise FileNotFoundError(f"Template geometry file '{template_geom_path}' does not exist.")

        next_geom_number = RasPlan.get_next_number(ras_obj.geom_df['geom_number'])

        new_geom_filename = f"{ras_obj.project_name}.g{next_geom_number}"
        new_geom_path = ras_obj.project_folder / new_geom_filename

        shutil.copyfile(template_geom_path, new_geom_path)
        print(f"Copied '{template_geom_path}' to '{new_geom_path}'.")

        # Handle HDF file copy
        template_hdf_path = template_geom_path.with_suffix('.g' + template_geom + '.hdf')
        new_hdf_path = new_geom_path.with_suffix('.g' + next_geom_number + '.hdf')
        if template_hdf_path.is_file():
            shutil.copyfile(template_hdf_path, new_hdf_path)
            print(f"Copied '{template_hdf_path}' to '{new_hdf_path}'.")
        else:
            print(f"Warning: Template geometry HDF file '{template_hdf_path}' does not exist. This is common, and not critical. Continuing without it.")

        with open(ras_obj.prj_file, 'r') as file:
            lines = file.readlines()

        # Prepare the new Geometry File entry line
        new_geom_line = f"Geom File=g{next_geom_number}\n"

        # Find the correct insertion point for the new Geometry File entry
        geom_file_pattern = re.compile(r'^Geom File=g(\d+)', re.IGNORECASE)
        insertion_index = None
        for i, line in enumerate(lines):
            match = geom_file_pattern.match(line.strip())
            if match:
                current_number = int(match.group(1))
                if current_number < int(next_geom_number):
                    continue
                else:
                    insertion_index = i
                    break

        if insertion_index is not None:
            lines.insert(insertion_index, new_geom_line)
        else:
            # Try to insert after the last Geometry File entry
            geom_indices = [i for i, line in enumerate(lines) if geom_file_pattern.match(line.strip())]
            if geom_indices:
                last_geom_index = geom_indices[-1]
                lines.insert(last_geom_index + 1, new_geom_line)
            else:
                # Append at the end if no Geometry File entries exist
                lines.append(new_geom_line)

        # Write the updated lines back to the project file
        with open(ras_obj.prj_file, 'w') as file:
            file.writelines(lines)

        print(f"Updated {ras_obj.prj_file} with new geometry file g{next_geom_number}")
        new_geom = next_geom_number
        
        # Update all dataframes in the ras object
        ras_obj.plan_df = ras_obj.get_plan_entries()
        ras_obj.geom_df = ras_obj.get_geom_entries()
        ras_obj.flow_df = ras_obj.get_flow_entries()
        ras_obj.unsteady_df = ras_obj.get_unsteady_entries()

        print(f"Updated geometry entries:\n{ras_obj.geom_df}")

        return new_geom
            
            
        
        
    @staticmethod
    def get_next_number(existing_numbers):
        """
        Determine the next available number from a list of existing numbers.
        
        Parameters:
        existing_numbers (list): List of existing numbers as strings
        
        Returns:
        str: Next available number as a zero-padded string
        
        Example:
        >>> existing_numbers = ['01', '02', '04']
        >>> RasPlan.get_next_number(existing_numbers)
        '03'
        >>> existing_numbers = ['01', '02', '03']
        >>> RasPlan.get_next_number(existing_numbers)
        '04'
        """
        existing_numbers = sorted(int(num) for num in existing_numbers)
        next_number = 1
        for num in existing_numbers:
            if num == next_number:
                next_number += 1
            else:
                break
        return f"{next_number:02d}"

==================================================

File: c:\GH\ras_commander\ras_commander\RasPrj.py
==================================================
"""RasPrj.py

This module provides a class for managing HEC-RAS projects.

Classes:
    RasPrj: A class for managing HEC-RAS projects.

Functions:
    init_ras_project: Initialize a RAS project.
    get_ras_exe: Determine the HEC-RAS executable path based on the input.

DEVELOPER NOTE:
This class is used to initialize a RAS project and is used in conjunction with the RasCommander class to manage the execution of RAS plans.
By default, the RasPrj class is initialized with the global 'ras' object.
However, you can create multiple RasPrj instances to manage multiple projects.
Do not mix and match global 'ras' object instances and custom instances of RasPrj - it will cause errors.
"""
# Example Terminal Output for RasPrj Functions:
# print(f"\n----- INSERT TEXT HERE -----\n")

from pathlib import Path
import pandas as pd
import re

class RasPrj:
    def __init__(self):
        self.initialized = False

    def initialize(self, project_folder, ras_exe_path):
        """
        Initialize a RasPrj instance.

        This method sets up the RasPrj instance with the given project folder and RAS executable path.
        It finds the project file, loads project data, and sets the initialization flag.

        Args:
            project_folder (str or Path): Path to the HEC-RAS project folder.
            ras_exe_path (str or Path): Path to the HEC-RAS executable.

        Raises:
            ValueError: If no HEC-RAS project file is found in the specified folder.

        Note:
            This method is intended for internal use. External users should use the init_ras_project function instead.
        """
        self.project_folder = Path(project_folder)
        self.prj_file = self.find_ras_prj(self.project_folder)
        if self.prj_file is None:
            raise ValueError(f"No HEC-RAS project file found in {self.project_folder}")
        self.project_name = Path(self.prj_file).stem
        self.ras_exe_path = ras_exe_path
        self._load_project_data()
        self.initialized = True
        print(f"\n-----Initialization complete for project: {self.project_name}-----")
        print(f"Plan entries: {len(self.plan_df)}, Flow entries: {len(self.flow_df)}, Unsteady entries: {len(self.unsteady_df)}, Geometry entries: {len(self.geom_df)}\n")

    def _load_project_data(self):
        """
        Load project data from the HEC-RAS project file.

        This method initializes DataFrames for plan, flow, unsteady, and geometry entries
        by calling the _get_prj_entries method for each entry type.
        """
        # Initialize DataFrames
        self.plan_df = self._get_prj_entries('Plan')
        self.flow_df = self._get_prj_entries('Flow')
        self.unsteady_df = self._get_prj_entries('Unsteady')
        self.geom_df = self._get_prj_entries('Geom')

    def _get_prj_entries(self, entry_type):
        """
        Extract entries of a specific type from the HEC-RAS project file.

        Args:
            entry_type (str): The type of entry to extract (e.g., 'Plan', 'Flow', 'Unsteady', 'Geom').

        Returns:
            pd.DataFrame: A DataFrame containing the extracted entries.

        Note:
            This method reads the project file and extracts entries matching the specified type.
            For 'Plan' entries, it also checks for the existence of HDF results files.
        """
        # Initialize an empty list to store entries
        entries = []
        # Create a regex pattern to match the specific entry type
        pattern = re.compile(rf"{entry_type} File=(\w+)")

        # Open and read the project file
        with open(self.prj_file, 'r') as file:
            for line in file:
                # Check if the line matches the pattern
                match = pattern.match(line.strip())
                if match:
                    # Extract the file name from the matched pattern
                    file_name = match.group(1)
                    # Create a dictionary for the current entry
                    entry = {
                        f'{entry_type.lower()}_number': file_name[1:],
                        'full_path': str(self.project_folder / f"{self.project_name}.{file_name}")
                    }

                    # Special handling for Plan entries
                    if entry_type == 'Plan':
                        # Construct the path for the HDF results file
                        hdf_results_path = self.project_folder / f"{self.project_name}.p{file_name[1:]}.hdf"
                        # Add the results_path to the entry, if the file exists
                        entry['HDF_Results_Path'] = str(hdf_results_path) if hdf_results_path.exists() else None

                    # Add the entry to the list
                    entries.append(entry)

        # Convert the list of entries to a DataFrame and return it
        return pd.DataFrame(entries)

    @property
    def is_initialized(self):
        """
        Check if the RasPrj instance has been initialized.

        Returns:
            bool: True if the instance has been initialized, False otherwise.
        """
        return self.initialized

    def check_initialized(self):
        """
        Ensure that the RasPrj instance has been initialized.

        Raises:
            RuntimeError: If the project has not been initialized.
        """
        if not self.initialized:
            raise RuntimeError("Project not initialized. Call init_ras_project() first.")

    @staticmethod
    def find_ras_prj(folder_path):
        """
        Find the appropriate HEC-RAS project file (.prj) in the given folder.
        
        Parameters:
        folder_path (str or Path): Path to the folder containing HEC-RAS files.
        
        Returns:
        Path: The full path of the selected .prj file or None if no suitable file is found.
        """
        folder_path = Path(folder_path)
        prj_files = list(folder_path.glob("*.prj"))
        rasmap_files = list(folder_path.glob("*.rasmap"))
        if len(prj_files) == 1:
            return prj_files[0].resolve()
        if len(prj_files) > 1:
            if len(rasmap_files) == 1:
                base_filename = rasmap_files[0].stem
                prj_file = folder_path / f"{base_filename}.prj"
                return prj_file.resolve()
            for prj_file in prj_files:
                with open(prj_file, 'r') as file:
                    if "Proj Title=" in file.read():
                        return prj_file.resolve()
        print("No suitable .prj file found after all checks.")
        return None

    def get_project_name(self):
        """
        Get the name of the HEC-RAS project.

        Returns:
            str: The name of the project.

        Raises:
            RuntimeError: If the project has not been initialized.
        """
        self.check_initialized()
        return self.project_name

    def get_prj_entries(self, entry_type):
        """
        Get entries of a specific type from the HEC-RAS project.

        Args:
            entry_type (str): The type of entry to retrieve (e.g., 'Plan', 'Flow', 'Unsteady', 'Geom').

        Returns:
            pd.DataFrame: A DataFrame containing the requested entries.

        Raises:
            RuntimeError: If the project has not been initialized.
        """
        self.check_initialized()
        return self._get_prj_entries(entry_type)

    def get_plan_entries(self):
        """
        Get all plan entries from the HEC-RAS project.

        Returns:
            pd.DataFrame: A DataFrame containing all plan entries.

        Raises:
            RuntimeError: If the project has not been initialized.
        """
        self.check_initialized()
        return self._get_prj_entries('Plan')

    def get_flow_entries(self):
        """
        Get all flow entries from the HEC-RAS project.

        Returns:
            pd.DataFrame: A DataFrame containing all flow entries.

        Raises:
            RuntimeError: If the project has not been initialized.
        """
        self.check_initialized()
        return self._get_prj_entries('Flow')

    def get_unsteady_entries(self):
        """
        Get all unsteady flow entries from the HEC-RAS project.

        Returns:
            pd.DataFrame: A DataFrame containing all unsteady flow entries.

        Raises:
            RuntimeError: If the project has not been initialized.
        """
        self.check_initialized()
        return self._get_prj_entries('Unsteady')

    def get_geom_entries(self):
        """
        Get all geometry entries from the HEC-RAS project.

        Returns:
            pd.DataFrame: A DataFrame containing all geometry entries.

        Raises:
            RuntimeError: If the project has not been initialized.
        """
        self.check_initialized()
        return self._get_prj_entries('Geom')
    
    def get_hdf_entries(self):
        """
        Get HDF entries for plans that have results.
        
        Returns:
        pd.DataFrame: A DataFrame containing plan entries with HDF results.
                  Returns an empty DataFrame if no HDF entries are found.
        """
        self.check_initialized()
        
        # Filter the plan_df to include only entries with existing HDF results
        hdf_entries = self.plan_df[self.plan_df['HDF_Results_Path'].notna()].copy()
        
        # If no HDF entries are found, return an empty DataFrame with the correct columns
        if hdf_entries.empty:
            return pd.DataFrame(columns=self.plan_df.columns)
        
        return hdf_entries
    
    def print_data(self):
        """Print all RAS Object data for this instance.
           If any objects are added, add them to the print statements below."""
        print(f"\n--- Data for {self.project_name} ---")
        print(f"Project folder: {self.project_folder}")
        print(f"PRJ file: {self.prj_file}")
        print(f"HEC-RAS executable: {self.ras_exe_path}")
        print("\nPlan files:")
        print(self.plan_df)
        print("\nFlow files:")
        print(self.flow_df)
        print("\nUnsteady flow files:")
        print(self.unsteady_df)
        print("\nGeometry files:")
        print(self.geom_df)
        print("\nHDF entries:")
        print(self.get_hdf_entries())
        print("----------------------------\n")


# Create a global instance named 'ras'
ras = RasPrj()

def init_ras_project(ras_project_folder, ras_version, ras_instance=None):
    """
    Initialize a RAS project.

    USE THIS FUNCTION TO INITIALIZE A RAS PROJECT, NOT THE INITIALIZE METHOD OF THE RasPrj CLASS.
    The initialize method of the RasPrj class only modifies the global 'ras' object.

    This function creates or initializes a RasPrj instance, providing a safer and more
    flexible interface than directly using the 'initialize' method.

    Parameters:
    -----------
    ras_project_folder : str
        The path to the RAS project folder.
    ras_version : str
        The version of RAS to use (e.g., "6.5").
        The version can also be a full path to the Ras.exe file. (Useful when calling ras objects for folder copies.)
    ras_instance : RasPrj, optional
        An instance of RasPrj to initialize. If None, the global 'ras' instance is used.

    Returns:
    --------
    RasPrj
        An initialized RasPrj instance.

    Usage:
    ------
    1. For general use with a single project:
        init_ras_project("/path/to/project", "6.5")
        # Use the global 'ras' object after initialization

    2. For managing multiple projects:
        project1 = init_ras_project("/path/to/project1", "6.5", ras_instance=RasPrj())
        project2 = init_ras_project("/path/to/project2", "6.5", ras_instance=RasPrj())

    Notes:
    ------
    - This function is preferred over directly calling the 'initialize' method.
    - It supports both the global 'ras' object and custom instances.
    - Be consistent in your approach: stick to either the global 'ras' object
      or custom instances throughout your script or application.
    - Document your choice of approach clearly in your code.

    Warnings:
    ---------
    Avoid mixing use of the global 'ras' object and custom instances to prevent
    confusion and potential bugs.
    """

    if not Path(ras_project_folder).exists():
        raise FileNotFoundError(f"The specified RAS project folder does not exist: {ras_project_folder}. Please check the path and try again.")

    ras_exe_path = get_ras_exe(ras_version)

    if ras_instance is None:
        print(f"\n-----Initializing global 'ras' object via init_ras_project function-----")
        ras_instance = ras
    elif not isinstance(ras_instance, RasPrj):
        print(f"\n-----Initializing custom RasPrj instance via init_ras_project function-----")
        raise TypeError("ras_instance must be an instance of RasPrj or None.")

    # Initialize the RasPrj instance
    ras_instance.initialize(ras_project_folder, ras_exe_path)

    #print(f"\n-----HEC-RAS project initialized via init_ras_project function: {ras_instance.project_name}-----\n")
    return ras_instance


def get_ras_exe(ras_version):
    """
    Determine the HEC-RAS executable path based on the input.
    
    Args:
    ras_version (str): Either a version number or a full path to the HEC-RAS executable.
    
    Returns:
    str: The full path to the HEC-RAS executable.
    
    Raises:
    ValueError: If the input is neither a valid version number nor a valid file path.
    FileNotFoundError: If the executable file does not exist at the specified or constructed path.
    """
    ras_version_numbers = [
        "6.5", "6.4.1", "6.3.1", "6.3", "6.2", "6.1", "6.0",
        "5.0.7", "5.0.6", "5.0.5", "5.0.4", "5.0.3", "5.0.1", "5.0",
        "4.1", "4.0", "3.1.3", "3.1.2", "3.1.1", "3.0", "2.2"
    ]
    
    hecras_path = Path(ras_version)
    
    if hecras_path.is_file() and hecras_path.suffix.lower() == '.exe':
        return str(hecras_path)
    
    if ras_version in ras_version_numbers:
        default_path = Path(f"C:/Program Files (x86)/HEC/HEC-RAS/{ras_version}/Ras.exe")
        if default_path.is_file():
            return str(default_path)
        else:
            raise FileNotFoundError(f"HEC-RAS executable not found at the expected path: {default_path}")
    
    try:
        version_float = float(ras_version)
        if version_float > max(float(v) for v in ras_version_numbers):
            newer_version_path = Path(f"C:/Program Files (x86)/HEC/HEC-RAS/{ras_version}/Ras.exe")
            if newer_version_path.is_file():
                return str(newer_version_path)
            else:
                raise FileNotFoundError(f"Newer version of HEC-RAS was specified. Check the version number or pass the full Ras.exe path as the function argument instead of the version number. The script looked for the executable at: {newer_version_path}")
    except ValueError:
        pass
    
    raise ValueError(f"Invalid HEC-RAS version or path: {ras_version}. "
                     f"Please provide a valid version number from {ras_version_numbers} "
                     "or a full path to the HEC-RAS executable.")
==================================================

File: c:\GH\ras_commander\ras_commander\RasUnsteady.py
==================================================
"""
Operations for handling unsteady flow files in HEC-RAS projects.
"""
from pathlib import Path
from .RasPrj import ras
import re

class RasUnsteady:
    """
    Class for all operations related to HEC-RAS unsteady flow files.
    """
    
    

    @staticmethod
    def update_unsteady_parameters(unsteady_file, modifications, ras_object=None):
        """
        Modify parameters in an unsteady flow file.
        
        Parameters:
        unsteady_file (str): Full path to the unsteady flow file
        modifications (dict): Dictionary of modifications to apply, where keys are parameter names and values are new values
        ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
        
        Returns:
        None

        Note:
            This function updates the ras object's unsteady dataframe after modifying the unsteady flow file.
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()
        ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
        
        unsteady_path = Path(unsteady_file)
        try:
            with open(unsteady_path, 'r') as f:
                lines = f.readlines()
        except FileNotFoundError:
            raise FileNotFoundError(f"Unsteady flow file not found: {unsteady_path}")
        except PermissionError:
            raise PermissionError(f"Permission denied when reading unsteady flow file: {unsteady_path}")
        
        updated = False
        for i, line in enumerate(lines):
            for param, new_value in modifications.items():
                if line.startswith(f"{param}="):
                    lines[i] = f"{param}={new_value}\n"
                    updated = True
                    print(f"Updated {param} to {new_value}")
        if updated:
            try:
                with open(unsteady_path, 'w') as f:
                    f.writelines(lines)
            except PermissionError:
                raise PermissionError(f"Permission denied when writing to unsteady flow file: {unsteady_path}")
            except IOError as e:
                raise IOError(f"Error writing to unsteady flow file: {unsteady_path}. {str(e)}")
            print(f"Applied modifications to {unsteady_file}")
        else:
            print(f"No matching parameters found in {unsteady_file}")

        ras_obj = ras_object or ras
        ras_obj.unsteady_df = ras_obj.get_unsteady_entries()

==================================================

File: c:\GH\ras_commander\ras_commander\RasUtils.py
==================================================
"""
Utility functions for the ras_commander library.
"""
import os
import shutil
import logging
import time
from pathlib import Path
from .RasPrj import ras
from typing import Union

class RasUtils:
    """
    A class containing utility functions for the ras_commander library.
    When integrating new functions that do not clearly fit into other classes, add them here.
    """

    @staticmethod
    def create_backup(file_path: Path, backup_suffix: str = "_backup", ras_object=None) -> Path:
        """
        Create a backup of the specified file.

        Parameters:
        file_path (Path): Path to the file to be backed up
        backup_suffix (str): Suffix to append to the backup file name
        ras_object (RasPrj, optional): RAS object to use. If None, uses the default ras object.

        Returns:
        Path: Path to the created backup file

        Example:
        >>> backup_path = RasUtils.create_backup(Path("project.prj"))
        >>> print(f"Backup created at: {backup_path}")
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()
        
        original_path = Path(file_path)
        backup_path = original_path.with_name(f"{original_path.stem}{backup_suffix}{original_path.suffix}")
        shutil.copy2(original_path, backup_path)
        logging.info(f"Backup created: {backup_path}")
        return backup_path

    @staticmethod
    def restore_from_backup(backup_path: Path, remove_backup: bool = True, ras_object=None) -> Path:
        """
        Restore a file from its backup.

        Parameters:
        backup_path (Path): Path to the backup file
        remove_backup (bool): Whether to remove the backup file after restoration
        ras_object (RasPrj, optional): RAS object to use. If None, uses the default ras object.

        Returns:
        Path: Path to the restored file

        Example:
        >>> restored_path = RasUtils.restore_from_backup(Path("project_backup.prj"))
        >>> print(f"File restored to: {restored_path}")
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()
        
        backup_path = Path(backup_path)
        original_path = backup_path.with_name(backup_path.stem.rsplit('_backup', 1)[0] + backup_path.suffix)
        shutil.copy2(backup_path, original_path)
        logging.info(f"File restored: {original_path}")
        if remove_backup:
            backup_path.unlink()
            logging.info(f"Backup removed: {backup_path}")
        return original_path

    @staticmethod
    def create_directory(directory_path: Path, ras_object=None) -> Path:
        """
        Ensure that a directory exists, creating it if necessary.

        Parameters:
        directory_path (Path): Path to the directory
        ras_object (RasPrj, optional): RAS object to use. If None, uses the default ras object.

        Returns:
        Path: Path to the ensured directory

        Example:
        >>> ensured_dir = RasUtils.create_directory(Path("output"))
        >>> print(f"Directory ensured: {ensured_dir}")
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()
        
        path = Path(directory_path)
        path.mkdir(parents=True, exist_ok=True)
        logging.info(f"Directory ensured: {path}")
        return path

    @staticmethod
    def find_files_by_extension(extension: str, ras_object=None) -> list:
        """
        List all files in the project directory with a specific extension.

        Parameters:
        extension (str): File extension to filter (e.g., '.prj')
        ras_object (RasPrj, optional): RAS object to use. If None, uses the default ras object.

        Returns:
        list: List of file paths matching the extension

        Example:
        >>> prj_files = RasUtils.find_files_by_extension('.prj')
        >>> print(f"Found {len(prj_files)} .prj files")
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()
        
        files = list(ras_obj.project_folder.glob(f"*{extension}"))
        return [str(file) for file in files]

    @staticmethod
    def get_file_size(file_path: Path, ras_object=None) -> int:
        """
        Get the size of a file in bytes.

        Parameters:
        file_path (Path): Path to the file
        ras_object (RasPrj, optional): RAS object to use. If None, uses the default ras object.

        Returns:
        int: Size of the file in bytes

        Example:
        >>> size = RasUtils.get_file_size(Path("project.prj"))
        >>> print(f"File size: {size} bytes")
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()
        
        path = Path(file_path)
        if path.exists():
            return path.stat().st_size
        else:
            logging.warning(f"File not found: {path}")
            return None

    @staticmethod
    def get_file_modification_time(file_path: Path, ras_object=None) -> float:
        """
        Get the last modification time of a file.

        Parameters:
        file_path (Path): Path to the file
        ras_object (RasPrj, optional): RAS object to use. If None, uses the default ras object.

        Returns:
        float: Last modification time as a timestamp

        Example:
        >>> mtime = RasUtils.get_file_modification_time(Path("project.prj"))
        >>> print(f"Last modified: {mtime}")
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()
        
        path = Path(file_path)
        if path.exists():
            return path.stat().st_mtime
        else:
            logging.warning(f"File not found: {path}")
            return None

    @staticmethod
    def get_plan_path(current_plan_number_or_path: Union[str, Path], ras_object=None) -> Path:
        """
        Get the path for a plan file with a given plan number or path.

        Parameters:
        current_plan_number_or_path (Union[str, Path]): The plan number (1 to 99) or full path to the plan file
        ras_object (RasPrj, optional): RAS object to use. If None, uses the default ras object.

        Returns:
        Path: Full path to the plan file

        Example:
        >>> plan_path = RasUtils.get_plan_path(1)
        >>> print(f"Plan file path: {plan_path}")
        >>> plan_path = RasUtils.get_plan_path("path/to/plan.p01")
        >>> print(f"Plan file path: {plan_path}")
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()
        
        plan_path = Path(current_plan_number_or_path)
        if plan_path.is_file():
            return plan_path
        
        try:
            current_plan_number = f"{int(current_plan_number_or_path):02d}"  # Ensure two-digit format
        except ValueError:
            raise ValueError(f"Invalid plan number: {current_plan_number_or_path}. Expected a number from 1 to 99.")
        
        plan_name = f"{ras_obj.project_name}.p{current_plan_number}"
        return ras_obj.project_folder / plan_name

    @staticmethod
    def remove_with_retry(path: Path, max_attempts: int = 5, initial_delay: float = 1.0, is_folder: bool = True, ras_object=None) -> bool:
        """
        Attempts to remove a file or folder with retry logic and exponential backoff.

        Parameters:
        path (Path): Path to the file or folder to be removed.
        max_attempts (int): Maximum number of removal attempts.
        initial_delay (float): Initial delay between attempts in seconds.
        is_folder (bool): If True, the path is treated as a folder; if False, it's treated as a file.
        ras_object (RasPrj, optional): RAS object to use. If None, uses the default ras object.

        Returns:
        bool: True if the file or folder was successfully removed, False otherwise.

        Example:
        >>> success = RasUtils.remove_with_retry(Path("temp_folder"), is_folder=True)
        >>> print(f"Removal successful: {success}")
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()

        path = Path(path)
        for attempt in range(max_attempts):
            try:
                if path.exists():
                    if is_folder:
                        shutil.rmtree(path)
                    else:
                        path.unlink()
                return True
            except PermissionError:
                if attempt < max_attempts - 1:
                    delay = initial_delay * (2 ** attempt)  # Exponential backoff
                    logging.warning(f"Failed to remove {path}. Retrying in {delay} seconds...")
                    time.sleep(delay)
                else:
                    logging.error(f"Failed to remove {path} after {max_attempts} attempts. Skipping.")
                    return False
        return False

    @staticmethod
    def update_plan_file(plan_number_or_path: Union[str, Path], file_type: str, entry_number: int, ras_object=None) -> None:
        """
        Update a plan file with a new file reference.

        Parameters:
        plan_number_or_path (Union[str, Path]): The plan number (1 to 99) or full path to the plan file
        file_type (str): Type of file to update ('Geom', 'Flow', or 'Unsteady')
        entry_number (int): Number (from 1 to 99) to set
        ras_object (RasPrj, optional): RAS object to use. If None, uses the default ras object.

        Raises:
        ValueError: If an invalid file_type is provided
        FileNotFoundError: If the plan file doesn't exist

        Example:
        >>> update_plan_entries(1, "Geom", 2)
        >>> update_plan_entries("path/to/plan.p01", "Geom", 2)
        """
        ras_obj = ras_object or ras
        ras_obj.check_initialized()
        
        valid_file_types = {'Geom': 'g', 'Flow': 'f', 'Unsteady': 'u'}
        if file_type not in valid_file_types:
            raise ValueError(f"Invalid file_type. Expected one of: {', '.join(valid_file_types.keys())}")

        plan_file_path = Path(plan_number_or_path)
        if not plan_file_path.is_file():
            plan_file_path = RasUtils.get_plan_path(plan_number_or_path, ras_object)
        
        if not plan_file_path.exists():
            raise FileNotFoundError(f"Plan file not found: {plan_file_path}")

        file_prefix = valid_file_types[file_type]
        search_pattern = f"{file_type} File="
        entry_number = f"{int(entry_number):02d}"  # Ensure two-digit format

        RasUtils.check_file_access(plan_file_path, 'r')
        with open(plan_file_path, 'r') as file:
            lines = file.readlines()

        for i, line in enumerate(lines):
            if line.startswith(search_pattern):
                lines[i] = f"{search_pattern}{file_prefix}{entry_number}\n"
                logging.info(f"Updated {file_type} File in {plan_file_path} to {file_prefix}{entry_number}")
                break

        with plan_file_path.open('w') as file:
            file.writelines(lines)

        logging.info(f"Successfully updated plan file: {plan_file_path}")
        ras_obj.plan_df = ras_obj.get_plan_entries()
        ras_obj.geom_df = ras_obj.get_geom_entries()
        ras_obj.flow_df = ras_obj.get_flow_entries()
        ras_obj.unsteady_df = ras_obj.get_unsteady_entries()

    @staticmethod
    def check_file_access(file_path, mode='r'):
        path = Path(file_path)
        if not path.exists():
            raise FileNotFoundError(f"File not found: {file_path}")
        if mode in ('r', 'rb') and not os.access(path, os.R_OK):
            raise PermissionError(f"Read permission denied for file: {file_path}")
        if mode in ('w', 'wb', 'a', 'ab') and not os.access(path.parent, os.W_OK):
            raise PermissionError(f"Write permission denied for directory: {path.parent}")


==================================================

File: c:\GH\ras_commander\ras_commander\README.md
==================================================
"""
# Developer's README

These notes should be followed by any developer who wants to use this library orcontribute to this project.

-----


# Developer's README for ras_commander

## Project Overview

ras_commander is a Python library for automating HEC-RAS operations. It provides a set of classes and functions to interact with HEC-RAS project files, execute simulations, and manage project data.

## Project Structure

The library is organized into several key modules:

- `RasPrj.py`: Handles project initialization and manages project-level information.
- `RasCommander.py`: Manages execution of HEC-RAS simulations.
- `RasPlan.py`: Provides functions for modifying and updating plan files.
- `RasGeo.py`: Handles operations related to geometry files.
- `RasUnsteady.py`: Manages unsteady flow file operations.
- `RasUtils.py`: Contains utility functions for file operations and data management.

## Key Concepts

### RAS Instance Management

The library supports both a global `ras` instance and the ability to create multiple instances for different projects:

- Use the global `ras` instance for simple, single-project scenarios.
- Create multiple `RasPrj` instances for working with multiple projects simultaneously.

### Function Design

Most functions in the library follow this pattern:

```python
def some_function(param1, param2, ras_object=None):
    ras_obj = ras_object or ras
    ras_obj.check_initialized()
    # Function implementation
```

This design allows for flexibility in using either the global instance or a specific project instance.

## ras_commander Best Practices

1. Always check if a project is initialized before performing operations:
   ```python
   ras_obj.check_initialized()
   ```

2. Use the `ras_object` parameter in functions to specify which project instance to use.

3. For complex projects with multiple HEC-RAS folders, prefer passing explicit `ras_object` instances to functions for clarity.

4. Use type hints and descriptive variable names to improve code readability.

5. Handle exceptions appropriately, especially for file operations and HEC-RAS interactions.

6. When adding new functionality, consider its placement within the existing class structure.

7. Update the `__init__.py` file when adding new modules or significant functionality.

## Testing

- Write unit tests for all new functions and methods.
- Ensure tests cover both single-project and multi-project scenarios.
- Use the `unittest` framework for consistency with existing tests.

## Documentation

- Keep docstrings up-to-date with any changes to function signatures or behavior.
- Update the main README.md file when adding new features or changing existing functionality.
- Consider adding or updating example scripts in the `examples/` directory for new features.
- Build a notebook first!  We have AI to help us integrate functions into the library once we have a working example. 


## Performance Considerations

- For parallel execution of plans, refer to the "Benchmarking is All You Need" blog post in the HEC-Commander repository for guidance on optimal core usage.

## Abbreviations

Consistently use these abbreviations throughout the codebase:

- ras: HEC-RAS
- prj: Project
- geom: Geometry
- pre: Preprocessor
- geompre: Geometry Preprocessor
- num: Number
- init: Initialize
- XS: Cross Section
- DSS: Data Storage System
- GIS: Geographic Information System
- BC: Boundary Condition
- IC: Initial Condition
- TW: Tailwater

## Future Development

Refer to the "Future Development Roadmap" for planned enhancements and features to be implemented.

By following these guidelines, we can maintain consistency, readability, and reliability across the ras_commander library.






















# Understanding and Using RAS Instances in ras_commander

The `RasPrj` class now supports both a global instance named `ras` and the ability to create multiple instances for different projects.

Key points about RAS instances:

1. **Global Instance**: A default global instance named `ras` is still available for backwards compatibility and simple use cases.
2. **Multiple Instances**: Users can create and manage multiple `RasPrj` instances for different projects.
3. **Flexible Function Calls**: Most functions now accept an optional `ras_object` parameter, allowing use of specific project instances.
4. **Consistent State**: Each instance maintains its own project state, ensuring data consistency within each project context.

## Using RAS Instances

### Global Instance
For simple, single-project scenarios:

```python
from ras_commander import ras, init_ras_project

# Initialize the global instance
init_ras_project("/path/to/project", "6.5")

# Use the global instance
print(f"Working with project: {ras.project_name}")
plan_file = ras.get_plan_path("01")
```

### Multiple Instances
For working with multiple projects:

```python
from ras_commander import RasPrj, init_ras_project

# Create and initialize separate instances
project1 = init_ras_project("/path/to/project1", "6.5")
project2 = init_ras_project("/path/to/project2", "6.5")

# Use specific instances in function calls
RasPlan.set_geom("01", "02", ras_object=project1)
RasPlan.set_geom("01", "03", ras_object=project2)
```

### Best Practices
1. Always check if a project is initialized before using:
   ```python
   def my_function(ras_object=None):
       ras_obj = ras_object or ras
       ras_obj.check_initialized()
       # Proceed with operations using ras_obj
   ```

2. Use the `ras_object` parameter in functions to specify which project instance to use.

3. For any advance usage with multiple projects, you shouldprefer passing explicit `ras_object` instances to functions for clarity and to avoid unintended use of the global instance.

By supporting both a global instance and multiple instances, ras_commander provides flexibility for various usage scenarios while maintaining simplicity for basic use cases.

"""
==================================================

File: c:\GH\ras_commander\ras_commander\__init__.py
==================================================
from importlib.metadata import version, PackageNotFoundError

try:
    __version__ = version("ras_commander")
except PackageNotFoundError:
    # package is not installed
    __version__ = "unknown"

# Import all necessary functions and classes directly
from .RasPrj import ras, init_ras_project, get_ras_exe
from .RasPrj import RasPrj
from .RasPlan import RasPlan
from .RasGeo import RasGeo
from .RasUnsteady import RasUnsteady
from .RasCommander import RasCommander
from .RasUtils import RasUtils
from .RasExamples import RasExamples

# Import all attributes from these modules
from .RasPrj import *
from .RasPrj import *
from .RasPlan import *
from .RasGeo import *
from .RasUnsteady import *
from .RasCommander import *
from .RasUtils import *
from .RasExamples import *
# Define __all__ to specify what should be imported when using "from ras_commander import *"
__all__ = [
    "ras",
    "init_ras_project",
    "get_ras_exe",
    "RasPrj",
    "RasPlan",
    "RasGeo",
    "RasUnsteady",
    "RasCommander",
    "RasUtils",
    "RasExamples"
]
==================================================

