aGrUM 3.1.1
a C++ library for (probabilistic) graphical models
recordCounter.cpp
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
47
49
50#ifndef DOXYGEN_SHOULD_SKIP_THIS
51
53# ifdef GUM_NO_INLINE
55# endif /* GUM_NO_INLINE */
56
57namespace gum {
58
59 namespace learning {
60
63 const std::vector< std::pair< std::size_t, std::size_t > >& ranges,
64 const Bijection< NodeId, std::size_t >& nodeId2columns) :
65 _nodeId2columns_(nodeId2columns) {
66 // check that the columns in nodeId2columns do belong to the database
67 const std::size_t db_nb_cols = parser.database().nbVariables();
68 for (auto iter = nodeId2columns.cbegin(); iter != nodeId2columns.cend(); ++iter) {
69 if (iter.second() >= db_nb_cols) {
70 GUM_ERROR(OutOfBounds,
71 "the mapping between ids and database columns "
72 << "is incorrect because Column " << iter.second()
73 << " does not belong to the database.");
74 }
75 }
76
77 // create the parsers. There should always be at least one parser
78 const auto max_nb_threads = ThreadNumberManager::getNumberOfThreads();
79 _parsers_.reserve(max_nb_threads);
80 for (std::size_t i = std::size_t(0); i < max_nb_threads; ++i)
81 _parsers_.push_back(parser);
82
83 // check that the ranges are within the bounds of the database and
84 // save them
85 _checkRanges_(ranges);
86 _ranges_.reserve(ranges.size());
87 for (const auto& range: ranges)
88 _ranges_.push_back(range);
89
90 // dispatch the ranges for the threads
91 _dispatchRangesToThreads_();
92
93 GUM_CONSTRUCTOR(RecordCounter);
94 }
95
97 RecordCounter::RecordCounter(const DBRowGeneratorParser& parser,
98 const Bijection< NodeId, std::size_t >& nodeId2columns) :
99 RecordCounter(parser,
100 std::vector< std::pair< std::size_t, std::size_t > >(),
101 nodeId2columns) {}
102
104 RecordCounter::RecordCounter(const RecordCounter& from) :
105 ThreadNumberManager(from), _parsers_(from._parsers_), _ranges_(from._ranges_),
106 _thread_ranges_(from._thread_ranges_), _nodeId2columns_(from._nodeId2columns_),
107 _last_DB_counting_(from._last_DB_counting_), _last_DB_ids_(from._last_DB_ids_),
108 _last_nonDB_counting_(from._last_nonDB_counting_), _last_nonDB_ids_(from._last_nonDB_ids_),
109 _min_nb_rows_per_thread_(from._min_nb_rows_per_thread_) {
110 GUM_CONS_CPY(RecordCounter);
111 }
112
114 RecordCounter::RecordCounter(RecordCounter&& from) :
115 ThreadNumberManager(std::move(from)), _parsers_(std::move(from._parsers_)),
116 _ranges_(std::move(from._ranges_)), _thread_ranges_(std::move(from._thread_ranges_)),
117 _nodeId2columns_(std::move(from._nodeId2columns_)),
118 _last_DB_counting_(std::move(from._last_DB_counting_)),
119 _last_DB_ids_(std::move(from._last_DB_ids_)),
120 _last_nonDB_counting_(std::move(from._last_nonDB_counting_)),
121 _last_nonDB_ids_(std::move(from._last_nonDB_ids_)),
122 _min_nb_rows_per_thread_(from._min_nb_rows_per_thread_) {
123 GUM_CONS_MOV(RecordCounter);
124 }
125
127 RecordCounter* RecordCounter::clone() const { return new RecordCounter(*this); }
128
130 RecordCounter::~RecordCounter() { GUM_DESTRUCTOR(RecordCounter); }
131
133 RecordCounter& RecordCounter::operator=(const RecordCounter& from) {
134 if (this != &from) {
135 ThreadNumberManager::operator=(from);
136 _parsers_ = from._parsers_;
137 _ranges_ = from._ranges_;
138 _thread_ranges_ = from._thread_ranges_;
139 _nodeId2columns_ = from._nodeId2columns_;
140 _last_DB_counting_ = from._last_DB_counting_;
141 _last_DB_ids_ = from._last_DB_ids_;
142 _last_nonDB_counting_ = from._last_nonDB_counting_;
143 _last_nonDB_ids_ = from._last_nonDB_ids_;
144 _min_nb_rows_per_thread_ = from._min_nb_rows_per_thread_;
145 }
146 return *this;
147 }
148
150 RecordCounter& RecordCounter::operator=(RecordCounter&& from) {
151 if (this != &from) {
152 ThreadNumberManager::operator=(std::move(from));
153 _parsers_ = std::move(from._parsers_);
154 _ranges_ = std::move(from._ranges_);
155 _thread_ranges_ = std::move(from._thread_ranges_);
156 _nodeId2columns_ = std::move(from._nodeId2columns_);
157 _last_DB_counting_ = std::move(from._last_DB_counting_);
158 _last_DB_ids_ = std::move(from._last_DB_ids_);
159 _last_nonDB_counting_ = std::move(from._last_nonDB_counting_);
160 _last_nonDB_ids_ = std::move(from._last_nonDB_ids_);
161 _min_nb_rows_per_thread_ = from._min_nb_rows_per_thread_;
162 }
163 return *this;
164 }
165
167 void RecordCounter::clear() {
168 _last_DB_counting_.clear();
169 _last_DB_ids_.clear();
170 _last_nonDB_counting_.clear();
171 _last_nonDB_ids_.clear();
172 }
173
174 // changes the number min of rows a thread should process in a
175 // multithreading context
176 void RecordCounter::setMinNbRowsPerThread(const std::size_t nb) const {
177 if (nb == std::size_t(0)) _min_nb_rows_per_thread_ = std::size_t(1);
178 else _min_nb_rows_per_thread_ = nb;
179 }
180
182 void RecordCounter::_raiseCheckException_(const std::vector< std::string >& bad_vars) const {
183 // generate the exception
184 if (bad_vars.size() == 1) {
186 std::format("Counts cannot be performed on continuous variables. "
187 "Unfortunately the following variable is continuous: {}",
188 bad_vars[0]))
189 } else {
190 std::string varList;
191 bool first = true;
192 for (const auto& name: bad_vars) {
193 if (!first) varList += ", ";
194 first = false;
195 varList += name;
196 }
198 std::format("Counts cannot be performed on continuous variables. "
199 "Unfortunately the following variables are continuous: {}",
200 varList))
201 }
202 }
203
205 void RecordCounter::_checkDiscreteVariables_(const IdCondSet& ids) const {
206 const std::size_t size = ids.size();
207 const DatabaseTable& database = _parsers_[0].data.database();
208
209 if (_nodeId2columns_.empty()) {
210 // check all the ids
211 for (std::size_t i = std::size_t(0); i < size; ++i) {
212 if (database.variable(i).varType() == VarType::CONTINUOUS) {
213 // here, var i does not correspond to a discrete variable.
214 // we check whether there are other non discrete variables, so that
215 // we can generate an exception mentioning all these variables
216 std::vector< std::string > bad_vars{database.variable(i).name()};
217 for (++i; i < size; ++i) {
218 if (database.variable(i).varType() == VarType::CONTINUOUS)
219 bad_vars.push_back(database.variable(i).name());
220 }
221 _raiseCheckException_(bad_vars);
222 }
223 }
224 } else {
225 // check all the ids
226 for (std::size_t i = std::size_t(0); i < size; ++i) {
227 // get the position of the variable in the database
228 std::size_t pos = _nodeId2columns_.second(ids[i]);
229
230 if (database.variable(pos).varType() == VarType::CONTINUOUS) {
231 // here, id does not correspond to a discrete variable.
232 // we check whether there are other non discrete variables, so that
233 // we can generate an exception mentioning all these variables
234 std::vector< std::string > bad_vars{database.variable(pos).name()};
235 for (++i; i < size; ++i) {
236 pos = _nodeId2columns_.second(ids[i]);
237 if (database.variable(pos).varType() == VarType::CONTINUOUS)
238 bad_vars.push_back(database.variable(pos).name());
239 }
240 _raiseCheckException_(bad_vars);
241 }
242 }
243 }
244 }
245
246 // returns a mapping from the nodes ids to the columns of the database
247 // for a given sequence of ids
248 HashTable< NodeId, std::size_t >
249 RecordCounter::_getNodeIds2Columns_(const IdCondSet& ids) const {
250 HashTable< NodeId, std::size_t > res(ids.size());
251 if (_nodeId2columns_.empty()) {
252 for (const auto id: ids) {
253 res.insert(id, std::size_t(id));
254 }
255 } else {
256 for (const auto id: ids) {
257 res.insert(id, _nodeId2columns_.second(id));
258 }
259 }
260 return res;
261 }
262
264 std::vector< double >&
265 RecordCounter::_extractFromCountings_(const IdCondSet& subset_ids,
266 const IdCondSet& superset_ids,
267 const std::vector< double >& superset_vect) {
268 // get a mapping between the node Ids and their columns in the database.
269 // This should be stored into _nodeId2columns_, except if the latter is
270 // empty, in which case there is an identity mapping
271 const auto nodeId2columns = _getNodeIds2Columns_(superset_ids);
272
273 // we first determine the size of the output vector, the domain of
274 // each of its variables and their offsets in the output vector
275 const auto& database = _parsers_[0].data.database();
276 std::size_t result_vect_size = std::size_t(1);
277 for (const auto id: subset_ids) {
278 result_vect_size *= database.domainSize(nodeId2columns[id]);
279 }
280
281 // we create the output vector
282 std::vector< double > result_vect(result_vect_size, 0.0);
283
284 // check if the subset_ids is the beginning of the sequence of superset_ids
285 // if this is the case, then we can outer loop over the variables not in
286 // subset_ids and, for each iteration of this loop add a vector of size
287 // result_size to result_vect
288 bool subset_begin = true;
289 const std::size_t subset_ids_size = std::size_t(subset_ids.size());
290 for (std::size_t i = 0; i < subset_ids_size; ++i) {
291 if (superset_ids.pos(subset_ids[i]) != i) {
292 subset_begin = false;
293 break;
294 }
295 }
296
297 if (subset_begin) {
298 const std::size_t superset_vect_size = superset_vect.size();
299 std::size_t i = std::size_t(0);
300 while (i < superset_vect_size) {
301 for (std::size_t j = std::size_t(0); j < result_vect_size; ++j, ++i) {
302 result_vect[j] += superset_vect[i];
303 }
304 }
305
306 // save the subset_ids and the result vector
307 try {
308 _last_nonDB_ids_ = subset_ids;
309 _last_nonDB_counting_ = std::move(result_vect);
310 return _last_nonDB_counting_;
311 } catch (...) {
312 _last_nonDB_ids_.clear();
313 _last_nonDB_counting_.clear();
314 throw;
315 }
316 }
317
318
319 // check if subset_ids is the end of the sequence of superset_ids.
320 // In this case, as above, there are two simple loops to perform the
321 // counts
322 bool subset_end = true;
323 const std::size_t superset_ids_size = std::size_t(superset_ids.size());
324 for (std::size_t i = 0; i < subset_ids_size; ++i) {
325 if (superset_ids.pos(subset_ids[i]) != i + superset_ids_size - subset_ids_size) {
326 subset_end = false;
327 break;
328 }
329 }
330
331 if (subset_end) {
332 // determine the size of the vector corresponding to the variables
333 // not belonging to subset_ids
334 std::size_t vect_not_subset_size = std::size_t(1);
335 for (std::size_t i = std::size_t(0); i < superset_ids_size - subset_ids_size; ++i)
336 vect_not_subset_size *= database.domainSize(nodeId2columns[superset_ids[i]]);
337
338 // perform the two loops
339 std::size_t i = std::size_t(0);
340 for (std::size_t j = std::size_t(0); j < result_vect_size; ++j) {
341 for (std::size_t k = std::size_t(0); k < vect_not_subset_size; ++k, ++i) {
342 result_vect[j] += superset_vect[i];
343 }
344 }
345
346 // save the subset_ids and the result vector
347 try {
348 _last_nonDB_ids_ = subset_ids;
349 _last_nonDB_counting_ = std::move(result_vect);
350 return _last_nonDB_counting_;
351 } catch (...) {
352 _last_nonDB_ids_.clear();
353 _last_nonDB_counting_.clear();
354 throw;
355 }
356 }
357
358 // here subset_ids is a subset of superset_ids neither prefixing nor
359 // postfixing it. So the computation is somewhat more complicated.
360
361 // We will parse the superset_vect sequentially (using ++ operator).
362 // Sometimes, we will need to change the offset of the cell of result_vect
363 // that will be affected, sometimes not. Vector before_incr will indicate
364 // whether we need to change the offset (value = 0) or not (value different
365 // from 0). Vectors result_domain will indicate how this offset should be
366 // computed. Here is an example of the values of these vectors. Assume that
367 // superset_ids = <A,B,C,D,E> and subset_ids = <A,D,C>. Then, the three
368 // vectors before_incr, result_domain and result_offset are indexed w.r.t.
369 // A,C,D, i.e., w.r.t. to the variables in subset_ids but order w.r.t.
370 // superset_ids (this is convenient as we will parse superset_vect
371 // sequentially. For a variable or a set of variables X, let M_X denote the
372 // domain size of X. Then the contents of the three vectors are as follows:
373 // before_incr = {0, M_B, 0} (this means that whenever we iterate over B's
374 // values, the offset in result_vect does not change)
375 // result_domain = { M_A, M_C, M_D } (i.e., the domain sizes of the variables
376 // in subset_ids, order w.r.t. superset_ids)
377 // result_offset = { 1, M_A*M_D, M_A } (this corresponds to the offsets
378 // in result_vect of variables A, C and D)
379 // Vector superset_order = { 0, 2, 1} : this is a map from the indices of
380 // the variables in subset_ids to the indices of these variables in the
381 // three vectors described above. For instance, the "2" means that variable
382 // D (which is at index 1 in subset_ids) is located at index 2 in vector
383 // before_incr
384 std::vector< std::size_t > before_incr(subset_ids_size);
385 std::vector< std::size_t > result_domain(subset_ids_size);
386 std::vector< std::size_t > result_offset(subset_ids_size);
387 {
388 std::size_t result_domain_size = std::size_t(1);
389 std::size_t tmp_before_incr = std::size_t(1);
390 std::vector< std::size_t > superset_order(subset_ids_size);
391
392 for (std::size_t h = std::size_t(0), j = std::size_t(0); j < subset_ids_size; ++h) {
393 if (subset_ids.exists(superset_ids[h])) {
394 before_incr[j] = tmp_before_incr - 1;
395 superset_order[subset_ids.pos(superset_ids[h])] = j;
396 tmp_before_incr = 1;
397 ++j;
398 } else {
399 tmp_before_incr *= database.domainSize(nodeId2columns[superset_ids[h]]);
400 }
401 }
402
403 // compute the offsets in the order of the superset_ids
404 for (std::size_t i = 0; i < subset_ids.size(); ++i) {
405 const std::size_t domain_size = database.domainSize(nodeId2columns[subset_ids[i]]);
406 const std::size_t j = superset_order[i];
407 result_domain[j] = domain_size;
408 result_offset[j] = result_domain_size;
409 result_domain_size *= domain_size;
410 }
411 }
412
413 std::vector< std::size_t > result_value(result_domain);
414 std::vector< std::size_t > current_incr(before_incr);
415 std::vector< std::size_t > result_down(result_offset);
416
417 for (std::size_t j = std::size_t(0); j < result_down.size(); ++j) {
418 result_down[j] *= (result_domain[j] - 1);
419 }
420
421 // now we can loop over the superset_vect to fill result_vect
422 const std::size_t superset_vect_size = superset_vect.size();
423 std::size_t the_result_offset = std::size_t(0);
424 for (std::size_t h = std::size_t(0); h < superset_vect_size; ++h) {
425 result_vect[the_result_offset] += superset_vect[h];
426
427 // update the offset of result_vect
428 for (std::size_t k = 0; k < current_incr.size(); ++k) {
429 // check if we need modify result_offset
430 if (current_incr[k]) {
431 --current_incr[k];
432 break;
433 }
434
435 current_incr[k] = before_incr[k];
436
437 // here we shall modify result_offset
438 --result_value[k];
439
440 if (result_value[k]) {
441 the_result_offset += result_offset[k];
442 break;
443 }
444
445 result_value[k] = result_domain[k];
446 the_result_offset -= result_down[k];
447 }
448 }
449
450 // save the subset_ids and the result vector
451 try {
452 _last_nonDB_ids_ = subset_ids;
453 _last_nonDB_counting_ = std::move(result_vect);
454 return _last_nonDB_counting_;
455 } catch (...) {
456 _last_nonDB_ids_.clear();
457 _last_nonDB_counting_.clear();
458 throw;
459 }
460 }
461
463 std::vector< double >& RecordCounter::_countFromDatabase_(const IdCondSet& ids) {
464 // if the ids vector is empty or the database is empty, return an
465 // empty vector
466 const auto& database = _parsers_[0].data.database();
467 if (ids.empty() || database.empty() || _thread_ranges_.empty()) {
468 _last_nonDB_counting_.clear();
469 _last_nonDB_ids_.clear();
470 return _last_nonDB_counting_;
471 }
472
473 // we translate the ids into their corresponding columns in the
474 // DatabaseTable
475 const auto nodeId2columns = _getNodeIds2Columns_(ids);
476
477 // we first determine the size of the counting vector, the domain of
478 // each of its variables and their offsets in the output vector
479 const std::size_t ids_size = ids.size();
480 std::size_t counting_vect_size = std::size_t(1);
481 std::vector< std::size_t > domain_sizes(ids_size);
482 std::vector< std::pair< std::size_t, std::size_t > > cols_offsets(ids_size);
483 {
484 std::size_t i = std::size_t(0);
485 for (const auto id: ids) {
486 const std::size_t domain_size = database.domainSize(nodeId2columns[id]);
487 domain_sizes[i] = domain_size;
488 cols_offsets[i].first = nodeId2columns[id];
489 cols_offsets[i].second = counting_vect_size;
490 counting_vect_size *= domain_size;
491 ++i;
492 }
493 }
494
495 // we sort the columns and offsets by increasing column index. This
496 // may speed up threaded counts by improving the cacheline hits
497 std::sort(
498 cols_offsets.begin(),
499 cols_offsets.end(),
500 [](const std::pair< std::size_t, std::size_t >& a,
501 const std::pair< std::size_t, std::size_t >& b) -> bool { return a.first < b.first; });
502
503 // create parsers if needed
504 const std::size_t nb_ranges = _thread_ranges_.size();
505 const auto max_nb_threads = ThreadNumberManager::getNumberOfThreads();
506 const std::size_t nb_threads = nb_ranges <= max_nb_threads ? nb_ranges : max_nb_threads;
507 while (_parsers_.size() < nb_threads) {
508 ThreadData< DBRowGeneratorParser > new_parser(_parsers_[0]);
509 _parsers_.push_back(std::move(new_parser));
510 }
511
512 // set the columns of interest for each parser. This specifies to the
513 // parser which columns are used for the counts. This is important
514 // for parsers like the EM parser that complete unobserved variables.
515 std::vector< std::size_t > cols_of_interest(ids_size);
516 for (std::size_t i = std::size_t(0); i < ids_size; ++i) {
517 cols_of_interest[i] = cols_offsets[i].first;
518 }
519 for (auto& parser: _parsers_) {
520 parser.data.setColumnsOfInterest(cols_of_interest);
521 }
522
523 // allocate all the counting vectors, including that which will add
524 // all the results provided by the threads. We initialize once and
525 // for all these vectors with zeroes
526 std::vector< double > counting_vect(counting_vect_size, 0.0);
527 std::vector< ThreadData< std::vector< double > > > thread_countings(
528 nb_threads,
529 ThreadData< std::vector< double > >(counting_vect));
530
531 // here, we create a lambda that will be executed by all the threads
532 // to perform the counts in a parallel manner
533 auto threadedCount = [this, nb_ranges, ids_size, &thread_countings, cols_offsets](
534 const std::size_t this_thread,
535 const std::size_t nb_threads,
536 const std::size_t nb_loop) -> void {
537 if (this_thread + nb_loop < nb_ranges) {
538 // get the database parser and the contingency table to fill
539 DBRowGeneratorParser& parser = this->_parsers_[this_thread].data;
540 parser.setRange(this->_thread_ranges_[this_thread + nb_loop].first,
541 this->_thread_ranges_[this_thread + nb_loop].second);
542 std::vector< double >& counts = thread_countings[this_thread].data;
543
544 // parse the database
545 try {
546 while (parser.hasRows()) {
547 // get the observed rows
548 const DBRow< DBTranslatedValue >& row = parser.row();
549
550 // fill the counts for the current row
551 std::size_t offset = std::size_t(0);
552 for (std::size_t i = std::size_t(0); i < ids_size; ++i) {
553 offset += row[cols_offsets[i].first].discr_val * cols_offsets[i].second;
554 }
555
556 counts[offset] += row.weight();
557 }
558 } catch (NotFound const&) {} // this exception is raised by the row filter
559 // if the row generators create no output row
560 // from the last rows of the database
561 }
562 };
563
564
565 // launch the threads
566 for (std::size_t i = std::size_t(0); i < nb_ranges; i += nb_threads) {
567 ThreadExecutor::execute(nb_threads, threadedCount, i);
568 }
569
570
571 // add the counts to counting_vect
572 for (std::size_t k = std::size_t(0); k < nb_threads; ++k) {
573 const auto& thread_counting = thread_countings[k].data;
574 for (std::size_t r = std::size_t(0); r < counting_vect_size; ++r) {
575 counting_vect[r] += thread_counting[r];
576 }
577 }
578
579 // save the final results
580 _last_DB_ids_ = ids;
581 _last_DB_counting_ = std::move(counting_vect);
582
583 return _last_DB_counting_;
584 }
585
587 void RecordCounter::_checkRanges_(
588 const std::vector< std::pair< std::size_t, std::size_t > >& new_ranges) const {
589 const std::size_t dbsize = _parsers_[0].data.database().nbRows();
590 std::vector< std::pair< std::size_t, std::size_t > > incorrect_ranges;
591 for (const auto& range: new_ranges) {
592 if ((range.first >= range.second) || (range.second > dbsize)) {
593 incorrect_ranges.push_back(range);
594 }
595 }
596 if (!incorrect_ranges.empty()) {
597 std::string rangeList;
598 bool first = true;
599 for (const auto& range: incorrect_ranges) {
600 if (!first) rangeList += ", ";
601 first = false;
602 rangeList += std::format("[{};{})", range.first, range.second);
603 }
604 if (incorrect_ranges.size() > 1) {
606 std::format("It is impossible to set the ranges because the following ones "
607 "are incorrect: {}",
608 rangeList))
609 } else {
611 std::format("It is impossible to set the ranges because the following one "
612 "is incorrect: {}",
613 rangeList))
614 }
615 }
616 }
617
619 void RecordCounter::_dispatchRangesToThreads_() {
620 _thread_ranges_.clear();
621
622 // ensure that _ranges_ contains the ranges asked by the user
623 bool add_range = false;
624 if (_ranges_.empty()) {
625 const auto& database = _parsers_[0].data.database();
626 _ranges_.push_back(
627 std::pair< std::size_t, std::size_t >(std::size_t(0), database.nbRows()));
628 add_range = true;
629 }
630
631 // dispatch the ranges
632 const auto max_nb_threads = ThreadNumberManager::getNumberOfThreads();
633 for (const auto& range: _ranges_) {
634 if (range.second > range.first) {
635 const std::size_t range_size = range.second - range.first;
636 std::size_t nb_threads = range_size / _min_nb_rows_per_thread_;
637 if (nb_threads < 1) nb_threads = 1;
638 else if (nb_threads > max_nb_threads) nb_threads = max_nb_threads;
639 std::size_t nb_rows_par_thread = range_size / nb_threads;
640 std::size_t rest_rows = range_size - nb_rows_par_thread * nb_threads;
641
642 std::size_t begin_index = range.first;
643 for (std::size_t i = std::size_t(0); i < nb_threads; ++i) {
644 std::size_t end_index = begin_index + nb_rows_par_thread;
645 if (rest_rows != std::size_t(0)) {
646 ++end_index;
647 --rest_rows;
648 }
649 _thread_ranges_.push_back(
650 std::pair< std::size_t, std::size_t >(begin_index, end_index));
651 begin_index = end_index;
652 }
653 }
654 }
655 if (add_range) _ranges_.clear();
656
657 // sort ranges by decreasing range size, so that if the number of
658 // ranges exceeds the number of threads allowed, we start a first round of
659 // threads with the highest range, then another round with lower ranges,
660 // and so on until all the ranges have been processed
661 std::sort(_thread_ranges_.begin(),
662 _thread_ranges_.end(),
663 [](const std::pair< std::size_t, std::size_t >& a,
664 const std::pair< std::size_t, std::size_t >& b) -> bool {
665 return (a.second - a.first) > (b.second - b.first);
666 });
667 }
668
670 void RecordCounter::setRanges(
671 const std::vector< std::pair< std::size_t, std::size_t > >& new_ranges) {
672 // first, we check that all ranges are within the database's bounds
673 _checkRanges_(new_ranges);
674
675 // since the ranges are OK, save them and clear the counting caches
676 const std::size_t new_size = new_ranges.size();
677 std::vector< std::pair< std::size_t, std::size_t > > ranges(new_size);
678 for (std::size_t i = std::size_t(0); i < new_size; ++i) {
679 ranges[i].first = new_ranges[i].first;
680 ranges[i].second = new_ranges[i].second;
681 }
682
683 clear();
684 _ranges_ = std::move(ranges);
685
686 // dispatch the ranges to the threads
687 _dispatchRangesToThreads_();
688 }
689
691 void RecordCounter::clearRanges() {
692 if (_ranges_.empty()) return;
693 clear();
694 _ranges_.clear();
695 _dispatchRangesToThreads_();
696 }
697
698
699 } /* namespace learning */
700
701} /* namespace gum */
702
703#endif /* DOXYGEN_SHOULD_SKIP_THIS */
Exception : the element we looked for cannot be found.
Exception : out of bound.
Exception : wrong type for this operation.
the class used to read a row in the database and to transform it into a set of DBRow instances that c...
RecordCounter(const DBRowGeneratorParser &parser, const std::vector< std::pair< std::size_t, std::size_t > > &ranges, const Bijection< NodeId, std::size_t > &nodeId2columns=Bijection< NodeId, std::size_t >())
default constructor
#define GUM_ERROR(type, msg)
Definition exceptions.h:76
include the inlined functions if necessary
Definition CSVParser.h:55
gum is the global namespace for all aGrUM entities
Definition agrum.h:46
STL namespace.
The class that computes counting of observations from the database.