Skip to content

Commit 361eb91

Browse files
committed
Docs for Bar-Yehuda
1 parent e54e272 commit 361eb91

1 file changed

Lines changed: 77 additions & 21 deletions

File tree

bar_yehuda.py

Lines changed: 77 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,27 @@
33

44

55
def find_maximal_2_3_subgraph(og_G):
6+
"""
7+
Finds a maximal subgraph of the input graph where all nodes have degree 2 or 3.
8+
This function iteratively constructs a subgraph by identifying cycles and paths
9+
that can be added while maintaining the degree constraints.
10+
11+
Args:
12+
og_G (nx.Graph): The input graph.
13+
14+
Returns:
15+
nx.Graph: A maximal subgraph of the input graph where all nodes have degree 2 or 3.
16+
"""
617
G = og_G.copy()
718
H = nx.Graph()
819
H.add_nodes_from(G.nodes())
920
nodes_to_visit = deque(G.nodes())
1021

11-
# Chemin actuel
22+
# Current path
1223
stack = []
1324
in_stack = set()
1425

15-
# stack[0] est connecté à un noeud de H de degré 2?
26+
# Is stack[0] connected to H by a degree 2 node?
1627
start_connected = False
1728

1829
while True:
@@ -25,7 +36,7 @@ def find_maximal_2_3_subgraph(og_G):
2536
break
2637

2738
if start_node is None:
28-
# Plus de noeuds à visiter
39+
# No more nodes to visit
2940
break
3041

3142
stack = [start_node]
@@ -35,7 +46,7 @@ def find_maximal_2_3_subgraph(og_G):
3546
else:
3647
start_connected = False
3748

38-
# Extrémité du chemin actuel
49+
# Extract the extremity of the path
3950
u = stack[-1]
4051
parent = stack[-2] if len(stack) > 1 else None
4152
nb = list(G.neighbors(u))
@@ -51,22 +62,21 @@ def find_maximal_2_3_subgraph(og_G):
5162
if len(edges_to_remove) > 0:
5263
G.remove_edges_from(edges_to_remove)
5364

54-
# Si pas de voisins du tout, ou seul voisin est le parent, ou tous les voisins (sans compter le parent)
55-
# ont un degré 3 dans H -> cul de sac
65+
# If no valid neighbors, backtrack
5666
if len(valid) == 0:
5767
if parent is not None:
5868
if G.has_edge(parent, u):
5969
G.remove_edge(parent, u)
6070
in_stack.remove(stack.pop())
6171

62-
# Reset si la stack est vide
72+
# Reset if stack is empty
6373
if len(stack) == 0:
6474
start_connected = False
6575
continue
6676

6777
v = valid[0]
6878

69-
# Si v est dans la stack, alors on a un cycle
79+
# If v is already in stack -> cycle detected
7080
if v in in_stack:
7181
idx = stack.index(v)
7282
cycle_nodes = stack[idx:]
@@ -77,17 +87,16 @@ def find_maximal_2_3_subgraph(og_G):
7787
H.add_edges_from(edges_to_add)
7888
G.remove_edges_from(edges_to_add)
7989

80-
# Si idx == 0, cycle formé par tous les sommets de la stack
90+
# Handle cycle cases
8191
if idx == 0:
8292
stack = []
8393
in_stack = set()
8494
start_connected = False
85-
8695
else:
8796
stack = stack[: idx + 1]
8897
in_stack = set(stack)
89-
# Le bout de la pile (v) est connecté à H
90-
# Si le début est connecté, alors c'est un chemin valide à ajouter
98+
# The end of the stack (v) is connected to H
99+
# If the start is connected, then it's a valid path to add
91100
if start_connected:
92101
path_edges = []
93102
for i in range(len(stack) - 1):
@@ -97,18 +106,16 @@ def find_maximal_2_3_subgraph(og_G):
97106
stack = []
98107
in_stack = set()
99108
start_connected = False
100-
101109
else:
102-
# La pile est inversée pour essayer d'attacher le début du chemin à H ou un autre cycle
110+
# The stack is reversed to try to connect the start of the path to H or another cycle
103111
stack.reverse()
104-
# v était à la fin de la stack, maintenant au début donc le début est connecté à H
112+
# v was at the end of the stack, now at the beginning so the start of the stack is connected to H
105113
start_connected = True
106114

107115
continue
108116

109117
elif H.degree(v) == 2:
110-
# v est déjà dans H et à un degré 2 donc la connexion du chemin dans stack est possible à v
111-
# Si le début est connecté, alors c'est un chemin valide à ajouter
118+
# Connect path to v if start is connected
112119
if start_connected:
113120
path_edges = []
114121
for i in range(len(stack) - 1):
@@ -119,15 +126,14 @@ def find_maximal_2_3_subgraph(og_G):
119126
stack = []
120127
in_stack = set()
121128
start_connected = False
122-
123129
else:
124130
stack.append(v)
125131
in_stack.add(v)
126132
stack.reverse()
127133
start_connected = True
128134
continue
129135

130-
# v est degré 0 ou 1 dans H, on peut l'ajouter au chemin
136+
# v has degree 0 or 1 in H, we can add it to the path
131137
else:
132138
stack.append(v)
133139
in_stack.add(v)
@@ -139,6 +145,18 @@ def find_maximal_2_3_subgraph(og_G):
139145

140146

141147
def get_critical_linkpoints(G, H):
148+
"""
149+
Identifies critical linkpoints in the subgraph.
150+
A critical linkpoint v is a node of degree 2 in H such that removing
151+
all the nodes of H except v from G creates a cycle including v.
152+
153+
Args:
154+
G (nx.Graph): The original graph.
155+
H (nx.Graph): The maximal subgraph with degree 2 or 3 nodes.
156+
157+
Returns:
158+
set: The set of critical linkpoints.
159+
"""
142160
linkpoints = {n for n in H.nodes if H.degree(n) == 2}
143161
critical_linkpoints = set()
144162
G_prime = nx.subgraph(G, set(G.nodes) - (set(H.nodes)))
@@ -155,11 +173,11 @@ def get_critical_linkpoints(G, H):
155173
if nb in H.nodes:
156174
continue
157175

158-
# Vérifier si un cycle comprenant n existe dans G\H avec n étant le sommet du cycle appartenant à H
176+
# Verify if a cycle including n exists in G\H with n being the vertex of the cycle belonging to H
159177
if nb in node_in_component:
160178
comp = node_in_component[nb]
161179
if comp in visited_components:
162-
# Si deux voisins appartiennent à la même composante connexe de G\H, alors il y a un cycle avec n
180+
# If two neighbors belong to the same connected component of G\H, then there is a cycle with n
163181
is_critical = True
164182
break
165183

@@ -172,10 +190,30 @@ def get_critical_linkpoints(G, H):
172190

173191

174192
def is_cycle(G):
193+
"""
194+
Checks if the graph is a cycle.
195+
196+
Args:
197+
G (nx.Graph): The input graph.
198+
199+
Returns:
200+
bool: True if the graph is a cycle, False otherwise.
201+
"""
175202
return all(G.degree(n) == 2 for n in G.nodes)
176203

177204

178205
def get_set_covering_cycles(H, X, Y):
206+
"""
207+
Finds a set of nodes that cover all cycles in the subgraph excluding critical linkpoints and high-degree nodes.
208+
209+
Args:
210+
H (nx.Graph): The subgraph with degree 2 or 3 nodes.
211+
X: Set of critical linkpoints.
212+
Y: Nodes with degree = 3 in H.
213+
214+
Returns:
215+
set: A set of nodes covering all cycles in the subgraph.
216+
"""
179217
sg = nx.subgraph(H, set(H.nodes) - X - Y)
180218
cover_set = set()
181219

@@ -188,6 +226,15 @@ def get_set_covering_cycles(H, X, Y):
188226

189227

190228
def subG_2_3(G):
229+
"""
230+
Approximates a FVS for G using Bar-Yehuda's algorithm.
231+
232+
Args:
233+
G (nx.Graph): The input graph.
234+
235+
Returns:
236+
set: An approximated FVS for G.
237+
"""
191238
if nx.is_forest(G):
192239
return set()
193240

@@ -199,4 +246,13 @@ def subG_2_3(G):
199246

200247

201248
def approx_decycling_number_bar_yehuda(G):
249+
"""
250+
Approximates the decycling number of the graph using Bar-Yehuda's algorithm.
251+
252+
Args:
253+
G (nx.Graph): The input graph.
254+
255+
Returns:
256+
int: The approximated decycling number.
257+
"""
202258
return len(subG_2_3(G))

0 commit comments

Comments
 (0)