Idealized data#

Here, we demonstrate the capabilities of tams.idealized for creating synthetic datasets for testing and experimentation.

import warnings

import cartopy.io
import geopandas as gpd
import matplotlib.pyplot as plt

import tams
from tams.idealized import Blob, Field, Sim

warnings.filterwarnings("ignore", category=cartopy.io.DownloadWarning)

Blob#

A Blob is an ellipse with parameters tendencies (set to zero by default) and a parabolic well.

The default Blob is a circle.

Blob().polygon
../_images/588ba7734d166b312c3b04ac1ca91253f1c3d0132b66bf2cee2835512166d15b.svg
Blob().ring
../_images/18c7ad78a17a24ec43587f8cb3b3caf1f9cb0095a9761c2d17c67bc2b7e94c94.svg
Blob().to_geopandas().plot();
../_images/24be79b67c417e8b820541bf7ba541fb37116c2c438bfc1545f33313c4d78b8f.png
_, ax = plt.subplots(layout="constrained", figsize=(4, 4))

ax.add_patch(Blob().to_patch(fc="forestgreen"))
ax.set_aspect("equal")
ax.autoscale()
../_images/0d6bc318ae4e42a0a26bd904fdd22fd226289edc3eb3867c16e9e7856d94f34c.png

The default Blob has zero tendency in its parameters, meaning that when evolve() is called, it won’t move or grow.

Blob().get_tendency()
{'center': array([0., 0.]),
 'width': 0.0,
 'height': 0.0,
 'angle': 0.0,
 'depth': 0.0}

Split#

Blob(center=(1, 2)).split()
[Blob(center=[1.   1.75], width=0.5, height=0.5, angle=0),
 Blob(center=[1.   2.25], width=0.5, height=0.5, angle=0)]
Field(Blob(center=(1, 2), width=6, height=2, angle=20).split(3)).to_geopandas().plot(fc="none");
../_images/c8e1848743df81f1a918c2e8af2a1d080766feb2ad2f4ff2ee5a0e61abc5957c.png

Merge#

b1 = Blob(width=6, height=2, angle=30)
b2 = Blob(width=8, height=4)
b3 = b1.merge(b2)

Field([b1, b2, b3]).to_geopandas().plot(fc="none");
../_images/462ba8715fb18dc2dae12dd7dcea9e657893d5888bfae23799e5d975b822bd7e.png

Field#

A Field is a collection of Blobs on a grid that, together, combine to make a field that we can run tams.identify() on.

Field()
Field(blobs=[])

Single well#

%%time

b = Blob(center=(0, 0), width=10, height=6)

ctt = Field(b).to_xarray()

(ce,) = tams.identify(ctt)

fig, ax = plt.subplots(figsize=(10, 6))

ctt.plot(ax=ax)
gpd.GeoSeries(b.ring).plot(ec="magenta", ax=ax)

ce.plot(ax=ax, lw=2.5, ls=":", ec="black", fc="none", zorder=10)

ax.set_aspect("equal")
CPU times: user 471 ms, sys: 184 ms, total: 655 ms
Wall time: 983 ms
../_images/81d0907b304faa0e5ccf3a4baad1144556baceec122acf89f8342b005be1c1ce.png
fig, [ax1, ax2] = plt.subplots(1, 2, figsize=(8, 4), sharex=True, sharey=True, layout="constrained")

ctt.sel(lat=0, method="nearest").plot(ax=ax1)
ctt.sel(lon=0, method="nearest").plot(ax=ax2)

ax2.set_ylabel("")
for ax in fig.get_axes():
    ax.axhline(235, c="0.35", ls=":")
../_images/6b94ca601b1ba29d7a7fdc608c340322175b4bc5d234b9f22fdc157ea4b0c301.png

Reverse well#

# Flipping depth and making CTT threshold greater than the background,
# we can get a reverse well.
ctt = Field(
    Blob(center=(0, 0), width=10, height=6, depth=-20)
).to_xarray(ctt_threshold=300)

# TAMS will still identify if we adjust the thresholds accordingly
(ce,) = tams.identify(ctt, ctt_threshold=300, ctt_core_threshold=310)

fig, ax = plt.subplots()

ctt.plot(ax=ax)
ce.plot(ax=ax, lw=2.5, ls=":", ec="black", fc="none");
../_images/4ac078a73af1c31f68cf459289e16ff34ea09a91e889c909c2349415588f70d2.png

Additive#

fld = Field(
    [
        Blob(center=(0, 0), width=10, height=6),
        Blob(center=(5, 0), width=10, height=6),
    ]
)

fig, [ax1, ax2] = plt.subplots(2, 1, figsize=(8, 6), sharex=True, sharey=True, layout="constrained")

kws = dict(vmin=170, vmax=270)
for ax, ctt in zip([ax1, ax2], [fld.to_xarray(), fld.to_xarray(additive=True)]):
    ctt.plot(ax=ax, **kws)
    ax.text(0.01, 0.98, f"min: {ctt.min().item():.2f}", ha="left", va="top", transform=ax.transAxes)
    (ce,) = tams.identify(ctt)
    ce.plot(ax=ax, lw=2.5, ls=":", ec="black", fc="none")

ax1.set_xlabel("")

for ax in [ax1, ax2]:
    ax.set_aspect("equal")
../_images/5d7c96a1a85a5258e789a11d3bd39007ca9488a9b73aa2dc58ea1d12e73a02c9.png

Sim#

A Sim is the evolution of a Field in time.

# Nothing (no blobs)
Sim().advance(2).to_xarray().plot(col="time");
../_images/68886f59fabe34352be6b545892ee33d1014c4dcf040132a9df913c14bba1d61.png
%%time

ctt = Sim(
    Field(
        [
            Blob(width=10).set_tendency(center=(2, 0)),
            Blob(center=(-20, -10), width=6).set_tendency(center=(5, 3), width=1),
        ],
        lat=(-15, 15, 61),
    ),
).advance(9).to_xarray()

ces = tams.identify(ctt)

fg = ctt.plot(col="time", cmap="inferno_r", col_wrap=5, aspect=1.35)
for ce, ax in zip(ces, fg.axs.flat):
    if not ce.empty:
        ce.plot(ax=ax, lw=2, ec="w", fc="none")
<timed exec>:11: UserWarning: No CEs identified for time steps: [8, 9]
CPU times: user 2.38 s, sys: 73.8 ms, total: 2.45 s
Wall time: 2.45 s
../_images/34cfc0da5b4ccb7426d71b5ec093722eef8470f80b683968ea99b552bd0f2459.png

👆 In TAMS v0.2, we lose track of the system(s) the last few time steps because the blobs are partially out of the domain and we drop open contours by default in the identify() stage. In this example, we could fix this by increasing the domain size when creating the Field.

tams.plot_tracked(
    tams.track(ces, ctt.time),
    size=5,
    alpha=0.35,
    add_colorbar=True,
    cmap="turbo",
)
../_images/1e54ee253a6fb5a727b7572b8fdd95eec47ceb9710fe854035bf21ccb9f4e25e.png