ras-commander 0.35.0__py3-none-any.whl → 0.36.0__py3-none-any.whl
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.
- ras_commander/RasCmdr.py +360 -332
- ras_commander/RasExamples.py +113 -80
- ras_commander/RasGeo.py +38 -28
- ras_commander/RasGpt.py +142 -0
- ras_commander/RasHdf.py +170 -253
- ras_commander/RasPlan.py +115 -166
- ras_commander/RasPrj.py +212 -141
- ras_commander/RasUnsteady.py +37 -22
- ras_commander/RasUtils.py +98 -82
- ras_commander/__init__.py +11 -13
- ras_commander/logging_config.py +80 -0
- {ras_commander-0.35.0.dist-info → ras_commander-0.36.0.dist-info}/METADATA +15 -11
- ras_commander-0.36.0.dist-info/RECORD +17 -0
- ras_commander-0.35.0.dist-info/RECORD +0 -15
- {ras_commander-0.35.0.dist-info → ras_commander-0.36.0.dist-info}/LICENSE +0 -0
- {ras_commander-0.35.0.dist-info → ras_commander-0.36.0.dist-info}/WHEEL +0 -0
- {ras_commander-0.35.0.dist-info → ras_commander-0.36.0.dist-info}/top_level.txt +0 -0
ras_commander/RasCmdr.py
CHANGED
@@ -1,8 +1,27 @@
|
|
1
1
|
"""
|
2
|
-
Execution operations for running HEC-RAS simulations
|
3
|
-
|
4
|
-
|
2
|
+
RasCmdr - Execution operations for running HEC-RAS simulations
|
3
|
+
|
4
|
+
This module is part of the ras-commander library and uses a centralized logging configuration.
|
5
|
+
|
6
|
+
Logging Configuration:
|
7
|
+
- The logging is set up in the logging_config.py file.
|
8
|
+
- A @log_call decorator is available to automatically log function calls.
|
9
|
+
- Log levels: DEBUG, INFO, WARNING, ERROR, CRITICAL
|
10
|
+
- Logs are written to both console and a rotating file handler.
|
11
|
+
- The default log file is 'ras_commander.log' in the 'logs' directory.
|
12
|
+
- The default log level is INFO.
|
5
13
|
|
14
|
+
To use logging in this module:
|
15
|
+
1. Use the @log_call decorator for automatic function call logging.
|
16
|
+
2. For additional logging, use logger.[level]() calls (e.g., logger.info(), logger.debug()).
|
17
|
+
|
18
|
+
Example:
|
19
|
+
@log_call
|
20
|
+
def my_function():
|
21
|
+
|
22
|
+
logger.debug("Additional debug information")
|
23
|
+
# Function logic here
|
24
|
+
"""
|
6
25
|
import os
|
7
26
|
import subprocess
|
8
27
|
import shutil
|
@@ -28,25 +47,35 @@ from threading import Lock, Thread, current_thread
|
|
28
47
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
29
48
|
from itertools import cycle
|
30
49
|
from typing import Union, List, Optional, Dict
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
50
|
+
from ras_commander.logging_config import get_logger, log_call
|
51
|
+
|
52
|
+
logger = get_logger(__name__)
|
53
|
+
|
54
|
+
# Module code starts here
|
55
|
+
|
56
|
+
# TODO: Future Enhancements
|
57
|
+
# 1. Alternate Run Mode for compute_plan and compute_parallel:
|
58
|
+
# - Use Powershell to execute HEC-RAS command
|
59
|
+
# - Hide RAS window and all child windows
|
60
|
+
# - Note: This mode may prevent execution if the plan has a popup
|
61
|
+
# - Intended for background runs or popup-free scenarios
|
62
|
+
# - Limit to non-commercial use
|
63
|
+
#
|
64
|
+
# 2. Implement compute_plan_remote:
|
65
|
+
# - Execute compute_plan on a remote machine via psexec
|
66
|
+
# - Use keyring package for secure credential storage
|
67
|
+
# - Implement psexec command for remote HEC-RAS execution
|
68
|
+
# - Create remote_worker objects to store machine details:
|
69
|
+
# (machine name, username, password, ras_exe_path, local folder path, etc.)
|
70
|
+
# - Develop RasRemote class for remote_worker management and abstractions
|
71
|
+
# - Implement compute_plan_remote in RasCmdr as a thin wrapper around RasRemote
|
72
|
+
# (similar to existing compute_plan functions but for remote execution)
|
46
73
|
|
47
74
|
|
48
75
|
class RasCmdr:
|
76
|
+
|
49
77
|
@staticmethod
|
78
|
+
@log_call
|
50
79
|
def compute_plan(
|
51
80
|
plan_number,
|
52
81
|
dest_folder=None,
|
@@ -74,252 +103,252 @@ class RasCmdr:
|
|
74
103
|
Raises:
|
75
104
|
ValueError: If the specified dest_folder already exists and is not empty, and overwrite_dest is False.
|
76
105
|
"""
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
if dest_folder is not None:
|
82
|
-
dest_folder = Path(ras_obj.project_folder).parent / dest_folder if isinstance(dest_folder, str) else Path(dest_folder)
|
83
|
-
|
84
|
-
if dest_folder.exists():
|
85
|
-
if overwrite_dest:
|
86
|
-
shutil.rmtree(dest_folder)
|
87
|
-
logging.info(f"Destination folder '{dest_folder}' exists. Overwriting as per overwrite_dest=True.")
|
88
|
-
elif any(dest_folder.iterdir()):
|
89
|
-
error_msg = f"Destination folder '{dest_folder}' exists and is not empty. Use overwrite_dest=True to overwrite."
|
90
|
-
logging.error(error_msg)
|
91
|
-
raise ValueError(error_msg)
|
92
|
-
|
93
|
-
dest_folder.mkdir(parents=True, exist_ok=True)
|
94
|
-
shutil.copytree(ras_obj.project_folder, dest_folder, dirs_exist_ok=True)
|
95
|
-
logging.info(f"Copied project folder to destination: {dest_folder}")
|
106
|
+
try:
|
107
|
+
ras_obj = ras_object if ras_object is not None else ras
|
108
|
+
logger.info(f"Using ras_object with project folder: {ras_obj.project_folder}")
|
109
|
+
ras_obj.check_initialized()
|
96
110
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
111
|
+
if dest_folder is not None:
|
112
|
+
dest_folder = Path(ras_obj.project_folder).parent / dest_folder if isinstance(dest_folder, str) else Path(dest_folder)
|
113
|
+
|
114
|
+
if dest_folder.exists():
|
115
|
+
if overwrite_dest:
|
116
|
+
shutil.rmtree(dest_folder)
|
117
|
+
logger.info(f"Destination folder '{dest_folder}' exists. Overwriting as per overwrite_dest=True.")
|
118
|
+
elif any(dest_folder.iterdir()):
|
119
|
+
error_msg = f"Destination folder '{dest_folder}' exists and is not empty. Use overwrite_dest=True to overwrite."
|
120
|
+
logger.error(error_msg)
|
121
|
+
raise ValueError(error_msg)
|
122
|
+
|
123
|
+
dest_folder.mkdir(parents=True, exist_ok=True)
|
124
|
+
shutil.copytree(ras_obj.project_folder, dest_folder, dirs_exist_ok=True)
|
125
|
+
logger.info(f"Copied project folder to destination: {dest_folder}")
|
126
|
+
|
127
|
+
compute_ras = RasPrj()
|
128
|
+
compute_ras.initialize(dest_folder, ras_obj.ras_exe_path)
|
129
|
+
compute_prj_path = compute_ras.prj_file
|
130
|
+
else:
|
131
|
+
compute_ras = ras_obj
|
132
|
+
compute_prj_path = ras_obj.prj_file
|
110
133
|
|
111
|
-
|
112
|
-
|
113
|
-
try:
|
114
|
-
RasGeo.clear_geompre_files(compute_plan_path, ras_object=compute_ras)
|
115
|
-
logging.info(f"Cleared geometry preprocessor files for plan: {plan_number}")
|
116
|
-
except Exception as e:
|
117
|
-
logging.error(f"Error clearing geometry preprocessor files for plan {plan_number}: {str(e)}")
|
134
|
+
# Determine the plan path
|
135
|
+
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)
|
118
136
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
137
|
+
if not compute_prj_path or not compute_plan_path:
|
138
|
+
logger.error(f"Could not find project file or plan file for plan {plan_number}")
|
139
|
+
return False
|
140
|
+
|
141
|
+
# Clear geometry preprocessor files if requested
|
142
|
+
if clear_geompre:
|
143
|
+
try:
|
144
|
+
RasGeo.clear_geompre_files(compute_plan_path, ras_object=compute_ras)
|
145
|
+
logger.info(f"Cleared geometry preprocessor files for plan: {plan_number}")
|
146
|
+
except Exception as e:
|
147
|
+
logger.error(f"Error clearing geometry preprocessor files for plan {plan_number}: {str(e)}")
|
148
|
+
|
149
|
+
# Set the number of cores if specified
|
150
|
+
if num_cores is not None:
|
151
|
+
try:
|
152
|
+
RasPlan.set_num_cores(compute_plan_path, num_cores=num_cores, ras_object=compute_ras)
|
153
|
+
logger.info(f"Set number of cores to {num_cores} for plan: {plan_number}")
|
154
|
+
except Exception as e:
|
155
|
+
logger.error(f"Error setting number of cores for plan {plan_number}: {str(e)}")
|
126
156
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
157
|
+
# Prepare the command for HEC-RAS execution
|
158
|
+
cmd = f'"{compute_ras.ras_exe_path}" -c "{compute_prj_path}" "{compute_plan_path}"'
|
159
|
+
logger.info("Running HEC-RAS from the Command Line:")
|
160
|
+
logger.info(f"Running command: {cmd}")
|
131
161
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
162
|
+
# Execute the HEC-RAS command
|
163
|
+
start_time = time.time()
|
164
|
+
try:
|
165
|
+
subprocess.run(cmd, check=True, shell=True, capture_output=True, text=True)
|
166
|
+
end_time = time.time()
|
167
|
+
run_time = end_time - start_time
|
168
|
+
logger.info(f"HEC-RAS execution completed for plan: {plan_number}")
|
169
|
+
logger.info(f"Total run time for plan {plan_number}: {run_time:.2f} seconds")
|
170
|
+
return True
|
171
|
+
except subprocess.CalledProcessError as e:
|
172
|
+
end_time = time.time()
|
173
|
+
run_time = end_time - start_time
|
174
|
+
logger.error(f"Error running plan: {plan_number}")
|
175
|
+
logger.error(f"Error message: {e.output}")
|
176
|
+
logger.info(f"Total run time for plan {plan_number}: {run_time:.2f} seconds")
|
177
|
+
return False
|
178
|
+
except Exception as e:
|
179
|
+
logger.critical(f"Error in compute_plan: {str(e)}")
|
147
180
|
return False
|
148
181
|
finally:
|
149
182
|
# Update the RAS object's dataframes
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
183
|
+
if ras_obj:
|
184
|
+
ras_obj.plan_df = ras_obj.get_plan_entries()
|
185
|
+
ras_obj.geom_df = ras_obj.get_geom_entries()
|
186
|
+
ras_obj.flow_df = ras_obj.get_flow_entries()
|
187
|
+
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
154
188
|
|
155
189
|
|
156
190
|
|
157
191
|
@staticmethod
|
192
|
+
@log_call
|
193
|
+
@staticmethod
|
194
|
+
@log_call
|
158
195
|
def compute_parallel(
|
159
196
|
plan_number: Union[str, List[str], None] = None,
|
160
197
|
max_workers: int = 2,
|
161
198
|
num_cores: int = 2,
|
162
199
|
clear_geompre: bool = False,
|
163
|
-
ras_object: Optional['RasPrj'] = None,
|
200
|
+
ras_object: Optional['RasPrj'] = None,
|
164
201
|
dest_folder: Union[str, Path, None] = None,
|
165
202
|
overwrite_dest: bool = False
|
166
203
|
) -> Dict[str, bool]:
|
167
204
|
"""
|
168
|
-
|
169
|
-
"""
|
170
|
-
ras_obj = ras_object or ras # Assuming 'ras' is a global RasPrj instance
|
171
|
-
ras_obj.check_initialized()
|
205
|
+
Compute multiple HEC-RAS plans in parallel.
|
172
206
|
|
173
|
-
|
207
|
+
Args:
|
208
|
+
plan_number (Union[str, List[str], None]): Plan number(s) to compute. If None, all plans are computed.
|
209
|
+
max_workers (int): Maximum number of parallel workers.
|
210
|
+
num_cores (int): Number of cores to use per plan computation.
|
211
|
+
clear_geompre (bool): Whether to clear geometry preprocessor files.
|
212
|
+
ras_object (Optional[RasPrj]): RAS project object. If None, uses global instance.
|
213
|
+
dest_folder (Union[str, Path, None]): Destination folder for computed results.
|
214
|
+
overwrite_dest (bool): Whether to overwrite existing destination folder.
|
174
215
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
worker_ras_objects[worker_id] = worker_ras_instance
|
218
|
-
|
219
|
-
# Distribute plans among workers in a round-robin fashion
|
220
|
-
worker_cycle = cycle(range(1, max_workers + 1))
|
221
|
-
plan_assignments = [(next(worker_cycle), plan_num) for plan_num in ras_obj.plan_df['plan_number']]
|
222
|
-
|
223
|
-
# Initialize ThreadPoolExecutor without tracking individual plan success
|
224
|
-
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
225
|
-
# Submit all plan executions to the executor
|
226
|
-
futures = [
|
227
|
-
executor.submit(
|
228
|
-
RasCmdr.compute_plan,
|
229
|
-
plan_num,
|
230
|
-
ras_object=worker_ras_objects[worker_id],
|
231
|
-
clear_geompre=clear_geompre,
|
232
|
-
num_cores=num_cores
|
233
|
-
)
|
234
|
-
for worker_id, plan_num in plan_assignments
|
235
|
-
]
|
216
|
+
Returns:
|
217
|
+
Dict[str, bool]: Dictionary of plan numbers and their execution success status.
|
218
|
+
"""
|
219
|
+
try:
|
220
|
+
ras_obj = ras_object or ras
|
221
|
+
ras_obj.check_initialized()
|
222
|
+
|
223
|
+
project_folder = Path(ras_obj.project_folder)
|
224
|
+
|
225
|
+
if dest_folder is not None:
|
226
|
+
dest_folder_path = Path(dest_folder)
|
227
|
+
if dest_folder_path.exists():
|
228
|
+
if overwrite_dest:
|
229
|
+
shutil.rmtree(dest_folder_path)
|
230
|
+
logger.info(f"Destination folder '{dest_folder_path}' exists. Overwriting as per overwrite_dest=True.")
|
231
|
+
elif any(dest_folder_path.iterdir()):
|
232
|
+
error_msg = f"Destination folder '{dest_folder_path}' exists and is not empty. Use overwrite_dest=True to overwrite."
|
233
|
+
logger.error(error_msg)
|
234
|
+
raise ValueError(error_msg)
|
235
|
+
dest_folder_path.mkdir(parents=True, exist_ok=True)
|
236
|
+
shutil.copytree(project_folder, dest_folder_path, dirs_exist_ok=True)
|
237
|
+
logger.info(f"Copied project folder to destination: {dest_folder_path}")
|
238
|
+
project_folder = dest_folder_path
|
239
|
+
|
240
|
+
if plan_number:
|
241
|
+
if isinstance(plan_number, str):
|
242
|
+
plan_number = [plan_number]
|
243
|
+
ras_obj.plan_df = ras_obj.plan_df[ras_obj.plan_df['plan_number'].isin(plan_number)]
|
244
|
+
logger.info(f"Filtered plans to execute: {plan_number}")
|
245
|
+
|
246
|
+
num_plans = len(ras_obj.plan_df)
|
247
|
+
max_workers = min(max_workers, num_plans) if num_plans > 0 else 1
|
248
|
+
logger.info(f"Adjusted max_workers to {max_workers} based on the number of plans: {num_plans}")
|
249
|
+
|
250
|
+
worker_ras_objects = {}
|
251
|
+
for worker_id in range(1, max_workers + 1):
|
252
|
+
worker_folder = project_folder.parent / f"{project_folder.name} [Worker {worker_id}]"
|
253
|
+
if worker_folder.exists():
|
254
|
+
shutil.rmtree(worker_folder)
|
255
|
+
logger.info(f"Removed existing worker folder: {worker_folder}")
|
256
|
+
shutil.copytree(project_folder, worker_folder)
|
257
|
+
logger.info(f"Created worker folder: {worker_folder}")
|
236
258
|
|
237
|
-
# Optionally, you can log when each plan starts and completes
|
238
|
-
for future, (worker_id, plan_num) in zip(as_completed(futures), plan_assignments):
|
239
259
|
try:
|
240
|
-
|
241
|
-
|
260
|
+
ras_instance = RasPrj()
|
261
|
+
worker_ras_instance = init_ras_project(
|
262
|
+
ras_project_folder=worker_folder,
|
263
|
+
ras_version=ras_obj.ras_exe_path,
|
264
|
+
ras_instance=ras_instance
|
265
|
+
)
|
266
|
+
worker_ras_objects[worker_id] = worker_ras_instance
|
242
267
|
except Exception as e:
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
268
|
+
logger.critical(f"Failed to initialize RAS project for worker {worker_id}: {str(e)}")
|
269
|
+
worker_ras_objects[worker_id] = None
|
270
|
+
|
271
|
+
worker_cycle = cycle(range(1, max_workers + 1))
|
272
|
+
plan_assignments = [(next(worker_cycle), plan_num) for plan_num in ras_obj.plan_df['plan_number']]
|
273
|
+
|
274
|
+
execution_results: Dict[str, bool] = {}
|
275
|
+
|
276
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
277
|
+
futures = [
|
278
|
+
executor.submit(
|
279
|
+
RasCmdr.compute_plan,
|
280
|
+
plan_num,
|
281
|
+
ras_object=worker_ras_objects[worker_id],
|
282
|
+
clear_geompre=clear_geompre,
|
283
|
+
num_cores=num_cores
|
284
|
+
)
|
285
|
+
for worker_id, plan_num in plan_assignments
|
286
|
+
]
|
287
|
+
|
288
|
+
for future, (worker_id, plan_num) in zip(as_completed(futures), plan_assignments):
|
289
|
+
try:
|
290
|
+
success = future.result()
|
291
|
+
execution_results[plan_num] = success
|
292
|
+
logger.info(f"Plan {plan_num} executed in worker {worker_id}: {'Successful' if success else 'Failed'}")
|
293
|
+
except Exception as e:
|
294
|
+
execution_results[plan_num] = False
|
295
|
+
logger.error(f"Plan {plan_num} failed in worker {worker_id}: {str(e)}")
|
296
|
+
|
297
|
+
final_dest_folder = dest_folder_path if dest_folder is not None else project_folder.parent / f"{project_folder.name} [Computed]"
|
298
|
+
final_dest_folder.mkdir(parents=True, exist_ok=True)
|
299
|
+
logger.info(f"Final destination for computed results: {final_dest_folder}")
|
300
|
+
|
301
|
+
for worker_ras in worker_ras_objects.values():
|
302
|
+
if worker_ras is None:
|
303
|
+
continue
|
304
|
+
worker_folder = Path(worker_ras.project_folder)
|
305
|
+
try:
|
306
|
+
for item in worker_folder.iterdir():
|
307
|
+
dest_path = final_dest_folder / item.name
|
308
|
+
if dest_path.exists():
|
309
|
+
if dest_path.is_dir():
|
310
|
+
shutil.rmtree(dest_path)
|
311
|
+
logger.debug(f"Removed existing directory at {dest_path}")
|
312
|
+
else:
|
313
|
+
dest_path.unlink()
|
314
|
+
logger.debug(f"Removed existing file at {dest_path}")
|
315
|
+
shutil.move(str(item), final_dest_folder)
|
316
|
+
logger.debug(f"Moved {item} to {final_dest_folder}")
|
317
|
+
shutil.rmtree(worker_folder)
|
318
|
+
logger.info(f"Removed worker folder: {worker_folder}")
|
319
|
+
except Exception as e:
|
320
|
+
logger.error(f"Error moving results from {worker_folder} to {final_dest_folder}: {str(e)}")
|
250
321
|
|
251
|
-
for worker_ras in worker_ras_objects.values():
|
252
|
-
worker_folder = Path(worker_ras.project_folder)
|
253
322
|
try:
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
dest_path.unlink()
|
262
|
-
logging.debug(f"Removed existing file at {dest_path}")
|
263
|
-
shutil.move(str(item), final_dest_folder)
|
264
|
-
logging.debug(f"Moved {item} to {final_dest_folder}")
|
265
|
-
shutil.rmtree(worker_folder)
|
266
|
-
logging.info(f"Removed worker folder: {worker_folder}")
|
323
|
+
final_dest_folder_ras_obj = RasPrj()
|
324
|
+
final_dest_folder_ras_obj = init_ras_project(
|
325
|
+
ras_project_folder=final_dest_folder,
|
326
|
+
ras_version=ras_obj.ras_exe_path,
|
327
|
+
ras_instance=final_dest_folder_ras_obj
|
328
|
+
)
|
329
|
+
final_dest_folder_ras_obj.check_initialized()
|
267
330
|
except Exception as e:
|
268
|
-
|
331
|
+
logger.critical(f"Failed to initialize RasPrj for final destination: {str(e)}")
|
269
332
|
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
# Initialize it using init_ras_project
|
276
|
-
final_dest_folder_ras_obj = init_ras_project(
|
277
|
-
ras_project_folder=final_dest_folder,
|
278
|
-
ras_version=ras_obj.ras_exe_path,
|
279
|
-
ras_instance=final_dest_folder_ras_obj
|
280
|
-
)
|
281
|
-
|
282
|
-
# Now we can check if it's initialized
|
283
|
-
final_dest_folder_ras_obj.check_initialized()
|
284
|
-
except Exception as e:
|
285
|
-
logging.error(f"Failed to initialize RasPrj for final destination: {str(e)}")
|
286
|
-
raise
|
333
|
+
logger.info("\nExecution Results:")
|
334
|
+
for plan_num, success in execution_results.items():
|
335
|
+
status = 'Successful' if success else 'Failed'
|
336
|
+
logger.info(f"Plan {plan_num}: {status}")
|
287
337
|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
raise
|
294
|
-
|
295
|
-
execution_results: Dict[str, bool] = {}
|
296
|
-
for _, row in ras_obj.plan_df.iterrows():
|
297
|
-
plan_num = row['plan_number']
|
298
|
-
# Find the corresponding entry in plan_entries
|
299
|
-
entry = plan_entries[plan_entries['plan_number'] == plan_num]
|
300
|
-
if not entry.empty:
|
301
|
-
hdf_path = entry.iloc[0].get('HDF_Results_Path')
|
302
|
-
success = hdf_path is not None and Path(hdf_path).exists()
|
303
|
-
else:
|
304
|
-
success = False
|
305
|
-
execution_results[plan_num] = success
|
306
|
-
|
307
|
-
# Print execution results for each plan
|
308
|
-
logging.info("\nExecution Results:")
|
309
|
-
for plan_num, success in execution_results.items():
|
310
|
-
status = 'Successful' if success else 'Failed'
|
311
|
-
logging.info(f"Plan {plan_num}: {status} \n(HDF_Results_Path: {hdf_path})")
|
312
|
-
|
313
|
-
ras_obj = ras_object or ras
|
314
|
-
ras_obj.plan_df = ras_obj.get_plan_entries()
|
315
|
-
ras_obj.geom_df = ras_obj.get_geom_entries()
|
316
|
-
ras_obj.flow_df = ras_obj.get_flow_entries()
|
317
|
-
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
338
|
+
ras_obj = ras_object or ras
|
339
|
+
ras_obj.plan_df = ras_obj.get_plan_entries()
|
340
|
+
ras_obj.geom_df = ras_obj.get_geom_entries()
|
341
|
+
ras_obj.flow_df = ras_obj.get_flow_entries()
|
342
|
+
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
318
343
|
|
319
|
-
|
320
|
-
|
344
|
+
return execution_results
|
345
|
+
|
346
|
+
except Exception as e:
|
347
|
+
logger.critical(f"Error in compute_parallel: {str(e)}")
|
348
|
+
return {}
|
321
349
|
|
322
350
|
@staticmethod
|
351
|
+
@log_call
|
323
352
|
def compute_test_mode(
|
324
353
|
plan_number=None,
|
325
354
|
dest_folder_suffix="[Test]",
|
@@ -329,11 +358,11 @@ class RasCmdr:
|
|
329
358
|
overwrite_dest=False
|
330
359
|
):
|
331
360
|
"""
|
332
|
-
Execute HEC-RAS plans in test mode.
|
361
|
+
Execute HEC-RAS plans in test mode. This is a re-creation of the HEC-RAS command line -test flag,
|
333
362
|
which does not work in recent versions of HEC-RAS.
|
334
363
|
|
335
364
|
As a special-purpose function that emulates the original -test flag, it operates differently than the
|
336
|
-
other two compute_ functions.
|
365
|
+
other two compute_ functions. Per the original HEC-RAS test flag, it creates a separate test folder,
|
337
366
|
copies the project there, and executes the specified plans in sequential order.
|
338
367
|
|
339
368
|
For most purposes, just copying a the project folder, initing that new folder, then running each plan
|
@@ -354,7 +383,7 @@ class RasCmdr:
|
|
354
383
|
overwrite_dest (bool, optional): If True, overwrite the destination folder if it exists. Defaults to False.
|
355
384
|
|
356
385
|
Returns:
|
357
|
-
|
386
|
+
Dict[str, bool]: Dictionary of plan numbers and their execution success status.
|
358
387
|
|
359
388
|
Example:
|
360
389
|
Run all plans: RasCommander.compute_test_mode()
|
@@ -373,110 +402,109 @@ class RasCmdr:
|
|
373
402
|
- Because copying the project is implicit, only a dest_folder_suffix option is provided.
|
374
403
|
- For more flexible run management, use the compute_parallel or compute_sequential functions.
|
375
404
|
"""
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
f"Compute folder '{compute_folder}' exists and is not empty. "
|
404
|
-
"Use overwrite_dest=True to overwrite."
|
405
|
-
)
|
406
|
-
logging.error(error_msg)
|
407
|
-
raise ValueError(error_msg)
|
408
|
-
else:
|
405
|
+
try:
|
406
|
+
ras_obj = ras_object or ras
|
407
|
+
ras_obj.check_initialized()
|
408
|
+
|
409
|
+
logger.info("Starting the compute_test_mode...")
|
410
|
+
|
411
|
+
project_folder = Path(ras_obj.project_folder)
|
412
|
+
|
413
|
+
if not project_folder.exists():
|
414
|
+
logger.error(f"Project folder '{project_folder}' does not exist.")
|
415
|
+
return {}
|
416
|
+
|
417
|
+
compute_folder = project_folder.parent / f"{project_folder.name} {dest_folder_suffix}"
|
418
|
+
logger.info(f"Creating the test folder: {compute_folder}...")
|
419
|
+
|
420
|
+
if compute_folder.exists():
|
421
|
+
if overwrite_dest:
|
422
|
+
shutil.rmtree(compute_folder)
|
423
|
+
logger.info(f"Compute folder '{compute_folder}' exists. Overwriting as per overwrite_dest=True.")
|
424
|
+
elif any(compute_folder.iterdir()):
|
425
|
+
error_msg = (
|
426
|
+
f"Compute folder '{compute_folder}' exists and is not empty. "
|
427
|
+
"Use overwrite_dest=True to overwrite."
|
428
|
+
)
|
429
|
+
logger.error(error_msg)
|
430
|
+
raise ValueError(error_msg)
|
431
|
+
|
409
432
|
try:
|
410
433
|
shutil.copytree(project_folder, compute_folder)
|
411
|
-
|
412
|
-
except FileNotFoundError:
|
413
|
-
logging.error(f"Unable to copy project folder. Source folder '{project_folder}' not found.")
|
414
|
-
return
|
415
|
-
except PermissionError:
|
416
|
-
logging.error(f"Permission denied when trying to create or copy to '{compute_folder}'.")
|
417
|
-
return
|
434
|
+
logger.info(f"Copied project folder to compute folder: {compute_folder}")
|
418
435
|
except Exception as e:
|
419
|
-
|
420
|
-
return
|
436
|
+
logger.critical(f"Error occurred while copying project folder: {str(e)}")
|
437
|
+
return {}
|
421
438
|
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
return
|
439
|
+
try:
|
440
|
+
compute_ras = RasPrj()
|
441
|
+
compute_ras.initialize(compute_folder, ras_obj.ras_exe_path)
|
442
|
+
compute_prj_path = compute_ras.prj_file
|
443
|
+
logger.info(f"Initialized RAS project in compute folder: {compute_prj_path}")
|
444
|
+
except Exception as e:
|
445
|
+
logger.critical(f"Error initializing RAS project in compute folder: {str(e)}")
|
446
|
+
return {}
|
431
447
|
|
432
|
-
|
433
|
-
|
434
|
-
|
448
|
+
if not compute_prj_path:
|
449
|
+
logger.error("Project file not found.")
|
450
|
+
return {}
|
435
451
|
|
436
|
-
|
437
|
-
logging.info("Getting plan entries...")
|
438
|
-
try:
|
439
|
-
ras_compute_plan_entries = compute_ras.plan_df
|
440
|
-
logging.info("Retrieved plan entries successfully.")
|
441
|
-
except Exception as e:
|
442
|
-
logging.error(f"Error retrieving plan entries: {str(e)}")
|
443
|
-
return
|
444
|
-
|
445
|
-
if plan_number:
|
446
|
-
if isinstance(plan_number, str):
|
447
|
-
plan_number = [plan_number]
|
448
|
-
ras_compute_plan_entries = ras_compute_plan_entries[
|
449
|
-
ras_compute_plan_entries['plan_number'].isin(plan_number)
|
450
|
-
]
|
451
|
-
logging.info(f"Filtered plans to execute: {plan_number}")
|
452
|
-
|
453
|
-
logging.info("Running selected plans sequentially...")
|
454
|
-
for _, plan in ras_compute_plan_entries.iterrows():
|
455
|
-
plan_number = plan["plan_number"]
|
456
|
-
start_time = time.time()
|
452
|
+
logger.info("Getting plan entries...")
|
457
453
|
try:
|
458
|
-
|
459
|
-
|
460
|
-
ras_object=compute_ras,
|
461
|
-
clear_geompre=clear_geompre,
|
462
|
-
num_cores=num_cores
|
463
|
-
)
|
464
|
-
if success:
|
465
|
-
logging.info(f"Successfully computed plan {plan_number}")
|
466
|
-
else:
|
467
|
-
logging.error(f"Failed to compute plan {plan_number}")
|
454
|
+
ras_compute_plan_entries = compute_ras.plan_df
|
455
|
+
logger.info("Retrieved plan entries successfully.")
|
468
456
|
except Exception as e:
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
457
|
+
logger.critical(f"Error retrieving plan entries: {str(e)}")
|
458
|
+
return {}
|
459
|
+
|
460
|
+
if plan_number:
|
461
|
+
if isinstance(plan_number, str):
|
462
|
+
plan_number = [plan_number]
|
463
|
+
ras_compute_plan_entries = ras_compute_plan_entries[
|
464
|
+
ras_compute_plan_entries['plan_number'].isin(plan_number)
|
465
|
+
]
|
466
|
+
logger.info(f"Filtered plans to execute: {plan_number}")
|
467
|
+
|
468
|
+
execution_results = {}
|
469
|
+
logger.info("Running selected plans sequentially...")
|
470
|
+
for _, plan in ras_compute_plan_entries.iterrows():
|
471
|
+
plan_number = plan["plan_number"]
|
472
|
+
start_time = time.time()
|
473
|
+
try:
|
474
|
+
success = RasCmdr.compute_plan(
|
475
|
+
plan_number,
|
476
|
+
ras_object=compute_ras,
|
477
|
+
clear_geompre=clear_geompre,
|
478
|
+
num_cores=num_cores
|
479
|
+
)
|
480
|
+
execution_results[plan_number] = success
|
481
|
+
if success:
|
482
|
+
logger.info(f"Successfully computed plan {plan_number}")
|
483
|
+
else:
|
484
|
+
logger.error(f"Failed to compute plan {plan_number}")
|
485
|
+
except Exception as e:
|
486
|
+
execution_results[plan_number] = False
|
487
|
+
logger.error(f"Error computing plan {plan_number}: {str(e)}")
|
488
|
+
finally:
|
489
|
+
end_time = time.time()
|
490
|
+
run_time = end_time - start_time
|
491
|
+
logger.info(f"Total run time for plan {plan_number}: {run_time:.2f} seconds")
|
492
|
+
|
493
|
+
logger.info("All selected plans have been executed.")
|
494
|
+
logger.info("compute_test_mode completed.")
|
495
|
+
|
496
|
+
logger.info("\nExecution Results:")
|
497
|
+
for plan_num, success in execution_results.items():
|
498
|
+
status = 'Successful' if success else 'Failed'
|
499
|
+
logger.info(f"Plan {plan_num}: {status}")
|
500
|
+
|
501
|
+
ras_obj.plan_df = ras_obj.get_plan_entries()
|
502
|
+
ras_obj.geom_df = ras_obj.get_geom_entries()
|
503
|
+
ras_obj.flow_df = ras_obj.get_flow_entries()
|
504
|
+
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
505
|
+
|
506
|
+
return execution_results
|
507
|
+
|
508
|
+
except Exception as e:
|
509
|
+
logger.critical(f"Error in compute_test_mode: {str(e)}")
|
510
|
+
return {}
|