A pass is the smallest unit of work the engine knows how to schedule. It reads the latest IR revision, decides what to change, and commits a new revision. If you want to inject a biological effect, a corruption mode, or an instrumentation hook that GenAIRR doesn't ship out of the box, you write a pass. This recipe walks through the three pieces every pass needs and shows how to slot it into a pipeline.
Every pass exposes three things the runtime needs to call it: a stable name (used by the
trace and error messages), a set of effects it declares (so the scheduler knows what
invariants it can preserve), and an apply function that reads the latest
simulation revision and returns a new one.
outcome.trace()This pass is a toy: at simulation time it walks the assembled sequence and transversions every Nth base. Useless for biology, perfect for getting the wiring right.
from GenAIRR._engine import Pass, Simulation, Rng
class EveryNthPass:
def __init__(self, stride: int):
self.stride = stride
def name(self) -> str:
return "custom.every_nth"
def effects(self):
return frozenset({"point_mutation"})
def apply(self, sim: Simulation, rng: Rng, contracts) -> Simulation | None:
new_sim = sim.to_builder()
for i in range(0, len(sim), self.stride):
old = sim.base_at(i)
new_sim.set_base(i, "T" if old != "T" else "A")
new_sim.record_choice(
address=f"custom.every_nth.flip[{i}]",
value={"pos": i, "from": old, "to": "T"},
)
return new_sim.build()
The Experiment DSL exposes the canonical phases. To insert a custom pass alongside them,
drop one level lower and assemble the PassPlan by hand. The runtime accepts
any list of passes; the DSL is just a curated default.
from GenAIRR._engine import PassPlan, CompiledSimulator
import GenAIRR as ga
refdata = ga.dataconfig_to_refdata(ga.HUMAN_IGH_OGRDB)
plan = PassPlan.recombine(refdata)
# slot the custom pass after recombination
plan.push_python_pass(EveryNthPass(stride=25))
sim = CompiledSimulator(plan)
out = sim.run(n=10, seed=42)
# the choices end up in the trace under your address
out[0].trace().prefix_query("custom.every_nth")
The engine source ships canonical passes that match the patterns you'll need. Read them for shape; copy the structure for your own.
See engine_rs/src/passes/mutate/s5f.rs for the SHM pattern: walk the V region,
draw a position from a per-base distribution, draw a substitute, set the base, record a
trace entry.
See engine_rs/src/passes/corrupt/indel.rs for insertions/deletions: more
bookkeeping because regions and provenance both shift, but the same name/effects/apply
skeleton.
See engine_rs/src/passes/corrupt/rev_comp.rs for a pass that touches only
metadata (flips orientation, no base edits). Useful template for adding annotation
without changing the pool.