Ensemble tracing with extra saving

Source code compat Author Update time

This example demonstrates tracing multiple protons in an analytic E field and numerical B field. See Demo: single tracing with additional diagnostics for explaining the unit conversion. Also check Demo: Ensemble for basic usages of the ensemble problem.

The output_func argument can be used to change saving outputs. It works as a reduction function, but here we demonstrate how to add additional outputs. Besides the regular outputs, we also save the magnetic field along the trajectory, together with the parallel velocity.

using TestParticle
using TestParticle: qᵢ, mᵢ
using OrdinaryDiffEq
using StaticArrays
using Statistics
using LinearAlgebra: normalize, ×, ⋅
using Random
using CairoMakie

seed = 1 # seed for random number
Random.seed!(seed)

"Set initial state for EnsembleProblem."
function prob_func(prob, i, repeat)
   B0 = prob.p[3](prob.u0)
   B0 = normalize(B0)

   Bperp1 = SA[0.0, -B0[3], B0[2]] |> normalize
   Bperp2 = B0 × Bperp1 |> normalize

   # initial azimuthal angle
   ϕ = 2π*rand()
   # initial pitch angle
   θ = acos(0.5)

   sinϕ, cosϕ = sincos(ϕ)
   u = @. (B0*cos(θ) + Bperp1*(sin(θ)*cosϕ) + Bperp2*(sin(θ)*sinϕ)) * U₀

   prob = @views remake(prob; u0=[prob.u0[1:3]..., u...])
end

function getmeanB(B)
   B₀sum = eltype(B)(0)
   for k in axes(B, 4), j in axes(B, 3), i in axes(B, 2)
      B₀sum += B[1,i,j,k]^2 + B[2,i,j,k]^2 + B[3,i,j,k]^2
   end

   sqrt(B₀sum / prod(size(B)[2:4]))
end

# Number of cells for the field along each dimension
nx, ny, nz = 4, 6, 8
# Spatial coordinates given in customized units
x = range(0, 1, length=nx)
y = range(0, 1, length=ny)
z = range(0, 1, length=nz)
# Numerical magnetic field given in customized units
B = Array{Float32, 4}(undef, 3, nx, ny, nz)

B[1,:,:,:] .= 0.0
B[2,:,:,:] .= 0.0
B[3,:,:,:] .= 2.0

# Reference values for unit conversions between the customized and dimensionless units
const B₀ = getmeanB(B)
const U₀ = 1.0
const l₀ = 2*nx
const t₀ = l₀ / U₀
const E₀ = U₀ * B₀;

Convert from customized to default dimensionless units

# Dimensionless spatial extents [l₀]
x /= l₀
y /= l₀
z /= l₀
# For full EM problems, the normalization of E and B should be done separately.
B ./= B₀
E(x) = SA[0.0/E₀, 0.0/E₀, 0.0/E₀]

# By default User type assumes q=1, m=1
# bc=2 uses periodic boundary conditions
param = prepare(x, y, z, E, B; species=User, bc=2)

# Initial condition
stateinit = let
   x0 = [0.0, 0.0, 0.0] # initial position [l₀]
   u0 = [1.0, 0.0, 0.0] # initial velocity [v₀]
   [x0..., u0...]
end
# Time span
tspan = (0.0, 2π) # one averaged gyroperiod based on B₀

saveat = tspan[2] / 40 # save interval

prob = ODEProblem(trace_normalized!, stateinit, tspan, param)

"Set customized outputs for the ensemble problem."
function output_func(sol, i)
   getB = sol.prob.p[3]
   b = getB.(sol.u)

   μ = [@views b[i] ⋅ sol[4:6, i] / sqrt(sum(x -> x^2, b[i])) for i in eachindex(sol)]

   (sol.u, b, μ), false
end

# Number of trajectories
trajectories = 2

ensemble_prob = EnsembleProblem(prob; prob_func, output_func, safetycopy=false)
sols = solve(ensemble_prob, Vern9(), EnsembleThreads(); trajectories, saveat);

Visualization

f = Figure(fontsize = 18)
ax = Axis3(f[1, 1],
   title = "Proton trajectories",
   xlabel = "X",
   ylabel = "Y",
   zlabel = "Z",
   aspect = :data,
)

for i in eachindex(sols)
   xp = [s[1] for s in sols[i][1]]
   yp = [s[2] for s in sols[i][1]]
   zp = [s[3] for s in sols[i][1]]
   lines!(ax, xp, yp, zp, label="$i")
end


This page was generated using DemoCards.jl and Literate.jl.