Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
151 commits
Select commit Hold shift + click to select a range
c920cee
Add design spec for array-based particle storage in the core
RudolfWeeber Jul 3, 2026
0d1aa0b
Add implementation plan for ParticleStore migration phases 0-1
RudolfWeeber Jul 3, 2026
2e3d662
benchmarks: add benchmark gate with shared-machine load check
RudolfWeeber Jul 3, 2026
3513fe4
benchmarks: fix CalledProcessError escape and label in compare output
RudolfWeeber Jul 3, 2026
77d9a6b
core: scaffold ParticleStore (migration phase 0)
RudolfWeeber Jul 3, 2026
8f62fca
core: add CellParticleStorage primitives (migration phase 1)
RudolfWeeber Jul 3, 2026
8942a1a
benchmarks: gate on foreign CPU instead of load average
RudolfWeeber Jul 3, 2026
e4eae3b
benchmarks: record phase-0 baseline for ParticleStore migration
RudolfWeeber Jul 3, 2026
16284b9
core: route CellStructure storage mutations through CellParticleStorage
RudolfWeeber Jul 3, 2026
a1b0915
core: route RegularDecomposition cell mutations through CellParticleS…
RudolfWeeber Jul 3, 2026
026b339
core: route Atom/HybridDecomposition cell mutations through CellParti…
RudolfWeeber Jul 3, 2026
4321afc
core: route ghost-cell resizing through CellParticleStorage
RudolfWeeber Jul 3, 2026
a905f52
CI: guard against direct cell-storage mutations (migration phase 1)
RudolfWeeber Jul 3, 2026
bb9072b
particle_store: address phases 0-1 final-review findings
RudolfWeeber Jul 3, 2026
71b837e
Add implementation plan for ParticleStore migration phase 2
RudolfWeeber Jul 3, 2026
9e3864e
benchmarks: add fixed-seed trajectory identity gate (spec section 5)
RudolfWeeber Jul 3, 2026
3955053
core: add VectorReference write-through proxy (migration phase 2)
RudolfWeeber Jul 3, 2026
e493b56
core: ParticleStore row bookkeeping and force/torque columns (phase 2)
RudolfWeeber Jul 3, 2026
3056495
core: prepare force/torque call sites for proxy accessors (phase 2)
RudolfWeeber Jul 3, 2026
8ae5dbb
core: ghost force reduction via explicit field values (phase 2)
RudolfWeeber Jul 3, 2026
d7a7c1b
ghosts: fix FORCE ghost direction logic and mixed-parts sizing
RudolfWeeber Jul 3, 2026
6412f55
python: fetch particle force/torque from owning rank (phase 2)
RudolfWeeber Jul 3, 2026
80892fd
core: force and torque live in ParticleStore columns (phase 2 flip)
RudolfWeeber Jul 3, 2026
bde8770
core: preserve force/torque across cross-rank particle migration
RudolfWeeber Jul 4, 2026
56557c0
core: address phase-2 final-review findings (ParticleStore migration)
RudolfWeeber Jul 4, 2026
8c852f8
h5md: synchronize ParticleStore before writing forces (phase 2)
RudolfWeeber Jul 4, 2026
885f096
Add implementation plan for ParticleStore migration phase 3
RudolfWeeber Jul 4, 2026
1dfdd28
core: generalize store proxies to BasicVectorReference<T> and Quatern…
RudolfWeeber Jul 4, 2026
1a88f73
core: default proxy copy constructors to silence -Wdeprecated-copy
RudolfWeeber Jul 4, 2026
85ee5fb
core: ParticleStore state columns and generalized rebuild (phase 3)
RudolfWeeber Jul 4, 2026
4ae6486
core: prepare position/image call sites for proxy accessors (phase 3)
RudolfWeeber Jul 4, 2026
4baa6c1
core: prepare quaternion call sites for proxy accessors (phase 3)
RudolfWeeber Jul 4, 2026
00ce7e5
core: ghost position exchange via explicit field values (phase 3)
RudolfWeeber Jul 4, 2026
a763695
core: fetch-cache snapshot store and owner-side sync choke point (pha…
RudolfWeeber Jul 4, 2026
dac3629
core: component-major layout for kernel particle views (phase 3)
RudolfWeeber Jul 4, 2026
e98a165
core: position, image box, and quaternion live in ParticleStore colum…
RudolfWeeber Jul 4, 2026
9c9f3b5
docs: record phase-3 adjudicated deviations; fix stale comments
RudolfWeeber Jul 4, 2026
6815121
Add implementation plan for ParticleStore migration phase 3.5
RudolfWeeber Jul 5, 2026
c27383b
core: kernels read ParticleStore columns directly (phase 3.5)
RudolfWeeber Jul 5, 2026
5218b20
core: hoist per-particle proxies in hot loops (phase 3.5)
RudolfWeeber Jul 5, 2026
13ff909
plan: add phase-3.5 task 2.5 (columnar ghost transfer)
RudolfWeeber Jul 5, 2026
210f857
core: columnar ghost position update and force reduction (phase 3.5)
RudolfWeeber Jul 5, 2026
7ac510e
core: trust ghost-range contiguity invariant in release builds (phase…
RudolfWeeber Jul 5, 2026
809f7f3
core: particle-major host layout for store vector columns
RudolfWeeber Jul 5, 2026
31ab892
core: inline hot thermostat kernels and reuse store column capacity
RudolfWeeber Jul 5, 2026
a85bf5b
core: direct column access in mixed-parts ghost serialization
RudolfWeeber Jul 5, 2026
bfd50ad
core: synchronize particle store between resort and ghost update
RudolfWeeber Jul 5, 2026
2d3f844
core: hoist column view acquisition out of ghost bulk-transfer loops
RudolfWeeber Jul 5, 2026
e3e9be6
core: cache ghost-communication row ranges by store generation
RudolfWeeber Jul 5, 2026
e885951
core: harden columnar ghost caching invariants (phase 3.5 review)
RudolfWeeber Jul 5, 2026
d66d1c3
benchmarks: re-record phase-0 baseline in current machine state
RudolfWeeber Jul 5, 2026
ad05892
docs: record user-approved temporary benchmark allowance (phases 3-4)
RudolfWeeber Jul 5, 2026
1680778
docs: amendment covers all 1000ppc configs under the sequential gate
RudolfWeeber Jul 5, 2026
c11fe8a
Add implementation plan for ParticleStore migration phase 4
RudolfWeeber Jul 5, 2026
7bbce14
core: prepare velocity/omega call sites for proxy accessors (phase 4)
RudolfWeeber Jul 6, 2026
90cadd2
core: mask omega through the particle before quaternion propagation
RudolfWeeber Jul 6, 2026
85623a2
core: ParticleStore velocity columns (phase 4)
RudolfWeeber Jul 6, 2026
03cef4b
core: ghost momentum exchange via explicit field values (phase 4)
RudolfWeeber Jul 6, 2026
7b45162
core: velocity and angular velocity live in ParticleStore columns (ph…
RudolfWeeber Jul 6, 2026
8347289
core: forward-declare active_particle_store for debug assertions
RudolfWeeber Jul 6, 2026
469bd65
Add implementation plan for ParticleStore migration phase 5
RudolfWeeber Jul 6, 2026
0c5e28e
core: prepare parameter call sites for column accessors (phase 5)
RudolfWeeber Jul 6, 2026
a27e117
core: ParticleStore parameter columns and sidecars (phase 5)
RudolfWeeber Jul 6, 2026
95d7167
core: ghost properties exchange via explicit field values (phase 5)
RudolfWeeber Jul 6, 2026
f34e2be
core: evict particle parameters into ParticleStore (phase 5)
RudolfWeeber Jul 6, 2026
b7f57c3
core: fix pack-index reads of store-aliased columns in bond kernels
RudolfWeeber Jul 6, 2026
e4bb87d
core: restore packed hot-path reads for pair kernels and integrator (…
RudolfWeeber Jul 6, 2026
ee7af46
docs: record phase-5 checkpoint results and amended perf budget
RudolfWeeber Jul 6, 2026
5bd57b4
Add implementation plan for ParticleStore migration phase 6
RudolfWeeber Jul 6, 2026
befa00a
core: ParticleStore ragged sidecars and rattle column (phase 6)
RudolfWeeber Jul 6, 2026
e8c5e0a
core: evict bonds, exclusions and rattle correction into ParticleStor…
RudolfWeeber Jul 6, 2026
f8350d0
core: fix ICC pack-charge synchronization on the observable-calc path…
RudolfWeeber Jul 6, 2026
3eb8d59
core: keep pack-rebuild hot path lean after ragged eviction (phase 6)
RudolfWeeber Jul 7, 2026
65d8653
docs: record phase-6 checkpoint results
RudolfWeeber Jul 7, 2026
a220b9f
Add implementation plan for ParticleStore migration phase 7a
RudolfWeeber Jul 7, 2026
3fd577d
core: cell row-index storage and particle view machinery (phase 7a)
RudolfWeeber Jul 7, 2026
ccbaf7e
core: verlet list holds store rows instead of particle pointers (phas…
RudolfWeeber Jul 7, 2026
31969c1
core: cells hold store rows and hand out particle views (phase 7a)
RudolfWeeber Jul 7, 2026
dcfdcc6
core: refresh documentation after phase-7a flip
RudolfWeeber Jul 7, 2026
d99ea17
core: rebind cached particle views instead of reconstructing them (ph…
RudolfWeeber Jul 7, 2026
4772a96
core: restore single-rebuild resort path (phase 7a)
RudolfWeeber Jul 7, 2026
d1b4ba5
Add implementation plan for ParticleStore migration phase 7b
RudolfWeeber Jul 7, 2026
e61dd4d
core: migration staging store and per-field pack machinery (phase 7b)
RudolfWeeber Jul 7, 2026
d36001c
core: per-field particle migration and fetch (phase 7b)
RudolfWeeber Jul 7, 2026
56d7340
core: delete the particle migration envelope; Particle is a view (pha…
RudolfWeeber Jul 7, 2026
1d74f72
docs: record phase-7a/7b checkpoint results and adjudications
RudolfWeeber Jul 7, 2026
3aae75c
Add implementation plan for ParticleStore migration phase 7e
RudolfWeeber Jul 7, 2026
5f1dc4b
core: resolve particles by id to store rows; retire the particle poin…
RudolfWeeber Jul 7, 2026
161d05c
docs: record phase-7e checkpoint results
RudolfWeeber Jul 7, 2026
c785c63
Add implementation plan for ParticleStore migration phase 7c
RudolfWeeber Jul 7, 2026
9f92fd1
core: column permutation rebuild machinery (phase 7c)
RudolfWeeber Jul 7, 2026
13edbfb
core: resort as column permutation; cells hold row ranges (phase 7c)
RudolfWeeber Jul 8, 2026
992629e
core: post-review polish for permutation resort (phase 7c)
RudolfWeeber Jul 8, 2026
f942db7
core: skip pending-removed rows in hybrid resort (phase 7c)
RudolfWeeber Jul 8, 2026
2a8f87c
docs: record phase-7c checkpoint results
RudolfWeeber Jul 8, 2026
c9318f1
cuda: document phase-8 GPU-view rework; verify build after ParticleSt…
RudolfWeeber Jul 8, 2026
b1831f8
core: silence gcc-14 maybe-uninitialized on ghost row contexts
RudolfWeeber Jul 8, 2026
9a55b9b
Add implementation plan for ParticleStore migration phase 8a
RudolfWeeber Jul 8, 2026
a4b6fbf
core: velocity-verlet translation as column kernels (phase 8a)
RudolfWeeber Jul 8, 2026
ccc9b05
core: velocity-verlet rotation as column kernels (phase 8a)
RudolfWeeber Jul 8, 2026
6d130ea
core: langevin thermostat as column kernels (phase 8a)
RudolfWeeber Jul 8, 2026
272e3fd
core: brownian dynamics as column kernels (phase 8a)
RudolfWeeber Jul 8, 2026
c7a2e85
core: recover integrator VV step via VectorReference kernels (phase 8a)
RudolfWeeber Jul 8, 2026
40fbd9a
docs: fix doxygen references from the ParticleStore migration
RudolfWeeber Jul 8, 2026
e7d0149
docs: record phase-8a checkpoint results
RudolfWeeber Jul 8, 2026
6b2eb86
cuda: per-field column staging for single-rank GPU transfers (phase 8c)
RudolfWeeber Jul 8, 2026
faf533f
core: bind host views by const reference for Kokkos view_host() compa…
RudolfWeeber Jul 8, 2026
6f8f170
core: harden disabled-feature view fallbacks in the integrator kernels
RudolfWeeber Jul 8, 2026
ff27401
core: remove dead particle struct anchors
RudolfWeeber Jul 8, 2026
fb20018
core: remove dead ELC aosoa correction overloads
RudolfWeeber Jul 8, 2026
c6b53a6
utils: fix Bag::erase self-move on the last element
RudolfWeeber Jul 8, 2026
d4d70bd
core: assert pack-charge freshness at P3M gather entry (additional ch…
RudolfWeeber Jul 8, 2026
986fc1e
cuda: drop the AoS staging fallback switch
RudolfWeeber Jul 8, 2026
a3bb62f
core: note the pre-existing prefetch predicate bug for an upstream PR
RudolfWeeber Jul 8, 2026
dffd33a
core: reword ParticleRattle note to avoid a stray doxygen @ref
RudolfWeeber Jul 8, 2026
85f020c
core: mask GHOSTTRANS_BONDS in the transmit sizer; fix Particle decla…
RudolfWeeber Jul 8, 2026
861a6fb
core: drop unused lambda capture in RowParticleRange test
RudolfWeeber Jul 8, 2026
08a5ea3
core: satisfy clang-tidy in the migration integrator/resort paths
RudolfWeeber Jul 8, 2026
0dcd9f9
core: avoid deprecated volatile compound assignment in RowParticleRan…
RudolfWeeber Jul 8, 2026
9d22cc5
core: guard zero-length memcpy in the migration pack cursors
RudolfWeeber Jul 8, 2026
bf9c6e2
core: satisfy clang-tidy static analysis on the migration branch
RudolfWeeber Jul 8, 2026
cdd69e8
core: perf-guided hot-path fixes from hardware profiling (phase 8)
RudolfWeeber Jul 8, 2026
baa345b
docs: record the phase-8 final checkpoint; original perf budget met
RudolfWeeber Jul 8, 2026
9bc5157
core: component-major particle store layout by default (phase 8)
RudolfWeeber Jul 9, 2026
e1948b5
core: satisfy clang-tidy findings from the GitLab sanitizer job
RudolfWeeber Jul 9, 2026
5f8ea86
core: finish clang-tidy cleanup for the sanitizer and empty CI jobs
RudolfWeeber Jul 9, 2026
b51494d
docs: describe the particle store and Particle view in final-state terms
RudolfWeeber Jul 9, 2026
49265bb
docs: describe the cell system in final-state terms
RudolfWeeber Jul 9, 2026
0a50880
docs: describe ghost transfer and the force/integrator hot paths in f…
RudolfWeeber Jul 9, 2026
3cafede
docs: describe particle bookkeeping and peripheral solvers in final-s…
RudolfWeeber Jul 9, 2026
2f30a58
docs: describe unit tests, script interface and testsuite in final-st…
RudolfWeeber Jul 9, 2026
1ca5583
docs: use plain code font for pack_particles_soa to resolve a doxygen…
RudolfWeeber Jul 9, 2026
c94b3de
Merge remote-tracking branch 'origin/python' into worktree-eliminate_…
RudolfWeeber Jul 10, 2026
7b18530
Add SIMD optimization campaign plan
RudolfWeeber Jul 10, 2026
da29efd
core: batched minimum-image with vectorized verlet gate and pair kern…
RudolfWeeber Jul 10, 2026
bd91a2f
Revert "core: batched minimum-image with vectorized verlet gate and p…
RudolfWeeber Jul 10, 2026
0ec0035
core: simd-friendly p3m charge and force assignment (simd ws2)
RudolfWeeber Jul 10, 2026
f991360
Revert "core: simd-friendly p3m charge and force assignment (simd ws2)"
RudolfWeeber Jul 10, 2026
cccccc0
core: simd-friendly force initialization and langevin sweep (simd ws3)
RudolfWeeber Jul 10, 2026
e060b9a
Revert "core: simd-friendly force initialization and langevin sweep (…
RudolfWeeber Jul 10, 2026
77fe08a
Reapply "core: simd-friendly p3m charge and force assignment (simd ws2)"
RudolfWeeber Jul 10, 2026
819e2fa
core: dispatch the p3m simd force gather on thread count
RudolfWeeber Jul 11, 2026
2e53851
docs: fix doxygen reference in the p3m interpolation notes
RudolfWeeber Jul 11, 2026
3a54ec1
core: split force initialization and verlet-list build into own trans…
RudolfWeeber Jul 11, 2026
5eefff3
core: branchless minimum-image fold and flat Verlet-list cutoff table
RudolfWeeber Jul 12, 2026
ade6386
p3m: don't force vectorization of the interpolation lines on clang
RudolfWeeber Jul 12, 2026
cc1c6c7
core: own-the-loop specialized non-bonded pair kernel
RudolfWeeber Jul 12, 2026
2d26933
docs: use a code span for the get_mi_coord_masked reference
RudolfWeeber Jul 12, 2026
1056d27
core: batch the specialized kernel's minimum-image gate over neighbors
RudolfWeeber Jul 14, 2026
244a6ab
core: compile inactive-feature branches out of the Verlet criterion
RudolfWeeber Jul 15, 2026
7304df6
docs: describe the criterion call operator without a doxygen ref
RudolfWeeber Jul 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
249 changes: 249 additions & 0 deletions .superpowers/sdd/phase7a-task-4-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
# Phase 7a Task 4 — THE FLIP: cells hand out views (report)

**Worktree:** `/tikhome/weeber/es/.claude/worktrees/eliminate_Particle`
**Branch:** `worktree-eliminate_Particle`
**HEAD at start:** `ccbaf7ecea`
**Date:** 2026-07-07
**Author:** Claude (agent)

---

## Summary

`Utils::Bag<Particle>` is removed from `Cell`. A cell now owns:
- `CellRows m_rows` — `Utils::Bag<int>` of committed ParticleStore row indices;
- `std::vector<Particle> m_staged` — not-yet-committed detached particles;
- `ParticleStore *m_store` — the store its rows index into.

`Cell::particles()` returns a `RowParticleRange` (row-range view over `m_rows` +
store); every particle handed out by the cell system is a non-owning
`{store,row}` view. All cell mutation stays behind `CellParticleStorage`, now
operating on rows + staging. Migration/ghost paths keep working via detached
snapshots + staging + rebuild. The transitional `m_particle_index` (id→`Particle*`)
now points into a stable per-CellStructure view pool refreshed on every rebuild.

---

## Staging design (insert / rebuild)

- `CellParticleStorage::insert_particle(Cell&, Particle&&)` appends the incoming
detached, carrier-laden particle to `cell.staged()` and returns a reference to
it. The caller marks the store dirty. The particle becomes a committed row
(columns seeded from its carriers via `assign_row`) only at the next store
rebuild.
- `ensure_particle_store_synchronized()` (CellStructure.cpp) now, per cell in
cell-traversal order (local cells then ghost cells):
1. captures the cell's surviving committed row indices;
2. clears the row bag;
3. for each surviving row, hands `assign_row` a `Particle` attached to the
OLD row so the column data is PRESERVED-by-old-row from the spare columns;
4. for each staged particle, hands `assign_row` the detached staged particle
so its row is SEEDED from carriers;
5. clears `cell.staged()`.
The store's `n_local`/`n_ghost` are counted as `rows + staged` per cell before
`begin_rebuild`. Row-assignment order (local prefix `[0,n_local)`, ghost suffix,
cell order, bag order within a cell) is byte-identical to pre-flip.
- `add_particle` / `add_local_particle` stage + mark dirty + **commit
immediately** (`ensure_particle_store_synchronized`) and return a stable
view-pool pointer, preserving the pre-flip contract that a newly added
particle is live (indexed, iterable, readable/writable) right after the call.
`set_particle_decomposition` and the migration re-insert paths batch (stage
all, one rebuild) to avoid O(n^2).

## snapshot_row semantics

`Particle ParticleStore::snapshot_row(int row)`:
- attaches a fresh `Particle` to `(this, row)`;
- calls `Particle::detach_from_store()`, which (a) captures the STRUCTURAL ghost
flag (`row >= n_local`) into the fallback `l.ghost` member, (b) runs
`sync_migration_carriers()` — the SAME field set/order as the serialize SAVE
block, refactored into a private method reused by both — copying every
column/sidecar value into the migration carriers, then (c) nulls the store
pointer / sets row -1.
- The result is a self-contained detached value the migration/resort paths move
into send buffers or a cell's staging area; it does NOT alias the store.
`extract_row(Cell&, index)` = `snapshot_row(cell.rows()[index])` + swap-with-back
remove of that row index (mirrors the pre-flip Bag erase; order not preserved).

## View-pool contract (`m_particle_index` transitional form)

- `m_view_pool` is a `std::deque<Particle>` of attached views, one per indexed
particle (locals in row order, then the ghost rows whose id is not owned by a
local — "locals win"). `std::deque` so refilling never relocates
already-handed-out element addresses within a generation.
- `m_particle_index` (`std::vector<Particle*>`, id-indexed) points into the pool.
`get_local_particle(id)` returns the pool pointer — valid between rebuilds,
exactly the pre-flip pointer-stability contract.
- Rebuilt by `rebuild_particle_index()` (LOCALS only; their id columns are valid
after the rebuild) inside `ensure_particle_store_synchronized`, then
`index_ghost_particles()` (ghost tail) AFTER `ghosts_update` has filled the
ghost id columns (a fresh ghost row carries a default id until then).
- `resort_particles` clears the index, resorts, then commits + reindexes so the
index is consistent on return (a direct `resort_particles` caller — e.g.
`check_particle_index` — sees a populated index).
- `set_index_map`'s `m_unique_particles` (`std::vector<Particle*>` consumed by
the Cabana kernels) is rebuilt to point into the pool (its local prefix ==
pack order == store rows `[0,n_local)`; ghost tail == deduped ghosts), since it
can no longer hold pointers into transient cell iteration.

## Migration / ghost choreography

- `RegularDecomposition::resort` / `AtomDecomposition::resort` /
`HybridDecomposition::resort`: iterate a cell's committed rows BY POSITION;
`fold_and_reset` writes through a view into the column; `extract_row`
snapshots + removes the row (swap-with-back → re-examine the swapped-in
position); the snapshot goes into a `ParticleList`/`std::vector<Particle>`
send buffer (exempt Bags, unchanged) or another cell's staging via
`insert_particle`. Received particles are re-inserted via `insert_particle`
(staging). `CellStructure::resort_particles` wires every cell's store before
the decomposition runs and commits + reindexes after.
- Ghost communication (`ghosts.cpp`): `GhostCommunication::part_lists` changed
from `std::vector<ParticleList*>` to `std::vector<Cell*>`. Loops iterate
`cell->particles()` (views); PARTNUM resize routes through
`resize_ghost_storage(Cell&, count)` (stages `count` default ghosts).
`contiguous_store_rows` reads the row range directly from `cell.rows()`.
- **Ordering fix (canary):** `update_ghosts_and_resort_particle` now runs
`resort → COMMIT (sync) → ghosts_count(PARTNUM) → COMMIT (sync) →
ghosts_update(DATA) → index_ghosts`. The commit before PARTNUM is required so
source (local) cells report their full committed row count.
- **Multi-layer ghost forwarding fix:** ghost resize STAGES (deferred commit),
so within one PARTNUM communicator pass a cell resized as a ghost destination
must report its pending size when read as a source for a downstream ghost
layer. `Cell::size()` = `rows + staged`, and all PARTNUM count paths
(`cell_cell_transfer`, `prepare_send_buffer`) use it.
- **Hybrid internal ghost comms:** `HybridDecomposition::resort` does its own
PARTNUM/DATA ghost comms after the child resorts (which stage). A
`set_commit_store` callback (wired by `set_hybrid_decomposition` to
`ensure_particle_store_synchronized`) commits before/between those comms.

## Cached-view multipass hazard (the systemic pitfall)

`RowParticleIterator::operator*` returns a reference to a view CACHED INSIDE the
iterator, overwritten on `++`. Any code that STORES `&p` from a
`for (auto &p : range)` loop (or an STL algorithm that holds references across
increments) dangles. Fixed at every such site:
- `fetch_particles` (observables): rebuilt from `get_local_particle(id)` stable
pool pointers.
- `lb_couple_particles`: snapshot coupled views into an owning `std::vector<Particle>`
(view copies alias the store, so force write-back lands in columns), pass
pointers into that.
- `dipolar_direct_sum::gather_particle_data`: returns `std::vector<Particle>`
(view copies) instead of `std::vector<Particle*>`; force/torque/dip_fld
write-back through them lands in columns.
- `cells.cpp get_interacting_neighbors`: collects neighbor IDS instead of
Particle pointers.
The verlet consume branch and the parallel `for_each`/`reduce` single-cell paths
build one view per index (thread-safe) or reuse one cached-view iterator per cell.

## Guard-script changes

`maintainer/CI/check_cell_storage_mutations.sh` now flags mutating calls on
`rows()` / `staged()` (and, defensively, the legacy `particles()`):
`insert|erase|clear|resize|push_back|emplace_back|pop_back|emplace`. Exempts
`ParticleListOperations.hpp` (the choke points) and `CellStructure.cpp` (the
store rebuild legitimately clears + refills each cell's row bag). Verified it
still CATCHES an out-of-band `cell.rows().insert(...)` (synthetic tripwire test)
and passes clean on the tree.

## Generation-invariant re-verification (T3 table, re-checked at the flip)

The verlet list holds store rows stamped with `(store, generation)`; a consume
is safe iff no generation bump occurred since the stamp without a verlet rebuild.
Every mark-dirty site and its verlet pairing after the flip:

| Site | Marks dirty | Bumps generation? | Verlet rebuild before next consume? |
|---|---|---|---|
| ctor | yes | on first sync | initial `m_rebuild_verlet_list=true`; safe |
| `remove_particle` | yes | at next resort's sync | removal → resort → flag set |
| `add_local_particle` | yes | IMMEDIATE sync | always followed by a resort before a force calc (controlled placement); debug guard covers |
| `add_particle` | yes | IMMEDIATE sync | sets a resort level → resort → flag set |
| `remove_all_particles` | yes | next sync | followed by resort |
| `ghosts_count` | yes | next sync (inside update_ghosts_and_resort) | only reached after `resort_particles` set the flag |
| `resort_particles` | yes | its own end-of-function sync | sets `m_rebuild_verlet_list=true` in the same function |
| `set_particle_decomposition` | yes | its own sync | full rebuild; next resort sets the flag |

New at the flip: the add paths and `resort_particles`/`set_particle_decomposition`
now bump the generation via an IMMEDIATE sync (not lazily). The verlet consume
branch is only reached from the CPU verlet/collision path, always after a resort
(which sets the flag → BUILD branch restamps). No production path consumes the
verlet list at a generation newer than its stamp. The debug
`ParticleStoreGuard::assert_generation` in the consume branch + the new
`execute_bond_handler` entry guard are the defense-in-depth canaries.

## ImmersedBoundaries dangling-ref hazard

`resolve_bond_partners` returns `Particle*` from `get_local_particles` →
`get_local_particle`, which now returns STABLE view-pool pointers. The
`Particle &p2 = *partners[0]` pattern therefore stays valid for the handler call
duration (no rebuild during a bond kernel). A debug generation guard is added at
`execute_bond_handler` entry, asserting the store generation is unchanged across
each handler call.

## What remains for 7b / 7c / 7e

- **7b:** the migration/ghost WIRE format is still whole-`Particle` boost
envelopes + the migration carriers; the fetch cache / PartCfg / mpiio still use
detached-Particle value snapshots. `snapshot_row` and the carrier sync are the
seam 7b replaces with per-field column packing. `mpiio` add loop and the
dipolar/lb view-copy buffers are O(n)-per-add / whole-view copies to revisit.
- **7c:** `CellRows` (`Bag<int>`) collapses to a contiguous `(offset,count)`
range once resort becomes a column permutation; staging + the per-cell row bag
go away.
- **7e:** `m_particle_index` (`Particle*` + view pool) retires for a pure id→row
map; `m_unique_particles` dies with the pack; the collision/bond held-pointer
sites adopt the row/generation model.

---

## Debugging lessons (bugs found + fixed during the flip)

1. **Ghost-index drop on bare sync.** `rebuild_particle_index` initially indexed
only locals; a bare sync (add_particle commit, forces.cpp) dropped the ghost
index entries a prior ghosts_update established -> `get_local_particle(ghost_id)`
returned nullptr -> LB coupling / collision null-deref. Fixed: rebuild also
indexes already-valid ghost rows (id >= 0); fresh ghosts (id -1) are added by
`index_ghost_particles` after ghosts_update.
2. **Stale ghosts re-indexed after remove-all.** `remove_all_particles` cleared
only local cells; stale ghost rows got re-indexed -> a fresh add of the same
id saw "already exists". Fixed: clear ghost cells too.
3. **Cached-view multipass hazard.** Storing `&p` from a `for (auto &p : range)`
loop over the row-view range dangles (the cached view is overwritten on ++).
Hit in fetch_particles (observables), lb_couple_particles, dipolar
gather_particle_data, cells.get_interacting_neighbors. Fixed via stable
pool pointers or owning view-copy buffers (see the multipass section above).
3. **PARTNUM ordering / pending-size.** Ghost count must run after locals are
committed; a cell resized as a ghost dst reports rows+staged (Cell::size())
for downstream ghost layers in the same PARTNUM pass.
4. **resize_ghost_storage does not mark the store dirty.** The hybrid's internal
commit-callback therefore has to `mark_particle_store_dirty()` before syncing,
or the staged ghosts never commit (the bare sync early-returns on a clean
store) -> MPI_ERR_TRUNCATE in the hybrid ghost DATA transfer. This was the
hybrid_decomposition (canary) failure.
6. **Staged inserts invisible within the same resort (hybrid ordering).** In
HybridDecomposition::resort the type-based moves stage particles into their
target child cells; pre-flip those inserts were immediate and VISIBLE to the
child resorts that follow. Post-flip they are staged (not committed), so the
child resorts iterated a different set -> different final placement/order ->
1-ULP energy drift in `test_resort`. Fixed by committing (commit-callback)
between the type-moves and the child resorts, restoring pre-flip visibility.

5. **add_particle immediate commit vs teardown.** add_particle/add_local_particle
commit immediately (so the returned pointer + index are live). A unit test
that never resets the global System then releases store Kokkos columns after
Kokkos::finalize -> abort; fixed by resetting the System in that test's main.

## Gates

| Gate | Result |
|---|---|
| C++ unit tests ALL (`build`, `ctest -L unit_test`) | **147/147 passed** |
| Identity `--mode lj` | `bdd2022cb08c8c1c74fe0860ccb53dd0d9833966e97fb2f8f2d8f7232c5d7a69` -- matches reference (BITWISE) |
| Identity `--mode p3m` | `141cc4aa9343884dd57756cd612343cc99016c59bc87b6f575e35b7fa02ee6ce` -- matches reference (BITWISE) |
| Physics battery (full pattern) | **72/74**; the 2 failures are the pre-existing stale-`*_processed.py` build-dir artifacts (sample_reaction_methods_*), which PASS after `find build/testsuite -name '*_processed.py' -delete` -- documented environmental in T1/T2/T3, not code |
| hybrid_decomposition (canary) `--repeat until-fail:3` | **3/3 PASS** (1-core + 4-rank) |
| Particle round-trip (particle_slice\|particle_property\|checkpoint) | **26/26 passed** |
| Guard script | PASS (verified it CATCHES a synthetic `rows().insert()`; clean on tree) |
| CI-mirror core (Debug + -Werror) | **0 errors / 0 warnings** |
| Maxset core (ADDITIONAL_CHECKS) + key maxset unit tests | **0 errors**; 14/14 maxset unit tests pass under runtime ADDITIONAL_CHECKS |

Commit: `b169cd56aa` (`core: cells hold store rows and hand out particle views (phase 7a)`).
1 change: 1 addition & 0 deletions build-ci-mirror
1 change: 1 addition & 0 deletions build-cuda
1 change: 1 addition & 0 deletions build-gcc14
1 change: 1 addition & 0 deletions build-maxset
Loading
Loading