Skip to content

Commit f08fb9a

Browse files
authored
Merge pull request #6 from SebVde/exact_mif_v3
Exact mif v3
2 parents 86b81a4 + 80af7de commit f08fb9a

1 file changed

Lines changed: 307 additions & 0 deletions

File tree

exact_mif_v3.py

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
import itertools
2+
import networkx as nx
3+
4+
5+
def get_generalized_neighbors(G, F, active_v, v):
6+
gen_nb = set(nx.neighbors(G, v)) - F
7+
ens = set(nx.neighbors(G, v)) & (F - {active_v})
8+
for s in ens:
9+
gen_nb.update(set(nx.neighbors(G, s)) - F)
10+
11+
return gen_nb
12+
13+
14+
def get_non_trivial_components(G):
15+
components = []
16+
for c in nx.connected_components(G):
17+
if len(c) > 1:
18+
components.append(c)
19+
20+
return components
21+
22+
23+
def find_short_pair(G, F, active_v):
24+
for u, v in itertools.combinations(set(G.nodes) - F, 2):
25+
nb_u = set(nx.neighbors(G, u))
26+
nb_v = set(nx.neighbors(G, v))
27+
# If parallel edges between u and v (so u,v is a short-cycle) or they have a common neighbor in F (also short-cycle)
28+
if (G.number_of_edges(u, v) > 1) or (
29+
G.number_of_edges(u, v) == 1 and any(w in F for w in nb_u & nb_v)
30+
):
31+
if not ((active_v is not None) and (active_v in nb_u or active_v in nb_v)):
32+
return u, v
33+
34+
return None, None
35+
36+
37+
def is_trigger_vertex(G, F, active_v, v):
38+
nb_active = set(nx.neighbors(G, active_v))
39+
for u in nb_active - F:
40+
gen_nb = get_generalized_neighbors(G, F, active_v, u)
41+
if len(gen_nb - nb_active) >= 3 and v in gen_nb:
42+
nb_v = set(nx.neighbors(G, v))
43+
nb_u = set(nx.neighbors(G, u))
44+
s_set = F - nb_v
45+
v_prime_set = F & nb_v
46+
for s in s_set:
47+
if nb_u == {active_v, v, s}:
48+
return True
49+
50+
for v_prime in v_prime_set:
51+
d_v_prime = G.degree(v_prime)
52+
if d_v_prime == 2 and (
53+
nb_u == {active_v, v_prime, s}
54+
or nb_u == {active_v, v, v_prime, s}
55+
):
56+
return True
57+
58+
return False
59+
60+
61+
def find_optimal_v(G, F, active_v):
62+
nb_active = set(nx.neighbors(G, active_v))
63+
G_not_F = set(G.nodes) - F
64+
possible = set()
65+
for v in G_not_F:
66+
gen_nb = get_generalized_neighbors(G, F, active_v, v)
67+
if len(gen_nb) < 3:
68+
continue
69+
70+
if is_trigger_vertex(G, F, active_v, v):
71+
return v
72+
73+
if len(gen_nb) == 3:
74+
possible.add(v)
75+
76+
if len(possible) > 0:
77+
return max(
78+
possible,
79+
key=lambda x: len(get_generalized_neighbors(G, F, active_v, x) - nb_active),
80+
)
81+
82+
else:
83+
return max(
84+
G_not_F,
85+
key=lambda x: (
86+
len(set(nx.neighbors(G, x)) & (F - {active_v})),
87+
len(get_generalized_neighbors(G, F, active_v, x) & nb_active),
88+
len(get_generalized_neighbors(G, F, active_v, x)),
89+
),
90+
)
91+
92+
93+
def main_procedure(G, F, active_v):
94+
a, b = find_short_pair(G, F, active_v)
95+
if a is not None and b is not None:
96+
return max(
97+
get_mif_len(nx.subgraph(G, set(G.nodes) - {a}), F | {b}, active_v),
98+
get_mif_len(nx.subgraph(G, set(G.nodes) - {b}), F, active_v),
99+
)
100+
101+
cut_v = set(nx.articulation_points(G))
102+
if len(cut_v) > 0:
103+
v = next(iter(cut_v))
104+
components = set(nx.connected_components(nx.subgraph(G, set(G.nodes) - {v})))
105+
H = min(components, key=lambda x: len(x))
106+
107+
F1 = F & (H | {v})
108+
G1 = nx.subgraph(G, H | {v})
109+
F2 = F - H
110+
G2 = nx.subgraph(G, set(G.nodes) - H)
111+
F1_star = F1 | {v}
112+
113+
if v in F:
114+
return get_mif_len(
115+
G1, F1, active_v if active_v in F1 else None
116+
) + get_mif_len(G2, F2, active_v if active_v in F2 else None)
117+
118+
sg_H = nx.subgraph(G, H)
119+
S1 = get_mif_len(sg_H, F & H, active_v if active_v in F & H else None)
120+
S1_star = get_mif_len(G1, F1_star, active_v if active_v in F1_star else None)
121+
122+
if S1_star > S1:
123+
return (
124+
S1_star - 1 + get_mif_len(G2, F2, active_v if active_v in F2 else None)
125+
)
126+
127+
else:
128+
return get_mif_len(
129+
sg_H, F1, active_v if active_v in F1 else None
130+
) + get_mif_len(
131+
nx.subgraph(G, set(G.nodes) - (H | {v})),
132+
F - (H | {v}),
133+
active_v if active_v in F - (H | {v}) else None,
134+
)
135+
136+
if len(F) == 0:
137+
degrees = dict(nx.degree(G))
138+
v = max(degrees, key=degrees.get)
139+
new_G = nx.subgraph(G, G.nodes - {v})
140+
return max(get_mif_len(G, F | {v}, v), get_mif_len(new_G, F, active_v))
141+
142+
if active_v is None:
143+
active_v = next(iter(F))
144+
145+
nb_active = set(nx.neighbors(G, active_v))
146+
for v in nb_active:
147+
gen_nb = get_generalized_neighbors(G, F, active_v, v)
148+
d_v = G.degree(v)
149+
150+
if 5 <= d_v - 1 <= len(gen_nb):
151+
return max(
152+
get_mif_len(G, F | {v}, active_v),
153+
get_mif_len(nx.subgraph(G, set(G.nodes) - {v}), F, active_v),
154+
)
155+
156+
for v in nb_active:
157+
gen_nb = get_generalized_neighbors(G, F, active_v, v)
158+
if len(gen_nb) == 2:
159+
if not nx.is_forest(nx.subgraph(G, F | gen_nb)):
160+
return get_mif_len(G, F | {v}, active_v)
161+
else:
162+
return max(
163+
get_mif_len(G, F | {v}, active_v),
164+
get_mif_len(
165+
nx.subgraph(G, set(G.nodes) - {v}),
166+
F | gen_nb,
167+
active_v,
168+
),
169+
)
170+
171+
optimal_v = find_optimal_v(G, F, active_v)
172+
gen_nb = get_generalized_neighbors(G, F, active_v, optimal_v)
173+
if len(gen_nb) == 3 and not is_trigger_vertex(G, F, active_v, optimal_v):
174+
v1, v2, v3 = None, None, None
175+
# v3 (if possible) not in N(active_v) AND should maximize degree (even if it is in N(active_v) in the end)
176+
not_in_nb = [x for x in gen_nb if x not in nb_active]
177+
if len(not_in_nb) > 0:
178+
v3 = max(not_in_nb, key=lambda x: G.degree(x))
179+
else:
180+
v3 = max(gen_nb, key=lambda x: G.degree(x))
181+
182+
v1, v2 = tuple(gen_nb - {v3})
183+
if not nx.is_forest(nx.subgraph(G, F | {v1, v2})):
184+
return max(
185+
get_mif_len(G, F | {optimal_v}, active_v),
186+
get_mif_len(
187+
nx.subgraph(G, set(G.nodes) - {optimal_v}),
188+
F | {v3},
189+
active_v,
190+
),
191+
)
192+
else:
193+
return max(
194+
get_mif_len(G, F | {optimal_v}, active_v),
195+
get_mif_len(
196+
nx.subgraph(G, set(G.nodes) - {optimal_v, v3}),
197+
F | {v1, v2},
198+
active_v,
199+
),
200+
get_mif_len(
201+
nx.subgraph(G, set(G.nodes) - {optimal_v}),
202+
F | {v3},
203+
active_v,
204+
),
205+
)
206+
207+
else:
208+
return max(
209+
get_mif_len(G, F | {optimal_v}, active_v),
210+
get_mif_len(nx.subgraph(G, set(G.nodes) - {optimal_v}), F, active_v),
211+
)
212+
213+
214+
def get_mif_len(G, F, active_v):
215+
if len(F) > 1 and not nx.is_forest(nx.subgraph(G, F)):
216+
print("Can't reduce cause F is not acyclic")
217+
return Exception
218+
219+
new_G = nx.MultiGraph(G)
220+
# Security measure but shouldn't be necessary, the code shouldn't provoque this case
221+
new_F = set(F) - set([n for n in F if n not in set(G.nodes)])
222+
S = set()
223+
224+
while True:
225+
# Step 1
226+
sg_F = nx.subgraph(new_G, new_F)
227+
non_trivial_components = get_non_trivial_components(sg_F)
228+
if len(non_trivial_components) > 0:
229+
T = non_trivial_components[0]
230+
v = next(iter(T))
231+
if (active_v is not None) and (active_v in T):
232+
v = active_v
233+
234+
for n in T - {v}:
235+
new_G = nx.contracted_nodes(new_G, v, n, self_loops=False)
236+
237+
S = S | (set(T) - {v})
238+
new_F = new_F - (set(T) - {v})
239+
continue
240+
241+
# Step 2
242+
v = None
243+
# If we find a node v not in new_F that has 2 parallel edges to a node u in new_F, we remove it from new_G
244+
for n in set(new_G.nodes) - new_F:
245+
nb_in_F = set(new_G.neighbors(n)) & new_F
246+
for u in nb_in_F:
247+
if new_G.number_of_edges(u, n) > 1:
248+
v = n
249+
break
250+
if v is not None:
251+
break
252+
253+
if v is not None:
254+
new_G.remove_node(v)
255+
new_F.discard(v)
256+
if active_v == v:
257+
active_v = None
258+
continue
259+
260+
# Step 3
261+
for n, deg in new_G.degree():
262+
if deg == 1:
263+
v = n
264+
break
265+
266+
if v is not None:
267+
S.add(v)
268+
new_G.remove_node(v)
269+
new_F.discard(v)
270+
if active_v == v:
271+
active_v = None
272+
continue
273+
274+
# Step 4
275+
for n, deg in new_G.degree():
276+
if n not in new_F and (
277+
deg == 2
278+
or (
279+
active_v is not None
280+
and len(get_generalized_neighbors(new_G, new_F, active_v, n)) <= 1
281+
)
282+
):
283+
v = n
284+
break
285+
286+
if v is not None:
287+
new_F.add(v)
288+
continue
289+
290+
else:
291+
break
292+
293+
if len(new_G.nodes) == 0:
294+
return len(S)
295+
296+
if set(new_G.nodes) == new_F:
297+
return len(S) + len(new_F)
298+
299+
else:
300+
return len(S) + main_procedure(new_G, new_F, active_v)
301+
302+
303+
def get_decycling_number_mif_v3(G):
304+
if nx.is_forest(G):
305+
return 0
306+
307+
return len(G.nodes) - get_mif_len(G, set(), None)

0 commit comments

Comments
 (0)