Library for the Development and Use of Phylogenetic Network Methods
Network topology modification operations for MCMC search (add/remove hybrid, NNI, node height).
Add a hybrid (reticulation) edge between two existing edges. This is the primary move for *increasing* network complexity. A new directed edge is created that represents a hybridization or introgression event between two lineages. How it works (normal case, source != destination):: BEFORE AFTER a x a x | | | | | | n1 -------> n2 (new hybrid edge) | | | | b y b y Two fresh internal nodes (n1, n2) are inserted by splitting the *source* and *destination* edges respectively. n2 is flagged as a reticulation node, and a directed edge n1 -> n2 is added. **Bubble (genome-duplication) case** -- when *source* and *destination* are the same edge, two parallel edges with equal inheritance probabilities (gamma = 0.5) are created between two new internal nodes, modeling a whole-genome duplication event.
| Parameter | Type | Description |
|---|---|---|
| net | The network to modify **in place**. | |
| source | The edge that will donate the new outgoing lineage. | |
| destination | The edge that will receive the incoming hybrid lineage. | |
| t_src | Optional divergence time for the new source-side node. | |
| t_dest | Optional divergence time for the new reticulation node. |
Remove a hybrid (reticulation) edge and clean up the two degree-2 nodes. This is the inverse of :func:`add_hybrid`. It lowers the network level by one by deleting the hybrid edge and the two internal nodes that were created when the hybrid was originally added. Expected local topology around the hybrid edge:: a x | | v v n1 -------> n2 <-- hybrid_edge (n1 -> n2, n2 is reticulation) | | v v b y After removal, n1 and n2 are deleted and replaced by two simple edges:: a x | | v v b y **Preconditions** -- the move only makes sense when n1 and n2 were originally inserted by :func:`add_hybrid`, meaning: * n1 has exactly one parent (a) and one non-hybrid child (b). * n2 has exactly one other parent (x) and one child (y).
| Parameter | Type | Description |
|---|---|---|
| net | The network to modify **in place**. | |
| hybrid_edge | The reticulation edge to remove. Its destination (n2) must be flagged as a reticulation node. |
NetworkError: If the edge is not a valid hybrid edge, or if the surrounding topology does not allow clean removal (i.e. the nodes don't satisfy the degree constraints listed above).Perform a random nearest-neighbor interchange (NNI) on the network. NNI is the workhorse topology-exploration move. It swaps two subtrees across an internal edge, producing a "neighboring" topology that differs by exactly one bipartition. Because it never adds or removes reticulations, the network level is unchanged. Diagram of the swap:: BEFORE AFTER a a / \ / \ c b d b / \ / \ d ... c ... A child *c* of *a* (that is not *b*) and a child *d* of *b* are chosen uniformly at random and exchanged. All edge metadata (length, gamma, weight, tag) travel with their respective subtrees. **Eligible edges** -- only internal edges whose endpoints are both non-reticulation and non-leaf are considered, so the move never disrupts reticulation structure.
| Parameter | Type | Description |
|---|---|---|
| net | The network to modify **in place**. |
NetworkError: If no eligible internal edge exists, or if the randomly selected edge lacks sufficient children on both sides.Change the divergence time (height) of an internal node. In a time-consistent (ultrametric) network, every node has a "time" value that increases from root toward the tips. This move slides a single node up or down the time axis while preserving the parent > child ordering on both sides. **Simple mode** (``extend=False``, default): Only the time of *n* is changed. Parent and child times are untouched, so the branch lengths into and out of *n* change accordingly. The new height must fall strictly between the nearest parent time and the nearest child time. **Extend mode** (``extend=True``): The node *and all of its immediate children* are shifted by the same delta, preserving the branch lengths between *n* and its children. This is useful when you want to move a speciation event without distorting the subtree below it. The move is rejected if any shifted child would collide with or pass its own grandchildren in time.
| Parameter | Type | Description |
|---|---|---|
| n | The internal node whose height should change. | |
| net | The network containing *n*. | |
| height | The proposed new time value for *n*. | |
| extend | If True, shift children by the same delta to keep branch lengths within the subtree constant. |
NetworkError: If *n* is a root or leaf (no parents or children), or if the proposed height violates time-ordering constraints.Perform a random subtree prune-and-regraft (SPR) on a tree. SPR is a larger-radius topology move than NNI: it detaches an entire subtree and reattaches it at a different location in the tree, allowing the search to jump further across tree space in a single step. **Restriction:** currently limited to level-0 networks (i.e. trees with no reticulations). This is checked up front. Algorithm outline:: 1. Pick a random "prune edge" (u, v) where v is not the root and at least one edge exists entirely outside the subtree rooted at v (so there is somewhere to reattach). 2. Remove the prune edge (u, v). 3. If u is now a degree-2 pass-through node, suppress it by merging its parent and child edges (see _suppress_chain_node). 4. Pick a random "host edge" (a, b) that lies completely outside the pruned subtree. 5. Insert a new internal node k on (a, b), splitting it into (a, k) and (k, b), and attach the pruned subtree as (k, v). The host edge's length is split randomly between (a, k) and (k, b), and the original prune-edge length is assigned to (k, v). The result is always a bifurcating tree with the same leaf set.
| Parameter | Type | Description |
|---|---|---|
| net | The tree to modify **in place**. |
NetworkError: If the network is not level-0, or if no valid prune or reattachment edge can be found.Yield every possible relabeling of the leaf taxa on the network. This is useful for exhaustive searches over small networks: given a fixed internal topology, the function produces one network copy for every permutation of the leaf labels across the existing pendant (leaf-edge) positions. For *n* leaves this yields *n!* networks, so use with caution on networks with more than ~10 leaves. How it works: 1. Record the canonical leaf ordering and each leaf's parent node. 2. For each permutation of the leaf list, make a deep copy of the network, detach all leaves from their parents, and rewire them according to the permutation -- leaf ``perm[i]`` gets attached to the parent that originally held leaf ``i``. 3. Edge metadata (length, gamma, weight, tag) travel with the leaf they were originally attached to, not with the slot. The original network is **never modified**.
| Parameter | Type | Description |
|---|---|---|
| net | The template network to permute. | |
| Yields | A deep copy of *net* with leaves rewired according to one permutation. |
NetworkError: If any leaf has zero or multiple parents (each leaf must have exactly one parent for the rewiring to be well-defined).