Skip to content

Commit ea50e12

Browse files
authored
Merge pull request #5 from SebVde/mif_version2
Mif version2
2 parents d0b8363 + 029f2da commit ea50e12

1 file changed

Lines changed: 281 additions & 0 deletions

File tree

exact_mif_v2.py

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
import networkx as nx
2+
import itertools
3+
4+
5+
def get_non_trivial_components(G):
6+
components = []
7+
for c in nx.connected_components(G):
8+
if len(c) > 1:
9+
components.append(c)
10+
11+
return components
12+
13+
14+
def construct_H(G, F, nb):
15+
H = nx.subgraph(G, nb).copy()
16+
for u, v in itertools.combinations(nb, 2):
17+
if not set(nx.common_neighbors(G, u, v)).isdisjoint(F):
18+
H.add_edge(u, v)
19+
20+
return H
21+
22+
23+
def is_foldable(G, v):
24+
nb = set(nx.neighbors(G, v))
25+
for x, y, z in itertools.combinations(nb, 3):
26+
if not (G.has_edge(x, y) or G.has_edge(y, z) or G.has_edge(z, x)):
27+
# If no edges between x-y-z, there is an anti-triangle so not foldable
28+
return False
29+
30+
return True
31+
32+
33+
def get_anti_edges(G, v):
34+
nb = set(nx.neighbors(G, v))
35+
anti_edges = set()
36+
for x, y in itertools.combinations(nb, 2):
37+
if not G.has_edge(x, y):
38+
anti_edges.add((x, y))
39+
40+
return anti_edges
41+
42+
43+
def fold_graph(G, v, anti_edges):
44+
nb_v = set(nx.neighbors(G, v))
45+
folded_G = G.copy()
46+
added_nodes = set()
47+
48+
for i, j in anti_edges:
49+
n = (i, j)
50+
folded_G.add_node(n)
51+
added_nodes.add(n)
52+
for u in set(nx.neighbors(G, i)) | set(nx.neighbors(G, j)):
53+
if u != v and u not in nb_v:
54+
folded_G.add_edge(n, u)
55+
56+
for a, b in itertools.combinations(added_nodes, 2):
57+
folded_G.add_edge(a, b)
58+
59+
return nx.subgraph(folded_G, set(folded_G.nodes) - nb_v - {v})
60+
61+
62+
def get_2_hop_neighbors(G, v):
63+
first_neighbors = set(G.neighbors(v))
64+
second_neighbors = set()
65+
for u in first_neighbors:
66+
second_neighbors.update(G.neighbors(u))
67+
68+
second_neighbors -= first_neighbors
69+
second_neighbors.discard(v)
70+
return second_neighbors
71+
72+
73+
def is_complete(G):
74+
return len(G.edges) == len(G.nodes) * (len(G.nodes) - 1) / 2
75+
76+
77+
def get_mirrors(G, v):
78+
mirrors = set()
79+
80+
for u in get_2_hop_neighbors(G, v):
81+
nb_u = set(nx.neighbors(G, u))
82+
nb_v = set(nx.neighbors(G, v))
83+
if (len(nb_v - nb_u) == 0) or is_complete(nx.subgraph(G, nb_v - nb_u)):
84+
mirrors.add(u)
85+
86+
return mirrors
87+
88+
89+
def get_max_indep_set(G):
90+
if len(G.nodes) == 0:
91+
return 0
92+
93+
if nx.number_connected_components(G) > 1:
94+
res = 0
95+
for c in nx.connected_components(G):
96+
res += get_max_indep_set(G.subgraph(c))
97+
98+
return res
99+
100+
for u, v in itertools.combinations(G.nodes, 2):
101+
nb_u = set(nx.neighbors(G, u))
102+
nb_v = set(nx.neighbors(G, v))
103+
if (nb_v | {v}).issubset(nb_u | {u}):
104+
return get_max_indep_set(nx.subgraph(G, set(G.nodes) - {u}))
105+
106+
node_degrees = list(nx.degree(G))
107+
max_deg_4 = [(n, d) for n, d in node_degrees if d <= 4]
108+
sorted_deg = sorted(max_deg_4, key=lambda x: x[1])
109+
110+
for comb in sorted_deg:
111+
v = comb[0]
112+
deg = comb[1]
113+
anti_edges = get_anti_edges(G, v)
114+
if (deg < 4 and is_foldable(G, v)) or (deg == 4 and len(anti_edges) < 4):
115+
return 1 + get_max_indep_set(fold_graph(G, v, anti_edges))
116+
117+
v = max(node_degrees, key=lambda x: x[1])[0]
118+
return max(
119+
get_max_indep_set(nx.subgraph(G, G.nodes - {v} - get_mirrors(G, v))),
120+
1 + get_max_indep_set(nx.subgraph(G, G.nodes - {v} - set(nx.neighbors(G, v)))),
121+
)
122+
123+
124+
def get_generalized_neighbors(G, F, active_v, v):
125+
K = {u for u in (F - {active_v}) if G.has_edge(u, v)}
126+
new_G = G.copy()
127+
128+
for n in K:
129+
new_G = nx.contracted_nodes(new_G, v, n, self_loops=False)
130+
131+
gen_nb = set(new_G.neighbors(v)) - {active_v}
132+
return gen_nb
133+
134+
135+
def get_mif_len(G, F, active_v):
136+
if nx.number_connected_components(G) > 1:
137+
res = 0
138+
for c in nx.connected_components(G):
139+
if nx.is_forest(nx.subgraph(G, c)):
140+
res += len(c)
141+
else:
142+
res += get_mif_len(
143+
nx.subgraph(G, c), F & set(c), active_v if active_v in c else None
144+
)
145+
146+
return res
147+
148+
sg_F = nx.subgraph(G, F)
149+
new_G = G.copy()
150+
new_F = set(F)
151+
# Verify is F is acyclic?
152+
if (
153+
len(sg_F.edges) != 0
154+
): # If F is not independent (if not every component of G[F] is an isolated vertex)
155+
for T in get_non_trivial_components(sg_F):
156+
# Get all neighbors of T in G and need to remove those with more than 1 connection to T
157+
nb_T = set()
158+
for v in T:
159+
nb_T.update(set(G.neighbors(v)))
160+
nb_T -= T
161+
vertices_to_remove = set()
162+
for v in nb_T:
163+
connections = sum(1 for u in T if G.has_edge(u, v))
164+
if connections > 1:
165+
vertices_to_remove.add(v)
166+
167+
v_T = next(iter(T))
168+
169+
for n in T - {v_T}:
170+
new_G = nx.contracted_nodes(new_G, v_T, n, self_loops=False)
171+
172+
new_G = nx.subgraph(new_G, new_G.nodes - vertices_to_remove)
173+
174+
if (active_v is not None) and (active_v in T):
175+
active_v = v_T
176+
177+
new_F -= T
178+
new_F.add(v_T)
179+
180+
return get_mif_len(new_G, new_F, active_v) + len(F - new_F)
181+
182+
else:
183+
return main_procedure(G, F, active_v)
184+
185+
186+
def main_procedure(G, F, active_v):
187+
188+
if F == set(G.nodes):
189+
return len(G.nodes)
190+
191+
if len(F) == 0:
192+
max_degree = int(max(dict(nx.degree(G)).values()))
193+
194+
if max_degree < 2:
195+
return len(G.nodes)
196+
else:
197+
t = next(n for n, d in G.degree() if d >= 2)
198+
new_G = nx.subgraph(G, G.nodes - {t})
199+
return max(
200+
get_mif_len(G, F | {t}, active_v), get_mif_len(new_G, F, active_v)
201+
)
202+
203+
if active_v is None:
204+
active_v = next(iter(F))
205+
206+
nb = set(nx.neighbors(G, active_v))
207+
if set(G.nodes) - F == nb:
208+
return len(F) + get_max_indep_set(construct_H(G, F - {active_v}, nb))
209+
210+
for v in nb:
211+
gen_nb = get_generalized_neighbors(G, F, active_v, v)
212+
if len(gen_nb) < 2:
213+
return get_mif_len(G, F | {v}, active_v)
214+
215+
for v in nb:
216+
gen_nb = get_generalized_neighbors(G, F, active_v, v)
217+
if len(gen_nb) > 3:
218+
return max(
219+
get_mif_len(G, F | {v}, active_v),
220+
get_mif_len(nx.subgraph(G, G.nodes - {v}), F, active_v),
221+
)
222+
223+
for v in nb:
224+
gen_nb = get_generalized_neighbors(G, F, active_v, v)
225+
if len(gen_nb) == 2:
226+
return max(
227+
get_mif_len(G, F | {v}, active_v),
228+
get_mif_len(
229+
nx.subgraph(G, set(G.nodes) - {v}),
230+
F | gen_nb,
231+
active_v,
232+
),
233+
)
234+
235+
# If every v in nb has exactly 3 generalized neighbors, find a v that has at least one generalized neighbor outside nb
236+
v = None
237+
good_gen_nb = None
238+
for n in nb:
239+
gen_nb = get_generalized_neighbors(G, F, active_v, n)
240+
if any(u not in nb for u in gen_nb):
241+
v = n
242+
good_gen_nb = gen_nb
243+
break
244+
245+
if v is not None and good_gen_nb is not None:
246+
w1, w2, w3 = None, None, None
247+
for u in good_gen_nb:
248+
if w1 is None and u not in nb:
249+
w1 = u
250+
elif w2 is None:
251+
w2 = u
252+
elif w3 is None:
253+
w3 = u
254+
255+
else:
256+
print("Problem")
257+
258+
return max(
259+
get_mif_len(G, F | {v}, active_v),
260+
get_mif_len(
261+
nx.subgraph(G, set(G.nodes) - {v}),
262+
F | {w1},
263+
active_v,
264+
),
265+
get_mif_len(
266+
nx.subgraph(G, set(G.nodes) - {v, w1}),
267+
F | {w2, w3},
268+
active_v,
269+
),
270+
)
271+
272+
else:
273+
print("Problem")
274+
return Exception("No suitable v found")
275+
276+
277+
def get_decycling_number_mif_v2(G):
278+
if nx.is_forest(G):
279+
return 0
280+
281+
return len(G.nodes) - get_mif_len(G, set(), None)

0 commit comments

Comments
 (0)