miχpods: MOM6 dev#

This is a development notebook and has many work-in-progress things.

  • daily cycle diagnostics

  • more wind dependencies figures + timing of descent marks

  • check background mixing formulation

  • bootstrap error on mean, median?

  • fix EUC max at 110

  • figure out time base

  • shear spectra

  • plot mean, medians on histograms.

Notes:#

Diagnostic notes/improvements#

  • match vertical resolution and extent

  • restrict TAO S2 to ADCP depth range

  • restrict to top 200m.

  • Filtering out MLD makes a big difference!

  • SInce we’re working with derivatives does the vertical grid matter (as long as it is coarser than observations)?

1 difvho ocean_vertical_heat_diffusivity 
2 difvso ocean_vertical_salt_diffusivity

Future model simulations#

  • Need lateral / neutral terms for total χ, ε

  • Add Atlantic TAO moorings + LOTUS, PAPA, others used in LMD94

  • Fix restart issue

Uncertainties#

  • Why can’t I reproduce the ε figure?

  • Why can’t I save the new station data

  • Need to consider Ri smoothing in models: From Gustavo:

    You mentioned some kind of inconsistency between your diagnostics and what MOM6 was doing for the interior shear mixing scheme. I am working to implement the option to apply vertical smoothing in Ri multiple times, instead of only once which is what we are doing right now. I noticed that the diagnostic ri_grad_shear is saved before the smoothing is applied. There is another diagnostic (ri_grad_shear_smooth) that saves the smoothed Ri. Perhaps you were looking at ri_grad_shear instead of ri_grad_shear_smooth and this can explain the inconsistency.

Diagnostics Effort notes#

  • go through prepare; pass in dataset and optional xgcm.Grid

  • different pre-processing steps for different datasets

  • order, sign of z coordinate is painful; here normalizing

  • composing with matplotlib subfigures

References#

Warner & Moum (2019)

Setup#

Hide code cell source
%load_ext autoreload
%load_ext rich
%load_ext watermark

import warnings

import cf_xarray as cfxr
import dask
import dask_jobqueue
import dcpy
import distributed
import flox.xarray
import holoviews as hv
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import tqdm
import xgcm
import xrft
from datatree import DataTree
from mom6_tools.sections import preprocess_mom6_sections
from pump.catalog import catalog_dict
import xarray as xr
from holoviews.plotting.bokeh.util import select_legends

%aimport pump
from pump import mixpods

hv.notebook_extension("bokeh")

cfxr.set_options(warn_on_missing_variables=False)
xr.set_options(keep_attrs=True, display_expand_data=False)

plt.style.use("bmh")
plt.rcParams["figure.dpi"] = 140

mixing_layer_depth_criteria = {
    "boundary_layer_depth": {"name": "KPPhbl|KPP_OBLdepth|ePBL_h_ML"},
}

%watermark -iv
Hide code cell output
The autoreload extension is already loaded. To reload it, use:
  %reload_ext autoreload
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/dask_jobqueue/core.py:20: FutureWarning: tmpfile is deprecated and will be removed in a future release. Please use dask.utils.tmpfile instead.
  from distributed.utils import tmpfile
cf_xarray    : 0.8.0
xrft         : 0.0.0
flox         : 0.6.10
xarray       : 2023.3.0
pump         : 1.0+266.g796539e.dirty
matplotlib   : 3.7.1
numpy        : 1.23.5
pandas       : 1.5.3
holoviews    : 1.15.4
tqdm         : 4.65.0
xgcm         : 0.6.1
dcpy         : 0.1.dev387+gd06c937
dask         : 2023.3.2
dask_jobqueue: 0.7.3
distributed  : 2023.3.2
Hide code cell source
if "client" in locals():
    client.close()
    del client
if "cluster" in locals():
    cluster.close()

cluster = dask_jobqueue.PBSCluster(
    cores=4,  # The number of cores you want
    memory="23GB",  # Amount of memory
    processes=1,  # How many processes
    queue="casper",  # The type of queue to utilize (/glade/u/apps/dav/opt/usr/bin/execcasper)
    local_directory="/local_scratch/pbs.$PBS_JOBID/dask/spill",
    log_directory="/glade/scratch/dcherian/dask/",
    resource_spec="select=1:ncpus=4:mem=23GB",  # Specify resources
    project="ncgd0011",  # Input your project ID here
    walltime="02:00:00",  # Amount of wall time
    interface="ib0",  # Interface to use
)
cluster.adapt(minimum_jobs=1, maximum_jobs=4)
client = distributed.Client(cluster)
client
Hide code cell output

Client

Client-a7da0dfc-f066-11ed-90a6-3cecef1ac636

Connection method: Cluster object Cluster type: dask_jobqueue.PBSCluster
Dashboard: https://jupyterhub.hpc.ucar.edu/stable/user/dcherian/proxy/8787/status

Cluster Info

Read data#

LES#

%autoreload

les = mixpods.load_les_moorings()
--> The keys in the returned dictionary of datasets are constructed as follows:
	'latitude.longitude.month.kind.length'
100.00% [1/1 00:49<00:00]

microstructure#

micro = mixpods.load_microstructure()
micro
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xarray/core/computation.py:771: RuntimeWarning: invalid value encountered in log10
  result_data = func(*input_data)
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xarray/core/computation.py:771: RuntimeWarning: invalid value encountered in log10
  result_data = func(*input_data)
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xarray/core/computation.py:771: RuntimeWarning: divide by zero encountered in log10
  result_data = func(*input_data)
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xarray/core/computation.py:771: RuntimeWarning: invalid value encountered in log10
  result_data = func(*input_data)
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xarray/core/computation.py:771: RuntimeWarning: invalid value encountered in log10
  result_data = func(*input_data)
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xarray/core/computation.py:771: RuntimeWarning: invalid value encountered in log10
  result_data = func(*input_data)
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xarray/core/computation.py:771: RuntimeWarning: invalid value encountered in log10
  result_data = func(*input_data)
<xarray.DatasetView>
Dimensions:  ()
Data variables:
    *empty*

TAO#

tao_Ri = xr.load_dataarray(
    "tao-hourly-Ri-seasonal-percentiles.nc"
).cf.guess_coord_axis()
%autoreload

with dask.config.set(scheduler="threads"):
    tao_gridded = mixpods.load_tao()
tao_gridded
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
 /glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xarray/backends/zarr.py 
 :405 in open_group                                                                               
                                                                                                  
   402 │   │                                                                                      
   403 │   │   if consolidated is None:                                                           
   404 │   │   │   try:                                                                           
 405 │   │   │   │   zarr_group = zarr.open_consolidated(store, **open_kwargs)                  
   406 │   │   │   except KeyError:                                                               
   407 │   │   │   │   try:                                                                       
   408 │   │   │   │   │   zarr_group = zarr.open_group(store, **open_kwargs)                     
                                                                                                  
 /glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/zarr/convenience.py:129 
 9 in open_consolidated                                                                           
                                                                                                  
   1296 │   │   │   metadata_key = 'meta/root/consolidated/' + metadata_key                       
   1297 │                                                                                         
   1298 │   # setup metadata store                                                                
 1299 meta_store = ConsolidatedStoreClass(store, metadata_key=metadata_key)                 
   1300 │                                                                                         
   1301 │   # pass through                                                                        
   1302 │   chunk_store = kwargs.pop('chunk_store', None) or store                                
                                                                                                  
 /glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/zarr/storage.py:2886 in 
 __init__                                                                                         
                                                                                                  
   2883 │   │   self.store = Store._ensure_store(store)                                           
   2884 │   │                                                                                     
   2885 │   │   # retrieve consolidated metadata                                                  
 2886 │   │   meta = json_loads(self.store[metadata_key])                                       
   2887 │   │                                                                                     
   2888 │   │   # check format of consolidated metadata                                           
   2889 │   │   consolidated_format = meta.get('zarr_consolidated_format', None)                  
                                                                                                  
 /glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/zarr/storage.py:1087 in 
 __getitem__                                                                                      
                                                                                                  
   1084 │   │   if os.path.isfile(filepath):                                                      
   1085 │   │   │   return self._fromfile(filepath)                                               
   1086 │   │   else:                                                                             
 1087 │   │   │   raise KeyError(key)                                                           
   1088 │                                                                                         
   1089 │   def __setitem__(self, key, value):                                                    
   1090 │   │   key = self._normalize_key(key)                                                    
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
KeyError: '.zmetadata'

During handling of the above exception, another exception occurred:

╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
 /glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xarray/backends/zarr.py 
 :408 in open_group                                                                               
                                                                                                  
   405 │   │   │   │   zarr_group = zarr.open_consolidated(store, **open_kwargs)                  
   406 │   │   │   except KeyError:                                                               
   407 │   │   │   │   try:                                                                       
 408 │   │   │   │   │   zarr_group = zarr.open_group(store, **open_kwargs)                     
   409 │   │   │   │   │   warnings.warn(                                                         
   410 │   │   │   │   │   │   "Failed to open Zarr store with consolidated metadata, "           
   411 │   │   │   │   │   │   "but successfully read with non-consolidated metadata. "           
                                                                                                  
 /glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/zarr/hierarchy.py:1443  
 in open_group                                                                                    
                                                                                                  
   1440 │   │   if not contains_group(store, path=path):                                          
   1441 │   │   │   if contains_array(store, path=path):                                          
   1442 │   │   │   │   raise ContainsArrayError(path)                                            
 1443 │   │   │   raise GroupNotFoundError(path)                                                
   1444 │                                                                                         
   1445 │   elif mode == 'w':                                                                     
   1446 │   │   init_group(store, overwrite=True, path=path, chunk_store=chunk_store)             
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
GroupNotFoundError: group not found at path ''

During handling of the above exception, another exception occurred:

╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
 /glade/scratch/dcherian/tmp/ipykernel_11229/2826082590.py:4 in <module>                          
                                                                                                  
 [Errno 2] No such file or directory: '/glade/scratch/dcherian/tmp/ipykernel_11229/2826082590.py' 
                                                                                                  
 /glade/u/home/dcherian/pump/pump/mixpods.py:1317 in load_tao                                     
                                                                                                  
   1314 def load_tao():                                                                           
   1315 │   from .obs import process_oni                                                          
   1316 │                                                                                         
 1317 tao_gridded = xr.open_dataset(                                                        
   1318 │   │   os.path.expanduser(f"{ROOT}/zarrs/tao-gridded-ancillary.zarr"),                   
   1319 │   │   chunks="auto",                                                                    
   1320 │   │   engine="zarr",                                                                    
                                                                                                  
 /glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xarray/backends/api.py: 
 526 in open_dataset                                                                              
                                                                                                  
    523 │   )                                                                                     
    524 │                                                                                         
    525 │   overwrite_encoded_chunks = kwargs.pop("overwrite_encoded_chunks", None)               
  526 backend_ds = backend.open_dataset(                                                    
    527 │   │   filename_or_obj,                                                                  
    528 │   │   drop_variables=drop_variables,                                                    
    529 │   │   **decoders,                                                                       
                                                                                                  
 /glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xarray/backends/zarr.py 
 :891 in open_dataset                                                                             
                                                                                                  
   888 │   │   zarr_version=None,                                                                 
   889 │   ):                                                                                     
   890 │   │   filename_or_obj = _normalize_path(filename_or_obj)                                 
 891 │   │   store = ZarrStore.open_group(                                                      
   892 │   │   │   filename_or_obj,                                                               
   893 │   │   │   group=group,                                                                   
   894 │   │   │   mode=mode,                                                                     
                                                                                                  
 /glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xarray/backends/zarr.py 
 :425 in open_group                                                                               
                                                                                                  
   422 │   │   │   │   │   │   stacklevel=stacklevel,                                             
   423 │   │   │   │   │   )                                                                      
   424 │   │   │   │   except zarr.errors.GroupNotFoundError:                                     
 425 │   │   │   │   │   raise FileNotFoundError(f"No such file or directory: '{store}'")       
   426 │   │   elif consolidated:                                                                 
   427 │   │   │   # TODO: an option to pass the metadata_key keyword                             
   428 │   │   │   zarr_group = zarr.open_consolidated(store, **open_kwargs)                      
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
FileNotFoundError: No such file or directory: 
'/glade/campaign/cgd/oce/projects/pump/zarrs/tao-gridded-ancillary.zarr'
np.log10(tao_gridded.eps).sel(time=slice("2005", "2015")).resample(
    time="M"
).mean().hvplot.quadmesh(clim=(-7.5, -6))
sub.v.hvplot()
sub = tao_gridded.sel(time="2010-01")

t = sub.theta.hvplot.quadmesh(cmap="turbo_r")
dt = (
    sub.theta - sub.theta.reset_coords(drop=True).cf.sel(Z=[0, -5]).cf.max("Z")
).hvplot.quadmesh(clim=(-0.15, 0.15), cmap="RdBu_r")
newmld = mixpods.get_mld_tao_theta(sub.theta.reset_coords(drop=True))

(
    dt
    * sub.reset_coords().mldT.hvplot.line(color="w", line_width=2)
    * newmld.reset_coords(drop=True).hvplot.line(color="orange", line_width=1)
).opts(frame_width=1200)
(
    tao_gridded.reset_coords().mldT.resample(time="5D").mean().hvplot.line()
    * mixpods.get_mld_tao_theta(tao_gridded.reset_coords().theta)
    .resample(time="5D")
    .mean()
    .hvplot.line()
)
tao_gridded.u.cf.plot()
tao_gridded.eucmax.plot()
[<matplotlib.lines.Line2D>]
_images/3a946f1b996d9cfc93e09718d8f29becf2633b045900e7ad003ba587cad536e4.png

MITgcm stations#

stations = pump.model.read_stations_20(stationdirname)

gcmeq = stations.sel(
    longitude=[-155.025, -140.025, -125.025, -110.025], method="nearest"
)
# enso = pump.obs.make_enso_mask()
# mitgcm["enso"] = enso.reindex(time=mitgcm.time.data, method="nearest")

# gcmeq["eucmax"] = pump.calc.get_euc_max(gcmeq.u)
# pump.calc.calc_reduced_shear(gcmeq)

# oni = pump.obs.process_oni()
# gcmeq["enso_transition"] = mixpods.make_enso_transition_mask(oni).reindex(time=gcmeq.time.data, method="nearest")


mitgcm = gcmeq.sel(longitude=-140.025, method="nearest")
metrics = mixpods.normalize_z(pump.model.read_metrics(stationdirname), sort=True)
mitgcm = mixpods.normalize_z(mitgcm, sort=True)

mitgcm_grid = xgcm.Grid(
    metrics.sel(latitude=mitgcm.latitude, longitude=mitgcm.longitude, method="nearest"),
    coords=({"Z": {"center": "depth", "outer": "RF"}, "Y": {"center": "latitude"}}),
    metrics={"Z": ("drF", "drC")},
    periodic=False,
    boundary="fill",
    fill_value=np.nan,
)

mitgcm.theta.attrs["standard_name"] = "sea_water_potential_temperature"
mitgcm.salt.attrs["standard_name"] = "sea_water_salinity"
mitgcm["KPPviscAz"].attrs["standard_name"] = "ocean_vertical_viscosity"
mitgcm["KPPdiffKzT"].attrs["standard_name"] = "ocean_vertical_heat_diffusivity"
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xarray/core/indexing.py:1374: PerformanceWarning: Slicing is producing a large chunk. To accept the large
chunk and silence this warning, set the option
    >>> with dask.config.set(**{'array.slicing.split_large_chunks': False}):
    ...     array[indexer]

To avoid creating the large chunks, set the option
    >>> with dask.config.set(**{'array.slicing.split_large_chunks': True}):
    ...     array[indexer]
  return self.array[key]
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xarray/core/indexing.py:1374: PerformanceWarning: Slicing is producing a large chunk. To accept the large
chunk and silence this warning, set the option
    >>> with dask.config.set(**{'array.slicing.split_large_chunks': False}):
    ...     array[indexer]

To avoid creating the large chunks, set the option
    >>> with dask.config.set(**{'array.slicing.split_large_chunks': True}):
    ...     array[indexer]
  return self.array[key]
mitgcm = (
    mixpods.prepare(mitgcm, grid=mitgcm_grid, oni=pump.obs.process_oni())
    .sel(latitude=0, method="nearest")
    .cf.sel(Z=slice(-250, 0))
)
mitgcm_grid
<xgcm.Grid>
Z Axis (not periodic, boundary='fill'):
  * center   depth --> outer
  * outer    RF --> center
Y Axis (not periodic, boundary='fill'):
  * center   latitude
mitgcm.u.cf.plot()
mitgcm.mldT.reset_coords(drop=True).cf.plot()
mitgcm.eucmax.reset_coords(drop=True).cf.plot()
[<matplotlib.lines.Line2D>]
_images/d46c7b8c2987c0ef2648ef1244623ce16ba4db3433f18083967672403b8ba06c.png
mixpods.plot_n2s2pdf(mitgcm.n2s2pdf.sel(enso_transition_phase="none"))
<matplotlib.contour.QuadContourSet>
_images/0687c893e1eaca992b13f8fe6166eda6df0e63d890df04891f6f05f5cee52932.png

MOM6#

Generate kechunk JSONs#

catalog_sub = {k: v for k, v in catalog_dict.items() if k}
catalog_sub
{
    'baseline.001': ('baseline', 'gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.001.mixpods'),
    'epbl': ('ePBL', 'gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.epbl.001.mixpods'),
    'kpp.lmd.002': (
        'KPP Ri0=0.5',
        'gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.kpp.lmd.002.mixpods'
    ),
    'kpp.lmd.003': (
        'KPP Ri0=0.5, Ric=0.2,',
        'gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.kpp.lmd.003.mixpods'
    ),
    'kpp.lmd.004': (
        'KPP ν0=2.5, Ric=0.2, Ri0=0.5',
        'gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.kpp.lmd.004.mixpods'
    ),
    'baseline.N150': ('baseline N=150', 'gmom.e23.GJRAv3.TL319_t061_zstar_N150.baseline.mixpods'),
    'kpp.lmd.004.N150': (
        'KPP ν0=2.5, Ric=0.2, Ri0=0.5, N=150',
        'gmom.e23.GJRAv3.TL319_t061_zstar_N150.kpp.lmd.004.mixpods'
    ),
    'baseline.hb': ('baseline', 'gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.hb'),
    'new_baseline.hb': ('KD=0, KV=0', 'gmom.e23.GJRAv3.TL319_t061_zstar_N65.new_baseline.hb'),
    'new_baseline.kpp.lmd.004': (
        'KPP ν0=2.5, Ric=0.2, Ri0=0.5',
        'gmom.e23.GJRAv3.TL319_t061_zstar_N65.new_baseline.kpp.lmd.004.mixpods'
    ),
    'new_baseline.kpp.lmd.005': (
        'KPP ν0=2.5, Ri0=0.5',
        'gmom.e23.GJRAv3.TL319_t061_zstar_N65.new_baseline.kpp.lmd.005.mixpods'
    )
}
%autoreload

from mom6_tools.kerchunk import generate_references_for_stream

for _, casename in tqdm.tqdm(catalog_sub.values()):
    caseroot = f"{mixpods.ROOT}/cesm/{casename}"
    if "N150" in casename:
        continue
    print(caseroot)
    for stream in ["h", "hm", "hm.wci", "sfc"]:
        generate_references_for_stream(
            caseroot=caseroot,
            stream=stream,
            missing_stream="warn",
            existing_output="overwrite",
        )

../pump-catalog catalog with 4 dataset(s) from 4 asset(s):

unique
casename 1
stream 4
path 4
baseline 1
levels 1
frequency 1
variables 75
shortname 1
description 1
derived_variables 0
  0%|          | 0/1 [00:00<?, ?it/s]
/glade/campaign/cgd/oce/projects/pump//cesm/gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.001.mixpods
/glade/u/home/dcherian/python/mom6-tools/mom6_tools/kerchunk.py:104: RuntimeWarning: No files found for caseroot: /glade/campaign/cgd/oce/projects/pump//cesm/gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.001.mixpods, stream: hm.wci
  warnings.warn(f"No files found for caseroot: {caseroot}, stream: {stream}", RuntimeWarning)
100%|██████████| 1/1 [01:34<00:00, 94.96s/it]

calculate ONI#

(
    oniobs.hvplot.line(x="time", label="obs")
    * onimom6.hvplot.line(x="time", label="MOM6")
).opts(ylabel="ONI [°C]")
oniobs.enso_transition_mask.plot()
onimom6.enso_transition_mask.plot(color="r")
[<matplotlib.lines.Line2D>]
_images/94103287fcd3b60c9257f03a6985bb4b98cad673ba31d02f429aca0a3f467360.png

MOM6 sections#

Combine sections#
catalog
{
    'baseline': ('baseline', 'gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.001.mixpods'),
    'epbl': ('ePBL', 'gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.epbl.001.mixpods'),
    'kpp.lmd.002': (
        'KPP Ri0=0.5',
        'gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.kpp.lmd.002.mixpods'
    ),
    'kpp.lmd.003': (
        'KPP Ri0=0.5, Ric=0.2,',
        'gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.kpp.lmd.003.mixpods'
    ),
    'kpp.lmd.004': (
        'KPP ν0=2.5, Ric=0.2, Ri0=0.5',
        'gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.kpp.lmd.004.mixpods'
    ),
    'baseline.N150': ('baseline N=150', 'gmom.e23.GJRAv3.TL319_t061_zstar_N150.baseline.mixpods'),
    'kpp.lmd.004.N150': (
        'KPP ν0=2.5, Ric=0.2, Ri0=0.5, N=150',
        'gmom.e23.GJRAv3.TL319_t061_zstar_N150.kpp.lmd.004.mixpods'
    ),
    'new_baseline.hb': ('KD=0, KV=0', 'gmom.e23.GJRAv3.TL319_t061_zstar_N65.new_baseline.hb'),
    'new_baseline.kpp.lmd.004': (
        'KPP ν0=2.5, Ric=0.2, Ri0=0.5',
        'gmom.e23.GJRAv3.TL319_t061_zstar_N65.new_baseline.kpp.lmd.004.mixpods'
    ),
    'new_baseline.kpp.lmd.005': (
        'KPP ν0=2.5, Ri0=0.5',
        'gmom.e23.GJRAv3.TL319_t061_zstar_N65.new_baseline.kpp.lmd.005.mixpods'
    )
}
casename = "gmom.e23.GJRAv3.TL319_t061_zstar_N65.new_baseline.kpp.lmd.005.mixpods"
ds = mixpods.read_mom6_sections(casename)
100%|██████████| 39/39 [02:32<00:00,  3.90s/it]
ds.drop_vars(
    ["average_DT", "average_T2", "average_T1", "time_bnds"], errors="ignore"
).chunk({"time": 24 * 365}).to_zarr(
    f"/glade/scratch/dcherian/archive/{casename}/ocn/moorings/tao.zarr",
    consolidated=True,
    mode="w",
)
<xarray.backends.zarr.ZarrStore object at 0x2aafa7cb0c10>
reload = mixpods.load_mom6_sections(casename)
reload.uo.count().compute()
%autoreload

casename = "gmom.e23.GJRAv3.TL319_t061_zstar_N65.new_baseline.kpp.lmd.005.mixpods"
casename = mixpods.mom6_sections_to_zarr(casename)
100%|██████████| 33/33 [00:55<00:00,  1.69s/it]
%autoreload

casename = "gmom.e23.GJRAv3.TL319_t061_zstar_N65.new_baseline.kpp.lmd.004.mixpods"
mixpods.mom6_sections_to_zarr(casename)
100%|██████████| 33/33 [00:59<00:00,  1.81s/it]
casename = "gmom.e23.GJRAv3.TL319_t061_zstar_N150.baseline.mixpods"
mixpods.mom6_sections_to_zarr(casename)
100%|██████████| 39/39 [00:29<00:00,  1.30it/s]
casename = "gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.001.mixpods"
mixpods.mom6_sections_to_zarr(casename)
casename = "gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.epbl.001.mixpods"
mixpods.mom6_sections_to_zarr(casename)
100%|██████████| 31/31 [01:50<00:00,  3.57s/it]
/glade/u/home/dcherian/pump/pump/mixpods.py:887: RuntimeWarning: Converting a CFTimeIndex with dates from a non-standard calendar, 'noleap', to a pandas.DatetimeIndex, which uses dates from the standard calendar.  This may lead to subtle errors in operations that depend on the length of time between dates.
  mom6tao["time"] = mom6tao.indexes["time"].to_datetimeindex()
casename = "gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.kpp.lmd.002.mixpods"
mixpods.mom6_sections_to_zarr(casename)
casename = "gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.kpp.lmd.003.mixpods"
mixpods.mom6_sections_to_zarr(casename)
100%|██████████| 38/38 [02:07<00:00,  3.36s/it]
/glade/u/home/dcherian/pump/pump/mixpods.py:900: RuntimeWarning: Converting a CFTimeIndex with dates from a non-standard calendar, 'noleap', to a pandas.DatetimeIndex, which uses dates from the standard calendar.  This may lead to subtle errors in operations that depend on the length of time between dates.
  mom6tao["time"] = mom6tao.indexes["time"].to_datetimeindex()
casename = "gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.kpp.lmd.004.mixpods"
mixpods.mom6_sections_to_zarr(casename)

Read sections#

dask.config.set(scheduler=client)
m = mixpods.read_mom6_sections(
    "gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.001.mixpods"
)
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/dask_jobqueue/core.py:255: FutureWarning: job_extra has been renamed to job_extra_directives. You are still using it (even if only set to []; please also check config files). If you did not set job_extra_directives yet, job_extra will be respected for now, but it will be removed in a future release. If you already set job_extra_directives, job_extra is ignored and you can remove it.
  warnings.warn(warn, FutureWarning)
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/dask_jobqueue/core.py:274: FutureWarning: env_extra has been renamed to job_script_prologue. You are still using it (even if only set to []; please also check config files). If you did not set job_script_prologue yet, env_extra will be respected for now, but it will be removed in a future release. If you already set job_script_prologue, env_extra is ignored and you can remove it.
  warnings.warn(warn, FutureWarning)
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/dask_jobqueue/pbs.py:82: FutureWarning: project has been renamed to account as this kwarg was used wit -A option. You are still using it (please also check config files). If you did not set account yet, project will be respected for now, but it will be removed in a future release. If you already set account, project is ignored and you can remove it.
  warnings.warn(warn, FutureWarning)
100%|██████████| 21/21 [02:14<00:00,  6.42s/it]
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xarray/coding/times.py:360: FutureWarning: Index.ravel returning ndarray is deprecated; in a future version this will return a view on self.
  sample = dates.ravel()[0]
/glade/u/home/dcherian/pump/pump/mixpods.py:847: RuntimeWarning: Converting a CFTimeIndex with dates from a non-standard calendar, 'noleap', to a pandas.DatetimeIndex, which uses dates from the standard calendar.  This may lead to subtle errors in operations that depend on the length of time between dates.
  mom6tao["time"] = mom6tao.indexes["time"].to_datetimeindex()
/glade/u/home/dcherian/pump/pump/mixpods.py:859: UserWarning: Kv_v not present. Assuming equal to Kv_u
  warnings.warn("Kv_v not present. Assuming equal to Kv_u")
m.drop_vars(["average_DT", "average_T1", "average_T2", "time_bnds"]).chunk(
    {"time": 365 * 24}
).to_zarr(
    "/glade/scratch/dcherian/archive/gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.001.mixpods/ocn/moorings/tao.zarr",
    mode="w",
)
m.sel(yh=0, method="nearest")[["ePBL_h_ML", "mlotst"]].to_array().hvplot.line(
    by="variable", x="time"
)
m.sel(yh=0, method="nearest")[["Kd_heat", "Kd_ePBL"]].to_array().hvplot.line(
    by="variable", groupby="time", logy=True, ylim=(1e-6, 1e-1), xlim=(0, 500)
)
mom6140 = mixpods.load_mom6_sections(casename).load()
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xgcm/grid.py:1471: UserWarning: Metric at ('time', 'zi', 'yh', 'xh') being interpolated from metrics at dimensions ('time', 'zl', 'yh', 'xh'). Boundary value set to 'extend'.
  warnings.warn(
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xgcm/grid.py:1471: UserWarning: Metric at ('time', 'zi', 'yh', 'xh') being interpolated from metrics at dimensions ('time', 'zl', 'yh', 'xh'). Boundary value set to 'extend'.
  warnings.warn(
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xgcm/grid.py:1471: UserWarning: Metric at ('time', 'zi', 'yq', 'xh') being interpolated from metrics at dimensions ('time', 'zl', 'yh', 'xh'). Boundary value set to 'extend'.
  warnings.warn(
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/cf_xarray/accessor.py:1638: UserWarning: Variables {'areacello'} not found in object but are referred to in the CF attributes.
  warnings.warn(
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xgcm/grid.py:1471: UserWarning: Metric at ('time', 'zi', 'yh', 'xh') being interpolated from metrics at dimensions ('time', 'zl', 'yh', 'xh'). Boundary value set to 'extend'.
  warnings.warn(
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xgcm/grid.py:1471: UserWarning: Metric at ('time', 'zi', 'yq', 'xh') being interpolated from metrics at dimensions ('time', 'zl', 'yh', 'xh'). Boundary value set to 'extend'.
  warnings.warn(
mom6140.uo.cf.plot(robust=True)
mom6140.eucmax.cf.plot()
mom6140.mldT.cf.plot(lw=0.5, color="k")
mixpods.plot_n2s2pdf(mom6140.n2s2pdf.sel(enso_transition_phase="none"))
<matplotlib.contour.QuadContourSet>
_images/b8bed40b81209ef9ccde157be74e5b3a06571f6e7cfba20a8759504585d48dfe.png

Pick simulations#

Build dict#

import warnings

datasets = {
    # "TAO": tao_gridded,
    # "MITgcm": mitgcm,
}

catalog_sub = {
    k: catalog_dict[k]
    for k in catalog_dict.keys()
    if (k == "kpp.lmd.004")  # or ("baseline" in k and "150" not in k)
}
display(catalog_sub)
{
    'kpp.lmd.004': (
        'KPP ν0=2.5, Ric=0.2, Ri0=0.5',
        'gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.kpp.lmd.004.mixpods'
    )
}
%autoreload

with warnings.catch_warnings():
    warnings.simplefilter("ignore", category=UserWarning)
    warnings.simplefilter("ignore", category=FutureWarning)
    for short_name, (long_name, folder) in tqdm.tqdm(catalog_sub.items()):
        datasets.update(
            {
                short_name: mixpods.load_mom6_sections(folder).assign_attrs(
                    {"title": long_name}
                )
            }
        )
  0%|          | 0/1 [00:00<?, ?it/s]
datasets["TAO"] = DataTree(tao_gridded)
datasets["les"] = les["0.0.-140.oct.average.month"].to_dataset()

# Offset LES to work with slicing below
datasets["les"]["time"] = datasets["les"]["time"] + pd.Timedelta(days=25 * 365)

Build tree#

tree = DataTree.from_dict(datasets)

tree = tree.sel(time=slice("2000", "2017"))

ref = tree["TAO"].ds.reset_coords(drop=True).cf.sel(Z=slice(-120, None))
counts = np.minimum(ref["S2"].cf.count("Z"), ref["N2T"].cf.count("Z")).load()


def calc_histograms(ds):
    ds = ds.copy()
    ds["tao_mask"] = counts.reindex(time=ds.time, method="nearest") > 5
    ds["tao_mask"].attrs = {
        "description": "True when there are more than 5 5-m T, u, v in TAO dataset"
    }
    # ds = ds.where(ds.tao_mask)
    return ds.update(mixpods.pdf_N2S2(ds))


tree = tree.map_over_subtree(calc_histograms)
if "les" in tree:
    tree["les"] = tree["les"].isel(z=slice(-2))
    tree["les"]["KT"].attrs["standard_name"] = "ocean_vertical_heat_diffusivity"

if "micro" in locals():
    tree.update(micro)
tree
<xarray.DatasetView>
Dimensions:  ()
Data variables:
    *empty*
%autoreload

dailies = tree.map_over_subtree(mixpods.daily_composites)
dailies
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/numpy/lib/nanfunctions.py:1217: RuntimeWarning: All-NaN slice encountered
  r, k = function_base._ureduce(a, func=_nanmedian, axis=axis, out=out,
<xarray.DatasetView>
Dimensions:  ()
Data variables:
    *empty*
tree = mixpods.persist_tree(tree)
mixpods.validate_tree(tree)

Comparing shear prod to eps#

(ds.shear_prod.mean("time").hvplot.line() * ds.eps.mean("time").hvplot.line()).opts(
    hv.opts.Curve(invert_axes=True, ylim=(1e-10, None), logx=True)
)

Daily composites#

ε, τ#

  • How well does the model get τ

    • τ bins are [0, 0.04, 0.075, inf]

def groupby_mean(node):
    return node["tau"].groupby_bins(node.tau, bins=np.linspace(0, 0.2, 51)).count()


tau_hist = (
    tree.dc.subset_nodes(["tau"])
    .map_over_subtree(groupby_mean)
    .reset_coords(drop=True)
    .dc.concatenate_nodes()
)
tau_hist["tau_bins"] = pd.IntervalIndex(tau_hist.tau_bins.data).mid
tau_hist.hvplot.step(by="node")
bins = np.linspace(0, 0.2, 51)
for name, node in tree.children.items():
    node["tau"].plot.hist(bins=bins, label=name, histtype="step", lw=3, density=True)
plt.legend()
dcpy.plots.linex([0.04, 0.075])
_images/966e87b1b06744b5507cb79d782543af57dcd110a6d571c5a1db1ab4c1e5261a.png

Verify depth is normalized#

for name, ds in datasets.items():
    (ds.cf["sea_water_x_velocity"].cf["Z"].plot(marker=".", ls="none", label=name))
plt.legend()
<matplotlib.legend.Legend>
_images/2372712bb196933da3d938a29d3f04e0e217fe29d04bb036dbceb08ab9ce12da.png

Compare EUC maximum and MLD#

Monthly climatology#

import tqdm

for node in tqdm.tqdm(tree.children):
    tree[node]["mldT"] = tree[node]["mldT"].load()
100%|██████████| 3/3 [00:28<00:00,  9.35s/it]
def to_dataset(tree):
    concat = xr.concat([v.ds for v in tree.children.values()], dim="node")
    concat["node"] = list(tree.children.keys())
    return concat


clim = to_dataset(
    tree.map_over_subtree(
        lambda x: x.reset_coords()[["mldT", "eucmax"]].groupby("time.month").mean()
    )
).load()
clim
<xarray.Dataset>
Dimensions:  (node: 3, month: 12)
Coordinates:
  * month    (month) int64 1 2 3 4 5 6 7 8 9 10 11 12
  * node     (node) <U16 'TAO' 'kpp.lmd.004' 'kpp.lmd.004.N150'
Data variables:
    mldT     (node, month) float64 -24.54 -16.38 -12.75 ... -21.87 -25.93 -31.85
    eucmax   (node, month) float64 -107.1 -100.9 -93.21 ... -103.6 -104.7 -103.6
hv.Layout(
    [
        hv.Overlay(
            [
                tree[node]
                .ds["mldT"]
                .reset_coords(drop=True)
                .groupby("time.month")[month]
                .hvplot.hist(label=node, legend=True)
                .opts(frame_width=150)
                for node in tree.children
            ]
        ).opts(title=str(month))
        for month in np.arange(1, 13)
    ]
).cols(4)
(clim.mldT.hvplot(by="node") + clim.eucmax.hvplot(by="node")).cols(1)
mixpods.plot_timeseries(tree, "mldT", obs="TAO")
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/bokeh/core/property/bases.py:259: DeprecationWarning: elementwise comparison failed; this will raise an error in the future.
  return new == old
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/bokeh/core/property/bases.py:259: DeprecationWarning: elementwise comparison failed; this will raise an error in the future.
  return new == old
datasets["TAO"].eucmax.attrs["long_name"] = "EUC maximum"
mixpods.plot_timeseries(tree.sel(time="2008"), "eucmax", obs="TAO")

MLD Maps#

def read_sfc(casename):
    path = f"/glade/scratch/dcherian/archive/{casename}/ocn/hist/*sfc*00[4-7]*.nc"
    sfc = xr.open_mfdataset(
        path, data_vars="minimal", coords="minimal", compat="override", parallel=True
    )
    return sfc


sfc = DataTree()
casenames = {
    "MOM6 KPP": "gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.001.mixpods",
    "MOM6 ePBL": "gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.epbl.001.mixpods",
}

for name, casename in casenames.items():
    sfc[name] = DataTree(read_sfc(casename))
sfc
<xarray.DatasetView>
Dimensions:  ()
Data variables:
    *empty*
mldclim = sfc.map_over_subtree(
    lambda node: node.mlotst.groupby("time.month").mean()
).compute()
filepath = (
    "/glade/work/gmarques/cesm/datasets/MLD/deBoyer/deBoyer_MLD_remapped_to_tx06v1.nc"
)
mldclim["obs"] = DataTree(xr.open_dataset(filepath))
mldclim
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xarray/coding/times.py:699: SerializationWarning: Unable to decode time axis into full numpy.datetime64 objects, continuing using cftime.datetime objects instead, reason: dates out of range
  dtype = _decode_cf_datetime_dtype(data, units, calendar, self.use_cftime)
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xarray/core/indexing.py:524: SerializationWarning: Unable to decode time axis into full numpy.datetime64 objects, continuing using cftime.datetime objects instead, reason: dates out of range
  return np.asarray(array[self.key], dtype=None)
<xarray.DatasetView>
Dimensions:  ()
Data variables:
    *empty*
mldclim.
mldclim["MOM6 KPP"] = DataTree(
    read_sfc("gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.001.mixpods")
    .mlotst.groupby("time.month")
    .mean()
)
mldclim["MOM6 ePBL"] = DataTree(
    read_sfc("gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.epbl.001.mixpods")
    .mlotst.groupby("time.month")
    .mean()
)
(
    (mlotst - mldclim.mld.groupby("time.month").first())
    .sel(yh=slice(-5, 5), xh=slice(-170, -80))
    .plot(col="month", robust=True, col_wrap=3, aspect=2)
)
<xarray.plot.facetgrid.FacetGrid>
_images/379b40bc88e48b23199166fe6ba7e8a7925e86dce6842cc7315c2a11db8e079a.png

Mixing layer depth?#

todrop = ["TAO"]
notao = DataTree.from_dict(
    {k: v.ds for k, v in tree.children.items() if k not in todrop}
)
with cfxr.set_options(custom_criteria=mixing_layer_criteria):
    plot = mixpods.plot_timeseries(notao, "boundary_layer_depth", obs=None)
plot
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/cf_xarray/accessor.py:1663: UserWarning: Variables {'areacello'} not found in object but are referred to in the CF attributes.
  warnings.warn(
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/cf_xarray/accessor.py:1663: UserWarning: Variables {'areacello'} not found in object but are referred to in the CF attributes.
  warnings.warn(

Mean profiles: mean, std#

Model mean S2 is lower by a factor of 2

S2 = mixpods.plot_profile_fill(tree, "S2", "S^2")
N2 = mixpods.plot_profile_fill(tree, "N2T", "N_T^2")
u = mixpods.plot_profile_fill(tree, "sea_water_x_velocity", "u")
T = mixpods.plot_profile_fill(tree, "sea_water_potential_temperature", "T")
Ri = mixpods.plot_median_Ri(tree)
Hide code cell source
S = mixpods.plot_profile_fill(tree, "sea_water_salinity", "S")
v = mixpods.plot_profile_fill(tree, "sea_water_y_velocity", "v")
# dTdz = plot_profile_fill(tree, "dTdz", "∂T/∂z")
# dSdz = plot_profile_fill(tree, "dSdz", "∂S/∂z")
((S2 + N2).opts("Curve", xrotation=30) + Ri.opts(xaxis="top", xrotation=30)).opts(
    sizing_mode="stretch_width"
)
((S2 + N2).opts("Curve", xrotation=30) + Ri.opts(xaxis="top", xrotation=30)).opts(
    sizing_mode="stretch_width"
)
(T + u.opts(ylim=(-0.5, 1.5))).opts(sizing_mode="stretch_width")
(T + S + u.opts(ylim=(-0.5, 1.5)) + v.opts(ylim=(-0.5, 0.5))).opts(
    sizing_mode="stretch_width"
)
S2 = plot_profile_fill(tree, "S2", "S^2")
u = plot_profile_fill(tree, "sea_water_x_velocity", "u")
popts = (
    hv.opts.Curve(fontscale=1.5, line_width=3, color=hv.Cycle("Dark2")),
    hv.opts.Area(fill_color=hv.Cycle("Dark2")),
)

h = (S2.opts(show_legend=False) + u).opts(
    hv.opts.Curve("Curve", xaxis="bottom", xlim=(-200, 0)),
    hv.opts.Overlay(frame_width=400, frame_height=600),
    *popts,
)
h

Remap to EUC coordinate#

gcmeq.coords["zeuc"] = gcmeq.depth - gcmeq.eucmax
remapped = flox.xarray.xarray_reduce(
    gcmeq.drop_vars(["SSH", "KPPhbl", "mld", "eucmax"], errors="ignore"),
    "zeuc",
    dim="depth",
    func="mean",
    expected_groups=(np.arange(-250, 250.1, 5),),
    isbin=(True,),
)
remapped["zeuc_bins"].attrs["units"] = "m"
remapped
<xarray.Dataset>
Dimensions:          (time: 174000, longitude: 4, zeuc_bins: 100)
Coordinates:
    latitude         float64 0.025
  * longitude        (longitude) float64 -155.0 -140.0 -125.0 -110.0
  * time             (time) datetime64[ns] 1998-12-31T18:00:00 ... 2018-11-06...
    cool_mask        (time) bool True True True True ... False False False False
    warm_mask        (time) bool False False False False ... True True True True
  * zeuc_bins        (zeuc_bins) object (-250.0, -245.0] ... (245.0, 250.0]
Data variables: (12/25)
    DFrI_TH          (time, longitude, zeuc_bins) float32 dask.array<chunksize=(6000, 1, 100), meta=np.ndarray>
    KPPdiffKzT       (time, longitude, zeuc_bins) float32 dask.array<chunksize=(6000, 1, 100), meta=np.ndarray>
    KPPg_TH          (time, longitude, zeuc_bins) float32 dask.array<chunksize=(6000, 1, 100), meta=np.ndarray>
    KPPviscAz        (time, longitude, zeuc_bins) float32 dask.array<chunksize=(6000, 1, 100), meta=np.ndarray>
    VISrI_Um         (time, longitude, zeuc_bins) float32 dask.array<chunksize=(6000, 1, 100), meta=np.ndarray>
    VISrI_Vm         (time, longitude, zeuc_bins) float32 dask.array<chunksize=(6000, 1, 100), meta=np.ndarray>
    ...               ...
    S2               (time, longitude, zeuc_bins) float32 dask.array<chunksize=(6000, 1, 100), meta=np.ndarray>
    shear            (time, longitude, zeuc_bins) float32 dask.array<chunksize=(6000, 1, 100), meta=np.ndarray>
    N2               (time, longitude, zeuc_bins) float32 dask.array<chunksize=(6000, 1, 100), meta=np.ndarray>
    shred2           (time, longitude, zeuc_bins) float32 dask.array<chunksize=(6000, 1, 100), meta=np.ndarray>
    Ri               (time, longitude, zeuc_bins) float32 dask.array<chunksize=(6000, 1, 100), meta=np.ndarray>
    enso_transition  (zeuc_bins, time) <U12 'La-Nina cool' ... 'El-Nino warm'
Attributes:
    easting:   longitude
    northing:  latitude
    title:     Station profile, index (i,j)=(1201,240)
remapped = remapped.persist()
cluster.scale(6)

Seasonal median Ri: (0, 140)#

remapped["longitude"] = [-155.0, -140, -125, -110]
fg = (
    remapped.Ri.groupby("time.season")
    .median()
    .sel(season=["DJF", "MAM", "JJA", "SON"], longitude=[-140, -110])
    .cf.plot(
        col="season", row="longitude", xlim=(0, 1.5), ylim=(-20, None), label="MITgcm"
    )
)
fg.data = tao_Ri.cf.sel(quantile=0.5, longitude=[-140, -110])
fg.map_dataarray_line(
    xr.plot.line, x=None, y="zeuc", hue="season", color="k", label="TAO"
)
fg.map(lambda: plt.axvline(0.25))
fg.axes[-1, -1].legend(bbox_to_anchor=(1.5, 1))
<matplotlib.legend.Legend>
_images/de806fb2b38296bab1ec713f0632b54dff28a3fa7f59f43ee018acdefada8e19.png

1D Climatological heat budget#

  • Using TAO swnet, qnet measurements instead of Tropflux

tree["TAO"] = DataTree(tao_gridded)
mixpods.climo_heat_budget_1d(tree["TAO"].ds, penetration="moum")
mixpods.climo_heat_budget_1d(tree["TAO"].ds, penetration="mom")
No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
_images/c81627abe7b7b8ad053498ae040d320f7f62ea4d9a4c7708bc775e490af071ae.png _images/8d3bf9bad20c6663b5023fd3acb30fbbbdc5c71561ce3d0818e2747e0166868d.png
f, ax = plt.subplots(
    1,
    len(tree),
    constrained_layout=True,
    squeeze=False,
    sharex=True,
    sharey=True,
    figsize=(10, 3),
)

for axx, (name, node) in zip(ax.flat, tree.children.items()):
    mixpods.plot_climo_heat_budget_1d(node.ds, mxldepth=-40, penetration="mom", ax=axx)
    axx.set_title(name)

dcpy.plots.clean_axes(ax)
No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
_images/fb5f66b090672708c55e4945761e9ee7c7f1d7203227dd8464c79bac381e3988.png
mixpods.plot_climo_heat_budget_1d(tree["kpp.lmd.004.N150"].ds, penetration="mom")
No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
_images/bb12a5ffc0d1660adedecbe434c2486c1dd78b6d4d48551b2d0ae7ac2c818880.png

Stability diagram: 4N2-S2 plane#

Warner & Moum (2019):

Both velocity and temperature are hourly averaged before interpolation to 5-m vertical bins

Contours enclose 50% of data

Compare daily vs hourly frequency#

resampled = tree.from_dict(
    {k: v.ds.resample(time="D").mean() for k, v in tree.children.items()}
)
# resampled["TAO"].update(mixpods.prepare(resampled["TAO"].ds, oni=mixpods.process_oni()))
for name, node in resampled.children.items():
    node.update(
        mixpods.prepare(
            node.ds,
            oni=tree[name]["oni"].resample(time="D").mean().reset_coords(drop=True),
        )
    )
    node.update(mixpods.pdf_N2S2(node.ds))
    mixpods.validate_tree(node)
resampled = mixpods.load_tree(resampled)
tree = mixpods.load_tree(tree)
fig = plt.figure(constrained_layout=True, figsize=(12, 6))

subfigs = fig.subfigures(2, 1)
mixpods.plot_stability_diagram_by_dataset(tree, fig=subfigs[0])
mixpods.plot_stability_diagram_by_dataset(resampled, fig=subfigs[1])
_images/e3c72d6db28d6cceaeca1332dcdea39cd873540b9b68965123d11396ffb7ef33.png

Older#

Hide code cell source
mixpods.plot_stability_diagram_by_dataset(datasets)
_images/2027c31463a49bfd091b264ec6d31d57a6debcc9415b3587f08d751aaac9ded3.png
Hide code cell source
mixpods.plot_stability_diagram_by_phase(datasets, obs="TAO", fig=None)
_images/311676930e479f61019987704cb52d24a341c248f3c24ad42f0aacf6347cda46.png

Composed#

excluding MLD#

  • have not matched vertical grid spacing yet.

  • minor axis is smaller for increasing shear with KPP

  • prandtl number

  • interpolate to TAO grid

  • hybrid HYCOM-like case may have equatorial refinement of vertical grid above EUC

  • compare hybrid coordinate case resolution

  • instrumental error on vertical gradients for S2, N2;

    • if added to model, would it change the bottom tail.

  • Horizontal or vertical res:

    • MOM6 at 5km? hah

    • TIW compositing? La-NIna dec?

      • top end’s are similar, so maybe not?

  • sensitivity of cold tongue mean state

    • horizontal viscosity; controls EUC strength? (increase?)

    • vertical res; yan li’s paper

    • POP :thermocline always too diffuse and EUC too thick;

    • higher order interpolants can control amount diffusion when remapping

  • get S2, N2 from TPOSE?

Questions:

  1. internal wave spectrum?

  2. minor axis is smaller for model, particularly MITgcm-> too diffusive?

  3. Can we think of this as the model relaxing to critical Ri too quickly

tree = mixpods.load_tree(tree)
fig = plt.figure(constrained_layout=True, figsize=(12, 6))

subfigs = fig.subfigures(2, 1)
mixpods.plot_stability_diagram_by_dataset(tree, fig=subfigs[0])
mixpods.plot_stability_diagram_by_phase(tree, fig=subfigs[1])
Hide code cell source
fig = plt.figure(constrained_layout=True, figsize=(12, 6))

subfigs = fig.subfigures(2, 1)
mixpods.plot_stability_diagram_by_dataset(tree, fig=subfigs[0])
mixpods.plot_stability_diagram_by_phase(tree, fig=subfigs[1])
_images/050e916a870137e4d90530d173b39cbc75d84ebadff2f5484a33570e58c658a2.png

including MLD#

Hide code cell source
fig = plt.figure(constrained_layout=True, figsize=(12, 6))
subfigs = fig.subfigures(2, 1, height_ratios=[1.2, 1])
top = subfigs[0].subfigures(1, 3, width_ratios=[1, 5, 1])

mixpods.plot_stability_diagram_by_dataset(datasets, fig=top[1])
mixpods.plot_stability_diagram_by_phase(datasets, fig=subfigs[1])
_images/678886ada6c73c9a3c0baadcc9597ed60922d04ef61fb87e2dde1c0d8ec1bafa.png
(
    tao_gridded.n2s2pdf.sel(
        enso_transition_phase=[
            "La-Nina cool",
            "La-Nina warm",
            "El-Nino warm",
            "El-Nino cool",
        ]
    )
    .sum("N2_bins")
    .plot(hue="enso_transition_phase")
)
plt.figure()
(
    tao_gridded.n2s2pdf.sel(
        enso_transition_phase=[
            "La-Nina cool",
            "La-Nina warm",
            "El-Nino warm",
            "El-Nino cool",
        ]
    )
    .sum("S2_bins")
    .plot(hue="enso_transition_phase")
)
[<matplotlib.lines.Line2D>,
 <matplotlib.lines.Line2D>,
 <matplotlib.lines.Line2D>,
 <matplotlib.lines.Line2D>]
_images/4e7b99af96d4ce455831441e3352c755dd0fed619ae4d068afa92e5d99bb25d8.png _images/1db17f0d2ed4f555c9f2e749345f58130bb2819db51a3f0a1ee17a981b967f7e.png
oni = pump.obs.process_oni().sel(time=slice("2005-Oct", "2015"))
(
    oni.hvplot.step(color="k")
    + pump.obs.make_enso_transition_mask()
    .sel(time=slice("2005-Jun", "2015"))
    .reset_coords(drop=True)
    .hvplot.step()
).cols(1)

Turbulence#

TODO Mean Turbulence Variable Profiles : depth-space#

h = {
    varname: mixpods.map_hvplot(
        lambda node, name, muted: (
            node.ds.cf[varname]
            .reset_coords(drop=True)
            .hvplot.line(
                ylabel=varname,
                label=name,
                logx=varname != "sea_water_x_velocity",
                invert=True,
            )
        ),
        tree,
    )
    for varname in [
        "sea_water_x_velocity",
        "sea_water_potential_temperature",
        "chi",
        "eps",
        "ocean_vertical_heat_diffusivity",
        "ocean_vertical_momentum_diffusivity",
    ]
}
h["chi"].opts(ylim=(1e-9, 1e-4))
h["eps"].opts(ylim=(1e-9, 1e-4))
h["ocean_vertical_heat_diffusivity"].opts(ylim=(5e-7, 3))
h["ocean_vertical_momentum_diffusivity"].opts(ylim=(1e-6, 1e-1))

h2 = {
    varname: mixpods.map_hvplot(
        lambda node, name, muted: node.ds[varname]
        .reset_coords(drop=True)
        .hvplot.line(ylabel=varname, label=name, logx=False, invert=True),
        tree,
    )
    for varname in ["Rig_T"]
}


plot = (
    hv.Layout(list(h.values()) + [h2["Rig_T"].opts(ylim=(0, 3))])
    .opts(
        hv.opts.Curve(frame_width=150, frame_height=300, xlim=(-150, 150)),
        hv.opts.Layout(shared_axes=True),
        hv.opts.Overlay(show_legend=True, show_grid=True, legend_position="right"),
        *mixpods.HV_TOOLS_OPTIONS,
        *mixpods.PRESENTATION_OPTS,
    )
    .cols(4)
)
select_legends(plot, figure_index=3, legend_position="right")
mixpods.map_hvplot(
    lambda ds, name, muted: ds.ds.cf["ocean_vertical_heat_diffusivity"]
    .mean("time")
    .hvplot.line(label=name, muted=muted),
    tree,
).opts(
    ylim=(1e-8, 1e1),
    legend_position="right",
    logx=True,
    invert_axes=True,
    frame_width=300,
    aspect=1 / 3,
)
WARNING:param.OverlayPlot19684: Due to internal constraints, when aspect and width/height is set, the bokeh backend uses those values as frame_width/frame_height instead. This ensures the aspect is respected, but means that the plot might be slightly larger than anticipated. Set the frame_width/frame_height explicitly to suppress this warning.
WARNING:param.OverlayPlot19684: aspect value was ignored because absolute width and height values were provided. Either supply explicit frame_width and frame_height to achieve desired aspect OR supply a combination of width or height and an aspect value.

mixpods.map_hvplot(
    lambda ds, name, muted: ds["eps"]
    .mean("time")
    .load()
    .hvplot.line(label=name, muted=muted),
    tree,
).opts(
    ylim=(1e-10, None),
    legend_position="right",
    logx=True,
    invert_axes=True,
    frame_width=300,
    aspect=1 / 3,
)
WARNING:param.OverlayPlot18581: Due to internal constraints, when aspect and width/height is set, the bokeh backend uses those values as frame_width/frame_height instead. This ensures the aspect is respected, but means that the plot might be slightly larger than anticipated. Set the frame_width/frame_height explicitly to suppress this warning.
WARNING:param.OverlayPlot18581: aspect value was ignored because absolute width and height values were provided. Either supply explicit frame_width and frame_height to achieve desired aspect OR supply a combination of width or height and an aspect value.

Dan’s suggestions#

  1. I liked your effort to look at shear production= KmdU/dz dot dU/dz = dissipation. I mentioned that it might be worth looking at buoyancy flux = KT dT/dz * db/dT + KS dS/dz * db/dS with db/dT, db/dS from equation of state. I think I said KT*N^2, but I hope after checking that this is the same in this region (K_T~K_S). You could then divide buoyancy flux by 0.2 to estimate epsilon for comparison with your estimate based on shear production.

  1. Dimensionless parameters. We alluded to this, but I think it would be interesting to explicitly quantify flux Richardson number Rif= buoyancy flux/shear production (as defined above). And Prt=KT/Km=Rif/Rig, where Rig is the usual gradient Richardson number. Following a similar logic, you might look at Reb= epsilon/nu N^2 with nu constant 1e-6 molecular viscosity. We discussed looking at diffusivity and viscosity in addition to epsilon. But note the similarity between dimensionless diffusivity KT/kappa and viscosity Km/nu, i.e. Reb=Rig*KM/nu. (with kappa a constant molecular diffusivity to make dimensionless)

  1. I like how you continue to push what we can learn from the distributions in S2-N2 parameter space in addition to physical space. It is good for comparing with limited observations or process models. I think you can push this even further to develop metrics quantifying how the momentum and buoyancy fluxes “tango” with each other and with S2 and N2. In addition, perhaps it would be valuable to flip the perspective and look at shear-production-buoyancy flux space? Or consider various two-dimensional spaces of non-dimensional parameters mentioned above: Rig, Reb, Rif, KT/kappa, Km/nu.

  1. Finally, I think it would be interesting to try to examine how momentum flux and shear production below the mixed layer relate to wind stress as well as the local shear. There might be a relationship that emerges in KPP, even if the K profile is formally dependent only on local Rig at these depths.

Deepening#

  • TODO think about local time zone conversion

Moum et al (2023)

We quantify the progression in time of the deepening of DC turbulence shown in Figures 7–9 by finding the time of the maximum rate of increase of \(𝜖\), \(𝜖_{𝑚𝑎𝑥}^𝑡\) , from each series after filtering each series at 1 hour. This analysis yields the deepening trajectories shown in Figures 10a-c, at the depths where an unambiguous determination of \(𝜖^{𝑚𝑎𝑥}_𝑡\) could be made. These trajectories show the arrival of DC turbulence to be progressively delayed at all depths with weakening winds.

Using a 5 hour running mean smoother

eps = dailies.dc.subset_nodes("eps").dc.concatenate_nodes().eps
layout = (
    eps.sel(node="TAO")
    .drop_vars("tau_bins")
    .hvplot.line(x="hour", col="depth", by="tau_bins", logy=True)
)
layout
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/holoviews/plotting/bokeh/plot.py:588: UserWarning: found multiple competing values for 'toolbar.active_drag' property; using the latest value
  plot = gridplot(plots[::-1],
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/holoviews/plotting/bokeh/plot.py:588: UserWarning: found multiple competing values for 'toolbar.active_scroll' property; using the latest value
  plot = gridplot(plots[::-1],

idx = (
    eps.diff("hour")  # .isel(depth=0)
    .rolling(hour=5, center=True, min_periods=1)
    .mean()
    .idxmax("hour")
    .assign_attrs(long_name="time of max $ε_t$")
)
# mask = ((-1 * idx.diff("depth", label="lower")) < 0).reindex(
#    depth=idx.depth, fill_value=False
# )
# idx = xr.where(mask, idx + 12, idx)
start = (3, -30)
end = 12

plot = hv.Layout(
    [
        da.hvplot.line(y="depth", by="node")
        * hv.Curve([start, (end, -30 - 6 * (end - start[0]))]).opts(
            line_dash="dashed", line_color="k"
        )
        for _, da in idx.groupby("tau_bins")
    ]
).opts(
    hv.opts.Curve(frame_height=300, frame_width=250),
    *mixpods.HV_TOOLS_OPTIONS,
    *mixpods.PRESENTATION_OPTS
)
select_legends(plot, 2, legend_position="right")
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/holoviews/plotting/bokeh/plot.py:967: UserWarning: found multiple competing values for 'toolbar.active_drag' property; using the latest value
  layout_plot = gridplot(

(
    eps.isel(depth=[0, 1, 2])
    # .diff("hour")
    .rolling(hour=5, center=True, min_periods=1).mean()
    # .idxmax("hour")
    .assign_attrs(long_name="time of max $ε_t$", units=None)
    # .drop_vars("tau_bins")
    .plot(col="depth", hue="tau_bins", yscale="log")
)
fg = eps.plot(x="hour", hue="depth", col="tau_bins", yscale="log")
for ax, loc in zip(fg.axs.flat, fg.name_dicts.flat):
    hour = idx.sel(loc)
    eps.sel(**loc, hour=hour).plot.line(
        ax=ax, x="hour", marker=".", ls="none", add_legend=False, _labels=False
    )
_images/f11d6ac714ae3bdb4f4779836a92bbf9ed9512802c33f56adc192eed7c0fda88.png

Test with EQUIX#

ds = tree["baseline.kpp.lmd.004"].to_dataset()
jb = ds.Jb.cf.sel(Z=slice(-150, 0), time=slice("2008-10-28", "2008-10-30")).load()
jb.hvplot.quadmesh(x="time", y="zi")
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/bokeh/core/property/bases.py:273: DeprecationWarning: elementwise comparison failed; this will raise an error in the future.
  return new == old
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/bokeh/core/property/bases.py:273: DeprecationWarning: elementwise comparison failed; this will raise an error in the future.
  return new == old
eps = (
    ds.eps.cf.sel(Z=slice(-150, 0), time=slice("2008-10-28", "2008-10-30"))
    .resample(time="H")
    .mean()
)
(eps.reset_coords(drop=True).hvplot.quadmesh(x="time", clim=(1e-9, 1e-6), cnorm="log"))
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/bokeh/core/property/bases.py:273: DeprecationWarning: elementwise comparison failed; this will raise an error in the future.
  return new == old
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/bokeh/core/property/bases.py:273: DeprecationWarning: elementwise comparison failed; this will raise an error in the future.
  return new == old
(
    eps.diff("time")
    .cf.sel(Z=-30, method="nearest")
    .resample(time="D")
    .map(lambda eps: eps.idxmax("time"))
    .load()
)
<xarray.DataArray 'time' (time: 3)>
2008-10-28T14:00:00 2008-10-29T11:00:00 2008-10-30T15:00:00
Coordinates:
    yh       float64 0.0625
    yq       float64 -0.0625
    xh       float64 -140.0
    zi       float64 -30.1
  * time     (time) datetime64[ns] 2008-10-28 2008-10-29 2008-10-30
Attributes:
    long_name:  $ε$
    units:      W/kg

Daily composites#

  • S2-N2 histogram by hour of day?

  • daily composites by el-nino phase

  • figure out what wind stress is used

    • wind stress diurnal cycle

    • wind stress in model is relative wind

    • look at the coupled model

  • either turbulence parameters

    • Jess Masich’s papers?

  • MOM6 uses Large & Pond; χpods use Large & Pond

    • does it have gustiness; MOM6 uses wind averaged over coupling interval;

  • Look at 69m-89m range with medium wind stress

  • Need to get rid of EUC effect:

    • either EUC-relative coordinate system

    • or use K, which removes a S2 factor

  • Do buoyancy flux or Γ

h = mixpods.plot_daily_composites(
    dailies.dc.subset_nodes(["S2", "N2", "Rig_T"]), logy=False
).opts(
    hv.opts.Curve(frame_width=200, xrotation=45),
    hv.opts.GridSpace(show_legend=True),
)
h
h = mixpods.plot_daily_composites(
    dailies.dc.subset_nodes(["eps", "KT", "chi"]), logy=True
)
h.opts(
    hv.opts.Curve(frame_width=200, xrotation=45),
    hv.opts.GridSpace(show_legend=True),
)

ε-Ri means#

mixpods.map_hvplot(
    lambda dt, name, muted: mixpods.plot_eps_ri_hist(
        dt["eps_ri"].load(), label=name, muted=muted
    ),
    tree.children,
).opts(show_legend=True).opts(hv.opts.Curve(ylim=(1e-8, 2e-6)))

Ri_f histogram#

(
    np.log10(np.abs(baseline.Rif.cf.sel(Z=slice(-110, 0))))
    .reset_coords(drop=True)
    .hvplot.hist(bins=np.linspace(-2, 1, 100))
) * hv.VLine(np.log10(0.2)).opts(line_color="k")
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xarray/core/computation.py:771: RuntimeWarning: divide by zero encountered in log10
  result_data = func(*input_data)

ε-Jb histogram#

from xhistogram.xarray import histogram
jpdf = histogram(
    np.log10(baseline.eps),
    np.log10(0.2 * baseline.eps_Jb),
    density=True,
    bins=np.linspace(-10, -4, 100),
).compute()
jpdf.plot.contourf(levels=52, robust=True, cmap=mpl.cm.nipy_spectral)
dcpy.plots.line45()
_images/d71a0f78595ea225a4feff93c027ecb3b5375406401ada2cdfb1a610fadd0fb3.png
from mom6_tools import wright_eos
mom6tao = baseline
mom6tao["α"] = wright_eos.alpha_wright_eos(mom6tao.thetao, mom6tao.so, p=0)
mom6tao["α"].attrs = {
    "standard_name": "sea_water_thermal_expansion_coefficient",
    "units": "kg m-3 C-1",
}
mom6tao["β"] = wright_eos.beta_wright_eos(mom6tao.thetao, mom6tao.so, p=0)
mom6tao["β"].attrs = {
    "standard_name": "sea_water_haline_contraction_coefficient",
    "units": "kg m-3",
}
(
    baseline.Kd_heat * baseline.α * baseline.Tz
    + baseline.Kd_heat * baseline.β * baseline.Sz
)
<xarray.DataArray (time: 507264, zi: 27, zl: 27)>
dask.array<chunksize=(8760, 26, 27), meta=np.ndarray>
Coordinates: (12/14)
  * time             (time) datetime64[ns] 1958-01-06T00:30:00 ... 2016-12-31...
    xh               float64 -140.0
    yh               float64 0.0625
    yq               float64 -0.0625
  * zi               (zi) float64 -230.8 -212.0 -194.4 -177.8 ... -5.0 -2.5 -0.0
    eucmax           (time) float64 dask.array<chunksize=(8760,), meta=np.ndarray>
    ...               ...
    en_mask          (time) bool False False False False ... False False False
    ln_mask          (time) bool False False False False ... False False False
    warm_mask        (time) bool True True True True ... True True True True
    cool_mask        (time) bool False False False False ... False False False
    enso_transition  (time) <U12 '____________' ... '____________'
  * zl               (zl) float64 -240.8 -221.4 -203.2 ... -6.25 -3.75 -1.25
Attributes:
    cell_measures:  area: areacello
    cell_methods:   area:mean zi:point yh:mean xh:mean time: mean
    long_name:      Total diapycnal diffusivity for heat at interfaces
    time_avg_info:  average_T1,average_T2,average_DT
    units:          m2 s-1

Mean SST, Jq#

def flatten(tree):
    varnames = next(iter(tree.children.values())).data_vars

    out = xr.Dataset()
    coord = xr.DataArray(list(tree.keys()), dims="node")
    for var in varnames:
        variables = (node[var] for name, node in tree.children.items())
        out[var] = xr.concat(variables, dim=coord)
    return out


sub = tree.map_over_subtree(
    lambda node: (
        node.cf[["Jq", "sea_surface_temperature", "ocean_vertical_heat_diffusivity"]]
        .pipe(np.abs)
        .cf.rename(
            {"sea_surface_temperature": "sst", "ocean_vertical_heat_diffusivity": "KT"}
        )
        .cf.sel(Z=slice(-60, -20))
        # .cf.mean("Z")
        .cf.groupby("time.month")
        .mean(["Z", "time"])
        .load()
    )
).pipe(flatten)
h = (
    sub.Jq.hvplot.line(by="node", legend=True)
    + sub.sst.hvplot.line(by="node", logy=True, legend=True)
).cols(1)
h
vertical = tree.map_over_subtree(
    lambda node: (
        node.cf[["Jq", "ocean_vertical_heat_diffusivity"]]
        .cf.rename({"ocean_vertical_heat_diffusivity": "KT"})
        .cf.sel(Z=slice(-120, -20))
        .groupby("time.season")
        .mean()
        .sel(season=["DJF", "MAM", "JJA", "SON"])
        .load()
    )
)
seasonal = {}
seasonal["Jq"] = mixpods.map_hvplot(
    lambda ds, name, muted: (
        ds["Jq"]
        .cf.rename({"Z": "depth"})
        .hvplot.line(y="depth", col="season", label=name, muted=muted)
    ),
    vertical.children,
)
seasonal["KT"] = mixpods.map_hvplot(
    lambda ds, name, muted: (
        ds["KT"]
        .cf.rename({"Z": "depth"})
        .hvplot.line(logx=True, y="depth", col="season", label=name, muted=muted)
    ),
    vertical.children,
)
def hvplot_facet(tree, varname, col, **kwargs):
    by = next(iter(tree.children.values()))[col].data
    handles = []
    for by_ in by:
        handles.append(
            hv.Overlay(
                [
                    node.ds.cf[varname]
                    .sel({col: by_})
                    .hvplot.line(label=name, **kwargs)
                    for name, node in tree.children.items()
                ]
            )
        )
    return hv.Layout(handles)


proc = vertical.map_over_subtree(lambda node: node.cf.rename({"Z": "Z"}))
layout = hv.Layout(
    [
        hvplot_facet(
            proc,
            varname="Jq",
            col="season",
        ),
        hvplot_facet(
            proc,
            varname="ocean_vertical_heat_diffusivity",
            col="season",
            logx=True,
            ylim=(1e-6, 1e-1),
        ),
    ]
)
layout.opts(
    hv.opts.Curve(invert_axes=True, xrotation=20, frame_width=160, frame_height=300),
    hv.opts.Overlay(legend_position="bottom_left", shared_axes=True, labelled=["x"]),
)
mixpods.map_hvplot(
    lambda ds, name, muted: (ds["Tz"] < 0)
    .sum("time")
    .hvplot.line(label=name, muted=muted, invert=True),
    tree,
)
np.log10(tree["kpp.lmd.004"]["Kd_heat"].resample(time="M").mean()).hvplot.quadmesh(
    y="zi"
)
mixpods.map_hvplot(
    lambda ds, name, muted: ds.ds.cf["ocean_vertical_heat_diffusivity"]
    .mean("time")
    .hvplot.line(label=name, muted=muted),
    tree.children,
).collate().opts(
    ylim=(1e-8, 1e1),
    legend_position="right",
    logx=True,
    invert_axes=True,
    frame_width=300,
    aspect=1 / 3,
)
WARNING:param.OverlayPlot02012: Due to internal constraints, when aspect and width/height is set, the bokeh backend uses those values as frame_width/frame_height instead. This ensures the aspect is respected, but means that the plot might be slightly larger than anticipated. Set the frame_width/frame_height explicitly to suppress this warning.
WARNING:param.OverlayPlot02012: aspect value was ignored because absolute width and height values were provided. Either supply explicit frame_width and frame_height to achieve desired aspect OR supply a combination of width or height and an aspect value.
WARNING:param.OverlayPlot02072: Due to internal constraints, when aspect and width/height is set, the bokeh backend uses those values as frame_width/frame_height instead. This ensures the aspect is respected, but means that the plot might be slightly larger than anticipated. Set the frame_width/frame_height explicitly to suppress this warning.
WARNING:param.OverlayPlot02072: aspect value was ignored because absolute width and height values were provided. Either supply explicit frame_width and frame_height to achieve desired aspect OR supply a combination of width or height and an aspect value.
mixpods.map_hvplot(
    lambda ds, name, muted: ds["Jq"].mean("time").hvplot.line(label=name, muted=muted),
    tree.children,
).collate().opts(
    legend_position="right", invert_axes=True, frame_width=300, aspect=1 / 3
)

Histograms: Ri, ε#

  • take out MLD?

handles = [
    mixpods.plot_distributions(tree, "chi", bins=np.linspace(-11, -4, 101), log=True),
    mixpods.plot_distributions(tree, "eps", bins=np.linspace(-11, -4, 101), log=True),
    mixpods.plot_distributions(
        tree, "ocean_vertical_heat_diffusivity", bins=np.linspace(-8, -1, 101), log=True
    ),
    # plot_distributions(tree, "Jq", bins=np.linspace(-1000, 0, 51), log=False),
    mixpods.plot_distributions(tree, "Rig_T", np.linspace(-0.5, 1.5, 61))
    * hv.VLine(0.25).opts(line_color="k"),
]
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xarray/core/computation.py:761: RuntimeWarning: invalid value encountered in log10
  result_data = func(*input_data)
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xarray/core/computation.py:761: RuntimeWarning: invalid value encountered in log10
  result_data = func(*input_data)
plot = (
    hv.Layout(handles)
    .opts("Overlay", frame_width=500)
    .cols(2)
    .opts(*mixpods.HV_TOOLS_OPTIONS)
)
select_legends(plot, figure_index=1, legend_position="right")
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/holoviews/plotting/bokeh/plot.py:967: UserWarning: found multiple competing values for 'toolbar.active_drag' property; using the latest value
  layout_plot = gridplot(

for node in ["baseline", "kpp.lmd.002", "kpp.lmd.003", "kpp.lmd.004"]:
    plt.figure()
    tree[node].sel(time=slice("2008-10-25", "2008-11-15")).ds.cf["ocean_vertical_heat_diffusivity"]
    .cf.sel(Z=slice(-120, None)).reset_coords(drop=True).cf.plot(
        cmap=mpl.cm.Spectral_r, size=3, aspect=5, norm=mpl.colors.LogNorm(1e-7, 1e-2)
    )
<Figure size 896x672 with 0 Axes>
_images/a53d29367e5dda09a0e1f6ac49d49c85e2f3fc6059252d26b38c3418d2c11c3e.png
<Figure size 896x672 with 0 Axes>
_images/188bf451e3f81e0d5d66bc10b7cfbbcda8e63b0f7a13ae45b6ebd4a929c6e981.png
<Figure size 896x672 with 0 Axes>
_images/b05865d1a8fbbb6c1b110ea191266f122a11440d655dcfe128666762ca0eb855.png
<Figure size 896x672 with 0 Axes>
_images/dc2193a1caf3834b8809236cd1e5cdb814ef1548b3602f1a6feed6cb455942ee.png
for node in ["baseline", "kpp.lmd.002", "kpp.lmd.003", "kpp.lmd.004"]:
    plt.figure()
    tree[node].sel(time=slice("2008-10-25", "2008-11-15"))["eps"].cf.sel(
        Z=slice(-120, None)
    ).reset_coords(drop=True).cf.plot(
        cmap=mpl.cm.Spectral_r, size=3, aspect=5, norm=mpl.colors.LogNorm(1e-9, 1e-5)
    )
<Figure size 896x672 with 0 Axes>
_images/afc6636caabd6f8f251314667803b93d38d79a6d71c5db2b1eae121ea814eb0e.png
<Figure size 896x672 with 0 Axes>
_images/dab8621b127ca67dd6e59789fa32dc7a2503a41f8ec831816f553c78366f8144.png
<Figure size 896x672 with 0 Axes>
_images/0924d5f87bf7108447438fec6abd2e7d33feb7cfd0c996b4af7b92af32a14f4a.png
<Figure size 896x672 with 0 Axes>
_images/f42c5d1bd0bc2a1d39eafb97ca8a55a74a08c113156d791088e84e78effd7d70.png

Compare boundary layer depth#

mixing_layer_depth_criteria = {
    "boundary_layer_depth": {"name": "KPPhbl|KPP_OBLdepth|ePBL_h_ML"},
}

hbl = (
    tree.drop_nodes("TAO")
    .dc.subset_nodes("KPP_OBLdepth")
    .dc.concatenate_nodes()
    .reset_coords(drop=True)
).load()
import hvplot.pandas

(
    # hbl.groupby("time.hour").mean().hvplot.line(by="node", flip_yaxis=True)
    hbl.groupby("time.hour")
    .mean()
    .hvplot.line(by="node", flip_yaxis=True)
    .opts(show_legend=False)
    + hbl.to_dataframe().hvplot.hist(
        by="node",
        bins=np.arange(0, 90, 1),
        normed=1,
        alpha=0.3,
        ylim=(0, 0.05),
        muted_alpha=0,
    )
).opts(
    hv.opts.Curve(invert_yaxis=True),
    *mixpods.HV_TOOLS_OPTIONS,
    *mixpods.PRESENTATION_OPTS,
)
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/holoviews/plotting/bokeh/plot.py:967: UserWarning: found multiple competing values for 'toolbar.active_drag' property; using the latest value
  layout_plot = gridplot(
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/holoviews/plotting/bokeh/plot.py:967: UserWarning: found multiple competing values for 'toolbar.active_scroll' property; using the latest value
  layout_plot = gridplot(

ε vs Ri#

Notes:

  1. My MLD seems deeper even though I use the same ΔT=0.15 threshold. It could be that they’ve used Akima splines to interpolate in the vertical

  2. ~I’ve filtered to DCL, so accounting for MLD and EUC movement. I’m not sure they did that.~

Only 𝜒pods between 29 and 69 m are used in this analysis as deeper 𝜒pods are more strongly influenced by the variability of zEUC than by surface forcing.

TODO

  • EUC strength is proportional to horizontal visc for 1° models

  • Add \(ε_χ\) for MOM6

  • Do for K

  • composite by TIW strength

  • start with 10min χpod data, then average to hourly.

  • make composites off the equator: look at strong off-equatorial du/dx; du/dz

f, ax = plt.subplots(
    2, len(datasets) // 2, sharex=True, sharey=True, constrained_layout=True
)
for axx, (name, ds) in zip(ax.flat, tree.children.items()):
    da = np.log10(ds["eps_ri"])
    # da["Rig_T_bins"] = pd.Index(
    #    [pd.Interval(np.log10(i.left), np.log10(i.right))
    #     for i in tao_gridded.Rig_T_bins.data]
    # )
    (
        da.sel(stat="mean")
        .sel(enso_transition_phase=["La-Nina cool", "El-Nino warm"])
        .plot(hue="enso_transition_phase", marker=".", ax=axx, add_legend=False)
    )
    axx.set_title(name)

    ticks = [0.04, 0.1, 0.25, 0.63, 1.6]
    # axx.set_yticks([-7.25, -7, -6.5, -6])
    axx.set_ylim([-7.5, -5.5])
    axx.set_xticks(np.log10(ticks))
    axx.set_xticklabels([f"{a:.2f}" for a in ticks])
    # axx.tick_params(axis='x', rotation=30)
ax[0, 0].set_ylabel("ε")
dcpy.plots.linex(np.log10(0.25), ax=ax.flat)
# dcpy.plots.clean_axes(ax)
f.set_size_inches((8, 4))
_images/c5c9326274e9719c4e1e803d16b84c2b6ea2a6fbdec8989d9a8caf9ed426ec83.png

Shear spectra#

Notes#

  • Now obvious difference between clockwise and counter-clockwise so I’ve chosen to just do the spectrum of the magnitude

  • think about tides

  • what is the “DATM coupling interval”

  • Kelvin’s KPP parameters

  • what are the momentum and heat fluxes over some resolvable timescale. And how do we compare to obs.

  • think about vertical migration of EUC

  • think about regridding, calculate shear…

for name, node in tree.children.items():
    ds = node.ds.drop_vars("w")
    with xr.set_options(keep_attrs=False):
        node["w"] = ds.cf["sea_water_x_velocity"] + 1j * ds.cf["sea_water_y_velocity"]
for name, node in euc.children.items():
    ds = node.ds  # .drop_vars("w")
    with xr.set_options(keep_attrs=False):
        node["w"] = ds.cf["sea_water_x_velocity"] + 1j * ds.cf["sea_water_y_velocity"]
    node["zeuc"].attrs["axis"] = "Z"

Frequency Spectra#

There is sensitivity to coordinate location chosen.

  • Models are doing quite well at daily frequency at the 40-50m range.

f, ax = plt.subplots(1, 2, sharey=True)
for axis, t, depth in zip(ax, [tree, euc], [-40, 30]):
    for name, node in t.children.items():
        da = node.ds["w"].reset_coords(drop=True)
        dcpy.ts.PlotSpectrum(
            np.abs(
                da.cf.differentiate("Z")
                .cf.sel(Z=depth, method="nearest")
                .sel(time="2010")
            ),
            multitaper=False,
            nsmooth=12,
            label=name,
            lw=0.75,
            ax=axis,
        )
ax[0].set_ylim([1e-9, None])
ax[0].legend()
ax[0].set_title("Shear spectra, depth=-40m")
ax[1].set_title("Shear spectra, zeuc=+30m")
f.set_size_inches((10, 4))

_images/213d6ffbe391c5f004a63d9a2dd73211240a37c667f4fcfd0293ac0613157530.png

OLD#

Velocity#
DEPTHS = [-50, -60, -75]

f, ax = plt.subplots(
    len(DEPTHS), 1, sharex="col", sharey=True, squeeze=False, constrained_layout=True
)
for name in tree.children.keys():
    if name == "tropicheat":
        continue
    ds = tree[name].ds.reset_coords()
    u = ds.cf["sea_water_x_velocity"]
    v = ds.cf["sea_water_y_velocity"]
    S = (u + 1j * v).cf.sel(Z=DEPTHS, method="nearest").load()

    for axx, z in zip(ax, DEPTHS):
        dcpy.ts.PlotSpectrum(
            np.abs(S).cf.sel(Z=z, method="nearest"),
            multitaper=False,
            nsmooth=12,
            label=name,
            lw=0.75,
            ax=axx[0],
        )
        axx[-1].text(x=0.9, y=0.9, s=f"{z} m", transform=axx[-1].transAxes)
ax.ravel()[0].set_ylim([1e-9, None])
ax.ravel()[-1].legend()
dcpy.plots.clean_axes(ax)
f.set_size_inches((4, 6))
_images/2a9cc8145adfd44fee0e508cb0f5ea188843038db10e594c100c3a6b2d76b4a1.png
Shear#
f, ax = plt.subplots(
    len(DEPTHS), 1, sharex="col", sharey=True, squeeze=False, constrained_layout=True
)
for name in tree.children.keys():
    ds = tree[name].ds.reset_coords()
    u = ds.cf["sea_water_x_velocity"]
    v = ds.cf["sea_water_y_velocity"]
    S = (
        (u.cf.differentiate("Z") + 1j * v.cf.differentiate("Z"))
        .cf.sel(Z=DEPTHS, method="nearest")
        .load()
    )

    for axx, z in zip(ax, DEPTHS):
        dcpy.ts.PlotSpectrum(
            np.abs(S).cf.sel(Z=z, method="nearest"),
            multitaper=False,
            nsmooth=12,
            label=name,
            lw=0.75,
            ax=axx[0],
        )
        axx[-1].text(x=0.9, y=0.9, s=f"{z} m", transform=axx[-1].transAxes)
ax.ravel()[0].set_ylim([1e-9, None])
ax.ravel()[-1].legend()
dcpy.plots.clean_axes(ax)
f.set_size_inches((4, 6))
_images/a2bcd74b33b920df1c47a215f0e4fc97d6790adbfddaab01522a7c28e91d64aa.png

Vertical wavenumber spectra#

  • regrid shear

  • better regridding method

  • add EQUIX

spec_zeuc = mixpods.hvplot_spectra(
    euc.dc.drop_leaves("mean").map_over_subtree(
        lambda node: np.abs(
            node["w"]
            # .reset_coords(drop=True)
            .cf.interpolate_na("Z")
            .cf.differentiate("Z")
            .cf.sel(Z=slice(0, 70))
        )
    ),
    "w",
).opts(title="zeuc=slice(0, 50)")
spec_z = mixpods.hvplot_spectra(
    tree.dc.drop_leaves("mean")
    .dc.drop_leaves("euc")
    .map_over_subtree(
        lambda node: (
            node["w"]
            .cf.differentiate("Z")
            .cf.sel(Z=slice(-200, 0))
            .cf.interp(Z=np.arange(-105, -52, 5))
        )
    ),
    "w",
).opts(title="Z=slice(-100, -50)")
(spec_z).opts(legend_position="right")

Two-sided shear frequency spectra#

f, ax = plt.subplots(1, 2, sharey=True, squeeze=False)
for name in tree.children.keys():
    ds = tree[name].ds.reset_coords()
    u = ds.cf["sea_water_x_velocity"].load()
    v = ds.cf["sea_water_y_velocity"].load()
    S = u.cf.differentiate("Z") + 1j * v.cf.differentiate("Z")
    dcpy.ts.PlotSpectrum(
        S.cf.sel(Z=-50, method="nearest").sel(time="2010"),
        multitaper=False,
        twoside=True,
        nsmooth=12,
        label=name,
        lw=0.75,
        ax=ax.ravel(),
    )
ax.ravel()[0].set_ylim([1e-10, None])
ax.ravel()[-1].legend()
f.set_size_inches((8, 4))
_images/fe8b29323164dac6185d3f4df5d5ab36ddabb6d0229fe307f54ccb71b72d9285.png

Seasonal mean heat flux#

remapped.Jq.load()
<xarray.DataArray 'Jq' (time: 174000, zeuc: 100)>
array([[        nan,         nan, -0.07841657, ...,         nan,
                nan,         nan],
       [        nan,         nan, -0.07973828, ...,         nan,
                nan,         nan],
       [        nan,         nan, -0.08094554, ...,         nan,
                nan,         nan],
       ...,
       [        nan, -0.07447129,         nan, ...,         nan,
                nan,         nan],
       [        nan, -0.07471326,         nan, ...,         nan,
                nan,         nan],
       [        nan, -0.07509062,         nan, ...,         nan,
                nan,         nan]])
Coordinates:
    latitude   float64 0.025
    longitude  float64 -140.0
  * time       (time) datetime64[ns] 1998-12-31T18:00:00 ... 2018-11-06T17:00:00
  * zeuc       (zeuc) float64 -247.5 -242.5 -237.5 -232.5 ... 237.5 242.5 247.5
(
    remapped.Jq.groupby("time.season")
    .mean()
    .reindex(season=["DJF", "MAM", "JJA", "SON"])
    .cf.plot.line(col="season")
)
<xarray.plot.facetgrid.FacetGrid>
_images/27e778991cb64aa8fa110aaf630c9fafb422676e51e9d35860ea1cc5ff1a5fe7.png

Below EUC mixing#

as a test for the background mixing params.

Frank: other places to look would be:

  • overflows

  • acc , gs

  • DWBC

  • ideal age metric?

  • vertically integrated heat budget term, global map

%autoreload

newtree = mixpods.bin_to_euc_centered_coordinate(tree)

for nodename, _ in tree.children.items():
    tree[f"{nodename}/euc"] = newtree[f"{nodename}/euc"]

euc_mean = mixpods.average_euc(tree)
euc_mean.load()
import os

import datatree

micro_zeuc = datatree.open_datatree(
    os.path.expanduser("~/datasets/microstructure/equix-tiwe-zeuc.average.nc")
).load()
for nodename, node in micro_zeuc.children.items():
    node["u"].attrs["standard_name"] = "sea_water_x_velocity"
    node["v"].attrs["standard_name"] = "sea_water_y_velocity"
    node["KT"].attrs["standard_name"] = "ocean_vertical_heat_diffusivity"
    node["ν"].attrs["standard_name"] = "ocean_vertical_momentum_diffusivity"

    del node["Rig_T"].attrs["standard_name"]
    del node["N2T"].attrs["standard_name"]
    # euc_mean[nodename] = node

for nodename, node in micro_zeuc.children.items():
    euc_mean[nodename] = node
euc_mean.to_netcdf("mom6-euc-mean.nc")
h = {
    varname: mixpods.map_hvplot(
        lambda node, name, muted: (
            node.ds.cf[varname]
            .reset_coords(drop=True)
            .hvplot.line(
                ylabel=varname,
                label=name,
                logx=varname != "sea_water_x_velocity",
                invert=True,
            )
        ),
        euc_mean,
    )
    for varname in [
        "sea_water_x_velocity",
        "sea_water_potential_temperature",
        "chi",
        "eps",
        "ocean_vertical_heat_diffusivity",
        "ocean_vertical_momentum_diffusivity",
    ]
}
h["chi"].opts(ylim=(1e-9, 1e-4))
h["eps"].opts(ylim=(1e-9, 1e-4))
h["ocean_vertical_heat_diffusivity"].opts(ylim=(5e-7, 3))
h["ocean_vertical_momentum_diffusivity"].opts(ylim=(1e-6, 1e-1))

h2 = {
    varname: mixpods.map_hvplot(
        lambda node, name, muted: node.ds[varname]
        .reset_coords(drop=True)
        .hvplot.line(ylabel=varname, label=name, logx=False, invert=True),
        euc_mean,
    )
    for varname in ["Rig_T"]
}

hv.Layout(list(h.values()) + [h2["Rig_T"].opts(ylim=(0, 3))]).opts(
    hv.opts.Curve(frame_width=150, frame_height=300, xlim=(-150, 150)),
    hv.opts.Layout(shared_axes=True),
    hv.opts.Overlay(show_legend=True, show_grid=True, legend_position="right"),
    *mixpods.HV_TOOLS_OPTIONS,
    *mixpods.PRESENTATION_OPTS,
).cols(4)
Ri = (
    tree.dc.extract_leaf("euc")
    .dc.subset_nodes(["Rig_T"])
    .sel(time=slice("1992", "2017"))
    .load()
)
mixpods.plot_distributions(
    Ri.dc.reorder_nodes(["TAO", ...]),
    "Rig_T",
    bins=np.arange(0, 5, 0.25),
    log=False,
    subset=False,
).opts(hv.opts.Overlay(legend_position="right"))

# tree["new_baseline.hb/euc"]["Rig_T"].sel(zeuc=slice(-120, -30)).hvplot.hist(bins=[0, 0.2, 0.5, 0.8, 1, 2, 3, 4, 5])
# tree["baseline/euc"]["Rig_T"].sel(zeuc=slice(-120, -30)).hvplot.hist(bins=[0, 0.2, 0.5, 0.8, 1, 2, 3, 4, 5])
monthly = catalog.search(frequency="monthly", variables="uo").to_datatree()
--> The keys in the returned dictionary of datasets are constructed as follows:
	'shortname/stream'
100.00% [8/8 00:00<00:00]
uo = (
    monthly.drop_nodes(
        set(monthly)
        - set(
            tree.dc.rename_nodes(
                {"baseline": "baseline.001", "kpp.lmd.004": "baseline.kpp.lmd.004"}
            )
        )
    )
    .dc.extract_leaf("h")
    .sel(xq=-140, method="nearest")
    .sel(yh=slice(-10, 10))
    .sel(time=slice("0046-01-01", "0058-01-01"))
    # .mean("time")
).load()
(
    hv.Layout(
        [
            node["uo"]
            .where(node["uo"] < 10)
            .reset_coords(drop=True)
            .hvplot.quadmesh(x="yh", clim=(-0.01, 0.01), cmap="coolwarm", title=name)
            for name, node in uo.mean("time").children.items()
        ]
    )
    .cols(2)
    .opts(
        hv.opts.QuadMesh(invert_yaxis=True, ylim=(2000, 0)),
        *mixpods.PRESENTATION_OPTS,
        *mixpods.HV_TOOLS_OPTIONS,
    )
)

Test out available variables#

  1. KPP_Kheat is non-zero only in KPP_OBL

(mom6140.Tflx_dia_diff * 1025 * 4000).cf.plot(robust=True)
<matplotlib.collections.QuadMesh>
_images/380ae0927607e64c1f1e455f3c2bcd9a0a70306e4e3510a888f8b10c5fd4535d.png
mom6140.Kv_u.cf.plot(norm=mpl.colors.LogNorm(1e-6, 1e-2))
<matplotlib.collections.QuadMesh>
_images/3b26c61324b5edb9b740b26809160c8c09dff78025129659a54429702b7bc67b.png
mom6140.Kd_heat.cf.plot(norm=mpl.colors.LogNorm(1e-6, 1e-2))
mom6140.KPP_OBLdepth.plot(color="r")

plt.figure()
mom6140.KPP_kheat.cf.plot(norm=mpl.colors.LogNorm(1e-6, 1e-2))
mom6140.KPP_OBLdepth.plot(color="r")

plt.figure()
(mom6140.KPP_kheat - mom6140.Kd_heat).cf.plot(
    robust=True, cmap=mpl.cm.mpl.cm.Reds_r, vmax=0
)
<matplotlib.collections.QuadMesh>
_images/3e83df3f9fea04390152de895b1d57178bcf8b50cf8f974de09d4ccaf8badbd6.png _images/f0f31fc31fc6c5f88350878aaf6306750183ad59680e27b9c7a37e7efb9042ce.png _images/2daecb46b41b3924e5589d365513732343962be989936b3c21f36dddb2677df9.png

Experiment with manual combining#

from intake.source.utils import reverse_format

years = []
tiles = []
for file in files[:10]:
    p = pathlib.Path(file)
    params = reverse_format("__{year}.nc.{tile}", p.stem + p.suffix)
    years.append(params["year"])
    tiles.append(params["tile"])
years, tiles

import toolz as tlz

bytile = {}
for tile, v in tlz.groupby(tileid, files).items():
    bytile[tile] = xr.concat(read_raw_files(v, parallel=True), dim="time")


print("\n".join([hash(ds.yh.data.tolist()) for ds in list(bytile.values())]))

from functools import partial


def hash_coords(ds, axis):
    dims = ds.cf.axes[axis]
    data = np.concatenate([ds[dim].data for dim in dims])
    return hash(tuple(data))


grouped = bytile

for axis, concat_axis in [("X", "Y"), ("Y", "X")]:
    grouped = tlz.groupby(partial(hash_coords, axis=axis), grouped.values())
    grouped = {
        k: cfconcat(v, axis=concat_axis, join="exact") for k, v in grouped.items()
    }
    break
grouped

cfconcat(list(grouped.values()), "X")

combined = xr.combine_by_coords(list(bytile.values()), combine_attrs="override")


def raise_if_bad_index(combined):
    bad = []
    for idx in combined.indexes:
        index = combined.indexes[idx]
        if not index.is_monotonic or index.has_duplicates:
            bad.append(idx)
    if bad:
        raise ValueError(f"Indexes {idx} are either not monotonic or have duplicates")


def tileid(path):
    p = pathlib.Path(path)
    # print(p.suffix)
    # params = reverse_format("__{year}.nc.{tile}", p.stem + p.suffix)
    return int(p.suffix[1:])  # params["tile"]
    # years.append(params["year"])
    # tiles.append(params["tile"])


sorted_files = sorted(files, key=tileid)
for tile, files in groupby(sorted_files, tileid):
    print(tile, len(list(files)))

Checking background visc, diff#

staticfile = "/glade/campaign/cgd/oce/projects/pump/cesm//gmom.e23.GJRAv3.TL319_t061_zstar_N65.new_baseline.hb/run/*static*.nc"
static = xr.open_mfdataset(staticfile).isel(time=1).squeeze()
static.Kd_bkgnd.cf.sel(Z=200, method="nearest").hvplot.quadmesh(
    cmap="spectral_r", cnorm="log", clim=(5e-7, 2e-4)
)
ds = static.cf.sel(xh=5, yh=-25, method="nearest")

(
    ds.Kd_bkgnd.load().hvplot.line(label="Kd_bkgnd")
    * ds.Kv_bkgnd.load().hvplot.line(label="Kv_bkgnd")
).opts(hv.opts.Curve(logx=True, ylim=(1e-7, 1e-4), invert_axes=True)).opts(
    legend_position="right", show_legend=True, width=400, height=400
)
ds = static.cf.sel(xh=-140, yh=0, method="nearest")

(
    ds.Kd_bkgnd.load().hvplot.line(label="Kd_bkgnd")
    * ds.Kv_bkgnd.load().hvplot.line(label="Kv_bkgnd")
).opts(hv.opts.Curve(logx=True, ylim=(1e-7, 1e-4), invert_axes=True)).opts(
    legend_position="right", show_legend=True, width=400, height=400
)

opottempdiff vs Tflx_dia_diff#

ds = mixpods.load_mom6_sections(
    "gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.kpp.lmd.004.mixpods"
)
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xgcm/grid.py:1515: UserWarning: Metric at ('time', 'zi', 'yh', 'xh') being interpolated from metrics at dimensions ('time', 'zl', 'yh', 'xh'). Boundary value set to 'extend'.
  warnings.warn(
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xgcm/grid.py:1515: UserWarning: Metric at ('time', 'zi', 'yh', 'xh') being interpolated from metrics at dimensions ('time', 'zl', 'yh', 'xh'). Boundary value set to 'extend'.
  warnings.warn(
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xgcm/grid.py:1515: UserWarning: Metric at ('time', 'zi', 'yh', 'xh') being interpolated from metrics at dimensions ('time', 'zl', 'yh', 'xh'). Boundary value set to 'extend'.
  warnings.warn(
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xgcm/grid.py:1515: UserWarning: Metric at ('time', 'zi', 'yh', 'xh') being interpolated from metrics at dimensions ('time', 'zl', 'yh', 'xh'). Boundary value set to 'extend'.
  warnings.warn(
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xgcm/grid.py:1515: UserWarning: Metric at ('time', 'zi', 'yq', 'xh') being interpolated from metrics at dimensions ('time', 'zl', 'yh', 'xh'). Boundary value set to 'extend'.
  warnings.warn(
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xgcm/grid.py:1515: UserWarning: Metric at ('time', 'zi', 'yh', 'xh') being interpolated from metrics at dimensions ('time', 'zl', 'yh', 'xh'). Boundary value set to 'extend'.
  warnings.warn(
/glade/u/home/dcherian/miniconda3/envs/pump/lib/python3.10/site-packages/xgcm/grid.py:1515: UserWarning: Metric at ('time', 'zi', 'yq', 'xh') being interpolated from metrics at dimensions ('time', 'zl', 'yh', 'xh'). Boundary value set to 'extend'.
  warnings.warn(
grid = xgcm.Grid(ds, coords={("Z",): {"left": "zi", "center": "zl"}})
grid
<xgcm.Grid>
('Z',) Axis (periodic, boundary=None):
  * left     zi --> center
  * center   zl --> left
dz = ds.zi.diff("zi").rename({"zi": "zl"}).variable
dz
<xarray.Variable (zl: 36)>
array([42.79, 38.5 , 34.87, 31.76, 29.1 , 26.79, 24.77, 23.  , 21.42,
       20.02, 18.76, 17.61, 16.56, 15.59, 14.69, 13.85, 13.06, 12.29,
       11.54, 10.81, 10.08,  9.37,  8.66,  7.97,  7.28,  6.61,  5.95,
        5.29,  4.65,  4.01,  3.38,  2.77,  2.5 ,  2.5 ,  2.5 ,  2.5 ])
Attributes:
    cartesian_axis:  Z
    long_name:       Interface pseudo-depth, -z*
    positive:        up
    units:           meter
ds.Tflx_dia_diff.load()
ds.opottempdiff.load()
<xarray.DataArray 'opottempdiff' (time: 218424, zl: 37)>
0.004551 0.006139 0.01125 0.01764 0.01268 ... 39.39 62.65 12.85 10.76 -125.7
Coordinates: (12/13)
  * time             (time) datetime64[ns] 2003-01-07T00:30:00 ... 2028-01-06...
    xh               float64 -140.0
    yh               float64 0.0625
    yq               float64 -0.0625
  * zl               (zl) float64 -547.8 -502.4 -461.8 ... -6.25 -3.75 -1.25
    eucmax           (time) float64 -114.5 -114.5 -114.5 ... -114.5 -114.5
    ...               ...
    oni              (time) float32 nan nan nan nan nan ... nan nan nan nan nan
    en_mask          (time) bool False False False False ... False False False
    ln_mask          (time) bool False False False False ... False False False
    warm_mask        (time) bool True True True True ... True True True True
    cool_mask        (time) bool False False False False ... False False False
    enso_transition  (time) <U12 '____________' ... '____________'
Attributes:
    cell_measures:  volume: volcello area: areacello
    cell_methods:   area:mean zl:sum yh:mean xh:mean time: mean
    long_name:      Tendency of sea water potential temperature expressed as ...
    standard_name:  tendency_of_sea_water_potential_temperature_expressed_as_...
    time_avg_info:  average_T1,average_T2,average_DT
    units:          W m-2
integ = (ds.opottempdiff.isel(zl=slice(1, None)) * dz).cumsum("zl")

(1028 * 4200 * ds["Tflx_dia_diff"]).isel(time=0).differentiate("zi").plot()
ds["opottempdiff"].isel(time=0).plot()
# integ.isel(time=0).plot()
[<matplotlib.lines.Line2D object at 0x2ac9c184fbb0>]
_images/73b000a016a621f96408dc93538661e016d07bcd6a96cc2fa316694bbdcfdc93.png

Debugging total visc, diff:#

Bug report: NCAR/MOM6#238

Kv_u should it match the sum of in diffusivities

(
    ds.Kd_heat.load().mean("time").hvplot.line(label="Kd_heat")
    * ds.Kv_u.load().mean("time").hvplot.line(label="Kv_u")
    * ds.Kv_v.load().mean("time").hvplot.line(label="Kv_v")
).opts(hv.opts.Curve(logx=True, ylim=(1e-7, None), invert_axes=True)).opts(
    legend_position="right", show_legend=True, width=400, height=400
)

Check Kd, Kv contributions#

What is happening with viscosity at 80-m? Lateral averaging? vertical filter?

  • somehow laundered through the tri-diagonal solver

need internal tidal dissipation; CVMix_Tidal; This is near-bottom

Need Kv_itides

moor = xr.open_dataset(
    "/glade/u/home/dcherian/scratch/"
    "gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.kpp.lmd.004.mixpods/run/"
    # "kd0kv0/"
    "gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.kpp.lmd.004.mixpods.mom6.TAO_140W__0046.nc.0460",
    use_cftime=True,
).pipe(preprocess_mom6_sections)
mixpods.decompose_diff_visc(moor)
returning average_T1
returning average_T2
returning average_DT
returning time_bnds
returning average_T1
returning average_T2
returning average_DT
returning time_bnds
moor = xr.open_dataset(
    "/glade/u/home/dcherian/scratch/"
    "gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.kpp.lmd.004.mixpods/run/"
    "kd0kv0/"
    "gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.kpp.lmd.004.mixpods.mom6.TAO_140W__0046.nc.0460",
    use_cftime=True,
)
mixpods.decompose_diff_visc(moor)

smoothing of shear only?

moor = xr.open_dataset(
    "/glade/u/home/dcherian/scratch/"
    "gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.kpp.lmd.004.mixpods/run/"
    "kd0kv0/"
    "gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.kpp.lmd.004.mixpods.mom6.TAO_140W__0046.nc.0493",
    use_cftime=True,
)
mixpods.decompose_diff_visc(moor)

KD=0, KV=0 (kd0kv0/)#

moor = xr.open_dataset(
    "/glade/u/home/dcherian/scratch/"
    "gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.kpp.lmd.004.mixpods/run/"
    "kd0kv0/"
    "gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.kpp.lmd.004.mixpods.mom6.TAO_140W__0046.nc.0428",
    use_cftime=True,
)
mixpods.decompose_diff_visc(moor)

KD=2e-5, KV=1e-4 (kddefkvdef)#

moor = xr.open_dataset(
    "/glade/u/home/dcherian/scratch/"
    "gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.kpp.lmd.004.mixpods/run/"
    "kddefkvdef/"
    "gmom.e23.GJRAv3.TL319_t061_zstar_N65.baseline.kpp.lmd.004.mixpods.mom6.TAO_140W__0046.nc.0428",
    use_cftime=True,
)
mixpods.decompose_diff_visc(moor)

Extra code#