11module  ChordalExtensionGraph
22
3- using  DataStructures
3+ import  CliqueTrees
4+ import  DataStructures
5+ import  SparseArrays
46
57export  AbstractCompletion, ChordalCompletion, ClusterCompletion
68export  LabelledGraph, add_node!, add_edge!, add_clique!, chordal_extension
@@ -9,26 +11,31 @@ abstract type AbstractGreedyAlgorithm end
911
1012abstract type  AbstractCompletion end 
1113
12- struct  ChordalCompletion{A<: AbstractGreedyAlgorithm } <:  AbstractCompletion 
14+ struct  ClusterCompletion <:  AbstractCompletion  end 
15+ 
16+ struct  ChordalCompletion{A<: CliqueTrees.EliminationAlgorithm } < :
17+        AbstractCompletion
1318    algo:: A 
1419end 
15- ChordalCompletion () =  ChordalCompletion (GreedyFillIn ())
1620
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 
1827
1928#  With a `Vector{Vector{Int}}` with unsorted neighbors, computing fill-in
2029#  would be inefficient.
2130#  With a `Vector{Vector{Int}}` with sorted neighbors, adding an edge would be
2231#  inefficient.
2332#  With `Vector{Set{Int}}` both adding edges and computing fill-in is efficient.
24- 
2533struct  Graph
2634    neighbors:: Vector{Set{Int}} 
27-     disabled:: BitSet 
2835end 
29- Graph () =  Graph (Set{Int}[],  BitSet () )
36+ Graph () =  Graph (Set{Int}[])
3037Base. 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))
3239function  add_node! (G:: Graph )
3340    push! (G. neighbors, Set {Int} ())
3441    return  length (G. neighbors)
@@ -42,15 +49,14 @@ function add_edge!(G::Graph, i::Int, j::Int)
4249    return  (i, j)
4350end 
4451function  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
4756            add_edge! (G, nodes[i], nodes[j])
4857        end 
4958    end 
5059end 
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)
5460
5561""" 
5662    neighbors(G::Graph, node::Int} 
@@ -61,94 +67,24 @@ function neighbors(G::Graph, node::Int)
6167    return  G. neighbors[node]
6268end 
6369
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- 
9970""" 
10071    is_clique(G::Graph{T}, x::Vector{T}) 
10172
10273Return a `Bool` indication whether `x` is a clique in `G`. 
10374""" 
10475function  is_clique (G:: Graph , nodes:: Vector{Int} )
105-     return  iszero (num_missing_edges_subgraph (G, nodes))
106- end 
76+     n =  length (nodes)
10777
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 
13283        end 
13384    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 
15087end 
151- is_enabled (G:: FillInCache , node:: Int ) =  is_enabled (G. graph, node)
15288
15389""" 
15490    struct LabelledGraph{T} 
@@ -244,54 +180,35 @@ function add_clique!(G::LabelledGraph{T}, x::Vector{T}) where {T}
244180    return  add_clique! (G. graph, add_node! .(G, x))
245181end 
246182
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) 
291185
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)
293210    end 
294-     return  candidate_cliques 
211+     return  H, cliques 
295212end 
296213
297214""" 
@@ -307,63 +224,34 @@ Information and Computation 208, no. 3 (2010): 259-275.
307224Utrecht University, Utrecht, The Netherlands www.cs.uu.nl 
308225""" 
309226function  completion (G:: Graph , comp:: ChordalCompletion )
227+     #  construct a copy H of the graph G
310228    H =  copy (G)
311229
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))
324232
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)))
330239    end 
331240
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))
335243
336-     return  H, maximal_cliques 
337- end 
244+     #  construct tree decomposition of H 
245+     label, tree  =  CliqueTrees . cliquetree (matrix; alg  =  comp . algo) 
338246
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) )
341249
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]
365252        add_clique! (H, clique)
366253    end 
254+ 
367255    return  H, cliques
368256end 
369257
0 commit comments