Status: planning (2026-09-22). This is a living plan: tick PRs off as they merge. The
solver itself lives in virtualcell/vcell-fenics; its
side of the contract is done, and its tracker is
docs/integration/vcell-solver-integration.md.
vcell-fenics already runs as a VCell solver on its own side (PRs #147–#157). It takes the
SimulationTask XML and writes a VTU + zarr results bundle. It speaks VCell's status protocol:
[[[progress/data]]] on stdout, or REST WorkerEvents through a Langevin-style messaging config. It
accepts -tid N, and its image and SIF are on GHCR. Nothing in this repo knows it exists yet.
The contract is fixed by vcell-fenics
ADR 010 (results bundle)
and ADR 011 (solver contract);
ADR 011 §6 lists the Java follow-up this plan sequences. It runs as a series of small PRs
against virtualcell/vcell, plus one against vcell-fluxcd.
Decisions taken:
- Desktop first: a Docker quick-run plus the local field viewer, all verifiable on a developer machine. HPC comes second.
- Gated behind a property until viewing works end to end.
- MPI later. The first HPC pass is single-rank.
- Each PR below is its own branch off
master, and each merges with a merge commit. - Build and test per
BUILDING.md(Java 17). Run per-module tests withmvn test -pl <module> -Dtest=…. - Mirror progress in the vcell-fenics tracker.
- Line numbers below are approximate, taken from
masterat925d63a83e.
docs/plan-fenics.md.
Templates: MovingBoundarySolver for shape, and LangevinSolver for the messaging config and
--vc-* argv (both on master).
Solver definition
vcell-core/.../solver/SolverDescription.java: addFEniCSx.- Database and short name
FEniCSx. - Features
{Feature_Spatial, Feature_Deterministic}. SupportedTimeSpec.DEFAULT_EXPLICIT_UNIFORM.- KiSAO
"KISAO"placeholder; long descriptionSolverLongDesc.FENICSX. - Add an
isFenicsSolver()helper. - No native
SolverExecutable.
- Database and short name
SolverExecutable.java: add no entry.canQuickRunneeds a branch for FEniCSx instead (getExeswould throwUnsupportedOperationExceptionon null).
Solver class
- New
vcell-core/.../solvers/FenicsSolver.java extends SimpleCompiledSolver. initialize()writesSimID_*_.fenicsMessagingConfigwhen messaging is on. Put it in the same properties format asLangevinSolver.writeLangevinMessagingConfig, which is whatstatus.pyparses. Add the extension constant toSimDataConstants.getMathExecutableCommand()returnsvcell-fenics --simtask <simtask.xml> --out <userDir>plus either--vc-send-status-config=<cfg>or--vc-print-status.- The simtask file already exists:
SolverFactory.createSolverwrites it. - The server appends
-tid Nitself, and the CLI accepts it.
- The simtask file already exists:
getApplicationMessage()parsesdata:/progress:, copied from Langevin.- A launcher hook (e.g. a settable
commandPrefix) lets V2 wrap the argv indocker run …. HPC doesn't need it: SlurmProxy adds its own container prefix.
Factory and gate
SolverFactory.java: add aFACTORY.put(FEniCSx, …)entry.- Gate: add a
vcell.fenics.enabledproperty (default false) toPropertyLoader. Filter FEniCSx out of the solver combo box (SolverTaskDescriptionAdvancedPanel~l.343, next to the deprecated filter) and out ofcanQuickRun/canServerRunwhile it is off.
Tests
- Extend
SolverDescriptionRegression(db-name/label round-trips; checkdiffSupportingMathfor the new spatial member). - A
FenicsSolverTestfor the argv in both messaging modes and for the config file contents.
Found while building V2 (vcell-fenics side), both resolved 2026-09-22:
- The image and SIF packages were private on GHCR, because the vcell-fenics repo is private. Both packages are now public, and anonymous pulls work.
- The image was linux/amd64 only.
ARM64_RUNNER=ubuntu-24.04-armis now set on vcell-fenics, so the image is multi-arch (amd64 + arm64) and Apple-silicon desktops run it natively. V2'slinux/amd64fallback remains for any image that lacks a native build.
Container runner
- New small
ContainerRunnerutility in vcell-core/org.vcell.util.exe. - It finds
dockerthroughResourceUtil.getExecutable(VCellConfiguration → PATH), then checks the daemon withdocker info. - Image comes from the property
vcell.fenics.image, default a pinnedghcr.io/virtualcell/vcell-fenics:<sha>. - It builds
docker run --rm -v <simdir>:<simdir> [--user uid:gid on unix] <image> vcell-fenics …. The entrypoint already takes this form.
Quick-run wiring
ClientSimManager.createQuickRunSolver(~l.1091): for FEniCSx, skipprepareSolverExecutableand install the docker prefix onFenicsSolver.SimulationListPanel.canQuickRun(~l.1058): allow FEniCSx when the gate is on and Docker is present. Otherwise show an explanatory message.- First pull:
docker image inspect, and if the image is missing, rundocker pullin anAsynchClientTaskwith a progress dialog before starting the solver.
Results and cleanup
- Results: until V3, finishing a FEniCSx quick run reports the bundle path instead of calling
showSimulationResults0. That call would fail inSimulationData, which looks for the.log. - Cleanup:
ResourceUtil.getLocalSimDir(~l.321) must deleteSimID_*.fenics/directories recursively.File.delete()fails silently on a non-empty directory.
Split in two:
- V3a ✅ (#2087): the
VtuGridParserfixes, plusorg.vcell.solver.fenics.FenicsBundle, the bundle reader. It lives in vcell-core so the data server can reuse it in V5. Both are tested against real bundles. - V3b ✅ (#2088): a bundle data source in
FieldViewerServer(FenicsBundleViews), point data in the endpoints and inwebapp-viewer, and the entry point: a finished FEniCSx quick run opens the browser viewer. The dimension fix belongs in the bundle's/grid(from gdim), not inhandleGridVtu, whose producers never send a 3D surface.
VtuGridParser fixes (vcell-client/.../viz/)
- Add
VTK_LINE(length measure, picking). - Compute triangle/polygon area and pick in 3D, instead of the shoelace formula that drops z.
- Reject a VTU with a
compressorattribute instead of misreading it. - In
handleGridVtu, reportdimensionfrom the domain's gdim so a 3D membrane isn't reported as 2D.
Bundle reader
- New
FenicsBundleinvcell-client/.../viz/(or vcell-core, if the data server needs it in V5). - Reads the manifest from
.zattrsand rejects a newerschema. - Row counts come from
times, never from array shapes. - Chunks: a hand-rolled zarr v2 read (
.zarrayJSON,"<row>.0"chunk files,Inflater, little-endian f8, NaN fill). No zarr library exists in the poms, and ADR 010 planned for this. - Handles only the one-segment case now, but parses
segments/prefixso a segmented bundle can follow.
FieldViewerServer
- Extract a small data-source interface from the
DataSourcerecord and its branches. - Add a
BundleSource: fixed profile → STATIC-like, segmented → TIME_VARYING keyed by segment. - Endpoints:
/infolists point variables./fieldreturns"location":"point"./timeseriesinterpolates within the located cell./statsreads the precomputedstats/array and adds atotalfield.
webapp-viewer/viewer.js
- Branch on
field.location === "point":getPointData().setScalarsandsetScalarModeToUsePointData. - Point-aware readout and stats.
- Render line cells for 2D membranes.
- Check the vtk.wasm calls with
probe.html.
Entry point
- For FEniCSx sims, "results" registers a
BundleSourcefor the local simdir and opens the browser viewer directly, bypassingPDEDataViewer. - The FEniCSx gate implies
vcell.fieldViewer.enabledfor this path.
Fixture
- A small bundle generated by
vcell-fenics --simtask tests/fixtures/simtask/…and committed undervcell-client/src/test/resources/. - Unit tests for the reader, the parser fixes and the endpoint JSON.
PR V4 — HPC, single rank (vcell + vcell-fluxcd) — ✅ vcell #2089; vcell-fluxcd #56 (dev) awaits a dev deploy
As built:
- The FEniCSx image properties are optional in
SlurmProxy: a site without them refuses the solver. So they are not added toHtcSimulationWorker's required list. - The server-side bundle cleanup is in
DBBackupAndClean.deleteFileAndLinkandResultSetCrawler.AmplistorUtils(legacy object store) is untouched. - The vcell-fenics image is now multi-arch (linux/amd64 + linux/arm64, native arm64 runner), so Apple-silicon desktops run it natively. The SIF stays amd64.
vcell
PropertyLoader: addhtc_vcellfenics_apptainer_image/htc_vcellfenics_solver_list, declared so theVCELL_HTC_VCELLFENICS_*env names are recognised.HtcSimulationWorkerrequired-property list (~l.463–526).SlurmProxy.generateScript(~l.941–955): add a fourthelse ifbranch and update its error message.- Test: a
SlurmProxyTestcase plus a golden fixture underslurm_fixtures/fenicsx/(simtask and the expected.slurm.sub). - Docs:
docs/apptainer-image-build.md, with vcell-fenics as an independently-tagged image. - Server cleanup:
DBBackupAndClean.deleteFileAndLinkandResultSetCrawlermust delete.fenics/directories recursively.
vcell-fluxcd (separate PR)
- Add
VCELL_HTC_VCELLFENICS_APPTAINER_IMAGEand_SOLVER_LIST=FEniCSxtosubmit.envfor every site (dev, stage, prod, island, remote). The prepull job treats an empty value as fatal. - Add the variable to
SIF_VARSinkustomize/base/vcell-sif-prepull-job.yaml. - Check the SIF size against the 4Gi
emptyDir/ephemeral limits and raise them if needed.
Verify on dev: deploy, run a FEniCSx sim from a client pointed at dev, and watch STARTING → PROGRESS → COMPLETED. The CLI sends 1003 itself, and the bundle lands in the user dir.
- V5, remote viewing ✅ (#2090): built as the RPC
DataSetController.getFenicsBundleFile(vcdID, path), a default method implemented on the local and messaging paths. Reads go through aBundleStore(a directory, or the data server viaDataServerBundleStore, cached). A server-run FEniCSx sim's results open in the field viewer. Access follows the data server's existing read policy; the bundle lookup confines paths to the bundle. Originally planned as: aDataSetControllerRPC (or vcell-rest route) that serves bundle files by relative path, plus a remoteBundleSourceso server-run results open in the field viewer. - Geometry check ✅ (#2091), found by trying a desktop Quick Run. FEniCSx refuses non-analytic
and non-2D/3D geometries as ERROR issues (
FenicsSolver.unsupportedReasons→Simulation.gatherIssues), so the run stops before a container starts. A failure shows only the solver'serror:line. - Run phases in the status: a FEniCSx run sat at "0%" while it meshed and JIT-compiled. vcell-fenics
(ADR 011 §4) now names its phase —
loading model,meshing,compiling,solving,writing results— as[[[progress:<phase>:NN.N%]]]locally and asWORKEREVENT_PROGRESS|<phase>in a PROGRESS event's status message remotely; older VCells parse both as plain progress.FenicsSolver.getProgressMessageturns the marker into a progressSimulationMessagewith the phase; the quick-run dialog and the simulation list's progress bar show it ("meshing", "solving 37%") throughSimulationMessage.getProgressPhase/SimulationStatus.getRunningPhaseDisplay; andSimulationJobStatus.isSupercededBylets a new phase at unchanged progress through. - V6, UI and options:
FenicsSolverOptionswired throughSolverTaskDescription/XMLTags/Xmlproducer/XmlReader/ VCML. Copy the VCML shape fromSundialsPdeSolverOptions, not MB, whosegetVCMLnever writesEndBlock. Keep it small:taskDescisvarchar2(4000).- A self-hiding options panel in
SolverTaskDescriptionAdvancedPanel. - FEM mesh size in
MeshTabPanel. - Estimates in
checkSimulationParameters.
- V7, MPI: emit
--ntasksinslurmScriptInit, wrap withsrun --mpi=pmi2/ in-containermpiexecfromncpus, and add fixture tests. - Flip the gate on ✅ (2026-09-26, after V3 and V4 ran end to end on dev):
vcell.fenics.enabledandvcell.fieldViewer.enableddefault to true inPropertyLoader. The installer'svmoptions.txtcarries both astrueso a user can find and turn them off, and vcell-fluxcd setsVCELL_FENICS_ENABLED/VCELL_FIELDVIEWER_ENABLEDfor the services. Release-notes entry: the CHANGELOG's[Unreleased]section. - pyvcell: a
FenicsResultbesideResult/MovingBoundaryResult(portvcell_fenics/results/reader.py, adding segment handling), and upstream the SimulationTask reader.
Why. VCell's moving-boundary applications list only the native MovingBoundary solver, although the FEniCSx ALE backend solves these problems and is cross-validated against mbsolver. The solver path refuses them today, so they aren't silently solved on the initial shape. M1–M4 are vcell-fenics PRs, tracked in its integration tracker. M5 is here.
Scope of the first pass (decided 2026-09-22):
- 2D, with species in the moving interior volume only (e.g. a cell carrying its cytoplasm, with an empty exterior).
- Remeshing included: a new bundle segment per remesh.
- Species-dependent front velocities allowed (explicit one-step lag), cross-validated against mbsolver.
- Membrane species on a moving front come in a later pass.
vcell-fenics
- M1 — bridge. Read
MembraneSubDomain/<Velocity>from the SimulationTask. pyvcell drops it; it is e.g.sobj_cell1_ec0_velX→sproc_0.velocityX→sin(t). Attach it as prescribed motion on the interior compartment (v = v_b), and replace the blanket refusal with specific ones. Also fixes an identifier-regex bug in function inlining. - M2 — results bundle. Per-row node coordinates (
_coords) and a new segment per remesh, as ADR 010 already reserves: writer, recorder (per-row domain measure), reader, and PVD export. - M3 — runner. A moving path using backward Euler with remeshing (
backend/ale.py), advancingsim.t(the ALE drivers didn't, sosin(t)froze at t = 0). - M4 — cross-validation against mbsolver: translation, a remeshing expansion, and a species-dependent velocity. It publishes a new image.
vcell
- M5 — offer and view:
Feature_MovingonSolverDescription.FEniCSx, so it's listed for moving-boundary applications.FenicsSolver.unsupportedReasonsmirrors M1's refusals: 2D, interior-only species.FenicsBundle.coords(domain, row)reads_coords.FenicsBundleViewsserves each row's geometry for an ALE segment (geometryId …@t<row>, which the viewer's body-fitted path already re-fetches), locates time-series points in each row's mesh, and uses the measure per row.- Bump the default image and the vcell-fluxcd pin to the M4 image.
- Verification: a moving-boundary application lists FEniCSx; a desktop Quick Run → the browser viewer shows the moving mesh while scrubbing time.
- Status (2026-09-23): M1–M4 merged in vcell-fenics (#160–#163). VCell's semantics turned out to be
lab-frame ("swept") species, not carried: the front moves and a species without its own velocity stays
put in the lab, so M3 transports it relative to the ALE mesh (the mesh velocity is bookkeeping and
cancels out). M5 is in review: the tests use two real moving bundles (a translation, and a remesh
across two segments), and the default image is
sha-bfdf853.
VCell image-based geometries (segmented 2D/3D label images, 16 % of the corpus) are realized by vcell-fenics
as smoothed, body-fitted meshes with any topology — nested regions, regions cut by the image edge, and
junctions where three subvolumes meet (vcell-fenics ADR 012; PRs #168–#173). On VCell's tutorial image a
nucleocytoplasmic-exchange model agrees with fvsolver to 1.7 % in the nuclear filling curve and 0.8 % in the
cytosolic field (vcell-fenics cross_validation/README.md).
- I6 (here):
FenicsSolver.unsupportedReasonsaccepts image subvolumes. It still refuses CSG, and image geometries in moving-boundary applications. The default image issha-cf08ec2: the first that realizes images issha-a651c4b, andsha-cf08ec2fixes a segfault it had meshing 3D images on Linux.
The cleavage furrow in 3D: a sphere pinched by an axisymmetric contractile ring, solved by vcell-fenics's 3D moving-boundary path (PRs #176–#178: 3D ALE, 3D remeshing, verification against the exact waist motion).
- Math:
MembraneSubDomaincarriesvelocityZ(VCML, the XML<Velocity><Z>, math comparison), andDiffEquMathMappinggenerates it on 3D geometries (it had refused anything but 2D). - Solvers: FEniCSx accepts 3D moving boundaries on analytic geometries. The native Moving Boundary solver
writes a 2D (x/y) input only, so it is refused for 3D with an ERROR issue (
MovingBoundary_Dimension_NotSupported). - GUI: the kinematics parameter table shows the Z components on 3D geometries.
- Tests:
FenicsSolverTestreads the task the client generated for the "Furrow 3D" application, checks the<Z>survives an XML round trip, and checks that the Moving Boundary solver is refused. - The default image is
sha-7790431: aftersha-8360060it adds models with any number of compartments and membranes (a nucleus in a cytosol in extracellular space, touching cells), membrane species beside a compartment with no species, subvolumes that touch the domain box, and per-compartment box-face boundary conditions (vcell-fenics #203, #204, #206, #207). Before it,sha-8360060added region sizes (vcRegionVolume/vcRegionArea), non-diffusing species (buffers, gating variables), a live simulation time, VCell's per-face reservoir BCs, and a clear refusal of FastSystem models (vcell-fenics #201, #202). Before it: the 3D remesh fallback ofsha-479d653(vcell-fenics #180) and, insha-ca4604f, models with several species per compartment and membrane species coupled to both compartments (vcell-fenics #184, #183: the Ran tutorial and receptor–ligand binding), it adds region variables. These are VCell's well-mixed species and the membrane potential, one value per region, solved on both coupled solvers (vcell-fenics #197, #198, #200). An exact interface-flux Jacobian comes with it. Still refused: membrane species with bulk species on one side only; region variables in single-compartment and moving-boundary models, and on several disconnected regions.
- Per PR:
mvn teston the touched modules with the new unit tests.- For V2/V3, a desktop end-to-end run:
- Build the client and set
vcell.fenics.enabled=trueandvcell.fieldViewer.enabled=true. - Open an analytic-geometry spatial model and choose FEniCSx.
- Quick-run: the image pulls, the progress bar advances, and the bundle appears under
~/.vcell/simdata/temp. - Open the results: the field viewer shows point data, and the stats match
python -m vcell_fenics.resultson the same bundle.
- Build the client and set
- Tracker: update
docs/integration/vcell-solver-integration.mdafter each merge. - V4: the golden-script test, then the dev-site run described above.