Skip to content

v0.9.0

Choose a tag to compare

@AADavin AADavin released this 27 Jul 04:18

Added

  • --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 documentsVersion 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)