oafuncs 0.0.98.51__tar.gz → 0.0.98.52__tar.gz
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.
- {oafuncs-0.0.98.51/oafuncs.egg-info → oafuncs-0.0.98.52}/PKG-INFO +1 -1
- oafuncs-0.0.98.52/oafuncs/_script/process_roms.py +620 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_linux.py +53 -4
- oafuncs-0.0.98.52/oafuncs/oa_model/roms.py +42 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52/oafuncs.egg-info}/PKG-INFO +1 -1
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs.egg-info/SOURCES.txt +2 -2
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/setup.py +1 -1
- oafuncs-0.0.98.51/oafuncs/oa_model/roms/__init__.py +0 -20
- oafuncs-0.0.98.51/oafuncs/oa_model/roms/test.py +0 -19
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/LICENSE.txt +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/MANIFEST.in +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/README.md +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/__init__.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/_data/hycom.png +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/_data/oafuncs.png +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/_script/cprogressbar.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/_script/data_interp.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/_script/email.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/_script/netcdf_merge.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/_script/netcdf_modify.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/_script/netcdf_write.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/_script/parallel.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/_script/parallel_bak.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/_script/plot_dataset.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/_script/replace_file_content.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_cmap.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_data.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_date.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_down/User_Agent-list.txt +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_down/__init__.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_down/hycom_3hourly.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_down/idm.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_down/literature.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_down/read_proxy.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_down/test_ua.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_down/user_agent.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_draw.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_file.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_geo.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_help.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_model/__init__.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_model/wrf/__init__.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_model/wrf/little_r.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_nc.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_python.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_sign/__init__.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_sign/meteorological.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_sign/ocean.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_sign/scientific.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs/oa_tool.py +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs.egg-info/dependency_links.txt +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs.egg-info/requires.txt +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/oafuncs.egg-info/top_level.txt +0 -0
- {oafuncs-0.0.98.51 → oafuncs-0.0.98.52}/setup.cfg +0 -0
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
process_roms_uv_fixed.py
|
|
4
|
+
|
|
5
|
+
ROMS -> lon/lat remapping with correct u/v handling following Fortran workflow:
|
|
6
|
+
- Average u/v to rho points (pad both ends then average)
|
|
7
|
+
- Vertical interpolate on SOURCE rho depths to standard target depths (spline fallback linear)
|
|
8
|
+
- Horizontal remap each target level to output lon/lat (xESMF)
|
|
9
|
+
Robust Cs handling, no silent NaN propagation, endpoint handling like Fortran splint.
|
|
10
|
+
|
|
11
|
+
Usage: edit __main__ input_nc/output_nc/varnames/target_lon/target_lat/target_depth.
|
|
12
|
+
Dependencies: numpy, xarray, scipy, xesmf
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from typing import Optional, Sequence, Dict, Any, Tuple
|
|
16
|
+
import numpy as np
|
|
17
|
+
import xarray as xr
|
|
18
|
+
from scipy.interpolate import interp1d, CubicSpline
|
|
19
|
+
from rich import print
|
|
20
|
+
|
|
21
|
+
# Verbose toggles
|
|
22
|
+
VERBOSE = True
|
|
23
|
+
VERBOSE_DEBUG = False
|
|
24
|
+
|
|
25
|
+
# -------------------------
|
|
26
|
+
# Utilities
|
|
27
|
+
# -------------------------
|
|
28
|
+
def safe_nanmin(a):
|
|
29
|
+
a = np.asarray(a)
|
|
30
|
+
if np.isfinite(a).any():
|
|
31
|
+
return np.nanmin(a)
|
|
32
|
+
return np.nan
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def safe_nanmax(a):
|
|
36
|
+
a = np.asarray(a)
|
|
37
|
+
if np.isfinite(a).any():
|
|
38
|
+
return np.nanmax(a)
|
|
39
|
+
return np.nan
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def avg_to_rho_axis_padboth(arr: np.ndarray, axis: int) -> np.ndarray:
|
|
43
|
+
"""
|
|
44
|
+
Pad both ends with edge values and average adjacent pairs along axis.
|
|
45
|
+
Input length n -> output length n+1. Works for N-D arrays.
|
|
46
|
+
"""
|
|
47
|
+
arr = np.asarray(arr)
|
|
48
|
+
if arr.size == 0:
|
|
49
|
+
return arr
|
|
50
|
+
arr = arr.astype(np.float64, copy=False)
|
|
51
|
+
axis = axis if axis >= 0 else arr.ndim + axis
|
|
52
|
+
pad = [(0, 0)] * arr.ndim
|
|
53
|
+
pad[axis] = (1, 1)
|
|
54
|
+
arr_pad = np.pad(arr, pad, mode='edge')
|
|
55
|
+
# average adjacent pairs
|
|
56
|
+
slic_l = [slice(None)] * arr.ndim
|
|
57
|
+
slic_r = [slice(None)] * arr.ndim
|
|
58
|
+
slic_l[axis] = slice(0, arr_pad.shape[axis] - 1)
|
|
59
|
+
slic_r[axis] = slice(1, arr_pad.shape[axis])
|
|
60
|
+
left = arr_pad[tuple(slic_l)]
|
|
61
|
+
right = arr_pad[tuple(slic_r)]
|
|
62
|
+
out = 0.5 * (left + right)
|
|
63
|
+
return np.ascontiguousarray(out)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _need_periodic(lon1d: np.ndarray, tol: float = 1.0) -> bool:
|
|
67
|
+
lon1d = np.asarray(lon1d, dtype=np.float64)
|
|
68
|
+
if lon1d.size == 0:
|
|
69
|
+
return False
|
|
70
|
+
span = np.nanmax(lon1d) - np.nanmin(lon1d)
|
|
71
|
+
return span > (360 - tol)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# -------------------------
|
|
75
|
+
# Vertical stretching helpers (Cs)
|
|
76
|
+
# -------------------------
|
|
77
|
+
def _compute_C_from_vstretching(s: np.ndarray, theta_s: float, theta_b: float, vstretching: int) -> np.ndarray:
|
|
78
|
+
s = np.asarray(s, dtype=np.float64)
|
|
79
|
+
vstretching = int(vstretching)
|
|
80
|
+
theta_s = float(theta_s)
|
|
81
|
+
theta_b = float(theta_b)
|
|
82
|
+
if vstretching == 1:
|
|
83
|
+
return (1 - theta_b) * (np.sinh(theta_s * s) / np.sinh(theta_s)) + \
|
|
84
|
+
theta_b * (-0.5 + 0.5 * np.tanh(theta_s * (s + 0.5)) / np.tanh(0.5 * theta_s))
|
|
85
|
+
elif vstretching == 2:
|
|
86
|
+
Csur = (1 - np.cosh(theta_s * s)) / (np.cosh(theta_s) - 1)
|
|
87
|
+
Cbot = -1 + (1 - np.sinh(theta_b * (s + 1))) / np.sinh(theta_b)
|
|
88
|
+
alpha, beta = 3.0, 3.0
|
|
89
|
+
Cweight = (s + 1) ** alpha * (1 + (alpha / beta) * (1 - (s + 1) ** beta))
|
|
90
|
+
return Cweight * Csur + (1 - Cweight) * Cbot
|
|
91
|
+
else:
|
|
92
|
+
# 4
|
|
93
|
+
Ctemp = (1 - np.cosh(theta_s * s)) / (np.cosh(theta_s) - 1)
|
|
94
|
+
denom = (1 - np.exp(-theta_b))
|
|
95
|
+
denom = denom if denom != 0 else 1e-12
|
|
96
|
+
return (np.exp(theta_b * Ctemp) - 1) / denom
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# -------------------------
|
|
100
|
+
# Compute source z (vectorized), validate Cs
|
|
101
|
+
# -------------------------
|
|
102
|
+
def get_roms_depths(ds: xr.Dataset, is_w: bool = False, time_index: Optional[int] = None,
|
|
103
|
+
eps: float = 1e-8) -> xr.DataArray:
|
|
104
|
+
vtransform = int(getattr(ds, 'Vtransform', ds.attrs.get('Vtransform', 2)))
|
|
105
|
+
vstretching = int(getattr(ds, 'Vstretching', ds.attrs.get('Vstretching', 4)))
|
|
106
|
+
theta_s = float(ds.attrs.get('theta_s', getattr(ds, 'theta_s', 5.0)))
|
|
107
|
+
theta_b = float(ds.attrs.get('theta_b', getattr(ds, 'theta_b', 0.4)))
|
|
108
|
+
|
|
109
|
+
h = ds['h'].astype('f8').values
|
|
110
|
+
if 'mask_rho' in ds.variables:
|
|
111
|
+
mask_rho = ds['mask_rho'].astype('f8').values
|
|
112
|
+
h = h.copy()
|
|
113
|
+
h[mask_rho == 0] = np.nan
|
|
114
|
+
|
|
115
|
+
hc = None
|
|
116
|
+
if 'hc' in ds.variables:
|
|
117
|
+
try:
|
|
118
|
+
hc = float(np.array(ds['hc'].values))
|
|
119
|
+
except Exception:
|
|
120
|
+
hc = None
|
|
121
|
+
if hc is None:
|
|
122
|
+
hc = float(ds.attrs.get('hc', 1.0))
|
|
123
|
+
hc = float(hc)
|
|
124
|
+
|
|
125
|
+
# zeta
|
|
126
|
+
if 'zeta' in ds.variables:
|
|
127
|
+
zeta_da = ds['zeta']
|
|
128
|
+
if 'ocean_time' in zeta_da.dims:
|
|
129
|
+
if time_index is None:
|
|
130
|
+
zeta = zeta_da.astype('f8').values
|
|
131
|
+
else:
|
|
132
|
+
zeta = zeta_da.isel(ocean_time=int(time_index)).astype('f8').values
|
|
133
|
+
else:
|
|
134
|
+
zeta = zeta_da.astype('f8').values
|
|
135
|
+
else:
|
|
136
|
+
if time_index is None and 'ocean_time' in ds.dims:
|
|
137
|
+
ntime = int(ds.sizes.get('ocean_time', 1))
|
|
138
|
+
zeta = np.zeros((ntime,) + h.shape, dtype=np.float64)
|
|
139
|
+
else:
|
|
140
|
+
zeta = np.zeros_like(h, dtype=np.float64)
|
|
141
|
+
|
|
142
|
+
if is_w:
|
|
143
|
+
Ns = int(ds.sizes.get('s_w', 0))
|
|
144
|
+
sc_name, Cs_name = 'sc_w', 'Cs_w'
|
|
145
|
+
s_dim = 's_w'
|
|
146
|
+
else:
|
|
147
|
+
Ns = int(ds.sizes.get('s_rho', 0))
|
|
148
|
+
sc_name, Cs_name = 'sc_r', 'Cs_r'
|
|
149
|
+
s_dim = 's_rho'
|
|
150
|
+
|
|
151
|
+
if sc_name in ds.variables:
|
|
152
|
+
s = np.asarray(ds[sc_name].values, dtype=np.float64).ravel()
|
|
153
|
+
else:
|
|
154
|
+
if is_w:
|
|
155
|
+
s = (np.arange(0, Ns) - Ns) / float(Ns)
|
|
156
|
+
else:
|
|
157
|
+
s = (np.arange(1, Ns + 1) - Ns - 0.5) / float(Ns)
|
|
158
|
+
|
|
159
|
+
# Cs read + validate
|
|
160
|
+
if Cs_name in ds.variables:
|
|
161
|
+
try:
|
|
162
|
+
C_read = np.asarray(ds[Cs_name].values, dtype=np.float64).ravel()
|
|
163
|
+
except Exception:
|
|
164
|
+
C_read = None
|
|
165
|
+
else:
|
|
166
|
+
C_read = None
|
|
167
|
+
|
|
168
|
+
if C_read is None:
|
|
169
|
+
C = _compute_C_from_vstretching(s, theta_s, theta_b, vstretching)
|
|
170
|
+
else:
|
|
171
|
+
cmin = safe_nanmin(C_read)
|
|
172
|
+
cmax = safe_nanmax(C_read)
|
|
173
|
+
if (not np.isfinite(cmin)) or (not np.isfinite(cmax)) or (cmax > 5.0) or (cmin < -50.0):
|
|
174
|
+
if VERBOSE:
|
|
175
|
+
print(f"[get_roms_depths] Cs suspicious (min={cmin}, max={cmax}), recomputing")
|
|
176
|
+
C = _compute_C_from_vstretching(s, theta_s, theta_b, vstretching)
|
|
177
|
+
else:
|
|
178
|
+
C = C_read
|
|
179
|
+
|
|
180
|
+
s = np.asarray(s, dtype=np.float64).ravel()
|
|
181
|
+
C = np.asarray(C, dtype=np.float64).ravel()
|
|
182
|
+
|
|
183
|
+
# prepare h arr
|
|
184
|
+
h_arr = np.asarray(h, dtype=np.float64).copy()
|
|
185
|
+
h_arr[~np.isfinite(h_arr)] = np.nan
|
|
186
|
+
h_arr[h_arr <= 0] = np.nan
|
|
187
|
+
|
|
188
|
+
s3 = s[:, None, None]
|
|
189
|
+
C3 = C[:, None, None]
|
|
190
|
+
h3 = h_arr[None, :, :]
|
|
191
|
+
|
|
192
|
+
if zeta.ndim == 3:
|
|
193
|
+
zeta3 = zeta[:, None, :, :]
|
|
194
|
+
if vtransform == 1:
|
|
195
|
+
Zo = hc * s3 + (h3 - hc) * C3
|
|
196
|
+
denom_safe = np.where(np.abs(h3) > eps, h3, np.nan)
|
|
197
|
+
z = Zo[None, ...] + zeta3 * (1.0 + Zo[None, ...] / denom_safe[None, ...])
|
|
198
|
+
else:
|
|
199
|
+
denom_safe = np.where(np.abs(hc + h3) > eps, (hc + h3), np.nan)
|
|
200
|
+
Zo = (hc * s3 + h3 * C3) / denom_safe
|
|
201
|
+
z = zeta3 + (zeta3 + h3[None, ...]) * Zo[None, ...]
|
|
202
|
+
time_dim = 'ocean_time'
|
|
203
|
+
else:
|
|
204
|
+
zeta3 = zeta[None, :, :]
|
|
205
|
+
if vtransform == 1:
|
|
206
|
+
Zo = hc * s3 + (h3 - hc) * C3
|
|
207
|
+
denom_safe = np.where(np.abs(h3) > eps, h3, np.nan)
|
|
208
|
+
z = Zo + zeta3 * (1.0 + Zo / denom_safe)
|
|
209
|
+
else:
|
|
210
|
+
denom_safe = np.where(np.abs(hc + h3) > eps, (hc + h3), np.nan)
|
|
211
|
+
Zo = (hc * s3 + h3 * C3) / denom_safe
|
|
212
|
+
z = zeta3 + (zeta3 + h3) * Zo
|
|
213
|
+
time_dim = None
|
|
214
|
+
|
|
215
|
+
# bounds
|
|
216
|
+
if z.ndim == 4:
|
|
217
|
+
zeta_b = zeta3; h_b = h3[None, ...]
|
|
218
|
+
else:
|
|
219
|
+
zeta_b = zeta3; h_b = h3
|
|
220
|
+
z_max_allowed = zeta_b
|
|
221
|
+
z_min_allowed = zeta_b - h_b
|
|
222
|
+
z = np.where((z <= (z_max_allowed + 1e-6)) & (z >= (z_min_allowed - 1e-6)), z, np.nan)
|
|
223
|
+
z = np.where(np.abs(z) >= 2e4, np.nan, z)
|
|
224
|
+
|
|
225
|
+
# build DataArray
|
|
226
|
+
if time_dim is not None:
|
|
227
|
+
dims = (time_dim, s_dim, 'eta_rho', 'xi_rho')
|
|
228
|
+
coords = {time_dim: ds['ocean_time'] if 'ocean_time' in ds.coords else np.arange(z.shape[0])}
|
|
229
|
+
else:
|
|
230
|
+
dims = (s_dim, 'eta_rho', 'xi_rho')
|
|
231
|
+
coords = {}
|
|
232
|
+
coords[s_dim] = s
|
|
233
|
+
coords['eta_rho'] = ds['eta_rho'] if 'eta_rho' in ds.coords else np.arange(h_arr.shape[0])
|
|
234
|
+
coords['xi_rho'] = ds['xi_rho'] if 'xi_rho' in ds.coords else np.arange(h_arr.shape[1])
|
|
235
|
+
|
|
236
|
+
z_da = xr.DataArray(data=z, dims=dims, coords=coords)
|
|
237
|
+
if VERBOSE:
|
|
238
|
+
try:
|
|
239
|
+
if time_dim is not None:
|
|
240
|
+
zk = z_da.isel({time_dim: 0})
|
|
241
|
+
print(f"[get_roms_depths] sample z min/max (time0): {safe_nanmin(zk.values):.3f}/{safe_nanmax(zk.values):.3f}")
|
|
242
|
+
else:
|
|
243
|
+
print(f"[get_roms_depths] sample z min/max: {safe_nanmin(z):.3f}/{safe_nanmax(z):.3f}")
|
|
244
|
+
except Exception:
|
|
245
|
+
pass
|
|
246
|
+
return z_da
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
# -------------------------
|
|
250
|
+
# vertical interpolation per-column with Fortran endpoint policy
|
|
251
|
+
# -------------------------
|
|
252
|
+
def vertical_interp_with_endpoints(depths: np.ndarray, data: np.ndarray, target_depth: Sequence[float],
|
|
253
|
+
allow_extrapolation: bool = False, cubic_min_points: int = 4) -> np.ndarray:
|
|
254
|
+
"""
|
|
255
|
+
Column-wise interpolation similar to Fortran spline+splint behavior:
|
|
256
|
+
- if n_valid >= cubic_min_points: cubic spline; for target outside source range assign endpoint value
|
|
257
|
+
- elif n_valid >=2: linear interpolation; for outside assign endpoint value
|
|
258
|
+
- else: NaN
|
|
259
|
+
depths: (Ns, eta, xi)
|
|
260
|
+
data: (Ns, eta, xi)
|
|
261
|
+
target_depth: 1D (negative depths)
|
|
262
|
+
"""
|
|
263
|
+
depths = np.asarray(depths, dtype=np.float64)
|
|
264
|
+
data = np.asarray(data, dtype=np.float64)
|
|
265
|
+
target_depth = np.asarray(target_depth, dtype=np.float64)
|
|
266
|
+
nt = len(target_depth)
|
|
267
|
+
eta = data.shape[1]
|
|
268
|
+
xi = data.shape[2]
|
|
269
|
+
out = np.full((nt, eta, xi), np.nan, dtype=np.float64)
|
|
270
|
+
|
|
271
|
+
# compute h_actual to mask deeper than local depth
|
|
272
|
+
if np.isfinite(depths).any():
|
|
273
|
+
h_actual = np.abs(np.nanmin(depths, axis=0))
|
|
274
|
+
else:
|
|
275
|
+
h_actual = np.zeros((eta, xi), dtype=np.float64)
|
|
276
|
+
h_actual = np.where(np.isfinite(h_actual), h_actual, 0.0)
|
|
277
|
+
|
|
278
|
+
for i in range(eta):
|
|
279
|
+
for j in range(xi):
|
|
280
|
+
dcol = depths[:, i, j]
|
|
281
|
+
vcol = data[:, i, j]
|
|
282
|
+
ok = np.isfinite(dcol) & np.isfinite(vcol)
|
|
283
|
+
n_ok = int(ok.sum())
|
|
284
|
+
if n_ok < 2:
|
|
285
|
+
continue
|
|
286
|
+
d_valid = dcol[ok]
|
|
287
|
+
v_valid = vcol[ok]
|
|
288
|
+
# sort ascending depth for interp
|
|
289
|
+
order = np.argsort(d_valid)
|
|
290
|
+
d_valid = d_valid[order]
|
|
291
|
+
v_valid = v_valid[order]
|
|
292
|
+
# unique depths
|
|
293
|
+
d_u, idx = np.unique(d_valid, return_index=True)
|
|
294
|
+
v_u = v_valid[idx]
|
|
295
|
+
if d_u.size < 2:
|
|
296
|
+
continue
|
|
297
|
+
try:
|
|
298
|
+
if d_u.size >= cubic_min_points:
|
|
299
|
+
cs = CubicSpline(d_u, v_u, extrapolate=False)
|
|
300
|
+
y = cs(target_depth)
|
|
301
|
+
# handle endpoints: where target < d_u[0] -> assign v_u[0]; where > d_u[-1] -> v_u[-1]
|
|
302
|
+
left_mask = target_depth < d_u[0]
|
|
303
|
+
right_mask = target_depth > d_u[-1]
|
|
304
|
+
if left_mask.any():
|
|
305
|
+
y[left_mask] = v_u[0]
|
|
306
|
+
if right_mask.any():
|
|
307
|
+
y[right_mask] = v_u[-1]
|
|
308
|
+
out[:, i, j] = y
|
|
309
|
+
else:
|
|
310
|
+
f = interp1d(d_u, v_u, bounds_error=False, fill_value=np.nan, assume_sorted=True)
|
|
311
|
+
y = f(target_depth)
|
|
312
|
+
# fill outside with endpoints
|
|
313
|
+
left_mask = target_depth < d_u[0]
|
|
314
|
+
right_mask = target_depth > d_u[-1]
|
|
315
|
+
if left_mask.any():
|
|
316
|
+
y[left_mask] = v_u[0]
|
|
317
|
+
if right_mask.any():
|
|
318
|
+
y[right_mask] = v_u[-1]
|
|
319
|
+
out[:, i, j] = y
|
|
320
|
+
except Exception:
|
|
321
|
+
# fallback linear
|
|
322
|
+
try:
|
|
323
|
+
f = interp1d(d_u, v_u, bounds_error=False, fill_value=np.nan, assume_sorted=True)
|
|
324
|
+
y = f(target_depth)
|
|
325
|
+
left_mask = target_depth < d_u[0]
|
|
326
|
+
right_mask = target_depth > d_u[-1]
|
|
327
|
+
if left_mask.any():
|
|
328
|
+
y[left_mask] = v_u[0]
|
|
329
|
+
if right_mask.any():
|
|
330
|
+
y[right_mask] = v_u[-1]
|
|
331
|
+
out[:, i, j] = y
|
|
332
|
+
except Exception:
|
|
333
|
+
if VERBOSE_DEBUG:
|
|
334
|
+
print(f"[vertical_interp] interpolation failed at col {i},{j}")
|
|
335
|
+
continue
|
|
336
|
+
# ensure target deeper than local bathymetry get NaN
|
|
337
|
+
deeper = np.abs(target_depth) > h_actual[i, j]
|
|
338
|
+
if np.isfinite(h_actual[i, j]) and deeper.any():
|
|
339
|
+
out[deeper, i, j] = np.nan
|
|
340
|
+
|
|
341
|
+
if VERBOSE_DEBUG:
|
|
342
|
+
print("[vertical_interp] done; out nan_frac=", np.isnan(out).mean())
|
|
343
|
+
return out
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
# -------------------------
|
|
347
|
+
# xESMF regridder helpers
|
|
348
|
+
# -------------------------
|
|
349
|
+
def _make_src_dataset_for_grid(ds: xr.Dataset, grid: str) -> xr.Dataset:
|
|
350
|
+
# grid: 'rho','u','v'
|
|
351
|
+
if grid == 'rho':
|
|
352
|
+
lon2, lat2 = ds['lon_rho'].values, ds['lat_rho'].values
|
|
353
|
+
dims = ('eta_rho', 'xi_rho')
|
|
354
|
+
elif grid == 'u':
|
|
355
|
+
lon2, lat2 = ds['lon_u'].values, ds['lat_u'].values
|
|
356
|
+
dims = ('eta_u', 'xi_u')
|
|
357
|
+
elif grid == 'v':
|
|
358
|
+
lon2, lat2 = ds['lon_v'].values, ds['lat_v'].values
|
|
359
|
+
dims = ('eta_v', 'xi_v')
|
|
360
|
+
else:
|
|
361
|
+
raise ValueError("Unknown grid")
|
|
362
|
+
return xr.Dataset({'lon': (dims, lon2), 'lat': (dims, lat2)})
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _make_dst_dataset(target_lon: Sequence[float], target_lat: Sequence[float]) -> xr.Dataset:
|
|
366
|
+
return xr.Dataset({'lon': (('lon',), np.asarray(target_lon, dtype=np.float64)),
|
|
367
|
+
'lat': (('lat',), np.asarray(target_lat, dtype=np.float64))})
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def _make_regridder_safe(src_ds: xr.Dataset, dst_ds: xr.Dataset, method: str, fname: str, periodic: bool, reuse: bool):
|
|
371
|
+
try:
|
|
372
|
+
import xesmf as xe
|
|
373
|
+
except Exception as e:
|
|
374
|
+
raise ImportError("xesmf required. Install: conda install --solver=classic -c conda-forge xesmf esmpy. Error: " + str(e))
|
|
375
|
+
try:
|
|
376
|
+
reb = xe.Regridder(src_ds, dst_ds, method=method, periodic=periodic, filename=fname, reuse_weights=reuse)
|
|
377
|
+
except OSError:
|
|
378
|
+
reb = xe.Regridder(src_ds, dst_ds, method=method, periodic=periodic, reuse_weights=False)
|
|
379
|
+
try:
|
|
380
|
+
reb.to_netcdf(fname)
|
|
381
|
+
except Exception:
|
|
382
|
+
pass
|
|
383
|
+
return reb
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
# -------------------------
|
|
387
|
+
# Main processing function (implements Fortran logic for u/v)
|
|
388
|
+
# -------------------------
|
|
389
|
+
def process_roms_file(input_nc: str, output_nc: str, varnames: Sequence[str],
|
|
390
|
+
target_lon: Sequence[float], target_lat: Sequence[float], target_depth: Sequence[float],
|
|
391
|
+
overwrite_weights: bool = False):
|
|
392
|
+
ds = xr.open_dataset(input_nc, decode_cf=True, mask_and_scale=True)
|
|
393
|
+
times = np.asarray(ds['ocean_time'].values) if 'ocean_time' in ds.dims else np.array([0])
|
|
394
|
+
|
|
395
|
+
# dst ds for xESMF
|
|
396
|
+
dst_ds = _make_dst_dataset(target_lon, target_lat)
|
|
397
|
+
periodic = _need_periodic(np.asarray(target_lon))
|
|
398
|
+
|
|
399
|
+
# create rho regridder (final remap uses rho grid)
|
|
400
|
+
src_rho = _make_src_dataset_for_grid(ds, 'rho')
|
|
401
|
+
fname_rho = f"weights_bilin_rho_{src_rho['lon'].shape[0]}x{src_rho['lon'].shape[1]}_dst{len(target_lat)}x{len(target_lon)}.nc"
|
|
402
|
+
reb_rho = _make_regridder_safe(src_rho, dst_ds, method='bilinear', fname=fname_rho, periodic=periodic, reuse=not overwrite_weights)
|
|
403
|
+
reb_mask_rho = _make_regridder_safe(src_rho, dst_ds, method='nearest_s2d', fname=fname_rho.replace('.nc', '_mask.nc'), periodic=periodic, reuse=not overwrite_weights)
|
|
404
|
+
|
|
405
|
+
interp_results = {}
|
|
406
|
+
|
|
407
|
+
for var in varnames:
|
|
408
|
+
if VERBOSE:
|
|
409
|
+
print(f"**[main]** processing {var}")
|
|
410
|
+
|
|
411
|
+
if var not in ds.variables:
|
|
412
|
+
raise RuntimeError(f"{var} not found in dataset")
|
|
413
|
+
|
|
414
|
+
is_w_kind = True if 's_w' in ds[var].dims else False
|
|
415
|
+
is_rho_kind = True if 's_rho' in ds[var].dims or 'eta_rho' in ds[var].dims or 'xi_rho' in ds[var].dims else False
|
|
416
|
+
is_u_kind = True if 'eta_u' in ds[var].dims or 'xi_u' in ds[var].dims else False
|
|
417
|
+
is_v_kind = True if 'eta_v' in ds[var].dims or 'xi_v' in ds[var].dims else False
|
|
418
|
+
|
|
419
|
+
if is_u_kind:
|
|
420
|
+
u_da = ds[var].astype('f8')
|
|
421
|
+
u_vals = np.where(np.isfinite(u_da.values), u_da.values, np.nan) # (time, s_rho, eta_u, xi_u)
|
|
422
|
+
# average to rho grid:
|
|
423
|
+
u_rho = avg_to_rho_axis_padboth(u_vals, axis=-1) # -> shape (time, s, eta_u, xi_u+1) ; eta_u == eta_rho
|
|
424
|
+
eta_rho_len = ds.sizes['eta_rho']; xi_rho_len = ds.sizes['xi_rho']
|
|
425
|
+
if u_rho.shape[-2] != eta_rho_len or u_rho.shape[-1] != xi_rho_len:
|
|
426
|
+
raise RuntimeError(f"u_rho shape mismatch {u_rho.shape[-2:]} vs rho ({eta_rho_len},{xi_rho_len})")
|
|
427
|
+
# 旋转到东向分量(如果有angle)
|
|
428
|
+
angle = ds['angle'].values if 'angle' in ds.variables else np.zeros((eta_rho_len, xi_rho_len))
|
|
429
|
+
nt = u_rho.shape[0]; ns = u_rho.shape[1]
|
|
430
|
+
u_east = np.full_like(u_rho, np.nan)
|
|
431
|
+
for t in range(nt):
|
|
432
|
+
print(f'Processing time step {t+1}/{nt} for variable {var} (u-component)')
|
|
433
|
+
for k in range(ns):
|
|
434
|
+
print(f' Processing vertical level {k+1}/{ns} for variable {var} (u-component)')
|
|
435
|
+
ur = u_rho[t, k, :, :]
|
|
436
|
+
ang = angle
|
|
437
|
+
if ang.shape != ur.shape:
|
|
438
|
+
ang = np.broadcast_to(angle, ur.shape)
|
|
439
|
+
cosA = np.cos(ang)
|
|
440
|
+
sinA = np.sin(ang)
|
|
441
|
+
# 只处理u分量(东向)
|
|
442
|
+
u_east[t, k, :, :] = ur * cosA
|
|
443
|
+
# vertical interpolation ON SOURCE (rho) grid using z_rho
|
|
444
|
+
if 'z_rho' in ds.variables:
|
|
445
|
+
z_rho_da = ds['z_rho'].astype('f8')
|
|
446
|
+
z_rho = np.where(np.isfinite(z_rho_da.values), z_rho_da.values, np.nan)
|
|
447
|
+
else:
|
|
448
|
+
z_rho = get_roms_depths(ds, is_w=is_w_kind).values
|
|
449
|
+
res_u_time = []
|
|
450
|
+
for t in range(nt):
|
|
451
|
+
print(f'Processing time step {t+1}/{nt} for variable {var} (vertical interpolation)')
|
|
452
|
+
zcol = z_rho[t] if z_rho.ndim == 4 else z_rho
|
|
453
|
+
ue_src = u_east[t]
|
|
454
|
+
ue_vert = vertical_interp_with_endpoints(zcol, ue_src, target_depth)
|
|
455
|
+
levels_u = []
|
|
456
|
+
for k in range(ue_vert.shape[0]):
|
|
457
|
+
da_u = xr.DataArray(ue_vert[k], dims=('eta_rho', 'xi_rho'),
|
|
458
|
+
coords={'eta_rho': ds['eta_rho'], 'xi_rho': ds['xi_rho'],
|
|
459
|
+
'lon': (('eta_rho', 'xi_rho'), ds['lon_rho'].values),
|
|
460
|
+
'lat': (('eta_rho', 'xi_rho'), ds['lat_rho'].values)})
|
|
461
|
+
ru = reb_rho(da_u)
|
|
462
|
+
if 'mask_rho' in ds.variables:
|
|
463
|
+
da_mask_src = xr.DataArray(ds['mask_rho'].astype(np.float64).values, dims=('eta_rho', 'xi_rho'),
|
|
464
|
+
coords={'eta_rho': ds['eta_rho'], 'xi_rho': ds['xi_rho'],
|
|
465
|
+
'lon': (('eta_rho', 'xi_rho'), ds['lon_rho'].values),
|
|
466
|
+
'lat': (('eta_rho', 'xi_rho'), ds['lat_rho'].values)})
|
|
467
|
+
mask_dst = reb_mask_rho(da_mask_src)
|
|
468
|
+
ru = ru.where(mask_dst >= 0.5, np.nan)
|
|
469
|
+
levels_u.append(ru.values)
|
|
470
|
+
res_u_time.append(np.array(levels_u))
|
|
471
|
+
interp_results[var] = np.array(res_u_time)
|
|
472
|
+
|
|
473
|
+
elif is_v_kind:
|
|
474
|
+
v_da = ds[var].astype('f8')
|
|
475
|
+
v_vals = np.where(np.isfinite(v_da.values), v_da.values, np.nan) # (time, s_rho, eta_v, xi_v)
|
|
476
|
+
v_rho = avg_to_rho_axis_padboth(v_vals, axis=-2) # -> shape (time, s, eta_v+1, xi_v) ; xi_v == xi_rho
|
|
477
|
+
eta_rho_len = ds.sizes['eta_rho']; xi_rho_len = ds.sizes['xi_rho']
|
|
478
|
+
if v_rho.shape[-2] != eta_rho_len or v_rho.shape[-1] != xi_rho_len:
|
|
479
|
+
raise RuntimeError(f"v_rho shape mismatch {v_rho.shape[-2:]} vs rho ({eta_rho_len},{xi_rho_len})")
|
|
480
|
+
# 旋转到北向分量(如果有angle)
|
|
481
|
+
angle = ds['angle'].values if 'angle' in ds.variables else np.zeros((eta_rho_len, xi_rho_len))
|
|
482
|
+
nt = v_rho.shape[0]; ns = v_rho.shape[1]
|
|
483
|
+
v_north = np.full_like(v_rho, np.nan)
|
|
484
|
+
for t in range(nt):
|
|
485
|
+
print(f'Processing time step {t+1}/{nt} for variable {var} (v-component)')
|
|
486
|
+
for k in range(ns):
|
|
487
|
+
print(f' Processing vertical level {k+1}/{ns} for variable {var} (v-component)')
|
|
488
|
+
vr = v_rho[t, k, :, :]
|
|
489
|
+
ang = angle
|
|
490
|
+
if ang.shape != vr.shape:
|
|
491
|
+
ang = np.broadcast_to(angle, vr.shape)
|
|
492
|
+
cosA = np.cos(ang)
|
|
493
|
+
sinA = np.sin(ang)
|
|
494
|
+
# 只处理v分量(北向)
|
|
495
|
+
v_north[t, k, :, :] = vr * cosA
|
|
496
|
+
if 'z_rho' in ds.variables:
|
|
497
|
+
z_rho_da = ds['z_rho'].astype('f8')
|
|
498
|
+
z_rho = np.where(np.isfinite(z_rho_da.values), z_rho_da.values, np.nan)
|
|
499
|
+
else:
|
|
500
|
+
z_rho = get_roms_depths(ds, is_w=is_w_kind).values
|
|
501
|
+
res_v_time = []
|
|
502
|
+
for t in range(nt):
|
|
503
|
+
print(f'Processing time step {t+1}/{nt} for variable {var} (vertical interpolation)')
|
|
504
|
+
zcol = z_rho[t] if z_rho.ndim == 4 else z_rho
|
|
505
|
+
vn_src = v_north[t]
|
|
506
|
+
vn_vert = vertical_interp_with_endpoints(zcol, vn_src, target_depth)
|
|
507
|
+
levels_v = []
|
|
508
|
+
for k in range(vn_vert.shape[0]):
|
|
509
|
+
da_v = xr.DataArray(vn_vert[k], dims=('eta_rho', 'xi_rho'),
|
|
510
|
+
coords={'eta_rho': ds['eta_rho'], 'xi_rho': ds['xi_rho'],
|
|
511
|
+
'lon': (('eta_rho', 'xi_rho'), ds['lon_rho'].values),
|
|
512
|
+
'lat': (('eta_rho', 'xi_rho'), ds['lat_rho'].values)})
|
|
513
|
+
rv = reb_rho(da_v)
|
|
514
|
+
if 'mask_rho' in ds.variables:
|
|
515
|
+
da_mask_src = xr.DataArray(ds['mask_rho'].astype(np.float64).values, dims=('eta_rho', 'xi_rho'),
|
|
516
|
+
coords={'eta_rho': ds['eta_rho'], 'xi_rho': ds['xi_rho'],
|
|
517
|
+
'lon': (('eta_rho', 'xi_rho'), ds['lon_rho'].values),
|
|
518
|
+
'lat': (('eta_rho', 'xi_rho'), ds['lat_rho'].values)})
|
|
519
|
+
mask_dst = reb_mask_rho(da_mask_src)
|
|
520
|
+
rv = rv.where(mask_dst >= 0.5, np.nan)
|
|
521
|
+
levels_v.append(rv.values)
|
|
522
|
+
res_v_time.append(np.array(levels_v))
|
|
523
|
+
interp_results[var] = np.array(res_v_time)
|
|
524
|
+
|
|
525
|
+
elif is_rho_kind:
|
|
526
|
+
# scalar processing: temp/salt/zeta (vertical-first -> horizontal remap)
|
|
527
|
+
da = ds[var].astype('f8')
|
|
528
|
+
vals = np.where(np.isfinite(da.values), da.values, np.nan)
|
|
529
|
+
if da.ndim == 4:
|
|
530
|
+
nt = vals.shape[0]
|
|
531
|
+
out_time = []
|
|
532
|
+
for t in range(nt):
|
|
533
|
+
print(f'Processing time step {t+1}/{nt} for variable {var} (vertical interpolation)')
|
|
534
|
+
src = vals[t]
|
|
535
|
+
if is_w_kind and 'z_w' in ds.variables:
|
|
536
|
+
zsrc = np.where(np.isfinite(ds['z_w'].values), ds['z_w'].values, np.nan)
|
|
537
|
+
zcol = zsrc[t] if zsrc.ndim == 4 else zsrc
|
|
538
|
+
elif not is_w_kind and 'z_rho' in ds.variables:
|
|
539
|
+
zsrc = np.where(np.isfinite(ds['z_rho'].values), ds['z_rho'].values, np.nan)
|
|
540
|
+
zcol = zsrc[t] if zsrc.ndim == 4 else zsrc
|
|
541
|
+
else:
|
|
542
|
+
zcol = get_roms_depths(ds, is_w=is_w_kind, time_index=t).values
|
|
543
|
+
vert = vertical_interp_with_endpoints(zcol, src, target_depth)
|
|
544
|
+
levels = []
|
|
545
|
+
for k in range(vert.shape[0]):
|
|
546
|
+
print(f' Processing vertical level {k+1}/{vert.shape[0]} for variable {var} (vertical interpolation)')
|
|
547
|
+
da_l = xr.DataArray(vert[k], dims=('eta_rho', 'xi_rho'),
|
|
548
|
+
coords={'eta_rho': ds['eta_rho'], 'xi_rho': ds['xi_rho'],
|
|
549
|
+
'lon': (('eta_rho', 'xi_rho'), ds['lon_rho'].values),
|
|
550
|
+
'lat': (('eta_rho', 'xi_rho'), ds['lat_rho'].values)})
|
|
551
|
+
rt = reb_rho(da_l)
|
|
552
|
+
if 'mask_rho' in ds.variables:
|
|
553
|
+
da_mask_src = xr.DataArray(ds['mask_rho'].astype(np.float64).values, dims=('eta_rho','xi_rho'),
|
|
554
|
+
coords={'eta_rho': ds['eta_rho'], 'xi_rho': ds['xi_rho'],
|
|
555
|
+
'lon': (('eta_rho','xi_rho'), ds['lon_rho'].values),
|
|
556
|
+
'lat': (('eta_rho','xi_rho'), ds['lat_rho'].values)})
|
|
557
|
+
mask_dst = reb_mask_rho(da_mask_src)
|
|
558
|
+
rt = rt.where(mask_dst >= 0.5, np.nan)
|
|
559
|
+
levels.append(rt.values)
|
|
560
|
+
out_time.append(np.array(levels))
|
|
561
|
+
interp_results[var] = np.array(out_time)
|
|
562
|
+
elif da.ndim == 3:
|
|
563
|
+
nt = vals.shape[0]
|
|
564
|
+
out = []
|
|
565
|
+
for t in range(nt):
|
|
566
|
+
print(f'Processing time step {t+1}/{nt} for variable {var} (horizontal remap)')
|
|
567
|
+
da_l = xr.DataArray(vals[t], dims=('eta_rho', 'xi_rho'),
|
|
568
|
+
coords={'eta_rho': ds['eta_rho'], 'xi_rho': ds['xi_rho'],
|
|
569
|
+
'lon': (('eta_rho','xi_rho'), ds['lon_rho'].values),
|
|
570
|
+
'lat': (('eta_rho','xi_rho'), ds['lat_rho'].values)})
|
|
571
|
+
rt = reb_rho(da_l)
|
|
572
|
+
if 'mask_rho' in ds.variables:
|
|
573
|
+
da_mask_src = xr.DataArray(ds['mask_rho'].astype(np.float64).values, dims=('eta_rho','xi_rho'),
|
|
574
|
+
coords={'eta_rho': ds['eta_rho'], 'xi_rho': ds['xi_rho'],
|
|
575
|
+
'lon': (('eta_rho','xi_rho'), ds['lon_rho'].values),
|
|
576
|
+
'lat': (('eta_rho','xi_rho'), ds['lat_rho'].values)})
|
|
577
|
+
mask_dst = reb_mask_rho(da_mask_src)
|
|
578
|
+
rt = rt.where(mask_dst >= 0.5, np.nan)
|
|
579
|
+
out.append(rt.values)
|
|
580
|
+
interp_results[var] = np.array(out)
|
|
581
|
+
else:
|
|
582
|
+
raise ValueError(f"Unsupported variable dims for {var}")
|
|
583
|
+
else:
|
|
584
|
+
raise ValueError(f"Unknown grid type for variable {var}")
|
|
585
|
+
|
|
586
|
+
# write netcdf output - simple writer for (time,depth,lat,lon) or (time,lat,lon)
|
|
587
|
+
times_out = times
|
|
588
|
+
data_vars = {}
|
|
589
|
+
coords = {'time': ('time', times_out), 'lat': ('lat', target_lat), 'lon': ('lon', target_lon)}
|
|
590
|
+
if target_depth is not None:
|
|
591
|
+
coords['depth'] = ('depth', target_depth)
|
|
592
|
+
|
|
593
|
+
for vname, arr in interp_results.items():
|
|
594
|
+
a = np.asarray(arr)
|
|
595
|
+
if a.ndim == 4:
|
|
596
|
+
data_vars[vname] = (('time', 'depth', 'lat', 'lon'), a)
|
|
597
|
+
elif a.ndim == 3:
|
|
598
|
+
data_vars[vname] = (('time', 'lat', 'lon'), a)
|
|
599
|
+
else:
|
|
600
|
+
raise ValueError(f"Unexpected ndim for output {vname}: {a.ndim}")
|
|
601
|
+
|
|
602
|
+
ds_out = xr.Dataset(data_vars=data_vars, coords=coords)
|
|
603
|
+
encoding = {name: {'zlib': True, 'complevel': 4} for name in data_vars.keys()}
|
|
604
|
+
ds_out.to_netcdf(output_nc, encoding=encoding)
|
|
605
|
+
if VERBOSE:
|
|
606
|
+
print(f"[main] wrote {output_nc} variables: {list(data_vars.keys())}")
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
# -------------------------
|
|
610
|
+
# __main__ example
|
|
611
|
+
# -------------------------
|
|
612
|
+
if __name__ == "__main__":
|
|
613
|
+
input_nc = "./2024090100/nwa_his_0001.nc"
|
|
614
|
+
output_nc = "roms_interp.nc"
|
|
615
|
+
varnames = ['temp', 'zeta', 'w', 'u', 'v', 'salt']
|
|
616
|
+
target_lon = np.linspace(108, 140, 641)
|
|
617
|
+
target_lat = np.linspace(15, 40, 501)
|
|
618
|
+
target_depth = [-5, -10, -20, -30, -50, -75, -100, -125, -150, -200,
|
|
619
|
+
-250, -300, -400, -500, -600, -700, -800, -900, -1000]
|
|
620
|
+
process_roms_file(input_nc, output_nc, varnames, target_lon, target_lat, target_depth, overwrite_weights=False)
|
|
@@ -2,7 +2,7 @@ from rich import print
|
|
|
2
2
|
import time
|
|
3
3
|
import os
|
|
4
4
|
|
|
5
|
-
__all__ = ["os_command", "get_queue_node", "query_queue", "running_jobs", "submit_job"]
|
|
5
|
+
__all__ = ["os_command", "get_queue_node", "query_queue", "running_jobs", "submit_job", "get_job_status"]
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
# 负责执行命令并返回输出
|
|
@@ -21,6 +21,7 @@ def os_command(cmd):
|
|
|
21
21
|
return None
|
|
22
22
|
return result.stdout
|
|
23
23
|
|
|
24
|
+
|
|
24
25
|
# 返回“队列名:节点数”的字典
|
|
25
26
|
def get_queue_node():
|
|
26
27
|
import re
|
|
@@ -50,6 +51,7 @@ def get_queue_node():
|
|
|
50
51
|
|
|
51
52
|
return queue_node_dict
|
|
52
53
|
|
|
54
|
+
|
|
53
55
|
def query_queue(need_node=1, queue_list =['dcu','bigmem','cpu_parallel','cpu_single']):
|
|
54
56
|
queue_dict = get_queue_node()
|
|
55
57
|
hs = None
|
|
@@ -65,6 +67,7 @@ def query_queue(need_node=1, queue_list =['dcu','bigmem','cpu_parallel','cpu_sin
|
|
|
65
67
|
break
|
|
66
68
|
return hs
|
|
67
69
|
|
|
70
|
+
|
|
68
71
|
def running_jobs():
|
|
69
72
|
# 通过qstat判断任务状态,是否还在进行中
|
|
70
73
|
# status = os.popen('qstat').read()
|
|
@@ -73,6 +76,41 @@ def running_jobs():
|
|
|
73
76
|
ids = [job.split()[0] for job in Jobs if job != '']
|
|
74
77
|
return ids
|
|
75
78
|
|
|
79
|
+
|
|
80
|
+
def get_job_status(jobid):
|
|
81
|
+
"""
|
|
82
|
+
获取指定任务ID的ST状态(如R/PD/S等)
|
|
83
|
+
:param jobid: 任务ID(整数或字符串格式均可)
|
|
84
|
+
:return: 任务的ST状态字符串,未找到任务返回None
|
|
85
|
+
"""
|
|
86
|
+
import re # 复用正则模块,内部导入避免依赖冲突
|
|
87
|
+
jobid_str = str(jobid).strip()
|
|
88
|
+
if not jobid_str.isdigit():
|
|
89
|
+
print(f'⚠️ 输入的任务ID {jobid} 格式无效,需为纯数字')
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
# 执行squeue命令精准匹配目标任务(避免多ID混淆)
|
|
93
|
+
cmd = f'squeue | grep -w {jobid_str}'
|
|
94
|
+
output = os_command(cmd)
|
|
95
|
+
|
|
96
|
+
if not output:
|
|
97
|
+
print(f'❌ 未找到任务ID {jobid_str} 的相关信息(可能已完成或输入错误)')
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
# 解析输出中的ST状态(匹配JOBID后的第五个字段,处理多空格分隔)
|
|
101
|
+
# 正则匹配逻辑:忽略前置空格 → 匹配JOBID → 匹配后续任意字符 → 捕获ST状态(单个大写字母/字母组合)
|
|
102
|
+
pattern = r'\s*\d+\s+\S+\s+\S+\s+\S+\s+(\S+)'
|
|
103
|
+
match = re.search(pattern, output)
|
|
104
|
+
if match:
|
|
105
|
+
st_status = match.group(1)
|
|
106
|
+
print(f'✅ 任务ID {jobid_str} 的ST状态:{st_status}')
|
|
107
|
+
return st_status
|
|
108
|
+
else:
|
|
109
|
+
print(f'❌ 无法解析任务ID {jobid_str} 的ST状态,命令输出:{output.strip()}')
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
76
114
|
def submit_job(working_dir=None, script_tmp='run.slurm', script_run='run.slurm', need_node=1, queue_tmp='<queue_name>', queue_list=['dcu', 'bigmem', 'cpu_parallel', 'cpu_single'], max_job=38, wait=False):
|
|
77
115
|
'''提交任务到集群,并返回任务ID'''
|
|
78
116
|
from .oa_file import replace_content
|
|
@@ -106,22 +144,33 @@ def submit_job(working_dir=None, script_tmp='run.slurm', script_run='run.slurm',
|
|
|
106
144
|
time.sleep(30)
|
|
107
145
|
else:
|
|
108
146
|
print(f'提交任务成功,{content_sub.strip()}')
|
|
147
|
+
# time_s = time.time()
|
|
109
148
|
job_id = content_sub.strip().split()[-1]
|
|
110
|
-
|
|
149
|
+
print(f'等待60秒后,继续检查任务状态!')
|
|
150
|
+
time.sleep(60)
|
|
151
|
+
job_st = get_job_status(job_id)
|
|
152
|
+
# if job_st == 'PD' and (time.time()-time_s) > 60:
|
|
153
|
+
if job_st == 'PD':
|
|
154
|
+
os_command(f'scancel {job_id}')
|
|
155
|
+
print(f'因作业{job_id}处于PD状态,取消!')
|
|
156
|
+
else:
|
|
157
|
+
break
|
|
111
158
|
else:
|
|
112
159
|
print('没有足够的计算资源,等待30秒后重试!')
|
|
113
160
|
time.sleep(30)
|
|
114
161
|
else:
|
|
115
162
|
print(f'当前系统任务数:{len(running_job)},等待60秒后重试!')
|
|
116
163
|
time.sleep(60)
|
|
117
|
-
|
|
164
|
+
|
|
165
|
+
print(f'等待10秒后,继续查询任务或进行下一个操作!')
|
|
118
166
|
time.sleep(10)
|
|
119
167
|
|
|
120
168
|
if wait:
|
|
121
169
|
while True:
|
|
122
170
|
if job_id in running_jobs():
|
|
123
171
|
print(f'Time: {datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
|
|
124
|
-
print(f'任务{job_id}正在队列中...')
|
|
172
|
+
# print(f'任务{job_id}正在队列中...')
|
|
173
|
+
get_job_status(job_id)
|
|
125
174
|
time.sleep(60)
|
|
126
175
|
else:
|
|
127
176
|
print(f'任务{job_id}已完成!')
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from typing import Sequence
|
|
2
|
+
|
|
3
|
+
__all__ = ['interp']
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def interp(input_nc: str, output_nc: str, varnames: Sequence[str],
|
|
7
|
+
target_lon: Sequence[float], target_lat: Sequence[float], target_depth: Sequence[float],
|
|
8
|
+
overwrite_weights: bool = False):
|
|
9
|
+
"""
|
|
10
|
+
Perform vertical interpolation and horizontal remapping of ROMS model data.
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
input_nc : str
|
|
15
|
+
Path to the input ROMS NetCDF file.
|
|
16
|
+
output_nc : str
|
|
17
|
+
Path to the output NetCDF file.
|
|
18
|
+
varnames : Sequence[str]
|
|
19
|
+
List of variable names to process.
|
|
20
|
+
target_lon : Sequence[float]
|
|
21
|
+
Target longitudes for horizontal remapping.
|
|
22
|
+
target_lat : Sequence[float]
|
|
23
|
+
Target latitudes for horizontal remapping.
|
|
24
|
+
target_depth : Sequence[float]
|
|
25
|
+
Target depths for vertical interpolation. Should be negative values (e.g., -5 for 5m depth).
|
|
26
|
+
overwrite_weights : bool, optional
|
|
27
|
+
Whether to overwrite existing regridding weights files. Default is False.
|
|
28
|
+
|
|
29
|
+
Examples
|
|
30
|
+
--------
|
|
31
|
+
input_nc = "./2024090100/nwa_his_0001.nc"
|
|
32
|
+
output_nc = "roms_interp.nc"
|
|
33
|
+
varnames = ['temp', 'zeta', 'w', 'u', 'v', 'salt']
|
|
34
|
+
target_lon = np.linspace(108, 140, 641)
|
|
35
|
+
target_lat = np.linspace(15, 40, 501)
|
|
36
|
+
target_depth = [-5, -10, -20, -30, -50, -75, -100, -125, -150, -200,
|
|
37
|
+
-250, -300, -400, -500, -600, -700, -800, -900, -1000]
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from oafuncs._script.process_roms import process_roms_file
|
|
41
|
+
process_roms_file(input_nc, output_nc, varnames, target_lon, target_lat, target_depth,
|
|
42
|
+
overwrite_weights=overwrite_weights)
|
|
@@ -30,6 +30,7 @@ oafuncs/_script/netcdf_write.py
|
|
|
30
30
|
oafuncs/_script/parallel.py
|
|
31
31
|
oafuncs/_script/parallel_bak.py
|
|
32
32
|
oafuncs/_script/plot_dataset.py
|
|
33
|
+
oafuncs/_script/process_roms.py
|
|
33
34
|
oafuncs/_script/replace_file_content.py
|
|
34
35
|
oafuncs/oa_down/User_Agent-list.txt
|
|
35
36
|
oafuncs/oa_down/__init__.py
|
|
@@ -40,8 +41,7 @@ oafuncs/oa_down/read_proxy.py
|
|
|
40
41
|
oafuncs/oa_down/test_ua.py
|
|
41
42
|
oafuncs/oa_down/user_agent.py
|
|
42
43
|
oafuncs/oa_model/__init__.py
|
|
43
|
-
oafuncs/oa_model/roms
|
|
44
|
-
oafuncs/oa_model/roms/test.py
|
|
44
|
+
oafuncs/oa_model/roms.py
|
|
45
45
|
oafuncs/oa_model/wrf/__init__.py
|
|
46
46
|
oafuncs/oa_model/wrf/little_r.py
|
|
47
47
|
oafuncs/oa_sign/__init__.py
|
|
@@ -18,7 +18,7 @@ URL = "https://github.com/Industry-Pays/OAFuncs"
|
|
|
18
18
|
EMAIL = "liukun0312@stu.ouc.edu.cn"
|
|
19
19
|
AUTHOR = "Kun Liu"
|
|
20
20
|
REQUIRES_PYTHON = ">=3.10.0" # 2025/03/13
|
|
21
|
-
VERSION = "0.0.98.
|
|
21
|
+
VERSION = "0.0.98.52"
|
|
22
22
|
|
|
23
23
|
# What packages are required for this module to be executed?
|
|
24
24
|
REQUIRED = [
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python
|
|
2
|
-
# coding=utf-8
|
|
3
|
-
"""
|
|
4
|
-
Author: Liu Kun && 16031215@qq.com
|
|
5
|
-
Date: 2025-03-09 16:30:02
|
|
6
|
-
LastEditors: Liu Kun && 16031215@qq.com
|
|
7
|
-
LastEditTime: 2025-03-09 18:23:30
|
|
8
|
-
FilePath: \\Python\\My_Funcs\\OAFuncs\\oafuncs\\oa_model\\roms\\__init__.py
|
|
9
|
-
Description:
|
|
10
|
-
EditPlatform: vscode
|
|
11
|
-
ComputerInfo: XPS 15 9510
|
|
12
|
-
SystemInfo: Windows 11
|
|
13
|
-
Python Version: 3.12
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
# 会导致OAFuncs直接导入所有函数,不符合模块化设计
|
|
20
|
-
from .test import *
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python
|
|
2
|
-
# coding=utf-8
|
|
3
|
-
"""
|
|
4
|
-
Author: Liu Kun && 16031215@qq.com
|
|
5
|
-
Date: 2025-03-09 16:30:54
|
|
6
|
-
LastEditors: Liu Kun && 16031215@qq.com
|
|
7
|
-
LastEditTime: 2025-03-09 16:30:54
|
|
8
|
-
FilePath: \\Python\\My_Funcs\\OAFuncs\\oafuncs\\oa_model\\roms\\depth.py
|
|
9
|
-
Description:
|
|
10
|
-
EditPlatform: vscode
|
|
11
|
-
ComputerInfo: XPS 15 9510
|
|
12
|
-
SystemInfo: Windows 11
|
|
13
|
-
Python Version: 3.12
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
__all__ = ['test']
|
|
17
|
-
|
|
18
|
-
def test():
|
|
19
|
-
print('test')
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|