ras-commander 0.45.0__py3-none-any.whl → 0.47.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/HdfBndry.py +9 -9
- ras_commander/HdfFluvialPluvial.py +317 -0
- ras_commander/HdfMesh.py +73 -27
- ras_commander/HdfPipe.py +587 -78
- ras_commander/HdfPlan.py +5 -0
- ras_commander/HdfPump.py +25 -11
- ras_commander/HdfResultsMesh.py +135 -62
- ras_commander/HdfResultsXsec.py +126 -297
- ras_commander/HdfStruc.py +148 -50
- ras_commander/HdfUtils.py +51 -0
- ras_commander/HdfXsec.py +467 -136
- ras_commander/RasPlan.py +298 -45
- ras_commander/RasPrj.py +2 -7
- ras_commander/RasToGo.py +21 -0
- ras_commander/RasUnsteady.py +615 -14
- ras_commander/__init__.py +3 -1
- {ras_commander-0.45.0.dist-info → ras_commander-0.47.0.dist-info}/METADATA +2 -2
- ras_commander-0.47.0.dist-info/RECORD +30 -0
- {ras_commander-0.45.0.dist-info → ras_commander-0.47.0.dist-info}/WHEEL +1 -1
- ras_commander-0.45.0.dist-info/RECORD +0 -28
- {ras_commander-0.45.0.dist-info → ras_commander-0.47.0.dist-info}/LICENSE +0 -0
- {ras_commander-0.45.0.dist-info → ras_commander-0.47.0.dist-info}/top_level.txt +0 -0
ras_commander/HdfPlan.py
CHANGED
ras_commander/HdfPump.py
CHANGED
@@ -7,6 +7,7 @@ from pathlib import Path
|
|
7
7
|
from shapely.geometry import Point
|
8
8
|
from typing import List, Dict, Any, Optional, Union
|
9
9
|
from .HdfUtils import HdfUtils
|
10
|
+
from .HdfBase import HdfBase
|
10
11
|
from .Decorators import standardize_input, log_call
|
11
12
|
from .LoggingConfig import get_logger
|
12
13
|
|
@@ -46,9 +47,14 @@ class HdfPump:
|
|
46
47
|
gdf = gpd.GeoDataFrame(geometry=geometries)
|
47
48
|
gdf['station_id'] = range(len(gdf))
|
48
49
|
|
49
|
-
# Add attributes
|
50
|
-
|
51
|
-
|
50
|
+
# Add attributes and decode byte strings
|
51
|
+
attr_df = pd.DataFrame(attributes)
|
52
|
+
string_columns = attr_df.select_dtypes([object]).columns
|
53
|
+
for col in string_columns:
|
54
|
+
attr_df[col] = attr_df[col].apply(lambda x: x.decode('utf-8') if isinstance(x, bytes) else x)
|
55
|
+
|
56
|
+
for col in attr_df.columns:
|
57
|
+
gdf[col] = attr_df[col]
|
52
58
|
|
53
59
|
# Set CRS if available
|
54
60
|
crs = HdfUtils.projection(hdf_path)
|
@@ -87,8 +93,11 @@ class HdfPump:
|
|
87
93
|
efficiency_curves_info = hdf['/Geometry/Pump Stations/Pump Groups/Efficiency Curves Info'][()]
|
88
94
|
efficiency_curves_values = hdf['/Geometry/Pump Stations/Pump Groups/Efficiency Curves Values'][()]
|
89
95
|
|
90
|
-
# Create DataFrame
|
96
|
+
# Create DataFrame and decode byte strings
|
91
97
|
df = pd.DataFrame(attributes)
|
98
|
+
string_columns = df.select_dtypes([object]).columns
|
99
|
+
for col in string_columns:
|
100
|
+
df[col] = df[col].apply(lambda x: x.decode('utf-8') if isinstance(x, bytes) else x)
|
92
101
|
|
93
102
|
# Add efficiency curve data
|
94
103
|
df['efficiency_curve_start'] = efficiency_curves_info[:, 0]
|
@@ -149,8 +158,9 @@ class HdfPump:
|
|
149
158
|
name=pump_station
|
150
159
|
)
|
151
160
|
|
152
|
-
# Add attributes
|
153
|
-
|
161
|
+
# Add attributes and decode byte strings
|
162
|
+
units = hdf[data_path].attrs.get('Variable_Unit', b'')
|
163
|
+
da.attrs['units'] = units.decode('utf-8') if isinstance(units, bytes) else units
|
154
164
|
da.attrs['pump_station'] = pump_station
|
155
165
|
|
156
166
|
return da
|
@@ -191,11 +201,11 @@ class HdfPump:
|
|
191
201
|
|
192
202
|
summary_data = hdf[summary_path][()]
|
193
203
|
|
194
|
-
# Create DataFrame
|
204
|
+
# Create DataFrame and decode byte strings
|
195
205
|
df = pd.DataFrame(summary_data)
|
196
|
-
|
197
|
-
|
198
|
-
|
206
|
+
string_columns = df.select_dtypes([object]).columns
|
207
|
+
for col in string_columns:
|
208
|
+
df[col] = df[col].apply(lambda x: x.decode('utf-8') if isinstance(x, bytes) else x)
|
199
209
|
|
200
210
|
return df
|
201
211
|
|
@@ -238,8 +248,12 @@ class HdfPump:
|
|
238
248
|
# Extract time information
|
239
249
|
time = HdfBase._get_unsteady_datetimes(hdf)
|
240
250
|
|
241
|
-
# Create DataFrame
|
251
|
+
# Create DataFrame and decode byte strings
|
242
252
|
df = pd.DataFrame(data, columns=['Flow', 'Stage HW', 'Stage TW', 'Pump Station', 'Pumps on'])
|
253
|
+
string_columns = df.select_dtypes([object]).columns
|
254
|
+
for col in string_columns:
|
255
|
+
df[col] = df[col].apply(lambda x: x.decode('utf-8') if isinstance(x, bytes) else x)
|
256
|
+
|
243
257
|
df['Time'] = time
|
244
258
|
|
245
259
|
return df
|
ras_commander/HdfResultsMesh.py
CHANGED
@@ -14,7 +14,7 @@ import xarray as xr
|
|
14
14
|
from pathlib import Path
|
15
15
|
import h5py
|
16
16
|
from typing import Union, List, Optional, Dict, Any, Tuple
|
17
|
-
|
17
|
+
from .HdfMesh import HdfMesh
|
18
18
|
from .HdfBase import HdfBase
|
19
19
|
from .HdfUtils import HdfUtils
|
20
20
|
from .Decorators import log_call, standardize_input
|
@@ -233,7 +233,7 @@ class HdfResultsMesh:
|
|
233
233
|
@standardize_input(file_type='plan_hdf')
|
234
234
|
def mesh_max_face_v(hdf_path: Path, round_to: str = "100ms") -> pd.DataFrame:
|
235
235
|
"""
|
236
|
-
Get maximum face velocity for each mesh
|
236
|
+
Get maximum face velocity for each mesh face.
|
237
237
|
|
238
238
|
Args:
|
239
239
|
hdf_path (Path): Path to the HDF file.
|
@@ -363,27 +363,22 @@ class HdfResultsMesh:
|
|
363
363
|
Raises:
|
364
364
|
ValueError: If there's an error processing the timeseries output data.
|
365
365
|
"""
|
366
|
-
|
367
|
-
"
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
"
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
"Groundwater Flow",
|
383
|
-
"Groundwater Velocity",
|
384
|
-
"Groundwater Velocity X",
|
385
|
-
"Groundwater Velocity Y",
|
386
|
-
]
|
366
|
+
TIME_SERIES_OUTPUT_VARS = {
|
367
|
+
"cell": [
|
368
|
+
"Water Surface", "Depth", "Velocity", "Velocity X", "Velocity Y",
|
369
|
+
"Froude Number", "Courant Number", "Shear Stress", "Bed Elevation",
|
370
|
+
"Precipitation Rate", "Infiltration Rate", "Evaporation Rate",
|
371
|
+
"Percolation Rate", "Groundwater Elevation", "Groundwater Depth",
|
372
|
+
"Groundwater Flow", "Groundwater Velocity", "Groundwater Velocity X",
|
373
|
+
"Groundwater Velocity Y"
|
374
|
+
],
|
375
|
+
"face": [
|
376
|
+
"Face Velocity", "Face Flow", "Face Water Surface", "Face Courant",
|
377
|
+
"Face Cumulative Volume", "Face Eddy Viscosity", "Face Flow Period Average",
|
378
|
+
"Face Friction Term", "Face Pressure Gradient Term", "Face Shear Stress",
|
379
|
+
"Face Tangential Velocity"
|
380
|
+
]
|
381
|
+
}
|
387
382
|
|
388
383
|
try:
|
389
384
|
start_time = HdfBase._get_simulation_start_time(hdf_file)
|
@@ -397,7 +392,7 @@ class HdfResultsMesh:
|
|
397
392
|
if var:
|
398
393
|
variables = [var]
|
399
394
|
else:
|
400
|
-
variables =
|
395
|
+
variables = TIME_SERIES_OUTPUT_VARS["cell"] + TIME_SERIES_OUTPUT_VARS["face"]
|
401
396
|
|
402
397
|
datasets = {}
|
403
398
|
for mesh_name in mesh_names:
|
@@ -417,13 +412,16 @@ class HdfResultsMesh:
|
|
417
412
|
truncated_time_stamps = time_stamps
|
418
413
|
|
419
414
|
if values.shape[0] != len(truncated_time_stamps):
|
420
|
-
logger.warning(f"Mismatch between
|
415
|
+
logger.warning(f"Mismatch between time steps ({len(truncated_time_stamps)}) and data shape ({values.shape}) for variable {variable}")
|
421
416
|
continue
|
422
417
|
|
418
|
+
# Determine if this is a face-based or cell-based variable
|
419
|
+
id_dim = "face_id" if any(face_var in variable for face_var in TIME_SERIES_OUTPUT_VARS["face"]) else "cell_id"
|
420
|
+
|
423
421
|
data_vars[variable] = xr.DataArray(
|
424
422
|
data=values,
|
425
|
-
dims=['time',
|
426
|
-
coords={'time': truncated_time_stamps,
|
423
|
+
dims=['time', id_dim],
|
424
|
+
coords={'time': truncated_time_stamps, id_dim: np.arange(values.shape[1])},
|
427
425
|
attrs={'units': units}
|
428
426
|
)
|
429
427
|
except KeyError:
|
@@ -454,16 +452,7 @@ class HdfResultsMesh:
|
|
454
452
|
Args:
|
455
453
|
hdf_file (h5py.File): Open HDF file object.
|
456
454
|
mesh_name (str): Name of the mesh.
|
457
|
-
var (str): Variable name to retrieve.
|
458
|
-
"Water Surface", "Face Velocity", "Cell Velocity X", "Cell Velocity Y",
|
459
|
-
"Face Flow", "Face Water Surface", "Cell Volume", "Cell Volume Error",
|
460
|
-
"Cell Water Surface Error", "Cell Courant", "Face Courant",
|
461
|
-
"Cell Hydraulic Depth", "Cell Invert Depth",
|
462
|
-
"Cell Cumulative Precipitation Depth", "Cell Divergence Term",
|
463
|
-
"Cell Eddy Viscosity X", "Cell Eddy Viscosity Y", "Cell Flow Balance",
|
464
|
-
"Cell Storage Term", "Cell Water Source Term", "Face Cumulative Volume",
|
465
|
-
"Face Eddy Viscosity", "Face Flow Period Average", "Face Friction Term",
|
466
|
-
"Face Pressure Gradient Term", "Face Shear Stress", "Face Tangential Velocity"
|
455
|
+
var (str): Variable name to retrieve.
|
467
456
|
truncate (bool): Whether to truncate the output to remove trailing zeros (default True).
|
468
457
|
|
469
458
|
Returns:
|
@@ -478,7 +467,6 @@ class HdfResultsMesh:
|
|
478
467
|
if path not in hdf_file:
|
479
468
|
raise ValueError(f"Path {path} not found in HDF file")
|
480
469
|
|
481
|
-
# Use h5py to get the dataset
|
482
470
|
dataset = hdf_file[path]
|
483
471
|
values = dataset[:]
|
484
472
|
units = dataset.attrs.get("Units", "").decode("utf-8")
|
@@ -491,11 +479,12 @@ class HdfResultsMesh:
|
|
491
479
|
values = values[start:end]
|
492
480
|
times = times[start:end]
|
493
481
|
|
494
|
-
#
|
495
|
-
|
482
|
+
# Determine if this is a face-based or cell-based variable
|
483
|
+
id_dim = "face_id" if "Face" in var else "cell_id"
|
484
|
+
dims = ["time", id_dim] if values.ndim == 2 else ["time"]
|
496
485
|
coords = {"time": times}
|
497
486
|
if values.ndim == 2:
|
498
|
-
coords[
|
487
|
+
coords[id_dim] = np.arange(values.shape[1])
|
499
488
|
|
500
489
|
return xr.DataArray(
|
501
490
|
values,
|
@@ -548,26 +537,30 @@ class HdfResultsMesh:
|
|
548
537
|
if isinstance(hdf_file[f"{base_path}/{name}"], h5py.Group):
|
549
538
|
mesh_names.append(name)
|
550
539
|
return mesh_names
|
551
|
-
|
540
|
+
|
552
541
|
@staticmethod
|
553
542
|
def _get_mesh_summary_output(hdf_file: h5py.File, var: str, round_to: str = "100ms") -> pd.DataFrame:
|
554
543
|
"""
|
555
544
|
Get the summary output data for a given variable from the HDF file.
|
556
545
|
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
Returns
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
546
|
+
Parameters
|
547
|
+
----------
|
548
|
+
hdf_file : h5py.File
|
549
|
+
Open HDF file object.
|
550
|
+
var : str
|
551
|
+
The summary output variable to retrieve.
|
552
|
+
round_to : str, optional
|
553
|
+
The time unit to round the datetimes to. Default is "100ms".
|
554
|
+
|
555
|
+
Returns
|
556
|
+
-------
|
557
|
+
pd.DataFrame
|
558
|
+
A DataFrame containing the summary output data with attributes as metadata.
|
559
|
+
|
560
|
+
Raises
|
561
|
+
------
|
562
|
+
ValueError
|
563
|
+
If the HDF file cannot be opened or read, or if the requested data is not found.
|
571
564
|
"""
|
572
565
|
try:
|
573
566
|
dfs = []
|
@@ -584,28 +577,44 @@ class HdfResultsMesh:
|
|
584
577
|
logger.debug(f"Attributes: {dict(group.attrs)}")
|
585
578
|
|
586
579
|
if data.ndim == 2 and data.shape[0] == 2:
|
587
|
-
#
|
580
|
+
# Handle 2D datasets (e.g. Maximum Water Surface)
|
588
581
|
row_variables = group.attrs.get('Row Variables', [b'Value', b'Time'])
|
589
582
|
row_variables = [v.decode('utf-8').strip() for v in row_variables]
|
590
583
|
|
591
584
|
df = pd.DataFrame({
|
592
585
|
"mesh_name": [mesh_name] * data.shape[1],
|
593
|
-
"cell_id": range(data.shape[1]),
|
586
|
+
"cell_id" if "Face" not in var else "face_id": range(data.shape[1]),
|
594
587
|
f"{var.lower().replace(' ', '_')}": data[0, :],
|
595
588
|
f"{var.lower().replace(' ', '_')}_time": HdfUtils._ras_timesteps_to_datetimes(
|
596
589
|
data[1, :], start_time, time_unit="days", round_to=round_to
|
597
590
|
)
|
598
591
|
})
|
592
|
+
|
599
593
|
elif data.ndim == 1:
|
600
|
-
# Handle 1D datasets (
|
594
|
+
# Handle 1D datasets (e.g. Cell Last Iteration)
|
601
595
|
df = pd.DataFrame({
|
602
596
|
"mesh_name": [mesh_name] * len(data),
|
603
|
-
"cell_id": range(len(data)),
|
597
|
+
"cell_id" if "Face" not in var else "face_id": range(len(data)),
|
604
598
|
var.lower().replace(' ', '_'): data
|
605
599
|
})
|
600
|
+
|
606
601
|
else:
|
607
602
|
raise ValueError(f"Unexpected data shape for {var} in {mesh_name}. "
|
608
|
-
|
603
|
+
f"Got shape {data.shape}")
|
604
|
+
|
605
|
+
# Add geometry based on variable type
|
606
|
+
if "Face" in var:
|
607
|
+
face_df = HdfMesh.mesh_cell_faces(hdf_file)
|
608
|
+
if not face_df.empty:
|
609
|
+
df = df.merge(face_df[['mesh_name', 'face_id', 'geometry']],
|
610
|
+
on=['mesh_name', 'face_id'],
|
611
|
+
how='left')
|
612
|
+
else:
|
613
|
+
cell_df = HdfMesh.mesh_cell_points(hdf_file)
|
614
|
+
if not cell_df.empty:
|
615
|
+
df = df.merge(cell_df[['mesh_name', 'cell_id', 'geometry']],
|
616
|
+
on=['mesh_name', 'cell_id'],
|
617
|
+
how='left')
|
609
618
|
|
610
619
|
# Add group attributes as metadata
|
611
620
|
df.attrs['mesh_name'] = mesh_name
|
@@ -618,6 +627,9 @@ class HdfResultsMesh:
|
|
618
627
|
|
619
628
|
dfs.append(df)
|
620
629
|
|
630
|
+
if not dfs:
|
631
|
+
return pd.DataFrame()
|
632
|
+
|
621
633
|
result = pd.concat(dfs, ignore_index=True)
|
622
634
|
|
623
635
|
# Combine attributes from all meshes
|
@@ -634,7 +646,7 @@ class HdfResultsMesh:
|
|
634
646
|
logger.info(f"Processed {len(result)} rows of summary output data")
|
635
647
|
return result
|
636
648
|
|
637
|
-
except
|
649
|
+
except Exception as e:
|
638
650
|
logger.error(f"Error processing summary output data: {e}")
|
639
651
|
raise ValueError(f"Error processing summary output data: {e}")
|
640
652
|
|
@@ -660,3 +672,64 @@ class HdfResultsMesh:
|
|
660
672
|
if output_item is None:
|
661
673
|
raise ValueError(f"Could not find HDF group or dataset at path '{output_path}'")
|
662
674
|
return output_item
|
675
|
+
|
676
|
+
@staticmethod
|
677
|
+
def plot_mesh_variable(variable_df: pd.DataFrame, variable_name: str, colormap: str = 'viridis', point_size: int = 10) -> None:
|
678
|
+
"""
|
679
|
+
Plot any mesh variable with consistent styling.
|
680
|
+
|
681
|
+
Args:
|
682
|
+
variable_df (pd.DataFrame): DataFrame containing the variable data
|
683
|
+
variable_name (str): Name of the variable (for labels)
|
684
|
+
colormap (str): Matplotlib colormap to use. Default: 'viridis'
|
685
|
+
point_size (int): Size of the scatter points. Default: 10
|
686
|
+
|
687
|
+
Returns:
|
688
|
+
None
|
689
|
+
|
690
|
+
Raises:
|
691
|
+
ImportError: If matplotlib is not installed
|
692
|
+
ValueError: If required columns are missing from variable_df
|
693
|
+
"""
|
694
|
+
try:
|
695
|
+
import matplotlib.pyplot as plt
|
696
|
+
except ImportError:
|
697
|
+
logger.error("matplotlib is required for plotting. Please install it with 'pip install matplotlib'")
|
698
|
+
raise ImportError("matplotlib is required for plotting")
|
699
|
+
|
700
|
+
# Get cell coordinates if not in variable_df
|
701
|
+
if 'geometry' not in variable_df.columns:
|
702
|
+
cell_coords = HdfMesh.mesh_cell_points(plan_hdf_path)
|
703
|
+
merged_df = pd.merge(variable_df, cell_coords, on=['mesh_name', 'cell_id'])
|
704
|
+
else:
|
705
|
+
merged_df = variable_df
|
706
|
+
|
707
|
+
# Extract coordinates, handling None values
|
708
|
+
merged_df = merged_df.dropna(subset=['geometry'])
|
709
|
+
merged_df['x'] = merged_df['geometry'].apply(lambda geom: geom.x if geom is not None else None)
|
710
|
+
merged_df['y'] = merged_df['geometry'].apply(lambda geom: geom.y if geom is not None else None)
|
711
|
+
|
712
|
+
# Drop any rows with None coordinates
|
713
|
+
merged_df = merged_df.dropna(subset=['x', 'y'])
|
714
|
+
|
715
|
+
if len(merged_df) == 0:
|
716
|
+
logger.error("No valid coordinates found for plotting")
|
717
|
+
raise ValueError("No valid coordinates found for plotting")
|
718
|
+
|
719
|
+
# Create plot
|
720
|
+
fig, ax = plt.subplots(figsize=(12, 8))
|
721
|
+
scatter = ax.scatter(merged_df['x'], merged_df['y'],
|
722
|
+
c=merged_df[variable_name],
|
723
|
+
cmap=colormap,
|
724
|
+
s=point_size)
|
725
|
+
|
726
|
+
# Customize plot
|
727
|
+
ax.set_title(f'{variable_name} per Cell')
|
728
|
+
ax.set_xlabel('X Coordinate')
|
729
|
+
ax.set_ylabel('Y Coordinate')
|
730
|
+
plt.colorbar(scatter, label=variable_name)
|
731
|
+
ax.grid(True, linestyle='--', alpha=0.7)
|
732
|
+
plt.rcParams.update({'font.size': 12})
|
733
|
+
plt.tight_layout()
|
734
|
+
plt.show()
|
735
|
+
|