diff --git a/docs/boundary_forcing.ipynb b/docs/boundary_forcing.ipynb index 5a171685d..9de3097ba 100644 --- a/docs/boundary_forcing.ipynb +++ b/docs/boundary_forcing.ipynb @@ -3140,6 +3140,7 @@ " end_time=end_time,\n", " source={\"name\": \"CESM_REGRIDDED\", \"path\": cesm_bgc_path, \"climatology\": True},\n", " type=\"bgc\",\n", + " physics_forcing=boundary_forcing, # use density-space interpolation\n", " use_dask=True,\n", ")" ] @@ -11209,6 +11210,7 @@ " end_time=end_time,\n", " source={\"name\": \"UNIFIED\", \"path\": unified_bgc_path, \"climatology\": True},\n", " type=\"bgc\",\n", + " physics_forcing=boundary_forcing, # use density-space interpolation\n", " use_dask=True,\n", ")" ] diff --git a/docs/end_to_end.ipynb b/docs/end_to_end.ipynb index 857c95224..7a125e38f 100644 --- a/docs/end_to_end.ipynb +++ b/docs/end_to_end.ipynb @@ -11215,6 +11215,7 @@ " end_time=end_time,\n", " source={\"name\": \"UNIFIED\", \"path\": unified_bgc_path, \"climatology\": True},\n", " type=\"bgc\",\n", + " physics_forcing=boundary_forcing, # for density-space BGC interpolation\n", " use_dask=True\n", ")" ] diff --git a/docs/releases.md b/docs/releases.md index ff2dc4fcb..597d93531 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -8,6 +8,7 @@ ### New Features +* Density-space vertical interpolation for BGC tracers in `InitialConditions` and `BoundaryForcing` via new `use_density_interpolation` parameter (default `True`). For `BoundaryForcing`, pass the physics `BoundaryForcing` as `physics_forcing=` to activate ([#606](https://github.com/CWorthy-ocean/roms-tools/pull/606)). * `make_edata` changed to `make_nesting_info` * `to_yaml` and `from_yaml` were adjusted to handle child grids after they've been modified ([#573](https://github.com/CWorthy-ocean/roms-tools/pull/573)) * Nesting now supports optional baroclinic pressure fluxes via metadata ([#568](https://github.com/CWorthy-ocean/roms-tools/pull/568)) @@ -41,6 +42,7 @@ * Document `close_narrow_channels` option in `Grid` and `update_mask()`; update notebook examples * Both the surface forcing and datasets notebooks are updated to reflect `restoring` function and WOA data ([#589](https://github.com/CWorthy-ocean/roms-tools/pull/589)) * The cdr notebook is updated to reflect interpolation option. Default is same as ROMS, no interpolation ([#601](https://github.com/CWorthy-ocean/roms-tools/pull/601)) +* Notebooks updated to document density-space BGC interpolation ([#606](https://github.com/CWorthy-ocean/roms-tools/pull/606)) ### Bugfixes diff --git a/pyproject.toml b/pyproject.toml index 3d0bf6625..a07047d5f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ dependencies = [ "bottleneck", "regionmask>=0.11.0", "xgcm>=0.9.0", + "gsw", "numba>=0.61.2", "pydantic>2,<3", ] diff --git a/roms_tools/setup/boundary_forcing.py b/roms_tools/setup/boundary_forcing.py index 69f0cfaf4..9dcb9ef0e 100644 --- a/roms_tools/setup/boundary_forcing.py +++ b/roms_tools/setup/boundary_forcing.py @@ -22,10 +22,12 @@ from roms_tools.regrid import LateralRegridToROMS, VerticalRegrid from roms_tools.setup.utils import ( RawDataSource, + _compute_bgc_source_density, add_time_info_to_ds, check_and_set_boundaries, compute_barotropic_velocity, compute_missing_bgc_variables, + compute_potential_density, from_yaml, get_boundary_coords, get_target_coords, @@ -37,6 +39,7 @@ write_to_yaml, ) from roms_tools.utils import ( + interpolate_cyclic_time, interpolate_from_rho_to_u, interpolate_from_rho_to_v, rotate_velocities, @@ -46,6 +49,50 @@ from roms_tools.vertical_coordinate import compute_depth +def _interpolate_phys_to_bgc_time( + phys_da: xr.DataArray, + time_dim: str, + bgc_time_coord: xr.DataArray, + bgc_climatology: bool, +) -> xr.DataArray: + """Linearly interpolate a physics DataArray onto the BGC time coordinate. + + Parameters + ---------- + phys_da : xr.DataArray + Physics data with a ``datetime64`` time dimension named ``time_dim``. + time_dim : str + Name of the time dimension in ``phys_da``. + bgc_time_coord : xr.DataArray + Target time coordinate from the BGC dataset (1-D). + bgc_climatology : bool + Whether the BGC dataset is a climatology. If True, ``bgc_time_coord`` + is expected to be ``timedelta64`` from the start of the year (as set by + ``assign_dates_to_climatology``), and physics times are mapped to + fractional day-of-year before a cyclic linear interpolation. If False, + a straight ``xr.DataArray.interp(method="linear")`` is performed in + ``datetime64`` space. + + Returns + ------- + xr.DataArray + ``phys_da`` interpolated to ``bgc_time_coord``, with time dimension + still named ``time_dim`` and coordinate set to ``bgc_time_coord``. + """ + if bgc_climatology: + # Map both axes to fractional day-of-year, then cyclic-interpolate. + bgc_doy = (bgc_time_coord / np.timedelta64(1, "D")).values + 1.0 + phys_doy = phys_da[time_dim].dt.dayofyear.values.astype(float) + phys_for_interp = phys_da.assign_coords( + {time_dim: xr.DataArray(phys_doy, dims=[time_dim])} + ) + result = interpolate_cyclic_time(phys_for_interp, time_dim, time_dim, bgc_doy) + return result.assign_coords({time_dim: bgc_time_coord.values}) + + # Non-climatology: standard datetime64 linear interpolation + return phys_da.interp({time_dim: bgc_time_coord}, method="linear") + + @dataclass(kw_only=True) class BoundaryForcing: """Represents boundary forcing input data for ROMS. @@ -104,6 +151,18 @@ class BoundaryForcing: Indicates whether to skip validation checks in the processed data. When set to True, the validation process that ensures no NaN values exist at wet points in the processed dataset is bypassed. Defaults to False. + use_density_interpolation : bool, optional + If True (default), BGC tracers are vertically interpolated in density space + rather than depth space when ``physics_forcing`` is provided. Preserves + water-mass properties. Only used when ``type='bgc'``. + + Interpolation uses ``xgcm.Grid.transform`` with the linear method inside + the source density range and edge-value (nearest-neighbor) extrapolation + outside (``mask_edges=False``). + physics_forcing : BoundaryForcing, optional + A physics ``BoundaryForcing`` object (``type='physics'``) whose T/S fields + supply the density coordinate for BGC tracer interpolation. When None and + ``use_density_interpolation=True``, falls back to depth-based interpolation. Examples @@ -143,6 +202,10 @@ class BoundaryForcing: """Optional initial bounding slice when loading source data (Dask); see dataset classes.""" bypass_validation: bool = False """Whether to skip validation checks in the processed data.""" + use_density_interpolation: bool = True + """Whether to interpolate BGC tracers in density space rather than depth space.""" + physics_forcing: "BoundaryForcing | None" = None + """Physics BoundaryForcing object supplying T/S for density-based BGC interpolation.""" ds: xr.Dataset = field(init=False, repr=False) """An xarray Dataset containing post-processed variables ready for input into @@ -156,9 +219,20 @@ def __post_init__(self): # Initialize depth coordinates self.adjust_depth_for_sea_surface_height = False self.ds_depth_coords = xr.Dataset() + self._phys_bdry_depth_data: dict = {} self._input_checks() + if ( + self.type == "bgc" + and self.use_density_interpolation + and self.physics_forcing is None + ): + logging.info( + "use_density_interpolation=True but no physics_forcing provided. " + "BGC tracers will be interpolated in depth space instead." + ) + target_coords = get_target_coords(self.grid) data = self._get_data() @@ -329,6 +403,16 @@ def __post_init__(self): zeta_u = 0 zeta_v = 0 + # Save physics T/S at source depth levels for density-based BGC interpolation + if self.type == "physics" and self.use_density_interpolation: + if "temp" in processed_fields and "salt" in processed_fields: + self._phys_bdry_depth_data[direction] = { + "temp": processed_fields["temp"], + "salt": processed_fields["salt"], + "depth_dim": bdry_data.dim_names["depth"], + "depth_coord": bdry_data.ds[bdry_data.dim_names["depth"]], + } + for location in ["rho", "u", "v"]: # Filter var_names by location and check for 3D variables filtered_vars = [ @@ -353,17 +437,43 @@ def __post_init__(self): vertical_regrid = VerticalRegrid( bdry_data.ds, source_dim=bdry_data.dim_names["depth"] ) + + use_density = ( + self.type == "bgc" + and self.use_density_interpolation + and location == "rho" + and self.physics_forcing is not None + and direction in self.physics_forcing._phys_bdry_depth_data + ) + + if use_density: + source_density, target_density = ( + self._compute_bgc_density_coords( + direction, + bdry_data, + processed_fields, + filtered_vars, + ) + ) + for var_name in filtered_vars: if var_name in processed_fields: - processed_fields[var_name] = vertical_regrid.apply( - processed_fields[var_name], - source_depth_coords=bdry_data.ds[ - bdry_data.dim_names["depth"] - ], - target_depth_coords=self.ds_depth_coords[ - f"layer_depth_{location}_{direction}" - ], - ) + if use_density: + processed_fields[var_name] = vertical_regrid.apply( + processed_fields[var_name], + source_depth_coords=source_density, + target_depth_coords=target_density, + ) + else: + processed_fields[var_name] = vertical_regrid.apply( + processed_fields[var_name], + source_depth_coords=bdry_data.ds[ + bdry_data.dim_names["depth"] + ], + target_depth_coords=self.ds_depth_coords[ + f"layer_depth_{location}_{direction}" + ], + ) # compute barotropic velocities if "u" in var_names and "v" in var_names: @@ -403,6 +513,91 @@ def __post_init__(self): self.ds = ds + def _compute_bgc_density_coords( + self, + direction: str, + bdry_data, + processed_fields: dict, + filtered_vars: list, + ) -> tuple[xr.DataArray, xr.DataArray]: + """Build source/target density coordinates for BGC density-space interpolation. + + Source density is computed from the physics BC's saved depth-level T/S and + regridded onto the BGC depth axis. Target density is computed from the + physics BC's sigma-level T/S, interpolated to the BGC time coordinate. + + Both arrays include the small monotonicity perturbation that + ``vertical_regrid.apply`` requires for a strictly monotonic coordinate. + """ + assert self.physics_forcing is not None + phys_data = self.physics_forcing._phys_bdry_depth_data[direction] + phys_temp, phys_salt = phys_data["temp"], phys_data["salt"] + bgc_climatology = bool(self.source["climatology"]) + + # The BGC time axis (if any) is shared across all 3D BGC vars; take it from + # the first one. dim_names["time"] gives the source-data convention name. + bgc_time_dim = bdry_data.dim_names.get("time") + first_bgc = processed_fields.get(filtered_vars[0]) if filtered_vars else None + bgc_time_coord = ( + first_bgc[bgc_time_dim] + if first_bgc is not None + and bgc_time_dim in (first_bgc.dims if first_bgc is not None else ()) + else None + ) + + def _align_time(da: xr.DataArray, time_dim: str) -> xr.DataArray: + """Align ``da``'s ``time_dim`` to the BGC time axis, or collapse it.""" + if time_dim not in da.dims: + return da + if bgc_time_coord is not None: + return _interpolate_phys_to_bgc_time( + da, time_dim, bgc_time_coord, bgc_climatology + ) + return da.mean(time_dim) + + # --- Source density: physics depth-level T/S → BGC depth axis --- + phys_temp = _align_time(phys_temp, "time") + phys_salt = _align_time(phys_salt, "time") + bgc_depth_dim = bdry_data.dim_names["depth"] + source_density = _compute_bgc_source_density( + phys_temp, + phys_salt, + phys_data["depth_dim"], + phys_data["depth_coord"], + bdry_data.ds[bgc_depth_dim], + bgc_depth_dim, + ) + + # --- Target density: physics sigma-level T/S, aligned to BGC time --- + # Physics BC dataset uses "bry_time" as the time dim with an "abs_time" + # datetime64 companion coord. Swap to the datetime view before time-aligning. + temp_sigma = self.physics_forcing.ds[f"temp_{direction}"] + salt_sigma = self.physics_forcing.ds[f"salt_{direction}"] + if "abs_time" in temp_sigma.coords: + temp_sigma = temp_sigma.swap_dims({"bry_time": "abs_time"}).rename( + {"abs_time": "time"} + ) + salt_sigma = salt_sigma.swap_dims({"bry_time": "abs_time"}).rename( + {"abs_time": "time"} + ) + temp_sigma = _align_time(temp_sigma, "time") + salt_sigma = _align_time(salt_sigma, "time") + else: + temp_sigma = _align_time(temp_sigma, "bry_time") + salt_sigma = _align_time(salt_sigma, "bry_time") + + target_density = compute_potential_density(temp_sigma, salt_sigma) + # Add a small perturbation along the vertical dim to ensure monotonicity. + vertical_dim = next(d for d in target_density.dims if d.startswith("s_")) + n_s = target_density.sizes[vertical_dim] + target_density = target_density + xr.DataArray( + np.arange(n_s) * 1e-7, dims=[vertical_dim] + ) + # xgcm.transform requires a single chunk along the target vertical dim. + target_density = target_density.chunk({vertical_dim: -1}) + + return source_density, target_density + def _input_checks(self) -> None: """Validate and normalize user-provided input parameters.""" # ------------------------------------------------------- @@ -1091,6 +1286,7 @@ def to_yaml(self, filepath: str | Path) -> None: "ds_depth_coords", "adjust_depth_for_sea_surface_height", "use_dask", + "physics_forcing", ], ) write_to_yaml(forcing_dict, filepath) diff --git a/roms_tools/setup/initial_conditions.py b/roms_tools/setup/initial_conditions.py index b601f655c..0efc1a111 100644 --- a/roms_tools/setup/initial_conditions.py +++ b/roms_tools/setup/initial_conditions.py @@ -26,8 +26,10 @@ ) from roms_tools.setup.utils import ( RawDataSource, + _compute_bgc_source_density, compute_barotropic_velocity, compute_missing_bgc_variables, + compute_potential_density, from_yaml, get_target_coords, get_variable_metadata, @@ -111,6 +113,17 @@ class InitialConditions: Indicates whether to skip validation checks in the processed data. When set to True, the validation process that ensures no NaN values exist at wet points in the processed dataset is bypassed. Defaults to False. + use_density_interpolation : bool, optional + If True (default), BGC tracers are vertically interpolated in density space + rather than depth space. Density is computed from the physics source T/S + (via TEOS-10 sigma-0) and used as the vertical coordinate for interpolation, + preserving water-mass properties. Only applies when ``bgc_source`` is provided + and the physics source is a lat/lon dataset (not a ROMS restart). Falls back + to depth-based interpolation silently if physics T/S are unavailable. + + Interpolation uses ``xgcm.Grid.transform`` with the linear method inside + the source density range and edge-value (nearest-neighbor) extrapolation + outside (``mask_edges=False``). @@ -161,6 +174,8 @@ class InitialConditions: """Optional initial bounding slice when loading lat/lon forcing data with Dask.""" bypass_validation: bool = False """Whether to skip validation checks in the processed data.""" + use_density_interpolation: bool = True + """Whether to interpolate BGC tracers in density space rather than depth space.""" ds: xr.Dataset = field(init=False, repr=False) """An xarray Dataset containing post-processed variables ready for input into ROMS.""" @@ -173,6 +188,9 @@ def __post_init__(self): # Initialize depth coordinates self.ds_depth_coords = xr.Dataset() + # Populated during physics processing if density interpolation is enabled + self._phys_depth_data: dict | None = None + self._input_checks() processed_fields = {} @@ -273,8 +291,23 @@ def _process_data(self, processed_fields, type="physics"): for location in ["rho", "u", "v"]: self._get_depth_coordinates(zeta, location, "layer") + # Save physics T/S at source depth levels for density-based BGC interpolation + if ( + type == "physics" + and self.use_density_interpolation + and isinstance(data, LatLonDataset) + ): + self._phys_depth_data = { + "temp": processed_fields["temp"], + "salt": processed_fields["salt"], + "depth_dim": data.dim_names["depth"], + "depth_coord": data.ds[data.dim_names["depth"]], + } + # Vertical regridding - processed_fields = self._regrid_vertically(data, processed_fields, var_names) + processed_fields = self._regrid_vertically( + data, processed_fields, var_names, type=type + ) # Compute barotropic velocities if "u" in var_names and "v" in var_names: @@ -353,6 +386,7 @@ def _regrid_vertically( data: ROMSDataset | LatLonDataset, processed_fields: dict[str, xr.DataArray], var_names: dict[str, dict[str, str | bool]], + type: str = "physics", ) -> dict[str, xr.DataArray]: """ Perform vertical regridding of 3D variables to the model's vertical grid. @@ -426,16 +460,53 @@ def _regrid_vertically( data.ds, source_dim=data.dim_names["depth"] ) + use_density = ( + type == "bgc" + and self.use_density_interpolation + and self._phys_depth_data is not None + ) + + if use_density: + assert self._phys_depth_data is not None + source_density = _compute_bgc_source_density( + self._phys_depth_data["temp"], + self._phys_depth_data["salt"], + self._phys_depth_data["depth_dim"], + self._phys_depth_data["depth_coord"], + data.ds[data.dim_names["depth"]], + data.dim_names["depth"], + ) + target_density = compute_potential_density( + processed_fields["temp"], processed_fields["salt"] + ) + # Add a small perturbation along the vertical dim to ensure monotonicity. + vertical_dim = next( + d for d in target_density.dims if d.startswith("s_") + ) + n_s = target_density.sizes[vertical_dim] + target_density = target_density + xr.DataArray( + np.arange(n_s) * 1e-7, dims=[vertical_dim] + ) + # xgcm.transform requires a single chunk along the target vertical dim. + target_density = target_density.chunk({vertical_dim: -1}) + for var_name in filtered_vars: if var_name not in processed_fields: continue - processed_fields[var_name] = vertical_regrid.apply( - processed_fields[var_name], - source_depth_coords=data.ds[data.dim_names["depth"]], - target_depth_coords=self.ds_depth_coords[ - f"layer_depth_{location}" - ], - ) + if use_density: + processed_fields[var_name] = vertical_regrid.apply( + processed_fields[var_name], + source_depth_coords=source_density, + target_depth_coords=target_density, + ) + else: + processed_fields[var_name] = vertical_regrid.apply( + processed_fields[var_name], + source_depth_coords=data.ds[data.dim_names["depth"]], + target_depth_coords=self.ds_depth_coords[ + f"layer_depth_{location}" + ], + ) return processed_fields diff --git a/roms_tools/setup/utils.py b/roms_tools/setup/utils.py index d952ae5c9..96ddfe110 100644 --- a/roms_tools/setup/utils.py +++ b/roms_tools/setup/utils.py @@ -10,6 +10,7 @@ from pathlib import Path from typing import Any, Literal, TypeAlias +import gsw import numba as nb import numpy as np import pandas as pd @@ -18,6 +19,8 @@ from pydantic import BaseModel from roms_tools.constants import R_EARTH +from roms_tools.regrid import VerticalRegrid +from roms_tools.utils import transpose_dimensions if typing.TYPE_CHECKING: from roms_tools.setup.grid import Grid @@ -498,6 +501,106 @@ def compute_missing_bgc_variables(bgc_data): return bgc_data +def compute_potential_density( + temp: "xr.DataArray", salt: "xr.DataArray" +) -> "xr.DataArray": + """Compute sigma-0 potential density anomaly (kg/m³ - 1000) via TEOS-10 (gsw). + + Wraps gsw.sigma0 with apply_ufunc for dask compatibility. Treats practical + salinity as Absolute Salinity and in-situ temperature as Conservative + Temperature — an approximation sufficient for density-coordinate interpolation. + + Parameters + ---------- + temp : xr.DataArray + In-situ temperature (°C). + salt : xr.DataArray + Practical salinity (PSU). + + Returns + ------- + xr.DataArray + Potential density anomaly sigma-0 (kg/m³ - 1000). + """ + density = xr.apply_ufunc( + gsw.sigma0, + salt, + temp, + dask="parallelized", + output_dtypes=[temp.dtype], + ) + density = transpose_dimensions(density) + density.name = "sigma0" + density.attrs["long_name"] = "potential density anomaly" + density.attrs["units"] = "kg/m^3 - 1000" + return density + + +def _compute_bgc_source_density( + phys_temp: "xr.DataArray", + phys_salt: "xr.DataArray", + phys_depth_dim: str, + phys_depth_coord: "xr.DataArray", + bgc_depth_coord: "xr.DataArray", + bgc_depth_dim: str, +) -> "xr.DataArray": + """Compute potential density at BGC source depth levels. + + Computes sigma-0 from physics T/S at physics depth levels, adds a small + depth-index perturbation to guarantee strict monotonicity (matching the + reference MATLAB implementation), then interpolates to the BGC depth grid. + + Parameters + ---------- + phys_temp : xr.DataArray + Physics temperature at source depth levels. + phys_salt : xr.DataArray + Physics salinity at source depth levels. + phys_depth_dim : str + Name of the depth dimension in the physics dataset. + phys_depth_coord : xr.DataArray + Depth coordinate values of the physics dataset (1D, metres). + bgc_depth_coord : xr.DataArray + Depth coordinate values of the BGC dataset (1D, metres). + bgc_depth_dim : str + Name of the depth dimension in the BGC dataset. + + Returns + ------- + xr.DataArray + Potential density anomaly sigma-0 at BGC depth levels. + """ + density = compute_potential_density(phys_temp, phys_salt) + + # Add small perturbation along depth to ensure strict monotonicity + n_depth = density.sizes[phys_depth_dim] + perturbation = xr.DataArray(np.arange(n_depth) * 1e-7, dims=[phys_depth_dim]) + density = density + perturbation + + # xgcm.transform requires a single chunk along the dim being transformed. + density = density.chunk({phys_depth_dim: -1}) + + # Regrid density from physics depth levels to BGC depth levels + ds_phys = xr.Dataset({phys_depth_dim: phys_depth_coord}) + vertical_regrid = VerticalRegrid(ds_phys, source_dim=phys_depth_dim) + source_density = vertical_regrid.apply( + density, + source_depth_coords=phys_depth_coord, + target_depth_coords=bgc_depth_coord, + ) + source_density = transpose_dimensions(source_density) + + # Add a small perturbation along the BGC depth dimension after interpolation, + # so the density profile xgcm uses as a source coordinate is strictly + # monotonic even where extrapolation flattens the original perturbation. + n_bgc_depth = source_density.sizes[bgc_depth_dim] + source_density = source_density + xr.DataArray( + np.arange(n_bgc_depth) * 1e-7, dims=[bgc_depth_dim] + ) + + return source_density + + def compute_missing_surface_bgc_variables(bgc_data): """Fills in missing surface biogeochemical (BGC) variables in the input dictionary. diff --git a/roms_tools/tests/test_setup/test_boundary_forcing.py b/roms_tools/tests/test_setup/test_boundary_forcing.py index a79f4919f..a071b039c 100644 --- a/roms_tools/tests/test_setup/test_boundary_forcing.py +++ b/roms_tools/tests/test_setup/test_boundary_forcing.py @@ -736,3 +736,94 @@ def test_nondefault_glorys_dataset_loading(small_grid: Grid, use_dask: bool) -> expected_vars = {"u_south", "v_south", "temp_south", "salt_south"} assert set(bf.ds.data_vars).issuperset(expected_vars) + + +# test density interpolation + + +def test_bgc_bc_density_fallback_without_physics_forcing( + bgc_boundary_forcing_from_unified_climatology, +): + """BGC BC with density interpolation but no physics_forcing falls back to depth-based.""" + bf = bgc_boundary_forcing_from_unified_climatology + assert bf.use_density_interpolation is True + assert bf.physics_forcing is None + # BGC variables should still be present (depth-based fallback succeeded) + assert any("NO3" in v for v in bf.ds.data_vars) + + +def test_bgc_bc_with_physics_forcing(use_dask): + """BGC BC with physics_forcing uses density interpolation and produces BGC variables.""" + # Use same grid / data as existing physics BC fixtures (North Atlantic, 2012) + grid = Grid( + nx=3, + ny=3, + size_x=400, + size_y=400, + center_lon=-8, + center_lat=58, + rot=0, + N=3, + theta_s=5.0, + theta_b=2.0, + hc=250.0, + ) + fname_phys = Path(download_test_data("GLORYS_NA_20120101.nc")) + fname_bgc = Path(download_test_data("coarsened_UNIFIED_bgc_dataset.nc")) + + physics_bc = BoundaryForcing( + grid=grid, + start_time=datetime(2012, 1, 1), + end_time=datetime(2012, 1, 2), + source={"path": fname_phys, "name": "GLORYS"}, + type="physics", + apply_2d_horizontal_fill=False, + use_dask=use_dask, + ) + + bgc_bc = BoundaryForcing( + grid=grid, + start_time=datetime(2012, 1, 1), + end_time=datetime(2012, 1, 2), + source={"path": fname_bgc, "name": "UNIFIED", "climatology": True}, + type="bgc", + physics_forcing=physics_bc, + apply_2d_horizontal_fill=True, + use_dask=use_dask, + ) + + assert bgc_bc.use_density_interpolation is True + assert bgc_bc.physics_forcing is physics_bc + for direction in ["south", "east", "north", "west"]: + if bgc_bc.boundaries[direction]: + assert f"NO3_{direction}" in bgc_bc.ds + assert f"DIC_{direction}" in bgc_bc.ds + + # Verify the density code path actually changed the output: produce a + # depth-based comparison and assert at least one BGC variable differs. + bgc_bc_depth = BoundaryForcing( + grid=grid, + start_time=datetime(2012, 1, 1), + end_time=datetime(2012, 1, 2), + source={"path": fname_bgc, "name": "UNIFIED", "climatology": True}, + type="bgc", + use_density_interpolation=False, + apply_2d_horizontal_fill=True, + use_dask=use_dask, + ) + any_diff = False + for direction in ["south", "east", "north", "west"]: + if not bgc_bc.boundaries[direction]: + continue + for var in ["NO3", "DIC", "ALK", "PO4", "O2"]: + name = f"{var}_{direction}" + if name in bgc_bc.ds and name in bgc_bc_depth.ds: + a = bgc_bc.ds[name].values + b = bgc_bc_depth.ds[name].values + valid = ~(np.isnan(a) | np.isnan(b)) + if valid.any() and np.abs(a[valid] - b[valid]).max() > 0: + any_diff = True + break + if any_diff: + break + assert any_diff, "Density interpolation produced identical output to depth-based" diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/ALK/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/ALK/c/0/0/0/0 index 36126fca0..f398e4427 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/ALK/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/ALK/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/ALK_ALT_CO2/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/ALK_ALT_CO2/c/0/0/0/0 index da7089d50..e4debaa91 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/ALK_ALT_CO2/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/ALK_ALT_CO2/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DIC/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DIC/c/0/0/0/0 index 2f75425ec..a2c55c723 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DIC/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DIC/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DIC_ALT_CO2/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DIC_ALT_CO2/c/0/0/0/0 index 19827b2ee..b1688c6fc 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DIC_ALT_CO2/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DIC_ALT_CO2/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOC/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOC/c/0/0/0/0 index 5e226fe3e..46f2a42cc 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOC/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOC/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOCr/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOCr/c/0/0/0/0 index 87dd2dac5..a21cc9a38 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOCr/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOCr/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DON/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DON/c/0/0/0/0 index b5ddfba7d..c1ad3d5d1 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DON/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DON/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DONr/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DONr/c/0/0/0/0 index 5cbfbcae1..81c4251ec 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DONr/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DONr/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOP/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOP/c/0/0/0/0 index 79e19ef88..5f998968d 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOP/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOP/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOPr/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOPr/c/0/0/0/0 index f379120b3..1beabfdcf 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOPr/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/DOPr/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/Fe/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/Fe/c/0/0/0/0 index 0502aaf6b..360d8503b 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/Fe/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/Fe/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/Lig/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/Lig/c/0/0/0/0 index c8f932284..73e9bf2a3 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/Lig/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/Lig/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/NH4/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/NH4/c/0/0/0/0 index 5a64a2bcb..4dcf5f48b 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/NH4/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/NH4/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/NO3/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/NO3/c/0/0/0/0 index f312d8f34..bd0545b02 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/NO3/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/NO3/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/O2/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/O2/c/0/0/0/0 index dfb137c05..c8484a9ea 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/O2/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/O2/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/PO4/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/PO4/c/0/0/0/0 index 89242a164..25eca56fd 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/PO4/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/PO4/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/SiO3/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/SiO3/c/0/0/0/0 index 1bc074c83..56f59f836 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/SiO3/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/SiO3/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatC/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatC/c/0/0/0/0 index 852c80771..23de58025 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatC/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatC/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatChl/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatChl/c/0/0/0/0 index 8075730e0..9ebe3f483 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatChl/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatChl/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatFe/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatFe/c/0/0/0/0 index c243797c5..8f743ffb4 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatFe/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatFe/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatP/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatP/c/0/0/0/0 index b166c1585..3c8e3a8bf 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatP/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatP/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatSi/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatSi/c/0/0/0/0 index 2031138a0..ae0951328 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatSi/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diatSi/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazC/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazC/c/0/0/0/0 index 85e79f1df..1b5dcc8e8 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazC/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazC/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazChl/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazChl/c/0/0/0/0 index 6365b6704..901683526 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazChl/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazChl/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazFe/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazFe/c/0/0/0/0 index 3f41dae5c..4a2ff13a7 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazFe/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazFe/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazP/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazP/c/0/0/0/0 index 64c7eec80..44471abee 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazP/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/diazP/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spC/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spC/c/0/0/0/0 index fdf6a4649..fd17fac13 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spC/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spC/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spCaCO3/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spCaCO3/c/0/0/0/0 index 8e3935c04..452dd71fe 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spCaCO3/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spCaCO3/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spChl/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spChl/c/0/0/0/0 index 3b42c03cf..5a982cd74 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spChl/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spChl/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spFe/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spFe/c/0/0/0/0 index bd7ea8f93..66e51dd34 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spFe/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spFe/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spP/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spP/c/0/0/0/0 index ecf7ef732..b61340f98 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spP/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/spP/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/zarr.json b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/zarr.json index 93fb96777..b0082ed04 100644 --- a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/zarr.json +++ b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/zarr.json @@ -1,7 +1,7 @@ { "attributes": { "title": "ROMS initial conditions file created by ROMS-Tools", - "roms_tools_version": "3.1.3.dev32+g91d580e67.d20250929", + "roms_tools_version": "10000.dev355+g36bf2550d", "ini_time": "2021-06-29 00:00:00", "model_reference_date": "2000-01-01 00:00:00", "adjust_depth_for_sea_surface_height": "False", @@ -16,22 +16,16 @@ "kind": "inline", "must_understand": false, "metadata": { - "spP": { + "abs_time": { "shape": [ - 1, - 3, - 4, - 4 + 1 ], - "data_type": "float32", + "data_type": "int64", "chunk_grid": { "name": "regular", "configuration": { "chunk_shape": [ - 1, - 3, - 4, - 4 + 1 ] } }, @@ -41,7 +35,7 @@ "separator": "/" } }, - "fill_value": 0.0, + "fill_value": 0, "codecs": [ { "name": "bytes", @@ -58,22 +52,18 @@ } ], "attributes": { - "long_name": "small phytoplankton phosphorous", - "units": "mmol/m^3", - "coordinates": "abs_time", - "_FillValue": "AAAAAAAA+H8=" + "long_name": "absolute time", + "units": "days since 2021-06-29 00:00:00", + "calendar": "proleptic_gregorian" }, "dimension_names": [ - "ocean_time", - "s_rho", - "eta_rho", - "xi_rho" + "ocean_time" ], "zarr_format": 3, "node_type": "array", "storage_transformers": [] }, - "DONr": { + "ALK": { "shape": [ 1, 3, @@ -115,8 +105,8 @@ } ], "attributes": { - "long_name": "refractory dissolved organic nitrogen", - "units": "mmol/m^3", + "long_name": "alkalinity", + "units": "meq/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" }, @@ -130,7 +120,7 @@ "node_type": "array", "storage_transformers": [] }, - "temp": { + "ALK_ALT_CO2": { "shape": [ 1, 3, @@ -172,8 +162,8 @@ } ], "attributes": { - "long_name": "potential temperature", - "units": "degrees Celsius", + "long_name": "alkalinity, alternative CO2", + "units": "meq/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" }, @@ -187,22 +177,16 @@ "node_type": "array", "storage_transformers": [] }, - "PO4": { + "Cs_r": { "shape": [ - 1, - 3, - 4, - 4 + 3 ], "data_type": "float32", "chunk_grid": { "name": "regular", "configuration": { "chunk_shape": [ - 1, - 3, - 4, - 4 + 3 ] } }, @@ -229,26 +213,19 @@ } ], "attributes": { - "long_name": "dissolved inorganic phosphate", - "units": "mmol/m^3", - "coordinates": "abs_time", + "long_name": "Vertical stretching function at rho-points", + "units": "nondimensional", "_FillValue": "AAAAAAAA+H8=" }, "dimension_names": [ - "ocean_time", - "s_rho", - "eta_rho", - "xi_rho" + "s_rho" ], "zarr_format": 3, "node_type": "array", "storage_transformers": [] }, - "spCaCO3": { + "Cs_w": { "shape": [ - 1, - 3, - 4, 4 ], "data_type": "float32", @@ -256,9 +233,6 @@ "name": "regular", "configuration": { "chunk_shape": [ - 1, - 3, - 4, 4 ] } @@ -286,22 +260,18 @@ } ], "attributes": { - "long_name": "small phytoplankton CaCO3", - "units": "mmol/m^3", - "coordinates": "abs_time", + "long_name": "Vertical stretching function at w-points", + "units": "nondimensional", "_FillValue": "AAAAAAAA+H8=" }, "dimension_names": [ - "ocean_time", - "s_rho", - "eta_rho", - "xi_rho" + "s_w" ], "zarr_format": 3, "node_type": "array", "storage_transformers": [] }, - "spC": { + "diatC": { "shape": [ 1, 3, @@ -343,7 +313,7 @@ } ], "attributes": { - "long_name": "small phytoplankton carbon", + "long_name": "diatom carbon", "units": "mmol/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" @@ -358,7 +328,7 @@ "node_type": "array", "storage_transformers": [] }, - "Lig": { + "diatChl": { "shape": [ 1, 3, @@ -400,8 +370,8 @@ } ], "attributes": { - "long_name": "iron binding ligand", - "units": "mmol/m^3", + "long_name": "diatom chloropyll", + "units": "mg/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" }, @@ -415,9 +385,10 @@ "node_type": "array", "storage_transformers": [] }, - "zeta": { + "diatFe": { "shape": [ 1, + 3, 4, 4 ], @@ -427,6 +398,7 @@ "configuration": { "chunk_shape": [ 1, + 3, 4, 4 ] @@ -455,13 +427,14 @@ } ], "attributes": { - "long_name": "sea surface height", - "units": "m", + "long_name": "diatom iron", + "units": "mmol/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" }, "dimension_names": [ "ocean_time", + "s_rho", "eta_rho", "xi_rho" ], @@ -469,7 +442,7 @@ "node_type": "array", "storage_transformers": [] }, - "diatFe": { + "diatP": { "shape": [ 1, 3, @@ -511,7 +484,7 @@ } ], "attributes": { - "long_name": "diatom iron", + "long_name": "diatom phosphorus", "units": "mmol/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" @@ -526,7 +499,7 @@ "node_type": "array", "storage_transformers": [] }, - "DIC": { + "diatSi": { "shape": [ 1, 3, @@ -568,7 +541,7 @@ } ], "attributes": { - "long_name": "dissolved inorganic carbon", + "long_name": "diatom silicate", "units": "mmol/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" @@ -583,16 +556,22 @@ "node_type": "array", "storage_transformers": [] }, - "abs_time": { + "diazC": { "shape": [ - 1 + 1, + 3, + 4, + 4 ], - "data_type": "int64", + "data_type": "float32", "chunk_grid": { "name": "regular", "configuration": { "chunk_shape": [ - 1 + 1, + 3, + 4, + 4 ] } }, @@ -602,7 +581,7 @@ "separator": "/" } }, - "fill_value": 0, + "fill_value": 0.0, "codecs": [ { "name": "bytes", @@ -619,18 +598,22 @@ } ], "attributes": { - "long_name": "absolute time", - "units": "days since 2021-06-29 00:00:00", - "calendar": "proleptic_gregorian" + "long_name": "diazotroph carbon", + "units": "mmol/m^3", + "coordinates": "abs_time", + "_FillValue": "AAAAAAAA+H8=" }, "dimension_names": [ - "ocean_time" + "ocean_time", + "s_rho", + "eta_rho", + "xi_rho" ], "zarr_format": 3, "node_type": "array", "storage_transformers": [] }, - "diazP": { + "diazChl": { "shape": [ 1, 3, @@ -672,8 +655,8 @@ } ], "attributes": { - "long_name": "diazotroph phosphorus", - "units": "mmol/m^3", + "long_name": "diazotroph chloropyll", + "units": "mg/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" }, @@ -687,7 +670,7 @@ "node_type": "array", "storage_transformers": [] }, - "diazC": { + "diazFe": { "shape": [ 1, 3, @@ -729,7 +712,7 @@ } ], "attributes": { - "long_name": "diazotroph carbon", + "long_name": "diazotroph iron", "units": "mmol/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" @@ -744,7 +727,7 @@ "node_type": "array", "storage_transformers": [] }, - "diatC": { + "diazP": { "shape": [ 1, 3, @@ -786,7 +769,7 @@ } ], "attributes": { - "long_name": "diatom carbon", + "long_name": "diazotroph phosphorus", "units": "mmol/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" @@ -801,7 +784,7 @@ "node_type": "array", "storage_transformers": [] }, - "O2": { + "DIC": { "shape": [ 1, 3, @@ -843,7 +826,7 @@ } ], "attributes": { - "long_name": "dissolved oxygen", + "long_name": "dissolved inorganic carbon", "units": "mmol/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" @@ -858,7 +841,7 @@ "node_type": "array", "storage_transformers": [] }, - "Fe": { + "DIC_ALT_CO2": { "shape": [ 1, 3, @@ -900,7 +883,7 @@ } ], "attributes": { - "long_name": "dissolved inorganic iron", + "long_name": "dissolved inorganic carbon, alternative CO2", "units": "mmol/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" @@ -915,7 +898,7 @@ "node_type": "array", "storage_transformers": [] }, - "diatChl": { + "DOC": { "shape": [ 1, 3, @@ -957,8 +940,8 @@ } ], "attributes": { - "long_name": "diatom chloropyll", - "units": "mg/m^3", + "long_name": "dissolved organic carbon", + "units": "mmol/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" }, @@ -972,7 +955,7 @@ "node_type": "array", "storage_transformers": [] }, - "diazFe": { + "DOCr": { "shape": [ 1, 3, @@ -1014,7 +997,7 @@ } ], "attributes": { - "long_name": "diazotroph iron", + "long_name": "refractory dissolved organic carbon", "units": "mmol/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" @@ -1086,7 +1069,7 @@ "node_type": "array", "storage_transformers": [] }, - "ALK": { + "DONr": { "shape": [ 1, 3, @@ -1128,8 +1111,8 @@ } ], "attributes": { - "long_name": "alkalinity", - "units": "meq/m^3", + "long_name": "refractory dissolved organic nitrogen", + "units": "mmol/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" }, @@ -1143,16 +1126,22 @@ "node_type": "array", "storage_transformers": [] }, - "ocean_time": { + "DOP": { "shape": [ - 1 + 1, + 3, + 4, + 4 ], - "data_type": "float64", + "data_type": "float32", "chunk_grid": { "name": "regular", "configuration": { "chunk_shape": [ - 1 + 1, + 3, + 4, + 4 ] } }, @@ -1179,19 +1168,26 @@ } ], "attributes": { - "long_name": "relative time: seconds since 2000-01-01 00:00:00", - "units": "seconds", + "long_name": "dissolved organic phosphorus", + "units": "mmol/m^3", + "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" }, "dimension_names": [ - "ocean_time" + "ocean_time", + "s_rho", + "eta_rho", + "xi_rho" ], "zarr_format": 3, "node_type": "array", "storage_transformers": [] }, - "Cs_w": { + "DOPr": { "shape": [ + 1, + 3, + 4, 4 ], "data_type": "float32", @@ -1199,6 +1195,9 @@ "name": "regular", "configuration": { "chunk_shape": [ + 1, + 3, + 4, 4 ] } @@ -1226,23 +1225,27 @@ } ], "attributes": { - "long_name": "Vertical stretching function at w-points", - "units": "nondimensional", + "long_name": "refractory dissolved organic phosphorus", + "units": "mmol/m^3", + "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" }, "dimension_names": [ - "s_w" + "ocean_time", + "s_rho", + "eta_rho", + "xi_rho" ], "zarr_format": 3, "node_type": "array", "storage_transformers": [] }, - "u": { + "Fe": { "shape": [ 1, 3, 4, - 3 + 4 ], "data_type": "float32", "chunk_grid": { @@ -1252,7 +1255,7 @@ 1, 3, 4, - 3 + 4 ] } }, @@ -1279,8 +1282,8 @@ } ], "attributes": { - "long_name": "u-flux component", - "units": "m/s", + "long_name": "dissolved inorganic iron", + "units": "mmol/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" }, @@ -1288,13 +1291,13 @@ "ocean_time", "s_rho", "eta_rho", - "xi_u" + "xi_rho" ], "zarr_format": 3, "node_type": "array", "storage_transformers": [] }, - "spFe": { + "Lig": { "shape": [ 1, 3, @@ -1336,7 +1339,7 @@ } ], "attributes": { - "long_name": "small phytoplankton iron", + "long_name": "iron binding ligand", "units": "mmol/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" @@ -1351,7 +1354,7 @@ "node_type": "array", "storage_transformers": [] }, - "DOCr": { + "NH4": { "shape": [ 1, 3, @@ -1393,7 +1396,7 @@ } ], "attributes": { - "long_name": "refractory dissolved organic carbon", + "long_name": "dissolved ammonia", "units": "mmol/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" @@ -1408,11 +1411,12 @@ "node_type": "array", "storage_transformers": [] }, - "ubar": { + "NO3": { "shape": [ 1, + 3, 4, - 3 + 4 ], "data_type": "float32", "chunk_grid": { @@ -1420,8 +1424,9 @@ "configuration": { "chunk_shape": [ 1, + 3, 4, - 3 + 4 ] } }, @@ -1448,21 +1453,22 @@ } ], "attributes": { - "long_name": "vertically integrated u-flux component", - "units": "m/s", + "long_name": "dissolved inorganic nitrate", + "units": "mmol/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" }, "dimension_names": [ "ocean_time", + "s_rho", "eta_rho", - "xi_u" + "xi_rho" ], "zarr_format": 3, "node_type": "array", "storage_transformers": [] }, - "diazChl": { + "O2": { "shape": [ 1, 3, @@ -1504,8 +1510,8 @@ } ], "attributes": { - "long_name": "diazotroph chloropyll", - "units": "mg/m^3", + "long_name": "dissolved oxygen", + "units": "mmol/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" }, @@ -1519,22 +1525,16 @@ "node_type": "array", "storage_transformers": [] }, - "SiO3": { + "ocean_time": { "shape": [ - 1, - 3, - 4, - 4 + 1 ], - "data_type": "float32", + "data_type": "float64", "chunk_grid": { "name": "regular", "configuration": { "chunk_shape": [ - 1, - 3, - 4, - 4 + 1 ] } }, @@ -1561,22 +1561,18 @@ } ], "attributes": { - "long_name": "dissolved inorganic silicate", - "units": "mmol/m^3", - "coordinates": "abs_time", + "long_name": "relative time: seconds since 2000-01-01 00:00:00", + "units": "seconds", "_FillValue": "AAAAAAAA+H8=" }, "dimension_names": [ - "ocean_time", - "s_rho", - "eta_rho", - "xi_rho" + "ocean_time" ], "zarr_format": 3, "node_type": "array", "storage_transformers": [] }, - "salt": { + "PO4": { "shape": [ 1, 3, @@ -1618,8 +1614,8 @@ } ], "attributes": { - "long_name": "salinity", - "units": "PSU", + "long_name": "dissolved inorganic phosphate", + "units": "mmol/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" }, @@ -1633,7 +1629,7 @@ "node_type": "array", "storage_transformers": [] }, - "DIC_ALT_CO2": { + "salt": { "shape": [ 1, 3, @@ -1675,8 +1671,8 @@ } ], "attributes": { - "long_name": "dissolved inorganic carbon, alternative CO2", - "units": "mmol/m^3", + "long_name": "salinity", + "units": "PSU", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" }, @@ -1690,10 +1686,11 @@ "node_type": "array", "storage_transformers": [] }, - "vbar": { + "SiO3": { "shape": [ 1, 3, + 4, 4 ], "data_type": "float32", @@ -1703,6 +1700,7 @@ "chunk_shape": [ 1, 3, + 4, 4 ] } @@ -1730,21 +1728,22 @@ } ], "attributes": { - "long_name": "vertically integrated v-flux component", - "units": "m/s", + "long_name": "dissolved inorganic silicate", + "units": "mmol/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" }, "dimension_names": [ "ocean_time", - "eta_v", + "s_rho", + "eta_rho", "xi_rho" ], "zarr_format": 3, "node_type": "array", "storage_transformers": [] }, - "DOP": { + "spC": { "shape": [ 1, 3, @@ -1786,7 +1785,7 @@ } ], "attributes": { - "long_name": "dissolved organic phosphorus", + "long_name": "small phytoplankton carbon", "units": "mmol/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" @@ -1801,7 +1800,7 @@ "node_type": "array", "storage_transformers": [] }, - "diatP": { + "spCaCO3": { "shape": [ 1, 3, @@ -1843,7 +1842,7 @@ } ], "attributes": { - "long_name": "diatom phosphorus", + "long_name": "small phytoplankton CaCO3", "units": "mmol/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" @@ -1858,7 +1857,7 @@ "node_type": "array", "storage_transformers": [] }, - "ALK_ALT_CO2": { + "spChl": { "shape": [ 1, 3, @@ -1900,8 +1899,8 @@ } ], "attributes": { - "long_name": "alkalinity, alternative CO2", - "units": "meq/m^3", + "long_name": "small phytoplankton chlorophyll", + "units": "mg/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" }, @@ -1915,7 +1914,7 @@ "node_type": "array", "storage_transformers": [] }, - "spChl": { + "spFe": { "shape": [ 1, 3, @@ -1957,8 +1956,8 @@ } ], "attributes": { - "long_name": "small phytoplankton chlorophyll", - "units": "mg/m^3", + "long_name": "small phytoplankton iron", + "units": "mmol/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" }, @@ -1972,7 +1971,7 @@ "node_type": "array", "storage_transformers": [] }, - "NO3": { + "spP": { "shape": [ 1, 3, @@ -2014,7 +2013,7 @@ } ], "attributes": { - "long_name": "dissolved inorganic nitrate", + "long_name": "small phytoplankton phosphorous", "units": "mmol/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" @@ -2029,7 +2028,7 @@ "node_type": "array", "storage_transformers": [] }, - "diatSi": { + "temp": { "shape": [ 1, 3, @@ -2071,8 +2070,8 @@ } ], "attributes": { - "long_name": "diatom silicate", - "units": "mmol/m^3", + "long_name": "potential temperature", + "units": "degrees Celsius", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" }, @@ -2086,12 +2085,12 @@ "node_type": "array", "storage_transformers": [] }, - "DOPr": { + "u": { "shape": [ 1, 3, 4, - 4 + 3 ], "data_type": "float32", "chunk_grid": { @@ -2101,7 +2100,7 @@ 1, 3, 4, - 4 + 3 ] } }, @@ -2128,8 +2127,8 @@ } ], "attributes": { - "long_name": "refractory dissolved organic phosphorus", - "units": "mmol/m^3", + "long_name": "u-flux component", + "units": "m/s", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" }, @@ -2137,18 +2136,17 @@ "ocean_time", "s_rho", "eta_rho", - "xi_rho" + "xi_u" ], "zarr_format": 3, "node_type": "array", "storage_transformers": [] }, - "DOC": { + "ubar": { "shape": [ 1, - 3, 4, - 4 + 3 ], "data_type": "float32", "chunk_grid": { @@ -2156,9 +2154,8 @@ "configuration": { "chunk_shape": [ 1, - 3, 4, - 4 + 3 ] } }, @@ -2185,26 +2182,25 @@ } ], "attributes": { - "long_name": "dissolved organic carbon", - "units": "mmol/m^3", + "long_name": "vertically integrated u-flux component", + "units": "m/s", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" }, "dimension_names": [ "ocean_time", - "s_rho", "eta_rho", - "xi_rho" + "xi_u" ], "zarr_format": 3, "node_type": "array", "storage_transformers": [] }, - "zooC": { + "v": { "shape": [ 1, 3, - 4, + 3, 4 ], "data_type": "float32", @@ -2214,7 +2210,7 @@ "chunk_shape": [ 1, 3, - 4, + 3, 4 ] } @@ -2242,26 +2238,25 @@ } ], "attributes": { - "long_name": "zooplankton carbon", - "units": "mmol/m^3", + "long_name": "v-flux component", + "units": "m/s", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" }, "dimension_names": [ "ocean_time", "s_rho", - "eta_rho", + "eta_v", "xi_rho" ], "zarr_format": 3, "node_type": "array", "storage_transformers": [] }, - "NH4": { + "vbar": { "shape": [ 1, 3, - 4, 4 ], "data_type": "float32", @@ -2271,7 +2266,6 @@ "chunk_shape": [ 1, 3, - 4, 4 ] } @@ -2299,15 +2293,14 @@ } ], "attributes": { - "long_name": "dissolved ammonia", - "units": "mmol/m^3", + "long_name": "vertically integrated v-flux component", + "units": "m/s", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" }, "dimension_names": [ "ocean_time", - "s_rho", - "eta_rho", + "eta_v", "xi_rho" ], "zarr_format": 3, @@ -2371,16 +2364,20 @@ "node_type": "array", "storage_transformers": [] }, - "Cs_r": { + "zeta": { "shape": [ - 3 + 1, + 4, + 4 ], "data_type": "float32", "chunk_grid": { "name": "regular", "configuration": { "chunk_shape": [ - 3 + 1, + 4, + 4 ] } }, @@ -2407,22 +2404,25 @@ } ], "attributes": { - "long_name": "Vertical stretching function at rho-points", - "units": "nondimensional", + "long_name": "sea surface height", + "units": "m", + "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" }, "dimension_names": [ - "s_rho" + "ocean_time", + "eta_rho", + "xi_rho" ], "zarr_format": 3, "node_type": "array", "storage_transformers": [] }, - "v": { + "zooC": { "shape": [ 1, 3, - 3, + 4, 4 ], "data_type": "float32", @@ -2432,7 +2432,7 @@ "chunk_shape": [ 1, 3, - 3, + 4, 4 ] } @@ -2460,15 +2460,15 @@ } ], "attributes": { - "long_name": "v-flux component", - "units": "m/s", + "long_name": "zooplankton carbon", + "units": "mmol/m^3", "coordinates": "abs_time", "_FillValue": "AAAAAAAA+H8=" }, "dimension_names": [ "ocean_time", "s_rho", - "eta_v", + "eta_rho", "xi_rho" ], "zarr_format": 3, diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/zooC/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/zooC/c/0/0/0/0 index dc1a75d87..8c745993c 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/zooC/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_bgc_from_climatology.zarr/zooC/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/ALK/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/ALK/c/0/0/0/0 index fd8ea3365..43a691d53 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/ALK/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/ALK/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/ALK_ALT_CO2/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/ALK_ALT_CO2/c/0/0/0/0 index fd8ea3365..43a691d53 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/ALK_ALT_CO2/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/ALK_ALT_CO2/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/DIC/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/DIC/c/0/0/0/0 index 31df7c219..3a017aceb 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/DIC/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/DIC/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/DIC_ALT_CO2/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/DIC_ALT_CO2/c/0/0/0/0 index 31df7c219..3a017aceb 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/DIC_ALT_CO2/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/DIC_ALT_CO2/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/NO3/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/NO3/c/0/0/0/0 index 1065da6d0..a3e5190f0 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/NO3/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/NO3/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/O2/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/O2/c/0/0/0/0 index f3972756d..788e788c2 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/O2/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/O2/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/PO4/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/PO4/c/0/0/0/0 index 7c9d8222b..d40112a6b 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/PO4/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/PO4/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/SiO3/c/0/0/0/0 b/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/SiO3/c/0/0/0/0 index e25e502d4..9678f0f29 100644 Binary files a/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/SiO3/c/0/0/0/0 and b/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/SiO3/c/0/0/0/0 differ diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/abs_time/zarr.json b/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/abs_time/zarr.json index 2bb19a596..ed8149eaa 100644 --- a/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/abs_time/zarr.json +++ b/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/abs_time/zarr.json @@ -2,7 +2,7 @@ "shape": [ 1 ], - "data_type": "float64", + "data_type": "int64", "chunk_grid": { "name": "regular", "configuration": { @@ -17,7 +17,7 @@ "separator": "/" } }, - "fill_value": 0.0, + "fill_value": 0, "codecs": [ { "name": "bytes", @@ -36,8 +36,7 @@ "attributes": { "long_name": "absolute time", "units": "days since 2021-06-29 00:00:00", - "calendar": "proleptic_gregorian", - "_FillValue": "AAAAAAAA+H8=" + "calendar": "proleptic_gregorian" }, "dimension_names": [ "ocean_time" diff --git a/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/zarr.json b/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/zarr.json index 286b9430d..d6b9c7369 100644 --- a/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/zarr.json +++ b/roms_tools/tests/test_setup/test_data/initial_conditions_with_unified_bgc_from_climatology.zarr/zarr.json @@ -1,7 +1,7 @@ { "attributes": { "title": "ROMS initial conditions file created by ROMS-Tools", - "roms_tools_version": "3.6.1.dev26+g58d7c491b", + "roms_tools_version": "10000.dev355+g36bf2550d", "ini_time": "2021-06-29 00:00:00", "model_reference_date": "2000-01-01 00:00:00", "adjust_depth_for_sea_surface_height": "False", @@ -20,7 +20,7 @@ "shape": [ 1 ], - "data_type": "float64", + "data_type": "int64", "chunk_grid": { "name": "regular", "configuration": { @@ -35,7 +35,7 @@ "separator": "/" } }, - "fill_value": 0.0, + "fill_value": 0, "codecs": [ { "name": "bytes", @@ -54,8 +54,7 @@ "attributes": { "long_name": "absolute time", "units": "days since 2021-06-29 00:00:00", - "calendar": "proleptic_gregorian", - "_FillValue": "AAAAAAAA+H8=" + "calendar": "proleptic_gregorian" }, "dimension_names": [ "ocean_time" diff --git a/roms_tools/tests/test_setup/test_initial_conditions.py b/roms_tools/tests/test_setup/test_initial_conditions.py index 71a3d3535..a0a3454dc 100644 --- a/roms_tools/tests/test_setup/test_initial_conditions.py +++ b/roms_tools/tests/test_setup/test_initial_conditions.py @@ -702,3 +702,61 @@ def test_bgc_var_type(): def test_invalid_var_type(): with pytest.raises(ValueError, match="Unsupported var_type"): _set_required_vars("invalid_type") + + +# test density interpolation + + +def test_ic_density_interpolation_saves_physics_attrs( + initial_conditions_with_unified_bgc_from_climatology, +): + """Verify that density interpolation (default) saves physics depth-level T/S attributes.""" + ic = initial_conditions_with_unified_bgc_from_climatology + assert ic.use_density_interpolation is True + assert ic._phys_depth_data is not None + assert {"temp", "salt", "depth_dim", "depth_coord"} <= ic._phys_depth_data.keys() + + +def test_ic_bgc_vars_present_with_density_interpolation( + initial_conditions_with_unified_bgc_from_climatology, +): + """BGC variables are present in the dataset when density interpolation is used.""" + ic = initial_conditions_with_unified_bgc_from_climatology + assert ic.use_density_interpolation is True + for var in ["NO3", "DIC", "ALK", "O2", "PO4"]: + assert var in ic.ds + + +def test_ic_density_vs_depth_interpolation(use_dask): + """Density-based and depth-based IC produce same shape/vars but (potentially) different values.""" + grid = Grid( + nx=2, + ny=2, + size_x=500, + size_y=1000, + center_lon=0, + center_lat=55, + rot=10, + N=3, + theta_s=5.0, + theta_b=2.0, + hc=250.0, + ) + fname = Path(download_test_data("GLORYS_coarse_test_data.nc")) + fname_bgc = Path(download_test_data("coarsened_UNIFIED_bgc_dataset.nc")) + + common_kwargs = dict( + grid=grid, + ini_time=datetime(2021, 6, 29), + source={"path": fname, "name": "GLORYS"}, + bgc_source={"path": fname_bgc, "name": "UNIFIED", "climatology": True}, + use_dask=use_dask, + ) + + ic_density = InitialConditions(**common_kwargs, use_density_interpolation=True) + ic_depth = InitialConditions(**common_kwargs, use_density_interpolation=False) + + for var in ["NO3", "DIC", "ALK"]: + assert var in ic_density.ds + assert var in ic_depth.ds + assert ic_density.ds[var].shape == ic_depth.ds[var].shape diff --git a/roms_tools/tests/test_setup/test_utils.py b/roms_tools/tests/test_setup/test_utils.py index 1e6311430..66aea609e 100644 --- a/roms_tools/tests/test_setup/test_utils.py +++ b/roms_tools/tests/test_setup/test_utils.py @@ -9,8 +9,10 @@ from roms_tools import BoundaryForcing, Grid from roms_tools.datasets.download import download_test_data from roms_tools.setup.utils import ( + _compute_bgc_source_density, _infer_valid_boundaries_from_mask, check_and_set_boundaries, + compute_potential_density, get_target_coords, validate_names, ) @@ -295,3 +297,114 @@ def test_check_and_set_full_user_boundaries(simple_mask): result = check_and_set_boundaries(boundaries, simple_mask) assert result == boundaries # unchanged + + +# test compute_potential_density + + +def test_compute_potential_density_known_value(): + """Verify sigma-0 against a known seawater value (T=20°C, S=35 PSU → ~24.64 kg/m³).""" + temp = xr.DataArray([[20.0]]) + salt = xr.DataArray([[35.0]]) + result = compute_potential_density(temp, salt) + assert float(result.values.flat[0]) == pytest.approx(24.64, abs=0.1) + + +def test_compute_potential_density_dask(): + """Verify compute_potential_density returns a lazy dask-backed array.""" + import dask.array as da + + temp = xr.DataArray(da.from_array([[20.0]], chunks=(1, 1))) + salt = xr.DataArray(da.from_array([[35.0]], chunks=(1, 1))) + result = compute_potential_density(temp, salt) + assert result.chunks is not None + + +def test_compute_bgc_source_density_constant_TS(): + """For constant T/S, source density at each BGC depth equals sigma0(S,T) + monotonicity perturbation.""" + import gsw + + n_phys = 4 + n_bgc = 3 + T_const, S_const = 10.0, 35.0 + phys_temp = xr.DataArray( + np.full((n_phys, 1, 1), T_const), dims=["depth", "eta", "xi"] + ) + phys_salt = xr.DataArray( + np.full((n_phys, 1, 1), S_const), dims=["depth", "eta", "xi"] + ) + phys_depth = xr.DataArray( + np.array([10.0, 100.0, 500.0, 2000.0]), + dims=["depth"], + coords={"depth": [10.0, 100.0, 500.0, 2000.0]}, + ) + bgc_depth = xr.DataArray( + np.array([20.0, 300.0, 1500.0]), + dims=["depth"], + coords={"depth": [20.0, 300.0, 1500.0]}, + ) + + result = _compute_bgc_source_density( + phys_temp, + phys_salt, + "depth", + phys_depth, + bgc_depth, + "depth", + ) + + # gsw.sigma0 is invariant in space for constant T/S; only the monotonicity + # perturbation along the BGC depth axis (added in _compute_bgc_source_density) + # should change the value across depth. + expected_base = gsw.sigma0(S_const, T_const) + expected = expected_base + np.arange(n_bgc) * 1e-7 + np.testing.assert_allclose( + result.values[:, 0, 0], + expected, + rtol=0.0, + atol=1e-6, + ) + + +def test_compute_bgc_source_density_linear_TS_profile(): + """Density at BGC depths is the interpolation of physics densities to BGC depths.""" + import gsw + + # Linear T/S profiles. Physics depths bracket the BGC depths so result is + # purely the linear interpolation in depth space (no extrapolation). + phys_depths = np.array([0.0, 100.0, 500.0, 2000.0]) + phys_T = np.array([20.0, 15.0, 8.0, 2.0]) + phys_S = np.array([34.5, 34.8, 34.9, 35.0]) + bgc_depths = np.array([50.0, 250.0, 1200.0]) + + phys_temp = xr.DataArray(phys_T.reshape(-1, 1, 1), dims=["depth", "eta", "xi"]) + phys_salt = xr.DataArray(phys_S.reshape(-1, 1, 1), dims=["depth", "eta", "xi"]) + phys_depth = xr.DataArray( + phys_depths, dims=["depth"], coords={"depth": phys_depths} + ) + bgc_depth = xr.DataArray(bgc_depths, dims=["depth"], coords={"depth": bgc_depths}) + + result = _compute_bgc_source_density( + phys_temp, + phys_salt, + "depth", + phys_depth, + bgc_depth, + "depth", + ) + + # Manual reference: compute density at phys depths with the same perturbation + # the function adds, then linearly interpolate to BGC depths, then add the + # BGC-depth perturbation. + phys_density_perturbed = ( + gsw.sigma0(phys_S, phys_T) + np.arange(len(phys_depths)) * 1e-7 + ) + expected = np.interp(bgc_depths, phys_depths, phys_density_perturbed) + expected = expected + np.arange(len(bgc_depths)) * 1e-7 + + np.testing.assert_allclose( + result.values[:, 0, 0], + expected, + rtol=1e-10, + atol=1e-10, + ) diff --git a/roms_tools/utils.py b/roms_tools/utils.py index 266e3aefe..1c6455c8e 100644 --- a/roms_tools/utils.py +++ b/roms_tools/utils.py @@ -819,11 +819,11 @@ def transpose_dimensions(da: xr.DataArray) -> xr.DataArray: Returns ------- xarray.DataArray - The DataArray with dimensions reordered so that 'time', 's_*', 'eta_*', + The DataArray with dimensions reordered so that 'time', 's_*', 'depth', 'eta_*', and 'xi_*' are first, in that order, if they exist. """ # List of preferred dimension patterns - preferred_order = ["time", "s_", "eta_", "xi_"] + preferred_order = ["time", "s_", "depth", "eta_", "xi_"] # Get the existing dimensions in the DataArray dims = list(da.dims)