=====================
Pusher Usage Cookbook
=====================
.. raw:: html
.. container:: ap-lang ap-lang-zh
.. rubric:: 这页解决什么问题
本页面面向 **已经把 A_Pusher 模块编译进自己代码**、要在 PIC 循环里调用推进器的开发者。
它说明怎么选模块、调用方需要负责什么、最小时间步循环长什么样、相对论场景何时
切换到 HC,以及常见集成错误。它不重复 :doc:`Learning Path `
里的理论,也不替代每个 routine 自己的 API 页。
.. rubric:: 先选哪个模块
.. list-table::
:header-rows: 1
:widths: 14 20 24 42
* - 模块
- 坐标
- 物理范围
- 何时选它
* - :doc:`A01 `
- Cartesian ``(x,y,z)``
- 非相对论
- 默认选择。粒子速度 ``v \ll c``,几何天然 Cartesian(如 3D 直角网格)。
* - :doc:`A02 `
- 柱坐标 ``(r,\theta,z)``
- 非相对论
- 几何天然柱对称(如圆柱腔、聚焦束流、等离子柱);粒子在 ``r=0``
附近也走 ``r,\theta`` 网格而不是临时转 Cartesian。
* - :doc:`A03 `
- Cartesian ``(x,y,z)``
- 相对论
- 任何工况下 ``\gamma`` 显著大于 1,或 ``v_\perp/c`` 接近 1。
相对论 ExB drift、激光-等离子相互作用、加速器束流都要走这条。
选型判据:
- 先按 **几何** 确定 Cartesian 还是 cylindrical。
- 在 Cartesian 里再按 **最大 γ** 决定 Boris (A01) 还是 HC (A03):
若 ``\gamma_\text{max} - 1 > 0.01`` 就该认真考虑 A03。
- 当前没有"相对论柱坐标 pusher"。需要时,请走 A02 做几何外推 + 在调用方
侧自己做 ``\gamma`` 缩放,或上报需求。
.. rubric:: 调用方必须负责的事情
推进器是无状态的;它 **只** 做一次状态更新。下面这些必须由调用方组织好:
1. **初值时间约定**。把 ``v`` 时间约定调到 ``v^{n-1/2}`` 而不是 ``v^n``。
初始化时常用半步回拉:用 ``E^0, B^0`` 反向做一次"半步推进"。
2. **Boris 参数 k**。每个时间步都计算 ``k = 0.5*qm*dt``。``qm`` 和 ``dt``
改了要重算;不要把 ``k`` 当常数缓存。
3. **场在粒子位置的取值**。用 gather 算子从网格插值,得到 ``E``、``B``
在当前粒子位置的值。对 A02,还要把 Cartesian 场分量按当前 ``\theta``
转换成柱坐标分量。
4. **位置更新归属**。
- A01 / A03:调用方在 routine 后写 ``r = r + v*dt``。
- A02:routine 内部已完成,不要再加一次。
5. **边界处理**。出界粒子的反射、周期 wrap、吸收等都不在推进器里。
6. **诊断和守恒检查**。``v^2`` 守恒(Boris)或 ``\gamma`` 守恒
(HC 在纯磁场下)由调用方测试;推进器不自检。
.. rubric:: 编译和精度
- 默认编译命令(跟测试目录里一致):
.. code-block:: bash
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。
.. rubric:: 最小时间步结构
伪代码骨架,所有 A_Pusher routine 通用:
.. code-block:: text
! 一次时间步推进,针对一个粒子 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`` (参考
:doc:`/rst_files/B_Scatter/scatter_testing_guide` );当前 A01-A03 都假设
场为外部输入、不与粒子自洽,所以仅做单粒子测试时可省略。
.. rubric:: 相对论场景的额外考虑
非相对论 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** :math:`\mathbf{v}`;A03 内部自行完成
:math:`\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`` 下的常数开销。
.. rubric:: 常见集成错误
- ``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 velocity ``v``;
:math:`\gamma\mathbf{v}` 的转换由 routine 内部完成。
- ``-fdefault-real-8`` **没开**。case 04 那种"E + v×B 严格抵消"的算例
会因为单精度舍入误差里出现寄生 drift。
.. container:: ap-lang ap-lang-en
.. rubric:: 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
:doc:`Learning Path ` and does not replace each
routine's API page.
.. rubric:: Choose the Module First
.. list-table::
:header-rows: 1
:widths: 14 20 24 42
* - Module
- Coordinates
- Physical range
- When to pick it
* - :doc:`A01 `
- Cartesian ``(x,y,z)``
- Non-relativistic
- Default. Particle speed ``v \ll c`` and geometry is naturally
Cartesian (e.g. 3D rectilinear grid).
* - :doc:`A02 `
- Cylindrical ``(r,\theta,z)``
- Non-relativistic
- Geometry is naturally cylindrical (cavities, focused beams,
plasma columns) and particles must remain on the ``r,\theta`` grid
even near ``r = 0`` rather than being transiently Cartesian.
* - :doc:`A03 `
- Cartesian ``(x,y,z)``
- Relativistic
- Any setup where ``\gamma`` is appreciably greater than 1 or
``v_\perp/c`` is near 1: relativistic ExB drifts,
laser-plasma interaction, accelerator beams.
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.01`` you should seriously consider A03.
- There is no relativistic cylindrical pusher today. If you need one,
use A02 with caller-side ``\gamma`` corrections or open an issue.
.. rubric:: What the Caller Owns
The pushers are stateless; they perform **exactly one** state update. The
caller is responsible for everything else:
1. **Initial time convention.** Make sure ``v`` is on the half step
``v^{n-1/2}``, not on ``v^n``. The usual trick at initialization is a
backward half-step push using ``E^0`` and ``B^0``.
2. **Boris parameter k.** Compute ``k = 0.5*qm*dt`` every step. If
``qm`` or ``dt`` changes, recompute it; don't cache.
3. **Field at the particle position.** Use a gather operator to
interpolate ``E`` and ``B`` from the grid to the current particle
position. For A02 you must also convert Cartesian field components to
cylindrical components at the current ``\theta``.
4. **Position update ownership.**
- A01 / A03: the caller writes ``r = r + v*dt`` after the call.
- A02: the routine already updates ``r``; **do not** add another
position step.
5. **Boundary handling.** Reflection, periodic wrap, and absorption of
out-of-domain particles are not inside the pusher.
6. **Diagnostics and conservation checks.** ``v^2`` conservation (Boris)
and ``\gamma`` conservation in pure magnetic fields (HC) are
caller-side tests; the pushers do not self-check.
.. rubric:: Compilation and Precision
- Default command line (matches what the test directories use):
.. code-block:: bash
gfortran -cpp -O3 -fdefault-real-8 main.f90
- ``-fdefault-real-8`` promotes every ``real`` to 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 ``real`` with no implicit KIND
assumptions; ifort, nvfortran etc. should compile out of the box.
- No external dependencies — no BLAS, FFTW, or MPI required.
.. rubric:: Minimal Time-Step Structure
Pseudo-code skeleton, shared by all A_Pusher routines:
.. code-block:: text
! 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 ``np`` particles in arrays (not particle by
particle) and hoists ``k`` out of the inner loop.
- ``compute_field_at_particle`` is the ``C_Gather`` module's job and is
not expanded here.
- ``deposit_current`` is the ``B_Scatter`` module (see
:doc:`/rst_files/B_Scatter/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.
.. rubric:: 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-``\gamma`` ExB drift, where
``\mathbf{E} + \mathbf{v}\times\mathbf{B} = 0`` should 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 ``v`` input and return value remain **lab velocity**
:math:`\mathbf{v}`; A03 handles the
:math:`\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``, where ``v``
is the lab velocity returned by A03.
- The conservation diagnostic changes from ``v^2`` to ``|u|^2`` in 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``.
.. rubric:: Common Integration Mistakes
- **k cached with stale dt.** Treating ``k`` as a global constant while
``dt`` varies under an adaptive scheme — the push scales wrong by the
``dt`` ratio.
- **Forgot the position update after A01:** ``r = r + v*dt``. Position never moves while
``v`` evolves. 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. Recompute
``E_r = E_x\cos\theta + E_y\sin\theta``,
``E_\phi = -E_x\sin\theta + E_y\cos\theta`` every step.
- **Passing proper velocity to A03:** ``u = γv``. At high ``\gamma`` the
result is off by a factor ``\gamma``. A03 accepts lab velocity ``v``
and handles the conversion internally — initialise with ``v``, not
``u = γv``.
- ``-fdefault-real-8`` **not enabled.** Single-precision rounding kills
the cancellation in cases like case 04 (``E + v×B = 0``) and produces
a spurious drift.