aGrUM 2.3.2
a C++ library for (probabilistic) graphical models
projectionPattern4BaseName.h
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
49
50// check if we allowed these patterns to be used
51#ifndef GUM_PROJECTION_PATTERN_ALLOWED
52
53// #warning To use projectionPattern, you must define
54// GUM_PROJECTION_PATTERN_ALLOWED
55
56#else
57namespace gum {
58
59 // a specialized function for projecting a multiDimImplementation over a subset
60 // of its variables
61
62# ifdef GUM_MULTI_DIM_PROJECTION_NAME
63# define GUM_MULTI_DIM_PROJECTION_TYPE GUM_SCALAR
64
65 template < typename GUM_SCALAR >
67 GUM_MULTI_DIM_PROJECTION_NAME(const MultiDimImplementation< GUM_SCALAR >* table,
68 const gum::VariableSet& del_vars)
69# endif
70
71 // clang-format off
72
73#ifdef GUM_MULTI_DIM_PROJECTION_POINTER_NAME
74#define GUM_MULTI_DIM_PROJECTION_TYPE GUM_SCALAR *
75#define GUM_MULTI_DIM_PROJECTION_POINTER
76 template <typename GUM_SCALAR>
77 MultiDimImplementation<GUM_SCALAR*>* GUM_MULTI_DIM_PROJECTION_POINTER_NAME(
79 const VariableSet& del_vars )
80#endif
81
82#ifdef GUM_MULTI_DIM_PROJECTION_NAME_F
83#define GUM_MULTI_DIM_PROJECTION_TYPE GUM_SCALAR
84 template <typename GUM_SCALAR>
85 MultiDimImplementation<GUM_SCALAR>* GUM_MULTI_DIM_PROJECTION_NAME_F(
87 const VariableSet& del_vars,
88 GUM_SCALAR ( *f )( const GUM_SCALAR&, const GUM_SCALAR& ) )
89#endif
90
91#ifdef GUM_MULTI_DIM_PROJECTION_POINTER_NAME_F
92#define GUM_MULTI_DIM_PROJECTION_TYPE GUM_SCALAR *
93#define GUM_MULTI_DIM_PROJECTION_POINTER
94 template <typename GUM_SCALAR>
96 GUM_MULTI_DIM_PROJECTION_POINTER_NAME_F(
98 const VariableSet& del_vars,
99 GUM_SCALAR* ( *f )( const GUM_SCALAR const*,
100 const GUM_SCALAR const* ) )
101#endif
102
103 // clang-format on
104
105 {
106
107 // create the neutral element used to fill the result upon its
108 // creation
109 const GUM_SCALAR neutral_element = GUM_MULTI_DIM_PROJECTION_NEUTRAL;
110
111 // first, compute whether we should loop over table or over the projected
112 // table first to get a faster algorithm.
113 const Sequence< const DiscreteVariable* >& table_vars = table->variablesSequence();
114 bool need_swapping = table_vars.size() >= 2 * del_vars.size();
115
116 if (!need_swapping) {
117 // Compute the variables that belong to both the projection set and
118 // table. Store the domain size of the Cartesian product of these
119 // variables (result_domain_size) as well as the domain size of the
120 // Cartesian product of the variables of table that do not belong to
121 // projection set, i.e., those variables that belong to table but not to
122 // del_vars (table_alone_domain_size). In addition, store the number of
123 // increments in the computation loops at the end of the function before
124 // which the variables of the projection set need be incremented (vector
125 // before incr).
126 std::vector< Idx > table_and_result_offset;
127 std::vector< Idx > table_and_result_domain;
128 std::vector< Idx > before_incr;
129 unsigned int nb_positive_before_incr = 0;
130 Idx table_alone_domain_size = 1;
131 Idx result_domain_size = 1;
132 Idx table_domain_size = 1;
134 {
135 Idx tmp_before_incr = 1;
136 bool has_before_incr = false;
137
138 for (const auto var: table_vars) {
139 table_domain_size *= var->domainSize();
140
141 if (!del_vars.exists(var)) {
142 if (has_before_incr) {
143 before_incr.push_back(tmp_before_incr - 1);
144 has_before_incr = false;
145 ++nb_positive_before_incr;
146 } else {
147 before_incr.push_back(0);
148 }
149
150 table_and_result_domain.push_back(var->domainSize());
151 table_and_result_offset.push_back(result_domain_size);
152 result_domain_size *= var->domainSize();
153 tmp_before_incr = 1;
154 result_varSeq << var;
155 } else {
156 tmp_before_incr *= var->domainSize();
157 has_before_incr = true;
158 table_alone_domain_size *= var->domainSize();
159 }
160 }
161 }
162 std::vector< Idx > table_and_result_value = table_and_result_domain;
163 std::vector< Idx > current_incr = before_incr;
164 std::vector< Idx > table_and_result_down = table_and_result_offset;
165
166 for (unsigned int i = 0; i < table_and_result_down.size(); ++i) {
167 table_and_result_down[i] *= (table_and_result_domain[i] - 1);
168 }
169
170 // create a table "result" containing only the variables of the
171 // projection: the variables are stored in the order in which they appear
172 // in "table" Hence, ++ operations on an instantiation on table will more
173 // or less correspond to a ++ operation on an instantiation on result
176
177 if (!result_varSeq.size()) { return result; }
178
179 result->beginMultipleChanges();
180
181 for (const auto var: result_varSeq)
182 *result << *var;
183
184 result->endMultipleChanges();
185
186// fill the matrix with the neutral element
187# ifdef GUM_MULTI_DIM_PROJECTION_POINTER
188
189 for (Idx i = 0; i < result_domain_size; ++i) {
190 result->unsafeSet(i, new GUM_SCALAR(neutral_element));
191 }
192
193# else
194 result->fill(neutral_element);
195# endif
196
197 // compute the projection: first loop over the variables X's in table
198 // that do not belong to result and, for each value of these X's, loop
199 // over the variables in both table and result. As such, in the internal
200 // loop, the offsets of "result" need only be incremented as usually to
201 // parse appropriately this table. For result, the problem is slightly
202 // more complicated: in the outer for loop, we shall always reset
203 // resul_offset to 0. For the inner loop, result_offset should be
204 // incremented (++) only when t1 before_incr[xxx] steps in the loop have
205 // already been made.
206
207 // but before doing so, check whether there exist positive_before_incr.
208 // If this is not the case, optimize by not using before_incr at all
209 if (!nb_positive_before_incr) {
210 Idx result_offset = 0;
211 // TODO: change into Instantiation table_inst(table); when Tensors will support
212 // thread-safe creations of Instantiations
213 Instantiation table_inst;
214 for (const auto var: table->variablesSequence())
215 table_inst.add(*var);
216
217 for (Idx i = 0; i < table_alone_domain_size; ++i) {
218 for (Idx j = 0; j < result_domain_size; ++j) {
219# ifdef GUM_MULTI_DIM_PROJECTION_POINTER
220 GUM_MULTI_DIM_PROJECTION_TYPE res = result->unsafeGet(result_offset);
221 *res = GUM_MULTI_DIM_PROJECTION(res, table->get(table_inst));
222# else
223 GUM_MULTI_DIM_PROJECTION_TYPE& res
224 = const_cast< GUM_MULTI_DIM_PROJECTION_TYPE& >(result->unsafeGet(result_offset));
225 res = GUM_MULTI_DIM_PROJECTION(res, table->get(table_inst));
226# endif
227
228 // update the offset of table and result
229 ++table_inst;
230 ++result_offset;
231 }
232
233 // update the offset of result
234 result_offset = 0;
235 }
236 } else {
237 // here there are positive before_incr and we should use them to know
238 // when result_offset needs be changed
239 Idx result_offset = 0;
240 // TODO: change into Instantiation table_inst(table); when Tensors support
241 // thread-safe creations of Instantiations
242 Instantiation table_inst;
243 for (const auto var: table->variablesSequence())
244 table_inst.add(*var);
245
246 for (Idx i = 0; i < table_domain_size; ++i) {
247# ifdef GUM_MULTI_DIM_PROJECTION_POINTER
248 GUM_MULTI_DIM_PROJECTION_TYPE res = result->unsafeGet(result_offset);
249 *res = GUM_MULTI_DIM_PROJECTION(res, table->get(table_inst));
250# else
251 GUM_MULTI_DIM_PROJECTION_TYPE& res
252 = const_cast< GUM_MULTI_DIM_PROJECTION_TYPE& >(result->unsafeGet(result_offset));
253 res = GUM_MULTI_DIM_PROJECTION(res, table->get(table_inst));
254# endif
255
256 // update the offset of table
257 ++table_inst;
258
259 // update the offset of result
260 for (unsigned int k = 0; k < current_incr.size(); ++k) {
261 // check if we need modify result_offset
262 if (current_incr[k]) {
263 --current_incr[k];
264 break;
265 }
266
267 current_incr[k] = before_incr[k];
268
269 // here we shall modify result_offset
270 --table_and_result_value[k];
271
272 if (table_and_result_value[k]) {
273 result_offset += table_and_result_offset[k];
274 break;
275 }
276
277 table_and_result_value[k] = table_and_result_domain[k];
278 result_offset -= table_and_result_down[k];
279 }
280 }
281 }
282
283 return result;
284 } else { // need_swapping = true
285
286 // Compute the variables that are in t1 but not in t2. For these
287 // variables, get the increment in the offset of t1 that would result
288 // from an increment in one of these variables (vector t1_alone_offset).
289 // Also store the domain size of these variables (vector t1_alone_domain)
290 // and, for the computation loops at the end of this function, |variable|
291 // - the current values of these variables (vector t1_alone_value). All
292 // these vectors reference the variables of t1 \ t2 in the order in which
293 // they appear in seq1. Keep as well the size of the Cartesian product of
294 // these variables.
295 std::vector< Idx > table_alone_offset;
296 std::vector< Idx > table_alone_domain;
297 Idx offset = 1;
298 Idx table_alone_domain_size = 1;
299 HashTable< const DiscreteVariable*, Idx > var1offset(table_vars.size());
300
301 for (const auto var: table_vars) {
302 if (del_vars.exists(var)) {
303 table_alone_domain.push_back(var->domainSize());
304 table_alone_offset.push_back(offset);
305 table_alone_domain_size *= var->domainSize();
306 }
307
308 var1offset.insert(var, offset);
309 offset *= var->domainSize();
310 }
311
312 std::vector< Idx > table_alone_value = table_alone_domain;
313 std::vector< Idx > table_alone_down = table_alone_offset;
314
315 for (unsigned int i = 0; i < table_alone_down.size(); ++i)
316 table_alone_down[i] *= (table_alone_domain[i] - 1);
317
318 // Compute the same vectors for the variables that belong to both t1 and
319 // t2. In this case, All these vectors reference the variables in the
320 // order in which they appear in seq2. In addition, store the number of
321 // increments in the computation loops at the end of the function before
322 // which the variables of t1 cap t2 need be incremented (vector
323 // t1_and_t2_before incr). Similarly, store the number of such
324 // increments currently still needed before the next incrementation of
325 // the variables of t1 cap t2. Keep as well the size of the Cartesian
326 // product of these variables.
328 std::vector< Idx > table_and_result_offset;
329 std::vector< Idx > table_and_result_domain;
330 Idx result_domain_size = 1;
331 bool has_before_incr = false;
332 bool found_proj_var = false;
333
334 for (const auto var: table_vars) {
335 if (!del_vars.exists(var)) {
336 table_and_result_domain.push_back(var->domainSize());
337 table_and_result_offset.push_back(var1offset[var]);
338 found_proj_var = true;
339 result_domain_size *= var->domainSize();
340 result_varSeq << var;
341 } else {
342 if (found_proj_var) has_before_incr = true;
343 }
344 }
345
346 std::vector< Idx > table_and_result_value = table_and_result_domain;
347 std::vector< Idx > table_and_result_down = table_and_result_offset;
348
349 for (unsigned int i = 0; i < table_and_result_down.size(); ++i) {
350 table_and_result_down[i] *= (table_and_result_domain[i] - 1);
351 }
352
353 // create a table "result" containing only the variables of the
354 // projection: the variables are stored in the order in which they appear
355 // in "table" Hence, ++ operations on an instantiation on table will more
356 // or less correspond to a ++ operation on an instantiation on result
359 result->beginMultipleChanges();
360
361 for (const auto var: result_varSeq)
362 *result << *var;
363
364 result->endMultipleChanges();
365
366// fill the matrix with the neutral element
367# ifdef GUM_MULTI_DIM_PROJECTION_POINTER
368
369 for (Idx i = 0; i < result_domain_size; ++i) {
370 result->unsafeSet(i, new GUM_SCALAR(neutral_element));
371 }
372
373# else
374 result->fill(neutral_element);
375# endif
376
377 // compute the sum: first loop over the variables X's both in table and
378 // in result and, for each value of these X's, loop over the variables
379 // that are in table but not result. As such, in the internal loop, there
380 // is no increment in the offset of "result", and in the outer loop, this
381 // offset is incremented using a simple ++ operator. For table, the
382 // problem is slightly more complicated: in the outer for loop, we shall
383 // increment the variables of table cap result according to vectors
384 // table_and_result_xxx. Each time a variable of these vectors has been
385 // incremented up to its max, we shall put it down to 0 and increment the
386 // next one, and so on. For the inner loop, this is similar except that
387 // we shall do these operations only when before_incr[xxx] steps in the
388 // loop have already been made.
389
390 // but before doing so, check whether there exist positive_before_incr.
391 // If this is not the case, optimize by not using before_incr at all
392 if (!has_before_incr) {
393 Idx result_offset = 0;
394 // TODO: change into Instantiation table_inst(table); when Tensors will support
395 // thread-safe creations of Instantiations
396 Instantiation table_inst;
397 for (const auto var: table->variablesSequence())
398 table_inst.add(*var);
399
400 for (Idx i = 0; i < result_domain_size; ++i) {
401 for (Idx j = 0; j < table_alone_domain_size; ++j) {
402# ifdef GUM_MULTI_DIM_PROJECTION_POINTER
403 GUM_MULTI_DIM_PROJECTION_TYPE res = result->unsafeGet(result_offset);
404 *res = GUM_MULTI_DIM_PROJECTION(res, table->get(table_inst));
405# else
406 GUM_MULTI_DIM_PROJECTION_TYPE& res
407 = const_cast< GUM_MULTI_DIM_PROJECTION_TYPE& >(result->unsafeGet(result_offset));
408 res = GUM_MULTI_DIM_PROJECTION(res, table->get(table_inst));
409# endif
410
411 // update the offset of table
412 ++table_inst;
413 }
414
415 // update the offset of result
416 ++result_offset;
417 }
418 } else {
419 // here there are positive before_incr and we should use them to know
420 // when result_offset needs be changed
421 Idx result_offset = 0;
422 // TODO: change into Instantiation table_inst(table); when Tensors will support
423 // thread-safe creations of Instantiations
424 Instantiation table_inst;
425 for (const auto var: table->variablesSequence())
426 table_inst.add(*var);
427
428 for (Idx j = 0; j < result_domain_size; ++j) {
429 for (Idx i = 0; i < table_alone_domain_size; ++i) {
430# ifdef GUM_MULTI_DIM_PROJECTION_POINTER
431 GUM_MULTI_DIM_PROJECTION_TYPE res = result->unsafeGet(result_offset);
432 *res = GUM_MULTI_DIM_PROJECTION(res, table->get(table_inst));
433# else
434 GUM_MULTI_DIM_PROJECTION_TYPE& res
435 = const_cast< GUM_MULTI_DIM_PROJECTION_TYPE& >(result->unsafeGet(result_offset));
436 res = GUM_MULTI_DIM_PROJECTION(res, table->get(table_inst));
437# endif
438
439 // update the increment of table for the inner loop
440 for (unsigned int k = 0; k < table_alone_value.size(); ++k) {
441 --table_alone_value[k];
442
443 if (table_alone_value[k]) {
444 table_inst += table_alone_offset[k];
445 break;
446 }
447
448 table_alone_value[k] = table_alone_domain[k];
449 table_inst -= table_alone_down[k];
450 }
451 }
452
453 // update the offset of table for the outer loop
454 for (unsigned int k = 0; k < table_and_result_value.size(); ++k) {
455 --table_and_result_value[k];
456
457 if (table_and_result_value[k]) {
458 table_inst += table_and_result_offset[k];
459 break;
460 }
461
462 table_and_result_value[k] = table_and_result_domain[k];
463 table_inst -= table_and_result_down[k];
464 }
465
466 // update the offset of result for the outer loop
467 ++result_offset;
468 }
469 }
470
471 return result;
472 }
473 }
474
475# undef GUM_MULTI_DIM_PROJECTION_TYPE
476
477# ifdef GUM_MULTI_DIM_PROJECTION_POINTER
478# undef GUM_MULTI_DIM_PROJECTION_POINTER
479# endif
480} // namespace gum
481#endif /* GUM_PROJECTION_PATTERN_ALLOWED */
The class for generic Hash Tables.
Definition hashTable.h:637
Class for assigning/browsing values to tuples of discrete variables.
void add(const DiscreteVariable &v) final
Adds a new variable in the Instantiation.
Multidimensional matrix stored as an array in memory.
<agrum/base/multidim/multiDimImplementation.h>
void beginMultipleChanges() override
Call this method before doing important changes in this MultiDimContainer.
Size size() const noexcept
Returns the size of the sequence.
The generic class for storing (ordered) sequences of objects.
Definition sequence.h:972
Size Idx
Type for indexes.
Definition types.h:79
gum is the global namespace for all aGrUM entities
Definition agrum.h:46
Set< const DiscreteVariable * > VariableSet