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.
@@ -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
@@ -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