aGrUM 3.1.1
a C++ library for (probabilistic) graphical models
multiDimCombineAndProjectDefault_tpl.h
Go to the documentation of this file.
1/****************************************************************************
2 * This file is part of the aGrUM/pyAgrum library. *
3 * *
4 * Copyright (c) 2005-2026 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-2026 *
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#pragma once
42
43
50
52#ifndef DOXYGEN_SHOULD_SKIP_THIS
53
54# include <limits>
55
56# include <agrum/agrum.h>
57
59
60namespace gum {
61
62 // default constructor
63 template < class TABLE >
65 TABLE (*combine)(const TABLE&, const TABLE&),
66 TABLE (*project)(const TABLE&, const gum::VariableSet&)) :
68 _combination_(new MultiDimCombinationDefault< TABLE >(combine)),
69 _projection_(new MultiDimProjection< TABLE >(project)) {
70 // for debugging purposes
71 GUM_CONSTRUCTOR(MultiDimCombineAndProjectDefault);
72 }
73
74 // copy constructor
75 template < class TABLE >
76 MultiDimCombineAndProjectDefault< TABLE >::MultiDimCombineAndProjectDefault(
77 const MultiDimCombineAndProjectDefault< TABLE >& from) :
78 MultiDimCombineAndProject< TABLE >(), _combination_(from._combination_->clone()),
79 _projection_(from._projection_->clone()) {
80 // for debugging purposes
81 GUM_CONS_CPY(MultiDimCombineAndProjectDefault);
82 }
83
84 // destructor
85 template < class TABLE >
86 MultiDimCombineAndProjectDefault< TABLE >::~MultiDimCombineAndProjectDefault() {
87 // for debugging purposes
88 GUM_DESTRUCTOR(MultiDimCombineAndProjectDefault);
89 delete _combination_;
90 delete _projection_;
91 }
92
93 // virtual constructor
94 template < class TABLE >
95 MultiDimCombineAndProjectDefault< TABLE >*
96 MultiDimCombineAndProjectDefault< TABLE >::clone() const {
97 return new MultiDimCombineAndProjectDefault< TABLE >(*this);
98 }
99
100 // combine and project
101 template < class TABLE >
102 Set< const TABLE* >
103 MultiDimCombineAndProjectDefault< TABLE >::execute(const Set< const TABLE* >& table_set,
104 const gum::VariableSet& del_vars) {
105 // create a vector with all the tables stored as multidims
106 std::vector< const IScheduleMultiDim* > tables;
107 tables.reserve(table_set.size());
108 for (const auto table: table_set) {
109 tables.push_back(new ScheduleMultiDim< TABLE >(*table, false));
110 }
111
112 // get the set of operations to perform and execute them
113 auto ops_plus_res = operations(tables, del_vars, false);
114 for (auto op: ops_plus_res.first) {
115 op->execute();
116 }
117
118 // get the schedule multidims resulting from the computations and save them
119 Set< const TABLE* > result(ops_plus_res.second.size());
120 for (const auto pot: ops_plus_res.second) {
121 auto& schedule_result = const_cast< ScheduleMultiDim< TABLE >& >(
122 static_cast< const ScheduleMultiDim< TABLE >& >(*pot));
123 auto potres = new TABLE(std::move(schedule_result.multiDim()));
124 result.insert(potres);
125 }
126
127 // delete all the operations created as well as all the schedule tables
128 _freeData_(tables, ops_plus_res.first);
129
130 return result;
131 }
132
133 // changes the function used for combining two TABLES
134 template < class TABLE >
135 void MultiDimCombineAndProjectDefault< TABLE >::setCombinationFunction(
136 TABLE (*combine)(const TABLE&, const TABLE&)) {
137 _combination_->setCombinationFunction(combine);
138 }
139
140 // returns the current combination function
141 template < class TABLE >
142 TABLE (*MultiDimCombineAndProjectDefault< TABLE >::combinationFunction())(const TABLE&,
143 const TABLE&) {
144 return _combination_->combinationFunction();
145 }
146
147 // changes the class that performs the combinations
148 template < class TABLE >
149 void MultiDimCombineAndProjectDefault< TABLE >::setCombinationClass(
150 const MultiDimCombination< TABLE >& comb_class) {
151 delete _combination_;
152 _combination_ = comb_class.clone();
153 }
154
155 // changes the function used for projecting TABLES
156 template < class TABLE >
157 void MultiDimCombineAndProjectDefault< TABLE >::setProjectionFunction(
158 TABLE (*proj)(const TABLE&, const gum::VariableSet&)) {
159 _projection_->setProjectionFunction(proj);
160 }
161
162 // returns the current projection function
163 template < class TABLE >
164 TABLE (*MultiDimCombineAndProjectDefault< TABLE >::projectionFunction())(
165 const TABLE&,
166 const gum::VariableSet&) {
167 return _projection_->projectionFunction();
168 }
169
170 // changes the class that performs the projections
171 template < class TABLE >
172 void MultiDimCombineAndProjectDefault< TABLE >::setProjectionClass(
173 const MultiDimProjection< TABLE >& proj_class) {
174 delete _projection_;
175 _projection_ = proj_class.clone();
176 }
177
180 template < class TABLE >
181 double MultiDimCombineAndProjectDefault< TABLE >::nbOperations(
182 const Set< const Sequence< const DiscreteVariable* >* >& table_set,
183 const gum::VariableSet& del_vars) const {
184 // create a vector with all the tables stored as multidims
185 std::vector< const IScheduleMultiDim* > tables;
186 tables.reserve(table_set.size());
187 for (const auto vars: table_set) {
188 tables.push_back(new ScheduleMultiDim< TABLE >(*vars, false));
189 }
190
191 // get the set of operations to perform and compute their number of operations
192 auto ops_plus_res = operations(tables, del_vars, false);
193 double nb_operations = 0.0;
194 for (auto op: ops_plus_res.first) {
195 nb_operations += op->nbOperations();
196 }
197
198 // delete all the operations created as well as all the schedule tables
199 _freeData_(tables, ops_plus_res.first);
200
201 return nb_operations;
202 }
203
206 template < class TABLE >
207 double MultiDimCombineAndProjectDefault< TABLE >::nbOperations(
208 const Set< const TABLE* >& set,
209 const gum::VariableSet& del_vars) const {
210 // create the set of sets of discrete variables involved in the tables
211 Set< const Sequence< const DiscreteVariable* >* > var_set(set.size());
212
213 for (const auto ptrTab: set) {
214 var_set << &(ptrTab->variablesSequence());
215 }
216
217 return nbOperations(var_set, del_vars);
218 }
219
220 // returns the memory consumption used during the combinations and
221 // projections
222 template < class TABLE >
223 std::pair< double, double > MultiDimCombineAndProjectDefault< TABLE >::memoryUsage(
224 const Set< const Sequence< const DiscreteVariable* >* >& table_set,
225 const gum::VariableSet& del_vars) const {
226 // create a vector with all the tables stored as multidims
227 std::vector< const IScheduleMultiDim* > tables;
228 tables.reserve(table_set.size());
229 for (const auto vars: table_set) {
230 tables.push_back(new ScheduleMultiDim< TABLE >(*vars, false));
231 }
232
233 // get the set of operations to perform and compute their number of operations
234 auto ops_plus_res = operations(tables, del_vars, false);
235
236 // the resulting memory consumtions
237 double max_memory = 0.0;
238 double end_memory = 0.0;
239 for (const auto op: ops_plus_res.first) {
240 const auto usage = op->memoryUsage();
241 if (end_memory + usage.first > max_memory) max_memory = end_memory + usage.first;
242 end_memory += usage.second;
243 }
244
245 // delete all the operations created as well as all the schedule tables
246 _freeData_(tables, ops_plus_res.first);
247
248 return {max_memory, end_memory};
249 }
250
251 // returns the memory consumption used during the combinations and
252 // projections
253 template < class TABLE >
254 std::pair< double, double > MultiDimCombineAndProjectDefault< TABLE >::memoryUsage(
255 const Set< const TABLE* >& set,
256 const gum::VariableSet& del_vars) const {
257 // create the set of sets of discrete variables involved in the tables
258 Set< const Sequence< const DiscreteVariable* >* > var_set(set.size());
259
260 for (const auto ptrTab: set) {
261 var_set << &(ptrTab->variablesSequence());
262 }
263
264 return memoryUsage(var_set, del_vars);
265 }
266
269 template < class TABLE >
270 std::pair< std::vector< ScheduleOperator* >, Set< const IScheduleMultiDim* > >
271 MultiDimCombineAndProjectDefault< TABLE >::operations(
272 const std::vector< const IScheduleMultiDim* >& original_tables,
273 const gum::VariableSet& del_vars,
274 const bool is_result_persistent) const {
275 Set< const IScheduleMultiDim* > tables_set(original_tables.size());
276 for (const auto table: original_tables) {
277 tables_set.insert(table);
278 }
279 return operations(tables_set, del_vars, is_result_persistent);
280 }
281
284 template < class TABLE >
285 std::pair< std::vector< ScheduleOperator* >, Set< const IScheduleMultiDim* > >
286 MultiDimCombineAndProjectDefault< TABLE >::operations(
287 const Set< const IScheduleMultiDim* >& original_tables,
288 const gum::VariableSet& original_del_vars,
289 const bool is_result_persistent) const {
290 // check if we need to combine and/or project something
291 const Size tabsize = original_tables.size();
292 if (tabsize < 2) {
293 if (tabsize == 1) {
294 auto res = _projection_->operations(*original_tables.begin(), original_del_vars);
295 return std::pair< std::vector< ScheduleOperator* >, Set< const IScheduleMultiDim* > >(
296 {res.first},
297 {res.second});
298 } else {
299 std::string names;
300 for (const auto& v: original_del_vars) {
301 names += v->name() + ", ";
302 }
304 "MultiDimCombineAndProject need at least one table to "
305 "have some work to do (original_del_vars ="
306 << names << ").");
307 }
308 }
309
310 // we copy the set of tables to be combined and the set of variables to
311 // delete because we will modify them during the combination/projection process
312 Set< const IScheduleMultiDim* > tables = original_tables;
313 gum::VariableSet del_vars = original_del_vars;
314
315 // when we remove a variable, we need to combine all the tables containing
316 // this variable in order to produce a new unique table containing this
317 // variable. Removing a variable is then performed by marginalizing it out of
318 // the table. In the combineAndProjectDefault algorithm, we wish to remove
319 // first variables that would produce small tables. This should speed up the
320 // whole marginalizing process.
321
322 Size nb_vars;
323 {
324 // determine the set of all the variables involved in the tables.
325 // this should help sizing correctly the hashtables used hereafter
326 gum::VariableSet all_vars;
327
328 for (const auto table: tables) {
329 for (const auto ptrVar: table->variablesSequence()) {
330 all_vars.insert(ptrVar);
331 }
332 }
333
334 nb_vars = all_vars.size();
335 }
336
337 // the tables containing a given variable
338 HashTable< const DiscreteVariable*, Set< const IScheduleMultiDim* > > tables_per_var(nb_vars);
339
340 // for a given variable X to be deleted, the list of all the variables of
341 // the tables containing X (actually, we also count the number of tables
342 // containing the variable. This is more efficient for computing and
343 // updating the product_size priority queue (see below) when some tables
344 // are removed)
345 HashTable< const DiscreteVariable*, HashTable< const DiscreteVariable*, unsigned int > >
346 clique_vars_per_var(nb_vars);
347
348 // initialize clique_vars_per_var and tables_per_var
349 {
350 Set< const IScheduleMultiDim* > empty_set(tables.size());
351 HashTable< const DiscreteVariable*, unsigned int > empty_hash(nb_vars);
352
353 for (const auto ptrVar: del_vars) {
354 tables_per_var.insert(ptrVar, empty_set);
355 clique_vars_per_var.insert(ptrVar, empty_hash);
356 }
357
358 // update properly tables_per_var and clique_vars_per_var
359 for (const auto ptrTab: tables) {
360 const auto& vars = ptrTab->variablesSequence();
361
362 for (const auto ptrVar: vars) {
363 if (del_vars.contains(ptrVar)) {
364 // add the table to the set of tables related to vars[i]
365 tables_per_var[ptrVar].insert(ptrTab);
366
367 // add the variables of the table to clique_vars_per_var[vars[i]]
368 auto& comb_vars = clique_vars_per_var[ptrVar];
369 for (const auto xptrVar: vars) {
370 if (auto ptr = comb_vars.tryGet(xptrVar)) ++(*ptr);
371 else comb_vars.insert(xptrVar, 1);
372 }
373 }
374 }
375 }
376 }
377
378 // create the set of operations to execute to perform the combinations and
379 // projections
380 std::vector< ScheduleOperator* > ops;
381 ops.reserve(2 * tables.size() + del_vars.size());
382
383 // keep track of the operations that created new tables. This is useful
384 // when requiring that results are persistent
385 HashTable< const IScheduleMultiDim*, ScheduleOperator* > multidim2op(tables.size());
386
387 // the sizes of the tables produced when removing a given discrete variable
388 PriorityQueue< const DiscreteVariable*, double > product_size;
389
390 // initialize properly product_size
391 for (const auto& elt: clique_vars_per_var) {
392 double size = 1.0;
393 const auto ptrVar = elt.first;
394 const auto& hashvars = elt.second; // HashTable<DiscreteVariable*, int>
395
396 if (!hashvars.empty()) {
397 for (const auto& xelt: hashvars) {
398 size *= (double)xelt.first->domainSize();
399 }
400
401 product_size.insert(ptrVar, size);
402 }
403 }
404
405 // now, remove all the variables in del_vars, starting from those that
406 // produce the smallest tables
407 while (!product_size.empty()) {
408 // get the best variable to remove
409 const DiscreteVariable* del_var = product_size.pop();
410 del_vars.erase(del_var);
411
412 // get the set of tables to combine
413 auto& tables_to_combine = tables_per_var[del_var];
414
415 // if there is no tables to combine, do nothing
416 if (tables_to_combine.empty()) continue;
417
418 // compute the combination of all the tables: if there is only one table,
419 // there is nothing to do, else we shall use the MultiDimCombination
420 // to perform the combination
421 const IScheduleMultiDim* joint = nullptr;
422 bool joint_to_delete;
423 if (tables_to_combine.size() == 1) {
424 joint = *(tables_to_combine.begin());
425 joint_to_delete = false;
426 } else {
427 // get the operations to perform to make the combination as well as
428 // the result of the combination
429 auto comb_ops = _combination_->operations(tables_to_combine);
430 ops.insert(ops.cend(), comb_ops.first.begin(), comb_ops.first.end());
431 joint = comb_ops.second;
432 joint_to_delete = true;
433 }
434
435 // compute the table resulting from marginalizing out del_var from joint
436 // and add the projection to the set of operations. Here, we know that the
437 // joint contains del_var, hence there is a nonempty projection to perform
438 gum::VariableSet del_one_var;
439 del_one_var << del_var;
440 auto proj_ops = _projection_->operations(joint, del_one_var);
441 ops.push_back(proj_ops.first);
442 const IScheduleMultiDim* marginal = proj_ops.second;
443 if (is_result_persistent) multidim2op.insert(marginal, proj_ops.first);
444
445 // remove the temporary joint if needed
446 if (joint_to_delete) {
447 auto deletion = new ScheduleDeletion< TABLE >(
448 static_cast< const ScheduleMultiDim< TABLE >& >(*joint));
449 ops.push_back(deletion);
450 }
451
452 // update clique_vars_per_var : remove the variables of the tables we
453 // combined from this hashtable
454 // update accordingly tables_per_vars : remove these tables
455 // update accordingly product_size : when a variable is no more used by
456 // any table, divide product_size by its domain size
457 for (const auto ptrTab: tables_to_combine) {
458 const auto& table_vars = ptrTab->variablesSequence();
459 const Size tab_vars_size = table_vars.size();
460
461 for (Size i = 0; i < tab_vars_size; ++i) {
462 if (del_vars.contains(table_vars[i])) {
463 // here we have a variable that needed to be removed => update
464 // product_size, tables_per_var and clique_vars_per_var: here,
465 // the update corresponds to removing table PtrTab
466 auto& table_vars_of_var_i = clique_vars_per_var[table_vars[i]];
467 double div_size = 1.0;
468
469 for (Size j = 0; j < tab_vars_size; ++j) {
470 unsigned int k = --table_vars_of_var_i[table_vars[j]];
471
472 if (k == 0) {
473 div_size *= table_vars[j]->domainSize();
474 table_vars_of_var_i.erase(table_vars[j]);
475 }
476 }
477
478 tables_per_var[table_vars[i]].erase(ptrTab);
479
480 if (div_size != 1.0) {
481 product_size.setPriority(table_vars[i],
482 product_size.priority(table_vars[i]) / div_size);
483 }
484 }
485 }
486
487 // if ptrTab is a table resulting from preceding combinations/projections,
488 // it is temporary and, therefore, it should be deleted
489 if (!original_tables.contains(ptrTab)) {
490 auto deletion = new ScheduleDeletion< TABLE >(
491 static_cast< const ScheduleMultiDim< TABLE >& >(*ptrTab));
492 ops.push_back(deletion);
493 }
494
495 tables.erase(ptrTab);
496 }
497
498 tables_per_var.erase(del_var);
499
500 // add the new projected marginal to the list of tables
501 const auto& marginal_vars = marginal->variablesSequence();
502 for (const auto mvar: marginal_vars) {
503 if (del_vars.contains(mvar)) {
504 // add the new marginal table to the set of tables of mvar
505 tables_per_var[mvar].insert(marginal);
506
507 // add the variables of the table to clique_vars_per_var[mvar]
508 auto& iter_vars = clique_vars_per_var[mvar];
509 double mult_size = 1.0;
510 for (const auto var: marginal_vars) {
511 if (auto ptr = iter_vars.tryGet(var)) {
512 ++(*ptr);
513 } else {
514 iter_vars.insert(var, 1);
515 mult_size *= (double)var->domainSize();
516 }
517 }
518
519 if (mult_size != 1.0) {
520 product_size.setPriority(mvar, product_size.priority(mvar) * mult_size);
521 }
522 }
523 }
524
525 tables.insert(marginal);
526 }
527
528 // here, Set "tables" contains the list of the tables resulting from
529 // marginalizing out of del_vars of the combination of the tables
530 // of original_tables. Note in particular that it will contain all the
531 // tensors with no dimension (constants)
532
533 // if we require persistent results, update the operations that produced some
534 // of the tables in Set "tables"
535 if (is_result_persistent) {
536 for (const auto table: tables) {
537 if (multidim2op.exists(table)) multidim2op[table]->makeResultsPersistent(true);
538 }
539 }
540
541 return {ops, tables};
542 }
543
545 template < class TABLE >
546 void MultiDimCombineAndProjectDefault< TABLE >::_freeData_(
547 std::vector< const IScheduleMultiDim* >& tables,
548 std::vector< ScheduleOperator* >& operations) const {
549 for (auto op: operations)
550 delete op;
551
552 for (auto table: tables)
553 delete table;
554 }
555
556} /* namespace gum */
557
558#endif /* DOXYGEN_SHOULD_SKIP_THIS */
A class to combine efficiently several MultiDim tables.
MultiDimCombineAndProjectDefault(TABLE(*combine)(const TABLE &, const TABLE &), TABLE(*project)(const TABLE &, const gum::VariableSet &))
Default constructor.
A generic interface to combine and project efficiently MultiDim tables.
A generic class to project efficiently a MultiDim table over a subset of its variables.
Exception : operation not allowed.
void insert(const Key &k)
Inserts a new element into the set.
Definition set_tpl.h:510
Size size() const noexcept
Returns the number of elements in the set.
Definition set_tpl.h:607
#define GUM_ERROR(type, msg)
Definition exceptions.h:76
gum is the global namespace for all aGrUM entities
Definition agrum.h:46
Set< const DiscreteVariable * > VariableSet