aGrUM  0.14.2
binaryJoinTreeConverterDefault.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * Copyright (C) 2005 by Christophe GONZALES and Pierre-Henri WUILLEMIN *
3  * {prenom.nom}_at_lip6.fr *
4  * *
5  * This program is free software; you can redistribute it and/or modify *
6  * it under the terms of the GNU General Public License as published by *
7  * the Free Software Foundation; either version 2 of the License, or *
8  * (at your option) any later version. *
9  * *
10  * This program is distributed in the hope that it will be useful, *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13  * GNU General Public License for more details. *
14  * *
15  * You should have received a copy of the GNU General Public License *
16  * along with this program; if not, write to the *
17  * Free Software Foundation, Inc., *
18  * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19  ***************************************************************************/
26 #include <agrum/agrum.h>
27 
30 
31 namespace gum {
32 
35  // for debugging purposes
36  GUM_CONSTRUCTOR(BinaryJoinTreeConverterDefault);
37  }
38 
41  GUM_DESTRUCTOR(BinaryJoinTreeConverterDefault);
42  }
43 
47  const CliqueGraph& JT, NodeId root, NodeProperty< bool >& mark) const {
48  // we mark the nodes in a depth first search manner. To avoid a recursive
49  // algorithm, use a vector to simulate a stack of nodes to inspect.
50  // stack => depth first search
51  std::vector< NodeId > nodes_to_inspect;
52  nodes_to_inspect.reserve(JT.sizeNodes());
53 
54  // the idea to populate the marks is to use the stack: root is
55  // put onto the stack. Then, while the stack is not empty, remove
56  // the top of the stack and mark it and put into the stack its
57  // adjacent nodes.
58  nodes_to_inspect.push_back(root);
59 
60  while (!nodes_to_inspect.empty()) {
61  // process the top of the stack
62  NodeId current_node = nodes_to_inspect.back();
63  nodes_to_inspect.pop_back();
64 
65  // only process the node if it has not been processed yet (actually,
66  // this should not occur unless the clique graph is not singly connected)
67 
68  if (!mark[current_node]) {
69  mark[current_node] = true;
70 
71  // put the neighbors onto the stack
72  for (const auto neigh : JT.neighbours(current_node))
73  if (!mark[neigh]) nodes_to_inspect.push_back(neigh);
74  }
75  }
76  }
77 
80  const NodeSet& nodes1,
81  const NodeSet& nodes2,
82  const NodeProperty< Size >& domain_sizes) const {
83  float result = 1;
84 
85  for (const auto node : nodes1)
86  result *= domain_sizes[node];
87 
88  for (const auto node : nodes2)
89  if (!nodes1.exists(node)) result *= domain_sizes[node];
90 
91  return result;
92  }
93 
96 
99  CliqueGraph& JT,
100  NodeId clique,
101  NodeId from,
102  const NodeProperty< Size >& domain_sizes) const {
103  // get the neighbors of clique. If there are fewer than 3 neighbors,
104  // there is nothing to do
105  const NodeSet& neighbors = JT.neighbours(clique);
106 
107  if (neighbors.size() <= 2) return;
108 
109  if ((neighbors.size() == 3) && (clique != from)) return;
110 
111  // here we need to transform the neighbors into a binary tree
112  // create a vector with all the ids of the cliques to combine
113  std::vector< NodeId > cliques;
114  cliques.reserve(neighbors.size());
115 
116  for (const auto nei : neighbors)
117  if (nei != from) cliques.push_back(nei);
118 
119  // create a vector indicating wether the elements in cliques contain
120  // relevant information or not (during the execution of the for
121  // loop below, a cell of vector cliques may actually contain only
122  // trash data)
123  std::vector< bool > is_cliques_relevant(cliques.size(), true);
124 
125  // for each pair of cliques (i,j), compute the size of the clique that would
126  // result from the combination of clique i with clique j and store the
127  // result
128  // into a priorityQueue
129  std::pair< NodeId, NodeId > pair;
130 
132 
133  for (NodeId i = 0; i < cliques.size(); ++i) {
134  pair.first = i;
135  const NodeSet& nodes1 = JT.separator(cliques[i], clique);
136 
137  for (NodeId j = i + 1; j < cliques.size(); ++j) {
138  pair.second = j;
139  queue.insert(
140  pair,
141  __combinedSize(nodes1, JT.separator(cliques[j], clique), domain_sizes));
142  }
143  }
144 
145  // now parse the priority queue: the top element (i,j) gives the combination
146  // to perform. When the result R has been computed, substitute i by R,
147  // remove
148  // table j and recompute all the priorities of all the pairs (R,k) still
149  // available.
150  for (NodeId k = 2; k < cliques.size(); ++k) {
151  // get the combination to perform and do it
152  pair = queue.pop();
153  NodeId ti = pair.first;
154  NodeId tj = pair.second;
155 
156  // create a new clique that will become adjacent to ti and tj
157  // and remove the edges between ti, tj and clique
158  const NodeSet& nodes1 = JT.separator(cliques[ti], clique);
159  const NodeSet& nodes2 = JT.separator(cliques[tj], clique);
160  NodeId new_node = JT.addNode(nodes1 + nodes2);
161  JT.addEdge(cliques[ti], new_node);
162  JT.addEdge(cliques[tj], new_node);
163  JT.addEdge(clique, new_node);
164  JT.eraseEdge(Edge(cliques[ti], clique));
165  JT.eraseEdge(Edge(cliques[tj], clique));
166 
167  // substitute cliques[pair.first] by the result
168  cliques[ti] = new_node;
169  is_cliques_relevant[tj] = false; // now tj is no more a neighbor of clique
170 
171  // remove all the pairs involving tj in the priority queue
172 
173  for (NodeId ind = 0; ind < tj; ++ind) {
174  if (is_cliques_relevant[ind]) {
175  pair.first = ind;
176  queue.erase(pair);
177  }
178  }
179 
180  pair.first = tj;
181 
182  for (NodeId ind = tj + 1; ind < cliques.size(); ++ind) {
183  if (is_cliques_relevant[ind]) {
184  pair.second = ind;
185  queue.erase(pair);
186  }
187  }
188 
189  // update the "combined" size of all the pairs involving "new_node"
190  {
191  const NodeSet& nodes1 = JT.separator(cliques[ti], clique);
192  pair.second = ti;
193  float newsize;
194 
195  for (NodeId ind = 0; ind < ti; ++ind) {
196  if (is_cliques_relevant[ind]) {
197  pair.first = ind;
198  newsize = __combinedSize(
199  nodes1, JT.separator(cliques[ind], clique), domain_sizes);
200  queue.setPriority(pair, newsize);
201  }
202  }
203 
204  pair.first = ti;
205 
206  for (NodeId ind = ti + 1; ind < cliques.size(); ++ind) {
207  if (is_cliques_relevant[ind]) {
208  pair.second = ind;
209  newsize = __combinedSize(
210  nodes1, JT.separator(cliques[ind], clique), domain_sizes);
211  queue.setPriority(pair, newsize);
212  }
213  }
214  }
215  }
216  }
217 
220  CliqueGraph& JT,
221  NodeId current_node,
222  NodeId from,
223  const NodeProperty< Size >& domain_sizes,
224  NodeProperty< bool >& mark) const {
225  // first, indicate that the node has been marked (this avoids looping
226  // if JT is not a tree
227  mark[current_node] = true;
228 
229  // parse all the neighbors except nodes already converted and convert them
230  for (const auto neigh : JT.neighbours(current_node)) {
231  if (!mark[neigh]) {
232  __convertConnectedComponent(JT, neigh, current_node, domain_sizes, mark);
233  }
234  }
235 
236  // convert the current node
237  __convertClique(JT, current_node, from, domain_sizes);
238  }
239 
242  const CliqueGraph& JT,
243  const NodeProperty< Size >& domain_sizes,
244  const NodeSet& specified_roots) {
245  // first, we copy the current clique graph. By default, this is what we
246  // will return
247  CliqueGraph binJT = JT;
248 
249  // check that there is no connected component without a root. In such a
250  // case,
251  // assign an arbitrary root to it
252  __roots = specified_roots;
253 
254  NodeProperty< bool > mark = JT.nodesProperty(false, JT.sizeNodes());
255 
256  // for each specified root, populate its connected component
257  for (const auto root : specified_roots) {
258  // check that the root has not already been marked
259  // in this case, this means that more than one root has been specified
260  // for a given connected component
261  if (mark[root])
263  "several roots have been specified for a given "
264  "connected component");
265 
266  __markConnectedComponent(JT, root, mark);
267  }
268 
269  // check that all nodes have been marked. If this is not the case, then
270  // this means that we need to add new roots
271  for (const auto& elt : mark)
272  if (!elt.second) {
273  __roots << elt.first;
274  __markConnectedComponent(JT, elt.first, mark);
275  }
276 
277  // here, we know that each connected component has one and only one root.
278  // Now we can apply a recursive collect algorithm starting from root
279  // that transforms each clique with more than 3 neighbors into a set of
280  // cliques having at most 3 neighbors.
281  NodeProperty< bool > mark2 = JT.nodesProperty(false, JT.sizeNodes());
282 
283  for (const auto root : __roots)
284  __convertConnectedComponent(binJT, root, root, domain_sizes, mark2);
285 
286  // binJT is now a binary join tree, so we can return it
287  return binJT;
288  }
289 
290 } /* namespace gum */
NodeId addNode(const NodeSet &clique)
adds a new clique to the graph
Val pop()
Removes the top element from the priority queue and return it.
CliqueGraph convert(const CliqueGraph &JT, const NodeProperty< Size > &domain_sizes, const NodeSet &roots)
returns a binary join tree corresponding to clique graph JT
const NodeSet & separator(const Edge &edge) const
returns the separator included in a given edge
NodeProperty< VAL > nodesProperty(VAL(*f)(const NodeId &), Size size=0) const
a method to create a HashTable with key:NodeId and value:VAL
void __markConnectedComponent(const CliqueGraph &JT, NodeId root, NodeProperty< bool > &mark) const
a function used to mark the nodes belonging to a given connected component
gum is the global namespace for all aGrUM entities
Definition: agrum.h:25
The class for generic Hash Tables.
Definition: hashTable.h:676
void erase(const Val &val)
Removes a given element from the priority queue (but does not return it).
const NodeSet & neighbours(const NodeId id) const
returns the set of edges adjacent to a given node
void setPriority(const Val &elt, const Priority &new_priority)
Modifies the priority of each instance of a given element.
const NodeSet & roots() const
returns all the roots considered for all the connected components
A priorityQueue is a heap in which each element has a mutable priorityA priority queue is quite simil...
Definition: priorityQueue.h:48
void __convertClique(CliqueGraph &JT, NodeId clique, NodeId from, const NodeProperty< Size > &domain_sizes) const
convert a clique and its adjacent cliques into a binary join tree
virtual void eraseEdge(const Edge &edge)
removes an edge (and its separator) from the clique graph
void __convertConnectedComponent(CliqueGraph &JT, NodeId current_node, NodeId from, const NodeProperty< Size > &domain_sizes, NodeProperty< bool > &mark) const
convert a whole connected component into a binary join tree
virtual void addEdge(const NodeId first, const NodeId second)
inserts a new edge between two cliques
Size insert(const Val &val, const Priority &priority)
Inserts a new (a copy) element in the priority queue.
priority queues (in which an element cannot appear more than once)
Basic graph of cliques.
Definition: cliqueGraph.h:55
Size sizeNodes() const
returns the number of nodes in the NodeGraphPart
The base class for all undirected edges.
NodeSet __roots
the new roots that have been created to compute the last query
An algorithm for converting a join tree into a binary join tree.
Size size() const noexcept
Returns the number of elements in the set.
Definition: set_tpl.h:698
float __combinedSize(const NodeSet &nodes1, const NodeSet &nodes2, const NodeProperty< Size > &domain_sizes) const
returns the domain size of the union of two cliques
Size NodeId
Type for node ids.
Definition: graphElements.h:97
#define GUM_ERROR(type, msg)
Definition: exceptions.h:52