roms-tools 2.6.2__py3-none-any.whl → 2.7.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.
- roms_tools/__init__.py +1 -0
- roms_tools/analysis/roms_output.py +11 -77
- roms_tools/analysis/utils.py +0 -66
- roms_tools/constants.py +2 -0
- roms_tools/download.py +46 -3
- roms_tools/plot.py +22 -5
- roms_tools/setup/cdr_forcing.py +1126 -0
- roms_tools/setup/datasets.py +742 -87
- roms_tools/setup/grid.py +42 -4
- roms_tools/setup/river_forcing.py +11 -84
- roms_tools/setup/tides.py +81 -411
- roms_tools/setup/utils.py +241 -37
- roms_tools/tests/test_setup/test_cdr_forcing.py +772 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/.zmetadata +53 -1
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_tracer/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/tracer_long_name/.zarray +20 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/tracer_long_name/.zattrs +6 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/tracer_long_name/0 +0 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/tracer_unit/.zarray +20 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/tracer_unit/.zattrs +6 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/tracer_unit/0 +0 -0
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/.zmetadata +53 -1
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/river_tracer/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/tracer_long_name/.zarray +20 -0
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/tracer_long_name/.zattrs +6 -0
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/tracer_long_name/0 +0 -0
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/tracer_unit/.zarray +20 -0
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/tracer_unit/.zattrs +6 -0
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/tracer_unit/0 +0 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/.zattrs +1 -2
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/.zmetadata +27 -5
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/ntides/.zarray +20 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/ntides/.zattrs +5 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/ntides/0 +0 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/omega/.zattrs +1 -3
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/pot_Im/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/pot_Re/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/ssh_Im/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/ssh_Re/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/u_Im/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/u_Re/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/v_Im/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/tidal_forcing.zarr/v_Re/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_datasets.py +103 -1
- roms_tools/tests/test_setup/test_tides.py +112 -47
- roms_tools/utils.py +115 -1
- {roms_tools-2.6.2.dist-info → roms_tools-2.7.0.dist-info}/METADATA +1 -1
- {roms_tools-2.6.2.dist-info → roms_tools-2.7.0.dist-info}/RECORD +51 -33
- {roms_tools-2.6.2.dist-info → roms_tools-2.7.0.dist-info}/WHEEL +1 -1
- {roms_tools-2.6.2.dist-info → roms_tools-2.7.0.dist-info}/licenses/LICENSE +0 -0
- {roms_tools-2.6.2.dist-info → roms_tools-2.7.0.dist-info}/top_level.txt +0 -0
roms_tools/setup/tides.py
CHANGED
|
@@ -10,7 +10,7 @@ from roms_tools import Grid
|
|
|
10
10
|
from roms_tools.plot import _plot
|
|
11
11
|
from roms_tools.regrid import LateralRegridToROMS
|
|
12
12
|
from roms_tools.utils import save_datasets
|
|
13
|
-
from roms_tools.setup.datasets import
|
|
13
|
+
from roms_tools.setup.datasets import TPXOManager
|
|
14
14
|
from roms_tools.setup.utils import (
|
|
15
15
|
nan_check,
|
|
16
16
|
substitute_nans_by_fillvalue,
|
|
@@ -33,20 +33,21 @@ class TidalForcing:
|
|
|
33
33
|
----------
|
|
34
34
|
grid : Grid
|
|
35
35
|
The grid object representing the ROMS grid associated with the tidal forcing data.
|
|
36
|
-
source : Dict[str, Union[str, Path,
|
|
36
|
+
source : Dict[str, Union[str, Path, Dict[str, Union[str, Path]]]]
|
|
37
37
|
Dictionary specifying the source of the tidal data. Keys include:
|
|
38
38
|
|
|
39
39
|
- "name" (str): Name of the data source (e.g., "TPXO").
|
|
40
|
-
- "path" (Union[str, Path,
|
|
40
|
+
- "path" (Union[str, Path, Dict[str, Union[str, Path]]]):
|
|
41
41
|
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
|
|
42
|
+
- If a string or Path is provided, it represents a single file.
|
|
43
|
+
- If "name" is "TPXO", "path" can also be a dictionary with the following keys:
|
|
44
|
+
|
|
45
|
+
- "grid" (Union[str, Path]): Path to the TPXO grid file.
|
|
46
|
+
- "h" (Union[str, Path]): Path to the TPXO h-file.
|
|
47
|
+
- "u" (Union[str, Path]): Path to the TPXO u-file.
|
|
45
48
|
|
|
46
49
|
ntides : int, optional
|
|
47
|
-
Number of constituents to consider. Maximum number is
|
|
48
|
-
allan_factor : float, optional
|
|
49
|
-
The Allan factor used in tidal model computation. Default is 2.0.
|
|
50
|
+
Number of constituents to consider. Maximum number is 15. Default is 10.
|
|
50
51
|
model_reference_date : datetime, optional
|
|
51
52
|
The reference date for the ROMS simulation. Default is datetime(2000, 1, 1).
|
|
52
53
|
use_dask: bool, optional
|
|
@@ -58,15 +59,27 @@ class TidalForcing:
|
|
|
58
59
|
|
|
59
60
|
Examples
|
|
60
61
|
--------
|
|
62
|
+
Using a TPXO dataset with separate grid, h, and u files:
|
|
63
|
+
|
|
61
64
|
>>> tidal_forcing = TidalForcing(
|
|
62
|
-
... grid=grid,
|
|
65
|
+
... grid=grid,
|
|
66
|
+
... source={
|
|
67
|
+
... "name": "TPXO",
|
|
68
|
+
... "path": {"grid": "tpxo_grid.nc", "h": "tpxo_h.nc", "u": "tpxo_u.nc"},
|
|
69
|
+
... },
|
|
70
|
+
... )
|
|
71
|
+
|
|
72
|
+
Using a single file as a source:
|
|
73
|
+
|
|
74
|
+
>>> tidal_forcing = TidalForcing(
|
|
75
|
+
... grid=grid,
|
|
76
|
+
... source={"name": "TPXO", "path": "tpxo_merged.nc"},
|
|
63
77
|
... )
|
|
64
78
|
"""
|
|
65
79
|
|
|
66
80
|
grid: Grid
|
|
67
81
|
source: Dict[str, Union[str, Path, List[Union[str, Path]]]]
|
|
68
82
|
ntides: int = 10
|
|
69
|
-
allan_factor: float = 2.0
|
|
70
83
|
model_reference_date: datetime = datetime(2000, 1, 1)
|
|
71
84
|
use_dask: bool = False
|
|
72
85
|
bypass_validation: bool = False
|
|
@@ -78,32 +91,35 @@ class TidalForcing:
|
|
|
78
91
|
self._input_checks()
|
|
79
92
|
target_coords = get_target_coords(self.grid)
|
|
80
93
|
|
|
81
|
-
|
|
82
|
-
data.check_number_constituents(self.ntides)
|
|
83
|
-
data.choose_subdomain(
|
|
84
|
-
target_coords,
|
|
85
|
-
buffer_points=20,
|
|
86
|
-
)
|
|
87
|
-
# Enforce double precision to ensure reproducibility
|
|
88
|
-
data.convert_to_float64()
|
|
94
|
+
tidal_data = self._get_data()
|
|
89
95
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
96
|
+
for key, data in tidal_data.datasets.items():
|
|
97
|
+
if key != "omega":
|
|
98
|
+
data.choose_subdomain(
|
|
99
|
+
target_coords,
|
|
100
|
+
buffer_points=20,
|
|
101
|
+
)
|
|
102
|
+
# Enforce double precision to ensure reproducibility
|
|
103
|
+
data.convert_to_float64()
|
|
93
104
|
|
|
94
|
-
|
|
105
|
+
tidal_data.correct_tides(self.model_reference_date)
|
|
95
106
|
|
|
96
107
|
self._set_variable_info()
|
|
97
108
|
var_names = self.variable_info.keys()
|
|
98
109
|
|
|
99
110
|
processed_fields = {}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
for
|
|
103
|
-
if
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
111
|
+
|
|
112
|
+
# lateral fill and regridding
|
|
113
|
+
for key, data in tidal_data.datasets.items():
|
|
114
|
+
if key != "omega":
|
|
115
|
+
data.apply_lateral_fill()
|
|
116
|
+
lateral_regrid = LateralRegridToROMS(target_coords, data.dim_names)
|
|
117
|
+
|
|
118
|
+
for var_name in var_names:
|
|
119
|
+
if var_name in data.var_names.keys():
|
|
120
|
+
processed_fields[var_name] = lateral_regrid.apply(
|
|
121
|
+
data.ds[data.var_names[var_name]]
|
|
122
|
+
)
|
|
107
123
|
|
|
108
124
|
# rotation of velocities and interpolation to u/v points
|
|
109
125
|
vector_pairs = get_vector_pairs(self.variable_info)
|
|
@@ -133,13 +149,15 @@ class TidalForcing:
|
|
|
133
149
|
|
|
134
150
|
d_meta = get_variable_metadata()
|
|
135
151
|
ds = self._write_into_dataset(processed_fields, d_meta)
|
|
136
|
-
ds["omega"] = data.ds["omega"]
|
|
137
152
|
|
|
138
153
|
ds = self._add_global_metadata(ds)
|
|
139
154
|
|
|
140
155
|
if not self.bypass_validation:
|
|
141
156
|
self._validate(ds)
|
|
142
157
|
|
|
158
|
+
ds = ds.assign_coords({"omega": tidal_data.datasets["omega"]})
|
|
159
|
+
ds["ntides"].attrs["long_name"] = "constituent label"
|
|
160
|
+
|
|
143
161
|
# substitute NaNs over land by a fill value to avoid blow-up of ROMS
|
|
144
162
|
for var_name in ds.data_vars:
|
|
145
163
|
ds[var_name] = substitute_nans_by_fillvalue(ds[var_name])
|
|
@@ -152,13 +170,40 @@ class TidalForcing:
|
|
|
152
170
|
raise ValueError("`source` must include a 'name'.")
|
|
153
171
|
if "path" not in self.source.keys():
|
|
154
172
|
raise ValueError("`source` must include a 'path'.")
|
|
173
|
+
if self.ntides > 15:
|
|
174
|
+
raise ValueError("`ntides` must be at most 15.")
|
|
155
175
|
|
|
156
176
|
def _get_data(self):
|
|
177
|
+
"""Loads tidal forcing data based on the specified source."""
|
|
157
178
|
|
|
158
179
|
if self.source["name"] == "TPXO":
|
|
159
|
-
|
|
180
|
+
if isinstance(self.source["path"], dict):
|
|
181
|
+
fname_dict = {
|
|
182
|
+
"grid": self.source["path"]["grid"],
|
|
183
|
+
"h": self.source["path"]["h"],
|
|
184
|
+
"u": self.source["path"]["u"],
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
elif isinstance(self.source["path"], (str, Path)):
|
|
188
|
+
fname_dict = {
|
|
189
|
+
"grid": self.source["path"],
|
|
190
|
+
"h": self.source["path"],
|
|
191
|
+
"u": self.source["path"],
|
|
192
|
+
}
|
|
193
|
+
else:
|
|
194
|
+
raise ValueError(
|
|
195
|
+
'For TPXO, source["path"] must be either a string, Path, or a dictionary with "grid", "h", and "u" keys.'
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
data = TPXOManager(
|
|
199
|
+
filenames=fname_dict,
|
|
200
|
+
ntides=self.ntides,
|
|
201
|
+
use_dask=self.use_dask,
|
|
202
|
+
)
|
|
203
|
+
|
|
160
204
|
else:
|
|
161
205
|
raise ValueError('Only "TPXO" is a valid option for source["name"].')
|
|
206
|
+
|
|
162
207
|
return data
|
|
163
208
|
|
|
164
209
|
def _set_variable_info(self):
|
|
@@ -168,7 +213,6 @@ class TidalForcing:
|
|
|
168
213
|
- `location`: Where the variable resides in the grid (e.g., rho, u, or v points).
|
|
169
214
|
- `is_vector`: Whether the variable is part of a vector (True for velocity components like 'u' and 'v').
|
|
170
215
|
- `vector_pair`: For vector variables, this indicates the associated variable that forms the vector (e.g., 'u' and 'v').
|
|
171
|
-
- `is_3d`: Indicates whether the variable is 3D (True for variables like 'temp' and 'salt') or 2D (False for 'zeta').
|
|
172
216
|
|
|
173
217
|
Returns
|
|
174
218
|
-------
|
|
@@ -179,7 +223,6 @@ class TidalForcing:
|
|
|
179
223
|
"location": "rho",
|
|
180
224
|
"is_vector": False,
|
|
181
225
|
"vector_pair": None,
|
|
182
|
-
"is_3d": False,
|
|
183
226
|
}
|
|
184
227
|
|
|
185
228
|
# Define a dictionary for variable names and their associated information
|
|
@@ -192,28 +235,24 @@ class TidalForcing:
|
|
|
192
235
|
"location": "u",
|
|
193
236
|
"is_vector": True,
|
|
194
237
|
"vector_pair": "v_Re",
|
|
195
|
-
"is_3d": False,
|
|
196
238
|
"validate": True,
|
|
197
239
|
},
|
|
198
240
|
"v_Re": {
|
|
199
241
|
"location": "v",
|
|
200
242
|
"is_vector": True,
|
|
201
243
|
"vector_pair": "u_Re",
|
|
202
|
-
"is_3d": False,
|
|
203
244
|
"validate": True,
|
|
204
245
|
},
|
|
205
246
|
"u_Im": {
|
|
206
247
|
"location": "u",
|
|
207
248
|
"is_vector": True,
|
|
208
249
|
"vector_pair": "v_Im",
|
|
209
|
-
"is_3d": False,
|
|
210
250
|
"validate": False,
|
|
211
251
|
},
|
|
212
252
|
"v_Im": {
|
|
213
253
|
"location": "v",
|
|
214
254
|
"is_vector": True,
|
|
215
255
|
"vector_pair": "u_Im",
|
|
216
|
-
"is_3d": False,
|
|
217
256
|
"validate": False,
|
|
218
257
|
},
|
|
219
258
|
}
|
|
@@ -247,7 +286,6 @@ class TidalForcing:
|
|
|
247
286
|
|
|
248
287
|
ds.attrs["source"] = self.source["name"]
|
|
249
288
|
ds.attrs["model_reference_date"] = str(self.model_reference_date)
|
|
250
|
-
ds.attrs["allan_factor"] = self.allan_factor
|
|
251
289
|
|
|
252
290
|
return ds
|
|
253
291
|
|
|
@@ -355,7 +393,10 @@ class TidalForcing:
|
|
|
355
393
|
lon_deg = xr.where(lon_deg > 180, lon_deg - 360, lon_deg)
|
|
356
394
|
field = field.assign_coords({"lon": lon_deg, "lat": lat_deg})
|
|
357
395
|
|
|
358
|
-
title = "%s,
|
|
396
|
+
title = "%s, constituent: %s" % (
|
|
397
|
+
field.long_name,
|
|
398
|
+
self.ds[var_name].ntides[ntides].values.item().decode("utf-8"),
|
|
399
|
+
)
|
|
359
400
|
|
|
360
401
|
vmax = max(field.max(), -field.min())
|
|
361
402
|
vmin = -vmax
|
|
@@ -443,374 +484,3 @@ class TidalForcing:
|
|
|
443
484
|
**tidal_forcing_params,
|
|
444
485
|
use_dask=use_dask,
|
|
445
486
|
)
|
|
446
|
-
|
|
447
|
-
def _correct_tides(self, data):
|
|
448
|
-
"""Apply tidal corrections to the dataset. This method corrects the dataset for
|
|
449
|
-
equilibrium tides, self-attraction and loading (SAL) effects, and adjusts phases
|
|
450
|
-
and amplitudes of tidal elevations and transports using Egbert's correction.
|
|
451
|
-
|
|
452
|
-
Parameters
|
|
453
|
-
----------
|
|
454
|
-
data : Dataset
|
|
455
|
-
The dataset containing tidal data, including variables for sea surface height (ssh), zonal and meridional
|
|
456
|
-
currents (u, v), and self-attraction and loading corrections (sal).
|
|
457
|
-
Returns
|
|
458
|
-
-------
|
|
459
|
-
None
|
|
460
|
-
The dataset is modified in-place with corrected real and imaginary components for ssh, u, v, and the
|
|
461
|
-
potential field ('pot_Re', 'pot_Im').
|
|
462
|
-
"""
|
|
463
|
-
|
|
464
|
-
# Get equilibrium tides
|
|
465
|
-
tpc = compute_equilibrium_tide(
|
|
466
|
-
data.ds[data.dim_names["longitude"]], data.ds[data.dim_names["latitude"]]
|
|
467
|
-
)
|
|
468
|
-
tpc = tpc.isel(ntides=data.ds["ntides"])
|
|
469
|
-
# Correct for SAL
|
|
470
|
-
tsc = self.allan_factor * (
|
|
471
|
-
data.ds[data.var_names["sal_Re"]] + 1j * data.ds[data.var_names["sal_Im"]]
|
|
472
|
-
)
|
|
473
|
-
tpc = tpc - tsc
|
|
474
|
-
|
|
475
|
-
# Elevations and transports
|
|
476
|
-
thc = data.ds[data.var_names["ssh_Re"]] + 1j * data.ds[data.var_names["ssh_Im"]]
|
|
477
|
-
tuc = data.ds[data.var_names["u_Re"]] + 1j * data.ds[data.var_names["u_Im"]]
|
|
478
|
-
tvc = data.ds[data.var_names["v_Re"]] + 1j * data.ds[data.var_names["v_Im"]]
|
|
479
|
-
|
|
480
|
-
# Apply correction for phases and amplitudes
|
|
481
|
-
pf, pu, aa = egbert_correction(self.model_reference_date)
|
|
482
|
-
pf = pf.isel(ntides=data.ds["ntides"])
|
|
483
|
-
pu = pu.isel(ntides=data.ds["ntides"])
|
|
484
|
-
aa = aa.isel(ntides=data.ds["ntides"])
|
|
485
|
-
|
|
486
|
-
dt = (self.model_reference_date - data.reference_date).days * 3600 * 24
|
|
487
|
-
|
|
488
|
-
thc = pf * thc * np.exp(1j * (data.ds["omega"] * dt + pu + aa))
|
|
489
|
-
tuc = pf * tuc * np.exp(1j * (data.ds["omega"] * dt + pu + aa))
|
|
490
|
-
tvc = pf * tvc * np.exp(1j * (data.ds["omega"] * dt + pu + aa))
|
|
491
|
-
tpc = pf * tpc * np.exp(1j * (data.ds["omega"] * dt + pu + aa))
|
|
492
|
-
|
|
493
|
-
data.ds[data.var_names["ssh_Re"]] = thc.real
|
|
494
|
-
data.ds[data.var_names["ssh_Im"]] = thc.imag
|
|
495
|
-
data.ds[data.var_names["u_Re"]] = tuc.real
|
|
496
|
-
data.ds[data.var_names["u_Im"]] = tuc.imag
|
|
497
|
-
data.ds[data.var_names["v_Re"]] = tvc.real
|
|
498
|
-
data.ds[data.var_names["v_Im"]] = tvc.imag
|
|
499
|
-
data.ds["pot_Re"] = tpc.real
|
|
500
|
-
data.ds["pot_Im"] = tpc.imag
|
|
501
|
-
|
|
502
|
-
# Update var_names dictionary
|
|
503
|
-
var_names = {**data.var_names, "pot_Re": "pot_Re", "pot_Im": "pot_Im"}
|
|
504
|
-
var_names.pop("sal_Re", None) # Remove "sal_Re" if it exists
|
|
505
|
-
var_names.pop("sal_Im", None) # Remove "sal_Im" if it exists
|
|
506
|
-
|
|
507
|
-
data.var_names = var_names
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
def modified_julian_days(year, month, day, hour=0):
|
|
511
|
-
"""Calculate the Modified Julian Day (MJD) for a given date and time.
|
|
512
|
-
|
|
513
|
-
The Modified Julian Day (MJD) is a modified Julian day count starting from
|
|
514
|
-
November 17, 1858 AD. It is commonly used in astronomy and geodesy.
|
|
515
|
-
|
|
516
|
-
Parameters
|
|
517
|
-
----------
|
|
518
|
-
year : int
|
|
519
|
-
The year.
|
|
520
|
-
month : int
|
|
521
|
-
The month (1-12).
|
|
522
|
-
day : int
|
|
523
|
-
The day of the month.
|
|
524
|
-
hour : float, optional
|
|
525
|
-
The hour of the day as a fractional number (0 to 23.999...). Default is 0.
|
|
526
|
-
|
|
527
|
-
Returns
|
|
528
|
-
-------
|
|
529
|
-
mjd : float
|
|
530
|
-
The Modified Julian Day (MJD) corresponding to the input date and time.
|
|
531
|
-
|
|
532
|
-
Notes
|
|
533
|
-
-----
|
|
534
|
-
The algorithm assumes that the input date (year, month, day) is within the
|
|
535
|
-
Gregorian calendar, i.e., after October 15, 1582. Negative MJD values are
|
|
536
|
-
allowed for dates before November 17, 1858.
|
|
537
|
-
|
|
538
|
-
References
|
|
539
|
-
----------
|
|
540
|
-
- Wikipedia article on Julian Day: https://en.wikipedia.org/wiki/Julian_day
|
|
541
|
-
- Wikipedia article on Modified Julian Day: https://en.wikipedia.org/wiki/Modified_Julian_day
|
|
542
|
-
|
|
543
|
-
Examples
|
|
544
|
-
--------
|
|
545
|
-
>>> modified_julian_days(2024, 5, 20, 12)
|
|
546
|
-
58814.0
|
|
547
|
-
>>> modified_julian_days(1858, 11, 17)
|
|
548
|
-
0.0
|
|
549
|
-
>>> modified_julian_days(1582, 10, 4)
|
|
550
|
-
-141428.5
|
|
551
|
-
"""
|
|
552
|
-
|
|
553
|
-
if month < 3:
|
|
554
|
-
year -= 1
|
|
555
|
-
month += 12
|
|
556
|
-
|
|
557
|
-
A = year // 100
|
|
558
|
-
B = A // 4
|
|
559
|
-
C = 2 - A + B
|
|
560
|
-
E = int(365.25 * (year + 4716))
|
|
561
|
-
F = int(30.6001 * (month + 1))
|
|
562
|
-
jd = C + day + hour / 24 + E + F - 1524.5
|
|
563
|
-
mjd = jd - 2400000.5
|
|
564
|
-
|
|
565
|
-
return mjd
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
def egbert_correction(date):
|
|
569
|
-
"""Correct phases and amplitudes for real-time runs using parts of the post-
|
|
570
|
-
processing code from Egbert's & Erofeeva's (OSU) TPXO model.
|
|
571
|
-
|
|
572
|
-
Parameters
|
|
573
|
-
----------
|
|
574
|
-
date : datetime.datetime
|
|
575
|
-
The date and time for which corrections are to be applied.
|
|
576
|
-
|
|
577
|
-
Returns
|
|
578
|
-
-------
|
|
579
|
-
pf : xr.DataArray
|
|
580
|
-
Amplitude scaling factor for each of the 15 tidal constituents.
|
|
581
|
-
pu : xr.DataArray
|
|
582
|
-
Phase correction [radians] for each of the 15 tidal constituents.
|
|
583
|
-
aa : xr.DataArray
|
|
584
|
-
Astronomical arguments [radians] associated with the corrections.
|
|
585
|
-
|
|
586
|
-
References
|
|
587
|
-
----------
|
|
588
|
-
- Egbert, G.D., and S.Y. Erofeeva. "Efficient inverse modeling of barotropic ocean
|
|
589
|
-
tides." Journal of Atmospheric and Oceanic Technology 19, no. 2 (2002): 183-204.
|
|
590
|
-
"""
|
|
591
|
-
|
|
592
|
-
year = date.year
|
|
593
|
-
month = date.month
|
|
594
|
-
day = date.day
|
|
595
|
-
hour = date.hour
|
|
596
|
-
minute = date.minute
|
|
597
|
-
second = date.second
|
|
598
|
-
|
|
599
|
-
rad = np.pi / 180.0
|
|
600
|
-
deg = 180.0 / np.pi
|
|
601
|
-
mjd = modified_julian_days(year, month, day)
|
|
602
|
-
tstart = mjd + hour / 24 + minute / (60 * 24) + second / (60 * 60 * 24)
|
|
603
|
-
|
|
604
|
-
# Determine nodal corrections pu & pf : these expressions are valid for period 1990-2010 (Cartwright 1990).
|
|
605
|
-
# Reset time origin for astronomical arguments to 4th of May 1860:
|
|
606
|
-
timetemp = tstart - 51544.4993
|
|
607
|
-
|
|
608
|
-
# mean longitude of lunar perigee
|
|
609
|
-
P = 83.3535 + 0.11140353 * timetemp
|
|
610
|
-
P = np.mod(P, 360.0)
|
|
611
|
-
if P < 0:
|
|
612
|
-
P = +360
|
|
613
|
-
P *= rad
|
|
614
|
-
|
|
615
|
-
# mean longitude of ascending lunar node
|
|
616
|
-
N = 125.0445 - 0.05295377 * timetemp
|
|
617
|
-
N = np.mod(N, 360.0)
|
|
618
|
-
if N < 0:
|
|
619
|
-
N = +360
|
|
620
|
-
N *= rad
|
|
621
|
-
|
|
622
|
-
sinn = np.sin(N)
|
|
623
|
-
cosn = np.cos(N)
|
|
624
|
-
sin2n = np.sin(2 * N)
|
|
625
|
-
cos2n = np.cos(2 * N)
|
|
626
|
-
sin3n = np.sin(3 * N)
|
|
627
|
-
|
|
628
|
-
pftmp = np.sqrt(
|
|
629
|
-
(1 - 0.03731 * cosn + 0.00052 * cos2n) ** 2
|
|
630
|
-
+ (0.03731 * sinn - 0.00052 * sin2n) ** 2
|
|
631
|
-
) # 2N2
|
|
632
|
-
|
|
633
|
-
pf = np.zeros(15)
|
|
634
|
-
pf[0] = pftmp # M2
|
|
635
|
-
pf[1] = 1.0 # S2
|
|
636
|
-
pf[2] = pftmp # N2
|
|
637
|
-
pf[3] = np.sqrt(
|
|
638
|
-
(1 + 0.2852 * cosn + 0.0324 * cos2n) ** 2
|
|
639
|
-
+ (0.3108 * sinn + 0.0324 * sin2n) ** 2
|
|
640
|
-
) # K2
|
|
641
|
-
pf[4] = np.sqrt(
|
|
642
|
-
(1 + 0.1158 * cosn - 0.0029 * cos2n) ** 2
|
|
643
|
-
+ (0.1554 * sinn - 0.0029 * sin2n) ** 2
|
|
644
|
-
) # K1
|
|
645
|
-
pf[5] = np.sqrt(
|
|
646
|
-
(1 + 0.189 * cosn - 0.0058 * cos2n) ** 2 + (0.189 * sinn - 0.0058 * sin2n) ** 2
|
|
647
|
-
) # O1
|
|
648
|
-
pf[6] = 1.0 # P1
|
|
649
|
-
pf[7] = np.sqrt((1 + 0.188 * cosn) ** 2 + (0.188 * sinn) ** 2) # Q1
|
|
650
|
-
pf[8] = 1.043 + 0.414 * cosn # Mf
|
|
651
|
-
pf[9] = 1.0 - 0.130 * cosn # Mm
|
|
652
|
-
pf[10] = pftmp**2 # M4
|
|
653
|
-
pf[11] = pftmp**2 # Mn4
|
|
654
|
-
pf[12] = pftmp**2 # Ms4
|
|
655
|
-
pf[13] = pftmp # 2n2
|
|
656
|
-
pf[14] = 1.0 # S1
|
|
657
|
-
pf = xr.DataArray(pf, dims="ntides")
|
|
658
|
-
|
|
659
|
-
putmp = (
|
|
660
|
-
np.arctan(
|
|
661
|
-
(-0.03731 * sinn + 0.00052 * sin2n)
|
|
662
|
-
/ (1.0 - 0.03731 * cosn + 0.00052 * cos2n)
|
|
663
|
-
)
|
|
664
|
-
* deg
|
|
665
|
-
) # 2N2
|
|
666
|
-
|
|
667
|
-
pu = np.zeros(15)
|
|
668
|
-
pu[0] = putmp # M2
|
|
669
|
-
pu[1] = 0.0 # S2
|
|
670
|
-
pu[2] = putmp # N2
|
|
671
|
-
pu[3] = (
|
|
672
|
-
np.arctan(
|
|
673
|
-
-(0.3108 * sinn + 0.0324 * sin2n) / (1.0 + 0.2852 * cosn + 0.0324 * cos2n)
|
|
674
|
-
)
|
|
675
|
-
* deg
|
|
676
|
-
) # K2
|
|
677
|
-
pu[4] = (
|
|
678
|
-
np.arctan(
|
|
679
|
-
(-0.1554 * sinn + 0.0029 * sin2n) / (1.0 + 0.1158 * cosn - 0.0029 * cos2n)
|
|
680
|
-
)
|
|
681
|
-
* deg
|
|
682
|
-
) # K1
|
|
683
|
-
pu[5] = 10.8 * sinn - 1.3 * sin2n + 0.2 * sin3n # O1
|
|
684
|
-
pu[6] = 0.0 # P1
|
|
685
|
-
pu[7] = np.arctan(0.189 * sinn / (1.0 + 0.189 * cosn)) * deg # Q1
|
|
686
|
-
pu[8] = -23.7 * sinn + 2.7 * sin2n - 0.4 * sin3n # Mf
|
|
687
|
-
pu[9] = 0.0 # Mm
|
|
688
|
-
pu[10] = putmp * 2.0 # M4
|
|
689
|
-
pu[11] = putmp * 2.0 # Mn4
|
|
690
|
-
pu[12] = putmp # Ms4
|
|
691
|
-
pu[13] = putmp # 2n2
|
|
692
|
-
pu[14] = 0.0 # S1
|
|
693
|
-
pu = xr.DataArray(pu, dims="ntides")
|
|
694
|
-
# convert from degrees to radians
|
|
695
|
-
pu = pu * rad
|
|
696
|
-
|
|
697
|
-
aa = xr.DataArray(
|
|
698
|
-
data=np.array(
|
|
699
|
-
[
|
|
700
|
-
1.731557546, # M2
|
|
701
|
-
0.0, # S2
|
|
702
|
-
6.050721243, # N2
|
|
703
|
-
3.487600001, # K2
|
|
704
|
-
0.173003674, # K1
|
|
705
|
-
1.558553872, # O1
|
|
706
|
-
6.110181633, # P1
|
|
707
|
-
5.877717569, # Q1
|
|
708
|
-
1.964021610, # Mm
|
|
709
|
-
1.756042456, # Mf
|
|
710
|
-
3.463115091, # M4
|
|
711
|
-
1.499093481, # Mn4
|
|
712
|
-
1.731557546, # Ms4
|
|
713
|
-
4.086699633, # 2n2
|
|
714
|
-
0.0, # S1
|
|
715
|
-
]
|
|
716
|
-
),
|
|
717
|
-
dims="ntides",
|
|
718
|
-
)
|
|
719
|
-
|
|
720
|
-
return pf, pu, aa
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
def compute_equilibrium_tide(lon, lat):
|
|
724
|
-
"""Compute equilibrium tide for given longitudes and latitudes.
|
|
725
|
-
|
|
726
|
-
Parameters
|
|
727
|
-
----------
|
|
728
|
-
lon : xr.DataArray
|
|
729
|
-
Longitudes in degrees.
|
|
730
|
-
lat : xr.DataArray
|
|
731
|
-
Latitudes in degrees.
|
|
732
|
-
|
|
733
|
-
Returns
|
|
734
|
-
-------
|
|
735
|
-
tpc : xr.DataArray
|
|
736
|
-
Equilibrium tide complex amplitude.
|
|
737
|
-
|
|
738
|
-
Notes
|
|
739
|
-
-----
|
|
740
|
-
This method computes the equilibrium tide complex amplitude for given longitudes
|
|
741
|
-
and latitudes. It considers 15 tidal constituents and their corresponding
|
|
742
|
-
amplitudes and elasticity factors. The types of tides are classified as follows:
|
|
743
|
-
- 2: semidiurnal
|
|
744
|
-
- 1: diurnal
|
|
745
|
-
- 0: long-term
|
|
746
|
-
"""
|
|
747
|
-
|
|
748
|
-
# Amplitudes and elasticity factors for 15 tidal constituents
|
|
749
|
-
A = xr.DataArray(
|
|
750
|
-
data=np.array(
|
|
751
|
-
[
|
|
752
|
-
0.242334, # M2
|
|
753
|
-
0.112743, # S2
|
|
754
|
-
0.046397, # N2
|
|
755
|
-
0.030684, # K2
|
|
756
|
-
0.141565, # K1
|
|
757
|
-
0.100661, # O1
|
|
758
|
-
0.046848, # P1
|
|
759
|
-
0.019273, # Q1
|
|
760
|
-
0.042041, # Mf
|
|
761
|
-
0.022191, # Mm
|
|
762
|
-
0.0, # M4
|
|
763
|
-
0.0, # Mn4
|
|
764
|
-
0.0, # Ms4
|
|
765
|
-
0.006141, # 2n2
|
|
766
|
-
0.000764, # S1
|
|
767
|
-
]
|
|
768
|
-
),
|
|
769
|
-
dims="ntides",
|
|
770
|
-
)
|
|
771
|
-
B = xr.DataArray(
|
|
772
|
-
data=np.array(
|
|
773
|
-
[
|
|
774
|
-
0.693, # M2
|
|
775
|
-
0.693, # S2
|
|
776
|
-
0.693, # N2
|
|
777
|
-
0.693, # K2
|
|
778
|
-
0.736, # K1
|
|
779
|
-
0.695, # O1
|
|
780
|
-
0.706, # P1
|
|
781
|
-
0.695, # Q1
|
|
782
|
-
0.693, # Mf
|
|
783
|
-
0.693, # Mm
|
|
784
|
-
0.693, # M4
|
|
785
|
-
0.693, # Mn4
|
|
786
|
-
0.693, # Ms4
|
|
787
|
-
0.693, # 2n2
|
|
788
|
-
0.693, # S1
|
|
789
|
-
]
|
|
790
|
-
),
|
|
791
|
-
dims="ntides",
|
|
792
|
-
)
|
|
793
|
-
|
|
794
|
-
# types: 2 = semidiurnal, 1 = diurnal, 0 = long-term
|
|
795
|
-
ityp = xr.DataArray(
|
|
796
|
-
data=np.array([2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 2, 1]), dims="ntides"
|
|
797
|
-
)
|
|
798
|
-
|
|
799
|
-
d2r = np.pi / 180
|
|
800
|
-
coslat2 = np.cos(d2r * lat) ** 2
|
|
801
|
-
sin2lat = np.sin(2 * d2r * lat)
|
|
802
|
-
|
|
803
|
-
p_amp = (
|
|
804
|
-
xr.where(ityp == 2, 1, 0) * A * B * coslat2 # semidiurnal
|
|
805
|
-
+ xr.where(ityp == 1, 1, 0) * A * B * sin2lat # diurnal
|
|
806
|
-
+ xr.where(ityp == 0, 1, 0) * A * B * (0.5 - 1.5 * coslat2) # long-term
|
|
807
|
-
)
|
|
808
|
-
p_pha = (
|
|
809
|
-
xr.where(ityp == 2, 1, 0) * (-2 * lon * d2r) # semidiurnal
|
|
810
|
-
+ xr.where(ityp == 1, 1, 0) * (-lon * d2r) # diurnal
|
|
811
|
-
+ xr.where(ityp == 0, 1, 0) * xr.zeros_like(lon) # long-term
|
|
812
|
-
)
|
|
813
|
-
|
|
814
|
-
tpc = p_amp * np.exp(-1j * p_pha)
|
|
815
|
-
|
|
816
|
-
return tpc
|