Changelog#
Version History#
0.2#
0.2.6#
Added#
Standard Graphviz
dotformat forMixedMultiGraph, alongside the existingphylozoo-dot(which remains the default). Undirected edges are written asu -> v [dir=none]inside adigraph, so — unlikephylozoo-dot, which mixes->and--in onegraphblock and is not valid DOT — the output opens in any Graphviz tool and round-trips losslessly, including parallel directed and undirected edges. The DOT scaffolding (escaping, attribute formatting/parsing, node-id coercion and document parsing) is now shared between the directed- and mixed-multigraph handlers in a newphylozoo.utils.io.format_utils.dotmodule.SemiDirectedGeneratorandDirectedGeneratornow inheritIOMixin, gainingsave/load/to_string/from_string/convert. They inherit the formats of their underlying graph —phylozoo-dot(default) anddotfor the semi-directed generator,dot(default) andedgelistfor the directed one — so a generator can be serialised and reconstructed directly, without routing throughgenerator.graph. Reading rebuilds (and validates) the generator from the parsed graph.Generator construction is now exposed as reusable single steps:
gambette_step()(apply the Gambette R1/R2 rules + isomorphism deletion to a collection of directed generators) andsemidirect_generators()(semi-direct a collection of directed generators + isomorphism deletion). Theall_level_k_generatorsfunctions are now thin loops over these, and a caller can advance / semi-direct their own (e.g. saved) generator set without rebuilding from level 0. Both accept mixed-level inputs (gambette_stepalso accepts level-0 generators) and assume their inputs are valid.
Changed#
Generator enumeration now deduplicates with a Weisfeiler-Lehman graph hash added to the cheap-invariant key (new helpers
_get_graph_wl_hashon the directed and mixed multigraph isomorphism modules; multiplicity- and direction-aware via a simple-graph encoding). This splits isomorphism candidates into far finer groups, so the exact VF2 check runs only within tiny groups — the level-4 directed enumeration drops from ~276 s to ~6 s (~44x), and the full level-4 semi-directed enumeration from ~285 s to ~9 s. Output is identical (1993 directed / 307 semi-directed level-4 generators); exactness is unchanged, since the hash is only a grouping key and VF2 remains the final check.
Fixed#
phylozoo-dotround-trip forMixedMultiGraph: the writer injected alabel=<node id>attribute on every label-less node, and the reader parsed that unquoted value back as anint. A round-tripped graph therefore carried a spurious non-stringlabelon every node, which broke downstream label validation — e.g.attach_leaves_to_generator()raisedPhyloZooTypeError: ... non-string label. The writer no longer emits the redundant label, and the reader keeps anylabelvalue as a string. Generator graphs (and anyMixedMultiGraph) now round-trip losslessly, including parallel directed and undirected edges, and a loaded generator is directly usable to build networks — without first rebuilding a clean graph from its edge lists.
0.2.5#
Fixed#
hybrid_sides: a reticulation whose child slot was already occupied by an undirected (backbone) edge was still reported as a hybrid side. Attaching the required leaf to it then produced an invalid hybrid (total degree = in-degree + 2), raising a degree error inattach_leaves_to_generator().hybrid_sidesnow only includes hybrid nodes whose child slot is free (no directed out-edge and no incident undirected edge); such occupied reticulations remain hybrid nodes (the generator’s level is unchanged) and their descendants are reached through the incident undirected edge side. The level-1 bidirected self-loop node is still a hybrid side.
0.2.4#
Fixed#
subnetwork(): the updown-path cache lookup used anor-chain (cache.get(key) or ...), which treated an empty cached path set (falsy) as a cache miss and silently recomputed the path. Fixed by using an explicit_MISS_SENTINEL = object()sentinel so empty sets are handled correctly.
0.2.3#
Fix PyPi deployment
0.2.2#
Added#
make_lsa: bool = Falseparameter ondisplayed_trees(): whenTrue, each displayed tree is converted to its LSA network before being returned.label_rotation(float | None)attribute onSDNetStyle.None(default) auto-aligns each taxon label with the direction of the leaf’s connecting edge; a fixed float (e.g.-7) applies a uniform tilt to all labels, which reduces overlap in dense force-directed layouts.anchorparameter on the internaldraw_labelrendering helper (phylozoo.viz._render): taxon labels are now automatically placed on the side of the node facing away from the connecting edge, preventing labels from crossing edges inneatoand related layouts.New tutorials:
Plotting a Semi-Directed Network — step-by-step guide to plotting a semi-directed phylogenetic network, using the Xiphophorus swordfish example from [Holtgrefe et al., 2025].
Tutorial on displayed-tree indistinguishable networks: introduces the concept and provides worked example code.
Changed#
Default layout for
SemiDirectedPhyNetworkchanged from'twopi'to'neato'(Graphviz spring-embedder). All affected call sites and documentation updated.SDNetStyledefault values updated to better suit theneatolayout:node_color='white',node_size=80.0,leaf_color='#0a0a0a',leaf_size=100.0,hybrid_color='#fcc0bc',label_offset=0.01(previously lighter colours and larger sizes).Semi-directed network visualization tutorial and styling manual updated to reflect the new style defaults,
neatolayout, andlabel_rotationparameter.Quickstart tutorial revised: network node accessor methods, hybrid network example, and plotting instructions updated.
Citation format in README and documentation updated to a full bibliographic reference for [Holtgrefe et al., 2025].
Performance#
k_taxon_subnetworks(): all \(\binom{n}{2}\) pairwise up-down vertex sets are now pre-computed once and cached before the combination loop. Eachsubnetwork()call performs a dictionary lookup instead of re-invokingupdown_path_vertices(). For \(k=5\) on \(n=13\) taxa this reducesupdown_path_verticescalls from \(\binom{13}{5} \cdot \binom{5}{2} = 12{,}870\) to \(\binom{13}{2} = 78\), a \({\approx}165\times\) reduction.subnetwork(): removed a redundantMixedMultiGraph.copy()call.subgraph()already returns a fresh independent object; the subsequent.copy()was an unnecessary second allocation.identify_parallel_edges()/subnetwork(): extracted_identify_parallel_edges_inplace(graph, exclude_nodes)as an internal helper that modifies aMixedMultiGraphin-place.subnetworknow calls this helper directly on the working graph before the singlesdnetwork_from_graph()call, eliminating oneMixedMultiGraph.copy()and oneSemiDirectedPhyNetworkconstruction per call whenidentify_parallel_edges=True. The publicidentify_parallel_edgesfunction is an unchanged thin wrapper.Combined effect: on a level-3 network with \(k=11\) backbone leaves (14 taxa, 2,002 quinnets) quinnet extraction time fell from ≈22 s to ≈4 s (≈5× end-to-end speedup).
Fixed#
License badge in
README.mdcorrected to display MIT.
Internal#
Distance matrix helper functions in
phylozoo.core.distance.classificationsandphylozoo.core.distance.decompositionrefactored for readability; no API change.Edge routing for the
pz-radialand spring-embedder (neato/fdp) layouts improved inphylozoo.viz.sdnetwork.layout.
0.2.1#
Added#
Split decomposition ([Bandelt and Dress, 1992]) for distance matrices:
isolation_index()— computes the isolation index of a bipartition with respect to a distance matrix.split_decomposition()— canonical decompositiond = d^0 + Σ α_S δ_S, returning aWeightedSplitSystemof all d-splits and the split-prime residual as aDistanceMatrix.Both functions are re-exported from
phylozoo.core.distance.
New classification functions in
phylozoo.core.distance.classifications:is_tree_metric()— four-point condition check (Numba-accelerated, \(O(n^4)\)).is_totally_decomposable()— checks whether the split-prime residual is zero.
distances_from_splitsystem()↔split_decomposition()round-trip tests andtree_from_splitsystem()↔induced_splits()round-trip tests.Documentation: new Split Decomposition section in the distance manual; updated Algorithms section in the split-system manual; new
api/core/distanceentry for the decomposition module. BibTeX entryBandelt1992added tobibliography.bib.
Changed#
_check_kalmanson_conditionsmoved from a nested closure insideis_kalmanson()to a module-level@njitfunction, consistent with the other Numba helpers in the module.
0.2.0#
Added#
New
phylozoo.core.tripletmodule providingTriplet,TripletProfile, andTripletProfileSetfor working with rooted three-taxon trees (characterised by a trivial 1|2 splita|bcor by an unresolved 3-taxon star). All three classes are re-exported from the top-levelphylozoonamespace.New
displayed_triplets()derivation onDirectedPhyNetwork, returning aTripletProfileSetaggregated over the displayed trees.Manual chapter
Triplets(overview + per-class pages) and anapi/core/tripletsAPI page.Manual page
Parallel Executionandapi/utils/parallelAPI page documentingphylozoo.utils.parallelas the standard interface for parallel execution in PhyloZoo. No functions currently expose aparallelparameter; the module is the intended way to introduce parallelization in future implementations.New
CIGitHub Actions workflow (.github/workflows/ci.yml) runningpytest(Python 3.10 and 3.11),ruff, andblack --checkon every push tomasterand every pull request. See the Testing Guide for details. A CI badge has been added to the project README.
Changed#
ParallelBackendclass docstring restructured to plain prose (the enum members are now described inline rather than in a NumPy-styleAttributessection) to avoid duplicate Sphinx object descriptions.Black, Ruff, and Mypy fixes have been made throughout the codebase.
Removed#
phylozoo.core.distance.operations(and the entireoperations.pyfile). The Traveling Salesman Problem solvers (optimal_tsp_tour,approximate_tsp_tour) have been removed, together with the corresponding manual section and API entry. Their implementation is now part of the dependent packagephysquirrel.phylozoo.core.quartet.qdistance.quartet_distance_with_partitionand its documentation in the Quartet Profile Sets manual page.
0.1#
0.1.2#
Fixed newick parsing branch length order: it must be after the hybrid marker, not before.
0.1.1#
Fixed docs build: added matplotlib to docs dependencies.
0.1.0#
Initial public release.