33
44
55def sort_nodes (node ):
6+ """
7+ Function to sort nodes for consistent ordering.
8+
9+ Args:
10+ node: A node in the graph, which can be of any hashable type.
11+
12+ Returns:
13+ tuple: A tuple where the first element indicates if the node is a tuple,
14+ and the second element is the string representation of the node.
15+ """
616 is_tuple = isinstance (node , tuple )
7- safe_val = str (node )
8- return (is_tuple , safe_val )
17+ string_rep = str (node )
18+ # False comes before True when sorting so non-tuple nodes are prioritized
19+ # If two nodes are of the same type, sort by their string representation
20+ return (is_tuple , string_rep )
921
1022
1123def get_non_trivial_components (G ):
24+ """
25+ Retrieves the connected components of G that contain more than one node.
26+
27+ Args:
28+ G (networkx.Graph): A networkx graph.
29+
30+ Returns:
31+ list: A list of sets reprenting the non-trivial components of G.
32+ """
1233 components = []
1334 for c in nx .connected_components (G ):
1435 if len (c ) > 1 :
@@ -18,6 +39,18 @@ def get_non_trivial_components(G):
1839
1940
2041def construct_H (G , F , nb ):
42+ """
43+ Constructs a subgraph H from the neighbors of a given set of nodes and adding edges
44+ between nodes that share a common neighbor in F.
45+
46+ Args:
47+ G (networkx.Graph): A networkx graph.
48+ F (set): A set of nodes in G.
49+ nb (set): A set of neighbors in G.
50+
51+ Returns:
52+ networkx.Graph: The constructed subgraph H.
53+ """
2154 H = nx .subgraph (G , nb ).copy ()
2255 for u , v in itertools .combinations (nb , 2 ):
2356 if not set (nx .common_neighbors (G , u , v )).isdisjoint (F ):
@@ -27,6 +60,17 @@ def construct_H(G, F, nb):
2760
2861
2962def is_foldable (G , v ):
63+ """
64+ Checks if a node v in the graph is foldable. A node is foldable if none of its neighbors
65+ form an anti-triangle. An anti-triangle is a set of three nodes with no edges between them.
66+
67+ Args:
68+ G (networkx.Graph): A networkx graph.
69+ v: A node in G.
70+
71+ Returns:
72+ bool: True if the node is foldable, False otherwise.
73+ """
3074 nb = sorted (nx .neighbors (G , v ), key = sort_nodes )
3175 for x , y , z in itertools .combinations (nb , 3 ):
3276 if not (G .has_edge (x , y ) or G .has_edge (y , z ) or G .has_edge (z , x )):
@@ -37,6 +81,17 @@ def is_foldable(G, v):
3781
3882
3983def get_anti_edges (G , v ):
84+ """
85+ Finds all anti-edges among the neighbors of a node v. An anti-edge is a pair of
86+ nodes that are not connected by an edge.
87+
88+ Args:
89+ G (networkx.Graph): A networkx graph.
90+ v: A node in G.
91+
92+ Returns:
93+ set: A set of tuples representing anti-edges.
94+ """
4095 nb = nx .neighbors (G , v )
4196 anti_edges = set ()
4297 for x , y in itertools .combinations (nb , 2 ):
@@ -47,25 +102,50 @@ def get_anti_edges(G, v):
47102
48103
49104def fold_graph (G , v , anti_edges ):
105+ """
106+ Folds the graph by contracting anti-edges of a node v and removing its neighbors.
107+
108+ Args:
109+ G (networkx.Graph): A networkx graph.
110+ v: A node in G.
111+ anti_edges: A set of anti-edges to be contracted.
112+
113+ Returns:
114+ networkx.Graph: The folded graph.
115+ """
50116 nb_v = set (nx .neighbors (G , v ))
51117 folded_G = G .copy ()
52118 added_nodes = set ()
53119
54120 for i , j in anti_edges :
121+ # Create a new node representing the contraction of i and j
55122 n = (i , j )
56123 folded_G .add_node (n )
57124 added_nodes .add (n )
58125 for u in set (nx .neighbors (G , i )) | set (nx .neighbors (G , j )):
126+ # Add edges between the new node and the neighbors of i and j except v and those in nb_v
127+ # (these nodes will be removed later)
59128 if u != v and u not in nb_v :
60129 folded_G .add_edge (n , u )
61130
62131 for a , b in itertools .combinations (added_nodes , 2 ):
132+ # Add edges between each newly created node
63133 folded_G .add_edge (a , b )
64134
65135 return nx .subgraph (folded_G , set (folded_G .nodes ) - nb_v - {v })
66136
67137
68138def get_2_hop_neighbors (G , v ):
139+ """
140+ Finds all nodes that are two hops away from a given node v.
141+
142+ Args:
143+ G (networkx.Graph): A networkx graph.
144+ v: A node in G.
145+
146+ Returns:
147+ set: A set of nodes that are two hops away from v.
148+ """
69149 first_neighbors = set (G .neighbors (v ))
70150 second_neighbors = set ()
71151 for u in first_neighbors :
@@ -77,10 +157,31 @@ def get_2_hop_neighbors(G, v):
77157
78158
79159def is_complete (G ):
160+ """
161+ Checks if a graph is complete. A graph is complete if every pair of nodes is connected.
162+
163+ Args:
164+ G (networkx.Graph): A networkx graph.
165+
166+ Returns:
167+ bool: True if the graph is complete, False otherwise.
168+ """
80169 return len (G .edges ) == len (G .nodes ) * (len (G .nodes ) - 1 ) / 2
81170
82171
83172def get_mirrors (G , v ):
173+ """
174+ Finds all mirror nodes of a given node v. A node u is a mirror of v if their
175+ neighborhoods are identical or if the subgraph induced by the difference of their
176+ neighborhoods is complete.
177+
178+ Args:
179+ G (networkx.Graph): A networkx graph.
180+ v: A node in G.
181+
182+ Returns:
183+ set: A set of mirror nodes of v.
184+ """
84185 mirrors = set ()
85186
86187 for u in get_2_hop_neighbors (G , v ):
@@ -93,6 +194,15 @@ def get_mirrors(G, v):
93194
94195
95196def get_max_indep_set (G ):
197+ """
198+ Computes the size of the maximum independent set of a graph.
199+
200+ Args:
201+ G (networkx.Graph): A networkx graph.
202+
203+ Returns:
204+ int: The size of the maximum independent set.
205+ """
96206 if len (G .nodes ) == 0 :
97207 return 0
98208
@@ -128,6 +238,19 @@ def get_max_indep_set(G):
128238
129239
130240def get_generalized_neighbors (G , F , active_v , v ):
241+ """
242+ Finds the generalized neighbors of a node v considering a set of nodes F
243+ and an active node.
244+
245+ Args:
246+ G (networkx.Graph): A networkx graph.
247+ F: A set of fixed nodes in G.
248+ active_v: The active node in G.
249+ v: The node for which generalized neighbors are computed.
250+
251+ Returns:
252+ set: A set of generalized neighbors of v.
253+ """
131254 K = {u for u in (F - {active_v }) if G .has_edge (u , v )}
132255 new_G = G .copy ()
133256
@@ -139,7 +262,19 @@ def get_generalized_neighbors(G, F, active_v, v):
139262
140263
141264def get_mif_len (G , F , active_v ):
265+ """
266+ Computes the length of a maximum induced forest (MIF) of a graph. This MIF must include all nodes in F.
267+
268+ Args:
269+ G (networkx.Graph): A networkx graph.
270+ F: A set of fixed nodes in G.
271+ active_v: The active node in G.
272+
273+ Returns:
274+ int: The length of a MIF of G.
275+ """
142276 if nx .number_connected_components (G ) > 1 :
277+ # If G is disconnected, compute MIF length for each component separately
143278 res = 0
144279 for c in nx .connected_components (G ):
145280 if nx .is_forest (nx .subgraph (G , c )):
@@ -152,7 +287,7 @@ def get_mif_len(G, F, active_v):
152287 return res
153288
154289 sg_F = nx .subgraph (G , F )
155- # Verify is F is acyclic
290+ # Verify if F is acyclic
156291 if len (sg_F .nodes ) > 0 and not nx .is_forest (sg_F ):
157292 return 0
158293
@@ -176,6 +311,7 @@ def get_mif_len(G, F, active_v):
176311
177312 v_T = list (sorted (T , key = sort_nodes ))[0 ]
178313
314+ # Contract all nodes in T into v_T
179315 for n in T - {v_T }:
180316 new_G = nx .contracted_nodes (new_G , v_T , n , self_loops = False )
181317
@@ -194,7 +330,18 @@ def get_mif_len(G, F, active_v):
194330
195331
196332def main_procedure (G , F , active_v ):
197-
333+ """
334+ Main procedure for computing the length of a maximum induced forest (MIF) of G.
335+ This MIF must include all nodes in F.
336+
337+ Args:
338+ G (networkx.Graph): A networkx graph.
339+ F: A set of fixed nodes in G.
340+ active_v: The active node in G.
341+
342+ Returns:
343+ int: The length of a MIF in G.
344+ """
198345 if F == set (G .nodes ):
199346 return len (G .nodes )
200347
@@ -204,6 +351,8 @@ def main_procedure(G, F, active_v):
204351 if max_degree < 2 :
205352 return len (G .nodes )
206353 else :
354+ # Choose a node t with degree at least 2
355+ # t is either contained in a MIF or not, thus we explore both subproblems
207356 t = list (sorted ([n for n , d in G .degree () if d >= 2 ], key = sort_nodes ))[0 ]
208357 new_G = nx .subgraph (G , G .nodes - {t })
209358 return max (
@@ -215,16 +364,19 @@ def main_procedure(G, F, active_v):
215364
216365 nb = set (nx .neighbors (G , active_v ))
217366 if set (G .nodes ) - F == nb :
367+ # Get a maximum independent set of the constructed graph H
218368 return len (F ) + get_max_indep_set (construct_H (G , F - {active_v }, nb ))
219369
220370 for v in sorted (nb , key = sort_nodes ):
221371 gen_nb = get_generalized_neighbors (G , F , active_v , v )
222372 if len (gen_nb ) < 2 :
373+ # Include v in the MIF
223374 return get_mif_len (G , F | {v }, active_v )
224375
225376 for v in sorted (nb , key = sort_nodes ):
226377 gen_nb = get_generalized_neighbors (G , F , active_v , v )
227378 if len (gen_nb ) > 3 :
379+ # Explore both subproblems of including and excluding v from the MIF
228380 return max (
229381 get_mif_len (G , F | {v }, active_v ),
230382 get_mif_len (nx .subgraph (G , G .nodes - {v }), F , active_v ),
@@ -233,6 +385,7 @@ def main_procedure(G, F, active_v):
233385 for v in sorted (nb , key = sort_nodes ):
234386 gen_nb = get_generalized_neighbors (G , F , active_v , v )
235387 if len (gen_nb ) == 2 :
388+ # Either include v in the MIF or exclude v and include its two generalized neighbors in the MIF
236389 return max (
237390 get_mif_len (G , F | {v }, active_v ),
238391 get_mif_len (
@@ -242,7 +395,8 @@ def main_procedure(G, F, active_v):
242395 ),
243396 )
244397
245- # If every v in nb has exactly 3 generalized neighbors, find a v that has at least one generalized neighbor outside nb
398+ # If every v in nb has exactly 3 generalized neighbors, find a v that has at least
399+ # one generalized neighbor outside nb
246400 v = None
247401 good_gen_nb = None
248402 for n in nb :
@@ -265,6 +419,8 @@ def main_procedure(G, F, active_v):
265419 else :
266420 print ("Problem" )
267421
422+ # Either include v in the MIF, or exclude v and include w1 (which is not in the neighborhood of
423+ # the active vertex), or exclude v and w1 and include w2 and w3 in the MIF
268424 return max (
269425 get_mif_len (G , F | {v }, active_v ),
270426 get_mif_len (
@@ -280,12 +436,21 @@ def main_procedure(G, F, active_v):
280436 )
281437
282438 else :
439+ # Should not happen but for safety
283440 print ("Problem" )
284441 return Exception ("No suitable v found" )
285442
286443
287444def get_decycling_number_fomin (G ):
445+ """
446+ Computes the decycling number of a graph using Fomin's algorithm.
447+
448+ Args:
449+ G (networkx.Graph): A networkx graph.
450+
451+ Returns:
452+ int: The decycling number of the graph.
453+ """
288454 if nx .is_forest (G ):
289455 return 0
290-
291456 return len (G .nodes ) - get_mif_len (G , set (), None )
0 commit comments