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.
Particle controls live in:
Mapping to control flags:
count -> -numParticlesinit_mode -> -pinitrestart_mode -> -particle_restart_moderandom_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 withinfields -> -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.
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).
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.
picurv accepts these exact canonical strings:
SurfaceVolumePointSourceSurfaceEdgesEnum mapping in C (ParticleInitializationType):
0: PARTICLE_INIT_SURFACE_RANDOM1: PARTICLE_INIT_VOLUME2: PARTICLE_INIT_POINT_SOURCE3: PARTICLE_INIT_SURFACE_EDGES| Value | Maps to |
|---|---|
PointSource | 2 |
Surface | 0 |
SurfaceEdges | 3 |
Volume | 1 |
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.
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.
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.
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.
Main setup flow:
AssignInitialPropertiesToSwarm seeds base particle state.PerformInitializedParticleSetup settles/migrates and couples to Eulerian fields.Mode details:
Surface (0):
CMx_c/CMy_c/CMz_c) before migration,ReinitializeParticlesOnInletSurface re-spreads particles on inlet partitions after first settlement.SurfaceEdges (3):
Volume (1):
PointSource (2):
(psrc_x, psrc_y, psrc_z).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:
N, use start_step: N.restart_mode: load to continue the existing particle swarm.restart_mode: init to reseed a fresh particle population in the restarted flow field.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:
For initialized particles (StartStep == 0 path):
LocateAllParticlesInGrid performs location/migration.fields values, and ParticleFieldPlanSummarize logs and records them.InterpolateAllFieldsToSwarm assigns flow fields at particle positions.For loaded particles:
MigrateRestartParticlesUsingCellID fast migration,LocateAllParticlesInGrid resolves invalid/missing cases,| Value | Maps to |
|---|---|
init | init |
load | load |
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.
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.
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.
Check banner/log output for:
Typical errors:
point_source.{x,y,z} for point source mode,{init, load},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.
If adding a new particle initialization mode:
picurv_cli/core.py,ParticleInitializationType and parser wiring in C,InitializeParticleBasicProperties and any inlet reinit path,For the full selector extension checklist, see Modular Selector Extension Guide.