aGrUM 3.1.1
a C++ library for (probabilistic) graphical models
inferenceEngine_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
48
49#include <algorithm>
50
51#include <agrum/agrum.h>
52
54
55namespace gum {
56 namespace credal {
57
58 /*template< GumScalar GUM_SCALAR >
59 InferenceEngine< GUM_SCALAR >::InferenceEngine () : ApproximationScheme() {
60 std::cout << "InferenceEngine construct ()" << std::endl;
61 GUM_CONSTRUCTOR ( InferenceEngine )
62 }*/
63
64 template < GUM_Numeric GUM_SCALAR >
75
76 template < GUM_Numeric GUM_SCALAR >
80
81 template < GUM_Numeric GUM_SCALAR >
85
86 template < GUM_Numeric GUM_SCALAR >
88 evidence_.clear();
89 query_.clear();
90 /*
91 marginalMin_.clear();
92 marginalMax_.clear();
93 oldMarginalMin_.clear();
94 oldMarginalMax_.clear();
95 */
97 /*
98 expectationMin_.clear();
99 expectationMax_.clear();
100 */
102
103 // marginalSets_.clear();
105
106 dynamicExpMin_.clear();
107 dynamicExpMax_.clear();
108
109 //_modal.clear();
110
111 //_t0.clear();
112 //_t1.clear();
113 }
114
115 /*
116 template< GumScalar GUM_SCALAR >
117 void InferenceEngine< GUM_SCALAR >::setIterStop ( const int &iter_stop ) {
118 iterStop_ = iter_stop;
119 }*/
120
121 template < GUM_Numeric GUM_SCALAR >
123 storeBNOpt_ = value;
124 }
125
126 template < GUM_Numeric GUM_SCALAR >
128 storeVertices_ = value;
129
130 if (value) initMarginalSets_();
131 }
132
133 template < GUM_Numeric GUM_SCALAR >
135 bool oldValue = repetitiveInd_;
136 repetitiveInd_ = repetitive;
137
138 // do not compute clusters more than once
139 if (repetitiveInd_ && !oldValue) repetitiveInit_();
140 }
141
142 template < GUM_Numeric GUM_SCALAR >
146
147 /*
148 template< GumScalar GUM_SCALAR >
149 int InferenceEngine< GUM_SCALAR >::iterStop () const {
150 return iterStop_;
151 }*/
152
153 template < GUM_Numeric GUM_SCALAR >
157
158 template < GUM_Numeric GUM_SCALAR >
162
163 template < GUM_Numeric GUM_SCALAR >
164 VarMod2BNsMap< GUM_SCALAR >* InferenceEngine< GUM_SCALAR >::getVarMod2BNsMap() {
165 return &dbnOpt_;
166 }
167
168 template < GUM_Numeric GUM_SCALAR >
170 std::ifstream mod_stream(std::string(path), std::ios::in);
171
172 if (!mod_stream.good()) {
174 "void InferenceEngine< GUM_SCALAR "
175 ">::insertModals(const std::string & path) : "
176 "could not open input file : "
177 << path);
178 }
179
180 if (!modal_.empty()) modal_.clear();
181
182 std::string line, tmp;
183 char * cstr, *p;
184
185 while (mod_stream.good()) {
186 getline(mod_stream, line);
187
188 if (line.size() == 0) continue;
190 cstr = new char[line.size() + 1];
191 strcpy(cstr, line.c_str());
192
193 p = strtok(cstr, " ");
194 tmp = p;
195
196 std::vector< GUM_SCALAR > values;
197 p = strtok(nullptr, " ");
198
199 while (p != nullptr) {
200 values.push_back(GUM_SCALAR(atof(p)));
201 p = strtok(nullptr, " ");
202 } // end of : line
203
204 modal_.insert(tmp, values); //[tmp] = values;
205
206 delete[] p;
207 delete[] cstr;
208 } // end of : file
209
210 mod_stream.close();
211
213 }
214
215 template < GUM_Numeric GUM_SCALAR >
217 const std::map< std::string, std::vector< GUM_SCALAR > >& modals) {
218 if (!modal_.empty()) modal_.clear();
219
220 for (auto it = modals.cbegin(), theEnd = modals.cend(); it != theEnd; ++it) {
221 if (!credalNet_->current_bn().exists(it->first)) continue;
222
223 NodeId id = credalNet_->current_bn().idFromName(it->first);
224
225 // check that modals are net compatible
226 auto dSize = credalNet_->current_bn().variable(id).domainSize();
227
228 if (dSize != it->second.size()) continue;
229
230 // GUM_ERROR(OperationNotAllowed, "void InferenceEngine< GUM_SCALAR
231 // >::insertModals( const std::map< std::string, std::vector< GUM_SCALAR
232 // > >
233 // &modals) : modalities does not respect variable cardinality : " <<
234 // credalNet_->current_bn().variable( id ).name() << " : " << dSize << "
235 // != "
236 // << it->second.size());
237
238 modal_.insert(it->first, it->second); //[ it->first ] = it->second;
239 }
241 //_modal = modals;
242
244 }
245
246 template < GUM_Numeric GUM_SCALAR >
248 const std::map< std::string, std::vector< GUM_SCALAR > >& eviMap) {
249 if (!evidence_.empty()) evidence_.clear();
250
251 for (auto it = eviMap.cbegin(), theEnd = eviMap.cend(); it != theEnd; ++it) {
252 if (!credalNet_->current_bn().exists(it->first)) continue;
254 NodeId id = credalNet_->current_bn().idFromName(it->first);
255
256 evidence_.insert(id, it->second);
257 }
258
259 // forces the computation of the begin iterator to avoid subsequent data races
260 // @TODO make HashTableConstIterator constructors thread safe
261 evidence_.begin();
262 }
263
264 // check that observed variables DO exists in the network (otherwise Lazy
265 // report
266 // an error and app crash)
267 template < GUM_Numeric GUM_SCALAR >
269 const NodeProperty< std::vector< GUM_SCALAR > >& evidence) {
270 if (!evidence_.empty()) evidence_.clear();
271
272 // use cbegin() to get const_iterator when available in aGrUM hashtables
273 for (const auto& elt: evidence) {
274 if (!credalNet_->current_bn().exists(elt.first)) continue;
275
276 evidence_.insert(elt.first, elt.second);
277 }
278
279 // forces the computation of the begin iterator to avoid subsequent data races
280 // @TODO make HashTableConstIterator constructors thread safe
281 evidence_.begin();
282 }
283
284 template < GUM_Numeric GUM_SCALAR >
286 std::ifstream evi_stream(std::string(path), std::ios::in);
287
288 if (!evi_stream.good()) {
290 "void InferenceEngine< GUM_SCALAR "
291 ">::insertEvidence(const std::string & path) : could not "
292 "open input file : "
293 << path);
294 }
295
296 if (!evidence_.empty()) evidence_.clear();
298 std::string line, tmp;
299 char * cstr, *p;
300
301 while (evi_stream.good() && std::strcmp(line.c_str(), "[EVIDENCE]") != 0) {
302 getline(evi_stream, line);
304
305 while (evi_stream.good()) {
306 getline(evi_stream, line);
307
308 if (std::strcmp(line.c_str(), "[QUERY]") == 0) break;
310 if (line.size() == 0) continue;
311
312 cstr = new char[line.size() + 1];
313 strcpy(cstr, line.c_str());
314
315 p = strtok(cstr, " ");
316 tmp = p;
317
318 // if user input is wrong
319 NodeId node = -1;
320
321 if (!credalNet_->current_bn().exists(tmp)) continue;
322 node = credalNet_->current_bn().idFromName(tmp);
323
324 std::vector< GUM_SCALAR > values;
325 p = strtok(nullptr, " ");
326
327 while (p != nullptr) {
328 values.push_back(GUM_SCALAR(atof(p)));
329 p = strtok(nullptr, " ");
330 } // end of : line
331
332 evidence_.insert(node, values);
334 delete[] p;
335 delete[] cstr;
336 } // end of : file
337
338 evi_stream.close();
340 // forces the computation of the begin iterator to avoid subsequent data races
341 // @TODO make HashTableConstIterator constructors thread safe
342 evidence_.begin();
343 }
344
345 template < GUM_Numeric GUM_SCALAR >
347 const NodeProperty< std::vector< bool > >& query) {
348 if (!query_.empty()) query_.clear();
350 for (const auto& elt: query) {
351 if (!credalNet_->current_bn().exists(elt.first)) continue;
352
353 query_.insert(elt.first, elt.second);
354 }
356
357 template < GUM_Numeric GUM_SCALAR >
359 std::ifstream evi_stream(std::string(path), std::ios::in);
360
361 if (!evi_stream.good()) {
363 "void InferenceEngine< GUM_SCALAR >::insertQuery(const "
364 "std::string & path) : could not open input file : "
365 << path);
366 }
367
368 if (!query_.empty()) query_.clear();
369
370 std::string line, tmp;
371 char * cstr, *p;
373 while (evi_stream.good() && std::strcmp(line.c_str(), "[QUERY]") != 0) {
374 getline(evi_stream, line);
375 }
376
377 while (evi_stream.good()) {
378 getline(evi_stream, line);
379
380 if (std::strcmp(line.c_str(), "[EVIDENCE]") == 0) break;
381
382 if (line.size() == 0) continue;
383
384 cstr = new char[line.size() + 1];
385 strcpy(cstr, line.c_str());
386
387 p = strtok(cstr, " ");
388 tmp = p;
389
390 // if user input is wrong
391 NodeId node = -1;
392
393 if (!credalNet_->current_bn().exists(tmp)) continue;
394 node = credalNet_->current_bn().idFromName(tmp);
395
396 auto dSize = credalNet_->current_bn().variable(node).domainSize();
397
398 p = strtok(nullptr, " ");
399
400 if (p == nullptr) {
401 query_.insert(node, std::vector< bool >(dSize, true));
402 } else {
403 std::vector< bool > values(dSize, false);
404
405 while (p != nullptr) {
406 if ((Size)atoi(p) >= dSize)
408 "void InferenceEngine< GUM_SCALAR "
409 ">::insertQuery(const std::string & path) : "
410 "query modality is higher or equal to "
411 "cardinality");
412
413 values[atoi(p)] = true;
414 p = strtok(nullptr, " ");
415 } // end of : line
416
417 query_.insert(node, values);
418 }
419
420 delete[] p;
421 delete[] cstr;
422 } // end of : file
423
424 evi_stream.close();
425 }
426
427 template < GUM_Numeric GUM_SCALAR >
428 Tensor< GUM_SCALAR >
429 InferenceEngine< GUM_SCALAR >::marginalMin(std::string_view varName) const {
430 return marginalMin(credalNet_->current_bn().idFromName(varName));
431 }
433 template < GUM_Numeric GUM_SCALAR >
434 Tensor< GUM_SCALAR >
435 InferenceEngine< GUM_SCALAR >::marginalMax(std::string_view varName) const {
436 return marginalMax(credalNet_->current_bn().idFromName(varName));
437 }
439 template < GUM_Numeric GUM_SCALAR >
441 Tensor< GUM_SCALAR > res;
442 res.add(credalNet_->current_bn().variable(id));
443 res.fillWith(marginalMin_[id]);
444 return res;
445 }
446
447 template < GUM_Numeric GUM_SCALAR >
449 Tensor< GUM_SCALAR > res;
450 res.add(credalNet_->current_bn().variable(id));
451 res.fillWith(marginalMax_[id]);
452 return res;
453 }
454
455 template < GUM_Numeric GUM_SCALAR >
456 const GUM_SCALAR&
457 InferenceEngine< GUM_SCALAR >::expectationMin(std::string_view varName) const {
458 return expectationMin_[credalNet_->current_bn().idFromName(varName)];
459 }
460
461 template < GUM_Numeric GUM_SCALAR >
462 const GUM_SCALAR&
463 InferenceEngine< GUM_SCALAR >::expectationMax(std::string_view varName) const {
464 return expectationMax_[credalNet_->current_bn().idFromName(varName)];
465 }
466
467 template < GUM_Numeric GUM_SCALAR >
468 const GUM_SCALAR& InferenceEngine< GUM_SCALAR >::expectationMin(const NodeId id) const {
469 return expectationMin_[id];
470 }
471
472 template < GUM_Numeric GUM_SCALAR >
473 const GUM_SCALAR& InferenceEngine< GUM_SCALAR >::expectationMax(const NodeId id) const {
474 return expectationMax_[id];
475 }
476
477 template < GUM_Numeric GUM_SCALAR >
478 const std::vector< GUM_SCALAR >&
479 InferenceEngine< GUM_SCALAR >::dynamicExpMin(std::string_view varName) const {
480 std::string errTxt = "const std::vector< GUM_SCALAR > & InferenceEngine< "
481 "GUM_SCALAR >::dynamicExpMin ( const std::string & "
482 "varName ) const : ";
483
484 if (dynamicExpMin_.empty())
485 GUM_ERROR(OperationNotAllowed, errTxt + "_dynamicExpectations() needs to be called before")
486
487 auto p = dynamicExpMin_.tryGet(varName);
488 if (!p /*dynamicExpMin_.find(varName) == dynamicExpMin_.end()*/)
489 GUM_ERROR(NotFound, errTxt + "variable name not found : " << varName)
490
491 return *p;
492 }
493
494 template < GUM_Numeric GUM_SCALAR >
495 const std::vector< GUM_SCALAR >&
496 InferenceEngine< GUM_SCALAR >::dynamicExpMax(std::string_view varName) const {
497 std::string errTxt = "const std::vector< GUM_SCALAR > & InferenceEngine< "
498 "GUM_SCALAR >::dynamicExpMax ( const std::string & "
499 "varName ) const : ";
500
501 if (dynamicExpMax_.empty())
502 GUM_ERROR(OperationNotAllowed, errTxt + "_dynamicExpectations() needs to be called before")
503
504 auto p = dynamicExpMax_.tryGet(varName);
505 if (!p /*dynamicExpMin_.find(varName) == dynamicExpMin_.end()*/)
506 GUM_ERROR(NotFound, errTxt + "variable name not found : " << varName)
507
508 return *p;
509 }
510
511 template < GUM_Numeric GUM_SCALAR >
512 const std::vector< std::vector< GUM_SCALAR > >&
516
517 template < GUM_Numeric GUM_SCALAR >
518 void InferenceEngine< GUM_SCALAR >::saveMarginals(std::string_view path) const {
519 std::ofstream m_stream(std::string(path), std::ios::out | std::ios::trunc);
520
521 if (!m_stream.good()) {
523 "void InferenceEngine< GUM_SCALAR >::saveMarginals(const "
524 "std::string & path) const : could not open output file "
525 ": " << path);
526 }
527
528 for (const auto& elt: marginalMin_) {
529 Size esize = Size(elt.second.size());
530
531 for (Size mod = 0; mod < esize; mod++) {
532 m_stream << credalNet_->current_bn().variable(elt.first).name() << " " << mod << " "
533 << (elt.second)[mod] << " " << marginalMax_[elt.first][mod] << std::endl;
534 }
535 }
536
537 m_stream.close();
538 }
539
540 template < GUM_Numeric GUM_SCALAR >
541 void InferenceEngine< GUM_SCALAR >::saveExpectations(std::string_view path) const {
542 if (dynamicExpMin_.empty()) //_modal.empty())
543 return;
544
545 // else not here, to keep the const (natural with a saving process)
546 // else if(dynamicExpMin_.empty() || dynamicExpMax_.empty())
547 //_dynamicExpectations(); // works with or without a dynamic network
548
549 std::ofstream m_stream(std::string(path), std::ios::out | std::ios::trunc);
550
551 if (!m_stream.good()) {
553 "void InferenceEngine< GUM_SCALAR "
554 ">::saveExpectations(const std::string & path) : could "
555 "not open output file : "
556 << path);
557 }
558
559 for (const auto& elt: dynamicExpMin_) {
560 m_stream << elt.first; // it->first;
561
562 // iterates over a vector
563 for (const auto& elt2: elt.second) {
564 m_stream << " " << elt2;
565 }
566
567 m_stream << std::endl;
569
570 for (const auto& elt: dynamicExpMax_) {
571 m_stream << elt.first;
572
573 // iterates over a vector
574 for (const auto& elt2: elt.second) {
575 m_stream << " " << elt2;
576 }
577
578 m_stream << std::endl;
579 }
581 m_stream.close();
582 }
583
584 template < GUM_Numeric GUM_SCALAR >
586 std::stringstream output;
587 output << std::endl;
588
589 // use cbegin() when available
590 for (const auto& elt: marginalMin_) {
591 Size esize = Size(elt.second.size());
592
593 for (Size mod = 0; mod < esize; mod++) {
594 output << "P(" << credalNet_->current_bn().variable(elt.first).name() << "=" << mod
595 << "|e) = [ ";
596 output << marginalMin_[elt.first][mod] << ", " << marginalMax_[elt.first][mod] << " ]";
597
598 if (!query_.empty()) {
599 if (auto p_query = query_.tryGet(elt.first); p_query && (*p_query)[mod])
600 output << " QUERY";
601 }
602
603 output << std::endl;
604 }
605
606 output << std::endl;
607 }
608
609 return output.str();
610 }
611
612 template < GUM_Numeric GUM_SCALAR >
613 void InferenceEngine< GUM_SCALAR >::saveVertices(std::string_view path) const {
614 std::ofstream m_stream(std::string(path), std::ios::out | std::ios::trunc);
615
616 if (!m_stream.good()) {
618 "void InferenceEngine< GUM_SCALAR >::saveVertices(const "
619 "std::string & path) : could not open outpul file : "
620 << path);
621 }
622
623 for (const auto& elt: marginalSets_) {
624 m_stream << credalNet_->current_bn().variable(elt.first).name() << std::endl;
625
626 for (const auto& elt2: elt.second) {
627 m_stream << "[";
628 bool first = true;
629
630 for (const auto& elt3: elt2) {
631 if (!first) { m_stream << ","; }
632 first = false;
633 m_stream << elt3;
634 }
635
636 m_stream << "]\n";
637 }
638 }
639
640 m_stream.close();
641 }
642
643 template < GUM_Numeric GUM_SCALAR >
645 marginalMin_.clear();
646 marginalMax_.clear();
647 oldMarginalMin_.clear();
648 oldMarginalMax_.clear();
649
650 for (auto node: credalNet_->current_bn().nodes()) {
651 auto dSize = credalNet_->current_bn().variable(node).domainSize();
652 marginalMin_.insert(node, std::vector< GUM_SCALAR >(dSize, 1));
653 oldMarginalMin_.insert(node, std::vector< GUM_SCALAR >(dSize, 1));
654
655 marginalMax_.insert(node, std::vector< GUM_SCALAR >(dSize, 0));
656 oldMarginalMax_.insert(node, std::vector< GUM_SCALAR >(dSize, 0));
657 }
658
659 // now that we know the sizes of marginalMin_ and marginalMax_, we can
660 // dispatch their processes to the threads
662 }
663
664 template < GUM_Numeric GUM_SCALAR >
666 marginalSets_.clear();
667
668 if (!storeVertices_) return;
669
670 for (auto node: credalNet_->current_bn().nodes())
671 marginalSets_.insert(node, std::vector< std::vector< GUM_SCALAR > >());
672 }
673
674 // since only monitored variables in modal_ will be alble to compute
675 // expectations, it is useless to initialize those for all variables
676 // modal_ variables will always be checked further, so it is not necessary
677 // to
678 // check it here, but doing so will use less memory
679 template < GUM_Numeric GUM_SCALAR >
681 expectationMin_.clear();
682 expectationMax_.clear();
683
684 if (modal_.empty()) return;
685
686 for (auto node: credalNet_->current_bn().nodes()) {
687 std::string var_name, time_step;
688
689 var_name = credalNet_->current_bn().variable(node).name();
690 auto delim = var_name.find_first_of("_");
691 var_name = var_name.substr(0, delim);
692
693 auto p_modal = modal_.tryGet(var_name);
694 if (!p_modal) continue;
695
696 expectationMin_.insert(node, p_modal->back());
697 expectationMax_.insert(node, p_modal->front());
698 }
699 }
700
701 template < GUM_Numeric GUM_SCALAR >
705
706 template < GUM_Numeric GUM_SCALAR >
708 // no modals, no expectations computed during inference
709 if (expectationMin_.empty() || modal_.empty()) return;
710
711 // already called by the algorithm or the user
712 if (dynamicExpMax_.size() > 0 && dynamicExpMin_.size() > 0) return;
713
714 using innerMap = typename gum::HashTable< int, GUM_SCALAR >;
715
716 using outerMap = typename gum::HashTable< std::string, innerMap >;
717
718
719 // if non dynamic, directly save expectationMin_ et Max (same but faster)
720 outerMap expectationsMin, expectationsMax;
721
722 for (const auto& elt: expectationMin_) {
723 std::string var_name, time_step;
724
725 var_name = credalNet_->current_bn().variable(elt.first).name();
726 auto delim = var_name.find_first_of("_");
727 time_step = var_name.substr(delim + 1, var_name.size());
728 var_name = var_name.substr(0, delim);
729
730 // to be sure (don't store not monitored variables' expectations)
731 // although it
732 // should be taken care of before this point
733 if (!modal_.exists(var_name)) continue;
734
735 expectationsMin.getWithDefault(var_name, innerMap())
736 .getWithDefault(atoi(time_step.c_str()), 0)
737 = elt.second; // we iterate with min iterators
738 expectationsMax.getWithDefault(var_name, innerMap())
739 .getWithDefault(atoi(time_step.c_str()), 0) = expectationMax_[elt.first];
740 }
741
742 for (const auto& elt: expectationsMin) {
743 typename std::vector< GUM_SCALAR > dynExp(elt.second.size());
744
745 for (const auto& elt2: elt.second)
746 dynExp[elt2.first] = elt2.second;
747
748 dynamicExpMin_.insert(elt.first, dynExp);
749 }
750
751 for (const auto& elt: expectationsMax) {
752 typename std::vector< GUM_SCALAR > dynExp(elt.second.size());
753
754 for (const auto& elt2: elt.second) {
755 dynExp[elt2.first] = elt2.second;
756 }
757
758 dynamicExpMax_.insert(elt.first, dynExp);
759 }
760 }
761
762 template < GUM_Numeric GUM_SCALAR >
764 timeSteps_ = 0;
765 t0_.clear();
766 t1_.clear();
767
768 // t = 0 vars belongs to t0_ as keys
769 for (auto node: credalNet_->current_bn().internalDag().nodes()) {
770 std::string var_name = credalNet_->current_bn().variable(node).name();
771 auto delim = var_name.find_first_of("_");
772
773 if (delim > var_name.size()) {
775 "void InferenceEngine< GUM_SCALAR "
776 ">::repetitiveInit_() : the network does not "
777 "appear to be dynamic");
778 }
779
780 std::string time_step = var_name.substr(delim + 1, 1);
781
782 if (time_step.compare("0") == 0) t0_.insert(node, std::vector< NodeId >());
783 }
784
785 // t = 1 vars belongs to either t0_ as member value or t1_ as keys
786 for (const auto& node: credalNet_->current_bn().internalDag().nodes()) {
787 std::string var_name = credalNet_->current_bn().variable(node).name();
788 auto delim = var_name.find_first_of("_");
789 std::string time_step = var_name.substr(delim + 1, var_name.size());
790 var_name = var_name.substr(0, delim);
791 delim = time_step.find_first_of("_");
792 time_step = time_step.substr(0, delim);
793
794 if (time_step.compare("1") == 0) {
795 bool found = false;
796
797 for (const auto& elt: t0_) {
798 std::string var_0_name = credalNet_->current_bn().variable(elt.first).name();
799 delim = var_0_name.find_first_of("_");
800 var_0_name = var_0_name.substr(0, delim);
801
802 if (var_name.compare(var_0_name) == 0) {
803 const Tensor< GUM_SCALAR >* tensor(&credalNet_->current_bn().cpt(node));
804 const Tensor< GUM_SCALAR >* tensor2(&credalNet_->current_bn().cpt(elt.first));
805
806 if (tensor->domainSize() == tensor2->domainSize()) t0_[elt.first].push_back(node);
807 else t1_.insert(node, std::vector< NodeId >());
808
809 found = true;
810 break;
811 }
812 }
813
814 if (!found) { t1_.insert(node, std::vector< NodeId >()); }
815 }
816 }
817
818 // t > 1 vars belongs to either t0_ or t1_ as member value
819 // remember timeSteps_
820 for (auto node: credalNet_->current_bn().internalDag().nodes()) {
821 std::string var_name = credalNet_->current_bn().variable(node).name();
822 auto delim = var_name.find_first_of("_");
823 std::string time_step = var_name.substr(delim + 1, var_name.size());
824 var_name = var_name.substr(0, delim);
825 delim = time_step.find_first_of("_");
826 time_step = time_step.substr(0, delim);
827
828 if (time_step.compare("0") != 0 && time_step.compare("1") != 0) {
829 // keep max time_step
830 if (atoi(time_step.c_str()) > timeSteps_) timeSteps_ = atoi(time_step.c_str());
831
832 std::string var_0_name;
833 bool found = false;
834
835 for (const auto& elt: t0_) {
836 std::string var_0_name = credalNet_->current_bn().variable(elt.first).name();
837 delim = var_0_name.find_first_of("_");
838 var_0_name = var_0_name.substr(0, delim);
839
840 if (var_name.compare(var_0_name) == 0) {
841 const Tensor< GUM_SCALAR >* tensor(&credalNet_->current_bn().cpt(node));
842 const Tensor< GUM_SCALAR >* tensor2(&credalNet_->current_bn().cpt(elt.first));
843
844 if (tensor->domainSize() == tensor2->domainSize()) {
845 t0_[elt.first].push_back(node);
846 found = true;
847 break;
848 }
849 }
850 }
851
852 if (!found) {
853 for (const auto& elt: t1_) {
854 std::string var_0_name = credalNet_->current_bn().variable(elt.first).name();
855 auto delim = var_0_name.find_first_of("_");
856 var_0_name = var_0_name.substr(0, delim);
857
858 if (var_name.compare(var_0_name) == 0) {
859 const Tensor< GUM_SCALAR >* tensor(&credalNet_->current_bn().cpt(node));
860 const Tensor< GUM_SCALAR >* tensor2(&credalNet_->current_bn().cpt(elt.first));
861
862 if (tensor->domainSize() == tensor2->domainSize()) {
863 t1_[elt.first].push_back(node);
864 break;
865 }
866 }
867 }
868 }
869 }
870 }
871 }
872
873 template < GUM_Numeric GUM_SCALAR >
875 const NodeId& id,
876 const std::vector< GUM_SCALAR >& vertex) {
877 std::string var_name = credalNet_->current_bn().variable(id).name();
878 auto delim = var_name.find_first_of("_");
879
880 var_name = var_name.substr(0, delim);
881
882 if (auto p_modal = modal_.tryGet(var_name)) {
883 GUM_SCALAR exp = 0;
884 auto vsize = vertex.size();
885
886 for (Size mod = 0; mod < vsize; mod++)
887 exp += vertex[mod] * (*p_modal)[mod];
888
889 if (exp > expectationMax_[id]) expectationMax_[id] = exp;
890
891 if (exp < expectationMin_[id]) expectationMin_[id] = exp;
892 }
893 }
894
895 template < GUM_Numeric GUM_SCALAR >
897 const std::vector< GUM_SCALAR >& vertex,
898 const bool& elimRedund) {
899 auto& nodeCredalSet = marginalSets_[id];
900 auto dsize = vertex.size();
901
902 bool eq = true;
903
904 for (auto it = nodeCredalSet.cbegin(), itEnd = nodeCredalSet.cend(); it != itEnd; ++it) {
905 eq = true;
906
907 for (Size i = 0; i < dsize; i++) {
908 if (std::fabs(vertex[i] - (*it)[i]) > 1e-6) {
909 eq = false;
910 break;
911 }
912 }
913
914 if (eq) break;
915 }
916
917 if (!eq || nodeCredalSet.size() == 0) {
918 nodeCredalSet.push_back(vertex);
919 } else return;
920
921 // because of next lambda return condition
922 if (nodeCredalSet.size() == 1) return;
923
924 // check that the point and all previously added ones are not inside the
925 // actual
926 // polytope
927 auto itEnd = std::remove_if(
928 nodeCredalSet.begin(),
929 nodeCredalSet.end(),
930 [&](const std::vector< GUM_SCALAR >& v) -> bool {
931 for (auto jt = v.cbegin(),
932 jtEnd = v.cend(),
933 minIt = marginalMin_[id].cbegin(),
934 minItEnd = marginalMin_[id].cend(),
935 maxIt = marginalMax_[id].cbegin(),
936 maxItEnd = marginalMax_[id].cend();
937 jt != jtEnd && minIt != minItEnd && maxIt != maxItEnd;
938 ++jt, ++minIt, ++maxIt) {
939 if ((std::fabs(*jt - *minIt) < 1e-6 || std::fabs(*jt - *maxIt) < 1e-6)
940 && std::fabs(*minIt - *maxIt) > 1e-6)
941 return false;
942 }
943 return true;
944 });
945
946 nodeCredalSet.erase(itEnd, nodeCredalSet.end());
947
948 // we need at least 2 points to make a convex combination
949 if (!elimRedund || nodeCredalSet.size() <= 2) return;
950
951 // there may be points not inside the polytope but on one of it's facet,
952 // meaning it's still a convex combination of vertices of this facet. Here
953 // we
954 // need lrs.
955 LRSWrapper< GUM_SCALAR > lrsWrapper;
956 lrsWrapper.setUpV((unsigned int)dsize, (unsigned int)(nodeCredalSet.size()));
957
958 for (const auto& vtx: nodeCredalSet)
959 lrsWrapper.fillV(vtx);
960
961 lrsWrapper.elimRedundVrep();
962
963 marginalSets_[id] = lrsWrapper.getOutput();
964 }
965
966 template < GUM_Numeric GUM_SCALAR >
971
972 template < GUM_Numeric GUM_SCALAR >
977
978 template < GUM_Numeric GUM_SCALAR >
980 // compute the number of threads and prepare for the result
981 const Size nb_threads = ThreadExecutor::nbRunningThreadsExecutors() == 0
982 ? this->threadRanges_.size() - 1
983 : 1; // no nested multithreading
984 std::vector< GUM_SCALAR > tEps(nb_threads, std::numeric_limits< GUM_SCALAR >::max());
985
986 // create the function to be executed by the threads
987 auto threadedEps = [this, &tEps](const std::size_t this_thread,
988 const std::size_t nb_threads,
989 const std::vector< std::pair< NodeId, Idx > >& ranges) {
990 auto& this_tEps = tEps[this_thread];
991 GUM_SCALAR delta;
992
993 // below, we will loop over indices i and j of marginalMin_ and
994 // marginalMax_. Index i represents nodes and j allow to parse their
995 // domain. To parse all the domains of all the nodes, we should theorically
996 // use 2 loops. However, here, we will use one loop: we start with node i
997 // and parse its domain with index j. When this is done, we move to the
998 // next node, and so on. The underlying idea is that, by doing so, we
999 // need not parse in this function the whole domain of a node: we can start
1000 // the loop at a given value of node i and complete the loop on another
1001 // value of another node. These values are computed in Vector threadRanges_
1002 // by Method dispatchMarginalsToThreads_(), which dispatches the loops
1003 // among threads
1004 auto i = ranges[this_thread].first;
1005 auto j = ranges[this_thread].second;
1006 auto domain_size = this->marginalMax_[i].size();
1007 const auto end_i = ranges[this_thread + 1].first;
1008 auto end_j = ranges[this_thread + 1].second;
1009 const auto marginalMax_size = this->marginalMax_.size();
1010
1011 while ((i < end_i) || (j < end_j)) {
1012 // on min
1013 delta = marginalMin_[i][j] - oldMarginalMin_[i][j];
1014 delta = (delta < 0) ? (-delta) : delta;
1015 this_tEps = (this_tEps < delta) ? delta : this_tEps;
1016
1017 // on max
1018 delta = marginalMax_[i][j] - oldMarginalMax_[i][j];
1019 delta = (delta < 0) ? (-delta) : delta;
1020 this_tEps = (this_tEps < delta) ? delta : this_tEps;
1021
1022 oldMarginalMin_[i][j] = marginalMin_[i][j];
1023 oldMarginalMax_[i][j] = marginalMax_[i][j];
1024
1025 if (++j == domain_size) {
1026 j = 0;
1027 ++i;
1028 if (i < marginalMax_size) domain_size = this->marginalMax_[i].size();
1029 }
1030 }
1031 };
1032
1033 // launch the threads
1035 nb_threads,
1036 threadedEps,
1037 (nb_threads == 1)
1038 ? std::vector< std::pair< NodeId, Idx > >{{0, 0}, {this->marginalMin_.size(), 0}}
1039 : this->threadRanges_);
1040
1041 // aggregate all the results
1042 GUM_SCALAR eps = tEps[0];
1043 for (const auto nb: tEps)
1044 if (eps < nb) eps = nb;
1045
1046 return eps;
1047 }
1048
1049 /*
1050 // old openMP code:
1051 GUM_SCALAR eps = 0;
1052 #pragma omp parallel
1053 {
1054 GUM_SCALAR tEps = 0;
1055 GUM_SCALAR delta;
1056
1058 int nsize = int(marginalMin_.size());
1059
1060 #pragma omp for
1061
1062 for (int i = 0; i < nsize; i++) {
1063 auto dSize = marginalMin_[i].size();
1064
1065 for (Size j = 0; j < dSize; j++) {
1066 // on min
1067 delta = marginalMin_[i][j] - oldMarginalMin_[i][j];
1068 delta = (delta < 0) ? (-delta) : delta;
1069 tEps = (tEps < delta) ? delta : tEps;
1070
1071 // on max
1072 delta = marginalMax_[i][j] - oldMarginalMax_[i][j];
1073 delta = (delta < 0) ? (-delta) : delta;
1074 tEps = (tEps < delta) ? delta : tEps;
1075
1076 oldMarginalMin_[i][j] = marginalMin_[i][j];
1077 oldMarginalMax_[i][j] = marginalMax_[i][j];
1078 }
1079 } // end of : all variables
1080
1081 #pragma omp critical(epsilon_max)
1082 {
1083 #pragma omp flush(eps)
1084 eps = (eps < tEps) ? tEps : eps;
1085 }
1086 }
1087
1088 return eps;
1089 }
1090 */
1091
1092
1093 template < GUM_Numeric GUM_SCALAR >
1095 // we compute the number of elements in the 2 loops (over i,j in marginalMin_[i][j])
1096 Size nb_elements = 0;
1097 const auto marginalMin_size = this->marginalMin_.size();
1098 for (const auto& marg_i: this->marginalMin_)
1099 nb_elements += marg_i.second.size();
1100
1101 // distribute evenly the elements among the threads
1102 auto nb_threads = ThreadNumberManager::getNumberOfThreads();
1103 if (nb_elements < nb_threads) nb_threads = nb_elements;
1104
1105 // the result that we return is a vector of pairs (NodeId, Idx). For thread number i, the
1106 // pair at index i is the beginning of the range that the thread will have to process: this
1107 // is the part of the marginal distribution vector of node NodeId starting at index Idx.
1108 // The pair at index i+1 is the end of this range (not included)
1109 threadRanges_.clear();
1110 threadRanges_.reserve(nb_threads + 1);
1111
1112 // try to balance the number of elements among the threads
1113 Idx nb_elts_par_thread = nb_elements / nb_threads;
1114 Idx rest_elts = nb_elements - nb_elts_par_thread * nb_threads;
1115
1116 NodeId current_node = 0;
1117 Idx current_domain_index = 0;
1118 Size current_domain_size = this->marginalMin_[0].size();
1119 threadRanges_.emplace_back(current_node, current_domain_index);
1120
1121 for (Idx i = Idx(0); i < nb_threads; ++i) {
1122 // compute the end of the threads, assuming that the current node has a domain
1123 // sufficiently large
1124 current_domain_index += nb_elts_par_thread;
1125 if (rest_elts != Idx(0)) {
1126 ++current_domain_index;
1127 --rest_elts;
1128 }
1129
1130 // if the current node is not sufficient to hold all the elements that
1131 // the current thread should process. So we should add elements of the
1132 // next nodes
1133 while (current_domain_index >= current_domain_size) {
1134 current_domain_index -= current_domain_size;
1135 ++current_node;
1136 current_domain_index = 0;
1137 if (current_node != marginalMin_size) {
1138 current_domain_size = this->marginalMin_[current_node].size();
1139 }
1140 }
1141
1142 // now we can store the range if elements
1143 threadRanges_.emplace_back(current_node, current_domain_index);
1144
1145 // compute the next begin_node
1146 if (current_domain_index == current_domain_size) {
1147 ++current_node;
1148 current_domain_index = 0;
1149 }
1150 }
1151 }
1152
1153 template < GUM_Numeric GUM_SCALAR >
1155 const std::vector< GUM_SCALAR >& vals) {
1156 evidence_.insert(id, vals);
1157 // forces the computation of the begin iterator to avoid subsequent data races
1158 // @TODO make HashTableConstIterator constructors thread safe
1159 evidence_.begin();
1160 }
1161
1163 template < GUM_Numeric GUM_SCALAR >
1165 std::vector< GUM_SCALAR > vals(this->credalNet_->current_bn().variable(id).domainSize(), 0);
1166 vals[val] = 1;
1167 addEvidence(id, vals);
1168 }
1169
1171 template < GUM_Numeric GUM_SCALAR >
1172 void InferenceEngine< GUM_SCALAR >::addEvidence(std::string_view nodeName, const Idx val) {
1173 addEvidence(this->credalNet_->current_bn().idFromName(nodeName), val);
1174 }
1175
1177 template < GUM_Numeric GUM_SCALAR >
1178 void InferenceEngine< GUM_SCALAR >::addEvidence(NodeId id, std::string_view label) {
1179 addEvidence(id, this->credalNet_->current_bn().variable(id)[label]);
1180 }
1181
1183 template < GUM_Numeric GUM_SCALAR >
1184 void InferenceEngine< GUM_SCALAR >::addEvidence(std::string_view nodeName,
1185 std::string_view label) {
1186 const NodeId id = this->credalNet_->current_bn().idFromName(nodeName);
1187 addEvidence(id, this->credalNet_->current_bn().variable(id)[label]);
1188 }
1189
1190 template < GUM_Numeric GUM_SCALAR >
1191 void InferenceEngine< GUM_SCALAR >::addEvidence(std::string_view nodeName,
1192 const std::vector< GUM_SCALAR >& vals) {
1193 addEvidence(this->credalNet_->current_bn().idFromName(nodeName), vals);
1194 }
1195
1196 template < GUM_Numeric GUM_SCALAR >
1197 void InferenceEngine< GUM_SCALAR >::addEvidence(const Tensor< GUM_SCALAR >& pot) {
1198 const auto id = this->credalNet_->current_bn().idFromName(pot.variable(0).name());
1199 std::vector< GUM_SCALAR > vals(this->credalNet_->current_bn().variable(id).domainSize(), 0);
1200 Instantiation I(pot);
1201 for (I.setFirst(); !I.end(); I.inc()) {
1202 vals[I.val(0)] = pot[I];
1203 }
1204 addEvidence(id, vals);
1205 }
1206
1207 template < GUM_Numeric GUM_SCALAR >
1211
1212 } // namespace credal
1213} // namespace gum
ApproximationScheme(bool verbosity=false)
The class for generic Hash Tables.
Definition hashTable.h:640
std::string messageApproximationScheme() const
Returns the approximation scheme message.
Exception : input/output problem.
Class for assigning/browsing values to tuples of discrete variables.
bool end() const
Returns true if the Instantiation reached the end.
void inc()
Operator increment.
Idx val(Idx i) const
Returns the current value of the variable at position i.
void setFirst()
Assign the first values to the tuple of the Instantiation.
Exception: at least one argument passed to a function is not what was expected.
Exception : the element we looked for cannot be found.
Exception : operation not allowed.
Exception : out of bound.
aGrUM's Tensor is a multi-dimensional array with tensor operators.
Definition tensor.h:85
Size getNumberOfThreads() const override
returns the current max number of threads used by the class containing this ThreadNumberManager
Class template representing a Credal Network.
Definition credalNet.h:97
void updateExpectations_(const NodeId &id, const std::vector< GUM_SCALAR > &vertex)
Given a node id and one of it's possible vertex obtained during inference, update this node lower and...
void repetitiveInit_()
Initialize t0_ and t1_ clusters.
void dynamicExpectations()
Compute dynamic expectations.
margi oldMarginalMax_
Old upper marginals used to compute epsilon.
const std::string getApproximationSchemeMsg()
Get approximation scheme state.
margi evidence_
Holds observed variables states.
const std::vector< GUM_SCALAR > & dynamicExpMin(std::string_view varName) const
Get the lower dynamic expectation of a given variable prefix (without the time step included,...
void dispatchMarginalsToThreads_()
computes Vector threadRanges_, that assigns some part of marginalMin_ and marginalMax_ to the threads
cluster t1_
Clusters of nodes used with dynamic networks.
dynExpe dynamicExpMin_
Lower dynamic expectations.
bool storeBNOpt_
Iterations limit stopping rule used by some algorithms such as CNMonteCarloSampling.
~InferenceEngine() override
Destructor.
void saveExpectations(std::string_view path) const
Saves expectations to file.
void insertModalsFile(std::string_view path)
Insert variables modalities from file to compute expectations.
void initExpectations_()
Initialize lower and upper expectations before inference, with the lower expectation being initialize...
void saveVertices(std::string_view path) const
Saves vertices to file.
void insertQueryFile(std::string_view path)
Insert query variables states from file.
void updateCredalSets_(const NodeId &id, const std::vector< GUM_SCALAR > &vertex, const bool &elimRedund=false)
Given a node id and one of it's possible vertex, update it's credal set.
bool repetitiveInd_
True if using repetitive independence ( dynamic network only ), False otherwise.
const NodeProperty< std::vector< NodeId > > & getT1Cluster() const
Get the t1_ cluster.
virtual const GUM_SCALAR computeEpsilon_()
Compute approximation scheme epsilon using the old marginals and the new ones.
const std::vector< std::vector< GUM_SCALAR > > & vertices(const NodeId id) const
Get the vertice of a given node id.
InferenceEngine(const CredalNet< GUM_SCALAR > &credalNet)
Construtor.
void initMarginalSets_()
Initialize credal set vertices with empty sets.
margi oldMarginalMin_
Old lower marginals used to compute epsilon.
bool storeVertices_
True if credal sets vertices are stored, False otherwise.
dynExpe dynamicExpMax_
Upper dynamic expectations.
const std::vector< GUM_SCALAR > & dynamicExpMax(std::string_view varName) const
Get the upper dynamic expectation of a given variable prefix (without the time step included,...
std::string toString() const
Print all nodes marginals to standart output.
void insertQuery(const NodeProperty< std::vector< bool > > &query)
Insert query variables and states from Property.
bool repetitiveInd() const
Get the current independence status.
NodeProperty< std::vector< bool > > query
void dynamicExpectations_()
Rearrange lower and upper expectations to suit dynamic networks.
bool storeVertices() const
Get the number of iterations without changes used to stop some algorithms.
const CredalNet< GUM_SCALAR > * credalNet_
A pointer to the Credal Net used.
void setRepetitiveInd(const bool repetitive)
virtual void addEvidence(NodeId id, const Idx val) final
adds a new hard evidence on node id
virtual void eraseAllEvidence()
removes all the evidence entered into the network
expe expectationMax_
Upper expectations, if some variables modalities were inserted.
void insertEvidence(const std::map< std::string, std::vector< GUM_SCALAR > > &eviMap)
Insert evidence from map.
void insertModals(const std::map< std::string, std::vector< GUM_SCALAR > > &modals)
Insert variables modalities from map to compute expectations.
query query_
Holds the query nodes states.
virtual void insertEvidenceFile(std::string_view path)
Insert evidence from file.
credalSet marginalSets_
Credal sets vertices, if enabled.
Tensor< GUM_SCALAR > marginalMin(const NodeId id) const
Get the lower marginals of a given node id.
void saveMarginals(std::string_view path) const
Saves marginals to file.
const CredalNet< GUM_SCALAR > & credalNet() const
Get this credal network.
VarMod2BNsMap< GUM_SCALAR > * getVarMod2BNsMap()
Get optimum IBayesNet.
margi marginalMin_
Lower marginals.
cluster t0_
Clusters of nodes used with dynamic networks.
const GUM_SCALAR & expectationMin(const NodeId id) const
Get the lower expectation of a given node id.
dynExpe modal_
Variables modalities used to compute expectations.
expe expectationMin_
Lower expectations, if some variables modalities were inserted.
Tensor< GUM_SCALAR > marginalMax(const NodeId id) const
Get the upper marginals of a given node id.
const GUM_SCALAR & expectationMax(const NodeId id) const
Get the upper expectation of a given node id.
int timeSteps_
The number of time steps of this network (only useful for dynamic networks).
std::vector< std::pair< NodeId, Idx > > threadRanges_
the ranges of elements of marginalMin_ and marginalMax_ processed by each thread
const NodeProperty< std::vector< NodeId > > & getT0Cluster() const
Get the t0_ cluster.
void initMarginals_()
Initialize lower and upper old marginals and marginals before inference, with the lower marginal bein...
VarMod2BNsMap< GUM_SCALAR > dbnOpt_
Object used to efficiently store optimal bayes net during inference, for some algorithms.
Class template acting as a wrapper for Lexicographic Reverse Search by David Avis.
Definition LrsWrapper.h:121
const matrix & getOutput() const
Get the output matrix solution of the problem.
void setUpV(const Size &card, const Size &vertices)
Sets up a V-representation.
void elimRedundVrep()
V-Redundancy elimination.
void fillV(const std::vector< GUM_SCALAR > &vertex)
Creates the V-representation of a polytope by adding a vertex to the problem input _input_.
Complete concept for GUM_SCALAR template parameter.
Definition concepts.h:148
#define GUM_ERROR(type, msg)
Definition exceptions.h:76
std::size_t Size
In aGrUM, hashed values are unsigned long int.
Definition types.h:74
Size Idx
Type for indexes.
Definition types.h:79
Size NodeId
Type for node ids.
HashTable< NodeId, VAL > NodeProperty
Property on graph elements.
Abstract class representing CredalNet inference engines.
namespace for all credal networks entities
Definition agrum.h:61
gum is the global namespace for all aGrUM entities
Definition agrum.h:46
STL namespace.
static void execute(std::size_t nb_threads, FUNCTION exec_func, ARGS &&... func_args)
executes a function using several threads
static int nbRunningThreadsExecutors()
indicates how many threadExecutors are currently running