Parasol Planning Library (PPL)
CCTracker.h
Go to the documentation of this file.
1 #ifndef PMPL_CC_TRACKER_H_
2 #define PMPL_CC_TRACKER_H_
3 
5 
6 #include "nonstd/call_on_destruct.h"
7 #include "nonstd/io.h"
8 
9 #include <cstddef>
10 #include <limits>
11 #include <memory>
12 #include <unordered_map>
13 #include <unordered_set>
14 
15 
35 template <typename RoadmapType>
36 class CCTracker final {
37 
38  public:
39 
42 
43  typedef typename RoadmapType::VID VID;
44  typedef typename RoadmapType::vertex_iterator vertex_iterator;
45  typedef typename RoadmapType::adj_edge_iterator edge_iterator;
46  typedef typename RoadmapType::VertexSet VertexSet;
47 
51 
53  typedef std::unordered_set<VertexSet*> ComponentSet;
54 
58  typedef std::function<void(const CCTrackerType* const,
59  const VertexSet* const)>
61 
65  typedef std::function<void(const CCTrackerType* const,
66  const VertexSet* const)>
68 
74  typedef std::function<void(const CCTrackerType* const,
75  const VertexSet* const, const VertexSet* const)>
77 
83  typedef std::function<void(const CCTrackerType* const,
84  const VertexSet* const, const VertexSet* const)>
86 
90 
93  CCTracker(RoadmapType* const _r);
94 
95  ~CCTracker();
96 
106 
107  CCTracker(const CCTracker&);
108  CCTracker(CCTracker&&);
109 
110  CCTracker& operator=(const CCTracker&);
112 
116 
118  void SetRoadmap(RoadmapType* const _r) noexcept;
119 
121  void SetStatClass(StatClass* const _stats) const noexcept;
122 
127 
129  size_t GetNumCCs() const noexcept;
130 
134  const VertexSet* GetCC(const VID _vid) const noexcept;
135 
138  VertexSet GetRepresentatives() const noexcept;
139 
144  bool InSameCC(const VID _vid1, const VID _vid2) const noexcept;
145 
150 
151  typename ComponentSet::const_iterator begin() const noexcept;
152  typename ComponentSet::const_iterator end() const noexcept;
153 
160 
161  void InstallCreatedHook(const std::string& _label, const CreatedHook& _h);
162  void InstallDeletedHook(const std::string& _label, const DeletedHook& _h);
163  void InstallBrokenHook(const std::string& _label, const BrokenHook& _h);
164  void InstallMergedHook(const std::string& _label, const MergedHook& _h);
165 
171 
172  void Disable();
173  void Enable();
174 
178 
182  void Print(std::ostream& _os, const std::string& _indent = {}) const;
183 
189 
192  void AddVertex(const vertex_iterator _vi) noexcept;
193 
196  void DeleteVertex(const vertex_iterator _vi) noexcept;
197 
200  void AddEdge(const edge_iterator _ei) noexcept;
201 
204  void DeleteEdge(const edge_iterator _ei) noexcept;
205 
207 
208  private:
209 
212 
216  const VertexSet* FindCC(const VID _vid) const noexcept;
217 
220  void RecomputeCCs() noexcept;
221 
223  VertexSet* FindCC(const VID _vid) noexcept;
224 
231  VertexSet BFS(const VID _start, VertexSet _goals = {}) const noexcept;
232 
236 
237  RoadmapType* m_roadmap{nullptr};
238  ComponentSet m_components;
239  std::unordered_map<VID, VertexSet*> m_vidToCC;
240 
241  // Clock helpers
242  mutable std::function<void(const std::string&)> m_clockStart;
243  mutable std::function<void(const std::string&)> m_clockStop;
244 
245  // Hooks
246  std::unordered_map<std::string, CreatedHook> m_createdHooks;
247  std::unordered_map<std::string, DeletedHook> m_deletedHooks;
248  std::unordered_map<std::string, BrokenHook> m_brokenHooks;
249  std::unordered_map<std::string, MergedHook> m_mergedHooks;
250  bool m_enable{true};
251 
252  static constexpr const bool s_debug = false;
253 
255 };
256 
257 /*------------------------------- Construction -------------------------------*/
258 
259 template <typename RoadmapType>
261 CCTracker(RoadmapType* const _r) : m_roadmap(_r) {
262  RecomputeCCs();
263 
264  // Initialize the clock start/stoppers.
265  SetStatClass(nullptr);
266 }
267 
268 
269 template <typename RoadmapType>
271 ~CCTracker() {
272  for(VertexSet* const cc : m_components)
273  delete cc;
274 }
275 
276 /*------------------------------ Move and Copy -------------------------------*/
277 
278 template <typename RoadmapType>
280 CCTracker(const CCTracker& _other) {
281  *this = _other;
282 }
283 
284 
285 template <typename RoadmapType>
287 CCTracker(CCTracker&& _other) {
288  *this = std::move(_other);
289 }
290 
291 
292 template <typename RoadmapType>
295 operator=(const CCTracker& _other) {
296  m_roadmap = _other.m_roadmap;
297  m_clockStart = _other.m_clockStart;
298  m_clockStop = _other.m_clockStop;
299 
300  // Copy the components. This must be done manually or we'll be sharing
301  // pointed-to VertexSets with _other.
302  m_components.clear();
303  m_components.reserve(_other.m_components.size());
304  m_vidToCC.clear();
305  m_vidToCC.reserve(_other.m_vidToCC.size());
306  for(const VertexSet* const otherCC : _other.m_components) {
307  VertexSet* const myCC = new VertexSet;
308  m_components.insert(myCC);
309  for(const VID vid : *otherCC) {
310  myCC->insert(vid);
311  m_vidToCC.emplace(vid, myCC);
312  }
313  }
314 
315  return *this;
316 }
317 
318 
319 template <typename RoadmapType>
322 operator=(CCTracker&& _other) {
323  m_roadmap = _other.m_roadmap;
324  m_components = std::move(_other.m_components);
325  // Clear _other's components to ensure they aren't deleted with it.
326  _other.m_components.clear();
327  m_vidToCC = std::move(_other.m_vidToCC);
328  m_clockStart = std::move(_other.m_clockStart);
329  m_clockStop = std::move(_other.m_clockStop);
330 
331  return *this;
332 }
333 
334 /*--------------------------------- Queries ----------------------------------*/
335 
336 template <typename RoadmapType>
337 inline
338 void
340 SetRoadmap(RoadmapType* const _r) noexcept {
341  m_roadmap = _r;
342 }
343 
344 
345 template <typename RoadmapType>
346 inline
347 void
349 SetStatClass(StatClass* const _stats) const noexcept {
350  // If we got a null pointer, set the clock stop/start functions to dummies.
351  if(!_stats) {
352  m_clockStart = [](const std::string&){};
353  m_clockStop = [](const std::string&){};
354  return;
355  }
356 
357  m_clockStart = [_stats](const std::string& _label) {
358  _stats->StartClock(_label);
359  };
360  m_clockStop = [_stats](const std::string& _label) {
361  _stats->StopClock(_label);
362  };
363 }
364 
365 /*--------------------------------- Queries ----------------------------------*/
366 
367 template <typename RoadmapType>
368 inline
369 size_t
371 GetNumCCs() const noexcept {
372  return m_components.size();
373 }
374 
375 
376 template <typename RoadmapType>
377 const typename CCTracker<RoadmapType>::VertexSet*
379 GetCC(const VID _vid) const noexcept {
380  return FindCC(_vid);
381 }
382 
383 
384 
385 template <typename RoadmapType>
388 GetRepresentatives() const noexcept {
389  VertexSet representatives;
390  for(const VertexSet* const cc : m_components)
391  representatives.insert(*cc->begin());
392  return representatives;
393 }
394 
395 
396 template <typename RoadmapType>
397 inline
398 bool
400 InSameCC(const VID _vid1, const VID _vid2) const noexcept {
401  return FindCC(_vid1) == FindCC(_vid2);
402 }
403 
404 /*-------------------------------- Iteration ---------------------------------*/
405 
406 template <typename RoadmapType>
407 inline
410 begin() const noexcept {
411  return m_components.begin();
412 }
413 
414 
415 template <typename RoadmapType>
416 inline
419 end() const noexcept {
420  return m_components.end();
421 }
422 
423 /*-------------------------------- Event Hooks -------------------------------*/
424 
425 template <template <typename...> class MapType, typename ValueType,
426  typename... Rest>
427 void
428 MapUniqueInsert(MapType<std::string, ValueType, Rest...>& _map,
429  const std::string& _label, const ValueType& _value) noexcept {
430  if(_map.count(_label))
431  throw RunTimeException(WHERE) << "Map already has an element labeled '"
432  << _label << "'.";
433  _map.emplace(_label, _value);
434 }
435 
436 
437 template <typename RoadmapType>
438 void
440 InstallCreatedHook(const std::string& _label, const CreatedHook& _h) {
441  MapUniqueInsert(m_createdHooks, _label, _h);
442 }
443 
444 
445 template <typename RoadmapType>
446 void
448 InstallDeletedHook(const std::string& _label, const DeletedHook& _h) {
449  MapUniqueInsert(m_deletedHooks, _label, _h);
450 }
451 
452 
453 template <typename RoadmapType>
454 void
456 InstallBrokenHook(const std::string& _label, const BrokenHook& _h) {
457  MapUniqueInsert(m_brokenHooks, _label, _h);
458 }
459 
460 
461 template <typename RoadmapType>
462 void
464 InstallMergedHook(const std::string& _label, const MergedHook& _h) {
465  MapUniqueInsert(m_mergedHooks, _label, _h);
466 }
467 
468 
469 template <typename RoadmapType>
470 void
472 Disable() {
473  m_enable = false;
474 }
475 
476 
477 template <typename RoadmapType>
478 void
480 Enable() {
481  m_enable = true;
482 }
483 
484 /*----------------------------------- Debug ----------------------------------*/
485 
486 template <typename RoadmapType>
487 void
489 Print(std::ostream& _os, const std::string& _indent) const {
490  _os << _indent << "There are " << GetNumCCs() << " CCs." << std::endl;
491  for(const VertexSet* const cc : m_components)
492  _os << _indent << " CC " << cc << ": " << *cc << std::endl;
493 }
494 
495 /*--------------------------------- Updates ----------------------------------*/
496 
497 template <typename RoadmapType>
498 void
500 AddVertex(const vertex_iterator _vi) noexcept {
501  if(!m_enable)
502  return;
503 
504  m_clockStart("CCTracker::AddVertex");
505  nonstd::call_on_destruct stopper(m_clockStop, "CCTracker::AddVertex");
506 
507  // Ensure we haven't already added this vertex.
508  const VID vid = _vi->descriptor();
509  if(m_vidToCC.count(vid))
510  throw RunTimeException(WHERE) << "Vertex " << vid << " is already in a CC.";
511 
512  // Create a new CC for this vertex.
513  VertexSet* const cc = new VertexSet{vid};
514  m_components.emplace(cc);
515  m_vidToCC.emplace(vid, cc);
516 
517  if(s_debug)
518  std::cout << "Added vertex " << vid << " to new CC " << cc << ". "
519  << "There are now " << GetNumCCs() << " CCs."
520  << std::endl;
521 
522  for(const auto& hook : m_createdHooks)
523  hook.second(this, cc);
524 }
525 
526 
527 template <typename RoadmapType>
528 void
530 DeleteVertex(const vertex_iterator _vi) noexcept {
531  if(!m_enable)
532  return;
533 
534  m_clockStart("CCTracker::DeleteVertex");
535  nonstd::call_on_destruct stopper(m_clockStop, "CCTracker::DeleteVertex");
536 
537  // We will assume that the edges are already deleted, as will normally be done
538  // by the roadmap. Enforce that assumption now.
539  if(_vi->begin() != _vi->end())
540  throw RunTimeException(WHERE) << "Edges should be deleted prior to deleting "
541  << "a vertex.";
542 
543  // Locate the CC.
544  const VID vid = _vi->descriptor();
545  VertexSet* const cc = FindCC(vid);
546 
547  // Assert that this is a CC of one vertex.
548  if(cc->size() != 1)
549  throw RunTimeException(WHERE) << "Found CC for vertex " << vid
550  << " with " << cc->size()
551  << " != 1 vertices.";
552  // Assert that the one vertex is the one being deleted.
553  if(!cc->count(vid))
554  throw RunTimeException(WHERE) << "CC for vertex " << vid
555  << " is missing the query VID. Contents: "
556  << *cc;
557 
558  for(const auto& hook : m_deletedHooks)
559  hook.second(this, cc);
560 
561  // Remove the CC.
562  m_components.erase(cc);
563  delete cc;
564  m_vidToCC.erase(vid);
565 
566  if(s_debug) {
567  std::cout << "Deleted vertex " << vid << " and CC " << cc << "."
568  << std::endl;
569  Print(std::cout, " ");
570  }
571 
572  return;
573 }
574 
575 
576 template <typename RoadmapType>
577 void
579 AddEdge(const edge_iterator _ei) noexcept {
580  if(!m_enable)
581  return;
582 
585  m_clockStart("CCTracker::AddEdge");
586  nonstd::call_on_destruct stopper(m_clockStop, "CCTracker::AddEdge");
587 
588  // Adding an edge should always merge two CCs, which may already be the same.
589  const VID sourceVID = (*_ei).source(),
590  targetVID = (*_ei).target();
591  VertexSet* const sourceCC = FindCC(sourceVID),
592  * const targetCC = FindCC(targetVID);
593 
594  if(s_debug)
595  std::cout << "Added edge (" << sourceVID << ", " << targetVID << ")."
596  << std::endl;
597 
598  // If the two are already the same, there is nothing to do.
599  if(sourceCC == targetCC) {
600  if(s_debug)
601  std::cout << " Vertices are already in the same CC."
602  << std::endl;
603  return;
604  }
605 
606  // Otherwise determine which CC is larger (by number of vertices).
607  VertexSet* smallCC, * largeCC;
608  if(sourceCC->size() < targetCC->size()) {
609  smallCC = sourceCC;
610  largeCC = targetCC;
611  }
612  else {
613  smallCC = targetCC;
614  largeCC = sourceCC;
615  }
616 
617  // Call hooks before merging.
618  for(const auto& hook : m_mergedHooks)
619  hook.second(this, largeCC, smallCC);
620 
621  // Merge the smaller CC into the larger one.
622  std::copy(smallCC->begin(), smallCC->end(),
623  std::inserter(*largeCC, largeCC->begin()));
624 
625  // Update the CCs for the vertices which used to be in the small set.
626  for(const VID vid : *smallCC)
627  m_vidToCC[vid] = largeCC;
628 
629  // Remove the small CC.
630  m_components.erase(smallCC);
631  delete smallCC;
632 
633  if(s_debug) {
634  std::cout << " Merged CC " << smallCC << " into " << largeCC << "."
635  << std::endl;
636  Print(std::cout, " ");
637  }
638 }
639 
640 
641 template <typename RoadmapType>
642 void
644 DeleteEdge(const edge_iterator _ei) noexcept {
645  if(!m_enable)
646  return;
647 
648  m_clockStart("CCTracker::DeleteEdge");
649  nonstd::call_on_destruct stopper(m_clockStop, "CCTracker::DeleteEdge");
650 
651  // Can't use -> here because there's no const-qualified version in STAPL.
652  const VID source = (*_ei).source(),
653  target = (*_ei).target();
654 
655  if(s_debug)
656  std::cout << "Deleted edge (" << source << ", " << target << ")."
657  << std::endl;
658 
659  // Assert that both cfgs started out in the same CC.
660  VertexSet* const sourceCC = FindCC(source),
661  * const targetCC = FindCC(target);
662  if(sourceCC != targetCC) {
663  const VertexSet discovered = BFS(0);
664  std::cout << " Roadmap has " << m_roadmap->Size() << " vertices.\n"
665  << " CC from root has " << discovered.size() << " vertices:\n"
666  << "\t" << discovered
667  << std::endl;
668  throw RunTimeException(WHERE) << "Edge vertices are not in the same CC.";
669  }
670 
671  // Check if the reverse edge exists. If so, this is an undirected graph and
672  // the (weak) CC is unchanged.
673  {
674  edge_iterator ei;
675  const bool exists = m_roadmap->GetEdge(target, source, ei);
676  if(exists) {
677  if(s_debug)
678  std::cout << " Vertices are still connected (reverse edge exists)."
679  << std::endl;
680  return;
681  }
682  }
683 
684  // This is either a directed tree with a now broken CC or an undirected graph
685  // with a maybe broken CC. Run a BFS from target and stop early if we find the
686  // source (which indicates undirected graph).
687  VertexSet discovered = BFS(target, {source});
688 
689  // If we found the source vertex, the vertices are still connected.
690  if(discovered.count(source)) {
691  if(s_debug)
692  std::cout << " Vertices are still connected."
693  << std::endl;
694  return;
695  }
696 
697  // Otherwise they are now disconnected. Move the discovered vertices into
698  // their own new CC.
699  VertexSet* const newCC = new VertexSet;
700  m_components.insert(newCC);
701  for(const VID vid : discovered) {
702  sourceCC->erase(vid);
703  newCC->insert(vid);
704  m_vidToCC[vid] = newCC;
705  }
706 
707  if(s_debug) {
708  std::cout << " Split off new CC " << newCC << " from "
709  << m_vidToCC.at(source) << "."
710  << std::endl;
711  Print(std::cout, " ");
712  }
713 
714  for(const auto& hook : m_brokenHooks)
715  hook.second(this, sourceCC, newCC);
716 }
717 
718 /*--------------------------------- Helpers ----------------------------------*/
719 
720 template <typename RoadmapType>
721 const typename CCTracker<RoadmapType>::VertexSet*
723 FindCC(const VID _vid) const noexcept {
724  // Assert that we have the CC id for this vertex.
725  auto iter = m_vidToCC.find(_vid);
726  if(iter == m_vidToCC.end())
727  throw RunTimeException(WHERE) << "No CC found for vertex " << _vid << ".";
728 
729  // Assert that the component is present in the component set.
730  VertexSet* const cc = iter->second;
731  if(!m_components.count(cc))
732  throw RunTimeException(WHERE) << "No CC " << cc << " found for vertex "
733  << _vid << ".";
734  return cc;
735 }
736 
737 
738 template <typename RoadmapType>
739 inline
742 FindCC(const VID _vid) noexcept {
743  // Use const version and cast back (since we know we have a mutable handle
744  // this is OK).
745  const VertexSet* const cc = const_cast<const CCTracker<RoadmapType>*>(this)->
746  FindCC(_vid);
747  return const_cast<VertexSet*>(cc);
748 }
749 
750 
751 template <typename RoadmapType>
752 void
754 RecomputeCCs() noexcept {
759 
760  // Clear any previous CC info.
761  m_components.clear();
762  m_vidToCC.clear();
763 
764  // Build a list of all vertices.
765  std::set<VID> unmappedDescriptors;
766  for(auto iter = m_roadmap->begin(); iter != m_roadmap->end(); ++iter)
767  unmappedDescriptors.insert(iter->descriptor());
768 
769  // Find CCs while unmapped vertices remain.
770  while(!unmappedDescriptors.empty()) {
771  // Get the next descriptor and compute its CC.
772  const VID current = *unmappedDescriptors.begin();
773  VertexSet* const cc = new VertexSet(BFS(current));
774  m_components.insert(cc);
775 
776  // Remove the discovered CC from the unmapped vertex set.
777  for(const VID vid : *cc) {
778  unmappedDescriptors.erase(vid);
779  m_vidToCC.emplace(vid, cc);
780  }
781  }
782 }
783 
784 
785 template <typename RoadmapType>
788 BFS(const VID _start, VertexSet _goals) const noexcept {
789  // If we received a set of goal VIDs, we are only interested in determining
790  // whether the _start is connected to all of them. In that case, we will stop
791  // early upon discovering this information.
792  const bool testGoals = !_goals.empty();
793 
794  // Set up a discovered list and queue for BFS.
795  VertexSet discovered{_start};
796  std::queue<VID> queue;
797  queue.push(_start);
798 
799  // Run BFS.
800  while(!queue.empty()) {
801  // Get the next node in the queue.
802  const VID current = queue.front();
803  queue.pop();
804 
805  // Enqueue undiscovered children.
806  auto vi = m_roadmap->find_vertex(current);
807  for(auto ei = vi->begin(); ei != vi->end(); ++ei) {
808  // Get the child VID.
809  const VID child = ei->target();
810 
811  // Skip if the child is discovered.
812  if(discovered.count(child))
813  continue;
814  discovered.insert(child);
815 
816  // Check if this child is a goal.
817  if(testGoals and _goals.count(child)) {
818  // The child is a goal, remove it from the goals list.
819  _goals.erase(child);
820 
821  // If we've visited all goals, we're done.
822  if(_goals.empty())
823  return discovered;
824  }
825 
826  // Enqueue the child.
827  queue.push(child);
828  }
829  }
830 
831  // The set of discovered nodes comprise the CC.
832  return discovered;
833 }
834 
835 /*----------------------------------------------------------------------------*/
836 
837 #endif
void MapUniqueInsert(MapType< std::string, ValueType, Rest... > &_map, const std::string &_label, const ValueType &_value) noexcept
Definition: CCTracker.h:428
#define WHERE
Macro for retrieving info about file, function, and line number.
Definition: RuntimeUtils.h:32
Definition: CCTracker.h:36
size_t GetNumCCs() const noexcept
Get the number of connected components in the graph.
Definition: CCTracker.h:371
ComponentSet::const_iterator end() const noexcept
Definition: CCTracker.h:419
void SetRoadmap(RoadmapType *const _r) noexcept
Set the roadmap object.
Definition: CCTracker.h:340
void InstallBrokenHook(const std::string &_label, const BrokenHook &_h)
Definition: CCTracker.h:456
void AddEdge(const edge_iterator _ei) noexcept
Definition: CCTracker.h:579
bool InSameCC(const VID _vid1, const VID _vid2) const noexcept
Definition: CCTracker.h:400
void InstallMergedHook(const std::string &_label, const MergedHook &_h)
Definition: CCTracker.h:464
~CCTracker()
Definition: CCTracker.h:271
void SetStatClass(StatClass *const _stats) const noexcept
Set the stat class pointer if we wish to do performance profiling.
Definition: CCTracker.h:349
std::function< void(const CCTrackerType *const, const VertexSet *const, const VertexSet *const)> MergedHook
Definition: CCTracker.h:85
void AddVertex(const vertex_iterator _vi) noexcept
Definition: CCTracker.h:500
RoadmapType::VID VID
Definition: CCTracker.h:43
void Enable()
Definition: CCTracker.h:480
void DeleteEdge(const edge_iterator _ei) noexcept
Definition: CCTracker.h:644
RoadmapType::adj_edge_iterator edge_iterator
Definition: CCTracker.h:45
std::function< void(const CCTrackerType *const, const VertexSet *const)> CreatedHook
Definition: CCTracker.h:60
std::function< void(const CCTrackerType *const, const VertexSet *const)> DeletedHook
Definition: CCTracker.h:67
ComponentSet::const_iterator begin() const noexcept
Definition: CCTracker.h:410
void InstallCreatedHook(const std::string &_label, const CreatedHook &_h)
Definition: CCTracker.h:440
VertexSet GetRepresentatives() const noexcept
Definition: CCTracker.h:388
std::function< void(const CCTrackerType *const, const VertexSet *const, const VertexSet *const)> BrokenHook
Definition: CCTracker.h:76
void DeleteVertex(const vertex_iterator _vi) noexcept
Definition: CCTracker.h:530
void Print(std::ostream &_os, const std::string &_indent={}) const
Definition: CCTracker.h:489
CCTracker & operator=(const CCTracker &)
Definition: CCTracker.h:295
RoadmapType::VertexSet VertexSet
Definition: CCTracker.h:46
const VertexSet * GetCC(const VID _vid) const noexcept
Definition: CCTracker.h:379
RoadmapType::vertex_iterator vertex_iterator
Definition: CCTracker.h:44
CCTracker< RoadmapType > CCTrackerType
Definition: CCTracker.h:52
void InstallDeletedHook(const std::string &_label, const DeletedHook &_h)
Definition: CCTracker.h:448
CCTracker(RoadmapType *const _r)
Definition: CCTracker.h:261
void Disable()
Definition: CCTracker.h:472
std::unordered_set< VertexSet * > ComponentSet
Definition: CCTracker.h:53
Definition: MetricUtils.h:29
Definition: PMPLExceptions.h:62