aGrUM  0.20.3
a C++ library for (probabilistic) graphical models
ShaferShenoyLIMIDInference_tpl.h
Go to the documentation of this file.
1 /**
2  *
3  * Copyright (c) 2005-2021 by Pierre-Henri WUILLEMIN(@LIP6) & Christophe GONZALES(@AMU)
4  * info_at_agrum_dot_org
5  *
6  * This library is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this library. If not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 
22 /**
23  * @file
24  * @brief Implementations of the classes defined
25  * in InfluenceDiagram/inference/ShaferShenoyLIMIDInference.h.
26  */
27 
28 #ifndef DOXYGEN_SHOULD_SKIP_THIS
29 
30 // to ease parsing by IDE
31 # include <limits>
32 
33 # include <agrum/ID/inference/ShaferShenoyLIMIDInference.h>
34 # include <agrum/ID/inference/tools/decisionPotential.h>
35 # include <agrum/tools/multidim/potential.h>
36 
37 //# define GUM_SSLI_TRACE_ON
38 //# define GUM_SSLI_POTENTIAL_TRACE_ON
39 
40 namespace gum {
41 
42  template < typename GUM_SCALAR >
43  ShaferShenoyLIMIDInference< GUM_SCALAR >::ShaferShenoyLIMIDInference(
44  const InfluenceDiagram< GUM_SCALAR >* infDiag) :
45  InfluenceDiagramInference< GUM_SCALAR >(infDiag) {
46  GUM_CONSTRUCTOR(ShaferShenoyLIMIDInference);
47  createReduced_();
48  }
49 
50  template < typename GUM_SCALAR >
53  }
54  template < typename GUM_SCALAR >
58  reduced_.clear();
65  }
66  template < typename GUM_SCALAR >
68  template < typename GUM_SCALAR >
70  bool isHardEvidence) {
72  if (infdiag.isUtilityNode(id)) { GUM_ERROR(InvalidNode, "No evidence on a utility node.") }
73  if (infdiag.isDecisionNode(id)) {
74  if (!isHardEvidence) GUM_ERROR(InvalidNode, "No soft evidence on a decision node.")
75  }
76  }
77  template < typename GUM_SCALAR >
79  bool isHardEvidence) {}
80  template < typename GUM_SCALAR >
82  }
83  template < typename GUM_SCALAR >
85  bool hasChangedSoftHard) {}
86  template < typename GUM_SCALAR >
89  }
90 
91  template < typename GUM_SCALAR >
94  }
95  template < typename GUM_SCALAR >
97 
98  template < typename GUM_SCALAR >
100  if (!isSolvable()) { GUM_ERROR(FatalError, "This LIMID/Influence Diagram is not solvable.") }
101 
104 
106  // message passing (using reverse order of solvabilityOrder)
107  // first collect of phis into root
108  const auto firstRootIndice = 0;
111 
116  psi,
119  }
121  }
122 
123  // last distribution
126  }
127 
128  template < typename GUM_SCALAR >
130  PsiArcProperty& psi) {
131  const auto& jt = *junctionTree();
132  const auto& infdiag = this->influenceDiagram();
133  // init JT potentials and separators
134 
135  for (const auto node: jt.nodes()) {
137  for (const auto nei: jt.neighbours(node)) {
139  if (node < nei) { // to do it only once by edge
140  // we create the set of vars in node and nei (cached in varsSeparators_)
141  for (const auto n: jt.clique(node) * jt.clique(nei))
143  .insert(&(infdiag.variable(n)));
144  }
145  }
146  }
147  for (const auto node: infdiag.nodes()) {
148  const auto clik = node_to_clique_[node];
149  if (this->hasEvidence(node)) {
150  auto q = *(this->evidence()[node]);
152  }
153 
154  if (infdiag.isDecisionNode(node)) {
155  if (!this->hasEvidence(node)) {
156  auto p = (Potential< GUM_SCALAR >() << infdiag.variable(node)).fillWith(1).normalize();
157  phi[clik].insertProba(p); // WITHOUT NORMALIZATION !!!
158  }
159  } else if (infdiag.isChanceNode(node))
161  else if (infdiag.isUtilityNode(node))
163  else
164  GUM_ERROR(FatalError, "Node " << node << " has no type.")
165  }
166  }
167 
168  template < typename GUM_SCALAR >
170  const auto& infdiag = this->influenceDiagram();
171  auto moral = reduced_.moralGraph();
172 
173  // once the moral graph is completed, we remove the utility nodes before
174  // triangulation
176  for (const auto node: infdiag.nodes())
177  if (infdiag.isUtilityNode(node)) {
179  } else {
181  }
185  }
186 
187  template < typename GUM_SCALAR >
190  // indicate, for each node of the moral graph a clique in _JT_ that can
191  // contain its conditional probability table
192  const auto& infdiag = this->influenceDiagram();
198  for (Idx i = Idx(0), size = JT_elim_order.size(); i < size; ++i)
200  for (const auto node: reduced_.nodes()) {
201  if (infdiag.isUtilityNode(node)) {
202  // utility nodes are not in the junction tree but we want to associate a
203  // clique as well
205  elim_number = std::numeric_limits< NodeId >::max(); // an impossible elim_number;
206  } else {
207  // get the variables in the potential of node (and its parents)
210  }
211 
212  for (const auto parent: reduced_.parents(node)) {
213  if (elim_order[parent] < elim_number) {
216  }
217  }
218 
219  // first_eliminated_node contains the first var (node or one of its
220  // parents) eliminated => the clique created during its elimination
221  // contains node and all of its parents => it can contain the potential
222  // assigned to the node in the BN
224  }
225  }
226 
227  template < typename GUM_SCALAR >
229  if (!this->isInferenceDone()) GUM_ERROR(OperationNotAllowed, "Call MakeInference first")
230 
232 
233  GUM_SCALAR resmean = 0;
234  GUM_SCALAR resvar = 0;
235  for (auto node: infdiag.nodes()) {
236  if (infdiag.isUtilityNode(node)) {
237  auto p = meanVar(node);
238  resmean += p.first;
239  resvar += p.second;
240  }
241  }
242  return std::pair< GUM_SCALAR, GUM_SCALAR >(resmean, resvar);
243  }
244 
245  template < typename GUM_SCALAR >
248  if (!this->isInferenceDone()) GUM_ERROR(OperationNotAllowed, "Call MakeInference first")
249 
254  << "(" << decisionId << ") is not a decision node.")
255 
256  return strategies_[decisionId];
257  }
258 
259 
260  template < typename GUM_SCALAR >
262  return (!solvabilityOrder_.empty());
263  }
264 
265  template < typename GUM_SCALAR >
267  // from LIMIDS of decision Problems, Lauritzen et Nilsson, 1999
268  reduced_.clear();
272  posteriors_.clear();
274  strategies_.clear();
276 
278 
279  // build reduced_
280  for (auto node: infdiag.nodes()) {
283  }
284 
285  for (const auto& arc: infdiag.arcs())
286  reduced_.addArc(arc.tail(), arc.head());
287 
291  if (isSolvable()) {
292  _reducingLIMID_();
294  }
295 
297  }
298 
299  template < typename GUM_SCALAR >
301  // force no forgetting if necessary
303  auto last = *(noForgettingOrder_.begin());
304  for (auto node: noForgettingOrder_)
305  if (node == last) // first one
306  continue;
307  else { // we deal with last->node
308  // adding the whole family of last as parents of node
310  for (auto par: reduced_.parents(last)) {
312  }
313  last = node;
314  }
315  }
316  }
317 
318  template < typename GUM_SCALAR >
323  return;
324  }
325 
327  for (const auto& sen: reversePartialOrder()) {
329  while (!tobetested.empty()) {
330  bool foundOne = false;
331  for (const auto& node: tobetested) {
332  const auto us = utilities * reduced_.descendants(node);
333  NodeSet decs;
334  for (const auto dec: tobetested)
335  if (dec != node) decs += reduced_.family(dec);
338  foundOne = true;
340  break;
341  }
342  }
343  if (!foundOne) { // no solvability
345  return;
346  }
347  }
348  }
349  }
350 
351  template < typename GUM_SCALAR >
353  for (const auto& sen: reversePartialOrder_) {
354  for (auto n: sen) {
355  for (auto p: nonRequisiteNodes_(n))
356  reduced_.eraseArc(Arc(p, n));
357  }
358  }
359  }
360 
361  template < typename GUM_SCALAR >
365 
366  for (const auto& node: utilities)
367  level.insert(node, 0); // utility node is level 0
368 
369  // creating the partial order
370  Size max_level = 0;
374  for (auto node: infdiag.nodes()) {
375  if (infdiag.isUtilityNode(node)) continue;
377  currents.clear();
379  level.insert(node, 0);
380  while (!currents.empty()) {
381  NodeId elt = *(currents.begin());
382  currents.erase(elt);
383 
385 
386  for (auto parent: reduced_.parents(elt)) {
387  Size lev = 0;
388  Size newl;
389  bool ok_to_add = true;
390  for (auto child: reduced_.children(parent)) {
391  if (!level.exists(child)) {
392  ok_to_add = false;
393  break;
394  }
395  newl = level[child];
396  if (infdiag.isDecisionNode(child)) newl += 1;
397  if (lev < newl) lev = newl;
398  }
399  if (ok_to_add) {
401  if (level.exists(parent)) {
402  if (level[parent] != lev)
404  "Trying to set level[" << parent << "] to level=" << lev
405  << " but already is " << level[parent]);
406  } else {
408  }
409 
410  if (max_level < lev) max_level = lev;
411  }
412  }
413  }
414  }
415  }
416  Size levmax = 0;
417  for (const auto& k: reversePartialOrder_) {
418  if (k.empty()) break;
419  levmax++;
420  }
422  }
423 
424  template < typename GUM_SCALAR >
426  return reversePartialOrder_;
427  }
428  template < typename GUM_SCALAR >
430  return !noForgettingOrder_.empty();
431  }
432 
433  template < typename GUM_SCALAR >
435  const std::vector< std::string >& names) {
437  }
438 
439  template < typename GUM_SCALAR >
441  const std::vector< NodeId >& ids) {
442  const auto& infdiag = this->influenceDiagram();
443  for (const auto node: ids) {
444  if (!infdiag.exists(node)) GUM_ERROR(NotFound, node << " is not a NodeId")
447  "Node " << node << " (" << infdiag.variable(node).name()
448  << ") is not a decision node");
449  }
450  if (infdiag.decisionNodeSize() != ids.size())
451  GUM_ERROR(SizeError, "Some decision nodes are missing in the sequence " << ids)
452 
454  createReduced_();
455  }
456  template < typename GUM_SCALAR >
459 
460  if (!infdiag.isDecisionNode(d)) GUM_ERROR(TypeError, d << " is not a decision node")
461 
462  NodeSet res;
463  if (reduced_.parents(d).empty()) return res;
464 
465  NodeSet descUs;
466  for (const auto n: reduced_.descendants(d))
468 
470  cumul << d;
472 
474  family << d;
475  bool notReq;
476  for (const auto p: reduced_.parents(d)) {
477  notReq = true;
478  for (const auto u: descUs) {
479  if (g.hasUndirectedPath(p, u, family)) {
480  notReq = false;
481  break;
482  }
483  }
484  if (notReq) res << p;
485  }
486  return res;
487  }
488 
489  template < typename GUM_SCALAR >
491  const auto& infdiag = this->influenceDiagram();
493  for (auto node: infdiag.nodes()) {
496  else if (infdiag.isDecisionNode(node))
498  else // (infdiag.isUtilityNode(node))
500  }
501 
502  for (const auto& arc: reduced_.arcs()) {
503  res.addArc(arc.tail(), arc.head());
504  }
505 
506  for (auto node: infdiag.nodes()) {
509  else if (infdiag.isUtilityNode(node))
511  }
512 
513  // Potentials !!!
514  return res;
515  }
516 
517 
518  template < typename GUM_SCALAR >
520  if (!isSolvable()) { GUM_ERROR(FatalError, "This LIMID/Influence Diagram is not solvable.") }
521  return &reducedJunctionTree_;
522  }
523 
524  template < typename GUM_SCALAR >
527  const NodeId rootClique) {
528  const auto& jt = *junctionTree();
529 
530  std::function< void(NodeId, NodeId) > parcours = [&](NodeId node, NodeId from) {
531  for (const auto nei: jt.neighbours(node)) {
532  if (nei != from) parcours(nei, node);
533  }
535  };
536 
537  for (const auto nei: jt.neighbours(rootClique)) {
539  }
540  }
541  template < typename GUM_SCALAR >
545  NodeId toClique) {
546  const auto& jt = *junctionTree();
547 
549  = [&](NodeId node, NodeId from, NodeId target) {
550  if (node == target) return true;
551 
552  bool found = false;
553  NodeId prec;
554  for (const auto nei: jt.neighbours(node)) {
555  if (nei != from)
556  if (revparcours(nei, node, target)) {
557  prec = nei;
558  found = true;
559  break;
560  }
561  }
562  if (found) { transmittingMessage_(phi, psi, prec, node); }
563  return found;
564  };
565 
567  }
568 
569  template < typename GUM_SCALAR >
573  const auto& infdiag = this->influenceDiagram();
574  const auto& jt = *junctionTree();
575 
577 
578  if (this->hasHardEvidence(decisionNode)) {
579  decision = *(this->evidence()[decisionNode]);
580  } else {
581  DecisionPotential< double > dp;
583 
584  SetOfVars sev;
586  for (const auto parent: reduced_.parents(decisionNode)) {
588  }
589  dp = dp ^ sev;
590 
591  // SPECIAL CASE FOR DETERMINISTIC DECISION
592  sev.erase(&infdiag.variable(decisionNode)); // only the parents in sev
593  if (sev.size() == 0) { // deterministic decision node
595  } else if (dp.probPot.margSumIn(sev).normalize().max()
596  == 1) { // with deterministic posterior probability
597  // we can use marginalization because we now that dp is deterministic
599  decisionNode,
601  }
603 
605  }
607  }
608 
609  template < typename GUM_SCALAR >
611  const Potential< GUM_SCALAR >& decision,
612  const Potential< GUM_SCALAR >& proba) const { // compute the decisions (as maxEU)
614  const auto& firstvar = decision.variable(0);
615  for (I.setFirst(); !I.end(); I.incNotVar(firstvar)) {
617  while (proba[I] == 0) {
618  I.incVar(firstvar);
619  if (I.end()) { // for non valid proba, we keep the first value (by
620  // default)²
622  break;
623  }
624  }
625  // we found a non null value of proba
626  Idx argm = I.val(firstvar);
628  GUM_SCALAR pmax = proba[I];
629  for (I.incVar(firstvar); !I.end(); I.incVar(firstvar)) {
630  // lexicographical order on (u,p)
631  if (proba[I] > 0) {
632  if ((umax < decision[I]) || ((umax == decision[I]) && (pmax < proba[I]))) {
633  umax = decision[I];
634  pmax = proba[I];
635  argm = I.val(firstvar);
636  }
637  }
638  }
639  for (I.setFirstVar(firstvar); !I.end(); I.incVar(firstvar))
640  decision.set(I, 0);
641  I.chgVal(firstvar, argm);
642  decision.set(I, 1);
643  }
644  }
645 
646  template < typename GUM_SCALAR >
649  NodeId rootClique) {
650  const auto& jt = *junctionTree();
651 
652  std::function< void(NodeId, NodeId) > parcours = [&](NodeId node, NodeId from) {
655  for (const auto nei: jt.neighbours(node)) {
656  if (nei != from) parcours(nei, node);
657  }
658  };
659 
660  // phi.set(rootClique, integrating_(phi, psi, rootClique));
661  for (const auto nei: jt.neighbours(rootClique)) {
663  }
664  }
665 
666  template < typename GUM_SCALAR >
670  NodeId toClique) {
671  // phi has been updated. No need to integrate anythin
674  }
675 
676  template < typename GUM_SCALAR >
680  NodeId toClique) {
684  }
685 
686  template < typename GUM_SCALAR >
687  DecisionPotential< double >
689  const PsiArcProperty& psi,
690  NodeId clique,
691  NodeId except) const {
692  const auto& jt = *junctionTree();
693  DecisionPotential< double > res = phi[clique];
694  for (const auto nei: jt.neighbours(clique))
695  if (nei != except) res *= psi[Arc(nei, clique)];
696 
697  return res;
698  }
699 
700  template < typename GUM_SCALAR >
701  DecisionPotential< double >
703  const PsiArcProperty& psi,
704  NodeId clique) const {
705  const auto& jt = *junctionTree();
706  DecisionPotential< double > res = phi[clique];
707 
708  for (const auto nei: jt.neighbours(clique))
709  res *= psi[Arc(nei, clique)];
710 
711  return res;
712  }
713 
714  template < typename GUM_SCALAR >
716  const PsiArcProperty& psi) {
718 
719  const auto& infdiag = this->influenceDiagram();
720  posteriors_.clear();
721  strategies_.clear();
722  for (const auto node: infdiag.nodes()) {
723  const auto clik = node_to_clique_[node];
725  const auto& finalphi = finalphis[clik];
726 
728  if (infdiag.isChanceNode(node)) {
729  SetOfVars sev;
731  res = finalphi ^ sev;
733  } else if (infdiag.isDecisionNode(node)) {
734  SetOfVars sev;
736  SetOfVars family = sev;
737  for (const auto par: reduced_.parents(node)) {
739  }
740  const auto dp = finalphi ^ family;
741 
745  res = dp ^ sev;
749  }
750  } else { // utility
752 
754  for (const auto par: reduced_.parents(node)) {
756  }
757  res = finalphi ^ family;
758 
761  }
762  }
763  }
764 
765  template < typename GUM_SCALAR >
767  return posteriors_[node].probPot;
768  }
769 
770  template < typename GUM_SCALAR >
771  const Potential< GUM_SCALAR >&
773  return posteriors_[node].utilPot;
774  }
775 
776 
777  template < typename GUM_SCALAR >
780  return posteriors_[node].meanVar();
781  }
782 
783 } /* namespace gum */
784 
785 #endif /* DOXYGEN_SHOULD_SKIP_THIS */
INLINE void emplace(Args &&... args)
Definition: set_tpl.h:643