Pusher Usage Cookbook
这页解决什么问题
本页面面向 已经把 A_Pusher 模块编译进自己代码、要在 PIC 循环里调用推进器的开发者。 它说明怎么选模块、调用方需要负责什么、最小时间步循环长什么样、相对论场景何时 切换到 HC,以及常见集成错误。它不重复 Learning Path 里的理论,也不替代每个 routine 自己的 API 页。
先选哪个模块
模块 |
坐标 |
物理范围 |
何时选它 |
|---|---|---|---|
Cartesian |
非相对论 |
默认选择。粒子速度 |
|
柱坐标 |
非相对论 |
几何天然柱对称(如圆柱腔、聚焦束流、等离子柱);粒子在 |
|
Cartesian |
相对论 |
任何工况下 |
选型判据:
先按 几何 确定 Cartesian 还是 cylindrical。
在 Cartesian 里再按 最大 γ 决定 Boris (A01) 还是 HC (A03): 若
\gamma_\text{max} - 1 > 0.01就该认真考虑 A03。当前没有”相对论柱坐标 pusher”。需要时,请走 A02 做几何外推 + 在调用方 侧自己做
\gamma缩放,或上报需求。
调用方必须负责的事情
推进器是无状态的;它 只 做一次状态更新。下面这些必须由调用方组织好:
初值时间约定。把
v时间约定调到v^{n-1/2}而不是v^n。 初始化时常用半步回拉:用E^0, B^0反向做一次”半步推进”。Boris 参数 k。每个时间步都计算
k = 0.5*qm*dt。qm和dt改了要重算;不要把k当常数缓存。场在粒子位置的取值。用 gather 算子从网格插值,得到
E、B在当前粒子位置的值。对 A02,还要把 Cartesian 场分量按当前\theta转换成柱坐标分量。位置更新归属。
A01 / A03:调用方在 routine 后写
r = r + v*dt。A02:routine 内部已完成,不要再加一次。
边界处理。出界粒子的反射、周期 wrap、吸收等都不在推进器里。
诊断和守恒检查。
v^2守恒(Boris)或\gamma守恒 (HC 在纯磁场下)由调用方测试;推进器不自检。
编译和精度
默认编译命令(跟测试目录里一致):
gfortran -cpp -O3 -fdefault-real-8 main.f90
-fdefault-real-8把所有real提升到 8 字节。强烈建议在 PIC 生产代码里也开这个,否则 case 04 这种”刚好抵消”的算例会被舍入误差吃掉。推进器源码使用 Fortran 默认
real,跨编译器没有显式 KIND 假设; 用 Intel ifort、NVIDIA nvfortran 都能直接编。没有外部依赖,不需要链接 BLAS / FFTW / MPI。
最小时间步结构
伪代码骨架,所有 A_Pusher routine 通用:
! 一次时间步推进,针对一个粒子 p
compute_field_at_particle(x_p, E_p, B_p) ! gather: 网格 → 粒子
k = 0.5*qm*dt ! Boris 参数
! ---- 速度推进 ----
if (rel.and.cartesian) call sub_A03_HC_relativistic_3Dxyz(v_p, E_p, B_p, k)
else if (.not.rel.and.cyl) call sub_A02_Boris_3Drtz_push_v_x(x_p, v_p, E_p, B_p, k, dt)
else call sub_A01_Boris_3Dxyz(v_p, E_p, B_p, k)
! ---- 位置推进(仅 A01 / A03) ----
if (.not.(.not.rel.and.cyl)) then
x_p = x_p + v_p*dt
end if
! ---- 边界 / 诊断 / 沉积 ----
apply_boundary(x_p, v_p)
deposit_current(x_p, v_p)
一些注释:
真实代码会按
np数组遍历(不是按粒子写循环),并把k提到外层算一次。compute_field_at_particle通常是C_Gather模块;本页不展开。deposit_current是B_Scatter(参考 Scatter Testing Guide );当前 A01-A03 都假设 场为外部输入、不与粒子自洽,所以仅做单粒子测试时可省略。
相对论场景的额外考虑
非相对论 Boris (A01/A02) 在 \gamma 显著大于 1 时不正确——
不是数值精度问题,而是物理上不对:磁场旋转角度直接用 q\Delta t / m 计算,
没有 1/\gamma 因子。常见症状:
高
\gamma的 ExB drift 工况:分析上\mathbf{E} + \mathbf{v}\times\mathbf{B} = 0时粒子应该走直线,但 A01 会偏移几千倍粒子半径。激光场里的 8 字形振荡幅度被低估。
切换到 A03 后:
输入和返回值仍为 lab velocity \(\mathbf{v}\);A03 内部自行完成 \(\mathbf{v} \to \mathbf{u} = \gamma\mathbf{v}\) 的转换,调用方无需处理。
位置更新与 A01 相同:
r = r + v*dt,v即 A03 返回的 lab velocity, 无需额外换算。守恒判据从
v^2改为|u|^2(在纯 B 场下)。HC 比 Boris 多出
\gamma求平方根步,但跟磁场旋转运算相比开销可忽略。
切回 A01 的代价:HC 在低 \gamma 退化为 Boris,结果几乎相同。
所以 统一用 A03 是安全的,区别只是极低 \gamma 下的常数开销。
常见集成错误
k缓存了旧 dt。把k当全局常数,dt在自适应步长里变了 却没重算——推进结果会按dt比例失真。A01 后忘了位置更新
r = r + v*dt。粒子位置永远停在初值,v在变。 现象:速度变化合理,但轨迹完全不动。A02 后又做了一次位置更新
r = r + v*dt。位置被推进了两次, 量值上看不出”两倍”,因为 A02 内部用的是柱坐标几何而不是简单加法, 但轨迹会明显偏离解析解。A02 直接传 Cartesian 场分量。粒子离
\theta = 0越远, 轨迹越离谱。改成每步重算E_r = E_x\cos\theta + E_y\sin\theta、E_\phi = -E_x\sin\theta + E_y\cos\theta。A03 初始化时误传 proper velocity
u = γv。high-\gamma工况下结果会偏\gamma倍。正确做法是传入 lab velocityv; \(\gamma\mathbf{v}\) 的转换由 routine 内部完成。-fdefault-real-8没开。case 04 那种”E + v×B 严格抵消”的算例 会因为单精度舍入误差里出现寄生 drift。
What This Page Is For
This page targets developers who have already built A_Pusher into
their own code and need to call the pushers inside a PIC loop. It covers
how to choose a module, what the caller owns, the minimal time-step
shape, when to switch to HC for relativistic cases, and common
integration mistakes. It does not repeat the theory from
Learning Path and does not replace each
routine’s API page.
Choose the Module First
Module |
Coordinates |
Physical range |
When to pick it |
|---|---|---|---|
Cartesian |
Non-relativistic |
Default. Particle speed |
|
Cylindrical |
Non-relativistic |
Geometry is naturally cylindrical (cavities, focused beams,
plasma columns) and particles must remain on the |
|
Cartesian |
Relativistic |
Any setup where |
Selection rules of thumb:
First choose Cartesian vs cylindrical by geometry.
Inside Cartesian, pick Boris (A01) vs HC (A03) by maximum γ: if
\gamma_\text{max} - 1 > 0.01you should seriously consider A03.There is no relativistic cylindrical pusher today. If you need one, use A02 with caller-side
\gammacorrections or open an issue.
What the Caller Owns
The pushers are stateless; they perform exactly one state update. The caller is responsible for everything else:
Initial time convention. Make sure
vis on the half stepv^{n-1/2}, not onv^n. The usual trick at initialization is a backward half-step push usingE^0andB^0.Boris parameter k. Compute
k = 0.5*qm*dtevery step. Ifqmordtchanges, recompute it; don’t cache.Field at the particle position. Use a gather operator to interpolate
EandBfrom the grid to the current particle position. For A02 you must also convert Cartesian field components to cylindrical components at the current\theta.Position update ownership.
A01 / A03: the caller writes
r = r + v*dtafter the call.A02: the routine already updates
r; do not add another position step.
Boundary handling. Reflection, periodic wrap, and absorption of out-of-domain particles are not inside the pusher.
Diagnostics and conservation checks.
v^2conservation (Boris) and\gammaconservation in pure magnetic fields (HC) are caller-side tests; the pushers do not self-check.
Compilation and Precision
Default command line (matches what the test directories use):
gfortran -cpp -O3 -fdefault-real-8 main.f90
-fdefault-real-8promotes everyrealto 8 bytes. Strongly recommended for production PIC code; without it, balanced cases like case 04 (E + v×B = 0) develop a spurious drift from single-precision rounding.The pusher source uses Fortran default
realwith no implicit KIND assumptions; ifort, nvfortran etc. should compile out of the box.No external dependencies — no BLAS, FFTW, or MPI required.
Minimal Time-Step Structure
Pseudo-code skeleton, shared by all A_Pusher routines:
! One time step for particle p
compute_field_at_particle(x_p, E_p, B_p) ! gather: grid → particle
k = 0.5*qm*dt ! Boris parameter
! ---- Velocity push ----
if (rel.and.cartesian) call sub_A03_HC_relativistic_3Dxyz(v_p, E_p, B_p, k)
else if (.not.rel.and.cyl) call sub_A02_Boris_3Drtz_push_v_x(x_p, v_p, E_p, B_p, k, dt)
else call sub_A01_Boris_3Dxyz(v_p, E_p, B_p, k)
! ---- Position push (A01 / A03 only) ----
if (.not.(.not.rel.and.cyl)) then
x_p = x_p + v_p*dt
end if
! ---- Boundary / diagnostics / deposition ----
apply_boundary(x_p, v_p)
deposit_current(x_p, v_p)
Notes:
Production code loops over
npparticles in arrays (not particle by particle) and hoistskout of the inner loop.compute_field_at_particleis theC_Gathermodule’s job and is not expanded here.deposit_currentis theB_Scattermodule (see Scatter Testing Guide); A01-A03 assume the field is externally supplied and not self-consistent with the particles, so single-particle tests can skip the deposition step.
Relativistic Considerations
Non-relativistic Boris (A01/A02) becomes wrong at non-trivial
\gamma — not in numerical accuracy but in physics: the magnetic
rotation angle uses q\Delta t / m directly without the 1/\gamma
factor. Typical symptoms:
In high-
\gammaExB drift, where\mathbf{E} + \mathbf{v}\times\mathbf{B} = 0should give a straight line, A01 drifts by thousands of gyroradii.The figure-8 amplitude inside a laser field is under-estimated.
Switching to A03:
The
vinput and return value remain lab velocity \(\mathbf{v}\); A03 handles the \(\mathbf{v} \to \mathbf{u} = \gamma\mathbf{v}\) conversion internally, so the caller needs no extra step.The position update is identical to A01:
r = r + v*dt, wherevis the lab velocity returned by A03.The conservation diagnostic changes from
v^2to|u|^2in pure magnetic fields.HC adds one square-root step compared to Boris, but it’s negligible against the rotation arithmetic.
The cost of staying on A03 at low \gamma: HC degenerates to Boris
and the result is essentially identical. Using A03 uniformly is
safe, the only penalty is a tiny constant overhead at very low
\gamma.
Common Integration Mistakes
k cached with stale dt. Treating
kas a global constant whiledtvaries under an adaptive scheme — the push scales wrong by thedtratio.Forgot the position update after A01:
r = r + v*dt. Position never moves whilevevolves. Symptom: velocities look fine, trajectory frozen.Added another position update after A02:
r = r + v*dt. Position is advanced twice. The factor isn’t a clean 2× because A02’s internal step uses cylindrical geometry, but the trajectory clearly diverges from the analytic answer.Passing Cartesian field components to A02. The further from
\theta = 0, the wronger the trajectory. RecomputeE_r = E_x\cos\theta + E_y\sin\theta,E_\phi = -E_x\sin\theta + E_y\cos\thetaevery step.Passing proper velocity to A03:
u = γv. At high\gammathe result is off by a factor\gamma. A03 accepts lab velocityvand handles the conversion internally — initialise withv, notu = γv.-fdefault-real-8not enabled. Single-precision rounding kills the cancellation in cases like case 04 (E + v×B = 0) and produces a spurious drift.