aGrUM  0.20.2
a C++ library for (probabilistic) graphical models
operatorPattern4MultiDimArray.h
Go to the documentation of this file.
1 /**
2  *
3  * Copyright 2005-2020 Pierre-Henri WUILLEMIN(@LIP6) & Christophe GONZALES(@AMU)
4  * info_at_agrum_dot_org
5  *
6  * This library is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this library. If not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 
22 /**
23  * @file
24  * @brief the pattern used by all binary MultiDimArrays operators
25  *
26  * @author Christophe GONZALES(@AMU) and Pierre-Henri WUILLEMIN(@LIP6)
27  */
28 
29 // check if we allowed these patterns to be used
30 #ifndef GUM_OPERATOR_PATTERN_ALLOWED
31 
32 // #warning To use operatorPattern4MultiDimArray.h, you must define
33 // GUM_OPERATOR_PATTERN_ALLOWED
34 
35 #else
36 
37 namespace gum {
38 
39  // a specialized function for combining two multiDimArrays
40 
41 # ifdef GUM_MULTI_DIM_OPERATOR_NAME
42 # define GUM_MULTI_DIM_OPERATOR_TYPE T
43  template < typename T >
44  MultiDimArray< T >* GUM_MULTI_DIM_OPERATOR_NAME(const MultiDimArray< T >* t1,
45  const MultiDimArray< T >* t2)
46 # endif
47 
48  // clang-format off
49 
50 #ifdef GUM_MULTI_DIM_OPERATOR_POINTER_NAME
51 #define GUM_MULTI_DIM_OPERATOR_TYPE T *
52  template <typename T>
53  MultiDimArray<T*>* GUM_MULTI_DIM_OPERATOR_POINTER_NAME(
54  const MultiDimArray<T*>* t1, const MultiDimArray<T*>* t2 )
55 #endif
56 
57 #ifdef GUM_MULTI_DIM_OPERATOR_NAME_F
58 #define GUM_MULTI_DIM_OPERATOR_TYPE T
59  template <typename T>
60  MultiDimArray<T>* GUM_MULTI_DIM_OPERATOR_NAME_F(
61  const MultiDimArray<T>* t1,
62  const MultiDimArray<T>* t2,
63  const T ( *f )( const T&, const T& ) )
64 #endif
65 
66 #ifdef GUM_MULTI_DIM_OPERATOR_POINTER_NAME_F
67 #define GUM_MULTI_DIM_OPERATOR_TYPE T *
68  template <typename T>
69  MultiDimArray<T*>* GUM_MULTI_DIM_OPERATOR_POINTER_NAME_F(
70  const MultiDimArray<T*>* t1,
71  const MultiDimArray<T*>* t2,
72  const T* ( *f )( const T*, const T* ) )
73 #endif
74 
75 #ifdef GUM_MULTI_DIM_OPERATOR_IMPL2ARRAY_NAME
76 #define GUM_MULTI_DIM_OPERATOR_TYPE T
77  template <typename T>
78  MultiDimImplementation<T>* GUM_MULTI_DIM_OPERATOR_IMPL2ARRAY_NAME(
79  const MultiDimImplementation<T>* tt1,
80  const MultiDimImplementation<T>* tt2 )
81 #endif
82 
83 #ifdef GUM_MULTI_DIM_OPERATOR_POINTER_IMPL2ARRAY_NAME
84 #define GUM_MULTI_DIM_OPERATOR_TYPE T *
85  template <typename T>
86  MultiDimImplementation<T*>*
87  GUM_MULTI_DIM_OPERATOR_POINTER_IMPL2ARRAY_NAME(
88  const MultiDimImplementation<T*>* tt1,
89  const MultiDimImplementation<T*>* tt2 )
90 #endif
91 
92  // clang-format on
93 
94  {
95 
96 # ifdef GUM_MULTI_DIM_OPERATOR_IMPL2ARRAY_NAME
97  const MultiDimArray< T >* t1
98  = reinterpret_cast< const MultiDimArray< T >* >(tt1);
99  const MultiDimArray< T >* t2
100  = reinterpret_cast< const MultiDimArray< T >* >(tt2);
101 # endif
102 
103 # ifdef GUM_MULTI_DIM_OPERATOR_POINTER_IMPL2ARRAY_NAME
104  const MultiDimArray< T* >* t1
105  = reinterpret_cast< const MultiDimArray< T* >* >(tt1);
106  const MultiDimArray< T* >* t2
107  = reinterpret_cast< const MultiDimArray< T* >* >(tt2);
108 # endif
109 
110  // get the variables of the tables
111  const Sequence< const DiscreteVariable* >& t1_vars = t1->variablesSequence();
112  const Sequence< const DiscreteVariable* >& t2_vars = t2->variablesSequence();
113 
114  // get the domain size of the tables' variables
115  HashTable< const DiscreteVariable*, Idx > t1_offsets;
116  {
117  Idx current_offset = 1;
118 
119  for (const auto var: t1_vars) {
120  t1_offsets.insert(var, current_offset);
121  current_offset *= var->domainSize();
122  }
123  }
124  HashTable< const DiscreteVariable*, Idx > t2_offsets;
125  {
126  Idx current_offset = 1;
127 
128  for (const auto var: t2_vars) {
129  t2_offsets.insert(var, current_offset);
130  current_offset *= var->domainSize();
131  }
132  }
133 
134  // we divide the variables of t1 and t2 into 3 separate sets: those that
135  // belong only to t1 (variables t1_alone_xxx), those that belong only to t2
136  // (variables t2_alone_xxx) and those that belong to both tables (variables
137  // t1_and_t2_xxx). For each set, we define how an increment of a given
138  // variable of the set changes the offset in the corresponding table
139  // (variable txxx_offset) and what is the domain size of the variable
140  // (txxx_domain). In addition, we compute the domain size of the Cartesian
141  // product of the variables in each of the 3 sets. Given these data, we
142  // will be able to parse both t1, t2 and the result table t1+t2 without
143  // resorting to instantiations.
144  std::vector< Idx > t1_alone_offset;
145  std::vector< Idx > t1_alone_domain;
146  Idx t1_alone_domain_size = 1;
147 
148  std::vector< Idx > t2_alone_offset;
149  std::vector< Idx > t2_alone_domain;
150  Idx t2_alone_domain_size = 1;
151 
152  std::vector< Idx > t1_and_t2_1_offset;
153  std::vector< Idx > t1_and_t2_2_offset;
154  std::vector< Idx > t1_and_t2_domain;
155  std::vector< const DiscreteVariable* > t1_and_t2_vars;
156  Idx t1_and_t2_domain_size = 1;
157 
158  {
159  for (const auto var: t1_vars)
160  if (t2_vars.exists(var)) {
161  t1_and_t2_domain.push_back(var->domainSize());
162  t1_and_t2_1_offset.push_back(t1_offsets[var]);
163  t1_and_t2_2_offset.push_back(t2_offsets[var]);
164  t1_and_t2_vars.push_back(var);
165  t1_and_t2_domain_size *= var->domainSize();
166  } else {
167  t1_alone_domain.push_back(var->domainSize());
168  t1_alone_offset.push_back(t1_offsets[var]);
169  t1_alone_domain_size *= var->domainSize();
170  }
171 
172  for (const auto var: t2_vars)
173  if (!t1_vars.exists(var)) {
174  t2_alone_domain.push_back(var->domainSize());
175  t2_alone_offset.push_back(t2_offsets[var]);
176  t2_alone_domain_size *= var->domainSize();
177  }
178  }
179 
180  // a Boolean indicating whether the variables that t1 and t2 have in common
181  // are the first variables and are in the same order. When this is true,
182  // computations can be performed faster
183  bool t1_and_t2_begin_vars = false;
184 
185  if (t1_and_t2_vars.size()) {
186  unsigned int nb_t1_t2_vars = 0;
187 
188  for (auto iter = t1_vars.begin(); nb_t1_t2_vars != t1_and_t2_vars.size();
189  ++iter, ++nb_t1_t2_vars)
190  if (*iter != t1_and_t2_vars[nb_t1_t2_vars]) break;
191 
192  if (nb_t1_t2_vars == t1_and_t2_vars.size()) {
193  nb_t1_t2_vars = 0;
194 
195  for (auto iter = t2_vars.begin(); nb_t1_t2_vars != t1_and_t2_vars.size();
196  ++iter, ++nb_t1_t2_vars)
197  if (*iter != t1_and_t2_vars[nb_t1_t2_vars]) break;
198 
199  if (nb_t1_t2_vars == t1_and_t2_vars.size()) {
200  t1_and_t2_begin_vars = true;
201  }
202  }
203  }
204 
205  // when we will parse t1 and t2 to fill the result table t1+t2, we will use
206  // variables txxx_value : at the beginning they are initialized to the
207  // domain size of the variables (which are, themselves initialized to 0).
208  // Each time we increment a variable (that is, we increase the offset of
209  // the table by txxx_offset), its corresponding txxx_value is decreased by
210  // 1. When the latter is equal to 0, this means that the variable itself
211  // should be reinitialized to 0 as well and that the next variable of the
212  // table should be increased (that is, this is similar to increasing 9 to
213  // 10). As such the offset of txxx should be decreased by txxx_offset * the
214  // domain size of the variable. This quantity is precisely what is stored
215  // into variables txxx_down.
216  std::vector< Idx > t1_and_t2_value = t1_and_t2_domain;
217  std::vector< Idx > t1_and_t2_1_down = t1_and_t2_1_offset;
218  std::vector< Idx > t1_and_t2_2_down = t1_and_t2_2_offset;
219 
220  for (unsigned int i = 0; i < t1_and_t2_domain.size(); ++i) {
221  t1_and_t2_1_down[i] *= (t1_and_t2_domain[i] - 1);
222  t1_and_t2_2_down[i] *= (t1_and_t2_domain[i] - 1);
223  }
224 
225  std::vector< Idx > t1_alone_value = t1_alone_domain;
226  std::vector< Idx > t1_alone_down = t1_alone_offset;
227 
228  for (unsigned int i = 0; i < t1_alone_domain.size(); ++i) {
229  t1_alone_down[i] *= (t1_alone_domain[i] - 1);
230  }
231 
232  std::vector< Idx > t2_alone_value = t2_alone_domain;
233  std::vector< Idx > t2_alone_down = t2_alone_offset;
234 
235  for (unsigned int i = 0; i < t2_alone_domain.size(); ++i) {
236  t2_alone_down[i] *= (t2_alone_domain[i] - 1);
237  }
238 
239  // create a table "result" containing all the variables: the first
240  // variables are those that belong to both t1 and t2. The next variables
241  // are those that belong to t2 but not to t1. Finally, the last variables
242  // are those that belong to t1 but not t2. This order will be used in the
243  // next for loops.
244  MultiDimArray< GUM_MULTI_DIM_OPERATOR_TYPE >* result
245  = new MultiDimArray< GUM_MULTI_DIM_OPERATOR_TYPE >;
246  result->beginMultipleChanges();
247 
248  for (const auto var: t1_vars)
249  if (t2_vars.exists(var)) *result << *var;
250 
251  for (const auto var: t2_vars)
252  if (!t1_vars.exists(var)) *result << *var;
253 
254  for (const auto var: t1_vars)
255  if (!t2_vars.exists(var)) *result << *var;
256 
257  result->endMultipleChanges();
258 
259  // here we fill result. The idea is to use 3 loops. The innermost loop
260  // corresponds to the variables that belongs both to t1 and t2. The middle
261  // loop to the variables that belong to t2 but not to t1. Finally, the
262  // outer loop corresponds to the variables that belong to t1 but not t2.
263  GUM_MULTI_DIM_OPERATOR_TYPE* pt1
264  = const_cast< GUM_MULTI_DIM_OPERATOR_TYPE* >(&(t1->unsafeGet(0)));
265  GUM_MULTI_DIM_OPERATOR_TYPE* pt2
266  = const_cast< GUM_MULTI_DIM_OPERATOR_TYPE* >(&(t2->unsafeGet(0)));
267  GUM_MULTI_DIM_OPERATOR_TYPE* pres
268  = const_cast< GUM_MULTI_DIM_OPERATOR_TYPE* >(&(result->unsafeGet(0)));
269  GUM_MULTI_DIM_OPERATOR_TYPE* pt2_deb = pt2;
270  GUM_MULTI_DIM_OPERATOR_TYPE* pt1_alone_begin;
271 
272  // test if all the variables in common in t1 and t2 are the first variables
273  // and are in the same order. In this case, we can speed-up the
274  // incrementation processes
275  if (t1_and_t2_begin_vars) {
276  for (Idx i = 0; i < t1_alone_domain_size; ++i) {
277  pt2 = pt2_deb;
278  pt1_alone_begin = pt1;
279 
280  for (Idx j = 0; j < t2_alone_domain_size; ++j) {
281  pt1 = pt1_alone_begin;
282 
283  for (Idx z = 0; z < t1_and_t2_domain_size; ++z) {
284  *pres = GUM_MULTI_DIM_OPERATOR(*pt1, *pt2);
285  ++pres;
286 
287  // update the offset of both t1 and t2
288  ++pt1;
289  ++pt2;
290  }
291  }
292  }
293  } else {
294  Idx t1_offset = 0;
295  Idx t2_offset = 0;
296  Idx t1_alone_begin_offset = 0;
297 
298  for (Idx i = 0; i < t1_alone_domain_size; ++i) {
299  t2_offset = 0;
300  t1_alone_begin_offset = t1_offset;
301 
302  for (Idx j = 0; j < t2_alone_domain_size; ++j) {
303  t1_offset = t1_alone_begin_offset;
304 
305  for (Idx z = 0; z < t1_and_t2_domain_size; ++z) {
306  *pres = GUM_MULTI_DIM_OPERATOR(pt1[t1_offset], pt2[t2_offset]);
307  ++pres;
308 
309  // update the offset of both t1 and t2
310  for (unsigned int k = 0; k < t1_and_t2_value.size(); ++k) {
311  if (--t1_and_t2_value[k]) {
312  t1_offset += t1_and_t2_1_offset[k];
313  t2_offset += t1_and_t2_2_offset[k];
314  break;
315  }
316 
317  t1_and_t2_value[k] = t1_and_t2_domain[k];
318  t1_offset -= t1_and_t2_1_down[k];
319  t2_offset -= t1_and_t2_2_down[k];
320  }
321  }
322 
323  // update the offset of t2 alone
324  for (unsigned int k = 0; k < t2_alone_value.size(); ++k) {
325  if (--t2_alone_value[k]) {
326  t2_offset += t2_alone_offset[k];
327  break;
328  }
329 
330  t2_alone_value[k] = t2_alone_domain[k];
331  t2_offset -= t2_alone_down[k];
332  }
333  }
334 
335  // update the offset of t1 alone
336  for (unsigned int k = 0; k < t1_alone_value.size(); ++k) {
337  if (--t1_alone_value[k]) {
338  t1_offset += t1_alone_offset[k];
339  break;
340  }
341 
342  t1_alone_value[k] = t1_alone_domain[k];
343  t1_offset -= t1_alone_down[k];
344  }
345  }
346  }
347 
348  return result;
349  }
350 
351 # undef GUM_MULTI_DIM_OPERATOR_TYPE
352 } // namespace gum
353 
354 #endif /* GUM_OPERATOR_PATTERN_ALLOWED */