GenAIRR 2.0 is a breaking release. The Python Experiment DSL was reworked
around one principle: a fluent chain should read like a wet-lab protocol,
not a configuration file. Scripts written against 1.x will not run
unmodified. This guide is the migration map.
The Rust engine (engine_rs/) is unchanged in shape — only the Python
DSL surface moved. Pre-built _engine.RefDataConfig / ContractSet
objects you may have constructed manually still work the same way.
| v1.x | v2.0 |
|---|---|
respect=ga.productive() |
.productive_only() |
.using(v=...) |
.restrict_alleles(v=...) |
.recombine(trim=True) |
.recombine() (default on) |
.recombine(trim=False) |
.recombine().trim(enabled=False) |
mutate(count=...) |
mutate(rate=...) (preferred) or mutate(count=...) |
corrupt_pcr(count=...) |
pcr_amplify(count=...) |
corrupt_quality(count=...) |
sequencing_errors(count=...) |
corrupt_contaminants(prob=...) |
contaminate(prob=...) |
corrupt_indels(count=..., insertion_prob=...) |
polymerase_indels(count=..., insertion_prob=...) |
corrupt_ns(count=...) |
ambiguous_base_calls(count=...) |
corrupt_reverse_complement(prob=...) |
random_strand_orientation(prob=...) |
corrupt_5prime_loss(length=...) |
primer_trim_5prime(length=...) |
corrupt_3prime_loss(length=...) |
primer_trim_3prime(length=...) |
with_clonal_structure(n_clones=N, size=K) |
expand_clones(n_clones=N, per_clone=K) |
Parameter renames are inside expand_clones: n_clones → n, size → per_clone.
A realistic memory-B-cell chain in v1.x:
result = (
ga.Experiment.on("human_igh")
.recombine(trim=True)
.expand_clones(n_clones=50, per_clone=20)
.mutate(model="s5f", count=(5, 15))
.corrupt_5prime_loss(length=(0, 8))
.corrupt_indels(count=(0, 2), insertion_prob=0.5)
.corrupt_pcr(count=(0, 3))
.corrupt_ns(count=(0, 2))
.with_metadata(donor="P1", tissue="peripheral_blood")
.run_records(seed=42, respect=ga.productive())
)The same chain in v2.0:
result = (
ga.Experiment.on("human_igh")
.recombine()
.expand_clones(n=50, per_clone=20)
.mutate(rate=0.05)
.primer_trim_5prime(length=(0, 8))
.polymerase_indels(count=(0, 2), insertion_prob=0.5)
.pcr_amplify(count=(0, 3))
.ambiguous_base_calls(count=(0, 2))
.with_metadata(donor="P1", tissue="peripheral_blood")
.productive_only()
.run_records(seed=42)
)Try exp.describe() (new in v2.0) to read the chain back as biology —
if it reads weird, the chain itself is wrong.
In v1 you passed a contract bundle as a runtime kwarg on .run() /
.compile(). In v2 it's a chainable method that declares the constraint
on the experiment itself. The method is order-independent —
.productive_only() anywhere in the chain produces the same compiled
simulator.
# v1
exp.run_records(n=100, seed=0, respect=ga.productive())
# v2
exp.productive_only().run_records(n=100, seed=0)The respect= kwarg is removed from compile(), run(), run_records(),
stream(), and stream_records(). Pass strict=True if you want
runtime-impossible contracts to raise instead of fall back to permissive
sampling (unchanged from v1).
Same semantics (narrows allele sampling to a named subset, sampling stays uniform within the subset), but the new name actually describes what's happening. v1's "using" was ambiguous — using what for what?
Trim is now its own chainable method, called after .recombine().
Default-on stays the default-on; you only need to call .trim() if you
want to disable it or supply custom distributions.
# v1
exp.recombine(trim=False).mutate(...)
# v2
exp.recombine().trim(enabled=False).mutate(...).trim() is position-guarded: calling it before .recombine() or
after a mutation / corruption step raises ValueError. This catches
the confusion where v1 users sometimes thought the kwarg controlled
post-recombination trimming.
For custom distributions:
exp.recombine().trim(
v_3=[(0, 1.0), (1, 1.0), (2, 1.0)],
j_5=[(0, 1.0), (1, 1.0)],
)Omitted segments keep their empirical defaults.
mutate(rate=0.03) is the canonical v2 form. Per record, the count is
drawn from Poisson(rate × pool_len) — so a 5% rate on a 350-bp record
expects ~17 mutations, but the actual count varies stochastically and
scales with each record's actual sequence length. This matches how
immunologists report SHM in the literature.
mutate(count=…) is still accepted for benchmark scripts that need a
deterministic count independent of record length. You must pass
exactly one of rate or count — both, or neither, raises
ValueError.
The v1 corrupt_* umbrella lumped PCR amplification, sequencing
readout, library prep, and contamination under one engineering verb.
v2 splits them by wet-lab stage. See the table at the top of this guide.
A note on primer_trim_5prime / primer_trim_3prime vs .trim():
these are biologically distinct operations at different stages.
.trim()configures exonuclease trim during V(D)J recombination (RAG-mediated, before NP insertion). Only one per experiment.primer_trim_5prime/primer_trim_3primemodel read-end loss during library prep / sequencing. They drop bases off the assembled sequence and can be called alongside other library-stage steps.
The new name is the verb of the biological process (B-cell clonal
expansion). Parameter rename inside: size=20 becomes
per_clone=20. (n_clones stays as-is.) Both must be positive
ints; calling expand_clones() twice still raises.
These continue to work identically:
Experiment.on(config_name | DataConfig | RefDataConfig)..recombine(np1_lengths=..., np2_lengths=..., v_allele_weights=...)..with_metadata(**fields)..run(n=..., seed=..., strict=...)returningOutcomeobjects..run_records(n=..., seed=..., strict=..., expose_provenance=...)returningSimulationResult.- Engine-direct
ga._engine.PassPlan+ga._engine.run(...). - The S5F kernels and their string IDs (
hh_s5f,hkl_s5f, etc.). ga.productive()still returns aContractSet— it's just no longer the way you wire it into anExperiment(use.productive_only()instead).
Before opening a PR with your migrated scripts:
grep -rn 'corrupt_\|respect=\|\.using\|with_clonal_structure\|trim=' your_scripts/returns nothing relevant.- Each migrated experiment prints a clean
exp.describe()— read it back as a wet-lab protocol. - Your test suite passes against the new method shapes.
If something still feels wrong, file an issue with a v1 / v2 chain pair and the expected behavior — the v2 surface is designed to read naturally, and discrepancies with the wet-lab mental model are bugs we want to know about.