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