Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Begin tracking latest events for each actor
[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>());
31   UnfoldingEvent e2(EventSet({&e1}), std::make_shared<IndependentAction>());
32   UnfoldingEvent e3(EventSet({&e2}), std::make_shared<IndependentAction>());
33   UnfoldingEvent e4(EventSet({&e3}), std::make_shared<IndependentAction>());
34   UnfoldingEvent e5(EventSet({&e3}), std::make_shared<IndependentAction>());
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>());
100   UnfoldingEvent e2(EventSet({&e1}), std::make_shared<IndependentAction>());
101   UnfoldingEvent e3(EventSet({&e2}), std::make_shared<IndependentAction>());
102   UnfoldingEvent e4(EventSet({&e2}), std::make_shared<IndependentAction>());
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>());
143   UnfoldingEvent e2(EventSet({&e1}), std::make_shared<IndependentAction>());
144   UnfoldingEvent e3(EventSet({&e2}), std::make_shared<IndependentAction>());
145   UnfoldingEvent e4(EventSet({&e3}), std::make_shared<IndependentAction>());
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>());
201   UnfoldingEvent e2(EventSet({&e1}), std::make_shared<IndependentAction>());
202   UnfoldingEvent e3(EventSet({&e2}), std::make_shared<IndependentAction>());
203   UnfoldingEvent e4(EventSet({&e3}), std::make_shared<IndependentAction>());
204   UnfoldingEvent e5(EventSet({&e4}), std::make_shared<IndependentAction>());
205   UnfoldingEvent e6(EventSet({&e3}), std::make_shared<IndependentAction>());
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>());
310   UnfoldingEvent e2(EventSet({&e1}), std::make_shared<IndependentAction>());
311   UnfoldingEvent e8(EventSet({&e1}), std::make_shared<IndependentAction>());
312   UnfoldingEvent e3(EventSet({&e2}), std::make_shared<IndependentAction>());
313   UnfoldingEvent e4(EventSet({&e3}), std::make_shared<IndependentAction>());
314   UnfoldingEvent e5(EventSet({&e4}), std::make_shared<IndependentAction>());
315   UnfoldingEvent e6(EventSet({&e4}), std::make_shared<IndependentAction>());
316   UnfoldingEvent e7(EventSet({&e2, &e8}), std::make_shared<IndependentAction>());
317   UnfoldingEvent e9(EventSet({&e6, &e7}), std::make_shared<IndependentAction>());
318   UnfoldingEvent e10(EventSet({&e7}), std::make_shared<IndependentAction>());
319   UnfoldingEvent e11(EventSet({&e8}), std::make_shared<IndependentAction>());
320   UnfoldingEvent e12(EventSet({&e5, &e9, &e10}), std::make_shared<IndependentAction>());
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>());
412   UnfoldingEvent e2(EventSet({&e1}), std::make_shared<IndependentAction>());
413   UnfoldingEvent e3(EventSet({&e2}), std::make_shared<IndependentAction>());
414   UnfoldingEvent e4(EventSet({&e3}), std::make_shared<IndependentAction>());
415   UnfoldingEvent e5(EventSet({&e1}), std::make_shared<IndependentAction>());
416   UnfoldingEvent e6(EventSet({&e5}), std::make_shared<IndependentAction>());
417   UnfoldingEvent e7(EventSet({&e6}), std::make_shared<IndependentAction>());
418   UnfoldingEvent e8(EventSet({&e6}), std::make_shared<IndependentAction>());
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>());
545   UnfoldingEvent e2(EventSet({&e1}), std::make_shared<IndependentAction>());
546   UnfoldingEvent e3(EventSet({&e1}), std::make_shared<IndependentAction>());
547   UnfoldingEvent e4(EventSet({&e2}), std::make_shared<IndependentAction>());
548   UnfoldingEvent e5(EventSet({&e2}), std::make_shared<IndependentAction>());
549   UnfoldingEvent e6(EventSet({&e3}), std::make_shared<IndependentAction>());
550   UnfoldingEvent e7(EventSet({&e3}), std::make_shared<IndependentAction>());
551   UnfoldingEvent e8(EventSet({&e4}), std::make_shared<IndependentAction>());
552   UnfoldingEvent e9(EventSet({&e4, &e5, &e6}), std::make_shared<IndependentAction>());
553   UnfoldingEvent e10(EventSet({&e6, &e7}), std::make_shared<IndependentAction>());
554   UnfoldingEvent e11(EventSet({&e8}), std::make_shared<IndependentAction>());
555   UnfoldingEvent e12(EventSet({&e8}), std::make_shared<IndependentAction>());
556   UnfoldingEvent e13(EventSet({&e9}), std::make_shared<IndependentAction>());
557   UnfoldingEvent e14(EventSet({&e9}), std::make_shared<IndependentAction>());
558   UnfoldingEvent e15(EventSet({&e10}), std::make_shared<IndependentAction>());
559   UnfoldingEvent e16(EventSet({&e5, &e11}), std::make_shared<IndependentAction>());
560   UnfoldingEvent e17(EventSet({&e12, &e13, &e14}), std::make_shared<IndependentAction>());
561   UnfoldingEvent e18(EventSet({&e14, &e15}), std::make_shared<IndependentAction>());
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: Computing Full Alternatives in Reader/Writer Example")
606 {
607   // The following tests concern the given event structure that is given as
608   // an example in figure 1 of the original UDPOR paper.
609   //                  e0
610   //              /  /   /
611   //            e1   e4   e7
612   //           /     /  //   /
613   //         /  /   e5  e8   e9
614   //        e2 e3   /        /
615   //               e6       e10
616   //
617   // Theses tests walk through exactly the configurations and sets of `D` that
618   // UDPOR COULD encounter as it walks through the unfolding. Note that
619   // if there are multiple alternatives to any given configuration, UDPOR can
620   // continue searching any one of them. The sequence assumes UDPOR first picks `e1`,
621   // then `e4`, and then `e7`
622   Unfolding U;
623
624   auto e0 = std::make_unique<UnfoldingEvent>(
625       EventSet(), std::make_shared<ConditionallyDependentAction>(Transition::Type::UNKNOWN, 0));
626   auto e0_handle = e0.get();
627
628   auto e1        = std::make_unique<UnfoldingEvent>(EventSet({e0_handle}),
629                                              std::make_shared<DependentAction>(Transition::Type::UNKNOWN, 0));
630   auto e1_handle = e1.get();
631
632   auto e2 = std::make_unique<UnfoldingEvent>(
633       EventSet({e1_handle}), std::make_shared<ConditionallyDependentAction>(Transition::Type::UNKNOWN, 1));
634   auto e2_handle = e2.get();
635
636   auto e3 = std::make_unique<UnfoldingEvent>(
637       EventSet({e1_handle}), std::make_shared<ConditionallyDependentAction>(Transition::Type::UNKNOWN, 2));
638   auto e3_handle = e3.get();
639
640   auto e4 = std::make_unique<UnfoldingEvent>(
641       EventSet({e0_handle}), std::make_shared<ConditionallyDependentAction>(Transition::Type::UNKNOWN, 1));
642   auto e4_handle = e4.get();
643
644   auto e5        = std::make_unique<UnfoldingEvent>(EventSet({e4_handle}),
645                                              std::make_shared<DependentAction>(Transition::Type::UNKNOWN, 0));
646   auto e5_handle = e5.get();
647
648   auto e6 = std::make_unique<UnfoldingEvent>(
649       EventSet({e5_handle}), std::make_shared<ConditionallyDependentAction>(Transition::Type::UNKNOWN, 2));
650   auto e6_handle = e6.get();
651
652   auto e7 = std::make_unique<UnfoldingEvent>(
653       EventSet({e0_handle}), std::make_shared<ConditionallyDependentAction>(Transition::Type::UNKNOWN, 2));
654   auto e7_handle = e7.get();
655
656   auto e8        = std::make_unique<UnfoldingEvent>(EventSet({e4_handle, e7_handle}),
657                                              std::make_shared<DependentAction>(Transition::Type::UNKNOWN, 0));
658   auto e8_handle = e8.get();
659
660   auto e9        = std::make_unique<UnfoldingEvent>(EventSet({e7_handle}),
661                                              std::make_shared<DependentAction>(Transition::Type::UNKNOWN, 0));
662   auto e9_handle = e9.get();
663
664   auto e10 = std::make_unique<UnfoldingEvent>(
665       EventSet({e9_handle}), std::make_shared<ConditionallyDependentAction>(Transition::Type::UNKNOWN, 1));
666   auto e10_handle = e10.get();
667
668   SECTION("Alternative computation call 1")
669   {
670     // During the first call to Alt(C, D + {e}),
671     // UDPOR believes `U` to be the following:
672     //
673     //                  e0
674     //              /  /   /
675     //            e1   e4   e7
676     //           /
677     //         /  /
678     //        e2 e3
679     //
680     // C := {e0, e1, e2} and `Explore(C, D, A)` picked `e3`
681     // (since en(C') where C' := {e0, e1, e2, e3} is empty
682     // [so UDPOR will simply return when C' is reached])
683     //
684     // Thus the computation is (since D is empty at first)
685     //
686     // Alt(C, D + {e}) --> Alt({e0, e1, e2}, {e3})
687     //
688     // where U is given above. There are no alternatives in
689     // this case since `e4` and `e7` conflict with `e1` (so
690     // they cannot be added to C to form a configuration)
691     const Configuration C{e0_handle, e1_handle, e2_handle};
692     const EventSet D_plus_e{e3_handle};
693
694     REQUIRE(U.empty());
695     U.insert(std::move(e0));
696     U.insert(std::move(e1));
697     U.insert(std::move(e2));
698     U.insert(std::move(e3));
699     U.insert(std::move(e4));
700     U.insert(std::move(e7));
701
702     const auto alternative = C.compute_alternative_to(D_plus_e, U);
703     REQUIRE_FALSE(alternative.has_value());
704   }
705
706   SECTION("Alternative computation call 2")
707   {
708     // During the second call to Alt(C, D + {e}),
709     // UDPOR believes `U` to be the following:
710     //
711     //                  e0
712     //              /  /   /
713     //            e1   e4   e7
714     //           /
715     //         /  /
716     //        e2 e3
717     //
718     // C := {e0, e1} and `Explore(C, D, A)` picked `e2`.
719     //
720     // Thus the computation is (since D is still empty)
721     //
722     // Alt(C, D + {e}) --> Alt({e0, e1}, {e2})
723     //
724     // where U is given above. There are no alternatives in
725     // this case since `e4` and `e7` conflict with `e1` (so
726     // they cannot be added to C to form a configuration) and
727     // e3 is NOT in conflict with either e0 or e1
728     const Configuration C{e0_handle, e1_handle};
729     const EventSet D_plus_e{e2_handle};
730
731     REQUIRE(U.empty());
732     U.insert(std::move(e0));
733     U.insert(std::move(e1));
734     U.insert(std::move(e2));
735     U.insert(std::move(e3));
736     U.insert(std::move(e4));
737     U.insert(std::move(e7));
738
739     const auto alternative = C.compute_alternative_to(D_plus_e, U);
740     REQUIRE_FALSE(alternative.has_value());
741   }
742
743   SECTION("Alternative computation call 3")
744   {
745     // During the thrid call to Alt(C, D + {e}),
746     // UDPOR believes `U` to be the following:
747     //
748     //                 e0
749     //              /  /   /
750     //            e1   e4   e7
751     //           /
752     //         /  /
753     //        e2 e3
754     //
755     // C := {e0} and `Explore(C, D, A)` picked `e1`.
756     //
757     // Thus the computation is (since D is still empty)
758     //
759     // Alt(C, D + {e}) --> Alt({e0}, {e1})
760     //
761     // where U is given above. There are two alternatives in this case:
762     // {e0, e4} and {e0, e7}. Either one would be a valid choice for
763     // UDPOR, so we must check for the precense of either
764     const Configuration C{e0_handle};
765     const EventSet D_plus_e{e1_handle};
766
767     REQUIRE(U.empty());
768     U.insert(std::move(e0));
769     U.insert(std::move(e1));
770     U.insert(std::move(e2));
771     U.insert(std::move(e3));
772     U.insert(std::move(e4));
773     U.insert(std::move(e7));
774
775     const auto alternative = C.compute_alternative_to(D_plus_e, U);
776     REQUIRE(alternative.has_value());
777
778     // The first alternative that is found is the one that is chosen. Since
779     // traversal over the elements of an unordered_set<> are not guaranteed,
780     // both {e0, e4} and {e0, e7} are valid alternatives
781     REQUIRE((alternative.value().get_events() == EventSet({e0_handle, e4_handle}) or
782              alternative.value().get_events() == EventSet({e0_handle, e7_handle})));
783   }
784
785   SECTION("Alternative computation call 4")
786   {
787     // During the fourth call to Alt(C, D + {e}),
788     // UDPOR believes `U` to be the following:
789     //
790     //                  e0
791     //              /  /   /
792     //            e1   e4   e7
793     //           /     /  //
794     //         /  /   e5  e8
795     //        e2 e3   /
796     //               e6
797     //
798     // C := {e0, e4, e5} and `Explore(C, D, A)` picked `e6`
799     // (since en(C') where C' := {e0, e4, e5, e6} is empty
800     // [so UDPOR will simply return when C' is reached])
801     //
802     // Thus the computation is (since D is {e1})
803     //
804     // Alt(C, D + {e}) --> Alt({e0, e4, e5}, {e1, e6})
805     //
806     // where U is given above. There are no alternatives in this
807     // case, since:
808     //
809     // 1.`e2/e3` are eliminated since their histories contain `e1`
810     // 2. `e7/e8` are eliminated because they conflict with `e5`
811     const Configuration C{e0_handle, e4_handle, e5_handle};
812     const EventSet D_plus_e{e1_handle, e6_handle};
813
814     REQUIRE(U.empty());
815     U.insert(std::move(e0));
816     U.insert(std::move(e1));
817     U.insert(std::move(e2));
818     U.insert(std::move(e3));
819     U.insert(std::move(e4));
820     U.insert(std::move(e6));
821     U.insert(std::move(e7));
822     U.insert(std::move(e8));
823
824     const auto alternative = C.compute_alternative_to(D_plus_e, U);
825     REQUIRE_FALSE(alternative.has_value());
826   }
827
828   SECTION("Alternative computation call 5")
829   {
830     // During the fifth call to Alt(C, D + {e}),
831     // UDPOR believes `U` to be the following:
832     //
833     //                  e0
834     //              /  /   /
835     //            e1   e4   e7
836     //           /     /  //
837     //         /  /   e5  e8
838     //        e2 e3   /
839     //               e6
840     //
841     // C := {e0, e4} and `Explore(C, D, A)` picked `e5`
842     // (since en(C') where C' := {e0, e4, e5, e6} is empty
843     // [so UDPOR will simply return when C' is reached])
844     //
845     // Thus the computation is (since D is {e1})
846     //
847     // Alt(C, D + {e}) --> Alt({e0, e4}, {e1, e5})
848     //
849     // where U is given above. There are THREE alternatives in this case,
850     // viz. {e0, e7}, {e0, e4, e7} and {e0, e4, e7, e8}.
851     //
852     // To continue the search, UDPOR computes J / C which in this
853     // case gives {e7, e8}. Since `e8` is not in en(C), UDPOR will
854     // choose `e7` next and add `e5` to `D`
855     const Configuration C{e0_handle, e4_handle};
856     const EventSet D_plus_e{e1_handle, e5_handle};
857
858     REQUIRE(U.empty());
859     U.insert(std::move(e0));
860     U.insert(std::move(e1));
861     U.insert(std::move(e2));
862     U.insert(std::move(e3));
863     U.insert(std::move(e4));
864     U.insert(std::move(e6));
865     U.insert(std::move(e7));
866     U.insert(std::move(e8));
867     REQUIRE(U.size() == 8);
868
869     const auto alternative = C.compute_alternative_to(D_plus_e, U);
870     REQUIRE(alternative.has_value());
871     REQUIRE((alternative.value().get_events() == EventSet({e0_handle, e7_handle}) or
872              alternative.value().get_events() == EventSet({e0_handle, e4_handle, e7_handle}) or
873              alternative.value().get_events() == EventSet({e0_handle, e4_handle, e7_handle, e8_handle})));
874   }
875
876   SECTION("Alternative computation call 6")
877   {
878     // During the sixth call to Alt(C, D + {e}),
879     // UDPOR believes `U` to be the following:
880     //
881     //                 e0
882     //              /  /   /
883     //            e1   e4   e7
884     //           /     /  //   /
885     //         /  /   e5  e8   e9
886     //        e2 e3   /
887     //               e6
888     //
889     // C := {e0, e4, e7} and `Explore(C, D, A)` picked `e8`
890     // (since en(C') where C' := {e0, e4, e7, e8} is empty
891     // [so UDPOR will simply return when C' is reached])
892     //
893     // Thus the computation is (since D is {e1, e5} [see the last step])
894     //
895     // Alt(C, D + {e}) --> Alt({e0, e4, e7}, {e1, e5, e8})
896     //
897     // where U is given above. There are no alternatives in this case
898     // since all `e9` conflicts with `e4` and all other events of `U`
899     // are eliminated since their history intersects `D`
900     const Configuration C{e0_handle, e4_handle, e7_handle};
901     const EventSet D_plus_e{e1_handle, e5_handle, e8_handle};
902
903     REQUIRE(U.empty());
904     U.insert(std::move(e0));
905     U.insert(std::move(e1));
906     U.insert(std::move(e2));
907     U.insert(std::move(e3));
908     U.insert(std::move(e4));
909     U.insert(std::move(e6));
910     U.insert(std::move(e7));
911     U.insert(std::move(e8));
912     U.insert(std::move(e9));
913
914     const auto alternative = C.compute_alternative_to(D_plus_e, U);
915     REQUIRE_FALSE(alternative.has_value());
916   }
917
918   SECTION("Alternative computation call 7")
919   {
920     // During the seventh call to Alt(C, D + {e}),
921     // UDPOR believes `U` to be the following:
922     //
923     //                 e0
924     //              /  /   /
925     //            e1   e4   e7
926     //           /     /  //   /
927     //         /  /   e5  e8   e9
928     //        e2 e3   /
929     //               e6
930     //
931     // C := {e0, e4} and `Explore(C, D, A)` picked `e7`
932     //
933     // Thus the computation is (since D is {e1, e5} [see call 5])
934     //
935     // Alt(C, D + {e}) --> Alt({e0, e4}, {e1, e5, e7})
936     //
937     // where U is given above. There are no alternatives again in this case
938     // since all `e9` conflicts with `e4` and all other events of `U`
939     // are eliminated since their history intersects `D`
940     const Configuration C{e0_handle, e4_handle};
941     const EventSet D_plus_e{e1_handle, e5_handle, e7_handle};
942
943     REQUIRE(U.empty());
944     U.insert(std::move(e0));
945     U.insert(std::move(e1));
946     U.insert(std::move(e2));
947     U.insert(std::move(e3));
948     U.insert(std::move(e4));
949     U.insert(std::move(e6));
950     U.insert(std::move(e7));
951     U.insert(std::move(e8));
952     U.insert(std::move(e9));
953
954     const auto alternative = C.compute_alternative_to(D_plus_e, U);
955     REQUIRE_FALSE(alternative.has_value());
956   }
957
958   SECTION("Alternative computation call 8")
959   {
960     // During the eigth call to Alt(C, D + {e}),
961     // UDPOR believes `U` to be the following:
962     //
963     //                 e0
964     //              /  /   /
965     //            e1   e4   e7
966     //           /     /  //   /
967     //         /  /   e5  e8   e9
968     //        e2 e3   /
969     //               e6
970     //
971     // C := {e0} and `Explore(C, D, A)` picked `e4`. At this
972     // point, UDPOR finished its recursive search of {e0, e4}
973     // after having finished {e0, e1} prior.
974     //
975     // Thus the computation is (since D = {e1})
976     //
977     // Alt(C, D + {e}) --> Alt({e0}, {e1, e4})
978     //
979     // where U is given above. There is one alternative in this
980     // case, viz {e0, e7, e9} since
981     // 1. e9 conflicts with e4 in D
982     // 2. e7 conflicts with e1 in D
983     // 3. the set {e7, e9} is conflict-free since `e7 < e9`
984     // 4. all other events are eliminated since their histories
985     // intersect D
986     //
987     // UDPOR will continue its recursive search following `e7`
988     // and add `e4` to D
989     const Configuration C{e0_handle};
990     const EventSet D_plus_e{e1_handle, e4_handle};
991
992     REQUIRE(U.empty());
993     U.insert(std::move(e0));
994     U.insert(std::move(e1));
995     U.insert(std::move(e2));
996     U.insert(std::move(e3));
997     U.insert(std::move(e4));
998     U.insert(std::move(e6));
999     U.insert(std::move(e7));
1000     U.insert(std::move(e8));
1001     U.insert(std::move(e9));
1002
1003     const auto alternative = C.compute_alternative_to(D_plus_e, U);
1004     REQUIRE(alternative.has_value());
1005     REQUIRE(alternative.value().get_events() == EventSet({e0_handle, e7_handle, e9_handle}));
1006   }
1007
1008   SECTION("Alternative computation call 9")
1009   {
1010     // During the ninth call to Alt(C, D + {e}),
1011     // UDPOR believes `U` to be the following:
1012     //
1013     //                  e0
1014     //              /  /   /
1015     //            e1   e4   e7
1016     //           /     /  //   /
1017     //         /  /   e5  e8   e9
1018     //        e2 e3   /        /
1019     //               e6       e10
1020     //
1021     // C := {e0, e7, e9} and `Explore(C, D, A)` picked `e10`.
1022     // (since en(C') where C' := {e0, e7, e9, e10} is empty
1023     // [so UDPOR will simply return when C' is reached]).
1024     //
1025     // Thus the computation is (since D = {e1, e4} [see the previous step])
1026     //
1027     // Alt(C, D + {e}) --> Alt({e0}, {e1, e4, e10})
1028     //
1029     // where U is given above. There are no alternatives in this case
1030     const Configuration C{e0_handle, e7_handle, e9_handle};
1031     const EventSet D_plus_e{e1_handle, e4_handle, e10_handle};
1032
1033     REQUIRE(U.empty());
1034     U.insert(std::move(e0));
1035     U.insert(std::move(e1));
1036     U.insert(std::move(e2));
1037     U.insert(std::move(e3));
1038     U.insert(std::move(e4));
1039     U.insert(std::move(e6));
1040     U.insert(std::move(e7));
1041     U.insert(std::move(e8));
1042     U.insert(std::move(e9));
1043     U.insert(std::move(e10));
1044
1045     const auto alternative = C.compute_alternative_to(D_plus_e, U);
1046     REQUIRE_FALSE(alternative.has_value());
1047   }
1048
1049   SECTION("Alternative computation call 10")
1050   {
1051     // During the tenth call to Alt(C, D + {e}),
1052     // UDPOR believes `U` to be the following:
1053     //
1054     //                  e0
1055     //              /  /   /
1056     //            e1   e4   e7
1057     //           /     /  //   /
1058     //         /  /   e5  e8   e9
1059     //        e2 e3   /        /
1060     //               e6       e10
1061     //
1062     // C := {e0, e7} and `Explore(C, D, A)` picked `e9`.
1063     //
1064     // Thus the computation is (since D = {e1, e4} [see call 8])
1065     //
1066     // Alt(C, D + {e}) --> Alt({e0}, {e1, e4, e9})
1067     //
1068     // where U is given above. There are no alternatives in this case
1069     const Configuration C{e0_handle, e7_handle};
1070     const EventSet D_plus_e{e1_handle, e4_handle, e9_handle};
1071
1072     REQUIRE(U.empty());
1073     U.insert(std::move(e0));
1074     U.insert(std::move(e1));
1075     U.insert(std::move(e2));
1076     U.insert(std::move(e3));
1077     U.insert(std::move(e4));
1078     U.insert(std::move(e6));
1079     U.insert(std::move(e7));
1080     U.insert(std::move(e8));
1081     U.insert(std::move(e9));
1082     U.insert(std::move(e10));
1083
1084     const auto alternative = C.compute_alternative_to(D_plus_e, U);
1085     REQUIRE_FALSE(alternative.has_value());
1086   }
1087
1088   SECTION("Alternative computation call 11 (final call)")
1089   {
1090     // During the eleventh and final call to Alt(C, D + {e}),
1091     // UDPOR believes `U` to be the following:
1092     //
1093     //                  e0
1094     //              /  /   /
1095     //            e1   e4   e7
1096     //           /     /  //   /
1097     //         /  /   e5  e8   e9
1098     //        e2 e3   /        /
1099     //               e6       e10
1100     //
1101     // C := {e0} and `Explore(C, D, A)` picked `e7`.
1102     //
1103     // Thus the computation is (since D = {e1, e4} [see call 8])
1104     //
1105     // Alt(C, D + {e}) --> Alt({e0}, {e1, e4, e7})
1106     //
1107     // where U is given above. There are no alternatives in this case:
1108     // everyone is eliminated!
1109     const Configuration C{e0_handle, e7_handle};
1110     const EventSet D_plus_e{e1_handle, e4_handle, e9_handle};
1111
1112     REQUIRE(U.empty());
1113     U.insert(std::move(e0));
1114     U.insert(std::move(e1));
1115     U.insert(std::move(e2));
1116     U.insert(std::move(e3));
1117     U.insert(std::move(e4));
1118     U.insert(std::move(e6));
1119     U.insert(std::move(e7));
1120     U.insert(std::move(e8));
1121     U.insert(std::move(e9));
1122     U.insert(std::move(e10));
1123
1124     const auto alternative = C.compute_alternative_to(D_plus_e, U);
1125     REQUIRE_FALSE(alternative.has_value());
1126   }
1127
1128   SECTION("Alternative computation next")
1129   {
1130     SECTION("Followed {e0, e7} first")
1131     {
1132       const EventSet D{e1_handle, e7_handle};
1133       const Configuration C{e0_handle};
1134
1135       REQUIRE(U.empty());
1136       U.insert(std::move(e0));
1137       U.insert(std::move(e1));
1138       U.insert(std::move(e2));
1139       U.insert(std::move(e3));
1140       U.insert(std::move(e4));
1141       U.insert(std::move(e5));
1142       U.insert(std::move(e7));
1143       U.insert(std::move(e8));
1144       U.insert(std::move(e9));
1145       U.insert(std::move(e10));
1146
1147       const auto alternative = C.compute_alternative_to(D, U);
1148       REQUIRE(alternative.has_value());
1149
1150       // In this case, only {e0, e4} is a valid alternative
1151       REQUIRE(alternative.value().get_events() == EventSet({e0_handle, e4_handle, e5_handle}));
1152     }
1153
1154     SECTION("Followed {e0, e4} first")
1155     {
1156       const EventSet D{e1_handle, e4_handle};
1157       const Configuration C{e0_handle};
1158
1159       REQUIRE(U.empty());
1160       U.insert(std::move(e0));
1161       U.insert(std::move(e1));
1162       U.insert(std::move(e2));
1163       U.insert(std::move(e3));
1164       U.insert(std::move(e4));
1165       U.insert(std::move(e5));
1166       U.insert(std::move(e6));
1167       U.insert(std::move(e7));
1168       U.insert(std::move(e8));
1169       U.insert(std::move(e9));
1170
1171       const auto alternative = C.compute_alternative_to(D, U);
1172       REQUIRE(alternative.has_value());
1173
1174       // In this case, only {e0, e7} is a valid alternative
1175       REQUIRE(alternative.value().get_events() == EventSet({e0_handle, e7_handle, e9_handle}));
1176     }
1177   }
1178 }