Skip to content

Commit 704e304

Browse files
committed
Added forest check + doc for razgon.py
1 parent dde7882 commit 704e304

1 file changed

Lines changed: 76 additions & 55 deletions

File tree

razgon.py

Lines changed: 76 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,45 @@
11
import networkx as nx
22

33

4-
class CycleDetected(Exception):
5-
def __init__(self, message):
6-
super().__init__(message)
7-
8-
9-
# Algorithm taken from the article "Exact Computation of Maximum Induced Forest"
10-
11-
124
def get_cnf(G, T):
135
"""
14-
Computes the set of conflicting vertices in G that are not in T but have at least 2 neighbors in a same connected
15-
component of the subgraph of G induced by T.
16-
:param G: a networkx graph
17-
:param T: a subset of vertices in G
18-
:return: a set of vertices representing the conflicting vertices
19-
"""
6+
Computes the set of conflicting vertices in G that are not in T but have at least 2 neighbors
7+
in the same connected component of the subgraph of G induced by T.
8+
9+
Args:
10+
G (networkx.Graph): A networkx graph.
11+
T (set): A subset of vertices in G.
2012
13+
Returns:
14+
set: A set of vertices representing the conflicting vertices.
15+
"""
2116
cnf = set()
2217
components = list(nx.connected_components(nx.subgraph(G, T)))
2318

2419
if len(components) > 0:
2520
for v in sorted(set(G.nodes) - T):
2621
# For each connected component from T, check if v has 2 or more neighbors in the component
2722
for comp in components:
28-
neighbors_in_comp = set(nx.neighbors(G, v)) & comp # & = intersection
23+
neighbors_in_comp = set(nx.neighbors(G, v)) & comp
2924
if len(neighbors_in_comp) >= 2:
3025
cnf.add(v)
31-
break # Because v only needs to have 2 neighbors in one of the components to be considered conflicting
26+
break # v only needs 2 neighbors in one component to be conflicting
3227

3328
return cnf
3429

3530

3631
def get_bnd(G, T):
3732
"""
38-
Computes the boundary vertices of T in G ie the vertices that are not in T but have exactly one neighbor in T.
39-
:param G: a networkx graph
40-
:param T: a subset of vertices in G
41-
:return: a set of vertices representing the boundary vertices of T in G
42-
"""
33+
Computes the boundary vertices of T in G, i.e., the vertices that are not in T but have exactly
34+
one neighbor in T.
4335
36+
Args:
37+
G (networkx.Graph): A networkx graph.
38+
T (set): A subset of vertices in G.
39+
40+
Returns:
41+
set: A set of vertices representing the boundary vertices of T in G.
42+
"""
4443
bnd = set()
4544

4645
for v in set(G.nodes) - T:
@@ -53,14 +52,17 @@ def get_bnd(G, T):
5352

5453
def get_K_connected(G, K, v):
5554
"""
56-
Computes the subset of vertices that are K-connected to v.
57-
To be K-connected to v, a vertex u in V(G) must be adjacent to v or have a path to v such that the whole path is in K.
58-
:param G: a networkx graph
59-
:param K: a subset of vertices in G
60-
:param v: a vertex in G
61-
:return: a set of vertices representing the K-connected vertices to v
62-
"""
55+
Computes the subset of vertices that are K-connected to v. A vertex u is K-connected to v if
56+
it is adjacent to v or has a path to v such that the whole path is in K.
57+
58+
Args:
59+
G (networkx.Graph): A networkx graph.
60+
K (set): A subset of vertices in G.
61+
v: A vertex in G.
6362
63+
Returns:
64+
set: A set of vertices representing the K-connected vertices to v.
65+
"""
6466
result = set(nx.neighbors(G, v))
6567
nb_in_K = result & K
6668
sg_K = nx.subgraph(G, K)
@@ -75,18 +77,43 @@ def get_K_connected(G, K, v):
7577

7678

7779
def T_update(G, T, K, v):
80+
"""
81+
Updates the sets T and K by adding the K-connected vertices to T and removing them from K,
82+
and removes conflicting vertices from the graph.
83+
84+
Args:
85+
G (networkx.Graph): A networkx graph.
86+
T (set): A subset of vertices in G.
87+
K (set): A subset of vertices in G.
88+
v: A vertex in G.
89+
90+
Returns:
91+
tuple: A tuple containing the updated graph, T, and K.
92+
"""
7893
S = get_K_connected(G, K, v) & K
7994
new_T = T | S | {v} # Update T with the K-connected vertices and v
8095
new_K = K - (S | {v}) # Update K by removing the K-connected vertices and v
8196
sg_nodes = set(G.nodes) - get_cnf(G, new_T)
8297
sg = nx.subgraph(
8398
G, sg_nodes
8499
) # Create a subgraph induced by the nodes that are not conflicting with the new T
85-
# All these conflicting vertices are removed because they would create cycles in the subgraph
86100
return sg, new_T, new_K
87101

88102

89103
def K_update(G, T, K, W):
104+
"""
105+
Updates the graph G, T, and K based on the set W.
106+
107+
Args:
108+
G (networkx.Graph): A networkx graph.
109+
T (set): A subset of vertices in G.
110+
K (set): A subset of vertices in G.
111+
W (set): A subset of 2 vertices in G.
112+
113+
Returns:
114+
tuple or None: A tuple containing the updated graph, T, and K, or None if the subgraph
115+
induced by T, K, and W contains cycles.
116+
"""
90117
sg = nx.subgraph(G, T | K | W)
91118

92119
if nx.is_forest(sg):
@@ -112,13 +139,17 @@ def K_update(G, T, K, W):
112139

113140
def find_mif_len(G, T, K):
114141
"""
115-
Searches for the length of a (T union K)-MIF of the graph G.
116-
:param G: a networkx graph
117-
:param T: a subset of vertices in G
118-
:param K: a subset of vertices in G
119-
:return: the length of a (T union K)-MIF of G
120-
"""
142+
Searches for the length of a (T union K)-MIF of the graph G. A (T union K)-MIF is a maximal
143+
induced forest that includes all vertices of T and K.
121144
145+
Args:
146+
G (networkx.Graph): A networkx graph.
147+
T (set): A subset of vertices in G.
148+
K (set): A subset of vertices in G.
149+
150+
Returns:
151+
int: The length of a (T union K)-MIF of G.
152+
"""
122153
if T | K == set(G.nodes):
123154
return len(T | K)
124155

@@ -152,26 +183,16 @@ def find_mif_len(G, T, K):
152183
)
153184

154185

155-
def get_mif_len(G, K):
156-
"""
157-
Main function to get the length of a K-MIF of a graph G.
158-
Given a subset K of V(G), a K-MIF is a largest superset of K such that the subgraph of G induced by K is acyclic.
159-
:param G: a networkx graph
160-
:param K: a subset of vertices in G
161-
:raises CycleDetected: if the subgraph induced by K contains cycles
162-
:return: the length of a K-MIF of G
186+
def get_decycling_number_razgon(G):
163187
"""
188+
Computes the decycling number of a graph G using Razgon's algorithm.
164189
165-
if len(K) == 0 or nx.is_forest(nx.subgraph(G, K)):
166-
sg_nodes = set(G.nodes) - get_cnf(G, K)
167-
sg = nx.subgraph(G, sg_nodes)
168-
return find_mif_len(sg, set(), K)
169-
170-
else:
171-
raise CycleDetected(
172-
"No K-MIF of G can be found because the subgraph induced by K contains cycles."
173-
)
190+
Args:
191+
G (networkx.Graph): A networkx graph.
174192
175-
176-
def get_decycling_number_razgon(G):
177-
return len(set(G.nodes)) - get_mif_len(G, set())
193+
Returns:
194+
int: The decycling number of G.
195+
"""
196+
if nx.is_forest(G):
197+
return 0
198+
return len(set(G.nodes)) - find_mif_len(G, set(), set())

0 commit comments

Comments
 (0)