aGrUM 3.1.1
a C++ library for (probabilistic) graphical models
graphicalModelInference_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
49
51
52namespace gum {
53
54
55 // Default Constructor
56 template < GUM_Numeric GUM_SCALAR >
63
64 // Default Constructor
65 template < GUM_Numeric GUM_SCALAR >
69
70 // Destructor
71 template < GUM_Numeric GUM_SCALAR >
73 // clear all evidence.
74 // Warning: Do not use method eraseAllEvidence () because it contains a call
75 // to pure virtual method onAllEvidenceErased_ which belongs to an inherited
76 // instance and, therefore, does not exist anymore when
77 // ~GraphicalModelInference () is called
78 for (const auto& pair: _evidence_) {
79 if (pair.second != nullptr) { delete (pair.second); }
80 }
81
82 GUM_DESTRUCTOR(GraphicalModelInference);
83 }
84
85 // returns whether the inference object is in a ready state
86 template < GUM_Numeric GUM_SCALAR >
90
91 // returns whether the inference object is in a OutdatedStructure state
92 template < GUM_Numeric GUM_SCALAR >
96
97 // returns whether the inference object is in a OutdatedTensor state
98 template < GUM_Numeric GUM_SCALAR >
102
103 // returns whether the inference object is in a InferenceDone state
104 template < GUM_Numeric GUM_SCALAR >
108
109 // returns the state of the inference engine
110 template < GUM_Numeric GUM_SCALAR >
113 return _state_;
114 }
115
116 // set the state of the inference
117 template < GUM_Numeric GUM_SCALAR >
124
125 // Returns a constant reference over the IBayesNet referenced by this class
126 template < GUM_Numeric GUM_SCALAR >
128 if (_model_ == nullptr)
130 "No Bayes net has been assigned to "
131 "the inference algorithm.");
132 return *_model_;
133 }
134
135 // assigns a new BN to the inference engine
136 template < GUM_Numeric GUM_SCALAR >
144
145 // assigns a BN to a newly constructed inference engine
146 template < GUM_Numeric GUM_SCALAR >
153
154 // clears all the data structures allocated for the last inference
155 template < GUM_Numeric GUM_SCALAR >
160
162 template < GUM_Numeric GUM_SCALAR >
164 _domain_sizes_.clear();
165 if (!hasNoModel_()) {
166 for (auto node: _model_->nodes()) {
167 _domain_sizes_.insert(node, _model_->variable(node).domainSize());
168 }
169 }
170 }
171
172 // get the domain sizes of the random variables of the BN
173 template < GUM_Numeric GUM_SCALAR >
177
178 // ##############################################################################
179 // Evidence
180 // ##############################################################################
181
182 // create the internal structure for a hard evidence
183 template < GUM_Numeric GUM_SCALAR >
184 Tensor< GUM_SCALAR >
186 // check that it is possible to create the evidence
187 if (_model_ == nullptr)
189 "No Bayes net has been assigned to the "
190 "inference algorithm");
191
192 if (!_model_->exists(id)) { GUM_ERROR(UndefinedElement, id << " is not a NodeId in the model") }
193
194 if (_model_->variable(id).domainSize() <= val) {
196 "node " << _model_->variable(id) << " has fewer possible values than " << val);
197 }
198
199 return Tensor< GUM_SCALAR >::deterministicTensor(_model_->variable(id), val);
200 }
201
202 // checks wether a tensor corresponds to a hard evidence
203 template < GUM_Numeric GUM_SCALAR >
205 Idx& val) const {
206 // checking if pot is determininstic
207 bool notZero = false;
208 Instantiation I(pot);
209
210 for (I.setFirst(); !I.end(); I.inc()) {
211 if (pot[I] != GUM_SCALAR(0.0)) {
212 if (notZero) { // we already met a non-zero value
213 return false;
214 } else {
215 val = I.val(0);
216 notZero = true; // this is the first met non-zero value
217 }
218 }
219 }
220
221 if (!notZero) { // we met no non-zero value
222 GUM_ERROR(FatalError, "Evidence of impossibility (vector of 0s)")
223 }
224
225 return true; // pot is deterministic
226 }
227
228 // adds a new hard evidence on node id
229 template < GUM_Numeric GUM_SCALAR >
233
234 // adds a new hard evidence on node id
235 template < GUM_Numeric GUM_SCALAR >
237 const Idx val) {
238 addEvidence(this->model().idFromName(nodeName), val);
239 }
240
241 // adds a new hard evidence on node id
242 template < GUM_Numeric GUM_SCALAR >
244 addEvidence(id, this->model().variable(id)[label]);
245 }
246
247 // adds a new hard evidence on node id
248 template < GUM_Numeric GUM_SCALAR >
250 std::string_view label) {
251 const NodeId id = this->model().idFromName(nodeName);
252 addEvidence(id, this->model().variable(id)[label]);
253 }
254
255 // adds a new evidence on node id (might be soft or hard)
256 template < GUM_Numeric GUM_SCALAR >
258 const std::vector< GUM_SCALAR >& vals) {
259 // checks that the evidence is meaningful
260 if (_model_ == nullptr)
262 "No Bayes net has been assigned to the "
263 "inference algorithm");
264
265 if (!_model_->exists(id)) { GUM_ERROR(UndefinedElement, id << " is not a NodeId in the model") }
266
267 if (_model_->variable(id).domainSize() != vals.size()) {
269 "node " << _model_->variable(id)
270 << " and its evidence vector have different sizes.");
271 }
272
273 Tensor< GUM_SCALAR > pot;
274 pot.add(_model_->variable(id));
275 pot.fillWith(vals);
276 addEvidence(std::move(pot));
277 }
278
279 // adds a new evidence on node id (might be soft or hard)
280 template < GUM_Numeric GUM_SCALAR >
282 const std::vector< GUM_SCALAR >& vals) {
283 addEvidence(this->model().idFromName(nodeName), vals);
284 }
285
286 // adds a new evidence on node id (might be soft or hard)
287 template < GUM_Numeric GUM_SCALAR >
288 void GraphicalModelInference< GUM_SCALAR >::addEvidence(Tensor< GUM_SCALAR >&& pot) {
289 // check if the tensor corresponds to an evidence
290 if (pot.nbrDim() != 1) { GUM_ERROR(InvalidArgument, pot << " is not mono-dimensional.") }
291 if (_model_ == nullptr)
293 "No Bayes net has been assigned to the "
294 "inference algorithm");
295
296 NodeId id = _model_->nodeId(pot.variable(0));
297
298 if (hasEvidence(id)) {
300 " node " << id << " already has an evidence. Please use chgEvidence().");
301 }
302
303 // check whether we have a hard evidence (and also check whether the
304 // tensor only contains 0 (in this case, this will automatically raise
305 // an exception) )
306 Idx val = 0;
307 bool is_hard_evidence = _isHardEvidence_(pot, val);
308
309 // insert the evidence
310 _evidence_.insert(id, new Tensor< GUM_SCALAR >(std::forward< Tensor< GUM_SCALAR > >(pot)));
311 if (is_hard_evidence) { // pot is deterministic
312 _hard_evidence_.insert(id, val);
313 _hard_evidence_nodes_.insert(id);
314 } else {
315 _soft_evidence_nodes_.insert(id);
316 }
318 onEvidenceAdded_(id, is_hard_evidence);
319 }
320
321 // adds a new evidence on node id (might be soft or hard)
322 template < GUM_Numeric GUM_SCALAR >
323 void GraphicalModelInference< GUM_SCALAR >::addEvidence(const Tensor< GUM_SCALAR >& pot) {
324 Tensor< GUM_SCALAR > new_pot(pot);
325 addEvidence(std::move(new_pot));
326 }
327
329 template < GUM_Numeric GUM_SCALAR >
331 const List< const Tensor< GUM_SCALAR >* >& potlist) {
332 for (const auto pot: potlist)
333 addEvidence(*pot);
334 }
335
337 template < GUM_Numeric GUM_SCALAR >
339 const Set< const Tensor< GUM_SCALAR >* >& potset) {
340 for (const auto pot: potset)
341 addEvidence(*pot);
342 }
343
344 // indicates whether some node(s) have received evidence
345 template < GUM_Numeric GUM_SCALAR >
347 return !_evidence_.empty();
348 }
349
350 // indicates whether node id has received an evidence
351 template < GUM_Numeric GUM_SCALAR >
353 return _evidence_.exists(id);
354 }
355
356 // indicates whether node id has received a hard evidence
357 template < GUM_Numeric GUM_SCALAR >
361
362 // indicates whether node id has received a soft evidence
363 template < GUM_Numeric GUM_SCALAR >
367
368 // indicates whether node id has received an evidence
369 template < GUM_Numeric GUM_SCALAR >
370 bool GraphicalModelInference< GUM_SCALAR >::hasEvidence(std::string_view nodeName) const {
371 return hasEvidence(this->model().idFromName(nodeName));
372 }
373
374 // indicates whether node id has received a hard evidence
375 template < GUM_Numeric GUM_SCALAR >
376 bool GraphicalModelInference< GUM_SCALAR >::hasHardEvidence(std::string_view nodeName) const {
377 return hasHardEvidence(this->model().idFromName(nodeName));
378 }
379
380 // indicates whether node id has received a soft evidence
381 template < GUM_Numeric GUM_SCALAR >
382 bool GraphicalModelInference< GUM_SCALAR >::hasSoftEvidence(std::string_view nodeName) const {
383 return hasSoftEvidence(this->model().idFromName(nodeName));
384 }
385
386 // change the value of an already existing hard evidence
387 template < GUM_Numeric GUM_SCALAR >
391
392 // change the value of an already existing hard evidence
393 template < GUM_Numeric GUM_SCALAR >
395 const Idx val) {
396 chgEvidence(this->model().idFromName(nodeName), val);
397 }
398
399 // change the value of an already existing hard evidence
400 template < GUM_Numeric GUM_SCALAR >
402 chgEvidence(id, this->model().variable(id)[label]);
403 }
404
405 // change the value of an already existing hard evidence
406 template < GUM_Numeric GUM_SCALAR >
408 std::string_view label) {
409 NodeId id = this->model().idFromName(nodeName);
410 chgEvidence(id, this->model().variable(id)[label]);
411 }
412
413 // change the value of an already existing evidence (might be soft or hard)
414 template < GUM_Numeric GUM_SCALAR >
416 const std::vector< GUM_SCALAR >& vals) {
417 // check whether this corresponds to an evidence
418 if (_model_ == nullptr)
420 "No Bayes net has been assigned to the "
421 "inference algorithm");
422
423 if (!_model_->exists(id)) { GUM_ERROR(UndefinedElement, id << " is not a NodeId in the model") }
424
425 if (_model_->variable(id).domainSize() != vals.size()) {
427 "node " << _model_->variable(id) << " and its evidence have different sizes.");
428 }
429
430 // create the tensor corresponding to vals
431 Tensor< GUM_SCALAR > pot;
432 pot.add(_model_->variable(id));
433 pot.fillWith(vals);
434 chgEvidence(pot);
435 }
436
437 // change the value of an already existing evidence (might be soft or hard)
438 template < GUM_Numeric GUM_SCALAR >
440 const std::vector< GUM_SCALAR >& vals) {
441 chgEvidence(this->model().idFromName(nodeName), vals);
442 }
443
444 // change the value of an already existing evidence (might be soft or hard)
445 template < GUM_Numeric GUM_SCALAR >
446 void GraphicalModelInference< GUM_SCALAR >::chgEvidence(const Tensor< GUM_SCALAR >& pot) {
447 // check if the tensor corresponds to an evidence
448 if (pot.nbrDim() != 1) {
449 GUM_ERROR(InvalidArgument, pot << " is not a mono-dimensional tensor.")
450 }
451 if (_model_ == nullptr)
453 "No Bayes net has been assigned to the "
454 "inference algorithm");
455
456 NodeId id = _model_->nodeId(pot.variable(0));
457
458 if (!hasEvidence(id)) {
459 GUM_ERROR(InvalidArgument, id << " has no evidence. Please use addEvidence().")
460 }
461
462 // check whether we have a hard evidence (and also check whether the
463 // tensor only contains 0 (in this case, this will automatically raise
464 // an exception) )
465 Idx val;
466 bool is_hard_evidence = _isHardEvidence_(pot, val);
467
468 // modify the evidence already stored
469 const Tensor< GUM_SCALAR >* localPot = _evidence_[id];
470 Instantiation I(pot);
471 for (I.setFirst(); !I.end(); I.inc()) {
472 localPot->set(I, pot[I]);
473 }
474
475 // the inference state will be different
476 // whether evidence change from Hard to Soft or not.
477 bool hasChangedSoftHard = false;
478
479 if (is_hard_evidence) {
480 if (!hasHardEvidence(id)) {
481 hasChangedSoftHard = true;
482 _hard_evidence_.insert(id, val);
483 _hard_evidence_nodes_.insert(id);
484 _soft_evidence_nodes_.erase(id);
485 } else {
486 _hard_evidence_[id] = val;
487 }
488 } else {
489 if (hasHardEvidence(id)) { // evidence was hard
490 _hard_evidence_.erase(id);
491 _hard_evidence_nodes_.erase(id);
492 _soft_evidence_nodes_.insert(id);
493 hasChangedSoftHard = true;
494 }
495 }
496
497 if (hasChangedSoftHard) {
499 } else {
501 }
502
503 onEvidenceChanged_(id, hasChangedSoftHard);
504 }
505
506 // removed the evidence, if any, corresponding to node id
507 template < GUM_Numeric GUM_SCALAR >
509 if (hasEvidence(id)) {
510 if (hasHardEvidence(id)) {
511 onEvidenceErased_(id, true);
512 _hard_evidence_.erase(id);
513 _hard_evidence_nodes_.erase(id);
515 } else {
516 onEvidenceErased_(id, false);
517 _soft_evidence_nodes_.erase(id);
519 }
520
521 delete (_evidence_[id]);
522 _evidence_.erase(id);
523 }
524 }
525
526 // removed the evidence, if any, corresponding to node of name nodeName
527 template < GUM_Numeric GUM_SCALAR >
529 eraseEvidence(this->model().idFromName(nodeName));
530 }
531
532 // removes all the evidence entered into the network
533 template < GUM_Numeric GUM_SCALAR >
535 bool has_hard_evidence = !_hard_evidence_.empty();
536 this->onAllEvidenceErased_(has_hard_evidence);
537
538 for (const auto& pair: _evidence_) {
539 if (pair.second != nullptr) { delete (pair.second); }
540 }
541
542 _evidence_.clear();
543 _hard_evidence_.clear();
544 _hard_evidence_nodes_.clear();
545 _soft_evidence_nodes_.clear();
546
547 if (has_hard_evidence) {
549 } else {
551 }
552 }
553
554 // returns the number of evidence entered into the Bayesian network
555 template < GUM_Numeric GUM_SCALAR >
559
560 // returns the number of hard evidence entered into the Bayesian network
561 template < GUM_Numeric GUM_SCALAR >
565
566 // returns the number of soft evidence entered into the Bayesian network
567 template < GUM_Numeric GUM_SCALAR >
571
572 // indicate for each node with hard evidence which value it took
573 template < GUM_Numeric GUM_SCALAR >
577
578 // the set of evidence entered into the network
579 template < GUM_Numeric GUM_SCALAR >
584
586 template < GUM_Numeric GUM_SCALAR >
590
592 template < GUM_Numeric GUM_SCALAR >
596
597 // ##############################################################################
598 // Inference
599 // ##############################################################################
600
601 // put the inference into an unprepared state
602 template < GUM_Numeric GUM_SCALAR >
606
609 template < GUM_Numeric GUM_SCALAR >
613
614 // prepare the internal inference structures for the next inference
615 template < GUM_Numeric GUM_SCALAR >
617 if (isInferenceReady() || isInferenceDone()) { return; }
618
619 if (_model_ == nullptr)
621 "No model been assigned to the "
622 "inference algorithm");
623
626
628 }
629
630 // perform the heavy computations needed to compute the targets' posteriors
631 template < GUM_Numeric GUM_SCALAR >
641
642 template < GUM_Numeric GUM_SCALAR >
644 return _model_ == nullptr;
645 }
646
647} /* namespace gum */
Exception : fatal (unknown ?) error.
GraphicalModelInference()
default constructor with a null model (useful for virtual inheritance)
virtual void prepareInference() final
prepare the internal inference structures for the next inference
void _computeDomainSizes_()
computes the domain sizes of the random variables
const NodeSet & softEvidenceNodes() const
returns the set of nodes with soft evidence
virtual void chgEvidence(NodeId id, const Idx val) final
change the value of an already existing hard evidence
virtual bool isInferenceDone() const noexcept final
returns whether the inference object is in a InferenceDone state
virtual void onAllEvidenceErased_(bool contains_hard_evidence)=0
fired before all the evidence are erased
virtual void onStateChanged_()=0
fired when the stage is changed
const NodeProperty< const Tensor< GUM_SCALAR > * > & evidence() const
returns the set of evidence
virtual StateOfInference state() const noexcept final
returns the state of the inference engine
virtual void onEvidenceChanged_(const NodeId id, bool hasChangedSoftHard)=0
fired after an evidence is changed, in particular when its status (soft/hard) changes
virtual bool isInferenceReady() const noexcept final
returns whether the inference object is in a ready state
virtual bool hasEvidence() const final
indicates whether some node(s) have received evidence
GraphicalModelInference(const GraphicalModel *model)
default constructor
virtual void setState_(const StateOfInference state) final
set the state of the inference engine and call the notification onStateChanged_ when necessary (i....
NodeProperty< const Tensor< GUM_SCALAR > * > _evidence_
the set of evidence entered into the network
virtual bool hasHardEvidence(NodeId id) const final
indicates whether node id has received a hard evidence
virtual void onModelChanged_(const GraphicalModel *model)=0
fired after a new Bayes net has been assigned to the engine
void setModel_(const GraphicalModel *model)
bool _isHardEvidence_(const Tensor< GUM_SCALAR > &pot, Idx &val) const
checks whether a tensor corresponds to a hard evidence or not
NodeSet _soft_evidence_nodes_
the set of nodes that received soft evidence
StateOfInference _state_
the current state of the inference (outdated/ready/done)
NodeProperty< Size > _domain_sizes_
the domain sizes of the random variables
const NodeProperty< Idx > & hardEvidence() const
indicate for each node with hard evidence which value it took
virtual const NodeProperty< Size > & domainSizes() const final
get the domain sizes of the random variables of the model
virtual void eraseEvidence(NodeId id) final
removed the evidence, if any, corresponding to node id
void setModelDuringConstruction_(const GraphicalModel *model)
assigns a model during the inference engine construction
void setOutdatedTensorsState_()
puts the inference into an OutdatedTensors state if it is not already in an OutdatedStructure state
const NodeSet & hardEvidenceNodes() const
returns the set of nodes with hard evidence
virtual void updateOutdatedTensors_()=0
prepares inference when the latter is in OutdatedTensors state
virtual void onEvidenceAdded_(const NodeId id, bool isHardEvidence)=0
fired after a new evidence is inserted
virtual bool isInferenceOutdatedTensors() const noexcept final
returns whether the inference object is in a OutdatedTensor state
virtual bool hasSoftEvidence(NodeId id) const final
indicates whether node id has received a soft evidence
virtual void makeInference_()=0
called when the inference has to be performed effectively
virtual Size nbrEvidence() const final
returns the number of evidence entered into the Bayesian network
virtual Size nbrSoftEvidence() const final
returns the number of soft evidence entered into the Bayesian network
virtual void addListOfEvidence(const List< const Tensor< GUM_SCALAR > * > &potlist) final
adds a new list of evidence
virtual void updateOutdatedStructure_()=0
prepares inference when the latter is in OutdatedStructure state
virtual Size nbrHardEvidence() const final
returns the number of hard evidence entered into the Bayesian network
virtual void eraseAllEvidence() final
removes all the evidence entered into the network
StateOfInference
current state of the inference
Tensor< GUM_SCALAR > _createHardEvidence_(NodeId id, Idx val) const
create the internal structure for a hard evidence
void setOutdatedStructureState_()
put the inference into an outdated model structure state
virtual void makeInference() final
perform the heavy computations needed to compute the targets' posteriors
virtual void addEvidence(NodeId id, const Idx val) final
adds a new hard evidence on node id
NodeSet _hard_evidence_nodes_
the set of nodes that received hard evidence
const GraphicalModel * _model_
the Bayes net on which we perform inferences
NodeProperty< Idx > _hard_evidence_
assign to each node with a hard evidence the index of its observed value
virtual void addSetOfEvidence(const Set< const Tensor< GUM_SCALAR > * > &potset) final
adds a new set of evidence
virtual void clear()
clears all the data structures allocated for the last inference
virtual void onEvidenceErased_(const NodeId id, bool isHardEvidence)=0
fired before an evidence is removed
virtual bool isInferenceOutdatedStructure() const noexcept final
returns whether the inference object is in a OutdatedStructure state
virtual const GraphicalModel & model() const final
Returns a constant reference over the IBayesNet referenced by this class.
Virtual base class for probabilistic graphical models.
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.
Generic doubly linked lists.
Definition list.h:378
Exception : a pointer or a reference on a nullptr (0) object.
Representation of a set.
Definition set.h:129
static Tensor< GUM_SCALAR > deterministicTensor(const DiscreteVariable &var, Idx value)
Exception : a looked-for element could not be found.
#define GUM_ERROR(type, msg)
Definition exceptions.h:76
This file contains abstract class definitions for graphical models inference classes.
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.
Set< NodeId > NodeSet
Some typdefs and define for shortcuts ...
gum is the global namespace for all aGrUM entities
Definition agrum.h:46