Tracer budgets MOM6 NCAR configuration

The following variables are required at double precision output for closing the heat budget:

‘opottemptend’, ‘T_advection_xy’, ‘Th_tendency_vert_remap’, ‘boundary_forcing_heat_tendency’, ‘opottempdiff’, ‘opottemppmdiff’, ‘frazil_heat_tendency’, ‘KPP_NLT_temp_budget’, ‘T_lbdxy_cont_tendency’

The following variables are required at double precision to close the salt budget:

‘S_advection_xy’, ‘Sh_tendency_vert_remap’, ‘boundary_forcing_salt_tendency’, osaltpmdiff’, ‘osaltdiff’, ‘KPP_NLT_saln_budget’*1e-3 (because there is a bug in the conversion factor), ‘S_lbdxy_cont_tendency’

A short run with all this output at double precision is available here: /glade/scratch/deppenme/archive/gmom.e23.GJRAv3.TL319_t061_zstar_N65.testbudget2.001/ocn/hist/

Description of what they are can be found in the labels of the plot below.


TO DO:

  • test different vertical coordinate

  • test different temporal resolutions

Load modules, start cluster

[1]:
# Load required modules
import warnings
warnings.filterwarnings("ignore") # I don't want any warnings (:

# the usual suspects
import numpy as np
from datetime import date
from matplotlib import pyplot as plt
import cartopy.crs as ccrs
import xarray as xr
import glob
import nc_time_axis # it says I need this to plot.. not sure

# dask helpers
from mom6_tools.jobqueue import get_cluster

# get mom6-tools
import mom6_tools
[2]:
parallel, cluster, client = get_cluster(
    40,
    cluster_class='PBSCluster',
    cores=4,
    processes=1,
    resource_spec='select=1:ncpus=1:mem=10GB',
)
client
Starting a dask cluster: PBSCluster

Requesting 40 workers...

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[2], line 1
----> 1 parallel, cluster, client = get_cluster(
      2     40,
      3     cluster_class='PBSCluster',
      4     cores=4,
      5     processes=1,
      6     resource_spec='select=1:ncpus=1:mem=10GB',
      7 )
      8 client

File ~/checkouts/readthedocs.org/user_builds/mom6-tools/envs/latest/lib/python3.10/site-packages/mom6_tools/jobqueue.py:300, in get_cluster(nw, cluster_class, args, config, **kwargs)
    297 print('Requesting {} workers... \n'.format(nw))
    298 if is_jobqueue_cluster:
    299   # dask.config.set({'distributed.dashboard.link': '/proxy/{port}/status'})
--> 300   cluster = cluster_class(**kwargs)
    301   cluster.scale(nw)
    302 else:

File ~/checkouts/readthedocs.org/user_builds/mom6-tools/envs/latest/lib/python3.10/site-packages/dask_jobqueue/core.py:661, in JobQueueCluster.__init__(self, n_workers, job_cls, loop, security, shared_temp_directory, silence_logs, name, asynchronous, dashboard_address, host, scheduler_options, scheduler_cls, interface, protocol, config_name, **job_kwargs)
    656 if "processes" in self._job_kwargs and self._job_kwargs["processes"] > 1:
    657     worker["group"] = [
    658         "-" + str(i) for i in range(self._job_kwargs["processes"])
    659     ]
--> 661 self._dummy_job  # trigger property to ensure that the job is valid
    663 super().__init__(
    664     scheduler=scheduler,
    665     worker=worker,
   (...)
    670     name=name,
    671 )
    673 if n_workers:

File ~/checkouts/readthedocs.org/user_builds/mom6-tools/envs/latest/lib/python3.10/site-packages/dask_jobqueue/core.py:690, in JobQueueCluster._dummy_job(self)
    688     address = "tcp://<insert-scheduler-address-here>:8786"
    689 try:
--> 690     return self.job_cls(
    691         address or "tcp://<insert-scheduler-address-here>:8786",
    692         # The 'name' parameter is replaced inside Job class by the
    693         # actual Dask worker name. Using 'dummy-name here' to make it
    694         # more clear that cluster.job_script() is similar to but not
    695         # exactly the same script as the script submitted for each Dask
    696         # worker
    697         name="dummy-name",
    698         **self._job_kwargs
    699     )
    700 except TypeError as exc:
    701     # Very likely this error happened in the self.job_cls constructor
    702     # because an unexpected parameter was used in the JobQueueCluster
    703     # constructor. The next few lines builds a more user-friendly error message.
    704     match = re.search("(unexpected keyword argument.+)", str(exc))

File ~/checkouts/readthedocs.org/user_builds/mom6-tools/envs/latest/lib/python3.10/site-packages/dask_jobqueue/pbs.py:55, in PBSJob.__init__(self, scheduler, name, queue, project, account, resource_spec, walltime, config_name, **base_class_kwargs)
     43 def __init__(
     44     self,
     45     scheduler=None,
   (...)
     53     **base_class_kwargs
     54 ):
---> 55     super().__init__(
     56         scheduler=scheduler, name=name, config_name=config_name, **base_class_kwargs
     57     )
     59     if queue is None:
     60         queue = dask.config.get("jobqueue.%s.queue" % self.config_name)

File ~/checkouts/readthedocs.org/user_builds/mom6-tools/envs/latest/lib/python3.10/site-packages/dask_jobqueue/core.py:202, in Job.__init__(self, scheduler, name, cores, memory, processes, nanny, protocol, security, interface, death_timeout, local_directory, extra, worker_command, worker_extra_args, job_extra, job_extra_directives, env_extra, job_script_prologue, header_skip, job_directives_skip, log_directory, shebang, python, job_name, config_name)
    200     job_class_name = self.__class__.__name__
    201     cluster_class_name = job_class_name.replace("Job", "Cluster")
--> 202     raise ValueError(
    203         "You must specify how much cores and memory per job you want to use, for example:\n"
    204         "cluster = {}(cores={}, memory={!r})".format(
    205             cluster_class_name, cores or 8, memory or "24GB"
    206         )
    207     )
    209 if python is None:
    210     python = dask.config.get("jobqueue.%s.python" % self.config_name)

ValueError: You must specify how much cores and memory per job you want to use, for example:
cluster = PBSCluster(cores=4, memory='24GB')
[3]:
# get the data
dirname = "/glade/scratch/deppenme/archive/gmom.e23.GJRAv3.TL319_t061_zstar_N65.testbudget2.001/ocn/hist"
static = xr.open_dataset(*glob.glob(f"{dirname}/*static*.nc"))
ds_hb = xr.open_mfdataset(
    sorted(glob.glob(f"{dirname}/gmom.e23.GJRAv3.TL319_t061_zstar_N65.testbudget2.001.mom6.hm_0001_01_??.nc")),
    coords="minimal",
    data_vars="minimal",
    compat="override",
    use_cftime=True,
    parallel=True,
)
ds_hb.coords.update(static.drop("time"))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[3], line 3
      1 # get the data
      2 dirname = "/glade/scratch/deppenme/archive/gmom.e23.GJRAv3.TL319_t061_zstar_N65.testbudget2.001/ocn/hist"
----> 3 static = xr.open_dataset(*glob.glob(f"{dirname}/*static*.nc"))
      4 ds_hb = xr.open_mfdataset(
      5     sorted(glob.glob(f"{dirname}/gmom.e23.GJRAv3.TL319_t061_zstar_N65.testbudget2.001.mom6.hm_0001_01_??.nc")),
      6     coords="minimal",
   (...)
     10     parallel=True,
     11 )
     12 ds_hb.coords.update(static.drop("time"))

TypeError: open_dataset() missing 1 required positional argument: 'filename_or_obj'

Heat budget

[4]:
# try for one location first
ds_0N140W = ds_hb.sel(xh=-140, yh=0, method='nearest')
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[4], line 2
      1 # try for one location first 
----> 2 ds_0N140W = ds_hb.sel(xh=-140, yh=0, method='nearest')

NameError: name 'ds_hb' is not defined
[5]:
fig, ax = plt.subplots(1, 1, figsize=(5,6))

ax.plot(ds_0N140W.opottemptend.isel(time=5), ds_0N140W.zl, label='grid cell time tend')
ax.plot(ds_0N140W.T_advection_xy.isel(time=5), ds_0N140W.zl, label='horizontal convergence advective heat flux')
ax.plot(ds_0N140W.Th_tendency_vert_remap.isel(time=5), ds_0N140W.zl, label='across layer adv remapping vertical')
ax.plot(ds_0N140W.boundary_forcing_heat_tendency.isel(time=5), ds_0N140W.zl, label='boundary forcing')
ax.plot(ds_0N140W.opottempdiff.isel(time=5), ds_0N140W.zl, label='parameterized dianeutral diffusion')
ax.plot(ds_0N140W.opottemppmdiff.isel(time=5), ds_0N140W.zl, label='parameterized mesoscalte neutral diffusion')
ax.plot(ds_0N140W.frazil_heat_tendency.isel(time=5), ds_0N140W.zl, label='frazil ice formation')
ax.plot(ds_0N140W.KPP_NLT_temp_budget.isel(time=5), ds_0N140W.zl, label='Non-local KPP')
ax.plot(ds_0N140W.T_lbdxy_cont_tendency.isel(time=5), ds_0N140W.zl, label='Lateral diffusion')

ax.set_ylim(500,0)
ax.legend(loc='lower left')
ax.set_title(r'0$^{\circ}$N, 140$^{\circ}$W')
plt.savefig('hb_0N140W_mom_zstar.png', bbox_inches='tight')
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[5], line 3
      1 fig, ax = plt.subplots(1, 1, figsize=(5,6))
----> 3 ax.plot(ds_0N140W.opottemptend.isel(time=5), ds_0N140W.zl, label='grid cell time tend')
      4 ax.plot(ds_0N140W.T_advection_xy.isel(time=5), ds_0N140W.zl, label='horizontal convergence advective heat flux')
      5 ax.plot(ds_0N140W.Th_tendency_vert_remap.isel(time=5), ds_0N140W.zl, label='across layer adv remapping vertical')

NameError: name 'ds_0N140W' is not defined
../_images/examples_close_tracer_budgets_7_1.png
[6]:
fig, ax = plt.subplots(1, 1, figsize=(5,6))

ax.plot(ds_0N140W.opottemptend.isel(time=5), ds_0N140W.zl, label='grid cell time tend')
ax.plot((ds_0N140W.T_advection_xy
        +
        ds_0N140W.Th_tendency_vert_remap
        +
        ds_0N140W.boundary_forcing_heat_tendency
        +
        ds_0N140W.opottempdiff
        +
        ds_0N140W.opottemppmdiff
        +
        ds_0N140W.frazil_heat_tendency
        +
        ds_0N140W.KPP_NLT_temp_budget
        +
        ds_0N140W.T_lbdxy_cont_tendency).isel(time=5), ds_0N140W.zl, label='sum others')

ax.set_ylim(500,0)
ax.legend(loc='lower left')

plt.savefig('hb_0N140W_mom_zstar_lhs_rhs_doubleprec.png', bbox_inches='tight')
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[6], line 3
      1 fig, ax = plt.subplots(1, 1, figsize=(5,6))
----> 3 ax.plot(ds_0N140W.opottemptend.isel(time=5), ds_0N140W.zl, label='grid cell time tend')
      4 ax.plot((ds_0N140W.T_advection_xy
      5         +
      6         ds_0N140W.Th_tendency_vert_remap
   (...)
     17         +
     18         ds_0N140W.T_lbdxy_cont_tendency).isel(time=5), ds_0N140W.zl, label='sum others')
     20 ax.set_ylim(500,0)

NameError: name 'ds_0N140W' is not defined
../_images/examples_close_tracer_budgets_8_1.png
[7]:
%%time
xr.testing.assert_allclose(ds_hb.opottemptend,
                           (ds_hb.T_advection_xy+ds_hb.Th_tendency_vert_remap+
                            ds_hb.boundary_forcing_heat_tendency+ds_hb.opottempdiff+
                            ds_hb.opottemppmdiff+ds_hb.frazil_heat_tendency+
                            ds_hb.KPP_NLT_temp_budget+ds_hb.T_lbdxy_cont_tendency), rtol=1e-10)

# This goes through so the heat budget is closed
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
File <timed eval>:1

NameError: name 'ds_hb' is not defined

Salt budget

[8]:
# figure with all components
fig, ax = plt.subplots(1, 1, figsize=(5,6))

ax.plot(ds_0N140W.osalttend.isel(time=3), ds_0N140W.zl, label='Tendency of Sea Water Salinity Expressed as Salt Content')
ax.plot(ds_0N140W.S_advection_xy.isel(time=3), ds_0N140W.zl, label='Horizontal convergence of residual mean advective fluxes of salt')
ax.plot(ds_0N140W.Sh_tendency_vert_remap.isel(time=3), ds_0N140W.zl, label='Vertical remapping tracer content tendency for Salt')
ax.plot(ds_0N140W.boundary_forcing_salt_tendency.isel(time=3), ds_0N140W.zl, label='Tendency due to parameterized mesoscale neutral diffusion')
ax.plot(ds_0N140W.osaltpmdiff.isel(time=3), ds_0N140W.zl, label='Tendency due to parameterized dianeutral mixing')
ax.plot(ds_0N140W.osaltdiff.isel(time=3), ds_0N140W.zl, label='across layer diffusion')
ax.plot(ds_0N140W.KPP_NLT_saln_budget.isel(time=3)*1e-3, ds_0N140W.zl, label='KPP non local salt')
ax.plot(ds_0N140W.S_lbdxy_cont_tendency.isel(time=3)*1e-3, ds_0N140W.zl, label='Redi')


ax.set_ylim(500,0)
ax.legend(loc='lower left')
ax.set_title(r'0$^{\circ}$N, 140$^{\circ}$W')
# plt.savefig('sb_0N140W_mom_zstar.png', bbox_inches='tight')
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[8], line 4
      1 # figure with all components
      2 fig, ax = plt.subplots(1, 1, figsize=(5,6))
----> 4 ax.plot(ds_0N140W.osalttend.isel(time=3), ds_0N140W.zl, label='Tendency of Sea Water Salinity Expressed as Salt Content')
      5 ax.plot(ds_0N140W.S_advection_xy.isel(time=3), ds_0N140W.zl, label='Horizontal convergence of residual mean advective fluxes of salt')
      6 ax.plot(ds_0N140W.Sh_tendency_vert_remap.isel(time=3), ds_0N140W.zl, label='Vertical remapping tracer content tendency for Salt')

NameError: name 'ds_0N140W' is not defined
../_images/examples_close_tracer_budgets_11_1.png
[9]:
# figure with total tendency and sum of components
fig, ax = plt.subplots(1, 1, figsize=(5,6))

ax.plot(ds_0N140W.osalttend.isel(time=3), ds_0N140W.zl, label='Tendency of Sea Water Salinity Expressed as Salt Content')


ax.plot((ds_0N140W.S_advection_xy + ds_0N140W.Sh_tendency_vert_remap +
        ds_0N140W.boundary_forcing_salt_tendency + ds_0N140W.osaltpmdiff +
       ds_0N140W.osaltdiff +  ds_0N140W.KPP_NLT_saln_budget*1e-3 +
        ds_0N140W.S_lbdxy_cont_tendency).isel(time=3), ds_0N140W.zl,
       label='budget')


ax.set_ylim(500,0)
ax.legend(loc='lower left')
ax.set_title(r'0$^{\circ}$N, 140$^{\circ}$W')
# plt.savefig('sb_0N140W_mom_zstar.png', bbox_inches='tight')
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[9], line 4
      1 # figure with total tendency and sum of components 
      2 fig, ax = plt.subplots(1, 1, figsize=(5,6))
----> 4 ax.plot(ds_0N140W.osalttend.isel(time=3), ds_0N140W.zl, label='Tendency of Sea Water Salinity Expressed as Salt Content')
      7 ax.plot((ds_0N140W.S_advection_xy + ds_0N140W.Sh_tendency_vert_remap +
      8         ds_0N140W.boundary_forcing_salt_tendency + ds_0N140W.osaltpmdiff +
      9        ds_0N140W.osaltdiff +  ds_0N140W.KPP_NLT_saln_budget*1e-3 +
     10         ds_0N140W.S_lbdxy_cont_tendency).isel(time=3), ds_0N140W.zl,
     11        label='budget')
     14 ax.set_ylim(500,0)

NameError: name 'ds_0N140W' is not defined
../_images/examples_close_tracer_budgets_12_1.png
[10]:
%%time
# test whether it closes all over

xr.testing.assert_allclose(ds_hb.osalttend,
                           (ds_hb.S_advection_xy+ds_hb.Sh_tendency_vert_remap+
                            ds_hb.boundary_forcing_salt_tendency+ds_hb.osaltpmdiff+
                            ds_hb.osaltdiff+ds_hb.KPP_NLT_saln_budget*1e-3+ds_hb.S_lbdxy_cont_tendency),
                           rtol=1e-10)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
File <timed eval>:3

NameError: name 'ds_hb' is not defined
[ ]: