Skip to content

Releases: AADavin/zombi2

v0.11.0

Choose a tag to compare

@AADavin AADavin released this 27 Jul 08:32

Added

  • An examples gallery, published at /gallery.html and 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 via ph.trees, genomes,
    synteny and alignments via ph.genomes). Click any figure for the exact code that reproduces it.
    The source lives in gallery/ (self-contained, like analyses/) 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

Choose a tag to compare

@AADavin AADavin released this 27 Jul 07:12

Added

  • A joint trait can jump at the split as well as along branches. traits.discrete(at_speciation=…)
    already worked inside simulate_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 as on_speciation, a change along a branch as on_branch,
    so the two can be told apart when scoring a method. (#256)

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)

v0.8.0

Choose a tag to compare

@AADavin AADavin released this 26 Jul 19:33

Changed

  • The genome level's first resolution is now family, not unordered. 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(…)
    GenomesResult FamilyGenomesResult
    UnorderedGenome FamilyGenome
    genomes.unordered(…) genomes.family(…)
    zombi2.genomes.unordered zombi2.genomes.family
    --resolution unordered, and the same value in a --params file --resolution family

    Renaming the result class also removes an asymmetry: the first resolution returned the unprefixed
    GenomesResult while its two siblings carried a prefix, which read as a default plus two variants
    rather than three resolutions of one model. The run log's resolution row and the run-completion
    summary now say family; 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 now family_names=, on simulate_genomes_family, simulate_genomes_ordered and
    genomes.family(…); zombi2 joint --families is likewise now --family-names. The argument takes a
    list of names and always did — the attribute it fills has been result.family_names all along — so
    input and output now agree, and initial_families=100 (a count) no longer reads as a sibling of the
    name list beside it. (#245)

v0.7.0

Choose a tag to compare

@AADavin AADavin released this 26 Jul 15:20

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-running genomes would orphan the sequences under it) and
    DrivenBy conditioning (re-running a traits run that a genomes rate was conditioned on orphans
    that genomes, and the sequences beneath it — the dependency is recorded in a small conditioned_on
    marker). --force re-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 — --flat commingles 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 --help now leads with the levels and the plain quickstart,
    with the DrivenBy / joint coupling note moved below them. (#244)

Fixed

  • A non-nucleotide sequences run no longer leaves an empty sequences/genomes/ directory behind (the
    assembled-genome FASTAs are a nucleotide-run output). (#244)
  • The sequences run 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 bare None args, so a run reproduces from its
    log alone, without the reader having to know each model's defaults. (#244)

v0.6.0

Choose a tag to compare

@AADavin AADavin released this 26 Jul 14:12

Added

  • --quiet on zombi2 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 tablegenomes.tsv, genome_events.tsv,
    gene_order.tsv, blocks.tsv and initial_genome.tsv, in both the copy and parent columns.
    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 held g<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 as wrote out/, not wrote out//. (#242)
  • A conditioned rate whose DrivenBy points 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 raw BrokenProcessPool when 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 .py script or the CLI still uses every core. (#242)

v0.5.0

Choose a tag to compare

@AADavin AADavin released this 24 Jul 20:35

Added

  • Opt-in parallelism for the unordered-genome and sequence engines (parallel= in Python,
    --parallel on the CLI): a separate, worker-count-invariant engine that evolves independent units
    (gene families, gene trees) across processes. It covers D/T/L/O with uniform/distance transfer;
    a driven or clade-based transfer_to falls back to the serial engine (loudly). Also stream_to=
    to write each family straight to disk for the many-families regime. The serial path is the default
    and is unchanged. (#233)
  • Clades transfer_to rule + the Between kernel — 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 is mod.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

Choose a tag to compare

@AADavin AADavin released this 24 Jul 09:50

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 the Tree datatype and its toolkit, all free functions: with_stem,
    make_ultrametric, rescale, relative_evolutionary_divergence, red_scaled, distance
    (Robinson–Foulds / branch-score), and read_newick(assume_extant=). (#228)
  • CLI: zombi2 tools tree (prune / round / stem / rescale / RED) and zombi2 tools treedist. (#228)
  • The autocorrelated molecular clock (FromParent) at the sequence level, alongside the
    uncorrelated ByLineage. (#228)
  • Release tooling: this CHANGELOG.md and scripts/release.sh.

Changed

  • Tree, Node, read_newick, to_newick, prune moved from zombi2.species to zombi2.tree
    — import them from zombi2.tree now (zombi2.species keeps the simulator and Event). (#228)
  • docs/design/MAP.md correctness pass: the quarantine now points at the sibling ZOMBI2_LEGACY/,
    the joint CLI is listed, sequences/traits are marked built, and stale signatures/sections fixed.
    (#228)

Fixed

  • zombi2 tools treedist matches 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

Choose a tag to compare

@AADavin AADavin released this 23 Jul 15:43
a9474f2

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 zombi2

What 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.0 is the clean core.

Links