aGrUM 2.3.2
a C++ library for (probabilistic) graphical models
binaryJoinTreeConverterDefault.cpp
Go to the documentation of this file.
1/****************************************************************************
2 * This file is part of the aGrUM/pyAgrum library. *
3 * *
4 * Copyright (c) 2005-2025 by *
5 * - Pierre-Henri WUILLEMIN(_at_LIP6) *
6 * - Christophe GONZALES(_at_AMU) *
7 * *
8 * The aGrUM/pyAgrum library is free software; you can redistribute it *
9 * and/or modify it under the terms of either : *
10 * *
11 * - the GNU Lesser General Public License as published by *
12 * the Free Software Foundation, either version 3 of the License, *
13 * or (at your option) any later version, *
14 * - the MIT license (MIT), *
15 * - or both in dual license, as here. *
16 * *
17 * (see https://agrum.gitlab.io/articles/dual-licenses-lgplv3mit.html) *
18 * *
19 * This aGrUM/pyAgrum library is distributed in the hope that it will be *
20 * useful, but WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
21 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES MERCHANTABILITY or FITNESS *
22 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
23 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
25 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
26 * OTHER DEALINGS IN THE SOFTWARE. *
27 * *
28 * See LICENCES for more details. *
29 * *
30 * SPDX-FileCopyrightText: Copyright 2005-2025 *
31 * - Pierre-Henri WUILLEMIN(_at_LIP6) *
32 * - Christophe GONZALES(_at_AMU) *
33 * SPDX-License-Identifier: LGPL-3.0-or-later OR MIT *
34 * *
35 * Contact : info_at_agrum_dot_org *
36 * homepage : http://agrum.gitlab.io *
37 * gitlab : https://gitlab.com/agrumery/agrum *
38 * *
39 ****************************************************************************/
40
41
47
48#include <agrum/agrum.h>
49
52
53namespace gum {
54
59
64
68 NodeId root,
69 NodeProperty< bool >& mark) const {
70 // we mark the nodes in a depth first search manner. To avoid a recursive
71 // algorithm, use a vector to simulate a stack of nodes to inspect.
72 // stack => depth first search
73 std::vector< NodeId > nodes_to_inspect;
74 nodes_to_inspect.reserve(JT.sizeNodes());
75
76 // the idea to populate the marks is to use the stack: root is
77 // put onto the stack. Then, while the stack is not empty, remove
78 // the top of the stack and mark it and put into the stack its
79 // adjacent nodes.
80 nodes_to_inspect.push_back(root);
81
82 while (!nodes_to_inspect.empty()) {
83 // process the top of the stack
84 NodeId current_node = nodes_to_inspect.back();
85 nodes_to_inspect.pop_back();
86
87 // only process the node if it has not been processed yet (actually,
88 // this should not occur unless the clique graph is not singly connected)
89
90 if (!mark[current_node]) {
91 mark[current_node] = true;
92
93 // put the neighbors onto the stack
94 for (const auto neigh: JT.neighbours(current_node))
95 if (!mark[neigh]) nodes_to_inspect.push_back(neigh);
96 }
97 }
98 }
99
102 const NodeSet& nodes1,
103 const NodeSet& nodes2,
104 const NodeProperty< Size >& domain_sizes) const {
105 float result = 1;
106
107 for (const auto node: nodes1)
108 result *= domain_sizes[node];
109
110 for (const auto node: nodes2)
111 if (!nodes1.exists(node)) result *= domain_sizes[node];
112
113 return result;
114 }
115
118
121 CliqueGraph& JT,
122 NodeId clique,
123 NodeId from,
124 const NodeProperty< Size >& domain_sizes) const {
125 // get the neighbors of clique. If there are fewer than 3 neighbors,
126 // there is nothing to do
127 const NodeSet& neighbors = JT.neighbours(clique);
128
129 if (neighbors.size() <= 2) return;
130
131 if ((neighbors.size() == 3) && (clique != from)) return;
132
133 // here we need to transform the neighbors into a binary tree
134 // create a vector with all the ids of the cliques to combine
135 std::vector< NodeId > cliques;
136 cliques.reserve(neighbors.size());
137
138 for (const auto nei: neighbors)
139 if (nei != from) cliques.push_back(nei);
140
141 // create a vector indicating wether the elements in cliques contain
142 // relevant information or not (during the execution of the for
143 // loop below, a cell of vector cliques may actually contain only
144 // trash data)
145 std::vector< bool > is_cliques_relevant(cliques.size(), true);
146
147 // for each pair of cliques (i,j), compute the size of the clique that would
148 // result from the combination of clique i with clique j and store the
149 // result
150 // into a priorityQueue
151 std::pair< NodeId, NodeId > pair;
152
154
155 for (NodeId i = 0; i < cliques.size(); ++i) {
156 pair.first = i;
157 const NodeSet& nodes1 = JT.separator(cliques[i], clique);
158
159 for (NodeId j = i + 1; j < cliques.size(); ++j) {
160 pair.second = j;
161 queue.insert(pair, _combinedSize_(nodes1, JT.separator(cliques[j], clique), domain_sizes));
162 }
163 }
164
165 // now parse the priority queue: the top element (i,j) gives the combination
166 // to perform. When the result R has been computed, substitute i by R,
167 // remove
168 // table j and recompute all the priorities of all the pairs (R,k) still
169 // available.
170 for (NodeId k = 2; k < cliques.size(); ++k) {
171 // get the combination to perform and do it
172 pair = queue.pop();
173 NodeId ti = pair.first;
174 NodeId tj = pair.second;
175
176 // create a new clique that will become adjacent to ti and tj
177 // and remove the edges between ti, tj and clique
178 const NodeSet& nodes1 = JT.separator(cliques[ti], clique);
179 const NodeSet& nodes2 = JT.separator(cliques[tj], clique);
180 NodeId new_node = JT.addNode(nodes1 + nodes2);
181 JT.addEdge(cliques[ti], new_node);
182 JT.addEdge(cliques[tj], new_node);
183 JT.addEdge(clique, new_node);
184 JT.eraseEdge(Edge(cliques[ti], clique));
185 JT.eraseEdge(Edge(cliques[tj], clique));
186
187 // substitute cliques[pair.first] by the result
188 cliques[ti] = new_node;
189 is_cliques_relevant[tj] = false; // now tj is no more a neighbor of clique
190
191 // remove all the pairs involving tj in the priority queue
192
193 for (NodeId ind = 0; ind < tj; ++ind) {
194 if (is_cliques_relevant[ind]) {
195 pair.first = ind;
196 queue.erase(pair);
197 }
198 }
199
200 pair.first = tj;
201
202 for (NodeId ind = tj + 1; ind < cliques.size(); ++ind) {
203 if (is_cliques_relevant[ind]) {
204 pair.second = ind;
205 queue.erase(pair);
206 }
207 }
208
209 // update the "combined" size of all the pairs involving "new_node"
210 {
211 const NodeSet& nodes1 = JT.separator(cliques[ti], clique);
212 pair.second = ti;
213 float newsize;
214
215 for (NodeId ind = 0; ind < ti; ++ind) {
216 if (is_cliques_relevant[ind]) {
217 pair.first = ind;
218 newsize = _combinedSize_(nodes1, JT.separator(cliques[ind], clique), domain_sizes);
219 queue.setPriority(pair, newsize);
220 }
221 }
222
223 pair.first = ti;
224
225 for (NodeId ind = ti + 1; ind < cliques.size(); ++ind) {
226 if (is_cliques_relevant[ind]) {
227 pair.second = ind;
228 newsize = _combinedSize_(nodes1, JT.separator(cliques[ind], clique), domain_sizes);
229 queue.setPriority(pair, newsize);
230 }
231 }
232 }
233 }
234 }
235
238 CliqueGraph& JT,
239 NodeId current_node,
240 NodeId from,
241 const NodeProperty< Size >& domain_sizes,
242 NodeProperty< bool >& mark) const {
243 // first, indicate that the node has been marked (this avoids looping
244 // if JT is not a tree
245 mark[current_node] = true;
246
247 // parse all the neighbors except nodes already converted and convert them
248 for (const auto neigh: JT.neighbours(current_node)) {
249 if (!mark[neigh]) {
250 _convertConnectedComponent_(JT, neigh, current_node, domain_sizes, mark);
251 }
252 }
253
254 // convert the current node
255 _convertClique_(JT, current_node, from, domain_sizes);
256 }
257
260 const NodeProperty< Size >& domain_sizes,
261 const NodeSet& specified_roots) {
262 // first, we copy the current clique graph. By default, this is what we
263 // will return
264 CliqueGraph binJT = JT;
265
266 // check that there is no connected component without a root. In such a
267 // case,
268 // assign an arbitrary root to it
269 _roots_ = specified_roots;
270
272
273 // for each specified root, populate its connected component
274 for (const auto root: specified_roots) {
275 // check that the root has not already been marked
276 // in this case, this means that more than one root has been specified
277 // for a given connected component
278 if (mark[root])
280 "several roots have been specified for a given "
281 "connected component (last : "
282 << root << ")");
283
284 _markConnectedComponent_(JT, root, mark);
285 }
286
287 // check that all nodes have been marked. If this is not the case, then
288 // this means that we need to add new roots
289 for (const auto& elt: mark)
290 if (!elt.second) {
291 _roots_ << elt.first;
292 _markConnectedComponent_(JT, elt.first, mark);
293 }
294
295 // here, we know that each connected component has one and only one root.
296 // Now we can apply a recursive collect algorithm starting from root
297 // that transforms each clique with more than 3 neighbors into a set of
298 // cliques having at most 3 neighbors.
300
301 for (const auto root: _roots_)
302 _convertConnectedComponent_(binJT, root, root, domain_sizes, mark2);
303
304 // binJT is now a binary join tree, so we can return it
305 return binJT;
306 }
307
308} /* namespace gum */
An algorithm for converting a join tree into a binary join tree.
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
NodeSet _roots_
the new roots that have been created to compute the last query
const NodeSet & roots() const
returns all the roots considered for all the connected components
void _markConnectedComponent_(const CliqueGraph &JT, NodeId root, NodeProperty< bool > &mark) const
a function used to mark the nodes belonging to a given connected component
float _combinedSize_(const NodeSet &nodes1, const NodeSet &nodes2, const NodeProperty< Size > &domain_sizes) const
returns the domain size of the union of two cliques
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
CliqueGraph convert(const CliqueGraph &JT, const NodeProperty< Size > &domain_sizes, const NodeSet &roots)
returns a binary join tree corresponding to clique graph JT
Basic graph of cliques.
Definition cliqueGraph.h:77
const NodeSet & separator(const Edge &edge) const
returns the separator included in a given edge
virtual void addEdge(NodeId first, NodeId second)
inserts a new edge between two cliques
NodeId addNode(const NodeSet &clique)
adds a new clique to the graph
virtual void eraseEdge(const Edge &edge)
removes an edge (and its separator) from the clique graph
const NodeSet & neighbours(NodeId id) const
returns the set of node neighbours to a given node
The base class for all undirected edges.
Exception : node does not exist.
Size sizeNodes() const
returns the number of nodes in the NodeGraphPart
NodeProperty< VAL > nodesPropertyFromVal(const VAL &a, Size size=0) const
a method to create a hashMap with key:NodeId and value:VAL
void erase(const Val &val)
Removes a given element from the priority queue (but does not return it).
void setPriority(const Val &elt, const Priority &new_priority)
Modifies the priority of each instance of a given element.
Val pop()
Removes the top element from the priority queue and return it.
Size insert(const Val &val, const Priority &priority)
Inserts a new (a copy) element in the priority queue.
A priorityQueue is a heap in which each element has a mutable priority.
Size size() const noexcept
Returns the number of elements in the set.
Definition set_tpl.h:636
bool exists(const Key &k) const
Indicates whether a given elements belong to the set.
Definition set_tpl.h:533
#define GUM_ERROR(type, msg)
Definition exceptions.h:72
Size NodeId
Type for node ids.
HashTable< NodeId, VAL > NodeProperty
Property on graph elements.
Set< NodeId > NodeSet
Some typdefs and define for shortcuts ...
gum is the global namespace for all aGrUM entities
Definition agrum.h:46
priority queues (in which an element cannot appear more than once)