Divergence

\[\mathrm{D} = \frac{\partial u}{\partial x} + \frac{\partial v}{\partial y} - \frac{v}{R} \tan \varphi\]

Before proceeding with all the steps, first import some necessary libraries and packages

import easyclimate as ecl
import xarray as xr
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import matplotlib.ticker as mticker

Read sample data

udata = ecl.open_tutorial_dataset("uwnd_2022_day5")["uwnd"].sortby("lat")
vdata = ecl.open_tutorial_dataset("vwnd_2022_day5")["vwnd"].sortby("lat")
uvdata = xr.Dataset(data_vars = {"u": udata,"v": vdata})
uvdata
<xarray.Dataset> Size: 7MB
Dimensions:  (time: 5, lon: 144, lat: 73, level: 17)
Coordinates:
  * time     (time) datetime64[ns] 40B 2022-01-01 2022-01-02 ... 2022-01-05
  * lon      (lon) float32 576B 0.0 2.5 5.0 7.5 10.0 ... 350.0 352.5 355.0 357.5
  * lat      (lat) float32 292B -90.0 -87.5 -85.0 -82.5 ... 82.5 85.0 87.5 90.0
  * level    (level) float32 68B 1e+03 925.0 850.0 700.0 ... 50.0 30.0 20.0 10.0
Data variables:
    u        (time, level, lat, lon) float32 4MB ...
    v        (time, level, lat, lon) float32 4MB ...


Calculating divergence using three types of functions

<xarray.DataArray 'divergence' (time: 5, level: 17, lat: 73, lon: 144)> Size: 7MB
array([[[[            nan,             nan,             nan, ...,
                      nan,             nan,             nan],
         [            nan, -2.19827472e-06, -6.31164010e-07, ...,
          -2.50061775e-06, -1.56277040e-06,             nan],
         [            nan, -2.84018520e-07,  4.33626285e-07, ...,
          -1.96411801e-06, -1.49405272e-06,             nan],
         ...,
         [            nan, -3.13727264e-06, -1.72531481e-06, ...,
          -5.80543778e-06, -4.34719430e-06,             nan],
         [            nan,  2.30969100e-06,  3.29272067e-06, ...,
           4.35664185e-07,  2.76442241e-06,             nan],
         [            nan,             nan,             nan, ...,
                      nan,             nan,             nan]],

        [[            nan,             nan,             nan, ...,
                      nan,             nan,             nan],
         [            nan, -2.71491953e-07, -6.31216804e-07, ...,
          -1.60466807e-06, -2.86333289e-06,             nan],
         [            nan, -3.75503378e-08, -3.51958919e-07, ...,
          -2.00885468e-06, -1.58363743e-06,             nan],
...
           3.11147315e-06,  3.04513947e-06,             nan],
         [            nan,  3.41895752e-06,  3.32153273e-06, ...,
           4.84256077e-06,  4.47522783e-06,             nan],
         [            nan,             nan,             nan, ...,
                      nan,             nan,             nan]],

        [[            nan,             nan,             nan, ...,
                      nan,             nan,             nan],
         [            nan,  2.04186930e-06,  4.74346649e-07, ...,
           1.04512318e-06,  1.22765801e-06,             nan],
         [            nan,  2.88392317e-07,  1.04996678e-06, ...,
          -4.93193199e-07, -7.40686344e-07,             nan],
         ...,
         [            nan, -1.80805994e-06, -1.31322278e-06, ...,
          -2.68457351e-06, -2.10119615e-06,             nan],
         [            nan, -1.02121489e-06, -1.56782790e-06, ...,
          -1.74404622e-06, -8.58425706e-07,             nan],
         [            nan,             nan,             nan, ...,
                      nan,             nan,             nan]]]],
      shape=(5, 17, 73, 144))
Coordinates:
  * time     (time) datetime64[ns] 40B 2022-01-01 2022-01-02 ... 2022-01-05
  * level    (level) float32 68B 1e+03 925.0 850.0 700.0 ... 50.0 30.0 20.0 10.0
  * lat      (lat) float32 292B -90.0 -87.5 -85.0 -82.5 ... 82.5 85.0 87.5 90.0
  * lon      (lon) float32 576B 0.0 2.5 5.0 7.5 10.0 ... 350.0 352.5 355.0 357.5
Attributes:
    long_name:     divergence
    units:         s^-1
    precision:     2
    GRIB_id:       33
    GRIB_name:     UGRD
    var_desc:      u-wind
    level_desc:    Pressure Levels
    statistic:     Mean
    parent_stat:   Individual Obs
    dataset:       NCEP Reanalysis Daily Averages
    actual_range:  [-83.05 116.85]


Plot the results of the divergence calculation

import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.ticker as mticker

lonlat_range = [70, 180, 0, 70]

proj = ccrs.PlateCarree()

fig, ax = plt.subplots(
    3, 1,
    figsize=(4.5, 9),
    subplot_kw={"projection": proj},
    constrained_layout=True
)

# Unify the color scale range
vmax = 2.5e-5
levels = np.linspace(-vmax, vmax, 21)
cmap = "RdBu_r"

# The three scenes that need to be drawn
fields = [
    (div_raw.isel(time=0).sel(level=850),  "div raw"),
    (div_ncl.isel(time=0).sel(level=850),  "div ncl"),
    (div_rust.isel(time=0).sel(level=850), "div rust"),
]

pcm = None

for i, (axi, (fld, title)) in enumerate(zip(ax.flat, fields)):
    axi.set_extent(lonlat_range, crs=ccrs.PlateCarree())

    # Coastlines and Base Maps
    axi.coastlines(resolution="50m", linewidth=0.8)

    # Grid lines
    gl = axi.gridlines(
        crs=ccrs.PlateCarree(),
        draw_labels=True,
        linewidth=0.5,
        color="gray",
        alpha=0.4,
        linestyle="--"
    )
    gl.top_labels = False
    gl.right_labels = False
    gl.left_labels = True
    gl.bottom_labels = True

    # It looks cleaner if you keep the bottom labels on only the bottom row.
    if i < 2:
        gl.bottom_labels = False

    gl.xlocator = mticker.FixedLocator([80, 100, 120, 140, 160, 180])
    gl.ylocator = mticker.FixedLocator([0, 10, 20, 30, 40, 50, 60, 70])

    # Drawing
    pcm = fld.plot.contourf(
        ax=axi,
        transform=ccrs.PlateCarree(),
        levels=levels,
        cmap=cmap,
        add_colorbar=False,
        extend="both"
    )

    axi.set_title(title, fontsize=13)

# Shared colorbar
cbar = fig.colorbar(
    pcm,
    ax=ax,
    orientation="horizontal",
    shrink=0.9,
    pad=0.04,
    aspect=40
)
cbar.set_label("divergence [s$^{-1}$]")
div raw, div ncl, div rust

When comparing the results of different calculation methods, it is generally recommended to use the Rust-based method.

fig, ax = plt.subplots(2, 1, figsize = (5, 8), sharex=True)

(div_ncl - div_raw).isel(time = 0).sel(level = 850).plot(ax = ax[0])
ax[0].set_title("NCL - Numpy")
ax[0].set_xlabel("")
ax[0].set_ylabel("")

(div_ncl - div_rust).isel(time = 0).sel(level = 850).plot(ax = ax[1])
ax[1].set_title("NCL - Rust")
ax[1].set_xlabel("")
ax[1].set_ylabel("")
NCL - Numpy, NCL - Rust
Text(18.09722222222222, 0.5, '')

Total running time of the script: (0 minutes 8.617 seconds)