Semi-Directed Generator#
The phylozoo.core.network.sdnetwork.generator module provides the
SemiDirectedGenerator class and related
functions for working with level-k semi-directed network generators.
Most classes and functions on this page can be imported from the semi-directed generator module:
from phylozoo.core.network.sdnetwork.generator import *
# or directly
from phylozoo.core.network.sdnetwork.generator import SemiDirectedGenerator
Some side classes are defined in the directed generator module and reused here.
What is a (semi-directed) generator?#
A semi-directed generator is a fundamental building block of a level-k semi-directed
phylogenetic network. Formally, it is a mixed multi-graph that can be obtained from a blob of a
semi-directed network by suppressing all vertices with degree 2 except those with indegree 2.
Equivalently, a semi-directed generator can be obtained from a simple semi-directed network
(i.e., a network consisting of a single internal blob) by removing all leaves and thereafter
suppressing all vertices with degree 2 except those with indegree 2. Generators are built from a
MixedMultiGraph.
Working with generators#
Creating a generator#
You can obtain a semi-directed generator in two ways.
From a graph
Build a generator from a MixedMultiGraph.
A level-0 generator is a single node with no edges. A level-1 generator is a single node with one
bidirected self-loop. Since MixedMultiGraphs do not support bidirected edges, the generator is built from a single node with one undirected self-loop.
from phylozoo.core.network.sdnetwork.generator import SemiDirectedGenerator
from phylozoo.core.primitives.m_multigraph import MixedMultiGraph
# Level-1 from MixedMultiGraph (one node, one undirected self-loop)
gen_graph = MixedMultiGraph(undirected_edges=[(4, 4)])
generator = SemiDirectedGenerator(gen_graph)
From a directed generator
The function dgenerator_to_sdgenerator() converts
a DirectedGenerator to a
SemiDirectedGenerator by: (1) turning
all non-hybrid edges into undirected edges and keeping edges into hybrid nodes directed; (2)
suppressing the degree-2 root node. Level-0 stays one node with no edges. Level-1 becomes one node
with one undirected self-loop (bidirected edge).
from phylozoo.core.network.sdnetwork.generator import dgenerator_to_sdgenerator
from phylozoo.core.network.dnetwork.generator import DirectedGenerator
from phylozoo.core.primitives.d_multigraph import DirectedMultiGraph
d_gen_graph = DirectedMultiGraph(edges=[(8, 4), (8, 4)])
d_gen = DirectedGenerator(d_gen_graph)
sd_gen = dgenerator_to_sdgenerator(d_gen)
sd_gen.level
# 1
sd_gen.graph.number_of_nodes()
# 1
list(sd_gen.graph.undirected_edges_iter(keys=True))
# [(4, 4, 0)]
Note
Upon construction, a generator is validated using validate() to ensure it has a valid structure that adheres to the definition of a semi-directed generator.
For details on PhyloZoo’s validation system and how to disable it for performance reasons, see
Validation.
Accessing generator attributes#
The underlying graph, level, and hybrid nodes of a semi-directed generator can be accessed as follows. The hybrid nodes are the nodes with indegree ≥ 2 from directed edges; the level is the number of hybrid nodes. (Semi-directed generators have no single root node.)
graph = generator.graph
level = generator.level
hybrid_nodes = generator.hybrid_nodes
Sides of a generator#
The sides of a generator are precisely the points where leaves can be attached to create a semi-directed network. The module provides node sides (from the directed generator module) and both directed and undirected edge sides.
Base classes
Side— The base class from which all other side types inherit.NodeSideis the base class for node attachment.EdgeSideis the base class for edge attachment.
Node sides
HybridSide— A hybrid node (in-degree ≥ 2 from directed edges) with out-degree 0 that serves as an attachment point.IsolatedNodeSide— The single vertex of a level-0 generator. Exactly one such side exists when level is 0.
Edge sides
DirEdgeSide— A directed edge (identified byu,v,key).UndirEdgeSide— An undirected edge (u,v,key).BidirectedEdgeSide— The undirected self-loop of a level-1 generator (single node, one edge). Identified bynodeandkey.
The sides of a generator can be accessed as follows:
from phylozoo.core.network.sdnetwork.generator import (
SemiDirectedGenerator, UndirEdgeSide, BidirectedEdgeSide,
)
from phylozoo.core.network.dnetwork.generator import HybridSide, DirEdgeSide, IsolatedNodeSide
from phylozoo.core.primitives.m_multigraph import MixedMultiGraph
mm = MixedMultiGraph(undirected_edges=[(1, 1)])
gen = SemiDirectedGenerator(mm)
hybrid_sides = gen.hybrid_sides
edge_sides = gen.edge_sides # DirEdgeSide | UndirEdgeSide | BidirectedEdgeSide
directed_edge_sides = gen.directed_edge_sides
undirected_edge_sides = gen.undirected_edge_sides
parallel_directed_sides = gen.parallel_directed_edge_sides
non_parallel_directed_sides = gen.non_parallel_directed_edge_sides
for side in gen.sides:
if isinstance(side, HybridSide):
print(f"Hybrid side at node: {side.node}")
elif isinstance(side, DirEdgeSide):
print(f"Directed edge side: ({side.u}, {side.v}, key={side.key})")
elif isinstance(side, UndirEdgeSide):
print(f"Undirected edge side: ({side.u}, {side.v}, key={side.key})")
elif isinstance(side, BidirectedEdgeSide):
print(f"Bidirected edge side: node={side.node}, key={side.key}")
elif isinstance(side, IsolatedNodeSide):
print(f"Isolated node side: {side.node}")
Attaching leaves to a generator#
The function attach_leaves_to_generator() takes a
semi-directed generator and a mapping side_taxa from sides to ordered lists of taxon labels, and returns a
SemiDirectedPhyNetwork by attaching new leaf nodes to
the generator’s graph.
Rules
Every
HybridSidemust appear inside_taxawith exactly one taxon.Every
IsolatedNodeSide(level-0 only) must appear inside_taxawith exactly three taxa (binary networks).Edge sides (
DirEdgeSide,UndirEdgeSide,BidirectedEdgeSide) may be omitted or given an empty list except for level-1: the singleBidirectedEdgeSidemust receive at least one taxon (so the self-loop is always subdivided away). Note that the order of the taxa on the sides is important.At least two taxa must be attached in total across all sides.
from phylozoo.core.network.sdnetwork.generator import (
SemiDirectedGenerator, attach_leaves_to_generator,
)
from phylozoo.core.primitives.m_multigraph import MixedMultiGraph
mm = MixedMultiGraph(undirected_edges=[(1, 1)])
gen = SemiDirectedGenerator(mm)
hybrid_side = gen.hybrid_sides[0]
bidir_side = gen.edge_sides[0]
network = attach_leaves_to_generator(
gen, {hybrid_side: ["H"], bidir_side: ["P", "Q"]}
)
sorted(network.taxa)
# ['H', 'P', 'Q']
Enumerating all generators#
The function all_level_k_generators() generates
all distinct level-k semi-directed generators (up to isomorphism). It obtains all level-k directed
generators, converts each with dgenerator_to_sdgenerator(),
then deduplicates by isomorphism.
from phylozoo.core.network.sdnetwork.generator import all_level_k_generators
level_0 = all_level_k_generators(0) # 1 generator
level_1 = all_level_k_generators(1) # 1 generator
level_2 = all_level_k_generators(2) # 2 generators
See Also#
Semi-Directed Network Class - The SemiDirectedPhyNetwork class
Directed Generator - Directed generators
Validation - Validation system and disabling validation
API Reference - Complete function signatures and detailed examples