10#ifndef MCRL2_PBES_SYMBOLIC_PARITY_GAME_H
11#define MCRL2_PBES_SYMBOLIC_PARITY_GAME_H
13#ifdef MCRL2_ENABLE_SYLVAN
15#include "mcrl2/pbes/srf_pbes.h"
16#include "mcrl2/pbes/pbes_equation_index.h"
17#include "mcrl2/symbolic/alternative_relprod.h"
18#include "mcrl2/symbolic/data_index.h"
19#include "mcrl2/symbolic/print.h"
20#include "mcrl2/utilities/logger.h"
21#include "mcrl2/utilities/text_utility.h"
22#include "mcrl2/utilities/stopwatch.h"
24#include "sylvan_ldd.hpp"
28namespace mcrl2::pbes_system {
30using sylvan::ldds::ldd;
36std::string print_pbes_info(
const srf_pbes& pbesspec)
38 std::ostringstream out;
39 pbes_equation_index equation_index(pbesspec);
40 for (
const auto& equation: pbesspec.equations())
42 const auto& name = equation.variable().name();
43 out << name <<
" rank = " << equation_index.rank(name) <<
" decoration = " << (equation.is_conjunctive() ?
"conjunctive" :
"disjunctive") << std::endl;
49template <
typename SummandGroup>
50std::string print_graph(
53 const std::vector<SummandGroup>& R,
54 const std::vector<symbolic::data_expression_index>& data_index,
56 const std::map<std::size_t, ldd>& rank_map
59 using namespace sylvan::ldds;
60 using utilities::detail::contains;
62 auto rank = [&](
const ldd& u)
64 for (
const auto& [r, U]: rank_map)
71 throw mcrl2::runtime_error(
"print_graph: could not find a rank");
74 auto index = [](
const std::vector<ldd>& v,
const ldd& x)
76 auto i = std::find(v.begin(), v.end(), x);
79 throw mcrl2::runtime_error(
"print_graph: index error");
84 auto values = [](
const ldd& X)
86 std::vector<ldd> result;
87 auto X_elements = ldd_solutions(X);
88 for (
const auto& x: X_elements)
90 result.push_back(cube(x));
92 return std::make_pair(result, X_elements);
95 auto succ = [&](
const ldd& U)
97 ldd result = empty_set();
98 for (std::size_t i = 0; i < R.size(); i++)
100 result = union_(result, alternative_relprod(U, R[i]));
105 auto [U_values, U_solutions] = values(U);
106 auto [V_values, V_solutions] = values(V);
108 std::vector<std::string> text(U_values.size());
110 for (std::size_t i = 0; i < U_values.size(); i++)
113 std::size_t u_index = index(V_values, u);
116 auto [W_values, W_solutions] = values(W);
117 std::vector<std::uint32_t> u_successors;
118 for (
const ldd& w: W_values)
120 if (contains(U_values, w))
122 u_successors.push_back(index(V_values, w));
125 text[i] = std::to_string(u_index) +
" " + symbolic::print_state(data_index, U_solutions[i]) +
", decoration = " + (includes(V0, u) ?
"disjunctive" :
"conjunctive") +
", rank = " + std::to_string(rank(u)) +
", successors = " + core::detail::print_list(u_successors);
127 return utilities::string_join(text,
"\n");
131inline std::string print_nodes(
const ldd& U,
const ldd& V)
133 using namespace sylvan::ldds;
134 assert(includes(V, U));
136 auto index = [](
const std::vector<ldd>& v,
const ldd& x)
138 auto i = std::find(v.begin(), v.end(), x);
141 throw mcrl2::runtime_error(
"print_nodes: index error");
143 return i - v.begin();
146 auto values = [](
const ldd& X)
148 std::vector<ldd> result;
149 auto X_elements = ldd_solutions(X);
150 for (
const auto& x: X_elements)
152 result.push_back(cube(x));
154 return std::make_pair(result, X_elements);
157 auto [V_values, V_solutions] = values(V);
158 auto [U_values, W0_solutions] = values(U);
160 std::vector<std::size_t> u;
161 for (
const ldd& x: U_values)
163 u.push_back(index(V_values, x));
165 return core::detail::print_set(u);
169inline std::string print_strategy(
const ldd& S,
const ldd& V)
171 using namespace sylvan::ldds;
173 auto index = [](
const std::vector<ldd>& v,
const ldd& x)
175 auto i = std::find(v.begin(), v.end(), x);
178 throw mcrl2::runtime_error(
"print_strategy: index error");
180 return i - v.begin();
183 auto values = [](
const ldd& X)
185 std::vector<ldd> result;
186 auto X_elements = ldd_solutions(X);
187 for (
const auto& x: X_elements)
189 result.push_back(cube(x));
191 return std::make_pair(result, X_elements);
194 auto interleaved_values = [](
const ldd& X)
196 std::vector<std::pair<ldd, ldd>> R;
197 std::vector<std::uint32_t> from;
198 std::vector<std::uint32_t> to;
200 auto X_elements = ldd_solutions(X);
201 for (
const auto& x: X_elements)
208 while (it != x.end())
216 R.emplace_back(cube(from), cube(to));
218 return std::make_tuple(R, X_elements);
221 auto [V_values, V_solutions] = values(V);
222 auto [R_values, R_solutions] = interleaved_values(S);
224 std::vector<std::pair<std::size_t, std::size_t>> u;
225 for (
const auto& [from, to]: R_values)
227 u.emplace_back(index(V_values, from), index(V_values, to));
229 return core::detail::print_map(u);
234inline std::map<std::size_t, std::pair<std::size_t,
bool>> compute_equation_info(
const pbes_system::srf_pbes& pbes,
235 const std::vector<symbolic::data_expression_index>& data_index)
237 pbes_system::pbes_equation_index equation_index(pbes);
240 std::map<core::identifier_string, std::uint32_t> propvar_index;
241 for (
const data::data_expression& X: data_index[0])
243 const auto& X_ = atermpp::down_cast<data::function_symbol>(X);
244 std::uint32_t i = propvar_index.size();
245 propvar_index[X_.name()] = i;
249 std::map<std::size_t, std::pair<std::size_t,
bool>> equation_info;
250 for (
const auto& equation: pbes.equations())
252 const core::identifier_string& name = equation.variable().name();
253 std::size_t rank = equation_index.rank(name);
254 bool is_disjunctive = !equation.is_conjunctive();
255 auto i = propvar_index.find(name);
256 if (i != propvar_index.end())
258 std::uint32_t ldd_value = i->second;
259 equation_info[ldd_value] = { rank, is_disjunctive };
263 return equation_info;
270class symbolic_parity_game
273 std::array<ldd,2> m_V;
274 const std::vector<symbolic::summand_group> m_summand_groups;
275 std::map<std::size_t, ldd> m_rank_map;
276 bool m_no_relprod =
false;
277 bool m_chaining =
false;
278 bool m_compute_strategy =
false;
280 const std::vector<symbolic::data_expression_index>& m_data_index;
287 symbolic_parity_game(
288 const srf_pbes& pbes,
289 const std::vector<symbolic::summand_group> summand_groups,
290 const std::vector<symbolic::data_expression_index>& data_index,
296 : m_summand_groups(summand_groups), m_no_relprod(no_relprod), m_chaining(chaining), m_compute_strategy(strategy), m_data_index(data_index), m_all_nodes(V)
298 using namespace sylvan::ldds;
299 using utilities::detail::contains;
302 auto equation_info = detail::compute_equation_info(pbes, data_index);
304 m_V[0] = empty_set();
305 m_V[1] = empty_set();
308 for (
const auto& [value, p]: equation_info)
311 auto is_disjunctive = p.second;
312 ldd X = fix_first_element(V, value);
314 auto j = m_rank_map.find(rank);
315 if (j == m_rank_map.end())
317 m_rank_map[rank] = X;
321 j->second = union_(j->second, X);
326 m_V[0] = union_(m_V[0], X);
330 m_V[1] = union_(m_V[1], X);
337 symbolic_parity_game(
const std::vector<symbolic::summand_group>& summand_groups,
338 const std::vector<symbolic::data_expression_index>& data_index,
341 const std::vector<ldd>& prio,
345 : m_summand_groups(summand_groups),
346 m_no_relprod(no_relprod),
347 m_chaining(chaining),
348 m_compute_strategy(strategy),
349 m_data_index(data_index),
353 m_V[1] = minus(V, Veven);
356 for (
const auto& p : prio)
364 void print_information()
366 mCRL2log(log::verbose) <<
"--- parity game information ---" << std::endl;
367 for (
const auto&[rank, Vrank] : m_rank_map)
369 mCRL2log(log::verbose) <<
"priority " << rank <<
": there are " << satcount(Vrank) <<
" vertices\n";
372 mCRL2log(log::verbose) <<
"there are " << satcount(m_V[0]) <<
" even vertices and " << satcount(m_V[1]) <<
" odd vertices\n";
376 std::string print_nodes(
const ldd& V)
const
378 return detail::print_nodes(V, m_all_nodes);
382 std::string print_strategy(
const ldd& V)
const
384 return detail::print_strategy(V, m_all_nodes);
388 std::string print_graph(
const ldd& V)
const
390 return detail::print_graph(V, m_all_nodes, m_summand_groups, m_data_index, m_V[0], m_rank_map);
399 std::pair<ldd, std::optional<ldd>> safe_attractor(
const ldd& U,
402 const std::array<
const ldd, 2>& Vplayer,
403 const ldd& I = sylvan::ldds::empty_set(),
404 const ldd& T = sylvan::ldds::empty_set())
const
406 stopwatch attractor_watch;
407 mCRL2log(log::debug) <<
"safe_attractor: start attractor set computation\n";
408 mCRL2log(log::trace) <<
" player = " << alpha <<
"\n"
409 <<
" U = " << print_nodes(U) <<
"\n"
410 <<
" I = " << print_nodes(I) <<
"\n"
411 <<
" T = " << print_nodes(T) <<
"\n";
413 using namespace sylvan::ldds;
415 std::size_t iter = 0;
418 ldd Zoutside = minus(V, Z);
419 std::optional<ldd> strategy = m_compute_strategy ? std::optional<ldd>(empty_set()) : std::nullopt;
421 while (todo != empty_set())
423 mCRL2log(log::trace) <<
"safe_attractor: start iteration " << iter <<
"\n";
424 mCRL2log(log::trace) <<
" Z = " << print_nodes(Z) <<
"\n"
425 <<
" todo = " << print_nodes(todo) <<
"\n"
426 <<
" Zoutside = " << print_nodes(Zoutside) <<
"\n"
427 << (strategy.has_value() ?
" strategy = " + print_strategy(strategy.value()) +
"\n" :
"");
430 if (intersect(T, Z) != empty_set() )
432 return std::make_pair(Z, strategy);
435 stopwatch iter_start;
437 const auto& [pred, pred_strategy] = safe_control_predecessors_impl(alpha, todo, Zoutside, Zoutside, V, Vplayer, I);
438 mCRL2log(log::trace) <<
"safe_attractor: computed safe_control_predecessors\n"
439 <<
" pred = " << print_nodes(pred) <<
"\n"
440 << (pred_strategy.has_value() ?
" pred_strategy = " + print_strategy(pred_strategy.value()) +
"\n" :
"");
442 todo = minus(pred, Z);
443 if (m_compute_strategy)
445 strategy = union_(strategy.value(), pred_strategy.value());
448 Zoutside = minus(Zoutside, todo);
450 mCRL2log(log::debug) <<
"safe_attractor: attractor set iteration " << iter
451 <<
" (time = " << std::setprecision(2) << std::fixed << iter_start.seconds() <<
"s)"
457 mCRL2log(log::debug) <<
"safe_attractor: finished attractor set computation (time = " << std::setprecision(2)
458 << std::fixed << attractor_watch.seconds() <<
"s)" << std::endl;
460 mCRL2log(log::trace) <<
"safe_attractor: start iteration " << iter <<
"\n";
461 mCRL2log(log::trace) <<
" Z = " << print_nodes(Z) <<
"\n"
462 <<
" todo = " << print_nodes(todo) <<
"\n"
463 <<
" Zoutside = " << print_nodes(Zoutside) <<
"\n"
464 << (strategy.has_value() ?
" strategy = " + print_strategy(strategy.value()) +
"\n" :
"");
465 return std::make_pair(Z, strategy);
473 ldd safe_monotone_attractor(
const ldd& U,
477 const std::array<
const ldd, 2>& Vplayer,
478 const ldd& I = sylvan::ldds::empty_set(),
479 const ldd& T = sylvan::ldds::empty_set())
const
481 using namespace sylvan::ldds;
483 stopwatch attractor_watch;
484 mCRL2log(log::debug) <<
"safe_monotone_attractor: start monotone attractor set computation\n";
486 using namespace sylvan::ldds;
489 ldd Vc = empty_set();
490 for (
const auto&[rank, Vrank] : m_rank_map)
494 Vc = union_(Vc, Vrank);
499 std::size_t iter = 0;
504 while (todo != empty_set())
507 if (intersect(T, Z) != empty_set() )
512 mCRL2log(log::trace) <<
"safe_monotone_attractor: todo = " << print_nodes(todo) << std::endl;
513 mCRL2log(log::trace) <<
"safe_monotone_attractor: Zoutside = " << print_nodes(Zoutside) << std::endl;
514 stopwatch iter_start;
516 todo = intersect(Vc, minus(safe_control_predecessors_impl(alpha, union_(todo, U), V, minus(Zoutside, U), Vc, Vplayer, I).first, Z));
518 Zoutside = minus(Zoutside, todo);
520 mCRL2log(log::debug) <<
"safe_monotone_attractor: monotone attractor set iteration " << iter
521 <<
" (time = " << std::setprecision(2) << std::fixed << iter_start.seconds() <<
"s)"
527 mCRL2log(log::debug) <<
"safe_monotone_attractor: finished monotone attractor set computation (time = "
528 << std::setprecision(2) << std::fixed << attractor_watch.seconds() <<
"s)" << std::endl;
533 std::pair<std::size_t, ldd> get_min_rank(
const ldd& V)
const
535 using namespace sylvan::ldds;
537 for (
const auto& i: m_rank_map)
539 ldd Vmin = intersect(V, i.second);
540 if (Vmin != empty_set())
542 std::size_t min_rank = i.first;
543 return { min_rank, Vmin };
547 throw mcrl2::runtime_error(
"get_min_rank did not find any nodes");
551 std::array<
const ldd, 2> players(
const ldd& V)
const
553 return { intersect(V, m_V[0]), intersect(V, m_V[1]) };
557 std::array<
const ldd, 2> parity(
const ldd& V)
const
559 std::array<ldd, 2> parity;
560 for (
const auto&[rank, Vrank] : ranks())
562 parity[rank % 2] = sylvan::ldds::union_(parity[rank % 2], Vrank);
565 ldd Vother = minus(V, sinks(V, V));
566 return { intersect(Vother, parity[0]), intersect(Vother, parity[1]) };
570 ldd prio_above(
const ldd& V, std::size_t c)
const
573 ldd Vc = sylvan::ldds::empty_set();
574 for (
const auto&[rank, Vrank] : m_rank_map)
578 Vc = union_(Vc, Vrank);
582 return intersect(V, Vc);
586 ldd compute_total_graph(
const ldd& V,
const ldd& I,
const ldd& Vsinks, std::array<ldd, 2>& winning, std::array<std::optional<ldd>, 2>& strategy)
const
588 using namespace sylvan::ldds;
589 std::array<
const ldd, 2> Vplayer = players(V);
592 mCRL2log(log::debug) <<
"compute_total_graph: removing winning regions" << std::endl;
593 if (Vsinks != empty_set())
595 mCRL2log(log::trace) <<
"compute_total_graph: adding sinks to winning sets.\n"
596 <<
" Vsinks = " << print_nodes(Vsinks) << std::endl;
597 winning[0] = union_(winning[0], intersect(Vsinks, m_V[1]));
598 winning[1] = union_(winning[1], intersect(Vsinks, m_V[0]));
599 mCRL2log(log::trace) <<
"compute_total_graph: new winning sets are:\n"
600 <<
" W[0] = " << print_nodes(winning[0]) <<
"\n"
601 <<
" W[1] = " << print_nodes(winning[1]) <<
"\n";
605 mCRL2log(log::trace) <<
"compute_total_graph: there are no sinks.\n";
609 <<
"compute_total_graph: extending winning sets using attractor set computations. Initial winning sets are:\n"
610 <<
" W[0] = " << print_nodes(winning[0]) <<
"\n"
611 <<
" W[1] = " << print_nodes(winning[1]) <<
"\n"
612 << (strategy[0].has_value() ?
" S[0] = " + print_strategy(strategy[0].value()) +
"\n" :
"")
613 << (strategy[1].has_value() ?
" S[1] = " + print_strategy(strategy[1].value()) +
"\n" :
"");
615 std::array<std::optional<ldd>, 2> attr_strategy;
616 mCRL2log(log::trace) <<
"compute_total_graph: compute safe attractor into W[0]\n";
617 std::tie(winning[0], attr_strategy[0]) = safe_attractor(winning[0], 0, V, Vplayer, I);
619 mCRL2log(log::trace) <<
"compute_total_graph: compute safe attractor into W[1]\n";
620 std::tie(winning[1], attr_strategy[1]) = safe_attractor(winning[1], 1, V, Vplayer, I);
622 mCRL2log(log::trace) <<
"compute_total_graph: extended winning sets to:\n"
623 <<
" W[0] = " << print_nodes(winning[0]) <<
"\n"
624 <<
" W[1] = " << print_nodes(winning[1]) <<
"\n"
625 <<
"with attractor strategy:\n"
626 << (attr_strategy[0].has_value() ?
" S[0] = " + print_strategy(attr_strategy[0].value()) +
"\n" :
"")
627 << (attr_strategy[1].has_value() ?
" S[1] = " + print_strategy(attr_strategy[1].value()) +
"\n" :
"");
630 if (m_compute_strategy)
632 strategy[0] = union_(strategy[0].value_or(empty_set()), attr_strategy[0].value());
633 strategy[1] = union_(strategy[1].value_or(empty_set()), attr_strategy[1].value());
636 mCRL2log(log::trace) <<
"compute_total_graph: combined strategies are:\n"
637 << (strategy[0].has_value() ?
" S[0] = " + print_strategy(strategy[0].value()) +
"\n" :
"")
638 << (strategy[1].has_value() ?
" S[1] = " + print_strategy(strategy[1].value()) +
"\n" :
"");
641 return minus(minus(V, winning[0]), winning[1]);
645 ldd compute_safe_vertices(
650 using namespace sylvan::ldds;
653 std::array<
const ldd, 2> Vplayer = players(V);
655 return minus(V, safe_attractor(union_(intersect(I, Vplayer[1-alpha]), S), 1-alpha, V, Vplayer).first);
659 const std::map<std::size_t, ldd>& ranks()
const {
return m_rank_map; }
662 ldd predecessors(
const ldd& U,
const ldd& V)
const
664 using namespace sylvan::ldds;
667 for (
int i =
static_cast<
int>(m_summand_groups.size()) - 1; i >= 0; --i)
669 const symbolic::summand_group& group = m_summand_groups[i];
672 result = union_(result, predecessors(U, V, group));
673 mCRL2log(log::trace) <<
"predecessors: added predecessors for group " << i <<
" out of " << m_summand_groups.size()
674 <<
" (time = " << std::setprecision(2) << std::fixed << watch.seconds() <<
"s)\n";
682 ldd safe_control_predecessors(std::size_t alpha,
686 const std::array<
const ldd, 2>& Vplayer,
687 const ldd& I = sylvan::ldds::empty_set())
const
689 ldd outside = minus(V, U);
690 return safe_control_predecessors_impl(alpha, U, V, outside, W, Vplayer, I).first;
694 ldd sinks(
const ldd& U,
const ldd& V)
const
696 return minus(U, predecessors(U, V));
700 symbolic_parity_game apply_strategy(
bool alpha,
const ldd& strategy)
const
702 std::vector<symbolic::summand_group> summand_groups;
704 for (
auto group : m_summand_groups)
706 std::vector<std::uint32_t> read_projection;
707 for (
const auto& idx : group.read_pos)
709 if (idx + 1 > read_projection.size())
711 read_projection.resize(idx + 1);
714 read_projection[idx] = 1;
717 mCRL2log(log::trace) <<
"L = " << print_relation(m_data_index, group.L, group.read, group.write) << std::endl;
720 bool is_odd = (sylvan::ldds::intersect(sylvan::ldds::project(group.L, sylvan::ldds::cube(read_projection)), sylvan::ldds::project(m_V[0], group.Ip)) == sylvan::ldds::empty_set());
723 mCRL2log(log::trace) <<
"apply_strategy: summand group " << summand_groups.size() <<
" belongs to player odd" << std::endl;
727 mCRL2log(log::trace) <<
"apply_strategy: summand group " << summand_groups.size() <<
" belongs to player even"
733 if (strategy != sylvan::ldds::empty_set())
736 std::vector<std::uint32_t> projection(sylvan::ldds::height(strategy), 0);
738 for (
const auto& read_idx : group.read)
740 projection[2*read_idx] = 1;
743 for (
const auto& write_idx : group.write)
745 projection[2*write_idx+1] = 1;
748 ldd projected_strategy = sylvan::ldds::project(strategy, sylvan::ldds::cube(projection));
750 group.L = sylvan::ldds::intersect(group.L, projected_strategy);
755 group.L = sylvan::ldds::empty_set();
758 mCRL2log(log::trace) <<
"L = " << print_relation(m_data_index, group.L, group.read, group.write) << std::endl;
760 summand_groups.push_back(group);
764 std::vector<ldd> prio;
765 for (
const auto& [p, vertices] : m_rank_map)
767 if (p + 1 > prio.size())
775 return symbolic_parity_game(
789 ldd predecessors(
const ldd& U,
const ldd& V,
const symbolic::summand_group& group)
const
791 return m_no_relprod ? symbolic::alternative_relprev(V, group, U) : relprev(V, group.L, group.Ir, U);
798 std::pair<ldd, std::optional<ldd>> predecessors_chaining(
const std::size_t alpha,
802 const std::array<
const ldd, 2>& Vplayer)
const
804 using namespace sylvan::ldds;
807 std::optional<ldd> strategy = m_compute_strategy ? std::optional<ldd>(empty_set()) : std::nullopt;
810 for (
int i =
static_cast<
int>(m_summand_groups.size()) - 1; i >= 0; --i)
812 const symbolic::summand_group& group = m_summand_groups[i];
815 ldd todo1 = predecessors(U, todo, group);
816 mCRL2log(log::trace) <<
"predecessors_chaining: added predecessors for group " << i <<
" out of "
817 << m_summand_groups.size() <<
" (time = " << std::setprecision(2) << std::fixed
818 << watch.seconds() <<
"s)\n";
820 P = union_(P, todo1);
821 if (m_compute_strategy)
823 strategy = union_(strategy.value(), merge(minus(intersect(todo1, Vplayer[alpha]), todo), todo));
825 todo = union_(todo, intersect(todo1, W));
828 return {P, strategy};
833 std::pair<ldd, std::optional<ldd>> safe_control_predecessors_impl(std::size_t alpha,
838 const std::array<
const ldd, 2>& Vplayer,
839 const ldd& I = sylvan::ldds::empty_set())
const
841 using namespace sylvan::ldds;
843 mCRL2log(log::trace) <<
"safe_control_predecessors_impl: computing safe control predecessors\n"
844 <<
" alpha = " << alpha <<
"\n"
845 <<
" U = " << print_nodes(U) <<
"\n"
846 <<
" outside = " << print_nodes(outside) <<
"\n"
847 <<
" W = " << print_nodes(W) <<
"\n"
848 <<
" I = " << print_nodes(I) <<
"\n";
851 std::optional<ldd> strategy = m_compute_strategy ? std::optional<ldd>(empty_set()) : std::nullopt;
854 std::tie(P, strategy) = predecessors_chaining(alpha, V, U, intersect(Vplayer[alpha], W), Vplayer);
858 P = predecessors(V, U);
861 ldd Palpha = intersect(P, Vplayer[alpha]);
862 ldd Pforced = minus(intersect(P, Vplayer[1-alpha]), I);
866 if(!m_chaining && m_compute_strategy)
868 strategy = merge(minus(Palpha, U), U);
871 mCRL2log(log::trace) <<
"safe_control_predecessors_impl: initialized to\n"
872 <<
" P = " << print_nodes(P) <<
"\n"
873 <<
" Palpha = " << print_nodes(Palpha) <<
"\n"
874 <<
" Pforced = " << print_nodes(Pforced) <<
"\n"
875 << (strategy.has_value() ?
" strategy = " + print_strategy(strategy.value()) +
"\n" :
"");
877 for (std::size_t i = 0; i < m_summand_groups.size(); ++i)
879 const symbolic::summand_group& group = m_summand_groups[i];
882 Pforced = minus(Pforced, predecessors(Pforced, outside, group));
884 mCRL2log(log::trace) <<
"safe_control_predecessors_impl: removed 1 - alpha predecessors for group " << i <<
" out of " << m_summand_groups.size()
885 <<
" (time = " << std::setprecision(2) << std::fixed << watch.seconds() <<
"s)\n";
888 return std::make_pair(union_(Palpha, Pforced), strategy);