Skip to content

Commit 930c9c6

Browse files
committed
Add another LAVD example
1 parent 9016b11 commit 930c9c6

File tree

7 files changed

+487
-40
lines changed

7 files changed

+487
-40
lines changed
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
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)

examples/16_network/pet_relative.py

Lines changed: 28 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -136,54 +136,46 @@
136136
ax.set_title(f"Clean network ({n_clean.infos()})")
137137
_ = n_clean.display_timeline(ax)
138138

139+
# %%
140+
# For further figure we will use clean path
141+
n = n_clean
139142

140143
# %%
141-
# change splittint-merging events
142-
# ------------------
144+
# Change splitting-merging events
145+
# -------------------------------
143146
# change event where seg A split to B, then A merge into B, to A split to B then B merge into A
144-
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(15, 12), dpi=120)
145-
146-
ax1.set_title(f"Clean network ({n_clean.infos()})")
147-
n_clean.display_timeline(ax1)
147+
fig = plt.figure(figsize=(15, 12))
148+
ax = fig.add_axes([0.04, 0.54, 0.90, 0.40])
149+
ax.set_title(f"Clean network ({n.infos()})")
150+
n.display_timeline(ax)
148151

149-
clean_modified = n_clean.copy()
150-
clean_modified.correct_close_events(100)
151-
ax2.set_title(f"resplitted network ({clean_modified.infos()})")
152-
_ = clean_modified.display_timeline(ax2)
152+
clean_modified = n.copy()
153+
# If it's happen in less than 40 days
154+
clean_modified.correct_close_events(40)
153155

156+
ax = fig.add_axes([0.04, 0.04, 0.90, 0.40])
157+
ax.set_title(f"resplitted network ({clean_modified.infos()})")
158+
_ = clean_modified.display_timeline(ax)
154159

155160
# %%
156-
# keep only observations where water could propagate from an observation
161+
# Keep only observations where water could propagate from an observation
157162
# ----------------------------------------------------------------------
158-
159-
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(15, 12), dpi=120)
160163
i_observation = 600
161-
only_linked = n_clean.find_link(i_observation)
162-
163-
for ax, dataset in zip([ax1, ax2], [n_clean, only_linked]):
164-
dataset.display_timeline(
165-
ax, field="segment", marker="+", lw=2, markersize=5, colors_mode="y"
166-
)
167-
ax.scatter(
168-
n_clean.time[i_observation],
169-
n_clean.segment[i_observation],
170-
marker="s",
171-
s=50,
172-
color="black",
173-
zorder=200,
174-
label="observation start",
175-
alpha=0.6,
176-
)
164+
only_linked = n.find_link(i_observation)
165+
166+
fig = plt.figure(figsize=(15, 12))
167+
ax1 = fig.add_axes([0.04, 0.54, 0.90, 0.40])
168+
ax2 = fig.add_axes([0.04, 0.04, 0.90, 0.40])
169+
170+
kw = dict(marker="s", s=300, color="black", zorder=200, label="observation start")
171+
for ax, dataset in zip([ax1, ax2], [n, only_linked]):
172+
dataset.display_timeline(ax, field="segment", lw=2, markersize=5, colors_mode="y")
173+
ax.scatter(n.time[i_observation], n.segment[i_observation], **kw)
177174
ax.legend()
178175

179-
ax1.set_title(f"full example ({n_clean.infos()})")
176+
ax1.set_title(f"full example ({n.infos()})")
180177
ax2.set_title(f"only linked observations ({only_linked.infos()})")
181-
ax2.set_xlim(ax1.get_xlim())
182-
ax2.set_ylim(ax1.get_ylim())
183-
184-
# %%
185-
# For further figure we will use clean path
186-
n = n_clean
178+
_ = ax2.set_xlim(ax1.get_xlim()), ax2.set_ylim(ax1.get_ylim())
187179

188180
# %%
189181
# Keep close relative
@@ -299,7 +291,6 @@
299291
ax.set_xlim(17.5, 27.5), ax.set_ylim(31, 36), ax.grid()
300292
m1
301293

302-
303294
# %%
304295
# Get spliting event
305296
# ------------------

notebooks/README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# rm build/sphinx/ doc/python_module/ doc/gen_modules/ -rf
2-
# rm doc/_autosummary/ doc/gen_modules -rf
1+
# rm build/sphinx/ doc/python_module/ doc/gen_modules/ doc/_autosummary/ -rf
32
python setup.py build_sphinx
43
rsync -vrltp doc/python_module notebooks/. --include '*/' --include '*.ipynb' --exclude '*' --prune-empty-dirs

0 commit comments

Comments
 (0)