mCRL2
Loading...
Searching...
No Matches
solve_structure_graph.h
Go to the documentation of this file.
1// Author(s): 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/// \file mcrl2/pbes/solve_structure_graph.h
10/// \brief add your file description here.
11
12#ifndef MCRL2_PBES_SOLVE_STRUCTURE_GRAPH_H
13#define MCRL2_PBES_SOLVE_STRUCTURE_GRAPH_H
14
15#include "mcrl2/atermpp/standard_containers/vector.h"
16#include "mcrl2/data/join.h"
17#include "mcrl2/lts/lts_algorithm.h"
18#include "mcrl2/pbes/pbes_equation_index.h"
19#include "mcrl2/pbes/pbessolve_attractors.h"
20#include "mcrl2/pbes/detail/pbes_remove_counterexample_info.h"
21
22namespace mcrl2::pbes_system {
23
24inline
26{
27 std::size_t min_rank = (std::numeric_limits<std::size_t>::max)();
28 std::size_t max_rank = 0;
29 std::vector<structure_graph::index_type> M; // vertices with minimal rank
30 std::size_t N = G.all_vertices().size();
31
32 for (std::size_t vi = 0; vi < N; vi++)
33 {
34 if (!G.contains(vi))
35 {
36 continue;
37 }
38 const auto& v = G.find_vertex(vi);
39 if (v.rank <= min_rank)
40 {
41 if (v.rank < min_rank)
42 {
43 M.clear();
44 min_rank = v.rank;
45 }
46 M.push_back(vi);
47 }
48 if (v.rank > max_rank)
49 {
50 max_rank = v.rank;
51 }
52 }
53 return std::make_tuple(min_rank, max_rank, vertex_set(N, M.begin(), M.end()));
54}
55
57{
58 protected:
59 // do a sanity check on the computed strategy
60 bool check_strategy = false;
61
63
64 // find a successor of u
65 static structure_graph::index_type succ(const structure_graph& G, structure_graph::index_type u)
66 {
67 for (structure_graph::index_type v: G.successors(u))
68 {
69 return v;
70 }
71 return undefined_vertex();
72 }
73
74 // find a successor of u in U, or a random one if no successor in U exists
75 static inline
76 structure_graph::index_type succ(const structure_graph& G, structure_graph::index_type u, const vertex_set& U)
77 {
78 auto result = undefined_vertex();
79 for (structure_graph::index_type v: G.successors(u))
80 {
81 if (U.contains(v))
82 {
83 return v;
84 }
85 result = v;
86 }
87 return result;
88 }
89
90 public:
91 // computes solve_recursive(G \ A)
92 inline
94 {
95 auto exclude = G.exclude() | A.include();
96 std::swap(G.exclude(), exclude);
97 auto result = solve_recursive(G);
98 std::swap(G.exclude(), exclude);
99 return result;
100 }
101
102 protected:
103 // pre: G does not contain nodes with decoration true or false.
104 //
105 // N.B. If use_toms_optimization is true, then the oomputed strategy may be incorrect.
106 // So this flag should only be used to compute the solution.
107 inline
109 {
110 mCRL2log(log::debug) << "\n --- solve_recursive input ---\n" << G << std::endl;
111 std::size_t N = G.extent();
112
113 if (G.is_empty())
114 {
115 return { vertex_set(N), vertex_set(N) };
116 }
117
118 auto q = get_minmax_rank(G);
119 std::size_t m = std::get<0>(q);
120 const vertex_set& U = std::get<2>(q);
121
122 std::size_t alpha = m % 2; // 0 = disjunctive, 1 = conjunctive
123
124 // set strategy
125 for (structure_graph::index_type ui: U.vertices())
126 {
127 const auto& u = G.find_vertex(ui);
128 if (u.decoration == alpha)
129 {
130 // auto v = succ(G, ui); // N.B. this may lead to a wrong strategy!
131 auto v = succ(G, ui, U);
132 if (v != undefined_vertex())
133 {
134 global_strategy<structure_graph>(G).set_strategy(ui, v);
135 }
136 }
137 }
138
139 vertex_set W[2] = { vertex_set(N), vertex_set(N) }; // NOLINT(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays)
140 vertex_set W_1[2]; // NOLINT(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays)
141
142 vertex_set A = attr_default(G, U, alpha);
143 std::tie(W_1[0], W_1[1]) = solve_recursive(G, A);
144
146 {
147 // More efficient than Zielonka, because some recursive calls are skipped.
148 // As a consequence, the computed strategy may be wrong.
149 vertex_set B = attr_default(G, W_1[1 - alpha], 1 - alpha);
150 if (W_1[1 - alpha].size() == B.size())
151 {
152 W[alpha] = set_union(A, W_1[alpha]);
153 W[1 - alpha] = B;
154 }
155 else
156 {
157 std::tie(W[0], W[1]) = solve_recursive(G, B);
158 W[1 - alpha] = set_union(W[1 - alpha], B);
159 }
160 }
161 else
162 {
163 // Original Zielonka version
164 if (W_1[1 - alpha].is_empty())
165 {
166 W[alpha] = set_union(A, W_1[alpha]);
167 W[1 - alpha].clear();
168 }
169 else
170 {
171 vertex_set B = attr_default(G, W_1[1 - alpha], 1 - alpha);
172 std::tie(W[0], W[1]) = solve_recursive(G, B);
173 W[1 - alpha] = set_union(W[1 - alpha], B);
174 }
175 }
176
177 mCRL2log(log::debug) << "\n --- solution for solve_recursive input ---\n" << G;
178 mCRL2log(log::debug) << " W0 = " << W[0] << std::endl;
179 mCRL2log(log::debug) << " W1 = " << W[1] << std::endl;
180 assert(W[0].size() + W[1].size() + G.exclude().count() == N);
181 return { W[0], W[1] };
182 }
183
184 // Handles nodes with decoration true or false.
185 inline
187 {
188 mCRL2log(log::debug) << "\n --- solve_recursive_extended input ---\n" << G << std::endl;
189
190 std::size_t N = G.extent();
191 vertex_set Vconj(N);
192 vertex_set Vdisj(N);
193
194 // find vertices Vconj with decoration false and Vdisj with decoration true
195 for (std::size_t vi = 0; vi < N; vi++)
196 {
197 if (!G.contains(vi))
198 {
199 continue;
200 }
201 const auto& v = G.find_vertex(vi);
202 if (v.decoration == structure_graph::d_false)
203 {
204 Vconj.insert(vi);
205 }
206 else if (v.decoration == structure_graph::d_true)
207 {
208 Vdisj.insert(vi);
209 }
210 }
211
212 // extend Vconj and Vdisj
213 if (!Vconj.is_empty())
214 {
215 Vconj = attr_default(G, Vconj, 1);
216 }
217 if (!Vdisj.is_empty())
218 {
219 Vdisj = attr_default(G, Vdisj, 0);
220 }
221
222 // default case
223 if (Vconj.is_empty() && Vdisj.is_empty())
224 {
225 return solve_recursive(G);
226 }
227 else
228 {
229 vertex_set Wconj(N);
230 vertex_set Wdisj(N);
231 vertex_set Vunion = set_union(Vconj, Vdisj);
232 std::tie(Wdisj, Wconj) = solve_recursive(G, Vunion);
233 return std::make_pair(set_union(Wdisj, Vdisj), set_union(Wconj, Vconj));
234 }
235 }
236
237 static void insert_edge(structure_graph::vertex_vector& V, structure_graph::index_type ui, structure_graph::index_type vi)
238 {
239 using utilities::detail::contains;
240 structure_graph::vertex& u = V[ui];
241 structure_graph::vertex& v = V[vi];
242 if (!contains(u.successors, vi))
243 {
244 u.successors.push_back(vi);
245 v.predecessors.push_back(ui);
246 }
247 }
248
249 void check_solve_recursive_solution(const structure_graph& G, bool is_disjunctive, const vertex_set& Wdisj, const vertex_set& Wconj)
250 {
251 using utilities::detail::contains;
252
253 mCRL2log(log::debug) << "\n--- CHECK STRATEGY ---" << std::endl;
254 log_vertex_set(G, Wconj, "Wconj");
255 log_vertex_set(G, Wdisj, "Wdisj");
256
257 using vertex = structure_graph::vertex;
258 structure_graph::index_type init = G.initial_vertex();
259
260 // V contains the vertices of G, but not the edges
261 structure_graph::vertex_vector V = G.all_vertices();
262 for (vertex& v: V)
263 {
264 v.successors.clear();
265 v.predecessors.clear();
266 }
267
268 std::set<structure_graph::index_type> todo = { init };
269 std::set<structure_graph::index_type> done;
270
271 while (!todo.empty())
272 {
273 structure_graph::index_type u = *todo.begin();
274 todo.erase(todo.begin());
275 done.insert(u);
276 if ((is_disjunctive && G.decoration(u) == structure_graph::d_disjunction) || (!is_disjunctive && G.decoration(u) == structure_graph::d_conjunction))
277 {
278 // explore only the strategy edge
279 structure_graph::index_type v = G.strategy(u);
280 insert_edge(V, u, v);
281 if (v != undefined_vertex() && !contains(done, v))
282 {
283 todo.insert(v);
284 }
285 }
286 else
287 {
288 // explore all outgoing edges
289 for (structure_graph::index_type v: G.successors(u))
290 {
291 insert_edge(V, u, v);
292 if (!contains(done, v))
293 {
294 todo.insert(v);
295 }
296 }
297 }
298 }
299
300 vertex_set Wconj1;
301 vertex_set Wdisj1;
302
303 structure_graph Gcopy(V, G.initial_vertex(), G.exclude());
304 std::tie(Wdisj1, Wconj1) = solve_recursive_extended(Gcopy);
305 bool is_disjunctive1;
307 {
308 is_disjunctive1 = true;
309 }
310 else if (Wconj1.contains(G.initial_vertex()))
311 {
312 is_disjunctive1 = false;
313 }
314 else
315 {
316 throw mcrl2::runtime_error("No solution found!!!");
317 }
318 if (is_disjunctive != is_disjunctive1)
319 {
320 log_vertex_set(Gcopy, Wconj1, "Wconj1");
321 log_vertex_set(Gcopy, Wdisj1, "Wdisj1");
322 throw mcrl2::runtime_error("check_solve_recursive_solution failed!");
323 }
324 }
325
326 public:
327 explicit solve_structure_graph_algorithm(bool check_strategy_ = false, bool use_toms_optimization_ = false)
328 : check_strategy(check_strategy_),
329 use_toms_optimization(use_toms_optimization_)
330 {}
331
332 /// Returns the winning player (alpha)
333 inline
335 {
336 auto W = solve_partitions(G);
337
338 bool is_disjunctive;
339 if (W.first.contains(G.initial_vertex()))
340 {
341 is_disjunctive = true;
342 }
343 else
344 {
345 is_disjunctive = false;
346 }
347
348 return is_disjunctive;
349 }
350
351 /// Returns the winning partition
352 inline
354 {
355 mCRL2log(log::verbose) << "Solving parity game..." << std::endl;
356 mCRL2log(log::debug) << G << std::endl;
357 assert(G.extent() > 0);
358 assert(G.is_defined());
359 auto W = solve_recursive_extended(G);
360 bool is_disjunctive;
361 if (W.first.contains(G.initial_vertex()))
362 {
363 is_disjunctive = true;
364 }
365 else if (W.second.contains(G.initial_vertex()))
366 {
367 is_disjunctive = false;
368 }
369 else
370 {
371 throw mcrl2::runtime_error("No solution found!!!");
372 }
373
374 if (check_strategy)
375 {
376 check_solve_recursive_solution(G, is_disjunctive, W.first, W.second);
377 }
378
379 mCRL2log(log::debug) << "\nSolved structure graph " << std::endl;
380 mCRL2log(log::debug) << G << std::endl;
381 return W;
382 }
383};
384
386{
387 protected:
388 static lps::specification create_counter_example_lps(structure_graph& G, const std::set<structure_graph::index_type>& V, const lps::specification& lpsspec, const pbes& p, const pbes_equation_index& p_index)
389 {
390 try {
391 lps::specification result = lpsspec;
392 result.process().action_summands().clear();
393 result.process().deadlock_summands().clear();
394 auto& action_summands = result.process().action_summands();
395 std::regex re("Z(neg|pos)_(\\d+)_.*");
396 std::size_t n = lpsspec.process().process_parameters().size();
397
398 for (structure_graph::index_type vi: V)
399 {
400 const auto& v = G.find_vertex(vi);
401 if (is_propositional_variable_instantiation(v.formula()))
402 {
403 // The variable Z below should be a reference, but this leads to crashes with the GCC compiler (March 2022).
404 // JFG: I think this is a GCC problem, which may resolve itself in due time.
405 const auto Z = atermpp::down_cast<propositional_variable_instantiation>(v.formula());
406 std::string Zname = Z.name();
407 std::smatch match;
408 if (std::regex_match(Zname, match, re))
409 {
410 std::size_t summand_index = std::stoul(match[2]);
411 if (summand_index >= lpsspec.process().action_summands().size())
412 {
413 throw mcrl2::runtime_error("Counter-example cannot be reconstructed from this LPS. Did you supply the correct file?");
414 }
415
416 // The parameters are [from] + [action_parameters] + [to]
417 lps::action_summand summand = lpsspec.process().action_summands().at(summand_index);
418 std::size_t equation_index = p_index.index(Z.name());
419 const pbes_equation& eqn = p.equations().at(equation_index);
420 const data::variable_list& d = eqn.variable().parameters();
421 data::variable_vector d1(d.begin(), d.end());
422
423 const data::data_expression_list& e = Z.parameters();
424 data::data_expression_vector e1(e.begin(), e.end());
425
426 data::data_expression_vector condition;
427 data::assignment_vector next_state_assignments;
428 std::size_t m = d.size() - 2 * n;
429
430 for (std::size_t i = 0; i < n; i++)
431 {
432 condition.push_back(data::equal_to(d1.at(i), e1.at(i)));
433 next_state_assignments.emplace_back(d1.at(i), e1.at(n + m + i));
434 }
435
436 process::action_vector actions;
437 std::size_t index = 0;
438 for (const process::action& a: summand.multi_action().actions())
439 {
440 if (index > e1.size() || index + a.arguments().size() > e1.size())
441 {
442 throw mcrl2::runtime_error("Invalid parameter index");
443 }
444
445 process::action a1(a.label(), data::data_expression_list(e1.begin() + static_cast<std::ptrdiff_t>(n + index), e1.begin() + static_cast<std::ptrdiff_t>(n + index + a.arguments().size())));
446 actions.push_back(a1);
447 index = index + a.arguments().size();
448 }
449
450 summand.summation_variables() = data::variable_list();
451 summand.condition() = data::join_and(condition.begin(), condition.end());
452 summand.multi_action() = lps::multi_action(process::action_list(actions.begin(), actions.end()),summand.multi_action().time());
453 summand.assignments() = data::assignment_list(next_state_assignments.begin(), next_state_assignments.end());
454
455 action_summands.push_back(summand);
456 }
457 }
458 }
459
460 if (!check_well_typedness(result))
461 {
462 throw mcrl2::runtime_error("The counter example LPS is not well typed, either wrong file provided or an internal error occurred.");
463 }
464
465 return result;
466 }
467 catch (const std::exception& e)
468 {
469 throw mcrl2::runtime_error(std::string("Counter-example cannot be reconstructed, either wrong file provided or an internal error occurred. ") + e.what());
470 }
471 }
472
473 public:
475
476 /// \brief Solve a pbes for some equation, while constructing a counter example or wittness based on the accompanying linear process.
477 /// \param G A structure graph.
478 /// \param lpsspec The original LPS that was used to create the PBES.
479 /// \param p The pbes to be solved.
480 /// \param p_index The index of the pbes equation to be solved.
481 /// \return A boolean indicating the solution and a linear process that represents the counter example.
482 std::pair<bool, lps::specification> solve_with_counter_example(structure_graph& G, const lps::specification& lpsspec, const pbes& p, const pbes_equation_index& p_index)
483 {
484 if (!lpsspec.global_variables().empty())
485 {
486 throw mcrl2::runtime_error("solve_with_counter_example requires an LPS without global variables.");
487 }
488 if (!p.global_variables().empty())
489 {
490 throw mcrl2::runtime_error("solve_with_counter_example requires a PBES without global variables.");
491 }
492
493 mCRL2log(log::verbose) << "Solving parity game..." << std::endl;
494 vertex_set Wconj;
495 vertex_set Wdisj;
496 std::tie(Wdisj, Wconj) = solve_recursive_extended(G);
497 structure_graph::index_type init = G.initial_vertex();
498
499 mCRL2log(log::verbose) << "Extracting evidence..." << std::endl;
500 std::set<structure_graph::index_type> W = extract_minimal_structure_graph(G, init, Wdisj, Wconj);
501 return { Wdisj.contains(init), create_counter_example_lps(G, W, lpsspec, p, p_index) };
502 }
503};
504
506{
507 protected:
508 // Removes all transitions from ltsspec, except the ones in transition_indices.
509 // After that, the unreachable parts of the LTS are removed.
510 static inline
511 void filter_transitions(lts::lts_lts_t& ltsspec, const std::set<std::size_t>& transition_indices)
512 {
513 // remove transitions
514 const auto& lts_transitions = ltsspec.get_transitions();
515 std::vector<lts::transition> transitions;
516 for (std::size_t i: transition_indices)
517 {
518 if (i >= lts_transitions.size())
519 {
520 throw mcrl2::runtime_error("Counter-example cannot be reconstructed from this LTS. Did you supply the correct file?");
521 }
522 transitions.push_back(lts_transitions.at(i));
523 }
524 ltsspec.get_transitions() = transitions;
525
526 // remove unreachable states
527 lts::reachability_check(ltsspec, true);
528 }
529
530 // modifies ltsspec
531 static inline
532 void create_counter_example_lts(structure_graph& G, const std::set<structure_graph::index_type>& V, lts::lts_lts_t& ltsspec)
533 {
534 std::regex re("Z(neg|pos)_(\\d+)_.*");
535
536 try
537 {
538 std::set<std::size_t> transition_indices;
539 for (structure_graph::index_type vi: V)
540 {
541 const auto& v = G.find_vertex(vi);
542 if (is_propositional_variable_instantiation(v.formula()))
543 {
544 const propositional_variable_instantiation& Z = atermpp::down_cast<propositional_variable_instantiation>(v.formula());
545 std::string Zname = Z.name();
546 std::smatch match;
547 if (std::regex_match(Zname, match, re))
548 {
549 std::size_t transition_index = std::stoul(match[2]);
550 transition_indices.insert(transition_index);
551 }
552 }
553 }
554 filter_transitions(ltsspec, transition_indices);
555 }
556 catch (const std::exception& e)
557 {
558 throw mcrl2::runtime_error(std::string("Counter-example cannot be reconstructed, either wrong file provided or an internal error occurred. ") + e.what());
559 }
560 }
561
562 public:
564
565 /// \brief Solve a boolean equation system while generating a counter example.
566 /// \param G A structure graph.
567 /// \param ltsspec The original LTS that was used to create the PBES.
568 inline
570 {
571 mCRL2log(log::verbose) << "Solving parity game..." << std::endl;
572 vertex_set Wconj;
573 vertex_set Wdisj;
574 std::tie(Wdisj, Wconj) = solve_recursive_extended(G);
575 structure_graph::index_type init = G.initial_vertex();
576
577 mCRL2log(log::verbose) << "Extracting evidence..." << std::endl;
578 std::set<structure_graph::index_type> W = extract_minimal_structure_graph(G, init, Wdisj, Wconj);
579 create_counter_example_lts(G, W, ltsspec);
580 return Wdisj.contains(init);
581 }
582};
583
584inline
585bool solve_structure_graph(structure_graph& G, bool check_strategy = false)
586{
587 bool use_toms_optimization = !check_strategy;
588 solve_structure_graph_algorithm algorithm(check_strategy, use_toms_optimization);
589 return algorithm.solve(G);
590}
591
592/// Returns a mapping from PBES variable instantations to vertices in the structure graph for vertices won by player alpha.
593inline
595{
599
600 bool is_disjunctive;
602 {
603 is_disjunctive = true;
604 }
605 else
606 {
607 is_disjunctive = false;
608 }
609
610 // Make a mapping from the formula to the index it belongs to.
614 }
615
618 }
619
620 return { is_disjunctive, mapping };
621}
622
623inline
625{
627 return algorithm.solve_with_counter_example(G, lpsspec, p, p_index);
628}
629
630/// \brief Solve this pbes_system using a structure graph generating a counter example.
631/// \param G The structure graph.
632/// \param ltsspec The original LTS that was used to create the PBES.
633inline
635{
637 return algorithm.solve_with_counter_example(G, ltsspec);
638}
639
640} // namespace mcrl2::pbes_system
641
642
643
644#endif // MCRL2_PBES_SOLVE_STRUCTURE_GRAPH_H
Linear process specification.
This class contains labelled transition systems in .lts format.
Definition lts_lts.h:370
std::pair< bool, lps::specification > solve_with_counter_example(structure_graph &G, const lps::specification &lpsspec, const pbes &p, const pbes_equation_index &p_index)
Solve a pbes for some equation, while constructing a counter example or wittness based on the accompa...
static lps::specification create_counter_example_lps(structure_graph &G, const std::set< structure_graph::index_type > &V, const lps::specification &lpsspec, const pbes &p, const pbes_equation_index &p_index)
static void filter_transitions(lts::lts_lts_t &ltsspec, const std::set< std::size_t > &transition_indices)
bool solve_with_counter_example(structure_graph &G, lts::lts_lts_t &ltsspec)
Solve a boolean equation system while generating a counter example.
static void create_counter_example_lts(structure_graph &G, const std::set< structure_graph::index_type > &V, lts::lts_lts_t &ltsspec)
parameterized boolean equation system
Definition pbes.h:54
std::pair< vertex_set, vertex_set > solve_recursive(structure_graph &G, const vertex_set &A)
static void insert_edge(structure_graph::vertex_vector &V, structure_graph::index_type ui, structure_graph::index_type vi)
solve_structure_graph_algorithm(bool check_strategy_=false, bool use_toms_optimization_=false)
static structure_graph::index_type succ(const structure_graph &G, structure_graph::index_type u)
void check_solve_recursive_solution(const structure_graph &G, bool is_disjunctive, const vertex_set &Wdisj, const vertex_set &Wconj)
bool solve(structure_graph &G)
Returns the winning player (alpha)
std::pair< vertex_set, vertex_set > solve_recursive(structure_graph &G)
static structure_graph::index_type succ(const structure_graph &G, structure_graph::index_type u, const vertex_set &U)
std::pair< vertex_set, vertex_set > solve_partitions(structure_graph &G)
Returns the winning partition.
std::pair< vertex_set, vertex_set > solve_recursive_extended(structure_graph &G)
#define mCRL2log(LEVEL)
mCRL2log(LEVEL) provides the stream used to log.
Definition logger.h:393
The main namespace for the LPS library.
Definition constelm.h:18
bool check_well_typedness(const specification &x)
Definition lps.cpp:118
bool solve_structure_graph(structure_graph &G, bool check_strategy=false)
vertex_set set_union(const vertex_set &V, const vertex_set &W)
std::pair< bool, lps::specification > solve_structure_graph_with_counter_example(structure_graph &G, const lps::specification &lpsspec, const pbes &p, const pbes_equation_index &p_index)
std::tuple< std::size_t, std::size_t, vertex_set > get_minmax_rank(const structure_graph &G)
bool solve_structure_graph_with_counter_example(structure_graph &G, lts::lts_lts_t &ltsspec)
Solve this pbes_system using a structure graph generating a counter example.
bool contains(structure_graph::index_type u) const