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
roms_tools/setup/utils.py
CHANGED
|
@@ -3,7 +3,6 @@ import numpy as np
|
|
|
3
3
|
from typing import Union, Any, Dict, Type
|
|
4
4
|
import pandas as pd
|
|
5
5
|
import cftime
|
|
6
|
-
from roms_tools.utils import partition
|
|
7
6
|
from pathlib import Path
|
|
8
7
|
from datetime import datetime
|
|
9
8
|
from dataclasses import fields, asdict
|
|
@@ -131,23 +130,36 @@ def interpolate_from_climatology(
|
|
|
131
130
|
time_dim_name: str,
|
|
132
131
|
time: Union[xr.DataArray, pd.DatetimeIndex],
|
|
133
132
|
) -> Union[xr.DataArray, xr.Dataset]:
|
|
134
|
-
"""
|
|
133
|
+
"""Temporally interpolates a field based on specified time points.
|
|
135
134
|
|
|
136
|
-
|
|
135
|
+
This function performs temporal interpolation on the input `field` to match the provided `time` values.
|
|
136
|
+
If the input `field` is an `xarray.Dataset`, the interpolation is applied to all its data variables individually.
|
|
137
137
|
|
|
138
138
|
Parameters
|
|
139
139
|
----------
|
|
140
140
|
field : xarray.DataArray or xarray.Dataset
|
|
141
|
-
The field
|
|
141
|
+
The input field to be interpolated.
|
|
142
|
+
- If `field` is an `xarray.DataArray`, it should have a time dimension identified by `time_dim_name`.
|
|
143
|
+
- If `field` is an `xarray.Dataset`, all variables within the dataset are interpolated along the specified time dimension.
|
|
144
|
+
The time dimension is assumed to represent `day_of_year` for climatological purposes.
|
|
142
145
|
time_dim_name : str
|
|
143
|
-
The name of the dimension in `field
|
|
146
|
+
The name of the time dimension in the `field`. This dimension is used for interpolation.
|
|
144
147
|
time : xarray.DataArray or pandas.DatetimeIndex
|
|
145
|
-
The target time points for interpolation.
|
|
148
|
+
The target time points for interpolation. The time values should be compatible with the time format used in the `field`.
|
|
146
149
|
|
|
147
150
|
Returns
|
|
148
151
|
-------
|
|
149
152
|
xarray.DataArray or xarray.Dataset
|
|
150
|
-
The field
|
|
153
|
+
The interpolated field, with the same type as the input (`xarray.DataArray` or `xarray.Dataset`),
|
|
154
|
+
but aligned to the specified `time` values.
|
|
155
|
+
|
|
156
|
+
Notes
|
|
157
|
+
-----
|
|
158
|
+
- The interpolation assumes the time dimension in `field` corresponds to `day_of_year`.
|
|
159
|
+
If the input time values are in a datetime format, ensure they are converted to `day_of_year` before calling this function.
|
|
160
|
+
For example, you can preprocess the time as follows:
|
|
161
|
+
|
|
162
|
+
>>> field["time"] = field["time"].dt.dayofyear
|
|
151
163
|
"""
|
|
152
164
|
|
|
153
165
|
def interpolate_single_field(data_array: xr.DataArray) -> xr.DataArray:
|
|
@@ -157,11 +169,11 @@ def interpolate_from_climatology(
|
|
|
157
169
|
day_of_year = time.dt.dayofyear
|
|
158
170
|
else:
|
|
159
171
|
if np.size(time) == 1:
|
|
160
|
-
|
|
172
|
+
# Convert single datetime64 object to pandas.Timestamp
|
|
173
|
+
day_of_year = pd.Timestamp(time).dayofyear
|
|
161
174
|
else:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
data_array[time_dim_name] = data_array[time_dim_name].dt.days
|
|
175
|
+
# Convert each datetime64 object in the array to pandas.Timestamp
|
|
176
|
+
day_of_year = np.array([pd.Timestamp(t).dayofyear for t in time])
|
|
165
177
|
|
|
166
178
|
# Concatenate across the beginning and end of the year
|
|
167
179
|
time_concat = xr.concat(
|
|
@@ -201,6 +213,7 @@ def interpolate_from_climatology(
|
|
|
201
213
|
for var, data_array in field.data_vars.items()
|
|
202
214
|
}
|
|
203
215
|
return xr.Dataset(interpolated_data_vars, attrs=field.attrs)
|
|
216
|
+
|
|
204
217
|
else:
|
|
205
218
|
raise TypeError("Input 'field' must be an xarray.DataArray or xarray.Dataset.")
|
|
206
219
|
|
|
@@ -568,59 +581,6 @@ def group_by_year(ds, filepath):
|
|
|
568
581
|
return dataset_list, output_filenames
|
|
569
582
|
|
|
570
583
|
|
|
571
|
-
def save_datasets(dataset_list, output_filenames, np_eta=None, np_xi=None):
|
|
572
|
-
"""Save the list of datasets to netCDF4 files, with optional spatial partitioning.
|
|
573
|
-
|
|
574
|
-
Parameters
|
|
575
|
-
----------
|
|
576
|
-
dataset_list : list
|
|
577
|
-
List of datasets to be saved.
|
|
578
|
-
output_filenames : list
|
|
579
|
-
List of filenames for the output files.
|
|
580
|
-
np_eta : int, optional
|
|
581
|
-
The number of partitions along the `eta` direction. If `None`, no spatial partitioning is performed.
|
|
582
|
-
np_xi : int, optional
|
|
583
|
-
The number of partitions along the `xi` direction. If `None`, no spatial partitioning is performed.
|
|
584
|
-
|
|
585
|
-
Returns
|
|
586
|
-
-------
|
|
587
|
-
List[Path]
|
|
588
|
-
A list of Path objects for the filenames that were saved.
|
|
589
|
-
"""
|
|
590
|
-
|
|
591
|
-
saved_filenames = []
|
|
592
|
-
|
|
593
|
-
if np_eta is None and np_xi is None:
|
|
594
|
-
# Save the dataset as a single file
|
|
595
|
-
output_filenames = [f"{filename}.nc" for filename in output_filenames]
|
|
596
|
-
xr.save_mfdataset(dataset_list, output_filenames)
|
|
597
|
-
|
|
598
|
-
saved_filenames.extend(Path(f) for f in output_filenames)
|
|
599
|
-
|
|
600
|
-
else:
|
|
601
|
-
# Partition the dataset and save each partition as a separate file
|
|
602
|
-
np_eta = np_eta or 1
|
|
603
|
-
np_xi = np_xi or 1
|
|
604
|
-
|
|
605
|
-
partitioned_datasets = []
|
|
606
|
-
partitioned_filenames = []
|
|
607
|
-
for dataset, base_filename in zip(dataset_list, output_filenames):
|
|
608
|
-
partition_indices, partitions = partition(
|
|
609
|
-
dataset, np_eta=np_eta, np_xi=np_xi
|
|
610
|
-
)
|
|
611
|
-
partition_filenames = [
|
|
612
|
-
f"{base_filename}.{index}.nc" for index in partition_indices
|
|
613
|
-
]
|
|
614
|
-
partitioned_datasets.extend(partitions)
|
|
615
|
-
partitioned_filenames.extend(partition_filenames)
|
|
616
|
-
|
|
617
|
-
xr.save_mfdataset(partitioned_datasets, partitioned_filenames)
|
|
618
|
-
|
|
619
|
-
saved_filenames.extend(Path(f) for f in partitioned_filenames)
|
|
620
|
-
|
|
621
|
-
return saved_filenames
|
|
622
|
-
|
|
623
|
-
|
|
624
584
|
def get_target_coords(grid, use_coarse_grid=False):
|
|
625
585
|
"""Retrieves longitude and latitude coordinates from the grid, adjusting them based
|
|
626
586
|
on longitude range.
|
|
@@ -652,8 +612,6 @@ def get_target_coords(grid, use_coarse_grid=False):
|
|
|
652
612
|
mask = grid.ds.get("mask_coarse")
|
|
653
613
|
if mask is not None:
|
|
654
614
|
mask = mask.rename({"eta_coarse": "eta_rho", "xi_coarse": "xi_rho"})
|
|
655
|
-
mask_u = interpolate_from_rho_to_u(mask, method="multiplicative")
|
|
656
|
-
mask_v = interpolate_from_rho_to_v(mask, method="multiplicative")
|
|
657
615
|
|
|
658
616
|
lat_psi = grid.ds.get("lat_psi_coarse")
|
|
659
617
|
lon_psi = grid.ds.get("lon_psi_coarse")
|
|
@@ -663,8 +621,6 @@ def get_target_coords(grid, use_coarse_grid=False):
|
|
|
663
621
|
lon = grid.ds.lon_rho
|
|
664
622
|
angle = grid.ds.angle
|
|
665
623
|
mask = grid.ds.get("mask_rho")
|
|
666
|
-
mask_u = grid.ds.get("mask_u")
|
|
667
|
-
mask_v = grid.ds.get("mask_v")
|
|
668
624
|
lat_psi = grid.ds.get("lat_psi")
|
|
669
625
|
lon_psi = grid.ds.get("lon_psi")
|
|
670
626
|
|
|
@@ -687,8 +643,6 @@ def get_target_coords(grid, use_coarse_grid=False):
|
|
|
687
643
|
"lon_psi": lon_psi,
|
|
688
644
|
"angle": angle,
|
|
689
645
|
"mask": mask,
|
|
690
|
-
"mask_u": mask_u,
|
|
691
|
-
"mask_v": mask_v,
|
|
692
646
|
"straddle": straddle,
|
|
693
647
|
}
|
|
694
648
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
import xarray as xr
|
|
4
|
+
import numpy as np
|
|
4
5
|
from roms_tools import Grid, BoundaryForcing
|
|
5
6
|
import textwrap
|
|
6
7
|
from roms_tools.download import download_test_data
|
|
7
|
-
from conftest import
|
|
8
|
+
from conftest import calculate_data_hash
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
import logging
|
|
10
11
|
|
|
@@ -13,19 +14,23 @@ import logging
|
|
|
13
14
|
"boundary_forcing_fixture",
|
|
14
15
|
[
|
|
15
16
|
"boundary_forcing",
|
|
16
|
-
|
|
17
|
+
"boundary_forcing_adjusted_for_zeta",
|
|
18
|
+
"boundary_forcing_with_2d_fill",
|
|
19
|
+
"boundary_forcing_with_2d_fill_adjusted_for_zeta",
|
|
17
20
|
],
|
|
18
21
|
)
|
|
19
22
|
def test_boundary_forcing_creation(boundary_forcing_fixture, request):
|
|
20
23
|
"""Test the creation of the BoundaryForcing object."""
|
|
21
24
|
|
|
22
|
-
fname = Path(download_test_data("GLORYS_coarse_test_data.nc"))
|
|
23
25
|
boundary_forcing = request.getfixturevalue(boundary_forcing_fixture)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
|
|
27
|
+
fname1 = Path(download_test_data("GLORYS_NA_20120101.nc"))
|
|
28
|
+
fname2 = Path(download_test_data("GLORYS_NA_20121231.nc"))
|
|
29
|
+
assert boundary_forcing.start_time == datetime(2012, 1, 1)
|
|
30
|
+
assert boundary_forcing.end_time == datetime(2012, 12, 31)
|
|
26
31
|
assert boundary_forcing.source == {
|
|
27
32
|
"name": "GLORYS",
|
|
28
|
-
"path":
|
|
33
|
+
"path": [fname1, fname2],
|
|
29
34
|
"climatology": False,
|
|
30
35
|
}
|
|
31
36
|
assert boundary_forcing.model_reference_date == datetime(2000, 1, 1)
|
|
@@ -44,16 +49,17 @@ def test_boundary_forcing_creation(boundary_forcing_fixture, request):
|
|
|
44
49
|
assert f"v_{direction}" in boundary_forcing.ds
|
|
45
50
|
assert f"zeta_{direction}" in boundary_forcing.ds
|
|
46
51
|
|
|
47
|
-
assert len(boundary_forcing.ds.bry_time) ==
|
|
52
|
+
assert len(boundary_forcing.ds.bry_time) == 2
|
|
48
53
|
assert boundary_forcing.ds.coords["bry_time"].attrs["units"] == "days"
|
|
49
54
|
assert not hasattr(boundary_forcing.ds, "climatology")
|
|
55
|
+
assert hasattr(boundary_forcing.ds, "adjust_depth_for_sea_surface_height")
|
|
56
|
+
assert hasattr(boundary_forcing.ds, "apply_2d_horizontal_fill")
|
|
50
57
|
|
|
51
58
|
|
|
52
59
|
@pytest.mark.parametrize(
|
|
53
60
|
"boundary_forcing_fixture",
|
|
54
61
|
[
|
|
55
62
|
"bgc_boundary_forcing_from_climatology",
|
|
56
|
-
# "bgc_boundary_forcing_from_climatology_with_2d_fill",
|
|
57
63
|
],
|
|
58
64
|
)
|
|
59
65
|
def test_boundary_forcing_creation_with_bgc(boundary_forcing_fixture, request):
|
|
@@ -155,6 +161,113 @@ def test_boundary_divided_by_land_warning(caplog, use_dask):
|
|
|
155
161
|
assert "the western boundary is divided by land" in caplog.text
|
|
156
162
|
|
|
157
163
|
|
|
164
|
+
def test_info_depth(caplog, use_dask):
|
|
165
|
+
|
|
166
|
+
grid = Grid(
|
|
167
|
+
nx=3,
|
|
168
|
+
ny=3,
|
|
169
|
+
size_x=400,
|
|
170
|
+
size_y=400,
|
|
171
|
+
center_lon=-8,
|
|
172
|
+
center_lat=58,
|
|
173
|
+
rot=0,
|
|
174
|
+
N=3, # number of vertical levels
|
|
175
|
+
theta_s=5.0, # surface control parameter
|
|
176
|
+
theta_b=2.0, # bottom control parameter
|
|
177
|
+
hc=250.0, # critical depth
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
fname1 = Path(download_test_data("GLORYS_NA_20120101.nc"))
|
|
181
|
+
fname2 = Path(download_test_data("GLORYS_NA_20121231.nc"))
|
|
182
|
+
|
|
183
|
+
with caplog.at_level(logging.INFO):
|
|
184
|
+
BoundaryForcing(
|
|
185
|
+
grid=grid,
|
|
186
|
+
start_time=datetime(2012, 1, 1),
|
|
187
|
+
end_time=datetime(2012, 12, 31),
|
|
188
|
+
source={"name": "GLORYS", "path": [fname1, fname2]},
|
|
189
|
+
adjust_depth_for_sea_surface_height=True,
|
|
190
|
+
use_dask=use_dask,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Verify the warning message in the log
|
|
194
|
+
assert "Sea surface height will be used to adjust depth coordinates." in caplog.text
|
|
195
|
+
|
|
196
|
+
# Clear the log before the next test
|
|
197
|
+
caplog.clear()
|
|
198
|
+
|
|
199
|
+
with caplog.at_level(logging.INFO):
|
|
200
|
+
|
|
201
|
+
BoundaryForcing(
|
|
202
|
+
grid=grid,
|
|
203
|
+
start_time=datetime(2012, 1, 1),
|
|
204
|
+
end_time=datetime(2012, 12, 31),
|
|
205
|
+
source={"name": "GLORYS", "path": [fname1, fname2]},
|
|
206
|
+
adjust_depth_for_sea_surface_height=False,
|
|
207
|
+
use_dask=use_dask,
|
|
208
|
+
)
|
|
209
|
+
# Verify the warning message in the log
|
|
210
|
+
assert (
|
|
211
|
+
"Sea surface height will NOT be used to adjust depth coordinates."
|
|
212
|
+
in caplog.text
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def test_info_fill(caplog, use_dask):
|
|
217
|
+
|
|
218
|
+
grid = Grid(
|
|
219
|
+
nx=3,
|
|
220
|
+
ny=3,
|
|
221
|
+
size_x=400,
|
|
222
|
+
size_y=400,
|
|
223
|
+
center_lon=-8,
|
|
224
|
+
center_lat=58,
|
|
225
|
+
rot=0,
|
|
226
|
+
N=3, # number of vertical levels
|
|
227
|
+
theta_s=5.0, # surface control parameter
|
|
228
|
+
theta_b=2.0, # bottom control parameter
|
|
229
|
+
hc=250.0, # critical depth
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
fname1 = Path(download_test_data("GLORYS_NA_20120101.nc"))
|
|
233
|
+
fname2 = Path(download_test_data("GLORYS_NA_20121231.nc"))
|
|
234
|
+
|
|
235
|
+
with caplog.at_level(logging.INFO):
|
|
236
|
+
BoundaryForcing(
|
|
237
|
+
grid=grid,
|
|
238
|
+
start_time=datetime(2012, 1, 1),
|
|
239
|
+
end_time=datetime(2012, 12, 31),
|
|
240
|
+
source={"name": "GLORYS", "path": [fname1, fname2]},
|
|
241
|
+
apply_2d_horizontal_fill=True,
|
|
242
|
+
use_dask=use_dask,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Verify the warning message in the log
|
|
246
|
+
assert (
|
|
247
|
+
"Applying 2D horizontal fill to the source data before regridding."
|
|
248
|
+
in caplog.text
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
# Clear the log before the next test
|
|
252
|
+
caplog.clear()
|
|
253
|
+
|
|
254
|
+
with caplog.at_level(logging.INFO):
|
|
255
|
+
|
|
256
|
+
BoundaryForcing(
|
|
257
|
+
grid=grid,
|
|
258
|
+
start_time=datetime(2012, 1, 1),
|
|
259
|
+
end_time=datetime(2012, 12, 31),
|
|
260
|
+
source={"name": "GLORYS", "path": [fname1, fname2]},
|
|
261
|
+
apply_2d_horizontal_fill=False,
|
|
262
|
+
use_dask=use_dask,
|
|
263
|
+
)
|
|
264
|
+
# Verify the warning message in the log
|
|
265
|
+
assert (
|
|
266
|
+
"Applying 1D horizontal fill separately to each regridded boundary."
|
|
267
|
+
in caplog.text
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
|
|
158
271
|
def test_1d_and_2d_fill_coincide_if_no_land(use_dask):
|
|
159
272
|
|
|
160
273
|
# this grid lies entirely over open ocean
|
|
@@ -182,23 +295,95 @@ def test_1d_and_2d_fill_coincide_if_no_land(use_dask):
|
|
|
182
295
|
xr.testing.assert_allclose(bf_1d_fill.ds, bf_2d_fill.ds, rtol=1.0e-4)
|
|
183
296
|
|
|
184
297
|
|
|
185
|
-
|
|
298
|
+
@pytest.mark.parametrize(
|
|
299
|
+
"boundary_forcing_fixture",
|
|
300
|
+
[
|
|
301
|
+
"boundary_forcing_adjusted_for_zeta",
|
|
302
|
+
"boundary_forcing_with_2d_fill_adjusted_for_zeta",
|
|
303
|
+
],
|
|
304
|
+
)
|
|
305
|
+
def test_correct_depth_coords_adjusted_for_zeta(
|
|
306
|
+
boundary_forcing_fixture, request, use_dask
|
|
307
|
+
):
|
|
308
|
+
|
|
309
|
+
boundary_forcing = request.getfixturevalue(boundary_forcing_fixture)
|
|
310
|
+
|
|
311
|
+
for direction in ["south", "east", "north", "west"]:
|
|
312
|
+
|
|
313
|
+
# Test that uppermost interface coincides with sea surface height
|
|
314
|
+
assert np.allclose(
|
|
315
|
+
boundary_forcing.ds_depth_coords[f"interface_depth_rho_{direction}"]
|
|
316
|
+
.isel(s_w=-1)
|
|
317
|
+
.values,
|
|
318
|
+
-boundary_forcing.ds[f"zeta_{direction}"].values,
|
|
319
|
+
atol=1e-6,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
@pytest.mark.parametrize(
|
|
324
|
+
"boundary_forcing_fixture",
|
|
325
|
+
[
|
|
326
|
+
"boundary_forcing",
|
|
327
|
+
"boundary_forcing_with_2d_fill",
|
|
328
|
+
],
|
|
329
|
+
)
|
|
330
|
+
def test_correct_depth_coords_zero_zeta(boundary_forcing_fixture, request, use_dask):
|
|
331
|
+
|
|
332
|
+
boundary_forcing = request.getfixturevalue(boundary_forcing_fixture)
|
|
333
|
+
|
|
334
|
+
for direction in ["south", "east", "north", "west"]:
|
|
335
|
+
|
|
336
|
+
# Test that uppermost interface coincides with sea surface height
|
|
337
|
+
assert np.allclose(
|
|
338
|
+
boundary_forcing.ds_depth_coords[f"interface_depth_rho_{direction}"]
|
|
339
|
+
.isel(s_w=-1)
|
|
340
|
+
.values,
|
|
341
|
+
0 * boundary_forcing.ds[f"zeta_{direction}"].values,
|
|
342
|
+
atol=1e-6,
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
@pytest.mark.parametrize(
|
|
347
|
+
"boundary_forcing_fixture",
|
|
348
|
+
[
|
|
349
|
+
"boundary_forcing",
|
|
350
|
+
"boundary_forcing_with_2d_fill",
|
|
351
|
+
"boundary_forcing_adjusted_for_zeta",
|
|
352
|
+
"boundary_forcing_with_2d_fill_adjusted_for_zeta",
|
|
353
|
+
],
|
|
354
|
+
)
|
|
355
|
+
def test_boundary_forcing_plot(boundary_forcing_fixture, request):
|
|
186
356
|
"""Test plot."""
|
|
357
|
+
boundary_forcing = request.getfixturevalue(boundary_forcing_fixture)
|
|
187
358
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
359
|
+
for direction in ["south", "east", "north", "west"]:
|
|
360
|
+
for layer_contours in [False, True]:
|
|
361
|
+
boundary_forcing.plot(
|
|
362
|
+
var_name=f"temp_{direction}", layer_contours=layer_contours
|
|
363
|
+
)
|
|
364
|
+
boundary_forcing.plot(
|
|
365
|
+
var_name=f"u_{direction}", layer_contours=layer_contours
|
|
366
|
+
)
|
|
367
|
+
boundary_forcing.plot(
|
|
368
|
+
var_name=f"v_{direction}", layer_contours=layer_contours
|
|
369
|
+
)
|
|
370
|
+
boundary_forcing.plot(var_name=f"zeta_{direction}")
|
|
371
|
+
boundary_forcing.plot(var_name=f"vbar_{direction}")
|
|
372
|
+
boundary_forcing.plot(var_name=f"ubar_{direction}")
|
|
198
373
|
|
|
199
374
|
|
|
200
|
-
|
|
375
|
+
@pytest.mark.parametrize(
|
|
376
|
+
"boundary_forcing_fixture",
|
|
377
|
+
[
|
|
378
|
+
"boundary_forcing",
|
|
379
|
+
"boundary_forcing_with_2d_fill",
|
|
380
|
+
"boundary_forcing_adjusted_for_zeta",
|
|
381
|
+
"boundary_forcing_with_2d_fill_adjusted_for_zeta",
|
|
382
|
+
],
|
|
383
|
+
)
|
|
384
|
+
def test_boundary_forcing_save(boundary_forcing_fixture, request, tmp_path):
|
|
201
385
|
"""Test save method."""
|
|
386
|
+
boundary_forcing = request.getfixturevalue(boundary_forcing_fixture)
|
|
202
387
|
|
|
203
388
|
for file_str in ["test_bf", "test_bf.nc"]:
|
|
204
389
|
# Create a temporary filepath using the tmp_path fixture
|
|
@@ -207,8 +392,8 @@ def test_boundary_forcing_save(boundary_forcing, tmp_path):
|
|
|
207
392
|
str(tmp_path / file_str),
|
|
208
393
|
]: # test for Path object and str
|
|
209
394
|
|
|
210
|
-
# Test saving without
|
|
211
|
-
saved_filenames = boundary_forcing.save(filepath)
|
|
395
|
+
# Test saving without grouping
|
|
396
|
+
saved_filenames = boundary_forcing.save(filepath, group=False)
|
|
212
397
|
|
|
213
398
|
filepath_str = str(Path(filepath).with_suffix(""))
|
|
214
399
|
expected_filepath = Path(f"{filepath_str}.nc")
|
|
@@ -217,28 +402,16 @@ def test_boundary_forcing_save(boundary_forcing, tmp_path):
|
|
|
217
402
|
assert expected_filepath.exists()
|
|
218
403
|
expected_filepath.unlink()
|
|
219
404
|
|
|
220
|
-
# Test saving
|
|
405
|
+
# Test saving with grouping
|
|
221
406
|
saved_filenames = boundary_forcing.save(filepath, group=True)
|
|
222
407
|
|
|
223
408
|
filepath_str = str(Path(filepath).with_suffix(""))
|
|
224
|
-
expected_filepath = Path(f"{filepath_str}
|
|
409
|
+
expected_filepath = Path(f"{filepath_str}_2012.nc")
|
|
225
410
|
|
|
226
411
|
assert saved_filenames == [expected_filepath]
|
|
227
412
|
assert expected_filepath.exists()
|
|
228
413
|
expected_filepath.unlink()
|
|
229
414
|
|
|
230
|
-
# Test saving with partitioning and grouping
|
|
231
|
-
saved_filenames = boundary_forcing.save(filepath, np_eta=2, group=True)
|
|
232
|
-
expected_filepath_list = [
|
|
233
|
-
Path(filepath_str + f"_202106.{index}.nc") for index in range(2)
|
|
234
|
-
]
|
|
235
|
-
|
|
236
|
-
assert saved_filenames == expected_filepath_list
|
|
237
|
-
|
|
238
|
-
for expected_filepath in expected_filepath_list:
|
|
239
|
-
assert expected_filepath.exists()
|
|
240
|
-
expected_filepath.unlink()
|
|
241
|
-
|
|
242
415
|
|
|
243
416
|
def test_bgc_boundary_forcing_plot(bgc_boundary_forcing_from_climatology):
|
|
244
417
|
"""Test plot method."""
|
|
@@ -264,7 +437,9 @@ def test_bgc_boundary_forcing_save(bgc_boundary_forcing_from_climatology, tmp_pa
|
|
|
264
437
|
]: # test for Path object and str
|
|
265
438
|
|
|
266
439
|
# Test saving without partitioning and grouping
|
|
267
|
-
saved_filenames = bgc_boundary_forcing_from_climatology.save(
|
|
440
|
+
saved_filenames = bgc_boundary_forcing_from_climatology.save(
|
|
441
|
+
filepath, group=False
|
|
442
|
+
)
|
|
268
443
|
|
|
269
444
|
filepath_str = str(Path(filepath).with_suffix(""))
|
|
270
445
|
expected_filepath = Path(f"{filepath_str}.nc")
|
|
@@ -283,20 +458,6 @@ def test_bgc_boundary_forcing_save(bgc_boundary_forcing_from_climatology, tmp_pa
|
|
|
283
458
|
assert expected_filepath.exists()
|
|
284
459
|
expected_filepath.unlink()
|
|
285
460
|
|
|
286
|
-
# Test saving with partitioning
|
|
287
|
-
saved_filenames = bgc_boundary_forcing_from_climatology.save(
|
|
288
|
-
filepath, np_xi=2, group=True
|
|
289
|
-
)
|
|
290
|
-
|
|
291
|
-
expected_filepath_list = [
|
|
292
|
-
Path(filepath_str + f"_clim.{index}.nc") for index in range(2)
|
|
293
|
-
]
|
|
294
|
-
assert saved_filenames == expected_filepath_list
|
|
295
|
-
|
|
296
|
-
for expected_filepath in expected_filepath_list:
|
|
297
|
-
assert expected_filepath.exists()
|
|
298
|
-
expected_filepath.unlink()
|
|
299
|
-
|
|
300
461
|
|
|
301
462
|
@pytest.mark.parametrize(
|
|
302
463
|
"bdry_forcing_fixture",
|
|
@@ -341,11 +502,12 @@ def test_files_have_same_hash(boundary_forcing, tmp_path, use_dask):
|
|
|
341
502
|
|
|
342
503
|
filepath_str1 = str(Path(filepath1).with_suffix(""))
|
|
343
504
|
filepath_str2 = str(Path(filepath2).with_suffix(""))
|
|
344
|
-
expected_filepath1 = f"{filepath_str1}
|
|
345
|
-
expected_filepath2 = f"{filepath_str2}
|
|
505
|
+
expected_filepath1 = f"{filepath_str1}_2012.nc"
|
|
506
|
+
expected_filepath2 = f"{filepath_str2}_2012.nc"
|
|
346
507
|
|
|
347
|
-
|
|
348
|
-
|
|
508
|
+
# Only compare hash of datasets because metadata is non-deterministic with dask
|
|
509
|
+
hash1 = calculate_data_hash(expected_filepath1)
|
|
510
|
+
hash2 = calculate_data_hash(expected_filepath2)
|
|
349
511
|
|
|
350
512
|
assert hash1 == hash2, f"Hashes do not match: {hash1} != {hash2}"
|
|
351
513
|
|
|
@@ -372,8 +534,9 @@ def test_files_have_same_hash_clim(
|
|
|
372
534
|
expected_filepath1 = f"{filepath_str1}_clim.nc"
|
|
373
535
|
expected_filepath2 = f"{filepath_str2}_clim.nc"
|
|
374
536
|
|
|
375
|
-
|
|
376
|
-
|
|
537
|
+
# Only compare hash of datasets because metadata is non-deterministic with dask
|
|
538
|
+
hash1 = calculate_data_hash(expected_filepath1)
|
|
539
|
+
hash2 = calculate_data_hash(expected_filepath2)
|
|
377
540
|
|
|
378
541
|
assert hash1 == hash2, f"Hashes do not match: {hash1} != {hash2}"
|
|
379
542
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
2
|
+
"adjust_depth_for_sea_surface_height": "False",
|
|
3
|
+
"apply_2d_horizontal_fill": "False",
|
|
4
|
+
"end_time": "2012-12-31 00:00:00",
|
|
3
5
|
"hc": 250.0,
|
|
4
6
|
"model_reference_date": "2000-01-01 00:00:00",
|
|
5
|
-
"roms_tools_version": "0.1.
|
|
7
|
+
"roms_tools_version": "0.1.dev177",
|
|
6
8
|
"source": "GLORYS",
|
|
7
|
-
"start_time": "
|
|
9
|
+
"start_time": "2012-01-01 00:00:00",
|
|
8
10
|
"theta_b": 2.0,
|
|
9
11
|
"theta_s": 5.0,
|
|
10
12
|
"title": "ROMS boundary forcing file created by ROMS-Tools"
|