6. Energy Budget#

Definitions#

There are four types of energy (per unit mass) that we need to keep track of in the fluid:

  1. \(I = c_v T\) 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

  2. \(\Phi = g z\) is the gravitational potential energy

  3. \(L = L_v q\) is the latent energy associated with the evaporated water in the air (\(L_v\) is the latent heat of vaporization and \(q\) is the specific humidity)

  4. \(K = \frac{1}{2} \left(u^2 + v^2 + w^2 \right)\) is the kinetic energy of organized motion of the fluid

We’ll define the total energy per unit mass in the atmosphere as

\[\begin{align*} E &= I + \Phi + L + K \\ &= c_v T + gz + L_v q + \frac{1}{2} \left(u^2 + v^2 + w^2 \right) \end{align*}\]

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:

\[ \frac{DE}{Dt} = -\alpha \nabla \cdot \left( \vec{v} p \right) - \alpha \nabla \cdot \vec{F}_{rad} + D_h \]

with \(D_h = c_p D_T + L_v D_q\) the net surface enthalpy flux and \(\vec{F}_{rad}\) the radiative flux.

Alternate form and Moist Static Energy#

We can rewrite the first law of thermodynamics as

\[\begin{align*} dQ &= dI + p d\alpha \\ &= dI + d(p\alpha) - \alpha dp \\ &= d(c_v T + RT) - \alpha dp \\ &= d(c_p T ) - \alpha dp \end{align*}\]

where \(c_p T\) is refered to as the sensible energy \(S\)

Using this, we can define another form of total energy

\[ \mathcal{E} = S + \Phi + L + K \]

and we’ll find it useful to define the moist static energy \(m\) as the first three terms of \(mathcal{E}\):

\[ m = S + \Phi + L = c_p T + g z + L_v q \]

We’ll see from the data that \(K\) is typically a small fraction of \(\mathcal{E}\) 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:

\[ \frac{\partial}{\partial t} [\overline{\mathcal{E}}] = -\frac{1}{a \cos\phi} \frac{\partial}{\partial \phi} \left( [\overline{v\mathcal{E}}] \cos\phi \right) -\frac{\partial}{\partial p} \left( [\overline{\omega \mathcal{E}}] \right) + g \left[ \frac{\partial \overline{\vec{F}_{rad}}}{\partial p} \right] + [\overline{D_h}]\]

and we can decompose the meridional energy flux into its components:

\[ [\overline{v\mathcal{E}}] = c_p [\overline{vT}] + g [\overline{vz}] + L_v [\overline{vq}] + [\overline{vK}]\]

Finally we can decompose each component into mean meridional, stationary eddy, and transient eddy terms, e.g.

\[ [\overline{vT}] = [\overline{v}][\overline{T}] + [\overline{v^*} \overline{T^*}] + [\overline{v^\prime T^\prime}] \]

and likewise for the other components.

The observed energy budget#

import numpy as np
import xarray as xr
import matplotlib.pyplot as plt

cfsr_path = '/cfsr/data/'
year = '2021'

ds = xr.open_mfdataset(cfsr_path + year + '/*.nc', chunks={'time':30, 'lev': 1})
ds
/knight/anaconda_aug22/envs/aug22_env/lib/python3.10/site-packages/dask/array/core.py:4796: PerformanceWarning: Increasing number of chunks by factor of 32
  result = blockwise(
/knight/anaconda_aug22/envs/aug22_env/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]
/knight/anaconda_aug22/envs/aug22_env/lib/python3.10/site-packages/dask/array/core.py:4796: PerformanceWarning: Increasing number of chunks by factor of 10
  result = blockwise(
/knight/anaconda_aug22/envs/aug22_env/lib/python3.10/site-packages/dask/array/core.py:4796: PerformanceWarning: Increasing number of chunks by factor of 32
  result = blockwise(
/knight/anaconda_aug22/envs/aug22_env/lib/python3.10/site-packages/dask/array/core.py:4796: PerformanceWarning: Increasing number of chunks by factor of 32
  result = blockwise(
/knight/anaconda_aug22/envs/aug22_env/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]
/knight/anaconda_aug22/envs/aug22_env/lib/python3.10/site-packages/dask/array/core.py:4796: PerformanceWarning: Increasing number of chunks by factor of 32
  result = blockwise(
/knight/anaconda_aug22/envs/aug22_env/lib/python3.10/site-packages/dask/array/core.py:4796: PerformanceWarning: Increasing number of chunks by factor of 10
  result = blockwise(
/knight/anaconda_aug22/envs/aug22_env/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]
/knight/anaconda_aug22/envs/aug22_env/lib/python3.10/site-packages/dask/array/core.py:4796: PerformanceWarning: Increasing number of chunks by factor of 32
  result = blockwise(
/knight/anaconda_aug22/envs/aug22_env/lib/python3.10/site-packages/dask/array/core.py:4796: PerformanceWarning: Increasing number of chunks by factor of 10
  result = blockwise(
/knight/anaconda_aug22/envs/aug22_env/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]
/knight/anaconda_aug22/envs/aug22_env/lib/python3.10/site-packages/dask/array/core.py:4796: PerformanceWarning: Increasing number of chunks by factor of 32
  result = blockwise(
<xarray.Dataset>
Dimensions:  (time: 1460, lat: 361, lon: 720, lev: 40)
Coordinates:
  * time     (time) datetime64[ns] 2021-01-01 ... 2021-12-31T18:00:00
  * lat      (lat) float32 -90.0 -89.5 -89.0 -88.5 -88.0 ... 88.5 89.0 89.5 90.0
  * lon      (lon) float32 -180.0 -179.5 -179.0 -178.5 ... 178.5 179.0 179.5
  * lev      (lev) float32 -2e-06 2e-06 10.0 20.0 ... 925.0 950.0 975.0 1e+03
Data variables: (12/16)
    g        (time, lev, lat, lon) float32 dask.array<chunksize=(30, 3, 361, 720), meta=np.ndarray>
    pmsl     (time, lat, lon) float32 dask.array<chunksize=(30, 361, 720), meta=np.ndarray>
    pres_pv  (time, lev, lat, lon) float32 dask.array<chunksize=(30, 1, 361, 720), meta=np.ndarray>
    psfc     (time, lat, lon) float32 dask.array<chunksize=(30, 361, 720), meta=np.ndarray>
    pv_isen  (time, lev, lat, lon) float32 dask.array<chunksize=(30, 15, 361, 720), meta=np.ndarray>
    pwat     (time, lat, lon) float32 dask.array<chunksize=(30, 361, 720), meta=np.ndarray>
    ...       ...
    u_isen   (time, lev, lat, lon) float32 dask.array<chunksize=(30, 15, 361, 720), meta=np.ndarray>
    u_pv     (time, lev, lat, lon) float32 dask.array<chunksize=(30, 1, 361, 720), meta=np.ndarray>
    v        (time, lev, lat, lon) float32 dask.array<chunksize=(30, 3, 361, 720), meta=np.ndarray>
    v_isen   (time, lev, lat, lon) float32 dask.array<chunksize=(30, 15, 361, 720), meta=np.ndarray>
    v_pv     (time, lev, lat, lon) float32 dask.array<chunksize=(30, 1, 361, 720), meta=np.ndarray>
    w        (time, lev, lat, lon) float32 dask.array<chunksize=(30, 3, 361, 720), meta=np.ndarray>
Attributes:
    description:    g 1000-10 hPa
    year:           2021
    source:         http://nomads.ncdc.noaa.gov/data.php?name=access#CFSR-data
    references:     Saha, et. al., (2010)
    created_by:     User: ab473731
    creation_date:  Sat Jan  2 06:01:03 UTC 2021
def bracket(data):
    return data.mean(dim='lon', skipna=True)

def star(data):
    return data - bracket(data)

def bar(data, interval=ds.time.dt.month):
    return data.groupby(interval).mean(skipna=True)

def prime(data, interval=ds.time.dt.month):
    return data.groupby(interval) - bar(data, interval=interval)

def global_mean(data):
    return data.weighted(np.cos(np.deg2rad(data.lat))).mean(dim=('lat','lon'), skipna=True)
v = ds.v
T = ds.t - global_mean(ds.t) # we subtract the global mean everywhere to avoid large cancellations 

vbar = bar(v)
vbracket = bracket(v)
vbarbracket = bracket(vbar)

Tbar = bar(T)
Tbracket = bracket(T)
Tbarbracket = bracket(Tbar)

vT = bracket(bar(v*T))
vT_transient = bracket(bar(prime(v)*prime(T)))
vT_stationary = bracket(star(vbar) * star(Tbar))
vT_mmc = vbarbracket * bracket(Tbar - global_mean(Tbar))

vT
<xarray.DataArray (month: 12, lev: 40, lat: 361)>
dask.array<mean_agg-aggregate, shape=(12, 40, 361), dtype=float32, chunksize=(1, 4, 361), chunktype=numpy.ndarray>
Coordinates:
  * lat      (lat) float32 -90.0 -89.5 -89.0 -88.5 -88.0 ... 88.5 89.0 89.5 90.0
  * lev      (lev) float32 -2e-06 2e-06 10.0 20.0 ... 925.0 950.0 975.0 1e+03
  * month    (month) int64 1 2 3 4 5 6 7 8 9 10 11 12
flux_list = [vT, vT_transient, vT_stationary, vT_mmc]
levs = np.arange(-50,55,5)

fig, axes = plt.subplots(4,1, figsize=(8,12))

for num, flux in enumerate(flux_list):
    flux_jan = flux.sel(month=1)
    CS = flux_jan.plot.contour(ax=axes[num],
                               yincrease=False, 
                               levels=levs,
                                 )
    axes[num].clabel(CS)

axes[0].set_title('Total sensible heat flux $[\overline{vT}]$ (January)')
axes[1].set_title('Sensible heat flux by transient eddies $[\overline{v^\prime T^\prime}]$ (January)');
axes[2].set_title('Sensible heat flux by stationary eddies $[\overline{v}^* \overline{T}^*]$ (January)')
axes[3].set_title('Sensible heat flux by MMC $[\overline{v}][\overline{T} - \overline{T}_{global}]$ (January)')
/knight/anaconda_aug22/envs/aug22_env/lib/python3.10/site-packages/flox/aggregations.py:258: RuntimeWarning: invalid value encountered in true_divide
  finalize=lambda sum_, count: sum_ / count,
Text(0.5, 1.0, 'Sensible heat flux by MMC $[\\overline{v}][\\overline{T} - \\overline{T}_{global}]$ (January)')
../_images/73718837fa4f0d005d1eb4524cf45354b9805031eacf0f6f7385ff30072e317a.png