Skip to content

Field viewer: draw the particles of hybrid PDE/particle runs - #2160

Merged
jcschaff merged 1 commit into
masterfrom
field-viewer/particles
Oct 5, 2026
Merged

jcschaff merged 1 commit into
masterfrom
field-viewer/particles

Conversation

@jcschaff

@jcschaff jcschaff commented Oct 4, 2026

Copy link
Copy Markdown
Member

Lets the field viewer show the molecules of a hybrid PDE/particle simulation over its fields. The source is a results bundle (vcell-fenics ADR 010) that also records molecule positions. viva-pde-particle's process-bigraph co-simulations (PDE engine + Smoldyn) write these bundles, through its SpatialRecorder.

The bundle extension

A second root .zattrs key sits next to the vcell_fenics manifest:

"particles": {"schema": 1, "species": {"A": {"xyz": "particles/A/xyz", "count": "particles/A/count"}}}

Per species there are two arrays, stored exactly like the field arrays (little-endian float64, one row per chunk, zlib), so FenicsBundle's existing row reader reads them:

  • xyz: (T, cap, 3) lab-frame positions, NaN past the row's count;
  • count: (T, 1).

Rows are global output rows. A bundle without the key has no particles. A newer extension schema is ignored rather than refused, so the fields stay viewable.

Changes

  • FenicsBundle (vcell-core): getParticleSpecies() and particles(species, row).
  • FieldViewerServer / FenicsBundleViews:
    • /info lists particleSpecies when there are any.
    • New /particles?time=&max=: each species' positions at one time, at most max per species (default 20,000, evenly strided), plus count and shown.
    • A run without particles gets a 400; an unknown dataset gets a 404, as on the other routes.
  • webapp-viewer:
    • A Particles toggle, shown only for such runs, with a colour-keyed legend giving each species' molecule count. The tooltip says how many are drawn.
    • Each species is drawn as point sprites (renderPointsAsSpheresOn, size 5) in colours that stay distinct from the blue→red field LUT.
    • Positions are refetched once per time step.
    • While a cut is active, the molecules beyond it are hidden, as the mesh there is.
    • All points go in one poly-vertex cell, so building the vertices is a single call.
  • Docs: docs/3d-renderer-design.md (the extension and the endpoint), webapp-viewer/test/README.md.

Not covered

Finite-volume hybrid runs (vcell-fvsolver with embedded Smoldyn) save particle counts per voxel, not positions, so they don't get the layer. That would need the solver to save positions, or a binned display.

Tests

  • Fixture: receptor_3d_particles.fenics is the existing receptor_3d.fenics (manifest and fields byte-identical) with species A (12, 8, 4 molecules at the three times) and B (3 at each), placed inside cyto_dom's tetrahedra.

  • Java: new FenicsBundleParticlesTest covers the reader, /info, /particles, the max striding, and the 400/404 cases. It passes, along with FenicsBundleViewsTest, FenicsBundleTest and FieldViewerServerFvTest (31 tests).

  • Browser: new test_particles.py checks:

    • the layer and legend appear;
    • the time slider updates the positions;
    • a cut hides the molecules beyond it;
    • the toggle works;
    • runs without particles get no toggle.

    It is registered as fixture fenicsParticles (sim 557) in FieldViewerFixtureServer. The full browser suite passes: 225 tests across Chromium, WebKit and Firefox.

  • Manual check: a viva-pde-particle co-simulation of a ball (Netgen P1 mesh + Smoldyn, 15,611 molecules at t = 0.5 s) renders with the particles over B and the cut applied (8,886 drawn).

🤖 Generated with Claude Code

A results bundle may carry molecule positions next to its fields: a root
.zattrs key "particles" = {schema: 1, species: {name: {xyz, count}}}, with
xyz (T, cap, 3) and count (T, 1) stored like the field arrays (float64, one
row per chunk). viva-pde-particle's co-simulations write it.

- FenicsBundle reads the extension (getParticleSpecies, particles(species,
  row)) with its existing row reader; a newer extension schema is ignored.
- FieldViewerServer: /info lists particleSpecies; new /particles?time=&max=
  returns each species' positions at one time (at most max per species,
  default 20,000, evenly strided) and its molecule count.
- webapp-viewer: a Particles toggle with a colour-keyed legend; each species
  is drawn as point sprites over the field, refetched per time step, and the
  molecules beyond an active cut are hidden as the mesh there is.
- Tests: FenicsBundleParticlesTest (reader and endpoints) and
  test_particles.py (browser, all three engines) over a new fixture,
  receptor_3d.fenics with two species added.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant