1
1
module ChordalExtensionGraph
2
2
3
- using DataStructures
3
+ import CliqueTrees
4
+ import DataStructures
5
+ import SparseArrays
4
6
5
7
export AbstractCompletion, ChordalCompletion, ClusterCompletion
6
8
export LabelledGraph, add_node!, add_edge!, add_clique!, chordal_extension
@@ -9,26 +11,31 @@ abstract type AbstractGreedyAlgorithm end
9
11
10
12
abstract type AbstractCompletion end
11
13
12
- struct ChordalCompletion{A<: AbstractGreedyAlgorithm } <: AbstractCompletion
14
+ struct ClusterCompletion <: AbstractCompletion end
15
+
16
+ struct ChordalCompletion{A<: CliqueTrees.EliminationAlgorithm } < :
17
+ AbstractCompletion
13
18
algo:: A
14
19
end
15
- ChordalCompletion () = ChordalCompletion (GreedyFillIn ())
16
20
17
- struct ClusterCompletion <: AbstractCompletion end
21
+ # the default algorithm is the greedy minimum fill heuristic
22
+ const GreedyFillIn = CliqueTrees. MF
23
+
24
+ function ChordalCompletion ()
25
+ return ChordalCompletion (GreedyFillIn ())
26
+ end
18
27
19
28
# With a `Vector{Vector{Int}}` with unsorted neighbors, computing fill-in
20
29
# would be inefficient.
21
30
# With a `Vector{Vector{Int}}` with sorted neighbors, adding an edge would be
22
31
# inefficient.
23
32
# With `Vector{Set{Int}}` both adding edges and computing fill-in is efficient.
24
-
25
33
struct Graph
26
34
neighbors:: Vector{Set{Int}}
27
- disabled:: BitSet
28
35
end
29
- Graph () = Graph (Set{Int}[], BitSet () )
36
+ Graph () = Graph (Set{Int}[])
30
37
Base. broadcastable (g:: Graph ) = Ref (g)
31
- Base. copy (G:: Graph ) = Graph (deepcopy (G. neighbors), copy (G . disabled) )
38
+ Base. copy (G:: Graph ) = Graph (deepcopy (G. neighbors))
32
39
function add_node! (G:: Graph )
33
40
push! (G. neighbors, Set {Int} ())
34
41
return length (G. neighbors)
@@ -42,15 +49,14 @@ function add_edge!(G::Graph, i::Int, j::Int)
42
49
return (i, j)
43
50
end
44
51
function add_clique! (G:: Graph , nodes:: Vector{Int} )
45
- for i in 1 : (length (nodes)- 1 )
46
- for j in (i+ 1 ): length (nodes)
52
+ n = length (nodes)
53
+
54
+ for i in 1 : (n- 1 )
55
+ for j in (i+ 1 ): n
47
56
add_edge! (G, nodes[i], nodes[j])
48
57
end
49
58
end
50
59
end
51
- disable_node! (G:: Graph , node:: Int ) = push! (G. disabled, node)
52
- is_enabled (G:: Graph , node:: Int ) = ! (node in G. disabled)
53
- enable_all_nodes! (G:: Graph ) = empty! (G. disabled)
54
60
55
61
"""
56
62
neighbors(G::Graph, node::Int}
@@ -61,94 +67,24 @@ function neighbors(G::Graph, node::Int)
61
67
return G. neighbors[node]
62
68
end
63
69
64
- function _num_edges_subgraph (
65
- G:: Graph ,
66
- nodes:: Union{Vector{Int},Set{Int}} ,
67
- node:: Int ,
68
- )
69
- neighs = neighbors (G, node)
70
- return count (nodes) do node
71
- return is_enabled (G, node) && node in neighs
72
- end
73
- end
74
- function num_edges_subgraph (G:: Graph , nodes:: Union{Vector{Int},Set{Int}} )
75
- return mapreduce (+ , nodes; init = 0 ) do node
76
- return is_enabled (G, node) ? _num_edges_subgraph (G, nodes, node) : 0
77
- end
78
- end
79
-
80
- function num_missing_edges_subgraph (
81
- G:: Graph ,
82
- nodes:: Union{Vector{Int},Set{Int}} ,
83
- )
84
- n = count (node -> is_enabled (G, node), nodes)
85
- # A clique is a completely connected graph. As such it has n*(n-1)/2 undirected
86
- # or equivalently n*(n-1) directed edges.
87
- return div (n * (n - 1 ) - num_edges_subgraph (G, nodes), 2 )
88
- end
89
-
90
- """
91
- fill_in(G::Graph{T}, i::T}
92
-
93
- Return number of edges that need to be added to make the neighbors of `i` a clique.
94
- """
95
- function fill_in (G:: Graph , node:: Int )
96
- return num_missing_edges_subgraph (G, neighbors (G, node))
97
- end
98
-
99
70
"""
100
71
is_clique(G::Graph{T}, x::Vector{T})
101
72
102
73
Return a `Bool` indication whether `x` is a clique in `G`.
103
74
"""
104
75
function is_clique (G:: Graph , nodes:: Vector{Int} )
105
- return iszero (num_missing_edges_subgraph (G, nodes))
106
- end
76
+ n = length (nodes)
107
77
108
- struct FillInCache
109
- graph:: Graph
110
- fill_in:: Vector{Int}
111
- end
112
- function FillInCache (graph:: Graph )
113
- return FillInCache (graph, [fill_in (graph, i) for i in 1 : num_nodes (graph)])
114
- end
115
- Base. copy (G:: FillInCache ) = FillInCache (copy (G. graph), copy (G. fill_in))
116
-
117
- num_nodes (G:: FillInCache ) = num_nodes (G. graph)
118
- neighbors (G:: FillInCache , node:: Int ) = neighbors (G. graph, node)
119
- function add_edge! (G:: FillInCache , i:: Int , j:: Int )
120
- ni = neighbors (G, i)
121
- nj = neighbors (G, j)
122
- if i in nj
123
- @assert j in ni
124
- return
125
- end
126
- @assert ! (j in ni)
127
- for node in ni
128
- @assert node != i
129
- @assert node != j
130
- if node in nj
131
- G. fill_in[node] -= 1
78
+ for i in 1 : (n- 1 )
79
+ for j in (i+ 1 ): n
80
+ if nodes[j] ∉ neighbors (G, nodes[i])
81
+ return false
82
+ end
132
83
end
133
84
end
134
- G. fill_in[i] +=
135
- count (k -> is_enabled (G. graph, k), ni) -
136
- _num_edges_subgraph (G. graph, ni, j)
137
- G. fill_in[j] +=
138
- count (k -> is_enabled (G. graph, k), nj) -
139
- _num_edges_subgraph (G. graph, nj, i)
140
- return add_edge! (G. graph, i, j)
141
- end
142
- fill_in (G:: FillInCache , node:: Int ) = G. fill_in[node]
143
- function disable_node! (G:: FillInCache , node:: Int )
144
- for neighbor in neighbors (G, node)
145
- nodes = neighbors (G, neighbor)
146
- G. fill_in[neighbor] -=
147
- (length (nodes) - 1 ) - _num_edges_subgraph (G. graph, nodes, node)
148
- end
149
- return disable_node! (G. graph, node)
85
+
86
+ return true
150
87
end
151
- is_enabled (G:: FillInCache , node:: Int ) = is_enabled (G. graph, node)
152
88
153
89
"""
154
90
struct LabelledGraph{T}
@@ -244,54 +180,35 @@ function add_clique!(G::LabelledGraph{T}, x::Vector{T}) where {T}
244
180
return add_clique! (G. graph, add_node! .(G, x))
245
181
end
246
182
247
- struct GreedyFillIn <: AbstractGreedyAlgorithm end
248
- cache (G:: Graph , :: GreedyFillIn ) = FillInCache (G)
249
- heuristic_value (G:: FillInCache , node:: Int , :: GreedyFillIn ) = fill_in (G, node)
250
-
251
- function _greedy_triangulation! (G, algo:: AbstractGreedyAlgorithm )
252
- candidate_cliques = Vector{Int}[]
253
- for i in 1 : num_nodes (G)
254
- node = argmin (map (1 : num_nodes (G)) do node
255
- if is_enabled (G, node)
256
- heuristic_value (G, node, algo)
257
- else
258
- typemax (Int)
259
- end
260
- end )
261
- #=
262
- # look at all its neighbors. In G we want the neighbors to form a clique.
263
- neighbor_nodes = collect(neighbors(G, node))
264
-
265
- # add neighbors and node as a potentially maximal clique
266
- candidate_clique = [neighbor for neighbor in neighbor_nodes if is_enabled(G, neighbor)]
267
- push!(candidate_clique, node)
268
- push!(candidate_cliques, candidate_clique)
269
-
270
- # add edges to G to make the new candidate_clique at least potentially a clique
271
- for i in eachindex(neighbor_nodes)
272
- if is_enabled(G, i)
273
- for j in (i + 1):length(neighbor_nodes)
274
- if is_enabled(G, j)
275
- add_edge!(G, neighbor_nodes[i], neighbor_nodes[j])
276
- end
277
- end
278
- end
279
- end
280
- =#
281
- neighbor_nodes = [
282
- neighbor for
283
- neighbor in neighbors (G, node) if is_enabled (G, neighbor)
284
- ]
285
- push! (candidate_cliques, [node, neighbor_nodes... ])
286
- for i in eachindex (neighbor_nodes)
287
- for j in (i+ 1 ): length (neighbor_nodes)
288
- add_edge! (G, neighbor_nodes[i], neighbor_nodes[j])
289
- end
290
- end
183
+ """
184
+ completion(G::Graph, comp::ClusterCompletion)
291
185
292
- disable_node! (G, node)
186
+ Return a cluster completion of `G` and the corresponding maximal cliques.
187
+ """
188
+ function completion (G:: Graph , :: ClusterCompletion )
189
+ H = copy (G)
190
+ union_find = DataStructures. IntDisjointSets (num_nodes (G))
191
+ for from in 1 : num_nodes (G)
192
+ for to in neighbors (G, from)
193
+ DataStructures. union! (union_find, from, to)
194
+ end
195
+ end
196
+ cliques = [Int[] for i in 1 : DataStructures. num_groups (union_find)]
197
+ ids = zeros (Int, num_nodes (G))
198
+ k = 0
199
+ for node in 1 : num_nodes (G)
200
+ root = DataStructures. find_root! (union_find, node)
201
+ if iszero (ids[root])
202
+ k += 1
203
+ ids[root] = k
204
+ end
205
+ push! (cliques[ids[root]], node)
206
+ end
207
+ @assert k == length (cliques)
208
+ for clique in cliques
209
+ add_clique! (H, clique)
293
210
end
294
- return candidate_cliques
211
+ return H, cliques
295
212
end
296
213
297
214
"""
@@ -307,63 +224,34 @@ Information and Computation 208, no. 3 (2010): 259-275.
307
224
Utrecht University, Utrecht, The Netherlands www.cs.uu.nl
308
225
"""
309
226
function completion (G:: Graph , comp:: ChordalCompletion )
227
+ # construct a copy H of the graph G
310
228
H = copy (G)
311
229
312
- candidate_cliques = _greedy_triangulation! (cache (H, comp. algo), comp. algo)
313
- enable_all_nodes! (H)
314
-
315
- sort! .(candidate_cliques)
316
- unique! (candidate_cliques)
317
-
318
- # check whether candidate cliques are actually cliques
319
- candidate_cliques = candidate_cliques[[
320
- is_clique (H, clique) for clique in candidate_cliques
321
- ]]
322
- sort! (candidate_cliques, by = x -> length (x))
323
- reverse! (candidate_cliques) # TODO use `rev=true` in `sort!`.
230
+ # construct adjacency matrix of H
231
+ matrix = SparseArrays. spzeros (Bool, num_nodes (H), num_nodes (H))
324
232
325
- maximal_cliques = [first (candidate_cliques)]
326
- for clique in Iterators. drop (candidate_cliques, 1 )
327
- if all (other_clique -> ! (clique ⊆ other_clique), maximal_cliques)
328
- push! (maximal_cliques, clique)
329
- end
233
+ for j in axes (matrix, 2 )
234
+ list = neighbors (H, j)
235
+ SparseArrays. getcolptr (matrix)[j+ 1 ] =
236
+ SparseArrays. getcolptr (matrix)[j] + length (list)
237
+ append! (SparseArrays. rowvals (matrix), list)
238
+ append! (SparseArrays. nonzeros (matrix), zeros (Bool, length (list)))
330
239
end
331
240
332
- if length (Set (Iterators. flatten (maximal_cliques))) != num_nodes (H)
333
- error (" Maximal cliques do not cover all nodes." )
334
- end
241
+ # sort row indices of adjacency matrix
242
+ matrix = copy (transpose (matrix))
335
243
336
- return H, maximal_cliques
337
- end
244
+ # construct tree decomposition of H
245
+ label, tree = CliqueTrees . cliquetree (matrix; alg = comp . algo)
338
246
339
- """
340
- completion(G::Graph, comp::ChordalCompletion )
247
+ # construct vector of cliques and append fill edges to H
248
+ cliques = Vector {Vector{Int}} (undef, length (tree) )
341
249
342
- Return a cluster completion of `G` and the corresponding maximal cliques.
343
- """
344
- function completion (G:: Graph , :: ClusterCompletion )
345
- H = copy (G)
346
- union_find = IntDisjointSets (num_nodes (G))
347
- for from in 1 : num_nodes (G)
348
- for to in neighbors (G, from)
349
- union! (union_find, from, to)
350
- end
351
- end
352
- cliques = [Int[] for i in 1 : num_groups (union_find)]
353
- ids = zeros (Int, num_nodes (G))
354
- k = 0
355
- for node in 1 : num_nodes (G)
356
- root = find_root! (union_find, node)
357
- if iszero (ids[root])
358
- k += 1
359
- ids[root] = k
360
- end
361
- push! (cliques[ids[root]], node)
362
- end
363
- @assert k == length (cliques)
364
- for clique in cliques
250
+ for (i, v) in enumerate (tree)
251
+ clique = cliques[i] = label[v]
365
252
add_clique! (H, clique)
366
253
end
254
+
367
255
return H, cliques
368
256
end
369
257
0 commit comments