Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Merge branch 'udpor-phase7' into 'master'
[simgrid.git] / src / mc / explo / udpor / Configuration_test.cpp
1 /* Copyright (c) 2017-2023. The SimGrid Team. All rights reserved.               */
2
3 /* This program is free software; you can redistribute it and/or modify it
4  * under the terms of the license (GNU LGPL) which comes with this package. */
5
6 #include "src/3rd-party/catch.hpp"
7 #include "src/mc/explo/udpor/Configuration.hpp"
8 #include "src/mc/explo/udpor/EventSet.hpp"
9 #include "src/mc/explo/udpor/History.hpp"
10 #include "src/mc/explo/udpor/Unfolding.hpp"
11 #include "src/mc/explo/udpor/UnfoldingEvent.hpp"
12 #include "src/mc/explo/udpor/maximal_subsets_iterator.hpp"
13 #include "src/mc/explo/udpor/udpor_tests_private.hpp"
14
15 #include <unordered_map>
16
17 using namespace simgrid::mc;
18 using namespace simgrid::mc::udpor;
19
20 TEST_CASE("simgrid::mc::udpor::Configuration: Constructing Configurations")
21 {
22   // The following tests concern the given event structure:
23   //                e1
24   //              /
25   //            e2
26   //           /
27   //          e3
28   //         /  /
29   //        e4   e5
30   UnfoldingEvent e1(EventSet(), std::make_shared<IndependentAction>(0));
31   UnfoldingEvent e2(EventSet({&e1}), std::make_shared<IndependentAction>(1));
32   UnfoldingEvent e3(EventSet({&e2}), std::make_shared<IndependentAction>(2));
33   UnfoldingEvent e4(EventSet({&e3}), std::make_shared<IndependentAction>(3));
34   UnfoldingEvent e5(EventSet({&e3}), std::make_shared<IndependentAction>(4));
35
36   SECTION("Creating a configuration without events")
37   {
38     Configuration C;
39     REQUIRE(C.get_events().empty());
40     REQUIRE(C.get_latest_event() == nullptr);
41   }
42
43   SECTION("Creating a configuration with events (test violation of being causally closed)")
44   {
45     // 5 choose 0 = 1 test
46     REQUIRE_NOTHROW(Configuration({&e1}));
47
48     // 5 choose 1 = 5 tests
49     REQUIRE_THROWS_AS(Configuration({&e2}), std::invalid_argument);
50     REQUIRE_THROWS_AS(Configuration({&e3}), std::invalid_argument);
51     REQUIRE_THROWS_AS(Configuration({&e4}), std::invalid_argument);
52     REQUIRE_THROWS_AS(Configuration({&e5}), std::invalid_argument);
53
54     // 5 choose 2 = 10 tests
55     REQUIRE_NOTHROW(Configuration({&e1, &e2}));
56     REQUIRE_THROWS_AS(Configuration({&e1, &e3}), std::invalid_argument);
57     REQUIRE_THROWS_AS(Configuration({&e1, &e4}), std::invalid_argument);
58     REQUIRE_THROWS_AS(Configuration({&e1, &e5}), std::invalid_argument);
59     REQUIRE_THROWS_AS(Configuration({&e2, &e3}), std::invalid_argument);
60     REQUIRE_THROWS_AS(Configuration({&e2, &e4}), std::invalid_argument);
61     REQUIRE_THROWS_AS(Configuration({&e2, &e5}), std::invalid_argument);
62     REQUIRE_THROWS_AS(Configuration({&e3, &e4}), std::invalid_argument);
63     REQUIRE_THROWS_AS(Configuration({&e3, &e5}), std::invalid_argument);
64     REQUIRE_THROWS_AS(Configuration({&e4, &e5}), std::invalid_argument);
65
66     // 5 choose 3 = 10 tests
67     REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3}));
68     REQUIRE_THROWS_AS(Configuration({&e1, &e2, &e4}), std::invalid_argument);
69     REQUIRE_THROWS_AS(Configuration({&e1, &e2, &e5}), std::invalid_argument);
70     REQUIRE_THROWS_AS(Configuration({&e1, &e3, &e4}), std::invalid_argument);
71     REQUIRE_THROWS_AS(Configuration({&e1, &e3, &e5}), std::invalid_argument);
72     REQUIRE_THROWS_AS(Configuration({&e1, &e4, &e5}), std::invalid_argument);
73     REQUIRE_THROWS_AS(Configuration({&e2, &e3, &e4}), std::invalid_argument);
74     REQUIRE_THROWS_AS(Configuration({&e2, &e3, &e5}), std::invalid_argument);
75     REQUIRE_THROWS_AS(Configuration({&e2, &e4, &e5}), std::invalid_argument);
76     REQUIRE_THROWS_AS(Configuration({&e3, &e4, &e5}), std::invalid_argument);
77
78     // 5 choose 4 = 5 tests
79     REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3, &e4}));
80     REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3, &e5}));
81     REQUIRE_THROWS_AS(Configuration({&e1, &e2, &e4, &e5}), std::invalid_argument);
82     REQUIRE_THROWS_AS(Configuration({&e1, &e3, &e4, &e5}), std::invalid_argument);
83     REQUIRE_THROWS_AS(Configuration({&e2, &e3, &e4, &e5}), std::invalid_argument);
84
85     // 5 choose 5 = 1 test
86     REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3, &e4, &e5}));
87   }
88 }
89
90 TEST_CASE("simgrid::mc::udpor::Configuration: Adding Events")
91 {
92   // The following tests concern the given event structure:
93   //                e1
94   //              /
95   //            e2
96   //           /
97   //         /  /
98   //        e3   e4
99   UnfoldingEvent e1(EventSet(), std::make_shared<IndependentAction>(0));
100   UnfoldingEvent e2(EventSet({&e1}), std::make_shared<IndependentAction>(1));
101   UnfoldingEvent e3(EventSet({&e2}), std::make_shared<IndependentAction>(2));
102   UnfoldingEvent e4(EventSet({&e2}), std::make_shared<IndependentAction>(3));
103
104   REQUIRE_THROWS_AS(Configuration().add_event(nullptr), std::invalid_argument);
105   REQUIRE_THROWS_AS(Configuration().add_event(&e2), std::invalid_argument);
106   REQUIRE_THROWS_AS(Configuration().add_event(&e3), std::invalid_argument);
107   REQUIRE_THROWS_AS(Configuration().add_event(&e4), std::invalid_argument);
108   REQUIRE_THROWS_AS(Configuration({&e1}).add_event(&e3), std::invalid_argument);
109   REQUIRE_THROWS_AS(Configuration({&e1}).add_event(&e4), std::invalid_argument);
110
111   REQUIRE_NOTHROW(Configuration().add_event(&e1));
112   REQUIRE_NOTHROW(Configuration({&e1}).add_event(&e1));
113   REQUIRE_NOTHROW(Configuration({&e1}).add_event(&e2));
114   REQUIRE_NOTHROW(Configuration({&e1, &e2}).add_event(&e1));
115   REQUIRE_NOTHROW(Configuration({&e1, &e2}).add_event(&e2));
116   REQUIRE_NOTHROW(Configuration({&e1, &e2}).add_event(&e3));
117   REQUIRE_NOTHROW(Configuration({&e1, &e2}).add_event(&e4));
118   REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3}).add_event(&e1));
119   REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3}).add_event(&e2));
120   REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3}).add_event(&e3));
121   REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3}).add_event(&e4));
122   REQUIRE_NOTHROW(Configuration({&e1, &e2, &e4}).add_event(&e1));
123   REQUIRE_NOTHROW(Configuration({&e1, &e2, &e4}).add_event(&e2));
124   REQUIRE_NOTHROW(Configuration({&e1, &e2, &e4}).add_event(&e3));
125   REQUIRE_NOTHROW(Configuration({&e1, &e2, &e4}).add_event(&e4));
126   REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3, &e4}).add_event(&e1));
127   REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3, &e4}).add_event(&e2));
128   REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3, &e4}).add_event(&e3));
129   REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3, &e4}).add_event(&e4));
130 }
131
132 TEST_CASE("simgrid::mc::udpor::Configuration: Topological Sort Order")
133 {
134   // The following tests concern the given event structure:
135   //               e1
136   //              /
137   //            e2
138   //           /
139   //          e3
140   //         /
141   //        e4
142   UnfoldingEvent e1(EventSet(), std::make_shared<IndependentAction>(1));
143   UnfoldingEvent e2(EventSet({&e1}), std::make_shared<IndependentAction>(2));
144   UnfoldingEvent e3(EventSet({&e2}), std::make_shared<IndependentAction>(3));
145   UnfoldingEvent e4(EventSet({&e3}), std::make_shared<IndependentAction>(4));
146
147   SECTION("Topological ordering for entire set")
148   {
149     Configuration C{&e1, &e2, &e3, &e4};
150     REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e4});
151     REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() ==
152             std::vector<const UnfoldingEvent*>{&e4, &e3, &e2, &e1});
153   }
154
155   SECTION("Topological ordering for subsets")
156   {
157     SECTION("No elements")
158     {
159       Configuration C;
160       REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{});
161       REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<const UnfoldingEvent*>{});
162     }
163
164     SECTION("e1 only")
165     {
166       Configuration C{&e1};
167       REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1});
168       REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<const UnfoldingEvent*>{&e1});
169     }
170
171     SECTION("e1 and e2 only")
172     {
173       Configuration C{&e1, &e2};
174       REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2});
175       REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<const UnfoldingEvent*>{&e2, &e1});
176     }
177
178     SECTION("e1, e2, and e3 only")
179     {
180       Configuration C{&e1, &e2, &e3};
181       REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3});
182       REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() ==
183               std::vector<const UnfoldingEvent*>{&e3, &e2, &e1});
184     }
185   }
186 }
187
188 TEST_CASE("simgrid::mc::udpor::Configuration: Topological Sort Order More Complicated")
189 {
190   // The following tests concern the given event structure:
191   //                e1
192   //              /
193   //            e2
194   //           /
195   //          e3
196   //         /  /
197   //        e4   e6
198   //        /
199   //       e5
200   UnfoldingEvent e1(EventSet(), std::make_shared<IndependentAction>(1));
201   UnfoldingEvent e2(EventSet({&e1}), std::make_shared<IndependentAction>(2));
202   UnfoldingEvent e3(EventSet({&e2}), std::make_shared<IndependentAction>(3));
203   UnfoldingEvent e4(EventSet({&e3}), std::make_shared<IndependentAction>(4));
204   UnfoldingEvent e5(EventSet({&e4}), std::make_shared<IndependentAction>(5));
205   UnfoldingEvent e6(EventSet({&e3}), std::make_shared<IndependentAction>(6));
206
207   SECTION("Topological ordering for subsets")
208   {
209     SECTION("No elements")
210     {
211       Configuration C;
212       REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{});
213       REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<const UnfoldingEvent*>{});
214     }
215
216     SECTION("e1 only")
217     {
218       Configuration C{&e1};
219       REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1});
220       REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<const UnfoldingEvent*>{&e1});
221     }
222
223     SECTION("e1 and e2 only")
224     {
225       Configuration C{&e1, &e2};
226       REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2});
227       REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<const UnfoldingEvent*>{&e2, &e1});
228     }
229
230     SECTION("e1, e2, and e3 only")
231     {
232       Configuration C{&e1, &e2, &e3};
233       REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3});
234       REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() ==
235               std::vector<const UnfoldingEvent*>{&e3, &e2, &e1});
236     }
237
238     SECTION("e1, e2, e3, and e6 only")
239     {
240       Configuration C{&e1, &e2, &e3, &e6};
241       REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e6});
242       REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() ==
243               std::vector<const UnfoldingEvent*>{&e6, &e3, &e2, &e1});
244     }
245
246     SECTION("e1, e2, e3, and e4 only")
247     {
248       Configuration C{&e1, &e2, &e3, &e4};
249       REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e4});
250       REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() ==
251               std::vector<const UnfoldingEvent*>{&e4, &e3, &e2, &e1});
252     }
253
254     SECTION("e1, e2, e3, e4, and e5 only")
255     {
256       Configuration C{&e1, &e2, &e3, &e4, &e5};
257       REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e4, &e5});
258       REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() ==
259               std::vector<const UnfoldingEvent*>{&e5, &e4, &e3, &e2, &e1});
260     }
261
262     SECTION("e1, e2, e3, e4 and e6 only")
263     {
264       // In this case, e4 and e6 are interchangeable. Hence, we have to check
265       // if the sorting gives us *any* of the combinations
266       Configuration C{&e1, &e2, &e3, &e4, &e6};
267       REQUIRE((C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e4, &e6} ||
268                C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e6, &e4}));
269       REQUIRE((C.get_topologically_sorted_events_of_reverse_graph() ==
270                    std::vector<const UnfoldingEvent*>{&e6, &e4, &e3, &e2, &e1} ||
271                C.get_topologically_sorted_events_of_reverse_graph() ==
272                    std::vector<const UnfoldingEvent*>{&e4, &e6, &e3, &e2, &e1}));
273     }
274
275     SECTION("Topological ordering for entire set")
276     {
277       // In this case, e4/e5 are both interchangeable with e6. Hence, again we have to check
278       // if the sorting gives us *any* of the combinations
279       Configuration C{&e1, &e2, &e3, &e4, &e5, &e6};
280       REQUIRE(
281           (C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e4, &e5, &e6} ||
282            C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e4, &e6, &e5} ||
283            C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e6, &e4, &e5}));
284       REQUIRE((C.get_topologically_sorted_events_of_reverse_graph() ==
285                    std::vector<const UnfoldingEvent*>{&e6, &e5, &e4, &e3, &e2, &e1} ||
286                C.get_topologically_sorted_events_of_reverse_graph() ==
287                    std::vector<const UnfoldingEvent*>{&e5, &e6, &e4, &e3, &e2, &e1} ||
288                C.get_topologically_sorted_events_of_reverse_graph() ==
289                    std::vector<const UnfoldingEvent*>{&e5, &e4, &e6, &e3, &e2, &e1}));
290     }
291   }
292 }
293
294 TEST_CASE("simgrid::mc::udpor::Configuration: Topological Sort Order Very Complicated")
295 {
296   // The following tests concern the given event structure:
297   //                e1
298   //              /   /
299   //            e2    e8
300   //           /  /    /  /
301   //          e3   /   /    /
302   //         /  /    /      e11
303   //        e4  e6   e7
304   //        /   /  /   /
305   //       e5   e9    e10
306   //        /   /     /
307   //         /  /   /
308   //         [   e12    ]
309   UnfoldingEvent e1(EventSet(), std::make_shared<IndependentAction>(1));
310   UnfoldingEvent e2(EventSet({&e1}), std::make_shared<IndependentAction>(2));
311   UnfoldingEvent e8(EventSet({&e1}), std::make_shared<IndependentAction>(3));
312   UnfoldingEvent e3(EventSet({&e2}), std::make_shared<IndependentAction>(4));
313   UnfoldingEvent e4(EventSet({&e3}), std::make_shared<IndependentAction>(5));
314   UnfoldingEvent e5(EventSet({&e4}), std::make_shared<IndependentAction>(6));
315   UnfoldingEvent e6(EventSet({&e4}), std::make_shared<IndependentAction>(7));
316   UnfoldingEvent e7(EventSet({&e2, &e8}), std::make_shared<IndependentAction>(8));
317   UnfoldingEvent e9(EventSet({&e6, &e7}), std::make_shared<IndependentAction>(9));
318   UnfoldingEvent e10(EventSet({&e7}), std::make_shared<IndependentAction>(10));
319   UnfoldingEvent e11(EventSet({&e8}), std::make_shared<IndependentAction>(11));
320   UnfoldingEvent e12(EventSet({&e5, &e9, &e10}), std::make_shared<IndependentAction>(12));
321   Configuration C{&e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12};
322
323   SECTION("Test every combination of the maximal configuration (forward graph)")
324   {
325     // To test this, we ensure that for the `i`th event
326     // in `ordered_events`, each event in `ordered_events[0...<i]
327     // is contained in the history of `ordered_events[i]`.
328     EventSet events_seen;
329     const auto ordered_events = C.get_topologically_sorted_events();
330
331     std::for_each(ordered_events.begin(), ordered_events.end(), [&events_seen](const UnfoldingEvent* e) {
332       History history(e);
333       for (auto* e_hist : history) {
334         // In this demo, we want to make sure that
335         // we don't mark not yet seeing `e` as an error.
336         // The history of `e` traverses `e` itself. All
337         // other events in e's history are included in
338         if (e_hist == e)
339           continue;
340
341         // If this event has not been "seen" before,
342         // this implies that event `e` appears earlier
343         // in the list than one of its dependencies
344         REQUIRE(events_seen.contains(e_hist));
345       }
346       events_seen.insert(e);
347     });
348   }
349
350   SECTION("Test every combination of the maximal configuration (reverse graph)")
351   {
352     // To test this, we ensure that for the `i`th event
353     // in `ordered_events`, no event in `ordered_events[0...<i]
354     // is contained in the history of `ordered_events[i]`.
355     EventSet events_seen;
356     const auto ordered_events = C.get_topologically_sorted_events_of_reverse_graph();
357
358     std::for_each(ordered_events.begin(), ordered_events.end(), [&events_seen](const UnfoldingEvent* e) {
359       History history(e);
360
361       for (auto* e_hist : history) {
362         // Unlike the test above, we DO want to ensure
363         // that `e` itself ALSO isn't yet seen
364
365         // If this event has been "seen" before,
366         // this implies that event `e` appears later
367         // in the list than one of its ancestors
368         REQUIRE_FALSE(events_seen.contains(e_hist));
369       }
370       events_seen.insert(e);
371     });
372   }
373
374   SECTION("Test that the topological ordering contains only the events of the configuration")
375   {
376     const EventSet events_seen = C.get_events();
377
378     SECTION("Forward direction")
379     {
380       auto ordered_events              = C.get_topologically_sorted_events();
381       const EventSet ordered_event_set = EventSet(std::move(ordered_events));
382       REQUIRE(events_seen == ordered_event_set);
383     }
384
385     SECTION("Reverse direction")
386     {
387       auto ordered_events              = C.get_topologically_sorted_events_of_reverse_graph();
388       const EventSet ordered_event_set = EventSet(std::move(ordered_events));
389       REQUIRE(events_seen == ordered_event_set);
390     }
391   }
392
393   SECTION("Test that the topological ordering is equivalent to that of the configuration's events")
394   {
395     REQUIRE(C.get_topologically_sorted_events() == C.get_events().get_topological_ordering());
396     REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() ==
397             C.get_events().get_topological_ordering_of_reverse_graph());
398   }
399 }
400
401 TEST_CASE("simgrid::mc::udpor::maximal_subsets_iterator: Basic Testing of Maximal Subsets")
402 {
403   // The following tests concern the given event structure:
404   //                e1
405   //              /   /
406   //             e2   e5
407   //            /     /
408   //           e3    e6
409   //           /     / /
410   //          e4    e7 e8
411   UnfoldingEvent e1(EventSet(), std::make_shared<IndependentAction>(0));
412   UnfoldingEvent e2(EventSet({&e1}), std::make_shared<IndependentAction>(1));
413   UnfoldingEvent e3(EventSet({&e2}), std::make_shared<IndependentAction>(2));
414   UnfoldingEvent e4(EventSet({&e3}), std::make_shared<IndependentAction>(3));
415   UnfoldingEvent e5(EventSet({&e1}), std::make_shared<IndependentAction>(4));
416   UnfoldingEvent e6(EventSet({&e5}), std::make_shared<IndependentAction>(5));
417   UnfoldingEvent e7(EventSet({&e6}), std::make_shared<IndependentAction>(6));
418   UnfoldingEvent e8(EventSet({&e6}), std::make_shared<IndependentAction>(7));
419
420   SECTION("Iteration over an empty configuration yields only the empty set")
421   {
422     Configuration C;
423     maximal_subsets_iterator first(C);
424     maximal_subsets_iterator last;
425
426     REQUIRE(*first == EventSet());
427     ++first;
428     REQUIRE(first == last);
429   }
430
431   SECTION("Check counts of maximal event sets discovered")
432   {
433     std::unordered_map<int, int> maximal_subset_counts;
434
435     Configuration C{&e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8};
436     maximal_subsets_iterator first(C);
437     maximal_subsets_iterator last;
438
439     for (; first != last; ++first) {
440       maximal_subset_counts[(*first).size()]++;
441     }
442
443     // First, ensure that there are only sets of size 0, 1, 2, and 3
444     CHECK(maximal_subset_counts.size() == 4);
445
446     // The empty set should appear only once
447     REQUIRE(maximal_subset_counts[0] == 1);
448
449     // 8 is the number of nodes in the graph
450     REQUIRE(maximal_subset_counts[1] == 8);
451
452     // 13 = 3 * 4 (each of the left branch can combine with one in the right branch) + 1 (e7 + e8)
453     REQUIRE(maximal_subset_counts[2] == 13);
454
455     // e7 + e8 must be included, so that means we can combine from the left branch
456     REQUIRE(maximal_subset_counts[3] == 3);
457   }
458
459   SECTION("Check counts of maximal event sets discovered with a filter")
460   {
461     std::unordered_map<int, int> maximal_subset_counts;
462
463     Configuration C{&e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8};
464
465     SECTION("Filter with events part of initial maximal set")
466     {
467       EventSet interesting_bunch{&e2, &e4, &e7, &e8};
468
469       maximal_subsets_iterator first(C, [&](const UnfoldingEvent* e) { return interesting_bunch.contains(e); });
470       maximal_subsets_iterator last;
471
472       for (; first != last; ++first) {
473         const auto& event_set = *first;
474         // Only events in `interesting_bunch` can appear: thus no set
475         // should include anything else other than `interesting_bunch`
476         REQUIRE(event_set.is_subset_of(interesting_bunch));
477         REQUIRE(event_set.is_maximal());
478         maximal_subset_counts[event_set.size()]++;
479       }
480
481       // The empty set should (still) appear only once
482       REQUIRE(maximal_subset_counts[0] == 1);
483
484       // 4 is the number of nodes in the `interesting_bunch`
485       REQUIRE(maximal_subset_counts[1] == 4);
486
487       // 5 = 2 * 2 (each of the left branch can combine with one in the right branch) + 1 (e7 + e8)
488       REQUIRE(maximal_subset_counts[2] == 5);
489
490       // e7 + e8 must be included, so that means we can combine from the left branch (only e2 and e4)
491       REQUIRE(maximal_subset_counts[3] == 2);
492
493       // There are no subsets of size 4 (or higher, but that
494       // is tested by asserting each maximal set traversed is a subset)
495       REQUIRE(maximal_subset_counts[4] == 0);
496     }
497
498     SECTION("Filter with interesting subset not initially part of the maximal set")
499     {
500       EventSet interesting_bunch{&e3, &e5, &e6};
501
502       maximal_subsets_iterator first(C, [&](const UnfoldingEvent* e) { return interesting_bunch.contains(e); });
503       maximal_subsets_iterator last;
504
505       for (; first != last; ++first) {
506         const auto& event_set = *first;
507         // Only events in `interesting_bunch` can appear: thus no set
508         // should include anything else other than `interesting_bunch`
509         REQUIRE(event_set.is_subset_of(interesting_bunch));
510         REQUIRE(event_set.is_maximal());
511         maximal_subset_counts[event_set.size()]++;
512       }
513
514       // The empty set should (still) appear only once
515       REQUIRE(maximal_subset_counts[0] == 1);
516
517       // 3 is the number of nodes in the `interesting_bunch`
518       REQUIRE(maximal_subset_counts[1] == 3);
519
520       // 2 = e3, e5 and e3, e6
521       REQUIRE(maximal_subset_counts[2] == 2);
522
523       // There are no subsets of size 3 (or higher, but that
524       // is tested by asserting each maximal set traversed is a subset)
525       REQUIRE(maximal_subset_counts[3] == 0);
526     }
527   }
528 }
529
530 TEST_CASE("simgrid::mc::udpor::maximal_subsets_iterator: Stress Test for Maximal Subsets Iteration")
531 {
532   // The following tests concern the given event structure:
533   //                              e1
534   //                            /   /
535   //                          e2    e3
536   //                          / /   /  /
537   //               +------* e4 *e5 e6  e7
538   //               |        /   ///   /  /
539   //               |       e8   e9    e10
540   //               |      /  /   /\      /
541   //               |   e11 e12 e13 e14   e15
542   //               |   /      / / /   /  /
543   //               +-> e16     e17     e18
544   UnfoldingEvent e1(EventSet(), std::make_shared<IndependentAction>(1));
545   UnfoldingEvent e2(EventSet({&e1}), std::make_shared<IndependentAction>(2));
546   UnfoldingEvent e3(EventSet({&e1}), std::make_shared<IndependentAction>(3));
547   UnfoldingEvent e4(EventSet({&e2}), std::make_shared<IndependentAction>(4));
548   UnfoldingEvent e5(EventSet({&e2}), std::make_shared<IndependentAction>(5));
549   UnfoldingEvent e6(EventSet({&e3}), std::make_shared<IndependentAction>(6));
550   UnfoldingEvent e7(EventSet({&e3}), std::make_shared<IndependentAction>(7));
551   UnfoldingEvent e8(EventSet({&e4}), std::make_shared<IndependentAction>(8));
552   UnfoldingEvent e9(EventSet({&e4, &e5, &e6}), std::make_shared<IndependentAction>(9));
553   UnfoldingEvent e10(EventSet({&e6, &e7}), std::make_shared<IndependentAction>(10));
554   UnfoldingEvent e11(EventSet({&e8}), std::make_shared<IndependentAction>(11));
555   UnfoldingEvent e12(EventSet({&e8}), std::make_shared<IndependentAction>(12));
556   UnfoldingEvent e13(EventSet({&e9}), std::make_shared<IndependentAction>(13));
557   UnfoldingEvent e14(EventSet({&e9}), std::make_shared<IndependentAction>(14));
558   UnfoldingEvent e15(EventSet({&e10}), std::make_shared<IndependentAction>(15));
559   UnfoldingEvent e16(EventSet({&e5, &e11}), std::make_shared<IndependentAction>(16));
560   UnfoldingEvent e17(EventSet({&e12, &e13, &e14}), std::make_shared<IndependentAction>(17));
561   UnfoldingEvent e18(EventSet({&e14, &e15}), std::make_shared<IndependentAction>(18));
562   Configuration C{&e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14, &e15, &e16, &e17, &e18};
563
564   SECTION("Every subset iterated over is maximal")
565   {
566     maximal_subsets_iterator first(C);
567     maximal_subsets_iterator last;
568
569     // Make sure we actually have something to iterate over
570     REQUIRE(first != last);
571
572     for (; first != last; ++first) {
573       REQUIRE((*first).size() <= C.get_events().size());
574       REQUIRE((*first).is_maximal());
575     }
576   }
577
578   SECTION("Test that the maximal set ordering is equivalent to that of the configuration's events")
579   {
580     maximal_subsets_iterator first_config(C);
581     maximal_subsets_iterator first_events(C.get_events());
582     maximal_subsets_iterator last;
583
584     // Make sure we actually have something to iterate over
585     REQUIRE(first_config != last);
586     REQUIRE(first_config == first_events);
587     REQUIRE(first_events != last);
588
589     for (; first_config != last; ++first_config, ++first_events) {
590       // first_events and first_config should always be at the same location
591       REQUIRE(first_events != last);
592       const auto& first_config_set = *first_config;
593       const auto& first_events_set = *first_events;
594
595       REQUIRE(first_config_set.size() <= C.get_events().size());
596       REQUIRE(first_config_set.is_maximal());
597       REQUIRE(first_events_set == first_config_set);
598     }
599
600     // Iteration with events directly should now also be finished
601     REQUIRE(first_events == last);
602   }
603 }
604
605 TEST_CASE("simgrid::mc::udpor::Configuration: Latest Transitions")
606 {
607   // The following tests concern the given event structure (labeled as "event(actor)")
608   //                  e1(1)
609   //                 /     /
610   //              e2(1)   e3(2)
611   //              /    //     /
612   //            e4(3) e5(2)  e6(1)
613   //                  /   /
614   //               e7(1) e8(1)
615   const auto t1 = std::make_shared<IndependentAction>(Transition::Type::UNKNOWN, 1);
616   const auto t2 = std::make_shared<IndependentAction>(Transition::Type::UNKNOWN, 1);
617   const auto t3 = std::make_shared<IndependentAction>(Transition::Type::UNKNOWN, 2);
618   const auto t4 = std::make_shared<IndependentAction>(Transition::Type::UNKNOWN, 3);
619   const auto t5 = std::make_shared<IndependentAction>(Transition::Type::UNKNOWN, 2);
620   const auto t6 = std::make_shared<IndependentAction>(Transition::Type::UNKNOWN, 1);
621   const auto t7 = std::make_shared<IndependentAction>(Transition::Type::UNKNOWN, 1);
622   const auto t8 = std::make_shared<IndependentAction>(Transition::Type::UNKNOWN, 1);
623
624   const UnfoldingEvent e1(EventSet(), t1);
625   const UnfoldingEvent e2(EventSet({&e1}), t2);
626   const UnfoldingEvent e3(EventSet({&e1}), t3);
627   const UnfoldingEvent e4(EventSet({&e2}), t4);
628   const UnfoldingEvent e5(EventSet({&e2, &e3}), t5);
629   const UnfoldingEvent e6(EventSet({&e3}), t6);
630   const UnfoldingEvent e7(EventSet({&e5}), t7);
631   const UnfoldingEvent e8(EventSet({&e5}), t8);
632
633   SECTION("Test that the latest events are correct on initialization")
634   {
635     SECTION("Empty configuration has no events")
636     {
637       Configuration C;
638       REQUIRE_FALSE(C.get_latest_event_of(1).has_value());
639       REQUIRE_FALSE(C.get_latest_event_of(2).has_value());
640       REQUIRE_FALSE(C.get_latest_event_of(3).has_value());
641
642       REQUIRE_FALSE(C.get_latest_action_of(1).has_value());
643       REQUIRE_FALSE(C.get_latest_action_of(2).has_value());
644       REQUIRE_FALSE(C.get_latest_action_of(3).has_value());
645     }
646
647     SECTION("Missing two actors")
648     {
649       Configuration C{&e1};
650       REQUIRE(C.get_latest_event_of(1).has_value());
651       REQUIRE(C.get_latest_event_of(1).value() == &e1);
652
653       REQUIRE_FALSE(C.get_latest_event_of(2).has_value());
654       REQUIRE_FALSE(C.get_latest_event_of(3).has_value());
655
656       REQUIRE(C.get_latest_action_of(1).has_value());
657       REQUIRE(C.get_latest_action_of(1).value() == t1.get());
658
659       REQUIRE_FALSE(C.get_latest_action_of(2).has_value());
660       REQUIRE_FALSE(C.get_latest_action_of(3).has_value());
661     }
662
663     SECTION("Two events with one actor yields the latest event")
664     {
665       Configuration C{&e1, &e2};
666       REQUIRE(C.get_latest_event_of(1).has_value());
667       REQUIRE(C.get_latest_event_of(1).value() == &e2);
668
669       REQUIRE_FALSE(C.get_latest_event_of(2).has_value());
670       REQUIRE_FALSE(C.get_latest_event_of(3).has_value());
671
672       REQUIRE(C.get_latest_action_of(1).has_value());
673       REQUIRE(C.get_latest_action_of(1).value() == t2.get());
674
675       REQUIRE_FALSE(C.get_latest_action_of(2).has_value());
676       REQUIRE_FALSE(C.get_latest_action_of(3).has_value());
677     }
678
679     SECTION("Two events with two actors")
680     {
681       Configuration C{&e1, &e3};
682       REQUIRE(C.get_latest_event_of(1).has_value());
683       REQUIRE(C.get_latest_event_of(1).value() == &e1);
684
685       REQUIRE(C.get_latest_event_of(2).has_value());
686       REQUIRE(C.get_latest_event_of(2).value() == &e3);
687
688       REQUIRE_FALSE(C.get_latest_event_of(3).has_value());
689
690       REQUIRE(C.get_latest_action_of(1).has_value());
691       REQUIRE(C.get_latest_action_of(1).value() == t1.get());
692
693       REQUIRE(C.get_latest_action_of(2).has_value());
694       REQUIRE(C.get_latest_action_of(2).value() == t3.get());
695
696       REQUIRE_FALSE(C.get_latest_action_of(3).has_value());
697     }
698
699     SECTION("Three different actors actors")
700     {
701       Configuration C{&e1, &e2, &e3, &e4, &e5};
702       REQUIRE(C.get_latest_event_of(1).has_value());
703       REQUIRE(C.get_latest_event_of(1).value() == &e2);
704
705       REQUIRE(C.get_latest_event_of(2).has_value());
706       REQUIRE(C.get_latest_event_of(2).value() == &e5);
707
708       REQUIRE(C.get_latest_event_of(3).has_value());
709       REQUIRE(C.get_latest_event_of(3).value() == &e4);
710
711       REQUIRE(C.get_latest_action_of(1).has_value());
712       REQUIRE(C.get_latest_action_of(1).value() == t2.get());
713
714       REQUIRE(C.get_latest_action_of(2).has_value());
715       REQUIRE(C.get_latest_action_of(2).value() == t5.get());
716
717       REQUIRE(C.get_latest_action_of(3).has_value());
718       REQUIRE(C.get_latest_action_of(3).value() == t4.get());
719     }
720   }
721
722   SECTION("Test that the latest events are correct when adding new events")
723   {
724     Configuration C;
725     REQUIRE_FALSE(C.get_latest_event_of(1).has_value());
726     REQUIRE_FALSE(C.get_latest_event_of(2).has_value());
727     REQUIRE_FALSE(C.get_latest_event_of(3).has_value());
728     REQUIRE_FALSE(C.get_latest_action_of(1).has_value());
729     REQUIRE_FALSE(C.get_latest_action_of(2).has_value());
730     REQUIRE_FALSE(C.get_latest_action_of(3).has_value());
731
732     C.add_event(&e1);
733     REQUIRE(C.get_latest_event_of(1).has_value());
734     REQUIRE(C.get_latest_event_of(1).value() == &e1);
735     REQUIRE_FALSE(C.get_latest_event_of(2).has_value());
736     REQUIRE_FALSE(C.get_latest_event_of(3).has_value());
737     REQUIRE(C.get_latest_action_of(1).has_value());
738     REQUIRE(C.get_latest_action_of(1).value() == t1.get());
739     REQUIRE_FALSE(C.get_latest_action_of(2).has_value());
740     REQUIRE_FALSE(C.get_latest_action_of(3).has_value());
741
742     C.add_event(&e2);
743     REQUIRE(C.get_latest_event_of(1).has_value());
744     REQUIRE(C.get_latest_event_of(1).value() == &e2);
745     REQUIRE_FALSE(C.get_latest_event_of(2).has_value());
746     REQUIRE_FALSE(C.get_latest_event_of(3).has_value());
747     REQUIRE(C.get_latest_action_of(1).has_value());
748     REQUIRE(C.get_latest_action_of(1).value() == t2.get());
749     REQUIRE_FALSE(C.get_latest_action_of(2).has_value());
750     REQUIRE_FALSE(C.get_latest_action_of(3).has_value());
751
752     C.add_event(&e3);
753     REQUIRE(C.get_latest_event_of(1).has_value());
754     REQUIRE(C.get_latest_event_of(1).value() == &e2);
755     REQUIRE(C.get_latest_event_of(2).has_value());
756     REQUIRE(C.get_latest_event_of(2).value() == &e3);
757     REQUIRE_FALSE(C.get_latest_event_of(3).has_value());
758     REQUIRE(C.get_latest_action_of(1).has_value());
759     REQUIRE(C.get_latest_action_of(1).value() == t2.get());
760     REQUIRE(C.get_latest_action_of(2).has_value());
761     REQUIRE(C.get_latest_action_of(2).value() == t3.get());
762     REQUIRE_FALSE(C.get_latest_action_of(3).has_value());
763
764     C.add_event(&e4);
765     REQUIRE(C.get_latest_event_of(1).has_value());
766     REQUIRE(C.get_latest_event_of(1).value() == &e2);
767     REQUIRE(C.get_latest_event_of(2).has_value());
768     REQUIRE(C.get_latest_event_of(2).value() == &e3);
769     REQUIRE(C.get_latest_event_of(3).has_value());
770     REQUIRE(C.get_latest_event_of(3).value() == &e4);
771     REQUIRE(C.get_latest_action_of(1).has_value());
772     REQUIRE(C.get_latest_action_of(1).value() == t2.get());
773     REQUIRE(C.get_latest_action_of(2).has_value());
774     REQUIRE(C.get_latest_action_of(2).value() == t3.get());
775     REQUIRE(C.get_latest_action_of(3).has_value());
776     REQUIRE(C.get_latest_action_of(3).value() == t4.get());
777
778     C.add_event(&e5);
779     REQUIRE(C.get_latest_event_of(1).has_value());
780     REQUIRE(C.get_latest_event_of(1).value() == &e2);
781     REQUIRE(C.get_latest_event_of(2).has_value());
782     REQUIRE(C.get_latest_event_of(2).value() == &e5);
783     REQUIRE(C.get_latest_event_of(3).has_value());
784     REQUIRE(C.get_latest_event_of(3).value() == &e4);
785     REQUIRE(C.get_latest_action_of(1).has_value());
786     REQUIRE(C.get_latest_action_of(1).value() == t2.get());
787     REQUIRE(C.get_latest_action_of(2).has_value());
788     REQUIRE(C.get_latest_action_of(2).value() == t5.get());
789     REQUIRE(C.get_latest_action_of(3).has_value());
790     REQUIRE(C.get_latest_action_of(3).value() == t4.get());
791   }
792 }
793
794 TEST_CASE("simgrid::mc::udpor::Configuration: Computing Full Alternatives in Reader/Writer Example")
795 {
796   // The following tests concern the given event structure that is given as
797   // an example in figure 1 of the original UDPOR paper.
798   //                  e0
799   //              /  /   /
800   //            e1   e4   e7
801   //           /     /  //   /
802   //         /  /   e5  e8   e9
803   //        e2 e3   /        /
804   //               e6       e10
805   //
806   // Theses tests walk through exactly the configurations and sets of `D` that
807   // UDPOR COULD encounter as it walks through the unfolding. Note that
808   // if there are multiple alternatives to any given configuration, UDPOR can
809   // continue searching any one of them. The sequence assumes UDPOR first picks `e1`,
810   // then `e4`, and then `e7`
811   Unfolding U;
812
813   auto e0 = std::make_unique<UnfoldingEvent>(
814       EventSet(), std::make_shared<ConditionallyDependentAction>(Transition::Type::UNKNOWN, 0));
815   auto e0_handle = e0.get();
816
817   auto e1        = std::make_unique<UnfoldingEvent>(EventSet({e0_handle}),
818                                              std::make_shared<DependentAction>(Transition::Type::UNKNOWN, 0));
819   auto e1_handle = e1.get();
820
821   auto e2 = std::make_unique<UnfoldingEvent>(
822       EventSet({e1_handle}), std::make_shared<ConditionallyDependentAction>(Transition::Type::UNKNOWN, 1));
823   auto e2_handle = e2.get();
824
825   auto e3 = std::make_unique<UnfoldingEvent>(
826       EventSet({e1_handle}), std::make_shared<ConditionallyDependentAction>(Transition::Type::UNKNOWN, 2));
827   auto e3_handle = e3.get();
828
829   auto e4 = std::make_unique<UnfoldingEvent>(
830       EventSet({e0_handle}), std::make_shared<ConditionallyDependentAction>(Transition::Type::UNKNOWN, 1));
831   auto e4_handle = e4.get();
832
833   auto e5        = std::make_unique<UnfoldingEvent>(EventSet({e4_handle}),
834                                              std::make_shared<DependentAction>(Transition::Type::UNKNOWN, 0));
835   auto e5_handle = e5.get();
836
837   auto e6 = std::make_unique<UnfoldingEvent>(
838       EventSet({e5_handle}), std::make_shared<ConditionallyDependentAction>(Transition::Type::UNKNOWN, 2));
839   auto e6_handle = e6.get();
840
841   auto e7 = std::make_unique<UnfoldingEvent>(
842       EventSet({e0_handle}), std::make_shared<ConditionallyDependentAction>(Transition::Type::UNKNOWN, 2));
843   auto e7_handle = e7.get();
844
845   auto e8        = std::make_unique<UnfoldingEvent>(EventSet({e4_handle, e7_handle}),
846                                              std::make_shared<DependentAction>(Transition::Type::UNKNOWN, 0));
847   auto e8_handle = e8.get();
848
849   auto e9        = std::make_unique<UnfoldingEvent>(EventSet({e7_handle}),
850                                              std::make_shared<DependentAction>(Transition::Type::UNKNOWN, 0));
851   auto e9_handle = e9.get();
852
853   auto e10 = std::make_unique<UnfoldingEvent>(
854       EventSet({e9_handle}), std::make_shared<ConditionallyDependentAction>(Transition::Type::UNKNOWN, 1));
855   auto e10_handle = e10.get();
856
857   SECTION("Alternative computation call 1")
858   {
859     // During the first call to Alt(C, D + {e}),
860     // UDPOR believes `U` to be the following:
861     //
862     //                  e0
863     //              /  /   /
864     //            e1   e4   e7
865     //           /
866     //         /  /
867     //        e2 e3
868     //
869     // C := {e0, e1, e2} and `Explore(C, D, A)` picked `e3`
870     // (since en(C') where C' := {e0, e1, e2, e3} is empty
871     // [so UDPOR will simply return when C' is reached])
872     //
873     // Thus the computation is (since D is empty at first)
874     //
875     // Alt(C, D + {e}) --> Alt({e0, e1, e2}, {e3})
876     //
877     // where U is given above. There are no alternatives in
878     // this case since `e4` and `e7` conflict with `e1` (so
879     // they cannot be added to C to form a configuration)
880     const Configuration C{e0_handle, e1_handle, e2_handle};
881     const EventSet D_plus_e{e3_handle};
882
883     REQUIRE(U.empty());
884     U.insert(std::move(e0));
885     U.insert(std::move(e1));
886     U.insert(std::move(e2));
887     U.insert(std::move(e3));
888     U.insert(std::move(e4));
889     U.insert(std::move(e7));
890
891     const auto alternative = C.compute_alternative_to(D_plus_e, U);
892     REQUIRE_FALSE(alternative.has_value());
893   }
894
895   SECTION("Alternative computation call 2")
896   {
897     // During the second call to Alt(C, D + {e}),
898     // UDPOR believes `U` to be the following:
899     //
900     //                  e0
901     //              /  /   /
902     //            e1   e4   e7
903     //           /
904     //         /  /
905     //        e2 e3
906     //
907     // C := {e0, e1} and `Explore(C, D, A)` picked `e2`.
908     //
909     // Thus the computation is (since D is still empty)
910     //
911     // Alt(C, D + {e}) --> Alt({e0, e1}, {e2})
912     //
913     // where U is given above. There are no alternatives in
914     // this case since `e4` and `e7` conflict with `e1` (so
915     // they cannot be added to C to form a configuration) and
916     // e3 is NOT in conflict with either e0 or e1
917     const Configuration C{e0_handle, e1_handle};
918     const EventSet D_plus_e{e2_handle};
919
920     REQUIRE(U.empty());
921     U.insert(std::move(e0));
922     U.insert(std::move(e1));
923     U.insert(std::move(e2));
924     U.insert(std::move(e3));
925     U.insert(std::move(e4));
926     U.insert(std::move(e7));
927
928     const auto alternative = C.compute_alternative_to(D_plus_e, U);
929     REQUIRE_FALSE(alternative.has_value());
930   }
931
932   SECTION("Alternative computation call 3")
933   {
934     // During the thrid call to Alt(C, D + {e}),
935     // UDPOR believes `U` to be the following:
936     //
937     //                 e0
938     //              /  /   /
939     //            e1   e4   e7
940     //           /
941     //         /  /
942     //        e2 e3
943     //
944     // C := {e0} and `Explore(C, D, A)` picked `e1`.
945     //
946     // Thus the computation is (since D is still empty)
947     //
948     // Alt(C, D + {e}) --> Alt({e0}, {e1})
949     //
950     // where U is given above. There are two alternatives in this case:
951     // {e0, e4} and {e0, e7}. Either one would be a valid choice for
952     // UDPOR, so we must check for the precense of either
953     const Configuration C{e0_handle};
954     const EventSet D_plus_e{e1_handle};
955
956     REQUIRE(U.empty());
957     U.insert(std::move(e0));
958     U.insert(std::move(e1));
959     U.insert(std::move(e2));
960     U.insert(std::move(e3));
961     U.insert(std::move(e4));
962     U.insert(std::move(e7));
963
964     const auto alternative = C.compute_alternative_to(D_plus_e, U);
965     REQUIRE(alternative.has_value());
966
967     // The first alternative that is found is the one that is chosen. Since
968     // traversal over the elements of an unordered_set<> are not guaranteed,
969     // both {e0, e4} and {e0, e7} are valid alternatives
970     REQUIRE((alternative.value().get_events() == EventSet({e0_handle, e4_handle}) or
971              alternative.value().get_events() == EventSet({e0_handle, e7_handle})));
972   }
973
974   SECTION("Alternative computation call 4")
975   {
976     // During the fourth call to Alt(C, D + {e}),
977     // UDPOR believes `U` to be the following:
978     //
979     //                  e0
980     //              /  /   /
981     //            e1   e4   e7
982     //           /     /  //
983     //         /  /   e5  e8
984     //        e2 e3   /
985     //               e6
986     //
987     // C := {e0, e4, e5} and `Explore(C, D, A)` picked `e6`
988     // (since en(C') where C' := {e0, e4, e5, e6} is empty
989     // [so UDPOR will simply return when C' is reached])
990     //
991     // Thus the computation is (since D is {e1})
992     //
993     // Alt(C, D + {e}) --> Alt({e0, e4, e5}, {e1, e6})
994     //
995     // where U is given above. There are no alternatives in this
996     // case, since:
997     //
998     // 1.`e2/e3` are eliminated since their histories contain `e1`
999     // 2. `e7/e8` are eliminated because they conflict with `e5`
1000     const Configuration C{e0_handle, e4_handle, e5_handle};
1001     const EventSet D_plus_e{e1_handle, e6_handle};
1002
1003     REQUIRE(U.empty());
1004     U.insert(std::move(e0));
1005     U.insert(std::move(e1));
1006     U.insert(std::move(e2));
1007     U.insert(std::move(e3));
1008     U.insert(std::move(e4));
1009     U.insert(std::move(e6));
1010     U.insert(std::move(e7));
1011     U.insert(std::move(e8));
1012
1013     const auto alternative = C.compute_alternative_to(D_plus_e, U);
1014     REQUIRE_FALSE(alternative.has_value());
1015   }
1016
1017   SECTION("Alternative computation call 5")
1018   {
1019     // During the fifth call to Alt(C, D + {e}),
1020     // UDPOR believes `U` to be the following:
1021     //
1022     //                  e0
1023     //              /  /   /
1024     //            e1   e4   e7
1025     //           /     /  //
1026     //         /  /   e5  e8
1027     //        e2 e3   /
1028     //               e6
1029     //
1030     // C := {e0, e4} and `Explore(C, D, A)` picked `e5`
1031     // (since en(C') where C' := {e0, e4, e5, e6} is empty
1032     // [so UDPOR will simply return when C' is reached])
1033     //
1034     // Thus the computation is (since D is {e1})
1035     //
1036     // Alt(C, D + {e}) --> Alt({e0, e4}, {e1, e5})
1037     //
1038     // where U is given above. There are THREE alternatives in this case,
1039     // viz. {e0, e7}, {e0, e4, e7} and {e0, e4, e7, e8}.
1040     //
1041     // To continue the search, UDPOR computes J / C which in this
1042     // case gives {e7, e8}. Since `e8` is not in en(C), UDPOR will
1043     // choose `e7` next and add `e5` to `D`
1044     const Configuration C{e0_handle, e4_handle};
1045     const EventSet D_plus_e{e1_handle, e5_handle};
1046
1047     REQUIRE(U.empty());
1048     U.insert(std::move(e0));
1049     U.insert(std::move(e1));
1050     U.insert(std::move(e2));
1051     U.insert(std::move(e3));
1052     U.insert(std::move(e4));
1053     U.insert(std::move(e6));
1054     U.insert(std::move(e7));
1055     U.insert(std::move(e8));
1056     REQUIRE(U.size() == 8);
1057
1058     const auto alternative = C.compute_alternative_to(D_plus_e, U);
1059     REQUIRE(alternative.has_value());
1060     REQUIRE((alternative.value().get_events() == EventSet({e0_handle, e7_handle}) or
1061              alternative.value().get_events() == EventSet({e0_handle, e4_handle, e7_handle}) or
1062              alternative.value().get_events() == EventSet({e0_handle, e4_handle, e7_handle, e8_handle})));
1063   }
1064
1065   SECTION("Alternative computation call 6")
1066   {
1067     // During the sixth call to Alt(C, D + {e}),
1068     // UDPOR believes `U` to be the following:
1069     //
1070     //                 e0
1071     //              /  /   /
1072     //            e1   e4   e7
1073     //           /     /  //   /
1074     //         /  /   e5  e8   e9
1075     //        e2 e3   /
1076     //               e6
1077     //
1078     // C := {e0, e4, e7} and `Explore(C, D, A)` picked `e8`
1079     // (since en(C') where C' := {e0, e4, e7, e8} is empty
1080     // [so UDPOR will simply return when C' is reached])
1081     //
1082     // Thus the computation is (since D is {e1, e5} [see the last step])
1083     //
1084     // Alt(C, D + {e}) --> Alt({e0, e4, e7}, {e1, e5, e8})
1085     //
1086     // where U is given above. There are no alternatives in this case
1087     // since all `e9` conflicts with `e4` and all other events of `U`
1088     // are eliminated since their history intersects `D`
1089     const Configuration C{e0_handle, e4_handle, e7_handle};
1090     const EventSet D_plus_e{e1_handle, e5_handle, e8_handle};
1091
1092     REQUIRE(U.empty());
1093     U.insert(std::move(e0));
1094     U.insert(std::move(e1));
1095     U.insert(std::move(e2));
1096     U.insert(std::move(e3));
1097     U.insert(std::move(e4));
1098     U.insert(std::move(e6));
1099     U.insert(std::move(e7));
1100     U.insert(std::move(e8));
1101     U.insert(std::move(e9));
1102
1103     const auto alternative = C.compute_alternative_to(D_plus_e, U);
1104     REQUIRE_FALSE(alternative.has_value());
1105   }
1106
1107   SECTION("Alternative computation call 7")
1108   {
1109     // During the seventh call to Alt(C, D + {e}),
1110     // UDPOR believes `U` to be the following:
1111     //
1112     //                 e0
1113     //              /  /   /
1114     //            e1   e4   e7
1115     //           /     /  //   /
1116     //         /  /   e5  e8   e9
1117     //        e2 e3   /
1118     //               e6
1119     //
1120     // C := {e0, e4} and `Explore(C, D, A)` picked `e7`
1121     //
1122     // Thus the computation is (since D is {e1, e5} [see call 5])
1123     //
1124     // Alt(C, D + {e}) --> Alt({e0, e4}, {e1, e5, e7})
1125     //
1126     // where U is given above. There are no alternatives again in this case
1127     // since all `e9` conflicts with `e4` and all other events of `U`
1128     // are eliminated since their history intersects `D`
1129     const Configuration C{e0_handle, e4_handle};
1130     const EventSet D_plus_e{e1_handle, e5_handle, e7_handle};
1131
1132     REQUIRE(U.empty());
1133     U.insert(std::move(e0));
1134     U.insert(std::move(e1));
1135     U.insert(std::move(e2));
1136     U.insert(std::move(e3));
1137     U.insert(std::move(e4));
1138     U.insert(std::move(e6));
1139     U.insert(std::move(e7));
1140     U.insert(std::move(e8));
1141     U.insert(std::move(e9));
1142
1143     const auto alternative = C.compute_alternative_to(D_plus_e, U);
1144     REQUIRE_FALSE(alternative.has_value());
1145   }
1146
1147   SECTION("Alternative computation call 8")
1148   {
1149     // During the eigth call to Alt(C, D + {e}),
1150     // UDPOR believes `U` to be the following:
1151     //
1152     //                 e0
1153     //              /  /   /
1154     //            e1   e4   e7
1155     //           /     /  //   /
1156     //         /  /   e5  e8   e9
1157     //        e2 e3   /
1158     //               e6
1159     //
1160     // C := {e0} and `Explore(C, D, A)` picked `e4`. At this
1161     // point, UDPOR finished its recursive search of {e0, e4}
1162     // after having finished {e0, e1} prior.
1163     //
1164     // Thus the computation is (since D = {e1})
1165     //
1166     // Alt(C, D + {e}) --> Alt({e0}, {e1, e4})
1167     //
1168     // where U is given above. There is one alternative in this
1169     // case, viz {e0, e7, e9} since
1170     // 1. e9 conflicts with e4 in D
1171     // 2. e7 conflicts with e1 in D
1172     // 3. the set {e7, e9} is conflict-free since `e7 < e9`
1173     // 4. all other events are eliminated since their histories
1174     // intersect D
1175     //
1176     // UDPOR will continue its recursive search following `e7`
1177     // and add `e4` to D
1178     const Configuration C{e0_handle};
1179     const EventSet D_plus_e{e1_handle, e4_handle};
1180
1181     REQUIRE(U.empty());
1182     U.insert(std::move(e0));
1183     U.insert(std::move(e1));
1184     U.insert(std::move(e2));
1185     U.insert(std::move(e3));
1186     U.insert(std::move(e4));
1187     U.insert(std::move(e6));
1188     U.insert(std::move(e7));
1189     U.insert(std::move(e8));
1190     U.insert(std::move(e9));
1191
1192     const auto alternative = C.compute_alternative_to(D_plus_e, U);
1193     REQUIRE(alternative.has_value());
1194     REQUIRE(alternative.value().get_events() == EventSet({e0_handle, e7_handle, e9_handle}));
1195   }
1196
1197   SECTION("Alternative computation call 9")
1198   {
1199     // During the ninth call to Alt(C, D + {e}),
1200     // UDPOR believes `U` to be the following:
1201     //
1202     //                  e0
1203     //              /  /   /
1204     //            e1   e4   e7
1205     //           /     /  //   /
1206     //         /  /   e5  e8   e9
1207     //        e2 e3   /        /
1208     //               e6       e10
1209     //
1210     // C := {e0, e7, e9} and `Explore(C, D, A)` picked `e10`.
1211     // (since en(C') where C' := {e0, e7, e9, e10} is empty
1212     // [so UDPOR will simply return when C' is reached]).
1213     //
1214     // Thus the computation is (since D = {e1, e4} [see the previous step])
1215     //
1216     // Alt(C, D + {e}) --> Alt({e0}, {e1, e4, e10})
1217     //
1218     // where U is given above. There are no alternatives in this case
1219     const Configuration C{e0_handle, e7_handle, e9_handle};
1220     const EventSet D_plus_e{e1_handle, e4_handle, e10_handle};
1221
1222     REQUIRE(U.empty());
1223     U.insert(std::move(e0));
1224     U.insert(std::move(e1));
1225     U.insert(std::move(e2));
1226     U.insert(std::move(e3));
1227     U.insert(std::move(e4));
1228     U.insert(std::move(e6));
1229     U.insert(std::move(e7));
1230     U.insert(std::move(e8));
1231     U.insert(std::move(e9));
1232     U.insert(std::move(e10));
1233
1234     const auto alternative = C.compute_alternative_to(D_plus_e, U);
1235     REQUIRE_FALSE(alternative.has_value());
1236   }
1237
1238   SECTION("Alternative computation call 10")
1239   {
1240     // During the tenth call to Alt(C, D + {e}),
1241     // UDPOR believes `U` to be the following:
1242     //
1243     //                  e0
1244     //              /  /   /
1245     //            e1   e4   e7
1246     //           /     /  //   /
1247     //         /  /   e5  e8   e9
1248     //        e2 e3   /        /
1249     //               e6       e10
1250     //
1251     // C := {e0, e7} and `Explore(C, D, A)` picked `e9`.
1252     //
1253     // Thus the computation is (since D = {e1, e4} [see call 8])
1254     //
1255     // Alt(C, D + {e}) --> Alt({e0}, {e1, e4, e9})
1256     //
1257     // where U is given above. There are no alternatives in this case
1258     const Configuration C{e0_handle, e7_handle};
1259     const EventSet D_plus_e{e1_handle, e4_handle, e9_handle};
1260
1261     REQUIRE(U.empty());
1262     U.insert(std::move(e0));
1263     U.insert(std::move(e1));
1264     U.insert(std::move(e2));
1265     U.insert(std::move(e3));
1266     U.insert(std::move(e4));
1267     U.insert(std::move(e6));
1268     U.insert(std::move(e7));
1269     U.insert(std::move(e8));
1270     U.insert(std::move(e9));
1271     U.insert(std::move(e10));
1272
1273     const auto alternative = C.compute_alternative_to(D_plus_e, U);
1274     REQUIRE_FALSE(alternative.has_value());
1275   }
1276
1277   SECTION("Alternative computation call 11 (final call)")
1278   {
1279     // During the eleventh and final call to Alt(C, D + {e}),
1280     // UDPOR believes `U` to be the following:
1281     //
1282     //                  e0
1283     //              /  /   /
1284     //            e1   e4   e7
1285     //           /     /  //   /
1286     //         /  /   e5  e8   e9
1287     //        e2 e3   /        /
1288     //               e6       e10
1289     //
1290     // C := {e0} and `Explore(C, D, A)` picked `e7`.
1291     //
1292     // Thus the computation is (since D = {e1, e4} [see call 8])
1293     //
1294     // Alt(C, D + {e}) --> Alt({e0}, {e1, e4, e7})
1295     //
1296     // where U is given above. There are no alternatives in this case:
1297     // everyone is eliminated!
1298     const Configuration C{e0_handle, e7_handle};
1299     const EventSet D_plus_e{e1_handle, e4_handle, e9_handle};
1300
1301     REQUIRE(U.empty());
1302     U.insert(std::move(e0));
1303     U.insert(std::move(e1));
1304     U.insert(std::move(e2));
1305     U.insert(std::move(e3));
1306     U.insert(std::move(e4));
1307     U.insert(std::move(e6));
1308     U.insert(std::move(e7));
1309     U.insert(std::move(e8));
1310     U.insert(std::move(e9));
1311     U.insert(std::move(e10));
1312
1313     const auto alternative = C.compute_alternative_to(D_plus_e, U);
1314     REQUIRE_FALSE(alternative.has_value());
1315   }
1316
1317   SECTION("Alternative computation next")
1318   {
1319     SECTION("Followed {e0, e7} first")
1320     {
1321       const EventSet D{e1_handle, e7_handle};
1322       const Configuration C{e0_handle};
1323
1324       REQUIRE(U.empty());
1325       U.insert(std::move(e0));
1326       U.insert(std::move(e1));
1327       U.insert(std::move(e2));
1328       U.insert(std::move(e3));
1329       U.insert(std::move(e4));
1330       U.insert(std::move(e5));
1331       U.insert(std::move(e7));
1332       U.insert(std::move(e8));
1333       U.insert(std::move(e9));
1334       U.insert(std::move(e10));
1335
1336       const auto alternative = C.compute_alternative_to(D, U);
1337       REQUIRE(alternative.has_value());
1338
1339       // In this case, only {e0, e4} is a valid alternative
1340       REQUIRE(alternative.value().get_events() == EventSet({e0_handle, e4_handle, e5_handle}));
1341     }
1342
1343     SECTION("Followed {e0, e4} first")
1344     {
1345       const EventSet D{e1_handle, e4_handle};
1346       const Configuration C{e0_handle};
1347
1348       REQUIRE(U.empty());
1349       U.insert(std::move(e0));
1350       U.insert(std::move(e1));
1351       U.insert(std::move(e2));
1352       U.insert(std::move(e3));
1353       U.insert(std::move(e4));
1354       U.insert(std::move(e5));
1355       U.insert(std::move(e6));
1356       U.insert(std::move(e7));
1357       U.insert(std::move(e8));
1358       U.insert(std::move(e9));
1359
1360       const auto alternative = C.compute_alternative_to(D, U);
1361       REQUIRE(alternative.has_value());
1362
1363       // In this case, only {e0, e7} is a valid alternative
1364       REQUIRE(alternative.value().get_events() == EventSet({e0_handle, e7_handle, e9_handle}));
1365     }
1366   }
1367 }