aGrUM 3.0.0
a C++ library for (probabilistic) graphical models
influenceDiagram_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
51
52#include <algorithm>
53#include <cstdio>
54#include <iostream>
55
58
59namespace gum {
60 template < GUM_Numeric GUM_SCALAR >
62 std::string node,
63 std::string_view domain) {
64 bool isUtil = false;
65 bool isDeci = false;
66 bool isChanc = false;
67 std::string ds(domain);
68 switch (*(node.begin())) {
69 case '*' :
70 isDeci = true;
71 node.erase(0, 1);
72 break;
73 case '$' :
74 isUtil = true;
75 ds = "[1]";
76 node.erase(0, 1);
77 break;
78 default : isChanc = true;
79 }
80 auto v = fastVariable< GUM_SCALAR >(node, ds);
81
82 NodeId res;
83 if (infdiag.exists(v->name())) {
84 res = infdiag.idFromName(v->name());
85 } else {
86 if (isChanc) res = infdiag.addChanceNode(*v);
87 else if (isDeci) res = infdiag.addDecisionNode(*v);
88 else if (isUtil) res = infdiag.addUtilityNode(*v);
89 else
91 "No type (chance, decision or utility) for the node '" << node << "'.")
92 }
93
94 return res;
95 }
96
97 template < GUM_Numeric GUM_SCALAR >
98 InfluenceDiagram< GUM_SCALAR >
99 InfluenceDiagram< GUM_SCALAR >::fastPrototype(std::string_view dotlike, Size domainSize) {
100 return fastPrototype(dotlike, "[" + std::to_string(domainSize) + "]");
101 }
102
103 template < GUM_Numeric GUM_SCALAR >
106 std::string_view domain) {
108
109 for (const auto& chaine: split(remove_newline(dotlike), ";")) {
110 NodeId lastId = 0;
111 bool notfirst = false;
112 for (const auto& souschaine: split(chaine, "->")) {
113 bool forward = true;
114 for (auto& node: split(souschaine, "<-")) {
115 auto idVar = build_node_for_ID(infdiag, node, domain);
116 if (notfirst) {
117 if (forward) {
118 infdiag.addArc(lastId, idVar);
119 forward = false;
120 } else {
121 infdiag.addArc(idVar, lastId);
122 }
123 } else {
124 notfirst = true;
125 forward = false;
126 }
127 lastId = idVar;
128 }
129 }
130 }
131
132 for (const auto n: infdiag.nodes()) {
133 if (infdiag.isChanceNode(n)) infdiag.cpt(n).randomCPT();
134 else if (infdiag.isUtilityNode(n)) { infdiag.utility(n).random().scale(50).translate(-10); }
135 }
136
137 infdiag.setProperty("name", "anonymousID");
138 return infdiag;
139 }
140
141 // ===========================================================================
142 // Constructors / Destructors
143 // ===========================================================================
144
145 /*
146 * Default constructor.
147 */
148 template < GUM_Numeric GUM_SCALAR >
152
153 /*
154 * Destructor.
155 */
156 template < GUM_Numeric GUM_SCALAR >
161
162 template < GUM_Numeric GUM_SCALAR >
164 :
165 DAGmodel(std::move(source)), _tensorMap_(std::move(source._tensorMap_)),
166 _utilityMap_(std::move(source._utilityMap_)),
167 _temporalOrder_(std::move(source._temporalOrder_)) {
168 GUM_CONS_MOV(InfluenceDiagram)
169 }
170
171 template < GUM_Numeric GUM_SCALAR >
174 if (this != &source) {
176 DAGmodel::operator=(std::move(source));
177 _tensorMap_ = std::move(source._tensorMap_);
178 _utilityMap_ = std::move(source._utilityMap_);
179 _temporalOrder_ = std::move(source._temporalOrder_);
180 GUM_OP_MOV(InfluenceDiagram)
181 }
182 return *this;
183 }
184
185 /*
186 * Copy Constructor
187 */
188 template < GUM_Numeric GUM_SCALAR >
193
194 /*
195 * Copy Operator
196 */
197 template < GUM_Numeric GUM_SCALAR >
200 if (this != &source) {
201 clear();
202 // Copying tables and structure
204 }
205
206 return *this;
207 }
208
209 template < GUM_Numeric GUM_SCALAR >
211 // Removing previous tensors
212 removeTables_();
213 this->varMap_.clear();
214 dag_.clear();
215 _tensorMap_.clear();
216 _utilityMap_.clear();
217 }
218
219 /*
220 * Removing ancient table
221 */
222 template < GUM_Numeric GUM_SCALAR >
224 for (const auto& [node, tensor]: _tensorMap_)
225 delete tensor;
226 for (const auto& [node, tensor]: _utilityMap_)
227 delete tensor;
228 }
229
230 /*
231 * Copying tables from another influence diagram
232 */
233 template < GUM_Numeric GUM_SCALAR >
235 const InfluenceDiagram< GUM_SCALAR >& IDsource) {
236 for (auto node: IDsource.nodes()) {
237 if (IDsource.isChanceNode(node)) addChanceNode(IDsource.variable(node), node);
238 else if (IDsource.isUtilityNode(node)) addUtilityNode(IDsource.variable(node), node);
239 else // decision node
240 addDecisionNode(IDsource.variable(node), node);
241 }
242 // we add arc in the same order of the tensors
243 for (auto node: IDsource.nodes()) {
244 const auto& s = IDsource.variable(node).name();
245 if (IDsource.isChanceNode(node)) {
246 for (Idx par = 1; par <= IDsource.parents(node).size(); par++)
247 addArc(IDsource.cpt(node).variable(par).name(), s);
248 } else if (IDsource.isUtilityNode(node)) {
249 for (Idx par = 1; par <= IDsource.parents(node).size(); par++)
250 addArc(IDsource.utility(node).variable(par).name(), s);
251 } else { // decision node
252 // here the order does not depend on a Tensor
253 for (NodeId par: IDsource.parents(node))
254 addArc(par, node);
255 }
256 }
257
258 // Copying tensors
259 for (auto node: IDsource.nodes()) {
260 const auto& s = IDsource.variable(node).name();
261 if (IDsource.isChanceNode(node)) {
262 cpt(node).fillWith(IDsource.cpt(s));
263 } else if (IDsource.isUtilityNode(node)) {
264 utility(node).fillWith(IDsource.utility(s));
265 }
266 }
267 }
268
269 template < GUM_Numeric GUM_SCALAR >
271 std::stringstream output;
272 std::stringstream decisionNode;
273 std::stringstream utilityNode;
274 std::stringstream chanceNode;
275 std::stringstream arcstream;
276
277 output << std::format("digraph \"{}\" {{\n", this->propertyWithDefault("name", "no_name"));
278
279 output << " node [bgcolor=\"#AAAAAA\", style=filled, height=0];" << std::endl;
280
281 decisionNode << "node [shape = box];" << std::endl;
282
283 utilityNode << "node [shape = hexagon, margin=0];" << std::endl;
284 chanceNode << "node [shape = ellipse];" << std::endl;
285 std::string tab = " ";
286
287 for (const auto node: dag_.nodes()) {
288 if (isChanceNode(node))
289 chanceNode << std::format(" \"{}-{}\";", node, variable(node).name());
290 else if (isUtilityNode(node))
291 utilityNode << std::format(" \"{}-{}\";", node, variable(node).name());
292 else decisionNode << std::format(" \"{}-{}\";", node, variable(node).name());
293
294 if (dag_.children(node).size() > 0)
295 for (const auto chi: dag_.children(node)) {
296 arcstream << std::format("\"{}-{}\" -> \"{}-{}\"",
297 node,
298 variable(node).name(),
299 chi,
300 variable(chi).name());
301 if (isDecisionNode(chi)) { arcstream << " [style=\"tapered, bold\"]"; }
302 arcstream << ";\n";
303 }
304 }
305
306 output << decisionNode.str() << std::endl
307 << utilityNode.str() << std::endl
308 << chanceNode.str() << std::endl
309 << std::endl
310 << arcstream.str() << std::endl
311 << "}" << std::endl;
312
313 return output.str();
314 }
315
316 template < GUM_Numeric GUM_SCALAR >
318 std::stringstream output;
319
320 output << "Influence Diagram{" << std::endl;
321 output << std::format(" chance: {},\n", chanceNodeSize());
322 output << std::format(" utility: {},\n", utilityNodeSize());
323 output << std::format(" decision: {},\n", decisionNodeSize());
324 output << std::format(" arcs: {},\n", dag().sizeArcs());
325
326 if (double dSize = log10DomainSize(); dSize > 6)
327 output << std::format(" domainSize: 10^{}", dSize);
328 else output << std::format(" domainSize: {}", std::round(std::pow(10.0, dSize)));
329
330 output << std::endl << "}";
331
332 return output.str();
333 }
334
335 // ===========================================================================
336 // Variable manipulation methods.
337 // ===========================================================================
338
339 /*
340 * Returns the CPT of a chance variable.
341 */
342 template < GUM_Numeric GUM_SCALAR >
343 const Tensor< GUM_SCALAR >& InfluenceDiagram< GUM_SCALAR >::cpt(NodeId varId) const {
344 return *(_tensorMap_[varId]);
345 }
346
347 /*
348 * Returns the utility table of a utility node.
349 */
350 template < GUM_Numeric GUM_SCALAR >
351 const Tensor< GUM_SCALAR >& InfluenceDiagram< GUM_SCALAR >::utility(NodeId varId) const {
352 return *(_utilityMap_[varId]);
353 }
354
355 /*
356 * Return true if node is a utility one
357 */
358 template < GUM_Numeric GUM_SCALAR >
360 return _utilityMap_.exists(varId);
361 }
362
363 /*
364 * Return true if node is a utility one
365 */
366 template < GUM_Numeric GUM_SCALAR >
368 bool ret = true;
369
370 if (isUtilityNode(varId) || isChanceNode(varId)) ret = false;
371
372 return ret;
373 }
374
375 /*
376 * Return true if node is a chance one
377 */
378 template < GUM_Numeric GUM_SCALAR >
380 return _tensorMap_.exists(varId);
381 }
382
383 /*
384 * Returns the number of utility nodes
385 */
386 template < GUM_Numeric GUM_SCALAR >
390
391 /*
392 * Returns the number of chance nodes
393 */
394 template < GUM_Numeric GUM_SCALAR >
398
399 /*
400 * Returns the number of decision nodes
401 */
402 template < GUM_Numeric GUM_SCALAR >
404 return (size() - _utilityMap_.size() - _tensorMap_.size());
405 }
406
407 /*
408 * Add a chance variable, it's associate node and it's CPT. The id of the new
409 * variable is automatically generated.
410 */
411 template < GUM_Numeric GUM_SCALAR >
413 return addChanceNode(var, varId);
414 }
415
416 /*
417 * Add a utility variable, it's associate node and it's UT. The id of the new
418 * variable is automatically generated.
419 * @Throws : Gum::InvalidArgument if var has more than one state
420 */
421 template < GUM_Numeric GUM_SCALAR >
423 auto newMultiDim = new MultiDimArray< GUM_SCALAR >();
424 NodeId res;
425
426 try {
427 res = addUtilityNode(var, newMultiDim, varId);
428 } catch (Exception const&) {
429 if (newMultiDim != nullptr) delete newMultiDim;
430 throw;
431 }
432
433 return res;
434 }
435
436 /*
437 * Add a decision variable. The id of the new
438 * variable is automatically generated.
439 */
440 template < GUM_Numeric GUM_SCALAR >
442 NodeId varId) {
443 return addNode_(var, varId);
444 }
445
446 /*
447 * Add a chance variable, it's associate node and it's CPT. The id of the new
448 * variable is automatically generated.
449 */
450 template < GUM_Numeric GUM_SCALAR >
452 auto newMultiDim = new MultiDimArray< GUM_SCALAR >();
453 NodeId res;
454
455 try {
456 res = addChanceNode(var, newMultiDim, varId);
457 } catch (Exception const&) {
458 delete newMultiDim;
459 throw;
460 }
461
462 return res;
463 }
464
465 /*
466 * Add a chance variable, it's associate node and it's CPT. The id of the new
467 * variable is automatically generated.
468 */
469 template < GUM_Numeric GUM_SCALAR >
470 NodeId
473 NodeId DesiredId) {
474 NodeId proposedId = addNode_(var, DesiredId);
475
476 auto varcpt = new Tensor< GUM_SCALAR >(aContent);
477 (*varcpt) << variable(proposedId);
478 _tensorMap_.insert(proposedId, varcpt);
479
480 return proposedId;
481 }
482
483 /*
484 * Add a utility variable, it's associate node and it's UT. The id of the new
485 * variable is automatically generated.
486 * @Throws : Gum::InvalidArgument if var has more than one state
487 */
488 template < GUM_Numeric GUM_SCALAR >
489 NodeId
492 NodeId DesiredId) {
493 if (var.domainSize() != 1) {
495 "Utility var have no state ( which implicates a "
496 "single label for data output reasons ).")
497 }
498
499 NodeId proposedId = addNode_(var, DesiredId);
500
501 auto varut = new Tensor< GUM_SCALAR >(aContent);
502
503 (*varut) << variable(proposedId);
504
505 _utilityMap_.insert(proposedId, varut);
506
507 return proposedId;
508 }
509
510 /*
511 * Add a node
512 */
513 template < GUM_Numeric GUM_SCALAR >
515 NodeId DesiredId) {
516 // None thread safe code!
517 NodeId proposedId;
518
519 if (DesiredId == 0) proposedId = dag_.nextNodeId();
520 else proposedId = DesiredId;
521
522 this->varMap_.insert(proposedId, variableType);
523
524 dag_.addNodeWithId(proposedId);
525
526 // end critical section
527 return proposedId;
528 }
529
530 /*
531 * Erase a Variable from the network and remove the variable from
532 * all children of id.
533 * If no variable matches the id, then nothing is done.
534 */
535 template < GUM_Numeric GUM_SCALAR >
537 if (this->varMap_.exists(varId)) {
538 // Reduce the variable child's CPT or Utility Table if necessary
539 for (const auto chi: dag_.children(varId))
540 if (isChanceNode(chi)) _tensorMap_[chi]->erase(variable(varId));
541 else if (isUtilityNode(chi)) _utilityMap_[chi]->erase(variable(varId));
542
543 if (isChanceNode(varId)) {
544 delete _tensorMap_[varId];
545 _tensorMap_.erase(varId);
546 } else if (isUtilityNode(varId)) {
547 delete _utilityMap_[varId];
548 _utilityMap_.erase(varId);
549 }
550
551 this->varMap_.erase(varId);
552 dag_.eraseNode(varId);
553 }
554 }
555
556 /*
557 * Erase a Variable from the network and remove the variable from
558 * all children of var.
559 * If no variable matches, then nothing is done.
560 */
561 template < GUM_Numeric GUM_SCALAR >
563 erase(this->varMap_.get(var));
564 }
565
566 /* we allow the user to change the name of a variable
567 */
568 template < GUM_Numeric GUM_SCALAR >
569 void InfluenceDiagram< GUM_SCALAR >::changeVariableName(NodeId id, std::string_view new_name) {
570 this->varMap_.changeName(id, new_name);
571 }
572
573 // ===========================================================================
574 // @name Arc manipulation methods.
575 // ===========================================================================
576 /*
577 * Add an arc in the ID, and update diagram's chance nodes cpt if necessary.
578 */
579 template < GUM_Numeric GUM_SCALAR >
581 if (isUtilityNode(tail)) { GUM_ERROR(InvalidArc, "Tail cannot be a utility node") }
582
583 dag_.addArc(tail, head);
584
585 if (isChanceNode(head))
586 // Add parent in the child's CPT
587 (*(_tensorMap_[head])) << variable(tail);
588 else if (isUtilityNode(head)) {
589 // Add parent in the child's UT
590 (*(_utilityMap_[head])) << variable(tail);
591 }
592 }
593
594 /*
595 * Removes an arc in the ID, and update diagram chance nodes cpt if necessary.
596 *
597 * If (tail, head) doesn't exist, the nothing happens.
598 */
599 template < GUM_Numeric GUM_SCALAR >
601 if (dag_.existsArc(arc)) {
602 NodeId head = arc.head();
603 NodeId tail = arc.tail();
604 dag_.eraseArc(arc);
605
606 if (isChanceNode(head))
607 // Removes parent in the child's CPT
608 (*(_tensorMap_[head])) >> variable(tail);
609 else if (isUtilityNode(head))
610 // Removes parent in the child's UT
611 (*(_utilityMap_[head])) >> variable(tail);
612 }
613 }
614
615 /*
616 * Removes an arc in the ID, and update diagram chance nodes cpt if necessary.
617 *
618 * If (tail, head) doesn't exist, the nothing happens.
619 */
620 template < GUM_Numeric GUM_SCALAR >
622 eraseArc(Arc(tail, head));
623 }
624
625 // ===========================================================================
626 // Graphical methods
627 // ===========================================================================
628
629 /*
630 * The node's id are coherent with the variables and nodes of the topology.
631 */
632 template < GUM_Numeric GUM_SCALAR >
634 for (const auto node: dag_.nodes())
635 if (!isUtilityNode(node)) graph.addNodeWithId(node);
636
637 for (const auto node: dag_.nodes()) {
638 if (!isDecisionNode(node))
639 for (const auto par: dag_.parents(node)) {
640 if (isChanceNode(node)) graph.addEdge(node, par);
641
642 for (const auto par2: dag_.parents(node))
643 if (par != par2) graph.addEdge(par, par2);
644 }
645 }
646 }
647
648 /*
649 * True if a directed path exist with all decision nodes
650 */
651 template < GUM_Numeric GUM_SCALAR >
653 const Sequence< NodeId > order = topologicalOrder();
654
655 // Finding first decision node
656 Sequence< NodeId >::const_iterator orderIter = order.begin();
657
658 while ((orderIter != order.end()) && (!isDecisionNode(*orderIter)))
659 ++orderIter;
660
661 if (orderIter == order.end()) return true;
662
663 NodeId parentDecision = (*orderIter);
664 ++orderIter;
665
666 // Checking path between decisions nodes
667 while (orderIter != order.end()) {
668 if (isDecisionNode(*orderIter)) {
669 if (!existsPathBetween(parentDecision, *orderIter)) return false;
670
671 parentDecision = *orderIter;
672 }
673
674 ++orderIter;
675 }
676
677 return true;
678 }
679
680 /*
681 * Returns true if a path exists between source and destination
682 */
683 template < GUM_Numeric GUM_SCALAR >
685 List< NodeId > nodeFIFO;
686 // mark[node] contains 0 if not visited
687 // mark[node] = predecessor if visited
688 NodeProperty< int > mark = dag_.nodesPropertyFromVal(-1);
689 NodeId current;
690
691 mark[src] = (int)src;
692 nodeFIFO.pushBack(src);
693
694 while (!nodeFIFO.empty()) {
695 current = nodeFIFO.front();
696 nodeFIFO.popFront();
697
698 for (const auto new_one: dag_.children(current)) {
699 if (mark[new_one] != -1) continue; // if this node is already marked, continue
700
701 mark[new_one] = (int)current;
702
703 if (new_one == dest) break; // if we reach *orderIter, stop.
704
705 nodeFIFO.pushBack(new_one);
706 }
707 }
708
709 if (mark[dest] == -1) return false;
710
711 return true;
712 }
713
714 /*
715 * Returns the decision graph
716 */
717 template < GUM_Numeric GUM_SCALAR >
719 gum::DAG temporalGraph;
720
721 for (const auto node: dag_.nodes()) {
722 if (isDecisionNode(node)) {
723 if (!temporalGraph.existsNode(node)) temporalGraph.addNodeWithId(node);
724
725 for (const auto chi: getChildrenDecision_(node)) {
726 if (!temporalGraph.existsNode(chi)) temporalGraph.addNodeWithId(chi);
727
728 temporalGraph.addArc(node, chi);
729 }
730 }
731 }
732
733 return temporalGraph;
734 }
735
736 /*
737 * Returns the list of children decision for a given nodeId
738 */
739 template < GUM_Numeric GUM_SCALAR >
742 Sequence< NodeId > childrenSeq;
743
744 List< NodeId > nodeFIFO;
745 NodeId current;
746
747 // mark[node] contains false if not visited
748 // mark[node] contains true if visited
749 NodeProperty< bool > mark = dag_.nodesPropertyFromVal(false);
750
751 mark[parentDecision] = true;
752
753 nodeFIFO.pushBack(parentDecision);
754
755 while (!nodeFIFO.empty()) {
756 current = nodeFIFO.front();
757 nodeFIFO.popFront();
758
759 for (const auto new_one: dag_.children(current)) {
760 if (mark[new_one]) continue; // if this node is already marked, continue
761
762 mark[new_one] = true;
763
764 if (!isDecisionNode(new_one)) nodeFIFO.pushBack(new_one);
765 else childrenSeq.insert(new_one);
766 }
767 }
768
769 return childrenSeq;
770 }
771
772 /*
773 * Returns the sequence of decision nodes
774 * @throw NotFound if such a sequence does not exist
775 */
776 template < GUM_Numeric GUM_SCALAR >
777 std::vector< NodeId > InfluenceDiagram< GUM_SCALAR >::decisionOrder() const {
778 if (!decisionOrderExists()) { GUM_ERROR(NotFound, "No decision path exists") }
779
780 std::vector< NodeId > decisionSequence;
781
782 for (const auto elt: topologicalOrder())
783 if (isDecisionNode(elt)) decisionSequence.push_back(elt);
784
785 return decisionSequence;
786 }
787
788 /*
789 * Returns partial temporal ordering
790 * @throw NotFound if such a sequence does not exist
791 */
792 template < GUM_Numeric GUM_SCALAR >
794 if (clear) {
795 _temporalOrder_.clear();
796
797 std::vector< NodeId > order = decisionOrder();
798 NodeSet nodeList = dag_.asNodeSet();
799
800 for (auto i: order) {
801 NodeSet partialOrderedSet;
802
803 for (const auto par: dag_.parents(i)) {
804 if (nodeList.contains(par) && isChanceNode(par)) {
805 partialOrderedSet.insert(par);
806 nodeList.erase(par);
807 }
808 }
809
810 if (!partialOrderedSet.empty()) _temporalOrder_.pushFront(partialOrderedSet);
811
812 NodeSet decisionSet;
813
814 decisionSet.insert(i);
815
816 _temporalOrder_.pushFront(decisionSet);
817 }
818
819 NodeSet lastSet; //= new gum::NodeSet();
820
821 for (const auto node: nodeList)
822 if (isChanceNode(node)) lastSet.insert(node);
823
824 if (!lastSet.empty()) _temporalOrder_.pushFront(lastSet);
825 }
826
827 return _temporalOrder_;
828 }
829
830 template < GUM_Numeric GUM_SCALAR >
831 NodeId InfluenceDiagram< GUM_SCALAR >::addChanceNode(std::string_view fast_description,
832 unsigned int default_nbrmod) {
833 auto v = fastVariable< GUM_SCALAR >(std::string(fast_description), default_nbrmod);
834 if (v->domainSize() < 2) GUM_ERROR(OperationNotAllowed, v->name() << " has a domain size <2")
835 return addChanceNode(*v);
836 }
837
838 template < GUM_Numeric GUM_SCALAR >
839 NodeId InfluenceDiagram< GUM_SCALAR >::addUtilityNode(std::string_view fast_description) {
840 auto v = fastVariable< GUM_SCALAR >(std::string(fast_description), 1);
841 if (v->domainSize() >= 2)
843 v->name() << " has a domain size >= 2 which is impossible for a utility node")
844 return addUtilityNode(*v);
845 }
846
847 template < GUM_Numeric GUM_SCALAR >
849 unsigned int default_nbrmod) {
850 auto v = fastVariable< GUM_SCALAR >(std::string(fast_description), default_nbrmod);
851 if (v->domainSize() < 2) GUM_ERROR(OperationNotAllowed, v->name() << " has a domain size <2")
852 return addDecisionNode(*v);
853 }
854
855 template < GUM_Numeric GUM_SCALAR >
856 NodeId InfluenceDiagram< GUM_SCALAR >::add(std::string_view fast_description,
857 unsigned int default_nbrmod) {
858 std::string node(fast_description);
859 switch (*(node.begin())) {
860 case '*' : node.erase(0, 1); return addDecisionNode(node, default_nbrmod);
861 case '$' : node.erase(0, 1); return addUtilityNode(node);
862 default : return addChanceNode(fast_description, default_nbrmod);
863 }
864 }
865
867 template < GUM_Numeric GUM_SCALAR >
869 for (const auto node: nodes())
870 if (isChanceNode(node)) _tensorMap_[node]->beginMultipleChanges();
871 else if (this->isUtilityNode(node)) _utilityMap_[node]->beginMultipleChanges();
872 }
873
875 template < GUM_Numeric GUM_SCALAR >
877 for (const auto node: nodes())
878 if (isChanceNode(node)) _tensorMap_[node]->endMultipleChanges();
879 else if (isUtilityNode(node)) _utilityMap_[node]->endMultipleChanges();
880 }
881
882 template < GUM_Numeric GUM_SCALAR >
884 if (size() != from.size()) { return false; }
885
886 if (sizeArcs() != from.sizeArcs()) { return false; }
887
888 // alignment of variables between the 2 BNs
890
891 for (auto node: nodes()) {
892 const auto& v1 = variable(node);
893 if (!from.exists(v1.name())) return false;
894 const auto& v2 = from.variableFromName(v1.name());
895 if (v1 != v2) { return false; }
896
897 if (isChanceNode(v1.name()) && !from.isChanceNode(v2.name())) { return false; }
898 if (isUtilityNode(v1.name()) && !from.isUtilityNode(v2.name())) { return false; }
899 if (isDecisionNode(v1.name()) && !from.isDecisionNode(v2.name())) { return false; }
900
901 alignment.insert(&variable(node), &from.variableFromName(v1.name()));
902 }
903
904 auto check_pot
905 = [&](const gum::Tensor< GUM_SCALAR >& p1, const gum::Tensor< GUM_SCALAR >& p2) -> bool {
906 if (p1.nbrDim() != p2.nbrDim()) { return false; }
907
908 if (p1.domainSize() != p2.domainSize()) { return false; }
909
910 Instantiation i(p1);
911 Instantiation j(p2);
912
913 for (i.setFirst(); !i.end(); i.inc()) {
914 for (Idx indice = 0; indice < p1.nbrDim(); ++indice) {
915 const DiscreteVariable* p = &(i.variable(indice));
916 j.chgVal(*(alignment.second(p)), i.val(*p));
917 }
918
919 if (std::pow(p1.get(i) - p2.get(j), (GUM_SCALAR)2) > (GUM_SCALAR)1e-6) { return false; }
920 }
921 return true;
922 };
923 for (auto node: nodes()) {
924 NodeId fromnode = from.idFromName(variable(node).name());
925 if (isChanceNode(node)) {
926 if (!check_pot(cpt(node), from.cpt(fromnode))) { return false; }
927 } else if (isUtilityNode(node)) {
928 if (!check_pot(utility(node), from.utility(fromnode))) { return false; }
929 }
930 }
931
932 return true;
933 }
934
935 template < GUM_Numeric GUM_SCALAR >
936 const Tensor< GUM_SCALAR >& InfluenceDiagram< GUM_SCALAR >::cpt(std::string_view name) const {
937 return cpt(idFromName(name));
938 }
939
940 template < GUM_Numeric GUM_SCALAR >
941 const Tensor< GUM_SCALAR >& InfluenceDiagram< GUM_SCALAR >::utility(std::string_view name) const {
942 return utility(idFromName(name));
943 }
944
945 template < GUM_Numeric GUM_SCALAR >
946 bool InfluenceDiagram< GUM_SCALAR >::isUtilityNode(std::string_view name) const {
947 return isUtilityNode(idFromName(name));
948 }
949
950 template < GUM_Numeric GUM_SCALAR >
951 bool InfluenceDiagram< GUM_SCALAR >::isDecisionNode(std::string_view name) const {
952 return isDecisionNode(idFromName(name));
953 }
954
955 template < GUM_Numeric GUM_SCALAR >
956 bool InfluenceDiagram< GUM_SCALAR >::isChanceNode(std::string_view name) const {
957 return isChanceNode(idFromName(name));
958 }
959
960 template < GUM_Numeric GUM_SCALAR >
961 const DiscreteVariable& InfluenceDiagram< GUM_SCALAR >::variable(std::string_view name) const {
962 return variable(idFromName(name));
963 }
964
965 template < GUM_Numeric GUM_SCALAR >
966 void InfluenceDiagram< GUM_SCALAR >::erase(std::string_view name) {
967 erase(idFromName(name));
968 }
969
970 template < GUM_Numeric GUM_SCALAR >
972 std::string_view new_name) {
973 changeVariableName(idFromName(name), new_name);
974 }
975
976 template < GUM_Numeric GUM_SCALAR >
977 void InfluenceDiagram< GUM_SCALAR >::addArc(std::string_view tail, std::string_view head) {
978 addArc(idFromName(tail), idFromName(head));
979 }
980
981 template < GUM_Numeric GUM_SCALAR >
982 void InfluenceDiagram< GUM_SCALAR >::eraseArc(std::string_view tail, std::string_view head) {
983 eraseArc(idFromName(tail), idFromName(head));
984 }
985
986 template < GUM_Numeric GUM_SCALAR >
988 std::string_view dest) const {
989 return existsPathBetween(idFromName(src), idFromName(dest));
990 }
991
992} // namespace gum
The base class for all directed edges.
GUM_NODISCARD NodeId head() const
returns the head of the arc
GUM_NODISCARD NodeId tail() const
returns the tail of the arc
const T2 & second(const T1 &first) const
Returns the second value of a pair given its first value.
void insert(const T1 &first, const T2 &second)
Inserts a new association in the gum::Bijection.
Set of pairs of elements with fast search for both elements.
Definition bijection.h:1640
Base class for dag.
Definition DAG.h:121
void addArc(NodeId tail, NodeId head) final
insert a new arc into the directed graph
Definition DAG_inl.h:75
DAG dag_
The DAG of this Directed Graphical Model.
Definition DAGmodel.h:284
DAGmodel()
Default constructor.
Definition DAGmodel.cpp:49
Size size() const final
Returns the number of variables in this Directed Graphical Model.
Size sizeArcs() const
Returns the number of arcs in this Directed Graphical Model.
Sequence< NodeId > topologicalOrder() const
The topological order stays the same as long as no variable or arcs are added or erased src the topol...
DAG dag() const
Returns a named copy of the internal DAG: each node id is assigned the name of the corresponding vari...
DAGmodel & operator=(const DAGmodel &source)
Private copy operator.
Definition DAGmodel.cpp:62
bool exists(NodeId node) const final
Return true if this node exists in this graphical model.
const NodeSet & parents(const NodeId id) const
returns the set of nodes with arc ingoing to a given node
const NodeGraphPart & nodes() const final
Returns a named copy of the internal DAG: each node id is assigned the name of the corresponding vari...
VariableNodeMap varMap_
Mapping between NodeIds and discrete variables.
Base class for discrete random variable.
virtual Size domainSize() const =0
Base class for all aGrUM's exceptions.
Definition exceptions.h:122
Exception : fatal (unknown ?) error.
void setProperty(std::string_view name, std::string_view value)
Add or change a property of this GraphicalModel.
double log10DomainSize() const
const std::string & propertyWithDefault(std::string_view name, const std::string &byDefault) const
Return the value of the property name of this GraphicalModel.
Class representing an Influence Diagram.
void beginTopologyTransformation()
When inserting/removing arcs, node CPTs/utilities change their dimension with a cost in time.
List< NodeSet > _temporalOrder_
The temporal order.
NodeId idFromName(std::string_view name) const override
Returns the NodeId of a variable given its name.
Size chanceNodeSize() const
Returns the number of chance nodes.
InfluenceDiagram()
Default constructor.
const List< NodeSet > & getPartialTemporalOrder(bool clear=true) const
Returns partial temporal ordering.
NodeId addChanceNode(const DiscreteVariable &variable, NodeId id=0)
Add a chance variable, it's associate node and it's CPT.
void addArc(NodeId tail, NodeId head)
Add an arc in the ID, and update diagram's tensor nodes cpt if necessary.
void endTopologyTransformation()
terminates a sequence of insertions/deletions of arcs by adjusting all CPTs/utilities dimensions.
static InfluenceDiagram< GUM_SCALAR > fastPrototype(std::string_view dotlike, Size domainSize)
Create an Influence Diagram with a dot-like syntax which specifies:
void removeTables_()
Removing ancient table.
void eraseArc(const Arc &arc)
Removes an arc in the ID, and update diagram's tensor nodes cpt if necessary.
NodeProperty< Tensor< GUM_SCALAR > * > _tensorMap_
Mapping between tensor variable's id and their CPT.
virtual void moralGraph_(UndiGraph &graph) const
Returns the moral graph of this InfluenceDiagram.
NodeId addNode_(const DiscreteVariable &variableType, NodeId DesiredId)
Add a node.
std::string toDot() const
bool decisionOrderExists() const
True if a directed path exist with all decision nodes.
bool isChanceNode(NodeId varId) const
Returns true if node is a chance one.
InfluenceDiagram< GUM_SCALAR > & operator=(const InfluenceDiagram< GUM_SCALAR > &source)
Copy Operator.
NodeId add(const DiscreteVariable &variable, NodeId id=0)
Add a chance variable, it's associate node and it's CPT.
NodeId addUtilityNode(const DiscreteVariable &variable, NodeId id=0)
Add a utility variable, it's associate node and it's UT.
std::string toString() const
void copyStructureAndTables_(const InfluenceDiagram< GUM_SCALAR > &IDsource)
Copying tables from another influence diagram.
NodeId addDecisionNode(const DiscreteVariable &variable, NodeId id=0)
Add a decision variable.
const DiscreteVariable & variableFromName(std::string_view name) const override
Returns a constant reference over a variable given its name.
bool isUtilityNode(NodeId varId) const
Returns true if node is a utility one.
bool operator==(const InfluenceDiagram< GUM_SCALAR > &other) const
Size utilityNodeSize() const
Returns the number of utility nodes.
NodeProperty< Tensor< GUM_SCALAR > * > _utilityMap_
Mapping between utility variable's id and their utility table.
gum::DAG getDecisionGraph() const
Returns the temporal Graph.
void changeVariableName(NodeId id, std::string_view new_name)
we allow the user to change the name of a variable
bool isDecisionNode(NodeId varId) const
Returns true if node is a decision one.
const DiscreteVariable & variable(std::string_view name) const
Returns the CPT of a tensor variable.
Sequence< NodeId > getChildrenDecision_(NodeId parentDecision) const
Returns the list of children decision for a given nodeId.
Size decisionNodeSize() const
Returns the number of decision nodes.
void erase(NodeId id)
Erase a Variable from the network and remove the variable from all his children.
virtual const Tensor< GUM_SCALAR > & cpt(NodeId varId) const
Returns the CPT of a tensor variable.
std::vector< NodeId > decisionOrder() const
Returns the sequence of decision nodes in the directed path.
virtual const Tensor< GUM_SCALAR > & utility(NodeId varId) const
Returns the utility table of a utility node.
bool existsPathBetween(NodeId src, NodeId dest) const
Returns true if a path exists between two nodes.
~InfluenceDiagram() override
Destructor.
Class for assigning/browsing values to tuples of discrete variables.
Instantiation & chgVal(const DiscreteVariable &v, Idx newval)
Assign newval to variable v in the Instantiation.
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.
const DiscreteVariable & variable(Idx i) const final
Returns the variable at position i in the tuple.
Exception : there is something wrong with an arc.
Exception: at least one argument passed to a function is not what was expected.
Generic doubly linked lists.
Definition list.h:378
Val & front() const
Returns a reference to first element of a list, if any.
Definition list_tpl.h:1694
Val & pushBack(const Val &val)
Inserts a new element (a copy) at the end of the chained list.
Definition list_tpl.h:1481
bool empty() const noexcept
Returns a boolean indicating whether the chained list is empty.
Definition list_tpl.h:1822
void popFront()
Removes the first element of a List, if any.
Definition list_tpl.h:1816
Multidimensional matrix stored as an array in memory.
Idx nbrDim() const final
Returns the number of vars in the multidimensional container.
Size domainSize() const final
Returns the product of the variables domain size.
GUM_ELEMENT get(const Instantiation &i) const final
Default implementation of MultiDimContainer::get().
bool existsNode(const NodeId id) const
returns true iff the NodeGraphPart contains the given nodeId
virtual void addNodeWithId(const NodeId id)
try to insert a node with the given id
Exception : the element we looked for cannot be found.
Exception : operation not allowed.
SequenceIterator< Key > const_iterator
Types for STL compliance.
Definition sequence.h:1006
bool contains(const Key &k) const
Indicates whether a given elements belong to the set.
Definition set_tpl.h:468
bool empty() const noexcept
Indicates whether the set is the empty set.
Definition set_tpl.h:613
void insert(const Key &k)
Inserts a new element into the set.
Definition set_tpl.h:510
void erase(const Key &k)
Erases an element from the set.
Definition set_tpl.h:553
aGrUM's Tensor is a multi-dimensional array with tensor operators.
Definition tensor.h:85
Base class for undirected graphs.
Definition undiGraph.h:130
const std::string & name() const
returns the name of the variable
#define GUM_ERROR(type, msg)
Definition exceptions.h:76
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 ...
std::string remove_newline(std::string_view s)
remove all newlines in a string
std::vector< std::string > split(std::string_view str, std::string_view delim)
Split str using the delimiter.
Class representing Influence Diagrams.
gum is the global namespace for all aGrUM entities
Definition agrum.h:46
NodeId build_node_for_ID(gum::InfluenceDiagram< GUM_SCALAR > &infdiag, std::string node, std::string_view domain)
std::unique_ptr< DiscreteVariable > fastVariable(std::string var_description, Size default_domain_size)
Create a pointer on a Discrete Variable from a "fast" syntax.