Extract variables at bottom of ocean: an example with Age¶
This notebook shows a simple example of plotting ocean Ideal Age. Ideal Age is a fictitious tracer which is set to zero in the surface grid-cell every timestep, and is aged by 1 year per year otherwise. It is a useful proxy for nutrients, such as carbon or oxygen (but not an exact analogue).
One of the interesting aspects of age is that we can use it to show pathways of the densest water in the ocean by plotting a map of age in the lowest grid cell. This plot requires a couple of tricks to extract information from the lowest cell.
Requirements: COSIMA Cookbook, preferably installed via the conda/analysis3 conda installation on NCI.
Compute times were calculated using the (48 cpus, 192 Gb mem) Jupyter Lab on NCI’s Gadi with conda environment analysis3-24.04.
[1]:
import intake
import matplotlib.pyplot as plt
import xarray as xr
import numpy as np
import cartopy.crs as ccrs
import cmocean as cm
import logging
logging.captureWarnings(True)
logging.getLogger('py.warnings').setLevel(logging.ERROR)
logging.getLogger('distributed.utils_perf').setLevel(logging.ERROR)
from dask.distributed import Client
[2]:
client = Client(threads_per_worker = 1)
client
[2]:
Client
Client-6e3854c5-4ce5-11ef-842a-0000007ffe80
| Connection method: Cluster object | Cluster type: distributed.LocalCluster |
| Dashboard: /proxy/8787/status |
Cluster Info
LocalCluster
4ba4cc0b
| Dashboard: /proxy/8787/status | Workers: 48 |
| Total threads: 48 | Total memory: 188.55 GiB |
| Status: running | Using processes: True |
Scheduler Info
Scheduler
Scheduler-e654c3d0-98e4-459f-b820-63d9d9224243
| Comm: tcp://127.0.0.1:41407 | Workers: 48 |
| Dashboard: /proxy/8787/status | Total threads: 48 |
| Started: Just now | Total memory: 188.55 GiB |
Workers
Worker: 0
| Comm: tcp://127.0.0.1:40425 | Total threads: 1 |
| Dashboard: /proxy/38163/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:34005 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-lw2hncxh | |
Worker: 1
| Comm: tcp://127.0.0.1:44665 | Total threads: 1 |
| Dashboard: /proxy/45977/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:34731 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-j0x12554 | |
Worker: 2
| Comm: tcp://127.0.0.1:35173 | Total threads: 1 |
| Dashboard: /proxy/38455/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:36475 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-ra_gj7lh | |
Worker: 3
| Comm: tcp://127.0.0.1:41665 | Total threads: 1 |
| Dashboard: /proxy/33023/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:33997 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-nn_5el00 | |
Worker: 4
| Comm: tcp://127.0.0.1:42277 | Total threads: 1 |
| Dashboard: /proxy/36185/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:33033 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-oj85s5et | |
Worker: 5
| Comm: tcp://127.0.0.1:44181 | Total threads: 1 |
| Dashboard: /proxy/40337/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:41017 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-2rxv8_4w | |
Worker: 6
| Comm: tcp://127.0.0.1:41627 | Total threads: 1 |
| Dashboard: /proxy/35947/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:38041 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-uziqfji2 | |
Worker: 7
| Comm: tcp://127.0.0.1:37185 | Total threads: 1 |
| Dashboard: /proxy/38315/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:38957 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-lfxwcvra | |
Worker: 8
| Comm: tcp://127.0.0.1:41327 | Total threads: 1 |
| Dashboard: /proxy/39577/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:44053 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-99i5yeos | |
Worker: 9
| Comm: tcp://127.0.0.1:43703 | Total threads: 1 |
| Dashboard: /proxy/43177/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:34469 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-fgcmdafl | |
Worker: 10
| Comm: tcp://127.0.0.1:42371 | Total threads: 1 |
| Dashboard: /proxy/36087/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:37989 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-2glr84a2 | |
Worker: 11
| Comm: tcp://127.0.0.1:43843 | Total threads: 1 |
| Dashboard: /proxy/44579/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:35387 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-d11s4xvx | |
Worker: 12
| Comm: tcp://127.0.0.1:41525 | Total threads: 1 |
| Dashboard: /proxy/37433/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:43815 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-b087yc9z | |
Worker: 13
| Comm: tcp://127.0.0.1:37927 | Total threads: 1 |
| Dashboard: /proxy/38531/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:41571 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-3d8g4lz1 | |
Worker: 14
| Comm: tcp://127.0.0.1:46079 | Total threads: 1 |
| Dashboard: /proxy/46479/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:35367 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-va2evm4l | |
Worker: 15
| Comm: tcp://127.0.0.1:40123 | Total threads: 1 |
| Dashboard: /proxy/43519/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:38083 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-y22mv0xu | |
Worker: 16
| Comm: tcp://127.0.0.1:44413 | Total threads: 1 |
| Dashboard: /proxy/41339/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:43761 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-tyqjcn2p | |
Worker: 17
| Comm: tcp://127.0.0.1:36939 | Total threads: 1 |
| Dashboard: /proxy/42191/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:36645 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-bjc0ar2g | |
Worker: 18
| Comm: tcp://127.0.0.1:43383 | Total threads: 1 |
| Dashboard: /proxy/44025/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:34433 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-3pgpg2sm | |
Worker: 19
| Comm: tcp://127.0.0.1:43229 | Total threads: 1 |
| Dashboard: /proxy/42967/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:40801 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-wj2dtr4h | |
Worker: 20
| Comm: tcp://127.0.0.1:34219 | Total threads: 1 |
| Dashboard: /proxy/34231/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:39035 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-yzneyi2b | |
Worker: 21
| Comm: tcp://127.0.0.1:44777 | Total threads: 1 |
| Dashboard: /proxy/40871/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:35005 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-6abedw5h | |
Worker: 22
| Comm: tcp://127.0.0.1:45449 | Total threads: 1 |
| Dashboard: /proxy/42579/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:45301 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-o8p_jg05 | |
Worker: 23
| Comm: tcp://127.0.0.1:37941 | Total threads: 1 |
| Dashboard: /proxy/39633/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:40881 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-xzrlk3vl | |
Worker: 24
| Comm: tcp://127.0.0.1:33399 | Total threads: 1 |
| Dashboard: /proxy/41765/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:33231 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-vymj6mlo | |
Worker: 25
| Comm: tcp://127.0.0.1:40407 | Total threads: 1 |
| Dashboard: /proxy/36615/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:34735 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-7dnclco5 | |
Worker: 26
| Comm: tcp://127.0.0.1:41109 | Total threads: 1 |
| Dashboard: /proxy/43923/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:43921 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-ic_w0xw7 | |
Worker: 27
| Comm: tcp://127.0.0.1:34875 | Total threads: 1 |
| Dashboard: /proxy/42221/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:38009 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-chxyv9um | |
Worker: 28
| Comm: tcp://127.0.0.1:33655 | Total threads: 1 |
| Dashboard: /proxy/45379/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:38465 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-bjy8vmtd | |
Worker: 29
| Comm: tcp://127.0.0.1:43069 | Total threads: 1 |
| Dashboard: /proxy/33671/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:39631 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-jklexs9l | |
Worker: 30
| Comm: tcp://127.0.0.1:45651 | Total threads: 1 |
| Dashboard: /proxy/46751/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:41247 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-ukvzl7me | |
Worker: 31
| Comm: tcp://127.0.0.1:34691 | Total threads: 1 |
| Dashboard: /proxy/35333/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:35371 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-_la4nh6l | |
Worker: 32
| Comm: tcp://127.0.0.1:45553 | Total threads: 1 |
| Dashboard: /proxy/44275/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:35025 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-q766d_2r | |
Worker: 33
| Comm: tcp://127.0.0.1:40035 | Total threads: 1 |
| Dashboard: /proxy/36287/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:39359 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-lqypoimm | |
Worker: 34
| Comm: tcp://127.0.0.1:46449 | Total threads: 1 |
| Dashboard: /proxy/46133/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:41543 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-j46xto5l | |
Worker: 35
| Comm: tcp://127.0.0.1:41711 | Total threads: 1 |
| Dashboard: /proxy/33965/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:41643 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-tn4dpbdp | |
Worker: 36
| Comm: tcp://127.0.0.1:41583 | Total threads: 1 |
| Dashboard: /proxy/41409/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:40889 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-sqe9x0e8 | |
Worker: 37
| Comm: tcp://127.0.0.1:38713 | Total threads: 1 |
| Dashboard: /proxy/33623/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:43731 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-syg066ft | |
Worker: 38
| Comm: tcp://127.0.0.1:34061 | Total threads: 1 |
| Dashboard: /proxy/36865/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:44901 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-hjt432ok | |
Worker: 39
| Comm: tcp://127.0.0.1:36515 | Total threads: 1 |
| Dashboard: /proxy/36871/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:45947 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-15dz_n1x | |
Worker: 40
| Comm: tcp://127.0.0.1:43297 | Total threads: 1 |
| Dashboard: /proxy/39551/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:34989 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-rduj1is_ | |
Worker: 41
| Comm: tcp://127.0.0.1:45879 | Total threads: 1 |
| Dashboard: /proxy/34227/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:38679 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-3z5_y_63 | |
Worker: 42
| Comm: tcp://127.0.0.1:35391 | Total threads: 1 |
| Dashboard: /proxy/34965/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:42353 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-dgn6viho | |
Worker: 43
| Comm: tcp://127.0.0.1:40765 | Total threads: 1 |
| Dashboard: /proxy/42983/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:36979 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-3dh10vvz | |
Worker: 44
| Comm: tcp://127.0.0.1:36469 | Total threads: 1 |
| Dashboard: /proxy/36779/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:33017 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-rdwsskkf | |
Worker: 45
| Comm: tcp://127.0.0.1:35329 | Total threads: 1 |
| Dashboard: /proxy/40637/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:32915 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-nyxgv_ki | |
Worker: 46
| Comm: tcp://127.0.0.1:38595 | Total threads: 1 |
| Dashboard: /proxy/43943/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:42359 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-vf131avk | |
Worker: 47
| Comm: tcp://127.0.0.1:36193 | Total threads: 1 |
| Dashboard: /proxy/38169/status | Memory: 3.93 GiB |
| Nanny: tcp://127.0.0.1:43029 | |
| Local directory: /jobfs/121869575.gadi-pbs/dask-scratch-space/worker-d_0ig2kn | |
Add a database session. No database file has been specified so it will use the default database that indexes a number of COSIMA datasets
[3]:
catalog = intake.cat.access_nri
Now, let’s set the experiment and time interval, and average ideal age over a year.
[6]:
experiment = '01deg_jra55v13_ryf9091'
variable = 'age_global'
start_time = '2099-01-01'
end_time = '2099-12-31'
cat_subset = catalog[experiment]
var_search = cat_subset.search(variable=variable, frequency='1mon')
darray = var_search.to_dask()
age = darray[variable]
age = age.sel(time=slice(start_time, end_time))
age
[6]:
<xarray.DataArray 'age_global' (time: 12, st_ocean: 75, yt_ocean: 2700,
xt_ocean: 3600)> Size: 35GB
dask.array<getitem, shape=(12, 75, 2700, 3600), dtype=float32, chunksize=(1, 7, 300, 400), chunktype=numpy.ndarray>
Coordinates:
* xt_ocean (xt_ocean) float64 29kB -279.9 -279.8 -279.7 ... 79.75 79.85 79.95
* yt_ocean (yt_ocean) float64 22kB -81.11 -81.07 -81.02 ... 89.89 89.94 89.98
* st_ocean (st_ocean) float64 600B 0.5413 1.681 2.94 ... 5.511e+03 5.709e+03
* time (time) object 96B 2099-01-16 12:00:00 ... 2099-12-16 12:00:00
Attributes:
long_name: Age (global)
units: yr
valid_range: [0.e+00 1.e+20]
cell_methods: time: mean
time_avg_info: average_T1,average_T2,average_DT
standard_name: sea_water_age_since_surface_contact[7]:
%%time
age_mean = age.mean(dim='time').compute()
CPU times: user 38.1 s, sys: 6.99 s, total: 45.1 s
Wall time: 46.6 s
The age variable is a 3D variable. There are a number of ways to extract the value at the bottom of the ocean. This notebook outlines two ways this can be achieved: (i) using masking and using (ii) indexing.
In this case masking is much slower than indexing, but for some use cases this has been the opposite. The masking approach has the benefit of not requiring the depth grid information.
I. Masking approach¶
Create a mask of all the bottom cells. Can achieve this by taking the data, shift it up one cell in the vertical grid, find all non-NAN cells, and then negate this mask. Then mask the same data with with this mask, which will select out only the lowest level of non-NAN values in the data.
In a second step turn it into a boolean array for neatness.
[9]:
bottom_mask = age_mean.where(~np.isfinite(age_mean.shift({'st_ocean': -1})))
bottom_mask = ~np.isnan(bottom_mask)
bottom_mask
[9]:
<xarray.DataArray 'age_global' (st_ocean: 75, yt_ocean: 2700, xt_ocean: 3600)> Size: 729MB
array([[[False, False, False, ..., False, False, False],
[False, False, False, ..., False, False, False],
[False, False, False, ..., False, False, False],
...,
[False, False, False, ..., False, False, False],
[False, False, False, ..., False, False, False],
[False, False, False, ..., False, False, False]],
[[False, False, False, ..., False, False, False],
[False, False, False, ..., False, False, False],
[False, False, False, ..., False, False, False],
...,
[False, False, False, ..., False, False, False],
[False, False, False, ..., False, False, False],
[False, False, False, ..., False, False, False]],
[[False, False, False, ..., False, False, False],
[False, False, False, ..., False, False, False],
[False, False, False, ..., False, False, False],
...,
...
...,
[False, False, False, ..., False, False, False],
[False, False, False, ..., False, False, False],
[False, False, False, ..., False, False, False]],
[[False, False, False, ..., False, False, False],
[False, False, False, ..., False, False, False],
[False, False, False, ..., False, False, False],
...,
[False, False, False, ..., False, False, False],
[False, False, False, ..., False, False, False],
[False, False, False, ..., False, False, False]],
[[False, False, False, ..., False, False, False],
[False, False, False, ..., False, False, False],
[False, False, False, ..., False, False, False],
...,
[False, False, False, ..., False, False, False],
[False, False, False, ..., False, False, False],
[False, False, False, ..., False, False, False]]])
Coordinates:
* xt_ocean (xt_ocean) float64 29kB -279.9 -279.8 -279.7 ... 79.75 79.85 79.95
* yt_ocean (yt_ocean) float64 22kB -81.11 -81.07 -81.02 ... 89.89 89.94 89.98
* st_ocean (st_ocean) float64 600B 0.5413 1.681 2.94 ... 5.511e+03 5.709e+03[10]:
%%time
bottom_age = age_mean.where(bottom_mask).sum(dim='st_ocean').compute()
bottom_age
CPU times: user 3.83 s, sys: 1.98 s, total: 5.81 s
Wall time: 3.91 s
[10]:
<xarray.DataArray 'age_global' (yt_ocean: 2700, xt_ocean: 3600)> Size: 39MB
array([[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
...,
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.]], dtype=float32)
Coordinates:
* xt_ocean (xt_ocean) float64 29kB -279.9 -279.8 -279.7 ... 79.75 79.85 79.95
* yt_ocean (yt_ocean) float64 22kB -81.11 -81.07 -81.02 ... 89.89 89.94 89.98Load some things we need for plotting. Bathymetry for plotting the land mask, and lat/lon:
[25]:
geolon_t
[25]:
<xarray.DataArray 'geolon_t' (grid_y_T: 2700, grid_x_T: 3600)> Size: 78MB
[9720000 values with dtype=float64]
Dimensions without coordinates: grid_y_T, grid_x_T
Attributes:
long_name: Geographic longitude of T_cell centers
units: degrees_E[26]:
cat_subset = catalog[experiment]
var_search = cat_subset.search(variable='ht')
var_search = var_search.search(path=var_search.df['path'][0])
darray = var_search.to_dask()
bathymetry = darray['ht']
land = xr.where(np.isnan(bathymetry.rename('land')), 1, np.nan)
ds = xr.open_dataset("/g/data/ik11/grids/ocean_grid_01.nc")
ds = ds.rename(
{
"grid_x_C": "xu_ocean",
"grid_y_C": "yu_ocean",
"grid_x_T": "xt_ocean",
"grid_y_T": "yt_ocean",
}
)
geolon_t = ds.geolon_t
geolat_t = ds.geolat_t
bathymetry = bathymetry.drop_vars(["geolon_t", "geolat_t"])
bathymetry = bathymetry.assign_coords({"geolon_t": geolon_t, "geolat_t": geolat_t})
bottom_age = bottom_age.drop_vars(["geolon_t", "geolat_t"])
bottom_age = bottom_age.assign_coords({"geolon_t": geolon_t, "geolat_t": geolat_t})
[27]:
fig = plt.figure(figsize=(10, 6))
ax = plt.axes(projection=ccrs.Robinson(central_longitude=-100))
# Add model land mask
land.plot.contourf(ax=ax,
colors='darkgrey',
zorder=2,
transform=ccrs.PlateCarree(),
add_colorbar=False)
# Add model coastline
land.fillna(0).plot.contour(ax=ax,
colors='k',
levels=[0, 1],
transform=ccrs.PlateCarree(),
add_colorbar=False,
linewidths=0.5)
ax.gridlines(draw_labels=False)
bottom_age.plot.contourf(ax=ax,
x='geolon_t', y='geolat_t',
cmap=cm.cm.matter,
vmin=60,
vmax=200,
transform=ccrs.PlateCarree(),
cbar_kwargs={"label": "Age (yrs)", "fraction": 0.03, "aspect": 15, "shrink": 0.7})
plt.title('Ocean Bottom Age');
II. Indexing approach¶
Here we grab the kmt variable out of ocean_grid.nc. Note that this is a static variable, so we just look for the last file (give n=-1 as keyword argument to getvar() below). The kmt variable tells us the lowest cell which is active at each \((x, y)\) location.
[28]:
cat_subset = catalog[experiment]
var_search = cat_subset.search(variable='kmt')
var_search = var_search.search(path=var_search.df['path'][0])
ds = var_search.to_dask()
kmt = ds['kmt']
kmt = kmt.fillna(1.0).astype(int) - 1
kmt.load()
[28]:
<xarray.DataArray 'kmt' (yt_ocean: 2700, xt_ocean: 3600)> Size: 78MB
array([[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
...,
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0]])
Coordinates:
* xt_ocean (xt_ocean) float64 29kB -279.9 -279.8 -279.7 ... 79.75 79.85 79.95
* yt_ocean (yt_ocean) float64 22kB -81.11 -81.07 -81.02 ... 89.89 89.94 89.98
geolon_t (yt_ocean, xt_ocean) float32 39MB nan nan nan nan ... nan nan nan
geolat_t (yt_ocean, xt_ocean) float32 39MB nan nan nan nan ... nan nan nanProvided that kmt is loaded, xarray is smart enough to figure out what this line means, and extracts a 2-D field of bottom age for us.
[29]:
%%time
bottom_age = age_mean.isel(st_ocean=kmt).compute()
CPU times: user 213 ms, sys: 88.9 ms, total: 302 ms
Wall time: 201 ms
And here is the plot:
[30]:
fig = plt.figure(figsize=(10, 6))
ax = plt.axes(projection=ccrs.Robinson(central_longitude=-100))
# Add model land mask
land.plot.contourf(ax=ax,
colors='darkgrey',
zorder=2,
transform=ccrs.PlateCarree(),
add_colorbar=False)
# Add model coastline
land.fillna(0).plot.contour(ax=ax,
colors='k',
levels=[0, 1],
transform=ccrs.PlateCarree(),
add_colorbar=False,
linewidths=0.5)
ax.gridlines(draw_labels=False)
bottom_age.plot.contourf(ax=ax,
x='geolon_t', y='geolat_t',
cmap=cm.cm.matter,
vmin=60,
vmax=200,
transform=ccrs.PlateCarree(),
cbar_kwargs={"label": "Age (yrs)", "fraction": 0.03, "aspect": 15, "shrink": 0.7})
plt.title('Ocean Bottom Age');
Some remarks¶
A few things to note here:
The continental shelves are all young - this is just because they are shallow.
The North Atlantic is also relatively young, due to formation of NADW. Note that both the Deep Western Boundary Currents and the Mid-Atlantic Ridge both sustain southward transport of this young water.
A signal following AABW pathways (northwards at the western boundaries) shows slightly younger water in these regions, but it has mixed somewhat with older water above.
Even after 200 years, the water in the NE Pacific has not experienced any ventilation…
Notes on performance¶
The indexing method requires the data to be loaded into memory and appears faster than it actually is if this isn’t factored in. Calculations with large datasets that do not fit within memory will struggle in this case.
The indexing method does not perform well in a dask workflow where lazy loading is being used.
The masking approach does not suffer from these limitations and when in doubt should be the preferred method. It also has the advantage of not requiring the grid data.
To illustrate this: a single month of bottom age from the original data using masking
[35]:
%%time
age.isel(time=1).where(bottom_mask).sum(dim='st_ocean').compute()
CPU times: user 9.56 s, sys: 2.29 s, total: 11.9 s
Wall time: 13.4 s
[35]:
<xarray.DataArray 'age_global' (yt_ocean: 2700, xt_ocean: 3600)> Size: 39MB
array([[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
...,
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.]], dtype=float32)
Coordinates:
* xt_ocean (xt_ocean) float64 29kB -279.9 -279.8 -279.7 ... 79.75 79.85 79.95
* yt_ocean (yt_ocean) float64 22kB -81.11 -81.07 -81.02 ... 89.89 89.94 89.98
time object 8B 2099-02-15 00:00:00The same with indexing (different month to ensure no caching effects) is significantly slower
[32]:
%%time
age.isel(time=3).isel(st_ocean=kmt).compute()
CPU times: user 1min 18s, sys: 3.82 s, total: 1min 22s
Wall time: 1min 25s
[32]:
<xarray.DataArray 'age_global' (yt_ocean: 2700, xt_ocean: 3600)> Size: 39MB
array([[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
...,
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan]], dtype=float32)
Coordinates:
* xt_ocean (xt_ocean) float64 29kB -279.9 -279.8 -279.7 ... 79.75 79.85 79.95
* yt_ocean (yt_ocean) float64 22kB -81.11 -81.07 -81.02 ... 89.89 89.94 89.98
st_ocean (yt_ocean, xt_ocean) float64 78MB 0.5413 0.5413 ... 0.5413 0.5413
time object 8B 2099-04-16 00:00:00
geolon_t (yt_ocean, xt_ocean) float32 39MB nan nan nan nan ... nan nan nan
geolat_t (yt_ocean, xt_ocean) float32 39MB nan nan nan nan ... nan nan nan
Attributes:
long_name: Age (global)
units: yr
valid_range: [0.e+00 1.e+20]
cell_methods: time: mean
time_avg_info: average_T1,average_T2,average_DT
standard_name: sea_water_age_since_surface_contactIt is much faster to preload the data and then index it, but this does rely on their being sufficient memory
[33]:
%%time
myage = age.isel(time=4).load()
CPU times: user 6.83 s, sys: 3.16 s, total: 9.99 s
Wall time: 9.64 s
[34]:
%%time
myage.isel(st_ocean=kmt).compute()
CPU times: user 178 ms, sys: 104 ms, total: 282 ms
Wall time: 189 ms
[34]:
<xarray.DataArray 'age_global' (yt_ocean: 2700, xt_ocean: 3600)> Size: 39MB
array([[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
...,
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan]], dtype=float32)
Coordinates:
* xt_ocean (xt_ocean) float64 29kB -279.9 -279.8 -279.7 ... 79.75 79.85 79.95
* yt_ocean (yt_ocean) float64 22kB -81.11 -81.07 -81.02 ... 89.89 89.94 89.98
st_ocean (yt_ocean, xt_ocean) float64 78MB 0.5413 0.5413 ... 0.5413 0.5413
time object 8B 2099-05-16 12:00:00
geolon_t (yt_ocean, xt_ocean) float32 39MB nan nan nan nan ... nan nan nan
geolat_t (yt_ocean, xt_ocean) float32 39MB nan nan nan nan ... nan nan nan
Attributes:
long_name: Age (global)
units: yr
valid_range: [0.e+00 1.e+20]
cell_methods: time: mean
time_avg_info: average_T1,average_T2,average_DT
standard_name: sea_water_age_since_surface_contact