Skip to content

Canonical Tracing Form ​

This example demonstrates tracing charged particles using the canonical Hamiltonian formulation in (x,p) phase space coordinates, compared against the standard velocity formulation (x,v).

Motivation: Why Canonical Coordinates? ​

In Hamiltonian mechanics, the motion of a charged particle in an electromagnetic field with scalar potential ϕ(x,t) and vector potential A(x,t) is governed by the canonical Hamiltonian:

H(x,p,t)=12m(p−qA(x,t))2+qϕ(x,t)

in the non-relativistic regime, and:

H(x,p,t)=m2c4+c2(p−qA(x,t))2+qϕ(x,t)

in the relativistic regime.

While standard particle pushers (such as Boris) operate on velocity (x,v) directly from (E,B) fields, the canonical formulation offers two major benefits:

  1. Exact Preservation of Noether Symmetries (Cyclic Momenta): If the system has a spatial symmetry (e.g. axisymmetry in tokamaks or translational invariance in 1D/2D models), the corresponding canonical momentum pk is strictly conserved to machine precision across all ODE solvers. In velocity coordinates, kinematic velocity vk continuously rotates, accumulating secular drift in the invariant.

  2. Canonical Symplectic Integration: Standard symplectic integrators (such as ImplicitMidpoint) directly preserve the canonical 2-form ω=∑dxi∧dpi and maintain bounded energy oscillations over long integration times.

julia
using TestParticle
using OrdinaryDiffEq, OrdinaryDiffEqSDIRK
using StaticArrays
using LinearAlgebra: norm
using CairoMakie

PotentialField & Non-Relativistic Landau Gauge ​

In a uniform magnetic field B=(0,0,B0), we choose the Landau gauge A=(0,B0x,0). Because the Hamiltonian does not depend on y (∂yH=0), the canonical momentum py=mvy+qAy is a strict invariant of motion.

julia
q = 1.0
m = 1.0
B0 = 1.0
Ω = q * B0 / m
T_period = 2π / Ω

# Define the vector potential; ForwardDiff automatically computes ∇A
A_landau(x) = SA[0.0, B0 * x[1], 0.0]
pf = PotentialField(A_landau)
param_can = prepare(pf; q = q, m = m)

# Initial condition: particle starting at origin moving in x direction
x0 = SA[0.0, 0.0, 0.0]
v0 = SA[1.0, 0.0, 0.0]
u0_can = velocity_to_canonical(x0, v0, param_can; relativistic = false)

t_end = 20 * T_period
tspan = (0.0, t_end)
dt = T_period / 40

# Trace in canonical form using ImplicitMidpoint
prob_can = TraceCanonicalProblem(u0_can, tspan, param_can; relativistic = false)
sol_can = solve(prob_can, ImplicitMidpoint(); dt = dt, adaptive = false)

# Trace in velocity form using Boris
param_xv = prepare(ZeroField(), (x, t) -> SA[0.0, 0.0, B0]; q = q, m = m)
prob_xv = TraceProblem([x0..., v0...], tspan, param_xv)
sol_xv = TestParticle.solve(prob_xv, Boris(); dt);

Comparing Cyclic Momentum Conservation ​

In velocity coordinates, py(t)=mvy(t)+qB0x(t). We compare the drift in py relative to its initial value (py(0)=0).

julia
ts_can = sol_can.t ./ T_period
py_can_err = [abs(u[5] - u0_can[5]) for u in sol_can.u]

ts_xv = sol_xv.t ./ T_period
py_xv_err = [abs(m * u[5] + q * B0 * u[1] - u0_can[5]) for u in sol_xv.u]

fig1 = Figure(size = (800, 450))
ax1 = Axis(
    fig1[1, 1],
    xlabel = "Gyrations (t / T)",
    ylabel = "|p_y(t) - p_y(0)|",
    yscale = log10,
    title = "Conservation of Cyclic Momentum (p_y) in Landau Gauge"
)
lines!(ax1, ts_xv, max.(py_xv_err, 1.0e-16), label = "Boris (x, v)", color = :crimson)
lines!(ax1, ts_can, max.(py_can_err, 1.0e-16), label = "ImplicitMidpoint (x, p)", color = :teal)
axislegend(ax1, position = :rt)
fig1

While Boris exhibits staggering phase oscillations of order ∼10−3, the canonical solver preserves py to exact machine zero across the entire simulation.

Relativistic Canonical Tracing ​

For relativistic dynamics (v0=0.6c), we compare energy conservation and momentum tracking between the canonical formulation and velocity-space formulations.

julia
c_val = 1.0
v0_rel = SA[0.6 * c_val, 0.0, 0.0]
param_can_rel = prepare(pf; q = q, m = m, c = c_val)
u0_can_rel = velocity_to_canonical(x0, v0_rel, param_can_rel; relativistic = true)

prob_can_rel = TraceCanonicalProblem(u0_can_rel, tspan, param_can_rel; relativistic = true)
sol_can_rel = solve(prob_can_rel, ImplicitMidpoint(); dt = dt, adaptive = false)

H0_rel = canonical_hamiltonian(sol_can_rel.u[1], param_can_rel; relativistic = true)
H_rel_err = [
    abs(canonical_hamiltonian(u, param_can_rel; relativistic = true) - H0_rel) / H0_rel
        for u in sol_can_rel.u
]

fig2 = Figure(size = (800, 450))
ax2 = Axis(
    fig2[1, 1],
    xlabel = "Gyrations (t / T)",
    ylabel = "Relative Energy Error |ΔH / H_0|",
    yscale = log10,
    title = "Relativistic Canonical Tracing: Bounded Symplectic Energy Error"
)
lines!(ax2, sol_can_rel.t ./ T_period, max.(H_rel_err, 1.0e-16), color = :midnightblue)
fig2

The Hamiltonian energy oscillates within a strictly bounded envelope without secular drift, confirming the structure-preserving property of the canonical symplectic integrator.