Releases: AADavin/zombi2
Release list
v0.11.0
Added
- An examples gallery, published at
/gallery.htmland linked from the landing page. Five levels
— species, genomes, sequences, traits, and the joining that couples them — each a short, runnable
recipe that simulates with ZOMBI2 and plots with Phylustrator (trees viaph.trees, genomes,
synteny and alignments viaph.genomes). Click any figure for the exact code that reproduces it.
The source lives ingallery/(self-contained, likeanalyses/) and a[gallery]optional extra
regenerates the page; the landing page's four level cards now link to its per-level sections, with a
"See the gallery" button beside "Read the docs". (#257)
v0.10.0
Added
- A joint trait can jump at the split as well as along branches.
traits.discrete(at_speciation=…)
already worked insidesimulate_joint, so a trait could drive speciation and change at each
speciation — but nothing said so, nothing tested it, and Chapter 9 documented only the along-branch
case, which made the combination look unavailable. It is now documented, with an example, and pinned
by tests: a jump at a split is logged ason_speciation, a change along a branch ason_branch,
so the two can be told apart when scoring a method. (#256)
v0.9.0
Added
--max-family-sizeand--family-speedonzombi2 genomes. Both existed in the Python API at
the family and ordered resolutions but had no flag, which mattered once the growth cap became
on-by-default at ordered: a command-line run had a bound it could neither adjust nor switch off. The
cap keeps the model's int/float distinction — an int is an absolute copy count, a float that
multiple of the tree's lineages — and takesnonefor unbounded growth.--family-speedtakes a
ByFamilydraw in the same written form as every rate flag. Both are refused under
--resolution nucleotide, each with its own reason rather than one blanket explanation. (#251)- An extent now takes modifiers, as SPEC §6 says it should:
base × modifiers, no scope. So
loss_extent = 150 * DrivenBy(habitat, {"host": 6.0, "free": 1.0})makes host-restricted lineages
delete in bigger chunks — a different model fromloss = … * DrivenBy(…), which makes them
delete more often. Set both and they multiply. An extent takes the modifiers its resolution
wires on a rate:OnTimeat ordered,OnTimeandDrivenByat nucleotide; anything else raises.
Unlike a rate's, an extent's modifier is read when an event fires, so it changes no rate and adds no
step to the Gillespie clock. The concept has its own module,zombi2.rates.extent(Extent,
as_extent), parallel torates.rate;as_extentmoved there fromrates.distributionsand now
returns anExtentrather than a bare distribution. A scope on an extent is refused — it is already
an absolute size, so there is no "per what?" to answer. (#250) - Conditioning at the nucleotide resolution: every rate there now takes a
DrivenBy. A trait can
drive how much DNA a lineage sheds — genome reduction as it is usually meant, on a genome measured
in base pairs rather than in family tokens — and can drive the rearrangements as well, which
nothing could do before: Chapter 9 named "let a trait speed up inversion" as the limitation people
hit first, and it is now expressible. Same mechanism and same written form as the family
resolution, from a trait file or an in-memory result:
loss = 0.8 * DrivenBy(habitat, {"host": 20.0, "free": 0.5}). A driven rate becomes per lineage, so
the affected lineage is drawn with the same weights the total was summed with, and a driver
switching mid-branch is a step the Gillespie stops at.transfer_to— where a transfer lands — stays
family-resolution only; a nucleotide transfer's rate can be driven, its recipient rule cannot.
(#249) - The nucleotide resolution now speaks the rate grammar. Its rates were bare floats with the
scope hardcoded; each is now ascope(base) × modifiersexpression like every other level's, with
the defaults stated rather than implied — per lineage for the gene events (the rate says how
often a lineage does it, the extent how much DNA it touches, so the number reads the same whatever
the genome's size) and per chromosome for the number-changing tier. A bare number stays a bare
number, so nothing existing changes. The skyline now works there:
inversion = 5.0 * OnTime({0: 1.0, 3: 0.2}), in Python, on the command line and in a--params
file; the engine re-reads its rates at each step instead of racing past it. The resolution declares
what it wires and refuses the rest —DrivenBystill raises, with a message saying so, rather than
being silently dropped. (#248) - Per-family rate heterogeneity at the ordered resolution.
ByFamilyonduplication,
transfer,loss,inversion,transpositionortranslocation, and the family-wide
family_speed=, now work at--resolution ordered; both were previously refused. The weight lands
on the segment an event covers, not on the gene it started from (SPEC §6) — weighting the start
would apply a family's own rate to its neighbours, and the neighbourhood is reshuffled by every
rearrangement, so the parameter would not name a fixed thing over a run. A run's weight is the
mean over the genes it covers, which is what makes a run with no weights set behave exactly as
before.ByFamilyremains refused onorigination(there is no family yet to have drawn a factor)
and on the chromosome tier (a fission acts on a whole replicon). (#247) max_family_sizenow works at the ordered resolution too, and is on by default. A segment may
carry several families, and several copies of one, so a run is refused when it would take any of
them past the quota — the whole run, never part of it, since clipping a run to the genes still under
quota would quietly shorten runs exactly where the genome is crowded and so reshape the extent
distribution. It reduces to the family resolution's condition when a run is a single gene. (#247)
Changed
- An ordered run is now bounded by default (
max_family_size=10.0, the same multiple of the
tree's lineages the family resolution has always used), where it previously had no cap at all.
Duplication compounds, so a family whose duplication rate sits above its loss rate — or one that
drew a highByFamilyfactor — could grow without bound; atByFamily(spread=1.0)an ordered run
produced roughly twice the duplications of the equivalent, already-capped family run. The two now
agree. Passmax_family_size=Nonefor the old unbounded behaviour. This changes results for any
ordered run that would have exceeded the cap. (#247) - One word for how much a segmental event takes:
extent. The ordered resolution called it
<event>_extensionand the nucleotide one<event>_length; both are now<event>_extent, in
Python and on the command line (--inversion-extent,--loss-extent, …). The unit is still set by
the resolution — genes at ordered, base pairs at nucleotide — which is what a resolution is for.
--root-lengthand--gene-lengthare unchanged: they size the initial genome, they are not
extents. (#246) - A bare extent number is now the mean, at every resolution.
duplication_extent=3at the ordered
resolution used to mean exactly three genes every time and now means runs averaging three, which
is what the same number has always meant at the nucleotide resolution. This is a change of
results, not just of spelling: an ordered run that passed a bare number will now produce a spread
of segment sizes rather than one size. WriteFixed(3)for the old behaviour.Noneremains a
single unit. The newzombi2.rates.as_extentcoerces this;as_distributionis untouched, since a
bare number is rightly a fixed value for the per-family rate specs that use it. (#246) - The nucleotide resolution refuses an extent shape it cannot honour. It draws each arc's far end
directly from the genome's legal breakpoints, so anything other than a geometric extent would have
to be re-weighted over that set rather than drawn; passing one now raises instead of being silently
approximated. An extent below 1 bp is also refused. (#246) - The manual's title page now names the ZOMBI2 version it documents —
Version 0.8.0 — July 2026,
under the author. The version is read from the package at build time rather than typed into the
manual, so the book cannot claim a release the code does not have, and the build stops if it cannot
be read. (#246)
Fixed
- Chapter 6 said an event whose extent the genome cannot supply "still fires, just shorter", which its
own table contradicts two lines above — an arc whose nearest legal breakpoint lies beyond the extent
you asked for comes out longer. The correction runs both ways, and the chapter now says so, along
with the one degenerate case (a replicon with no legal end in reach) where the event is skipped. (#246)
v0.8.0
Changed
-
The genome level's first resolution is now
family, notunordered. It is the default
resolution and the first thing a new user meets, and it was named for the feature it lacks rather
than for what it is. The other two resolutions keep their names, and the model itself is untouched —
the three still share one engine, one rate grammar and one event log. This is a breaking rename
with no deprecation aliases:was is simulate_genomes_unordered(…)simulate_genomes_family(…)GenomesResultFamilyGenomesResultUnorderedGenomeFamilyGenomegenomes.unordered(…)genomes.family(…)zombi2.genomes.unorderedzombi2.genomes.family--resolution unordered, and the same value in a--paramsfile--resolution familyRenaming the result class also removes an asymmetry: the first resolution returned the unprefixed
GenomesResultwhile its two siblings carried a prefix, which read as a default plus two variants
rather than three resolutions of one model. The run log'sresolutionrow and the run-completion
summary now sayfamily; no output filename, directory or column header changes, so nothing that
reads a run's tables needs updating. Chapter 4 is now Genomes I: gene families. (#245) -
families=is nowfamily_names=, onsimulate_genomes_family,simulate_genomes_orderedand
genomes.family(…);zombi2 joint --familiesis likewise now--family-names. The argument takes a
list of names and always did — the attribute it fills has beenresult.family_namesall along — so
input and output now agree, andinitial_families=100(a count) no longer reads as a sibling of the
name list beside it. (#245)
v0.7.0
Added
- Cross-level staleness guard. A level refuses to re-run in place when a later level built from it
is already in the run directory — re-running would leave that downstream output silently mismatched.
This covers both the pipeline chain (re-runninggenomeswould orphan thesequencesunder it) and
DrivenByconditioning (re-running atraitsrun that agenomesrate was conditioned on orphans
that genomes, and the sequences beneath it — the dependency is recorded in a smallconditioned_on
marker).--forcere-runs anyway and removes the now-stale downstream, so a run's levels can never
quietly disagree. The forward pipeline is unaffected (each level is run once); applies to the default
grouped layout —--flatcommingles the levels and is left to the user. (#243) - Onboarding nudges. After a level runs, a one-line pointer names the output file(s) worth looking
at — a species run names both its complete and its extant tree — so a newcomer can find what they
made. It says only what was written and where, never what to do next (a run is not one road);
suppressed by--quiet. The top-level--helpnow leads with the levels and the plain quickstart,
with theDrivenBy/ joint coupling note moved below them. (#244)
Fixed
- A non-nucleotide
sequencesrun no longer leaves an emptysequences/genomes/directory behind (the
assembled-genome FASTAs are a nucleotide-run output). (#244) - The
sequencesrun log now records the substitution model's effective parameters (e.g.kappa 2.0,
frequencies [0.25, 0.25, 0.25, 0.25]) instead of the bareNoneargs, so a run reproduces from its
log alone, without the reader having to know each model's defaults. (#244)
v0.6.0
Added
--quietonzombi2 tools format, matching the level commands — it suppresses the summary line,
for a log file or a batch of runs. (#242)
Changed
- Gene-copy ids are now written
g<id>in every genome table —genomes.tsv,genome_events.tsv,
gene_order.tsv,blocks.tsvandinitial_genome.tsv, in both thecopyandparentcolumns.
This is the same token the gene-tree Newick leaves, the alignment FASTA headers and the homology
tables already use, so a gene copy now joins across every file of a run with no translation step
(previously the tables held a bare integer while the trees/alignments heldg<id>). The column
names are unchanged; this is a breaking change to the values those columns hold. - A joint (
zombi2 joint) driver mapping that names a state outside the trait's declared alphabet is
now refused. A key that can never occur — a typo such as{"caev": 4.0}for a cave/surface trait —
is caught up front instead of silently applying to nothing. The conditioned (file-driven) path is
unchanged: it still refuses only a mapping that matches none of the driver's observed states.
Fixed
- The run-completion line no longer doubles a trailing slash: a directory given as
out/(as the
quickstart shows) now echoes aswrote out/, notwrote out//. (#242) - A conditioned rate whose
DrivenBypoints at a missing file now reports it as a missing driver file
(with the path and how to fix it), instead of a bare[Errno 2] No such file or directory. (#242) parallel=no longer crashes with a rawBrokenProcessPoolwhen called from a notebook,python -c,
or a stdin heredoc (where worker processes cannot re-import your program). It falls back to
single-process with a one-line note; a.pyscript or the CLI still uses every core. (#242)
v0.5.0
Added
- Opt-in parallelism for the unordered-genome and sequence engines (
parallel=in Python,
--parallelon the CLI): a separate, worker-count-invariant engine that evolves independent units
(gene families, gene trees) across processes. It covers D/T/L/O withuniform/distancetransfer;
a driven or clade-basedtransfer_tofalls back to the serial engine (loudly). Alsostream_to=
to write each family straight to disk for the many-families regime. The serial path is the default
and is unchanged. (#233) Cladestransfer_to rule + theBetweenkernel — weight horizontal transfers by the donor's
and the recipient's named clade, so transfer can be steered to run between two clades rather
than within them (a topological rule, sibling of"distance"). Name a clade by a few of its tips or
a node id. The trait-driven form ismod.DrivenBy(trait, Between({...})). (#235)
Changed
- Faster nucleotide sequence runs at lower memory: a run-wide CDF cache and lazy genome assembly,
plus batched decoding and a streaming FASTA writer. (#234)
v0.4.0
Added
analyses/— a home for self-contained validation studies, each regenerating from fixed seeds:
RED node-age validation (does Relative Evolutionary Divergence recover node ages under a
realistic clock?) and the yeast inversion-rate study (inferring the genome inversion rate from
synteny with the nucleotide genome model). (#227)zombi2.tree— one home for theTreedatatype and its toolkit, all free functions:with_stem,
make_ultrametric,rescale,relative_evolutionary_divergence,red_scaled,distance
(Robinson–Foulds / branch-score), andread_newick(assume_extant=). (#228)- CLI:
zombi2 tools tree(prune / round / stem / rescale / RED) andzombi2 tools treedist. (#228) - The autocorrelated molecular clock (
FromParent) at the sequence level, alongside the
uncorrelatedByLineage. (#228) - Release tooling: this
CHANGELOG.mdandscripts/release.sh.
Changed
Tree,Node,read_newick,to_newick,prunemoved fromzombi2.speciestozombi2.tree
— import them fromzombi2.treenow (zombi2.specieskeeps the simulator andEvent). (#228)docs/design/MAP.mdcorrectness pass: the quarantine now points at the siblingZOMBI2_LEGACY/,
thejointCLI is listed, sequences/traits are marked built, and stale signatures/sections fixed.
(#228)
Fixed
zombi2 tools treedistmatches tips by label, not by parse-order node ids, so two external trees
are compared by taxon rather than by structural position. (#228)
ZOMBI2 0.3.0
The first release of the rewritten ZOMBI2 — a phylogenetic simulator of species trees, genomes, molecular sequences, and traits, as a pure-Python library and command-line interface. It simulates each level independently, conditioned, or jointly, and records the true history behind every dataset.
pip install zombi2What it does
- Species trees — birth–death with time / diversity / inherited-rate modifiers, mass extinctions, incomplete sampling, and fossils.
- Genomes, three resolutions:
- unordered — gene families under duplication, transfer, loss, and origination;
- ordered — chromosomes with inversions, transpositions, and translocations;
- nucleotide — genes and intergenes over a base-pair coordinate space.
Each resolution recovers the true gene trees.
- Sequences — nucleotide and protein substitution models along the gene trees, with strict and relaxed molecular clocks; emits alignments, ancestral sequences, and phylograms (branch lengths in substitutions/site).
- Traits — continuous (Brownian) and discrete trait evolution along the species tree.
- Conditioning and joining — grow one level driven by another.
- Tools — exact ortholog / paralog / xenolog homology tables and read-back utilities.
Notes
- Pure Python (
numpy+tqdm); no build step, no compiled extension. - Supersedes
0.2.0, which is yanked.0.3.0is the clean core.
Links
- Documentation: https://aadavin.github.io/zombi2/docs/
- Manual (PDF): https://aadavin.github.io/zombi2/zombi2-manual.pdf