You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
--max-family-size and --family-speed on zombi2 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 takes none for unbounded growth. --family-speed takes a ByFamily draw 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 from loss = … * DrivenBy(…), which makes them
delete more often. Set both and they multiply. An extent takes the modifiers its resolution
wires on a rate: OnTime at ordered, OnTime and DrivenBy at 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 to rates.rate; as_extent moved there from rates.distributions and now
returns an Extent rather 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 a scope(base) × modifiers expression 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 — DrivenBy still raises, with a message saying so, rather than
being silently dropped. (#248)
Per-family rate heterogeneity at the ordered resolution.ByFamily on duplication, transfer, loss, inversion, transposition or translocation, 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. ByFamily remains refused on origination (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_size now 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 high ByFamily factor — could grow without bound; at ByFamily(spread=1.0) an ordered run
produced roughly twice the duplications of the equivalent, already-capped family run. The two now
agree. Pass max_family_size=None for 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>_extension and 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-length and --gene-length are unchanged: they size the initial genome, they are not
extents. (#246)
A bare extent number is now the mean, at every resolution.duplication_extent=3 at 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. Write Fixed(3) for the old behaviour. None remains a
single unit. The new zombi2.rates.as_extent coerces this; as_distribution is 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)