ras-commander 0.35.0__py3-none-any.whl → 0.37.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.37.0.dist-info}/METADATA +22 -19
- ras_commander-0.37.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.37.0.dist-info}/LICENSE +0 -0
- {ras_commander-0.35.0.dist-info → ras_commander-0.37.0.dist-info}/WHEEL +0 -0
- {ras_commander-0.35.0.dist-info → ras_commander-0.37.0.dist-info}/top_level.txt +0 -0
ras_commander/RasPlan.py
CHANGED
@@ -1,3 +1,29 @@
|
|
1
|
+
"""
|
2
|
+
RasPlan - Operations for handling plan files in HEC-RAS projects
|
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.
|
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
|
+
3. Obtain the logger using: logger = logging.getLogger(__name__)
|
18
|
+
|
19
|
+
Example:
|
20
|
+
@log_call
|
21
|
+
def my_function():
|
22
|
+
logger = logging.getLogger(__name__)
|
23
|
+
logger.debug("Additional debug information")
|
24
|
+
# Function logic here
|
25
|
+
"""
|
26
|
+
import os
|
1
27
|
import re
|
2
28
|
import logging
|
3
29
|
from pathlib import Path
|
@@ -6,28 +32,20 @@ from typing import Union, Optional
|
|
6
32
|
import pandas as pd
|
7
33
|
from .RasPrj import RasPrj, ras
|
8
34
|
from .RasUtils import RasUtils
|
9
|
-
|
10
|
-
|
11
35
|
from pathlib import Path
|
12
36
|
from typing import Union, Any
|
13
37
|
import logging
|
14
38
|
import re
|
39
|
+
from ras_commander import get_logger
|
40
|
+
from ras_commander.logging_config import log_call
|
15
41
|
|
16
|
-
|
17
|
-
# Configure logging
|
18
|
-
logging.basicConfig(
|
19
|
-
level=logging.INFO,
|
20
|
-
format='%(asctime)s - %(levelname)s - %(message)s',
|
21
|
-
handlers=[
|
22
|
-
logging.StreamHandler()
|
23
|
-
]
|
24
|
-
)
|
42
|
+
logger = get_logger(__name__)
|
25
43
|
|
26
44
|
class RasPlan:
|
27
45
|
"""
|
28
46
|
A class for operations on HEC-RAS plan files.
|
29
47
|
"""
|
30
|
-
|
48
|
+
@log_call
|
31
49
|
@staticmethod
|
32
50
|
def set_geom(plan_number: Union[str, int], new_geom: Union[str, int], ras_object=None) -> pd.DataFrame:
|
33
51
|
"""
|
@@ -110,6 +128,7 @@ class RasPlan:
|
|
110
128
|
return ras_obj.plan_df
|
111
129
|
|
112
130
|
@staticmethod
|
131
|
+
@log_call
|
113
132
|
def set_steady(plan_number: str, new_steady_flow_number: str, ras_object=None):
|
114
133
|
"""
|
115
134
|
Apply a steady flow file to a plan file.
|
@@ -132,7 +151,6 @@ class RasPlan:
|
|
132
151
|
Note:
|
133
152
|
This function updates the ras object's dataframes after modifying the project structure.
|
134
153
|
"""
|
135
|
-
logging.info(f"Setting steady flow file to {new_steady_flow_number} in Plan {plan_number}")
|
136
154
|
ras_obj = ras_object or ras
|
137
155
|
ras_obj.check_initialized()
|
138
156
|
|
@@ -140,27 +158,23 @@ class RasPlan:
|
|
140
158
|
ras_obj.flow_df = ras_obj.get_flow_entries()
|
141
159
|
|
142
160
|
if new_steady_flow_number not in ras_obj.flow_df['flow_number'].values:
|
143
|
-
logging.error(f"Steady flow number {new_steady_flow_number} not found in project file.")
|
144
161
|
raise ValueError(f"Steady flow number {new_steady_flow_number} not found in project file.")
|
145
162
|
|
146
163
|
# Resolve the full path of the plan file
|
147
164
|
plan_file_path = RasPlan.get_plan_path(plan_number, ras_obj)
|
148
165
|
if not plan_file_path:
|
149
|
-
logging.error(f"Plan file not found: {plan_number}")
|
150
166
|
raise FileNotFoundError(f"Plan file not found: {plan_number}")
|
151
167
|
|
152
168
|
try:
|
153
169
|
with open(plan_file_path, 'r') as f:
|
154
170
|
lines = f.readlines()
|
155
171
|
except FileNotFoundError:
|
156
|
-
|
157
|
-
raise
|
172
|
+
raise FileNotFoundError(f"Plan file not found: {plan_file_path}")
|
158
173
|
|
159
174
|
with open(plan_file_path, 'w') as f:
|
160
175
|
for line in lines:
|
161
176
|
if line.startswith("Flow File=f"):
|
162
177
|
f.write(f"Flow File=f{new_steady_flow_number}\n")
|
163
|
-
logging.info(f"Updated Flow File in {plan_file_path} to f{new_steady_flow_number}")
|
164
178
|
else:
|
165
179
|
f.write(line)
|
166
180
|
|
@@ -171,6 +185,7 @@ class RasPlan:
|
|
171
185
|
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
172
186
|
|
173
187
|
@staticmethod
|
188
|
+
@log_call
|
174
189
|
def set_unsteady(plan_number: str, new_unsteady_flow_number: str, ras_object=None):
|
175
190
|
"""
|
176
191
|
Apply an unsteady flow file to a plan file.
|
@@ -193,8 +208,6 @@ class RasPlan:
|
|
193
208
|
Note:
|
194
209
|
This function updates the ras object's dataframes after modifying the project structure.
|
195
210
|
"""
|
196
|
-
logging.info(f"Setting unsteady flow file to {new_unsteady_flow_number} in Plan {plan_number}")
|
197
|
-
|
198
211
|
ras_obj = ras_object or ras
|
199
212
|
ras_obj.check_initialized()
|
200
213
|
|
@@ -202,21 +215,17 @@ class RasPlan:
|
|
202
215
|
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
203
216
|
|
204
217
|
if new_unsteady_flow_number not in ras_obj.unsteady_df['unsteady_number'].values:
|
205
|
-
logging.error(f"Unsteady number {new_unsteady_flow_number} not found in project file.")
|
206
218
|
raise ValueError(f"Unsteady number {new_unsteady_flow_number} not found in project file.")
|
207
219
|
|
208
220
|
# Get the full path of the plan file
|
209
221
|
plan_file_path = RasPlan.get_plan_path(plan_number, ras_obj)
|
210
222
|
if not plan_file_path:
|
211
|
-
logging.error(f"Plan file not found: {plan_number}")
|
212
223
|
raise FileNotFoundError(f"Plan file not found: {plan_number}")
|
213
224
|
|
214
225
|
try:
|
215
226
|
RasUtils.update_plan_file(plan_file_path, 'Unsteady', new_unsteady_flow_number)
|
216
|
-
logging.info(f"Updated unsteady flow file in {plan_file_path} to u{new_unsteady_flow_number}")
|
217
227
|
except Exception as e:
|
218
|
-
|
219
|
-
raise
|
228
|
+
raise Exception(f"Failed to update unsteady flow file: {e}")
|
220
229
|
|
221
230
|
# Update the ras object's dataframes
|
222
231
|
ras_obj.plan_df = ras_obj.get_plan_entries()
|
@@ -225,6 +234,7 @@ class RasPlan:
|
|
225
234
|
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
226
235
|
|
227
236
|
@staticmethod
|
237
|
+
@log_call
|
228
238
|
def set_num_cores(plan_number, num_cores, ras_object=None):
|
229
239
|
"""
|
230
240
|
Update the maximum number of cores to use in the HEC-RAS plan file.
|
@@ -253,8 +263,6 @@ class RasPlan:
|
|
253
263
|
Note:
|
254
264
|
This function updates the ras object's dataframes after modifying the project structure.
|
255
265
|
"""
|
256
|
-
logging.info(f"Setting num_cores to {num_cores} in Plan {plan_number}")
|
257
|
-
|
258
266
|
ras_obj = ras_object or ras
|
259
267
|
ras_obj.check_initialized()
|
260
268
|
|
@@ -262,7 +270,6 @@ class RasPlan:
|
|
262
270
|
if Path(plan_number).is_file():
|
263
271
|
plan_file_path = Path(plan_number)
|
264
272
|
if not plan_file_path.exists():
|
265
|
-
logging.error(f"Plan file not found: {plan_file_path}. Please provide a valid plan number or path.")
|
266
273
|
raise FileNotFoundError(f"Plan file not found: {plan_file_path}. Please provide a valid plan number or path.")
|
267
274
|
else:
|
268
275
|
# Update the plan dataframe in the ras instance to ensure it is current
|
@@ -271,7 +278,6 @@ class RasPlan:
|
|
271
278
|
# Get the full path of the plan file
|
272
279
|
plan_file_path = RasPlan.get_plan_path(plan_number, ras_obj)
|
273
280
|
if not plan_file_path:
|
274
|
-
logging.error(f"Plan file not found: {plan_number}. Please provide a valid plan number or path.")
|
275
281
|
raise FileNotFoundError(f"Plan file not found: {plan_number}. Please provide a valid plan number or path.")
|
276
282
|
|
277
283
|
cores_pattern = re.compile(r"(UNET D1 Cores= )\d+")
|
@@ -279,17 +285,14 @@ class RasPlan:
|
|
279
285
|
with open(plan_file_path, 'r') as file:
|
280
286
|
content = file.read()
|
281
287
|
except FileNotFoundError:
|
282
|
-
|
283
|
-
raise
|
288
|
+
raise FileNotFoundError(f"Plan file not found: {plan_file_path}")
|
284
289
|
|
285
290
|
new_content = cores_pattern.sub(rf"\g<1>{num_cores}", content)
|
286
291
|
try:
|
287
292
|
with open(plan_file_path, 'w') as file:
|
288
293
|
file.write(new_content)
|
289
|
-
logging.info(f"Updated {plan_file_path} with {num_cores} cores.")
|
290
294
|
except IOError as e:
|
291
|
-
|
292
|
-
raise
|
295
|
+
raise IOError(f"Failed to write to plan file: {e}")
|
293
296
|
|
294
297
|
# Update the ras object's dataframes
|
295
298
|
ras_obj.plan_df = ras_obj.get_plan_entries()
|
@@ -298,6 +301,7 @@ class RasPlan:
|
|
298
301
|
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
299
302
|
|
300
303
|
@staticmethod
|
304
|
+
@log_call
|
301
305
|
def set_geom_preprocessor(file_path, run_htab, use_ib_tables, ras_object=None):
|
302
306
|
"""
|
303
307
|
Update the simulation plan file to modify the `Run HTab` and `UNET Use Existing IB Tables` settings.
|
@@ -330,37 +334,27 @@ class RasPlan:
|
|
330
334
|
ras_obj.check_initialized()
|
331
335
|
|
332
336
|
if run_htab not in [-1, 0]:
|
333
|
-
logging.error("Invalid value for `Run HTab`. Expected `0` or `-1`.")
|
334
337
|
raise ValueError("Invalid value for `Run HTab`. Expected `0` or `-1`.")
|
335
338
|
if use_ib_tables not in [-1, 0]:
|
336
|
-
logging.error("Invalid value for `UNET Use Existing IB Tables`. Expected `0` or `-1`.")
|
337
339
|
raise ValueError("Invalid value for `UNET Use Existing IB Tables`. Expected `0` or `-1`.")
|
338
340
|
try:
|
339
|
-
logging.info(f"Reading the file: {file_path}")
|
340
341
|
with open(file_path, 'r') as file:
|
341
342
|
lines = file.readlines()
|
342
|
-
logging.info("Updating the file with new settings...")
|
343
343
|
updated_lines = []
|
344
344
|
for line in lines:
|
345
345
|
if line.lstrip().startswith("Run HTab="):
|
346
346
|
updated_line = f"Run HTab= {run_htab} \n"
|
347
347
|
updated_lines.append(updated_line)
|
348
|
-
logging.info(f"Updated 'Run HTab' to {run_htab}")
|
349
348
|
elif line.lstrip().startswith("UNET Use Existing IB Tables="):
|
350
349
|
updated_line = f"UNET Use Existing IB Tables= {use_ib_tables} \n"
|
351
350
|
updated_lines.append(updated_line)
|
352
|
-
logging.info(f"Updated 'UNET Use Existing IB Tables' to {use_ib_tables}")
|
353
351
|
else:
|
354
352
|
updated_lines.append(line)
|
355
|
-
logging.info(f"Writing the updated settings back to the file: {file_path}")
|
356
353
|
with open(file_path, 'w') as file:
|
357
354
|
file.writelines(updated_lines)
|
358
|
-
logging.info("File update completed successfully.")
|
359
355
|
except FileNotFoundError:
|
360
|
-
logging.error(f"The file '{file_path}' does not exist.")
|
361
356
|
raise FileNotFoundError(f"The file '{file_path}' does not exist.")
|
362
357
|
except IOError as e:
|
363
|
-
logging.error(f"An error occurred while reading or writing the file: {e}")
|
364
358
|
raise IOError(f"An error occurred while reading or writing the file: {e}")
|
365
359
|
|
366
360
|
# Update the ras object's dataframes
|
@@ -369,9 +363,8 @@ class RasPlan:
|
|
369
363
|
ras_obj.flow_df = ras_obj.get_flow_entries()
|
370
364
|
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
371
365
|
|
372
|
-
# Get Functions to retrieve file paths for plan, flow, unsteady, geometry and results files
|
373
|
-
|
374
366
|
@staticmethod
|
367
|
+
@log_call
|
375
368
|
def get_results_path(plan_number: str, ras_object=None) -> Optional[str]:
|
376
369
|
"""
|
377
370
|
Retrieve the results file path for a given HEC-RAS plan number.
|
@@ -403,24 +396,18 @@ class RasPlan:
|
|
403
396
|
# Ensure plan_number is a string
|
404
397
|
plan_number = str(plan_number).zfill(2)
|
405
398
|
|
406
|
-
# Log the plan dataframe for debugging
|
407
|
-
logging.debug("Plan DataFrame:")
|
408
|
-
logging.debug(ras_obj.plan_df)
|
409
|
-
|
410
399
|
plan_entry = ras_obj.plan_df[ras_obj.plan_df['plan_number'] == plan_number]
|
411
400
|
if not plan_entry.empty:
|
412
401
|
results_path = plan_entry['HDF_Results_Path'].iloc[0]
|
413
402
|
if results_path and Path(results_path).exists():
|
414
|
-
logging.info(f"Results file for Plan number {plan_number} exists at: {results_path}")
|
415
403
|
return results_path
|
416
404
|
else:
|
417
|
-
logging.warning(f"Results file for Plan number {plan_number} does not exist.")
|
418
405
|
return None
|
419
406
|
else:
|
420
|
-
logging.warning(f"Plan number {plan_number} not found in the entries.")
|
421
407
|
return None
|
422
408
|
|
423
409
|
@staticmethod
|
410
|
+
@log_call
|
424
411
|
def get_plan_path(plan_number: str, ras_object=None) -> Optional[str]:
|
425
412
|
"""
|
426
413
|
Return the full path for a given plan number.
|
@@ -456,13 +443,12 @@ class RasPlan:
|
|
456
443
|
|
457
444
|
if not plan_path.empty:
|
458
445
|
full_path = plan_path['full_path'].iloc[0]
|
459
|
-
logging.info(f"Plan file for Plan number {plan_number} found at: {full_path}")
|
460
446
|
return full_path
|
461
447
|
else:
|
462
|
-
logging.warning(f"Plan number {plan_number} not found in the updated plan entries.")
|
463
448
|
return None
|
464
449
|
|
465
450
|
@staticmethod
|
451
|
+
@log_call
|
466
452
|
def get_flow_path(flow_number: str, ras_object=None) -> Optional[str]:
|
467
453
|
"""
|
468
454
|
Return the full path for a given flow number.
|
@@ -494,13 +480,12 @@ class RasPlan:
|
|
494
480
|
flow_path = ras_obj.flow_df[ras_obj.flow_df['flow_number'] == flow_number]
|
495
481
|
if not flow_path.empty:
|
496
482
|
full_path = flow_path['full_path'].iloc[0]
|
497
|
-
logging.info(f"Flow file for Flow number {flow_number} found at: {full_path}")
|
498
483
|
return full_path
|
499
484
|
else:
|
500
|
-
logging.warning(f"Flow number {flow_number} not found in the updated flow entries.")
|
501
485
|
return None
|
502
486
|
|
503
487
|
@staticmethod
|
488
|
+
@log_call
|
504
489
|
def get_unsteady_path(unsteady_number: str, ras_object=None) -> Optional[str]:
|
505
490
|
"""
|
506
491
|
Return the full path for a given unsteady number.
|
@@ -532,13 +517,12 @@ class RasPlan:
|
|
532
517
|
unsteady_path = ras_obj.unsteady_df[ras_obj.unsteady_df['unsteady_number'] == unsteady_number]
|
533
518
|
if not unsteady_path.empty:
|
534
519
|
full_path = unsteady_path['full_path'].iloc[0]
|
535
|
-
logging.info(f"Unsteady file for Unsteady number {unsteady_number} found at: {full_path}")
|
536
520
|
return full_path
|
537
521
|
else:
|
538
|
-
logging.warning(f"Unsteady number {unsteady_number} not found in the updated unsteady entries.")
|
539
522
|
return None
|
540
523
|
|
541
524
|
@staticmethod
|
525
|
+
@log_call
|
542
526
|
def get_geom_path(geom_number: str, ras_object=None) -> Optional[str]:
|
543
527
|
"""
|
544
528
|
Return the full path for a given geometry number.
|
@@ -570,15 +554,14 @@ class RasPlan:
|
|
570
554
|
geom_path = ras_obj.geom_df[ras_obj.geom_df['geom_number'] == geom_number]
|
571
555
|
if not geom_path.empty:
|
572
556
|
full_path = geom_path['full_path'].iloc[0]
|
573
|
-
logging.info(f"Geometry file for Geom number {geom_number} found at: {full_path}")
|
574
557
|
return full_path
|
575
558
|
else:
|
576
|
-
logging.warning(f"Geometry number {geom_number} not found in the updated geometry entries.")
|
577
559
|
return None
|
578
560
|
|
579
561
|
# Clone Functions to copy unsteady, flow, and geometry files from templates
|
580
562
|
|
581
563
|
@staticmethod
|
564
|
+
@log_call
|
582
565
|
def clone_plan(template_plan, new_plan_shortid=None, ras_object=None):
|
583
566
|
"""
|
584
567
|
Create a new plan file based on a template and update the project file.
|
@@ -610,18 +593,15 @@ class RasPlan:
|
|
610
593
|
new_plan_path = ras_obj.project_folder / f"{ras_obj.project_name}.p{new_plan_num}"
|
611
594
|
|
612
595
|
if not template_plan_path.exists():
|
613
|
-
logging.error(f"Template plan file '{template_plan_path}' does not exist.")
|
614
596
|
raise FileNotFoundError(f"Template plan file '{template_plan_path}' does not exist.")
|
615
597
|
|
616
598
|
shutil.copy(template_plan_path, new_plan_path)
|
617
|
-
logging.info(f"Copied {template_plan_path} to {new_plan_path}")
|
618
599
|
|
619
600
|
try:
|
620
601
|
with open(new_plan_path, 'r') as f:
|
621
602
|
plan_lines = f.readlines()
|
622
603
|
except FileNotFoundError:
|
623
|
-
|
624
|
-
raise
|
604
|
+
raise FileNotFoundError(f"New plan file not found after copying: {new_plan_path}")
|
625
605
|
|
626
606
|
shortid_pattern = re.compile(r'^Short Identifier=(.*)$', re.IGNORECASE)
|
627
607
|
for i, line in enumerate(plan_lines):
|
@@ -633,23 +613,19 @@ class RasPlan:
|
|
633
613
|
else:
|
634
614
|
new_shortid = new_plan_shortid[:24]
|
635
615
|
plan_lines[i] = f"Short Identifier={new_shortid}\n"
|
636
|
-
logging.info(f"Updated 'Short Identifier' to '{new_shortid}' in {new_plan_path}")
|
637
616
|
break
|
638
617
|
|
639
618
|
try:
|
640
619
|
with open(new_plan_path, 'w') as f:
|
641
620
|
f.writelines(plan_lines)
|
642
|
-
logging.info(f"Updated short identifier in {new_plan_path}")
|
643
621
|
except IOError as e:
|
644
|
-
|
645
|
-
raise
|
622
|
+
raise IOError(f"Failed to write updated short identifier to {new_plan_path}: {e}")
|
646
623
|
|
647
624
|
try:
|
648
625
|
with open(ras_obj.prj_file, 'r') as f:
|
649
626
|
lines = f.readlines()
|
650
627
|
except FileNotFoundError:
|
651
|
-
|
652
|
-
raise
|
628
|
+
raise FileNotFoundError(f"Project file not found: {ras_obj.prj_file}")
|
653
629
|
|
654
630
|
# Prepare the new Plan File entry line
|
655
631
|
new_plan_line = f"Plan File=p{new_plan_num}\n"
|
@@ -669,27 +645,22 @@ class RasPlan:
|
|
669
645
|
|
670
646
|
if insertion_index is not None:
|
671
647
|
lines.insert(insertion_index, new_plan_line)
|
672
|
-
logging.info(f"Inserted new plan line at index {insertion_index}")
|
673
648
|
else:
|
674
649
|
# Try to insert after the last Plan File entry
|
675
650
|
plan_indices = [i for i, line in enumerate(lines) if plan_file_pattern.match(line.strip())]
|
676
651
|
if plan_indices:
|
677
652
|
last_plan_index = plan_indices[-1]
|
678
653
|
lines.insert(last_plan_index + 1, new_plan_line)
|
679
|
-
logging.info(f"Inserted new plan line after index {last_plan_index}")
|
680
654
|
else:
|
681
655
|
# Append at the end if no Plan File entries exist
|
682
656
|
lines.append(new_plan_line)
|
683
|
-
logging.info(f"Appended new plan line at the end of the project file")
|
684
657
|
|
685
658
|
try:
|
686
659
|
# Write the updated lines back to the project file
|
687
660
|
with open(ras_obj.prj_file, 'w') as f:
|
688
661
|
f.writelines(lines)
|
689
|
-
logging.info(f"Updated {ras_obj.prj_file} with new plan p{new_plan_num}")
|
690
662
|
except IOError as e:
|
691
|
-
|
692
|
-
raise
|
663
|
+
raise IOError(f"Failed to write updated project file: {e}")
|
693
664
|
|
694
665
|
new_plan = new_plan_num
|
695
666
|
|
@@ -704,6 +675,7 @@ class RasPlan:
|
|
704
675
|
return new_plan
|
705
676
|
|
706
677
|
@staticmethod
|
678
|
+
@log_call
|
707
679
|
def clone_unsteady(template_unsteady, ras_object=None):
|
708
680
|
"""
|
709
681
|
Copy unsteady flow files from a template, find the next unsteady number,
|
@@ -735,27 +707,21 @@ class RasPlan:
|
|
735
707
|
new_unsteady_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{new_unsteady_num}"
|
736
708
|
|
737
709
|
if not template_unsteady_path.exists():
|
738
|
-
logging.error(f"Template unsteady file '{template_unsteady_path}' does not exist.")
|
739
710
|
raise FileNotFoundError(f"Template unsteady file '{template_unsteady_path}' does not exist.")
|
740
711
|
|
741
712
|
shutil.copy(template_unsteady_path, new_unsteady_path)
|
742
|
-
logging.info(f"Copied {template_unsteady_path} to {new_unsteady_path}")
|
743
713
|
|
744
714
|
# Copy the corresponding .hdf file if it exists
|
745
715
|
template_hdf_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{template_unsteady}.hdf"
|
746
716
|
new_hdf_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{new_unsteady_num}.hdf"
|
747
717
|
if template_hdf_path.exists():
|
748
718
|
shutil.copy(template_hdf_path, new_hdf_path)
|
749
|
-
logging.info(f"Copied {template_hdf_path} to {new_hdf_path}")
|
750
|
-
else:
|
751
|
-
logging.warning(f"No corresponding .hdf file found for '{template_unsteady_path}'. Skipping '.hdf' copy.")
|
752
719
|
|
753
720
|
try:
|
754
721
|
with open(ras_obj.prj_file, 'r') as f:
|
755
722
|
lines = f.readlines()
|
756
723
|
except FileNotFoundError:
|
757
|
-
|
758
|
-
raise
|
724
|
+
raise FileNotFoundError(f"Project file not found: {ras_obj.prj_file}")
|
759
725
|
|
760
726
|
# Prepare the new Unsteady Flow File entry line
|
761
727
|
new_unsteady_line = f"Unsteady File=u{new_unsteady_num}\n"
|
@@ -775,27 +741,22 @@ class RasPlan:
|
|
775
741
|
|
776
742
|
if insertion_index is not None:
|
777
743
|
lines.insert(insertion_index, new_unsteady_line)
|
778
|
-
logging.info(f"Inserted new unsteady flow line at index {insertion_index}")
|
779
744
|
else:
|
780
745
|
# Try to insert after the last Unsteady Flow File entry
|
781
746
|
unsteady_indices = [i for i, line in enumerate(lines) if unsteady_file_pattern.match(line.strip())]
|
782
747
|
if unsteady_indices:
|
783
748
|
last_unsteady_index = unsteady_indices[-1]
|
784
749
|
lines.insert(last_unsteady_index + 1, new_unsteady_line)
|
785
|
-
logging.info(f"Inserted new unsteady flow line after index {last_unsteady_index}")
|
786
750
|
else:
|
787
751
|
# Append at the end if no Unsteady Flow File entries exist
|
788
752
|
lines.append(new_unsteady_line)
|
789
|
-
logging.info(f"Appended new unsteady flow line at the end of the project file")
|
790
753
|
|
791
754
|
try:
|
792
755
|
# Write the updated lines back to the project file
|
793
756
|
with open(ras_obj.prj_file, 'w') as f:
|
794
757
|
f.writelines(lines)
|
795
|
-
logging.info(f"Updated {ras_obj.prj_file} with new unsteady flow file u{new_unsteady_num}")
|
796
758
|
except IOError as e:
|
797
|
-
|
798
|
-
raise
|
759
|
+
raise IOError(f"Failed to write updated project file: {e}")
|
799
760
|
|
800
761
|
new_unsteady = new_unsteady_num
|
801
762
|
|
@@ -810,6 +771,7 @@ class RasPlan:
|
|
810
771
|
return new_unsteady
|
811
772
|
|
812
773
|
@staticmethod
|
774
|
+
@log_call
|
813
775
|
def clone_steady(template_flow, ras_object=None):
|
814
776
|
"""
|
815
777
|
Copy steady flow files from a template, find the next flow number,
|
@@ -841,19 +803,16 @@ class RasPlan:
|
|
841
803
|
new_flow_path = ras_obj.project_folder / f"{ras_obj.project_name}.f{new_flow_num}"
|
842
804
|
|
843
805
|
if not template_flow_path.exists():
|
844
|
-
logging.error(f"Template steady flow file '{template_flow_path}' does not exist.")
|
845
806
|
raise FileNotFoundError(f"Template steady flow file '{template_flow_path}' does not exist.")
|
846
807
|
|
847
808
|
shutil.copy(template_flow_path, new_flow_path)
|
848
|
-
logging.info(f"Copied {template_flow_path} to {new_flow_path}")
|
849
809
|
|
850
810
|
# Read the contents of the project file
|
851
811
|
try:
|
852
812
|
with open(ras_obj.prj_file, 'r') as f:
|
853
813
|
lines = f.readlines()
|
854
814
|
except FileNotFoundError:
|
855
|
-
|
856
|
-
raise
|
815
|
+
raise FileNotFoundError(f"Project file not found: {ras_obj.prj_file}")
|
857
816
|
|
858
817
|
# Prepare the new Steady Flow File entry line
|
859
818
|
new_flow_line = f"Flow File=f{new_flow_num}\n"
|
@@ -873,27 +832,22 @@ class RasPlan:
|
|
873
832
|
|
874
833
|
if insertion_index is not None:
|
875
834
|
lines.insert(insertion_index, new_flow_line)
|
876
|
-
logging.info(f"Inserted new steady flow line at index {insertion_index}")
|
877
835
|
else:
|
878
836
|
# Try to insert after the last Steady Flow File entry
|
879
837
|
flow_indices = [i for i, line in enumerate(lines) if flow_file_pattern.match(line.strip())]
|
880
838
|
if flow_indices:
|
881
839
|
last_flow_index = flow_indices[-1]
|
882
840
|
lines.insert(last_flow_index + 1, new_flow_line)
|
883
|
-
logging.info(f"Inserted new steady flow line after index {last_flow_index}")
|
884
841
|
else:
|
885
842
|
# Append at the end if no Steady Flow File entries exist
|
886
843
|
lines.append(new_flow_line)
|
887
|
-
logging.info(f"Appended new steady flow line at the end of the project file")
|
888
844
|
|
889
845
|
try:
|
890
846
|
# Write the updated lines back to the project file
|
891
847
|
with open(ras_obj.prj_file, 'w') as f:
|
892
848
|
f.writelines(lines)
|
893
|
-
logging.info(f"Updated {ras_obj.prj_file} with new steady flow file f{new_flow_num}")
|
894
849
|
except IOError as e:
|
895
|
-
|
896
|
-
raise
|
850
|
+
raise IOError(f"Failed to write updated project file: {e}")
|
897
851
|
|
898
852
|
new_steady = new_flow_num
|
899
853
|
|
@@ -908,6 +862,7 @@ class RasPlan:
|
|
908
862
|
return new_steady
|
909
863
|
|
910
864
|
@staticmethod
|
865
|
+
@log_call
|
911
866
|
def clone_geom(template_geom, ras_object=None):
|
912
867
|
"""
|
913
868
|
Copy geometry files from a template, find the next geometry number,
|
@@ -928,13 +883,11 @@ class RasPlan:
|
|
928
883
|
|
929
884
|
# Update geometry entries without reinitializing the entire project
|
930
885
|
ras_obj.geom_df = ras_obj.get_prj_entries('Geom')
|
931
|
-
logging.debug(f"Updated geometry entries:\n{ras_obj.geom_df}")
|
932
886
|
|
933
887
|
template_geom_filename = f"{ras_obj.project_name}.g{template_geom}"
|
934
888
|
template_geom_path = ras_obj.project_folder / template_geom_filename
|
935
889
|
|
936
890
|
if not template_geom_path.is_file():
|
937
|
-
logging.error(f"Template geometry file '{template_geom_path}' does not exist.")
|
938
891
|
raise FileNotFoundError(f"Template geometry file '{template_geom_path}' does not exist.")
|
939
892
|
|
940
893
|
next_geom_number = RasPlan.get_next_number(ras_obj.geom_df['geom_number'])
|
@@ -943,23 +896,18 @@ class RasPlan:
|
|
943
896
|
new_geom_path = ras_obj.project_folder / new_geom_filename
|
944
897
|
|
945
898
|
shutil.copyfile(template_geom_path, new_geom_path)
|
946
|
-
logging.info(f"Copied '{template_geom_path}' to '{new_geom_path}'.")
|
947
899
|
|
948
900
|
# Handle HDF file copy
|
949
901
|
template_hdf_path = ras_obj.project_folder / f"{ras_obj.project_name}.g{template_geom}.hdf"
|
950
902
|
new_hdf_path = ras_obj.project_folder / f"{ras_obj.project_name}.g{next_geom_number}.hdf"
|
951
903
|
if template_hdf_path.is_file():
|
952
904
|
shutil.copyfile(template_hdf_path, new_hdf_path)
|
953
|
-
logging.info(f"Copied '{template_hdf_path}' to '{new_hdf_path}'.")
|
954
|
-
else:
|
955
|
-
logging.warning(f"Template geometry HDF file '{template_hdf_path}' does not exist. Skipping '.hdf' copy.")
|
956
905
|
|
957
906
|
try:
|
958
907
|
with open(ras_obj.prj_file, 'r') as file:
|
959
908
|
lines = file.readlines()
|
960
909
|
except FileNotFoundError:
|
961
|
-
|
962
|
-
raise
|
910
|
+
raise FileNotFoundError(f"Project file not found: {ras_obj.prj_file}")
|
963
911
|
|
964
912
|
# Prepare the new Geometry File entry line
|
965
913
|
new_geom_line = f"Geom File=g{next_geom_number}\n"
|
@@ -979,27 +927,22 @@ class RasPlan:
|
|
979
927
|
|
980
928
|
if insertion_index is not None:
|
981
929
|
lines.insert(insertion_index, new_geom_line)
|
982
|
-
logging.info(f"Inserted new geometry line at index {insertion_index}")
|
983
930
|
else:
|
984
931
|
# Try to insert after the last Geometry File entry
|
985
932
|
geom_indices = [i for i, line in enumerate(lines) if geom_file_pattern.match(line.strip())]
|
986
933
|
if geom_indices:
|
987
934
|
last_geom_index = geom_indices[-1]
|
988
935
|
lines.insert(last_geom_index + 1, new_geom_line)
|
989
|
-
logging.info(f"Inserted new geometry line after index {last_geom_index}")
|
990
936
|
else:
|
991
937
|
# Append at the end if no Geometry File entries exist
|
992
938
|
lines.append(new_geom_line)
|
993
|
-
logging.info(f"Appended new geometry line at the end of the project file")
|
994
939
|
|
995
940
|
try:
|
996
941
|
# Write the updated lines back to the project file
|
997
942
|
with open(ras_obj.prj_file, 'w') as file:
|
998
943
|
file.writelines(lines)
|
999
|
-
logging.info(f"Updated {ras_obj.prj_file} with new geometry file g{next_geom_number}")
|
1000
944
|
except IOError as e:
|
1001
|
-
|
1002
|
-
raise
|
945
|
+
raise IOError(f"Failed to write updated project file: {e}")
|
1003
946
|
|
1004
947
|
new_geom = next_geom_number
|
1005
948
|
|
@@ -1009,11 +952,10 @@ class RasPlan:
|
|
1009
952
|
ras_obj.flow_df = ras_obj.get_flow_entries()
|
1010
953
|
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
1011
954
|
|
1012
|
-
logging.debug(f"Updated geometry entries:\n{ras_obj.geom_df}")
|
1013
|
-
|
1014
955
|
return new_geom
|
1015
956
|
|
1016
957
|
@staticmethod
|
958
|
+
@log_call
|
1017
959
|
def get_next_number(existing_numbers):
|
1018
960
|
"""
|
1019
961
|
Determine the next available number from a list of existing numbers.
|
@@ -1041,8 +983,8 @@ class RasPlan:
|
|
1041
983
|
break
|
1042
984
|
return f"{next_number:02d}"
|
1043
985
|
|
1044
|
-
|
1045
986
|
@staticmethod
|
987
|
+
@log_call
|
1046
988
|
def get_plan_value(
|
1047
989
|
plan_number_or_path: Union[str, Path],
|
1048
990
|
key: str,
|
@@ -1064,47 +1006,51 @@ class RasPlan:
|
|
1064
1006
|
IOError: If there's an error reading the plan file
|
1065
1007
|
|
1066
1008
|
Available keys and their expected types:
|
1067
|
-
- '
|
1068
|
-
- '
|
1069
|
-
- '
|
1070
|
-
- '
|
1071
|
-
- '
|
1072
|
-
- '
|
1073
|
-
- '
|
1074
|
-
- '
|
1075
|
-
- '
|
1076
|
-
- '
|
1077
|
-
- '
|
1078
|
-
- '
|
1079
|
-
- '
|
1080
|
-
- '
|
1081
|
-
- '
|
1082
|
-
- '
|
1083
|
-
- '
|
1084
|
-
- '
|
1085
|
-
- '
|
1086
|
-
- '
|
1087
|
-
- '
|
1088
|
-
- '
|
1089
|
-
|
1009
|
+
- 'Computation Interval' (str): Time value for computational time step (e.g., '5SEC', '2MIN')
|
1010
|
+
- 'DSS File' (str): Name of the DSS file used
|
1011
|
+
- 'Flow File' (str): Name of the flow input file
|
1012
|
+
- 'Friction Slope Method' (int): Method selection for friction slope (e.g., 1, 2)
|
1013
|
+
- 'Geom File' (str): Name of the geometry input file
|
1014
|
+
- 'Mapping Interval' (str): Time interval for mapping output
|
1015
|
+
- 'Plan File' (str): Name of the plan file
|
1016
|
+
- 'Plan Title' (str): Title of the simulation plan
|
1017
|
+
- 'Program Version' (str): Version number of HEC-RAS
|
1018
|
+
- 'Run HTAB' (int): Flag to run HTab module (-1 or 1)
|
1019
|
+
- 'Run Post Process' (int): Flag to run post-processing (-1 or 1)
|
1020
|
+
- 'Run Sediment' (int): Flag to run sediment transport module (0 or 1)
|
1021
|
+
- 'Run UNET' (int): Flag to run unsteady network module (-1 or 1)
|
1022
|
+
- 'Run WQNET' (int): Flag to run water quality module (0 or 1)
|
1023
|
+
- 'Short Identifier' (str): Short name or ID for the plan
|
1024
|
+
- 'Simulation Date' (str): Start and end dates/times for simulation
|
1025
|
+
- 'UNET D1 Cores' (int): Number of cores used in 1D calculations
|
1026
|
+
- 'UNET Use Existing IB Tables' (int): Flag for using existing internal boundary tables (-1, 0, or 1)
|
1027
|
+
- 'UNET 1D Methodology' (str): 1D calculation methodology
|
1028
|
+
- 'UNET D2 Solver Type' (str): 2D solver type
|
1029
|
+
- 'UNET D2 Name' (str): Name of the 2D area
|
1030
|
+
- 'Run RASMapper' (int): Flag to run RASMapper for floodplain mapping (-1 for off, 0 for on)
|
1031
|
+
|
1032
|
+
|
1033
|
+
Note:
|
1034
|
+
Writing Multi line keys like 'Description' are not supported by this function.
|
1090
1035
|
|
1091
1036
|
Example:
|
1092
|
-
>>> computation_interval = RasPlan.get_plan_value("01", "
|
1037
|
+
>>> computation_interval = RasPlan.get_plan_value("01", "Computation Interval")
|
1093
1038
|
>>> print(f"Computation interval: {computation_interval}")
|
1094
1039
|
"""
|
1095
1040
|
ras_obj = ras_object or ras
|
1096
1041
|
ras_obj.check_initialized()
|
1097
1042
|
|
1098
|
-
|
1099
|
-
'
|
1100
|
-
'
|
1101
|
-
'
|
1102
|
-
'
|
1103
|
-
'
|
1043
|
+
supported_plan_keys = {
|
1044
|
+
'Description', 'Computation Interval', 'DSS File', 'Flow File', 'Friction Slope Method',
|
1045
|
+
'Geom File', 'Mapping Interval', 'Plan File', 'Plan Title', 'Program Version',
|
1046
|
+
'Run HTAB', 'Run Post Process', 'Run Sediment', 'Run UNET', 'Run WQNET',
|
1047
|
+
'Short Identifier', 'Simulation Date', 'UNET D1 Cores', 'UNET Use Existing IB Tables',
|
1048
|
+
'UNET 1D Methodology', 'UNET D2 Solver Type', 'UNET D2 Name', 'Run RASMapper'
|
1104
1049
|
}
|
1105
1050
|
|
1106
|
-
if key not in
|
1107
|
-
|
1051
|
+
if key not in supported_plan_keys:
|
1052
|
+
logger = logging.getLogger(__name__)
|
1053
|
+
logger.warning(f"Unknown key: {key}. Valid keys are: {', '.join(supported_plan_keys)}\n Add more keys and explanations in get_plan_value() as needed.")
|
1108
1054
|
|
1109
1055
|
plan_file_path = Path(plan_number_or_path)
|
1110
1056
|
if not plan_file_path.is_file():
|
@@ -1116,22 +1062,25 @@ class RasPlan:
|
|
1116
1062
|
with open(plan_file_path, 'r') as file:
|
1117
1063
|
content = file.read()
|
1118
1064
|
except IOError as e:
|
1119
|
-
logging.
|
1065
|
+
logger = logging.getLogger(__name__)
|
1066
|
+
logger.error(f"Error reading plan file {plan_file_path}: {e}")
|
1120
1067
|
raise
|
1121
1068
|
|
1122
|
-
if key == '
|
1069
|
+
if key == 'Description':
|
1123
1070
|
match = re.search(r'Begin DESCRIPTION(.*?)END DESCRIPTION', content, re.DOTALL)
|
1124
1071
|
return match.group(1).strip() if match else None
|
1125
1072
|
else:
|
1126
|
-
pattern = f"{key
|
1073
|
+
pattern = f"{key}=(.*)"
|
1127
1074
|
match = re.search(pattern, content)
|
1128
1075
|
if match:
|
1129
1076
|
return match.group(1).strip()
|
1130
1077
|
else:
|
1131
|
-
logging.
|
1078
|
+
logger = logging.getLogger(__name__)
|
1079
|
+
logger.error(f"Key '{key}' not found in the plan file.")
|
1132
1080
|
return None
|
1133
1081
|
|
1134
1082
|
@staticmethod
|
1083
|
+
@log_call
|
1135
1084
|
def update_plan_value(
|
1136
1085
|
plan_number_or_path: Union[str, Path],
|
1137
1086
|
key: str,
|
@@ -1161,16 +1110,16 @@ class RasPlan:
|
|
1161
1110
|
ras_obj = ras_object or ras
|
1162
1111
|
ras_obj.check_initialized()
|
1163
1112
|
|
1164
|
-
|
1165
|
-
'
|
1166
|
-
'
|
1167
|
-
'
|
1168
|
-
'
|
1169
|
-
'
|
1113
|
+
supported_plan_keys = {
|
1114
|
+
'Description', 'Computation Interval', 'DSS File', 'Flow File', 'Friction Slope Method',
|
1115
|
+
'Geom File', 'Mapping Interval', 'Plan File', 'Plan Title', 'Program Version',
|
1116
|
+
'Run HTAB', 'Run Post Process', 'Run Sediment', 'Run UNET', 'Run WQNET',
|
1117
|
+
'Short Identifier', 'Simulation Date', 'UNET D1 Cores', 'UNET Use Existing IB Tables',
|
1118
|
+
'UNET 1D Methodology', 'UNET D2 Solver Type', 'UNET D2 Name', 'Run RASMapper'
|
1170
1119
|
}
|
1171
|
-
|
1172
|
-
if key not in
|
1173
|
-
|
1120
|
+
logger = logging.getLogger(__name__)
|
1121
|
+
if key not in supported_plan_keys:
|
1122
|
+
logger.warning(f"Unknown key: {key}. Valid keys are: {', '.join(supported_plan_keys)}")
|
1174
1123
|
|
1175
1124
|
plan_file_path = Path(plan_number_or_path)
|
1176
1125
|
if not plan_file_path.is_file():
|
@@ -1182,7 +1131,7 @@ class RasPlan:
|
|
1182
1131
|
with open(plan_file_path, 'r') as file:
|
1183
1132
|
lines = file.readlines()
|
1184
1133
|
except IOError as e:
|
1185
|
-
|
1134
|
+
logger.error(f"Error reading plan file {plan_file_path}: {e}")
|
1186
1135
|
raise
|
1187
1136
|
|
1188
1137
|
# Special handling for description
|
@@ -1209,15 +1158,15 @@ class RasPlan:
|
|
1209
1158
|
updated = True
|
1210
1159
|
break
|
1211
1160
|
if not updated:
|
1212
|
-
|
1161
|
+
logger.error(f"Key '{key}' not found in the plan file.")
|
1213
1162
|
return
|
1214
1163
|
|
1215
1164
|
try:
|
1216
1165
|
with open(plan_file_path, 'w') as file:
|
1217
1166
|
file.writelines(lines)
|
1218
|
-
|
1167
|
+
logger.info(f"Updated {key} in plan file: {plan_file_path}")
|
1219
1168
|
except IOError as e:
|
1220
|
-
|
1169
|
+
logger.error(f"Error writing to plan file {plan_file_path}: {e}")
|
1221
1170
|
raise
|
1222
1171
|
|
1223
1172
|
# Refresh RasPrj dataframes
|