aGrUM 2.3.2
a C++ library for (probabilistic) graphical models
ShaferShenoyLIMIDInference_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-2025 by *
5 * - Pierre-Henri WUILLEMIN(_at_LIP6) *
6 * - Christophe GONZALES(_at_AMU) *
7 * *
8 * The aGrUM/pyAgrum library is free software; you can redistribute it *
9 * and/or modify it under the terms of either : *
10 * *
11 * - the GNU Lesser General Public License as published by *
12 * the Free Software Foundation, either version 3 of the License, *
13 * or (at your option) any later version, *
14 * - the MIT license (MIT), *
15 * - or both in dual license, as here. *
16 * *
17 * (see https://agrum.gitlab.io/articles/dual-licenses-lgplv3mit.html) *
18 * *
19 * This aGrUM/pyAgrum library is distributed in the hope that it will be *
20 * useful, but WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
21 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES MERCHANTABILITY or FITNESS *
22 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
23 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
25 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
26 * OTHER DEALINGS IN THE SOFTWARE. *
27 * *
28 * See LICENCES for more details. *
29 * *
30 * SPDX-FileCopyrightText: Copyright 2005-2025 *
31 * - Pierre-Henri WUILLEMIN(_at_LIP6) *
32 * - Christophe GONZALES(_at_AMU) *
33 * SPDX-License-Identifier: LGPL-3.0-or-later OR MIT *
34 * *
35 * Contact : info_at_agrum_dot_org *
36 * homepage : http://agrum.gitlab.io *
37 * gitlab : https://gitlab.com/agrumery/agrum *
38 * *
39 ****************************************************************************/
40#pragma once
41
42
48
49#ifndef DOXYGEN_SHOULD_SKIP_THIS
50
51// to ease parsing by IDE
52# include <limits>
53
57
58# define GUM_SSLI_TRACE_ON(x) // GUM_TRACE(x)
59# define GUM_SSLI_TENSOR_TRACE_ON(x) // GUM_TRACE(x)
60
61namespace gum {
62
63 template < typename GUM_SCALAR >
65 const InfluenceDiagram< GUM_SCALAR >* infDiag) :
66 InfluenceDiagramInference< GUM_SCALAR >(infDiag) {
67 GUM_CONSTRUCTOR(ShaferShenoyLIMIDInference);
68 createReduced_();
69 }
70
71 template < typename GUM_SCALAR >
72 ShaferShenoyLIMIDInference< GUM_SCALAR >::~ShaferShenoyLIMIDInference() {
73 GUM_DESTRUCTOR(ShaferShenoyLIMIDInference);
74 }
75
76 template < typename GUM_SCALAR >
77 void ShaferShenoyLIMIDInference< GUM_SCALAR >::clear() {
78 GraphicalModelInference< GUM_SCALAR >::clear();
79 noForgettingOrder_.clear();
80 reduced_.clear();
81 reducedJunctionTree_.clear();
82 solvabilityOrder_.clear();
83 posteriors_.clear();
84 unconditionalDecisions_.clear();
85 strategies_.clear();
86 reversePartialOrder_.clear();
87 }
88
89 template < typename GUM_SCALAR >
90 void ShaferShenoyLIMIDInference< GUM_SCALAR >::onStateChanged_() {}
91
92 template < typename GUM_SCALAR >
93 void ShaferShenoyLIMIDInference< GUM_SCALAR >::onEvidenceAdded_(const NodeId id,
94 bool isHardEvidence) {
95 const InfluenceDiagram< GUM_SCALAR >& infdiag = this->influenceDiagram();
96 if (infdiag.isUtilityNode(id)) { GUM_ERROR(InvalidNode, "No evidence on a utility node.") }
97 if (infdiag.isDecisionNode(id)) {
98 if (!isHardEvidence) GUM_ERROR(InvalidNode, "No soft evidence on a decision node.")
99 }
100 }
101
102 template < typename GUM_SCALAR >
103 void ShaferShenoyLIMIDInference< GUM_SCALAR >::onEvidenceErased_(const NodeId id,
104 bool isHardEvidence) {}
105
106 template < typename GUM_SCALAR >
107 void ShaferShenoyLIMIDInference< GUM_SCALAR >::onAllEvidenceErased_(bool contains_hard_evidence) {
108 }
109
110 template < typename GUM_SCALAR >
111 void ShaferShenoyLIMIDInference< GUM_SCALAR >::onEvidenceChanged_(const NodeId id,
112 bool hasChangedSoftHard) {}
113
114 template < typename GUM_SCALAR >
115 void ShaferShenoyLIMIDInference< GUM_SCALAR >::onModelChanged_(const GraphicalModel* model) {
116 createReduced_();
117 }
118
119 template < typename GUM_SCALAR >
120 void ShaferShenoyLIMIDInference< GUM_SCALAR >::updateOutdatedStructure_() {
121 createReduced_();
122 }
123
124 template < typename GUM_SCALAR >
125 void ShaferShenoyLIMIDInference< GUM_SCALAR >::updateOutdatedTensors_() {}
126
127 template < typename GUM_SCALAR >
128 void ShaferShenoyLIMIDInference< GUM_SCALAR >::makeInference_() {
129 if (!isSolvable()) { GUM_ERROR(FatalError, "This LIMID/Influence Diagram is not solvable.") }
130
131 PhiNodeProperty phi;
132 PsiArcProperty psi;
133
134 GUM_SSLI_TRACE_ON("\n\n")
135
136 initializingInference_(phi, psi);
137 // message passing (using reverse order of solvabilityOrder)
138 // first collect of phis into root
139 const auto firstRootIndice = 0;
140 collectingMessage_(phi, psi, node_to_clique_[solvabilityOrder_[firstRootIndice]]);
141 deciding_(phi, psi, solvabilityOrder_[firstRootIndice]);
142
143 for (Idx nextRootIndice = 1; nextRootIndice < solvabilityOrder_.size(); nextRootIndice++) {
144 if (node_to_clique_[solvabilityOrder_[nextRootIndice - 1]]
145 != node_to_clique_[solvabilityOrder_[nextRootIndice]]) {
146 collectingToFollowingRoot_(phi,
147 psi,
148 node_to_clique_[solvabilityOrder_[nextRootIndice - 1]],
149 node_to_clique_[solvabilityOrder_[nextRootIndice]]);
150 }
151 deciding_(phi, psi, solvabilityOrder_[nextRootIndice]);
152 }
153
154 // last distribution
155 distributingMessage_(phi,
156 psi,
157 node_to_clique_[solvabilityOrder_[solvabilityOrder_.size() - 1]]);
158 computingPosteriors_(phi, psi);
159 }
160
161 template < typename GUM_SCALAR >
162 void ShaferShenoyLIMIDInference< GUM_SCALAR >::initializingInference_(PhiNodeProperty& phi,
163 PsiArcProperty& psi) {
164 const auto& jt = *junctionTree();
165 const auto& infdiag = this->influenceDiagram();
166 // init JT tensors and separators
167
168 for (const auto node: jt.nodes()) {
169 phi.insert(node, DecisionTensor< GUM_SCALAR >());
170 for (const auto nei: jt.neighbours(node)) {
171 psi.insert(Arc(node, nei), DecisionTensor< GUM_SCALAR >());
172 if (node < nei) { // to do it only once by edge
173 // we create the set of vars in node and nei (cached in varsSeparators_)
174 for (const auto n: jt.clique(node) * jt.clique(nei))
175 varsSeparator_.getWithDefault(Edge(node, nei), SetOfVars())
176 .insert(&(infdiag.variable(n)));
177 }
178 }
179 }
180 for (const auto node: infdiag.nodes()) {
181 const auto clik = node_to_clique_[node];
182 if (this->hasEvidence(node)) {
183 auto q = *(this->evidence()[node]);
184 phi[clik].insertProba(q.normalize());
185 }
186
187 if (infdiag.isDecisionNode(node)) {
188 if (!this->hasEvidence(node)) {
189 auto p = (Tensor< GUM_SCALAR >() << infdiag.variable(node)).fillWith(1).normalize();
190 phi[clik].insertProba(p); // WITHOUT NORMALIZATION !!!
191 }
192 } else if (infdiag.isChanceNode(node)) phi[clik].insertProba(infdiag.cpt(node));
193 else if (infdiag.isUtilityNode(node)) phi[clik].insertUtility(infdiag.utility(node));
194 else GUM_ERROR(FatalError, "Node " << node << " has no type.")
195 }
196 }
197
198 template < typename GUM_SCALAR >
199 void ShaferShenoyLIMIDInference< GUM_SCALAR >::_creatingJunctionTree_() {
200 const auto& infdiag = this->influenceDiagram();
201 auto moral = reduced_.moralGraph();
202
203 // once the moral graph is completed, we remove the utility nodes before
204 // triangulation
205 NodeProperty< Size > modalities;
206 for (const auto node: infdiag.nodes())
207 if (infdiag.isUtilityNode(node)) {
208 moral.eraseNode(node);
209 } else {
210 modalities.insert(node, infdiag.variable(node).domainSize());
211 }
212 DefaultTriangulation triangulation(&moral, &modalities);
213 reducedJunctionTree_ = triangulation.junctionTree();
214 _findingCliqueForEachNode_(triangulation);
215 }
216
217 template < typename GUM_SCALAR >
218 void ShaferShenoyLIMIDInference< GUM_SCALAR >::_findingCliqueForEachNode_(
219 DefaultTriangulation& triangulation) {
220 // indicate, for each node of the moral graph a clique in _JT_ that can
221 // contain its conditional probability table
222 const auto& infdiag = this->influenceDiagram();
223 NodeId first_eliminated_node;
224 Idx elim_number;
225 node_to_clique_.clear();
226 const std::vector< NodeId >& JT_elim_order = triangulation.eliminationOrder();
227 NodeProperty< Idx > elim_order(Size(JT_elim_order.size()));
228 for (Idx i = Idx(0), size = JT_elim_order.size(); i < size; ++i)
229 elim_order.insert(JT_elim_order[i], (int)i);
230 for (const auto node: reduced_.nodes()) {
231 if (infdiag.isUtilityNode(node)) {
232 // utility nodes are not in the junction tree but we want to associate a
233 // clique as well
234 first_eliminated_node = node;
235 elim_number = std::numeric_limits< NodeId >::max(); // an impossible elim_number;
236 } else {
237 // get the variables in the tensor of node (and its parents)
238 first_eliminated_node = node;
239 elim_number = elim_order[first_eliminated_node];
240 }
241
242 for (const auto parent: reduced_.parents(node)) {
243 if (elim_order[parent] < elim_number) {
244 elim_number = elim_order[parent];
245 first_eliminated_node = parent;
246 }
247 }
248
249 // first_eliminated_node contains the first var (node or one of its
250 // parents) eliminated => the clique created during its elimination
251 // contains node and all of its parents => it can contain the tensor
252 // assigned to the node in the BN
253 node_to_clique_.insert(node, triangulation.createdJunctionTreeClique(first_eliminated_node));
254 }
255 }
256
257 template < typename GUM_SCALAR >
258 std::pair< GUM_SCALAR, GUM_SCALAR > ShaferShenoyLIMIDInference< GUM_SCALAR >::MEU() {
259 if (!this->isInferenceDone()) GUM_ERROR(OperationNotAllowed, "Call MakeInference first")
260
261 const InfluenceDiagram< GUM_SCALAR >& infdiag = this->influenceDiagram();
262
263 GUM_SCALAR resmean = 0;
264 GUM_SCALAR resvar = 0;
265 for (auto node: infdiag.nodes()) {
266 if (infdiag.isUtilityNode(node)) {
267 auto p = meanVar(node);
268 resmean += p.first;
269 resvar += p.second;
270 }
271 }
272 return std::pair< GUM_SCALAR, GUM_SCALAR >(resmean, resvar);
273 }
274
275 template < typename GUM_SCALAR >
277 ShaferShenoyLIMIDInference< GUM_SCALAR >::optimalDecision(NodeId decisionId) {
278 if (!this->isInferenceDone()) GUM_ERROR(OperationNotAllowed, "Call MakeInference first")
279
280 const InfluenceDiagram< GUM_SCALAR >& infdiag = this->influenceDiagram();
281 if (!infdiag.isDecisionNode(decisionId))
283 infdiag.variable(decisionId).name()
284 << "(" << decisionId << ") is not a decision node.")
285
286 return strategies_[decisionId];
287 }
288
289 template < typename GUM_SCALAR >
290 bool ShaferShenoyLIMIDInference< GUM_SCALAR >::isSolvable() const {
291 return (!solvabilityOrder_.empty());
292 }
293
294 template < typename GUM_SCALAR >
295 void ShaferShenoyLIMIDInference< GUM_SCALAR >::createReduced_() {
296 // from LIMIDS of decision Problems, Lauritzen et Nilsson, 1999
297 reduced_.clear();
298 reducedJunctionTree_.clear();
299 solvabilityOrder_.clear();
300 reversePartialOrder_.clear();
301 posteriors_.clear();
302 unconditionalDecisions_.clear();
303 strategies_.clear();
304 const InfluenceDiagram< GUM_SCALAR >& infdiag = this->influenceDiagram();
305
306 NodeSet utilities;
307
308 // build reduced_
309 for (auto node: infdiag.nodes()) {
310 reduced_.addNodeWithId(node);
311 if (infdiag.isUtilityNode(node)) { utilities.insert(node); }
312 }
313
314 for (const auto& arc: infdiag.arcs())
315 reduced_.addArc(arc.tail(), arc.head());
316
317 _completingNoForgettingAssumption_();
318 _creatingPartialOrder_(utilities);
319 _checkingSolvability_(utilities);
320 if (isSolvable()) {
321 _reducingLIMID_();
322 _creatingJunctionTree_();
323 }
324
325 this->setState_(GraphicalModelInference< GUM_SCALAR >::StateOfInference::OutdatedStructure);
326 }
327
328 template < typename GUM_SCALAR >
329 void ShaferShenoyLIMIDInference< GUM_SCALAR >::_completingNoForgettingAssumption_() {
330 // force no forgetting if necessary
331 if (hasNoForgettingAssumption()) {
332 auto last = *(noForgettingOrder_.begin());
333 for (auto node: noForgettingOrder_)
334 if (node == last) // first one
335 continue;
336 else { // we deal with last->node
337 // adding the whole family of last as parents of node
338 if (!reduced_.existsArc(last, node)) { reduced_.addArc(last, node); }
339 for (auto par: reduced_.parents(last)) {
340 if (!reduced_.existsArc(par, node)) reduced_.addArc(par, node);
341 }
342 last = node;
343 }
344 }
345 }
346
347 template < typename GUM_SCALAR >
348 void ShaferShenoyLIMIDInference< GUM_SCALAR >::_checkingSolvability_(const NodeSet& utilities) {
349 if (hasNoForgettingAssumption()) {
350 solvabilityOrder_ = noForgettingOrder_;
351 std::reverse(solvabilityOrder_.begin(), solvabilityOrder_.end());
352 return;
353 }
354
355 solvabilityOrder_.clear();
356 for (const auto& sen: reversePartialOrder()) {
357 NodeSet tobetested = sen;
358 while (!tobetested.empty()) {
359 bool foundOne = false;
360 for (const auto& node: tobetested) {
361 const auto us = utilities * reduced_.descendants(node);
362 NodeSet decs;
363 for (const auto dec: tobetested)
364 if (dec != node) decs += reduced_.family(dec);
365 if (reduced_.dSeparation(decs, us, reduced_.family(node))) {
366 solvabilityOrder_.push_back(node);
367 foundOne = true;
368 tobetested.erase(node);
369 break;
370 }
371 }
372 if (!foundOne) { // no solvability
373 solvabilityOrder_.clear();
374 return;
375 }
376 }
377 }
378 }
379
380 template < typename GUM_SCALAR >
381 void ShaferShenoyLIMIDInference< GUM_SCALAR >::_reducingLIMID_() {
382 for (const auto& sen: reversePartialOrder_) {
383 for (auto n: sen) {
384 for (auto p: nonRequisiteNodes_(n))
385 reduced_.eraseArc(Arc(p, n));
386 }
387 }
388 }
389
390 template < typename GUM_SCALAR >
391 void ShaferShenoyLIMIDInference< GUM_SCALAR >::_creatingPartialOrder_(const NodeSet& utilities) {
392 const InfluenceDiagram< GUM_SCALAR >& infdiag = this->influenceDiagram();
393 NodeProperty< Size > level;
394
395 for (const auto& node: utilities)
396 level.insert(node, 0); // utility node is level 0
397
398 // creating the partial order
399 Size max_level = 0;
400 reversePartialOrder_.clear();
401 reversePartialOrder_.resize(infdiag.size());
402 NodeSet currents;
403 for (auto node: infdiag.nodes()) {
404 if (infdiag.isUtilityNode(node)) continue;
405 if (reduced_.children(node).isSubsetOrEqual(utilities)) {
406 currents.clear();
407 currents.insert(node);
408 level.insert(node, 0);
409 while (!currents.empty()) {
410 NodeId elt = *(currents.begin());
411 currents.erase(elt);
412
413 if (infdiag.isDecisionNode(elt)) reversePartialOrder_[level[elt]].insert(elt);
414
415 for (auto parent: reduced_.parents(elt)) {
416 Size lev = 0;
417 Size newl;
418 bool ok_to_add = true;
419 for (auto child: reduced_.children(parent)) {
420 if (!level.exists(child)) {
421 ok_to_add = false;
422 break;
423 }
424 newl = level[child];
425 if (infdiag.isDecisionNode(child)) newl += 1;
426 if (lev < newl) lev = newl;
427 }
428 if (ok_to_add) {
429 currents.insert(parent);
430 if (level.exists(parent)) {
431 if (level[parent] != lev)
433 "Trying to set level[" << parent << "] to level=" << lev
434 << " but already is " << level[parent]);
435 } else {
436 level.insert(parent, lev);
437 }
438
439 if (max_level < lev) max_level = lev;
440 }
441 }
442 }
443 }
444 }
445 Size levmax = 0;
446 for (const auto& k: reversePartialOrder_) {
447 if (k.empty()) break;
448 levmax++;
449 }
450 reversePartialOrder_.resize(levmax);
451 }
452
453 template < typename GUM_SCALAR >
454 std::vector< NodeSet > ShaferShenoyLIMIDInference< GUM_SCALAR >::reversePartialOrder() const {
455 return reversePartialOrder_;
456 }
457
458 template < typename GUM_SCALAR >
459 bool ShaferShenoyLIMIDInference< GUM_SCALAR >::hasNoForgettingAssumption() const {
460 return !noForgettingOrder_.empty();
461 }
462
463 template < typename GUM_SCALAR >
464 void ShaferShenoyLIMIDInference< GUM_SCALAR >::addNoForgettingAssumption(
465 const std::vector< std::string >& names) {
466 addNoForgettingAssumption(this->influenceDiagram().ids(names));
467 }
468
469 template < typename GUM_SCALAR >
470 void ShaferShenoyLIMIDInference< GUM_SCALAR >::addNoForgettingAssumption(
471 const std::vector< NodeId >& ids) {
472 const auto& infdiag = this->influenceDiagram();
473 for (const auto node: ids) {
474 if (!infdiag.exists(node)) GUM_ERROR(NotFound, node << " is not a NodeId")
475 if (!infdiag.isDecisionNode(node))
477 "Node " << node << " (" << infdiag.variable(node).name()
478 << ") is not a decision node");
479 }
480 if (infdiag.decisionNodeSize() != ids.size())
481 GUM_ERROR(SizeError, "Some decision nodes are missing in the sequence " << ids)
482
483 noForgettingOrder_ = ids;
484 createReduced_();
485 }
486
487 template < typename GUM_SCALAR >
488 NodeSet ShaferShenoyLIMIDInference< GUM_SCALAR >::nonRequisiteNodes_(NodeId d) const {
489 const InfluenceDiagram< GUM_SCALAR >& infdiag = this->influenceDiagram();
490
491 if (!infdiag.isDecisionNode(d)) GUM_ERROR(TypeError, d << " is not a decision node")
492
493 NodeSet res;
494 if (reduced_.parents(d).empty()) return res;
495
496 NodeSet descUs;
497 for (const auto n: reduced_.descendants(d))
498 if (infdiag.isUtilityNode(n)) descUs.insert(n);
499
500 NodeSet cumul{descUs};
501 cumul << d;
502 auto g = reduced_.moralizedAncestralGraph(cumul);
503
504 NodeSet family{reduced_.parents(d)};
505 family << d;
506 bool notReq;
507 for (const auto p: reduced_.parents(d)) {
508 notReq = true;
509 for (const auto u: descUs) {
510 if (g.hasUndirectedPath(p, u, family)) {
511 notReq = false;
512 break;
513 }
514 }
515 if (notReq) res << p;
516 }
517 return res;
518 }
519
520 template < typename GUM_SCALAR >
521 InfluenceDiagram< GUM_SCALAR > ShaferShenoyLIMIDInference< GUM_SCALAR >::reducedLIMID() const {
522 const auto& infdiag = this->influenceDiagram();
523 InfluenceDiagram< GUM_SCALAR > res;
524 for (auto node: infdiag.nodes()) {
525 if (infdiag.isChanceNode(node)) res.addChanceNode(infdiag.variable(node), node);
526 else if (infdiag.isDecisionNode(node)) res.addDecisionNode(infdiag.variable(node), node);
527 else // (infdiag.isUtilityNode(node))
528 res.addUtilityNode(infdiag.variable(node), node);
529 }
530
531 for (const auto& arc: reduced_.arcs()) {
532 res.addArc(arc.tail(), arc.head());
533 }
534
535 for (auto node: infdiag.nodes()) {
536 if (infdiag.isChanceNode(node)) res.cpt(node).fillWith(infdiag.cpt(node));
537 else if (infdiag.isUtilityNode(node)) res.utility(node).fillWith(infdiag.utility(node));
538 }
539
540 // Tensors !!!
541 return res;
542 }
543
544 template < typename GUM_SCALAR >
545 const JunctionTree* ShaferShenoyLIMIDInference< GUM_SCALAR >::junctionTree() const {
546 if (!isSolvable()) { GUM_ERROR(FatalError, "This LIMID/Influence Diagram is not solvable.") }
547 return &reducedJunctionTree_;
548 }
549
550 template < typename GUM_SCALAR >
551 void ShaferShenoyLIMIDInference< GUM_SCALAR >::collectingMessage_(PhiNodeProperty& phi,
552 PsiArcProperty& psi,
553 const NodeId rootClique) {
554 const auto& jt = *junctionTree();
555 GUM_SSLI_TRACE_ON("COLLECTING TO " << rootClique << ":"
556 << this->influenceDiagram().names(jt.clique(rootClique)))
557
558 std::function< void(NodeId, NodeId) > parcours = [&](NodeId node, NodeId toRoot) {
559 for (const auto nei: jt.neighbours(node)) {
560 if (nei != toRoot) parcours(nei, node);
561 }
562 transmittingMessage_(phi, psi, node, toRoot);
563 GUM_SSLI_TRACE_ON(" " << node << "->" << toRoot << " transmitted")
564 };
565
566 for (const auto nei: jt.neighbours(rootClique)) {
567 parcours(nei, rootClique);
568 }
569 }
570
571 template < typename GUM_SCALAR >
572 void ShaferShenoyLIMIDInference< GUM_SCALAR >::collectingToFollowingRoot_(PhiNodeProperty& phi,
573 PsiArcProperty& psi,
574 NodeId fromClique,
575 NodeId toClique) {
576 GUM_SSLI_TRACE_ON("COLLECTING FROM ROOT " << fromClique << " TO FOLLOWING ROOT " << toClique)
577 const auto& jt = *junctionTree();
578
579 std::function< bool(NodeId, NodeId, NodeId) > revparcours
580 = [&](NodeId node, NodeId from, NodeId target) {
581 if (node == target) return true;
582
583 bool found = false;
584 NodeId prec;
585 for (const auto nei: jt.neighbours(node)) {
586 if (nei != from)
587 if (revparcours(nei, node, target)) {
588 prec = nei;
589 found = true;
590 break;
591 }
592 }
593 if (found) { transmittingMessage_(phi, psi, prec, node); }
594 return found;
595 };
596
597 revparcours(toClique, std::numeric_limits< NodeId >::max(), fromClique);
598 }
599
600 template < typename GUM_SCALAR >
601 void ShaferShenoyLIMIDInference< GUM_SCALAR >::deciding_(PhiNodeProperty& phi,
602 PsiArcProperty& psi,
603 NodeId decisionNode) {
604 const auto& infdiag = this->influenceDiagram();
605 GUM_SSLI_TRACE_ON("DECIDING for " << infdiag.variable(decisionNode).name())
606
607 auto& decision = strategies_.getWithDefault(decisionNode, Tensor< GUM_SCALAR >());
608
609 if (this->hasHardEvidence(decisionNode)) {
610 decision = *(this->evidence()[decisionNode]);
611 } else {
612 DecisionTensor< double > dp;
613 dp = integrating_(phi, psi, node_to_clique_[decisionNode]);
614 GUM_SSLI_TENSOR_TRACE_ON(dp)
615
616 SetOfVars sev;
617 sev.insert(&infdiag.variable(decisionNode));
618 for (const auto parent: reduced_.parents(decisionNode)) {
619 sev.insert(&infdiag.variable(parent));
620 }
621 dp = dp ^ sev;
622 GUM_SSLI_TENSOR_TRACE_ON(dp)
623
624 // SPECIAL CASE FOR DETERMINISTIC DECISION
625 sev.erase(&infdiag.variable(decisionNode)); // only the parents in sev
626 if (sev.size() == 0) { // deterministic decision node
627 unconditionalDecisions_.set(decisionNode, dp);
628 } else if (dp.probPot.sumIn(sev).normalize().max()
629 == 1) { // with deterministic posterior probability
630 // we can use marginalization because we know that dp is deterministic
631 unconditionalDecisions_.set(
632 decisionNode,
633 DecisionTensor< double >(dp.probPot.sumOut(sev), dp.utilPot.sumOut(sev)));
634 }
635 decision = dp.utilPot.putFirst(&infdiag.variable(decisionNode));
636
637 binarizingMax_(decision, dp.probPot);
638 GUM_SSLI_TENSOR_TRACE_ON(decision)
639 }
640 GUM_SSLI_TENSOR_TRACE_ON(phi[node_to_clique_[decisionNode]])
641 phi[node_to_clique_[decisionNode]].insertProba(decision);
642 GUM_SSLI_TENSOR_TRACE_ON(phi[node_to_clique_[decisionNode]])
643 }
644
645 template < typename GUM_SCALAR >
646 void ShaferShenoyLIMIDInference< GUM_SCALAR >::binarizingMax_(
647 const Tensor< GUM_SCALAR >& decision,
648 const Tensor< GUM_SCALAR >& proba) const { // compute the decisions (as maxEU)
649 Instantiation I(decision);
650 const auto& firstvar = decision.variable(0);
651 for (I.setFirst(); !I.end(); I.incNotVar(firstvar)) {
652 I.setFirstVar(firstvar);
653 while (proba[I] == 0) {
654 I.incVar(firstvar);
655 if (I.end()) { // for non valid proba, we keep the first value (by
656 // default)²
657 I.setFirstVar(firstvar);
658 break;
659 }
660 }
661 // we found a non null value of proba
662 Idx argm = I.val(firstvar);
663 GUM_SCALAR umax = decision[I];
664 GUM_SCALAR pmax = proba[I];
665 for (I.incVar(firstvar); !I.end(); I.incVar(firstvar)) {
666 // lexicographical order on (u,p)
667 if (proba[I] > 0) {
668 if ((umax < decision[I]) || ((umax == decision[I]) && (pmax < proba[I]))) {
669 umax = decision[I];
670 pmax = proba[I];
671 argm = I.val(firstvar);
672 }
673 }
674 }
675 for (I.setFirstVar(firstvar); !I.end(); I.incVar(firstvar))
676 decision.set(I, 0);
677 I.chgVal(firstvar, argm);
678 decision.set(I, 1);
679 }
680 }
681
682 template < typename GUM_SCALAR >
683 void ShaferShenoyLIMIDInference< GUM_SCALAR >::distributingMessage_(PhiNodeProperty& phi,
684 PsiArcProperty& psi,
685 NodeId rootClique) {
686 const auto& jt = *junctionTree();
687 GUM_SSLI_TRACE_ON("DISTRIBUTING FROM " << rootClique << ":"
688 << this->influenceDiagram().names(jt.clique(rootClique)))
689
690 std::function< void(NodeId, NodeId) > parcours = [&](NodeId node, NodeId from) {
691 transmittingFinalMessage_(phi, psi, from, node);
692 auto res = integrating_(phi, psi, node);
693
694 res.probPot
696 psi[gum::Arc(node, from)].probPot);
697 res.utilPot = res.utilPot - psi[gum::Arc(node, from)].utilPot;
698
699 phi.set(node, res);
700 GUM_SSLI_TRACE_ON(" -> phi[" << node << "] updated")
701 for (const auto nei: jt.neighbours(node)) {
702 if (nei != from) parcours(nei, node);
703 }
704 };
705
706 phi.set(rootClique, integrating_(phi, psi, rootClique));
707 GUM_SSLI_TRACE_ON(" -> phi[" << rootClique << "] updated")
708 GUM_SSLI_TENSOR_TRACE_ON(phi[rootClique])
709
710 for (const auto nei: jt.neighbours(rootClique)) {
711 parcours(nei, rootClique);
712 }
713 }
714
715 template < typename GUM_SCALAR >
716 void ShaferShenoyLIMIDInference< GUM_SCALAR >::transmittingFinalMessage_(PhiNodeProperty& phi,
717 PsiArcProperty& psi,
718 NodeId fromClique,
719 NodeId toClique) {
720 GUM_SSLI_TRACE_ON(fromClique << "->" << toClique << " [final]")
721 // no need to integrate phi : already done
722 psi.set(Arc(fromClique, toClique),
723 phi[fromClique] ^ varsSeparator_[Edge(fromClique, toClique)]);
724 }
725
726 template < typename GUM_SCALAR >
727 void ShaferShenoyLIMIDInference< GUM_SCALAR >::transmittingMessage_(PhiNodeProperty& phi,
728 PsiArcProperty& psi,
729 NodeId fromClique,
730 NodeId toClique) {
731 GUM_SSLI_TRACE_ON(fromClique << "->" << toClique)
732 psi.set(Arc(fromClique, toClique),
733 integrating_(phi, psi, fromClique, toClique)
734 ^ varsSeparator_[Edge(fromClique, toClique)]);
735 }
736
737 template < typename GUM_SCALAR >
738 DecisionTensor< double >
739 ShaferShenoyLIMIDInference< GUM_SCALAR >::integrating_(const PhiNodeProperty& phi,
740 const PsiArcProperty& psi,
741 NodeId inClique,
742 NodeId except) const {
743 const auto& jt = *junctionTree();
744 GUM_SSLI_TRACE_ON(" integrating (except from "
745 << except << ") in " << inClique << ":"
746 << this->influenceDiagram().names(jt.clique(inClique)))
747 DecisionTensor< double > res = phi[inClique];
748 for (const auto nei: jt.neighbours(inClique))
749 if (nei != except) res *= psi[Arc(nei, inClique)];
750
751 return res;
752 }
753
754 template < typename GUM_SCALAR >
755 DecisionTensor< double >
756 ShaferShenoyLIMIDInference< GUM_SCALAR >::integrating_(const PhiNodeProperty& phi,
757 const PsiArcProperty& psi,
758 NodeId inClique) const {
759 const auto& jt = *junctionTree();
760 GUM_SSLI_TRACE_ON(" integrating in " << inClique << ":"
761 << this->influenceDiagram().names(jt.clique(inClique)))
762 DecisionTensor< double > res = phi[inClique];
763
764 for (const auto nei: jt.neighbours(inClique))
765 res *= psi[Arc(nei, inClique)];
766
767 return res;
768 }
769
770 template < typename GUM_SCALAR >
771 void ShaferShenoyLIMIDInference< GUM_SCALAR >::computingPosteriors_(const PhiNodeProperty& phi,
772 const PsiArcProperty& psi) {
773 NodeProperty< DecisionTensor< double > > finalphis;
774
775 const auto& infdiag = this->influenceDiagram();
776 posteriors_.clear();
777 strategies_.clear();
778 for (const auto node: infdiag.nodes()) {
779 const auto clik = node_to_clique_[node];
780 // if (!finalphis.exists(clik)) finalphis.insert(clik, integrating_(phi, psi, clik));
781 // const auto& finalphi = finalphis[clik];
782 const auto& finalphi = phi[clik];
783 GUM_SSLI_TRACE_ON("posterior for " << infdiag.variable(node).name())
784
785 DecisionTensor< GUM_SCALAR > res;
786
787 if (infdiag.isChanceNode(node)) {
788 SetOfVars sev;
789 sev.insert(&infdiag.variable(node));
790 res = finalphi ^ sev;
791 const auto f = res.probPot.sum();
792 res.probPot.scale(1 / f);
793 } else if (infdiag.isDecisionNode(node)) {
794 SetOfVars sev;
795 sev.insert(&infdiag.variable(node));
796 SetOfVars family = sev;
797 for (const auto par: reduced_.parents(node)) {
798 if (infdiag.isChanceNode(par)) family.insert(&infdiag.variable(par));
799 }
800 const auto dp = finalphi ^ family;
801
802 gum::Tensor< double > decision = dp.utilPot.putFirst(&infdiag.variable(node));
803 binarizingMax_(decision, dp.probPot);
804 strategies_.insert(node, decision);
805 res = dp ^ sev;
806 res.probPot.normalize();
807 if (unconditionalDecisions_.exists(node)) {
808 res.utilPot = unconditionalDecisions_[node].utilPot;
809 }
810 } else { // utility
811 SetOfVars family;
812
813 family.insert(&infdiag.variable(node));
814 for (const auto par: reduced_.parents(node)) {
815 family.insert(&infdiag.variable(par));
816 }
817 res = finalphi ^ family;
818
819 res.probPot.normalize();
820 res.utilPot = infdiag.utility(node);
821 }
822
823 posteriors_.set(node, res);
824 }
825 }
826
827 template < typename GUM_SCALAR >
828 const Tensor< GUM_SCALAR >& ShaferShenoyLIMIDInference< GUM_SCALAR >::posterior(NodeId node) {
829 return posteriors_[node].probPot;
830 }
831
832 template < typename GUM_SCALAR >
833 const Tensor< GUM_SCALAR >&
834 ShaferShenoyLIMIDInference< GUM_SCALAR >::posteriorUtility(NodeId node) {
835 return posteriors_[node].utilPot;
836 }
837
838 template < typename GUM_SCALAR >
839 std::pair< GUM_SCALAR, GUM_SCALAR >
840 ShaferShenoyLIMIDInference< GUM_SCALAR >::meanVar(NodeId node) {
841 return posteriors_[node].meanVar();
842 }
843
844} /* namespace gum */
845
846#endif /* DOXYGEN_SHOULD_SKIP_THIS */
Implementation of an influence diagram inference algorithm based upon Shaffer-Shenoy's one for bayes ...
The base class for all directed edges.
static Tensor< GUM_SCALAR > divideEvenZero(const Tensor< GUM_SCALAR > &p1, const Tensor< GUM_SCALAR > &p2)
Exception : fatal (unknown ?) error.
<agrum/ID/inference/influenceDiagramInference.h>
Class representing an Influence Diagram.
Exception : node does not exist.
Exception : the element we looked for cannot be found.
Exception : operation not allowed.
void resize(Size new_capacity)
Changes the size of the underlying hash table containing the set.
Definition set_tpl.h:468
ShaferShenoyLIMIDInference(const InfluenceDiagram< GUM_SCALAR > *infDiag)
Default constructor.
Exception : problem with size.
aGrUM's Tensor is a multi-dimensional array with tensor operators.
Definition tensor.h:85
Exception : wrong type for this operation.
This file contains abstract class definitions influence diagrams inference classes.
#define GUM_ERROR(type, msg)
Definition exceptions.h:72
Set< NodeId > NodeSet
Some typdefs and define for shortcuts ...
gum is the global namespace for all aGrUM entities
Definition agrum.h:46
CliqueGraph JunctionTree
a junction tree is a clique graph satisfying the running intersection property and such that no cliqu...
Header of the Tensor class.