33 #include <agrum/tools/variables/rangeVariable.h> 34 #include <agrum/tools/variables/labelizedVariable.h> 35 #include <agrum/tools/variables/discretizedVariable.h> 37 #include <agrum/ID/influenceDiagram.h> 40 template <
typename GUM_SCALAR >
41 NodeId build_node_for_ID(gum::InfluenceDiagram< GUM_SCALAR >& infdiag,
43 gum::Size default_domain_size) {
44 auto ds = default_domain_size;
46 long range_max =
long(ds) - 1;
47 std::vector< std::string > labels;
48 std::vector< GUM_SCALAR > ticks;
49 bool isUtil, isDeci, isChanc;
54 switch (*(node.begin())) {
67 std::string name = node;
68 if (*(node.rbegin()) ==
']') {
69 auto posBrack = node.find(
'[');
70 if (posBrack != std::string::npos) {
71 name = node.substr(0, posBrack);
72 const auto& s_args = node.substr(posBrack + 1, node.size() - posBrack - 2);
73 const auto& args = split(s_args,
",");
75 GUM_ERROR(InvalidArgument,
"Empty range for variable " << node)
76 }
else if (args.size() == 1) {
77 ds =
static_cast< Size >(std::stoi(args[0]));
79 range_max =
long(ds) - 1;
80 }
else if (args.size() == 2) {
81 range_min = std::stol(args[0]);
82 range_max = std::stol(args[1]);
83 if (1 + range_max - range_min < 2) {
84 GUM_ERROR(InvalidArgument,
"Invalid range for variable " << node)
86 ds =
static_cast< Size >(1 + range_max - range_min);
88 for (
const auto& tick: args) {
90 static_cast< GUM_SCALAR >(std::strtod(tick.c_str(),
nullptr)));
92 ds =
static_cast< Size >(args.size() - 1);
95 }
else if (*(node.rbegin()) ==
'}') {
96 auto posBrack = node.find(
'{');
97 if (posBrack != std::string::npos) {
98 name = node.substr(0, posBrack);
99 labels = split(node.substr(posBrack + 1, node.size() - posBrack - 2),
"|");
100 if (labels.size() < 2) {
101 GUM_ERROR(InvalidArgument,
"Not enough labels in node " << node)
103 if (!hasUniqueElts(labels)) {
104 GUM_ERROR(InvalidArgument,
"Duplicate labels in node " << node)
106 ds =
static_cast< Size >(labels.size());
111 GUM_ERROR(InvalidArgument,
"No value for variable " << name <<
".")
112 }
else if (ds == 1) {
113 GUM_ERROR(InvalidArgument,
114 "Only one value for variable " << name
115 <<
" (2 at least are needed).")
121 idVar = infdiag.idFromName(name);
122 }
catch (gum::NotFound&) {
124 if (!labels.empty()) {
125 idVar = infdiag.addChanceNode(LabelizedVariable(name, name, labels));
126 }
else if (!ticks.empty()) {
127 idVar = infdiag.addChanceNode(
128 DiscretizedVariable< GUM_SCALAR >(name, name, ticks));
130 idVar = infdiag.addChanceNode(
131 RangeVariable(name, name, range_min, range_max));
134 if (!labels.empty()) {
135 idVar = infdiag.addDecisionNode(LabelizedVariable(name, name, labels));
136 }
else if (!ticks.empty()) {
137 idVar = infdiag.addDecisionNode(
138 DiscretizedVariable< GUM_SCALAR >(name, name, ticks));
140 idVar = infdiag.addDecisionNode(
141 RangeVariable(name, name, range_min, range_max));
144 idVar = infdiag.addUtilityNode(LabelizedVariable(name, name, 1));
146 GUM_ERROR(FatalError,
147 "No type (chance, decision or utility) for the node '" << node
156 template <
typename GUM_SCALAR >
157 InfluenceDiagram< GUM_SCALAR >
158 InfluenceDiagram< GUM_SCALAR >::fastPrototype(
const std::string& dotlike,
160 gum::InfluenceDiagram< GUM_SCALAR > infdiag;
162 for (
const auto& chaine: split(dotlike,
";")) {
164 bool notfirst =
false;
165 for (
const auto& souschaine: split(chaine,
"->")) {
167 for (
const auto& node: split(souschaine,
"<-")) {
168 auto idVar = build_node_for_ID(infdiag, node, domainSize);
171 infdiag.addArc(lastId, idVar);
174 infdiag.addArc(idVar, lastId);
185 for (
const auto n: infdiag.nodes()) {
186 if (infdiag.isChanceNode(n))
187 infdiag.cpt(n).randomCPT();
188 else if (infdiag.isUtilityNode(n)) {
189 infdiag.utility(n).random().scale(50).translate(-10);
193 infdiag.setProperty(
"name",
"fastPrototype");
203 template <
typename GUM_SCALAR >
204 INLINE InfluenceDiagram< GUM_SCALAR >::InfluenceDiagram() : DAGmodel() {
205 GUM_CONSTRUCTOR(InfluenceDiagram);
211 template <
typename GUM_SCALAR >
212 InfluenceDiagram< GUM_SCALAR >::~InfluenceDiagram() {
213 GUM_DESTRUCTOR(InfluenceDiagram);
220 template <
typename GUM_SCALAR >
221 InfluenceDiagram< GUM_SCALAR >::InfluenceDiagram(
222 const InfluenceDiagram< GUM_SCALAR >& source) {
223 GUM_CONS_CPY(InfluenceDiagram);
224 copyStructureAndTables_(source);
230 template <
typename GUM_SCALAR >
231 InfluenceDiagram< GUM_SCALAR >& InfluenceDiagram< GUM_SCALAR >::operator=(
232 const InfluenceDiagram< GUM_SCALAR >& source) {
233 if (
this != &source) {
236 copyStructureAndTables_(source);
242 template <
typename GUM_SCALAR >
243 void InfluenceDiagram< GUM_SCALAR >::InfluenceDiagram::clear() {
246 variableMap__.clear();
248 potentialMap__.clear();
249 utilityMap__.clear();
255 template <
typename GUM_SCALAR >
256 void InfluenceDiagram< GUM_SCALAR >::removeTables_() {
257 for (
const auto node: dag_.nodes()) {
258 if (isChanceNode(node))
260 else if (isUtilityNode(node))
261 delete &utility(node);
268 template <
typename GUM_SCALAR >
269 void InfluenceDiagram< GUM_SCALAR >::copyStructureAndTables_(
270 const InfluenceDiagram< GUM_SCALAR >& IDsource) {
271 for (
auto node: IDsource.nodes()) {
272 if (IDsource.isChanceNode(node))
273 addChanceNode(IDsource.variable(node), node);
274 else if (IDsource.isUtilityNode(node))
275 addUtilityNode(IDsource.variable(node), node);
277 addDecisionNode(IDsource.variable(node), node);
280 for (
auto node: IDsource.nodes()) {
281 const auto& s = IDsource.variable(node).name();
282 if (IDsource.isChanceNode(node)) {
283 for (Idx par = 1; par <= IDsource.parents(node).size(); par++)
284 addArc(IDsource.cpt(node).variable(par).name(), s);
285 }
else if (IDsource.isUtilityNode(node)) {
286 for (Idx par = 1; par <= IDsource.parents(node).size(); par++)
287 addArc(IDsource.utility(node).variable(par).name(), s);
290 for (NodeId par: IDsource.parents(node))
296 for (
auto node: IDsource.nodes()) {
297 const auto& s = IDsource.variable(node).name();
298 if (IDsource.isChanceNode(node)) {
299 cpt(node).fillWith(IDsource.cpt(s));
300 }
else if (IDsource.isUtilityNode(node)) {
301 utility(node).fillWith(IDsource.utility(s));
306 template <
typename GUM_SCALAR >
307 std::string InfluenceDiagram< GUM_SCALAR >::toDot()
const {
308 std::stringstream output;
309 std::stringstream decisionNode;
310 std::stringstream utilityNode;
311 std::stringstream chanceNode;
312 std::stringstream arcstream;
313 output <<
"digraph \"";
316 output <<
this->property(
"name") <<
"\" {" << std::endl;
317 }
catch (NotFound&) { output <<
"no_name\" {" << std::endl; }
319 output <<
" node [bgcolor=\"#AAAAAA\", style=filled, height=0];" << std::endl;
321 decisionNode <<
"node [shape = box];" << std::endl;
323 utilityNode <<
"node [shape = hexagon, margin=0];" << std::endl;
324 chanceNode <<
"node [shape = ellipse];" << std::endl;
325 std::string tab =
" ";
327 for (
const auto node: dag_.nodes()) {
328 if (isChanceNode(node))
329 chanceNode << tab <<
"\"" << node <<
"-" << variable(node).name() <<
"\"" 331 else if (isUtilityNode(node))
332 utilityNode << tab <<
"\"" << node <<
"-" << variable(node).name() <<
"\"" 335 decisionNode << tab <<
"\"" << node <<
"-" << variable(node).name() <<
"\"" 338 if (dag_.children(node).size() > 0)
339 for (
const auto chi: dag_.children(node)) {
340 arcstream <<
"\"" << node <<
"-" << variable(node).name() <<
"\"" 342 <<
"\"" << chi <<
"-" << variable(chi).name() <<
"\"";
343 if (isDecisionNode(chi)) { arcstream <<
" [style=\"tapered, bold\"]"; }
344 arcstream <<
";" << std::endl;
348 output << decisionNode.str() << std::endl
349 << utilityNode.str() << std::endl
350 << chanceNode.str() << std::endl
352 << arcstream.str() << std::endl
358 template <
typename GUM_SCALAR >
359 std::string InfluenceDiagram< GUM_SCALAR >::toString()
const {
360 std::stringstream output;
362 output <<
"Influence Diagram{" << std::endl;
363 output <<
" chance: " << chanceNodeSize() <<
"," << std::endl;
364 output <<
" utility: " << utilityNodeSize() <<
"," << std::endl;
365 output <<
" decision: " << decisionNodeSize() <<
"," << std::endl;
366 output <<
" arcs: " << dag().sizeArcs() <<
"," << std::endl;
368 double dSize = log10DomainSize();
371 output <<
" domainSize: 10^" << dSize;
373 output <<
" domainSize: " << std::round(std::pow(10.0, dSize));
375 output << std::endl <<
"}";
387 template <
typename GUM_SCALAR >
388 INLINE
const Potential< GUM_SCALAR >&
389 InfluenceDiagram< GUM_SCALAR >::cpt(NodeId varId)
const {
390 return *(potentialMap__[varId]);
396 template <
typename GUM_SCALAR >
397 INLINE
const Potential< GUM_SCALAR >&
398 InfluenceDiagram< GUM_SCALAR >::utility(NodeId varId)
const {
399 return *(utilityMap__[varId]);
405 template <
typename GUM_SCALAR >
406 INLINE
bool InfluenceDiagram< GUM_SCALAR >::isUtilityNode(NodeId varId)
const {
407 return utilityMap__.exists(varId);
413 template <
typename GUM_SCALAR >
414 INLINE
bool InfluenceDiagram< GUM_SCALAR >::isDecisionNode(NodeId varId)
const {
417 if (isUtilityNode(varId) || isChanceNode(varId)) ret =
false;
425 template <
typename GUM_SCALAR >
426 INLINE
bool InfluenceDiagram< GUM_SCALAR >::isChanceNode(NodeId varId)
const {
427 return potentialMap__.exists(varId);
433 template <
typename GUM_SCALAR >
434 INLINE Size InfluenceDiagram< GUM_SCALAR >::utilityNodeSize()
const {
435 return utilityMap__.size();
441 template <
typename GUM_SCALAR >
442 INLINE Size InfluenceDiagram< GUM_SCALAR >::chanceNodeSize()
const {
443 return potentialMap__.size();
449 template <
typename GUM_SCALAR >
450 INLINE Size InfluenceDiagram< GUM_SCALAR >::decisionNodeSize()
const {
451 return (size() - utilityMap__.size() - potentialMap__.size());
458 template <
typename GUM_SCALAR >
459 INLINE
const VariableNodeMap&
460 InfluenceDiagram< GUM_SCALAR >::variableNodeMap()
const {
461 return variableMap__;
467 template <
typename GUM_SCALAR >
468 INLINE
const DiscreteVariable&
469 InfluenceDiagram< GUM_SCALAR >::variable(NodeId id)
const {
470 return variableMap__[id];
476 template <
typename GUM_SCALAR >
478 InfluenceDiagram< GUM_SCALAR >::nodeId(
const DiscreteVariable& var)
const {
479 return variableMap__.get(var);
483 template <
typename GUM_SCALAR >
485 InfluenceDiagram< GUM_SCALAR >::idFromName(
const std::string& name)
const {
486 return variableMap__.idFromName(name);
490 template <
typename GUM_SCALAR >
491 INLINE
const DiscreteVariable& InfluenceDiagram< GUM_SCALAR >::variableFromName(
492 const std::string& name)
const {
493 return variableMap__.variableFromName(name);
500 template <
typename GUM_SCALAR >
501 NodeId InfluenceDiagram< GUM_SCALAR >::add(
const DiscreteVariable& var,
503 return addChanceNode(var, varId);
511 template <
typename GUM_SCALAR >
513 InfluenceDiagram< GUM_SCALAR >::addUtilityNode(
const DiscreteVariable& var,
515 auto newMultiDim =
new MultiDimArray< GUM_SCALAR >();
519 res = addUtilityNode(var, newMultiDim, varId);
520 }
catch (Exception&) {
521 if (newMultiDim !=
nullptr)
delete newMultiDim;
532 template <
typename GUM_SCALAR >
534 InfluenceDiagram< GUM_SCALAR >::addDecisionNode(
const DiscreteVariable& var,
536 return addNode_(var, varId);
543 template <
typename GUM_SCALAR >
544 NodeId InfluenceDiagram< GUM_SCALAR >::addChanceNode(
const DiscreteVariable& var,
546 auto newMultiDim =
new MultiDimArray< GUM_SCALAR >();
550 res = addChanceNode(var, newMultiDim, varId);
551 }
catch (Exception&) {
563 template <
typename GUM_SCALAR >
564 NodeId InfluenceDiagram< GUM_SCALAR >::addChanceNode(
565 const DiscreteVariable& var,
566 MultiDimImplementation< GUM_SCALAR >* aContent,
568 NodeId proposedId = addNode_(var, DesiredId);
570 auto varcpt =
new Potential< GUM_SCALAR >(aContent);
571 (*varcpt) << variable(proposedId);
572 potentialMap__.insert(proposedId, varcpt);
582 template <
typename GUM_SCALAR >
583 NodeId InfluenceDiagram< GUM_SCALAR >::addUtilityNode(
584 const DiscreteVariable& var,
585 MultiDimImplementation< GUM_SCALAR >* aContent,
587 if (var.domainSize() != 1) {
588 GUM_ERROR(InvalidArgument,
589 "Utility var have no state ( which implicates a " 590 "single label for data output reasons ).")
593 NodeId proposedId = addNode_(var, DesiredId);
595 auto varut =
new Potential< GUM_SCALAR >(aContent);
597 (*varut) << variable(proposedId);
599 utilityMap__.insert(proposedId, varut);
607 template <
typename GUM_SCALAR >
609 InfluenceDiagram< GUM_SCALAR >::addNode_(
const DiscreteVariable& variableType,
615 proposedId = dag_.nextNodeId();
617 proposedId = DesiredId;
619 variableMap__.insert(proposedId, variableType);
621 dag_.addNodeWithId(proposedId);
632 template <
typename GUM_SCALAR >
633 void InfluenceDiagram< GUM_SCALAR >::erase(NodeId varId) {
634 if (variableMap__.exists(varId)) {
636 for (
const auto chi: dag_.children(varId))
637 if (isChanceNode(chi))
638 potentialMap__[chi]->erase(variable(varId));
639 else if (isUtilityNode(chi))
640 utilityMap__[chi]->erase(variable(varId));
642 if (isChanceNode(varId)) {
643 delete potentialMap__[varId];
644 potentialMap__.erase(varId);
645 }
else if (isUtilityNode(varId)) {
646 delete utilityMap__[varId];
647 utilityMap__.erase(varId);
650 variableMap__.erase(varId);
651 dag_.eraseNode(varId);
660 template <
typename GUM_SCALAR >
661 INLINE
void InfluenceDiagram< GUM_SCALAR >::erase(
const DiscreteVariable& var) {
662 erase(variableMap__.get(var));
667 template <
typename GUM_SCALAR >
668 INLINE
void InfluenceDiagram< GUM_SCALAR >::changeVariableName(
670 const std::string& new_name) {
671 variableMap__.changeName(id, new_name);
680 template <
typename GUM_SCALAR >
681 INLINE
void InfluenceDiagram< GUM_SCALAR >::addArc(NodeId tail, NodeId head) {
682 if (isUtilityNode(tail)) {
683 GUM_ERROR(InvalidArc,
"Tail cannot be a utility node")
686 dag_.addArc(tail, head);
688 if (isChanceNode(head))
690 (*(potentialMap__[head])) << variable(tail);
691 else if (isUtilityNode(head)) {
693 (*(utilityMap__[head])) << variable(tail);
702 template <
typename GUM_SCALAR >
703 INLINE
void InfluenceDiagram< GUM_SCALAR >::eraseArc(
const Arc& arc) {
704 if (dag_.existsArc(arc)) {
705 NodeId head = arc.head(), tail = arc.tail();
708 if (isChanceNode(head))
710 (*(potentialMap__[head])) >> variable(tail);
711 else if (isUtilityNode(head))
713 (*(utilityMap__[head])) >> variable(tail);
722 template <
typename GUM_SCALAR >
723 INLINE
void InfluenceDiagram< GUM_SCALAR >::eraseArc(NodeId tail, NodeId head) {
724 eraseArc(Arc(tail, head));
734 template <
typename GUM_SCALAR >
735 void InfluenceDiagram< GUM_SCALAR >::moralGraph_(UndiGraph& graph)
const {
736 for (
const auto node: dag_.nodes())
737 if (!isUtilityNode(node)) graph.addNodeWithId(node);
739 for (
const auto node: dag_.nodes()) {
740 if (!isDecisionNode(node))
741 for (
const auto par: dag_.parents(node)) {
742 if (isChanceNode(node)) graph.addEdge(node, par);
744 for (
const auto par2: dag_.parents(node))
745 if (par != par2) graph.addEdge(par, par2);
753 template <
typename GUM_SCALAR >
754 bool InfluenceDiagram< GUM_SCALAR >::decisionOrderExists()
const {
755 const Sequence< NodeId >& order = topologicalOrder(
true);
758 Sequence< NodeId >::const_iterator orderIter = order.begin();
760 while ((orderIter != order.end()) && (!isDecisionNode(*orderIter)))
763 if (orderIter == order.end())
return true;
765 NodeId parentDecision = (*orderIter);
769 while (orderIter != order.end()) {
770 if (isDecisionNode(*orderIter)) {
771 if (!existsPathBetween(parentDecision, *orderIter))
return false;
773 parentDecision = *orderIter;
785 template <
typename GUM_SCALAR >
786 bool InfluenceDiagram< GUM_SCALAR >::existsPathBetween(NodeId src,
788 List< NodeId > nodeFIFO;
791 NodeProperty<
int > mark = dag_.nodesProperty((
int)-1);
794 mark[src] = (
int)src;
795 nodeFIFO.pushBack(src);
797 while (!nodeFIFO.empty()) {
798 current = nodeFIFO.front();
801 for (
const auto new_one: dag_.children(current)) {
802 if (mark[new_one] != -1)
805 mark[new_one] = (
int)current;
807 if (new_one == dest)
break;
809 nodeFIFO.pushBack(new_one);
813 if (mark[dest] == -1)
return false;
821 template <
typename GUM_SCALAR >
822 gum::DAG* InfluenceDiagram< GUM_SCALAR >::getDecisionGraph()
const {
823 auto temporalGraph =
new gum::DAG();
825 for (
const auto node: dag_.nodes()) {
826 if (isDecisionNode(node)) {
827 if (!temporalGraph->existsNode(node)) temporalGraph->addNodeWithId(node);
829 for (
const auto chi: getChildrenDecision_(node)) {
830 if (!temporalGraph->existsNode(chi)) temporalGraph->addNodeWithId(chi);
832 temporalGraph->addArc(node, chi);
837 return temporalGraph;
843 template <
typename GUM_SCALAR >
844 Sequence< NodeId > InfluenceDiagram< GUM_SCALAR >::getChildrenDecision_(
845 NodeId parentDecision)
const {
846 Sequence< NodeId > childrenSeq;
848 List< NodeId > nodeFIFO;
853 NodeProperty<
bool > mark = dag_.nodesProperty(
false);
855 mark[parentDecision] =
true;
857 nodeFIFO.pushBack(parentDecision);
859 while (!nodeFIFO.empty()) {
860 current = nodeFIFO.front();
863 for (
const auto new_one: dag_.children(current)) {
864 if (mark[new_one])
continue;
866 mark[new_one] =
true;
868 if (!isDecisionNode(new_one))
869 nodeFIFO.pushBack(new_one);
871 childrenSeq.insert(new_one);
882 template <
typename GUM_SCALAR >
883 std::vector< NodeId > InfluenceDiagram< GUM_SCALAR >::decisionOrder()
const {
884 if (!decisionOrderExists()) { GUM_ERROR(NotFound,
"No decision path exists") }
886 std::vector< NodeId > decisionSequence;
888 for (
const auto elt: topologicalOrder(
false))
889 if (isDecisionNode(elt)) decisionSequence.push_back(elt);
891 return decisionSequence;
898 template <
typename GUM_SCALAR >
899 const List< NodeSet >&
900 InfluenceDiagram< GUM_SCALAR >::getPartialTemporalOrder(
bool clear)
const {
902 temporalOrder__.clear();
904 std::vector< NodeId > order = decisionOrder();
905 NodeSet nodeList = dag_.asNodeSet();
907 for (
auto i: order) {
908 NodeSet partialOrderedSet;
910 for (
const auto par: dag_.parents(i)) {
911 if (nodeList.contains(par) && isChanceNode(par)) {
912 partialOrderedSet.insert(par);
917 if (!partialOrderedSet.empty())
918 temporalOrder__.pushFront(partialOrderedSet);
922 decisionSet.insert(i);
924 temporalOrder__.pushFront(decisionSet);
929 for (
const auto node: nodeList)
930 if (isChanceNode(node)) lastSet.insert(node);
932 if (!lastSet.empty()) temporalOrder__.pushFront(lastSet);
935 return temporalOrder__;