ras-commander 0.1.6__py2.py3-none-any.whl → 0.20.dev0__py2.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/README.md +187 -0
- ras_commander/RasCommander.py +456 -0
- ras_commander/RasExamples.py +304 -0
- ras_commander/RasGeo.py +83 -0
- ras_commander/RasPlan.py +1216 -0
- ras_commander/RasPrj.py +400 -0
- ras_commander/RasUnsteady.py +53 -0
- ras_commander/RasUtils.py +283 -0
- ras_commander/__init__.py +33 -0
- ras_commander/_version.py +2 -2
- ras_commander-0.20.dev0.dist-info/METADATA +342 -0
- ras_commander-0.20.dev0.dist-info/RECORD +15 -0
- {ras_commander-0.1.6.dist-info → ras_commander-0.20.dev0.dist-info}/WHEEL +1 -1
- ras_commander/execution.py +0 -315
- ras_commander/file_operations.py +0 -173
- ras_commander/geometry_operations.py +0 -184
- ras_commander/plan_operations.py +0 -307
- ras_commander/project_config.py +0 -64
- ras_commander/project_init.py +0 -174
- ras_commander/project_management.py +0 -227
- ras_commander/project_setup.py +0 -15
- ras_commander/unsteady_operations.py +0 -172
- ras_commander/utilities.py +0 -195
- ras_commander-0.1.6.dist-info/METADATA +0 -133
- ras_commander-0.1.6.dist-info/RECORD +0 -17
- {ras_commander-0.1.6.dist-info → ras_commander-0.20.dev0.dist-info}/LICENSE +0 -0
- {ras_commander-0.1.6.dist-info → ras_commander-0.20.dev0.dist-info}/top_level.txt +0 -0
ras_commander/RasPrj.py
ADDED
@@ -0,0 +1,400 @@
|
|
1
|
+
"""RasPrj.py
|
2
|
+
|
3
|
+
This module provides a class for managing HEC-RAS projects.
|
4
|
+
|
5
|
+
Classes:
|
6
|
+
RasPrj: A class for managing HEC-RAS projects.
|
7
|
+
|
8
|
+
Functions:
|
9
|
+
init_ras_project: Initialize a RAS project.
|
10
|
+
get_ras_exe: Determine the HEC-RAS executable path based on the input.
|
11
|
+
|
12
|
+
DEVELOPER NOTE:
|
13
|
+
This class is used to initialize a RAS project and is used in conjunction with the RasCommander class to manage the execution of RAS plans.
|
14
|
+
By default, the RasPrj class is initialized with the global 'ras' object.
|
15
|
+
However, you can create multiple RasPrj instances to manage multiple projects.
|
16
|
+
Do not mix and match global 'ras' object instances and custom instances of RasPrj - it will cause errors.
|
17
|
+
"""
|
18
|
+
# Example Terminal Output for RasPrj Functions:
|
19
|
+
# print(f"\n----- INSERT TEXT HERE -----\n")
|
20
|
+
|
21
|
+
from pathlib import Path
|
22
|
+
import pandas as pd
|
23
|
+
import re
|
24
|
+
|
25
|
+
class RasPrj:
|
26
|
+
def __init__(self):
|
27
|
+
self.initialized = False
|
28
|
+
|
29
|
+
def initialize(self, project_folder, ras_exe_path):
|
30
|
+
"""
|
31
|
+
Initialize a RasPrj instance.
|
32
|
+
|
33
|
+
This method sets up the RasPrj instance with the given project folder and RAS executable path.
|
34
|
+
It finds the project file, loads project data, and sets the initialization flag.
|
35
|
+
|
36
|
+
Args:
|
37
|
+
project_folder (str or Path): Path to the HEC-RAS project folder.
|
38
|
+
ras_exe_path (str or Path): Path to the HEC-RAS executable.
|
39
|
+
|
40
|
+
Raises:
|
41
|
+
ValueError: If no HEC-RAS project file is found in the specified folder.
|
42
|
+
|
43
|
+
Note:
|
44
|
+
This method is intended for internal use. External users should use the init_ras_project function instead.
|
45
|
+
"""
|
46
|
+
self.project_folder = Path(project_folder)
|
47
|
+
self.prj_file = self.find_ras_prj(self.project_folder)
|
48
|
+
if self.prj_file is None:
|
49
|
+
raise ValueError(f"No HEC-RAS project file found in {self.project_folder}")
|
50
|
+
self.project_name = Path(self.prj_file).stem
|
51
|
+
self.ras_exe_path = ras_exe_path
|
52
|
+
self._load_project_data()
|
53
|
+
self.initialized = True
|
54
|
+
print(f"\n-----Initialization complete for project: {self.project_name}-----")
|
55
|
+
print(f"Plan entries: {len(self.plan_df)}, Flow entries: {len(self.flow_df)}, Unsteady entries: {len(self.unsteady_df)}, Geometry entries: {len(self.geom_df)}\n")
|
56
|
+
|
57
|
+
def _load_project_data(self):
|
58
|
+
"""
|
59
|
+
Load project data from the HEC-RAS project file.
|
60
|
+
|
61
|
+
This method initializes DataFrames for plan, flow, unsteady, and geometry entries
|
62
|
+
by calling the _get_prj_entries method for each entry type.
|
63
|
+
"""
|
64
|
+
# Initialize DataFrames
|
65
|
+
self.plan_df = self._get_prj_entries('Plan')
|
66
|
+
self.flow_df = self._get_prj_entries('Flow')
|
67
|
+
self.unsteady_df = self._get_prj_entries('Unsteady')
|
68
|
+
self.geom_df = self._get_prj_entries('Geom')
|
69
|
+
|
70
|
+
def _get_prj_entries(self, entry_type):
|
71
|
+
"""
|
72
|
+
Extract entries of a specific type from the HEC-RAS project file.
|
73
|
+
|
74
|
+
Args:
|
75
|
+
entry_type (str): The type of entry to extract (e.g., 'Plan', 'Flow', 'Unsteady', 'Geom').
|
76
|
+
|
77
|
+
Returns:
|
78
|
+
pd.DataFrame: A DataFrame containing the extracted entries.
|
79
|
+
|
80
|
+
Note:
|
81
|
+
This method reads the project file and extracts entries matching the specified type.
|
82
|
+
For 'Plan' entries, it also checks for the existence of HDF results files.
|
83
|
+
"""
|
84
|
+
# Initialize an empty list to store entries
|
85
|
+
entries = []
|
86
|
+
# Create a regex pattern to match the specific entry type
|
87
|
+
pattern = re.compile(rf"{entry_type} File=(\w+)")
|
88
|
+
|
89
|
+
# Open and read the project file
|
90
|
+
with open(self.prj_file, 'r') as file:
|
91
|
+
for line in file:
|
92
|
+
# Check if the line matches the pattern
|
93
|
+
match = pattern.match(line.strip())
|
94
|
+
if match:
|
95
|
+
# Extract the file name from the matched pattern
|
96
|
+
file_name = match.group(1)
|
97
|
+
# Create a dictionary for the current entry
|
98
|
+
entry = {
|
99
|
+
f'{entry_type.lower()}_number': file_name[1:],
|
100
|
+
'full_path': str(self.project_folder / f"{self.project_name}.{file_name}")
|
101
|
+
}
|
102
|
+
|
103
|
+
# Special handling for Plan entries
|
104
|
+
if entry_type == 'Plan':
|
105
|
+
# Construct the path for the HDF results file
|
106
|
+
hdf_results_path = self.project_folder / f"{self.project_name}.p{file_name[1:]}.hdf"
|
107
|
+
# Add the results_path to the entry, if the file exists
|
108
|
+
entry['HDF_Results_Path'] = str(hdf_results_path) if hdf_results_path.exists() else None
|
109
|
+
|
110
|
+
# Add the entry to the list
|
111
|
+
entries.append(entry)
|
112
|
+
|
113
|
+
# Convert the list of entries to a DataFrame and return it
|
114
|
+
return pd.DataFrame(entries)
|
115
|
+
|
116
|
+
@property
|
117
|
+
def is_initialized(self):
|
118
|
+
"""
|
119
|
+
Check if the RasPrj instance has been initialized.
|
120
|
+
|
121
|
+
Returns:
|
122
|
+
bool: True if the instance has been initialized, False otherwise.
|
123
|
+
"""
|
124
|
+
return self.initialized
|
125
|
+
|
126
|
+
def check_initialized(self):
|
127
|
+
"""
|
128
|
+
Ensure that the RasPrj instance has been initialized.
|
129
|
+
|
130
|
+
Raises:
|
131
|
+
RuntimeError: If the project has not been initialized.
|
132
|
+
"""
|
133
|
+
if not self.initialized:
|
134
|
+
raise RuntimeError("Project not initialized. Call init_ras_project() first.")
|
135
|
+
|
136
|
+
@staticmethod
|
137
|
+
def find_ras_prj(folder_path):
|
138
|
+
"""
|
139
|
+
Find the appropriate HEC-RAS project file (.prj) in the given folder.
|
140
|
+
|
141
|
+
Parameters:
|
142
|
+
folder_path (str or Path): Path to the folder containing HEC-RAS files.
|
143
|
+
|
144
|
+
Returns:
|
145
|
+
Path: The full path of the selected .prj file or None if no suitable file is found.
|
146
|
+
"""
|
147
|
+
folder_path = Path(folder_path)
|
148
|
+
prj_files = list(folder_path.glob("*.prj"))
|
149
|
+
rasmap_files = list(folder_path.glob("*.rasmap"))
|
150
|
+
if len(prj_files) == 1:
|
151
|
+
return prj_files[0].resolve()
|
152
|
+
if len(prj_files) > 1:
|
153
|
+
if len(rasmap_files) == 1:
|
154
|
+
base_filename = rasmap_files[0].stem
|
155
|
+
prj_file = folder_path / f"{base_filename}.prj"
|
156
|
+
return prj_file.resolve()
|
157
|
+
for prj_file in prj_files:
|
158
|
+
with open(prj_file, 'r') as file:
|
159
|
+
if "Proj Title=" in file.read():
|
160
|
+
return prj_file.resolve()
|
161
|
+
print("No suitable .prj file found after all checks.")
|
162
|
+
return None
|
163
|
+
|
164
|
+
def get_project_name(self):
|
165
|
+
"""
|
166
|
+
Get the name of the HEC-RAS project.
|
167
|
+
|
168
|
+
Returns:
|
169
|
+
str: The name of the project.
|
170
|
+
|
171
|
+
Raises:
|
172
|
+
RuntimeError: If the project has not been initialized.
|
173
|
+
"""
|
174
|
+
self.check_initialized()
|
175
|
+
return self.project_name
|
176
|
+
|
177
|
+
def get_prj_entries(self, entry_type):
|
178
|
+
"""
|
179
|
+
Get entries of a specific type from the HEC-RAS project.
|
180
|
+
|
181
|
+
Args:
|
182
|
+
entry_type (str): The type of entry to retrieve (e.g., 'Plan', 'Flow', 'Unsteady', 'Geom').
|
183
|
+
|
184
|
+
Returns:
|
185
|
+
pd.DataFrame: A DataFrame containing the requested entries.
|
186
|
+
|
187
|
+
Raises:
|
188
|
+
RuntimeError: If the project has not been initialized.
|
189
|
+
"""
|
190
|
+
self.check_initialized()
|
191
|
+
return self._get_prj_entries(entry_type)
|
192
|
+
|
193
|
+
def get_plan_entries(self):
|
194
|
+
"""
|
195
|
+
Get all plan entries from the HEC-RAS project.
|
196
|
+
|
197
|
+
Returns:
|
198
|
+
pd.DataFrame: A DataFrame containing all plan entries.
|
199
|
+
|
200
|
+
Raises:
|
201
|
+
RuntimeError: If the project has not been initialized.
|
202
|
+
"""
|
203
|
+
self.check_initialized()
|
204
|
+
return self._get_prj_entries('Plan')
|
205
|
+
|
206
|
+
def get_flow_entries(self):
|
207
|
+
"""
|
208
|
+
Get all flow entries from the HEC-RAS project.
|
209
|
+
|
210
|
+
Returns:
|
211
|
+
pd.DataFrame: A DataFrame containing all flow entries.
|
212
|
+
|
213
|
+
Raises:
|
214
|
+
RuntimeError: If the project has not been initialized.
|
215
|
+
"""
|
216
|
+
self.check_initialized()
|
217
|
+
return self._get_prj_entries('Flow')
|
218
|
+
|
219
|
+
def get_unsteady_entries(self):
|
220
|
+
"""
|
221
|
+
Get all unsteady flow entries from the HEC-RAS project.
|
222
|
+
|
223
|
+
Returns:
|
224
|
+
pd.DataFrame: A DataFrame containing all unsteady flow entries.
|
225
|
+
|
226
|
+
Raises:
|
227
|
+
RuntimeError: If the project has not been initialized.
|
228
|
+
"""
|
229
|
+
self.check_initialized()
|
230
|
+
return self._get_prj_entries('Unsteady')
|
231
|
+
|
232
|
+
def get_geom_entries(self):
|
233
|
+
"""
|
234
|
+
Get all geometry entries from the HEC-RAS project.
|
235
|
+
|
236
|
+
Returns:
|
237
|
+
pd.DataFrame: A DataFrame containing all geometry entries.
|
238
|
+
|
239
|
+
Raises:
|
240
|
+
RuntimeError: If the project has not been initialized.
|
241
|
+
"""
|
242
|
+
self.check_initialized()
|
243
|
+
return self._get_prj_entries('Geom')
|
244
|
+
|
245
|
+
def get_hdf_entries(self):
|
246
|
+
"""
|
247
|
+
Get HDF entries for plans that have results.
|
248
|
+
|
249
|
+
Returns:
|
250
|
+
pd.DataFrame: A DataFrame containing plan entries with HDF results.
|
251
|
+
Returns an empty DataFrame if no HDF entries are found.
|
252
|
+
"""
|
253
|
+
self.check_initialized()
|
254
|
+
|
255
|
+
# Filter the plan_df to include only entries with existing HDF results
|
256
|
+
hdf_entries = self.plan_df[self.plan_df['HDF_Results_Path'].notna()].copy()
|
257
|
+
|
258
|
+
# If no HDF entries are found, return an empty DataFrame with the correct columns
|
259
|
+
if hdf_entries.empty:
|
260
|
+
return pd.DataFrame(columns=self.plan_df.columns)
|
261
|
+
|
262
|
+
return hdf_entries
|
263
|
+
|
264
|
+
def print_data(self):
|
265
|
+
"""Print all RAS Object data for this instance.
|
266
|
+
If any objects are added, add them to the print statements below."""
|
267
|
+
print(f"\n--- Data for {self.project_name} ---")
|
268
|
+
print(f"Project folder: {self.project_folder}")
|
269
|
+
print(f"PRJ file: {self.prj_file}")
|
270
|
+
print(f"HEC-RAS executable: {self.ras_exe_path}")
|
271
|
+
print("\nPlan files:")
|
272
|
+
print(self.plan_df)
|
273
|
+
print("\nFlow files:")
|
274
|
+
print(self.flow_df)
|
275
|
+
print("\nUnsteady flow files:")
|
276
|
+
print(self.unsteady_df)
|
277
|
+
print("\nGeometry files:")
|
278
|
+
print(self.geom_df)
|
279
|
+
print("\nHDF entries:")
|
280
|
+
print(self.get_hdf_entries())
|
281
|
+
print("----------------------------\n")
|
282
|
+
|
283
|
+
|
284
|
+
# Create a global instance named 'ras'
|
285
|
+
ras = RasPrj()
|
286
|
+
|
287
|
+
def init_ras_project(ras_project_folder, ras_version, ras_instance=None):
|
288
|
+
"""
|
289
|
+
Initialize a RAS project.
|
290
|
+
|
291
|
+
USE THIS FUNCTION TO INITIALIZE A RAS PROJECT, NOT THE INITIALIZE METHOD OF THE RasPrj CLASS.
|
292
|
+
The initialize method of the RasPrj class only modifies the global 'ras' object.
|
293
|
+
|
294
|
+
This function creates or initializes a RasPrj instance, providing a safer and more
|
295
|
+
flexible interface than directly using the 'initialize' method.
|
296
|
+
|
297
|
+
Parameters:
|
298
|
+
-----------
|
299
|
+
ras_project_folder : str
|
300
|
+
The path to the RAS project folder.
|
301
|
+
ras_version : str
|
302
|
+
The version of RAS to use (e.g., "6.5").
|
303
|
+
The version can also be a full path to the Ras.exe file. (Useful when calling ras objects for folder copies.)
|
304
|
+
ras_instance : RasPrj, optional
|
305
|
+
An instance of RasPrj to initialize. If None, the global 'ras' instance is used.
|
306
|
+
|
307
|
+
Returns:
|
308
|
+
--------
|
309
|
+
RasPrj
|
310
|
+
An initialized RasPrj instance.
|
311
|
+
|
312
|
+
Usage:
|
313
|
+
------
|
314
|
+
1. For general use with a single project:
|
315
|
+
init_ras_project("/path/to/project", "6.5")
|
316
|
+
# Use the global 'ras' object after initialization
|
317
|
+
|
318
|
+
2. For managing multiple projects:
|
319
|
+
project1 = init_ras_project("/path/to/project1", "6.5", ras_instance=RasPrj())
|
320
|
+
project2 = init_ras_project("/path/to/project2", "6.5", ras_instance=RasPrj())
|
321
|
+
|
322
|
+
Notes:
|
323
|
+
------
|
324
|
+
- This function is preferred over directly calling the 'initialize' method.
|
325
|
+
- It supports both the global 'ras' object and custom instances.
|
326
|
+
- Be consistent in your approach: stick to either the global 'ras' object
|
327
|
+
or custom instances throughout your script or application.
|
328
|
+
- Document your choice of approach clearly in your code.
|
329
|
+
|
330
|
+
Warnings:
|
331
|
+
---------
|
332
|
+
Avoid mixing use of the global 'ras' object and custom instances to prevent
|
333
|
+
confusion and potential bugs.
|
334
|
+
"""
|
335
|
+
|
336
|
+
if not Path(ras_project_folder).exists():
|
337
|
+
raise FileNotFoundError(f"The specified RAS project folder does not exist: {ras_project_folder}. Please check the path and try again.")
|
338
|
+
|
339
|
+
ras_exe_path = get_ras_exe(ras_version)
|
340
|
+
|
341
|
+
if ras_instance is None:
|
342
|
+
print(f"\n-----Initializing global 'ras' object via init_ras_project function-----")
|
343
|
+
ras_instance = ras
|
344
|
+
elif not isinstance(ras_instance, RasPrj):
|
345
|
+
print(f"\n-----Initializing custom RasPrj instance via init_ras_project function-----")
|
346
|
+
raise TypeError("ras_instance must be an instance of RasPrj or None.")
|
347
|
+
|
348
|
+
# Initialize the RasPrj instance
|
349
|
+
ras_instance.initialize(ras_project_folder, ras_exe_path)
|
350
|
+
|
351
|
+
#print(f"\n-----HEC-RAS project initialized via init_ras_project function: {ras_instance.project_name}-----\n")
|
352
|
+
return ras_instance
|
353
|
+
|
354
|
+
|
355
|
+
def get_ras_exe(ras_version):
|
356
|
+
"""
|
357
|
+
Determine the HEC-RAS executable path based on the input.
|
358
|
+
|
359
|
+
Args:
|
360
|
+
ras_version (str): Either a version number or a full path to the HEC-RAS executable.
|
361
|
+
|
362
|
+
Returns:
|
363
|
+
str: The full path to the HEC-RAS executable.
|
364
|
+
|
365
|
+
Raises:
|
366
|
+
ValueError: If the input is neither a valid version number nor a valid file path.
|
367
|
+
FileNotFoundError: If the executable file does not exist at the specified or constructed path.
|
368
|
+
"""
|
369
|
+
ras_version_numbers = [
|
370
|
+
"6.5", "6.4.1", "6.3.1", "6.3", "6.2", "6.1", "6.0",
|
371
|
+
"5.0.7", "5.0.6", "5.0.5", "5.0.4", "5.0.3", "5.0.1", "5.0",
|
372
|
+
"4.1", "4.0", "3.1.3", "3.1.2", "3.1.1", "3.0", "2.2"
|
373
|
+
]
|
374
|
+
|
375
|
+
hecras_path = Path(ras_version)
|
376
|
+
|
377
|
+
if hecras_path.is_file() and hecras_path.suffix.lower() == '.exe':
|
378
|
+
return str(hecras_path)
|
379
|
+
|
380
|
+
if ras_version in ras_version_numbers:
|
381
|
+
default_path = Path(f"C:/Program Files (x86)/HEC/HEC-RAS/{ras_version}/Ras.exe")
|
382
|
+
if default_path.is_file():
|
383
|
+
return str(default_path)
|
384
|
+
else:
|
385
|
+
raise FileNotFoundError(f"HEC-RAS executable not found at the expected path: {default_path}")
|
386
|
+
|
387
|
+
try:
|
388
|
+
version_float = float(ras_version)
|
389
|
+
if version_float > max(float(v) for v in ras_version_numbers):
|
390
|
+
newer_version_path = Path(f"C:/Program Files (x86)/HEC/HEC-RAS/{ras_version}/Ras.exe")
|
391
|
+
if newer_version_path.is_file():
|
392
|
+
return str(newer_version_path)
|
393
|
+
else:
|
394
|
+
raise FileNotFoundError(f"Newer version of HEC-RAS was specified. Check the version number or pass the full Ras.exe path as the function argument instead of the version number. The script looked for the executable at: {newer_version_path}")
|
395
|
+
except ValueError:
|
396
|
+
pass
|
397
|
+
|
398
|
+
raise ValueError(f"Invalid HEC-RAS version or path: {ras_version}. "
|
399
|
+
f"Please provide a valid version number from {ras_version_numbers} "
|
400
|
+
"or a full path to the HEC-RAS executable.")
|
@@ -0,0 +1,53 @@
|
|
1
|
+
"""
|
2
|
+
Operations for handling unsteady flow files in HEC-RAS projects.
|
3
|
+
"""
|
4
|
+
from pathlib import Path
|
5
|
+
from .RasPrj import ras
|
6
|
+
import re
|
7
|
+
|
8
|
+
class RasUnsteady:
|
9
|
+
"""
|
10
|
+
Class for all operations related to HEC-RAS unsteady flow files.
|
11
|
+
"""
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
@staticmethod
|
16
|
+
def update_unsteady_parameters(unsteady_file, modifications, ras_object=None):
|
17
|
+
"""
|
18
|
+
Modify parameters in an unsteady flow file.
|
19
|
+
|
20
|
+
Parameters:
|
21
|
+
unsteady_file (str): Full path to the unsteady flow file
|
22
|
+
modifications (dict): Dictionary of modifications to apply, where keys are parameter names and values are new values
|
23
|
+
ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
|
24
|
+
|
25
|
+
Returns:
|
26
|
+
None
|
27
|
+
|
28
|
+
Note:
|
29
|
+
This function updates the ras object's unsteady dataframe after modifying the unsteady flow file.
|
30
|
+
"""
|
31
|
+
ras_obj = ras_object or ras
|
32
|
+
ras_obj.check_initialized()
|
33
|
+
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
34
|
+
|
35
|
+
unsteady_path = Path(unsteady_file)
|
36
|
+
with open(unsteady_path, 'r') as f:
|
37
|
+
lines = f.readlines()
|
38
|
+
updated = False
|
39
|
+
for i, line in enumerate(lines):
|
40
|
+
for param, new_value in modifications.items():
|
41
|
+
if line.startswith(f"{param}="):
|
42
|
+
lines[i] = f"{param}={new_value}\n"
|
43
|
+
updated = True
|
44
|
+
print(f"Updated {param} to {new_value}")
|
45
|
+
if updated:
|
46
|
+
with open(unsteady_path, 'w') as f:
|
47
|
+
f.writelines(lines)
|
48
|
+
print(f"Applied modifications to {unsteady_file}")
|
49
|
+
else:
|
50
|
+
print(f"No matching parameters found in {unsteady_file}")
|
51
|
+
|
52
|
+
ras_obj = ras_object or ras
|
53
|
+
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|