|
| 1 | +""" |
| 2 | +LAVD detection and geometric detection |
| 3 | +====================================== |
| 4 | +
|
| 5 | +Naive method to reproduce LAVD(Lagrangian-Averaged Vorticity deviation). |
| 6 | +In the current example we didn't remove a mean vorticity. |
| 7 | +
|
| 8 | +Method are described here: |
| 9 | +
|
| 10 | + - Abernathey, Ryan, and George Haller. "Transport by Lagrangian Vortices in the Eastern Pacific", |
| 11 | + Journal of Physical Oceanography 48, 3 (2018): 667-685, accessed Feb 16, 2021, |
| 12 | + https://doi.org/10.1175/JPO-D-17-0102.1 |
| 13 | + - `Transport by Coherent Lagrangian Vortices`_, |
| 14 | + R. Abernathey, Sinha A., Tarshish N., Liu T., Zhang C., Haller G., 2019, |
| 15 | + Talk a t the Sources and Sinks of Ocean Mesoscale Eddy Energy CLIVAR Workshop |
| 16 | +
|
| 17 | +.. _Transport by Coherent Lagrangian Vortices: |
| 18 | + https://usclivar.org/sites/default/files/meetings/2019/presentations/Aberernathey_CLIVAR.pdf |
| 19 | +
|
| 20 | +""" |
| 21 | +from datetime import datetime |
| 22 | + |
| 23 | +from matplotlib import pyplot as plt |
| 24 | +from numpy import arange, isnan, ma, meshgrid, zeros |
| 25 | + |
| 26 | +import py_eddy_tracker.gui |
| 27 | +from py_eddy_tracker import start_logger |
| 28 | +from py_eddy_tracker.data import get_path |
| 29 | +from py_eddy_tracker.dataset.grid import GridCollection, RegularGridDataset |
| 30 | + |
| 31 | +start_logger().setLevel("ERROR") |
| 32 | + |
| 33 | + |
| 34 | +# %% |
| 35 | +class LAVDGrid(RegularGridDataset): |
| 36 | + def init_speed_coef(self, uname="u", vname="v"): |
| 37 | + """Hack to be able to identify eddy with LAVD field""" |
| 38 | + self._speed_ev = self.grid("lavd") |
| 39 | + |
| 40 | + @classmethod |
| 41 | + def from_(cls, x, y, z): |
| 42 | + z.mask += isnan(z.data) |
| 43 | + datas = dict(lavd=z, lon=x, lat=y) |
| 44 | + return cls.with_array(coordinates=("lon", "lat"), datas=datas, centered=True) |
| 45 | + |
| 46 | + |
| 47 | +# %% |
| 48 | +def start_ax(title="", dpi=90): |
| 49 | + fig = plt.figure(figsize=(12, 5), dpi=dpi) |
| 50 | + ax = fig.add_axes([0.05, 0.08, 0.9, 0.9], projection="full_axes") |
| 51 | + ax.set_xlim(-6, 36), ax.set_ylim(31, 45) |
| 52 | + ax.set_title(title) |
| 53 | + return fig, ax, ax.text(3, 32, "", fontsize=20) |
| 54 | + |
| 55 | + |
| 56 | +def update_axes(ax, mappable=None): |
| 57 | + ax.grid() |
| 58 | + if mappable: |
| 59 | + cb = plt.colorbar( |
| 60 | + mappable, |
| 61 | + cax=ax.figure.add_axes([0.05, 0.1, 0.9, 0.01]), |
| 62 | + orientation="horizontal", |
| 63 | + ) |
| 64 | + cb.set_label("LAVD at initial position") |
| 65 | + return cb |
| 66 | + |
| 67 | + |
| 68 | +kw_lavd = dict(vmin=0, vmax=2e-5, cmap="viridis") |
| 69 | + |
| 70 | +# %% |
| 71 | +# Data |
| 72 | +# ---- |
| 73 | + |
| 74 | +# Load data cube of 3 month |
| 75 | +c = GridCollection.from_netcdf_cube( |
| 76 | + get_path("dt_med_allsat_phy_l4_2005T2.nc"), |
| 77 | + "longitude", |
| 78 | + "latitude", |
| 79 | + "time", |
| 80 | + heigth="adt", |
| 81 | +) |
| 82 | + |
| 83 | +# Add vorticity at each time step |
| 84 | +for g in c: |
| 85 | + u_y = g.compute_stencil(g.grid("u"), vertical=True) |
| 86 | + v_x = g.compute_stencil(g.grid("v")) |
| 87 | + g.vars["vort"] = v_x - u_y |
| 88 | + |
| 89 | +# %% |
| 90 | +# Particles |
| 91 | +# --------- |
| 92 | + |
| 93 | +# Time properties, for example with advection only 25 days |
| 94 | +nb_days, step_by_day = 25, 6 |
| 95 | +nb_time = step_by_day * nb_days |
| 96 | +kw_p = dict(nb_step=1, time_step=86400 / step_by_day) |
| 97 | +t0 = 20236 |
| 98 | +t0_grid = c[t0] |
| 99 | +# Geographic properties, we use a coarser resolution for time consuming reasons |
| 100 | +step = 1 / 32.0 |
| 101 | +x_g, y_g = arange(-6, 36, step), arange(30, 46, step) |
| 102 | +x0, y0 = meshgrid(x_g, y_g) |
| 103 | +original_shape = x0.shape |
| 104 | +x0, y0 = x0.reshape(-1), y0.reshape(-1) |
| 105 | +# Get all particles in defined area |
| 106 | +m = ~isnan(t0_grid.interp("vort", x0, y0)) |
| 107 | +x0, y0 = x0[m], y0[m] |
| 108 | +print(f"{x0.size} particles advected") |
| 109 | +# Gridded mask |
| 110 | +m = m.reshape(original_shape) |
| 111 | + |
| 112 | +# %% |
| 113 | +# LAVD forward (dynamic field) |
| 114 | +# ---------------------------- |
| 115 | +lavd = zeros(original_shape) |
| 116 | +lavd_ = lavd[m] |
| 117 | +p = c.advect(x0.copy(), y0.copy(), "u", "v", t_init=t0, **kw_p) |
| 118 | +for _ in range(nb_time): |
| 119 | + t, x, y = p.__next__() |
| 120 | + lavd_ += abs(c.interp("vort", t / 86400.0, x, y)) |
| 121 | +lavd[m] = lavd_ / nb_time |
| 122 | +# Put LAVD result in a standard py eddy tracker grid |
| 123 | +lavd_forward = LAVDGrid.from_(x_g, y_g, ma.array(lavd, mask=~m).T) |
| 124 | +# Display |
| 125 | +fig, ax, _ = start_ax("LAVD with a forward advection") |
| 126 | +mappable = lavd_forward.display(ax, "lavd", **kw_lavd) |
| 127 | +_ = update_axes(ax, mappable) |
| 128 | + |
| 129 | +# %% |
| 130 | +# LAVD backward (dynamic field) |
| 131 | +# ----------------------------- |
| 132 | +lavd = zeros(original_shape) |
| 133 | +lavd_ = lavd[m] |
| 134 | +p = c.advect(x0.copy(), y0.copy(), "u", "v", t_init=t0, backward=True, **kw_p) |
| 135 | +for i in range(nb_time): |
| 136 | + t, x, y = p.__next__() |
| 137 | + lavd_ += abs(c.interp("vort", t / 86400.0, x, y)) |
| 138 | +lavd[m] = lavd_ / nb_time |
| 139 | +# Put LAVD result in a standard py eddy tracker grid |
| 140 | +lavd_backward = LAVDGrid.from_(x_g, y_g, ma.array(lavd, mask=~m).T) |
| 141 | +# Display |
| 142 | +fig, ax, _ = start_ax("LAVD with a backward advection") |
| 143 | +mappable = lavd_backward.display(ax, "lavd", **kw_lavd) |
| 144 | +_ = update_axes(ax, mappable) |
| 145 | + |
| 146 | +# %% |
| 147 | +# LAVD forward (static field) |
| 148 | +# --------------------------- |
| 149 | +lavd = zeros(original_shape) |
| 150 | +lavd_ = lavd[m] |
| 151 | +p = t0_grid.advect(x0.copy(), y0.copy(), "u", "v", **kw_p) |
| 152 | +for _ in range(nb_time): |
| 153 | + x, y = p.__next__() |
| 154 | + lavd_ += abs(t0_grid.interp("vort", x, y)) |
| 155 | +lavd[m] = lavd_ / nb_time |
| 156 | +# Put LAVD result in a standard py eddy tracker grid |
| 157 | +lavd_forward_static = LAVDGrid.from_(x_g, y_g, ma.array(lavd, mask=~m).T) |
| 158 | +# Display |
| 159 | +fig, ax, _ = start_ax("LAVD with a forward advection on a static velocity field") |
| 160 | +mappable = lavd_forward_static.display(ax, "lavd", **kw_lavd) |
| 161 | +_ = update_axes(ax, mappable) |
| 162 | + |
| 163 | +# %% |
| 164 | +# LAVD backward (static field) |
| 165 | +# ---------------------------- |
| 166 | +lavd = zeros(original_shape) |
| 167 | +lavd_ = lavd[m] |
| 168 | +p = t0_grid.advect(x0.copy(), y0.copy(), "u", "v", backward=True, **kw_p) |
| 169 | +for i in range(nb_time): |
| 170 | + x, y = p.__next__() |
| 171 | + lavd_ += abs(t0_grid.interp("vort", x, y)) |
| 172 | +lavd[m] = lavd_ / nb_time |
| 173 | +# Put LAVD result in a standard py eddy tracker grid |
| 174 | +lavd_backward_static = LAVDGrid.from_(x_g, y_g, ma.array(lavd, mask=~m).T) |
| 175 | +# Display |
| 176 | +fig, ax, _ = start_ax("LAVD with a backward advection on a static velocity field") |
| 177 | +mappable = lavd_backward_static.display(ax, "lavd", **kw_lavd) |
| 178 | +_ = update_axes(ax, mappable) |
| 179 | + |
| 180 | +# %% |
| 181 | +# Contourdetection |
| 182 | +# ---------------- |
| 183 | +# To extract contour from LAVD grid, we will used method design for SSH, with some hacks and adapted options. |
| 184 | +# It will produce false amplitude and speed. |
| 185 | +kw_ident = dict( |
| 186 | + force_speed_unit="m/s", |
| 187 | + force_height_unit="m", |
| 188 | + pixel_limit=(40, 200000), |
| 189 | + date=datetime(2005, 5, 18), |
| 190 | + uname=None, |
| 191 | + vname=None, |
| 192 | + grid_height="lavd", |
| 193 | + shape_error=70, |
| 194 | + step=1e-6, |
| 195 | +) |
| 196 | +fig, ax, _ = start_ax("Detection of eddies with several method") |
| 197 | +t0_grid.bessel_high_filter("adt", 700) |
| 198 | +a, c = t0_grid.eddy_identification( |
| 199 | + "adt", "u", "v", kw_ident["date"], step=0.002, shape_error=70 |
| 200 | +) |
| 201 | +kw_ed = dict(ax=ax, intern=True, ref=-10) |
| 202 | +a.filled( |
| 203 | + facecolors="#FFEFCD", label="Anticyclonic SSH detection {nb_obs} eddies", **kw_ed |
| 204 | +) |
| 205 | +c.filled(facecolors="#DEDEDE", label="Cyclonic SSH detection {nb_obs} eddies", **kw_ed) |
| 206 | +kw_cont = dict(ax=ax, extern_only=True, ls="-", ref=-10) |
| 207 | +forward, _ = lavd_forward.eddy_identification(**kw_ident) |
| 208 | +forward.display(label="LAVD forward {nb_obs} eddies", color="g", **kw_cont) |
| 209 | +backward, _ = lavd_backward.eddy_identification(**kw_ident) |
| 210 | +backward.display(label="LAVD backward {nb_obs} eddies", color="r", **kw_cont) |
| 211 | +forward, _ = lavd_forward_static.eddy_identification(**kw_ident) |
| 212 | +forward.display(label="LAVD forward static {nb_obs} eddies", color="cyan", **kw_cont) |
| 213 | +backward, _ = lavd_backward_static.eddy_identification(**kw_ident) |
| 214 | +backward.display( |
| 215 | + label="LAVD backward static {nb_obs} eddies", color="orange", **kw_cont |
| 216 | +) |
| 217 | +ax.legend() |
| 218 | +update_axes(ax) |
0 commit comments