roms-tools 0.0.2__py3-none-any.whl → 0.1.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.
- ci/environment.yml +28 -0
- roms_tools/__init__.py +3 -0
- roms_tools/_version.py +1 -1
- roms_tools/setup/atmospheric_forcing.py +993 -0
- roms_tools/setup/datasets.py +48 -0
- roms_tools/setup/fill.py +263 -0
- roms_tools/setup/grid.py +483 -324
- roms_tools/setup/plot.py +58 -0
- roms_tools/setup/tides.py +676 -0
- roms_tools/setup/topography.py +242 -0
- roms_tools/tests/test_setup.py +145 -18
- roms_tools-0.1.0.dist-info/METADATA +89 -0
- roms_tools-0.1.0.dist-info/RECORD +17 -0
- {roms_tools-0.0.2.dist-info → roms_tools-0.1.0.dist-info}/WHEEL +1 -1
- {roms_tools-0.0.2.dist-info → roms_tools-0.1.0.dist-info}/top_level.txt +2 -0
- roms_tools/setup/old_grid_script.py +0 -438
- roms_tools-0.0.2.dist-info/METADATA +0 -134
- roms_tools-0.0.2.dist-info/RECORD +0 -11
- {roms_tools-0.0.2.dist-info → roms_tools-0.1.0.dist-info}/LICENSE +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import pooch
|
|
2
|
+
import xarray as xr
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
FRANK = pooch.create(
|
|
6
|
+
# Use the default cache folder for the operating system
|
|
7
|
+
path=pooch.os_cache("roms-tools"),
|
|
8
|
+
base_url="https://github.com/CWorthy-ocean/roms-tools-data/raw/main/",
|
|
9
|
+
# If this is a development version, get the data from the "main" branch
|
|
10
|
+
# The registry specifies the files that can be fetched
|
|
11
|
+
registry={
|
|
12
|
+
"etopo5.nc": "sha256:23600e422d59bbf7c3666090166a0d468c8ee16092f4f14e32c4e928fbcd627b",
|
|
13
|
+
},
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def fetch_topo(topography_source) -> xr.Dataset:
|
|
18
|
+
"""
|
|
19
|
+
Load the global topography data as an xarray Dataset.
|
|
20
|
+
"""
|
|
21
|
+
# Mapping from user-specified topography options to corresponding filenames in the registry
|
|
22
|
+
topo_dict = {"etopo5": "etopo5.nc"}
|
|
23
|
+
|
|
24
|
+
# The file will be downloaded automatically the first time this is run
|
|
25
|
+
# returns the file path to the downloaded file. Afterwards, Pooch finds
|
|
26
|
+
# it in the local cache and doesn't repeat the download.
|
|
27
|
+
fname = FRANK.fetch(topo_dict[topography_source])
|
|
28
|
+
# The "fetch" method returns the full path to the downloaded data file.
|
|
29
|
+
# All we need to do now is load it with our standard Python tools.
|
|
30
|
+
ds = xr.open_dataset(fname)
|
|
31
|
+
return ds
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def fetch_ssr_correction(correction_source) -> xr.Dataset:
|
|
35
|
+
"""
|
|
36
|
+
Load the SSR correction data as an xarray Dataset.
|
|
37
|
+
"""
|
|
38
|
+
# Mapping from user-specified topography options to corresponding filenames in the registry
|
|
39
|
+
topo_dict = {"corev2": "SSR_correction.nc"}
|
|
40
|
+
|
|
41
|
+
# The file will be downloaded automatically the first time this is run
|
|
42
|
+
# returns the file path to the downloaded file. Afterwards, Pooch finds
|
|
43
|
+
# it in the local cache and doesn't repeat the download.
|
|
44
|
+
fname = FRANK.fetch(topo_dict[correction_source])
|
|
45
|
+
# The "fetch" method returns the full path to the downloaded data file.
|
|
46
|
+
# All we need to do now is load it with our standard Python tools.
|
|
47
|
+
ds = xr.open_dataset(fname)
|
|
48
|
+
return ds
|
roms_tools/setup/fill.py
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import xarray as xr
|
|
3
|
+
from numba import jit
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def lateral_fill(var, land_mask, dims=["latitude", "longitude"]):
|
|
7
|
+
"""
|
|
8
|
+
Perform lateral fill on an xarray DataArray using a land mask.
|
|
9
|
+
|
|
10
|
+
Parameters
|
|
11
|
+
----------
|
|
12
|
+
var : xarray.DataArray
|
|
13
|
+
DataArray on which to fill NaNs. The fill is performed on the dimensions specified
|
|
14
|
+
in `dims`.
|
|
15
|
+
|
|
16
|
+
land_mask : xarray.DataArray
|
|
17
|
+
Boolean DataArray indicating valid values: `True` where data should be filled. Must have the
|
|
18
|
+
same shape as `var` for the specified dimensions.
|
|
19
|
+
|
|
20
|
+
dims : list of str, optional, default=['latitude', 'longitude']
|
|
21
|
+
Dimensions along which to perform the fill. The default is ['latitude', 'longitude'].
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
var_filled : xarray.DataArray
|
|
26
|
+
DataArray with NaNs filled by iterative smoothing, except for the regions
|
|
27
|
+
specified by `land_mask` where NaNs are preserved.
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
var_filled = xr.apply_ufunc(
|
|
31
|
+
_lateral_fill_np_array,
|
|
32
|
+
var,
|
|
33
|
+
land_mask,
|
|
34
|
+
input_core_dims=[dims, dims],
|
|
35
|
+
output_core_dims=[dims],
|
|
36
|
+
dask="parallelized",
|
|
37
|
+
output_dtypes=[var.dtype],
|
|
38
|
+
vectorize=True,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
return var_filled
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _lateral_fill_np_array(var, isvalid_mask, tol=1.0e-4, rc=1.8, max_iter=10000):
|
|
45
|
+
"""
|
|
46
|
+
Perform lateral fill on a numpy array.
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
var : numpy.array
|
|
51
|
+
Two-dimensional array on which to fill NaNs.Only NaNs where `isvalid_mask` is
|
|
52
|
+
True will be filled.
|
|
53
|
+
|
|
54
|
+
isvalid_mask : numpy.array, boolean
|
|
55
|
+
Valid values mask: `True` where data should be filled. Must have same shape
|
|
56
|
+
as `var`.
|
|
57
|
+
|
|
58
|
+
tol : float, optional, default=1.0e-4
|
|
59
|
+
Convergence criteria: stop filling when the value change is less than
|
|
60
|
+
or equal to `tol * var`, i.e., `delta <= tol * np.abs(var[j, i])`.
|
|
61
|
+
|
|
62
|
+
rc : float, optional, default=1.8
|
|
63
|
+
Over-relaxation coefficient to use in the Successive Over-Relaxation (SOR)
|
|
64
|
+
fill algorithm. Larger arrays (or extent of region to be filled if not global)
|
|
65
|
+
typically converge faster with larger coefficients. For completely
|
|
66
|
+
land-filling a 1-degree grid (360x180), a coefficient in the range 1.85-1.9
|
|
67
|
+
is near optimal. Valid bounds are (1.0, 2.0).
|
|
68
|
+
|
|
69
|
+
max_iter : int, optional, default=10000
|
|
70
|
+
Maximum number of iterations to perform before giving up if the tolerance
|
|
71
|
+
is not reached.
|
|
72
|
+
|
|
73
|
+
Returns
|
|
74
|
+
-------
|
|
75
|
+
var : numpy.array
|
|
76
|
+
Array with NaNs filled by iterative smoothing, except for the regions
|
|
77
|
+
specified by `isvalid_mask` where NaNs are preserved.
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
Example
|
|
81
|
+
-------
|
|
82
|
+
>>> import numpy as np
|
|
83
|
+
>>> var = np.array([[1, 2, np.nan], [4, np.nan, 6]])
|
|
84
|
+
>>> isvalid_mask = np.array([[True, True, True], [True, True, True]])
|
|
85
|
+
>>> filled_var = lateral_fill_np_array(var, isvalid_mask)
|
|
86
|
+
>>> print(filled_var)
|
|
87
|
+
"""
|
|
88
|
+
nlat, nlon = var.shape[-2:]
|
|
89
|
+
var = var.copy()
|
|
90
|
+
|
|
91
|
+
fillmask = np.isnan(var) # Fill all NaNs
|
|
92
|
+
keepNaNs = ~isvalid_mask & np.isnan(var)
|
|
93
|
+
var = _iterative_fill_sor(nlat, nlon, var, fillmask, tol, rc, max_iter)
|
|
94
|
+
var[keepNaNs] = np.nan # Replace NaNs in areas not designated for filling
|
|
95
|
+
|
|
96
|
+
return var
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@jit(nopython=True, parallel=True)
|
|
100
|
+
def _iterative_fill_sor(nlat, nlon, var, fillmask, tol, rc, max_iter):
|
|
101
|
+
"""
|
|
102
|
+
Perform an iterative land fill algorithm using the Successive Over-Relaxation (SOR)
|
|
103
|
+
solution of the Laplace Equation.
|
|
104
|
+
|
|
105
|
+
Parameters
|
|
106
|
+
----------
|
|
107
|
+
nlat : int
|
|
108
|
+
Number of latitude points in the array.
|
|
109
|
+
|
|
110
|
+
nlon : int
|
|
111
|
+
Number of longitude points in the array.
|
|
112
|
+
|
|
113
|
+
var : numpy.array
|
|
114
|
+
Two-dimensional array on which to fill NaNs.
|
|
115
|
+
|
|
116
|
+
fillmask : numpy.array, boolean
|
|
117
|
+
Mask indicating positions to be filled: `True` where data should be filled.
|
|
118
|
+
|
|
119
|
+
tol : float
|
|
120
|
+
Convergence criterion: the iterative process stops when the maximum residual change
|
|
121
|
+
is less than or equal to `tol`.
|
|
122
|
+
|
|
123
|
+
rc : float
|
|
124
|
+
Over-relaxation coefficient used in the SOR algorithm. Must be between 1.0 and 2.0.
|
|
125
|
+
|
|
126
|
+
max_iter : int
|
|
127
|
+
Maximum number of iterations allowed before the process is terminated.
|
|
128
|
+
|
|
129
|
+
Returns
|
|
130
|
+
-------
|
|
131
|
+
None
|
|
132
|
+
The input array `var` is modified in-place with the NaN values filled.
|
|
133
|
+
|
|
134
|
+
Notes
|
|
135
|
+
-----
|
|
136
|
+
This function performs the following steps:
|
|
137
|
+
1. Computes a zonal mean to use as an initial guess for the fill.
|
|
138
|
+
2. Replaces missing values in the input array with the computed zonal average.
|
|
139
|
+
3. Iteratively fills the missing values using the SOR algorithm until the specified
|
|
140
|
+
tolerance `tol` is reached or the maximum number of iterations `max_iter` is exceeded.
|
|
141
|
+
|
|
142
|
+
Example
|
|
143
|
+
-------
|
|
144
|
+
>>> nlat, nlon = 180, 360
|
|
145
|
+
>>> var = np.random.rand(nlat, nlon)
|
|
146
|
+
>>> fillmask = np.isnan(var)
|
|
147
|
+
>>> tol = 1.0e-4
|
|
148
|
+
>>> rc = 1.8
|
|
149
|
+
>>> max_iter = 10000
|
|
150
|
+
>>> _iterative_fill_sor(nlat, nlon, var, fillmask, tol, rc, max_iter)
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
# If field consists only of zeros, fill NaNs in with zeros and all done
|
|
154
|
+
# Note: this will happen for shortwave downward radiation at night time
|
|
155
|
+
if np.max(np.fabs(var)) == 0.0:
|
|
156
|
+
var = np.zeros_like(var)
|
|
157
|
+
return var
|
|
158
|
+
|
|
159
|
+
# Compute a zonal mean to use as a first guess
|
|
160
|
+
zoncnt = np.zeros(nlat)
|
|
161
|
+
zonavg = np.zeros(nlat)
|
|
162
|
+
for j in range(0, nlat):
|
|
163
|
+
zoncnt[j] = np.sum(np.where(fillmask[j, :], 0, 1))
|
|
164
|
+
zonavg[j] = np.sum(np.where(fillmask[j, :], 0, var[j, :]))
|
|
165
|
+
if zoncnt[j] != 0:
|
|
166
|
+
zonavg[j] = zonavg[j] / zoncnt[j]
|
|
167
|
+
|
|
168
|
+
# Fill missing zonal averages for rows that are entirely land
|
|
169
|
+
for j in range(0, nlat - 1): # northward pass
|
|
170
|
+
if zoncnt[j] > 0 and zoncnt[j + 1] == 0:
|
|
171
|
+
zoncnt[j + 1] = 1
|
|
172
|
+
zonavg[j + 1] = zonavg[j]
|
|
173
|
+
for j in range(nlat - 1, 0, -1): # southward pass
|
|
174
|
+
if zoncnt[j] > 0 and zoncnt[j - 1] == 0:
|
|
175
|
+
zoncnt[j - 1] = 1
|
|
176
|
+
zonavg[j - 1] = zonavg[j]
|
|
177
|
+
|
|
178
|
+
# Replace the input array missing values with zonal average as first guess
|
|
179
|
+
for j in range(0, nlat):
|
|
180
|
+
for i in range(0, nlon):
|
|
181
|
+
if fillmask[j, i]:
|
|
182
|
+
var[j, i] = zonavg[j]
|
|
183
|
+
|
|
184
|
+
# Now do the iterative 2D fill
|
|
185
|
+
res = np.zeros((nlat, nlon)) # work array hold residuals
|
|
186
|
+
res_max = tol
|
|
187
|
+
iter_cnt = 0
|
|
188
|
+
while iter_cnt < max_iter and res_max >= tol:
|
|
189
|
+
res[:] = 0.0 # reset the residual to zero for this iteration
|
|
190
|
+
|
|
191
|
+
for j in range(1, nlat - 1):
|
|
192
|
+
jm1 = j - 1
|
|
193
|
+
jp1 = j + 1
|
|
194
|
+
|
|
195
|
+
for i in range(1, nlon - 1):
|
|
196
|
+
if fillmask[j, i]:
|
|
197
|
+
im1 = i - 1
|
|
198
|
+
ip1 = i + 1
|
|
199
|
+
|
|
200
|
+
# this is SOR
|
|
201
|
+
res[j, i] = (
|
|
202
|
+
var[j, ip1]
|
|
203
|
+
+ var[j, im1]
|
|
204
|
+
+ var[jm1, i]
|
|
205
|
+
+ var[jp1, i]
|
|
206
|
+
- 4.0 * var[j, i]
|
|
207
|
+
)
|
|
208
|
+
var[j, i] = var[j, i] + rc * 0.25 * res[j, i]
|
|
209
|
+
|
|
210
|
+
# do 1D smooth on top and bottom row if there is some valid data there in the input
|
|
211
|
+
# otherwise leave it set to zonal average
|
|
212
|
+
for j in [0, nlat - 1]:
|
|
213
|
+
if zoncnt[j] > 1:
|
|
214
|
+
|
|
215
|
+
for i in range(1, nlon - 1):
|
|
216
|
+
if fillmask[j, i]:
|
|
217
|
+
im1 = i - 1
|
|
218
|
+
ip1 = i + 1
|
|
219
|
+
|
|
220
|
+
res[j, i] = var[j, ip1] + var[j, im1] - 2.0 * var[j, i]
|
|
221
|
+
var[j, i] = var[j, i] + rc * 0.5 * res[j, i]
|
|
222
|
+
|
|
223
|
+
# do 1D smooth in the vertical on left and right column
|
|
224
|
+
for i in [0, nlon - 1]:
|
|
225
|
+
|
|
226
|
+
for j in range(1, nlat - 1):
|
|
227
|
+
if fillmask[j, i]:
|
|
228
|
+
jm1 = j - 1
|
|
229
|
+
jp1 = j + 1
|
|
230
|
+
|
|
231
|
+
res[j, i] = var[jp1, i] + var[jm1, i] - 2.0 * var[j, i]
|
|
232
|
+
var[j, i] = var[j, i] + rc * 0.5 * res[j, i]
|
|
233
|
+
|
|
234
|
+
# four corners
|
|
235
|
+
for j in [0, nlat - 1]:
|
|
236
|
+
if j == 0:
|
|
237
|
+
jp1 = j + 1
|
|
238
|
+
jm1 = j
|
|
239
|
+
elif j == nlat - 1:
|
|
240
|
+
jp1 = j
|
|
241
|
+
jm1 = j - 1
|
|
242
|
+
|
|
243
|
+
for i in [0, nlon - 1]:
|
|
244
|
+
if i == 0:
|
|
245
|
+
ip1 = i + 1
|
|
246
|
+
im1 = i
|
|
247
|
+
elif i == nlon - 1:
|
|
248
|
+
ip1 = i
|
|
249
|
+
im1 = i - 1
|
|
250
|
+
|
|
251
|
+
res[j, i] = (
|
|
252
|
+
var[j, ip1]
|
|
253
|
+
+ var[j, im1]
|
|
254
|
+
+ var[jm1, i]
|
|
255
|
+
+ var[jp1, i]
|
|
256
|
+
- 4.0 * var[j, i]
|
|
257
|
+
)
|
|
258
|
+
var[j, i] = var[j, i] + rc * 0.25 * res[j, i]
|
|
259
|
+
|
|
260
|
+
res_max = np.max(np.fabs(res)) / np.max(np.fabs(var))
|
|
261
|
+
iter_cnt += 1
|
|
262
|
+
|
|
263
|
+
return var
|