roms-tools 3.4.0__py3-none-any.whl → 3.5.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/datasets/lat_lon_datasets.py +12 -0
- roms_tools/datasets/roms_dataset.py +140 -53
- roms_tools/datasets/utils.py +14 -2
- roms_tools/regrid.py +76 -0
- roms_tools/setup/boundary_forcing.py +2 -2
- roms_tools/setup/grid.py +17 -3
- roms_tools/setup/initial_conditions.py +314 -55
- roms_tools/setup/mask.py +2 -5
- roms_tools/setup/nesting.py +6 -3
- roms_tools/setup/surface_forcing.py +1 -2
- roms_tools/setup/tides.py +6 -5
- roms_tools/setup/utils.py +220 -142
- roms_tools/tests/test_datasets/test_roms_dataset.py +225 -21
- roms_tools/tests/test_regrid.py +120 -1
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ALK/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ALK/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ALK_ALT_CO2/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ALK_ALT_CO2/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Cs_r/c/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Cs_r/zarr.json +47 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Cs_w/c/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Cs_w/zarr.json +47 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DIC/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DIC/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DIC_ALT_CO2/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DIC_ALT_CO2/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOC/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOC/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOCr/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOCr/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DON/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DON/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DONr/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DONr/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOP/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOP/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOPr/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/DOPr/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Fe/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Fe/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Lig/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/Lig/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/NH4/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/NH4/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/NO3/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/NO3/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/O2/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/O2/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/PO4/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/PO4/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/SiO3/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/SiO3/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/abs_time/zarr.json +47 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatC/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatC/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatChl/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatChl/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatFe/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatFe/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatP/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatP/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatSi/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diatSi/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazC/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazC/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazChl/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazChl/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazFe/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazFe/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazP/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/diazP/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ocean_time/c/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ocean_time/zarr.json +47 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/salt/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/salt/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spC/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spC/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spCaCO3/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spCaCO3/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spChl/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spChl/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spFe/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spFe/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spP/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/spP/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/temp/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/temp/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/u/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/u/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ubar/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/ubar/zarr.json +54 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/v/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/v/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/vbar/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/vbar/zarr.json +54 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/w/zarr.json +57 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/zarr.json +2481 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/zeta/c/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/zeta/zarr.json +54 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/zooC/c/0/0/0/0 +0 -0
- roms_tools/tests/test_setup/test_data/initial_conditions_from_roms.zarr/zooC/zarr.json +57 -0
- roms_tools/tests/test_setup/test_grid.py +24 -0
- roms_tools/tests/test_setup/test_initial_conditions.py +128 -11
- roms_tools/tests/test_setup/test_validation.py +15 -0
- roms_tools/tests/test_utils.py +287 -0
- roms_tools/utils.py +177 -72
- {roms_tools-3.4.0.dist-info → roms_tools-3.5.0.dist-info}/METADATA +2 -3
- {roms_tools-3.4.0.dist-info → roms_tools-3.5.0.dist-info}/RECORD +111 -24
- {roms_tools-3.4.0.dist-info → roms_tools-3.5.0.dist-info}/WHEEL +1 -1
- {roms_tools-3.4.0.dist-info → roms_tools-3.5.0.dist-info}/licenses/LICENSE +0 -0
- {roms_tools-3.4.0.dist-info → roms_tools-3.5.0.dist-info}/top_level.txt +0 -0
|
@@ -661,6 +661,18 @@ class LatLonDataset:
|
|
|
661
661
|
if "depth" in self.dim_names:
|
|
662
662
|
self.ds = extrapolate_deepest_to_bottom(self.ds, self.dim_names["depth"])
|
|
663
663
|
|
|
664
|
+
def rotate_velocities_to_east_and_north(self) -> None:
|
|
665
|
+
"""
|
|
666
|
+
Rotate velocity components to east/north directions.
|
|
667
|
+
|
|
668
|
+
For lat-lon datasets, velocity components are already defined in
|
|
669
|
+
earth-relative east/north coordinates. Therefore, no rotation is
|
|
670
|
+
required and this method is a no-op.
|
|
671
|
+
This method is provided for API compatibility with ROMSDataset,
|
|
672
|
+
where an explicit rotation using the grid angle is necessary.
|
|
673
|
+
"""
|
|
674
|
+
return None
|
|
675
|
+
|
|
664
676
|
@classmethod
|
|
665
677
|
def from_ds(cls, original_dataset: LatLonDataset, ds: xr.Dataset) -> LatLonDataset:
|
|
666
678
|
"""Substitute the internal dataset of a LatLonDataset object with a new xarray
|
|
@@ -18,7 +18,12 @@ from roms_tools.datasets.utils import (
|
|
|
18
18
|
validate_start_end_time,
|
|
19
19
|
)
|
|
20
20
|
from roms_tools.fill import LateralFill
|
|
21
|
-
from roms_tools.utils import
|
|
21
|
+
from roms_tools.utils import (
|
|
22
|
+
get_dask_chunks,
|
|
23
|
+
load_data,
|
|
24
|
+
rotate_velocities,
|
|
25
|
+
wrap_longitudes,
|
|
26
|
+
)
|
|
22
27
|
from roms_tools.vertical_coordinate import (
|
|
23
28
|
compute_depth_coordinates,
|
|
24
29
|
)
|
|
@@ -578,6 +583,12 @@ class ROMSDataset:
|
|
|
578
583
|
)
|
|
579
584
|
self.ds = subdomain
|
|
580
585
|
|
|
586
|
+
subdomain_grid_ds = choose_subdomain(
|
|
587
|
+
self.grid.ds, self.grid.ds, target_coords, buffer_points
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
self.grid = self.grid.copy_with_ds(subdomain_grid_ds)
|
|
591
|
+
|
|
581
592
|
def convert_to_float64(self) -> None:
|
|
582
593
|
"""Convert all data variables in the dataset to float64.
|
|
583
594
|
|
|
@@ -664,6 +675,45 @@ class ROMSDataset:
|
|
|
664
675
|
if filler is not None:
|
|
665
676
|
self.ds[var_name] = filler.apply(var)
|
|
666
677
|
|
|
678
|
+
def rotate_velocities_to_east_and_north(
|
|
679
|
+
self,
|
|
680
|
+
velocity_pairs: tuple[tuple[str, str], ...] = (("u", "v"),),
|
|
681
|
+
) -> None:
|
|
682
|
+
"""
|
|
683
|
+
Rotate model-grid velocity components to earth-relative east/north directions.
|
|
684
|
+
|
|
685
|
+
Parameters
|
|
686
|
+
----------
|
|
687
|
+
velocity_pairs : tuple of (str, str), optional
|
|
688
|
+
Pairs of velocity variable keys (as used in ``self.var_names``) to rotate,
|
|
689
|
+
e.g. ("u", "v") or ("ubar", "vbar"). By default, only the 3D velocities
|
|
690
|
+
("u", "v") are rotated.
|
|
691
|
+
"""
|
|
692
|
+
if self.var_names is None:
|
|
693
|
+
return
|
|
694
|
+
|
|
695
|
+
angle = -self.grid.ds["angle"]
|
|
696
|
+
|
|
697
|
+
for u_key, v_key in velocity_pairs:
|
|
698
|
+
if u_key not in self.var_names or v_key not in self.var_names:
|
|
699
|
+
continue
|
|
700
|
+
|
|
701
|
+
u_name = self.var_names[u_key]
|
|
702
|
+
v_name = self.var_names[v_key]
|
|
703
|
+
|
|
704
|
+
self.ds[u_name], self.ds[v_name] = rotate_velocities(
|
|
705
|
+
self.ds[u_name],
|
|
706
|
+
self.ds[v_name],
|
|
707
|
+
angle,
|
|
708
|
+
interpolate_before=True,
|
|
709
|
+
)
|
|
710
|
+
|
|
711
|
+
if self.use_dask:
|
|
712
|
+
chunks = get_dask_chunks(self.dim_names)
|
|
713
|
+
# Only keep chunks for dimensions that exist in the dataset
|
|
714
|
+
chunks = {dim: size for dim, size in chunks.items() if dim in self.ds.dims}
|
|
715
|
+
self.ds = self.ds.chunk(chunks)
|
|
716
|
+
|
|
667
717
|
|
|
668
718
|
def choose_subdomain(
|
|
669
719
|
ds: xr.Dataset,
|
|
@@ -699,69 +749,106 @@ def choose_subdomain(
|
|
|
699
749
|
ValueError
|
|
700
750
|
If the selected latitude or longitude range does not intersect with the dataset.
|
|
701
751
|
"""
|
|
702
|
-
#
|
|
703
|
-
ds = wrap_longitudes(ds, target_coords["straddle"])
|
|
704
|
-
|
|
752
|
+
# Extract lat/lon min/max from target
|
|
705
753
|
lat_min = target_coords["lat"].min().values
|
|
706
754
|
lat_max = target_coords["lat"].max().values
|
|
707
755
|
lon_min = target_coords["lon"].min().values
|
|
708
756
|
lon_max = target_coords["lon"].max().values
|
|
709
757
|
|
|
710
|
-
#
|
|
711
|
-
dx = 0.5 * ((1 / ds_grid.pm).mean() + (1 / ds_grid.pn).mean())
|
|
712
|
-
buffer = dx * buffer_points
|
|
758
|
+
# Compute buffer in degrees
|
|
759
|
+
dx = 0.5 * ((1 / ds_grid.pm).mean() + (1 / ds_grid.pn).mean())
|
|
760
|
+
buffer = dx * buffer_points
|
|
761
|
+
lat_center = np.deg2rad(0.5 * (lat_min + lat_max))
|
|
762
|
+
margin_lat = buffer / 111_320.0
|
|
763
|
+
margin_lon = buffer / (111_320.0 * np.cos(lat_center))
|
|
764
|
+
|
|
765
|
+
lon_min_buf = lon_min - margin_lon
|
|
766
|
+
lon_max_buf = lon_max + margin_lon
|
|
767
|
+
|
|
768
|
+
# Normalize buffered bounds to target convention
|
|
769
|
+
if target_coords["straddle"]:
|
|
770
|
+
# [-180, 180]
|
|
771
|
+
if lon_min_buf < -180:
|
|
772
|
+
lon_min_buf += 360
|
|
773
|
+
if lon_max_buf > 180:
|
|
774
|
+
lon_max_buf -= 360
|
|
775
|
+
else:
|
|
776
|
+
# [0, 360]
|
|
777
|
+
if lon_min_buf < 0:
|
|
778
|
+
lon_min_buf += 360
|
|
779
|
+
if lon_max_buf >= 360:
|
|
780
|
+
lon_max_buf -= 360
|
|
781
|
+
|
|
782
|
+
# Wrap dataset longitudes to target convention
|
|
783
|
+
ds = wrap_longitudes(ds, target_coords["straddle"])
|
|
713
784
|
|
|
714
|
-
|
|
785
|
+
# Rho points
|
|
786
|
+
location = "rho"
|
|
787
|
+
eta_dim, xi_dim = "eta_rho", "xi_rho"
|
|
788
|
+
lat_coord, lon_coord = f"lat_{location}", f"lon_{location}"
|
|
789
|
+
_check_latlon_coords(ds, eta_dim, xi_dim, location)
|
|
790
|
+
ds_lon = ds[lon_coord]
|
|
791
|
+
|
|
792
|
+
if lon_max_buf < lon_min_buf: # crosses dateline
|
|
793
|
+
subset_mask_lon = (ds_lon >= lon_min_buf) | (ds_lon <= lon_max_buf)
|
|
794
|
+
else:
|
|
795
|
+
subset_mask_lon = (ds_lon >= lon_min_buf) & (ds_lon <= lon_max_buf)
|
|
796
|
+
|
|
797
|
+
# Full mask including latitude
|
|
798
|
+
subset_mask = (
|
|
799
|
+
(ds[lat_coord] >= lat_min - margin_lat)
|
|
800
|
+
& (ds[lat_coord] <= lat_max + margin_lat)
|
|
801
|
+
& subset_mask_lon
|
|
802
|
+
)
|
|
715
803
|
|
|
716
|
-
|
|
717
|
-
|
|
804
|
+
eta_mask = subset_mask.any(dim=xi_dim)
|
|
805
|
+
xi_mask = subset_mask.any(dim=eta_dim)
|
|
806
|
+
eta_indices = np.where(eta_mask)[0]
|
|
807
|
+
xi_indices = np.where(xi_mask)[0]
|
|
808
|
+
first_eta, last_eta = eta_indices[0], eta_indices[-1]
|
|
809
|
+
first_xi, last_xi = xi_indices[0], xi_indices[-1]
|
|
810
|
+
|
|
811
|
+
# Subset rho points
|
|
812
|
+
ds = ds.isel(
|
|
813
|
+
**{
|
|
814
|
+
"eta_rho": slice(first_eta, last_eta + 1),
|
|
815
|
+
"xi_rho": slice(first_xi, last_xi + 1),
|
|
816
|
+
}
|
|
817
|
+
)
|
|
718
818
|
|
|
719
|
-
|
|
720
|
-
|
|
819
|
+
# Subset u points only if these dimensions exist
|
|
820
|
+
if "xi_u" in ds.dims:
|
|
821
|
+
ds = ds.isel(
|
|
822
|
+
**{
|
|
823
|
+
"xi_u": slice(first_xi, last_xi),
|
|
824
|
+
}
|
|
825
|
+
)
|
|
721
826
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
827
|
+
# Subset v points only if these dimensions exist
|
|
828
|
+
if "eta_v" in ds.dims:
|
|
829
|
+
ds = ds.isel(
|
|
830
|
+
**{
|
|
831
|
+
"eta_v": slice(first_eta, last_eta),
|
|
832
|
+
}
|
|
833
|
+
)
|
|
727
834
|
|
|
728
|
-
|
|
729
|
-
eta_dim, xi_dim = mapping[location]
|
|
835
|
+
return ds
|
|
730
836
|
|
|
731
|
-
if eta_dim in ds.dims and xi_dim in ds.dims:
|
|
732
|
-
# Check that lat/lon coordinates exist
|
|
733
|
-
lat_coord = f"lat_{location}"
|
|
734
|
-
lon_coord = f"lon_{location}"
|
|
735
|
-
if lat_coord not in ds.coords or lon_coord not in ds.coords:
|
|
736
|
-
raise ValueError(
|
|
737
|
-
f"Dataset is missing expected coordinates for location '{location}': "
|
|
738
|
-
f"expected '{lat_coord}' and '{lon_coord}'"
|
|
739
|
-
)
|
|
740
837
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
& (ds[lon_coord] < lon_max + margin_lon)
|
|
747
|
-
)
|
|
838
|
+
def _check_latlon_coords(
|
|
839
|
+
ds: xr.Dataset, eta_dim: str, xi_dim: str, location: str
|
|
840
|
+
) -> None:
|
|
841
|
+
"""
|
|
842
|
+
Ensure latitude and longitude coordinates exist for a given grid location.
|
|
748
843
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
# Reduce along eta_dim
|
|
755
|
-
xi_mask = subset_mask.any(dim=eta_dim)
|
|
756
|
-
xi_indices = np.where(xi_mask)[0]
|
|
757
|
-
first_xi, last_xi = xi_indices[0], xi_indices[-1]
|
|
758
|
-
|
|
759
|
-
# Subset the dataset
|
|
760
|
-
ds = ds.isel(
|
|
761
|
-
**{
|
|
762
|
-
eta_dim: slice(first_eta, last_eta + 1),
|
|
763
|
-
xi_dim: slice(first_xi, last_xi + 1),
|
|
764
|
-
}
|
|
765
|
-
)
|
|
844
|
+
Raises ValueError if the expected coordinates are missing.
|
|
845
|
+
"""
|
|
846
|
+
if eta_dim in ds.dims and xi_dim in ds.dims:
|
|
847
|
+
lat_coord = f"lat_{location}"
|
|
848
|
+
lon_coord = f"lon_{location}"
|
|
766
849
|
|
|
767
|
-
|
|
850
|
+
if lat_coord not in ds.coords or lon_coord not in ds.coords:
|
|
851
|
+
raise ValueError(
|
|
852
|
+
f"Dataset missing coordinates for location '{location}': "
|
|
853
|
+
f"expected '{lat_coord}' and '{lon_coord}'"
|
|
854
|
+
)
|
roms_tools/datasets/utils.py
CHANGED
|
@@ -36,9 +36,21 @@ def extrapolate_deepest_to_bottom(ds: xr.Dataset, depth_dim: str) -> xr.Dataset:
|
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
def convert_to_float64(ds: xr.Dataset) -> xr.Dataset:
|
|
39
|
-
"""Convert all
|
|
39
|
+
"""Convert all data variables in the dataset to float64.
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
This method updates the dataset by converting all of its data variables to the
|
|
42
|
+
`float64` data type, ensuring consistency for numerical operations that require
|
|
43
|
+
high precision. Variables whose names start with ``"mask_"`` are left unchanged.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
ds : xr.Dataset
|
|
48
|
+
Input dataset
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
xr.Dataset:
|
|
53
|
+
Input dataset with data variables converted to double precision.
|
|
42
54
|
"""
|
|
43
55
|
dtype_map = {
|
|
44
56
|
name: ("float64" if not name.startswith("mask_") else var.dtype)
|
roms_tools/regrid.py
CHANGED
|
@@ -295,3 +295,79 @@ class VerticalRegridFromROMS:
|
|
|
295
295
|
)
|
|
296
296
|
|
|
297
297
|
return transformed
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class VerticalRegrid:
|
|
301
|
+
"""Regrid ROMS variables along the vertical, using spatially varying coordinates.
|
|
302
|
+
|
|
303
|
+
This class uses the `xgcm` package to transform data from a ROMS vertical coordinate
|
|
304
|
+
system (`s_rho`) to a user-defined set of target depth levels, where both the source
|
|
305
|
+
and target coordinates can vary spatially (i.e., 2D fields in horizontal space).
|
|
306
|
+
|
|
307
|
+
Attributes
|
|
308
|
+
----------
|
|
309
|
+
grid : xgcm.Grid
|
|
310
|
+
The XGCM grid object used for vertical regridding, initialized with the input dataset `ds`.
|
|
311
|
+
"""
|
|
312
|
+
|
|
313
|
+
def __init__(self, ds: "xr.Dataset"):
|
|
314
|
+
"""Initialize the VerticalRegrid object with a ROMS dataset.
|
|
315
|
+
|
|
316
|
+
Parameters
|
|
317
|
+
----------
|
|
318
|
+
ds : xarray.Dataset
|
|
319
|
+
The ROMS dataset containing the vertical coordinate `s_rho` and the variable(s)
|
|
320
|
+
to be regridded.
|
|
321
|
+
"""
|
|
322
|
+
self.grid = xgcm.Grid(
|
|
323
|
+
ds,
|
|
324
|
+
coords={"s_rho": {"center": "s_rho"}},
|
|
325
|
+
periodic=False,
|
|
326
|
+
autoparse_metadata=False,
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
def apply(
|
|
330
|
+
self,
|
|
331
|
+
da: "xr.DataArray",
|
|
332
|
+
source_depth_coords: "xr.DataArray",
|
|
333
|
+
target_depth_coords: "xr.DataArray",
|
|
334
|
+
mask_edges: bool = True,
|
|
335
|
+
) -> "xr.DataArray":
|
|
336
|
+
"""Regrid a ROMS variable from source vertical coordinates to target vertical coordinates.
|
|
337
|
+
|
|
338
|
+
This method supports spatially varying vertical coordinates for both source and target,
|
|
339
|
+
meaning that the depth levels can vary across the horizontal grid.
|
|
340
|
+
|
|
341
|
+
Parameters
|
|
342
|
+
----------
|
|
343
|
+
da : xarray.DataArray
|
|
344
|
+
The data array to regrid. Must have a vertical dimension corresponding to `s_rho`.
|
|
345
|
+
|
|
346
|
+
source_depth_coords : array-like (1D or 2D)
|
|
347
|
+
Depth coordinates of the source data. Can be a 1D array (same for all horizontal points)
|
|
348
|
+
or a 2D array (varying in horizontal space).
|
|
349
|
+
|
|
350
|
+
target_depth_coords : array-like (1D or 2D)
|
|
351
|
+
Desired depth coordinates of the regridded data. Can also be 1D or 2D.
|
|
352
|
+
|
|
353
|
+
mask_edges : bool, optional
|
|
354
|
+
If True, target values outside the range of source depth coordinates are masked with NaN.
|
|
355
|
+
Defaults to True.
|
|
356
|
+
|
|
357
|
+
Returns
|
|
358
|
+
-------
|
|
359
|
+
xarray.DataArray
|
|
360
|
+
A new `DataArray` containing the regridded variable at the target depth coordinates.
|
|
361
|
+
"""
|
|
362
|
+
with warnings.catch_warnings():
|
|
363
|
+
warnings.filterwarnings("ignore", category=FutureWarning, module="xgcm")
|
|
364
|
+
transformed = self.grid.transform(
|
|
365
|
+
da,
|
|
366
|
+
"s_rho",
|
|
367
|
+
target=target_depth_coords,
|
|
368
|
+
target_data=source_depth_coords,
|
|
369
|
+
target_dim="s_rho",
|
|
370
|
+
mask_edges=mask_edges,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
return transformed
|
|
@@ -32,7 +32,6 @@ from roms_tools.setup.utils import (
|
|
|
32
32
|
get_variable_metadata,
|
|
33
33
|
group_dataset,
|
|
34
34
|
nan_check,
|
|
35
|
-
rotate_velocities,
|
|
36
35
|
substitute_nans_by_fillvalue,
|
|
37
36
|
to_dict,
|
|
38
37
|
write_to_yaml,
|
|
@@ -40,6 +39,7 @@ from roms_tools.setup.utils import (
|
|
|
40
39
|
from roms_tools.utils import (
|
|
41
40
|
interpolate_from_rho_to_u,
|
|
42
41
|
interpolate_from_rho_to_v,
|
|
42
|
+
rotate_velocities,
|
|
43
43
|
save_datasets,
|
|
44
44
|
transpose_dimensions,
|
|
45
45
|
)
|
|
@@ -273,7 +273,7 @@ class BoundaryForcing:
|
|
|
273
273
|
processed_fields["u"],
|
|
274
274
|
processed_fields["v"],
|
|
275
275
|
angle,
|
|
276
|
-
|
|
276
|
+
interpolate_after=True,
|
|
277
277
|
)
|
|
278
278
|
if self.adjust_depth_for_sea_surface_height:
|
|
279
279
|
zeta_u = interpolate_from_rho_to_u(zeta_vector)
|
roms_tools/setup/grid.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import copy
|
|
1
2
|
import importlib.metadata
|
|
2
3
|
import logging
|
|
3
4
|
import re
|
|
@@ -19,12 +20,14 @@ from roms_tools.setup.utils import (
|
|
|
19
20
|
extract_single_value,
|
|
20
21
|
gc_dist,
|
|
21
22
|
get_target_coords,
|
|
22
|
-
interpolate_from_rho_to_u,
|
|
23
|
-
interpolate_from_rho_to_v,
|
|
24
23
|
pop_grid_data,
|
|
25
24
|
write_to_yaml,
|
|
26
25
|
)
|
|
27
|
-
from roms_tools.utils import
|
|
26
|
+
from roms_tools.utils import (
|
|
27
|
+
interpolate_from_rho_to_u,
|
|
28
|
+
interpolate_from_rho_to_v,
|
|
29
|
+
save_datasets,
|
|
30
|
+
)
|
|
28
31
|
from roms_tools.vertical_coordinate import compute_depth_coordinates, sigma_stretch
|
|
29
32
|
|
|
30
33
|
|
|
@@ -1266,6 +1269,17 @@ class Grid:
|
|
|
1266
1269
|
|
|
1267
1270
|
return z_centers, z_faces
|
|
1268
1271
|
|
|
1272
|
+
def copy_with_ds(self, ds: xr.Dataset) -> "Grid":
|
|
1273
|
+
"""
|
|
1274
|
+
Return a copy of this Grid with the given Dataset.
|
|
1275
|
+
|
|
1276
|
+
Grid metadata is preserved; only the backing xarray Dataset
|
|
1277
|
+
is replaced. The original Grid is not modified.
|
|
1278
|
+
"""
|
|
1279
|
+
new = copy.copy(self) # shallow copy of metadata
|
|
1280
|
+
new.ds = ds
|
|
1281
|
+
return new
|
|
1282
|
+
|
|
1269
1283
|
|
|
1270
1284
|
def _rotate(coords, rot):
|
|
1271
1285
|
"""Rotate grid counterclockwise relative to surface of Earth by rot degrees."""
|