-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[ENH] Adding acyclification procedure (#17)
* Remove license in doc/index.rst (#14) * Adding acyclification algorithm Signed-off-by: Adam Li <[email protected]>
- Loading branch information
Showing
7 changed files
with
183 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
from .cyclic import * # noqa: F403 | ||
from .generic import * # noqa: F403 | ||
from .pag import * # noqa: F403 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import networkx as nx | ||
|
||
|
||
def acyclification( | ||
G: nx.MixedEdgeGraph, | ||
directed_edge_type: str = "directed", | ||
bidirected_edge_type: str = "bidirected", | ||
copy: bool = True, | ||
) -> 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 : 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 : nx.MixedEdgeGraph | ||
The acyclified graph. | ||
Notes | ||
----- | ||
This takes | ||
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() | ||
bidirected_G: nx.Graph = G.get_graphs(bidirected_edge_type).copy() | ||
|
||
# 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) | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import networkx as nx | ||
|
||
import pywhy_graphs | ||
|
||
|
||
def test_acyclification(): | ||
"""Test acyclification procedure as specified in :footcite:`Mooij2020cyclic`. | ||
Tests the graphs as presented in Figure 2. | ||
""" | ||
directed_edges = nx.DiGraph( | ||
[ | ||
("x8", "x2"), | ||
("x9", "x2"), | ||
("x10", "x1"), | ||
("x2", "x4"), | ||
("x4", "x6"), # start of cycle | ||
("x6", "x5"), | ||
("x5", "x3"), | ||
("x3", "x4"), # end of cycle | ||
("x6", "x7"), | ||
] | ||
) | ||
bidirected_edges = nx.Graph([("x1", "x3")]) | ||
G = nx.MixedEdgeGraph([directed_edges, bidirected_edges], ["directed", "bidirected"]) | ||
acyclic_G = pywhy_graphs.acyclification(G) | ||
|
||
directed_edges = nx.DiGraph( | ||
[ | ||
("x8", "x2"), | ||
("x9", "x2"), | ||
("x10", "x1"), | ||
("x2", "x4"), | ||
("x6", "x7"), | ||
("x2", "x3"), | ||
("x2", "x5"), | ||
("x2", "x4"), | ||
("x2", "x6"), | ||
] | ||
) | ||
bidirected_edges = nx.Graph( | ||
[ | ||
("x1", "x3"), | ||
("x4", "x6"), | ||
("x6", "x5"), | ||
("x5", "x3"), | ||
("x3", "x4"), | ||
("x4", "x5"), | ||
("x3", "x6"), | ||
("x1", "x3"), | ||
("x1", "x5"), | ||
("x1", "x4"), | ||
("x1", "x6"), | ||
] | ||
) | ||
expected_G = nx.MixedEdgeGraph([directed_edges, bidirected_edges], ["directed", "bidirected"]) | ||
|
||
for edge_type, graph in acyclic_G.get_graphs().items(): | ||
expected_graph = expected_G.get_graphs(edge_type) | ||
assert nx.is_isomorphic(graph, expected_graph) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters