Examples
Basic use of with analytical fields
This example demonstrates the most basic use of TestParticles
. The electromagnetic fields are analytically prescribed, and the initial conditions of the particle ensemble are pre-determined. In this case, the only tool that is needed from TestParticles
are the equations of motion for the particles. The rest is setup for solving an EnsembleProblem
from DifferentialEquations
.
using TestParticles: lorentzforce!
using DifferentialEquations
"""
We will in this example simulate two charged particles embedded in an
electromagnetic field.
"""
eom = lorentzforce! # The equations of motion of charge particles
# in a collisionless electromagntic field is the
# Lorentz force.
# Here we define the initial conditions of our two particles. The initial
# conditions are 3 position- and 3 velocity-components, which could be
# represented in a 6-component state vector.
ic = [[0.0, 1.0, 0.0, 1.0, 0.0, 0.0], [0.0, 0.5, 0.0, -0.5, 0.0, 0.0]]
# The parameters of the Lorentz equation is the particle's charge, mass and
# functor a giving the electromagnetic fields at the particle position.
mass = 1
charge = [1, -1]
struct StaticUniformElectromagneticField
bx::Real
by::Real
bz::Real
ex::Real
ey::Real
ez::Real
end
function (field::StaticUniformElectromagneticField)(x,y,z)
return [field.bx, field.by, field.bz, field.ex, field.ey, field.ez]
end
emfield = StaticUniformElectromagneticField(0,0,1,0,1,0)
tspan = (0,10) # Define the simulation time-span
npart = 2 # Define the number of particles in the simulation
prob = ODEProblem(eom, ic[1], tspan) # Create an initial ODE-problem
# Define a problem function, which will define a new ODE-problem for each
# particle.
function prob_func(prob, i, repeat)
remake(prob, u0=ic[i], p=(mass, charge[i], emfield))
end
# Define an output function - what to be saved from each particle solution
# The ODESolution type contains a lot of metadata, including alogorithm
# choice, number of algorithm switches, etc. In a large ensemble, this
# overhead will quickly consume a lot of memory.
#
# In this output function we save only the initial and final state of the
# particle, in addition to its timesteps. We set the required "rerun" return
# argument to false.
function output_func(sol,i)
return ([first(sol), last(sol)], sol.t), false
end
# Define an Ensamble-problem, to simulate the ensemble of particles
ensamble_prob = EnsembleProblem(
prob # Initial problem
;
prob_func=prob_func,
output_func=output_func
)
# Run the simulation
sol = DifferentialEquations.solve(
ensamble_prob,
EnsembleSerial()
;
trajectories=npart
)
Gridded fields and statistical sampling
The following example demonstrates a more complex test particle simulation, using
- interpolated electromagnetic field,
- initial positions drawn from a proposal probability density distribution.
- importance weights corresponding to a target distribution,
- initial velocities drawn from Maxwell-Boltzmann distributions,
- the guiding centre approximation (GCA) as equations of motion,
- callbacks terminating the particle if the GCA is invalid or the particle is out of bounds,
- a reduction and output function that stores the initial and final state of the solution to an HDF5-file, along with some observables,
- a large particle ensemble.
This script assumes you have the external electromangetic field and temperature of the system, and the as interpolation objects stored in JLD2 files. target_itp
and proposal_itp
are probability density distributions
used to draw new particle initial conditions.
n
using TestParticles
using DifferentialEquations
using Interpolations
using JLD2
JLD2.@load "electromagneticfield.jld2" em_fields_itp
JLD2.@load "temperature.jld2" temperature_itp
JLD2.@load "target_distribution" target_itp
JLD2.@load "proposal_distribution" proposal_itp
tspan = (0, 1) # Temporal bounds of the simulation [s]
xbounds = (0.0, 31.958047e6) # Spatial bounds [m]
zbounds = (-32e6, 0.00) # This is a 2.5D simulation
# Particle parameters
charge = -TestParticles.e # Charge of particles
mass = TestParticles.m_e # Mass of particles
eom = TestParticles.gca_2Dxz! # Equation of motion
npart = 10_000_000
rng = MersenneTwister() # Random number generator
alg = Rosenbrock23() # Numerical integration algorithm
# Parameters for saving data
expname = "test"
datadir = "data"
# Size of a "particle batch"
batch_size = 1_000_000
nbatches = ceil(Int, npart / batch_size)
# Maximum value of the proposal distribution is needed for the rejection
# algorithm
maxval = maximum(propdistr.itp.itp.coefs)
samplingdomain = [xbounds, zbounds]
#
# Define Callbacks
#
# Terminate particle when it is outside bounds
out_cb = DiscreteCallback(
tp.OutOfDomainCondition_2Dxz(xbounds, zbounds),
tp.outofdomainaffect!
)
# Terminate the particle when EoM is invalid
gca_cb = tp.GCABreakDownCB("lowmem", "2Dxz", tolerance=0.001)
cbset = CallbackSet(out_cb, gca_cb)
# Define the problem function that draws particle initial conditions.
# `DposMBvel` draws position from the proposal distribition and velocity from
# a Maxwell-Boltzmann distribution at the position. It also gives the particle
# a statistical weight according to the ratio between the proposal distribution
# and the target distribution (importance weighing).
prob_func = DposMBvel(
rng, proposal_itp, target_itp, samplingdomain, maxval, temperature_itp
)
output_func = output_func_max_lightweight
# The output function determines what to with each particle solution.
# `output_func_max_lightweight` keeps only the initial and final state of the
# solution, together with a crude estimate of the largest experienced Larmor
# radius, smallest experienced characteristic length scale of the magnetic
# field, and largest experiences ration between them.
# The reduciton determines what to with each particle batch.
# `SaveBatchAsHDF5` saves each batch in seperate directories in an HDF5-file.
# It also prints some summary statistics for each batch.
reduction = SaveBatchAsHDF5(datadir, expname, batch_size, nbatches)
#
# Define the ODE- and ensemble problem and solve
#
# See the documentation of DifferentialEquations.jl for more keyword arguments
# and solver options
#
prob = ODEProblem(
eom,
zeros(4), # Dummy initial condition
( # Parameters
charge=charge,
mass=mass,
fields=em_fields_itp,
magneticmoment=nothing # Dummy magnetic moment
)
)
ensemble_prob = EnsembleProblem(
prob;
prob_func=prob_func,
output_func=output_func,
reduction=reduction,
safetycopy=false
)
sol = solve(
ensemble_prob,
alg,
EnsembleSerial();
reltol=3e-8,
abstol=1e0,
trajectories=npart,
maxiters=1e6,
callback=cbset,
batch_size=batch_size,
)
# Calculate and save `GCAState`s for the initial and final solution for each
# particle. A `GCAState` includes more information than the solution, including
# drifts, Larmor radius, electromagnetic fields etc. See the documentation for
# more information.
save_gcastates(get_filename(reduction), em_fields_itp)