Directed Network (Class)#

The phylozoo.core.network.dnetwork module defines the DirectedPhyNetwork class, the central representation of rooted, directed phylogenetic networks in PhyloZoo.

A directed phylogenetic network is a graph with a distinguished root and leaves representing taxa. The directed edges encode ancestor–descendant relationships, with nodes of higher in-degree represent reticulate evolutionary events such as hybridization, recombination, or horizontal gene transfer.

All classes and functions on this page can be imported from the core network module:

from phylozoo import DirectedPhyNetwork
# or directly
from phylozoo.core.network.dnetwork import DirectedPhyNetwork

What is a directed phylogenetic network?#

Mathematically, a directed phylogenetic network is a weakly connected, directed acyclic multigraph (i.e. allowing parallel edges) with the following nodes:

  • Root: exactly one node with in-degree 0

  • Leaves: nodes with out-degree 0 and in-degree 1, each corresponding to a taxon

  • Tree nodes: internal nodes with in-degree 1 and out-degree ≥ 2

  • Hybrid nodes: internal nodes with in-degree ≥ 2 and out-degree 1

Internal nodes may be unlabeled. Leaf nodes carry labels (taxa), either provided explicitly or generated automatically. For technical completeness, the empty network and the single-node network (where root and leaf coincide) are also considered valid.

Example Networks

A simple directed tree, in which all internal nodes are tree nodes:

        ┌─A
    ┌───┤
    │   └─B
────┤
    │   ┌─C
    └───┤
        └─D

A directed network containing a hybrid node with two incoming edges:

        ┌────A
    ┌───┤
    │   └──┬─B
    |      │
────┤      ├─C
    |      │
    │   ┌──┴─D
    └───┤
        └────E

Creating a DirectedPhyNetwork#

A DirectedPhyNetwork is defined by its graph structure and optional attributes. There are three main ways to construct such a network.

From edge and node specifications#

Edges may be given as tuples or dictionaries; nodes can explicitly be specified via (node_id, attributes) pairs, but are also automatically generated from the edges if not provided.

from phylozoo import DirectedPhyNetwork

network = DirectedPhyNetwork(
    edges=[("root", "A"), ("root", "B"), ("root", "C")],
)
# or
network = DirectedPhyNetwork(
    edges=[("root", "A"), ("root", "B"), ("root", "C")],
    nodes=[("A", {"label": "A"}), ("B", {"label": "B"}), ("C", {"label": "C"})]
)

From an existing graph#

Directed networks can also be constructed from existing graph objects using the dnetwork_from_graph() function. This is particularly useful when implementing algorithms that operate on mutable graph representations.

from phylozoo.core.network.dnetwork.conversions import dnetwork_from_graph
import networkx as nx

G = nx.MultiDiGraph()
G.add_edge("root", "A")

network = dnetwork_from_graph(G)

Tip

The dnetwork_from_graph function is especially useful when writing your own algorithms. It allows you to work directly with the underlying NetworkX or PhyloZoo graph object of a DirectedPhyNetwork, after which you can convert back to a DirectedPhyNetwork at the end of the algorithm.

# Example of writing an algorithm that works with the underlying graph
def my_algorithm(network: DirectedPhyNetwork):
    # Get the underlying PhyloZoo DirectedMultiGraph
    phylzoo_dm_graph = network._graph
    # Get the underlying NetworkX MultiDiGraph
    nx_graph = phylzoo_dm_graph._graph
    # Do something with the NetworkX graph or the PhyloZoo DirectedMultiGraph
    ...
    # Convert back to a DirectedPhyNetwork
    return dnetwork_from_graph(nx_graph)

File Input/Output#

DirectedPhyNetwork supports reading and writing in standard formats:

  • eNewick (default): Extended Newick for rooted networks with hybrid nodes and edge attributes — see eNewick

  • DOT: GraphViz format for visualization and storage — see DOT format

# Load from file (auto-detects format by extension)
network = DirectedPhyNetwork.load("network.enewick")

# Load with explicit format
network = DirectedPhyNetwork.load("network.dot", format="dot")

# Load from string
network = DirectedPhyNetwork.from_string("(A,B);", format="enewick")

# Save to file
network.save("output.enewick")
network.save("output.dot", format="dot")

See also

The DirectedPhyNetwork class uses the IOMixin interface, providing consistent file handling across PhyloZoo classes. For details on the I/O system, see the I/O manual.

Validation on Construction#

Every DirectedPhyNetwork is validated at construction time using the validate() method. Validation guarantees that the object represents a well-defined phylogenetic network:

  • the graph is acyclic and weakly connected,

  • there is exactly one root,

  • node degrees match the root / leaf / tree / hybrid definitions,

  • special attributes lie in their permitted ranges (see below for more details),

  • taxon labels are unique.

By default, invalid networks cannot be constructed. Validation can be disabled for performance-critical operations or when working with intermediate network states that may temporarily violate validation rules. See the Validation documentation for details on how to disable validation.

Attributes#

Nodes, edges, and the network itself may carry arbitrary attributes. Most attributes are stored without interpretation; however, the following attributes have a special, validated semantic role in directed phylogenetic networks.

Setting Attributes#

Attributes are set when creating a network by providing them in the edge, node, or attributes specifications:

Edge Attributes

Edge attributes are specified in edge dictionaries:

network = DirectedPhyNetwork(
    edges=[
        {"u": "root", "v": "A", "branch_length": 0.5, "bootstrap": 0.95},
        {"u": "root", "v": "B", "branch_length": 0.3}
    ],
    nodes=[("A", {"label": "A"}), ("B", {"label": "B"})]
)

Node Attributes

Node attributes are specified in node tuples:

network = DirectedPhyNetwork(
    edges=[("root", "A"), ("root", "B")],
    nodes=[
        ("A", {"label": "A", "custom_attr": "value1"}),
        ("B", {"label": "B", "custom_attr": "value2"})
    ]
)

Network Attributes

Network-level attributes are specified via the attributes parameter:

network = DirectedPhyNetwork(
    edges=[("root", "A")],
    nodes=[("A", {"label": "A"})],
    attributes={"source": "simulation", "version": "1.0"}
)

Special edge attributes#

There are three special edge attributes that have a special, validated semantic role in directed phylogenetic networks and which may be used by certain algorithms.

branch_length (float)

Represents evolutionary distance along an edge.

bootstrap (float in [0, 1])

A statistical support value associated with the edge, for example from bootstrap or posterior analyses.

gamma (float in [0, 1])

The inheritance probability for hybrid edges (edges entering a hybrid node). For each hybrid node, the incoming gamma values must sum to 1.0.

These attributes can be accessed via dedicated methods:

network.get_branch_length("root", "A")
network.get_bootstrap("root", "A")
network.get_gamma("u1", "h")

Special node attribute#

There is one special node attribute that has a special, validated semantic role in directed phylogenetic networks and which may be used by certain algorithms.

label (string)

A unique identifier for a node. Labels must be unique across all nodes and must be strings. Leaf nodes always have labels (these are referred to as taxa), but internal nodes may be unlabeled. If a leaf node is created without an explicit label, one is automatically generated from the node ID.

Labels can be accessed via dedicated methods:

# Get label from node ID
label = network.get_label(node_id)  # Returns label string or None

# Get node ID from label
node_id = network.get_node_id("A")  # Returns node ID or None

Additional attributes#

Any additional node or edge attributes may be attached freely. Attributes without a special semantic role are preserved by the network object and by I/O operations but are not interpreted or validated. You can access these using generic attribute access methods:

# Get node attribute
node_attr = network.get_node_attribute(node_id, "custom_attr")

# Get edge attribute
edge_attr = network.get_edge_attribute(u, v, key=0, attr="custom_attr")

# Get network-level attribute
net_attr = network.get_network_attribute("source")

Accessing Network Properties#

The DirectedPhyNetwork class provides read-only access to fundamental structural properties. These properties are computed from the validated network structure and cannot be modified directly. All properties are cached for efficient repeated access.

Basic Properties#

Basic properties provide fundamental information about the network size and structure.

Nodes

The nodes cached property exposes a view of all node IDs (same interface as the underlying graph). Use network.nodes() to iterate, or network.nodes(data=True) for attributes.

# Get nodes (cached view)
list(network.nodes)   # or network.nodes()
list(network.nodes(data=True))  # with attributes

Edges

The edges cached property exposes a view of all directed edges (same interface as the underlying graph). Use network.edges(), network.edges(keys=True), or network.edges(keys=True, data=True).

# Get edges (cached view)
list(network.edges)   # or network.edges(keys=True)
list(network.edges(keys=True, data=True))  # with keys and attributes

Node and Edge Counts

The number_of_nodes() and number_of_edges() methods return the total count of nodes and edges in the network, respectively.

# Get counts
num_nodes = network.number_of_nodes()  # Total number of nodes
num_edges = network.number_of_edges()  # Total number of edges

Check if Edge Exists

The has_edge() method checks if an edge exists between two nodes.

# Check if edge exists between nodes u and v
has_edge = network.has_edge(u, v)  # Returns True if edge exists, False otherwise

# Check if edge exists with a specific key
has_edge_with_key = network.has_edge(u, v, key=0)  # Returns True if edge exists with key 0, False otherwise

Node Types#

Network structure properties identify key nodes and their roles in the network topology.

Root

The root_node property returns the unique root node (in-degree 0).

# Get root node
root = network.root_node  # Returns the root node ID

Leaves and Taxa

The leaves property returns a set of all leaf node IDs (out-degree 0). The taxa property returns a set of all taxon labels (strings associated with leaves).

# Get leaves
leaves = network.leaves  # Set of leaf node IDs
# Get taxa
taxa = network.taxa  # Set of taxon labels

Internal Nodes

The internal_nodes property returns a set of all internal (non-leaf) node IDs.

# Get internal nodes
internal = network.internal_nodes  # Set of internal node IDs

Tree and Hybrid Nodes

The tree_nodes property returns a set of all tree node IDs (internal nodes with in-degree 1 and out-degree ≥ 2). The hybrid_nodes property returns a set of all hybrid node IDs (internal nodes with in-degree ≥ 2 and out-degree 1).

# Get tree nodes
tree = network.tree_nodes  # Set of tree node IDs
# Get hybrid nodes
hybrid = network.hybrid_nodes  # Set of hybrid node IDs

Edge Types#

Edge type properties classify edges according to their role in the network structure.

Tree and Hybrid Edges

The tree_edges property returns all tree edges (edges that are not hybrid edges) as a set of (u, v, key) tuples. The hybrid_edges property returns all hybrid edges (edges entering hybrid nodes) as a set of (u, v, key) tuples.

# Get edge type sets
tree_edges = network.tree_edges    # Set of (u, v, key) tuples for tree edges
hybrid_edges = network.hybrid_edges  # Set of (u, v, key) tuples for hybrid edges

# Example: iterate over hybrid edges
for u, v, key in network.hybrid_edges:
    gamma = network.get_gamma(u, v, key=key)
    print(f"Edge ({u}, {v}, {key}) has gamma={gamma}")

Node Connectivity#

Connectivity properties provide information about how nodes are connected in the network.

Node Degrees

The degree(), indegree(), and outdegree() methods return the total degree, in-degree, and out-degree of a node, respectively.

# Get node degrees
total_degree = network.degree(v)      # Total degree (in + out)
in_degree = network.indegree(v)       # Number of incoming edges
out_degree = network.outdegree(v)     # Number of outgoing edges

Neighbors

The parents(), children(), and neighbors() methods return iterators over parent nodes, child nodes, and all adjacent nodes, respectively.

# Get parent nodes (nodes with edges pointing to v)
parents = list(network.parents(v))  # List of parent node IDs

# Get child nodes (nodes with edges from v)
children = list(network.children(v))  # List of child node IDs

# Get all neighbors (parents and children)
neighbors = list(network.neighbors(v))  # List of all adjacent node IDs

# Example: iterate over children
for child in network.children(v):
    print(f"Node {v} has child {child}")

Incident Edges

The incident_parent_edges() and incident_child_edges() methods return iterators over edges entering and leaving a node, respectively. These methods can optionally include edge keys and attribute data.

# Get incoming edges (from parents)
incoming = list(network.incident_parent_edges(v))
# Returns: [(u1, v), (u2, v), ...]

# Get incoming edges with keys
incoming_with_keys = list(network.incident_parent_edges(v, keys=True))
# Returns: [(u1, v, key1), (u2, v, key2), ...]

# Get incoming edges with data
incoming_with_data = list(network.incident_parent_edges(v, data=True))
# Returns: [(u1, v, {'branch_length': 0.5, ...}), ...]

# Get outgoing edges (to children)
outgoing = list(network.incident_child_edges(v))
# Returns: [(v, w1), (v, w2), ...]

# Example: get all edge attributes for incoming edges
for u, v, key, data in network.incident_parent_edges(v, keys=True, data=True):
    branch_length = data.get('branch_length')
    bootstrap = data.get('bootstrap')
    print(f"Edge ({u}, {v}, {key}): bl={branch_length}, bs={bootstrap}")

Copying#

The copy() method creates a shallow copy of the network. The copy shares the same underlying graph structure but is a separate instance.

# Create a copy
network_copy = network.copy()

# Copies are independent instances
assert network_copy is not network
assert network_copy.root_node == network.root_node

See also

Some less commonly used or computationally expensive properties are available as functions in the advanced features module. These include blobs, omnian nodes, cut edges/vertices, and various network classifications. See Directed Network (Advanced) for details.

Visualization#

DirectedPhyNetwork can be visualized using the PhyloZoo visualization module. The visualization system supports multiple layout algorithms and styling options.

Example directed network with hybrid

Example visualization of a directed network with a hybrid node.#

from phylozoo.viz import plot

# Plot network with default rectangular layout
plot(network)

For more visualization options, including different layout types, styling, and customization, see the Visualization documentation.

See Also#