Track waves
This demo shows how to track wave and plot the dispersion relation On a equatorial plane, B ∥ ẑ, we can choose an arbitrary line in-plane. On a meridional plane, B is in-plane, we can find a local line region ∥ B and ⟂ B.
Currently it only works on a equatorial plane.
Usage:
julia -t 4 demo_wave_tracing_mt.jl
or
JULIA_NUM_THREADS=4 julia demo_wave_tracing_mt.jl
using Vlasiator, VlasiatorPyPlot
using Vlasiator: qᵢ, μ₀, c, mᵢ, ϵ₀, RE
using DSP, FFTW, ImageFiltering, Interpolations
using Statistics: mean
using LinearAlgebra
## Types
struct Variables
varnames::Vector{String}
varnames_print::Vector{String}
components::Vector{Int}
end
## Methods
ispolar(meta::MetaVLSV) = findfirst(==(1), meta.ncells) == 2
"Extract `component` of variable `varname` at `cellids` from `files`."
function extract_var(files, varname, cellids, distances, component=0)
sample_loc = range(distances[1], distances[end], length=length(distances))
var = zeros(length(distances), length(files))
Threads.@threads for i in eachindex(files)
meta = load(files[i])
if component == 0
var_line = readvariable(meta, varname, cellids)[:]
else
var_line = readvariable(meta, varname, cellids)[component,:]
end
#TODO: do we need high order interpolations?
interp_linear = LinearInterpolation(distances, var_line)
var_line_resample = interp_linear.(sample_loc)
var_line_smooth = imfilter(var_line_resample, Kernel.gaussian((3,)))
var[:,i] = var_line_smooth
end
var
end
"CFL constrained normalized frequency."
dispersion_CFL(k, dx, dt, di, ωci) = dx/dt * abs(k) /(di * ωci)
"Normalized frequency of fast magnetosonic waves along angle `θ` with Doppler shift."
function dispersion_fast_perp(k, θ, vS, vA, v, di, ωci)
ω = zeros(length(k))
turnindex = findfirst(>=(0), k)
vbulkpar = v[1]*cos(θ) + v[2]*sin(θ)
dv1 = √(vS^2 + vA^2) + vbulkpar # propagate along +θ direction
dv2 = -√(vS^2 + vA^2) + vbulkpar # propagate along -θ direction
if dv1 < 0; dv1 = 0.0; end
if dv2 > 0; dv2 = 0.0; end
for i in 1:turnindex-1
ω[i] = dv2*k[i] /(di * ωci)
end
for i in turnindex:length(k)
ω[i] = dv1*k[i] /(di * ωci)
end
ω
end
"Normalized frequency of bulk flow along tilted angle `θ`."
function dispersion_bulk_flow(k, θ, v, di, ωci)
ω = zeros(length(k))
turnindex = findfirst(>=(0), k)
vbulkpar = v[1]*cos(θ) + v[2]*sin(θ)
irange = vbulkpar > 0 ? (turnindex:length(k)) : (1:turnindex)
for i in irange # otherwise 0
ω[i] = vbulkpar*k[i] /(di * ωci)
end
ω
end
"Return the index in sorted `vec` with value closest to `x`."
function searchsortednearest(vec, x)
idx = searchsortedfirst(vec, x)
if idx == 1
return idx
elseif idx > length(vec)
return length(vec)
elseif vec[idx] == x
return idx
elseif abs(vec[idx]-x) < abs(vec[idx-1]-x)
return idx
else
return idx-1
end
end
"Obtain fast mode and bulk speed along line `angle` w.r.t. x-axis for cell ID `cid` in
`meta`."
function getCharacteristicSpeeds(meta, cid, angle)
n = readvariable(meta, "proton/vg_rho", cid)
B = readvariable(meta, "vg_b_vol", cid)
p = readvariable(meta, "vg_pressure", cid)
v = readvariable(meta, "proton/vg_v", cid)
Bmag = norm.(eachcol(B))
vA = @. Bmag / √(μ₀ * n * mᵢ) # Alfven speed, [m/s]
vS = @. √(γ * p / (n * mᵢ)) # sonic speed, [m/s]
vFast = @. √(vA^2 + vS^2)
vBulk = @. v[1,:]*cos(angle) + v[2,:]*sin(angle)
vFast, vBulk
end
"Trace along line with `angle` w.r.t. x-axis at possible wave speeds."
function tracewave(xstart, angle, files, dtfile, t, cellids, distances)
x1 = zeros(length(t)); x1[1] = xstart
x2 = copy(x1)
x3 = copy(x1)
tfile = let
tfilefirst = getproperty(load(files[1]), :time)
tfilelast = getproperty(load(files[end]), :time)
tfilefirst:dtfile:tfilelast
end
ifile = 1 # file index tracker
dt = t[2] - t[1] # timestep
for it in eachindex(t)[1:end-1]
# Find the closest output saving time to t[it], 0th order interpolation
for i = ifile:length(tfile)
if abs(tfile[i] - t[it]) < 0.5*dtfile
ifile = i
break
end
end
meta = load(files[ifile])
# Find the closest cell to the wave location
cid = let
c1 = searchsortednearest(distances, x1[it])
c2 = searchsortednearest(distances, x2[it])
c3 = searchsortednearest(distances, x3[it])
cellids[c1], cellids[c2], cellids[c3]
end
vFast, vBulk = getCharacteristicSpeeds(meta, cid, angle)
x1[it+1] = x1[it] + dt * vBulk[1]
x2[it+1] = x2[it] + dt * (vBulk[2] + vFast[2])
x3[it+1] = x3[it] + dt * (vBulk[3] - vFast[3])
end
x1, x2, x3
end
"Evaluate the average quantities at `cellids` at the middle of `files`."
function estimate_meanstates(files, cellids)
# Select the snapshot in the middle
nfile = length(files)
meta = load(files[nfile÷2+1])
n = readvariable(meta, "proton/vg_rho", cellids)
v = readvariable(meta, "proton/vg_v", cellids)
p = readvariable(meta, "vg_pressure", cellids)
if hasvariable(meta, "vg_b_vol")
B = readvariable(meta, "vg_b_vol", cellids)
elseif hasvariable(meta, "fg_b")
B = readvariable(meta, "fg_b", cellids)
else
B = readvariable(meta, "b", cellids)
end
vperp = @view v[1:2,:]
vpar = @view v[3,:]
# Obtain average states
n̄ = mean(n)
p̄ = mean(p)
v̄par = mean(vpar)
v̄perp = @views [mean(vperp[1,:]), mean(vperp[2,:])]
# Characteristic parameters
Bnorm = @views abs(mean(B[3,:]))
di = √(mᵢ*ϵ₀/(n̄))*c/qᵢ # ion inertial length, [m]
ωci = qᵢ*Bnorm/mᵢ # [/s]
v̄A = Bnorm / √(μ₀ * n̄ * mᵢ) # Alfven speed, [m/s]
v̄S = √(γ * p̄ / (n̄ * mᵢ)) # sonic speed, [m/s]
println("--------------------------------------------------")
println("* Average states along the line at the middle snapshot")
println("Density : ", rpad(round(n̄/1e6; digits=2), 8), "amu/cc")
println("Pressure : ", rpad(round(p̄*1e9; digits=3), 8), "nPa")
println("Parallel velocity : ", rpad(round(v̄par/1e3; digits=2), 8), "km/s")
println("Perpendicular velocity: ", rpad(round.(v̄perp/1e3; digits=2), 8), "km/s")
println("Flow angle : ", rpad(round(atand(v̄perp[2], v̄perp[1]); digits=2), 8),
"degrees")
println("Ion inertial length : ", rpad(round(di/1e3; digits=2), 8), "km")
println("Gyrofrequency : ", rpad(round(ωci; digits=2), 8), "Hz")
println("Alfven speed : ", rpad(round(v̄A/1e3; digits=2), 8), "km/s")
println("Sonic speed : ", rpad(round(v̄S/1e3; digits=2), 8), "km/s")
println("--------------------------------------------------")
di, ωci, v̄A, v̄S, v̄perp
end
"Plot the process of wave checks."
function plot_dispersion(files, vars, cellids, distances, coords, meanstates, dtfile, Δt,
outdir)
# Parameters
nfile = length(files)
npoints = length(cellids)
nt = nfile ÷ 2 + 1
varnames = vars.varnames
varnames_print = vars.varnames_print
components = vars.components
di, ωci, v̄A, v̄S, v̄perp = meanstates
tfile1st = load(files[1]).time
# Output timestamps
t = [dtfile * ifile + tfile1st for ifile in 0:nfile-1]
# Selected line tilted angle ∈ [-π, π]
θ = atan(coords[2,end] - coords[2,1], coords[1,end] - coords[1,1])
# Sample width, [m]
dx = norm(coords[:,end] .- coords[:,1]) /(npoints - 1)
println("spatial resolution: ", round(dx/1e3; digits=2), " km")
# Trace wave along the line in a space-time domain
twave = let
tstart = 400.0
tend = 430.0
tstart:200Δt:tend
end
xwave = tracewave(distances[npoints÷2+1], θ, files, dtfile, twave, cellids, distances)
twave2 = let
tstart = 600.0
tend = 630.0
tstart:200Δt:tend
end
xwave2 = tracewave(distances[npoints÷2+1], θ, files, dtfile, twave2, cellids, distances)
# Dispersion plotting ranges
kmin = -π / dx * di # minimum wave number
kmax = π / dx * di # maximum wave number
ωmin = 0 # minimum angular frequency
ωmax = π / dtfile / ωci # maximum angular frequency
# Only the 1st quadrature
krange = range(kmin, kmax, length=npoints)
ωrange = range(ωmin, ωmax, length=nt)
axisunit = EARTH
# Precalculated lines
ωCFL = dispersion_CFL.(krange, dx, Δt, di, ωci)
ωfast = dispersion_fast_perp(krange, θ, v̄S, v̄A, v̄perp, di, ωci)
ωbulk = dispersion_bulk_flow(krange, θ, v̄perp, di, ωci)
# Window filtering for avoiding spectral leakage
window = hanning(npoints) * hanning(nfile)'
meta = load(files[end])
for i in eachindex(varnames)
println("variable name: ", varnames[i])
var = extract_var(files, varnames[i], cellids, distances, components[i])
# 2DFFT
F̃ = window .* var |> fft |> fftshift
# Visualization
fig = figure(figsize=(12,12), constrained_layout=false)
ax = [subplot(221), subplot(223), subplot(222), subplot(224, projection="3d")]
dispersion = reverse!(abs.(F̃.*F̃)[:, end-nt+1:end]', dims=1)
im1 = ax[1].pcolormesh(krange, ωrange, dispersion, norm=matplotlib.colors.LogNorm())
ax[1].plot([krange[1], 0.0, krange[end]], [ωCFL[1], 0.0, ωCFL[end]], "--",
linewidth=1.0, color="k", label="CFL Condition")
ax[1].plot(krange, ωfast, "--",
linewidth=1.2, color="#d62728", label="Fast Mode")
ax[1].plot(krange, ωbulk, "--",
linewidth=1.2, color="#9467bd", label="Flow Speed")
ax[1].set_xlim(kmin, kmax)
ax[1].set_ylim(ωmin, ωmax)
cb = colorbar(im1; ax=ax[1])
cb.ax.tick_params(direction="in")
ax[1].legend(;fontsize="x-small")
ax[1].set_xlabel(L"$k_\perp \cdot d_i$")
ax[1].set_ylabel(L"$\omega/\Omega_{ci}$")
ax[1].set_title(L"$k_\perp$ angle w.r.t. x = %$θ")
im2 = ax[2].pcolormesh((distances .+ coords[1,1])./RE, t, var')
ax[2].plot((xwave[1] .+ coords[1,1])./RE, twave, ".--",
color="#d62728", label=L"$V_{bulk}$")
ax[2].plot((xwave[2] .+ coords[1,1])./RE, twave, ".--",
color="#9467bd", label=L"$V_{bulk} + V_{fast}$")
ax[2].plot((xwave[3] .+ coords[1,1])./RE, twave, ".--",
color="#ff7f0e", label=L"$V_{bulk} - V_{fast}$")
ax[2].plot((xwave2[1] .+ coords[1,1])./RE, twave2, ".--",
color="#d62728")
ax[2].plot((xwave2[2] .+ coords[1,1])./RE, twave2, ".--",
color="#9467bd")
ax[2].plot((xwave2[3] .+ coords[1,1])./RE, twave2, ".--",
color="#ff7f0e")
cb = colorbar(im2; ax=ax[2])
cb.ax.tick_params(direction="in")
ax[2].set_xlim(coords[1,1]/RE, coords[1,end]/RE)
ax[2].legend(loc="upper center", bbox_to_anchor=(0.5, -0.13),
fancybox=true, shadow=true, ncol=3)
ax[2].set_xlabel(L"x [$R_E$]")
ax[2].set_ylabel(L"time [s]")
ax[2].set_title("$(varnames_print[i])_$(components[i])")
pArgs = Vlasiator.set_args(meta, varnames[i], axisunit; normal=:none)
x, y = Vlasiator.get_axis(pArgs)
data = Vlasiator.prep2d(meta, varnames[i], components[i])'
cnorm, cticks = set_colorbar(Linear, -Inf, Inf, data)
cmesh = ax[3].pcolormesh(x, y, data, norm=cnorm)
ax[3].set_xlabel(pArgs.strx)
ax[3].set_ylabel(pArgs.stry)
ax[3].set_aspect(1)
cb = colorbar(cmesh; ax=ax[3], ticks=cticks, fraction=0.046, pad=0.04)
cb.ax.set_ylabel(pArgs.cb_title)
cb.ax.tick_params(direction="in")
@views ax[3].scatter(coords[1,:]./RE, coords[2,:]./RE; s=0.2, color="k")
xCoord = (distances .+ coords[1,1])./RE
# meshgrid
X = [x for _ in t, x in xCoord]
Y = [y for y in t, _ in xCoord]
ax[4].view_init(elev=40., azim=-30.)
ax[4].plot_surface(X, Y, var', cmap=matplotlib.cm.turbo, antialiased=false)
ax[4].set_xlabel(L"x [$R_E$]"; fontsize="small")
ax[4].set_ylabel("time [s]"; fontsize="small")
#ax[4].set_zlabel(pArgs.cb_title)
ax[4].tick_params(labelsize="small")
outname = "dispersion_$(varnames_print[i])_$(components[i]).png"
savefig(joinpath(outdir, outname), bbox_inches="tight")
close(fig)
end
end
function main()
outdir = "../out/"
γ = 5 / 3
xStart = [10.0, 0.0, 0.0].*RE
xEnd = [13.3, 0.0, 0.0].*RE
varnames = ["proton/vg_rho", "vg_b_vol", "vg_e_vol", "vg_pressure"]
varnames_print = ["rho", "b", "e", "p"]
components = [0, 3, 1, 0] # 0 for scalar, 1-3 for vector components
dir = "../run_rho2_bz-5_timevarying_startfrom300s"
files = filter(contains(r"^bulk.*\.vlsv$"), readdir(dir))
vars = Variables(varnames, varnames_print, components)
dtfile = 0.5 # output interval, [s]
Δt = 0.0147176 # discrete time step from runlog, [s]
meta = load(files[1])
if ispolar(meta) # polar plane
@error "not implemented!"
end
cellids, distances, coords = getcellinline(meta, xStart, xEnd)
meanstates = estimate_meanstates(files, cellids)
println("number of extracted points: ", length(cellids))
println("xStart: ", xStart)
println("xEnd: ", xEnd)
tbegin = load(files[1]).time
tend = load(files[end]).time
println("time from $tbegin to $tend s")
@time plot_dispersion(files, vars, cellids, distances, coords, meanstates, dtfile, Δt,
outdir)
end
main()
This page was generated using DemoCards.jl.