roms-tools 2.3.0__py3-none-any.whl → 2.4.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 +1 -0
- roms_tools/__init__.py +1 -0
- roms_tools/analysis/roms_output.py +10 -6
- roms_tools/setup/boundary_forcing.py +178 -193
- roms_tools/setup/datasets.py +58 -1
- roms_tools/setup/grid.py +31 -97
- roms_tools/setup/initial_conditions.py +172 -126
- roms_tools/setup/nesting.py +2 -23
- roms_tools/setup/river_forcing.py +34 -67
- roms_tools/setup/surface_forcing.py +111 -61
- roms_tools/setup/tides.py +7 -30
- roms_tools/setup/utils.py +24 -70
- roms_tools/tests/test_setup/test_boundary_forcing.py +220 -57
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/.zattrs +5 -3
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/.zmetadata +156 -121
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/abs_time/.zarray +2 -2
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/abs_time/.zattrs +2 -1
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/abs_time/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/bry_time/.zarray +2 -2
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/bry_time/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/bry_time/0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_east/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_north/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_south/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_west/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/salt_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_east/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_north/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_south/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_west/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/temp_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_east/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_north/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_south/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_west/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/u_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_east/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_east/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_north/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_north/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_south/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_south/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_west/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/ubar_west/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_east/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_east/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_north/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_north/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_south/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_south/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_west/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/v_west/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_east/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_east/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_north/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_north/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_south/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_south/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_west/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/vbar_west/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zeta_east/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zeta_east/.zattrs +8 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zeta_east/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zeta_north/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zeta_north/.zattrs +8 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zeta_north/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zeta_south/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zeta_south/.zattrs +8 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zeta_south/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zeta_west/.zarray +4 -4
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zeta_west/.zattrs +8 -0
- roms_tools/tests/test_setup/test_data/boundary_forcing.zarr/zeta_west/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/.zattrs +4 -4
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/.zmetadata +4 -4
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/angle/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/angle_coarse/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/f/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/h/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lat_coarse/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lat_rho/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lat_u/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lat_v/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lon_coarse/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lon_rho/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lon_u/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/lon_v/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_coarse/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_rho/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_u/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/mask_v/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/pm/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/grid_that_straddles_dateline.zarr/pn/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/.zattrs +2 -1
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/.zmetadata +6 -4
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/Cs_r/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/Cs_w/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/NH4/0.0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/NO3/0.0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/PO4/0.0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/abs_time/.zattrs +1 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatSi/0.0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/ocean_time/.zattrs +1 -1
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spC/0.0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spCaCO3/0.0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spFe/0.0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/temp/0.0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/u/0.0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/ubar/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/v/0.0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/vbar/0.0.0 +0 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/.zmetadata +30 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_location/.zarray +22 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_location/.zattrs +8 -0
- roms_tools/tests/test_setup/test_data/river_forcing_no_climatology.zarr/river_location/0.0 +0 -0
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/.zmetadata +30 -0
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/river_location/.zarray +22 -0
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/river_location/.zattrs +8 -0
- roms_tools/tests/test_setup/test_data/river_forcing_with_bgc.zarr/river_location/0.0 +0 -0
- roms_tools/tests/test_setup/test_grid.py +0 -13
- roms_tools/tests/test_setup/test_initial_conditions.py +204 -66
- roms_tools/tests/test_setup/test_nesting.py +0 -16
- roms_tools/tests/test_setup/test_river_forcing.py +8 -36
- roms_tools/tests/test_setup/test_surface_forcing.py +102 -73
- roms_tools/tests/test_setup/test_tides.py +4 -16
- roms_tools/tests/test_setup/test_utils.py +1 -0
- roms_tools/tests/{test_utils.py → test_tiling/test_partition.py} +1 -1
- roms_tools/tiling/partition.py +338 -0
- roms_tools/utils.py +66 -333
- roms_tools/vertical_coordinate.py +54 -133
- {roms_tools-2.3.0.dist-info → roms_tools-2.4.0.dist-info}/METADATA +1 -1
- {roms_tools-2.3.0.dist-info → roms_tools-2.4.0.dist-info}/RECORD +143 -136
- {roms_tools-2.3.0.dist-info → roms_tools-2.4.0.dist-info}/LICENSE +0 -0
- {roms_tools-2.3.0.dist-info → roms_tools-2.4.0.dist-info}/WHEEL +0 -0
- {roms_tools-2.3.0.dist-info → roms_tools-2.4.0.dist-info}/top_level.txt +0 -0
|
@@ -9,13 +9,13 @@ from pathlib import Path
|
|
|
9
9
|
import matplotlib.pyplot as plt
|
|
10
10
|
from roms_tools import Grid
|
|
11
11
|
from roms_tools.plot import _get_projection, _add_field_to_ax
|
|
12
|
+
from roms_tools.utils import save_datasets
|
|
12
13
|
from roms_tools.setup.datasets import DaiRiverDataset
|
|
13
14
|
from roms_tools.setup.utils import (
|
|
14
15
|
get_target_coords,
|
|
15
16
|
gc_dist,
|
|
16
17
|
substitute_nans_by_fillvalue,
|
|
17
18
|
convert_to_roms_time,
|
|
18
|
-
save_datasets,
|
|
19
19
|
_to_yaml,
|
|
20
20
|
_from_yaml,
|
|
21
21
|
get_variable_metadata,
|
|
@@ -91,8 +91,9 @@ class RiverForcing:
|
|
|
91
91
|
object.__setattr__(self, "original_indices", original_indices)
|
|
92
92
|
|
|
93
93
|
if len(original_indices["station"]) > 0:
|
|
94
|
-
self._move_rivers_to_closest_coast(target_coords, data)
|
|
95
94
|
ds = self._create_river_forcing(data)
|
|
95
|
+
self._move_rivers_to_closest_coast(target_coords, data)
|
|
96
|
+
ds = self._write_indices_into_dataset(ds)
|
|
96
97
|
self._validate(ds)
|
|
97
98
|
|
|
98
99
|
for var_name in ds.data_vars:
|
|
@@ -345,49 +346,44 @@ class RiverForcing:
|
|
|
345
346
|
"xi_rho": indices[2],
|
|
346
347
|
"name": names,
|
|
347
348
|
}
|
|
348
|
-
self._write_indices_into_grid_file(indices)
|
|
349
349
|
object.__setattr__(self, "updated_indices", indices)
|
|
350
350
|
|
|
351
|
-
def
|
|
352
|
-
"""
|
|
353
|
-
variable.
|
|
351
|
+
def _write_indices_into_dataset(self, ds):
|
|
352
|
+
"""Adds river location indices to the dataset as the "river_location" variable.
|
|
354
353
|
|
|
355
|
-
This method checks if the
|
|
356
|
-
If it does, the method removes it. Then, it creates a new
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
cell indices (eta_rho and xi_rho).
|
|
354
|
+
This method checks if the "river_location" variable already exists in the dataset.
|
|
355
|
+
If it does, the method removes it. Then, it creates a new "river_location" variable
|
|
356
|
+
using river station indices from `self.updated_indices` and assigns it to the dataset.
|
|
357
|
+
The indices specify the river station locations in terms of eta_rho and xi_rho grid cell indices.
|
|
360
358
|
|
|
361
|
-
Parameters
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
It must contain the following keys:
|
|
366
|
-
- "name" (list or array): Names of the river stations.
|
|
367
|
-
- "station" (list or array): River station identifiers.
|
|
368
|
-
- "eta_rho" (list or array): The eta (row) index for each river location.
|
|
369
|
-
- "xi_rho" (list or array): The xi (column) index for each river location.
|
|
359
|
+
Parameters
|
|
360
|
+
----------
|
|
361
|
+
ds : xarray.Dataset
|
|
362
|
+
The dataset to which the "river_location" variable will be added.
|
|
370
363
|
|
|
371
|
-
Returns
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
364
|
+
Returns
|
|
365
|
+
-------
|
|
366
|
+
xarray.Dataset
|
|
367
|
+
The modified dataset with the "river_location" variable added.
|
|
375
368
|
"""
|
|
376
369
|
|
|
377
|
-
if "
|
|
378
|
-
ds =
|
|
379
|
-
object.__setattr__(self.grid, "ds", ds)
|
|
370
|
+
if "river_location" in ds:
|
|
371
|
+
ds = ds.drop_vars("river_location")
|
|
380
372
|
|
|
381
373
|
river_locations = xr.zeros_like(self.grid.ds.mask_rho)
|
|
382
|
-
for i in range(len(
|
|
383
|
-
station =
|
|
384
|
-
eta_index =
|
|
385
|
-
xi_index =
|
|
374
|
+
for i in range(len(self.updated_indices["name"])):
|
|
375
|
+
station = self.updated_indices["station"][i]
|
|
376
|
+
eta_index = self.updated_indices["eta_rho"][i]
|
|
377
|
+
xi_index = self.updated_indices["xi_rho"][i]
|
|
386
378
|
river_locations[eta_index, xi_index] = station + 2
|
|
387
379
|
|
|
388
|
-
river_locations.attrs["long_name"] = "River volume
|
|
380
|
+
river_locations.attrs["long_name"] = "River index plus local volume fraction"
|
|
389
381
|
river_locations.attrs["units"] = "none"
|
|
390
|
-
|
|
382
|
+
ds["river_location"] = river_locations
|
|
383
|
+
|
|
384
|
+
ds = ds.drop_vars(["lat_rho", "lon_rho"])
|
|
385
|
+
|
|
386
|
+
return ds
|
|
391
387
|
|
|
392
388
|
def _validate(self, ds):
|
|
393
389
|
"""Validates the dataset by checking for NaN values in river forcing data.
|
|
@@ -592,37 +588,13 @@ class RiverForcing:
|
|
|
592
588
|
def save(
|
|
593
589
|
self,
|
|
594
590
|
filepath: Union[str, Path],
|
|
595
|
-
filepath_grid: Union[str, Path],
|
|
596
|
-
np_eta: int = None,
|
|
597
|
-
np_xi: int = None,
|
|
598
591
|
) -> None:
|
|
599
|
-
"""Save the river forcing
|
|
600
|
-
required because a new field `river_flux` has been added.
|
|
601
|
-
|
|
602
|
-
This method allows saving the river forcing and grid data either each as a single file or each partitioned into multiple files, based on the provided options. The dataset can be saved in two modes:
|
|
603
|
-
|
|
604
|
-
1. **Single File Mode (default)**:
|
|
605
|
-
- If both `np_eta` and `np_xi` are `None`, the entire dataset is saved as a single netCDF4 file.
|
|
606
|
-
- The file is named based on the provided `filepath`, with `.nc` automatically appended to the filename.
|
|
607
|
-
|
|
608
|
-
2. **Partitioned Mode**:
|
|
609
|
-
- If either `np_eta` or `np_xi` is specified, the dataset is partitioned spatially along the `eta` and `xi` axes into tiles.
|
|
610
|
-
- Each tile is saved as a separate netCDF4 file. Filenames will be modified with an index to represent each partition, e.g., `"filepath_YYYYMM.0.nc"`, `"filepath_YYYYMM.1.nc"`, etc.
|
|
592
|
+
"""Save the river forcing to netCDF4 file.
|
|
611
593
|
|
|
612
594
|
Parameters
|
|
613
595
|
----------
|
|
614
596
|
filepath : Union[str, Path]
|
|
615
|
-
The base path and filename for the output files.
|
|
616
|
-
If partitioning is used, additional indices will be appended to the filenames, e.g., `"filepath.0.nc"`, `"filepath.1.nc"`, etc.
|
|
617
|
-
|
|
618
|
-
filepath_grid : Union[str, Path]
|
|
619
|
-
The base path and filename for saving the grid file.
|
|
620
|
-
|
|
621
|
-
np_eta : int, optional
|
|
622
|
-
The number of partitions along the `eta` direction. If `None`, no spatial partitioning is performed along the `eta` axis.
|
|
623
|
-
|
|
624
|
-
np_xi : int, optional
|
|
625
|
-
The number of partitions along the `xi` direction. If `None`, no spatial partitioning is performed along the `xi` axis.
|
|
597
|
+
The base path and filename for the output files.
|
|
626
598
|
|
|
627
599
|
Returns
|
|
628
600
|
-------
|
|
@@ -632,20 +604,15 @@ class RiverForcing:
|
|
|
632
604
|
|
|
633
605
|
# Ensure filepath is a Path object
|
|
634
606
|
filepath = Path(filepath)
|
|
635
|
-
filepath_grid = Path(filepath_grid)
|
|
636
607
|
|
|
637
608
|
# Remove ".nc" suffix if present
|
|
638
609
|
if filepath.suffix == ".nc":
|
|
639
610
|
filepath = filepath.with_suffix("")
|
|
640
|
-
if filepath_grid.suffix == ".nc":
|
|
641
|
-
filepath_grid = filepath_grid.with_suffix("")
|
|
642
611
|
|
|
643
|
-
dataset_list = [self.ds
|
|
644
|
-
output_filenames = [str(filepath)
|
|
612
|
+
dataset_list = [self.ds]
|
|
613
|
+
output_filenames = [str(filepath)]
|
|
645
614
|
|
|
646
|
-
saved_filenames = save_datasets(
|
|
647
|
-
dataset_list, output_filenames, np_eta=np_eta, np_xi=np_xi
|
|
648
|
-
)
|
|
615
|
+
saved_filenames = save_datasets(dataset_list, output_filenames)
|
|
649
616
|
|
|
650
617
|
return saved_filenames
|
|
651
618
|
|
|
@@ -5,8 +5,10 @@ from datetime import datetime
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
import matplotlib.pyplot as plt
|
|
7
7
|
from pathlib import Path
|
|
8
|
+
import logging
|
|
8
9
|
from typing import Dict, Union, List
|
|
9
10
|
from roms_tools import Grid
|
|
11
|
+
from roms_tools.utils import save_datasets
|
|
10
12
|
from roms_tools.regrid import LateralRegrid
|
|
11
13
|
from roms_tools.plot import _plot
|
|
12
14
|
from roms_tools.setup.datasets import (
|
|
@@ -21,7 +23,6 @@ from roms_tools.setup.utils import (
|
|
|
21
23
|
interpolate_from_climatology,
|
|
22
24
|
get_variable_metadata,
|
|
23
25
|
group_dataset,
|
|
24
|
-
save_datasets,
|
|
25
26
|
rotate_velocities,
|
|
26
27
|
convert_to_roms_time,
|
|
27
28
|
_to_yaml,
|
|
@@ -59,9 +60,14 @@ class SurfaceForcing:
|
|
|
59
60
|
- "bgc": for biogeochemical forcing.
|
|
60
61
|
|
|
61
62
|
correct_radiation : bool
|
|
62
|
-
Whether to correct shortwave radiation. Default is
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
Whether to correct shortwave radiation. Default is True.
|
|
64
|
+
coarse_grid_mode : str, optional
|
|
65
|
+
Specifies whether to interpolate onto grid coarsened by a factor of two. Options are:
|
|
66
|
+
|
|
67
|
+
- "auto" (default): Automatically decide based on the comparison of source and target spatial resolutions.
|
|
68
|
+
- "always": Always interpolate onto the coarse grid.
|
|
69
|
+
- "never": Never use the coarse grid; interpolate onto the fine grid instead.
|
|
70
|
+
|
|
65
71
|
model_reference_date : datetime, optional
|
|
66
72
|
Reference date for the model. Default is January 1, 2000.
|
|
67
73
|
use_dask: bool, optional
|
|
@@ -88,8 +94,8 @@ class SurfaceForcing:
|
|
|
88
94
|
end_time: datetime
|
|
89
95
|
source: Dict[str, Union[str, Path, List[Union[str, Path]]]]
|
|
90
96
|
type: str = "physics"
|
|
91
|
-
correct_radiation: bool =
|
|
92
|
-
|
|
97
|
+
correct_radiation: bool = True
|
|
98
|
+
coarse_grid_mode: str = "auto"
|
|
93
99
|
model_reference_date: datetime = datetime(2000, 1, 1)
|
|
94
100
|
use_dask: bool = False
|
|
95
101
|
bypass_validation: bool = False
|
|
@@ -99,10 +105,23 @@ class SurfaceForcing:
|
|
|
99
105
|
def __post_init__(self):
|
|
100
106
|
|
|
101
107
|
self._input_checks()
|
|
108
|
+
data = self._get_data()
|
|
109
|
+
|
|
110
|
+
if self.coarse_grid_mode == "always":
|
|
111
|
+
use_coarse_grid = True
|
|
112
|
+
elif self.coarse_grid_mode == "never":
|
|
113
|
+
use_coarse_grid = False
|
|
114
|
+
elif self.coarse_grid_mode == "auto":
|
|
115
|
+
use_coarse_grid = self._determine_coarse_grid_usage(data)
|
|
116
|
+
if use_coarse_grid:
|
|
117
|
+
logging.info("Data will be interpolated onto grid coarsened by factor 2.")
|
|
118
|
+
else:
|
|
119
|
+
logging.info("Data will be interpolated onto fine grid.")
|
|
120
|
+
object.__setattr__(self, "use_coarse_grid", use_coarse_grid)
|
|
121
|
+
|
|
102
122
|
target_coords = get_target_coords(self.grid, self.use_coarse_grid)
|
|
103
123
|
object.__setattr__(self, "target_coords", target_coords)
|
|
104
124
|
|
|
105
|
-
data = self._get_data()
|
|
106
125
|
data.choose_subdomain(
|
|
107
126
|
target_coords,
|
|
108
127
|
buffer_points=20, # lateral fill needs some buffer from data margin
|
|
@@ -166,6 +185,47 @@ class SurfaceForcing:
|
|
|
166
185
|
{**self.source, "climatology": self.source.get("climatology", False)},
|
|
167
186
|
)
|
|
168
187
|
|
|
188
|
+
# Validate 'coarse_grid_mode'
|
|
189
|
+
valid_modes = ["auto", "always", "never"]
|
|
190
|
+
if self.coarse_grid_mode not in valid_modes:
|
|
191
|
+
raise ValueError(
|
|
192
|
+
f"`coarse_grid_mode` must be one of {valid_modes}, but got '{self.coarse_grid_mode}'."
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def _determine_coarse_grid_usage(self, data):
|
|
196
|
+
"""Determine if coarse grid interpolation should be used based on the resolution
|
|
197
|
+
of the dataset and the target grid.
|
|
198
|
+
|
|
199
|
+
Parameters
|
|
200
|
+
----------
|
|
201
|
+
data : object
|
|
202
|
+
The dataset object containing the data to be analyzed for grid spacing.
|
|
203
|
+
|
|
204
|
+
Returns
|
|
205
|
+
-------
|
|
206
|
+
use_coarse_grid : bool
|
|
207
|
+
Whether to use the coarse grid or not.
|
|
208
|
+
"""
|
|
209
|
+
# Get the target coordinates and select the subdomain of the data
|
|
210
|
+
target_coords = get_target_coords(self.grid, use_coarse_grid=False)
|
|
211
|
+
data_coords = data.choose_subdomain(
|
|
212
|
+
target_coords, buffer_points=1, return_coords_only=True
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Compute minimal grid spacing in the data subdomain
|
|
216
|
+
min_grid_spacing_data = data.compute_minimal_grid_spacing(data_coords)
|
|
217
|
+
|
|
218
|
+
# Compute the maximum grid spacing in the ROMS grid
|
|
219
|
+
max_grid_spacing = max((1 / self.grid.ds.pm).max(), (1 / self.grid.ds.pn).max())
|
|
220
|
+
|
|
221
|
+
# Determine whether to use coarse grid based on grid spacing comparison
|
|
222
|
+
if 2 * max_grid_spacing < min_grid_spacing_data:
|
|
223
|
+
use_coarse_grid = True
|
|
224
|
+
else:
|
|
225
|
+
use_coarse_grid = False
|
|
226
|
+
|
|
227
|
+
return use_coarse_grid
|
|
228
|
+
|
|
169
229
|
def _get_data(self):
|
|
170
230
|
|
|
171
231
|
data_dict = {
|
|
@@ -238,14 +298,14 @@ class SurfaceForcing:
|
|
|
238
298
|
"qair": {**default_info, "validate": True},
|
|
239
299
|
"rain": {**default_info, "validate": False},
|
|
240
300
|
"uwnd": {
|
|
241
|
-
"location": "
|
|
301
|
+
"location": "rho",
|
|
242
302
|
"is_vector": True,
|
|
243
303
|
"vector_pair": "vwnd",
|
|
244
304
|
"is_3d": False,
|
|
245
305
|
"validate": True,
|
|
246
306
|
},
|
|
247
307
|
"vwnd": {
|
|
248
|
-
"location": "
|
|
308
|
+
"location": "rho",
|
|
249
309
|
"is_vector": True,
|
|
250
310
|
"vector_pair": "uwnd",
|
|
251
311
|
"is_3d": False,
|
|
@@ -266,7 +326,7 @@ class SurfaceForcing:
|
|
|
266
326
|
def _apply_correction(self, processed_fields, data):
|
|
267
327
|
|
|
268
328
|
correction_data = self._get_correction_data()
|
|
269
|
-
#
|
|
329
|
+
# Match subdomain to forcing data to reuse the mask
|
|
270
330
|
coords_correction = {
|
|
271
331
|
"lat": data.ds[data.dim_names["latitude"]],
|
|
272
332
|
"lon": data.ds[data.dim_names["longitude"]],
|
|
@@ -275,23 +335,39 @@ class SurfaceForcing:
|
|
|
275
335
|
coords_correction, straddle=self.target_coords["straddle"]
|
|
276
336
|
)
|
|
277
337
|
correction_data.ds["mask"] = data.ds["mask"] # use mask from ERA5 data
|
|
338
|
+
correction_data.ds["time"] = correction_data.ds["time"].dt.days
|
|
339
|
+
|
|
278
340
|
correction_data.apply_lateral_fill()
|
|
279
|
-
# regrid
|
|
280
|
-
lateral_regrid = LateralRegrid(self.target_coords, correction_data.dim_names)
|
|
281
|
-
corr_factor = lateral_regrid.apply(
|
|
282
|
-
correction_data.ds[correction_data.var_names["swr_corr"]]
|
|
283
|
-
)
|
|
284
341
|
|
|
285
|
-
#
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
time
|
|
290
|
-
|
|
342
|
+
# Temporal interpolation: Perform before spatial regridding for better performance
|
|
343
|
+
if self.use_dask:
|
|
344
|
+
# Perform temporal interpolation for each time slice to enforce chunking in time.
|
|
345
|
+
# This reduces memory usage by processing one time step at a time.
|
|
346
|
+
# The interpolated slices are then concatenated along the "time" dimension.
|
|
347
|
+
corr_factor = xr.concat(
|
|
348
|
+
[
|
|
349
|
+
interpolate_from_climatology(
|
|
350
|
+
correction_data.ds[correction_data.var_names["swr_corr"]],
|
|
351
|
+
correction_data.dim_names["time"],
|
|
352
|
+
time=time,
|
|
353
|
+
)
|
|
354
|
+
for time in processed_fields["swrad"].time
|
|
355
|
+
],
|
|
356
|
+
dim="time",
|
|
357
|
+
)
|
|
358
|
+
else:
|
|
359
|
+
# Interpolate across all time steps at once
|
|
360
|
+
corr_factor = interpolate_from_climatology(
|
|
361
|
+
correction_data.ds[correction_data.var_names["swr_corr"]],
|
|
362
|
+
correction_data.dim_names["time"],
|
|
363
|
+
time=processed_fields["swrad"].time,
|
|
364
|
+
)
|
|
291
365
|
|
|
292
|
-
|
|
366
|
+
# Spatial regridding
|
|
367
|
+
lateral_regrid = LateralRegrid(self.target_coords, correction_data.dim_names)
|
|
368
|
+
corr_factor = lateral_regrid.apply(corr_factor)
|
|
293
369
|
|
|
294
|
-
|
|
370
|
+
processed_fields["swrad"] = processed_fields["swrad"] * corr_factor
|
|
295
371
|
|
|
296
372
|
return processed_fields
|
|
297
373
|
|
|
@@ -357,12 +433,8 @@ class SurfaceForcing:
|
|
|
357
433
|
|
|
358
434
|
for var_name in ds.data_vars:
|
|
359
435
|
if self.variable_info[var_name]["validate"]:
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
elif self.variable_info[var_name]["location"] == "u":
|
|
363
|
-
mask = self.target_coords["mask_u"]
|
|
364
|
-
elif self.variable_info[var_name]["location"] == "v":
|
|
365
|
-
mask = self.target_coords["mask_v"]
|
|
436
|
+
# all variables are at rho-points
|
|
437
|
+
mask = self.target_coords["mask"]
|
|
366
438
|
nan_check(ds[var_name].isel(time=0), mask)
|
|
367
439
|
|
|
368
440
|
def _add_global_metadata(self, ds=None):
|
|
@@ -476,42 +548,26 @@ class SurfaceForcing:
|
|
|
476
548
|
def save(
|
|
477
549
|
self,
|
|
478
550
|
filepath: Union[str, Path],
|
|
479
|
-
|
|
480
|
-
np_xi: int = None,
|
|
481
|
-
group: bool = False,
|
|
551
|
+
group: bool = True,
|
|
482
552
|
) -> None:
|
|
483
553
|
"""Save the surface forcing fields to one or more netCDF4 files.
|
|
484
554
|
|
|
485
|
-
This method saves the dataset
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
1. **Single File Mode (default)**:
|
|
489
|
-
- If both `np_eta` and `np_xi` are `None`, the entire dataset is saved as a single netCDF4 file.
|
|
490
|
-
- The file is named based on the `filepath`, with `.nc` automatically appended.
|
|
491
|
-
|
|
492
|
-
2. **Partitioned Mode**:
|
|
493
|
-
- If either `np_eta` or `np_xi` is specified, the dataset is partitioned into spatial tiles along the `eta` and `xi` axes.
|
|
494
|
-
- Each tile is saved as a separate netCDF4 file, and filenames are modified with an index (e.g., `"filepath_YYYYMM.0.nc"`, `"filepath_YYYYMM.1.nc"`).
|
|
495
|
-
|
|
496
|
-
Additionally, if `group` is set to `True`, the dataset is first grouped into temporal subsets, resulting in multiple grouped files before partitioning and saving.
|
|
555
|
+
This method saves the dataset to disk as either a single netCDF4 file or multiple files, depending on the `group` parameter.
|
|
556
|
+
If `group` is `True`, the dataset is divided into subsets (e.g., monthly or yearly) based on the temporal frequency
|
|
557
|
+
of the data, and each subset is saved to a separate file.
|
|
497
558
|
|
|
498
559
|
Parameters
|
|
499
560
|
----------
|
|
500
561
|
filepath : Union[str, Path]
|
|
501
|
-
The base path and filename for the output
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
The number of partitions along the `eta` direction. If `None`, no spatial partitioning is performed.
|
|
506
|
-
np_xi : int, optional
|
|
507
|
-
The number of partitions along the `xi` direction. If `None`, no spatial partitioning is performed.
|
|
508
|
-
group: bool, optional
|
|
509
|
-
If `True`, groups the dataset into multiple files based on temporal data frequency. Defaults to `False`.
|
|
562
|
+
The base path and filename for the output file(s). If `group` is `True`, the filenames will include additional
|
|
563
|
+
time-based information (e.g., year or month) to distinguish the subsets.
|
|
564
|
+
group : bool, optional
|
|
565
|
+
Whether to divide the dataset into multiple files based on temporal frequency. Defaults to `True`.
|
|
510
566
|
|
|
511
567
|
Returns
|
|
512
568
|
-------
|
|
513
569
|
List[Path]
|
|
514
|
-
A list of Path objects
|
|
570
|
+
A list of `Path` objects representing the filenames of the saved file(s).
|
|
515
571
|
"""
|
|
516
572
|
|
|
517
573
|
# Ensure filepath is a Path object
|
|
@@ -521,12 +577,6 @@ class SurfaceForcing:
|
|
|
521
577
|
if filepath.suffix == ".nc":
|
|
522
578
|
filepath = filepath.with_suffix("")
|
|
523
579
|
|
|
524
|
-
if self.use_dask:
|
|
525
|
-
from dask.diagnostics import ProgressBar
|
|
526
|
-
|
|
527
|
-
with ProgressBar():
|
|
528
|
-
self.ds.load()
|
|
529
|
-
|
|
530
580
|
if group:
|
|
531
581
|
dataset_list, output_filenames = group_dataset(self.ds, str(filepath))
|
|
532
582
|
else:
|
|
@@ -534,7 +584,7 @@ class SurfaceForcing:
|
|
|
534
584
|
output_filenames = [str(filepath)]
|
|
535
585
|
|
|
536
586
|
saved_filenames = save_datasets(
|
|
537
|
-
dataset_list, output_filenames,
|
|
587
|
+
dataset_list, output_filenames, use_dask=self.use_dask
|
|
538
588
|
)
|
|
539
589
|
|
|
540
590
|
return saved_filenames
|
roms_tools/setup/tides.py
CHANGED
|
@@ -9,6 +9,7 @@ from dataclasses import dataclass, field
|
|
|
9
9
|
from roms_tools import Grid
|
|
10
10
|
from roms_tools.plot import _plot
|
|
11
11
|
from roms_tools.regrid import LateralRegrid
|
|
12
|
+
from roms_tools.utils import save_datasets
|
|
12
13
|
from roms_tools.setup.datasets import TPXODataset
|
|
13
14
|
from roms_tools.setup.utils import (
|
|
14
15
|
nan_check,
|
|
@@ -16,7 +17,6 @@ from roms_tools.setup.utils import (
|
|
|
16
17
|
interpolate_from_rho_to_u,
|
|
17
18
|
interpolate_from_rho_to_v,
|
|
18
19
|
get_variable_metadata,
|
|
19
|
-
save_datasets,
|
|
20
20
|
get_target_coords,
|
|
21
21
|
rotate_velocities,
|
|
22
22
|
get_vector_pairs,
|
|
@@ -368,36 +368,19 @@ class TidalForcing:
|
|
|
368
368
|
kwargs=kwargs,
|
|
369
369
|
)
|
|
370
370
|
|
|
371
|
-
def save(
|
|
372
|
-
self, filepath: Union[str, Path], np_eta: int = None, np_xi: int = None
|
|
373
|
-
) -> None:
|
|
371
|
+
def save(self, filepath: Union[str, Path]) -> None:
|
|
374
372
|
"""Save the tidal forcing information to a netCDF4 file.
|
|
375
373
|
|
|
376
|
-
This method supports saving the dataset in two modes:
|
|
377
|
-
|
|
378
|
-
1. **Single File Mode (default)**:
|
|
379
|
-
|
|
380
|
-
If both `np_eta` and `np_xi` are `None`, the entire dataset is saved as a single netCDF4 file
|
|
381
|
-
with the base filename specified by `filepath.nc`.
|
|
382
|
-
|
|
383
|
-
2. **Partitioned Mode**:
|
|
384
|
-
|
|
385
|
-
- If either `np_eta` or `np_xi` is specified, the dataset is divided into spatial tiles along the eta-axis and xi-axis.
|
|
386
|
-
- Each spatial tile is saved as a separate netCDF4 file.
|
|
387
|
-
|
|
388
374
|
Parameters
|
|
389
375
|
----------
|
|
390
376
|
filepath : Union[str, Path]
|
|
391
|
-
The
|
|
392
|
-
|
|
393
|
-
The number of partitions along the `eta` direction. If `None`, no spatial partitioning is performed.
|
|
394
|
-
np_xi : int, optional
|
|
395
|
-
The number of partitions along the `xi` direction. If `None`, no spatial partitioning is performed.
|
|
377
|
+
The path or filename where the dataset will be saved. If a directory is specified,
|
|
378
|
+
the file will be saved with a default name within that directory.
|
|
396
379
|
|
|
397
380
|
Returns
|
|
398
381
|
-------
|
|
399
|
-
|
|
400
|
-
A
|
|
382
|
+
Path
|
|
383
|
+
A `Path` object representing the location of the saved file.
|
|
401
384
|
"""
|
|
402
385
|
|
|
403
386
|
# Ensure filepath is a Path object
|
|
@@ -407,17 +390,11 @@ class TidalForcing:
|
|
|
407
390
|
if filepath.suffix == ".nc":
|
|
408
391
|
filepath = filepath.with_suffix("")
|
|
409
392
|
|
|
410
|
-
if self.use_dask:
|
|
411
|
-
from dask.diagnostics import ProgressBar
|
|
412
|
-
|
|
413
|
-
with ProgressBar():
|
|
414
|
-
self.ds.load()
|
|
415
|
-
|
|
416
393
|
dataset_list = [self.ds]
|
|
417
394
|
output_filenames = [str(filepath)]
|
|
418
395
|
|
|
419
396
|
saved_filenames = save_datasets(
|
|
420
|
-
dataset_list, output_filenames,
|
|
397
|
+
dataset_list, output_filenames, use_dask=self.use_dask
|
|
421
398
|
)
|
|
422
399
|
|
|
423
400
|
return saved_filenames
|