From 8d31f54020b8e2d8108c79456096d589dad93694 Mon Sep 17 00:00:00 2001 From: benbar Date: Fri, 26 Jun 2026 16:25:18 +0100 Subject: [PATCH 01/10] Adding regridder class that replaces profiles in a model climatology. --- OceanOSSE/gridding/regridder_simple.py | 118 +++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 OceanOSSE/gridding/regridder_simple.py diff --git a/OceanOSSE/gridding/regridder_simple.py b/OceanOSSE/gridding/regridder_simple.py new file mode 100644 index 0000000..cbc800e --- /dev/null +++ b/OceanOSSE/gridding/regridder_simple.py @@ -0,0 +1,118 @@ +# =================================================================== +# Copyright 2025 National Oceanography Centre +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. See the License for the specific language governing +# permissions and limitations under the License. +# =================================================================== + +""" +sampler_nearest_neighbour.py + +Description: Sampling module for OceanOSSE package. + +Created By: OceanOSSE Development Team (NOC, UK) +""" + +# -- Import Dependencies -- # +from __future__ import annotations + +import logging + +import xarray as xr +import numpy as np + +from OceanOSSE.utils import import_class +from OceanOSSE.gridding.regridder import Regridder + +logger = logging.getLogger(__name__) + + +class SwapRegridder(Regridder): + """ + Regridding class for synthetic ocean observations onto + the original model grid using climatology and exchanging profiles. + + Parameters + ---------- + target_grid : xarray.Dataset or None, optional + Dataset describing the target grid (coordinates, masks, etc.). + """ + + def __init__( + self, + target_grid: xr.Dataset | None = None, + ) -> None: + if target_grid is not None and not isinstance(target_grid, xr.Dataset): + raise TypeError("``target_grid`` must be an xarray.Dataset or None.") + self._target_grid = target_grid + self.ds_clim = self.climatology() + + def __repr__(self) -> str: + has_grid = self._target_grid is not None + return f"{type(self).__name__}(target_grid={'' if has_grid else None})" + + + def from_config(cls, config: dict) -> Self: + """ + Construct a Regridder from the from the `[regridding]` table of + the .toml configuration file. + + Parameters + ---------- + config : dict + Configuration dictionary containing input parameters from .toml + configuration file. + + Returns + ------- + Self + Initialised Regridder instance. + """ + + return self + + + def regrid(self, ds: xr.Dataset) -> xr.Dataset: + """ + Regrid the synthetic observation dataset into the target grid. + + Parameters + ---------- + ds : xarray.Dataset + Synthetic observations dataset. + + Returns + ------- + xarray.Dataset + Dataset of synthetic observations placed into target grid. + """ + # Use indices in synthetic profile set to replace data in the climatology with model data + ds_model = self.ds_clim + n_profile = len(ds.coords['profile_id']) + + # loop over profiles + for p in range(n_profile): + i_ind = ds.coords['i'][p].to_numpy() + j_ind = ds.coords['j'][p].to_numpy() + t_ind = ds.coords['t'][p].to_numpy() + ps = ds.coords['profile_id'][p].to_numpy() + + profile = ds['votemper'].isel(profile_id=ps) + + ds_model['votemper'].loc[ + dict( + t=ds.t.sel(profile_id=ps), + j=ds.j.sel(profile_id=ps), + i=ds.i.sel(profile_id=ps)) + ] = profile.values + + return ds_model + + From a1299f7d8c420aa5a3d4f8bda292c52be6101af2 Mon Sep 17 00:00:00 2001 From: benbar Date: Fri, 26 Jun 2026 16:36:17 +0100 Subject: [PATCH 02/10] Adding tests for regidder. Tests have criteria for getting the same mean and a single value --- tests/unit/test_regridder.py | 118 +++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 tests/unit/test_regridder.py diff --git a/tests/unit/test_regridder.py b/tests/unit/test_regridder.py new file mode 100644 index 0000000..974a8d6 --- /dev/null +++ b/tests/unit/test_regridder.py @@ -0,0 +1,118 @@ +# =================================================================== +# Copyright 2025 National Oceanography Centre +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. See the License for the specific language governing +# permissions and limitations under the License. +# =================================================================== +""" +test_sampler.py + +Description: +This module includes unit tests for extracting profiles. + +Author: +Benjamin Barton (benbar@noc.ac.uk) +""" + +import pytest +import datetime as dt +import numpy as np +import xarray as xr + +from OceanOSSE.gridding.regridder_simple import SwapRegridder + + +def test_regrid(): + """ + Test replacing profiles in climatology with model data. + """ + ds = construct_ds() + synth_profiles = construct_profile_ds() + + regrid_data = SwapRegridder(ds) + ds_model = regrid_data.regrid(synth_profiles) + + assert ((ds_model != regrid_data.ds_clim) + & (ds_model.isel(i=3, j=5, t=31) == synth_profiles.isel(profile_id=0))) + + +def construct_ds(): + """ + Build a dataset for testing. + """ + lat = np.arange(0, 8) + lon = np.arange(0, 10) + depth = np.arange(0, 150, 10) + st_date = dt.datetime(2020, 5, 1) + num_days = 730 + model_dates = np.array([st_date + dt.timedelta(days=x) for x in range(num_days)]) + model_day = np.array([x for x in range(num_days)]) + + # Broadcast to 4D (time, depth, lat, lon) + t, d, y, x = np.meshgrid(model_day, depth, lat, lon, indexing='ij') + + votemper = 15 - (y * 0.4) + (x * 0.2) - (d * 0.2) + (t * 0.000005) + + # Build dataset + ds = xr.Dataset( + { + "votemper": (("t", "d", "j", "i"), votemper), + "lat": (("j", "i"), y[0, 0, :, :]), + "lon": (("j", "i"), x[0, 0, :, :]), + "depth": (("d", "j", "i"), d[0, :, :, :]), + "time": (("t"), t[:, 0, 0, 0]) + }, + coords={ + "d": depth, + "j": lat, + "i": lon, + "t": model_dates + }, + ) + + return ds + + +def construct_profile_ds(): + d = np.arange(0, 150, 10) + profile_id = np.arange(2) + + j = np.array([5, 6]) + i = np.array([3, 8]) + + depth = np.tile(d[:, None], (1, profile_id.size)) + + # Time coordinate + st_date = dt.datetime(2020, 5, 1) + time = np.array([ + dt.datetime(2020, 6, 1), + dt.datetime(2020, 7, 2), + ]) + time_day = np.array([(x - st_date).days for x in time]) + + votemper = 15 - depth * 0.02 - j[None, :] * 0.2 + i[None, :] * 0.1 + (time_day * 0.000005) + + ds = xr.Dataset( + data_vars={ + "votemper": (("d", "profile_id"), votemper), + "lat": (("profile_id",), j), + "lon": (("profile_id",), i), + "depth": (("d", "profile_id"), depth), + }, + coords={ + "d": d, + "profile_id": profile_id, + "t": (("profile_id",), time), + "j": (("profile_id",), j), + "i": (("profile_id",), i), + }, + ) + + return ds From c9cf250f356c36672ac17e384398ca2f149a02f2 Mon Sep 17 00:00:00 2001 From: benbar Date: Mon, 29 Jun 2026 15:17:50 +0100 Subject: [PATCH 03/10] Added month coordinate so it could be used indexing the climatology in the future if that is needed. --- OceanOSSE/gridding/regridder_simple.py | 36 ++++++++++++++++---------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/OceanOSSE/gridding/regridder_simple.py b/OceanOSSE/gridding/regridder_simple.py index cbc800e..a05e203 100644 --- a/OceanOSSE/gridding/regridder_simple.py +++ b/OceanOSSE/gridding/regridder_simple.py @@ -51,8 +51,7 @@ def __init__( ) -> None: if target_grid is not None and not isinstance(target_grid, xr.Dataset): raise TypeError("``target_grid`` must be an xarray.Dataset or None.") - self._target_grid = target_grid - self.ds_clim = self.climatology() + self.target_grid = target_grid def __repr__(self) -> str: has_grid = self._target_grid is not None @@ -79,7 +78,7 @@ def from_config(cls, config: dict) -> Self: return self - def regrid(self, ds: xr.Dataset) -> xr.Dataset: + def regrid(self, ds_profile: xr.Dataset) -> xr.Dataset: """ Regrid the synthetic observation dataset into the target grid. @@ -93,24 +92,33 @@ def regrid(self, ds: xr.Dataset) -> xr.Dataset: xarray.Dataset Dataset of synthetic observations placed into target grid. """ - # Use indices in synthetic profile set to replace data in the climatology with model data - ds_model = self.ds_clim - n_profile = len(ds.coords['profile_id']) + # Use indices in synthetic profile set to replace data in the + # climatology with model data + ds_model = self.target_grid # in future this will already be a climatology + # calculate month for profiles to put them on climatology + ds_profile = ds_profile.assign_coords( + month=("profile_id", ds_profile.t.dt.strftime("%m").astype(int).data) + ) + + n_profile = len(ds_profile.coords['profile_id']) + # loop over profiles for p in range(n_profile): - i_ind = ds.coords['i'][p].to_numpy() - j_ind = ds.coords['j'][p].to_numpy() - t_ind = ds.coords['t'][p].to_numpy() - ps = ds.coords['profile_id'][p].to_numpy() + i_ind = ds_profile.coords['i'][p].to_numpy() + j_ind = ds_profile.coords['j'][p].to_numpy() + t_ind = ds_profile.coords['t'][p].to_numpy() + ps = ds_profile.coords['profile_id'][p].to_numpy() - profile = ds['votemper'].isel(profile_id=ps) + profile = ds_profile['votemper'].isel(profile_id=ps) ds_model['votemper'].loc[ dict( - t=ds.t.sel(profile_id=ps), - j=ds.j.sel(profile_id=ps), - i=ds.i.sel(profile_id=ps)) + #month=ds_profile.month.sel(profile_id=ps), + t=ds_profile.t.sel(profile_id=ps), + j=ds_profile.j.sel(profile_id=ps), + i=ds_profile.i.sel(profile_id=ps)) + #d=ds_profile.d.sel(profile_id=ps)) ] = profile.values return ds_model From 12a039aba9491faf03b8afe71936f015f032eff6 Mon Sep 17 00:00:00 2001 From: benbar Date: Mon, 29 Jun 2026 15:19:00 +0100 Subject: [PATCH 04/10] Added climatology calculation to test dataset so it will the the same as the input. --- tests/unit/test_regridder.py | 37 ++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_regridder.py b/tests/unit/test_regridder.py index 974a8d6..52e6669 100644 --- a/tests/unit/test_regridder.py +++ b/tests/unit/test_regridder.py @@ -34,12 +34,14 @@ def test_regrid(): Test replacing profiles in climatology with model data. """ ds = construct_ds() + ds_clim = climatology(ds) synth_profiles = construct_profile_ds() - regrid_data = SwapRegridder(ds) + regrid_data = SwapRegridder(ds_clim) ds_model = regrid_data.regrid(synth_profiles) + print(ds_model) - assert ((ds_model != regrid_data.ds_clim) + assert ((ds_model != ds_clim) & (ds_model.isel(i=3, j=5, t=31) == synth_profiles.isel(profile_id=0))) @@ -116,3 +118,34 @@ def construct_profile_ds(): ) return ds + + +def climatology(ds): + """ + Calculate the climatology of the target grid. + + Parameters + ---------- + ds : xarray.Dataset + Input time varying dataset. + + Returns + ------- + xarray.Dataset + Dataset of monthly means. + """ + ds = ds.assign_coords( + month=("t", ds.t.dt.strftime("%m").astype(int).data) + ) + # calculate climatology + ds_clim = ds.groupby('month').mean() + + # tile the climatology data back over full time series + ds_clim_full = ds_clim.sel(month=ds.month) + + # Remove not needed time dim from variables + for v in ["lat", "lon", "depth"]: + ds_clim_full[v] = ds_clim_full[v].isel(t=0, drop=True) + #ds_clim_full = ds_clim_full.drop_vars('month') + + return ds_clim_full From ac08b456f4625c41005c733a44ac6b6b7ebd5e3c Mon Sep 17 00:00:00 2001 From: benbar Date: Mon, 29 Jun 2026 15:23:16 +0100 Subject: [PATCH 05/10] Added test of climatology function --- tests/unit/test_regridder.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/unit/test_regridder.py b/tests/unit/test_regridder.py index 52e6669..db95639 100644 --- a/tests/unit/test_regridder.py +++ b/tests/unit/test_regridder.py @@ -44,6 +44,27 @@ def test_regrid(): assert ((ds_model != ds_clim) & (ds_model.isel(i=3, j=5, t=31) == synth_profiles.isel(profile_id=0))) +def test_climatology(): + """ + Test producing a daily climatology. + """ + ds = construct_ds() + clim = climatology(ds) + + ts = ds.votemper.mean(dim=["d", "j", "i"]) + clim_mean = clim.votemper.mean(dim=["d", "j", "i"]) + + st_date = dt.datetime(2020, 5, 1) + test_sec1 = (dt.datetime(2020, 5, 30) - st_date).days + test_sec2 = (dt.datetime(2021, 5, 30) - st_date).days + test_temp1 = 15 - (0 * 0.4) + (0 * 0.2) - (0 * 0.2) + (test_sec1 * 0.000005) + test_temp2 = 15 - (0 * 0.4) + (0 * 0.2) - (0 * 0.2) + (test_sec2 * 0.000005) + test_temp = (test_temp1 + test_temp2) / 2 + clim_day = clim.votemper.sel(t='2020-05-30').isel(d=0, j=0, i=0) + + assert (np.isclose(ts.mean().to_numpy(), clim_mean.mean().to_numpy(), atol=1e8) + & (clim_day.to_numpy() == test_temp)) + def construct_ds(): """ From 5a298bc111abeaecd604aadc64482dce6d19678e Mon Sep 17 00:00:00 2001 From: benbar Date: Mon, 29 Jun 2026 15:46:31 +0100 Subject: [PATCH 06/10] Accounted for changing to monthly climatology instead of daily --- tests/unit/test_regridder.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tests/unit/test_regridder.py b/tests/unit/test_regridder.py index db95639..a82b56d 100644 --- a/tests/unit/test_regridder.py +++ b/tests/unit/test_regridder.py @@ -50,19 +50,24 @@ def test_climatology(): """ ds = construct_ds() clim = climatology(ds) + print(clim) ts = ds.votemper.mean(dim=["d", "j", "i"]) clim_mean = clim.votemper.mean(dim=["d", "j", "i"]) - st_date = dt.datetime(2020, 5, 1) - test_sec1 = (dt.datetime(2020, 5, 30) - st_date).days - test_sec2 = (dt.datetime(2021, 5, 30) - st_date).days + st_date1 = dt.datetime(2020, 5, 1) + st_date2 = dt.datetime(2021, 5, 1) + test_date1 = np.array([st_date1 + dt.timedelta(days=x) for x in range(31)]) + test_date2 = np.array([st_date2 + dt.timedelta(days=x) for x in range(31)]) + test_sec1 = np.array([(x - st_date1).days for x in test_date1]) + test_sec2 = np.array([(x - st_date1).days for x in test_date2]) + print(test_sec1) test_temp1 = 15 - (0 * 0.4) + (0 * 0.2) - (0 * 0.2) + (test_sec1 * 0.000005) test_temp2 = 15 - (0 * 0.4) + (0 * 0.2) - (0 * 0.2) + (test_sec2 * 0.000005) - test_temp = (test_temp1 + test_temp2) / 2 - clim_day = clim.votemper.sel(t='2020-05-30').isel(d=0, j=0, i=0) + test_temp = np.sum(test_temp1 + test_temp2) / (2 * 31) + clim_day = clim.votemper.sel(t='2020-05-01').isel(d=0, j=0, i=0) - assert (np.isclose(ts.mean().to_numpy(), clim_mean.mean().to_numpy(), atol=1e8) + assert (np.isclose(ts.mean().to_numpy(), clim_mean.mean().to_numpy(), atol=1e-8) & (clim_day.to_numpy() == test_temp)) From a9879d6485c85a32dcbe51006d8cd55f92292ac8 Mon Sep 17 00:00:00 2001 From: benbar Date: Tue, 30 Jun 2026 17:25:41 +0100 Subject: [PATCH 07/10] Adding fixtures --- tests/unit/test_regridder.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/tests/unit/test_regridder.py b/tests/unit/test_regridder.py index a82b56d..1728fb9 100644 --- a/tests/unit/test_regridder.py +++ b/tests/unit/test_regridder.py @@ -29,28 +29,27 @@ from OceanOSSE.gridding.regridder_simple import SwapRegridder -def test_regrid(): +def test_regrid(construct_ds, construct_profile_ds): """ Test replacing profiles in climatology with model data. """ - ds = construct_ds() + ds_profile = construct_profile_ds + ds = construct_ds ds_clim = climatology(ds) - synth_profiles = construct_profile_ds() regrid_data = SwapRegridder(ds_clim) - ds_model = regrid_data.regrid(synth_profiles) - print(ds_model) + ds_model = regrid_data.regrid(ds_profile) assert ((ds_model != ds_clim) - & (ds_model.isel(i=3, j=5, t=31) == synth_profiles.isel(profile_id=0))) + & (ds_model.isel(i=3, j=5, t=31) + == ds_profile.isel(profile_id=0))) -def test_climatology(): +def test_climatology(construct_ds): """ Test producing a daily climatology. """ - ds = construct_ds() + ds = construct_ds clim = climatology(ds) - print(clim) ts = ds.votemper.mean(dim=["d", "j", "i"]) clim_mean = clim.votemper.mean(dim=["d", "j", "i"]) @@ -61,7 +60,6 @@ def test_climatology(): test_date2 = np.array([st_date2 + dt.timedelta(days=x) for x in range(31)]) test_sec1 = np.array([(x - st_date1).days for x in test_date1]) test_sec2 = np.array([(x - st_date1).days for x in test_date2]) - print(test_sec1) test_temp1 = 15 - (0 * 0.4) + (0 * 0.2) - (0 * 0.2) + (test_sec1 * 0.000005) test_temp2 = 15 - (0 * 0.4) + (0 * 0.2) - (0 * 0.2) + (test_sec2 * 0.000005) test_temp = np.sum(test_temp1 + test_temp2) / (2 * 31) @@ -70,8 +68,9 @@ def test_climatology(): assert (np.isclose(ts.mean().to_numpy(), clim_mean.mean().to_numpy(), atol=1e-8) & (clim_day.to_numpy() == test_temp)) - -def construct_ds(): + +@pytest.fixture +def construct_ds() -> xr.Dataset: """ Build a dataset for testing. """ @@ -87,11 +86,14 @@ def construct_ds(): t, d, y, x = np.meshgrid(model_day, depth, lat, lon, indexing='ij') votemper = 15 - (y * 0.4) + (x * 0.2) - (d * 0.2) + (t * 0.000005) + vosaline = 33 + (y * 0.4) - (x * 0.2) + (d * 0.2) + (t * 0.000005) # Build dataset ds = xr.Dataset( { "votemper": (("t", "d", "j", "i"), votemper), + "vosaline": (("t", "d", "j", "i"), vosaline), + "lat": (("j", "i"), y[0, 0, :, :]), "lon": (("j", "i"), x[0, 0, :, :]), "depth": (("d", "j", "i"), d[0, :, :, :]), @@ -108,7 +110,8 @@ def construct_ds(): return ds -def construct_profile_ds(): +@pytest.fixture +def construct_profile_ds() -> xr.Dataset: d = np.arange(0, 150, 10) profile_id = np.arange(2) @@ -126,10 +129,13 @@ def construct_profile_ds(): time_day = np.array([(x - st_date).days for x in time]) votemper = 15 - depth * 0.02 - j[None, :] * 0.2 + i[None, :] * 0.1 + (time_day * 0.000005) + vosaline = 33 + depth * 0.02 + j[None, :] * 0.2 - i[None, :] * 0.1 + (time_day * 0.000005) + ds = xr.Dataset( data_vars={ "votemper": (("d", "profile_id"), votemper), + "vosaline": (("d", "profile_id"), vosaline), "lat": (("profile_id",), j), "lon": (("profile_id",), i), "depth": (("d", "profile_id"), depth), From d43244e57ce10532f6a1e519677846f9591fa430 Mon Sep 17 00:00:00 2001 From: benbar Date: Tue, 30 Jun 2026 17:29:21 +0100 Subject: [PATCH 08/10] Anomalies added on zero grid and list of variables to replace have been added. --- OceanOSSE/gridding/regridder_simple.py | 114 +++++++++++++++++++------ 1 file changed, 86 insertions(+), 28 deletions(-) diff --git a/OceanOSSE/gridding/regridder_simple.py b/OceanOSSE/gridding/regridder_simple.py index a05e203..98ec257 100644 --- a/OceanOSSE/gridding/regridder_simple.py +++ b/OceanOSSE/gridding/regridder_simple.py @@ -52,6 +52,9 @@ def __init__( if target_grid is not None and not isinstance(target_grid, xr.Dataset): raise TypeError("``target_grid`` must be an xarray.Dataset or None.") self.target_grid = target_grid + # In future load these names from config + self.varlist = ['votemper', 'vosaline'] + self.upper = 2000 # upper ocean threshold def __repr__(self) -> str: has_grid = self._target_grid is not None @@ -74,7 +77,6 @@ def from_config(cls, config: dict) -> Self: Self Initialised Regridder instance. """ - return self @@ -84,43 +86,99 @@ def regrid(self, ds_profile: xr.Dataset) -> xr.Dataset: Parameters ---------- - ds : xarray.Dataset - Synthetic observations dataset. + ds_profile : xarray.Dataset + Model profile dataset. Returns ------- xarray.Dataset Dataset of synthetic observations placed into target grid. """ - # Use indices in synthetic profile set to replace data in the - # climatology with model data - ds_model = self.target_grid # in future this will already be a climatology - + ds_zeros = self.initialise_anomaly(ds_profile) + + ds_anom = self.insert_profiles(ds_profile, ds_zeros) + # select data above 2000 m + ds_anom = ds_anom.where(ds_anom.depth > self.upper) + + ds_out = ds_anom + self.target_grid + + return ds_out + + + def initialise_anomaly(self, ds_profile): + """ + Make a dataset of zeros on the same grid as the climatology. + + Parameters + ---------- + ds_profile : xarray.Dataset + Model profile dataset. + + Returns + ------- + xarray.Dataset + zeros on target grid. + """ + # initialise a zero array to sum profile anomalies on + zero_anomaly = np.zeros((list(self.target_grid.sizes.values()))) + ds_zeros = self.target_grid.copy(deep=True) + + for var in self.varlist: + if var not in list(ds_profile.keys()): + raise ValueError(var + " is not in the profile dataset.") + if var not in list(self.target_grid.keys()): + raise ValueError(var + " is not in the climatology dataset.") + + + ds_zeros[var].data = zero_anomaly.copy() + + return ds_zeros + + + def insert_profiles(self, ds_profile, ds_anom): + """ + Regrid the synthetic observation dataset into the target grid. + + Parameters + ---------- + ds_profile : xarray.Dataset + Model profile dataset. + ds_anom : xarray.Dataset + zeros on target grid. + + Returns + ------- + xarray.Dataset + Dataset of summed anomaly model profiles. + """ + # calculate month for profiles to put them on climatology ds_profile = ds_profile.assign_coords( - month=("profile_id", ds_profile.t.dt.strftime("%m").astype(int).data) - ) + month=("profile_id", + ds_profile.t.dt.strftime("%m").astype(int).data)) n_profile = len(ds_profile.coords['profile_id']) - # loop over profiles - for p in range(n_profile): - i_ind = ds_profile.coords['i'][p].to_numpy() - j_ind = ds_profile.coords['j'][p].to_numpy() - t_ind = ds_profile.coords['t'][p].to_numpy() - ps = ds_profile.coords['profile_id'][p].to_numpy() + # Use indices in synthetic profile set to replace data in the + # climatology with model data - profile = ds_profile['votemper'].isel(profile_id=ps) + for var in self.varlist: + # loop over profiles + for p in range(n_profile): + i_ind = ds_profile.coords['i'][p].to_numpy() + j_ind = ds_profile.coords['j'][p].to_numpy() + t_ind = ds_profile.coords['t'][p].to_numpy() + ps = ds_profile.coords['profile_id'][p].to_numpy() + + profile = ds_profile[var].isel(profile_id=ps) + + ds_anom[var].loc[ + dict( + t=ds_profile.t.sel(profile_id=ps), + j=ds_profile.j.sel(profile_id=ps), + i=ds_profile.i.sel(profile_id=ps)) + ] += profile.data - ds_model['votemper'].loc[ - dict( - #month=ds_profile.month.sel(profile_id=ps), - t=ds_profile.t.sel(profile_id=ps), - j=ds_profile.j.sel(profile_id=ps), - i=ds_profile.i.sel(profile_id=ps)) - #d=ds_profile.d.sel(profile_id=ps)) - ] = profile.values - - return ds_model - - + return ds_anom + + From 0def903d34d4c74fd5a1211fa7b9bd09d9b41c5e Mon Sep 17 00:00:00 2001 From: benbar Date: Wed, 1 Jul 2026 11:25:44 +0100 Subject: [PATCH 09/10] Changing file name --- OceanOSSE/gridding/{regridder_simple.py => regridder_swap.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename OceanOSSE/gridding/{regridder_simple.py => regridder_swap.py} (100%) diff --git a/OceanOSSE/gridding/regridder_simple.py b/OceanOSSE/gridding/regridder_swap.py similarity index 100% rename from OceanOSSE/gridding/regridder_simple.py rename to OceanOSSE/gridding/regridder_swap.py From 499b77423489ca09693d6d06393f03b6b4bcbf98 Mon Sep 17 00:00:00 2001 From: benbar Date: Wed, 1 Jul 2026 11:38:34 +0100 Subject: [PATCH 10/10] rename is test --- tests/unit/test_regridder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_regridder.py b/tests/unit/test_regridder.py index 1728fb9..7bf173a 100644 --- a/tests/unit/test_regridder.py +++ b/tests/unit/test_regridder.py @@ -26,7 +26,7 @@ import numpy as np import xarray as xr -from OceanOSSE.gridding.regridder_simple import SwapRegridder +from OceanOSSE.gridding.regridder_swap import SwapRegridder def test_regrid(construct_ds, construct_profile_ds):