aGrUM  0.21.0
a C++ library for (probabilistic) graphical models
BayesNet_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 Template implementation of BN/BayesNet.h class.
25  *
26  * @author Pierre-Henri WUILLEMIN(@LIP6) and Lionel TORTI
27  */
28 
29 #include <limits>
30 #include <set>
31 #include <algorithm>
32 
33 #include <agrum/BN/BayesNet.h>
34 
35 #include <agrum/tools/variables/rangeVariable.h>
36 #include <agrum/tools/variables/labelizedVariable.h>
37 #include <agrum/tools/variables/integerVariable.h>
38 #include <agrum/tools/variables/discretizedVariable.h>
39 
40 #include <agrum/tools/multidim/aggregators/amplitude.h>
41 #include <agrum/tools/multidim/aggregators/and.h>
42 #include <agrum/tools/multidim/aggregators/count.h>
43 #include <agrum/tools/multidim/aggregators/exists.h>
44 #include <agrum/tools/multidim/aggregators/forall.h>
45 #include <agrum/tools/multidim/aggregators/max.h>
46 #include <agrum/tools/multidim/aggregators/median.h>
47 #include <agrum/tools/multidim/aggregators/min.h>
48 #include <agrum/tools/multidim/aggregators/or.h>
49 #include <agrum/tools/multidim/aggregators/sum.h>
50 
51 #include <agrum/tools/multidim/ICIModels/multiDimNoisyAND.h>
52 #include <agrum/tools/multidim/ICIModels/multiDimNoisyORCompound.h>
53 #include <agrum/tools/multidim/ICIModels/multiDimNoisyORNet.h>
54 
55 #include <agrum/tools/multidim/ICIModels/multiDimLogit.h>
56 
57 #include <agrum/BN/generator/simpleCPTGenerator.h>
58 #include <agrum/tools/core/utils_string.h>
59 
60 namespace gum {
61  template < typename GUM_SCALAR >
62  NodeId
64  std::string name = node;
65  auto ds = default_domain_size;
66  long range_min = 0;
67  long range_max = long(ds) - 1;
68  std::vector< std::string > labels;
70 
71  if (*(node.rbegin()) == ']') {
72  auto posBrack = node.find('[');
73  if (posBrack != std::string::npos) {
74  name = node.substr(0, posBrack);
75  const auto& s_args = node.substr(posBrack + 1, node.size() - posBrack - 2);
76  const auto& args = split(s_args, ",");
77  if (args.size() == 0) { // n[]
78  GUM_ERROR(InvalidArgument, "Empty range for variable " << node)
79  } else if (args.size() == 1) { // n[4]
80  ds = static_cast< Size >(std::stoi(args[0]));
81  range_min = 0;
82  range_max = long(ds) - 1;
83  } else if (args.size() == 2) { // n[5,10]
84  range_min = std::stol(args[0]);
85  range_max = std::stol(args[1]);
86  if (1 + range_max - range_min < 2) {
87  GUM_ERROR(InvalidArgument, "Invalid range for variable " << node)
88  }
89  ds = static_cast< Size >(1 + range_max - range_min);
90  } else { // n[3.14,5,10,12]
91  for (const auto& tick: args) {
92  ticks.push_back(static_cast< GUM_SCALAR >(std::atof(tick.c_str())));
93  }
94  ds = static_cast< Size >(args.size() - 1);
95  }
96  }
97  } else if (*(node.rbegin()) == '}') { // node like "n{one|two|three}"
98  auto posBrack = node.find('{');
99  if (posBrack != std::string::npos) {
100  name = node.substr(0, posBrack);
101  labels = split(node.substr(posBrack + 1, node.size() - posBrack - 2), "|");
102  if (labels.size() < 2) { GUM_ERROR(InvalidArgument, "Not enough labels in node " << node) }
103  if (!hasUniqueElts(labels)) {
104  GUM_ERROR(InvalidArgument, "Duplicate labels in node " << node)
105  }
106  ds = static_cast< Size >(labels.size());
107  }
108  }
109 
110  if (ds == 0) {
111  GUM_ERROR(InvalidArgument, "No value for variable " << name << ".")
112  } else if (ds == 1) {
114  "Only one value for variable " << name << " (2 at least are needed).")
115  }
116 
117  std::vector< int > values;
118  if (!labels.empty()) {
119  if (std::all_of(labels.begin(), labels.end(), isInteger)) {
120  for (const auto& label: labels)
122  }
123  }
124 
125  // now we add the node in the BN
126  NodeId idVar;
127  try {
128  idVar = bn.idFromName(name);
129  } catch (gum::NotFound&) {
130  if (!values.empty()) {
132  } else if (!labels.empty()) {
134  } else if (!ticks.empty()) {
136  } else {
138  }
139  }
140 
141  return idVar;
142  }
143 
144  template < typename GUM_SCALAR >
145  BayesNet< GUM_SCALAR > BayesNet< GUM_SCALAR >::fastPrototype(const std::string& dotlike,
146  Size domainSize) {
148 
149 
150  for (const auto& chaine: split(dotlike, ";")) {
151  NodeId lastId = 0;
152  bool notfirst = false;
153  for (const auto& souschaine: split(chaine, "->")) {
154  bool forward = true;
155  for (const auto& node: split(souschaine, "<-")) {
156  auto idVar = build_node(bn, node, domainSize);
157  if (notfirst) {
158  if (forward) {
159  bn.addArc(lastId, idVar);
160  forward = false;
161  } else {
162  bn.addArc(idVar, lastId);
163  }
164  } else {
165  notfirst = true;
166  forward = false;
167  }
168  lastId = idVar;
169  }
170  }
171  }
172  bn.generateCPTs();
173  bn.setProperty("name", "fastPrototype");
174  return bn;
175  }
176 
177  template < typename GUM_SCALAR >
180  }
181 
182  template < typename GUM_SCALAR >
185  }
186 
187  template < typename GUM_SCALAR >
191 
193  }
194 
195  template < typename GUM_SCALAR >
197  if (this != &source) {
200 
203  }
204 
205  return *this;
206  }
207 
208  template < typename GUM_SCALAR >
211  for (const auto p: _probaMap_) {
212  delete p.second;
213  }
214  }
215 
216  template < typename GUM_SCALAR >
218  return _varMap_.get(id);
219  }
220 
221  template < typename GUM_SCALAR >
224  }
225 
226  template < typename GUM_SCALAR >
228  const std::string& old_label,
229  const std::string& new_label) {
230  if (variable(id).varType() != VarType::Labelized)
231  GUM_ERROR(NotFound, "Variable " << id << " is not a LabelizedVariable.")
232 
234  = dynamic_cast< LabelizedVariable* >(const_cast< DiscreteVariable* >(&variable(id)));
235 
237  }
238 
239 
240  template < typename GUM_SCALAR >
242  return _varMap_.get(var);
243  }
244 
245  template < typename GUM_SCALAR >
247  auto ptr = new MultiDimArray< GUM_SCALAR >();
248  try {
249  return add(var, ptr);
250  } catch (Exception&) {
251  delete ptr;
252  throw;
253  }
254  }
255 
256  template < typename GUM_SCALAR >
257  INLINE NodeId BayesNet< GUM_SCALAR >::add(const std::string& name, unsigned int nbrmod) {
258  if (nbrmod < 2) {
260  "Variable " << name << "needs more than " << nbrmod << " modalities")
261  }
262 
263  RangeVariable v(name, name, 0, nbrmod - 1);
264  return add(v);
265  }
266 
267  template < typename GUM_SCALAR >
271 
272  return add(var, aContent, proposedId);
273  }
274 
275  template < typename GUM_SCALAR >
277  auto ptr = new MultiDimArray< GUM_SCALAR >();
278 
279  try {
280  return add(var, ptr, id);
281 
282  } catch (Exception&) {
283  delete ptr;
284  throw;
285  }
286  }
287 
288  template < typename GUM_SCALAR >
291  NodeId id) {
292  _varMap_.insert(id, var);
293  this->dag_.addNodeWithId(id);
294 
295  auto cpt = new Potential< GUM_SCALAR >(aContent);
296  (*cpt) << variable(id);
298  return id;
299  }
300 
301  template < typename GUM_SCALAR >
303  return _varMap_.idFromName(name);
304  }
305 
306  template < typename GUM_SCALAR >
307  INLINE const DiscreteVariable&
310  }
311 
312  template < typename GUM_SCALAR >
314  return *(_probaMap_[varId]);
315  }
316 
317  template < typename GUM_SCALAR >
319  return _varMap_;
320  }
321 
322  template < typename GUM_SCALAR >
324  erase(_varMap_.get(var));
325  }
326 
327  template < typename GUM_SCALAR >
329  if (_varMap_.exists(varId)) {
330  // Reduce the variable child's CPT
331  const NodeSet& children = this->children(varId);
332 
333  for (const auto c: children) {
335  }
336 
337  delete _probaMap_[varId];
338 
341  this->dag_.eraseNode(varId);
342  }
343  }
344 
345  template < typename GUM_SCALAR >
346  void BayesNet< GUM_SCALAR >::clear() {
347  if (!this->empty()) {
348  auto l = this->nodes();
349  for (const auto no: l) {
350  this->erase(no);
351  }
352  }
353  }
354 
355  template < typename GUM_SCALAR >
357  if (this->dag_.existsArc(tail, head)) {
358  GUM_ERROR(DuplicateElement, "The arc (" << tail << "," << head << ") already exists.")
359  }
360 
361  this->dag_.addArc(tail, head);
362  // Add parent in the child's CPT
363  (*(_probaMap_[head])) << variable(tail);
364  }
365 
366  template < typename GUM_SCALAR >
367  INLINE void BayesNet< GUM_SCALAR >::addArc(const std::string& tail, const std::string& head) {
368  try {
369  addArc(this->idFromName(tail), this->idFromName(head));
370  } catch (DuplicateElement) {
371  GUM_ERROR(DuplicateElement, "The arc " << tail << "->" << head << " already exists.")
372  }
373  }
374 
375  template < typename GUM_SCALAR >
377  if (_varMap_.exists(arc.tail()) && _varMap_.exists(arc.head())) {
378  NodeId head = arc.head(), tail = arc.tail();
379  this->dag_.eraseArc(arc);
380  // Remove parent from child's CPT
381  (*(_probaMap_[head])) >> variable(tail);
382  }
383  }
384 
385  template < typename GUM_SCALAR >
387  eraseArc(Arc(tail, head));
388  }
389 
390  template < typename GUM_SCALAR >
391  void BayesNet< GUM_SCALAR >::reverseArc(const Arc& arc) {
392  // check that the arc exists
393  if (!_varMap_.exists(arc.tail()) || !_varMap_.exists(arc.head()) || !dag().existsArc(arc)) {
394  GUM_ERROR(InvalidArc, "a non-existing arc cannot be reversed")
395  }
396 
397  NodeId tail = arc.tail(), head = arc.head();
398 
399  // check that the reversal does not induce a cycle
400  try {
401  DAG d = dag();
402  d.eraseArc(arc);
403  d.addArc(head, tail);
404  } catch (Exception&) {
405  GUM_ERROR(InvalidArc, "this arc reversal would induce a directed cycle")
406  }
407 
408  // with the same notations as Shachter (1986), "evaluating influence
409  // diagrams", p.878, we shall first compute the product of probabilities:
410  // pi_j^old (x_j | x_c^old(j) ) * pi_i^old (x_i | x_c^old(i) )
412 
413  // modify the topology of the graph: add to tail all the parents of head
414  // and add to head all the parents of tail
417  for (const auto node: this->parents(tail))
419  for (const auto node: this->parents(head))
421  // remove arc (head, tail)
422  eraseArc(arc);
423 
424  // add the necessary arcs to the tail
425  for (const auto p: new_parents) {
426  if ((p != tail) && !dag().existsArc(p, tail)) { addArc(p, tail); }
427  }
428 
429  addArc(head, tail);
430  // add the necessary arcs to the head
432 
433  for (const auto p: new_parents) {
434  if ((p != head) && !dag().existsArc(p, head)) { addArc(p, head); }
435  }
436 
438 
439  // update the conditional distributions of head and tail
440  Set< const DiscreteVariable* > del_vars;
441  del_vars << &(variable(tail));
443 
444  auto& cpt_head = const_cast< Potential< GUM_SCALAR >& >(cpt(head));
446 
448  auto& cpt_tail = const_cast< Potential< GUM_SCALAR >& >(cpt(tail));
450  }
451 
452  template < typename GUM_SCALAR >
454  reverseArc(Arc(tail, head));
455  }
456 
457 
458  //==============================================
459  // Aggregators
460  //=============================================
461  template < typename GUM_SCALAR >
463  return add(var, new aggregator::Amplitude< GUM_SCALAR >());
464  }
465 
466  template < typename GUM_SCALAR >
468  if (var.domainSize() > 2) GUM_ERROR(SizeError, "an AND has to be boolean")
469 
470  return add(var, new aggregator::And< GUM_SCALAR >());
471  }
472 
473  template < typename GUM_SCALAR >
475  return add(var, new aggregator::Count< GUM_SCALAR >(value));
476  }
477 
478  template < typename GUM_SCALAR >
480  if (var.domainSize() > 2) GUM_ERROR(SizeError, "an EXISTS has to be boolean")
481 
482  return add(var, new aggregator::Exists< GUM_SCALAR >(value));
483  }
484 
485  template < typename GUM_SCALAR >
487  if (var.domainSize() > 2) GUM_ERROR(SizeError, "an EXISTS has to be boolean")
488 
489  return add(var, new aggregator::Forall< GUM_SCALAR >(value));
490  }
491 
492  template < typename GUM_SCALAR >
494  return add(var, new aggregator::Max< GUM_SCALAR >());
495  }
496 
497  template < typename GUM_SCALAR >
499  return add(var, new aggregator::Median< GUM_SCALAR >());
500  }
501 
502  template < typename GUM_SCALAR >
504  return add(var, new aggregator::Min< GUM_SCALAR >());
505  }
506 
507  template < typename GUM_SCALAR >
509  if (var.domainSize() > 2) GUM_ERROR(SizeError, "an OR has to be boolean")
510 
511  return add(var, new aggregator::Or< GUM_SCALAR >());
512  }
513 
514  template < typename GUM_SCALAR >
516  return add(var, new aggregator::Sum< GUM_SCALAR >());
517  }
518 
519  //================================
520  // ICIModels
521  //================================
522  template < typename GUM_SCALAR >
526  }
527 
528  template < typename GUM_SCALAR >
532  }
533 
534  template < typename GUM_SCALAR >
538  }
539 
540  template < typename GUM_SCALAR >
544  }
545 
546  template < typename GUM_SCALAR >
550  }
551 
552  template < typename GUM_SCALAR >
555  NodeId id) {
557  }
558 
559  template < typename GUM_SCALAR >
562  NodeId id) {
564  }
565 
566  template < typename GUM_SCALAR >
569  NodeId id) {
570  return add(var, new MultiDimLogit< GUM_SCALAR >(external_weight), id);
571  }
572 
573  template < typename GUM_SCALAR >
576  NodeId id) {
578  }
579 
580  template < typename GUM_SCALAR >
583  NodeId id) {
585  }
586 
587  template < typename GUM_SCALAR >
589  auto* CImodel = dynamic_cast< const MultiDimICIModel< GUM_SCALAR >* >(cpt(head).content());
590 
591  if (CImodel != 0) {
592  // or is OK
593  addArc(tail, head);
594 
596  } else {
598  "Head variable (" << variable(head).name() << ") is not a CIModel variable !")
599  }
600  }
601 
602  template < typename GUM_SCALAR >
604  output << bn.toString();
605  return output;
606  }
607 
608  /// begin Multiple Change for all CPTs
609  template < typename GUM_SCALAR >
611  for (const auto node: nodes())
613  }
614 
615  /// end Multiple Change for all CPTs
616  template < typename GUM_SCALAR >
618  for (const auto node: nodes())
620  }
621 
622  /// clear all potentials
623  template < typename GUM_SCALAR >
625  // Removing previous potentials
626  for (const auto& elt: _probaMap_) {
627  delete elt.second;
628  }
629 
630  _probaMap_.clear();
631  }
632 
633  /// copy of potentials from a BN to another, using names of vars as ref.
634  template < typename GUM_SCALAR >
636  // Copying potentials
637 
638  for (const auto src: source._probaMap_) {
639  // First we build the node's CPT
642  for (gum::Idx i = 0; i < src.second->nbrDim(); i++) {
644  }
647 
648  // We add the CPT to the CPT hashmap
650  }
651  }
652 
653  template < typename GUM_SCALAR >
655  for (const auto node: nodes())
656  generateCPT(node);
657  }
658 
659  template < typename GUM_SCALAR >
662 
664  }
665 
666  template < typename GUM_SCALAR >
668  if (cpt(id).nbrDim() != newPot->nbrDim()) {
670  "cannot exchange potentials with different "
671  "dimensions for variable with id "
672  << id)
673  }
674 
675  for (Idx i = 0; i < cpt(id).nbrDim(); i++) {
676  if (&cpt(id).variable(i) != &(newPot->variable(i))) {
678  "cannot exchange potentials because, for variable with id "
679  << id << ", dimension " << i << " differs. ")
680  }
681  }
682 
684  }
685 
686  template < typename GUM_SCALAR >
688  delete _probaMap_[id];
689  _probaMap_[id] = newPot;
690  }
691 
692  template < typename GUM_SCALAR >
696  }
697 
698 } /* namespace gum */
INLINE void emplace(Args &&... args)
Definition: set_tpl.h:643
NodeId build_node(gum::BayesNet< GUM_SCALAR > &bn, std::string node, gum::Size default_domain_size)
Definition: BayesNet_tpl.h:63