11import 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-
124def 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
3631def 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
5453def 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
7779def 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
89103def 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
113140def 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