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) {
89 ticks.push_back(
static_cast< GUM_SCALAR >(std::strtod(tick.c_str(),
nullptr)));
91 ds =
static_cast< Size >(args.size() - 1);
94 }
else if (*(node.rbegin()) ==
'}') {
95 auto posBrack = node.find(
'{');
96 if (posBrack != std::string::npos) {
97 name = node.substr(0, posBrack);
98 labels = split(node.substr(posBrack + 1, node.size() - posBrack - 2),
"|");
99 if (labels.size() < 2) { GUM_ERROR(InvalidArgument,
"Not enough labels in node " << node) }
100 if (!hasUniqueElts(labels)) {
101 GUM_ERROR(InvalidArgument,
"Duplicate labels in node " << node)
103 ds =
static_cast< Size >(labels.size());
108 GUM_ERROR(InvalidArgument,
"No value for variable " << name <<
".")
109 }
else if (ds == 1) {
110 GUM_ERROR(InvalidArgument,
111 "Only one value for variable " << name <<
" (2 at least are needed).")
117 idVar = infdiag.idFromName(name);
118 }
catch (gum::NotFound&) {
120 if (!labels.empty()) {
121 idVar = infdiag.addChanceNode(LabelizedVariable(name, name, labels));
122 }
else if (!ticks.empty()) {
123 idVar = infdiag.addChanceNode(DiscretizedVariable< GUM_SCALAR >(name, name, ticks));
125 idVar = infdiag.addChanceNode(RangeVariable(name, name, range_min, range_max));
128 if (!labels.empty()) {
129 idVar = infdiag.addDecisionNode(LabelizedVariable(name, name, labels));
130 }
else if (!ticks.empty()) {
131 idVar = infdiag.addDecisionNode(DiscretizedVariable< GUM_SCALAR >(name, name, ticks));
133 idVar = infdiag.addDecisionNode(RangeVariable(name, name, range_min, range_max));
136 idVar = infdiag.addUtilityNode(LabelizedVariable(name, name, 1));
138 GUM_ERROR(FatalError,
139 "No type (chance, decision or utility) for the node '" << node <<
"'.")
147 template <
typename GUM_SCALAR >
148 InfluenceDiagram< GUM_SCALAR >
149 InfluenceDiagram< GUM_SCALAR >::fastPrototype(
const std::string& dotlike, Size domainSize) {
150 gum::InfluenceDiagram< GUM_SCALAR > infdiag;
152 for (
const auto& chaine: split(dotlike,
";")) {
154 bool notfirst =
false;
155 for (
const auto& souschaine: split(chaine,
"->")) {
157 for (
const auto& node: split(souschaine,
"<-")) {
158 auto idVar = build_node_for_ID(infdiag, node, domainSize);
161 infdiag.addArc(lastId, idVar);
164 infdiag.addArc(idVar, lastId);
175 for (
const auto n: infdiag.nodes()) {
176 if (infdiag.isChanceNode(n))
177 infdiag.cpt(n).randomCPT();
178 else if (infdiag.isUtilityNode(n)) {
179 infdiag.utility(n).random().scale(50).translate(-10);
183 infdiag.setProperty(
"name",
"fastPrototype");
193 template <
typename GUM_SCALAR >
194 INLINE InfluenceDiagram< GUM_SCALAR >::InfluenceDiagram() : DAGmodel() {
195 GUM_CONSTRUCTOR(InfluenceDiagram);
201 template <
typename GUM_SCALAR >
202 InfluenceDiagram< GUM_SCALAR >::~InfluenceDiagram() {
203 GUM_DESTRUCTOR(InfluenceDiagram);
210 template <
typename GUM_SCALAR >
211 InfluenceDiagram< GUM_SCALAR >::InfluenceDiagram(
const InfluenceDiagram< GUM_SCALAR >& source) {
212 GUM_CONS_CPY(InfluenceDiagram);
213 copyStructureAndTables_(source);
219 template <
typename GUM_SCALAR >
220 InfluenceDiagram< GUM_SCALAR >&
221 InfluenceDiagram< GUM_SCALAR >::operator=(
const InfluenceDiagram< GUM_SCALAR >& source) {
222 if (
this != &source) {
225 copyStructureAndTables_(source);
231 template <
typename GUM_SCALAR >
232 void InfluenceDiagram< GUM_SCALAR >::InfluenceDiagram::clear() {
235 _variableMap_.clear();
237 _potentialMap_.clear();
238 _utilityMap_.clear();
244 template <
typename GUM_SCALAR >
245 void InfluenceDiagram< GUM_SCALAR >::removeTables_() {
246 for (
const auto node: dag_.nodes()) {
247 if (isChanceNode(node))
249 else if (isUtilityNode(node))
250 delete &utility(node);
257 template <
typename GUM_SCALAR >
258 void InfluenceDiagram< GUM_SCALAR >::copyStructureAndTables_(
259 const InfluenceDiagram< GUM_SCALAR >& IDsource) {
260 for (
auto node: IDsource.nodes()) {
261 if (IDsource.isChanceNode(node))
262 addChanceNode(IDsource.variable(node), node);
263 else if (IDsource.isUtilityNode(node))
264 addUtilityNode(IDsource.variable(node), node);
266 addDecisionNode(IDsource.variable(node), node);
269 for (
auto node: IDsource.nodes()) {
270 const auto& s = IDsource.variable(node).name();
271 if (IDsource.isChanceNode(node)) {
272 for (Idx par = 1; par <= IDsource.parents(node).size(); par++)
273 addArc(IDsource.cpt(node).variable(par).name(), s);
274 }
else if (IDsource.isUtilityNode(node)) {
275 for (Idx par = 1; par <= IDsource.parents(node).size(); par++)
276 addArc(IDsource.utility(node).variable(par).name(), s);
279 for (NodeId par: IDsource.parents(node))
285 for (
auto node: IDsource.nodes()) {
286 const auto& s = IDsource.variable(node).name();
287 if (IDsource.isChanceNode(node)) {
288 cpt(node).fillWith(IDsource.cpt(s));
289 }
else if (IDsource.isUtilityNode(node)) {
290 utility(node).fillWith(IDsource.utility(s));
295 template <
typename GUM_SCALAR >
296 std::string InfluenceDiagram< GUM_SCALAR >::toDot()
const {
297 std::stringstream output;
298 std::stringstream decisionNode;
299 std::stringstream utilityNode;
300 std::stringstream chanceNode;
301 std::stringstream arcstream;
302 output <<
"digraph \"";
305 output <<
this->property(
"name") <<
"\" {" << std::endl;
306 }
catch (NotFound&) { output <<
"no_name\" {" << std::endl; }
308 output <<
" node [bgcolor=\"#AAAAAA\", style=filled, height=0];" << std::endl;
310 decisionNode <<
"node [shape = box];" << std::endl;
312 utilityNode <<
"node [shape = hexagon, margin=0];" << std::endl;
313 chanceNode <<
"node [shape = ellipse];" << std::endl;
314 std::string tab =
" ";
316 for (
const auto node: dag_.nodes()) {
317 if (isChanceNode(node))
318 chanceNode << tab <<
"\"" << node <<
"-" << variable(node).name() <<
"\"" 320 else if (isUtilityNode(node))
321 utilityNode << tab <<
"\"" << node <<
"-" << variable(node).name() <<
"\"" 324 decisionNode << tab <<
"\"" << node <<
"-" << variable(node).name() <<
"\"" 327 if (dag_.children(node).size() > 0)
328 for (
const auto chi: dag_.children(node)) {
329 arcstream <<
"\"" << node <<
"-" << variable(node).name() <<
"\"" 331 <<
"\"" << chi <<
"-" << variable(chi).name() <<
"\"";
332 if (isDecisionNode(chi)) { arcstream <<
" [style=\"tapered, bold\"]"; }
333 arcstream <<
";" << std::endl;
337 output << decisionNode.str() << std::endl
338 << utilityNode.str() << std::endl
339 << chanceNode.str() << std::endl
341 << arcstream.str() << std::endl
347 template <
typename GUM_SCALAR >
348 std::string InfluenceDiagram< GUM_SCALAR >::toString()
const {
349 std::stringstream output;
351 output <<
"Influence Diagram{" << std::endl;
352 output <<
" chance: " << chanceNodeSize() <<
"," << std::endl;
353 output <<
" utility: " << utilityNodeSize() <<
"," << std::endl;
354 output <<
" decision: " << decisionNodeSize() <<
"," << std::endl;
355 output <<
" arcs: " << dag().sizeArcs() <<
"," << std::endl;
357 double dSize = log10DomainSize();
360 output <<
" domainSize: 10^" << dSize;
362 output <<
" domainSize: " << std::round(std::pow(10.0, dSize));
364 output << std::endl <<
"}";
376 template <
typename GUM_SCALAR >
377 INLINE
const Potential< GUM_SCALAR >& InfluenceDiagram< GUM_SCALAR >::cpt(NodeId varId)
const {
378 return *(_potentialMap_[varId]);
384 template <
typename GUM_SCALAR >
385 INLINE
const Potential< GUM_SCALAR >&
386 InfluenceDiagram< GUM_SCALAR >::utility(NodeId varId)
const {
387 return *(_utilityMap_[varId]);
393 template <
typename GUM_SCALAR >
394 INLINE
bool InfluenceDiagram< GUM_SCALAR >::isUtilityNode(NodeId varId)
const {
395 return _utilityMap_.exists(varId);
401 template <
typename GUM_SCALAR >
402 INLINE
bool InfluenceDiagram< GUM_SCALAR >::isDecisionNode(NodeId varId)
const {
405 if (isUtilityNode(varId) || isChanceNode(varId)) ret =
false;
413 template <
typename GUM_SCALAR >
414 INLINE
bool InfluenceDiagram< GUM_SCALAR >::isChanceNode(NodeId varId)
const {
415 return _potentialMap_.exists(varId);
421 template <
typename GUM_SCALAR >
422 INLINE Size InfluenceDiagram< GUM_SCALAR >::utilityNodeSize()
const {
423 return _utilityMap_.size();
429 template <
typename GUM_SCALAR >
430 INLINE Size InfluenceDiagram< GUM_SCALAR >::chanceNodeSize()
const {
431 return _potentialMap_.size();
437 template <
typename GUM_SCALAR >
438 INLINE Size InfluenceDiagram< GUM_SCALAR >::decisionNodeSize()
const {
439 return (size() - _utilityMap_.size() - _potentialMap_.size());
446 template <
typename GUM_SCALAR >
447 INLINE
const VariableNodeMap& InfluenceDiagram< GUM_SCALAR >::variableNodeMap()
const {
448 return _variableMap_;
454 template <
typename GUM_SCALAR >
455 INLINE
const DiscreteVariable& InfluenceDiagram< GUM_SCALAR >::variable(NodeId id)
const {
456 return _variableMap_[id];
462 template <
typename GUM_SCALAR >
463 INLINE NodeId InfluenceDiagram< GUM_SCALAR >::nodeId(
const DiscreteVariable& var)
const {
464 return _variableMap_.get(var);
468 template <
typename GUM_SCALAR >
469 INLINE NodeId InfluenceDiagram< GUM_SCALAR >::idFromName(
const std::string& name)
const {
470 return _variableMap_.idFromName(name);
474 template <
typename GUM_SCALAR >
475 INLINE
const DiscreteVariable&
476 InfluenceDiagram< GUM_SCALAR >::variableFromName(
const std::string& name)
const {
477 return _variableMap_.variableFromName(name);
484 template <
typename GUM_SCALAR >
485 NodeId InfluenceDiagram< GUM_SCALAR >::add(
const DiscreteVariable& var, NodeId varId) {
486 return addChanceNode(var, varId);
494 template <
typename GUM_SCALAR >
495 NodeId InfluenceDiagram< GUM_SCALAR >::addUtilityNode(
const DiscreteVariable& var, NodeId varId) {
496 auto newMultiDim =
new MultiDimArray< GUM_SCALAR >();
500 res = addUtilityNode(var, newMultiDim, varId);
501 }
catch (Exception&) {
502 if (newMultiDim !=
nullptr)
delete newMultiDim;
513 template <
typename GUM_SCALAR >
514 NodeId InfluenceDiagram< GUM_SCALAR >::addDecisionNode(
const DiscreteVariable& var,
516 return addNode_(var, varId);
523 template <
typename GUM_SCALAR >
524 NodeId InfluenceDiagram< GUM_SCALAR >::addChanceNode(
const DiscreteVariable& var, NodeId varId) {
525 auto newMultiDim =
new MultiDimArray< GUM_SCALAR >();
529 res = addChanceNode(var, newMultiDim, varId);
530 }
catch (Exception&) {
542 template <
typename GUM_SCALAR >
544 InfluenceDiagram< GUM_SCALAR >::addChanceNode(
const DiscreteVariable& var,
545 MultiDimImplementation< GUM_SCALAR >* aContent,
547 NodeId proposedId = addNode_(var, DesiredId);
549 auto varcpt =
new Potential< GUM_SCALAR >(aContent);
550 (*varcpt) << variable(proposedId);
551 _potentialMap_.insert(proposedId, varcpt);
561 template <
typename GUM_SCALAR >
563 InfluenceDiagram< GUM_SCALAR >::addUtilityNode(
const DiscreteVariable& var,
564 MultiDimImplementation< GUM_SCALAR >* aContent,
566 if (var.domainSize() != 1) {
567 GUM_ERROR(InvalidArgument,
568 "Utility var have no state ( which implicates a " 569 "single label for data output reasons ).")
572 NodeId proposedId = addNode_(var, DesiredId);
574 auto varut =
new Potential< GUM_SCALAR >(aContent);
576 (*varut) << variable(proposedId);
578 _utilityMap_.insert(proposedId, varut);
586 template <
typename GUM_SCALAR >
587 NodeId InfluenceDiagram< GUM_SCALAR >::addNode_(
const DiscreteVariable& variableType,
593 proposedId = dag_.nextNodeId();
595 proposedId = DesiredId;
597 _variableMap_.insert(proposedId, variableType);
599 dag_.addNodeWithId(proposedId);
610 template <
typename GUM_SCALAR >
611 void InfluenceDiagram< GUM_SCALAR >::erase(NodeId varId) {
612 if (_variableMap_.exists(varId)) {
614 for (
const auto chi: dag_.children(varId))
615 if (isChanceNode(chi))
616 _potentialMap_[chi]->erase(variable(varId));
617 else if (isUtilityNode(chi))
618 _utilityMap_[chi]->erase(variable(varId));
620 if (isChanceNode(varId)) {
621 delete _potentialMap_[varId];
622 _potentialMap_.erase(varId);
623 }
else if (isUtilityNode(varId)) {
624 delete _utilityMap_[varId];
625 _utilityMap_.erase(varId);
628 _variableMap_.erase(varId);
629 dag_.eraseNode(varId);
638 template <
typename GUM_SCALAR >
639 INLINE
void InfluenceDiagram< GUM_SCALAR >::erase(
const DiscreteVariable& var) {
640 erase(_variableMap_.get(var));
645 template <
typename GUM_SCALAR >
646 INLINE
void InfluenceDiagram< GUM_SCALAR >::changeVariableName(NodeId id,
647 const std::string& new_name) {
648 _variableMap_.changeName(id, new_name);
657 template <
typename GUM_SCALAR >
658 INLINE
void InfluenceDiagram< GUM_SCALAR >::addArc(NodeId tail, NodeId head) {
659 if (isUtilityNode(tail)) { GUM_ERROR(InvalidArc,
"Tail cannot be a utility node") }
661 dag_.addArc(tail, head);
663 if (isChanceNode(head))
665 (*(_potentialMap_[head])) << variable(tail);
666 else if (isUtilityNode(head)) {
668 (*(_utilityMap_[head])) << variable(tail);
677 template <
typename GUM_SCALAR >
678 INLINE
void InfluenceDiagram< GUM_SCALAR >::eraseArc(
const Arc& arc) {
679 if (dag_.existsArc(arc)) {
680 NodeId head = arc.head(), tail = arc.tail();
683 if (isChanceNode(head))
685 (*(_potentialMap_[head])) >> variable(tail);
686 else if (isUtilityNode(head))
688 (*(_utilityMap_[head])) >> variable(tail);
697 template <
typename GUM_SCALAR >
698 INLINE
void InfluenceDiagram< GUM_SCALAR >::eraseArc(NodeId tail, NodeId head) {
699 eraseArc(Arc(tail, head));
709 template <
typename GUM_SCALAR >
710 void InfluenceDiagram< GUM_SCALAR >::moralGraph_(UndiGraph& graph)
const {
711 for (
const auto node: dag_.nodes())
712 if (!isUtilityNode(node)) graph.addNodeWithId(node);
714 for (
const auto node: dag_.nodes()) {
715 if (!isDecisionNode(node))
716 for (
const auto par: dag_.parents(node)) {
717 if (isChanceNode(node)) graph.addEdge(node, par);
719 for (
const auto par2: dag_.parents(node))
720 if (par != par2) graph.addEdge(par, par2);
728 template <
typename GUM_SCALAR >
729 bool InfluenceDiagram< GUM_SCALAR >::decisionOrderExists()
const {
730 const Sequence< NodeId >& order = topologicalOrder(
true);
733 Sequence< NodeId >::const_iterator orderIter = order.begin();
735 while ((orderIter != order.end()) && (!isDecisionNode(*orderIter)))
738 if (orderIter == order.end())
return true;
740 NodeId parentDecision = (*orderIter);
744 while (orderIter != order.end()) {
745 if (isDecisionNode(*orderIter)) {
746 if (!existsPathBetween(parentDecision, *orderIter))
return false;
748 parentDecision = *orderIter;
760 template <
typename GUM_SCALAR >
761 bool InfluenceDiagram< GUM_SCALAR >::existsPathBetween(NodeId src, NodeId dest)
const {
762 List< NodeId > nodeFIFO;
765 NodeProperty<
int > mark = dag_.nodesProperty((
int)-1);
768 mark[src] = (
int)src;
769 nodeFIFO.pushBack(src);
771 while (!nodeFIFO.empty()) {
772 current = nodeFIFO.front();
775 for (
const auto new_one: dag_.children(current)) {
776 if (mark[new_one] != -1)
continue;
778 mark[new_one] = (
int)current;
780 if (new_one == dest)
break;
782 nodeFIFO.pushBack(new_one);
786 if (mark[dest] == -1)
return false;
794 template <
typename GUM_SCALAR >
795 gum::DAG* InfluenceDiagram< GUM_SCALAR >::getDecisionGraph()
const {
796 auto temporalGraph =
new gum::DAG();
798 for (
const auto node: dag_.nodes()) {
799 if (isDecisionNode(node)) {
800 if (!temporalGraph->existsNode(node)) temporalGraph->addNodeWithId(node);
802 for (
const auto chi: getChildrenDecision_(node)) {
803 if (!temporalGraph->existsNode(chi)) temporalGraph->addNodeWithId(chi);
805 temporalGraph->addArc(node, chi);
810 return temporalGraph;
816 template <
typename GUM_SCALAR >
818 InfluenceDiagram< GUM_SCALAR >::getChildrenDecision_(NodeId parentDecision)
const {
819 Sequence< NodeId > childrenSeq;
821 List< NodeId > nodeFIFO;
826 NodeProperty<
bool > mark = dag_.nodesProperty(
false);
828 mark[parentDecision] =
true;
830 nodeFIFO.pushBack(parentDecision);
832 while (!nodeFIFO.empty()) {
833 current = nodeFIFO.front();
836 for (
const auto new_one: dag_.children(current)) {
837 if (mark[new_one])
continue;
839 mark[new_one] =
true;
841 if (!isDecisionNode(new_one))
842 nodeFIFO.pushBack(new_one);
844 childrenSeq.insert(new_one);
855 template <
typename GUM_SCALAR >
856 std::vector< NodeId > InfluenceDiagram< GUM_SCALAR >::decisionOrder()
const {
857 if (!decisionOrderExists()) { GUM_ERROR(NotFound,
"No decision path exists") }
859 std::vector< NodeId > decisionSequence;
861 for (
const auto elt: topologicalOrder(
false))
862 if (isDecisionNode(elt)) decisionSequence.push_back(elt);
864 return decisionSequence;
871 template <
typename GUM_SCALAR >
872 const List< NodeSet >& InfluenceDiagram< GUM_SCALAR >::getPartialTemporalOrder(
bool clear)
const {
874 _temporalOrder_.clear();
876 std::vector< NodeId > order = decisionOrder();
877 NodeSet nodeList = dag_.asNodeSet();
879 for (
auto i: order) {
880 NodeSet partialOrderedSet;
882 for (
const auto par: dag_.parents(i)) {
883 if (nodeList.contains(par) && isChanceNode(par)) {
884 partialOrderedSet.insert(par);
889 if (!partialOrderedSet.empty()) _temporalOrder_.pushFront(partialOrderedSet);
893 decisionSet.insert(i);
895 _temporalOrder_.pushFront(decisionSet);
900 for (
const auto node: nodeList)
901 if (isChanceNode(node)) lastSet.insert(node);
903 if (!lastSet.empty()) _temporalOrder_.pushFront(lastSet);
906 return _temporalOrder_;