Definitions¶
There are four types of energy (per unit mass) that we need to keep track of in the fluid:
is the internal energy (valid for an ideal gas, see e.g. Vallis (2017)) -- this is a measure of the disorganized molecular-scale motion in the fluid
is the gravitational potential energy
is the latent energy associated with the evaporated water in the air ( is the latent heat of vaporization and is the specific humidity)
is the kinetic energy of organized motion of the fluid
We’ll define the total energy per unit mass in the atmosphere as
Budget equations for energy components¶
Potential energy¶
Latent energy¶
Internal energy¶
Kinetic energy¶
A total energy equation and global conservation¶
After some work and cancellation of various conversion terms:
with the net surface enthalpy flux and the radiative flux.
Alternate form and Moist Static Energy¶
We can rewrite the first law of thermodynamics as
where is refered to as the sensible energy
Using this, we can define another form of total energy
and we’ll find it useful to define the moist static energy as the first three terms of :
We’ll see from the data that is typically a small fraction of so is often ignored in the total energy budget, and we will talk instead about fluxes of moist static energy rather than fluxes of total energy.
Merdional energy transport¶
Write the zonal- and time-averaged energy budget in pressure coordinates and flux form:
and we can decompose the meridional energy flux into its components:
Finally we can decompose each component into mean meridional, stationary eddy, and transient eddy terms, e.g.
and likewise for the other components.
The observed energy budget¶
import numpy as np
import xarray as xr
import matplotlib.pyplot as plt
from arraylake import Client as alClient
from dask.distributed import Client as dClientalclient = alClient()
alclient.login()repo = alclient.get_repo("earthmover-public/era5")
session = repo.readonly_session("main")
pressure_spatial = xr.open_zarr(session.store, group="pressure/spatial", chunks={})
pressure_spatial# ## Option 1: run this on a laptop
# ## Comment out this cell if using option 2
# dclient = dClient()
# dclient## Option 2: If running this notebook on jupyterlab.its.albany.edu, uncomment and run this cell
## This sets up a cluster of workers to parallelize the calculations
from dask_jobqueue import SLURMCluster
cluster = SLURMCluster(cores=64, # size of a single job -- typically one node of the HPC cluster
memory="374GB", # the max memory on the 64-core burst-daes nodes, I believe
walltime="02:00:00",
queue="burst-daes",
)
cluster.scale(1)
dclient = dClient(cluster)
dclientdef bracket(data):
return data.mean(dim='longitude', skipna=True)
def star(data):
return data - bracket(data)
def bar(data):
return data.mean(dim="valid_time", skipna=True)
def prime(data):
return data - bar(data)
def global_mean(data):
return data.weighted(np.cos(np.deg2rad(data.latitude))).mean(dim=('latitude','longitude'), skipna=True)# Now do the whole year
interval = "2021"v = pressure_spatial.v.sel(valid_time=interval)
T = pressure_spatial.t.sel(valid_time=interval)
vbar = bar(v)
vbracket = bracket(v)
vbarbracket = bracket(vbar)
Tbar = bar(T)
Tbracket = bracket(T)
Tbarbracket = bar(Tbracket)%%time
Tbar_global = global_mean(Tbar).compute() # we subtract the global mean everywhere to avoid large cancellations/network/rit/lab/snowclus/pixi/aug26/.pixi/envs/default/lib/python3.14/site-packages/distributed/client.py:3429: UserWarning: Sending large graph of size 12.66 MiB.
This may cause some slowdown.
Consider loading the data with Dask directly
or using futures or delayed objects to embed the data into the graph without repetition.
See also https://docs.dask.org/en/stable/best-practices.html#load-data-with-dask for more information.
warnings.warn(
CPU times: user 2min 53s, sys: 13.2 s, total: 3min 6s
Wall time: 6min 51s
vT = bar(bracket(v*(T - Tbar_global)))
vT_transient = bracket(bar(prime(v)*prime(T)))
vT_stationary = bracket(star(vbar) * star(Tbar))
vT_mmc = vbarbracket * bracket(Tbar - Tbar_global)
vTsensible_heat_flux = {}%%time
sensible_heat_flux[r'total $[\overline{vT}]$'] = vT.compute()/network/rit/lab/snowclus/pixi/aug26/.pixi/envs/default/lib/python3.14/site-packages/distributed/client.py:3429: UserWarning: Sending large graph of size 25.91 MiB.
This may cause some slowdown.
Consider loading the data with Dask directly
or using futures or delayed objects to embed the data into the graph without repetition.
See also https://docs.dask.org/en/stable/best-practices.html#load-data-with-dask for more information.
warnings.warn(
CPU times: user 6min 50s, sys: 27.3 s, total: 7min 17s
Wall time: 13min 57s
%%time
sensible_heat_flux[r'transient eddies $[\overline{v^\prime T^\prime}]$'] = vT_transient.compute()/network/rit/lab/snowclus/pixi/aug26/.pixi/envs/default/lib/python3.14/site-packages/distributed/client.py:3429: UserWarning: Sending large graph of size 28.21 MiB.
This may cause some slowdown.
Consider loading the data with Dask directly
or using futures or delayed objects to embed the data into the graph without repetition.
See also https://docs.dask.org/en/stable/best-practices.html#load-data-with-dask for more information.
warnings.warn(
CPU times: user 12min 12s, sys: 36.8 s, total: 12min 49s
Wall time: 24min 4s
%%time
sensible_heat_flux[r'stationary eddies $[\overline{v}^* \overline{T}^*]$'] = vT_stationary.compute()/network/rit/lab/snowclus/pixi/aug26/.pixi/envs/default/lib/python3.14/site-packages/distributed/client.py:3429: UserWarning: Sending large graph of size 25.29 MiB.
This may cause some slowdown.
Consider loading the data with Dask directly
or using futures or delayed objects to embed the data into the graph without repetition.
See also https://docs.dask.org/en/stable/best-practices.html#load-data-with-dask for more information.
warnings.warn(
CPU times: user 5min 53s, sys: 23.8 s, total: 6min 17s
Wall time: 14min 9s
%%time
sensible_heat_flux[r'mean meridional circulation $[\overline{v}][\overline{T} - \overline{T}_{global}]$'] = vT_mmc.compute()CPU times: user 5min 45s, sys: 23.7 s, total: 6min 8s
Wall time: 13min 54s
levs = np.arange(-50,55,5)
fig, axes = plt.subplots(4,1, figsize=(8,12))
for num, flux_name in enumerate(sensible_heat_flux):
flux = sensible_heat_flux[flux_name]
CS = flux.plot.contour(ax=axes[num],
yincrease=False,
levels=levs,
)
axes[num].clabel(CS)
axes[num].set_title(flux_name + ', ' + interval)
dclient.close()- Vallis, G. K. (2017). Atmospheric and Oceanic Fluid Dynamics: fundamentals and large-scale circulation (2nd ed.). Cambridge University Press.