STEPS modeling (API_2 / steps.interface)
STEPS simulates stochastic reaction-diffusion in 3D geometries. Since v3.6 the
idiomatic interface is API_2 (steps.interface) — a pythonic, context-manager
style. Write all new models in it. This skill encodes the manual's conventions
(https://steps.sourceforge.net/manual) plus gotchas that bite AI agents.
Read reference.md for the full cheatsheet (solver table, units, EField/complexes/parallel, ResultSelector recipes, mesh-from-pipeline notes). Start from a template and adapt. The rules below are the ones you must not get wrong.
Cardinal rules
-
Imports, in order.
import steps.interfaceFIRST, then star-import the submodules. Never mix API_1 and API_2 in one script.import steps.interface from steps.model import * from steps.geom import * from steps.rng import * from steps.sim import * from steps.saving import * -
Objects name themselves from the assignment.
cyt = Compartment.Create(...)names the object'cyt'by reading the source line. You then address it in simulation paths assim.cyt.Ca.Count. So the left-hand variable name is the simulation name — pick names you will use, and they must be valid Python identifiers. -
.Create()needs a realname = X.Create(...)source line. It fails in a loop, comprehension, multi-line call,python -c, or any REPL without source ("does not match the expected format for automatic assignment"). To create objects programmatically (e.g. one ROI per region in a loop), call the constructor withname=instead:ROI(triList, name=regionName). -
Reactions live in a
withblock and use a ReactionManagerr.r = ReactionManager() with vsys: A + B <r['bind']> C # reversible r['bind'].K = 1e6, 0.7 # (kf, kb); irreversible '>r[..]>' takes one KSurface reactions tag each species with its location:
.iinner compartment,.oouter compartment,.sthe patch surface. All volume reactants must be in the same compartment. A pump:Ca.i + P.s >r['pump']> Ca.o + P.s. -
Patch inner compartment comes first:
Patch.Create(tris, inner, outer, ssys). A boundary membrane with nothing outside usesouter=None. -
Everything is SI, but
Concis molar. Diffusion constants m²/s, lengths m,Countis integer molecules,Concis mol/L (molar).TetMesh.LoadGmsh(path, scale=...)multiplies mesh coords into metres — a nanometre mesh needsscale=1e-9. -
Record with ResultSelector, not ad-hoc reads.
rs = ResultSelector(sim) caConc = rs.cyt.Ca.Conc # build a selector sim.toSave(caConc, dt=0.01) # register BEFORE running ... caConc.data[run, timeIdx, col]; caConc.time[run]; caConc.labelsGroup access:
.ALL(),.LIST(A, B),.MATCH(regex),.SUM(...), and for mesh elementssim.TET(t)/TETS(list),sim.TRI(t)/TRIS(list).sim.MATCH('regex')over the whole sim selects every compartment/patch/ROI whose name matches — the clean way to address many named regions at once. -
Run loop:
sim.newRun()resets state; set initial conditions after it; thensim.run(endTime). Wrap multiple runs in aforloop for statistics. -
Reserved names. Single capitals (
A=Area,V=Volume,D,I, ...) and feature words (Ves, ...) are reserved — Species/object names must be descriptive multi-letter identifiers (Ca,IP3,mitoMemb).
Canonical skeleton (spatial, Tetexact)
See templates/spatial_tetexact.py for a runnable version; templates/well_mixed.py for the well-mixed case.
import steps.interface
from steps.model import *
from steps.geom import *
from steps.rng import *
from steps.sim import *
from steps.saving import *
mdl = Model()
r = ReactionManager()
with mdl:
Ca, P = Species.Create()
vsys = VolumeSystem.Create()
with vsys:
Diffusion(Ca, 1e-12)
ssys = SurfaceSystem.Create()
with ssys:
Ca.i + P.s >r['pump']> Ca.o + P.s
r['pump'].K = 2e8
mesh = TetMesh.LoadGmsh('model.msh', scale=1e-9) # mesh coords nm -> m
with mesh:
cyt = Compartment.Create(mesh.tetGroups[(0, 'cytosol')], vsys)
er = Compartment.Create(mesh.tetGroups[(0, 'ER')], vsys)
memb = Patch.Create(mesh.triGroups[(0, 'ER_surface')], er, cyt, ssys)
rng = RNG('mt19937', 512, 1234)
sim = Simulation('Tetexact', mdl, mesh, rng)
rs = ResultSelector(sim)
caConc = rs.cyt.Ca.Conc
sim.toSave(caConc, dt=0.01)
sim.newRun()
sim.cyt.Ca.Conc = 1e-6 # molar
sim.memb.P.Count = 100
sim.run(1.0)
print(caConc.data[0, -1], 'at t =', caConc.time[0, -1])
Validate a script
Before running a STEPS script, lint it for the pitfalls above — no execution, STEPS not required:
python validate_steps_script.py model.py
It reports each issue with a concrete fix: import order / API_1↔API_2 mixing,
.Create() misuse (loops, no assignment), reserved Species names, units &
biological scale (Conc is molar, Diffusion in m²/s, mesh scale=),
newRun/toSave/run ordering, and reaction rates declared but never set. Exit code is
non-zero on ERRORs (so it fits a pre-run / CI gate); --selftest checks the checker.
Maintaining this skill
When you hit a STEPS scripting error this skill didn't prevent, extend it in the
same change: add a check (with a self-test case) to validate_steps_script.py, and
record the trap in the cardinal rules above, the reference.md common-errors table,
and the debugging checklist below. Keep the validator and the docs in sync — the
validator should catch every documented gotcha.
Debugging checklist
Assertion Fail: i < 4at solver setup → a surface triangle appears as two elements for one tet face (duplicate interface facets). Each facet needs exactly one triangle element; dedup the mesh. (See reference.md → "Meshes from a pipeline".)- "does not match the expected format for automatic assignment" → a
.Create()not on its ownname = ...Create(...)line; use the constructor withname=(rule 3). - "name is reserved" → rename the Species/object (rule 9).
- "Outer compartment not defined for this patch" → a surface reaction used
.oon a boundary patch created withouter=None; that patch can only host.i/.sspecies. - Counts all zero after a run → reaction
Ktoo small, or the volume reactant's density at the surface is ~0; raiseKorCount, or check units (Concis molar).