aGrUM  0.20.3
a C++ library for (probabilistic) graphical models
graphChangesSelector4DiGraph_tpl.h
Go to the documentation of this file.
1 /**
2  *
3  * Copyright (c) 2005-2021 by 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, GRAPH_CHANGES_GENERATOR, ALLOC >::
45  _score_(score.clone()),
47  _parents_.resize(32);
49  }
50 
51  /// copy constructor
52  template < typename STRUCTURAL_CONSTRAINT,
53  typename GRAPH_CHANGES_GENERATOR,
54  template < typename >
55  class ALLOC >
59  ALLOC >& from) :
60  _score_(from._score_ != nullptr ? from._score_->clone() : nullptr),
67  // for debugging
69  }
70 
71  /// move constructor
72  template < typename STRUCTURAL_CONSTRAINT,
73  typename GRAPH_CHANGES_GENERATOR,
74  template < typename >
75  class ALLOC >
79  from) :
90  from._score_ = nullptr;
91  // for debugging
93  }
94 
95  /// destructor
96  template < typename STRUCTURAL_CONSTRAINT,
97  typename GRAPH_CHANGES_GENERATOR,
98  template < typename >
99  class ALLOC >
101 
105  if (_score_ != nullptr) {
109  }
111  }
112 
113  /// copy operator
114  template < typename STRUCTURAL_CONSTRAINT,
115  typename GRAPH_CHANGES_GENERATOR,
116  template < typename >
117  class ALLOC >
122  ALLOC >& from) {
123  if (this != &from) {
124  // remove the old score
125  if (_score_ != nullptr) {
129  _score_ = nullptr;
130  }
131 
132  if (from._score_ != nullptr) _score_ = from._score_->clone();
144  }
145 
146  return *this;
147  }
148 
149  /// move operator
150  template < typename STRUCTURAL_CONSTRAINT,
151  typename GRAPH_CHANGES_GENERATOR,
152  template < typename >
153  class ALLOC >
156  operator=(
158  from) {
159  if (this != &from) {
160  _score_ = from._score_;
161  from._score_ = nullptr;
162 
174  }
175 
176  return *this;
177  }
178 
179 
180  /// indicates whether a given change is valid or not
181  template < typename STRUCTURAL_CONSTRAINT,
182  typename GRAPH_CHANGES_GENERATOR,
183  template < typename >
184  class ALLOC >
185  INLINE bool
187  isChangeValid(const GraphChange& change) const {
189  }
190 
191 
192  /// indicates whether a given change is valid or not
193  template < typename STRUCTURAL_CONSTRAINT,
194  typename GRAPH_CHANGES_GENERATOR,
195  template < typename >
196  class ALLOC >
197  INLINE bool
199  _isChangeValid_(const std::size_t index) const {
200  return isChangeValid(_changes_[index]);
201  }
202 
203 
204  /// sets the graph from which scores are computed
205  template < typename STRUCTURAL_CONSTRAINT,
206  typename GRAPH_CHANGES_GENERATOR,
207  template < typename >
208  class ALLOC >
211  // fill the DAG with all the missing nodes
213  const auto& nodeId2Columns = _score_->nodeId2Columns();
214 
215  if (nodeId2Columns.empty()) {
217  for (NodeId i = NodeId(0); i < nb_nodes; ++i) {
218  if (!graph.existsNode(i)) { graph.addNodeWithId(i); }
219  }
220  } else {
221  for (auto iter = nodeId2Columns.cbegin(); iter != nodeId2Columns.cend(); ++iter) {
222  const NodeId id = iter.first();
223  if (!graph.existsNode(id)) { graph.addNodeWithId(id); }
224  }
225  }
226 
227 
228  // remove the node that do belong neither to the database
229  // nor to nodeId2Columns
230  if (nodeId2Columns.empty()) {
232  for (auto node: graph) {
233  if (node >= nb_nodes) { graph.eraseNode(node); }
234  }
235  } else {
236  for (auto node: graph) {
238  }
239  }
240 
241 
242  // _constraint_ is the constraint used by the selector to restrict the set
243  // of applicable changes. However, the generator may have a different set
244  // of constraints (e.g., a constraintSliceOrder needs be tested only by the
245  // generator because the changes returned by the generator will always
246  // statisfy this constraint, hence the selector needs not test this
247  // constraint). Therefore, if the selector and generator have different
248  // constraints, both should use method setGraph() to initialize
249  // themselves.
251  if (reinterpret_cast< STRUCTURAL_CONSTRAINT* >(&(_changes_generator_->constraint()))
252  != _constraint_) {
254  }
255 
257 
258 
259  // save the set of parents of each node (this will speed-up the
260  // computations of the scores)
261  const std::size_t nb_nodes = graph.size();
262  {
263  const std::vector< NodeId, ALLOC< NodeId > > empty_pars;
264  _parents_.clear();
266  for (const auto node: graph) {
269  if (!dag_parents.empty()) {
271  std::size_t j = std::size_t(0);
272  for (const auto par: dag_parents) {
273  node_parents[j] = par;
274  ++j;
275  }
276  }
277  }
278  }
279 
280  // assign a score to each node given its parents in the current graph
283  for (const auto node: graph) {
285  }
286 
287  // compute all the possible changes
288  _changes_.clear();
290  for (const auto& change: *_changes_generator_) {
291  _changes_ << change;
292  }
294 
295  // determine the changes that are illegal and prepare the computation of
296  // the scores of all the legal changes
298 
299  // set the _change_scores_ and _change_queue_per_node_ for legal changes
302  std::pair< double, double >(std::numeric_limits< double >::min(),
303  std::numeric_limits< double >::min()));
306  {
307  const PriorityQueue< std::size_t, double, std::greater< double > > empty_prio;
308  for (const auto node: graph) {
310  }
311  }
312 
313  for (std::size_t i = std::size_t(0); i < _changes_.size(); ++i) {
314  if (!_isChangeValid_(i)) {
316  } else {
317  const GraphChange& change = _changes_[i];
318 
319  switch (change.type()) {
321  auto& parents = _parents_[change.node2()];
323  const double delta
325  parents.pop_back();
326 
329  } break;
330 
332  auto& parents = _parents_[change.node2()];
333  for (auto& par: parents) {
334  if (par == change.node1()) {
335  par = *(parents.rbegin());
336  parents.pop_back();
337  break;
338  }
339  }
340  const double delta
343 
346  } break;
347 
349  // remove arc ( node1 -> node2 )
350  auto& parents2 = _parents_[change.node2()];
351  for (auto& par: parents2) {
352  if (par == change.node1()) {
353  par = *(parents2.rbegin());
354  parents2.pop_back();
355  break;
356  }
357  }
358 
359  const double delta2
362 
363  // add arc ( node2 -> node1 )
364  auto& parents1 = _parents_[change.node1()];
366  const double delta1
368  parents1.pop_back();
369 
372 
373  const double delta = delta1 + delta2;
376 
377  } break;
378 
379  default: {
381  "Method setGraph of GraphChangesSelector4DiGraph "
382  << "does not handle yet graph change of type " << change.type());
383  }
384  }
385  }
386  }
387 
388  // update the global queue
390  for (const auto node: graph) {
393  ? std::numeric_limits< double >::min()
395  }
396  _queues_valid_ = true;
398  }
399 
400 
401  /// put a change into the illegal set
402  template < typename STRUCTURAL_CONSTRAINT,
403  typename GRAPH_CHANGES_GENERATOR,
404  template < typename >
405  class ALLOC >
410  // remove the tail change from its priority queue
411  PriorityQueue< std::size_t, double, std::greater< double > >& queue1
414 
415  // recompute the top priority for the changes of the head
416  const double new_priority
417  = queue1.empty() ? std::numeric_limits< double >::min() : queue1.topPriority();
419  }
420 
421  // remove the head change from its priority queue
422  PriorityQueue< std::size_t, double, std::greater< double > >& queue2
425 
426  // recompute the top priority for the changes of the head
427  const double new_priority
428  = queue2.empty() ? std::numeric_limits< double >::min() : queue2.topPriority();
430 
431  // put the change into the illegal set
433  }
434 
435 
436  /// indicates whether the selector still contain graph changes
437  template < typename STRUCTURAL_CONSTRAINT,
438  typename GRAPH_CHANGES_GENERATOR,
439  template < typename >
440  class ALLOC >
442  empty() {
443  // put into the illegal change set all the top elements of the different
444  // queues that are not valid anymore
445  if (!_queues_valid_) {
446  for (auto& queue_pair: _change_queue_per_node_) {
447  auto& queue = queue_pair.second;
448  while (!queue.empty() && !_isChangeValid_(queue.top())) {
450  }
451  }
452  _queues_valid_ = true;
453  }
454 
455  return _node_queue_.topPriority() == std::numeric_limits< double >::min();
456  }
457 
458 
459  /// indicates whether the selector still contain graph changes
460  /// in the ith queue
461  template < typename STRUCTURAL_CONSTRAINT,
462  typename GRAPH_CHANGES_GENERATOR,
463  template < typename >
464  class ALLOC >
465  bool
467  const NodeId node) {
468  // put into the illegal change set all the top elements of the different
469  // queues that are not valid anymore
470  if (!_queues_valid_) {
471  for (auto& queue_pair: _change_queue_per_node_) {
472  auto& queue = queue_pair.second;
473  while (!queue.empty() && !_isChangeValid_(queue.top())) {
475  }
476  }
477  _queues_valid_ = true;
478  }
479 
481  }
482 
483 
484  /// returns the best graph change to examine
485  template < typename STRUCTURAL_CONSTRAINT,
486  typename GRAPH_CHANGES_GENERATOR,
487  template < typename >
488  class ALLOC >
491  ALLOC >::bestChange() {
492  if (!empty())
494  else
495  GUM_ERROR(NotFound, "there exists no graph change applicable")
496  }
497 
498 
499  /// returns the best graph change to examine in the ith queue
500  template < typename STRUCTURAL_CONSTRAINT,
501  typename GRAPH_CHANGES_GENERATOR,
502  template < typename >
503  class ALLOC >
506  ALLOC >::bestChange(const NodeId node) {
507  if (!empty(node))
509  else
510  GUM_ERROR(NotFound, "there exists no graph change applicable")
511  }
512 
513 
514  /// return the score of the best graph change
515  template < typename STRUCTURAL_CONSTRAINT,
516  typename GRAPH_CHANGES_GENERATOR,
517  template < typename >
518  class ALLOC >
521  ALLOC >::bestScore() {
522  if (!empty())
524  else
525  GUM_ERROR(NotFound, "there exists no graph change applicable")
526  }
527 
528 
529  /// return the score of the best graph change in the ith queue
530  template < typename STRUCTURAL_CONSTRAINT,
531  typename GRAPH_CHANGES_GENERATOR,
532  template < typename >
533  class ALLOC >
536  ALLOC >::bestScore(const NodeId node) {
537  if (!empty(node))
539  else
540  GUM_ERROR(NotFound, "there exists no graph change applicable")
541  }
542 
543 
544  /// remove the now legal changes from the illegal set
545  template < typename STRUCTURAL_CONSTRAINT,
546  typename GRAPH_CHANGES_GENERATOR,
547  template < typename >
548  class ALLOC >
552  if (_isChangeValid_(*iter)) {
553  const GraphChange& change = _changes_[*iter];
556  std::numeric_limits< double >::min());
557  }
559  std::numeric_limits< double >::min());
560 
563  }
564  }
565  }
566 
567 
568  /// finds the changes that are affected by a given node modification
569  template < typename STRUCTURAL_CONSTRAINT,
570  typename GRAPH_CHANGES_GENERATOR,
571  template < typename >
572  class ALLOC >
575  const NodeId target_node) {
576  const HashTable< std::size_t, Size >& changes
578  for (auto iter = changes.cbeginSafe(); iter != changes.cendSafe(); ++iter) {
580  if (_isChangeValid_(iter.key())) {
582  } else {
584  }
585  }
586  }
587  }
588 
589 
590  /// perform the necessary updates of the scores
591  template < typename STRUCTURAL_CONSTRAINT,
592  typename GRAPH_CHANGES_GENERATOR,
593  template < typename >
594  class ALLOC >
598 
599  for (const auto change_index: changes_to_recompute) {
601 
602  switch (change.type()) {
604  // add the arc
605  auto& parents = _parents_[change.node2()];
607  const double delta
609  parents.pop_back();
610 
611  // update the score
613 
614  // update the head queue
616  // indicate which queue was modified
618  } break;
619 
621  // remove the arc
622  auto& parents = _parents_[change.node2()];
623  for (auto& par: parents) {
624  if (par == change.node1()) {
625  par = *(parents.rbegin());
626  parents.pop_back();
627  break;
628  }
629  }
630  const double delta
633 
634  // update the score
636 
637  // update the head queue
639  // indicate which queue was modified
641  } break;
642 
644  // remove arc ( node1 -> node2 )
645  auto& parents2 = _parents_[change.node2()];
646  for (auto& par: parents2) {
647  if (par == change.node1()) {
648  par = *(parents2.rbegin());
649  parents2.pop_back();
650  break;
651  }
652  }
653 
654  const double delta2
657 
658  // add arc ( node2 -> node1 )
659  auto& parents1 = _parents_[change.node1()];
661  const double delta1
663  parents1.pop_back();
664 
665  // update the scores
668 
669  // update the queues
670  const double delta = delta1 + delta2;
673 
674  // indicate which queues were modified
677  } break;
678 
679  default: {
681  "Method _updateScores_ of GraphChangesSelector4DiGraph "
682  << "does not handle yet graph change of type " << change.type());
683  }
684  }
685  }
686 
687  // update the node queue
688  for (const auto node: modified_nodes) {
691  ? std::numeric_limits< double >::min()
693  }
694  }
695 
696 
697  /// get from the graph change generator a new set of changes
698  template < typename STRUCTURAL_CONSTRAINT,
699  typename GRAPH_CHANGES_GENERATOR,
700  template < typename >
701  class ALLOC >
703  _getNewChanges_() {
704  // ask the graph change generator for all its available changes
705  for (const auto& change: *_changes_generator_) {
706  // check that the change does not already exist
707  if (!_changes_.exists(change)) {
708  // add the new change. To make the addition simple, we put the new
709  // change into the illegal changes set. Afterwards, the applyChange
710  // function will put the legal changes again into the queues
712  _changes_ << change;
714  std::pair< double, double >(std::numeric_limits< double >::min(),
715  std::numeric_limits< double >::min()));
716  }
717  }
718 
719  // indicate to the generator that we have finished retrieving its changes
721  }
722 
723 
724  /// indicate to the selector that its best score has been applied
725  template < typename STRUCTURAL_CONSTRAINT,
726  typename GRAPH_CHANGES_GENERATOR,
727  template < typename >
728  class ALLOC >
730  applyChange(const GraphChange& change) {
731  // first, we get the index of the change
733 
734  // perform the change
736  switch (change.type()) {
738  // update the current score
741 
742  // inform the constraint that the graph has been modified
743  _constraint_->modifyGraph(static_cast< const ArcAddition& >(change));
744  if (reinterpret_cast< STRUCTURAL_CONSTRAINT* >(&(_changes_generator_->constraint()))
745  != _constraint_) {
747  static_cast< const ArcAddition& >(change));
748  }
749 
750  // get new possible changes from the graph change generator
751  // warning: put the next 3 lines before calling _illegal2LegalChanges_
752  _changes_generator_->modifyGraph(static_cast< const ArcAddition& >(change));
753  _getNewChanges_();
754 
755  // check whether some illegal changes can be put into the valid queues
760  } break;
761 
763  // update the current score
765  auto& parents = _parents_[change.node2()];
766  for (auto& par: parents) {
767  if (par == change.node1()) {
768  par = *(parents.rbegin());
769  parents.pop_back();
770  break;
771  }
772  }
773 
774  // inform the constraint that the graph has been modified
775  _constraint_->modifyGraph(static_cast< const ArcDeletion& >(change));
776  if (reinterpret_cast< STRUCTURAL_CONSTRAINT* >(&(_changes_generator_->constraint()))
777  != _constraint_) {
779  static_cast< const ArcDeletion& >(change));
780  }
781 
782  // get new possible changes from the graph change generator
783  // warning: put the next 3 lines before calling _illegal2LegalChanges_
784  _changes_generator_->modifyGraph(static_cast< const ArcDeletion& >(change));
785  _getNewChanges_();
786 
787  // check whether some illegal changes can be put into the valid queues
792  } break;
793 
795  // update the current score
799  auto& parents = _parents_[change.node2()];
800  for (auto& par: parents) {
801  if (par == change.node1()) {
802  par = *(parents.rbegin());
803  parents.pop_back();
804  break;
805  }
806  }
807 
808  // inform the constraint that the graph has been modified
809  _constraint_->modifyGraph(static_cast< const ArcReversal& >(change));
810  if (reinterpret_cast< STRUCTURAL_CONSTRAINT* >(&(_changes_generator_->constraint()))
811  != _constraint_) {
813  static_cast< const ArcReversal& >(change));
814  }
815 
816  // get new possible changes from the graph change generator
817  // warning: put the next 3 lines before calling _illegal2LegalChanges_
818  _changes_generator_->modifyGraph(static_cast< const ArcReversal& >(change));
819  _getNewChanges_();
820 
821  // check whether some illegal changes can be put into the valid queues
827  } break;
828 
829  default:
831  "Method applyChange of GraphChangesSelector4DiGraph "
832  << "does not handle yet graph change of type " << change.type());
833  }
834 
835  _queues_valid_ = false;
836  }
837 
838 
839  /// applies several changes at a time
840  template < typename STRUCTURAL_CONSTRAINT,
841  typename GRAPH_CHANGES_GENERATOR,
842  template < typename >
843  class ALLOC >
846  // first, we get the index of the change
848 
849  // perform the change
850  switch (change.type()) {
852  // update the current score
855 
856  // inform the constraint that the graph has been modified
857  _constraint_->modifyGraph(static_cast< const ArcAddition& >(change));
858  if (reinterpret_cast< STRUCTURAL_CONSTRAINT* >(&(_changes_generator_->constraint()))
859  != _constraint_) {
861  static_cast< const ArcAddition& >(change));
862  }
863 
864  // get new possible changes from the graph change generator
865  // warning: put the next 3 lines before calling _illegal2LegalChanges_
866  _changes_generator_->modifyGraph(static_cast< const ArcAddition& >(change));
867  _getNewChanges_();
868 
869  // indicate that we have just applied the change
871 
872  // indicate that the queue to which the change belongs needs be
873  // updated
875  } break;
876 
878  // update the current score
880  auto& parents = _parents_[change.node2()];
881  for (auto& par: parents) {
882  if (par == change.node1()) {
883  par = *(parents.rbegin());
884  parents.pop_back();
885  break;
886  }
887  }
888 
889  // inform the constraint that the graph has been modified
890  _constraint_->modifyGraph(static_cast< const ArcDeletion& >(change));
891  if (reinterpret_cast< STRUCTURAL_CONSTRAINT* >(&(_changes_generator_->constraint()))
892  != _constraint_) {
894  static_cast< const ArcDeletion& >(change));
895  }
896 
897  // get new possible changes from the graph change generator
898  // warning: put the next 3 lines before calling _illegal2LegalChanges_
899  _changes_generator_->modifyGraph(static_cast< const ArcDeletion& >(change));
900  _getNewChanges_();
901 
902  // indicate that we have just applied the change
904 
905  // indicate that the queue to which the change belongs needs be
906  // updated
908  } break;
909 
911  // update the current score
915  auto& parents = _parents_[change.node2()];
916  for (auto& par: parents) {
917  if (par == change.node1()) {
918  par = *(parents.rbegin());
919  parents.pop_back();
920  break;
921  }
922  }
923 
924  // inform the constraint that the graph has been modified
925  _constraint_->modifyGraph(static_cast< const ArcReversal& >(change));
926  if (reinterpret_cast< STRUCTURAL_CONSTRAINT* >(&(_changes_generator_->constraint()))
927  != _constraint_) {
929  static_cast< const ArcReversal& >(change));
930  }
931 
932  // get new possible changes from the graph change generator
933  // warning: put the next 3 lines before calling _illegal2LegalChanges_
934  _changes_generator_->modifyGraph(static_cast< const ArcReversal& >(change));
935  _getNewChanges_();
936 
937  // indicate that we have just applied the change
939 
940  // indicate that the queue to which the change belongs needs be
941  // updated
944  } break;
945 
946  default:
948  "Method applyChangeWithoutScoreUpdate of "
949  << "GraphChangesSelector4DiGraph "
950  << "does not handle yet graph change of type " << change.type());
951  }
952  }
953 
954 
955  /// applies several changes at a time
956  template < typename STRUCTURAL_CONSTRAINT,
957  typename GRAPH_CHANGES_GENERATOR,
958  template < typename >
959  class ALLOC >
962  // determine which changes in the illegal set are now legal
965  if (_isChangeValid_(*iter)) {
968  }
969  }
970 
971  // update the scores that need be updated
973  for (const auto& node: _queues_to_update_) {
975  }
977 
978  // put the previously illegal changes that are now legal into their queues
979  for (const auto change_index: new_legal_changes) {
983  std::numeric_limits< double >::min());
984  }
986  std::numeric_limits< double >::min());
987 
989  }
990 
991  // compute the scores that we need
993 
994  _queues_valid_ = false;
995  }
996 
997 
998  /// returns the set of queues sorted by decreasing top priority
999  template < typename STRUCTURAL_CONSTRAINT,
1000  typename GRAPH_CHANGES_GENERATOR,
1001  template < typename >
1002  class ALLOC >
1003  std::vector< std::pair< NodeId, double > >
1005  nodesSortedByBestScore() const {
1006  std::vector< std::pair< NodeId, double > > result(_node_queue_.size());
1007  for (std::size_t i = std::size_t(0); i < _node_queue_.size(); ++i) {
1008  result[i].first = _node_queue_[i];
1010  }
1011 
1012  std::sort(result.begin(),
1013  result.end(),
1014  [](const std::pair< NodeId, double >& a,
1015  const std::pair< NodeId, double >& b) -> bool { return a.second > b.second; });
1016 
1017  return result;
1018  }
1019 
1020 
1021  /// returns the set of queues sorted by decreasing top priority
1022  template < typename STRUCTURAL_CONSTRAINT,
1023  typename GRAPH_CHANGES_GENERATOR,
1024  template < typename >
1025  class ALLOC >
1026  std::vector< std::pair< NodeId, double > >
1028  nodesUnsortedWithScore() const {
1029  std::vector< std::pair< NodeId, double > > result(_node_queue_.size());
1030  for (std::size_t i = std::size_t(0); i < _node_queue_.size(); ++i) {
1031  result[i].first = _node_queue_[i];
1033  }
1034 
1035  return result;
1036  }
1037 
1038 
1039  /// returns the generator used by the selector
1040  template < typename STRUCTURAL_CONSTRAINT,
1041  typename GRAPH_CHANGES_GENERATOR,
1042  template < typename >
1043  class ALLOC >
1046  ALLOC >::GeneratorType&
1048  graphChangeGenerator() const noexcept {
1049  return *_changes_generator_;
1050  }
1051 
1052 
1053  } /* namespace learning */
1054 
1055 } /* namespace gum */
1056 
1057 #endif /* DOXYGEN_SHOULD_SKIP_THIS */
INLINE void emplace(Args &&... args)
Definition: set_tpl.h:643
Database(const std::string &filename, const BayesNet< GUM_SCALAR > &bn, const std::vector< std::string > &missing_symbols)