Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Add first unit tests for topological sorting
authorMaxwell Pirtle <maxwellpirtle@gmail.com>
Thu, 23 Feb 2023 10:17:27 +0000 (11:17 +0100)
committerMaxwell Pirtle <maxwellpirtle@gmail.com>
Fri, 24 Feb 2023 09:00:42 +0000 (10:00 +0100)
A few unit tests were added to support topological
sorting of events in a configuration. In the future,
a more robust test will be added which ensures that each
newly-discovered event in the reverse order has never
been discovered before

src/mc/explo/udpor/Configuration.cpp
src/mc/explo/udpor/Configuration.hpp
src/mc/explo/udpor/Configuration_test.cpp

index e2e2c3a..8fa1dd6 100644 (file)
@@ -45,7 +45,7 @@ void Configuration::add_event(UnfoldingEvent* e)
   }
 }
 
-std::vector<UnfoldingEvent*> Configuration::get_topogolically_sorted_events_of_reverse_graph() const
+std::vector<UnfoldingEvent*> Configuration::get_topologically_sorted_events() const
 {
   if (events_.empty()) {
     return std::vector<UnfoldingEvent*>();
@@ -69,35 +69,52 @@ std::vector<UnfoldingEvent*> Configuration::get_topogolically_sorted_events_of_r
         // detect a cycle and if we see it again here
         // we can detect that the node is re-processed
         temporarily_marked_events.insert(evt);
-      } else {
 
+        EventSet immediate_causes = evt->get_immediate_causes();
+        if (!immediate_causes.empty() && immediate_causes.is_subset_of(temporarily_marked_events)) {
+          throw std::invalid_argument("Attempted to perform a topological sort on a configuration "
+                                      "whose contents contain a cycle. The configuration (and the graph "
+                                      "connecting all of the events) is an invalid event structure");
+        }
+        immediate_causes.subtract(discovered_events);
+        immediate_causes.subtract(permanently_marked_events);
+        const EventSet undiscovered_causes = std::move(immediate_causes);
+
+        for (const auto cause : undiscovered_causes) {
+          event_stack.push(cause);
+        }
+      } else {
         // Mark this event as:
         // 1. discovered across all DFSs performed
-        // 2. part of the topological search
+        // 2. permanently marked
+        // 3. part of the topological search
+        unknown_events.remove(evt);
         temporarily_marked_events.remove(evt);
         permanently_marked_events.insert(evt);
+
+        // In moving this event to the end of the list,
+        // we are saying this events "happens before" other
+        // events that are added later.
         topological_ordering.push_back(evt);
 
         // Only now do we remove the event, i.e. once
         // we've processed the same event again
         event_stack.pop();
       }
-
-      const EventSet immediate_causes = evt->get_immediate_causes();
-      if (immediate_causes.is_subset_of(temporarily_marked_events)) {
-        throw std::invalid_argument("Attempted to perform a topological sort on a configuration "
-                                    "whose contents contain a cycle. The configuration (and the graph "
-                                    "connecting all of the events) is an invalid event structure");
-      }
-      const EventSet undiscovered_causes = std::move(immediate_causes).subtracting(discovered_events);
-
-      for (const auto cause : undiscovered_causes) {
-        event_stack.push(cause);
-      }
     }
   }
-
   return topological_ordering;
 }
 
+std::vector<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
+  // of some graph G is simply the reverse of any
+  // topological sorting S of G.
+  auto topological_events = get_topologically_sorted_events();
+  std::reverse(topological_events.begin(), topological_events.end());
+  return topological_events;
+}
+
 } // namespace simgrid::mc::udpor
index eb365d5..1ccb9e8 100644 (file)
@@ -61,9 +61,45 @@ public:
   void add_event(UnfoldingEvent* e);
 
   /**
+   * @brief Orders the events of the configuration such that
+   * "more recent" events (i.e. those that are farther down in
+   * the event structure's dependency chain) come after those
+   * that appeared "farther in the past"
    *
+   * @returns a vector `V` with the following property:
+   *
+   * 1. Let i(e) := C -> I map events to their indices in `V`.
+   * For every pair of events e, e' in C, if e < e' then i(e) < i(e')
+   *
+   * Intuitively, events that are closer to the "bottom" of the event
+   * structure appear farther along in the list than those that appear
+   * closer to the "top"
+   */
+  std::vector<UnfoldingEvent*> get_topologically_sorted_events() const;
+
+  /**
+   * @brief Orders the events of the configuration such that
+   * "more recent" events (i.e. those that are farther down in
+   * the event structure's dependency chain) come before those
+   * that appear "farther in the past"
+   *
+   * @note The events of the event structure are arranged such that
+   * e < e' implies a directed edge from e to e'. However, it is
+   * also useful to be able to traverse the *reverse* graph (for
+   * example when computing the compatibility graph of a configuration),
+   * hence the distinction between "reversed" and the method
+   * "Configuration::get_topologically_sorted_events()"
+   *
+   * @returns a vector `V` with the following property:
+   *
+   * 1. Let i(e) := C -> I map events to their indices in `V`.
+   * For every pair of events e, e' in C, if e < e' then i(e) > i(e')
+   *
+   * Intuitively, events that are closer to the "top" of the event
+   * structure appear farther along in the list than those that appear
+   * closer to the "bottom"
    */
-  std::vector<UnfoldingEvent*> get_topogolically_sorted_events_of_reverse_graph() const;
+  std::vector<UnfoldingEvent*> get_topologically_sorted_events_of_reverse_graph() const;
 
 private:
   /**
index 3ba2795..10d8c87 100644 (file)
@@ -120,3 +120,158 @@ TEST_CASE("simgrid::mc::udpor::Configuration: Adding Events")
   REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3, &e4}).add_event(&e3));
   REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3, &e4}).add_event(&e4));
 }
+
+TEST_CASE("simgrid::mc::udpor::Configuration: Topological Sort Order")
+{
+  // The following tests concern the given event structure:
+  //               e1
+  //              /
+  //            e2
+  //           /
+  //          e3
+  //         /
+  //        e4
+  UnfoldingEvent e1;
+  UnfoldingEvent e2{&e1};
+  UnfoldingEvent e3{&e2};
+  UnfoldingEvent e4{&e3};
+
+  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});
+  }
+
+  SECTION("Topological ordering for subsets")
+  {
+    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*>{});
+    }
+
+    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});
+    }
+
+    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});
+    }
+
+    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});
+    }
+  }
+}
+
+TEST_CASE("simgrid::mc::udpor::Configuration: Topological Sort Order More Complicated")
+{
+  // The following tests concern the given event structure:
+  //                e1
+  //              /
+  //            e2
+  //           /
+  //          e3
+  //         /  /
+  //        e4   e6
+  //        /
+  //       e5
+  UnfoldingEvent e1;
+  UnfoldingEvent e2{&e1};
+  UnfoldingEvent e3{&e2};
+  UnfoldingEvent e4{&e3}, e6{&e3};
+  UnfoldingEvent e5{&e4};
+
+  SECTION("Topological ordering for subsets")
+  {
+    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*>{});
+    }
+
+    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});
+    }
+
+    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});
+    }
+
+    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});
+    }
+
+    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});
+    }
+
+    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});
+    }
+
+    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_of_reverse_graph() ==
+              std::vector<UnfoldingEvent*>{&e5, &e4, &e3, &e2, &e1});
+    }
+
+    SECTION("e1, e2, e3, e4 and e6 only")
+    {
+      // 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_of_reverse_graph() ==
+                   std::vector<UnfoldingEvent*>{&e6, &e4, &e3, &e2, &e1} ||
+               C.get_topologically_sorted_events_of_reverse_graph() ==
+                   std::vector<UnfoldingEvent*>{&e4, &e6, &e3, &e2, &e1}));
+    }
+
+    SECTION("Topological ordering for entire set")
+    {
+      // 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_of_reverse_graph() ==
+                   std::vector<UnfoldingEvent*>{&e6, &e5, &e4, &e3, &e2, &e1} ||
+               C.get_topologically_sorted_events_of_reverse_graph() ==
+                   std::vector<UnfoldingEvent*>{&e5, &e6, &e4, &e3, &e2, &e1} ||
+               C.get_topologically_sorted_events_of_reverse_graph() ==
+                   std::vector<UnfoldingEvent*>{&e5, &e4, &e6, &e3, &e2, &e1}));
+    }
+  }
+}
\ No newline at end of file