aGrUM  0.20.2
a C++ library for (probabilistic) graphical models
graphChangesSelector4DiGraph_tpl.h
Go to the documentation of this file.
1 /**
2  *
3  * Copyright 2005-2020 Pierre-Henri WUILLEMIN(@LIP6) & Christophe GONZALES(@AMU)
4  * info_at_agrum_dot_org
5  *
6  * This library is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this library. If not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 
22 /** @file
23  * @brief The mecanism to compute the next available graph changes for directed
24  * structure learning search algorithms.
25  *
26  * @author Christophe GONZALES(@AMU) and Pierre-Henri WUILLEMIN(@LIP6)
27  */
28 #ifndef DOXYGEN_SHOULD_SKIP_THIS
29 
30 # include <limits>
31 
32 namespace gum {
33 
34  namespace learning {
35 
36  /// default constructor
37  template < typename STRUCTURAL_CONSTRAINT,
38  typename GRAPH_CHANGES_GENERATOR,
39  template < typename >
40  class ALLOC >
41  INLINE GraphChangesSelector4DiGraph< STRUCTURAL_CONSTRAINT,
43  ALLOC >::
47  score__(score.clone()),
49  parents__.resize(32);
51  }
52 
53  /// copy constructor
54  template < typename STRUCTURAL_CONSTRAINT,
55  typename GRAPH_CHANGES_GENERATOR,
56  template < typename >
57  class ALLOC >
60  ALLOC >::
64  ALLOC >& from) :
65  score__(from.score__ != nullptr ? from.score__->clone() : nullptr),
74  // for debugging
76  }
77 
78  /// move constructor
79  template < typename STRUCTURAL_CONSTRAINT,
80  typename GRAPH_CHANGES_GENERATOR,
81  template < typename >
82  class ALLOC >
85  ALLOC >::
89  ALLOC >&& from) :
102  from.score__ = nullptr;
103  // for debugging
105  }
106 
107  /// destructor
108  template < typename STRUCTURAL_CONSTRAINT,
109  typename GRAPH_CHANGES_GENERATOR,
110  template < typename >
111  class ALLOC >
113 
117  if (score__ != nullptr) {
121  }
123  }
124 
125  /// copy operator
126  template < typename STRUCTURAL_CONSTRAINT,
127  typename GRAPH_CHANGES_GENERATOR,
128  template < typename >
129  class ALLOC >
132  ALLOC >&
135  ALLOC >::
138  ALLOC >& from) {
139  if (this != &from) {
140  // remove the old score
141  if (score__ != nullptr) {
145  score__ = nullptr;
146  }
147 
148  if (from.score__ != nullptr) score__ = from.score__->clone();
160  }
161 
162  return *this;
163  }
164 
165  /// move operator
166  template < typename STRUCTURAL_CONSTRAINT,
167  typename GRAPH_CHANGES_GENERATOR,
168  template < typename >
169  class ALLOC >
172  ALLOC >&
178  ALLOC >&& from) {
179  if (this != &from) {
180  score__ = from.score__;
181  from.score__ = nullptr;
182 
194  }
195 
196  return *this;
197  }
198 
199 
200  /// indicates whether a given change is valid or not
201  template < typename STRUCTURAL_CONSTRAINT,
202  typename GRAPH_CHANGES_GENERATOR,
203  template < typename >
204  class ALLOC >
205  INLINE bool
209  change) const {
211  }
212 
213 
214  /// indicates whether a given change is valid or not
215  template < typename STRUCTURAL_CONSTRAINT,
216  typename GRAPH_CHANGES_GENERATOR,
217  template < typename >
218  class ALLOC >
219  INLINE bool
222  ALLOC >::isChangeValid__(const std::size_t
223  index) const {
224  return isChangeValid(changes__[index]);
225  }
226 
227 
228  /// sets the graph from which scores are computed
229  template < typename STRUCTURAL_CONSTRAINT,
230  typename GRAPH_CHANGES_GENERATOR,
231  template < typename >
232  class ALLOC >
235  ALLOC >::setGraph(DiGraph& graph) {
236  // fill the DAG with all the missing nodes
238  const auto& nodeId2Columns = score__->nodeId2Columns();
239 
240  if (nodeId2Columns.empty()) {
242  for (NodeId i = NodeId(0); i < nb_nodes; ++i) {
243  if (!graph.existsNode(i)) { graph.addNodeWithId(i); }
244  }
245  } else {
246  for (auto iter = nodeId2Columns.cbegin(); iter != nodeId2Columns.cend();
247  ++iter) {
248  const NodeId id = iter.first();
249  if (!graph.existsNode(id)) { graph.addNodeWithId(id); }
250  }
251  }
252 
253 
254  // remove the node that do belong neither to the database
255  // nor to nodeId2Columns
256  if (nodeId2Columns.empty()) {
258  for (auto node: graph) {
259  if (node >= nb_nodes) { graph.eraseNode(node); }
260  }
261  } else {
262  for (auto node: graph) {
264  }
265  }
266 
267 
268  // constraint__ is the constraint used by the selector to restrict the set
269  // of applicable changes. However, the generator may have a different set
270  // of constraints (e.g., a constraintSliceOrder needs be tested only by the
271  // generator because the changes returned by the generator will always
272  // statisfy this constraint, hence the selector needs not test this
273  // constraint). Therefore, if the selector and generator have different
274  // constraints, both should use method setGraph() to initialize
275  // themselves.
277  if (reinterpret_cast< STRUCTURAL_CONSTRAINT* >(
279  != constraint__) {
281  }
282 
284 
285 
286  // save the set of parents of each node (this will speed-up the
287  // computations of the scores)
288  const std::size_t nb_nodes = graph.size();
289  {
290  const std::vector< NodeId, ALLOC< NodeId > > empty_pars;
291  parents__.clear();
293  for (const auto node: graph) {
296  if (!dag_parents.empty()) {
298  std::size_t j = std::size_t(0);
299  for (const auto par: dag_parents) {
300  node_parents[j] = par;
301  ++j;
302  }
303  }
304  }
305  }
306 
307  // assign a score to each node given its parents in the current graph
310  for (const auto node: graph) {
312  }
313 
314  // compute all the possible changes
315  changes__.clear();
317  for (const auto& change: *changes_generator__) {
318  changes__ << change;
319  }
321 
322  // determine the changes that are illegal and prepare the computation of
323  // the scores of all the legal changes
325 
326  // set the change_scores__ and change_queue_per_node__ for legal changes
329  changes__.size(),
330  std::pair< double, double >(std::numeric_limits< double >::min(),
331  std::numeric_limits< double >::min()));
334  {
335  const PriorityQueue< std::size_t, double, std::greater< double > >
336  empty_prio;
337  for (const auto node: graph) {
339  }
340  }
341 
342  for (std::size_t i = std::size_t(0); i < changes__.size(); ++i) {
343  if (!isChangeValid__(i)) {
345  } else {
346  const GraphChange& change = changes__[i];
347 
348  switch (change.type()) {
350  auto& parents = parents__[change.node2()];
352  const double delta = score__->score(change.node2(), parents)
354  parents.pop_back();
355 
358  } break;
359 
361  auto& parents = parents__[change.node2()];
362  for (auto& par: parents) {
363  if (par == change.node1()) {
364  par = *(parents.rbegin());
365  parents.pop_back();
366  break;
367  }
368  }
369  const double delta = score__->score(change.node2(), parents)
372 
375  } break;
376 
378  // remove arc ( node1 -> node2 )
379  auto& parents2 = parents__[change.node2()];
380  for (auto& par: parents2) {
381  if (par == change.node1()) {
382  par = *(parents2.rbegin());
383  parents2.pop_back();
384  break;
385  }
386  }
387 
388  const double delta2 = score__->score(change.node2(), parents2)
391 
392  // add arc ( node2 -> node1 )
393  auto& parents1 = parents__[change.node1()];
395  const double delta1 = score__->score(change.node1(), parents1)
397  parents1.pop_back();
398 
401 
402  const double delta = delta1 + delta2;
405 
406  } break;
407 
408  default: {
410  "Method setGraph of GraphChangesSelector4DiGraph "
411  << "does not handle yet graph change of type "
412  << change.type());
413  }
414  }
415  }
416  }
417 
418  // update the global queue
420  for (const auto node: graph) {
423  ? std::numeric_limits< double >::min()
425  }
426  queues_valid__ = true;
428  }
429 
430 
431  /// put a change into the illegal set
432  template < typename STRUCTURAL_CONSTRAINT,
433  typename GRAPH_CHANGES_GENERATOR,
434  template < typename >
435  class ALLOC >
436  void
440  change_index) {
443  // remove the tail change from its priority queue
444  PriorityQueue< std::size_t, double, std::greater< double > >& queue1
447 
448  // recompute the top priority for the changes of the head
449  const double new_priority = queue1.empty()
450  ? std::numeric_limits< double >::min()
451  : queue1.topPriority();
453  }
454 
455  // remove the head change from its priority queue
456  PriorityQueue< std::size_t, double, std::greater< double > >& queue2
459 
460  // recompute the top priority for the changes of the head
461  const double new_priority = queue2.empty()
462  ? std::numeric_limits< double >::min()
463  : queue2.topPriority();
465 
466  // put the change into the illegal set
468  }
469 
470 
471  /// indicates whether the selector still contain graph changes
472  template < typename STRUCTURAL_CONSTRAINT,
473  typename GRAPH_CHANGES_GENERATOR,
474  template < typename >
475  class ALLOC >
478  ALLOC >::empty() {
479  // put into the illegal change set all the top elements of the different
480  // queues that are not valid anymore
481  if (!queues_valid__) {
482  for (auto& queue_pair: change_queue_per_node__) {
483  auto& queue = queue_pair.second;
484  while (!queue.empty() && !isChangeValid__(queue.top())) {
486  }
487  }
488  queues_valid__ = true;
489  }
490 
491  return node_queue__.topPriority() == std::numeric_limits< double >::min();
492  }
493 
494 
495  /// indicates whether the selector still contain graph changes
496  /// in the ith queue
497  template < typename STRUCTURAL_CONSTRAINT,
498  typename GRAPH_CHANGES_GENERATOR,
499  template < typename >
500  class ALLOC >
503  ALLOC >::empty(const NodeId node) {
504  // put into the illegal change set all the top elements of the different
505  // queues that are not valid anymore
506  if (!queues_valid__) {
507  for (auto& queue_pair: change_queue_per_node__) {
508  auto& queue = queue_pair.second;
509  while (!queue.empty() && !isChangeValid__(queue.top())) {
511  }
512  }
513  queues_valid__ = true;
514  }
515 
517  }
518 
519 
520  /// returns the best graph change to examine
521  template < typename STRUCTURAL_CONSTRAINT,
522  typename GRAPH_CHANGES_GENERATOR,
523  template < typename >
524  class ALLOC >
525  INLINE const GraphChange&
528  ALLOC >::bestChange() {
529  if (!empty())
531  else
532  GUM_ERROR(NotFound, "there exists no graph change applicable");
533  }
534 
535 
536  /// returns the best graph change to examine in the ith queue
537  template < typename STRUCTURAL_CONSTRAINT,
538  typename GRAPH_CHANGES_GENERATOR,
539  template < typename >
540  class ALLOC >
541  INLINE const GraphChange&
544  ALLOC >::bestChange(const NodeId node) {
545  if (!empty(node))
547  else
548  GUM_ERROR(NotFound, "there exists no graph change applicable");
549  }
550 
551 
552  /// return the score of the best graph change
553  template < typename STRUCTURAL_CONSTRAINT,
554  typename GRAPH_CHANGES_GENERATOR,
555  template < typename >
556  class ALLOC >
559  ALLOC >::bestScore() {
560  if (!empty())
562  else
563  GUM_ERROR(NotFound, "there exists no graph change applicable");
564  }
565 
566 
567  /// return the score of the best graph change in the ith queue
568  template < typename STRUCTURAL_CONSTRAINT,
569  typename GRAPH_CHANGES_GENERATOR,
570  template < typename >
571  class ALLOC >
572  INLINE double
575  ALLOC >::bestScore(const NodeId node) {
576  if (!empty(node))
578  else
579  GUM_ERROR(NotFound, "there exists no graph change applicable");
580  }
581 
582 
583  /// remove the now legal changes from the illegal set
584  template < typename STRUCTURAL_CONSTRAINT,
585  typename GRAPH_CHANGES_GENERATOR,
586  template < typename >
587  class ALLOC >
592  for (auto iter = illegal_changes__.beginSafe();
594  ++iter) {
595  if (isChangeValid__(*iter)) {
596  const GraphChange& change = changes__[*iter];
599  *iter,
600  std::numeric_limits< double >::min());
601  }
603  *iter,
604  std::numeric_limits< double >::min());
605 
608  }
609  }
610  }
611 
612 
613  /// finds the changes that are affected by a given node modification
614  template < typename STRUCTURAL_CONSTRAINT,
615  typename GRAPH_CHANGES_GENERATOR,
616  template < typename >
617  class ALLOC >
620  ALLOC >::
622  const NodeId target_node) {
623  const HashTable< std::size_t, Size >& changes
625  for (auto iter = changes.cbeginSafe(); iter != changes.cendSafe(); ++iter) {
627  if (isChangeValid__(iter.key())) {
629  } else {
631  }
632  }
633  }
634  }
635 
636 
637  /// perform the necessary updates of the scores
638  template < typename STRUCTURAL_CONSTRAINT,
639  typename GRAPH_CHANGES_GENERATOR,
640  template < typename >
641  class ALLOC >
647 
648  for (const auto change_index: changes_to_recompute) {
650 
651  switch (change.type()) {
653  // add the arc
654  auto& parents = parents__[change.node2()];
656  const double delta = score__->score(change.node2(), parents)
658  parents.pop_back();
659 
660  // update the score
662 
663  // update the head queue
665  delta);
666  // indicate which queue was modified
668  } break;
669 
671  // remove the arc
672  auto& parents = parents__[change.node2()];
673  for (auto& par: parents) {
674  if (par == change.node1()) {
675  par = *(parents.rbegin());
676  parents.pop_back();
677  break;
678  }
679  }
680  const double delta = score__->score(change.node2(), parents)
683 
684  // update the score
686 
687  // update the head queue
689  delta);
690  // indicate which queue was modified
692  } break;
693 
695  // remove arc ( node1 -> node2 )
696  auto& parents2 = parents__[change.node2()];
697  for (auto& par: parents2) {
698  if (par == change.node1()) {
699  par = *(parents2.rbegin());
700  parents2.pop_back();
701  break;
702  }
703  }
704 
705  const double delta2 = score__->score(change.node2(), parents2)
708 
709  // add arc ( node2 -> node1 )
710  auto& parents1 = parents__[change.node1()];
712  const double delta1 = score__->score(change.node1(), parents1)
714  parents1.pop_back();
715 
716  // update the scores
719 
720  // update the queues
721  const double delta = delta1 + delta2;
723  delta);
725  delta);
726 
727  // indicate which queues were modified
730  } break;
731 
732  default: {
734  "Method updateScores__ of GraphChangesSelector4DiGraph "
735  << "does not handle yet graph change of type "
736  << change.type());
737  }
738  }
739  }
740 
741  // update the node queue
742  for (const auto node: modified_nodes) {
745  ? std::numeric_limits< double >::min()
747  }
748  }
749 
750 
751  /// get from the graph change generator a new set of changes
752  template < typename STRUCTURAL_CONSTRAINT,
753  typename GRAPH_CHANGES_GENERATOR,
754  template < typename >
755  class ALLOC >
758  ALLOC >::getNewChanges__() {
759  // ask the graph change generator for all its available changes
760  for (const auto& change: *changes_generator__) {
761  // check that the change does not already exist
762  if (!changes__.exists(change)) {
763  // add the new change. To make the addition simple, we put the new
764  // change into the illegal changes set. Afterwards, the applyChange
765  // function will put the legal changes again into the queues
767  changes__ << change;
769  std::pair< double, double >(std::numeric_limits< double >::min(),
770  std::numeric_limits< double >::min()));
771  }
772  }
773 
774  // indicate to the generator that we have finished retrieving its changes
776  }
777 
778 
779  /// indicate to the selector that its best score has been applied
780  template < typename STRUCTURAL_CONSTRAINT,
781  typename GRAPH_CHANGES_GENERATOR,
782  template < typename >
783  class ALLOC >
786  ALLOC >::applyChange(const GraphChange&
787  change) {
788  // first, we get the index of the change
790 
791  // perform the change
793  switch (change.type()) {
795  // update the current score
799 
800  // inform the constraint that the graph has been modified
801  constraint__->modifyGraph(static_cast< const ArcAddition& >(change));
802  if (reinterpret_cast< STRUCTURAL_CONSTRAINT* >(
804  != constraint__) {
806  static_cast< const ArcAddition& >(change));
807  }
808 
809  // get new possible changes from the graph change generator
810  // warning: put the next 3 lines before calling illegal2LegalChanges__
812  static_cast< const ArcAddition& >(change));
813  getNewChanges__();
814 
815  // check whether some illegal changes can be put into the valid queues
820  } break;
821 
823  // update the current score
826  auto& parents = parents__[change.node2()];
827  for (auto& par: parents) {
828  if (par == change.node1()) {
829  par = *(parents.rbegin());
830  parents.pop_back();
831  break;
832  }
833  }
834 
835  // inform the constraint that the graph has been modified
836  constraint__->modifyGraph(static_cast< const ArcDeletion& >(change));
837  if (reinterpret_cast< STRUCTURAL_CONSTRAINT* >(
839  != constraint__) {
841  static_cast< const ArcDeletion& >(change));
842  }
843 
844  // get new possible changes from the graph change generator
845  // warning: put the next 3 lines before calling illegal2LegalChanges__
847  static_cast< const ArcDeletion& >(change));
848  getNewChanges__();
849 
850  // check whether some illegal changes can be put into the valid queues
855  } break;
856 
858  // update the current score
864  auto& parents = parents__[change.node2()];
865  for (auto& par: parents) {
866  if (par == change.node1()) {
867  par = *(parents.rbegin());
868  parents.pop_back();
869  break;
870  }
871  }
872 
873  // inform the constraint that the graph has been modified
874  constraint__->modifyGraph(static_cast< const ArcReversal& >(change));
875  if (reinterpret_cast< STRUCTURAL_CONSTRAINT* >(
877  != constraint__) {
879  static_cast< const ArcReversal& >(change));
880  }
881 
882  // get new possible changes from the graph change generator
883  // warning: put the next 3 lines before calling illegal2LegalChanges__
885  static_cast< const ArcReversal& >(change));
886  getNewChanges__();
887 
888  // check whether some illegal changes can be put into the valid queues
894  } break;
895 
896  default:
898  "Method applyChange of GraphChangesSelector4DiGraph "
899  << "does not handle yet graph change of type "
900  << change.type());
901  }
902 
903  queues_valid__ = false;
904  }
905 
906 
907  /// applies several changes at a time
908  template < typename STRUCTURAL_CONSTRAINT,
909  typename GRAPH_CHANGES_GENERATOR,
910  template < typename >
911  class ALLOC >
916  // first, we get the index of the change
918 
919  // perform the change
920  switch (change.type()) {
922  // update the current score
926 
927  // inform the constraint that the graph has been modified
928  constraint__->modifyGraph(static_cast< const ArcAddition& >(change));
929  if (reinterpret_cast< STRUCTURAL_CONSTRAINT* >(
931  != constraint__) {
933  static_cast< const ArcAddition& >(change));
934  }
935 
936  // get new possible changes from the graph change generator
937  // warning: put the next 3 lines before calling illegal2LegalChanges__
939  static_cast< const ArcAddition& >(change));
940  getNewChanges__();
941 
942  // indicate that we have just applied the change
944 
945  // indicate that the queue to which the change belongs needs be
946  // updated
948  } break;
949 
951  // update the current score
954  auto& parents = parents__[change.node2()];
955  for (auto& par: parents) {
956  if (par == change.node1()) {
957  par = *(parents.rbegin());
958  parents.pop_back();
959  break;
960  }
961  }
962 
963  // inform the constraint that the graph has been modified
964  constraint__->modifyGraph(static_cast< const ArcDeletion& >(change));
965  if (reinterpret_cast< STRUCTURAL_CONSTRAINT* >(
967  != constraint__) {
969  static_cast< const ArcDeletion& >(change));
970  }
971 
972  // get new possible changes from the graph change generator
973  // warning: put the next 3 lines before calling illegal2LegalChanges__
975  static_cast< const ArcDeletion& >(change));
976  getNewChanges__();
977 
978  // indicate that we have just applied the change
980 
981  // indicate that the queue to which the change belongs needs be
982  // updated
984  } break;
985 
987  // update the current score
993  auto& parents = parents__[change.node2()];
994  for (auto& par: parents) {
995  if (par == change.node1()) {
996  par = *(parents.rbegin());
997  parents.pop_back();
998  break;
999  }
1000  }
1001 
1002  // inform the constraint that the graph has been modified
1003  constraint__->modifyGraph(static_cast< const ArcReversal& >(change));
1004  if (reinterpret_cast< STRUCTURAL_CONSTRAINT* >(
1006  != constraint__) {
1008  static_cast< const ArcReversal& >(change));
1009  }
1010 
1011  // get new possible changes from the graph change generator
1012  // warning: put the next 3 lines before calling illegal2LegalChanges__
1014  static_cast< const ArcReversal& >(change));
1015  getNewChanges__();
1016 
1017  // indicate that we have just applied the change
1019 
1020  // indicate that the queue to which the change belongs needs be
1021  // updated
1024  } break;
1025 
1026  default:
1028  "Method applyChangeWithoutScoreUpdate of "
1029  << "GraphChangesSelector4DiGraph "
1030  << "does not handle yet graph change of type "
1031  << change.type());
1032  }
1033  }
1034 
1035 
1036  /// applies several changes at a time
1037  template < typename STRUCTURAL_CONSTRAINT,
1038  typename GRAPH_CHANGES_GENERATOR,
1039  template < typename >
1040  class ALLOC >
1044  // determine which changes in the illegal set are now legal
1046  for (auto iter = illegal_changes__.beginSafe();
1048  ++iter) {
1049  if (isChangeValid__(*iter)) {
1052  }
1053  }
1054 
1055  // update the scores that need be updated
1057  for (const auto& node: queues_to_update__) {
1059  }
1061 
1062  // put the previously illegal changes that are now legal into their queues
1063  for (const auto change_index: new_legal_changes) {
1067  change_index,
1068  std::numeric_limits< double >::min());
1069  }
1071  change_index,
1072  std::numeric_limits< double >::min());
1073 
1075  }
1076 
1077  // compute the scores that we need
1079 
1080  queues_valid__ = false;
1081  }
1082 
1083 
1084  /// returns the set of queues sorted by decreasing top priority
1085  template < typename STRUCTURAL_CONSTRAINT,
1086  typename GRAPH_CHANGES_GENERATOR,
1087  template < typename >
1088  class ALLOC >
1089  std::vector< std::pair< NodeId, double > >
1092  ALLOC >::nodesSortedByBestScore() const {
1093  std::vector< std::pair< NodeId, double > > result(node_queue__.size());
1094  for (std::size_t i = std::size_t(0); i < node_queue__.size(); ++i) {
1095  result[i].first = node_queue__[i];
1097  }
1098 
1099  std::sort(result.begin(),
1100  result.end(),
1101  [](const std::pair< NodeId, double >& a,
1102  const std::pair< NodeId, double >& b) -> bool {
1103  return a.second > b.second;
1104  });
1105 
1106  return result;
1107  }
1108 
1109 
1110  /// returns the set of queues sorted by decreasing top priority
1111  template < typename STRUCTURAL_CONSTRAINT,
1112  typename GRAPH_CHANGES_GENERATOR,
1113  template < typename >
1114  class ALLOC >
1115  std::vector< std::pair< NodeId, double > >
1118  ALLOC >::nodesUnsortedWithScore() const {
1119  std::vector< std::pair< NodeId, double > > result(node_queue__.size());
1120  for (std::size_t i = std::size_t(0); i < node_queue__.size(); ++i) {
1121  result[i].first = node_queue__[i];
1123  }
1124 
1125  return result;
1126  }
1127 
1128 
1129  /// returns the generator used by the selector
1130  template < typename STRUCTURAL_CONSTRAINT,
1131  typename GRAPH_CHANGES_GENERATOR,
1132  template < typename >
1133  class ALLOC >
1136  ALLOC >::GeneratorType&
1140  const noexcept {
1141  return *changes_generator__;
1142  }
1143 
1144 
1145  } /* namespace learning */
1146 
1147 } /* namespace gum */
1148 
1149 #endif /* DOXYGEN_SHOULD_SKIP_THIS */
INLINE void emplace(Args &&... args)
Definition: set_tpl.h:669
Database(const std::string &filename, const BayesNet< GUM_SCALAR > &bn, const std::vector< std::string > &missing_symbols)