Source code for pywhy_graphs.algorithms.cyclic
import networkx as nx
import pywhy_graphs.networkx as pywhy_nx
[docs]
def acyclification(
G: pywhy_nx.MixedEdgeGraph,
directed_edge_type: str = "directed",
bidirected_edge_type: str = "bidirected",
copy: bool = True,
) -> pywhy_nx.MixedEdgeGraph:
"""Acyclify a cyclic graph.
Applies the acyclification procedure presented in :footcite:`Mooij2020cyclic`.
This converts to G to what is called :math:`G^{acy}` in the reference.
Parameters
----------
G : pywhy_nx.MixedEdgeGraph
A graph with cycles.
directed_edge_type : str
The name of the sub-graph of directed edges.
bidirected_edge_type : str
The name of the sub-graph of bidirected edges.
copy : bool
Whether to operate on the graph in place, or make a copy.
Returns
-------
G : pywhy_nx.MixedEdgeGraph
The acyclified graph.
Notes
-----
This replaces all strongly connected components of G by fully connected
bidirected components without any directed edges. Then any node with an
edge pointing into the SC (i.e. a directed edge, or bidirected edge) is
made fully connected with the nodes of the SC either with a directed, or
bidirected edge.
References
----------
.. footbibliography::
"""
if copy:
G = G.copy()
# extract the subgraph of directed edges
directed_G: nx.DiGraph = G.get_graphs(directed_edge_type).copy()
if bidirected_edge_type in G.edge_types:
bidirected_G: nx.Graph = G.get_graphs(bidirected_edge_type).copy()
else:
bidirected_G = nx.Graph()
bidirected_G.add_nodes_from(G.nodes)
# first detect all strongly connected components
scomps = nx.strongly_connected_components(directed_G)
# loop over all strongly connected components and their nodes
for comp in scomps:
if len(comp) == 1:
continue
# extract the parents, or c-components of any node
# in the strongly-connected component
scomp_parents = set()
scomp_c_components = set()
scomp_children = []
for node in comp:
# get any predecessors of SC
for parent in directed_G.predecessors(node):
if parent in comp:
continue
scomp_parents.add(parent)
# get any bidirected edges pointing to elements of SC
for nbr in bidirected_G.neighbors(node):
if nbr in comp:
continue
scomp_c_components.add(nbr)
# keep track of any edges pointing out of the SC
for child in directed_G.successors(node):
if child in comp:
continue
scomp_children.append((node, child))
# first remove all nodes in the cycle
G.remove_nodes_from(comp)
# add them back in as a fully connected bidirected graph
bidirected_fc_G = nx.complete_graph(comp)
if bidirected_edge_type not in G.edge_types:
G.add_edge_type(nx.Graph(), bidirected_edge_type)
G.add_edges_from(bidirected_fc_G.edges, bidirected_edge_type)
# add back the children
G.add_edges_from(scomp_children, directed_edge_type)
# make all variables connect to the strongly connected component
for node in comp:
for parent in scomp_parents:
G.add_edge(parent, node, directed_edge_type)
for c_component in scomp_c_components:
G.add_edge(c_component, node, bidirected_edge_type)
return G
def sigma_separated(G: pywhy_nx.MixedEdgeGraph, x, y, z):
acy_G = acyclification(G)
return pywhy_nx.m_separated(acy_G, x, y, z)