ras-commander 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 William M. Katzenmeyer
4
+
5
+ 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.
@@ -0,0 +1,135 @@
1
+ Metadata-Version: 2.1
2
+ Name: ras_commander
3
+ Version: 0.1.0
4
+ Summary: A library for automating HEC-RAS operations using python functions.
5
+ Home-page: https://github.com/yourusername/ras_commander
6
+ Author: William Katzenmeyer, P.E., C.F.M.
7
+ Author-email: heccommander@gmail.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: pandas
15
+
16
+ ## RAS-Commander Library Organization
17
+
18
+ | Directory/File | Purpose |
19
+ |---|---|
20
+ | ras_commander/__init__.py | Initializes the library and defines the public API. |
21
+ | ras_commander/execution.py | Handles execution of HEC-RAS simulations. |
22
+ | ras_commander/file_operations.py | Provides functions for reading and parsing HEC-RAS project files. |
23
+ | ras_commander/geometry_operations.py | Provides functions for manipulating geometry files. |
24
+ | ras_commander/plan_operations.py | Provides functions for modifying and updating plan files. |
25
+ | ras_commander/project_config.py | Defines the ProjectConfig class for managing project-level information. |
26
+ | ras_commander/project_init.py | Provides the `init_ras_project` function to initialize a project. |
27
+ | ras_commander/project_management.py | Provides functions for managing HEC-RAS projects (e.g., copying files, updating project file). |
28
+ | ras_commander/project_setup.py | Provides helper functions for project setup (e.g., finding project file, loading project data). |
29
+ | ras_commander/unsteady_operations.py | Provides functions for manipulating unsteady flow files. |
30
+ | ras_commander/utilities.py | Provides general utility functions (e.g., file backup, directory creation). |
31
+
32
+
33
+ ## Project Organization Diagram
34
+
35
+ ```
36
+ ras_commander
37
+ ├── execution.py
38
+ ├── file_operations.py
39
+ ├── geometry_operations.py
40
+ ├── plan_operations.py
41
+ ├── project_config.py
42
+ ├── project_init.py
43
+ ├── project_management.py
44
+ ├── project_setup.py
45
+ ├── unsteady_operations.py
46
+ └── utilities.py
47
+ ```
48
+
49
+ ## Functions Overview
50
+
51
+ | Function | Arguments | Purpose |
52
+ |---|---|---|
53
+ | `init_ras_project` | `ras_project_folder`, `hecras_exe_path` | Initializes a HEC-RAS project by setting up the `ProjectConfig` with project details. |
54
+ | `compute_hecras_plan` | `plan_file` | Executes a HEC-RAS plan file. |
55
+ | `compute_hecras_plan_from_folder` | `test_plan_file`, `test_folder_path` | Execute a single HEC-RAS plan from a folder other than the project path.
56
+ | `recreate_test_function` | `project_folder` | Recreates the -test function from the HEC-RAS interface, primarily by copying the project directory, forcing recomputation, and running each plan. |
57
+ | `run_plans_parallel` | `config`, `max_workers`, `cores_per_run` | Run HEC-RAS plans in parallel. |
58
+ | `run_all_plans_parallel` | `project_folder`, `hecras_exe_path` | Run all HEC-RAS plans in parallel from a project folder path. |
59
+ | `find_hecras_project_file` | `folder_path` | Locates the HEC-RAS project file (.prj) within a given folder. |
60
+ | `get_project_name` | `project_path` | Extracts the project name from the project file path. |
61
+ | `get_plan_entries` | `project_file` | Parses the project file to extract plan file information into a DataFrame. |
62
+ | `get_flow_entries` | `project_file` | Parses the project file to extract steady flow file information into a DataFrame. |
63
+ | `get_unsteady_entries` | `project_file` | Parses the project file to extract unsteady flow file information into a DataFrame. |
64
+ | `get_geom_entries` | `project_file` | Parses the project file to extract geometry file information into a DataFrame. |
65
+ | `clear_geometry_preprocessor_files` | `plan_file` | Deletes the geometry preprocessor files (.cXX) associated with a plan file. |
66
+ | `clear_geometry_preprocessor_files_for_all_plans` | | Deletes geometry preprocessor files for all plans in the project directory. |
67
+ | `copy_geometry_files` | `dst_folder`, `template_geom` | Copies geometry files from a template to a destination folder, assigning the next available geometry number. |
68
+ | `rename_geometry_files` | `old_number`, `new_number` | Renames geometry files (both .gXX and .gXX.hdf) in the project folder. |
69
+ | `update_geometry_reference_in_plan` | `plan_file`, `new_geometry_number` | Updates the "Geom File=" entry in a plan file to reference a new geometry number. |
70
+ | `apply_geometry_to_plan` | `plan_file`, `geometry_number` | Sets the geometry file used by a plan file. |
71
+ | `apply_flow_to_plan` | `plan_file`, `flow_number` | Sets the steady flow file used by a plan file. |
72
+ | `copy_plan_from_template` | `template_plan` | Creates a new plan file by copying a template and updates the project file with the new plan entry. |
73
+ | `get_next_available_number` | `existing_numbers` | Finds the next available number (e.g., for plans, unsteady flows) based on existing ones. |
74
+ | `apply_unsteady_to_plan` | `plan_file`, `unsteady_number` | Sets the unsteady flow file used by a plan file. |
75
+ | `set_num_cores` | `plan_file`, `num_cores` | Sets the maximum number of cores to be used for a plan file. |
76
+ | `update_geompre_flags` | `file_path`, `run_htab_value`, `use_ib_tables_value` | Updates the geometry preprocessor flags in a plan file. |
77
+ | `get_plan_full_path` | `plan_number` | Returns the full path to a plan file based on its number. |
78
+ | `get_results_full_path` | `plan_number` | Returns the full path to a plan's results file (.hdf) based on its number. |
79
+ | `get_flow_full_path` | `flow_number` | Returns the full path to a steady flow file based on its number. |
80
+ | `get_unsteady_full_path` | `unsteady_number` | Returns the full path to an unsteady flow file based on its number. |
81
+ | `get_geom_full_path` | `geometry_number` | Returns the full path to a geometry file based on its number. |
82
+ | `copy_unsteady_files` | `dst_folder`, `template_unsteady` | Copies unsteady flow files from a template to a destination folder, assigning the next available unsteady number. |
83
+ | `rename_unsteady_files` | `old_number`, `new_number` | Renames unsteady flow files (both .uXX and .uXX.hdf) in the project folder. |
84
+ | `update_unsteady_reference_in_plan` | `plan_file`, `new_unsteady_number` | Updates the "Unsteady File=" entry in a plan file to reference a new unsteady flow number. |
85
+ | `modify_unsteady_flow_parameters` | `unsteady_file`, `modifications` | Modifies parameters within an unsteady flow file based on a dictionary of changes. |
86
+ | `create_backup` | `file_path`, `backup_suffix` | Creates a backup copy of a file. |
87
+ | `restore_from_backup` | `backup_path`, `remove_backup` | Restores a file from a backup copy. |
88
+ | `safe_remove` | `file_path` | Removes a file if it exists, handling potential errors. |
89
+ | `ensure_directory` | `directory_path` | Creates a directory if it does not exist. |
90
+ | `list_files_with_extension` | `extension` | Lists files with a specific extension in the project directory. |
91
+ | `get_file_size` | `file_path` | Returns the size of a file in bytes. |
92
+ | `get_modification_time` | `file_path` | Returns the last modification time of a file. |
93
+ | `get_plan_path` | `current_plan_number` | Returns the full path to a plan file based on its number. |
94
+ | `retry_remove_folder` | `folder_path`, `max_attempts`, `initial_delay` | Attempts to remove a folder with retry and a delay. |
95
+
96
+
97
+
98
+ ## Potential Uses of RAS-Commander Functions
99
+
100
+ The RAS-Commander library offers a wide range of functionalities that can be used to automate various aspects of HEC-RAS modeling workflows. Here are some potential uses:
101
+
102
+ **1. Automated Plan Creation and Execution:**
103
+
104
+ * **Batch processing of multiple scenarios:** RAS-Commander allows you to create new plan files based on templates, modify plan parameters (e.g., geometry, flow, unsteady flow files, number of cores), and execute them in parallel. This can be useful for analyzing multiple scenarios with different inputs or model configurations.
105
+ * **Sensitivity analysis and optimization:** By combining RAS-Commander with other libraries (e.g., for parameter sampling or optimization), you can automate sensitivity analysis and parameter optimization studies.
106
+ * **Monte Carlo simulations:** RAS-Commander can be used to automate the execution of Monte Carlo simulations by creating multiple plan files with randomly sampled input parameters.
107
+
108
+ **2. Project Management and Organization:**
109
+
110
+ * **Automated file management:** Functions for copying, renaming, and updating file references help maintain consistency and organization within your HEC-RAS project.
111
+ * **Backup and restore functionalities:** Ensure project integrity by creating backups of project files and restoring them when needed.
112
+ * **Project setup and initialization:** Streamline the process of setting up new HEC-RAS projects with standardized configurations.
113
+
114
+ **3. Advanced Modeling Techniques:**
115
+
116
+ * **Coupled modeling:** RAS-Commander can be used to automate the setup and execution of coupled models (e.g., HEC-RAS with other hydrodynamic or hydrologic models).
117
+ * **Data assimilation and calibration:** By integrating RAS-Commander with data assimilation or calibration tools, you can automate the process of updating model parameters based on observed data.
118
+ * **Post-processing and analysis:** RAS-Commander can be used to extract results from HEC-RAS output files and perform post-processing and analysis tasks.
119
+
120
+
121
+ **4. Integration with Other Tools:**
122
+
123
+ * **Python scripting and automation:** RAS-Commander can be easily integrated into Python scripts and workflows for more complex automation tasks.
124
+ * **Web applications and dashboards:** Develop web applications and dashboards that allow users to interact with HEC-RAS models and visualize results through a user-friendly interface.
125
+ * **Integration with GIS software:** RAS-Commander can be used to link HEC-RAS models with GIS software for spatial analysis and visualization.
126
+
127
+
128
+ **Examples:**
129
+
130
+ * **Floodplain mapping:** Automate the creation of floodplain maps for different return periods by creating and executing multiple plan files with varying flow conditions.
131
+ * **Dam break analysis:** Automate the setup and execution of dam break simulations by modifying unsteady flow parameters and boundary conditions.
132
+ * **Bridge scour analysis:** Automate the assessment of bridge scour potential by integrating HEC-RAS with bridge scour analysis tools.
133
+
134
+
135
+ By automating repetitive tasks and providing a framework for managing complex workflows, RAS-Commander can significantly improve the efficiency and reproducibility of HEC-RAS modeling projects. This can lead to better decision-making and more accurate and reliable results.
@@ -0,0 +1,120 @@
1
+ ## RAS-Commander Library Organization
2
+
3
+ | Directory/File | Purpose |
4
+ |---|---|
5
+ | ras_commander/__init__.py | Initializes the library and defines the public API. |
6
+ | ras_commander/execution.py | Handles execution of HEC-RAS simulations. |
7
+ | ras_commander/file_operations.py | Provides functions for reading and parsing HEC-RAS project files. |
8
+ | ras_commander/geometry_operations.py | Provides functions for manipulating geometry files. |
9
+ | ras_commander/plan_operations.py | Provides functions for modifying and updating plan files. |
10
+ | ras_commander/project_config.py | Defines the ProjectConfig class for managing project-level information. |
11
+ | ras_commander/project_init.py | Provides the `init_ras_project` function to initialize a project. |
12
+ | ras_commander/project_management.py | Provides functions for managing HEC-RAS projects (e.g., copying files, updating project file). |
13
+ | ras_commander/project_setup.py | Provides helper functions for project setup (e.g., finding project file, loading project data). |
14
+ | ras_commander/unsteady_operations.py | Provides functions for manipulating unsteady flow files. |
15
+ | ras_commander/utilities.py | Provides general utility functions (e.g., file backup, directory creation). |
16
+
17
+
18
+ ## Project Organization Diagram
19
+
20
+ ```
21
+ ras_commander
22
+ ├── execution.py
23
+ ├── file_operations.py
24
+ ├── geometry_operations.py
25
+ ├── plan_operations.py
26
+ ├── project_config.py
27
+ ├── project_init.py
28
+ ├── project_management.py
29
+ ├── project_setup.py
30
+ ├── unsteady_operations.py
31
+ └── utilities.py
32
+ ```
33
+
34
+ ## Functions Overview
35
+
36
+ | Function | Arguments | Purpose |
37
+ |---|---|---|
38
+ | `init_ras_project` | `ras_project_folder`, `hecras_exe_path` | Initializes a HEC-RAS project by setting up the `ProjectConfig` with project details. |
39
+ | `compute_hecras_plan` | `plan_file` | Executes a HEC-RAS plan file. |
40
+ | `compute_hecras_plan_from_folder` | `test_plan_file`, `test_folder_path` | Execute a single HEC-RAS plan from a folder other than the project path.
41
+ | `recreate_test_function` | `project_folder` | Recreates the -test function from the HEC-RAS interface, primarily by copying the project directory, forcing recomputation, and running each plan. |
42
+ | `run_plans_parallel` | `config`, `max_workers`, `cores_per_run` | Run HEC-RAS plans in parallel. |
43
+ | `run_all_plans_parallel` | `project_folder`, `hecras_exe_path` | Run all HEC-RAS plans in parallel from a project folder path. |
44
+ | `find_hecras_project_file` | `folder_path` | Locates the HEC-RAS project file (.prj) within a given folder. |
45
+ | `get_project_name` | `project_path` | Extracts the project name from the project file path. |
46
+ | `get_plan_entries` | `project_file` | Parses the project file to extract plan file information into a DataFrame. |
47
+ | `get_flow_entries` | `project_file` | Parses the project file to extract steady flow file information into a DataFrame. |
48
+ | `get_unsteady_entries` | `project_file` | Parses the project file to extract unsteady flow file information into a DataFrame. |
49
+ | `get_geom_entries` | `project_file` | Parses the project file to extract geometry file information into a DataFrame. |
50
+ | `clear_geometry_preprocessor_files` | `plan_file` | Deletes the geometry preprocessor files (.cXX) associated with a plan file. |
51
+ | `clear_geometry_preprocessor_files_for_all_plans` | | Deletes geometry preprocessor files for all plans in the project directory. |
52
+ | `copy_geometry_files` | `dst_folder`, `template_geom` | Copies geometry files from a template to a destination folder, assigning the next available geometry number. |
53
+ | `rename_geometry_files` | `old_number`, `new_number` | Renames geometry files (both .gXX and .gXX.hdf) in the project folder. |
54
+ | `update_geometry_reference_in_plan` | `plan_file`, `new_geometry_number` | Updates the "Geom File=" entry in a plan file to reference a new geometry number. |
55
+ | `apply_geometry_to_plan` | `plan_file`, `geometry_number` | Sets the geometry file used by a plan file. |
56
+ | `apply_flow_to_plan` | `plan_file`, `flow_number` | Sets the steady flow file used by a plan file. |
57
+ | `copy_plan_from_template` | `template_plan` | Creates a new plan file by copying a template and updates the project file with the new plan entry. |
58
+ | `get_next_available_number` | `existing_numbers` | Finds the next available number (e.g., for plans, unsteady flows) based on existing ones. |
59
+ | `apply_unsteady_to_plan` | `plan_file`, `unsteady_number` | Sets the unsteady flow file used by a plan file. |
60
+ | `set_num_cores` | `plan_file`, `num_cores` | Sets the maximum number of cores to be used for a plan file. |
61
+ | `update_geompre_flags` | `file_path`, `run_htab_value`, `use_ib_tables_value` | Updates the geometry preprocessor flags in a plan file. |
62
+ | `get_plan_full_path` | `plan_number` | Returns the full path to a plan file based on its number. |
63
+ | `get_results_full_path` | `plan_number` | Returns the full path to a plan's results file (.hdf) based on its number. |
64
+ | `get_flow_full_path` | `flow_number` | Returns the full path to a steady flow file based on its number. |
65
+ | `get_unsteady_full_path` | `unsteady_number` | Returns the full path to an unsteady flow file based on its number. |
66
+ | `get_geom_full_path` | `geometry_number` | Returns the full path to a geometry file based on its number. |
67
+ | `copy_unsteady_files` | `dst_folder`, `template_unsteady` | Copies unsteady flow files from a template to a destination folder, assigning the next available unsteady number. |
68
+ | `rename_unsteady_files` | `old_number`, `new_number` | Renames unsteady flow files (both .uXX and .uXX.hdf) in the project folder. |
69
+ | `update_unsteady_reference_in_plan` | `plan_file`, `new_unsteady_number` | Updates the "Unsteady File=" entry in a plan file to reference a new unsteady flow number. |
70
+ | `modify_unsteady_flow_parameters` | `unsteady_file`, `modifications` | Modifies parameters within an unsteady flow file based on a dictionary of changes. |
71
+ | `create_backup` | `file_path`, `backup_suffix` | Creates a backup copy of a file. |
72
+ | `restore_from_backup` | `backup_path`, `remove_backup` | Restores a file from a backup copy. |
73
+ | `safe_remove` | `file_path` | Removes a file if it exists, handling potential errors. |
74
+ | `ensure_directory` | `directory_path` | Creates a directory if it does not exist. |
75
+ | `list_files_with_extension` | `extension` | Lists files with a specific extension in the project directory. |
76
+ | `get_file_size` | `file_path` | Returns the size of a file in bytes. |
77
+ | `get_modification_time` | `file_path` | Returns the last modification time of a file. |
78
+ | `get_plan_path` | `current_plan_number` | Returns the full path to a plan file based on its number. |
79
+ | `retry_remove_folder` | `folder_path`, `max_attempts`, `initial_delay` | Attempts to remove a folder with retry and a delay. |
80
+
81
+
82
+
83
+ ## Potential Uses of RAS-Commander Functions
84
+
85
+ The RAS-Commander library offers a wide range of functionalities that can be used to automate various aspects of HEC-RAS modeling workflows. Here are some potential uses:
86
+
87
+ **1. Automated Plan Creation and Execution:**
88
+
89
+ * **Batch processing of multiple scenarios:** RAS-Commander allows you to create new plan files based on templates, modify plan parameters (e.g., geometry, flow, unsteady flow files, number of cores), and execute them in parallel. This can be useful for analyzing multiple scenarios with different inputs or model configurations.
90
+ * **Sensitivity analysis and optimization:** By combining RAS-Commander with other libraries (e.g., for parameter sampling or optimization), you can automate sensitivity analysis and parameter optimization studies.
91
+ * **Monte Carlo simulations:** RAS-Commander can be used to automate the execution of Monte Carlo simulations by creating multiple plan files with randomly sampled input parameters.
92
+
93
+ **2. Project Management and Organization:**
94
+
95
+ * **Automated file management:** Functions for copying, renaming, and updating file references help maintain consistency and organization within your HEC-RAS project.
96
+ * **Backup and restore functionalities:** Ensure project integrity by creating backups of project files and restoring them when needed.
97
+ * **Project setup and initialization:** Streamline the process of setting up new HEC-RAS projects with standardized configurations.
98
+
99
+ **3. Advanced Modeling Techniques:**
100
+
101
+ * **Coupled modeling:** RAS-Commander can be used to automate the setup and execution of coupled models (e.g., HEC-RAS with other hydrodynamic or hydrologic models).
102
+ * **Data assimilation and calibration:** By integrating RAS-Commander with data assimilation or calibration tools, you can automate the process of updating model parameters based on observed data.
103
+ * **Post-processing and analysis:** RAS-Commander can be used to extract results from HEC-RAS output files and perform post-processing and analysis tasks.
104
+
105
+
106
+ **4. Integration with Other Tools:**
107
+
108
+ * **Python scripting and automation:** RAS-Commander can be easily integrated into Python scripts and workflows for more complex automation tasks.
109
+ * **Web applications and dashboards:** Develop web applications and dashboards that allow users to interact with HEC-RAS models and visualize results through a user-friendly interface.
110
+ * **Integration with GIS software:** RAS-Commander can be used to link HEC-RAS models with GIS software for spatial analysis and visualization.
111
+
112
+
113
+ **Examples:**
114
+
115
+ * **Floodplain mapping:** Automate the creation of floodplain maps for different return periods by creating and executing multiple plan files with varying flow conditions.
116
+ * **Dam break analysis:** Automate the setup and execution of dam break simulations by modifying unsteady flow parameters and boundary conditions.
117
+ * **Bridge scour analysis:** Automate the assessment of bridge scour potential by integrating HEC-RAS with bridge scour analysis tools.
118
+
119
+
120
+ By automating repetitive tasks and providing a framework for managing complex workflows, RAS-Commander can significantly improve the efficiency and reproducibility of HEC-RAS modeling projects. This can lead to better decision-making and more accurate and reliable results.
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools>=42", "wheel"]
3
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,24 @@
1
+ """
2
+ ras_commander
3
+ A library for automating HEC-RAS operations.
4
+ """
5
+ from .project_init import init_ras_project
6
+ from .file_operations import FileOperations
7
+ from .project_management import ProjectManager
8
+ from .plan_operations import PlanOperations
9
+ from .geometry_operations import GeometryOperations
10
+ from .unsteady_operations import UnsteadyOperations
11
+ from .execution import RasExecutor
12
+ from .utilities import Utilities
13
+
14
+ __all__ = [
15
+ 'init_ras_project',
16
+ 'FileOperations',
17
+ 'ProjectManager',
18
+ 'PlanOperations',
19
+ 'GeometryOperations',
20
+ 'UnsteadyOperations',
21
+ 'RasExecutor',
22
+ 'Utilities'
23
+ ]
24
+ __version__ = "0.1.0"
@@ -0,0 +1,315 @@
1
+ """
2
+ Execution operations for runningHEC-RAS simulations using subprocess.
3
+ 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.
4
+ """
5
+
6
+ import os
7
+ import subprocess
8
+ import shutil
9
+ from pathlib import Path
10
+ from concurrent.futures import ThreadPoolExecutor, as_completed
11
+ from .file_operations import FileOperations
12
+ from .plan_operations import PlanOperations
13
+ import subprocess
14
+ import os
15
+ import logging
16
+ import time
17
+ from .project_config import ProjectConfig
18
+ import pandas as pd
19
+
20
+ class RasExecutor:
21
+
22
+ @staticmethod
23
+ def compute_hecras_plan(compute_plan_file):
24
+ """
25
+ Compute a HEC-RAS plan using the provided plan file.
26
+
27
+ This method executes a HEC-RAS plan by running the HEC-RAS executable
28
+ with the specified plan file. It uses the plan file's path to determine
29
+ the corresponding project file path.
30
+
31
+ Args:
32
+ compute_plan_file (str): The full path to the HEC-RAS plan file (.p**)
33
+
34
+ Returns:
35
+ bool: True if the plan computation was successful, False otherwise.
36
+
37
+ Raises:
38
+ subprocess.CalledProcessError: If the HEC-RAS execution fails.
39
+
40
+ Note:
41
+ This method assumes that the project file (.prj) is in the same
42
+ directory as the plan file and has the same name (different extension).
43
+ """
44
+ config = ProjectConfig()
45
+ config.check_initialized()
46
+
47
+ # Derive the project file path from the plan file path
48
+ compute_project_file = Path(compute_plan_file).with_suffix('.prj')
49
+
50
+ cmd = f'"{config.hecras_exe_path}" -c "{compute_project_file}" "{compute_plan_file}"'
51
+ print(f"Running command: {cmd}")
52
+ try:
53
+ subprocess.run(cmd, check=True, shell=True, capture_output=True, text=True)
54
+ logging.info(f"HEC-RAS execution completed for plan: {Path(compute_plan_file).name}")
55
+ return True
56
+ except subprocess.CalledProcessError as e:
57
+ logging.error(f"Error running plan: {Path(compute_plan_file).name}")
58
+ logging.error(f"Error message: {e.output}")
59
+ return False
60
+
61
+
62
+ @staticmethod
63
+ def compute_hecras_plan_from_folder(plan_for_compute, folder_for_compute):
64
+ """
65
+ Compute a HEC-RAS plan from a specified folder.
66
+
67
+ This function allows running a plan directly from a different folder than the project folder,
68
+ which is useful for the -test function and parallel runs. It uses the HEC-RAS executable path
69
+ from the ProjectConfig, but derives other paths from the provided arguments.
70
+
71
+ Args:
72
+ plan_for_compute (str): The full path to the HEC-RAS plan file (.p**) to be computed.
73
+ folder_for_compute (str): The folder containing the HEC-RAS project files.
74
+
75
+ Returns:
76
+ bool: True if the plan computation was successful, False otherwise.
77
+
78
+ Raises:
79
+ subprocess.CalledProcessError: If the HEC-RAS execution fails.
80
+
81
+ Note:
82
+ This function uses the ProjectConfig only to get the hecras_exe_path.
83
+ Other paths are derived from plan_for_compute and folder_for_compute.
84
+ """
85
+ config = ProjectConfig()
86
+ config.check_initialized()
87
+ compute_project_file = FileOperations.find_hecras_project_file(folder_for_compute)
88
+ cmd = f'"{config.hecras_exe_path}" -c "{compute_project_file}" "{plan_for_compute}"'
89
+ print(f"Running command: {cmd}")
90
+ try:
91
+ subprocess.run(cmd, check=True, shell=True, capture_output=True, text=True)
92
+ logging.info(f"HEC-RAS execution completed for plan: {Path(plan_for_compute).name}")
93
+ return True
94
+ except subprocess.CalledProcessError as e:
95
+ logging.error(f"Error running plan: {Path(plan_for_compute).name}")
96
+ logging.error(f"Error message: {e.output}")
97
+ return False
98
+
99
+
100
+ @staticmethod
101
+ def recreate_test_function(project_folder):
102
+ """
103
+ Recreate the -test function of HEC-RAS command line.
104
+
105
+ Parameters:
106
+ project_folder (str): Path to the HEC-RAS project folder
107
+
108
+ Returns:
109
+ None
110
+
111
+
112
+ This function executes all ras plans in a separate folder defined by compute_folder='[Test]', so we need to call the individual functions and use distinct variable names
113
+ For this function, we are using "compute" in the varable names as these are the values used for the compute operations in a separate copy of the ras project folder
114
+
115
+ """
116
+ print("Starting the recreate_test_function...")
117
+
118
+ # Create the test folder path
119
+ compute_folder='[Test]'
120
+ folder_for_compute = Path(project_folder).parent / f"{Path(project_folder).name} {compute_folder}"
121
+ print(f"Creating the test folder: {folder_for_compute}...")
122
+
123
+ # Copy the project folder to the test folder
124
+ print("Copying project folder to the test folder...")
125
+ shutil.copytree(project_folder, folder_for_compute, dirs_exist_ok=True)
126
+ print(f"Test folder created at: {folder_for_compute}")
127
+
128
+ # Find the project file
129
+ print("Finding the project file...")
130
+ compute_project_file = FileOperations.find_hecras_project_file(folder_for_compute)
131
+
132
+ if not compute_project_file:
133
+ print("Project file not found.")
134
+ return
135
+ print(f"Project file found: {compute_project_file}")
136
+
137
+ # Parse the project file to get plan entries
138
+ print("Parsing the project file to get plan entries...")
139
+ ras_compute_plan_entries = FileOperations.get_plan_entries(compute_project_file)
140
+ print("Parsed project file successfully.")
141
+
142
+ # Enforce recomputing of geometry preprocessor and IB tables
143
+ print("Enforcing recomputing of geometry preprocessor and IB tables...")
144
+ for plan_file in ras_compute_plan_entries['full_path']:
145
+ PlanOperations.update_geompre_flags(plan_file, run_htab_value=-1, use_ib_tables_value=-1)
146
+ print("Recomputing enforced successfully.")
147
+
148
+ # Change max cores to 1
149
+ print("Changing max cores to 2 for all plan files...")
150
+ for plan_file in ras_compute_plan_entries['full_path']:
151
+ PlanOperations.set_num_cores(plan_file, num_cores=2)
152
+ print("Max cores updated successfully.")
153
+
154
+ # Run all plans sequentially
155
+ print("Running all plans sequentially...")
156
+ for _, plan in ras_compute_plan_entries.iterrows():
157
+ plan_for_compute = plan["full_path"]
158
+ RasExecutor.compute_hecras_plan_from_folder(plan_for_compute, folder_for_compute)
159
+
160
+ print("All plans have been executed.")
161
+ print("recreate_test_function completed.")
162
+
163
+ @staticmethod
164
+ def run_plans_parallel(config, max_workers, cores_per_run):
165
+ """
166
+ Run HEC-RAS plans in parallel using ThreadPoolExecutor.
167
+
168
+ Parameters:
169
+ config (ProjectConfig): Configuration object containing project information
170
+ max_workers (int): Maximum number of parallel runs
171
+ cores_per_run (int): Number of cores to use per run
172
+
173
+ Returns:
174
+ dict: Dictionary with plan numbers as keys and execution success as values
175
+
176
+ This function executes all ras plans in separate folders defined by the max_workers and the numbering of the test folders [Test 1], [Test 2], etc.
177
+ Each worker operates sequentially on its assigned folder while all workers operate in parallel.
178
+
179
+ Revisions:
180
+ 1. Created a pandas DataFrame to map each folder to a plan for execution.
181
+ 2. Implemented logic to assign worker numbers and compute folders.
182
+ 3. Used find_hecras_project_file function to get the full path of project files.
183
+ 4. Updated plan file paths to use compute folders.
184
+ 5. Revised the parallel execution to use separate threads for each worker.
185
+ 6. Implemented a queue system for each worker to handle plans sequentially.
186
+ 7. Updated the cleanup process to use the new folder structure.
187
+ """
188
+ import queue
189
+ from threading import Thread
190
+
191
+ project_folder = Path(config.project_file).parent
192
+ test_folders = []
193
+
194
+ # Create multiple copies of the project folder
195
+ for i in range(1, max_workers + 1):
196
+ folder_for_compute = project_folder.parent / f"{project_folder.name} [Test {i}]"
197
+ shutil.copytree(project_folder, folder_for_compute, dirs_exist_ok=True)
198
+ test_folders.append(folder_for_compute)
199
+ print(f"Created test folder: {folder_for_compute}")
200
+
201
+ compute_parallel_entries = []
202
+ for i, (_, plan_row) in enumerate(config.ras_plan_entries.iterrows()):
203
+ worker_number = i % max_workers
204
+ compute_folder = test_folders[worker_number]
205
+ compute_project_file = FileOperations.find_hecras_project_file(compute_folder)
206
+ compute_plan_file = compute_folder / Path(plan_row['full_path']).name
207
+ compute_parallel_entries.append({
208
+ 'worker_number': worker_number,
209
+ 'compute_folder': compute_folder,
210
+ 'compute_project_file': compute_project_file,
211
+ 'compute_plan_file': compute_plan_file,
212
+ 'plan_number': plan_row['plan_number']
213
+ })
214
+
215
+ compute_parallel_df = pd.DataFrame(compute_parallel_entries)
216
+ print("compute_parallel_entries dataframe:")
217
+ display(compute_parallel_df)
218
+
219
+ results = {}
220
+ worker_queues = [queue.Queue() for _ in range(max_workers)]
221
+
222
+ def worker_thread(worker_id):
223
+ """
224
+ Execute HEC-RAS plans assigned to a specific worker thread.
225
+
226
+ This function continuously processes plans from the worker's queue until it's empty.
227
+ It sets the number of cores for each plan, computes the plan, and records the result.
228
+
229
+ Parameters:
230
+ worker_id (int): The ID of the worker thread.
231
+
232
+ Notes:
233
+ - Uses PlanOperations.set_num_cores to set the number of cores for each plan.
234
+ - Uses RasExecutor.compute_hecras_plan to execute each plan.
235
+ - Records success or failure in the 'results' dictionary.
236
+ - Prints status messages for completed or failed plans.
237
+ """
238
+ while True:
239
+ try:
240
+ row = worker_queues[worker_id].get(block=False)
241
+ PlanOperations.set_num_cores(str(row['compute_plan_file']), cores_per_run)
242
+ success = RasExecutor.compute_hecras_plan(row['compute_plan_file'])
243
+ results[row['plan_number']] = success
244
+ print(f"Completed: Plan {row['plan_number']} in worker {worker_id}")
245
+ except queue.Empty:
246
+ break
247
+ except Exception as e:
248
+ results[row['plan_number']] = False
249
+ print(f"Failed: Plan {row['plan_number']} in worker {worker_id}. Error: {str(e)}")
250
+
251
+ # Distribute plans to worker queues
252
+ for _, row in compute_parallel_df.iterrows():
253
+ worker_queues[row['worker_number']].put(row)
254
+
255
+ # Start worker threads
256
+ threads = []
257
+ for i in range(max_workers):
258
+ thread = Thread(target=worker_thread, args=(i,))
259
+ thread.start()
260
+ threads.append(thread)
261
+
262
+ # Wait for all threads to complete
263
+ for thread in threads:
264
+ thread.join()
265
+
266
+ # Clean up and consolidate results
267
+ time.sleep(3) # Allow files to close
268
+ final_test_folder = project_folder.parent / f"{project_folder.name} [Test]"
269
+ final_test_folder.mkdir(exist_ok=True)
270
+
271
+ for test_folder in test_folders:
272
+ for item in test_folder.iterdir():
273
+ dest_path = final_test_folder / item.name
274
+ if dest_path.exists():
275
+ if dest_path.is_dir():
276
+ shutil.rmtree(dest_path)
277
+ else:
278
+ dest_path.unlink()
279
+ shutil.move(str(item), final_test_folder)
280
+ shutil.rmtree(test_folder)
281
+ print(f"Moved and removed test folder: {test_folder}")
282
+
283
+ return results
284
+
285
+ @staticmethod
286
+ def run_all_plans_parallel(project_folder, hecras_exe_path):
287
+ """
288
+ Run all plans in a project folder in parallel.
289
+
290
+ Parameters:
291
+ project_folder (str): The path to the project folder.
292
+ hecras_exe_path (str): The path to the HEC-RAS executable.
293
+
294
+ Returns:
295
+ dict: A dictionary with plan numbers as keys and execution success status as values.
296
+ """
297
+ config = ProjectConfig.init_ras_project(project_folder, hecras_exe_path)
298
+
299
+ if config:
300
+ print("ras_plan_entries dataframe:")
301
+ display(config.ras_plan_entries)
302
+
303
+ max_workers = 2 # Number of parallel runs
304
+ cores_per_run = 2 # Number of cores per run
305
+
306
+ results = RasExecutor.run_plans_parallel(config, max_workers, cores_per_run)
307
+
308
+ print("\nExecution Results:")
309
+ for plan_number, success in results.items():
310
+ print(f"Plan {plan_number}: {'Successful' if success else 'Failed'}")
311
+
312
+ return results
313
+ else:
314
+ print("Failed to initialize project configuration.")
315
+ return None