34 #include <agrum/tools/variables/rangeVariable.h> 35 #include <agrum/tools/variables/labelizedVariable.h> 36 #include <agrum/tools/variables/integerVariable.h> 37 #include <agrum/tools/variables/discretizedVariable.h> 39 #include <agrum/ID/influenceDiagram.h> 42 template <
typename GUM_SCALAR >
43 NodeId build_node_for_ID(gum::InfluenceDiagram< GUM_SCALAR >& infdiag,
45 gum::Size default_domain_size) {
46 auto ds = default_domain_size;
48 long range_max =
long(ds) - 1;
49 std::vector< std::string > labels;
50 std::vector< GUM_SCALAR > ticks;
51 bool isUtil, isDeci, isChanc;
56 switch (*(node.begin())) {
69 std::string name = node;
70 if (*(node.rbegin()) ==
']') {
71 auto posBrack = node.find(
'[');
72 if (posBrack != std::string::npos) {
73 name = node.substr(0, posBrack);
74 const auto& s_args = node.substr(posBrack + 1, node.size() - posBrack - 2);
75 const auto& args = split(s_args,
",");
77 GUM_ERROR(InvalidArgument,
"Empty range for variable " << node)
78 }
else if (args.size() == 1) {
79 ds =
static_cast< Size >(std::stoi(args[0]));
81 range_max =
long(ds) - 1;
82 }
else if (args.size() == 2) {
83 range_min = std::stol(args[0]);
84 range_max = std::stol(args[1]);
85 if (1 + range_max - range_min < 2) {
86 GUM_ERROR(InvalidArgument,
"Invalid range for variable " << node)
88 ds =
static_cast< Size >(1 + range_max - range_min);
90 for (
const auto& tick: args) {
91 ticks.push_back(
static_cast< GUM_SCALAR >(std::strtod(tick.c_str(),
nullptr)));
93 ds =
static_cast< Size >(args.size() - 1);
96 }
else if (*(node.rbegin()) ==
'}') {
97 auto posBrack = node.find(
'{');
98 if (posBrack != std::string::npos) {
99 name = node.substr(0, posBrack);
100 labels = split(node.substr(posBrack + 1, node.size() - posBrack - 2),
"|");
101 if (labels.size() < 2) { GUM_ERROR(InvalidArgument,
"Not enough labels in node " << node) }
102 if (!hasUniqueElts(labels)) {
103 GUM_ERROR(InvalidArgument,
"Duplicate labels in node " << node)
105 ds =
static_cast< Size >(labels.size());
110 GUM_ERROR(InvalidArgument,
"No value for variable " << name <<
".")
111 }
else if (ds == 1) {
112 GUM_ERROR(InvalidArgument,
113 "Only one value for variable " << name <<
" (2 at least are needed).")
116 std::vector<
int > values;
117 if (!labels.empty()) {
118 if (std::all_of(labels.begin(), labels.end(), isInteger)) {
119 for (
const auto& label: labels)
120 values.push_back(std::stoi(label));
127 idVar = infdiag.idFromName(name);
128 }
catch (gum::NotFound&) {
130 if (!values.empty()) {
131 idVar = infdiag.addChanceNode(IntegerVariable(name, name, values));
132 }
else if (!labels.empty()) {
133 idVar = infdiag.addChanceNode(LabelizedVariable(name, name, labels));
134 }
else if (!ticks.empty()) {
135 idVar = infdiag.addChanceNode(DiscretizedVariable< GUM_SCALAR >(name, name, ticks));
137 idVar = infdiag.addChanceNode(RangeVariable(name, name, range_min, range_max));
140 if (!values.empty()) {
141 idVar = infdiag.addDecisionNode(IntegerVariable(name, name, values));
142 }
else if (!labels.empty()) {
143 idVar = infdiag.addDecisionNode(LabelizedVariable(name, name, labels));
144 }
else if (!ticks.empty()) {
145 idVar = infdiag.addDecisionNode(DiscretizedVariable< GUM_SCALAR >(name, name, ticks));
147 idVar = infdiag.addDecisionNode(RangeVariable(name, name, range_min, range_max));
150 idVar = infdiag.addUtilityNode(LabelizedVariable(name, name, 1));
152 GUM_ERROR(FatalError,
153 "No type (chance, decision or utility) for the node '" << node <<
"'.")
161 template <
typename GUM_SCALAR >
162 InfluenceDiagram< GUM_SCALAR >
163 InfluenceDiagram< GUM_SCALAR >::fastPrototype(
const std::string& dotlike, Size domainSize) {
164 gum::InfluenceDiagram< GUM_SCALAR > infdiag;
166 for (
const auto& chaine: split(dotlike,
";")) {
168 bool notfirst =
false;
169 for (
const auto& souschaine: split(chaine,
"->")) {
171 for (
const auto& node: split(souschaine,
"<-")) {
172 auto idVar = build_node_for_ID(infdiag, node, domainSize);
175 infdiag.addArc(lastId, idVar);
178 infdiag.addArc(idVar, lastId);
189 for (
const auto n: infdiag.nodes()) {
190 if (infdiag.isChanceNode(n))
191 infdiag.cpt(n).randomCPT();
192 else if (infdiag.isUtilityNode(n)) {
193 infdiag.utility(n).random().scale(50).translate(-10);
197 infdiag.setProperty(
"name",
"fastPrototype");
207 template <
typename GUM_SCALAR >
208 INLINE InfluenceDiagram< GUM_SCALAR >::InfluenceDiagram() : DAGmodel() {
209 GUM_CONSTRUCTOR(InfluenceDiagram);
215 template <
typename GUM_SCALAR >
216 InfluenceDiagram< GUM_SCALAR >::~InfluenceDiagram() {
217 GUM_DESTRUCTOR(InfluenceDiagram);
224 template <
typename GUM_SCALAR >
225 InfluenceDiagram< GUM_SCALAR >::InfluenceDiagram(
const InfluenceDiagram< GUM_SCALAR >& source) {
226 GUM_CONS_CPY(InfluenceDiagram);
227 copyStructureAndTables_(source);
233 template <
typename GUM_SCALAR >
234 InfluenceDiagram< GUM_SCALAR >&
235 InfluenceDiagram< GUM_SCALAR >::operator=(
const InfluenceDiagram< GUM_SCALAR >& source) {
236 if (
this != &source) {
239 copyStructureAndTables_(source);
245 template <
typename GUM_SCALAR >
246 void InfluenceDiagram< GUM_SCALAR >::InfluenceDiagram::clear() {
249 _variableMap_.clear();
251 _potentialMap_.clear();
252 _utilityMap_.clear();
258 template <
typename GUM_SCALAR >
259 void InfluenceDiagram< GUM_SCALAR >::removeTables_() {
260 for (
const auto node: dag_.nodes()) {
261 if (isChanceNode(node))
263 else if (isUtilityNode(node))
264 delete &utility(node);
271 template <
typename GUM_SCALAR >
272 void InfluenceDiagram< GUM_SCALAR >::copyStructureAndTables_(
273 const InfluenceDiagram< GUM_SCALAR >& IDsource) {
274 for (
auto node: IDsource.nodes()) {
275 if (IDsource.isChanceNode(node))
276 addChanceNode(IDsource.variable(node), node);
277 else if (IDsource.isUtilityNode(node))
278 addUtilityNode(IDsource.variable(node), node);
280 addDecisionNode(IDsource.variable(node), node);
283 for (
auto node: IDsource.nodes()) {
284 const auto& s = IDsource.variable(node).name();
285 if (IDsource.isChanceNode(node)) {
286 for (Idx par = 1; par <= IDsource.parents(node).size(); par++)
287 addArc(IDsource.cpt(node).variable(par).name(), s);
288 }
else if (IDsource.isUtilityNode(node)) {
289 for (Idx par = 1; par <= IDsource.parents(node).size(); par++)
290 addArc(IDsource.utility(node).variable(par).name(), s);
293 for (NodeId par: IDsource.parents(node))
299 for (
auto node: IDsource.nodes()) {
300 const auto& s = IDsource.variable(node).name();
301 if (IDsource.isChanceNode(node)) {
302 cpt(node).fillWith(IDsource.cpt(s));
303 }
else if (IDsource.isUtilityNode(node)) {
304 utility(node).fillWith(IDsource.utility(s));
309 template <
typename GUM_SCALAR >
310 std::string InfluenceDiagram< GUM_SCALAR >::toDot()
const {
311 std::stringstream output;
312 std::stringstream decisionNode;
313 std::stringstream utilityNode;
314 std::stringstream chanceNode;
315 std::stringstream arcstream;
316 output <<
"digraph \"";
319 output <<
this->property(
"name") <<
"\" {" << std::endl;
320 }
catch (NotFound&) { output <<
"no_name\" {" << std::endl; }
322 output <<
" node [bgcolor=\"#AAAAAA\", style=filled, height=0];" << std::endl;
324 decisionNode <<
"node [shape = box];" << std::endl;
326 utilityNode <<
"node [shape = hexagon, margin=0];" << std::endl;
327 chanceNode <<
"node [shape = ellipse];" << std::endl;
328 std::string tab =
" ";
330 for (
const auto node: dag_.nodes()) {
331 if (isChanceNode(node))
332 chanceNode << tab <<
"\"" << node <<
"-" << variable(node).name() <<
"\"" 334 else if (isUtilityNode(node))
335 utilityNode << tab <<
"\"" << node <<
"-" << variable(node).name() <<
"\"" 338 decisionNode << tab <<
"\"" << node <<
"-" << variable(node).name() <<
"\"" 341 if (dag_.children(node).size() > 0)
342 for (
const auto chi: dag_.children(node)) {
343 arcstream <<
"\"" << node <<
"-" << variable(node).name() <<
"\"" 345 <<
"\"" << chi <<
"-" << variable(chi).name() <<
"\"";
346 if (isDecisionNode(chi)) { arcstream <<
" [style=\"tapered, bold\"]"; }
347 arcstream <<
";" << std::endl;
351 output << decisionNode.str() << std::endl
352 << utilityNode.str() << std::endl
353 << chanceNode.str() << std::endl
355 << arcstream.str() << std::endl
361 template <
typename GUM_SCALAR >
362 std::string InfluenceDiagram< GUM_SCALAR >::toString()
const {
363 std::stringstream output;
365 output <<
"Influence Diagram{" << std::endl;
366 output <<
" chance: " << chanceNodeSize() <<
"," << std::endl;
367 output <<
" utility: " << utilityNodeSize() <<
"," << std::endl;
368 output <<
" decision: " << decisionNodeSize() <<
"," << std::endl;
369 output <<
" arcs: " << dag().sizeArcs() <<
"," << std::endl;
371 double dSize = log10DomainSize();
374 output <<
" domainSize: 10^" << dSize;
376 output <<
" domainSize: " << std::round(std::pow(10.0, dSize));
378 output << std::endl <<
"}";
390 template <
typename GUM_SCALAR >
391 INLINE
const Potential< GUM_SCALAR >& InfluenceDiagram< GUM_SCALAR >::cpt(NodeId varId)
const {
392 return *(_potentialMap_[varId]);
398 template <
typename GUM_SCALAR >
399 INLINE
const Potential< GUM_SCALAR >&
400 InfluenceDiagram< GUM_SCALAR >::utility(NodeId varId)
const {
401 return *(_utilityMap_[varId]);
407 template <
typename GUM_SCALAR >
408 INLINE
bool InfluenceDiagram< GUM_SCALAR >::isUtilityNode(NodeId varId)
const {
409 return _utilityMap_.exists(varId);
415 template <
typename GUM_SCALAR >
416 INLINE
bool InfluenceDiagram< GUM_SCALAR >::isDecisionNode(NodeId varId)
const {
419 if (isUtilityNode(varId) || isChanceNode(varId)) ret =
false;
427 template <
typename GUM_SCALAR >
428 INLINE
bool InfluenceDiagram< GUM_SCALAR >::isChanceNode(NodeId varId)
const {
429 return _potentialMap_.exists(varId);
435 template <
typename GUM_SCALAR >
436 INLINE Size InfluenceDiagram< GUM_SCALAR >::utilityNodeSize()
const {
437 return _utilityMap_.size();
443 template <
typename GUM_SCALAR >
444 INLINE Size InfluenceDiagram< GUM_SCALAR >::chanceNodeSize()
const {
445 return _potentialMap_.size();
451 template <
typename GUM_SCALAR >
452 INLINE Size InfluenceDiagram< GUM_SCALAR >::decisionNodeSize()
const {
453 return (size() - _utilityMap_.size() - _potentialMap_.size());
460 template <
typename GUM_SCALAR >
461 INLINE
const VariableNodeMap& InfluenceDiagram< GUM_SCALAR >::variableNodeMap()
const {
462 return _variableMap_;
468 template <
typename GUM_SCALAR >
469 INLINE
const DiscreteVariable& InfluenceDiagram< GUM_SCALAR >::variable(NodeId id)
const {
470 return _variableMap_[id];
476 template <
typename GUM_SCALAR >
477 INLINE NodeId InfluenceDiagram< GUM_SCALAR >::nodeId(
const DiscreteVariable& var)
const {
478 return _variableMap_.get(var);
482 template <
typename GUM_SCALAR >
483 INLINE NodeId InfluenceDiagram< GUM_SCALAR >::idFromName(
const std::string& name)
const {
484 return _variableMap_.idFromName(name);
488 template <
typename GUM_SCALAR >
489 INLINE
const DiscreteVariable&
490 InfluenceDiagram< GUM_SCALAR >::variableFromName(
const std::string& name)
const {
491 return _variableMap_.variableFromName(name);
498 template <
typename GUM_SCALAR >
499 NodeId InfluenceDiagram< GUM_SCALAR >::add(
const DiscreteVariable& var, NodeId varId) {
500 return addChanceNode(var, varId);
508 template <
typename GUM_SCALAR >
509 NodeId InfluenceDiagram< GUM_SCALAR >::addUtilityNode(
const DiscreteVariable& var, NodeId varId) {
510 auto newMultiDim =
new MultiDimArray< GUM_SCALAR >();
514 res = addUtilityNode(var, newMultiDim, varId);
515 }
catch (Exception&) {
516 if (newMultiDim !=
nullptr)
delete newMultiDim;
527 template <
typename GUM_SCALAR >
528 NodeId InfluenceDiagram< GUM_SCALAR >::addDecisionNode(
const DiscreteVariable& var,
530 return addNode_(var, varId);
537 template <
typename GUM_SCALAR >
538 NodeId InfluenceDiagram< GUM_SCALAR >::addChanceNode(
const DiscreteVariable& var, NodeId varId) {
539 auto newMultiDim =
new MultiDimArray< GUM_SCALAR >();
543 res = addChanceNode(var, newMultiDim, varId);
544 }
catch (Exception&) {
556 template <
typename GUM_SCALAR >
558 InfluenceDiagram< GUM_SCALAR >::addChanceNode(
const DiscreteVariable& var,
559 MultiDimImplementation< GUM_SCALAR >* aContent,
561 NodeId proposedId = addNode_(var, DesiredId);
563 auto varcpt =
new Potential< GUM_SCALAR >(aContent);
564 (*varcpt) << variable(proposedId);
565 _potentialMap_.insert(proposedId, varcpt);
575 template <
typename GUM_SCALAR >
577 InfluenceDiagram< GUM_SCALAR >::addUtilityNode(
const DiscreteVariable& var,
578 MultiDimImplementation< GUM_SCALAR >* aContent,
580 if (var.domainSize() != 1) {
581 GUM_ERROR(InvalidArgument,
582 "Utility var have no state ( which implicates a " 583 "single label for data output reasons ).")
586 NodeId proposedId = addNode_(var, DesiredId);
588 auto varut =
new Potential< GUM_SCALAR >(aContent);
590 (*varut) << variable(proposedId);
592 _utilityMap_.insert(proposedId, varut);
600 template <
typename GUM_SCALAR >
601 NodeId InfluenceDiagram< GUM_SCALAR >::addNode_(
const DiscreteVariable& variableType,
607 proposedId = dag_.nextNodeId();
609 proposedId = DesiredId;
611 _variableMap_.insert(proposedId, variableType);
613 dag_.addNodeWithId(proposedId);
624 template <
typename GUM_SCALAR >
625 void InfluenceDiagram< GUM_SCALAR >::erase(NodeId varId) {
626 if (_variableMap_.exists(varId)) {
628 for (
const auto chi: dag_.children(varId))
629 if (isChanceNode(chi))
630 _potentialMap_[chi]->erase(variable(varId));
631 else if (isUtilityNode(chi))
632 _utilityMap_[chi]->erase(variable(varId));
634 if (isChanceNode(varId)) {
635 delete _potentialMap_[varId];
636 _potentialMap_.erase(varId);
637 }
else if (isUtilityNode(varId)) {
638 delete _utilityMap_[varId];
639 _utilityMap_.erase(varId);
642 _variableMap_.erase(varId);
643 dag_.eraseNode(varId);
652 template <
typename GUM_SCALAR >
653 INLINE
void InfluenceDiagram< GUM_SCALAR >::erase(
const DiscreteVariable& var) {
654 erase(_variableMap_.get(var));
659 template <
typename GUM_SCALAR >
660 INLINE
void InfluenceDiagram< GUM_SCALAR >::changeVariableName(NodeId id,
661 const std::string& new_name) {
662 _variableMap_.changeName(id, new_name);
671 template <
typename GUM_SCALAR >
672 INLINE
void InfluenceDiagram< GUM_SCALAR >::addArc(NodeId tail, NodeId head) {
673 if (isUtilityNode(tail)) { GUM_ERROR(InvalidArc,
"Tail cannot be a utility node") }
675 dag_.addArc(tail, head);
677 if (isChanceNode(head))
679 (*(_potentialMap_[head])) << variable(tail);
680 else if (isUtilityNode(head)) {
682 (*(_utilityMap_[head])) << variable(tail);
691 template <
typename GUM_SCALAR >
692 INLINE
void InfluenceDiagram< GUM_SCALAR >::eraseArc(
const Arc& arc) {
693 if (dag_.existsArc(arc)) {
694 NodeId head = arc.head(), tail = arc.tail();
697 if (isChanceNode(head))
699 (*(_potentialMap_[head])) >> variable(tail);
700 else if (isUtilityNode(head))
702 (*(_utilityMap_[head])) >> variable(tail);
711 template <
typename GUM_SCALAR >
712 INLINE
void InfluenceDiagram< GUM_SCALAR >::eraseArc(NodeId tail, NodeId head) {
713 eraseArc(Arc(tail, head));
723 template <
typename GUM_SCALAR >
724 void InfluenceDiagram< GUM_SCALAR >::moralGraph_(UndiGraph& graph)
const {
725 for (
const auto node: dag_.nodes())
726 if (!isUtilityNode(node)) graph.addNodeWithId(node);
728 for (
const auto node: dag_.nodes()) {
729 if (!isDecisionNode(node))
730 for (
const auto par: dag_.parents(node)) {
731 if (isChanceNode(node)) graph.addEdge(node, par);
733 for (
const auto par2: dag_.parents(node))
734 if (par != par2) graph.addEdge(par, par2);
742 template <
typename GUM_SCALAR >
743 bool InfluenceDiagram< GUM_SCALAR >::decisionOrderExists()
const {
744 const Sequence< NodeId >& order = topologicalOrder(
true);
747 Sequence< NodeId >::const_iterator orderIter = order.begin();
749 while ((orderIter != order.end()) && (!isDecisionNode(*orderIter)))
752 if (orderIter == order.end())
return true;
754 NodeId parentDecision = (*orderIter);
758 while (orderIter != order.end()) {
759 if (isDecisionNode(*orderIter)) {
760 if (!existsPathBetween(parentDecision, *orderIter))
return false;
762 parentDecision = *orderIter;
774 template <
typename GUM_SCALAR >
775 bool InfluenceDiagram< GUM_SCALAR >::existsPathBetween(NodeId src, NodeId dest)
const {
776 List< NodeId > nodeFIFO;
779 NodeProperty<
int > mark = dag_.nodesProperty((
int)-1);
782 mark[src] = (
int)src;
783 nodeFIFO.pushBack(src);
785 while (!nodeFIFO.empty()) {
786 current = nodeFIFO.front();
789 for (
const auto new_one: dag_.children(current)) {
790 if (mark[new_one] != -1)
continue;
792 mark[new_one] = (
int)current;
794 if (new_one == dest)
break;
796 nodeFIFO.pushBack(new_one);
800 if (mark[dest] == -1)
return false;
808 template <
typename GUM_SCALAR >
809 gum::DAG* InfluenceDiagram< GUM_SCALAR >::getDecisionGraph()
const {
810 auto temporalGraph =
new gum::DAG();
812 for (
const auto node: dag_.nodes()) {
813 if (isDecisionNode(node)) {
814 if (!temporalGraph->existsNode(node)) temporalGraph->addNodeWithId(node);
816 for (
const auto chi: getChildrenDecision_(node)) {
817 if (!temporalGraph->existsNode(chi)) temporalGraph->addNodeWithId(chi);
819 temporalGraph->addArc(node, chi);
824 return temporalGraph;
830 template <
typename GUM_SCALAR >
832 InfluenceDiagram< GUM_SCALAR >::getChildrenDecision_(NodeId parentDecision)
const {
833 Sequence< NodeId > childrenSeq;
835 List< NodeId > nodeFIFO;
840 NodeProperty<
bool > mark = dag_.nodesProperty(
false);
842 mark[parentDecision] =
true;
844 nodeFIFO.pushBack(parentDecision);
846 while (!nodeFIFO.empty()) {
847 current = nodeFIFO.front();
850 for (
const auto new_one: dag_.children(current)) {
851 if (mark[new_one])
continue;
853 mark[new_one] =
true;
855 if (!isDecisionNode(new_one))
856 nodeFIFO.pushBack(new_one);
858 childrenSeq.insert(new_one);
869 template <
typename GUM_SCALAR >
870 std::vector< NodeId > InfluenceDiagram< GUM_SCALAR >::decisionOrder()
const {
871 if (!decisionOrderExists()) { GUM_ERROR(NotFound,
"No decision path exists") }
873 std::vector< NodeId > decisionSequence;
875 for (
const auto elt: topologicalOrder(
false))
876 if (isDecisionNode(elt)) decisionSequence.push_back(elt);
878 return decisionSequence;
885 template <
typename GUM_SCALAR >
886 const List< NodeSet >& InfluenceDiagram< GUM_SCALAR >::getPartialTemporalOrder(
bool clear)
const {
888 _temporalOrder_.clear();
890 std::vector< NodeId > order = decisionOrder();
891 NodeSet nodeList = dag_.asNodeSet();
893 for (
auto i: order) {
894 NodeSet partialOrderedSet;
896 for (
const auto par: dag_.parents(i)) {
897 if (nodeList.contains(par) && isChanceNode(par)) {
898 partialOrderedSet.insert(par);
903 if (!partialOrderedSet.empty()) _temporalOrder_.pushFront(partialOrderedSet);
907 decisionSet.insert(i);
909 _temporalOrder_.pushFront(decisionSet);
914 for (
const auto node: nodeList)
915 if (isChanceNode(node)) lastSet.insert(node);
917 if (!lastSet.empty()) _temporalOrder_.pushFront(lastSet);
920 return _temporalOrder_;