2D maps of structure functions¶
This example will demonstrate how to create maps of various structure functions from a 2D velocity field. These maps describe how the structure functions vary with separation distance and separation direction.
General procedure:
Set up plot environment & load/format the velocity data set
Calculate 2D maps of structure functions
Format output to create 2D (x-y) map of structure functions
Plot 2D maps of structure functions
Validate 2D-map structure functions against other modules
Setup plot environment & load/format the velocity data set¶
We will use h5py
to load a .jld2
file, the output from GeophysicalFlows.jl
, a numerical ocean simulator written in Julia. The data consists of 2D (horizontal) fields simulated a periodic domain. There are multiple snapshots of this data, corresponding to different times. We will
[1]:
import warnings
import h5py
import matplotlib_inline.backend_inline
import seaborn as sns
warnings.filterwarnings("ignore") # Ignore warnings for the purpose of this tutorial
sns.set_style(style="white")
sns.set_context("talk")
matplotlib_inline.backend_inline.set_matplotlib_formats("png", dpi=200)
f = h5py.File("example_data/2layer_128.jld2", "r")
grid = f["grid"]
snapshots = f["snapshots"]
# Initialize the grid of x and y coordinates
x = grid["x"][()]
y = grid["y"][()]
# Isolate the top layer [0] of the final snapshot ['20050'] for the example calculations
u_data_for_example = snapshots["u"]["20050"][0]
v_data_for_example = snapshots["v"]["20050"][0]
Make a couple of quick plots to see the velocity fields.
Calculate 2D maps of structure functions¶
By default this calculates the velocity advective structure function, but we also specify the traditional longitudinal structure functions (LLL and LL; the latter is calculated by default when LLL is calculated)
[2]:
import fluidsf
sf_2D_maps = fluidsf.generate_sf_maps_2d(
u_data_for_example, v_data_for_example, x, y, sf_type=["ASF_V", "LLL", "LL"]
)
Format output to create 2D (x-y) map of structure functions¶
For computational efficiency, the package only calculates structure functions for separation vectors \(\bf{r}\) where \(\bf{r}\cdot\bf{\hat{x}} \ge 0\). The structure functions for the other half of the domain (\(\bf{r}\cdot\bf{\hat{x}} \lt 0\)) can be diagnosed from reflectional symmetry, noting that the structure functions here have the property \(SF(\mathbf{r})=SF(-\mathbf{r})\).
[3]:
# Now we construct a 2D map of the advective structure function
# To do this, note that a separation vector with a positive x and y component
# is equivalent to a separation vector with negative x and y component,
# similarly the negative-y positive-x quadrant maps to positive-y negative-x
import matplotlib.pyplot as plt
import numpy as np
# First we construct these polar map arrays by appending these quadrants to the two
# already calculated
TwoD_map_x_separations = np.append(
-np.flip(sf_2D_maps["x_separations"]), sf_2D_maps["x_separations"], axis=0
)
TwoD_map_y_separations = np.append(
-np.flip(sf_2D_maps["y_separations"]), sf_2D_maps["y_separations"], axis=0
)
TwoD_map_advective_SF = np.append(
np.flip(sf_2D_maps["SF_advection_velocity_xy"]),
sf_2D_maps["SF_advection_velocity_xy"],
axis=0,
)
TwoD_map_LL_SF = np.append(
np.flip(sf_2D_maps["SF_LL_xy"]), sf_2D_maps["SF_LL_xy"], axis=0
)
TwoD_map_LLL_SF = np.append(
np.flip(sf_2D_maps["SF_LLL_xy"]), sf_2D_maps["SF_LLL_xy"], axis=0
)
Plot 2D maps of structure functions¶
We plot maps of the three velocity-based statistics that were diagnosed by the package:
Advective structure function,
Third-order longitudinal structure function, and
Second-order longitudinal structure function
[4]:
fig, axs = plt.subplots(1, 3, figsize=(15, 5), layout="constrained")
Advective = axs[0].pcolormesh(
TwoD_map_x_separations,
TwoD_map_y_separations,
TwoD_map_advective_SF,
vmin=-4,
vmax=4,
cmap=plt.cm.RdBu_r,
)
axs[0].set_xlabel("x separation [m]")
axs[0].set_ylabel("y separation [m]")
axs[0].set_title("ASF$_{V}$ [m$^2$ s$^{-3}$]")
cbar = fig.colorbar(Advective, ax=axs[0], location="bottom")
# ticks = cbar.get_ticks()
# # Remove -10e-5 and 10e-5 from the ticks
# ticks = [tick for tick in ticks if tick not in [-10e-4, 10e-4]]
# cbar.set_ticks(ticks)
axs[0].axhline(0, color="black", lw=0.5)
axs[0].axvline(0, color="black", lw=0.5)
LLL = axs[1].pcolormesh(
TwoD_map_x_separations,
TwoD_map_y_separations,
TwoD_map_LLL_SF,
vmin=-4,
vmax=4,
cmap=plt.cm.BrBG,
)
axs[1].set_xlabel("x separation [m]")
axs[1].set_title("SF$_{LLL}$ [m$^3$ s$^{-3}$]")
cbar = fig.colorbar(LLL, ax=axs[1], location="bottom")
# cbar.set_ticks([-0.05, 0, 0.05])
axs[1].axhline(0, color="black", lw=0.5)
axs[1].axvline(0, color="black", lw=0.5)
LL = axs[2].pcolormesh(
TwoD_map_x_separations,
TwoD_map_y_separations,
TwoD_map_LL_SF,
# norm=LogNorm(vmin=1e0, vmax=1e1),
cmap=plt.cm.RdPu,
vmin=0,
vmax=10,
)
axs[2].set_xlabel("x separation [m]")
axs[2].set_title("SF$_{LL}$ [m$^2$ s$^{-2}$]")
cbar = fig.colorbar(LL, ax=axs[2], location="bottom")
# cbar.set_ticks([0, 1, 2])
axs[2].axhline(0, color="black", lw=0.5)
axs[2].axvline(0, color="black", lw=0.5);
Plot some other useful output: separation distance and separation angle¶
The 2D map modules also output separation distance and angle. These could be useful for users, for example when binning or performing analyses. We visualize them here to demonstrate how to access and use these arrays
[5]:
fig, axs = plt.subplots(1, 2, figsize=(9, 4), layout="constrained")
distances = axs[0].pcolormesh(
sf_2D_maps["x_separations"],
sf_2D_maps["y_separations"],
sf_2D_maps["separation_distances"],
cmap=plt.cm.viridis,
)
axs[0].set_title("Separation Distance [m]")
axs[0].set_xlabel("x separation")
axs[0].set_ylabel("y separation")
# Make a colorbar for the ContourSet returned by the contourf call.
cbar = fig.colorbar(distances, ax=axs[0])
angles = axs[1].pcolormesh(
sf_2D_maps["x_separations"],
sf_2D_maps["y_separations"],
sf_2D_maps["separation_angles"],
cmap=plt.cm.bwr,
)
axs[1].set_title("Angle [radians]")
axs[1].set_xlabel("x separation")
# Make a colorbar for the ContourSet returned by the contourf call.
cbar = fig.colorbar(angles, ax=axs[1]);
Validate 2D-map structure functions against other modules¶
First, let us check that the polar code calculates the same zonal (purely x-separated) and meridional (purely y-separated) advective structure functions as the core 1D calculation modules. Then we will repeat for the third- and second-order longitudinal structure functions. We will start by calculating the zonal and meridional structure functions directly.
[6]:
sf_zonal_meridional = fluidsf.generate_structure_functions_2d(
u_data_for_example, v_data_for_example, x, y, sf_type=["ASF_V", "LLL", "LL"]
)
[7]:
# Contrasting advective structure functions
import matplotlib.pyplot as plt
fig, (ax1) = plt.subplots()
ax1.semilogx(
sf_zonal_meridional["x-diffs"],
sf_zonal_meridional["SF_advection_velocity_x"],
label=r"Zonal (direct calc.)",
color="k",
)
ax1.semilogx(
sf_zonal_meridional["y-diffs"],
sf_zonal_meridional["SF_advection_velocity_y"],
label=r"Meridional (direct calc.)",
color="tab:blue",
)
ax1.semilogx(
sf_zonal_meridional["y-diffs"],
sf_2D_maps["SF_advection_velocity_xy"][:, 64],
label=r"Zonal from polar",
color="tab:red",
linestyle="dashed",
)
ax1.semilogx(
sf_zonal_meridional["y-diffs"],
sf_2D_maps["SF_advection_velocity_xy"][0, 64:],
label=r"Meridional from polar",
color="y",
linestyle="dashed",
)
ax1.set_ylabel(r"Advective SF [m$^2$ s$^{-3}$]")
ax1.set_xlabel(r"Separation distance [m]")
ax1.set_xlim(3e-2, 3e0)
ax1.legend()
plt.hlines(0, 3e-2, 3e0, color="k", linestyle="dashed", alpha=0.3)
plt.title("Advective velocity structure functions");
[8]:
# Then repeat check for third-order longitudinal SF
import matplotlib.pyplot as plt
fig, (ax1) = plt.subplots()
ax1.semilogx(
sf_zonal_meridional["x-diffs"],
sf_zonal_meridional["SF_LLL_x"],
label=r"Zonal (direct calc.)",
color="k",
)
ax1.semilogx(
sf_zonal_meridional["y-diffs"],
sf_zonal_meridional["SF_LLL_y"],
label=r"Meridional (direct calc.)",
color="tab:blue",
)
ax1.semilogx(
sf_zonal_meridional["y-diffs"],
sf_2D_maps["SF_LLL_xy"][:, 64],
label=r"Zonal from polar",
color="tab:red",
linestyle="dashed",
)
ax1.semilogx(
sf_zonal_meridional["y-diffs"],
sf_2D_maps["SF_LLL_xy"][0, 64:],
label=r"Meridional from polar",
color="y",
linestyle="dashed",
)
ax1.set_ylabel(r"SF$_{LLL}$ [m$^2$ s$^{-3}$]")
ax1.set_xlabel(r"Separation distance [m]")
ax1.set_xlim(3e-2, 3e0)
ax1.legend()
plt.hlines(0, 3e-2, 3e0, color="k", linestyle="dashed", alpha=0.3)
plt.title("Third-order Longitudinal velocity SFs");
[9]:
# Then repeat check for second-order longitudinal SF
import matplotlib.pyplot as plt
fig, (ax1) = plt.subplots()
ax1.semilogx(
sf_zonal_meridional["x-diffs"],
sf_zonal_meridional["SF_LL_x"],
label=r"Zonal (direct calc.)",
color="k",
)
ax1.semilogx(
sf_zonal_meridional["y-diffs"],
sf_zonal_meridional["SF_LL_y"],
label=r"Meridional (direct calc.)",
color="tab:blue",
)
ax1.semilogx(
sf_zonal_meridional["y-diffs"],
sf_2D_maps["SF_LL_xy"][:, 64],
label=r"Zonal from polar",
color="tab:red",
linestyle="dashed",
)
ax1.semilogx(
sf_zonal_meridional["y-diffs"],
sf_2D_maps["SF_LL_xy"][0, 64:],
label=r"Meridional from polar",
color="y",
linestyle="dashed",
)
ax1.set_ylabel(r"SF$_{LL}$ [m$^2$ s$^{-3}$]")
ax1.set_xlabel(r"Separation distance [m]")
ax1.set_xlim(3e-2, 3e0)
ax1.legend()
plt.hlines(0, 3e-2, 3e0, color="k", linestyle="dashed", alpha=0.3)
plt.title("Second-order Longitudinal velocity SFs");