Finite-Time Lyapunov Exponent (FTLE)

In this example, we will compute the finite-time Laypunov exponent (FTLE) field for a co-rotating vortex pair.

using ILMPostProcessing
using ViscousFlow
using Plots

Setup the Co-rotating Vortices Problem

The grid Re number is chosen at 10.0 to speed up computations.

my_params = Dict()
my_params["Re"] = 300
xlim = (-3.0,3.0)
ylim = (-3.0,3.0)
my_params["grid Re"] = 10.0

g = setup_grid(xlim,ylim,my_params)

sys = viscousflow_system(g,phys_params=my_params)

σ = 0.1
x01, y01 = 1.0, 0.0
x02, y02 = -1.0, 0.0
A = 3
twogauss = SpatialGaussian(σ,σ,x01,y01,A) + SpatialGaussian(σ,σ,x02,y02,A)

u0 = init_sol(twogauss,sys)

plot(vorticity(u0,sys,0.0),sys)
Example block output

Solve the Problem

Step the integrator repeatedly until the solution is generated for t = (0.0, 18.0).

T = 19.0
tspan = (0.0,T)
integrator = init(u0,tspan,sys)

@time begin
    step!(integrator,T)
end

sol = integrator.sol

plt = plot(layout = (4,5), size = (800, 800), legend=:false)
tsnap = 0.0:1.0:T
for (i, t) in enumerate(tsnap)
    plot!(plt[i],vorticity(sol,sys,t),sys,levels=range(0.1,5,length=31))
end
plt
Example block output

Generate a Sequence of Velocity Fields

This step obtains the computed velocity field at a sequence of times, and stores them as a sequence of interpolatable fields in velseq. This will greatly speed up the steps in which we compute the flow deformation fields.

t_start = 0.0
t_end = 19.0
dt = timestep(u0,sys)
tr = t_start:dt:t_end

velxy = velocity_xy(sol,sys,tr) # Vector of interpolatable velocities
velseq = VectorFieldSequence(tr,velxy); # Bundle together with the time array

Generate Initial Conditions

Here, we generate a grid of initial locations from which to integrate trajectories.

X_MIN = -2.0
X_MAX = 2.0
Y_MIN = -2.0
Y_MAX = 2.0
dx = 0.01
ftlegrid = PhysicalGrid((X_MIN,X_MAX),(Y_MIN,Y_MAX),dx)
ftle_cache = SurfaceScalarCache(ftlegrid)
x0, y0 = x_grid(ftle_cache), y_grid(ftle_cache)
(CartesianGrids.Primal nodes in a (nx = 405, ny = 405) cell grid of type Float64 data
  Number of CartesianGrids.Primal nodes: (nx = 404, ny = 404), CartesianGrids.Primal nodes in a (nx = 405, ny = 405) cell grid of type Float64 data
  Number of CartesianGrids.Primal nodes: (nx = 404, ny = 404))

Solve the IVP and Generate FTLE Fields

Computing the FTLE Field at One Time Snapshot

To compute the particle displacement field, we choose an integration time T. We also choose a time t0 at which we desire to see the FTLE field. Note that we will compute both a forward and backward time FTLE field at t0, so we need to ensure we have velocity data available from t0 - T to t0 + T.

For integration purposes we use the forward Euler method, but any time marching method can be used.

T = 6.0
t0 = 6.0
6.0

The forward displacement field and FTLE field

xf, yf = displacement_field(velseq,x0,y0,(t0,t0+T),alg=Euler())

fFTLE = similar(x0)
compute_FTLE!(fFTLE,xf,yf,dx,dx,T);

and now the backward displacement field and FTLE field. We don't actually need to specify the alg because Euler() is the default.

xb, yb = displacement_field(velseq,x0,y0,(t0,t0-T))

bFTLE = similar(x0)
compute_FTLE!(bFTLE,xb,yb,dx,dx,T);

Plot the fields on top of each other

plot(fFTLE,ftle_cache,color=:inferno,size=(800,800))
plot!(bFTLE,ftle_cache,color=:viridis,xlim=(-2,2),ylim=(-2,2),title="FTLE, t = $t0", xlabel="x", ylabel="y")
Example block output

Computing the FTLE Fields at a Range of Times

Let's see some blocks of particles and how they move as the FTLE field evolves. The example places initial points at t = 6 near the unstable manifold (orange). We will compute the FTLE field after 4 time units (t = 10) and see the particles. The initial block of points is roughly colored according to which side of this manifold it is on.

xp_min = -1.0
xp_max = 0.0
yp_min = 0.5
yp_max = 1.5
dxp = 0.1
p_grid = PhysicalGrid((xp_min,xp_max),(yp_min,yp_max),dxp)
p_cache = SurfaceScalarCache(p_grid);
xp0, yp0 = x_grid(p_cache), y_grid(p_cache);

plot(fFTLE,ftle_cache,color=:inferno,size=(800,800))
plot!(bFTLE,ftle_cache,color=:viridis,xlim=(-2,2),ylim=(-2,2),title="FTLE, t = $t0", xlabel="x", ylabel="y")
scatter!(vec(xp0[1:5,1:end]),vec(yp0[1:5,1:end]))
scatter!(vec(xp0[8:end,1:end]),vec(yp0[8:end,1:end]))
Example block output

Now we will advance the block of particles to t = 10 and compute the FTLE fields at that instant.

t0_ftle = 10.0
xpf, ypf = displacement_field(velseq,xp0,yp0,(t0,t0_ftle))

xf, yf = displacement_field(velseq,x0,y0,(t0_ftle,t0_ftle+T))
compute_FTLE!(fFTLE,xf,yf,dx,dx,T)

xb, yb = displacement_field(velseq,x0,y0,(t0_ftle,t0_ftle-T))
compute_FTLE!(bFTLE,xb,yb,dx,dx,T);

Now plot the FTLE fields and particles

plot(fFTLE,ftle_cache,color=:inferno,size=(800,800))
plot!(bFTLE,ftle_cache,color=:viridis,xlim=(-2,2),ylim=(-2,2),title="FTLE, t = $t0_ftle", xlabel="x", ylabel="y")
scatter!(vec(xpf[1:5,1:end]),vec(ypf[1:5,1:end]))
scatter!(vec(xpf[8:end,1:end]),vec(ypf[8:end,1:end]))
Example block output

The code here creates a gif

@gif for t0_ftle in 6.5:0.5:12.0
    print(t0_ftle)

    xpf, ypf = displacement_field(velseq,xp0,yp0,(t0,t0_ftle))

    xf, yf = displacement_field(velseq,x0,y0,(t0_ftle,t0_ftle+T))
    compute_FTLE!(fFTLE,xf,yf,dx,dx,T)
    xb, yb = displacement_field(velseq,x0,y0,(t0_ftle,t0_ftle-T))
    compute_FTLE!(bFTLE,xb,yb,dx,dx,T)

    plot(fFTLE,ftle_cache,color=:inferno,size=(800,800))
    plot!(bFTLE,ftle_cache,color=:viridis,xlim=(-2,2),ylim=(-2,2),title="FTLE, t = $t0_ftle", xlabel="x", ylabel="y")
    scatter!(vec(xpf[1:5,1:end]),vec(ypf[1:5,1:end]))
    scatter!(vec(xpf[8:end,1:end]),vec(ypf[8:end,1:end]))

end every 1 fps = 2

This page was generated using Literate.jl.