10#ifndef MCRL2_PBES_SYMBOLIC_PBESSOLVE_H
11#define MCRL2_PBES_SYMBOLIC_PBESSOLVE_H
13#ifdef MCRL2_ENABLE_SYLVAN
14#include "sylvan_ldd.hpp"
16#include "symbolic_parity_game.h"
17#include "mcrl2/utilities/exception.h"
18#include "mcrl2/utilities/logger.h"
22namespace mcrl2::pbes_system {
24using sylvan::ldds::ldd;
28struct symbolic_solution_t
31 std::array<ldd,2> winning;
34 std::array<std::optional<ldd>,2> strategy;
36 symbolic_solution_t(
bool instantiated_strategies)
37 : winning({sylvan::ldds::empty_set(), sylvan::ldds::empty_set()}),
38 strategy({std::nullopt, std::nullopt})
40 if (instantiated_strategies)
42 strategy = { sylvan::ldds::empty_set(), sylvan::ldds::empty_set() };
47 bool solution_found(
const ldd& vertex)
const
49 return includes(winning[0], vertex) || includes(winning[1], vertex);
55std::string print_solution(
const symbolic_parity_game& G,
const symbolic_solution_t& solution)
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())
62 os <<
"S0 = " << G.print_strategy(solution.strategy[0].value()) << std::endl;
64 if (solution.strategy[1].has_value())
66 os <<
"S1 = " << G.print_strategy(solution.strategy[1].value()) << std::endl;
71class symbolic_pbessolve_algorithm
74 const symbolic_parity_game& m_G;
75 bool m_check_strategy =
false;
76 bool m_compute_strategy =
false;
79 symbolic_pbessolve_algorithm(
const symbolic_parity_game& G,
bool check_strategy =
false,
bool compute_strategy =
false) :
81 m_check_strategy(check_strategy),
82 m_compute_strategy(compute_strategy)
85 symbolic_solution_t zielonka(
const ldd& V)
87 using namespace sylvan::ldds;
89 symbolic_solution_t solution(m_compute_strategy);
90 if (m_compute_strategy)
92 solution.strategy = {empty_set(), empty_set()};
101 mCRL2log(log::debug) <<
"start zielonka recursion\n";
104 std::array<
const ldd, 2> Vplayer = m_G.players(V);
106 auto [m, U] = m_G.get_min_rank(V);
107 std::size_t alpha = m % 2;
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;
113 symbolic_solution_t solution_V_minus_A = zielonka(minus(V, A));
114 if (solution_V_minus_A.winning[1 - alpha] == empty_set())
116 solution.winning[alpha] = union_(A, solution_V_minus_A.winning[alpha]);
117 solution.winning[1 - alpha] = empty_set();
119 if (m_compute_strategy)
121 solution.strategy[alpha]
122 = union_(union_(A_strategy.value(), solution_V_minus_A.strategy[alpha].value()), merge(intersect(U, Vplayer[alpha]), V));
123 solution.strategy[1 - alpha] = empty_set();
125 assert(union_(solution.winning[0], solution.winning[1]) == V);
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)
135 solution.strategy[1 - alpha] = union_(union_(solution_V_minus_A.strategy[1 - alpha].value(), B_strategy.value()), solution.strategy[1 - alpha].value());
137 assert(union_(solution.winning[0], solution.winning[1]) == V);
140 mCRL2log(log::debug) <<
"finished zielonka recursion (time = " << std::setprecision(2) << std::fixed << timer.seconds() <<
"s)\n";
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;
145 assert(union_(solution.winning[0], solution.winning[1]) == V);
155 std::tuple<
bool, symbolic_solution_t> solve(
const ldd& initial_vertex,
158 const symbolic_solution_t& partial_solution,
159 bool allow_early_termination =
true)
161 using namespace sylvan::ldds;
164 symbolic_solution_t solution = partial_solution;
166 ldd Vtotal = m_G.compute_total_graph(V, empty_set(), Vsinks, solution.winning, solution.strategy);
168 if (!solution.solution_found(initial_vertex) || !allow_early_termination)
171 mCRL2log(log::trace) <<
"\n--- apply zielonka to ---\n" << m_G.print_graph(Vtotal) << std::endl;
172 symbolic_solution_t zielonka_solution = zielonka(Vtotal);
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)
179 solution.strategy[0] = union_(zielonka_solution.strategy[0].value(), solution.strategy[0].value());
180 solution.strategy[1] = union_(zielonka_solution.strategy[1].value(), solution.strategy[1].value());
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;
187 if (solution.solution_found(initial_vertex))
189 bool result = includes(solution.winning[0], initial_vertex);
190 assert(result || includes(solution.winning[1], initial_vertex));
191 if (m_check_strategy)
193 check_strategy(initial_vertex, V, solution, !result);
195 return {result, solution};
199 throw mcrl2::runtime_error(
"No solution found!");
206 symbolic_solution_t partial_solve(
const ldd& initial_vertex,
210 const symbolic_solution_t& partial_solution)
213 using namespace sylvan::ldds;
214 symbolic_solution_t solution = partial_solution;
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))
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)
227 zielonka_solution_0.strategy[0] = union_(zielonka_solution_0.strategy[0].value(),
228 solution.strategy[0].value());
230 assert(!zielonka_solution_0.strategy[0].has_value() && !zielonka_solution_0.strategy[1].has_value());
233 if (includes(zielonka_solution_0.winning[0], initial_vertex))
235 zielonka_solution_0.winning[1] = solution.winning[1];
236 zielonka_solution_0.strategy[1] = solution.strategy[1];
237 return zielonka_solution_0;
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)
244 zielonka_solution_1.strategy[1] = union_(zielonka_solution_1.strategy[1].value(),
245 solution.strategy[1].value());
247 assert(!zielonka_solution_1.strategy[0].has_value() && !zielonka_solution_0.strategy[1].has_value());
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;
259 symbolic_solution_t detect_solitair_cycles(
const ldd& initial_vertex,
264 const symbolic_solution_t& partial_solution)
266 using namespace sylvan::ldds;
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" ;
273 symbolic_solution_t solution = partial_solution;
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))
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())
289 parity[rank % 2] = union_(parity[rank % 2], Vrank);
292 std::array<ldd, 2> Vsafe;
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";
301 for (std::size_t alpha = 0; alpha <= 1; ++alpha)
303 mCRL2log(log::debug) <<
"solitair winning cycle detection for player " << alpha <<
"\n";
306 ldd Unext = intersect(parity[alpha], Vplayer[alpha]);
309 Unext = intersect(Unext, Vsafe[alpha]);
312 std::size_t iter = 0;
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";
321 Unext = m_G.predecessors(U, U);
323 mCRL2log(log::debug) <<
"iteration " << iter <<
" (time = " << std::setprecision(2) << std::fixed << timer.seconds() <<
"s)\n";
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";
334 if (m_compute_strategy)
336 solution.strategy[alpha] = union_(solution.strategy[alpha].value(), merge(U, U));
338 assert(!solution.strategy[alpha].has_value());
341 if (solution.strategy[alpha].has_value())
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";
347 mCRL2log(log::trace) <<
"detect_solitair_cycles: computing safe attractor for player " << alpha <<
" into extended winning set\n";
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)
355 solution.strategy[alpha] = union_(solution.strategy[alpha].value(), attr.second.value());
357 assert(!attr.second.has_value());
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)
366 solution.strategy[alpha] = union_(solution.strategy[alpha].value(), attr.second.value());
368 assert(!attr.second.has_value());
371 mCRL2log(log::trace) <<
"detect_solitair_cycles: extended winning sets and strategy for player " << alpha
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" :
"");
377 mCRL2log(log::trace) <<
"detect_solitair_cycles: partial solution after detecting solitair cycles:\n"
378 << print_solution(m_G, solution) << std::endl;
387 symbolic_solution_t detect_forced_cycles(
const ldd& initial_vertex,
392 const symbolic_solution_t& partial_solution)
394 using namespace sylvan::ldds;
396 symbolic_solution_t solution = partial_solution;
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))
406 mCRL2log(log::trace) <<
"\n--- apply forced winning cycle detection to ---\n" << m_G.print_graph(V) << std::endl;
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())
413 parity[rank % 2] = union_(parity[rank % 2], Vrank);
416 std::array<ldd, 2> Vsafe;
419 Vsafe = { m_G.compute_safe_vertices(0, Vtotal, I), m_G.compute_safe_vertices(1, Vtotal, I) };
422 for (std::size_t alpha = 0; alpha <= 1; ++alpha)
426 ldd Unext = parity[alpha];
429 Unext = intersect(Unext, Vsafe[alpha]);
432 mCRL2log(log::debug) <<
"forced winning cycle detection for player " << alpha <<
"\n";
434 std::size_t iter = 0;
441 Unext = intersect(U, m_G.safe_control_predecessors(alpha, U, Vtotal, U, Vplayer, I));
445 Unext = intersect(U, m_G.safe_control_predecessors(alpha, U, Vsafe[alpha], U, Vplayer));
448 mCRL2log(log::debug) <<
"iteration " << iter <<
" (time = " << std::setprecision(2) << std::fixed << timer.seconds() <<
"s)\n";
453 mCRL2log(log::debug) <<
"found " << std::setw(12) << satcount(U) <<
" states in cycles for player " << alpha <<
"\n";
456 if (m_compute_strategy)
458 solution.strategy[alpha] = union_(solution.strategy[alpha].value(), merge(U, U));
460 assert(!solution.strategy[alpha].has_value());
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)
469 solution.strategy[alpha] = union_(solution.strategy[alpha].value(), attr.second.value());
471 assert(!attr.second.has_value());
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)
480 solution.strategy[alpha] = union_(solution.strategy[alpha].value(), attr.second.value());
482 assert(!attr.second.has_value());
487 mCRL2log(log::trace) << print_solution(m_G, solution) << std::endl;
493 std::pair<ldd, ldd> detect_fatal_attractors(
const ldd& initial_vertex,
498 const ldd& W0 = sylvan::ldds::empty_set(),
499 const ldd& W1 = sylvan::ldds::empty_set())
501 using namespace sylvan::ldds;
504 std::array<ldd, 2> winning = { W0, W1 };
505 std::array<std::optional<ldd>, 2> strategy;
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))
510 return { winning[0], winning[1] };
513 std::array<
const ldd, 2> Vplayer = m_G.players(Vtotal);
516 std::array<ldd, 2> Vsafe;
519 Vsafe = { m_G.compute_safe_vertices(0, Vtotal, I), m_G.compute_safe_vertices(1, Vtotal, I) };
522 mCRL2log(log::trace) <<
"\n--- apply fatal attractor detection to ---\n" << m_G.print_graph(Vtotal) << std::endl;
525 for (
auto it = m_G.ranks().rbegin(); it != m_G.ranks().rend(); it++)
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]);
533 while (X != empty_set() && X != Y)
536 ldd Z = m_G.safe_monotone_attractor(X, alpha, c, safe_variant ? Vtotal : Vsafe[alpha], Vplayer, safe_variant ? I : empty_set());
543 winning[alpha] = union_(winning[alpha], m_G.safe_attractor(Z, alpha, Vtotal, Vplayer, I).first);
547 winning[alpha] = union_(winning[alpha], m_G.safe_attractor(Z, alpha, Vsafe[alpha], Vplayer).first);
549 mCRL2log(log::debug) <<
"found " << std::setw(12) << satcount(Z) <<
" states in fatal attractors for priority " << c <<
"\n";
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;
563 return { winning[0], winning[1] };
568 ldd compute_deadlocks(
const ldd& V,
const symbolic_parity_game& G)
570 ldd predecessors = G.predecessors(V, V);
571 ldd deadlocks = sylvan::ldds::minus(V, predecessors);
578 void check_strategy(
const ldd& initial_vertex,
580 const symbolic_solution_t& solution,
583 using namespace sylvan::ldds;
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());
587 mCRL2log(log::trace) <<
"Minimal parity game G = " << new_G.print_graph(V) << std::endl;
589 ldd new_Vsinks = compute_deadlocks(V, new_G);
591 symbolic_pbessolve_algorithm check(new_G,
false, m_compute_strategy);
593 auto [result, solution_prime] = check.solve(initial_vertex, V, new_Vsinks, symbolic_solution_t(m_compute_strategy),
false);
595 if (includes(union_(solution.winning[0], solution.winning[1]), V))
598 assert(includes(union_(solution_prime.winning[0],solution_prime.winning[1]), V));
602 if (!(solution.winning[0] == solution_prime.winning[0]
603 && solution.winning[1] == solution_prime.winning[1]
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");
618 if (!(includes(solution_prime.winning[0], solution.winning[0])
619 && includes(solution_prime.winning[1], solution.winning[1])
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");