mCRL2
Loading...
Searching...
No Matches
symbolic_parity_game.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_PARITY_GAME_H
11#define MCRL2_PBES_SYMBOLIC_PARITY_GAME_H
12
13#ifdef MCRL2_ENABLE_SYLVAN
14
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"
23
24#include "sylvan_ldd.hpp"
25
26
27
28namespace mcrl2::pbes_system {
29
30using sylvan::ldds::ldd;
31
32namespace detail
33{
34
35inline
36std::string print_pbes_info(const srf_pbes& pbesspec)
37{
38 std::ostringstream out;
39 pbes_equation_index equation_index(pbesspec);
40 for (const auto& equation: pbesspec.equations())
41 {
42 const auto& name = equation.variable().name();
43 out << name << " rank = " << equation_index.rank(name) << " decoration = " << (equation.is_conjunctive() ? "conjunctive" : "disjunctive") << std::endl;
44 }
45 return out.str();
46}
47
48// print the subgraph U of V
49template <typename SummandGroup>
50std::string print_graph(
51 const ldd& U,
52 const ldd& V,
53 const std::vector<SummandGroup>& R,
54 const std::vector<symbolic::data_expression_index>& data_index,
55 const ldd& V0, // disjunctive nodes
56 const std::map<std::size_t, ldd>& rank_map // maps rank to the corresponding set of nodes
57)
58{
59 using namespace sylvan::ldds;
60 using utilities::detail::contains;
61
62 auto rank = [&](const ldd& u)
63 {
64 for (const auto& [r, U]: rank_map)
65 {
66 if (includes(U, u))
67 {
68 return r;
69 }
70 }
71 throw mcrl2::runtime_error("print_graph: could not find a rank");
72 };
73
74 auto index = [](const std::vector<ldd>& v, const ldd& x)
75 {
76 auto i = std::find(v.begin(), v.end(), x);
77 if (i == v.end())
78 {
79 throw mcrl2::runtime_error("print_graph: index error");
80 }
81 return i - v.begin();
82 };
83
84 auto values = [](const ldd& X)
85 {
86 std::vector<ldd> result;
87 auto X_elements = ldd_solutions(X);
88 for (const auto& x: X_elements)
89 {
90 result.push_back(cube(x));
91 }
92 return std::make_pair(result, X_elements);
93 };
94
95 auto succ = [&](const ldd& U)
96 {
97 ldd result = empty_set();
98 for (std::size_t i = 0; i < R.size(); i++)
99 {
100 result = union_(result, alternative_relprod(U, R[i]));
101 }
102 return result;
103 };
104
105 auto [U_values, U_solutions] = values(U);
106 auto [V_values, V_solutions] = values(V);
107
108 std::vector<std::string> text(U_values.size());
109
110 for (std::size_t i = 0; i < U_values.size(); i++)
111 {
112 ldd u = U_values[i];
113 std::size_t u_index = index(V_values, u);
114
115 ldd W = succ(u);
116 auto [W_values, W_solutions] = values(W);
117 std::vector<std::uint32_t> u_successors;
118 for (const ldd& w: W_values)
119 {
120 if (contains(U_values, w))
121 {
122 u_successors.push_back(index(V_values, w));
123 }
124 }
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);
126 }
127 return utilities::string_join(text, "\n");
128}
129
130/// print the indices of U (must be a subset of V)
131inline std::string print_nodes(const ldd& U, const ldd& V)
132{
133 using namespace sylvan::ldds;
134 assert(includes(V, U));
135
136 auto index = [](const std::vector<ldd>& v, const ldd& x)
137 {
138 auto i = std::find(v.begin(), v.end(), x);
139 if (i == v.end())
140 {
141 throw mcrl2::runtime_error("print_nodes: index error");
142 }
143 return i - v.begin();
144 };
145
146 auto values = [](const ldd& X)
147 {
148 std::vector<ldd> result;
149 auto X_elements = ldd_solutions(X);
150 for (const auto& x: X_elements)
151 {
152 result.push_back(cube(x));
153 }
154 return std::make_pair(result, X_elements);
155 };
156
157 auto [V_values, V_solutions] = values(V);
158 auto [U_values, W0_solutions] = values(U);
159
160 std::vector<std::size_t> u;
161 for (const ldd& x: U_values)
162 {
163 u.push_back(index(V_values, x));
164 }
165 return core::detail::print_set(u);
166}
167
168// print the indices of U (subset of V)
169inline std::string print_strategy(const ldd& S, const ldd& V)
170{
171 using namespace sylvan::ldds;
172
173 auto index = [](const std::vector<ldd>& v, const ldd& x)
174 {
175 auto i = std::find(v.begin(), v.end(), x);
176 if (i == v.end())
177 {
178 throw mcrl2::runtime_error("print_strategy: index error");
179 }
180 return i - v.begin();
181 };
182
183 auto values = [](const ldd& X)
184 {
185 std::vector<ldd> result;
186 auto X_elements = ldd_solutions(X);
187 for (const auto& x: X_elements)
188 {
189 result.push_back(cube(x));
190 }
191 return std::make_pair(result, X_elements);
192 };
193
194 auto interleaved_values = [](const ldd& X)
195 {
196 std::vector<std::pair<ldd, ldd>> R;
197 std::vector<std::uint32_t> from;
198 std::vector<std::uint32_t> to;
199
200 auto X_elements = ldd_solutions(X);
201 for (const auto& x: X_elements)
202 {
203 // Take the interleaved strategy and compute two cubes.
204 auto it = x.begin();
205 from.clear();
206 to.clear();
207
208 while (it != x.end())
209 {
210 from.push_back(*it);
211 ++it;
212 to.push_back(*it);
213 ++it;
214 }
215
216 R.emplace_back(cube(from), cube(to));
217 }
218 return std::make_tuple(R, X_elements);
219 };
220
221 auto [V_values, V_solutions] = values(V);
222 auto [R_values, R_solutions] = interleaved_values(S);
223
224 std::vector<std::pair<std::size_t, std::size_t>> u;
225 for (const auto& [from, to]: R_values)
226 {
227 u.emplace_back(index(V_values, from), index(V_values, to));
228 }
229 return core::detail::print_map(u);
230}
231
232
233/// \brief maps proposition variable ldd values to (rank, is_disjunctive)
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)
236{
237 pbes_system::pbes_equation_index equation_index(pbes);
238
239 // map propositional variable names to the corresponding ldd value
240 std::map<core::identifier_string, std::uint32_t> propvar_index;
241 for (const data::data_expression& X: data_index[0])
242 {
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;
246 }
247
248 // maps ldd values to (rank, is_disjunctive)
249 std::map<std::size_t, std::pair<std::size_t, bool>> equation_info;
250 for (const auto& equation: pbes.equations())
251 {
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())
257 {
258 std::uint32_t ldd_value = i->second;
259 equation_info[ldd_value] = { rank, is_disjunctive };
260 }
261 }
262
263 return equation_info;
264}
265
266} // namespace detail
267
268/// \brief This class represents a symbolic (incomplete) parity game with sinks.
269/// Many functions have a parameter V that restricts operations to that set of vertices.
270class symbolic_parity_game
271{
272 protected:
273 std::array<ldd,2> m_V; // m_V[0] is the set of even nodes, m_V[1] is the set of odd nodes
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;
279
280 const std::vector<symbolic::data_expression_index>& m_data_index; // for debugging only
281 ldd m_all_nodes; // for debugging only
282
283 public:
284
285 /// \brief Determine a symbolic parity game from the given pbes, transition groups and index.
286 /// \param V the set of reachable vertices.
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,
291 const ldd& V,
292 bool no_relprod,
293 bool chaining,
294 bool strategy
295 )
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)
297 {
298 using namespace sylvan::ldds;
299 using utilities::detail::contains;
300
301 // Determine priority and owner from the given pbes.
302 auto equation_info = detail::compute_equation_info(pbes, data_index);
303
304 m_V[0] = empty_set();
305 m_V[1] = empty_set();
306
307 // determine the rank and owner of all states.
308 for (const auto& [value, p]: equation_info)
309 {
310 auto rank = p.first;
311 auto is_disjunctive = p.second;
312 ldd X = fix_first_element(V, value);
313
314 auto j = m_rank_map.find(rank);
315 if (j == m_rank_map.end())
316 {
317 m_rank_map[rank] = X;
318 }
319 else
320 {
321 j->second = union_(j->second, X);
322 }
323
324 if (is_disjunctive)
325 {
326 m_V[0] = union_(m_V[0], X);
327 }
328 else
329 {
330 m_V[1] = union_(m_V[1], X);
331 }
332 }
333 }
334
335 /// \brief Determine a symbolic parity game from the given pbes, transition groups and index.
336 /// \param V the set of reachable vertices.
337 symbolic_parity_game(const std::vector<symbolic::summand_group>& summand_groups,
338 const std::vector<symbolic::data_expression_index>& data_index,
339 const ldd& V,
340 const ldd& Veven,
341 const std::vector<ldd>& prio,
342 bool no_relprod,
343 bool chaining,
344 bool strategy)
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),
350 m_all_nodes(V)
351 {
352 m_V[0] = Veven;
353 m_V[1] = minus(V, Veven);
354
355 std::size_t i = 0;
356 for (const auto& p : prio)
357 {
358 m_rank_map[i] = p;
359 ++i;
360 }
361 }
362
363 /// \returns Prints basic parity game information such as number of vertices per priority and per owners.
364 void print_information()
365 {
366 mCRL2log(log::verbose) << "--- parity game information ---" << std::endl;
367 for (const auto&[rank, Vrank] : m_rank_map)
368 {
369 mCRL2log(log::verbose) << "priority " << rank << ": there are " << satcount(Vrank) << " vertices\n";
370 }
371
372 mCRL2log(log::verbose) << "there are " << satcount(m_V[0]) << " even vertices and " << satcount(m_V[1]) << " odd vertices\n";
373 }
374
375 /// \returns A string representing the given vertex set in human readable form.
376 std::string print_nodes(const ldd& V) const
377 {
378 return detail::print_nodes(V, m_all_nodes);
379 }
380
381 /// \returns A string representing the given strategy in human readable form..
382 std::string print_strategy(const ldd& V) const
383 {
384 return detail::print_strategy(V, m_all_nodes);
385 }
386
387 /// \returns A string representing the graph restricted to V.
388 std::string print_graph(const ldd& V) const
389 {
390 return detail::print_graph(V, m_all_nodes, m_summand_groups, m_data_index, m_V[0], m_rank_map);
391 }
392
393 /// \brief Compute the attractor set for U assuming that sinks(V) = emptyset. Optionally computes a strategy if enabled, empty otherwise.
394 /// \param alpha the current player
395 /// \param V is the set of states
396 /// \param Vplayer a partitioning of the nodes into the sets of even nodes V[0] and odd V[1].
397 /// \param I is a set of incomplete vertices.
398 /// \param T A set of states such that iteration stops when one of them occurs in the attractor.
399 std::pair<ldd, std::optional<ldd>> safe_attractor(const ldd& U,
400 std::size_t alpha,
401 const ldd& V,
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
405 {
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";
412
413 using namespace sylvan::ldds;
414
415 std::size_t iter = 0;
416 ldd Z = U;
417 ldd todo = U;
418 ldd Zoutside = minus(V, Z);
419 std::optional<ldd> strategy = m_compute_strategy ? std::optional<ldd>(empty_set()) : std::nullopt;
420
421 while (todo != empty_set())
422 {
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" : "");
428
429 // Terminate early when a vertex in T was found.
430 if (intersect(T, Z) != empty_set() )
431 {
432 return std::make_pair(Z, strategy);
433 }
434
435 stopwatch iter_start;
436
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" : "");
441
442 todo = minus(pred, Z);
443 if (m_compute_strategy)
444 {
445 strategy = union_(strategy.value(), pred_strategy.value()); // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
446 }
447 Z = union_(Z, todo);
448 Zoutside = minus(Zoutside, todo);
449
450 mCRL2log(log::debug) << "safe_attractor: attractor set iteration " << iter
451 << " (time = " << std::setprecision(2) << std::fixed << iter_start.seconds() << "s)"
452 << std::endl;
453
454 ++iter;
455 }
456
457 mCRL2log(log::debug) << "safe_attractor: finished attractor set computation (time = " << std::setprecision(2)
458 << std::fixed << attractor_watch.seconds() << "s)" << std::endl;
459
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);
466 }
467
468 /// \brief Compute the monotone attractor set for U assuming that sinks(V) = emptyset.
469 /// \param alpha the current player.
470 /// \param c priority.
471 /// \param V the set of all vertices.
472 /// \param Vplayer a partitioning of the nodes into the sets of even nodes V[0] and odd V[1].
473 ldd safe_monotone_attractor(const ldd& U,
474 std::size_t alpha,
475 std::size_t c,
476 const ldd& V,
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
480 {
481 using namespace sylvan::ldds;
482
483 stopwatch attractor_watch;
484 mCRL2log(log::debug) << "safe_monotone_attractor: start monotone attractor set computation\n";
485
486 using namespace sylvan::ldds;
487
488 // Compute the set of states with at least priority c.
489 ldd Vc = empty_set();
490 for (const auto&[rank, Vrank] : m_rank_map)
491 {
492 if (rank >= c)
493 {
494 Vc = union_(Vc, Vrank);
495 }
496 }
497
498 // Vertices of player alpha and priority c
499 std::size_t iter = 0;
500 ldd Z = empty_set();
501 ldd todo = U; // union of U and X
502
503 ldd Zoutside = V; // V minus Z
504 while (todo != empty_set())
505 {
506 // Terminate early when a vertex in T was found.
507 if (intersect(T, Z) != empty_set() )
508 {
509 return Z;
510 }
511
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;
515
516 todo = intersect(Vc, minus(safe_control_predecessors_impl(alpha, union_(todo, U), V, minus(Zoutside, U), Vc, Vplayer, I).first, Z));
517 Z = union_(Z, todo);
518 Zoutside = minus(Zoutside, todo);
519
520 mCRL2log(log::debug) << "safe_monotone_attractor: monotone attractor set iteration " << iter
521 << " (time = " << std::setprecision(2) << std::fixed << iter_start.seconds() << "s)"
522 << std::endl;
523
524 ++iter;
525 }
526
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;
529 return Z;
530 }
531
532 /// \returns (min, Vmin) where min is the minimum rank in V and Vmin is the set of vertices with the minimum rank in V
533 std::pair<std::size_t, ldd> get_min_rank(const ldd& V) const
534 {
535 using namespace sylvan::ldds;
536
537 for (const auto& i: m_rank_map)
538 {
539 ldd Vmin = intersect(V, i.second);
540 if (Vmin != empty_set())
541 {
542 std::size_t min_rank = i.first;
543 return { min_rank, Vmin };
544 }
545 }
546
547 throw mcrl2::runtime_error("get_min_rank did not find any nodes");
548 }
549
550 /// \brief Computes the pair of even and odd vertices.
551 std::array<const ldd, 2> players(const ldd& V) const
552 {
553 return { intersect(V, m_V[0]), intersect(V, m_V[1]) };
554 }
555
556 /// \brief Computes the vertices with even parity priority that are not sinks and the same for odd.
557 std::array<const ldd, 2> parity(const ldd& V) const
558 {
559 std::array<ldd, 2> parity;
560 for (const auto&[rank, Vrank] : ranks())
561 {
562 parity[rank % 2] = sylvan::ldds::union_(parity[rank % 2], Vrank);
563 }
564
565 ldd Vother = minus(V, sinks(V, V));
566 return { intersect(Vother, parity[0]), intersect(Vother, parity[1]) };
567 }
568
569 /// \brief Computes all vertices above priority c.
570 ldd prio_above(const ldd& V, std::size_t c) const
571 {
572 // Compute the set of states with at least priority c.
573 ldd Vc = sylvan::ldds::empty_set();
574 for (const auto&[rank, Vrank] : m_rank_map)
575 {
576 if (rank >= c)
577 {
578 Vc = union_(Vc, Vrank);
579 }
580 }
581
582 return intersect(V, Vc);
583 }
584
585 /// \brief Removes all winning states (and updates winning partition).
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
587 {
588 using namespace sylvan::ldds;
589 std::array<const ldd, 2> Vplayer = players(V);
590
591 // After removing the deadlock (winning) states the resulting set of states is a total graph.
592 mCRL2log(log::debug) << "compute_total_graph: removing winning regions" << std::endl;
593 if (Vsinks != empty_set())
594 {
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";
602 }
603 else
604 {
605 mCRL2log(log::trace) << "compute_total_graph: there are no sinks.\n";
606 }
607
608 mCRL2log(log::trace)
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" : "") // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
613 << (strategy[1].has_value() ? " S[1] = " + print_strategy(strategy[1].value()) + "\n" : ""); // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
614
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);
618
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);
621
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" : "") // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
627 << (attr_strategy[1].has_value() ? " S[1] = " + print_strategy(attr_strategy[1].value()) + "\n" : ""); // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
628
629 // Update strategy with attractor strategy. Note this is done in-place
630 if (m_compute_strategy)
631 {
632 strategy[0] = union_(strategy[0].value_or(empty_set()), attr_strategy[0].value()); // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
633 strategy[1] = union_(strategy[1].value_or(empty_set()), attr_strategy[1].value()); // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
634 }
635
636 mCRL2log(log::trace) << "compute_total_graph: combined strategies are:\n"
637 << (strategy[0].has_value() ? " S[0] = " + print_strategy(strategy[0].value()) + "\n" : "") // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
638 << (strategy[1].has_value() ? " S[1] = " + print_strategy(strategy[1].value()) + "\n" : ""); // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
639
640 // After removing the deadlock (winning) states the resulting set of states is a total graph.
641 return minus(minus(V, winning[0]), winning[1]);
642 }
643
644 /// \brief Computes the set of vertices for which partial solving is safe w.r.t. alpha.
645 ldd compute_safe_vertices(
646 std::size_t alpha,
647 const ldd& V,
648 const ldd& I) const
649 {
650 using namespace sylvan::ldds;
651
652 // Compute the safe sets from the resulting subgraph.
653 std::array<const ldd, 2> Vplayer = players(V);
654 ldd S = sinks(I, V);
655 return minus(V, safe_attractor(union_(intersect(I, Vplayer[1-alpha]), S), 1-alpha, V, Vplayer).first);
656 }
657
658 /// \brief Returns the mapping from priorities (ranks) to vertex sets.
659 const std::map<std::size_t, ldd>& ranks() const { return m_rank_map; }
660
661 /// \returns The set { u in U | exists v in V: u -> v }
662 ldd predecessors(const ldd& U, const ldd& V) const
663 {
664 using namespace sylvan::ldds;
665
666 ldd result;
667 for (int i = static_cast<int>(m_summand_groups.size()) - 1; i >= 0; --i)
668 {
669 const symbolic::summand_group& group = m_summand_groups[i];
670
671 stopwatch watch;
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";
675 }
676
677 return result;
678 }
679
680 /// \brief Compute the safe control attractor set for U w.r.t. vertices in V.
681 /// The set W is a set of vertices that restrict chaining.
682 ldd safe_control_predecessors(std::size_t alpha,
683 const ldd& U,
684 const ldd& V,
685 const ldd& W,
686 const std::array<const ldd, 2>& Vplayer,
687 const ldd& I = sylvan::ldds::empty_set()) const
688 {
689 ldd outside = minus(V, U);
690 return safe_control_predecessors_impl(alpha, U, V, outside, W, Vplayer, I).first;
691 }
692
693 /// \brief Computes the set of vertices in U subseteq V that are sinks (no outgoing edges into V).
694 ldd sinks(const ldd& U, const ldd& V) const
695 {
696 return minus(U, predecessors(U, V));
697 }
698
699 /// Returns a symbolic parity game where the strategy has been applied for vertices belonging to player alpha.
700 symbolic_parity_game apply_strategy(bool alpha, const ldd& strategy) const
701 {
702 std::vector<symbolic::summand_group> summand_groups;
703
704 for (auto group : m_summand_groups)
705 {
706 std::vector<std::uint32_t> read_projection;
707 for (const auto& idx : group.read_pos)
708 {
709 if (idx + 1 > read_projection.size())
710 {
711 read_projection.resize(idx + 1);
712 }
713
714 read_projection[idx] = 1;
715 }
716
717 mCRL2log(log::trace) << "L = " << print_relation(m_data_index, group.L, group.read, group.write) << std::endl;
718
719 // Figure out if the group belongs to player alpha.
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());
721 if (is_odd)
722 {
723 mCRL2log(log::trace) << "apply_strategy: summand group " << summand_groups.size() << " belongs to player odd" << std::endl;
724 }
725 else
726 {
727 mCRL2log(log::trace) << "apply_strategy: summand group " << summand_groups.size() << " belongs to player even"
728 << std::endl;
729 }
730
731 if (is_odd == alpha)
732 {
733 if (strategy != sylvan::ldds::empty_set())
734 {
735 // Compute the projection vector based on the read and write parameters of the summand group.
736 std::vector<std::uint32_t> projection(sylvan::ldds::height(strategy), 0);
737
738 for (const auto& read_idx : group.read)
739 {
740 projection[2*read_idx] = 1;
741 }
742
743 for (const auto& write_idx : group.write)
744 {
745 projection[2*write_idx+1] = 1;
746 }
747
748 ldd projected_strategy = sylvan::ldds::project(strategy, sylvan::ldds::cube(projection));
749
750 group.L = sylvan::ldds::intersect(group.L, projected_strategy);
751 }
752 else
753 {
754 // Deal with the special case that the strategy is empty.
755 group.L = sylvan::ldds::empty_set();
756 }
757 }
758 mCRL2log(log::trace) << "L = " << print_relation(m_data_index, group.L, group.read, group.write) << std::endl;
759
760 summand_groups.push_back(group);
761 }
762
763 // This conversion is kind of unnecessary.
764 std::vector<ldd> prio;
765 for (const auto& [p, vertices] : m_rank_map)
766 {
767 if (p + 1 > prio.size())
768 {
769 prio.resize(p + 1);
770 }
771
772 prio[p] = vertices;
773 }
774
775 return symbolic_parity_game(
776 summand_groups,
777 m_data_index,
778 m_all_nodes,
779 m_V[0],
780 prio,
781 m_no_relprod,
782 m_chaining,
783 m_compute_strategy
784 );
785 }
786
787private:
788 /// \returns The set { u in U | exists v in V: u -> v }, where -> is described by the given group.
789 ldd predecessors(const ldd& U, const ldd& V, const symbolic::summand_group& group) const
790 {
791 return m_no_relprod ? symbolic::alternative_relprev(V, group, U) : relprev(V, group.L, group.Ir, U);
792 }
793
794 /// \returns A set of vertices P = { u in U | exists v in V: u ->* v } where ->* only visits intermediate vertices in W (but u may be outside W)
795 /// (without chaining ->* = ->), and a strategy for player alpha on P \setminus U.
796 ///
797 /// \pre U,W subseteq V.
798 std::pair<ldd, std::optional<ldd>> predecessors_chaining(const std::size_t alpha,
799 const ldd& U,
800 const ldd& V,
801 const ldd& W,
802 const std::array<const ldd, 2>& Vplayer) const
803 {
804 using namespace sylvan::ldds;
805
806 ldd P(empty_set());
807 std::optional<ldd> strategy = m_compute_strategy ? std::optional<ldd>(empty_set()) : std::nullopt;
808
809 ldd todo = V;
810 for (int i = static_cast<int>(m_summand_groups.size()) - 1; i >= 0; --i)
811 {
812 const symbolic::summand_group& group = m_summand_groups[i];
813
814 stopwatch watch;
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";
819
820 P = union_(P, todo1);
821 if (m_compute_strategy)
822 {
823 strategy = union_(strategy.value(), merge(minus(intersect(todo1, Vplayer[alpha]), todo), todo)); // NOLINT(bugprone-unchecked-optional-access) optional is known to be engaged here
824 }
825 todo = union_(todo, intersect(todo1, W));
826 }
827
828 return {P, strategy};
829 }
830
831 /// \brief Compute the safe control attractor set for U where chaining is restricted to W and V are vertices considered as control predecessors (can be different from outside).
832 /// The set outside should be minus(V, U)
833 std::pair<ldd, std::optional<ldd>> safe_control_predecessors_impl(std::size_t alpha,
834 const ldd& U,
835 const ldd& V,
836 const ldd& outside,
837 const ldd& W,
838 const std::array<const ldd, 2>& Vplayer,
839 const ldd& I = sylvan::ldds::empty_set()) const
840 {
841 using namespace sylvan::ldds;
842
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";
849
850 ldd P(empty_set());
851 std::optional<ldd> strategy = m_compute_strategy ? std::optional<ldd>(empty_set()) : std::nullopt;
852 if(m_chaining)
853 {
854 std::tie(P, strategy) = predecessors_chaining(alpha, V, U, intersect(Vplayer[alpha], W), Vplayer);
855 }
856 else
857 {
858 P = predecessors(V, U);
859 }
860
861 ldd Palpha = intersect(P, Vplayer[alpha]);
862 ldd Pforced = minus(intersect(P, Vplayer[1-alpha]), I);
863
864 // the predecessor computation without chaining does not compute a strategy
865 // so we still need to calculate it.
866 if(!m_chaining && m_compute_strategy)
867 {
868 strategy = merge(minus(Palpha, U), U);
869 }
870
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" : "");
876
877 for (std::size_t i = 0; i < m_summand_groups.size(); ++i)
878 {
879 const symbolic::summand_group& group = m_summand_groups[i];
880
881 stopwatch watch;
882 Pforced = minus(Pforced, predecessors(Pforced, outside, group));
883
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";
886 }
887
888 return std::make_pair(union_(Palpha, Pforced), strategy);
889 }
890};
891
892} // namespace mcrl2::pbes_system
893
894
895
896#endif // MCRL2_ENABLE_SYLVAN
897
898#endif // MCRL2_PBES_SYMBOLIC_PBESSOLVE_H