m_multigraph#

Mixed multi-graph module.

This module provides the MixedMultiGraph class and related functions for working with mixed multi-graphs. A mixed multi-graph contains both directed and undirected edges and allows multiple edges between vertices. It is the underlying graph structure for semi-directed phylogenetic networks, where tree edges are undirected and reticulation arcs are directed.

Main Class#

Mixed multi-graph module.

This module provides the MixedMultiGraph class for working with mixed multi-graphs.

class phylozoo.core.primitives.m_multigraph.base.MixedMultiGraph(directed_edges: list[tuple[T, T] | tuple[T, T, int] | dict[str, Any]] | None = None, undirected_edges: list[tuple[T, T] | tuple[T, T, int] | dict[str, Any]] | None = None, attributes: dict[str, Any] | None = None)[source]#

Bases: IOMixin

Mixed multi-graph with undirected and directed edges.

This class uses composition with separate NetworkX graphs:

  • _undirected: nx.MultiGraph for undirected edges

  • _directed: nx.MultiDiGraph for directed edges

  • _combined: nx.MultiGraph combining all edges for connectivity analysis

This class allows parallel edges for both directed and undirected edges, including self-loops. However, edges between the same two nodes must be either all directed or all undirected - mixing is not allowed. This mutual exclusivity is enforced automatically: adding a directed edge will remove any undirected edges between the same nodes, and vice versa. Each parallel edge can have different parameters (weights, attributes, etc.) via edge keys.

Parameters:
  • directed_edges (list[tuple[T, T] | tuple[T, T, int] | dict[str, Any]] | None, optional) –

    List of directed edges. Can be:

    • (u, v) tuples (key auto-generated)

    • (u, v, key) tuples (explicit key)

    • Dict with ‘u’, ‘v’ keys and optional ‘key’ and edge attributes

    If keys are not provided, they will be auto-generated. By default None.

  • undirected_edges (list[tuple[T, T] | tuple[T, T, int] | dict[str, Any]] | None, optional) –

    List of undirected edges. Can be:

    • (u, v) tuples (key auto-generated)

    • (u, v, key) tuples (explicit key)

    • Dict with ‘u’, ‘v’ keys and optional ‘key’ and edge attributes

    If keys are not provided, they will be auto-generated. By default None.

Notes

The underlying graphs (_undirected, _directed, _combined) are accessible but should NOT be modified directly. All modifications must go through the class methods (add_edge, remove_edge, etc.) to ensure state synchronization. Direct modification will desynchronize the graphs and cause incorrect behavior.

Supported I/O formats:

  • phylozoo-dot (default): .pzdot

Examples

>>> G = MixedMultiGraph()
>>> G.add_directed_edge(1, 2)
0
>>> G.add_directed_edge(1, 2)  # Parallel directed edge
1
>>> G.add_undirected_edge(2, 3)  # Undirected edge
0
>>> G.add_undirected_edge(2, 3)  # Parallel undirected edge
1
>>> from phylozoo.core.primitives.m_multigraph.features import number_of_connected_components
>>> number_of_connected_components(G)
1
>>> # Initialize with edges (including attributes)
>>> G2 = MixedMultiGraph(
...     undirected_edges=[(1, 2), {'u': 2, 'v': 3, 'weight': 5.0}],
...     directed_edges=[(3, 4), {'u': 4, 'v': 5, 'weight': 10.0, 'label': 'test'}]
... )
>>> G2.number_of_edges()
4
>>> # Create from NetworkX graphs
>>> import networkx as nx
>>> from phylozoo.core.primitives.m_multigraph.conversions import graph_to_mixedmultigraph
>>> nx_g = nx.Graph()
>>> nx_g.add_edge(1, 2, weight=1.0)
>>> G3 = graph_to_mixedmultigraph(nx_g)
>>> G3.number_of_edges()
1
_undirected#

NetworkX MultiGraph storing undirected edges. Warning: Do not modify directly. Use class methods instead.

Type:

nx.MultiGraph

_directed#

NetworkX MultiDiGraph storing directed edges. Warning: Do not modify directly. Use class methods instead.

Type:

nx.MultiDiGraph

_combined#

Combined undirected view of all edges for connectivity analysis. Warning: Do not modify directly. Use class methods instead.

Type:

nx.MultiGraph

class EdgeView(items: list[tuple[T, T]], callable_func: callable)[source]#

Bases: object

Edge view that works as both attribute and method, similar to NetworkX’s EdgeView.

This class provides a list-like interface for edges while also being callable as a method to get iterators with keys or data.

__call__(keys: bool = False, data: bool | str = False)[source]#

Call as method to get iterator with keys or data.

Parameters:
  • keys (bool, optional) – If True, return edge keys. By default False.

  • data (bool | str, optional) – If False (default), no edge data is included. If True, return edge data dictionaries. If string, return value of that edge attribute.

Returns:

Iterator over edges. Format depends on keys and data parameters.

Return type:

Iterator

__contains__(item: tuple[T, T]) bool[source]#

Check if edge in view.

__iter__()[source]#

Iterate over edges.

__len__() int[source]#

Number of edges.

__repr__() str[source]#

String representation.

class NodeView(items: set[T], callable_func: callable)[source]#

Bases: object

Node view that works as both attribute and method, similar to NetworkX’s NodeView.

This class provides a set-like interface for nodes while also being callable as a method to get iterators or node data.

__and__(other)[source]#

Intersection with other set.

__call__(data: bool | str = False)[source]#

Call as method to get iterator or node data.

Parameters:

data (bool | str, optional) – If False (default), return iterator over nodes. If True, return iterator of (node, data_dict) tuples. If string, return iterator of (node, attribute_value) tuples.

Returns:

Iterator over nodes or (node, data) tuples.

Return type:

Iterator[T] | Iterator[tuple[T, Any]]

__contains__(item: T) bool[source]#

Check if node in view.

__iter__()[source]#

Iterate over nodes.

__len__() int[source]#

Number of nodes.

__or__(other)[source]#

Union with other set.

__repr__() str[source]#

String representation.

issubset(other)[source]#

Check if this is a subset of other.

__contains__(v: T) bool[source]#

Check if node v is in the graph.

Parameters:

v (T) – Node to check.

Returns:

True if v is in the graph, False otherwise.

Return type:

bool

Examples

>>> G = MixedMultiGraph()
>>> G.add_node(1)
>>> 1 in G
True
>>> 2 in G
False
__getitem__(v: T) dict[T, dict[int, dict[str, Any]]][source]#

Return adjacency dict for node v.

Parameters:

v (T) – Node.

Returns:

Adjacency dict (actually returns NetworkX’s AdjacencyView, which is dict-like and supports all dict operations).

Return type:

dict[T, dict[int, dict[str, Any]]]

Examples

>>> G = MixedMultiGraph()
>>> G.add_undirected_edge(1, 2, weight=1.0)
0
>>> G[1]
{2: {0: {'weight': 1.0}}}
__iter__() Iterator[T][source]#

Iterate over nodes.

Returns:

Iterator over nodes.

Return type:

Iterator[T]

Examples

>>> G = MixedMultiGraph()
>>> G.add_nodes_from([1, 2, 3])
>>> list(G)
[1, 2, 3]
__len__() int[source]#

Return the number of nodes.

Returns:

Number of nodes.

Return type:

int

Examples

>>> G = MixedMultiGraph()
>>> G.add_nodes_from([1, 2, 3])
>>> len(G)
3
__repr__() str[source]#

Return a concise representation.

Returns:

Representation containing counts of nodes, directed edges, and undirected edges.

Return type:

str

add_directed_edge(u: T, v: T, key: int | None = None, **attr: Any) int[source]#

Add directed edge (u, v) to the graph.

Parallel directed edges are allowed. Each parallel edge can have different attributes (weights, etc.) via the key parameter.

Mutual Exclusivity: If there are any undirected edges between u and v, they will be automatically removed. Edges between the same two nodes must be either all directed or all undirected - mixing is not allowed. This ensures consistent graph semantics.

Parameters:
  • u (T) – Source node.

  • v (T) – Target node.

  • key (int | None, optional) – Edge key. If None, auto-generates a key. By default None.

  • **attr – Edge attributes (e.g., weight, label, etc.).

Returns:

The key of the added edge.

Return type:

int

Examples

>>> G = MixedMultiGraph()
>>> key1 = G.add_directed_edge(1, 2, weight=1.0)
>>> key2 = G.add_directed_edge(1, 2, weight=2.0)  # Parallel edge
>>> key1 != key2
True
>>> G.add_undirected_edge(1, 2)  # This removes the directed edges
>>> G.add_directed_edge(1, 2)  # This removes the undirected edge
add_directed_edges_from(edges: list[tuple[T, T] | tuple[T, T, int]], **attr: Any) None[source]#

Add all directed edges in ‘edges’ to the graph.

Parameters:
  • edges (list[tuple[T, T] | tuple[T, T, int]]) – List of directed edges. Can be (u, v) or (u, v, key) tuples.

  • **attr – Edge attributes applied to all edges.

Examples

>>> G = MixedMultiGraph()
>>> G.add_directed_edges_from([(1, 2), (2, 3), (3, 1)])
add_node(v: T, **attr: Any) None[source]#

Add node v to the graph.

Parameters:
  • v (T) – Node to add.

  • **attr – Node attributes.

Examples

>>> G = MixedMultiGraph()
>>> G.add_node(1, weight=2.0)
>>> 1 in G
True
add_nodes_from(nodes: list[T] | set[T], **attr: Any) None[source]#

Add nodes from iterable.

Parameters:
  • nodes (list[T] | set[T]) – Iterable of nodes.

  • **attr – Attributes to add to all nodes.

Examples

>>> G = MixedMultiGraph()
>>> G.add_nodes_from([1, 2, 3])
>>> len(G)
3
add_undirected_edge(u: T, v: T, key: int | None = None, **attr: Any) int[source]#

Add undirected edge (u, v) to the graph.

Parallel undirected edges are allowed. Each parallel edge can have different attributes (weights, etc.) via the key parameter.

Mutual Exclusivity: If there are any directed edges between u and v, they will be automatically removed. Edges between the same two nodes must be either all directed or all undirected - mixing is not allowed. This ensures consistent graph semantics.

Parameters:
  • u (T) – First node.

  • v (T) – Second node.

  • key (int | None, optional) – Edge key. If None, auto-generates a key. By default None.

  • **attr – Edge attributes.

Returns:

The key of the added edge.

Return type:

int

Examples

>>> G = MixedMultiGraph()
>>> key1 = G.add_undirected_edge(1, 2, weight=1.0)
>>> key2 = G.add_undirected_edge(1, 2, weight=2.0)  # Parallel edge
>>> key1 != key2
True
>>> G.add_directed_edge(1, 2)  # This removes the undirected edges
>>> G.add_undirected_edge(1, 2)  # This removes the directed edge
add_undirected_edges_from(edges: list[tuple[T, T] | tuple[T, T, int]], **attr: Any) None[source]#

Add all undirected edges in ‘edges’ to the graph.

Parameters:
  • edges (list[tuple[T, T] | tuple[T, T, int]]) – List of undirected edges. Can be (u, v) or (u, v, key) tuples.

  • **attr – Edge attributes applied to all edges.

Examples

>>> G = MixedMultiGraph()
>>> G.add_undirected_edges_from([(1, 2), (2, 3), (3, 1)])
clear() None[source]#

Clear the whole mixed graph.

Removes all nodes and edges.

Examples

>>> G = MixedMultiGraph()
>>> G.add_directed_edge(1, 2)
0
>>> G.add_undirected_edge(2, 3)
0
>>> G.clear()
>>> len(G.nodes())
0
property combined_graph: MultiGraph#

Get combined undirected graph for NetworkX algorithms.

Returns:

Combined graph treating all edges as undirected.

Return type:

nx.MultiGraph

Examples

>>> G = MixedMultiGraph()
>>> G.add_directed_edge(1, 2)
0
>>> G.add_undirected_edge(2, 3)
0
>>> nx.is_connected(G.combined_graph)
True
copy() MixedMultiGraph[source]#

Create a copy of the graph.

Returns:

A copy of the graph.

Return type:

MixedMultiGraph

Examples

>>> G = MixedMultiGraph()
>>> G.add_directed_edge(1, 2)
0
>>> H = G.copy()
>>> H.add_undirected_edge(2, 3)
0
>>> G.number_of_edges()
1
>>> H.number_of_edges()
2
degree(v: T) int[source]#

Return the total degree of vertex v.

The degree is the sum of undirected edges and directed edges (both incoming and outgoing).

Parameters:

v (T) – Vertex.

Returns:

Total degree of v. Returns 0 if v is not in the graph.

Return type:

int

Examples

>>> G = MixedMultiGraph()
>>> G.add_undirected_edge(1, 2)
0
>>> G.add_directed_edge(1, 3)
0
>>> G.degree(1)
2
>>> G.degree(99)  # Node not in graph
0
property directed_edges: EdgeView#

Get all directed edges (works as both attribute and method).

When accessed as attribute, returns an EdgeView object (list-like). When called as method, returns an iterator with optional keys and data.

Returns:

Edge view object that’s both callable and list-like.

Return type:

EdgeView

Examples

>>> G = MixedMultiGraph()
>>> G.add_directed_edge(1, 2, weight=1.0)
0
>>> G.add_directed_edge(1, 2, weight=2.0)  # Parallel edge
1
>>> G.add_undirected_edge(2, 3)
0
>>> G.directed_edges  # Attribute access
[(1, 2), (1, 2)]
>>> list(G.directed_edges())  # Method call
[(1, 2), (1, 2)]
>>> list(G.directed_edges(keys=True))  # With keys
[(1, 2, 0), (1, 2, 1)]
>>> list(G.directed_edges(data=True))  # With data
[(1, 2, {'weight': 1.0}), (1, 2, {'weight': 2.0})]
>>> list(G.directed_edges(keys=True, data=True))  # With keys and data
[(1, 2, 0, {'weight': 1.0}), (1, 2, 1, {'weight': 2.0})]
directed_edges_iter(keys: bool = False, data: bool | str = False) Iterator[tuple[T, T] | tuple[T, T, int] | tuple[T, T, Any] | tuple[T, T, dict[str, Any]] | tuple[T, T, int, Any] | tuple[T, T, int, dict[str, Any]]][source]#

Return an iterator over directed edges.

Parameters:
  • keys (bool, optional) – If True, return edge keys. By default False.

  • data (bool | str, optional) – If False (default), no edge data is included. If True, return edge data dictionaries. If string, return value of that edge attribute.

Returns:

Iterator over directed edges. Format depends on keys and data parameters.

Return type:

Iterator

Examples

>>> G = MixedMultiGraph()
>>> G.add_directed_edge(1, 2, weight=1.0)
0
>>> list(G.directed_edges_iter())
[(1, 2)]
>>> list(G.directed_edges_iter(keys=True))
[(1, 2, 0)]
>>> list(G.directed_edges_iter(data='weight'))
[(1, 2, 1.0)]
property edges: EdgeView#

Get all edges (works as both attribute and method).

When accessed as attribute, returns an EdgeView object (list-like). When called as method, returns an iterator.

Returns:

Edge view object that’s both callable and list-like.

Return type:

EdgeView

Examples

>>> G = MixedMultiGraph()
>>> G.add_undirected_edge(1, 2)
0
>>> G.add_directed_edge(2, 3)
0
>>> G.edges  # Attribute access
[(1, 2), (2, 3)]
>>> list(G.edges())  # Method call
[(1, 2), (2, 3)]
edges_iter(keys: bool = False, data: bool | str = False) Iterator[tuple[T, T] | tuple[T, T, int] | tuple[T, T, Any] | tuple[T, T, dict[str, Any]] | tuple[T, T, int, Any] | tuple[T, T, int, dict[str, Any]]][source]#

Return an iterator over edges.

Parameters:
  • keys (bool, optional) – If True, return edge keys. By default False.

  • data (bool | str, optional) – If False (default), no edge data is included. If True, return edge data dictionaries. If string, return value of that edge attribute.

Returns:

Iterator over edges. Format depends on keys and data parameters.

Return type:

Iterator

Examples

>>> G = MixedMultiGraph()
>>> G.add_undirected_edge(1, 2, weight=1.0)
0
>>> list(G.edges_iter())
[(1, 2)]
>>> list(G.edges_iter(keys=True))
[(1, 2, 0)]
>>> list(G.edges_iter(data=True))
[(1, 2, {'weight': 1.0})]
>>> list(G.edges_iter(keys=True, data='weight'))
[(1, 2, 0, 1.0)]
generate_node_ids(count: int) Iterator[int][source]#

Generate new integer node IDs that are not in the graph.

Finds the largest integer node ID in the graph and generates count consecutive integer IDs starting from max + 1.

Parameters:

count (int) – Number of node IDs to generate.

Yields:

int – Consecutive integer node IDs starting from max + 1.

Raises:

ValueError – If count is negative.

Examples

>>> G = MixedMultiGraph()
>>> G.add_node(1)
>>> G.add_node(5)
>>> list(G.generate_node_ids(3))
[6, 7, 8]
>>> G = MixedMultiGraph()
>>> list(G.generate_node_ids(2))
[0, 1]
has_edge(u: T, v: T, key: int | None = None) bool[source]#

Check if edge exists (directed or undirected).

Parameters:
  • u (T) – Source node.

  • v (T) – Target node.

  • key (int | None, optional) – Edge key. By default None.

Returns:

True if edge exists, False otherwise.

Return type:

bool

Examples

>>> G = MixedMultiGraph()
>>> G.add_undirected_edge(1, 2)
0
>>> G.has_edge(1, 2)
True
>>> G.has_edge(2, 3)
False
incident_child_edges(v: T, keys: bool = False, data: bool | str = False) Iterator[tuple[T, T] | tuple[T, T, int] | tuple[T, T, Any] | tuple[T, T, dict[str, Any]] | tuple[T, T, int, Any] | tuple[T, T, int, dict[str, Any]]][source]#

Return an iterator over directed edges leaving node v (to child nodes).

Parameters:
  • v (T) – Node.

  • keys (bool, optional) – If True, return edge keys. By default False.

  • data (bool | str, optional) – If False (default), no edge data is included. If True, return edge data dictionaries. If string, return value of that edge attribute.

Returns:

Iterator over outgoing edges. Format depends on keys and data parameters.

Return type:

Iterator

Examples

>>> G = MixedMultiGraph()
>>> G.add_directed_edge(1, 2, weight=1.0)
0
>>> G.add_directed_edge(1, 3, weight=2.0)
0
>>> list(G.incident_child_edges(1))
[(1, 2), (1, 3)]
>>> list(G.incident_child_edges(1, keys=True, data=True))
[(1, 2, 0, {'weight': 1.0}), (1, 3, 0, {'weight': 2.0})]
>>> list(G.incident_child_edges(1, data='weight'))
[(1, 2, 1.0), (1, 3, 2.0)]
incident_parent_edges(v: T, keys: bool = False, data: bool | str = False) Iterator[tuple[T, T] | tuple[T, T, int] | tuple[T, T, Any] | tuple[T, T, dict[str, Any]] | tuple[T, T, int, Any] | tuple[T, T, int, dict[str, Any]]][source]#

Return an iterator over directed edges entering node v (from parent nodes).

Parameters:
  • v (T) – Node.

  • keys (bool, optional) – If True, return edge keys. By default False.

  • data (bool | str, optional) – If False (default), no edge data is included. If True, return edge data dictionaries. If string, return value of that edge attribute.

Returns:

Iterator over incoming edges. Format depends on keys and data parameters.

Return type:

Iterator

Examples

>>> G = MixedMultiGraph()
>>> G.add_directed_edge(1, 2, weight=1.0)
0
>>> G.add_directed_edge(3, 2, weight=2.0)
0
>>> list(G.incident_parent_edges(2))
[(1, 2), (3, 2)]
>>> list(G.incident_parent_edges(2, keys=True, data=True))
[(1, 2, 0, {'weight': 1.0}), (3, 2, 0, {'weight': 2.0})]
>>> list(G.incident_parent_edges(2, data='weight'))
[(1, 2, 1.0), (3, 2, 2.0)]
incident_undirected_edges(v: T, keys: bool = False, data: bool | str = False) Iterator[tuple[T, T] | tuple[T, T, int] | tuple[T, T, Any] | tuple[T, T, dict[str, Any]] | tuple[T, T, int, Any] | tuple[T, T, int, dict[str, Any]]][source]#

Return an iterator over undirected edges incident to node v.

Parameters:
  • v (T) – Node.

  • keys (bool, optional) – If True, return edge keys. By default False.

  • data (bool | str, optional) – If False (default), no edge data is included. If True, return edge data dictionaries. If string, return value of that edge attribute.

Returns:

Iterator over incident undirected edges. Format depends on keys and data parameters.

Return type:

Iterator

Examples

>>> G = MixedMultiGraph()
>>> G.add_undirected_edge(1, 2, weight=1.0)
0
>>> G.add_undirected_edge(2, 3, weight=2.0)
0
>>> list(G.incident_undirected_edges(2))
[(1, 2), (2, 3)]
>>> list(G.incident_undirected_edges(2, keys=True, data=True))
[(1, 2, 0, {'weight': 1.0}), (2, 3, 0, {'weight': 2.0})]
>>> list(G.incident_undirected_edges(2, data='weight'))
[(1, 2, 1.0), (2, 3, 2.0)]
indegree(v: T) int[source]#

Return the indegree of vertex v.

The indegree is the number of directed edges pointing to v.

Parameters:

v (T) – Vertex.

Returns:

Indegree of v. Returns 0 if v is not in the graph.

Return type:

int

Examples

>>> G = MixedMultiGraph()
>>> G.add_directed_edge(1, 2)
0
>>> G.add_directed_edge(3, 2)
0
>>> G.indegree(2)
2
>>> G.indegree(99)  # Node not in graph
0
neighbors(v: T) Iterator[T][source]#

Return an iterator over neighbors of node v.

Parameters:

v (T) – Node.

Returns:

Iterator over neighbors.

Return type:

Iterator[T]

Examples

>>> G = MixedMultiGraph()
>>> G.add_undirected_edge(1, 2)
0
>>> G.add_directed_edge(1, 3)
0
>>> list(G.neighbors(1))
[2, 3]
property nodes: NodeView#

Get all nodes (works as both attribute and method).

When accessed as attribute, returns a NodeView object (set-like). When called as method, returns an iterator.

Returns:

Node view object that’s both callable and set-like.

Return type:

NodeView

Examples

>>> G = MixedMultiGraph()
>>> G.add_nodes_from([1, 2, 3])
>>> G.nodes  # Attribute access
{1, 2, 3}
>>> list(G.nodes())  # Method call
[1, 2, 3]
>>> list(G.nodes(data=True))  # Method call with data
[(1, {}), (2, {}), (3, {})]
nodes_iter(data: bool | str = False) Iterator[T] | Iterator[tuple[T, Any]][source]#

Return an iterator over nodes.

Parameters:

data (bool | str, optional) – If False (default), return iterator over nodes. If True, return iterator of (node, data_dict) tuples. If string, return iterator of (node, attribute_value) tuples for the given attribute name.

Returns:

Iterator over nodes or (node, data) tuples.

Return type:

Iterator[T] | Iterator[tuple[T, Any]]

Examples

>>> G = MixedMultiGraph()
>>> G.add_node(1, weight=2.0)
>>> G.add_node(2, weight=3.0)
>>> list(G.nodes_iter())
[1, 2]
>>> list(G.nodes_iter(data=True))
[(1, {'weight': 2.0}), (2, {'weight': 3.0})]
>>> list(G.nodes_iter(data='weight'))
[(1, 2.0), (2, 3.0)]
classmethod normalize_undirected_edge(u: Any, v: Any, key: int | None = None) tuple[Any, Any] | tuple[Any, Any, int][source]#

Normalize an undirected edge tuple to a canonical form.

Uses type-aware comparison to handle mixed node ID types (e.g., int and str). The normalization ensures that (u, v) and (v, u) map to the same tuple.

Parameters:
  • u (Any) – First node ID.

  • v (Any) – Second node ID.

  • key (int | None, optional) – Optional edge key. If provided, included in the returned tuple. By default None.

Returns:

Normalized edge tuple. If key is None, returns (smaller, larger). If key is provided, returns (smaller, larger, key).

Return type:

tuple[Any, Any] | tuple[Any, Any, int]

Examples

>>> MixedMultiGraph.normalize_undirected_edge(1, 2)
(1, 2)
>>> MixedMultiGraph.normalize_undirected_edge(2, 1, key=0)
(1, 2, 0)
>>> MixedMultiGraph.normalize_undirected_edge(1, 'a')
(1, 'a')
>>> MixedMultiGraph.normalize_undirected_edge('b', 'a')
('a', 'b')
number_of_edges() int[source]#

Return the number of edges (directed + undirected).

Returns:

Number of edges.

Return type:

int

Examples

>>> G = MixedMultiGraph()
>>> G.add_directed_edge(1, 2)
0
>>> G.add_undirected_edge(2, 3)
0
>>> G.number_of_edges()
2
number_of_nodes() int[source]#

Return the number of nodes.

Returns:

Number of nodes.

Return type:

int

Examples

>>> G = MixedMultiGraph()
>>> G.add_nodes_from([1, 2, 3])
>>> G.number_of_nodes()
3
outdegree(v: T) int[source]#

Return the outdegree of vertex v.

The outdegree is the number of directed edges pointing from v.

Parameters:

v (T) – Vertex.

Returns:

Outdegree of v. Returns 0 if v is not in the graph.

Return type:

int

Examples

>>> G = MixedMultiGraph()
>>> G.add_directed_edge(1, 2)
0
>>> G.add_directed_edge(1, 3)
0
>>> G.outdegree(1)
2
>>> G.outdegree(99)  # Node not in graph
0
remove_directed_edge(u: T, v: T, key: int | None = None) None[source]#

Remove directed edge (u, v) from the graph.

Parameters:
  • u (T) – Source node.

  • v (T) – Target node.

  • key (int | None, optional) – Edge key. If None, removes one directed edge. By default None.

Raises:

ValueError – If no directed edge (u, v) exists with the specified key.

Examples

>>> G = MixedMultiGraph()
>>> key = G.add_directed_edge(1, 2)
0
>>> G.remove_directed_edge(1, 2, key=key)
remove_directed_edges_from(edges: list[tuple[T, T] | tuple[T, T, int]]) None[source]#

Remove all directed edges in ‘edges’ from the graph.

Parameters:

edges (list[tuple[T, T] | tuple[T, T, int]]) – List of directed edges to remove. Can be (u, v) or (u, v, key) tuples.

Examples

>>> G = MixedMultiGraph()
>>> G.add_directed_edge(1, 2)
0
>>> G.add_directed_edge(2, 3)
0
>>> G.remove_directed_edges_from([(1, 2), (2, 3)])
remove_edge(u: T, v: T, key: int | None = None) None[source]#

Remove edge (u, v) from the graph.

If key is provided, removes the specific edge with that key. If key is None, tries undirected first, then directed.

Parameters:
  • u (T) – Source node.

  • v (T) – Target node.

  • key (int | None, optional) – Edge key. If None, removes one edge (directed or undirected). By default None.

Raises:

KeyError – If the specified edge does not exist.

Examples

>>> G = MixedMultiGraph()
>>> key = G.add_directed_edge(1, 2)
0
>>> G.remove_edge(1, 2, key=key)
remove_edges_from(edges: list[tuple[T, T] | tuple[T, T, int]]) None[source]#

Remove all edges in ‘edges’ from the graph.

Parameters:

edges (list[tuple[T, T] | tuple[T, T, int]]) – List of edges to remove. Can be (u, v) or (u, v, key) tuples.

Examples

>>> G = MixedMultiGraph()
>>> G.add_directed_edge(1, 2)
0
>>> G.add_undirected_edge(2, 3)
0
>>> G.remove_edges_from([(1, 2), (2, 3)])
remove_node(v: T) None[source]#

Remove node v from the graph.

Also removes all edges incident to v, including both directed and undirected edges.

Parameters:

v (T) – Node to remove.

Examples

>>> G = MixedMultiGraph()
>>> G.add_directed_edge(1, 2)
0
>>> G.add_undirected_edge(2, 3)
0
>>> G.remove_node(2)
>>> list(G.nodes())
[1, 3]
remove_nodes_from(nodes: list[T] | set[T]) None[source]#

Remove all nodes in ‘nodes’ from the graph.

Parameters:

nodes (list[T] | set[T]) – Iterable of nodes to remove.

Examples

>>> G = MixedMultiGraph()
>>> G.add_node(1)
>>> G.add_node(2)
>>> G.add_node(3)
>>> G.remove_nodes_from([1, 2])
>>> list(G.nodes())
[3]
set_graph_attribute(key: str, value: Any) None[source]#

Set a graph attribute in all underlying graphs.

Sets the same attribute value in the directed, undirected, and combined graph’s .graph attribute dictionaries.

Parameters:
  • key (str) – The attribute key.

  • value (Any) – The attribute value.

Examples

>>> G = MixedMultiGraph()
>>> G.set_graph_attribute('probability', 0.5)
>>> G._directed.graph.get('probability')
0.5
>>> G._undirected.graph.get('probability')
0.5
>>> G._combined.graph.get('probability')
0.5
undirected_degree(v: T) int[source]#

Return the undirected degree of vertex v.

The undirected degree is the number of undirected edges incident to v. Each undirected edge contributes 1 to the degree.

Parameters:

v (T) – Vertex.

Returns:

Undirected degree of v. Returns 0 if v is not in the graph.

Return type:

int

Examples

>>> G = MixedMultiGraph()
>>> G.add_undirected_edge(1, 2)
0
>>> G.add_undirected_edge(1, 2)  # Parallel edge
1
>>> G.add_undirected_edge(1, 3)
0
>>> G.undirected_degree(1)
3
>>> G.undirected_degree(99)  # Node not in graph
0
property undirected_edges: EdgeView#

Get all undirected edges (works as both attribute and method).

When accessed as attribute, returns an EdgeView object (list-like). When called as method, returns an iterator with optional keys and data.

Returns:

Edge view object that’s both callable and list-like.

Return type:

EdgeView

Examples

>>> G = MixedMultiGraph()
>>> G.add_undirected_edge(1, 2, weight=1.0)
0
>>> G.add_undirected_edge(1, 2, weight=2.0)  # Parallel edge
1
>>> G.add_directed_edge(2, 3)
0
>>> G.undirected_edges  # Attribute access
[(1, 2), (1, 2)]
>>> list(G.undirected_edges())  # Method call
[(1, 2), (1, 2)]
>>> list(G.undirected_edges(keys=True))  # With keys
[(1, 2, 0), (1, 2, 1)]
>>> list(G.undirected_edges(data=True))  # With data
[(1, 2, {'weight': 1.0}), (1, 2, {'weight': 2.0})]
>>> list(G.undirected_edges(keys=True, data=True))  # With keys and data
[(1, 2, 0, {'weight': 1.0}), (1, 2, 1, {'weight': 2.0})]
undirected_edges_iter(keys: bool = False, data: bool | str = False) Iterator[tuple[T, T] | tuple[T, T, int] | tuple[T, T, Any] | tuple[T, T, dict[str, Any]] | tuple[T, T, int, Any] | tuple[T, T, int, dict[str, Any]]][source]#

Return an iterator over undirected edges.

Parameters:
  • keys (bool, optional) – If True, return edge keys. By default False.

  • data (bool | str, optional) – If False (default), no edge data is included. If True, return edge data dictionaries. If string, return value of that edge attribute.

Returns:

Iterator over undirected edges. Format depends on keys and data parameters.

Return type:

Iterator

Examples

>>> G = MixedMultiGraph()
>>> G.add_undirected_edge(1, 2, weight=1.0)
0
>>> list(G.undirected_edges_iter())
[(1, 2)]
>>> list(G.undirected_edges_iter(keys=True))
[(1, 2, 0)]
>>> list(G.undirected_edges_iter(data='weight'))
[(1, 2, 1.0)]

Features#

Graph features module.

This module provides functions to extract and identify features of MixedMultiGraph instances (e.g., connectivity, self-loops, source components, etc.).

phylozoo.core.primitives.m_multigraph.features.bi_edge_connected_components(graph: MixedMultiGraph) Iterator[set[T]][source]#

Get bi-edge connected components (2-edge-connected components) of the graph.

A bi-edge connected component is a maximal subgraph that remains connected after removing any single edge. This is equivalent to finding connected components after removing all bridges (cut edges).

Parameters:

graph (MixedMultiGraph) – The graph to analyze.

Returns:

Iterator over sets of nodes in each bi-edge connected component.

Return type:

Iterator[set[T]]

Examples

>>> from phylozoo.core.primitives.m_multigraph.base import MixedMultiGraph
>>> # Graph with cycle: 1-2-3-1, edge 3-4 is bridge
>>> # Bi-edge connected components: {1, 2, 3}, {4}
>>> G = MixedMultiGraph()
>>> _ = G.add_undirected_edge(1, 2)
>>> _ = G.add_undirected_edge(2, 3)
>>> _ = G.add_undirected_edge(3, 1)
>>> _ = G.add_undirected_edge(3, 4)
>>> comps = list(bi_edge_connected_components(G))
>>> {1, 2, 3} in comps
True
>>> {4} in comps
True
>>> # Graph with two cycles connected by bridge: 1-2-3-1 and 4-5-6-4, connected via bridge 3-4
>>> # Bi-edge connected components: {1, 2, 3}, {4, 5, 6}
>>> G2 = MixedMultiGraph()
>>> _ = G2.add_undirected_edge(1, 2)
>>> _ = G2.add_undirected_edge(2, 3)
>>> _ = G2.add_undirected_edge(3, 1)
>>> _ = G2.add_undirected_edge(3, 4)  # Bridge
>>> _ = G2.add_undirected_edge(4, 5)
>>> _ = G2.add_undirected_edge(5, 6)
>>> _ = G2.add_undirected_edge(6, 4)
>>> comps2 = list(bi_edge_connected_components(G2))
>>> {1, 2, 3} in comps2
True
>>> {4, 5, 6} in comps2
True
phylozoo.core.primitives.m_multigraph.features.biconnected_components(graph: MixedMultiGraph) Iterator[set[T]][source]#

Get biconnected components of the underlying undirected graph.

Parameters:

graph (MixedMultiGraph) – The graph to analyze.

Returns:

Iterator over sets of nodes in each biconnected component.

Return type:

Iterator[set[T]]

Examples

>>> from phylozoo.core.primitives.m_multigraph.base import MixedMultiGraph
>>> G = MixedMultiGraph()
>>> # Single connected component with two biconnected components:
>>> # Cycle 1: 1-2-3-1 and Cycle 2: 3-4-5-3 (articulation at 3)
>>> _ = G.add_undirected_edge(1, 2)
>>> _ = G.add_undirected_edge(2, 3)
>>> _ = G.add_undirected_edge(3, 1)
>>> _ = G.add_undirected_edge(3, 4)
>>> _ = G.add_undirected_edge(4, 5)
>>> _ = G.add_undirected_edge(5, 3)
>>> comps = list(biconnected_components(G))
>>> {1, 2, 3} in comps  # first cycle
True
>>> {3, 4, 5} in comps  # second cycle sharing articulation 3
True
phylozoo.core.primitives.m_multigraph.features.connected_components(graph: MixedMultiGraph) Iterator[set[T]][source]#

Get weakly connected components.

Parameters:

graph (MixedMultiGraph) – The graph to analyze.

Returns:

Iterator over sets of nodes in each component.

Return type:

Iterator[set[T]]

Examples

>>> from phylozoo.core.primitives.m_multigraph.base import MixedMultiGraph
>>> G = MixedMultiGraph()
>>> G.add_undirected_edge(1, 2)
0
>>> G.add_directed_edge(3, 4)
0
>>> list(connected_components(G))
[{1, 2}, {3, 4}]
phylozoo.core.primitives.m_multigraph.features.cut_edges(graph: MixedMultiGraph, keys: bool = False, data: bool | str = False) set[tuple[T, T]] | set[tuple[T, T, int]] | set[tuple[T, T, Any]] | set[tuple[T, T, int, Any]] | list[tuple[T, T, dict[str, Any]]] | list[tuple[T, T, int, dict[str, Any]]][source]#

Find all cut-edges (bridges) in the graph.

A cut-edge is an edge whose removal increases the number of connected components.

Parameters:
  • graph (MixedMultiGraph) – The graph to analyze.

  • keys (bool, optional) – If True, return 3-tuples (u, v, key). If False, return 2-tuples (u, v). Default is False.

  • data (bool | str, optional) – If False, return edges without data. If True, return edges with full data dict. If a string, return edges with the value of that attribute. Default is False.

Returns:

Cut-edges. Format depends on keys and data parameters:

  • keys=False, data=False: {(u, v), …} (set)

  • keys=True, data=False: {(u, v, key), …} (set)

  • keys=False, data=True: [(u, v, data_dict), …] (list, since dicts are unhashable)

  • keys=True, data=True: [(u, v, key, data_dict), …] (list, since dicts are unhashable)

  • keys=False, data=’attr’: {(u, v, attr_value), …} (set)

  • keys=True, data=’attr’: {(u, v, key, attr_value), …} (set)

Return type:

set or list

Examples

>>> from phylozoo.core.primitives.m_multigraph.base import MixedMultiGraph
>>> from phylozoo.core.primitives.m_multigraph.features import cut_edges
>>> G = MixedMultiGraph()
>>> G.add_undirected_edge(1, 2)
0
>>> G.add_undirected_edge(2, 3)
0
>>> edges = cut_edges(G)
>>> (1, 2) in edges or (2, 1) in edges
True
>>> edges_with_keys = cut_edges(G, keys=True)
>>> (1, 2, 0) in edges_with_keys or (2, 1, 0) in edges_with_keys
True

Notes

This function uses Tarjan’s algorithm for finding bridges, which runs in O(V + E) time. The algorithm works on the underlying undirected (weakly connected) representation. Parallel edges are never bridges, so this implementation optimizes by iterating through edges once and checking bridge membership.

phylozoo.core.primitives.m_multigraph.features.cut_vertices(graph: MixedMultiGraph, data: bool | str = False) set[T] | set[tuple[T, Any]] | list[tuple[T, dict[str, Any]]][source]#

Find all cut-vertices (articulation points) in the graph.

A cut-vertex is a vertex whose removal increases the number of connected components.

Parameters:
  • graph (MixedMultiGraph) – The graph to analyze.

  • data (bool | str, optional) – If False, return vertices without data. If True, return vertices with full data dict. If a string, return vertices with the value of that attribute. Default is False.

Returns:

Cut-vertices. Format depends on data parameter:

  • data=False: {v, …} (set)

  • data=True: [(v, data_dict), …] (list, since dicts are unhashable)

  • data=’attr’: {(v, attr_value), …} (set)

Return type:

set or list

Examples

>>> from phylozoo.core.primitives.m_multigraph.base import MixedMultiGraph
>>> from phylozoo.core.primitives.m_multigraph.features import cut_vertices
>>> G = MixedMultiGraph()
>>> G.add_undirected_edge(1, 2)
0
>>> G.add_undirected_edge(2, 3)
0
>>> G.add_undirected_edge(2, 4)
0
>>> vertices = cut_vertices(G)
>>> 2 in vertices
True
>>> 1 in vertices
False

Notes

This function uses NetworkX’s articulation_points algorithm, which runs in O(V + E) time. The algorithm works on the underlying undirected (weakly connected) representation.

phylozoo.core.primitives.m_multigraph.features.has_parallel_edges(graph: MixedMultiGraph) bool[source]#

Check if the graph has any parallel edges.

Parallel edges are multiple edges between the same pair of nodes, either as multiple directed edges in the same direction or multiple undirected edges.

Parameters:

graph (MixedMultiGraph) – The graph to check.

Returns:

True if the graph has at least one pair of parallel edges, False otherwise.

Return type:

bool

Examples

>>> from phylozoo.core.primitives.m_multigraph.base import MixedMultiGraph
>>> G = MixedMultiGraph()
>>> G.add_directed_edge(1, 2)
0
>>> G.add_undirected_edge(2, 3)
0
>>> has_parallel_edges(G)
False
>>> G.add_directed_edge(1, 2)  # Add parallel directed edge
1
>>> has_parallel_edges(G)
True
phylozoo.core.primitives.m_multigraph.features.has_self_loops(graph: MixedMultiGraph) bool[source]#

Check whether the mixed multigraph contains any self-loops (directed or undirected).

Parameters:

graph (MixedMultiGraph) – The graph to inspect.

Returns:

True if at least one self-loop exists, False otherwise.

Return type:

bool

Examples

>>> from phylozoo.core.primitives.m_multigraph.base import MixedMultiGraph
>>> from phylozoo.core.primitives.m_multigraph.features import has_self_loops
>>> G = MixedMultiGraph()
>>> has_self_loops(G)
False
>>> G.add_directed_edge(1, 1)
0
>>> has_self_loops(G)
True
>>> G.add_undirected_edge(2, 2)
0
>>> has_self_loops(G)
True

Notes

MixedMultiGraph enforces mutual exclusivity between directed and undirected edges on the same node pair. Adding a directed self-loop to nodes that currently have an undirected self-loop will remove the undirected edge (and vice versa). This function checks both directed and undirected graphs, so it returns True if either side currently contains a self-loop.

phylozoo.core.primitives.m_multigraph.features.is_connected(graph: MixedMultiGraph) bool[source]#

Check if graph is weakly connected.

Parameters:

graph (MixedMultiGraph) – The graph to check.

Returns:

True if graph is connected, False otherwise.

Return type:

bool

Examples

>>> from phylozoo.core.primitives.m_multigraph.base import MixedMultiGraph
>>> G = MixedMultiGraph()
>>> G.add_undirected_edge(1, 2)
0
>>> G.add_directed_edge(2, 3)
0
>>> is_connected(G)
True
phylozoo.core.primitives.m_multigraph.features.number_of_connected_components(graph: MixedMultiGraph) int[source]#

Return the number of weakly connected components.

Parameters:

graph (MixedMultiGraph) – The graph to analyze.

Returns:

Number of connected components.

Return type:

int

Examples

>>> from phylozoo.core.primitives.m_multigraph.base import MixedMultiGraph
>>> G = MixedMultiGraph()
>>> G.add_undirected_edge(1, 2)
0
>>> G.add_directed_edge(3, 4)
0
>>> number_of_connected_components(G)
2
phylozoo.core.primitives.m_multigraph.features.source_components(graph: MixedMultiGraph) list[tuple[list[T], list[tuple[T, T, int]], list[tuple[T, T, int]]]][source]#

Find all source components of a mixed multigraph.

A source component is a connected component C of the undirected graph (i.e., the undirected part of the graph) with the property that there are no directed edges (u, v) in the multigraph with u not in C and v in C (i.e., no directed edges pointing into C).

Parameters:

graph (MixedMultiGraph) – The graph to analyze.

Returns:

For each source component, returns a tuple containing:

  • List of nodes in the component

  • List of undirected edges (u, v, key) within the component (includes all parallel edges)

  • List of directed edges (u, v, key) with u in the component and v not in the component (all outgoing edges of the component, includes all parallel edges)

Return type:

list[tuple[list[T], list[tuple[T, T, int]], list[tuple[T, T, int]]]]

Examples

>>> from phylozoo.core.primitives.m_multigraph.base import MixedMultiGraph
>>> G = MixedMultiGraph()
>>> G.add_undirected_edge(1, 2)
0
>>> G.add_undirected_edge(2, 3)
0
>>> G.add_directed_edge(3, 4)
0
>>> components = source_components(G)
>>> len(components)
1
>>> nodes, undirected_edges, outgoing_edges = components[0]
>>> sorted(nodes)
[1, 2, 3]
>>> sorted(undirected_edges)
[(1, 2, 0), (2, 3, 0)]
>>> outgoing_edges
[(3, 4, 0)]
phylozoo.core.primitives.m_multigraph.features.updown_path_vertices(graph: MixedMultiGraph, x: T, y: T) set[T][source]#

Find all vertices on up-down paths between two vertices x and y.

An up-down path from x to y is a path where no two edges are oriented towards each other. Equivalently, it is a path where the first k edges can be oriented towards x (where undirected edges can be oriented in either way) and the remaining l-k edges can be oriented towards y.

This function finds all vertices v such that there exists an up-down path from x to y that passes through v.

Parameters:
  • graph (MixedMultiGraph) – The mixed multigraph to analyze.

  • x (T) – Source vertex.

  • y (T) – Target vertex.

Returns:

Set of all vertices on up-down paths from x to y, including x and y.

Return type:

set[T]

Examples

>>> from phylozoo.core.primitives.m_multigraph.base import MixedMultiGraph
>>> from phylozoo.core.primitives.m_multigraph.features import updown_path_vertices
>>> G = MixedMultiGraph()
>>> G.add_undirected_edge(1, 2)
0
>>> G.add_undirected_edge(2, 3)
0
>>> G.add_directed_edge(3, 4)
0
>>> G.add_undirected_edge(4, 5)
0
>>> vertices = updown_path_vertices(G, 1, 5)
>>> vertices == {1, 2, 3, 4, 5}
True

Notes

This implementation uses NetworkX’s all_simple_paths to find all paths between x and y, then filters for up-down paths.

Transformations#

Graph transformations module.

This module provides functions to transform MixedMultiGraph instances (e.g., identify nodes, orient edges, suppress degree-2 nodes, etc.).

phylozoo.core.primitives.m_multigraph.transformations.identify_parallel_edge(graph: MixedMultiGraph, u: T, v: T, merged_attrs: dict[str, Any] | None = None) None[source]#

Identify all parallel edges between two nodes by keeping one edge.

This function removes all parallel edges between u and v except one, effectively merging them into a single edge. The first edge (lowest key) is kept, and all others are removed.

For mixed multigraphs, parallel edges can be either:

  • Multiple directed edges from u to v (if any directed edges exist)

  • Multiple undirected edges between u and v (if any undirected edges exist)

Note: Directed and undirected edges between the same nodes are mutually exclusive in MixedMultiGraph, so this function handles only one type at a time.

Edge attributes are handled as follows. If merged_attrs is provided, these attributes are used directly for the kept edge (allowing the caller to apply special merging logic before identification). If merged_attrs is None, attributes are merged by taking the first edge’s data, then subsequent edges’ data overriding; for attributes present in multiple edges, the last edge’s value overrides earlier values.

Parameters:
  • graph (MixedMultiGraph) – The mixed multigraph to modify. This function modifies the graph in place.

  • u (T) – First node.

  • v (T) – Second node.

  • merged_attrs (dict[str, Any] | None, optional) –

    Pre-merged attributes to use for the kept edge. If None, attributes are merged by taking the first edge’s data first, then subsequent edges’ data overriding.

    When provided, these attributes will be used directly for the kept edge. This is useful when special attribute handling is needed.

Raises:

PhyloZooValueError – If either node is not in the graph, or if no edges exist between u and v.

Examples

>>> from phylozoo.core.primitives.m_multigraph.base import MixedMultiGraph
>>> G = MixedMultiGraph()
>>> G.add_directed_edge(1, 2, weight=1.0)
0
>>> G.add_directed_edge(1, 2, weight=2.0)
1
>>> G.add_directed_edge(1, 2, label='test')
2
>>> identify_parallel_edge(G, 1, 2)
>>> G.number_of_edges(1, 2)
1
>>> # With undirected edges
>>> G2 = MixedMultiGraph()
>>> G2.add_undirected_edge(1, 2, weight=1.0)
0
>>> G2.add_undirected_edge(1, 2, weight=2.0)
1
>>> identify_parallel_edge(G2, 1, 2, merged_attrs={'weight': 3.0, 'merged': True})
>>> edge_data = G2._undirected[1][2][0]
>>> edge_data['weight']
3.0
>>> edge_data['merged']
True
phylozoo.core.primitives.m_multigraph.transformations.identify_vertices(graph: MixedMultiGraph, vertices: list[T], merged_attrs: dict[str, Any] | None = None) None[source]#

Identify multiple vertices by keeping the first vertex.

This function identifies all vertices in the list with the first vertex. All edges incident to the other vertices are moved to the first vertex, and the other vertices are removed. The first vertex’s attributes are preserved (or replaced with merged_attrs if provided).

This operation modifies the graph in place. Identification may create new parallel edges. Self-loops are not created (edges from a vertex to itself are removed).

Parameters:
  • graph (MixedMultiGraph) – The mixed multigraph to modify. This function modifies the graph in place.

  • vertices (list[T]) – List of vertices to identify. The first vertex will be kept, and all others will be merged into it.

  • merged_attrs (dict[str, Any] | None, optional) – Attributes to use for the kept vertex. If None, the first vertex’s attributes are preserved. If provided, these attributes replace the first vertex’s attributes.

Raises:

PhyloZooValueError – If the vertices list is empty, if any vertex is not in the graph, if identification would create both directed and undirected edges between the same pair of nodes, or if identification would create edges in both directions between the same pair of nodes (u->v and v->u), which is not allowed.

Notes

This function does not create self-loops. Any edges that would become self-loops after identification are removed.

Identification may create parallel edges. However, if identification would result in both directed and undirected edges between the same pair of nodes (e.g., both a directed edge u->v and an undirected edge u-v), or edges in both directions (e.g., both u->v and v->u), a PhyloZooValueError is raised as this violates the graph’s constraints.

Examples

>>> from phylozoo.core.primitives.m_multigraph.base import MixedMultiGraph
>>> G = MixedMultiGraph()
>>> G.add_directed_edge(1, 2)
0
>>> G.add_undirected_edge(2, 3)
0
>>> G.add_undirected_edge(3, 4)
0
>>> identify_vertices(G, [1, 2, 3])
>>> list(G.nodes())
[1, 4]
>>> G.has_edge(1, 4)
True
phylozoo.core.primitives.m_multigraph.transformations.orient_away_from_vertex(graph: MixedMultiGraph, root: T) DirectedMultiGraph[source]#

Orient all edges in a mixed multigraph away from a root vertex using BFS.

This function performs a BFS from the given root vertex, orienting all undirected edges away from the current vertex. When a directed edge (u, v) is encountered, v is stored in a separate queue. After processing all undirected edges in the BFS, the function continues BFS from vertices in the directed queue, orienting undirected edges away from those vertices. Directed edges cannot be reoriented.

The result is a DirectedMultiGraph where all edges are directed away from the root.

Parameters:
  • graph (MixedMultiGraph) – The mixed multigraph to orient.

  • root (T) – The root vertex from which to orient all edges.

Returns:

A new directed multigraph with all edges oriented away from the root.

Return type:

DirectedMultiGraph

Raises:

PhyloZooValueError – If the root vertex is not in the graph, or if the graph contains cycles that prevent a valid orientation.

Examples

>>> from phylozoo.core.primitives.m_multigraph.base import MixedMultiGraph
>>> G = MixedMultiGraph()
>>> G.add_undirected_edge(1, 2)
0
>>> G.add_undirected_edge(2, 3)
0
>>> dm = orient_away_from_vertex(G, 1)
>>> list(dm.edges())
[(1, 2), (2, 3)]
phylozoo.core.primitives.m_multigraph.transformations.subgraph(graph: MixedMultiGraph, nodes: Iterable[T]) MixedMultiGraph[source]#

Return the induced subgraph of graph on the given nodes.

The returned object is a new MixedMultiGraph instance containing only the specified nodes and any edges (both undirected and directed, including parallel edges and their keys/attributes) whose endpoints are both in nodes. Node and edge attributes are preserved.

Parameters:
  • graph (MixedMultiGraph) – Source mixed multigraph.

  • nodes (Iterable[T]) – Iterable of node identifiers to include in the induced subgraph.

Returns:

A new MixedMultiGraph containing the induced subgraph.

Return type:

MixedMultiGraph

Raises:

PhyloZooValueError – If any node in nodes is not present in graph.

Examples

>>> from phylozoo.core.primitives.m_multigraph.base import MixedMultiGraph
>>> from phylozoo.core.primitives.m_multigraph.transformations import subgraph
>>> G = MixedMultiGraph()
>>> G.add_node(1, label='A')
>>> G.add_node(2, label='B')
>>> G.add_undirected_edge(1, 2, weight=1.0)
0
>>> H = subgraph(G, [1, 2])
>>> list(H.nodes())
[1, 2]
>>> list(H.undirected_edges_iter(keys=True, data=True))
[(1, 2, 0, {'weight': 1.0})]
phylozoo.core.primitives.m_multigraph.transformations.suppress_degree2_node(graph: MixedMultiGraph, node: T, merged_attrs: dict[str, Any] | None = None) None[source]#

Suppress a single degree-2 node in a mixed multigraph in place.

A degree-2 node has exactly two incident edges. Suppression connects the two neighbors directly, preserving edge types according to these rules:

  • undirected + undirected -> undirected edge

  • directed_in + directed_out (u->x, x->v) -> directed edge (u->v)

  • directed_in + undirected (u->x, x—v) -> undirected edge (u—v)

  • undirected + directed_out (u—x, x->v) -> directed edge (u->v)

Invalid combinations that raise ValueError:

  • directed_in + directed_in: Multiple incoming directed edges

  • directed_out + directed_out: Multiple outgoing directed edges

Note: Edge types are processed in order (directed_in first, then undirected, then directed_out), so the order in the rules above is deterministic.

This operation modifies the graph in place. Suppression may create parallel edges.

Edge attributes are handled as follows. If merged_attrs is provided, these attributes are used directly for the new edge (allowing the caller to apply special merging logic before suppression). If merged_attrs is None, attributes are merged by taking the first edge’s data, then the second edge’s data overriding. The order of edges is determined by edge type priority: directed_in edges first, then undirected edges, then directed_out edges. If both edges are of the same type, the order may be non-deterministic (depends on graph iteration order). For attributes present in both edges, the second edge’s value overrides the first.

Parameters:
  • graph (MixedMultiGraph) – The mixed multigraph to modify. This function modifies the graph in place.

  • node (T) – The degree-2 node to suppress.

  • merged_attrs (dict[str, Any] | None, optional) –

    Pre-merged attributes to use for the resulting edge. If None, attributes are merged by taking edge1_data first, then edge2_data overriding.

    When provided, these attributes will be used directly for the new edge created during suppression. This is useful when special attribute handling is needed.

Raises:

PhyloZooValueError – If the node is not degree-2, or has an invalid edge configuration (e.g., multiple directed edges in the same direction, or more than 2 incident edges).

Examples

>>> from phylozoo.core.primitives.m_multigraph.base import MixedMultiGraph
>>> G = MixedMultiGraph()
>>> G.add_undirected_edge(1, 2)
0
>>> G.add_undirected_edge(2, 3)
0
>>> suppress_degree2_node(G, 2)
>>> list(G.edges())
[(1, 3)]

Conversions#

Conversion functions for MixedMultiGraph.

This module provides functions for converting NetworkX graphs to MixedMultiGraph.

phylozoo.core.primitives.m_multigraph.conversions.directedmultigraph_to_mixedmultigraph(graph: DirectedMultiGraph) MixedMultiGraph[source]#

Create a MixedMultiGraph from a DirectedMultiGraph.

All edges from the DirectedMultiGraph are added as directed edges, preserving parallel edges and their keys.

Parameters:

graph (DirectedMultiGraph) – DirectedMultiGraph instance to convert.

Returns:

New MixedMultiGraph instance with all edges as directed.

Return type:

MixedMultiGraph

Examples

>>> from phylozoo.core.primitives.d_multigraph import DirectedMultiGraph
>>> G = DirectedMultiGraph()
>>> G.add_edge(1, 2, weight=1.0)
0
>>> M = directedmultigraph_to_mixedmultigraph(G)
>>> M._directed.number_of_edges()
1
phylozoo.core.primitives.m_multigraph.conversions.graph_to_mixedmultigraph(graph: Graph) MixedMultiGraph[source]#

Create a MixedMultiGraph from a NetworkX Graph.

All edges from the Graph are added as undirected edges.

Parameters:

graph (nx.Graph) – NetworkX Graph to convert.

Returns:

New MixedMultiGraph instance with all edges as undirected.

Return type:

MixedMultiGraph

Examples

>>> import networkx as nx
>>> G = nx.Graph()
>>> G.add_edge(1, 2, weight=5.0)
>>> G.add_edge(2, 3)
>>> M = graph_to_mixedmultigraph(G)
>>> M.number_of_edges()
2
phylozoo.core.primitives.m_multigraph.conversions.multidigraph_to_mixedmultigraph(graph: MultiDiGraph) MixedMultiGraph[source]#

Create a MixedMultiGraph from a NetworkX MultiDiGraph.

All edges from the MultiDiGraph are added as directed edges, preserving parallel edges and their keys.

Parameters:

graph (nx.MultiDiGraph) – NetworkX MultiDiGraph to convert.

Returns:

New MixedMultiGraph instance with all edges as directed.

Return type:

MixedMultiGraph

Examples

>>> import networkx as nx
>>> G = nx.MultiDiGraph()
>>> G.add_edge(1, 2, key=0, weight=1.0)
0
>>> G.add_edge(1, 2, key=1, weight=2.0)
1
>>> M = multidigraph_to_mixedmultigraph(G)
>>> M._directed.number_of_edges()
2
phylozoo.core.primitives.m_multigraph.conversions.multigraph_to_mixedmultigraph(graph: MultiGraph) MixedMultiGraph[source]#

Create a MixedMultiGraph from a NetworkX MultiGraph.

All edges from the MultiGraph are added as undirected edges, preserving parallel edges and their keys.

Parameters:

graph (nx.MultiGraph) – NetworkX MultiGraph to convert.

Returns:

New MixedMultiGraph instance with all edges as undirected.

Return type:

MixedMultiGraph

Examples

>>> import networkx as nx
>>> G = nx.MultiGraph()
>>> G.add_edge(1, 2, key=0, weight=1.0)
0
>>> G.add_edge(1, 2, key=1, weight=2.0)
1
>>> M = multigraph_to_mixedmultigraph(G)
>>> M.number_of_edges()
2

Isomorphism#

Isomorphism module for mixed multi-graphs.

This module provides functions for checking graph isomorphism between MixedMultiGraph instances.

phylozoo.core.primitives.m_multigraph.isomorphism.is_isomorphic(G1: MixedMultiGraph[T], G2: MixedMultiGraph[T], node_attrs: list[str] | None = None, edge_attrs: list[str] | None = None, graph_attrs: list[str] | None = None) bool[source]#

Check if two mixed multi-graphs are isomorphic.

Two graphs are isomorphic if there exists a bijection between their node sets that preserves adjacency, edge direction, and parallel edges. Undirected edges are treated as bidirectional, so they must match with other undirected edges (which appear as bidirectional pairs in the converted graph).

Parameters:
  • G1 (MixedMultiGraph) – First mixed multi-graph.

  • G2 (MixedMultiGraph) – Second mixed multi-graph.

  • node_attrs (list[str] | None, optional) – List of node attribute names to match. If None, node attributes are ignored. Nodes must have matching values for all specified attributes to be considered isomorphic. If a node doesn’t have an attribute, it matches only with nodes that also don’t have that attribute. By default None.

  • edge_attrs (list[str] | None, optional) – List of edge attribute names to match. If None, edge attributes are ignored. Edges must have matching values for all specified attributes. If a node doesn’t have an attribute, it matches only with nodes that also don’t have that attribute.By default None.

  • graph_attrs (list[str] | None, optional) – List of graph-level attribute names to match. If None, graph attributes are ignored. Graph attributes are checked before isomorphism checking for efficiency. By default None.

Returns:

True if the graphs are isomorphic, False otherwise.

Return type:

bool

Examples

>>> from phylozoo.core.primitives.m_multigraph import MixedMultiGraph
>>> G1 = MixedMultiGraph(undirected_edges=[(1, 2)], directed_edges=[(2, 3)])
>>> G2 = MixedMultiGraph(undirected_edges=[(4, 5)], directed_edges=[(5, 6)])
>>> is_isomorphic(G1, G2)
True
>>> # With node labels
>>> G1.add_node(1, label='A')
>>> G2.add_node(4, label='A')
>>> is_isomorphic(G1, G2, node_attrs=['label'])
True
>>> # Edge attributes
>>> G3 = MixedMultiGraph(undirected_edges=[{'u': 1, 'v': 2, 'weight': 1.0}])
>>> G4 = MixedMultiGraph(undirected_edges=[{'u': 3, 'v': 4, 'weight': 1.0}])
>>> is_isomorphic(G3, G4, edge_attrs=['weight'])
True
>>> # Mixed directed and undirected edges
>>> G5 = MixedMultiGraph(
...     undirected_edges=[(1, 2)],
...     directed_edges=[(2, 3), (3, 4)]
... )
>>> G6 = MixedMultiGraph(
...     undirected_edges=[(5, 6)],
...     directed_edges=[(6, 7), (7, 8)]
... )
>>> is_isomorphic(G5, G6)
True

Notes

  • Graph attributes are checked first for efficiency (early exit if they don’t match).

  • NetworkX’s efficient categorical matching functions are used internally.

  • Undirected edges are converted to bidirectional directed edges for matching.

I/O#

Mixed multi-graph I/O module.

This module provides format handlers for reading and writing mixed multi-graphs to/from files. Format handlers are registered with FormatRegistry for use with the IOMixin system.

The following format handlers are defined and registered:

  • phylozoo-dot: PhyloZoo DOT format (extensions: .pzdot). Writer: to_phylozoo_dot() converts MixedMultiGraph to phylozoo-dot string. Reader: from_phylozoo_dot() parses phylozoo-dot string to MixedMultiGraph.

These handlers are automatically registered when this module is imported. MixedMultiGraph inherits from IOMixin, so you can use:

  • graph.save(‘file.pzdot’) - Save to file (auto-detects format)

  • graph.load(‘file.pzdot’) - Load from file (auto-detects format)

  • graph.to_string(format=’phylozoo-dot’) - Convert to string

  • graph.from_string(string, format=’phylozoo-dot’) - Parse from string

  • MixedMultiGraph.convert(‘in.pzdot’, ‘out.pzdot’) - Convert between formats

Notes

PhyloZoo DOT format:

  • Similar to Graphviz DOT format but supports both directed and undirected edges

  • Uses for undirected edges

  • Uses -> for directed edges

  • Supports node attributes (label, shape, color, etc.)

  • Supports edge attributes (label, weight, color, etc.)

  • Supports graph attributes

  • Supports parallel edges (multigraph)

  • Uses node_id as the label if no explicit label is provided

phylozoo.core.primitives.m_multigraph.io.from_phylozoo_dot(pzdot_string: str, **kwargs: Any) MixedMultiGraph[source]#

Parse a phylozoo-dot format string and create a MixedMultiGraph.

Parameters:
  • pzdot_string (str) – PhyloZoo DOT format string containing graph data.

  • **kwargs – Additional arguments (currently unused, for compatibility).

Returns:

Parsed mixed multi-graph.

Return type:

MixedMultiGraph

Raises:

PhyloZooParseError – If the phylozoo-dot string is malformed or cannot be parsed.

Examples

>>> from phylozoo.core.primitives.m_multigraph.io import from_phylozoo_dot
>>>
>>> pzdot_str = '''graph {
...     1 [label="Node1"];
...     2 [label="Node2"];
...     1 -> 2 [weight=1.0];
...     2 -- 3 [weight=2.0];
... }'''
>>>
>>> G = from_phylozoo_dot(pzdot_str)
>>> G.number_of_nodes()
3
>>> G.number_of_edges()
2

Notes

This parser expects:

  • graph declaration (not digraph)

  • Node declarations (optional attributes)

  • Undirected edge declarations with (optional attributes)

  • Directed edge declarations with -> (optional attributes)

  • Graph attributes (optional)

  • Support for parallel edges

phylozoo.core.primitives.m_multigraph.io.to_phylozoo_dot(graph: MixedMultiGraph, **kwargs: Any) str[source]#

Convert a MixedMultiGraph to a phylozoo-dot format string.

Parameters:
  • graph (MixedMultiGraph) – The mixed multi-graph to convert.

  • **kwargs – Additional arguments (currently unused, for compatibility).

Returns:

The phylozoo-dot format string representation of the graph.

Return type:

str

Examples

>>> from phylozoo.core.primitives.m_multigraph import MixedMultiGraph
>>> from phylozoo.core.primitives.m_multigraph.io import to_phylozoo_dot
>>>
>>> G = MixedMultiGraph()
>>> G.add_directed_edge(1, 2, weight=1.0)
0
>>> G.add_undirected_edge(2, 3, weight=2.0)
0
>>> pzdot_str = to_phylozoo_dot(G)
>>> 'graph' in pzdot_str
True
>>> '1 -> 2' in pzdot_str
True
>>> '2 -- 3' in pzdot_str
True

Notes

The phylozoo-dot format includes:

  • graph declaration (not digraph, since we have both types)

  • Node declarations with attributes

  • Undirected edge declarations with

  • Directed edge declarations with ->

  • Graph attributes (if any)

  • Support for parallel edges (multigraph)