roms-tools 2.5.0__py3-none-any.whl → 2.6.1__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.
- ci/environment-with-xesmf.yml +16 -0
- roms_tools/analysis/roms_output.py +521 -187
- roms_tools/analysis/utils.py +169 -0
- roms_tools/plot.py +351 -214
- roms_tools/regrid.py +161 -9
- roms_tools/setup/boundary_forcing.py +22 -22
- roms_tools/setup/datasets.py +40 -44
- roms_tools/setup/grid.py +28 -28
- roms_tools/setup/initial_conditions.py +23 -31
- roms_tools/setup/nesting.py +3 -3
- roms_tools/setup/river_forcing.py +22 -23
- roms_tools/setup/surface_forcing.py +14 -13
- roms_tools/setup/tides.py +7 -7
- roms_tools/setup/topography.py +2 -2
- roms_tools/tests/test_analysis/test_roms_output.py +299 -188
- roms_tools/tests/test_regrid.py +85 -2
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/.zmetadata +2 -2
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/.zmetadata +2 -2
- roms_tools/tests/test_setup/test_river_forcing.py +47 -51
- roms_tools/tests/test_vertical_coordinate.py +73 -0
- roms_tools/utils.py +11 -7
- roms_tools/vertical_coordinate.py +7 -0
- {roms_tools-2.5.0.dist-info → roms_tools-2.6.1.dist-info}/METADATA +22 -11
- {roms_tools-2.5.0.dist-info → roms_tools-2.6.1.dist-info}/RECORD +33 -30
- {roms_tools-2.5.0.dist-info → roms_tools-2.6.1.dist-info}/WHEEL +1 -1
- /roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/{river_location → river_flux}/.zarray +0 -0
- /roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/{river_location → river_flux}/.zattrs +0 -0
- /roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/{river_location → river_flux}/0.0 +0 -0
- /roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/{river_location → river_flux}/.zarray +0 -0
- /roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/{river_location → river_flux}/.zattrs +0 -0
- /roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/{river_location → river_flux}/0.0 +0 -0
- {roms_tools-2.5.0.dist-info → roms_tools-2.6.1.dist-info/licenses}/LICENSE +0 -0
- {roms_tools-2.5.0.dist-info → roms_tools-2.6.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _validate_plot_inputs(field, s, eta, xi, depth, lat, lon, include_boundary):
|
|
5
|
+
"""Validate input parameters for the plot method.
|
|
6
|
+
|
|
7
|
+
Parameters
|
|
8
|
+
----------
|
|
9
|
+
field : xr.DataArray
|
|
10
|
+
Input data to be plotted.
|
|
11
|
+
s : int, float, or None
|
|
12
|
+
Depth level index or value for the s-coordinate. Use None for surface plotting.
|
|
13
|
+
eta : int or None
|
|
14
|
+
Eta index for ROMS grid selection. Must be within bounds.
|
|
15
|
+
xi : int or None
|
|
16
|
+
Xi index for ROMS grid selection. Must be within bounds.
|
|
17
|
+
depth : int, float, or None
|
|
18
|
+
Depth value for slicing. Not yet implemented.
|
|
19
|
+
lat : float or None
|
|
20
|
+
Latitude value for slicing. Must be specified with `lon` if provided.
|
|
21
|
+
lon : float or None
|
|
22
|
+
Longitude value for slicing. Must be specified with `lat` if provided.
|
|
23
|
+
include_boundary : bool
|
|
24
|
+
Whether to include boundary points when selecting grid indices.
|
|
25
|
+
|
|
26
|
+
Raises
|
|
27
|
+
------
|
|
28
|
+
ValueError
|
|
29
|
+
If conflicting dimensions are specified.
|
|
30
|
+
If eta or xi indices are out of bounds.
|
|
31
|
+
If eta or xi lie on the boundary when `include_boundary=False`.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
# Check conflicting dimension choices
|
|
35
|
+
if s is not None and depth is not None:
|
|
36
|
+
raise ValueError(
|
|
37
|
+
"Conflicting input: You cannot specify both 's' and 'depth' at the same time."
|
|
38
|
+
)
|
|
39
|
+
if any([eta is not None, xi is not None]) and any(
|
|
40
|
+
[lat is not None, lon is not None]
|
|
41
|
+
):
|
|
42
|
+
raise ValueError(
|
|
43
|
+
"Conflicting input: You cannot specify 'lat' or 'lon' simultaneously with 'eta' or 'xi'."
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# 3D fields: Check for valid dimension specification
|
|
47
|
+
if len(field.dims) == 3:
|
|
48
|
+
if not any(
|
|
49
|
+
[
|
|
50
|
+
s is not None,
|
|
51
|
+
eta is not None,
|
|
52
|
+
xi is not None,
|
|
53
|
+
depth is not None,
|
|
54
|
+
lat is not None,
|
|
55
|
+
lon is not None,
|
|
56
|
+
]
|
|
57
|
+
):
|
|
58
|
+
raise ValueError(
|
|
59
|
+
"Invalid input: For 3D fields, you must specify at least one of the dimensions 's', 'eta', 'xi', 'depth', 'lat', or 'lon'."
|
|
60
|
+
)
|
|
61
|
+
if sum([dim is not None for dim in [s, eta, xi, depth, lat, lon]]) > 2:
|
|
62
|
+
raise ValueError(
|
|
63
|
+
"Ambiguous input: For 3D fields, specify at most two of 's', 'eta', 'xi', 'depth', 'lat', or 'lon'. Specifying more than two is not allowed."
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# 2D fields: Check for conflicts in dimension choices
|
|
67
|
+
if len(field.dims) == 2:
|
|
68
|
+
if s is not None:
|
|
69
|
+
raise ValueError("Vertical dimension 's' should be None for 2D fields.")
|
|
70
|
+
if depth is not None:
|
|
71
|
+
raise ValueError("Vertical dimension 'depth' should be None for 2D fields.")
|
|
72
|
+
if all([eta is not None, xi is not None]):
|
|
73
|
+
raise ValueError(
|
|
74
|
+
"Conflicting input: For 2D fields, specify only one dimension, either 'eta' or 'xi', not both."
|
|
75
|
+
)
|
|
76
|
+
if all([lat is not None, lon is not None]):
|
|
77
|
+
raise ValueError(
|
|
78
|
+
"Conflicting input: For 2D fields, specify only one dimension, either 'lat' or 'lon', not both."
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Check that indices are within bounds
|
|
82
|
+
if eta is not None:
|
|
83
|
+
dim = "eta_rho" if "eta_rho" in field.dims else "eta_v"
|
|
84
|
+
if not eta < len(field[dim]):
|
|
85
|
+
raise ValueError(
|
|
86
|
+
f"Invalid eta index: {eta} is out of bounds. Must be between 0 and {len(field[dim]) - 1}."
|
|
87
|
+
)
|
|
88
|
+
if not include_boundary:
|
|
89
|
+
if eta == 0 or eta == len(field[dim]) - 1:
|
|
90
|
+
raise ValueError(
|
|
91
|
+
f"Invalid eta index: {eta} lies on the boundary, which is excluded when `include_boundary = False`. "
|
|
92
|
+
"Either set `include_boundary = True`, or adjust eta to avoid boundary values."
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if xi is not None:
|
|
96
|
+
dim = "xi_rho" if "xi_rho" in field.dims else "xi_u"
|
|
97
|
+
if not xi < len(field[dim]):
|
|
98
|
+
raise ValueError(
|
|
99
|
+
f"Invalid eta index: {xi} is out of bounds. Must be between 0 and {len(field[dim]) - 1}."
|
|
100
|
+
)
|
|
101
|
+
if not include_boundary:
|
|
102
|
+
if xi == 0 or xi == len(field[dim]) - 1:
|
|
103
|
+
raise ValueError(
|
|
104
|
+
f"Invalid xi index: {xi} lies on the boundary, which is excluded when `include_boundary = False`. "
|
|
105
|
+
"Either set `include_boundary = True`, or adjust eta to avoid boundary values."
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _generate_coordinate_range(min, max, resolution):
|
|
110
|
+
"""Generate an array of target coordinates (e.g., latitude or longitude) within a
|
|
111
|
+
specified range, with a resolution that is rounded to the nearest value of the form
|
|
112
|
+
`1/n` (or integer).
|
|
113
|
+
|
|
114
|
+
This method generates an array of target coordinates between the provided `min` and `max`
|
|
115
|
+
values, ensuring that both `min` and `max` are included in the resulting range. The resolution
|
|
116
|
+
is rounded to the nearest fraction of the form `1/n` or an integer, based on the input.
|
|
117
|
+
|
|
118
|
+
Parameters
|
|
119
|
+
----------
|
|
120
|
+
min : float
|
|
121
|
+
The minimum value (in degrees) of the coordinate range (inclusive).
|
|
122
|
+
|
|
123
|
+
max : float
|
|
124
|
+
The maximum value (in degrees) of the coordinate range (inclusive).
|
|
125
|
+
|
|
126
|
+
resolution : float
|
|
127
|
+
The spacing (in degrees) between each coordinate in the array. The resolution will
|
|
128
|
+
be rounded to the nearest value of the form `1/n` or an integer, depending on the size
|
|
129
|
+
of the resolution value.
|
|
130
|
+
|
|
131
|
+
Returns
|
|
132
|
+
-------
|
|
133
|
+
numpy.ndarray
|
|
134
|
+
An array of target coordinates generated from the specified range, with the resolution
|
|
135
|
+
rounded to a suitable fraction (e.g., `1/n`) or integer, depending on the input resolution.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
# Find the closest fraction of the form 1/n or integer to match the resolution
|
|
139
|
+
resolution_rounded = None
|
|
140
|
+
min_diff = float("inf") # Initialize the minimum difference as infinity
|
|
141
|
+
|
|
142
|
+
# Search for the best fraction or integer approximation to the resolution
|
|
143
|
+
for n in range(1, 1000): # Try fractions 1/n, where n ranges from 1 to 999
|
|
144
|
+
if resolution <= 1:
|
|
145
|
+
fraction = (
|
|
146
|
+
1 / n
|
|
147
|
+
) # For small resolutions (<= 1), consider fractions of the form 1/n
|
|
148
|
+
else:
|
|
149
|
+
fraction = n # For larger resolutions (>1), consider integers (n)
|
|
150
|
+
|
|
151
|
+
diff = abs(
|
|
152
|
+
fraction - resolution
|
|
153
|
+
) # Calculate the difference between the fraction and the resolution
|
|
154
|
+
|
|
155
|
+
if diff < min_diff: # If the current fraction is a better approximation
|
|
156
|
+
min_diff = diff
|
|
157
|
+
resolution_rounded = fraction # Update the best fraction (or integer) found
|
|
158
|
+
|
|
159
|
+
# Adjust the start and end of the range to include integer values
|
|
160
|
+
start_int = np.floor(min) # Round the minimum value down to the nearest integer
|
|
161
|
+
end_int = np.ceil(max) # Round the maximum value up to the nearest integer
|
|
162
|
+
|
|
163
|
+
# Generate the array of target coordinates, including both the min and max values
|
|
164
|
+
target = np.arange(start_int, end_int + resolution_rounded, resolution_rounded)
|
|
165
|
+
|
|
166
|
+
# Truncate any values that exceed max (including small floating point errors)
|
|
167
|
+
target = target[target <= end_int + 1e-10]
|
|
168
|
+
|
|
169
|
+
return target.astype(np.float32)
|