Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Add monotonically-increasing IDs for UnfoldingEvent
authorMaxwell Pirtle <maxwellpirtle@gmail.com>
Tue, 18 Apr 2023 08:17:51 +0000 (10:17 +0200)
committerMaxwell Pirtle <maxwellpirtle@gmail.com>
Fri, 9 Jun 2023 07:58:43 +0000 (09:58 +0200)
UDPOR is inherently non-deterministic in the
sense that multiple routes may exist that UDPOR
can follow from any given configuration. Correctness
and optimality are not affected by these results;
however, we seek for SimGrid to have a deterministic
execution on all platforms while running UDPOR.
Prior to this commit, arbitrarily the first event in
an unordered set was selected. But since the "first"
event in an unordered set is implementation-defined,
we seek to avoid leaving it up to the standard library's
ordering and define instead an ordering ourselves.

This motivates assigning a monotonically-increasing
ID to each newly-created event. Note that since
events may be created and then ultimately
destroyed after UDPOR realizes it has created a duplicate
event (which can happen when computing the extension
set of two configurations whose extension sets overlap),
events that exist in the unfolding need not contain
a contiguous range of IDs.

To provide a deterministic order, we must first process
the transitions that are enabled from any given state
in a determinstic order so that events are discovered
in a determinstic order. Furthermore, we must always
pick events from the intersection of en(C) and A in
a determinstic order. Here, we always pick events
with the smallest assigned ID first.

src/mc/explo/UdporChecker.cpp
src/mc/explo/udpor/EventSet.cpp
src/mc/explo/udpor/EventSet.hpp
src/mc/explo/udpor/ExtensionSetCalculator.cpp
src/mc/explo/udpor/UnfoldingEvent.cpp
src/mc/explo/udpor/UnfoldingEvent.hpp

index 6bd87a6..74ead2a 100644 (file)
@@ -150,6 +150,11 @@ EventSet UdporChecker::compute_exC(const Configuration& C, const State& stateC,
   EventSet exC                = prev_exC;
   exC.remove(e_cur);
 
+  // IMPORTANT NOTE: In order to have deterministic results, we need to process
+  // the actors in a deterministic manner so that events are discovered by
+  // UDPOR in a deterministic order. The processing done here always processes
+  // actors in a consistent order since `std::map` is by-default ordered using
+  // `std::less<Key>` (see the return type of `State::get_actors_list()`)
   for (const auto& [aid, actor_state] : stateC.get_actors_list()) {
     for (const auto& transition : actor_state.get_enabled_transitions()) {
       XBT_DEBUG("\t Considering partial extension for %s", transition->to_string().c_str());
@@ -236,16 +241,23 @@ UnfoldingEvent* UdporChecker::select_next_unfolding_event(const EventSet& A, con
                                 "empty before attempting to select an event from it?");
   }
 
+  // UDPOR's exploration is non-deterministic (as is DPOR's)
+  // in the sense that at any given point there may
+  // be multiple paths that can be followed. The correctness and optimality
+  // of the algorithm remains unaffected by the route taken by UDPOR when
+  // given multiple choices; but to ensure that SimGrid itself has deterministic
+  // behavior on all platforms, we always pick events with lower id's
+  // to ensure we explore the unfolding deterministically.
   if (A.empty()) {
-    return const_cast<UnfoldingEvent*>(*(enC.begin()));
-  }
-
-  for (const auto& event : A) {
-    if (enC.contains(event)) {
-      return const_cast<UnfoldingEvent*>(event);
-    }
+    const auto min_event = std::min_element(enC.begin(), enC.end(),
+                                            [](const auto e1, const auto e2) { return e1->get_id() < e2->get_id(); });
+    return const_cast<UnfoldingEvent*>(*min_event);
+  } else {
+    const auto intersection = A.make_intersection(enC);
+    const auto min_event    = std::min_element(intersection.begin(), intersection.end(),
+                                               [](const auto e1, const auto e2) { return e1->get_id() < e2->get_id(); });
+    return const_cast<UnfoldingEvent*>(*min_event);
   }
-  return nullptr;
 }
 
 void UdporChecker::clean_up_explore(const UnfoldingEvent* e, const Configuration& C, const EventSet& D)
index 0485cd8..feef1dd 100644 (file)
@@ -90,6 +90,19 @@ EventSet EventSet::make_union(const Configuration& config) const
   return make_union(config.get_events());
 }
 
+EventSet EventSet::make_intersection(const EventSet& other) const
+{
+  std::unordered_set<const UnfoldingEvent*> result;
+
+  for (const UnfoldingEvent* e : other.events_) {
+    if (contains(e)) {
+      result.insert(e);
+    }
+  }
+
+  return EventSet(std::move(result));
+}
+
 EventSet EventSet::get_local_config() const
 {
   return History(*this).get_all_events();
index 67314f6..9c09185 100644 (file)
@@ -50,6 +50,7 @@ public:
   EventSet make_union(const UnfoldingEvent*) const;
   EventSet make_union(const EventSet&) const;
   EventSet make_union(const Configuration&) const;
+  EventSet make_intersection(const EventSet&) const;
   EventSet get_local_config() const;
 
   size_t size() const;
index 6a1a8ca..dfbe926 100644 (file)
@@ -65,8 +65,8 @@ EventSet ExtensionSetCalculator::partially_extend_CommSend(const Configuration&
   }
 
   // 2. foreach e ∈ C s.t. λ(e) ∈ {AsyncSend(m, _), TestAny(Com)} where
-  // Com contains a matching c' = AsyncReceive(m, _) with a
-  for (const auto* e : C) {
+  // Com contains a matching c' = AsyncReceive(m, _) with `action`
+  for (const auto e : C) {
     const bool transition_type_check = [&]() {
       if (const auto* async_send = dynamic_cast<const CommSendTransition*>(e->get_transition());
           async_send != nullptr) {
@@ -111,7 +111,7 @@ EventSet ExtensionSetCalculator::partially_extend_CommRecv(const Configuration&
 
   // 2. foreach e ∈ C s.t. λ(e) ∈ {AsyncSend(m, _), TestAny(Com)} where
   // Com contains a matching c' = AsyncReceive(m, _) with a
-  for (const auto* e : C) {
+  for (const auto e : C) {
     const bool transition_type_check = [&]() {
       if (const auto* async_recv = dynamic_cast<const CommRecvTransition*>(e->get_transition());
           async_recv != nullptr && async_recv->get_mailbox() == recv_mailbox) {
@@ -288,8 +288,6 @@ EventSet ExtensionSetCalculator::partially_extend_CommWait(const Configuration&
         continue;
       }
 
-      // TODO: Compute the send and receive positions
-
       // What send # is the issuer
       const unsigned send_position = std::count_if(e_issuer_history.begin(), e_issuer_history.end(), [=](const auto e) {
         const CommSendTransition* e_send = dynamic_cast<const CommSendTransition*>(e->get_transition());
index b1d7382..2e7e20a 100644 (file)
@@ -20,6 +20,8 @@ UnfoldingEvent::UnfoldingEvent(std::initializer_list<const UnfoldingEvent*> init
 UnfoldingEvent::UnfoldingEvent(EventSet immediate_causes, std::shared_ptr<Transition> transition)
     : associated_transition(std::move(transition)), immediate_causes(std::move(immediate_causes))
 {
+  static uint64_t event_id = 0;
+  this->id                 = ++event_id;
 }
 
 bool UnfoldingEvent::operator==(const UnfoldingEvent& other) const
@@ -50,11 +52,13 @@ std::string UnfoldingEvent::to_string() const
 
   dependencies_string += "[";
   for (const auto* e : immediate_causes) {
+    dependencies_string += " ";
     dependencies_string += e->to_string();
+    dependencies_string += "and ";
   }
   dependencies_string += "]";
 
-  return xbt::string_printf("(%p) Actor %ld: %s (%zu dependencies: %s)", this, associated_transition->aid_,
+  return xbt::string_printf("Event %lu, Actor %ld: %s (%zu dependencies: %s)", this->id, associated_transition->aid_,
                             associated_transition->to_string().c_str(), immediate_causes.size(),
                             dependencies_string.c_str());
 }
index bad411c..5486709 100644 (file)
@@ -49,9 +49,10 @@ public:
   bool is_dependent_with(const Transition*) const;
   bool is_dependent_with(const UnfoldingEvent* other) const;
 
+  unsigned get_id() const { return this->id; }
+  aid_t get_actor() const { return get_transition()->aid_; }
   const EventSet& get_immediate_causes() const { return this->immediate_causes; }
   Transition* get_transition() const { return this->associated_transition.get(); }
-  aid_t get_actor() const { return get_transition()->aid_; }
 
   void set_transition(std::shared_ptr<Transition> t) { this->associated_transition = std::move(t); }
 
@@ -91,6 +92,12 @@ private:
    * so on.
    */
   EventSet immediate_causes;
+
+  /**
+   * @brief An identifier which is used to sort events
+   * deterministically
+   */
+  uint64_t id = 0;
 };
 
 } // namespace simgrid::mc::udpor