mCRL2
Loading...
Searching...
No Matches
symbolic_pbessolve.h
Go to the documentation of this file.
1// Author(s): Maurice Laveaux and Wieger Wesselink
2// Copyright: see the accompanying file COPYING or copy at
3// https://github.com/mCRL2org/mCRL2/blob/master/COPYING
4//
5// Distributed under the Boost Software License, Version 1.0.
6// (See accompanying file LICENSE_1_0.txt or copy at
7// http://www.boost.org/LICENSE_1_0.txt)
8//
9
10#ifndef MCRL2_PBES_SYMBOLIC_PBESSOLVE_H
11#define MCRL2_PBES_SYMBOLIC_PBESSOLVE_H
12
13#ifdef MCRL2_ENABLE_SYLVAN
14#include "sylvan_ldd.hpp"
15
16#include "symbolic_parity_game.h"
17#include "mcrl2/utilities/exception.h"
18#include "mcrl2/utilities/logger.h"
19
20
21
22namespace mcrl2::pbes_system {
23
24using sylvan::ldds::ldd;
25
26/// Container type for symbolic solutions, consisting of
27/// winning sets and strategies for both players.
28struct symbolic_solution_t
29{
30 /// Winning sets of both players
31 std::array<ldd,2> winning;
32
33 /// Strategies for both players
34 std::array<std::optional<ldd>,2> strategy;
35
36 symbolic_solution_t(bool instantiated_strategies)
37 : winning({sylvan::ldds::empty_set(), sylvan::ldds::empty_set()}),
38 strategy({std::nullopt, std::nullopt})
39 {
40 if (instantiated_strategies)
41 {
42 strategy = { sylvan::ldds::empty_set(), sylvan::ldds::empty_set() };
43 }
44 }
45
46 /// Determine if vertex is part of the solution.
47 bool solution_found(const ldd& vertex) const
48 {
49 return includes(winning[0], vertex) || includes(winning[1], vertex);
50 }
51};
52
53/// Print solution to string
54inline
55std::string print_solution(const symbolic_parity_game& G, const symbolic_solution_t& solution)
56{
57 std::ostringstream os;
58 os << "W0 = " << G.print_nodes(solution.winning[0]) << std::endl;
59 os << "W1 = " << G.print_nodes(solution.winning[1]) << std::endl;
60 if (solution.strategy[0].has_value())
61 {
62 os << "S0 = " << G.print_strategy(solution.strategy[0].value()) << std::endl; // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
63 }
64 if (solution.strategy[1].has_value())
65 {
66 os << "S1 = " << G.print_strategy(solution.strategy[1].value()) << std::endl; // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
67 }
68 return os.str();
69}
70
71class symbolic_pbessolve_algorithm
72{
73 private:
74 const symbolic_parity_game& m_G;
75 bool m_check_strategy = false;
76 bool m_compute_strategy = false;
77
78 public:
79 symbolic_pbessolve_algorithm(const symbolic_parity_game& G, bool check_strategy = false, bool compute_strategy = false) :
80 m_G(G),
81 m_check_strategy(check_strategy),
82 m_compute_strategy(compute_strategy)
83 {}
84
85 symbolic_solution_t zielonka(const ldd& V)
86 {
87 using namespace sylvan::ldds;
88
89 symbolic_solution_t solution(m_compute_strategy);
90 if (m_compute_strategy)
91 {
92 solution.strategy = {empty_set(), empty_set()};
93 }
94
95 if (V == empty_set())
96 {
97 return solution;
98 }
99
100 stopwatch timer;
101 mCRL2log(log::debug) << "start zielonka recursion\n";
102
103 // Compute the partitioning of V for players 0 (in V[0]) and 1 (in V[1]).
104 std::array<const ldd, 2> Vplayer = m_G.players(V);
105
106 auto [m, U] = m_G.get_min_rank(V);
107 std::size_t alpha = m % 2; // 0 = disjunctive, 1 = conjunctive
108
109 const auto [A, A_strategy] = m_G.safe_attractor(U, alpha, V, Vplayer);
110 mCRL2log(log::trace) << "A = attractor(" << m_G.print_nodes(U) << ", " << m_G.print_nodes(V) << ") = " << m_G.print_nodes(A) << std::endl;
111
112 // Original Zielonka version
113 symbolic_solution_t solution_V_minus_A = zielonka(minus(V, A));
114 if (solution_V_minus_A.winning[1 - alpha] == empty_set())
115 {
116 solution.winning[alpha] = union_(A, solution_V_minus_A.winning[alpha]);
117 solution.winning[1 - alpha] = empty_set();
118
119 if (m_compute_strategy)
120 {
121 solution.strategy[alpha]
122 = union_(union_(A_strategy.value(), solution_V_minus_A.strategy[alpha].value()), merge(intersect(U, Vplayer[alpha]), V)); // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
123 solution.strategy[1 - alpha] = empty_set();
124 }
125 assert(union_(solution.winning[0], solution.winning[1]) == V);
126 }
127 else
128 {
129 const auto [B, B_strategy] = m_G.safe_attractor(solution_V_minus_A.winning[1 - alpha], 1 - alpha, V, Vplayer);
130 mCRL2log(log::trace) << "B = attractor(" << m_G.print_nodes(solution_V_minus_A.winning[1 - alpha]) << ", " << m_G.print_nodes(V) << ") = " << m_G.print_nodes(B) << std::endl;
131 solution = zielonka(minus(V, B));
132 solution.winning[1 - alpha] = union_(solution.winning[1 - alpha], B);
133 if (m_compute_strategy)
134 {
135 solution.strategy[1 - alpha] = union_(union_(solution_V_minus_A.strategy[1 - alpha].value(), B_strategy.value()), solution.strategy[1 - alpha].value()); // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
136 }
137 assert(union_(solution.winning[0], solution.winning[1]) == V);
138 }
139
140 mCRL2log(log::debug) << "finished zielonka recursion (time = " << std::setprecision(2) << std::fixed << timer.seconds() << "s)\n";
141
142 mCRL2log(log::trace) << "\n --- zielonka solution for ---\n" << m_G.print_graph(V) << std::endl;
143 mCRL2log(log::trace) << print_solution(m_G, solution) << std::endl;
144
145 assert(union_(solution.winning[0], solution.winning[1]) == V);
146 return solution;
147 }
148
149 public:
150
151 /// \brief Solve the given game restricted to V with Zielonka's recursive algorithm as solver.
152 /// The remaining parameters are sinks, vertices won by even and odd respectively.
153 /// Terminates early when initial_vertex has been solved.
154 /// \returns The winner and W0, W1, S0, S1. Where S0 and S1 are the strategies.
155 std::tuple<bool, symbolic_solution_t> solve(const ldd& initial_vertex,
156 const ldd& V,
157 const ldd& Vsinks,
158 const symbolic_solution_t& partial_solution,
159 bool allow_early_termination = true)
160 {
161 using namespace sylvan::ldds;
162 stopwatch timer;
163
164 symbolic_solution_t solution = partial_solution;
165
166 ldd Vtotal = m_G.compute_total_graph(V, empty_set(), Vsinks, solution.winning, solution.strategy);
167
168 if (!solution.solution_found(initial_vertex) || !allow_early_termination)
169 {
170 // If the initial vertex has not yet been won then run the zielonka solver as well.
171 mCRL2log(log::trace) << "\n--- apply zielonka to ---\n" << m_G.print_graph(Vtotal) << std::endl;
172 symbolic_solution_t zielonka_solution = zielonka(Vtotal);
173
174 // Ensure that previously solved sets are included.
175 solution.winning[0] = union_(zielonka_solution.winning[0], solution.winning[0]);
176 solution.winning[1] = union_(zielonka_solution.winning[1], solution.winning[1]);
177 if (m_compute_strategy)
178 {
179 solution.strategy[0] = union_(zielonka_solution.strategy[0].value(), solution.strategy[0].value()); // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
180 solution.strategy[1] = union_(zielonka_solution.strategy[1].value(), solution.strategy[1].value()); // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
181 }
182 }
183
184 mCRL2log(log::verbose) << "finished solving (time = " << std::setprecision(2) << std::fixed << timer.seconds() << "s)\n";
185 mCRL2log(log::trace) << print_solution(m_G, solution) << std::endl;
186
187 if (solution.solution_found(initial_vertex))
188 {
189 bool result = includes(solution.winning[0], initial_vertex);
190 assert(result || includes(solution.winning[1], initial_vertex)); // if result is false, initial_vertex must be won by player 1.
191 if (m_check_strategy)
192 {
193 check_strategy(initial_vertex, V, solution, !result);
194 }
195 return {result, solution};
196 }
197 else
198 {
199 throw mcrl2::runtime_error("No solution found!");
200 }
201 }
202
203 /// \brief Solve the given incomplete parity game (m_G, I) restricted to V.
204 /// The remaining parameters are sinks, vertices won by even and odd respectively.
205 /// Terminates early when initial_vertex has been solved.
206 symbolic_solution_t partial_solve(const ldd& initial_vertex,
207 const ldd& V,
208 const ldd& I,
209 const ldd& Vsinks,
210 const symbolic_solution_t& partial_solution)
211 {
212 // Make the game total.
213 using namespace sylvan::ldds;
214 symbolic_solution_t solution = partial_solution;
215
216 ldd Vtotal = m_G.compute_total_graph(V, I, Vsinks, solution.winning, solution.strategy);
217 if (includes(solution.winning[0], initial_vertex) || includes(solution.winning[1], initial_vertex))
218 {
219 return solution;
220 }
221
222 // Solve with zielonka twice for the safe sets.
223 symbolic_solution_t zielonka_solution_0 = zielonka(m_G.compute_safe_vertices(0, Vtotal, I));
224 zielonka_solution_0.winning[0] = union_(zielonka_solution_0.winning[0], solution.winning[0]);
225 if (m_compute_strategy)
226 {
227 zielonka_solution_0.strategy[0] = union_(zielonka_solution_0.strategy[0].value(), // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
228 solution.strategy[0].value()); // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
229 } else {
230 assert(!zielonka_solution_0.strategy[0].has_value() && !zielonka_solution_0.strategy[1].has_value());
231 }
232
233 if (includes(zielonka_solution_0.winning[0], initial_vertex))
234 {
235 zielonka_solution_0.winning[1] = solution.winning[1];
236 zielonka_solution_0.strategy[1] = solution.strategy[1];
237 return zielonka_solution_0;
238 }
239
240 symbolic_solution_t zielonka_solution_1 = zielonka(m_G.compute_safe_vertices(1, Vtotal, I));
241 zielonka_solution_1.winning[1] = union_(zielonka_solution_1.winning[1], solution.winning[1]);
242 if (m_compute_strategy)
243 {
244 zielonka_solution_1.strategy[1] = union_(zielonka_solution_1.strategy[1].value(), // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
245 solution.strategy[1].value()); // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
246 } else {
247 assert(!zielonka_solution_1.strategy[0].has_value() && !zielonka_solution_0.strategy[1].has_value());
248 }
249
250 zielonka_solution_1.winning[0] = zielonka_solution_0.winning[0];
251 zielonka_solution_1.strategy[0] = zielonka_solution_0.strategy[0];
252 return zielonka_solution_1;
253 }
254
255 /// \brief Detect solitair winning cycles for the given incomplete parity game (m_G, I) restricted to V.
256 /// The remaining parameters are sinks, and a partial solution, respectively.
257 /// Terminates early when initial_vertex has been solved.
258 /// \param safe_variant Whether to use the safe attractor variant (as opposed to computing safe vertices first).
259 symbolic_solution_t detect_solitair_cycles(const ldd& initial_vertex,
260 const ldd& V,
261 const ldd& I,
262 bool safe_variant,
263 const ldd& Vsinks,
264 const symbolic_solution_t& partial_solution)
265 {
266 using namespace sylvan::ldds;
267
268 mCRL2log(log::trace) << "\n--- apply solitair winning cycle detection to ---\n"
269 << m_G.print_graph(V) << std::endl;
270 mCRL2log(log::trace) << "detect_solitair_cycles: starting with partial solution\n"
271 << print_solution(m_G, partial_solution) << "\n" ;
272
273 symbolic_solution_t solution = partial_solution;
274 // Make the game total and removed winning sets.
275
276 ldd Vtotal = m_G.compute_total_graph(V, I, Vsinks, solution.winning, solution.strategy);
277 if (includes(solution.winning[0], initial_vertex) || includes(solution.winning[1], initial_vertex))
278 {
279 return solution;
280 }
281
282
283
284 // Computes two vertex sets of all even priority and odd priority nodes respectively.
285 std::array<ldd, 2> parity;
286 std::array<const ldd, 2> Vplayer = m_G.players(Vtotal);
287 for (const auto&[rank, Vrank] : m_G.ranks())
288 {
289 parity[rank % 2] = union_(parity[rank % 2], Vrank);
290 }
291
292 std::array<ldd, 2> Vsafe;
293 if (!safe_variant)
294 {
295 Vsafe = { m_G.compute_safe_vertices(0, Vtotal, I), m_G.compute_safe_vertices(1, Vtotal, I) };
296 mCRL2log(log::trace) << "detect_solitair_cycles: computed safe vertices\n"
297 << " Vsafe[0] = " << Vsafe[0] << "\n"
298 << " Vsafe[1] = " << Vsafe[1] << "\n";
299 }
300
301 for (std::size_t alpha = 0; alpha <= 1; ++alpha)
302 {
303 mCRL2log(log::debug) << "solitair winning cycle detection for player " << alpha << "\n";
304 // Determine the cycles for this player.
305 ldd U = empty_set();
306 ldd Unext = intersect(parity[alpha], Vplayer[alpha]);
307 if (!safe_variant)
308 {
309 Unext = intersect(Unext, Vsafe[alpha]);
310 }
311
312 std::size_t iter = 0;
313 while (U != Unext)
314 {
315 mCRL2log(log::trace) << "detect_solitair_cycles: starting iteration " << iter << "\n"
316 << " U = " << m_G.print_nodes(U)
317 << " Unext = " << m_G.print_nodes(Unext) << "\n";
318
319 stopwatch timer;
320 U = Unext;
321 Unext = m_G.predecessors(U, U);
322
323 mCRL2log(log::debug) << "iteration " << iter << " (time = " << std::setprecision(2) << std::fixed << timer.seconds() << "s)\n";
324
325 ++iter;
326 }
327 // At this point, all vertices in U have an edge to another vertex in U, and
328 // are thus winning for player alpha. We can set the strategy to U x U.
329
330 mCRL2log(log::debug) << "found " << std::setw(12) << satcount(U) << " states in cycles for player " << alpha << "\n";
331 mCRL2log(log::trace) << "detect_solitair_cycles: states in cycles:\n"
332 << "U = " << m_G.print_nodes(U) << "\n";
333
334 if (m_compute_strategy)
335 {
336 solution.strategy[alpha] = union_(solution.strategy[alpha].value(), merge(U, U)); // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
337 } else {
338 assert(!solution.strategy[alpha].has_value());
339 }
340
341 if (solution.strategy[alpha].has_value())
342 {
343 mCRL2log(log::trace) << "detect_solitair_cycles: extended strategy for player " << alpha << " to \n"
344 << " S[alpha] = " << m_G.print_strategy(solution.strategy[alpha].value()) << "\n"; // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
345 }
346
347 mCRL2log(log::trace) << "detect_solitair_cycles: computing safe attractor for player " << alpha << " into extended winning set\n";
348
349 if (safe_variant)
350 {
351 std::pair<ldd, std::optional<ldd>> attr = m_G.safe_attractor(U, alpha, Vtotal, Vplayer, I);
352 solution.winning[alpha] = union_(solution.winning[alpha], attr.first);
353 if(m_compute_strategy)
354 {
355 solution.strategy[alpha] = union_(solution.strategy[alpha].value(), attr.second.value()); // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
356 } else {
357 assert(!attr.second.has_value());
358 }
359 }
360 else
361 {
362 std::pair<ldd, std::optional<ldd>> attr = m_G.safe_attractor(U, alpha, Vsafe[alpha], Vplayer);
363 solution.winning[alpha] = union_(solution.winning[alpha], attr.first);
364 if(m_compute_strategy)
365 {
366 solution.strategy[alpha] = union_(solution.strategy[alpha].value(), attr.second.value()); // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
367 } else {
368 assert(!attr.second.has_value());
369 }
370 }
371 mCRL2log(log::trace) << "detect_solitair_cycles: extended winning sets and strategy for player " << alpha
372 << " to \n"
373 << " W[alpha] = " << m_G.print_nodes(solution.winning[alpha]) << "\n"
374 << (solution.strategy[alpha].has_value() ? " S[alpha] = " + m_G.print_strategy(solution.strategy[alpha].value()) + "\n" : ""); // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
375 }
376
377 mCRL2log(log::trace) << "detect_solitair_cycles: partial solution after detecting solitair cycles:\n"
378 << print_solution(m_G, solution) << std::endl;
379
380 return solution;
381 }
382
383 /// \brief Detect forced winning cycles for the given incomplete parity game (m_G, I) restricted to V.
384 /// The remaining parameters are sinks, vertices won by even and odd respectively.
385 /// Terminates early when initial_vertex has been solved.
386 /// \param safe Whether to use the safe attractor variant (as opposed to computing safe vertices first).
387 symbolic_solution_t detect_forced_cycles(const ldd& initial_vertex,
388 const ldd& V,
389 const ldd& I,
390 bool safe_variant,
391 const ldd& Vsinks,
392 const symbolic_solution_t& partial_solution)
393 {
394 using namespace sylvan::ldds;
395
396 symbolic_solution_t solution = partial_solution;
397
398 // Make the game total and removed winning sets.
399
400 ldd Vtotal = m_G.compute_total_graph(V, I, Vsinks, solution.winning, solution.strategy);
401 if (includes(solution.winning[0], initial_vertex) || includes(solution.winning[1], initial_vertex))
402 {
403 return solution;
404 }
405
406 mCRL2log(log::trace) << "\n--- apply forced winning cycle detection to ---\n" << m_G.print_graph(V) << std::endl;
407
408 // Computes two vertex sets of all even priority and odd priority nodes respectively.
409 std::array<ldd, 2> parity;
410 std::array<const ldd, 2> Vplayer = m_G.players(Vtotal);
411 for (const auto&[rank, Vrank] : m_G.ranks())
412 {
413 parity[rank % 2] = union_(parity[rank % 2], Vrank);
414 }
415
416 std::array<ldd, 2> Vsafe;
417 if (!safe_variant)
418 {
419 Vsafe = { m_G.compute_safe_vertices(0, Vtotal, I), m_G.compute_safe_vertices(1, Vtotal, I) };
420 }
421
422 for (std::size_t alpha = 0; alpha <= 1; ++alpha)
423 {
424 // Determine the cycles for this player.
425 ldd U = empty_set();
426 ldd Unext = parity[alpha];
427 if (!safe_variant)
428 {
429 Unext = intersect(Unext, Vsafe[alpha]);
430 }
431
432 mCRL2log(log::debug) << "forced winning cycle detection for player " << alpha << "\n";
433
434 std::size_t iter = 0;
435 while (U != Unext)
436 {
437 stopwatch timer;
438 U = Unext;
439 if (safe_variant)
440 {
441 Unext = intersect(U, m_G.safe_control_predecessors(alpha, U, Vtotal, U, Vplayer, I));
442 }
443 else
444 {
445 Unext = intersect(U, m_G.safe_control_predecessors(alpha, U, Vsafe[alpha], U, Vplayer));
446 }
447
448 mCRL2log(log::debug) << "iteration " << iter << " (time = " << std::setprecision(2) << std::fixed << timer.seconds() << "s)\n";
449
450 ++iter;
451 }
452
453 mCRL2log(log::debug) << "found " << std::setw(12) << satcount(U) << " states in cycles for player " << alpha << "\n";
454
455 // Overapproximate strategy for the forced winning cycles
456 if (m_compute_strategy)
457 {
458 solution.strategy[alpha] = union_(solution.strategy[alpha].value(), merge(U, U)); // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
459 } else {
460 assert(!solution.strategy[alpha].has_value());
461 }
462
463 if (safe_variant)
464 {
465 std::pair<ldd, std::optional<ldd>> attr = m_G.safe_attractor(U, alpha, Vtotal, Vplayer, I);
466 solution.winning[alpha] = union_(solution.winning[alpha], attr.first);
467 if (m_compute_strategy)
468 {
469 solution.strategy[alpha] = union_(solution.strategy[alpha].value(), attr.second.value()); // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
470 } else {
471 assert(!attr.second.has_value());
472 }
473 }
474 else
475 {
476 std::pair<ldd, std::optional<ldd>> attr = m_G.safe_attractor(U, alpha, Vsafe[alpha], Vplayer);
477 solution.winning[alpha] = union_(solution.winning[alpha], attr.first);
478 if (m_compute_strategy)
479 {
480 solution.strategy[alpha] = union_(solution.strategy[alpha].value(), attr.second.value()); // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
481 } else {
482 assert(!attr.second.has_value());
483 }
484 }
485 }
486
487 mCRL2log(log::trace) << print_solution(m_G, solution) << std::endl;
488
489 return solution;
490 }
491
492 /// \returns Partial solve using the fatal attractors.
493 std::pair<ldd, ldd> detect_fatal_attractors(const ldd& initial_vertex,
494 const ldd& V,
495 const ldd& I,
496 bool safe_variant,
497 const ldd& Vsinks,
498 const ldd& W0 = sylvan::ldds::empty_set(),
499 const ldd& W1 = sylvan::ldds::empty_set())
500 {
501 using namespace sylvan::ldds;
502 stopwatch timer;
503
504 std::array<ldd, 2> winning = { W0, W1 };
505 std::array<std::optional<ldd>, 2> strategy;
506
507 ldd Vtotal = m_G.compute_total_graph(V, I, Vsinks, winning, strategy);
508 if (includes(winning[0], initial_vertex) || includes(winning[1], initial_vertex))
509 {
510 return { winning[0], winning[1] };
511 }
512
513 std::array<const ldd, 2> Vplayer = m_G.players(Vtotal);
514
515 // Compute safe vertices if necessary
516 std::array<ldd, 2> Vsafe;
517 if (!safe_variant)
518 {
519 Vsafe = { m_G.compute_safe_vertices(0, Vtotal, I), m_G.compute_safe_vertices(1, Vtotal, I) };
520 }
521
522 mCRL2log(log::trace) << "\n--- apply fatal attractor detection to ---\n" << m_G.print_graph(Vtotal) << std::endl;
523
524 // For priorities in descending order
525 for (auto it = m_G.ranks().rbegin(); it != m_G.ranks().rend(); it++)
526 {
527 std::size_t c = it->first;
528 std::size_t alpha = c % 2;
529 mCRL2log(log::debug) << "fatal attractor detection for priority " << c << "\n";
530 ldd X = safe_variant ? it->second : intersect(it->second, Vsafe[alpha]);
531 ldd Y = empty_set();
532
533 while (X != empty_set() && X != Y)
534 {
535 Y = X;
536 ldd Z = m_G.safe_monotone_attractor(X, alpha, c, safe_variant ? Vtotal : Vsafe[alpha], Vplayer, safe_variant ? I : empty_set());
537
538
539 if (includes(Z, X))
540 {
541 if (safe_variant)
542 {
543 winning[alpha] = union_(winning[alpha], m_G.safe_attractor(Z, alpha, Vtotal, Vplayer, I).first);
544 }
545 else
546 {
547 winning[alpha] = union_(winning[alpha], m_G.safe_attractor(Z, alpha, Vsafe[alpha], Vplayer).first);
548 }
549 mCRL2log(log::debug) << "found " << std::setw(12) << satcount(Z) << " states in fatal attractors for priority " << c << "\n";
550 break;
551 }
552 else
553 {
554 X = intersect(X, Z);
555 }
556 }
557 }
558
559 mCRL2log(log::debug) << "finished fatal attractor detection (time = " << std::setprecision(2) << std::fixed << timer.seconds() << "s)\n";
560 mCRL2log(log::trace) << "W0 = " << m_G.print_nodes(winning[0]) << std::endl;
561 mCRL2log(log::trace) << "W1 = " << m_G.print_nodes(winning[1]) << std::endl;
562
563 return { winning[0], winning[1] };
564 }
565
566 /// Computes an LDD that has the deadlock states in a given set of vertices V in parity game G
567 /// The deadlocks are those states that are not the predecessor of another state in V.
568 ldd compute_deadlocks(const ldd& V, const symbolic_parity_game& G)
569 {
570 ldd predecessors = G.predecessors(V, V);
571 ldd deadlocks = sylvan::ldds::minus(V, predecessors);
572 return deadlocks;
573 }
574
575 /// Checks whether the computed strategy is indeed a correct certificate for the winning partition.
576 ///
577 /// Throws an exception when the strategy is invalid.
578 void check_strategy(const ldd& initial_vertex,
579 const ldd& V,
580 const symbolic_solution_t& solution,
581 bool alpha)
582 {
583 using namespace sylvan::ldds;
584
585 mCRL2log(log::debug) << "Checking the strategy of the solved parity game..." << std::endl;
586 symbolic_parity_game new_G = m_G.apply_strategy(alpha, alpha?solution.strategy[1].value():solution.strategy[0].value()); // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
587 mCRL2log(log::trace) << "Minimal parity game G = " << new_G.print_graph(V) << std::endl;
588 // there may be new sinks due to vertices whose strategy is not defined.
589 ldd new_Vsinks = compute_deadlocks(V, new_G);
590
591 symbolic_pbessolve_algorithm check(new_G, false, m_compute_strategy);
592
593 auto [result, solution_prime] = check.solve(initial_vertex, V, new_Vsinks, symbolic_solution_t(m_compute_strategy), false);
594
595 if (includes(union_(solution.winning[0], solution.winning[1]), V))
596 {
597 // new solution should also cover all vertices
598 assert(includes(union_(solution_prime.winning[0],solution_prime.winning[1]), V));
599
600 // Full solution for V was computed, after applying the strategy, the winning sets
601 // must stay identical.
602 if (!(solution.winning[0] == solution_prime.winning[0]
603 && solution.winning[1] == solution_prime.winning[1]
604 && result != alpha))
605 {
606 mCRL2log(log::trace) << "W0 = " << m_G.print_nodes(solution.winning[0]) << "\n";
607 mCRL2log(log::trace) << "W0' = " << m_G.print_nodes(solution_prime.winning[0]) << "\n";
608 mCRL2log(log::trace) << "W1 = " << m_G.print_nodes(solution.winning[1]) << "\n";
609 mCRL2log(log::trace) << "W1' = " << m_G.print_nodes(solution.winning[1]) << "\n";
610 throw mcrl2::runtime_error("Computed strategy does not match the winning partition");
611 }
612 }
613 else
614 {
615 // The game was not fully solved. After applying the strategy, we may find
616 // the solution to more vertices, so we can only check inclusion.
617 // The winner should not change.
618 if (!(includes(solution_prime.winning[0], solution.winning[0])
619 && includes(solution_prime.winning[1], solution.winning[1])
620 && result != alpha))
621 {
622 mCRL2log(log::trace) << "W0 = " << m_G.print_nodes(solution.winning[0]) << "\n";
623 mCRL2log(log::trace) << "W0' = " << m_G.print_nodes(solution_prime.winning[0]) << "\n";
624 mCRL2log(log::trace) << "W1 = " << m_G.print_nodes(solution.winning[1]) << "\n";
625 mCRL2log(log::trace) << "W1' = " << m_G.print_nodes(solution.winning[1]) << "\n";
626 throw mcrl2::runtime_error("Computed strategy of partially solved game does not match the winning partition");
627 }
628 }
629
630 }
631};
632
633} // namespace mcrl2::pbes_system
634
635
636
637#endif // MCRL2_ENABLE_SYLVAN
638
639#endif // MCRL2_PBES_SYMBOLIC_PBESSOLVE_H