PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
Particle Initialization and Restart Guide

This page documents particle seeding, restart behavior, and early-step migration/settling logic. It is written for both case authors and contributors working in src/ParticleSwarm.c and src/ParticleMotion.c.

1. User Contract in <tt>case.yml</tt>

Particle controls live in:

models:
physics:
particles:
count: 50000
init_mode: "Surface" # Surface | Volume | PointSource | SurfaceEdges
restart_mode: "init" # init | load
random_seed: 12345 # optional; seeds placement and Brownian draws
point_source:
x: 0.5
y: 0.5
z: 0.5
fields: # optional; see 1.1
Psi: 0.5

Mapping to control flags:

  • count -> -numParticles
  • init_mode -> -pinit
  • restart_mode -> -particle_restart_mode
  • random_seed -> -particle_random_seed (optional; integer 0..2147483647, default 12345)
  • point_source -> -psrc_x/-psrc_y/-psrc_z (required when init_mode is PointSource); a physical position, divided by length_ref like the grid bounds it sits within
  • fields -> -particle_fields_* (optional; see 1.1 Configured Particle Values (fields))

Note: The interpolation method (Trilinear / CornerAveraged) is configured in solver.yml, not case.yml. See Configuration Reference: Solver YAML and Trilinear Interpolation and Particle-Grid Projection.

1.1 Configured Particle Values (<tt>fields</tt>)

fields gives particle-carried fields a value when the population is created. Only fields the particle carries from step to step can be set; every other particle field is re-derived from the Eulerian fields or from particle location, so a value given to it would not survive. Today that is Psi, the scalar that IEM micromixing relaxes (see IEM Mixing and Statistical Averaging).

models:
physics:
particles:
count: 50000
init_mode: "Volume"
fields:
Psi: 0.5 # a number
# Psi: "0.5 + 0.5*sin(2*pi*xn)" # or an expression
# Psi: # or regions over a background
# params: {r: 0.1}
# background: 0
# regions:
# - {shape: half_space, axis: x, at: 0.5, side: above, value: 1,
# edge: {profile: smoothstep, width: 0.02}}
# - {shape: ball, center: [0.5, 0.5, 0.5], radius: r, value: "uniform()"}

Each value is one of:

Form Meaning
number every particle gets it
expression string evaluated per particle
{params, value} an expression that may use the named numbers in params
{params, background, regions} start from background; each region, in order, paints its value over what came before

Expressions use the language of ic_gen Eulerian initial conditions (Initial Condition Modes): + - * / % **, comparisons (chains such as 0.2 < x < 0.5 included), and or not, abs sin cos tan exp sqrt minimum maximum where(c, a, b), and pi. A comparison or logical expression is 1 when true and 0 when false. Numbers are decimal literals. A particle expression may use:

Name Value
x, y, z particle position, physical
xn, yn, zn position normalized to the domain bounding box, 0 to 1
pid particle ID
t physical time of the event that creates the particle
uniform(), normal() a per-particle draw in [0, 1), or from N(0, 1)

A draw is a pure function of random_seed, the particle ID, and the field; it does not depend on the rank a particle lives on or the order particles are visited. uniform(k) and normal(k) with a non-negative integer literal k name a stream shared across fields, so two fields can draw the same number for one particle.

Regions are half_space (axis, at, side: above | below), slab (axis, from, to), box (min, max), ball (center, radius), and cylinder (axis, center in the other two coordinates, radius), all in physical coordinates. Without edge, a region's boundary is sharp; edge: {profile, width} blends across a band of that width centred on the boundary with linear, smoothstep, or cosine, reaching one half on the boundary itself. Region numbers may name a params entry.

When it applies. A value is applied once each particle's position is final: after placement and location, and before interpolation and the first scatter, so the t=0 cell mean already reflects it. A particle restored from a checkpoint (restart_mode: load) keeps its saved value, and fields is ignored with a warning.

Units. Values are physical, in the field's units, and are divided by the field's reference scale as they are stored (Units and Non-Dimensionalization). Psi is dimensionless.

Validation. picurv validate refuses an unknown or re-derived field, an expression outside the language, a region with a missing or unknown key, fields without particles, and fields together with solver.yml verification.sources.scalar, which prescribes Psi at every step. It warns when a PointSource value has no draw, because every particle then starts at one point with one value.

Diagnostics. The launcher prints each lowered expression. After applying them, the solver logs each field's particle count, mean, variance, minimum, and maximum (info level), and particle_initial_fields.csv in the run's analysis directory records the same per field and component, with a ten-bin histogram between the minimum and maximum.

Mapping to control flags: -particle_fields_count, then per field -particle_fields_<i>_name and one -particle_fields_<i>_expr_<c> per component, holding the lowered expression.

2. Accepted <tt>init_mode</tt> Values

picurv accepts these exact canonical strings:

  • Surface
  • Volume
  • PointSource
  • SurfaceEdges

Enum mapping in C (ParticleInitializationType):

  • 0: PARTICLE_INIT_SURFACE_RANDOM
  • 1: PARTICLE_INIT_VOLUME
  • 2: PARTICLE_INIT_POINT_SOURCE
  • 3: PARTICLE_INIT_SURFACE_EDGES

3. Particle Initialization Mode Entries

Value Maps to
PointSource2
Surface0
SurfaceEdges3
Volume1

Surface

Identity. particles.init_mode: Surface -> -pinit 0 -> ParticleInitializationType.

What it does. Seeds particles across a bounding surface of the domain.

When to choose it. Studying what enters through a face - inlet seeding, or transport from a boundary into the interior. Choose Volume when you want the interior populated from the start.

Parameters it owns. The particle count; the surface is derived from the configured inlet face.

Interactions. Needs an identifiable inlet face; the startup log reports which face was chosen.

Diagnostics. "Inlet face for particle initialization identified as Face N" at startup, and the per-step particle count.

Evidence. Analytically verified - particle-seeding-restart-2026-09-21: 16,000 particles land on the inlet plane (within 2.5e-7 of it) and are uniform across it, Kolmogorov-Smirnov statistic 1.05 on one rank and 1.00 on two.

Limitations. Concentrates particles at one boundary, so interior statistics take time to become meaningful.

Volume

Identity. particles.init_mode: Volume -> -pinit 1.

What it does. Distributes particles through the domain volume.

When to choose it. Whenever you want interior statistics immediately - dispersion, mixing, or any case where waiting for particles to arrive from a boundary wastes the run.

Parameters it owns. The particle count.

Interactions. Particle density follows the cell distribution, so a graded grid gives a non-uniform physical density. Across ranks, each rank seeds a share proportional to the cells it owns, so the density does not depend on the decomposition.

Diagnostics. Per-step particle count and the lost-particle counter.

Evidence. Production exercised - examples/scatter_verification seeds this way. Analytically verified - particle-seeding-restart-2026-09-21: uniform on one rank over four seeds (KS at most 1.10, pairwise coordinate correlation at most 0.013), and on two and three ranks after the fix below (KS at most 0.99, normal per-cell chi-square, particle IDs unique). Before it, each rank seeded an equal count into unequal subdomains, 889 against 1143 particles per cell layer on two ranks.

Limitations. No control over the spatial distribution beyond the grid itself.

PointSource

Identity. particles.init_mode: PointSource -> -pinit 2, with coordinates from -psrc_x/-psrc_y/-psrc_z.

What it does. Releases all particles from a single specified point.

When to choose it. Plume and dispersion studies, and the verification cases where a known release point makes an analytic comparison possible.

Parameters it owns. The point-source coordinates.

Interactions. The point must lie inside the domain; a point outside it produces immediate particle loss.

Diagnostics. The lost-particle counter is the first signal of a misplaced source.

Evidence. Production exercised - examples/drift_uniform_flow and examples/brownian_motion are built on this mode. Analytically verified - particle-seeding-restart-2026-09-21: every particle starts exactly at the source.

Limitations. All particles share one origin, so early statistics are highly correlated.

SurfaceEdges

Identity. particles.init_mode: SurfaceEdges -> -pinit 3.

What it does. Seeds particles along the edges of a bounding surface rather than across its face.

When to choose it. Exercising the locate-and-migrate machinery, where edges and corners are the hard cases. It is a diagnostic seeding mode more than a physical one.

Parameters it owns. The particle count.

Interactions. Edge and corner cells are exactly where the walking search is most likely to struggle, which is the point.

Diagnostics. Search metrics at Search Robustness Metrics Reference.

Evidence. Analytically verified - particle-seeding-restart-2026-09-21: every particle sits exactly where its deterministic lattice formula places it.

Limitations. Not a physically motivated distribution.

4. Mode Behavior in C

Main setup flow:

  1. InitializeParticleSwarm creates the DMSwarm and fields.
  2. AssignInitialPropertiesToSwarm seeds base particle state.
  3. PerformInitializedParticleSetup settles/migrates and couples to Eulerian fields.

Mode details:

Surface (0):

  • requires an identified INLET face from BC parsing,
  • ranks not servicing inlet place particles at inlet-center fallback (CMx_c/CMy_c/CMz_c) before migration,
  • ReinitializeParticlesOnInletSurface re-spreads particles on inlet partitions after first settlement.

SurfaceEdges (3):

  • deterministic placement by particle ID on inlet-face lattice,
  • if deterministic target is non-local after migration, code falls back to random inlet placement.

Volume (1):

  • the global count is split across ranks in proportion to owned cells, by largest remainder, and particle IDs are numbered contiguously across ranks,
  • random logical coordinates inside locally owned cells,
  • mapped to physical space via metric interpolation.

PointSource (2):

  • all particles start at fixed coordinates (psrc_x, psrc_y, psrc_z).

5. Restart Behavior Matrix

InitializeParticleSwarm behavior depends on StartStep and restart mode:

StartStep particle_restart_mode Behavior
0 any initialize new population
>0 init initialize new population in restarted flow
>0 load load particle fields from restart files

Operational note:

  • For a run completed through step N, use start_step: N.
  • Choose restart_mode: load to continue the existing particle swarm.
  • Choose restart_mode: init to reseed a fresh particle population in the restarted flow field.
  • Point picurv at the previous run with --restart-from <previous_run_dir> on the CLI (or --continue to resume the most recent run of the same case).

For loaded particles, fast migration path:

5. Early-Step Settlement and Coupling

For initialized particles (StartStep == 0 path):

  1. LocateAllParticlesInGrid performs location/migration.
  2. surface modes call ReinitializeParticlesOnInletSurface.
  3. statuses are reset and location pass is repeated.
  4. ParticleFieldPlanApply sets configured fields values, and ParticleFieldPlanSummarize logs and records them.
  5. InterpolateAllFieldsToSwarm assigns flow fields at particle positions.
  6. the scatter updates Eulerian particle-derived fields.

For loaded particles:

  1. MigrateRestartParticlesUsingCellID fast migration,
  2. LocateAllParticlesInGrid resolves invalid/missing cases,
  3. interpolation/scatter synchronize coupling state before stepping.

5.1 Particle Restart Mode Entries

Value Maps to
initinit
loadload

init

Identity. models.physics.particles.restart_mode: init (the default).

What it does. Seeds the swarm from init_mode at the start of the run, regardless of whether Eulerian fields are being restarted. Particle state in a checkpoint is ignored.

When to choose it. Whenever the particles are the thing you are varying: re-seeding onto a restarted flow field lets you launch a fresh swarm into developed turbulence without re-running the spin-up. Also the correct choice for any first run.

Parameters it owns. None of its own. It selects which branch consumes models.physics.particles.init_mode and its parameters - see 3. Particle Initialization Mode Entries.

Interactions. Independent of eulerian_field_source: a run may load fields and still init particles. Combined with a field restart it is the standard 'new swarm, developed flow' configuration.

Diagnostics. The startup banner reports the resolved particle restart mode and the seeded count. A swarm that seeds at the wrong size shows here, before the first step.

Evidence. Integration verified - make unit-particles covers the seeding branches. Analytically verified - particle-seeding-restart-2026-09-21: a restart with init reseeded the run's own step-0 population and advected it by the carrier velocity to 5.5e-16.

Limitations. Any statistics accumulated by a previous run's particles are lost, because the particles they described no longer exist. Placement is seeded from random_seed, so with the seed unchanged a restart reseeds the same positions the run started with; change the seed for a statistically independent swarm.

load

Identity. models.physics.particles.restart_mode: load.

What it does. Restores particle positions, velocities, and swarm fields from the checkpoint named by the restart, continuing the same particles rather than seeding new ones.

When to choose it. When particle history matters - dispersion, mean-squared displacement, residence time - and the trajectory must continue rather than restart. Required for any statistic accumulated across a restart boundary.

Parameters it owns. None of its own; the checkpoint supplies the state. The restart source is chosen by --restart-from or --continue, not by this key.

Interactions. Requires a checkpoint that actually contains particle state: a run whose particles were disabled writes none. The restart matrix in 5. Restart Behavior Matrix gives the full combination table against eulerian_field_source.

Diagnostics. A missing or empty particle group in the checkpoint is a fatal startup error naming the step, not a silent reseed. The restored count is reported in the banner.

Evidence. Integration verified - make unit-particles covers the restore path. Analytically verified - particle-seeding-restart-2026-09-21: a 10-step run restarted for 10 more reproduced the continuous 20-step swarm bitwise, lost particles included.

Limitations. The particle state itself restarts exactly, as the evidence shows on an analytical carrier. Under a solved flow the Eulerian restart re-applies boundary conditions, which perturbs the flow around 1e-7 regardless of solver tolerance, and the particles inherit that; it is a floor on any claim of trajectory continuity across a solved-flow restart.

6. Swarm Fields Initialized at Startup

After position/PID/cell placeholders, initialization sets defaults for:

  • velocity (vector),
  • weight (vector),
  • Diffusivity (scalar),
  • DiffusivityGradient (vector),
  • Psi (scalar).

These are the catalog defaults. Once positions are final, configured fields values (1.1 Configured Particle Values (fields)) replace the defaults of the fields they name.

Cell IDs start at -1 until location confirms host cells.

7. Diagnostics and Sanity Checks

Check banner/log output for:

  • selected particle initialization mode,
  • identified inlet face for surface modes,
  • migration/lost-particle counters after first steps.

Typical errors:

  • no INLET face with surface modes,
  • missing point_source.{x,y,z} for point source mode,
  • restart mode not in {init, load},
  • a fields entry naming a field the runtime re-derives, or an expression outside the language (the error names the configuration path of the value).

The [Particle IC] log line and particle_initial_fields.csv show what the configured values produced: a minimum and maximum that are equal where a spread was expected usually means the expression does not depend on anything that varies across the particles.

8. Contributor Extension Points

If adding a new particle initialization mode:

  1. add validation and mapping in picurv_cli/core.py,
  2. extend ParticleInitializationType and parser wiring in C,
  3. implement placement logic in InitializeParticleBasicProperties and any inlet reinit path,
  4. update logging string mappings and tests,
  5. update this page and related method references.

For the full selector extension checklist, see Modular Selector Extension Guide.

9. Related Pages