Scatter Usage Cookbook
这页解决什么问题
本页面面向 已经把 B_Scatter 模块编译进自己代码、要在 PIC 循环里调用沉积算子 的开发者。它说明怎么选模块、调用方需要负责什么、最小时间步循环长什么样、 OMP / 并行场景的注意事项,以及常见集成错误。不重复 Learning Path 里的理论, 也不替代每个 routine 自己的 API 页。
先选哪个模块
模块 / 子程序 |
坐标 |
沉积量 |
何时选它 |
|---|---|---|---|
|
Cartesian |
标量密度 |
默认选择:3D Cartesian PIC,需要节点密度做 Poisson 求解或电流沉积。 |
|
Cartesian |
速度矩 |
需要节点上的速度分量 |
|
Cartesian |
温度(方差) |
需要节点上的方差/温度时调用;用最近格子法,不是 CIC。 |
柱坐标 |
|
几何天然柱对称(圆柱腔、聚焦束流、等离子柱);需要 charge 和 current deposition 一起做。 |
选型判据:
先按 几何 确定 Cartesian (B01) 还是 cylindrical (B02)。
在 Cartesian 内,按 需要的物理量 选择
B01/_v/_T。 三个互不冲突,可以在同一时间步里依次调用。当前没有”相对论 scatter”——所有 routine 都假设宏粒子权重是经典质量/电荷。 高
\gamma工况下电流沉积要在调用方侧把v换成\gamma v后再传入。
调用方必须负责的事情
scatter routine 是无状态的;它 只 做一次累加。下面这些都必须由调用方组织:
粒子数组布局。所有 B_Scatter routine 都要求
par(1:3, 1:np)是位置(B01)或par(1:6, 1:np)包含位置+速度(B01 _v / _T需要v分量)。 传入维度必须严格匹配子程序声明,否则会 stride 错位(见 Learning Path 里的 “20 单位质量堆 (0,0,0)” 案例)。网格初始化。目标数组为
den;den = 0必须 显式 完成。routine 采用 累加 方式, 不是覆盖式;第二次调用会叠加在第一次的结果上。粒子权重
w。多数 routine 把w当作整体缩放因子在循环结束后乘上;B02里粒子权重直接进沉积公式。请按各 API 页的约定填。Ghost cell / 边界。
den至少要分配(il-1:iu+1, ...)范围以容纳 落在域边的粒子的 CIC 权重外溢;调用方在 routine 之后再处理 ghost cell 的同步或周期 wrap。粒子是否在域内。落在
[il-1, iu+1]之外的粒子由调用方在 scatter 之前处理(注入、反射、删除等),scatter routine 自己不检查。柱坐标的体积归一化。
B02沉积出的rho是 “charge per cell” 而非物理电荷密度;要除以r \Delta r \Delta\phi \Delta z得到密度, 或在场求解器里直接处理”按 cell 体积积过的源项”。
编译和精度
默认编译命令(跟测试目录里一致):
gfortran -cpp -O3 -fdefault-real-8 -fopenmp main.f90
-fdefault-real-8把所有real提升到 8 字节。强烈建议在生产代码里 开这个:CIC 权重的累加在np大时会受单精度误差影响,sum(den) = sum(w_p)守恒可能就保不住。-fopenmp是必须的:上游!$omp parallel没有 fallback; 如果用ifort改成-qopenmp。没有外部依赖,不需要链接 BLAS / FFTW / MPI。
最小时间步结构
伪代码骨架,针对一次完整的密度沉积:
! 一次时间步内的 scatter
apply_boundary_or_inject(par, np) ! 出域粒子和注入由调用方处理
den = 0.0 ! 必须先清零
call sub_B01_scatter_3Dxyz(il, iu, den, np, par, w) ! 累加
! 可选:同步 ghost cell(MPI 跨域)
call exchange_ghost_cells(den)
! 可选:同步 velocity moment / temperature
den_v = 0.0
call sub_B01_scatter_3Dxyz_v(il, iu, den_v, np, par, w, d=4)
! den_T 无需清零,子程序内部自行归零
call sub_B01_scatter_3Dxyz_T(il, iu, den_T, np, par, w, d=4)
! 后续:场求解、诊断
call poisson_solve(den, phi)
一些注释:
真实代码会按
np数组 直接 传入,不需要按粒子写循环;上游已经内部 并行。apply_boundary_or_inject是用户代码,本页不展开。exchange_ghost_cells是H_MPI_Exchange模块;在单进程跑测试时 可省略。多次调用
_v需要每次都清零对应的目标数组;_T由子程序内部清零,调用方无需操作。
OMP 和并行考虑
上游 sub_B01_scatter_3Dxyz 的 OMP 设计是:
!$omp parallel default(firstprivate) reduction(+:den)
!$omp do
do p = 1, np
... CIC accumulation ...
end do
!$omp end do
!$omp end parallel
两个隐藏代价:
OpenMP 的
default(firstprivate)会把par拷到每个线程的栈。par维度是3 × np,np = 1e7时每线程占 ~240 MB。64 线程 = 15 GB 拷贝, 系统栈 8 MB 直接溢出。因此 测试目录的 run 脚本已设置所需栈参数:ulimit -s unlimited和OMP_STACKSIZE=1G。集成到生产代码时 要同样设置,否则np一上去就崩。OpenMP 的
reduction(+:den)在循环结束时做归约。每个线程私有的den在循环结束累加到主den。线程数高时归约开销不小,但相比par拷贝是次要的。
实测数据(见 B01 鲲鹏对比):
小
np(1e4):OMP 开销主导,2-4 线程是最优档位,再加线程负扩展。大
np(1e7):firstprivate拷贝主导,64 线程比 1 线程慢 37 倍(在 AMD服务器上),鲲鹏好一些但也只能做到 2.6 倍慢。任何
np:实测加速比不会超过 ~3×,远低于线性。
换句话说,当前 kernel 不适合高 OMP 并行。 生产代码里集成时的可行折中:
把线程数控制在 4-8 档位附近。
真要做高并行,绕开上游 routine,自己实现
default(shared)+ atomic add 或 per-thread local accumulation 后手动合并的版本。这超出了本页范围。
常见集成错误
par维度不匹配子程序声明。调用方分配par(1:6, 1:np_max), 直接传给只接收par(1:3, 1:np)的 routine——Fortran 按 stride 3 重解释,每隔一列读到的是速度槽(0),位置严重错位。这是上线前必须查的 bug。den没清零。多步 PIC 循环里前一步的den残留累加到这一步。没分配 guard cell。
den(il:iu, ...)不留外延一圈,落在域边的粒子的 CIC 权重写到边界外、触发段错误或 silent corruption。正确分配是den(il-1:iu+1, ...)。B02 把”per cell” 当成 “density”。
B02直接输出”格内积分量”, 不是物理密度。后续 Poisson 求解时要约定是用积分量还是密度。栈大小没调。
ulimit -s默认 8 MB,OMP_STACKSIZE默认 ~4 MB。np = 1e7配firstprivate会直接段错误。修复见上面 OMP 节。温度沉积期望 CIC 守恒。
sub_..._T是最近格子法,不能套sum(den_T) = sum(w*v^2)。它给出节点级方差,不是”二阶矩”。
What This Page Is For
This page targets developers who have already built B_Scatter into
their own code and need to call the scatter operators inside a PIC loop.
It covers how to choose a module, what the caller owns, the minimal
time-step shape, OMP / parallel considerations, 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 / subroutine |
Coordinates |
Deposited quantity |
When to pick it |
|---|---|---|---|
|
Cartesian |
Scalar density |
Default: 3D Cartesian PIC needing node density for Poisson or current deposition. |
|
Cartesian |
Velocity moment |
When you need a node-level velocity component |
|
Cartesian |
Temperature / variance |
When you need node-level variance / temperature. Uses nearest-cell, not CIC. |
Cylindrical |
|
Naturally cylindrical geometry (cavities, focused beams, plasma columns) where charge and current deposition are done together. |
Selection rules of thumb:
First choose Cartesian (B01) vs cylindrical (B02) by geometry.
Inside Cartesian, choose
B01/_v/_Tby the physical quantity you need. The three are independent and can be called in sequence within the same time step.There is no “relativistic scatter” yet — every routine assumes classical macro-particle mass / charge. At high
\gammathe caller must replacevwith\gamma vbefore passing it to_v.
What the Caller Owns
The scatter routines are stateless; they only accumulate. The caller owns:
Particle array layout. Every B_Scatter routine wants
par(1:3, 1:np)for position-only (B01) orpar(1:6, 1:np)including velocity (B01 _v / _T). The shape must match the subroutine declaration exactly, or Fortran reinterprets the memory by the wrong stride (see the “20 units of mass at(0,0,0)” case in Learning Path).Initialising the grid array
den.den = 0must be done explicitly. The routine accumulates, not overwrites; a second call adds on top of the first.Particle weight
w. Most routines applywas a global scale after the loop;B02mixeswinto the deposition formula directly. Follow each API page’s convention.Ghost cells / boundaries.
denshould be allocated at least(il-1:iu+1, ...)to absorb the CIC overflow from particles at the domain edge; the caller synchronises ghost cells (or applies periodic wrap) after the call.Out-of-domain particles. Particles outside
[il-1, iu+1]are not checked. The caller handles injection / reflection / deletion before scatter.Cylindrical volume normalisation.
B02produces “charge per cell”, not physical charge density. Divide byr \Delta r \Delta\phi \Delta zto recover density, or arrange the field solver to consume the cell-integrated source directly.
Compilation and Precision
Default command line (matches the test directories):
gfortran -cpp -O3 -fdefault-real-8 -fopenmp main.f90
-fdefault-real-8promotes everyrealto 8 bytes. Strongly recommended in production: the CIC accumulation losessum(den) = sum(w_p)conservation under single-precision rounding oncenpis large.-fopenmpis required: the upstream!$omp parallelhas no fallback. Use-qopenmpif you compile withifort.No external dependencies — no BLAS, FFTW, or MPI required.
Minimal Time-Step Structure
Pseudo-code skeleton for one full density deposition:
! Scatter inside one time step
apply_boundary_or_inject(par, np) ! out-of-domain / injection is caller-side
den = 0.0 ! must zero first
call sub_B01_scatter_3Dxyz(il, iu, den, np, par, w) ! accumulate
! Optional: synchronise ghost cells across MPI domains
call exchange_ghost_cells(den)
! Optional: velocity moment / temperature
den_v = 0.0
call sub_B01_scatter_3Dxyz_v(il, iu, den_v, np, par, w, d=4)
! den_T needs no pre-zeroing; the subroutine zeroes it internally
call sub_B01_scatter_3Dxyz_T(il, iu, den_T, np, par, w, d=4)
! Downstream: field solve, diagnostics
call poisson_solve(den, phi)
Notes:
Production code passes
parstraight in bynp— no per-particle loop; the upstream routine parallelises internally.apply_boundary_or_injectis user code; not expanded here.exchange_ghost_cellsis theH_MPI_Exchangemodule; can be skipped in single-process test runs.Each
_vcall needs its own zero-initialised target array._Tis zeroed internally by the subroutine; no pre-zeroing is required from the caller.
OMP and Parallel Considerations
The upstream sub_B01_scatter_3Dxyz uses:
!$omp parallel default(firstprivate) reduction(+:den)
!$omp do
do p = 1, np
... CIC accumulation ...
end do
!$omp end do
!$omp end parallel
Two hidden costs:
OpenMP
default(firstprivate)copiesparto every thread stack.paris3 × npreals; atnp = 1e7each thread takes ~240 MB. 64 threads = 15 GB of copies, and the default 8 MB OS stack overflows immediately. The test directories’ run scripts setulimit -s unlimitedandOMP_STACKSIZE=1Gfor exactly this reason — production code must do the same or it will crash as soon asnpis non-trivial.OpenMP
reduction(+:den)does a final merge. Each thread’s privatedenis summed into the shared one at loop exit. The merge cost is non-trivial at high thread counts but is secondary compared to theparcopy.
Measured behaviour (see B01 Kunpeng comparison):
Small problem (
np = 1e4): OMP overhead dominates; the sweet spot is 2-4 threads, and adding more makes it slower.Large problem (
np = 1e7):firstprivatecopies dominate; 64 threads are 37× slower than 1 thread on the AMD server, and even on Kunpeng the kernel is only 2.6× slower at 64 threads.Across all particle counts (
np): measured speedup never reaches more than ~3×, well below linear.
In other words, the current kernel does not scale well. Practical compromises:
Keep the thread count in the 4-8 range.
For genuine high parallelism, bypass the upstream routine and write a custom version using
default(shared)with atomic adds or per-thread local accumulation merged by hand. That is beyond this page.
Common Integration Mistakes
parshape mismatch. Caller allocatespar(1:6, 1:np_max)and passes the slab to a routine declared aspar(1:3, 1:np)— Fortran reinterprets the memory with stride 3 and reads every other column as garbage velocity slots. Always check this before going live.dennot zeroed. Residue from the previous step accumulates into the current one inside a multi-step PIC loop.No guard cell allocated.
den(il:iu, ...)with no overflow ring catches CIC weights from edge particles writing past the boundary, producing a segfault or silent corruption. The correct layout isden(il-1:iu+1, ...).Treating B02 “per cell” output as density.
B02produces the cell-integrated quantity, not the physical density. Decide once whether the downstream solver consumes cell integrals or densities.Stack too small. Default
ulimit -sis 8 MB andOMP_STACKSIZEis around 4 MB;np = 1e7withfirstprivatesegfaults immediately. Fix per the OMP section above.Expecting CIC conservation from
_T.sub_..._Tuses nearest-cell, not CIC, sosum(den_T) = sum(w*v^2)does not hold. It provides per-node variance, not “the second moment”.