header

Gulf Stream: Benchmark DUACS geostrophic currents maps

2023-04-27 DUACS_SSH_BENCHMARK_DEMO


Authors: CLS & Datlas Copyright: 2023 CLS & Datlas License: MIT

Gulf Stream: Benchmark of DUACS geostrophic currents maps

The notebook aims to evaluate the surface current maps produced by the DUACS system.

<h5> These maps are equivalent to the SEALEVEL_GLO_PHY_L4_MY_008_047 product distributed by the Copernicus Marine Service, except that a nadir altimeter (SARAL/Altika, SEALEVEL_GLO_PHY_L3_MY_008_062 product) has been excluded from the mapping. </h5>
    <h5> We provide below a demonstration of the validation of these maps against the current data from the drifters database distributed by CMEMS (INSITU_GLO_PHY_UV_DISCRETE_MY_013_044 product) </h5>

General Note 1: Execute each cell through the button from the top MENU (or keyboard shortcut Shift + Enter). General Note 2: If, for any reason, the kernel is not working anymore, in the top MENU, click on the button. Then, in the top MENU, click on “Cell” and select “Run All Above Selected Cell”. ***

[1]:
from glob import glob
import numpy as np
import os
import warnings
warnings.filterwarnings("ignore")
[2]:
import sys
sys.path.append('..')
from src.mod_plot import *
from src.mod_stat import *
from src.mod_spectral import *
[3]:
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
[4]:
time_min = '2019-01-01'                                        # time min for analysis
time_max = '2019-12-31'                                        # time max for analysis
output_dir = '../results'                                      # output directory path
os.system(f'mkdir -p {output_dir}')

# Gulf Stream region
region = 'GS'
lon_min = 295                                          # domain min longitude
lon_max = 305                                          # domain max longitude
lat_min = 33.                                          # domain min latitude
lat_max = 43.                                          # domain max latitude
box_lonlat_GS = {'lon_min':lon_min,'lon_max':lon_max,'lat_min':lat_min,'lat_max':lat_max}

method_name = 'DUACS'

stat_output_filename = f'{output_dir}/stat_uv_duacs_geos_GS.nc'   # output statistical analysis filename
psd_output_filename = f'{output_dir}/psd_uv_duacs_geos_GS.nc'     # output spectral analysis filename
segment_lenght = np.timedelta64(40, 'D')                      # spectral parameter: drifters segment lenght in days to consider in the spectral analysis

Sea Surface currents from Drifters database

[5]:
filenames_drifters = sorted(glob('../data/independent_drifters/indep_drifters_GS.nc'))
[6]:
ds_drifter = xr.open_mfdataset(filenames_drifters, combine='nested', concat_dim='time')
ds_drifter = ds_drifter.where((ds_drifter.time >= np.datetime64(time_min)) & (ds_drifter.time <=  np.datetime64(time_max)), drop=True)
ds_drifter
[6]:
<xarray.Dataset>
Dimensions:    (time: 11235)
Coordinates:
  * time       (time) datetime64[ns] 2019-01-01 2019-01-01 ... 2019-12-31
    latitude   (time) float32 dask.array<chunksize=(11235,), meta=np.ndarray>
    longitude  (time) float32 dask.array<chunksize=(11235,), meta=np.ndarray>
Data variables:
    EWCT       (time) float32 dask.array<chunksize=(11235,), meta=np.ndarray>
    NSCT       (time) float32 dask.array<chunksize=(11235,), meta=np.ndarray>
    sensor_id  (time) float64 dask.array<chunksize=(11235,), meta=np.ndarray>
Attributes: (12/46)
    data_type:                   OceanSITES trajectory data
    format_version:              2.0
    platform_code:               116275
    date_update:                 2020-10-13T12:17:40Z
    institution:                 AOML
    institution_edmo_code:       1799
    ...                          ...
    deployment_lat:              -58.44
    last_longitude_observation:  82.75
    last_latitude_observation:   -18.49
    date_drog_lost:              2017-01-21T03:37:00Z
    death_type:                  stop transmitting
    last_date_observation:       2019-01-16T01:51:00Z

Sea Surface current maps to evaluate

[7]:
list_of_maps = sorted(glob('../data/maps/DUACS_GS.nc'))
ds_maps = xr.open_mfdataset(list_of_maps, combine='nested', concat_dim='time')
ds_maps = ds_maps.sel(time=slice(time_min, time_max))
ds_maps
[7]:
<xarray.Dataset>
Dimensions:    (latitude: 44, longitude: 44, time: 365)
Coordinates:
  * latitude   (latitude) float32 32.62 32.88 33.12 33.38 ... 42.88 43.12 43.38
  * longitude  (longitude) float64 294.6 294.9 295.1 295.4 ... 304.9 305.1 305.4
  * time       (time) datetime64[ns] 2019-01-01 2019-01-02 ... 2019-12-31
Data variables:
    sla        (time, latitude, longitude) float64 dask.array<chunksize=(365, 44, 44), meta=np.ndarray>
    ugosa      (time, latitude, longitude) float64 dask.array<chunksize=(365, 44, 44), meta=np.ndarray>
    vgosa      (time, latitude, longitude) float64 dask.array<chunksize=(365, 44, 44), meta=np.ndarray>
    adt        (time, latitude, longitude) float64 dask.array<chunksize=(365, 44, 44), meta=np.ndarray>
    ugos       (time, latitude, longitude) float64 dask.array<chunksize=(365, 44, 44), meta=np.ndarray>
    vgos       (time, latitude, longitude) float64 dask.array<chunksize=(365, 44, 44), meta=np.ndarray>

2.1 Interpolate sea surface currents maps onto drifters positions

[8]:
ds_interp = run_interpolation_drifters(ds_maps, ds_drifter, time_min, time_max)
ds_interp = ds_interp.dropna('time')
ds_interp = ds_interp.sortby('time')
ds_interp
2023-07-05 16:56:37 INFO     fetch data from 2019-01-01 00:00:00 to 2019-02-01 00:00:00
2023-07-05 16:56:37 INFO     fetch data from 2019-01-01 00:00:00 to 2019-02-01 00:00:00
2023-07-05 16:56:37 INFO     fetch data from 2019-01-31 00:00:00 to 2019-03-01 00:00:00
2023-07-05 16:56:37 INFO     fetch data from 2019-01-31 00:00:00 to 2019-03-01 00:00:00
2023-07-05 16:56:37 INFO     fetch data from 2019-02-28 00:00:00 to 2019-04-01 00:00:00
2023-07-05 16:56:37 INFO     fetch data from 2019-02-28 00:00:00 to 2019-04-01 00:00:00
2023-07-05 16:56:37 INFO     fetch data from 2019-03-31 00:00:00 to 2019-05-01 00:00:00
2023-07-05 16:56:37 INFO     fetch data from 2019-03-31 00:00:00 to 2019-05-01 00:00:00
2023-07-05 16:56:37 INFO     fetch data from 2019-04-30 00:00:00 to 2019-06-01 00:00:00
2023-07-05 16:56:37 INFO     fetch data from 2019-04-30 00:00:00 to 2019-06-01 00:00:00
2023-07-05 16:56:37 INFO     fetch data from 2019-05-31 00:00:00 to 2019-07-01 00:00:00
2023-07-05 16:56:38 INFO     fetch data from 2019-05-31 00:00:00 to 2019-07-01 00:00:00
2023-07-05 16:56:38 INFO     fetch data from 2019-06-30 00:00:00 to 2019-08-01 00:00:00
2023-07-05 16:56:38 INFO     fetch data from 2019-06-30 00:00:00 to 2019-08-01 00:00:00
2023-07-05 16:56:38 INFO     fetch data from 2019-07-31 00:00:00 to 2019-09-01 00:00:00
2023-07-05 16:56:38 INFO     fetch data from 2019-07-31 00:00:00 to 2019-09-01 00:00:00
2023-07-05 16:56:38 INFO     fetch data from 2019-08-31 00:00:00 to 2019-10-01 00:00:00
2023-07-05 16:56:38 INFO     fetch data from 2019-08-31 00:00:00 to 2019-10-01 00:00:00
2023-07-05 16:56:38 INFO     fetch data from 2019-09-30 00:00:00 to 2019-11-01 00:00:00
2023-07-05 16:56:38 INFO     fetch data from 2019-09-30 00:00:00 to 2019-11-01 00:00:00
2023-07-05 16:56:38 INFO     fetch data from 2019-10-31 00:00:00 to 2019-12-01 00:00:00
2023-07-05 16:56:38 INFO     fetch data from 2019-10-31 00:00:00 to 2019-12-01 00:00:00
2023-07-05 16:56:38 INFO     fetch data from 2019-11-30 00:00:00 to 2019-12-31 00:00:00
2023-07-05 16:56:38 INFO     fetch data from 2019-11-30 00:00:00 to 2019-12-31 00:00:00
[8]:
<xarray.Dataset>
Dimensions:            (time: 11230)
Coordinates:
  * time               (time) datetime64[ns] 2019-01-01 ... 2019-12-31
Data variables:
    EWCT               (time) float32 -0.1839 -0.2353 0.2235 ... -0.05235 -0.391
    NSCT               (time) float32 -0.3496 -0.2228 ... -0.3979 -0.4466
    sensor_id          (time) float64 6.188e+07 6.322e+07 ... 6.548e+07 6.64e+07
    latitude           (time) float32 35.95 36.12 36.39 ... 35.21 33.88 37.28
    longitude          (time) float32 -55.56 -57.22 -56.21 ... -56.45 -61.2
    ugos_interpolated  (time) float64 -0.2183 -0.2837 0.2636 ... -0.2945 -0.5087
    vgos_interpolated  (time) float64 -0.2328 -0.09264 ... -0.209 -0.6738

2.2 Compute grid boxes statistics & statistics by regime (coastal, offshore low variability, offshore high variability)

Once the surface currents maps have been interpolated to the position of the drifters, it is possible to calculate different statistics on the time series of zonal and meridional velocities.

We propose below the following statistics: error variance maps (static by 1°x1° box), explained variance maps.

[9]:
# Compute gridded stats
compute_stat_scores_uv(ds_interp, stat_output_filename,method_name=method_name)
2023-07-05 16:56:38 INFO     Compute mapping error all scales
2023-07-05 16:56:38 INFO     Compute statistics
2023-07-05 16:56:41 INFO     Stat file saved as: ../results/stat_uv_duacs_geos_GS.nc
2023-07-05 16:56:41 INFO     Compute statistics by oceanic regime
[10]:
# Plot gridded stats
# Hvplot
# plot_stat_score_map_uv(stat_output_filename)
# Matplotlib
plot_stat_score_map_uv_png(stat_output_filename,region=region,box_lonlat=box_lonlat_GS)
../_images/gallery_uv_scores_DUACS_geos_GS_20_0.png
../_images/gallery_uv_scores_DUACS_geos_GS_20_1.png

The figure shows that the maximum mapping errors are found in intense current systems, for example in the GulfStream, Kuroshio and Agulhas regions.

However, when considering the full scale of motion in the drifter database, the surface current maps capture up to 80% of the variability of drifter currents in the Western Boundary Currents and Antarctic Circumpolar Currents (ACC). The geostrophic signal dominates the ageostrophic signal in these regions. In regions with low ocean variability, only a few percent of the total drifter current variability is recovered in the maps, which may be associated with a larger ageostrophic signal in these regions.

[11]:
plot_stat_uv_by_regimes(stat_output_filename)
[11]:
mapping_err_u_var [m²/s²] mapping_err_v_var [m²/s²] ugos_interpolated_var [m²/s²] EWCT_var [m²/s²] vgos_interpolated_var [m²/s²] NSCT_var [m²/s²] var_score_u_allscale var_score_v_allscale
coastal 0.026696 0.028006 0.036708 0.057370 0.052822 0.075548 0.534664 0.629296
offshore_highvar 0.058719 0.056771 0.165690 0.201561 0.132635 0.167143 0.708681 0.660345
offshore_lowvar 0.025558 0.024275 0.033281 0.058642 0.027226 0.053325 0.564163 0.544767
equatorial_band NaN NaN NaN NaN NaN NaN NaN NaN
arctic NaN NaN NaN NaN NaN NaN NaN NaN
antarctic NaN NaN NaN NaN NaN NaN NaN NaN

2.4 Compute Spectral scores

[12]:
# Compute PSD scores
compute_psd_scores_current(ds_interp, psd_output_filename, lenght_scale=segment_lenght, method_name=method_name)
2023-07-05 16:57:18 INFO     Segment computation...
2023-07-05 16:57:19 INFO     Spectral analysis...
2023-07-05 16:57:19 INFO     Write output...
2023-07-05 16:57:19 INFO     PSD file saved as: ../results/psd_uv_duacs_geos_GS.nc
[13]:
# Plot Zonally averaged rotary spectra
# Hvplot
# plot_psd_scores_currents(psd_output_filename)
# Matplotlib
plot_psd_scores_currents_png(psd_output_filename,region=region)
../_images/gallery_uv_scores_DUACS_geos_GS_25_0.png
[14]:
# Plot Zonally averaged rotary spectra
plot_psd_scores_currents_1D(psd_output_filename)
[14]:

The interactive plot above allows you to explore the spectral metrics by latitude band