Skip to content

Add MathDescription data model to VCML reader/writer#41

Open
jcschaff wants to merge 4 commits into
mainfrom
math-description-datamodel
Open

Add MathDescription data model to VCML reader/writer#41
jcschaff wants to merge 4 commits into
mainfrom
math-description-datamodel

Conversation

@jcschaff

@jcschaff jcschaff commented Jun 17, 2026

Copy link
Copy Markdown
Member

Summary

VCell math descriptions are generated by libvcell for each biomodel application (e.g. as a side effect of utils.update_biomodel), but they were not exposed in pyvcell's datamodels. The VCML reader skipped the <MathDescription> child of each SimulationSpec, and the writer emitted only a dummy placeholder.

This PR introduces a MathDescription pydantic data model and wires it into the reader and writer, so the generated math is now a first-class, inspectable part of the Application — mirroring the VCML structure where MathDescription is a child of SimulationSpec. Along the way it also fixes pre-existing writer gaps that prevented nonspatial (dim-0) and stochastic biomodels from round-tripping.

import pyvcell.vcml as vc

bm = vc.load_vcml_file("model.vcml")
bm = vc.update_biomodel(bm)          # libvcell (re)generates math
math = bm.applications[0].math_description
math.constants                       # [Constant(name='KMOLE', exp='...'), ...]
math.compartment_subdomains[0].pde_equations   # [PdeEquation(name='s0', ...), ...]

What's modeled

MathDescription → constants, functions, state variables (volume/membrane/particle/stochastic/…), and CompartmentSubDomain / MembraneSubDomain containing:

  • ODE (nonspatial) and PDE (spatial: rate, diffusion, initial, boundaries, velocity) equations
  • Membrane jump conditions (in/out flux)
  • Stochastic constructs — variable initial counts, jump processes, effects
  • Particle (Smoldyn) constructs — particle jump processes, particle properties / initial counts

Element/attribute names follow VCell's Xmlproducer/XmlReader. Rare constructs (random variables, fast systems, events, filament/point subdomains, post-processing, Langevin specifics) are not modeled yet and are skipped consistently on read; they can be added incrementally.

Writer fixes (nonspatial / stochastic / counts)

These resolve pre-existing limitations so nonspatial and stochastic biomodels round-trip:

  • Nonspatial geometry: <BoundariesTypes> was emitted unconditionally, indexing six boundary faces and raising IndexError for nonspatial models (nonspatial membrane mappings legitimately carry none). Now emitted only when all six faces are present.
  • Stochastic flag: SimulationSpec was written without the Stochastic attribute, so a stochastic application came back as deterministic. Now written.
  • <InitialCount> species mappings: stochastic/particle models express species initial conditions as molecule counts rather than concentrations. The reader ignored <InitialCount>, so the initial condition was lost and libvcell rejected the written VCML with "Unrecognizable initial condition". Added SpeciesMapping.init_count (parsed/written; included in expressions for field-data extraction).

Implementation notes

  • Reader (vcml_reader.py): the MathDescription subtree is parsed by a dedicated, self-contained parser rather than the global tag-dispatch visitor, to avoid colliding with existing species-mapping handlers (Boundaries/Diffusion/Initial). Empty placeholder math descriptions are ignored.
  • Writer (vcml_writer.py): faithfully serializes math_description. When an application has no math (e.g. an in-memory model), the empty dummy_math_description placeholder is still written — libvcell requires it because each Simulation references a MathDescription.
  • Round-trip fidelity is preserved: the existing biomodel == new_biomodel writer tests pass with math now parsed.

Round-trip status

fixture pyvcell read→write→read libvcell (to_vcml_str / update_biomodel)
spatial deterministic (ODE/PDE/membrane, image-based)
nonspatial ODE
nonspatial stochastic
spatial particle (Smoldyn) ❌ — separate, deeper gap (see below)

Known follow-up (not in this PR): spatial particle models now pass the initial-condition check but fail inside libvcell's ParticleMathMapping.refreshMathDescription ("failed to generate math: null") — the writer doesn't yet emit everything the Smoldyn math mapping needs. They still round-trip through pyvcell itself.

Tests

New tests/vcml/test_math_description.py plus fixtures (copied from the vcell repo) covering nonspatial ODE, spatial PDE, membrane jump conditions, nonspatial stochastic, and spatial particle models, including full-biomodel round-trip equality across nonspatial and spatial geometries. Also a test_species_mapping_initial_count test. mypy and ruff clean; pytest tests/vcml tests/sbml passes (39 passed, 5 skipped).

🤖 Generated with Claude Code

jcschaff and others added 4 commits June 17, 2026 00:46
VCell math descriptions are generated by libvcell for each biomodel
application (e.g. as a side effect of utils.update_biomodel) but were not
exposed in pyvcell's datamodels — the VCML reader skipped the
<MathDescription> child of each SimulationSpec and the writer emitted only
a dummy placeholder.

This adds a MathDescription pydantic model (constants, functions, state
variables, compartment/membrane subdomains, ODE/PDE equations, membrane
jump conditions, and stochastic/particle constructs) as an optional
math_description field on Application, mirroring the VCML structure where
MathDescription is a child of SimulationSpec.

- models.py: MathDescription and child node types
- vcml_reader.py: self-contained MathDescription subtree parser (avoids
  tag collisions with the existing species-mapping visitor); empty
  placeholder math descriptions are ignored
- vcml_writer.py: faithful MathDescription serializer; the dummy
  placeholder is still written when no math is present (required by
  libvcell, which references it from each Simulation)
- tests + fixtures covering ODE (nonspatial), PDE (spatial), membrane
  jump conditions, stochastic jump processes, and particle processes,
  plus round-trip fidelity

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The biomodel writer assumed every compartment mapping carried six boundary
types and emitted <BoundariesTypes> unconditionally, raising IndexError for
nonspatial models (nonspatial membrane mappings legitimately carry none).
It also dropped the Application.stochastic flag, since SimulationSpec was
written without the Stochastic attribute.

- Emit <BoundariesTypes> only when all six faces are present
- Write the Stochastic attribute on SimulationSpec

Nonspatial ODE and stochastic biomodels, and spatial particle biomodels,
now round-trip through the writer; the math round-trip test uses the full
biomodel writer (read -> write -> read) across nonspatial and spatial
geometries.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stochastic and particle models specify species initial conditions as
molecule counts (<InitialCount>) rather than concentrations
(<InitialConcentration>). The reader ignored <InitialCount>, so the
initial condition was lost and libvcell rejected the written VCML with
"Unrecognizable initial condition".

- SpeciesMapping gains an init_count field (and includes it in
  expressions for field-data extraction)
- Reader parses <InitialCount>; writer emits it when present

Nonspatial stochastic biomodels now round-trip through both pyvcell and
libvcell (to_vcml_str / update_biomodel). Spatial particle (Smoldyn)
models round-trip through pyvcell; their libvcell math generation is a
separate, deeper mapping gap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
make check is now clean: pre-commit, mypy (303 files), and deptry all pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <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