aGrUM  0.20.2
a C++ library for (probabilistic) graphical models
ShaferShenoyLIMIDInference_tpl.h
Go to the documentation of this file.
1 /**
2  *
3  * Copyright 2005-2020 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  const NodeId id,
71  bool isHardEvidence) {
73  if (infdiag.isUtilityNode(id)) {
74  GUM_ERROR(InvalidNode, "No evidence on a utility node.");
75  }
76  if (infdiag.isDecisionNode(id)) {
77  if (!isHardEvidence)
78  GUM_ERROR(InvalidNode, "No soft evidence on a decision node.");
79  }
80  }
81  template < typename GUM_SCALAR >
83  const NodeId id,
84  bool isHardEvidence) {}
85  template < typename GUM_SCALAR >
87  bool contains_hard_evidence) {}
88  template < typename GUM_SCALAR >
90  const NodeId id,
91  bool hasChangedSoftHard) {}
92  template < typename GUM_SCALAR >
94  const GraphicalModel* model) {
96  }
97 
98  template < typename GUM_SCALAR >
100  createReduced_();
101  }
102  template < typename GUM_SCALAR >
104 
105  template < typename GUM_SCALAR >
107  if (!isSolvable()) {
108  GUM_ERROR(FatalError, "This LIMID/Influence Diagram is not solvable.")
109  }
110 
113 
115 # ifdef GUM_SSLI_TRACE_ON
117 # endif
118  // message passing (using reverse order of solvabilityOrder)
119  // first collect of phis into root
120  const auto firstRootIndice = 0;
122  psi,
125 
127  nextRootIndice++) {
131  phi,
132  psi,
135  }
137  }
138 
139  // last distribution
142  }
143 
144  template < typename GUM_SCALAR >
147  PsiArcProperty& psi) {
148  const auto& jt = *junctionTree();
149  const auto& infdiag = this->influenceDiagram();
150  // init JT potentials and separators
151 
152  for (const auto node: jt.nodes()) {
154  for (const auto nei: jt.neighbours(node)) {
156  if (node < nei) { // to do it only once by edge
157  // we create the set of vars in node and nei (cached in varsSeparators_)
158  for (const auto n: jt.clique(node) * jt.clique(nei))
160  .insert(&(infdiag.variable(n)));
161  }
162  }
163  }
164  for (const auto node: infdiag.nodes()) {
165  const auto clik = node_to_clique_[node];
166  if (this->hasEvidence(node)) {
167  auto q = *(this->evidence()[node]);
169  }
170 
171  if (infdiag.isDecisionNode(node)) {
172  if (!this->hasEvidence(node)) {
173  auto p = (Potential< GUM_SCALAR >() << infdiag.variable(node))
174  .fillWith(1)
175  .normalize();
176  phi[clik].insertProba(p); // WITHOUT NORMALIZATION !!!
177  }
178  } else if (infdiag.isChanceNode(node))
180  else if (infdiag.isUtilityNode(node))
182  else
183  GUM_ERROR(FatalError, "Node " << node << " has no type.");
184  }
185  }
186 
187  template < typename GUM_SCALAR >
189  const auto& infdiag = this->influenceDiagram();
190  auto moral = reduced_.moralGraph();
191 
192  // once the moral graph is completed, we remove the utility nodes before
193  // triangulation
195  for (const auto node: infdiag.nodes())
196  if (infdiag.isUtilityNode(node)) {
198  } else {
200  }
204  }
205 
206  template < typename GUM_SCALAR >
209  // indicate, for each node of the moral graph a clique in JT__ that can
210  // contain its conditional probability table
211  const auto& infdiag = this->influenceDiagram();
217  for (Idx i = Idx(0), size = JT_elim_order.size(); i < size; ++i)
219  for (const auto node: reduced_.nodes()) {
220  if (infdiag.isUtilityNode(node)) {
221  // utility nodes are not in the junction tree but we want to associate a
222  // clique as well
225  = std::numeric_limits< NodeId >::max(); // an impossible elim_number;
226  } else {
227  // get the variables in the potential of node (and its parents)
230  }
231 
232  for (const auto parent: reduced_.parents(node)) {
233  if (elim_order[parent] < elim_number) {
236  }
237  }
238 
239  // first_eliminated_node contains the first var (node or one of its
240  // parents) eliminated => the clique created during its elimination
241  // contains node and all of its parents => it can contain the potential
242  // assigned to the node in the BN
244  node,
246  }
247  }
248 
249  template < typename GUM_SCALAR >
252  if (!this->isInferenceDone())
253  GUM_ERROR(OperationNotAllowed, "Call MakeInference first")
254 
256 
257  GUM_SCALAR resmean = 0;
258  GUM_SCALAR resvar = 0;
259  for (auto node: infdiag.nodes()) {
260  if (infdiag.isUtilityNode(node)) {
261 # ifdef GUM_SSLI_TRACE_ON
263 # endif
264  auto p = meanVar(node);
265  resmean += p.first;
266  resvar += p.second;
267  }
268  }
269  return std::pair< GUM_SCALAR, GUM_SCALAR >(resmean, resvar);
270  }
271 
272  template < typename GUM_SCALAR >
275  if (!this->isInferenceDone())
276  GUM_ERROR(OperationNotAllowed, "Call MakeInference first")
277 
282  << "(" << decisionId << ") is not a decision node.")
283 
284  return strategies_[decisionId];
285  }
286 
287 
288  template < typename GUM_SCALAR >
290  return (!solvabilityOrder_.empty());
291  }
292 
293  template < typename GUM_SCALAR >
295  // from LIMIDS of decision Problems, Lauritzen et Nilsson, 1999
296  reduced_.clear();
300  posteriors_.clear();
302  strategies_.clear();
304 
306 
307  // build reduced_
308  for (auto node: infdiag.nodes()) {
311  }
312 
313  for (const auto& arc: infdiag.arcs())
314  reduced_.addArc(arc.tail(), arc.head());
315 
319  if (isSolvable()) {
320  reducingLIMID__();
322  }
323 
324  this->setState_(
326  }
327 
328  template < typename GUM_SCALAR >
331  // force no forgetting if necessary
333  auto last = *(noForgettingOrder_.begin());
334  for (auto node: noForgettingOrder_)
335  if (node == last) // first one
336  continue;
337  else { // we deal with last->node
338  // adding the whole family of last as parents of node
340  for (auto par: reduced_.parents(last)) {
342  }
343  last = node;
344  }
345  }
346  }
347 
348  template < typename GUM_SCALAR >
350  const NodeSet& utilities) {
354  return;
355  }
356 
358  for (const auto& sen: reversePartialOrder()) {
360  while (!tobetested.empty()) {
361  bool foundOne = false;
362  for (const auto& node: tobetested) {
363  const auto us = utilities * reduced_.descendants(node);
364  NodeSet decs;
365  for (const auto dec: tobetested)
366  if (dec != node) decs += reduced_.family(dec);
369  foundOne = true;
371  break;
372  }
373  }
374  if (!foundOne) { // no solvability
376  return;
377  }
378  }
379  }
380  }
381 
382  template < typename GUM_SCALAR >
384  for (const auto& sen: reversePartialOrder_) {
385  for (auto n: sen) {
386  for (auto p: nonRequisiteNodes_(n))
387  reduced_.eraseArc(Arc(p, n));
388  }
389  }
390  }
391 
392  template < typename GUM_SCALAR >
394  const NodeSet& utilities) {
397 
398  for (const auto& node: utilities)
399  level.insert(node, 0); // utility node is level 0
400 
401  // creating the partial order
402  Size max_level = 0;
406  for (auto node: infdiag.nodes()) {
407  if (infdiag.isUtilityNode(node)) continue;
409  currents.clear();
411  level.insert(node, 0);
412  while (!currents.empty()) {
413  NodeId elt = *(currents.begin());
414  currents.erase(elt);
415 
418 
419  for (auto parent: reduced_.parents(elt)) {
420  Size lev = 0;
421  Size newl;
422  bool ok_to_add = true;
423  for (auto child: reduced_.children(parent)) {
424  if (!level.exists(child)) {
425  ok_to_add = false;
426  break;
427  }
428  newl = level[child];
429  if (infdiag.isDecisionNode(child)) newl += 1;
430  if (lev < newl) lev = newl;
431  }
432  if (ok_to_add) {
434  if (level.exists(parent)) {
435  if (level[parent] != lev)
437  "Trying to set level["
438  << parent << "] to level=" << lev
439  << " but already is " << level[parent]);
440  } else {
442  }
443 
444  if (max_level < lev) max_level = lev;
445  }
446  }
447  }
448  }
449  }
450  Size levmax = 0;
451  for (const auto& k: reversePartialOrder_) {
452  if (k.empty()) break;
453  levmax++;
454  }
456  }
457 
458  template < typename GUM_SCALAR >
459  std::vector< NodeSet >
461  return reversePartialOrder_;
462  }
463  template < typename GUM_SCALAR >
464  bool
466  return !noForgettingOrder_.empty();
467  }
468 
469  template < typename GUM_SCALAR >
471  const std::vector< std::string >& names) {
473  }
474 
475  template < typename GUM_SCALAR >
477  const std::vector< NodeId >& ids) {
478  const auto& infdiag = this->influenceDiagram();
479  for (const auto node: ids) {
480  if (!infdiag.exists(node)) GUM_ERROR(NotFound, node << " is not a NodeId");
483  "Node " << node << " (" << infdiag.variable(node).name()
484  << ") is not a decision node");
485  }
486  if (infdiag.decisionNodeSize() != ids.size())
488  "Some decision nodes are missing in the sequence " << ids);
489 
491  createReduced_();
492  }
493  template < typename GUM_SCALAR >
494  NodeSet
497 
498  if (!infdiag.isDecisionNode(d))
499  GUM_ERROR(TypeError, d << " is not a decision node");
500 
501  NodeSet res;
502  if (reduced_.parents(d).empty()) return res;
503 
504  NodeSet descUs;
505  for (const auto n: reduced_.descendants(d))
507 
509  cumul << d;
511 
513  family << d;
514  bool notReq;
515  for (const auto p: reduced_.parents(d)) {
516  notReq = true;
517  for (const auto u: descUs) {
518  if (g.hasUndirectedPath(p, u, family)) {
519  notReq = false;
520  break;
521  }
522  }
523  if (notReq) res << p;
524  }
525  return res;
526  }
527 
528  template < typename GUM_SCALAR >
531  const auto& infdiag = this->influenceDiagram();
533  for (auto node: infdiag.nodes()) {
536  else if (infdiag.isDecisionNode(node))
538  else // (infdiag.isUtilityNode(node))
540  }
541 
542  for (const auto& arc: reduced_.arcs()) {
543  res.addArc(arc.tail(), arc.head());
544  }
545 
546  for (auto node: infdiag.nodes()) {
549  else if (infdiag.isUtilityNode(node))
551  }
552 
553  // Potentials !!!
554  return res;
555  }
556 
557 
558  template < typename GUM_SCALAR >
559  const JunctionTree*
561  if (!isSolvable()) {
562  GUM_ERROR(FatalError, "This LIMID/Influence Diagram is not solvable.")
563  }
564  return &reducedJunctionTree_;
565  }
566 
567  template < typename GUM_SCALAR >
571  const NodeId rootClique) {
572  const auto& jt = *junctionTree();
573 
574 # ifdef GUM_SSLI_TRACE_ON
575  GUM_TRACE("COLLECTING TO ROOT " << rootClique)
576 # endif
577  std::function< void(NodeId, NodeId) > parcours
578  = [&](NodeId node, NodeId from) {
579  for (const auto nei: jt.neighbours(node)) {
580  if (nei != from) parcours(nei, node);
581  }
583  };
584 
585  for (const auto nei: jt.neighbours(rootClique)) {
587  }
588  }
589  template < typename GUM_SCALAR >
594  NodeId toClique) {
595  const auto& jt = *junctionTree();
596 
597 # ifdef GUM_SSLI_TRACE_ON
598  GUM_TRACE("COLLECTING TO FOLLOWING ROOT from " << fromClique << " to "
599  << toClique)
600 # endif
602  = [&](NodeId node, NodeId from, NodeId target) {
603  if (node == target) return true;
604 
605  bool found = false;
606  NodeId prec;
607  for (const auto nei: jt.neighbours(node)) {
608  if (nei != from)
609  if (revparcours(nei, node, target)) {
610  prec = nei;
611  found = true;
612  break;
613  }
614  }
615  if (found) { transmittingMessage_(phi, psi, prec, node); }
616  return found;
617  };
618 
620  }
621 
622  template < typename GUM_SCALAR >
626  const auto& infdiag = this->influenceDiagram();
627  const auto& jt = *junctionTree();
628 
629 # ifdef GUM_SSLI_TRACE_ON
630  GUM_TRACE(" Deciding for "
631  << infdiag.variable(decisionNode).name() << " in "
632  << node_to_clique_[decisionNode] << "("
634 # endif
635  auto& decision
637 
638  if (this->hasHardEvidence(decisionNode)) {
639  decision = *(this->evidence()[decisionNode]);
640  } else {
641  DecisionPotential< double > dp;
643 
644  SetOfVars sev;
646  for (const auto parent: reduced_.parents(decisionNode)) {
647  // GUM_TRACE(" parent : " << infdiag.variable(parent).name())
649  }
650  dp = dp ^ sev;
651 
652  // SPECIAL CASE FOR DETERMINISTIC DECISION
653  sev.erase(&infdiag.variable(decisionNode)); // only the parents in sev
654  if (sev.size() == 0) { // deterministic decision node
656  } else if (dp.probPot.margSumIn(sev).normalize().max()
657  == 1) { // with deterministic posterior probability
658  // we can use marginalization because we now that dp is deterministic
660  decisionNode,
663  }
665 
667  }
668 # ifdef GUM_SSLI_TRACE_ON
669  GUM_TRACE(" UPDATING PHI(" << node_to_clique_[decisionNode] << ")")
670 # ifdef GUM_SSLI_POTENTIAL_TRACE_ON
673 # endif
674 # endif
676 # ifdef GUM_SSLI_POTENTIAL_TRACE_ON
678 # endif
679  }
680 
681  template < typename GUM_SCALAR >
683  const Potential< GUM_SCALAR >& decision,
684  const Potential< GUM_SCALAR >& proba)
685  const { // compute the decisions (as maxEU)
687  const auto& firstvar = decision.variable(0);
688  for (I.setFirst(); !I.end(); I.incNotVar(firstvar)) {
690  while (proba[I] == 0) {
691  I.incVar(firstvar);
692  if (I.end()) { // for non valid proba, we keep the first value (by
693  // default)²
695  break;
696  }
697  }
698  // we found a non null value of proba
699  Idx argm = I.val(firstvar);
701  GUM_SCALAR pmax = proba[I];
702  for (I.incVar(firstvar); !I.end(); I.incVar(firstvar)) {
703  // lexicographical order on (u,p)
704  if (proba[I] > 0) {
705  if ((umax < decision[I])
706  || ((umax == decision[I]) && (pmax < proba[I]))) {
707  umax = decision[I];
708  pmax = proba[I];
709  argm = I.val(firstvar);
710  }
711  }
712  }
713  for (I.setFirstVar(firstvar); !I.end(); I.incVar(firstvar))
714  decision.set(I, 0);
715  I.chgVal(firstvar, argm);
716  decision.set(I, 1);
717  }
718  }
719 
720  template < typename GUM_SCALAR >
724  NodeId rootClique) {
725  const auto& jt = *junctionTree();
726 
727  std::function< void(NodeId, NodeId) > parcours
728  = [&](NodeId node, NodeId from) {
731  for (const auto nei: jt.neighbours(node)) {
732  if (nei != from) parcours(nei, node);
733  }
734  };
735 # ifdef GUM_SSLI_TRACE_ON
736  GUM_TRACE(" DISTRIBUTING")
737 # endif
738 
739  // phi.set(rootClique, integrating_(phi, psi, rootClique));
740  for (const auto nei: jt.neighbours(rootClique)) {
742  }
743  }
744 
745  template < typename GUM_SCALAR >
750  NodeId toClique) {
751  // phi has been updated. No need to integrate anythin
754 
755 # ifdef GUM_SSLI_TRACE_ON
756  const auto& jt = *junctionTree();
757  const auto& infdiag = this->influenceDiagram();
758  GUM_TRACE("Computing final message : "
759  << fromClique << ":" << infdiag.names(jt.clique(fromClique)) << "->"
760  << toClique << ":" << infdiag.names(jt.clique(toClique)))
761 # ifdef GUM_SSLI_POTENTIAL_TRACE_ON
763 # endif
764 # endif
765  }
766 
767  template < typename GUM_SCALAR >
772  NodeId toClique) {
776 
777 # ifdef GUM_SSLI_TRACE_ON
778  const auto& jt = *junctionTree();
779  const auto& infdiag = this->influenceDiagram();
780  GUM_TRACE("Computing message : "
781  << fromClique << ":" << infdiag.names(jt.clique(fromClique)) << "->"
782  << toClique << ":" << infdiag.names(jt.clique(toClique)))
783 
784  //# ifdef GUM_SSLI_POTENTIAL_TRACE_ON
786 //# endif
787 # endif
788  }
789 
790  template < typename GUM_SCALAR >
791  DecisionPotential< double >
793  const PhiNodeProperty& phi,
794  const PsiArcProperty& psi,
795  NodeId clique,
796  NodeId except) const {
797  const auto& jt = *junctionTree();
798  DecisionPotential< double > res = phi[clique];
799  for (const auto nei: jt.neighbours(clique))
800  if (nei != except) res *= psi[Arc(nei, clique)];
801 
802 # ifdef GUM_SSLI_TRACE_ON
803  const auto& infdiag = this->influenceDiagram();
804  GUM_TRACE(" Integrating into : " << clique << ":"
806 
807 # ifdef GUM_SSLI_POTENTIAL_TRACE_ON
810 # endif
811 # endif
812  return res;
813  }
814 
815  template < typename GUM_SCALAR >
816  DecisionPotential< double >
818  const PhiNodeProperty& phi,
819  const PsiArcProperty& psi,
820  NodeId clique) const {
821  const auto& jt = *junctionTree();
822  DecisionPotential< double > res = phi[clique];
823 
824 # ifdef GUM_SSLI_TRACE_ON
825  const auto& infdiag = this->influenceDiagram();
826  GUM_TRACE(" Full-Integrating into : " << clique << ":"
828 # endif
829 
830  for (const auto nei: jt.neighbours(clique))
831  res *= psi[Arc(nei, clique)];
832 
833 # ifdef GUM_SSLI_TRACE_ON
834  GUM_TRACE(" After Full-Integrating into : "
835  << clique << ":" << infdiag.names(jt.clique(clique)))
836 # endif
837 
838 
839 # ifdef GUM_SSLI_TRACE_ON
840 # ifdef GUM_SSLI_POTENTIAL_TRACE_ON
842 # endif
843 # endif
844 
845  return res;
846  }
847 
848  template < typename GUM_SCALAR >
850  const PhiNodeProperty& phi,
851  const PsiArcProperty& psi) {
853 
854  const auto& infdiag = this->influenceDiagram();
855  posteriors_.clear();
856  strategies_.clear();
857  for (const auto node: infdiag.nodes()) {
858 # ifdef GUM_SSLI_TRACE_ON
859  GUM_TRACE(" ---> Posterior for variable " << infdiag.variable(node).name())
861 # endif
862  const auto clik = node_to_clique_[node];
863  if (!finalphis.exists(clik))
865  const auto& finalphi = finalphis[clik];
866 
867  auto& res
869  if (infdiag.isChanceNode(node)) {
870  SetOfVars sev;
872  res = finalphi ^ sev;
874  } else if (infdiag.isDecisionNode(node)) {
875  SetOfVars sev;
877  SetOfVars family = sev;
878  for (const auto par: reduced_.parents(node)) {
880  }
881  const auto dp = finalphi ^ family;
882 
883 # ifdef GUM_SSLI_TRACE_ON
885 # ifdef GUM_SSLI_POTENTIAL_TRACE_ON
887 
889 # endif
890 # endif
891 
892  gum::Potential< double > decision
896  res = dp ^ sev;
900  }
901  } else { // utility
903 
905  for (const auto par: reduced_.parents(node)) {
907  }
908  res = finalphi ^ family;
909 
912  }
913  }
914  }
915 
916  template < typename GUM_SCALAR >
917  const Potential< GUM_SCALAR >&
919  return posteriors_[node].probPot;
920  }
921 
922  template < typename GUM_SCALAR >
923  const Potential< GUM_SCALAR >&
925  return posteriors_[node].utilPot;
926  }
927 
928 
929  template < typename GUM_SCALAR >
932  return posteriors_[node].meanVar();
933  }
934 
935 } /* namespace gum */
936 
937 #endif /* DOXYGEN_SHOULD_SKIP_THIS */
INLINE void emplace(Args &&... args)
Definition: set_tpl.h:669