Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Merge branch 'maximal-subset-search' into 'master'
authorMartin Quinson <martin.quinson@ens-rennes.fr>
Sun, 5 Mar 2023 20:13:24 +0000 (20:13 +0000)
committerMartin Quinson <martin.quinson@ens-rennes.fr>
Sun, 5 Mar 2023 20:13:24 +0000 (20:13 +0000)
Phase 3 of UDPOR Integration: Iteration Over Maximal Sets of a Configuration

See merge request simgrid/simgrid!135

15 files changed:
MANIFEST.in
src/mc/explo/UdporChecker.cpp
src/mc/explo/UdporChecker.hpp
src/mc/explo/udpor/Configuration.cpp
src/mc/explo/udpor/Configuration.hpp
src/mc/explo/udpor/Configuration_test.cpp
src/mc/explo/udpor/EventSet.cpp
src/mc/explo/udpor/EventSet.hpp
src/mc/explo/udpor/History.cpp
src/mc/explo/udpor/History.hpp
src/mc/explo/udpor/UnfoldingEvent.cpp
src/mc/explo/udpor/UnfoldingEvent.hpp
src/mc/explo/udpor/maximal_subsets_iterator.cpp [new file with mode: 0644]
src/mc/explo/udpor/maximal_subsets_iterator.hpp [new file with mode: 0644]
tools/cmake/DefinePackages.cmake

index 97c3528..c4b443b 100644 (file)
@@ -2169,6 +2169,8 @@ include src/mc/explo/udpor/UnfoldingEvent.cpp
 include src/mc/explo/udpor/UnfoldingEvent.hpp
 include src/mc/explo/udpor/UnfoldingEvent_test.cpp
 include src/mc/explo/udpor/Unfolding_test.cpp
+include src/mc/explo/udpor/maximal_subsets_iterator.cpp
+include src/mc/explo/udpor/maximal_subsets_iterator.hpp
 include src/mc/explo/udpor/udpor_forward.hpp
 include src/mc/inspect/DwarfExpression.cpp
 include src/mc/inspect/DwarfExpression.hpp
index 8a298fb..02452aa 100644 (file)
@@ -73,7 +73,7 @@ void UdporChecker::explore(const Configuration& C, EventSet D, EventSet A, std::
 
   // TODO: Add verbose logging about which event is being explored
 
-  UnfoldingEvent* e = select_next_unfolding_event(A, enC);
+  const UnfoldingEvent* e = select_next_unfolding_event(A, enC);
   xbt_assert(e != nullptr, "\n\n****** INVARIANT VIOLATION ******\n"
                            "UDPOR guarantees that an event will be chosen at each point in\n"
                            "the search, yet no events were actually chosen\n"
@@ -128,8 +128,8 @@ std::tuple<EventSet, EventSet> UdporChecker::compute_extension(const Configurati
   //
   // ex(C) = ex(C' + {e_cur}) = ex(C') / {e_cur} +
   //    U{<a, K> : K is maximal, `a` depends on all of K, `a` enabled at K }
-  UnfoldingEvent* e_cur = C.get_latest_event();
-  EventSet exC          = prev_exC;
+  const UnfoldingEvent* e_cur = C.get_latest_event();
+  EventSet exC                = prev_exC;
   exC.remove(e_cur);
 
   for (const auto& [aid, actor_state] : stateC.get_actors_list()) {
@@ -195,7 +195,7 @@ std::unique_ptr<State> UdporChecker::record_current_state()
   return next_state;
 }
 
-UnfoldingEvent* UdporChecker::select_next_unfolding_event(const EventSet& A, const EventSet& enC)
+const UnfoldingEvent* UdporChecker::select_next_unfolding_event(const EventSet& A, const EventSet& enC)
 {
   if (!enC.empty()) {
     return *(enC.begin());
index 30407c4..5e0b575 100644 (file)
@@ -115,7 +115,7 @@ private:
    * by the UDPOR algorithm to select new events to search. See the original
    * paper [1] for more details
    */
-  UnfoldingEvent* select_next_unfolding_event(const EventSet& A, const EventSet& enC);
+  const UnfoldingEvent* select_next_unfolding_event(const EventSet& A, const EventSet& enC);
 
   /**
    * @brief Computes the sets `ex(C)` and `en(C)` of the given configuration
index a05d3a7..70f4659 100644 (file)
@@ -14,7 +14,8 @@
 
 namespace simgrid::mc::udpor {
 
-Configuration::Configuration(std::initializer_list<UnfoldingEvent*> events) : Configuration(EventSet(std::move(events)))
+Configuration::Configuration(std::initializer_list<const UnfoldingEvent*> events)
+    : Configuration(EventSet(std::move(events)))
 {
 }
 
@@ -25,7 +26,7 @@ Configuration::Configuration(const EventSet& events) : events_(events)
   }
 }
 
-void Configuration::add_event(UnfoldingEvent* e)
+void Configuration::add_event(const UnfoldingEvent* e)
 {
   if (e == nullptr) {
     throw std::invalid_argument("Expected a nonnull `UnfoldingEvent*` but received NULL instead");
@@ -46,14 +47,14 @@ void Configuration::add_event(UnfoldingEvent* e)
   }
 }
 
-std::vector<UnfoldingEvent*> Configuration::get_topologically_sorted_events() const
+std::vector<const UnfoldingEvent*> Configuration::get_topologically_sorted_events() const
 {
   if (events_.empty()) {
-    return std::vector<UnfoldingEvent*>();
+    return std::vector<const UnfoldingEvent*>();
   }
 
-  std::stack<UnfoldingEvent*> event_stack;
-  std::vector<UnfoldingEvent*> topological_ordering;
+  std::stack<const UnfoldingEvent*> event_stack;
+  std::vector<const UnfoldingEvent*> topological_ordering;
   EventSet unknown_events = events_;
   EventSet temporarily_marked_events;
   EventSet permanently_marked_events;
@@ -63,7 +64,7 @@ std::vector<UnfoldingEvent*> Configuration::get_topologically_sorted_events() co
     event_stack.push(*unknown_events.begin());
 
     while (not event_stack.empty()) {
-      UnfoldingEvent* evt = event_stack.top();
+      const UnfoldingEvent* evt = event_stack.top();
       discovered_events.insert(evt);
 
       if (not temporarily_marked_events.contains(evt)) {
@@ -109,7 +110,7 @@ std::vector<UnfoldingEvent*> Configuration::get_topologically_sorted_events() co
   return topological_ordering;
 }
 
-std::vector<UnfoldingEvent*> Configuration::get_topologically_sorted_events_of_reverse_graph() const
+std::vector<const UnfoldingEvent*> Configuration::get_topologically_sorted_events_of_reverse_graph() const
 {
   // The method exploits the property that
   // a topological sorting S^R of the reverse graph G^R
index 8b2c219..a820a41 100644 (file)
@@ -9,8 +9,10 @@
 #include "src/mc/explo/udpor/EventSet.hpp"
 #include "src/mc/explo/udpor/udpor_forward.hpp"
 
+#include <boost/iterator/iterator_facade.hpp>
 #include <functional>
 #include <initializer_list>
+#include <optional>
 #include <vector>
 
 namespace simgrid::mc::udpor {
@@ -23,14 +25,14 @@ public:
   Configuration(Configuration&&)                 = default;
 
   explicit Configuration(const EventSet& events);
-  explicit Configuration(std::initializer_list<UnfoldingEvent*> events);
+  explicit Configuration(std::initializer_list<const UnfoldingEvent*> events);
 
   auto begin() const { return this->events_.begin(); }
   auto end() const { return this->events_.end(); }
 
-  bool contains(UnfoldingEvent* e) const { return this->events_.contains(e); }
+  bool contains(const UnfoldingEvent* e) const { return this->events_.contains(e); }
   const EventSet& get_events() const { return this->events_; }
-  UnfoldingEvent* get_latest_event() const { return this->newest_event; }
+  const UnfoldingEvent* get_latest_event() const { return this->newest_event; }
 
   /**
    * @brief Insert a new event into the configuration
@@ -59,7 +61,7 @@ public:
    * we shouldn't focus so much on this (let alone the additional benefit of the
    * assertions)
    */
-  void add_event(UnfoldingEvent* e);
+  void add_event(const UnfoldingEvent* e);
 
   /**
    * @brief Orders the events of the configuration such that
@@ -76,7 +78,7 @@ public:
    * structure appear farther along in the list than those that appear
    * closer to the "top"
    */
-  std::vector<UnfoldingEvent*> get_topologically_sorted_events() const;
+  std::vector<const UnfoldingEvent*> get_topologically_sorted_events() const;
 
   /**
    * @brief Orders the events of the configuration such that
@@ -100,13 +102,13 @@ public:
    * structure appear farther along in the list than those that appear
    * closer to the "bottom"
    */
-  std::vector<UnfoldingEvent*> get_topologically_sorted_events_of_reverse_graph() const;
+  std::vector<const UnfoldingEvent*> get_topologically_sorted_events_of_reverse_graph() const;
 
 private:
   /**
    * @brief The most recent event added to the configuration
    */
-  UnfoldingEvent* newest_event = nullptr;
+  const UnfoldingEvent* newest_event = nullptr;
 
   /**
    * @brief The events which make up this configuration
index 9a5a756..d1f1030 100644 (file)
@@ -8,6 +8,9 @@
 #include "src/mc/explo/udpor/EventSet.hpp"
 #include "src/mc/explo/udpor/History.hpp"
 #include "src/mc/explo/udpor/UnfoldingEvent.hpp"
+#include "src/mc/explo/udpor/maximal_subsets_iterator.hpp"
+
+#include <unordered_map>
 
 using namespace simgrid::mc::udpor;
 
@@ -141,8 +144,9 @@ TEST_CASE("simgrid::mc::udpor::Configuration: Topological Sort Order")
   SECTION("Topological ordering for entire set")
   {
     Configuration C{&e1, &e2, &e3, &e4};
-    REQUIRE(C.get_topologically_sorted_events() == std::vector<UnfoldingEvent*>{&e1, &e2, &e3, &e4});
-    REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<UnfoldingEvent*>{&e4, &e3, &e2, &e1});
+    REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e4});
+    REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() ==
+            std::vector<const UnfoldingEvent*>{&e4, &e3, &e2, &e1});
   }
 
   SECTION("Topological ordering for subsets")
@@ -150,29 +154,30 @@ TEST_CASE("simgrid::mc::udpor::Configuration: Topological Sort Order")
     SECTION("No elements")
     {
       Configuration C;
-      REQUIRE(C.get_topologically_sorted_events() == std::vector<UnfoldingEvent*>{});
-      REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<UnfoldingEvent*>{});
+      REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{});
+      REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<const UnfoldingEvent*>{});
     }
 
     SECTION("e1 only")
     {
       Configuration C{&e1};
-      REQUIRE(C.get_topologically_sorted_events() == std::vector<UnfoldingEvent*>{&e1});
-      REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<UnfoldingEvent*>{&e1});
+      REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1});
+      REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<const UnfoldingEvent*>{&e1});
     }
 
     SECTION("e1 and e2 only")
     {
       Configuration C{&e1, &e2};
-      REQUIRE(C.get_topologically_sorted_events() == std::vector<UnfoldingEvent*>{&e1, &e2});
-      REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<UnfoldingEvent*>{&e2, &e1});
+      REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2});
+      REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<const UnfoldingEvent*>{&e2, &e1});
     }
 
     SECTION("e1, e2, and e3 only")
     {
       Configuration C{&e1, &e2, &e3};
-      REQUIRE(C.get_topologically_sorted_events() == std::vector<UnfoldingEvent*>{&e1, &e2, &e3});
-      REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<UnfoldingEvent*>{&e3, &e2, &e1});
+      REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3});
+      REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() ==
+              std::vector<const UnfoldingEvent*>{&e3, &e2, &e1});
     }
   }
 }
@@ -201,51 +206,54 @@ TEST_CASE("simgrid::mc::udpor::Configuration: Topological Sort Order More Compli
     SECTION("No elements")
     {
       Configuration C;
-      REQUIRE(C.get_topologically_sorted_events() == std::vector<UnfoldingEvent*>{});
-      REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<UnfoldingEvent*>{});
+      REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{});
+      REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<const UnfoldingEvent*>{});
     }
 
     SECTION("e1 only")
     {
       Configuration C{&e1};
-      REQUIRE(C.get_topologically_sorted_events() == std::vector<UnfoldingEvent*>{&e1});
-      REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<UnfoldingEvent*>{&e1});
+      REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1});
+      REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<const UnfoldingEvent*>{&e1});
     }
 
     SECTION("e1 and e2 only")
     {
       Configuration C{&e1, &e2};
-      REQUIRE(C.get_topologically_sorted_events() == std::vector<UnfoldingEvent*>{&e1, &e2});
-      REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<UnfoldingEvent*>{&e2, &e1});
+      REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2});
+      REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<const UnfoldingEvent*>{&e2, &e1});
     }
 
     SECTION("e1, e2, and e3 only")
     {
       Configuration C{&e1, &e2, &e3};
-      REQUIRE(C.get_topologically_sorted_events() == std::vector<UnfoldingEvent*>{&e1, &e2, &e3});
-      REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<UnfoldingEvent*>{&e3, &e2, &e1});
+      REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3});
+      REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() ==
+              std::vector<const UnfoldingEvent*>{&e3, &e2, &e1});
     }
 
     SECTION("e1, e2, e3, and e6 only")
     {
       Configuration C{&e1, &e2, &e3, &e6};
-      REQUIRE(C.get_topologically_sorted_events() == std::vector<UnfoldingEvent*>{&e1, &e2, &e3, &e6});
-      REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<UnfoldingEvent*>{&e6, &e3, &e2, &e1});
+      REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e6});
+      REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() ==
+              std::vector<const UnfoldingEvent*>{&e6, &e3, &e2, &e1});
     }
 
     SECTION("e1, e2, e3, and e4 only")
     {
       Configuration C{&e1, &e2, &e3, &e4};
-      REQUIRE(C.get_topologically_sorted_events() == std::vector<UnfoldingEvent*>{&e1, &e2, &e3, &e4});
-      REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<UnfoldingEvent*>{&e4, &e3, &e2, &e1});
+      REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e4});
+      REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() ==
+              std::vector<const UnfoldingEvent*>{&e4, &e3, &e2, &e1});
     }
 
     SECTION("e1, e2, e3, e4, and e5 only")
     {
       Configuration C{&e1, &e2, &e3, &e4, &e5};
-      REQUIRE(C.get_topologically_sorted_events() == std::vector<UnfoldingEvent*>{&e1, &e2, &e3, &e4, &e5});
+      REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e4, &e5});
       REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() ==
-              std::vector<UnfoldingEvent*>{&e5, &e4, &e3, &e2, &e1});
+              std::vector<const UnfoldingEvent*>{&e5, &e4, &e3, &e2, &e1});
     }
 
     SECTION("e1, e2, e3, e4 and e6 only")
@@ -253,12 +261,12 @@ TEST_CASE("simgrid::mc::udpor::Configuration: Topological Sort Order More Compli
       // In this case, e4 and e6 are interchangeable. Hence, we have to check
       // if the sorting gives us *any* of the combinations
       Configuration C{&e1, &e2, &e3, &e4, &e6};
-      REQUIRE((C.get_topologically_sorted_events() == std::vector<UnfoldingEvent*>{&e1, &e2, &e3, &e4, &e6} ||
-               C.get_topologically_sorted_events() == std::vector<UnfoldingEvent*>{&e1, &e2, &e3, &e6, &e4}));
+      REQUIRE((C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e4, &e6} ||
+               C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e6, &e4}));
       REQUIRE((C.get_topologically_sorted_events_of_reverse_graph() ==
-                   std::vector<UnfoldingEvent*>{&e6, &e4, &e3, &e2, &e1} ||
+                   std::vector<const UnfoldingEvent*>{&e6, &e4, &e3, &e2, &e1} ||
                C.get_topologically_sorted_events_of_reverse_graph() ==
-                   std::vector<UnfoldingEvent*>{&e4, &e6, &e3, &e2, &e1}));
+                   std::vector<const UnfoldingEvent*>{&e4, &e6, &e3, &e2, &e1}));
     }
 
     SECTION("Topological ordering for entire set")
@@ -266,15 +274,16 @@ TEST_CASE("simgrid::mc::udpor::Configuration: Topological Sort Order More Compli
       // In this case, e4/e5 are both interchangeable with e6. Hence, again we have to check
       // if the sorting gives us *any* of the combinations
       Configuration C{&e1, &e2, &e3, &e4, &e5, &e6};
-      REQUIRE((C.get_topologically_sorted_events() == std::vector<UnfoldingEvent*>{&e1, &e2, &e3, &e4, &e5, &e6} ||
-               C.get_topologically_sorted_events() == std::vector<UnfoldingEvent*>{&e1, &e2, &e3, &e4, &e6, &e5} ||
-               C.get_topologically_sorted_events() == std::vector<UnfoldingEvent*>{&e1, &e2, &e3, &e6, &e4, &e5}));
+      REQUIRE(
+          (C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e4, &e5, &e6} ||
+           C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e4, &e6, &e5} ||
+           C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e6, &e4, &e5}));
       REQUIRE((C.get_topologically_sorted_events_of_reverse_graph() ==
-                   std::vector<UnfoldingEvent*>{&e6, &e5, &e4, &e3, &e2, &e1} ||
+                   std::vector<const UnfoldingEvent*>{&e6, &e5, &e4, &e3, &e2, &e1} ||
                C.get_topologically_sorted_events_of_reverse_graph() ==
-                   std::vector<UnfoldingEvent*>{&e5, &e6, &e4, &e3, &e2, &e1} ||
+                   std::vector<const UnfoldingEvent*>{&e5, &e6, &e4, &e3, &e2, &e1} ||
                C.get_topologically_sorted_events_of_reverse_graph() ==
-                   std::vector<UnfoldingEvent*>{&e5, &e4, &e6, &e3, &e2, &e1}));
+                   std::vector<const UnfoldingEvent*>{&e5, &e4, &e6, &e3, &e2, &e1}));
     }
   }
 }
@@ -316,7 +325,7 @@ TEST_CASE("simgrid::mc::udpor::Configuration: Topological Sort Order Very Compli
     EventSet events_seen;
     const auto ordered_events = C.get_topologically_sorted_events();
 
-    std::for_each(ordered_events.begin(), ordered_events.end(), [&events_seen](UnfoldingEvent* e) {
+    std::for_each(ordered_events.begin(), ordered_events.end(), [&events_seen](const UnfoldingEvent* e) {
       History history(e);
       for (auto* e_hist : history) {
         // In this demo, we want to make sure that
@@ -343,7 +352,7 @@ TEST_CASE("simgrid::mc::udpor::Configuration: Topological Sort Order Very Compli
     EventSet events_seen;
     const auto ordered_events = C.get_topologically_sorted_events_of_reverse_graph();
 
-    std::for_each(ordered_events.begin(), ordered_events.end(), [&events_seen](UnfoldingEvent* e) {
+    std::for_each(ordered_events.begin(), ordered_events.end(), [&events_seen](const UnfoldingEvent* e) {
       History history(e);
 
       for (auto* e_hist : history) {
@@ -358,4 +367,201 @@ TEST_CASE("simgrid::mc::udpor::Configuration: Topological Sort Order Very Compli
       events_seen.insert(e);
     });
   }
+
+  SECTION("Test that the topological ordering contains only the events of the configuration")
+  {
+    const EventSet events_seen = C.get_events();
+
+    SECTION("Forward direction")
+    {
+      auto ordered_events              = C.get_topologically_sorted_events();
+      const EventSet ordered_event_set = EventSet(std::move(ordered_events));
+      REQUIRE(events_seen == ordered_event_set);
+    }
+
+    SECTION("Reverse direction")
+    {
+      auto ordered_events              = C.get_topologically_sorted_events_of_reverse_graph();
+      const EventSet ordered_event_set = EventSet(std::move(ordered_events));
+      REQUIRE(events_seen == ordered_event_set);
+    }
+  }
+}
+
+TEST_CASE("simgrid::mc::udpor::maximal_subsets_iterator: Basic Testing of Maximal Subsets")
+{
+  // The following tests concern the given event structure:
+  //                e1
+  //              /   /
+  //             e2   e5
+  //            /     /
+  //           e3    e6
+  //           /     / /
+  //          e4    e7 e8
+  UnfoldingEvent e1;
+  UnfoldingEvent e2{&e1};
+  UnfoldingEvent e3{&e2};
+  UnfoldingEvent e4{&e3};
+  UnfoldingEvent e5{&e1};
+  UnfoldingEvent e6{&e5};
+  UnfoldingEvent e7{&e6};
+  UnfoldingEvent e8{&e6};
+
+  SECTION("Iteration over an empty configuration yields only the empty set")
+  {
+    Configuration C;
+    maximal_subsets_iterator first(C);
+    maximal_subsets_iterator last;
+
+    REQUIRE(*first == EventSet());
+    ++first;
+    REQUIRE(first == last);
+  }
+
+  SECTION("Check counts of maximal event sets discovered")
+  {
+    std::unordered_map<int, int> maximal_subset_counts;
+
+    Configuration C{&e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8};
+    maximal_subsets_iterator first(C);
+    maximal_subsets_iterator last;
+
+    for (; first != last; ++first) {
+      maximal_subset_counts[(*first).size()]++;
+    }
+
+    // First, ensure that there are only sets of size 0, 1, 2, and 3
+    CHECK(maximal_subset_counts.size() == 4);
+
+    // The empty set should appear only once
+    REQUIRE(maximal_subset_counts[0] == 1);
+
+    // 8 is the number of nodes in the graph
+    REQUIRE(maximal_subset_counts[1] == 8);
+
+    // 13 = 3 * 4 (each of the left branch can combine with one in the right branch) + 1 (e7 + e8)
+    REQUIRE(maximal_subset_counts[2] == 13);
+
+    // e7 + e8 must be included, so that means we can combine from the left branch
+    REQUIRE(maximal_subset_counts[3] == 3);
+  }
+
+  SECTION("Check counts of maximal event sets discovered with a filter")
+  {
+    std::unordered_map<int, int> maximal_subset_counts;
+
+    Configuration C{&e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8};
+
+    SECTION("Filter with events part of initial maximal set")
+    {
+      EventSet interesting_bunch{&e2, &e4, &e7, &e8};
+
+      maximal_subsets_iterator first(C, [&](const UnfoldingEvent* e) { return interesting_bunch.contains(e); });
+      maximal_subsets_iterator last;
+
+      for (; first != last; ++first) {
+        const auto& event_set = *first;
+        // Only events in `interesting_bunch` can appear: thus no set
+        // should include anything else other than `interesting_bunch`
+        REQUIRE(event_set.is_subset_of(interesting_bunch));
+        REQUIRE(event_set.is_maximal_event_set());
+        maximal_subset_counts[event_set.size()]++;
+      }
+
+      // The empty set should (still) appear only once
+      REQUIRE(maximal_subset_counts[0] == 1);
+
+      // 4 is the number of nodes in the `interesting_bunch`
+      REQUIRE(maximal_subset_counts[1] == 4);
+
+      // 5 = 2 * 2 (each of the left branch can combine with one in the right branch) + 1 (e7 + e8)
+      REQUIRE(maximal_subset_counts[2] == 5);
+
+      // e7 + e8 must be included, so that means we can combine from the left branch (only e2 and e4)
+      REQUIRE(maximal_subset_counts[3] == 2);
+
+      // There are no subsets of size 4 (or higher, but that
+      // is tested by asserting each maximal set traversed is a subset)
+      REQUIRE(maximal_subset_counts[4] == 0);
+    }
+
+    SECTION("Filter with interesting subset not initially part of the maximal set")
+    {
+      EventSet interesting_bunch{&e3, &e5, &e6};
+
+      maximal_subsets_iterator first(C, [&](const UnfoldingEvent* e) { return interesting_bunch.contains(e); });
+      maximal_subsets_iterator last;
+
+      for (; first != last; ++first) {
+        const auto& event_set = *first;
+        // Only events in `interesting_bunch` can appear: thus no set
+        // should include anything else other than `interesting_bunch`
+        REQUIRE(event_set.is_subset_of(interesting_bunch));
+        REQUIRE(event_set.is_maximal_event_set());
+        maximal_subset_counts[event_set.size()]++;
+      }
+
+      // The empty set should (still) appear only once
+      REQUIRE(maximal_subset_counts[0] == 1);
+
+      // 3 is the number of nodes in the `interesting_bunch`
+      REQUIRE(maximal_subset_counts[1] == 3);
+
+      // 2 = e3, e5 and e3, e6
+      REQUIRE(maximal_subset_counts[2] == 2);
+
+      // There are no subsets of size 3 (or higher, but that
+      // is tested by asserting each maximal set traversed is a subset)
+      REQUIRE(maximal_subset_counts[3] == 0);
+    }
+  }
+}
+
+TEST_CASE("simgrid::mc::udpor::maximal_subsets_iterator: Stress Test for Maximal Subsets Iteration")
+{
+  // The following tests concern the given event structure:
+  //                              e1
+  //                            /   /
+  //                          e2    e3
+  //                          / /   /  /
+  //               +------* e4 *e5 e6  e7
+  //               |        /   ///   /  /
+  //               |       e8   e9    e10
+  //               |      /  /   /\      /
+  //               |   e11 e12 e13 e14   e15
+  //               |   /      / / /   /  /
+  //               +-> e16     e17     e18
+  UnfoldingEvent e1;
+  UnfoldingEvent e2{&e1};
+  UnfoldingEvent e3{&e1};
+  UnfoldingEvent e4{&e2};
+  UnfoldingEvent e5{&e2};
+  UnfoldingEvent e6{&e3};
+  UnfoldingEvent e7{&e3};
+  UnfoldingEvent e8{&e4};
+  UnfoldingEvent e9{&e4, &e5, &e6};
+  UnfoldingEvent e10{&e6, &e7};
+  UnfoldingEvent e11{&e8};
+  UnfoldingEvent e12{&e8};
+  UnfoldingEvent e13{&e9};
+  UnfoldingEvent e14{&e9};
+  UnfoldingEvent e15{&e10};
+  UnfoldingEvent e16{&e5, &e11};
+  UnfoldingEvent e17{&e12, &e13, &e14};
+  UnfoldingEvent e18{&e14, &e15};
+  Configuration C{&e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14, &e15, &e16, &e17, &e18};
+
+  SECTION("Every subset iterated over is maximal")
+  {
+    maximal_subsets_iterator first(C);
+    maximal_subsets_iterator last;
+
+    // Make sure we actually have something to iterate over
+    REQUIRE(first != last);
+
+    for (; first != last; ++first) {
+      REQUIRE((*first).size() <= C.get_events().size());
+      REQUIRE((*first).is_maximal_event_set());
+    }
+  }
 }
\ No newline at end of file
index b03bef5..5417103 100644 (file)
@@ -13,7 +13,7 @@ namespace simgrid::mc::udpor {
 
 EventSet::EventSet(Configuration&& config) : EventSet(config.get_events()) {}
 
-void EventSet::remove(UnfoldingEvent* e)
+void EventSet::remove(const UnfoldingEvent* e)
 {
   this->events_.erase(e);
 }
@@ -30,9 +30,9 @@ void EventSet::subtract(const Configuration& config)
 
 EventSet EventSet::subtracting(const EventSet& other) const
 {
-  std::unordered_set<UnfoldingEvent*> result = this->events_;
+  std::unordered_set<const UnfoldingEvent*> result = this->events_;
 
-  for (UnfoldingEvent* e : other.events_)
+  for (const UnfoldingEvent* e : other.events_)
     result.erase(e);
 
   return EventSet(std::move(result));
@@ -43,14 +43,14 @@ EventSet EventSet::subtracting(const Configuration& config) const
   return subtracting(config.get_events());
 }
 
-EventSet EventSet::subtracting(UnfoldingEvent* e) const
+EventSet EventSet::subtracting(const UnfoldingEvent* e) const
 {
   auto result = this->events_;
   result.erase(e);
   return EventSet(std::move(result));
 }
 
-void EventSet::insert(UnfoldingEvent* e)
+void EventSet::insert(const UnfoldingEvent* e)
 {
   this->events_.insert(e);
 }
@@ -65,7 +65,7 @@ void EventSet::form_union(const Configuration& config)
   form_union(config.get_events());
 }
 
-EventSet EventSet::make_union(UnfoldingEvent* e) const
+EventSet EventSet::make_union(const UnfoldingEvent* e) const
 {
   auto result = this->events_;
   result.insert(e);
@@ -74,9 +74,9 @@ EventSet EventSet::make_union(UnfoldingEvent* e) const
 
 EventSet EventSet::make_union(const EventSet& other) const
 {
-  std::unordered_set<UnfoldingEvent*> result = this->events_;
+  std::unordered_set<const UnfoldingEvent*> result = this->events_;
 
-  for (UnfoldingEvent* e : other.events_)
+  for (const UnfoldingEvent* e : other.events_)
     result.insert(e);
 
   return EventSet(std::move(result));
@@ -97,7 +97,7 @@ bool EventSet::empty() const
   return this->events_.empty();
 }
 
-bool EventSet::contains(UnfoldingEvent* e) const
+bool EventSet::contains(const UnfoldingEvent* e) const
 {
   return this->events_.find(e) != this->events_.end();
 }
@@ -114,18 +114,19 @@ bool EventSet::is_valid_configuration() const
 {
   /// @invariant: A collection of events `E` is a configuration
   /// if and only if following while following the history of
-  /// each event `e` of `E`you remain in `E`. In other words, you
+  /// each event `e` of `E` you remain in `E`. In other words, you
   /// only see events from set `E`
   ///
-  /// The proof is based on the definition of a configuration
-  /// which requires that all
+  /// The simple proof is based on the definition of a configuration
+  /// which requires that all events have their history contained
+  /// in the set
   const History history(*this);
   return this->contains(history);
 }
 
 bool EventSet::contains(const History& history) const
 {
-  return std::all_of(history.begin(), history.end(), [=](UnfoldingEvent* e) { return this->contains(e); });
+  return std::all_of(history.begin(), history.end(), [=](const UnfoldingEvent* e) { return this->contains(e); });
 }
 
 bool EventSet::is_maximal_event_set() const
index 26a910a..b9b635f 100644 (file)
 #include <cstddef>
 #include <initializer_list>
 #include <unordered_set>
+#include <vector>
 
 namespace simgrid::mc::udpor {
 
 class EventSet {
 private:
-  std::unordered_set<UnfoldingEvent*> events_;
+  std::unordered_set<const UnfoldingEvent*> events_;
 
 public:
   EventSet()                           = default;
@@ -25,31 +26,32 @@ public:
   EventSet& operator=(EventSet&&)      = default;
   EventSet(EventSet&&)                 = default;
   explicit EventSet(Configuration&& config);
-  explicit EventSet(std::unordered_set<UnfoldingEvent*>&& raw_events) : events_(raw_events) {}
-  explicit EventSet(std::initializer_list<UnfoldingEvent*> event_list) : events_(std::move(event_list)) {}
+  explicit EventSet(std::vector<const UnfoldingEvent*>&& raw_events) : events_(raw_events.begin(), raw_events.end()) {}
+  explicit EventSet(std::unordered_set<const UnfoldingEvent*>&& raw_events) : events_(raw_events) {}
+  explicit EventSet(std::initializer_list<const UnfoldingEvent*> event_list) : events_(std::move(event_list)) {}
 
   auto begin() const { return this->events_.begin(); }
   auto end() const { return this->events_.end(); }
   auto cbegin() const { return this->events_.cbegin(); }
   auto cend() const { return this->events_.cend(); }
 
-  void remove(UnfoldingEvent*);
+  void remove(const UnfoldingEvent*);
   void subtract(const EventSet&);
   void subtract(const Configuration&);
-  EventSet subtracting(UnfoldingEvent*) const;
+  EventSet subtracting(const UnfoldingEvent*) const;
   EventSet subtracting(const EventSet&) const;
   EventSet subtracting(const Configuration&) const;
 
-  void insert(UnfoldingEvent*);
+  void insert(const UnfoldingEvent*);
   void form_union(const EventSet&);
   void form_union(const Configuration&);
-  EventSet make_union(UnfoldingEvent*) const;
+  EventSet make_union(const UnfoldingEvent*) const;
   EventSet make_union(const EventSet&) const;
   EventSet make_union(const Configuration&) const;
 
   size_t size() const;
   bool empty() const;
-  bool contains(UnfoldingEvent*) const;
+  bool contains(const UnfoldingEvent*) const;
   bool contains(const History&) const;
   bool is_subset_of(const EventSet&) const;
 
@@ -66,6 +68,12 @@ public:
    * @brief Whether or not this set of events is
    * a *maximal event set*, i.e. whether each element
    * of the set causes none of the others
+   *
+   * A set of events `E` is said to be _maximal_ if
+   * it is causally-free. Formally,
+   *
+   * 1. For each event `e` in `E`, there is no event
+   * `e'` in `E` such that `e < e'`
    */
   bool is_maximal_event_set() const;
 };
index e6a6405..d9d43d3 100644 (file)
@@ -20,11 +20,11 @@ History::Iterator::Iterator(const EventSet& initial_events, optional_configurati
   // and all subsequent events that are added by the iterator
 }
 
-History::Iterator& History::Iterator::operator++()
+void History::Iterator::increment()
 {
   if (not frontier.empty()) {
     // "Pop" the event at the "front"
-    UnfoldingEvent* e = *frontier.begin();
+    const UnfoldingEvent* e = *frontier.begin();
     frontier.remove(e);
 
     // If there is a configuration and if the
@@ -33,7 +33,7 @@ History::Iterator& History::Iterator::operator++()
     // be searched since the configuration contains
     // them (configuration invariant)
     if (configuration.has_value() && configuration->get().contains(e)) {
-      return *this;
+      return;
     }
 
     // Mark this event as seen
@@ -47,7 +47,19 @@ History::Iterator& History::Iterator::operator++()
     candidates.subtract(current_history);
     frontier.form_union(candidates);
   }
-  return *this;
+}
+
+const UnfoldingEvent* const& History::Iterator::dereference() const
+{
+  return *frontier.begin();
+}
+
+bool History::Iterator::equal(const Iterator& other) const
+{
+  // If what the iterator sees next is the same, we consider them
+  // to be the same iterator. This way, once the iterator has completed
+  // its search, it will be "equal" to an iterator searching nothing
+  return this->frontier == other.frontier;
 }
 
 EventSet History::get_all_events() const
index 7087927..da35244 100644 (file)
@@ -10,6 +10,7 @@
 #include "src/mc/explo/udpor/EventSet.hpp"
 #include "src/mc/explo/udpor/udpor_forward.hpp"
 
+#include <boost/iterator/iterator_facade.hpp>
 #include <functional>
 #include <optional>
 
@@ -53,8 +54,8 @@ public:
   History& operator=(History const&) = default;
   History(History&&)                 = default;
 
-  explicit History(UnfoldingEvent* e) : events_({e}) {}
   explicit History(EventSet event_set = EventSet()) : events_(std::move(event_set)) {}
+  explicit History(const UnfoldingEvent* e) : events_({e}) {}
 
   auto begin() const { return Iterator(events_); }
   auto end() const { return Iterator(EventSet()); }
@@ -99,25 +100,9 @@ private:
   /**
    * @brief An iterator which traverses the history of a set of events
    */
-  struct Iterator {
+  struct Iterator : boost::iterator_facade<Iterator, const UnfoldingEvent* const, boost::forward_traversal_tag> {
   public:
-    Iterator& operator++();
-    auto operator->() const { return frontier.begin().operator->(); }
-    auto operator*() const { return *frontier.begin(); }
-
-    // If what the iterator sees next is the same, we consider them
-    // to be the same iterator. This way, once the iterator has completed
-    // its search, it will be "equal" to an iterator searching nothing
-    bool operator==(const Iterator& other) const { return this->frontier == other.frontier; }
-    bool operator!=(const Iterator& other) const { return not(this->operator==(other)); }
-
-    using iterator_category      = std::forward_iterator_tag;
-    using difference_type        = int; // # of steps between
-    using value_type             = UnfoldingEvent*;
-    using pointer                = value_type*;
-    using reference              = value_type&;
     using optional_configuration = std::optional<std::reference_wrapper<const Configuration>>;
-
     Iterator(const EventSet& initial_events, optional_configuration config = std::nullopt);
 
   private:
@@ -129,8 +114,21 @@ private:
     EventSet current_history = EventSet();
 
     /// @brief What the iterator currently believes
+    // to be the set of maximal events
     EventSet maximal_events;
     optional_configuration configuration;
+
+    // boost::iterator_facade<...> interface to implement
+    void increment();
+    bool equal(const Iterator& other) const;
+
+    const UnfoldingEvent* const& dereference() const;
+
+    // Allows boost::iterator_facade<...> to function properly
+    friend class boost::iterator_core_access;
+
+    // Allow the `History` class to use some of the
+    // computation of the iterator
     friend History;
   };
 };
index ee6de7c..88f4b82 100644 (file)
@@ -4,10 +4,11 @@
  * under the terms of the license (GNU LGPL) which comes with this package. */
 
 #include "src/mc/explo/udpor/UnfoldingEvent.hpp"
+#include "src/mc/explo/udpor/History.hpp"
 
 namespace simgrid::mc::udpor {
 
-UnfoldingEvent::UnfoldingEvent(std::initializer_list<UnfoldingEvent*> init_list)
+UnfoldingEvent::UnfoldingEvent(std::initializer_list<const UnfoldingEvent*> init_list)
     : UnfoldingEvent(EventSet(std::move(init_list)))
 {
 }
@@ -34,4 +35,9 @@ bool UnfoldingEvent::operator==(const UnfoldingEvent& other) const
   return this->immediate_causes == other.immediate_causes;
 }
 
+EventSet UnfoldingEvent::get_history() const
+{
+  return History(this).get_all_events();
+}
+
 } // namespace simgrid::mc::udpor
index 4468b21..f19c41e 100644 (file)
@@ -18,7 +18,7 @@ namespace simgrid::mc::udpor {
 
 class UnfoldingEvent {
 public:
-  explicit UnfoldingEvent(std::initializer_list<UnfoldingEvent*> init_list);
+  explicit UnfoldingEvent(std::initializer_list<const UnfoldingEvent*> init_list);
   UnfoldingEvent(EventSet immediate_causes              = EventSet(),
                  std::shared_ptr<Transition> transition = std::make_unique<Transition>());
 
diff --git a/src/mc/explo/udpor/maximal_subsets_iterator.cpp b/src/mc/explo/udpor/maximal_subsets_iterator.cpp
new file mode 100644 (file)
index 0000000..1dd635a
--- /dev/null
@@ -0,0 +1,163 @@
+#include "src/mc/explo/udpor/maximal_subsets_iterator.hpp"
+#include "src/mc/explo/udpor/UnfoldingEvent.hpp"
+#include "xbt/asserts.h"
+
+#include <algorithm>
+
+namespace simgrid::mc::udpor {
+
+maximal_subsets_iterator::maximal_subsets_iterator(const Configuration& config,
+                                                   std::optional<node_filter_function> filter)
+    : config({config}), current_maximal_set({EventSet()})
+{
+  const auto candidate_ordering = config.get_topologically_sorted_events_of_reverse_graph();
+  if (filter.has_value()) {
+    // Only store the events in the ordering that "matter" to us
+    std::copy_if(std::move_iterator(candidate_ordering.begin()), std::move_iterator(candidate_ordering.end()),
+                 std::back_inserter(topological_ordering), filter.value());
+  } else {
+    topological_ordering = std::move(candidate_ordering);
+  }
+}
+
+void maximal_subsets_iterator::increment()
+{
+  if (current_maximal_set == std::nullopt) {
+    return;
+  }
+
+  if (topological_ordering.empty()) {
+    // Stop immediately if there's nothing to search
+    current_maximal_set = std::nullopt;
+    return;
+  }
+
+  const auto next_event_ref = [&]() {
+    if (!has_started_searching) {
+      has_started_searching = true;
+      return bookkeeper.find_next_candidate_event(topological_ordering.begin(), topological_ordering.end());
+    } else {
+      return continue_traversal_of_maximal_events_tree();
+    }
+  }();
+
+  if (next_event_ref == topological_ordering.end()) {
+    current_maximal_set = std::nullopt;
+    return;
+  }
+
+  // We found some other event `e'` which is not in causally related with anything
+  // that currently exists in `current_maximal_set`, so add it in
+  add_element_to_current_maximal_set(*next_event_ref);
+  backtrack_points.push(next_event_ref);
+}
+
+maximal_subsets_iterator::topological_order_position
+maximal_subsets_iterator::continue_traversal_of_maximal_events_tree()
+{
+  // Nothing needs to be done if there isn't anyone to search for...
+  if (backtrack_points.empty()) {
+    return topological_ordering.end();
+  }
+
+  // 1. First, check if we can keep expanding from the
+  // maximal set that we currently have
+  {
+    // This is an iterator which points to the latest event `e` that
+    // was added to what is currently the maximal set
+    const auto latest_event_ref = backtrack_points.top();
+
+    // Look for the next event to test with what we currently
+    // have based on the conflicts we've already kept track of.
+    //
+    // NOTE: We only need to search FROM `e` and not
+    // from the beginning of the topological sort. The fact that the
+    // set is topologically ordered ensures that removing `e`
+    // will not change whether or not to now allow someone before `e`
+    // in the ordering (otherwise, they would have to be in `e`'s history
+    // and therefore would come after `e`)
+    const auto next_event_ref = bookkeeper.find_next_candidate_event(latest_event_ref, topological_ordering.end());
+
+    // If we can expand from what we currently have, we can stop
+    if (next_event_ref != topological_ordering.end()) {
+      return next_event_ref;
+    }
+  }
+
+  // Otherwise, we backtrack: we repeatedly pop off events that we know we
+  // are finished with
+  while (not backtrack_points.empty()) {
+    // Note: it is important to remove the element FIRST before performing
+    // the search, as removal may enable dependencies of `e` to be selected
+    const auto latest_event_ref = backtrack_points.top();
+    remove_element_from_current_maximal_set(*latest_event_ref);
+    backtrack_points.pop();
+
+    // We begin the search AFTER the event we popped: we only want
+    // to consider those events that could be added AFTER `e` and
+    // not `e` itself again
+    const auto next_event_ref = bookkeeper.find_next_candidate_event(latest_event_ref + 1, topological_ordering.end());
+    if (next_event_ref != topological_ordering.end()) {
+      return next_event_ref;
+    }
+  }
+  return topological_ordering.end();
+}
+
+bool maximal_subsets_iterator::bookkeeper::is_candidate_event(const UnfoldingEvent* e) const
+{
+  if (const auto e_count = event_counts.find(e); e_count != event_counts.end()) {
+    return e_count->second == 0;
+  }
+  return true;
+}
+
+void maximal_subsets_iterator::add_element_to_current_maximal_set(const UnfoldingEvent* e)
+{
+  xbt_assert(current_maximal_set.has_value(), "Attempting to add an event to the maximal set "
+                                              "when iteration has completed. This indicates that "
+                                              "the termination condition for the iterator is broken");
+  current_maximal_set.value().insert(e);
+  bookkeeper.mark_included_in_maximal_set(e);
+}
+
+void maximal_subsets_iterator::remove_element_from_current_maximal_set(const UnfoldingEvent* e)
+{
+  xbt_assert(current_maximal_set.has_value(), "Attempting to remove an event to the maximal set "
+                                              "when iteration has completed. This indicates that "
+                                              "the termination condition for the iterator is broken");
+  current_maximal_set.value().remove(e);
+  bookkeeper.mark_removed_from_maximal_set(e);
+}
+
+maximal_subsets_iterator::topological_order_position
+maximal_subsets_iterator::bookkeeper::find_next_candidate_event(topological_order_position first,
+                                                                topological_order_position last) const
+{
+  return std::find_if(first, last, [&](const UnfoldingEvent* e) { return is_candidate_event(e); });
+}
+
+void maximal_subsets_iterator::bookkeeper::mark_included_in_maximal_set(const UnfoldingEvent* e)
+{
+  const auto e_history = e->get_history();
+  for (const auto e_hist : e_history) {
+    event_counts[e_hist]++;
+  }
+}
+
+void maximal_subsets_iterator::bookkeeper::mark_removed_from_maximal_set(const UnfoldingEvent* e)
+{
+  const auto e_history = e->get_history();
+  for (const auto e_hist : e_history) {
+    xbt_assert(event_counts.find(e_hist) != event_counts.end(),
+               "Invariant Violation: Attempted to remove an event which was not previously added");
+    xbt_assert(event_counts[e_hist] > 0, "Invariant Violation: An event `e` had a count of `0` at this point "
+                                         "of the bookkeeping, which means that it is a candidate maximal event. "
+                                         "Yet some event that `e'` which contains `e` in its history was removed "
+                                         "first. This incidates that the topological sorting of events of the "
+                                         "configuration has failed and should be investigated first");
+    event_counts[e_hist]--;
+  }
+}
+
+} // namespace simgrid::mc::udpor
\ No newline at end of file
diff --git a/src/mc/explo/udpor/maximal_subsets_iterator.hpp b/src/mc/explo/udpor/maximal_subsets_iterator.hpp
new file mode 100644 (file)
index 0000000..128e244
--- /dev/null
@@ -0,0 +1,137 @@
+/* Copyright (c) 2007-2023. The SimGrid Team. All rights reserved.          */
+
+/* This program is free software; you can redistribute it and/or modify it
+ * under the terms of the license (GNU LGPL) which comes with this package. */
+
+#ifndef SIMGRID_MC_UDPOR_MAXIMAL_SUBSETS_ITERATOR_HPP
+#define SIMGRID_MC_UDPOR_MAXIMAL_SUBSETS_ITERATOR_HPP
+
+#include "src/mc/explo/udpor/Configuration.hpp"
+
+#include <boost/iterator/iterator_facade.hpp>
+#include <optional>
+#include <stack>
+#include <unordered_map>
+
+namespace simgrid::mc::udpor {
+
+/**
+ * @brief An iterator over the tree of sets of (non-empty) maximal events that
+ * can be generated from a given configuration
+ *
+ * This iterator traverses all possible sets of maximal events that
+ * can be formed from a configuration, each of which satisfy a predicate.
+ *
+ * Iteration over the maximal events of a configuration is an important
+ * step in computing the extension set of a configuration for an action
+ * whose identity is not "exploitable" (i.e. one whose type information cannot
+ * help us narrow down our search).
+ */
+struct maximal_subsets_iterator
+    : public boost::iterator_facade<maximal_subsets_iterator, const EventSet, boost::forward_traversal_tag> {
+public:
+  // A function which answers the question "do I need to consider maximal sets
+  // that contain this node?"
+  using node_filter_function       = std::function<bool(const UnfoldingEvent*)>;
+  using topological_order_position = std::vector<const UnfoldingEvent*>::const_iterator;
+
+  maximal_subsets_iterator() = default;
+  explicit maximal_subsets_iterator(const Configuration& config) : maximal_subsets_iterator(config, std::nullopt) {}
+  maximal_subsets_iterator(const Configuration& config, std::optional<node_filter_function> filter);
+
+private:
+  const std::optional<std::reference_wrapper<const Configuration>> config;
+  std::vector<const UnfoldingEvent*> topological_ordering;
+
+  // The boolean is a bit of an annoyance, but it works. Effectively,
+  // there's no way to distinguish between "we're starting the search
+  // after the empty set" and "we've finished the search" since the resulting
+  // maximal set and backtracking point stack will both be empty in both cases
+  bool has_started_searching                              = false;
+  std::optional<EventSet> current_maximal_set             = std::nullopt;
+  std::stack<topological_order_position> backtrack_points = std::stack<topological_order_position>();
+
+  /**
+   * @brief A small class which provides functionality for managing
+   * the "counts" as the iterator proceeds forward in time
+   *
+   * As an instance of the `maximal_subsets_iterator` traverses
+   * the configuration, it keeps track of how many events
+   * further down in the causality tree have been signaled as in-conflict
+   * with events that are its current maximal event set (i.e.
+   * its `current_maximal_set`)
+   */
+  struct bookkeeper {
+  public:
+    using topological_order_position = maximal_subsets_iterator::topological_order_position;
+
+    void mark_included_in_maximal_set(const UnfoldingEvent*);
+    void mark_removed_from_maximal_set(const UnfoldingEvent*);
+    topological_order_position find_next_candidate_event(topological_order_position first,
+                                                         topological_order_position last) const;
+
+  private:
+    std::unordered_map<const UnfoldingEvent*, unsigned> event_counts;
+
+    /// @brief Whether or not the given event, according to the
+    /// bookkeeping that has been done thus far, can be added to the
+    /// current candidate maximal set
+    bool is_candidate_event(const UnfoldingEvent*) const;
+
+  } bookkeeper;
+
+  void add_element_to_current_maximal_set(const UnfoldingEvent*);
+  void remove_element_from_current_maximal_set(const UnfoldingEvent*);
+
+  /**
+   * @brief Moves to the next node in the topological ordering
+   * by continuing the search in the tree of maximal event sets
+   * from where we currently believe we are in the tree
+   *
+   * At each stage of the iteration, the iterator points to
+   * a maximal event set that can be thought of as `R` + `A`:
+   *
+   * |   R    | A
+   * +--------+
+   *
+   * where `R` is some set of events and `A` is another event.
+   *
+   * The iterator first tries expansion from `R` + `A`. If it finds
+   * node `B` to expand, this means that there is a node in the tree of
+   * maximal event sets of `C` (the configuration traversed) such that
+   * `R` + `A` + `B` needs to be checked.
+   *
+   * If no such node is found, then the iterator must check `R` +
+   * some other node AFTER `A`. The new set of possibilities potentially
+   * includes some of `A`'s dependencies, so their counts are decremented
+   * prior to searching.
+   *
+   * @note: This method is a mutating method: it manipulates the
+   * iterator such that the iterator refers to the next maximal
+   * set sans the element returned. The `increment()` function performs
+   * the rest of the work needed to actually complete the transition
+   *
+   * @returns an iterator poiting to the event that should next
+   * be added to the set of maximal events if such an event exists,
+   * or to the end of the topological ordering if no such event exists
+   */
+  topological_order_position continue_traversal_of_maximal_events_tree();
+
+  // boost::iterator_facade<...> interface to implement
+  void increment();
+  bool equal(const maximal_subsets_iterator& other) const { return current_maximal_set == other.current_maximal_set; }
+  const EventSet& dereference() const
+  {
+    static const EventSet empty_set = EventSet();
+    if (current_maximal_set.has_value()) {
+      return current_maximal_set.value();
+    }
+    return empty_set;
+  }
+
+  // Allows boost::iterator_facade<...> to function properly
+  friend class boost::iterator_core_access;
+};
+
+} // namespace simgrid::mc::udpor
+#endif
index f420c1c..8e7b1ac 100644 (file)
@@ -533,6 +533,8 @@ set(MC_SRC
   src/mc/explo/udpor/EventSet.hpp
   src/mc/explo/udpor/History.cpp
   src/mc/explo/udpor/History.hpp
+  src/mc/explo/udpor/maximal_subsets_iterator.cpp
+  src/mc/explo/udpor/maximal_subsets_iterator.hpp
   src/mc/explo/udpor/UnfoldingEvent.cpp
   src/mc/explo/udpor/UnfoldingEvent.hpp
   src/mc/explo/udpor/Unfolding.cpp