Skip to content

Move particle-cloud packing out of the solver into pre_process #1737

Description

@sbryngelson

Summary

Particle-cloud generation (src/simulation/m_particle_cloud.fpp) is geometry preprocessing, not flow solving — rejection sampling, triangular/FCC lattices, hemisphere-shell CDF sampling, a spatial hash grid, and periodic minimum-image wrapping have nothing to do with the solver. It currently lives in the simulation startup path only because IBM marker computation (m_ibm.fpp, s_ibm_setup) is simulation-side and the packer was placed next to it.

Proposal: expand the compact particle_cloud spec in pre_process, write the resulting IB-patch list to a file, and have the solver just read it. The solver becomes geometry-agnostic about beds and simulation/ sheds the whole subsystem.

This is a follow-up to the review on #1667 — not part of it. #1667 is a feature PR; this is a cross-executable refactor and should land after it merges, so we relocate a finished subsystem rather than a moving target.

Boundary

pre_process expands each particle_cloud into a global IB-patch list and writes restart_data/particle_cloud_ibs.dat; the solver reads that list at fresh start, then reduces to each rank's neighborhood (unchanged). ib_patch_parameters already lives in src/common/m_derived_types.fpp, so both stages share the type.

What moves

File Change ~LOC
src/simulation/m_particle_cloud.fpp deleted -556
src/simulation/m_start_up.fpp swap s_generate_particle_clouds(...) -> s_read_particle_cloud_ibs(...) +30 / -5
src/pre_process/m_particle_cloud.fpp new — the packers, minus the neighborhood filter (pre_process places the global set) ~+500
src/pre_process/m_start_up.fpp run packer after grid/IC, write patch file ~+40
src/pre_process/m_global_parameters.fpp, m_mpi_proxy.fpp add particle_cloud namelist awareness + broadcast (pre_process has none today) ~+20
src/common/ shared read/write helper for the patch file ~+40

Net: simulation/ loses ~525 lines (556-line subsystem -> ~30-line reader).

Design decisions

  1. Reduction stays solver-side. f_neighborhood_ranks_own_location / s_reduce_ib_patch_array depend on the simulation's domain decomposition, so pre_process writes the global list and the solver reduces on read. The lattice packer's inline neighborhood filter disappears (it packs global now) — a simplification.
  2. Scaling win, not loss. Today every rank redundantly recomputes the full deterministic placement; under the file boundary it is computed once. The list is bounded by num_ib_patches_max_namelist (54000) -> tiny file, so the old "avoid broadcasting N patches" concern goes away.
  3. Restart path. On restart (t_step_start > 0) the solver already skips generation and reads ib_state; keep that branch. The .dat read fires only on fresh start (mirrors the current if/else in m_start_up.fpp:903-913).
  4. RNG / goldens. Packing runs in a different binary, so regenerate the 6 particle-cloud goldens rather than assume a bit-identical xorshift stream. The ib_state geometric verifier in test.py is unchanged (reads solver output either way).
  5. Validation. case_validator.py is untouched — it validates the compact user spec. This is the payoff of the input-only/runtime split from Move input-only checks to case_validator, delete dead params_tests, enforce the split #1717.

Test plan

  • Build all three targets.
  • Regenerate the 6 particle-cloud goldens.
  • Add a ppn=2 case (classic "did the broadcast/decomposition survive" catch, per .claude/rules/common-pitfalls.md).
  • Keep the geometric ib_state verifier.

Effort

Medium — ~80% mechanical relocation, ~20% genuinely new (file writer/reader + pre_process param plumbing). The real risk surface is parallel correctness, not the algorithms.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions