mCRL2
Loading...
Searching...
No Matches
liblts_bisim.h
Go to the documentation of this file.
1// Author(s): Muck van Weerdenburg, Jan Friso Groote
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 lts/detail/liblts_bisim.h
10
11#ifndef MCRL2_LTS_DETAIL_LIBLTS_BISIM_H
12#define MCRL2_LTS_DETAIL_LIBLTS_BISIM_H
13
14#include <fstream>
15#include "mcrl2/modal_formula/state_formula.h"
16#include "mcrl2/lts/lts_utilities.h"
17#include "mcrl2/lts/detail/liblts_scc.h"
18#include "mcrl2/lts/detail/liblts_merge.h"
19#include "mcrl2/lts/lts_aut.h"
20#include "mcrl2/lts/lts_fsm.h"
21#include "mcrl2/lts/lts_dot.h"
22
23namespace mcrl2::lts::detail
24{
25
26template < class LTS_TYPE>
28{
29
30 public:
31 /** \brief Creates a bisimulation partitioner for an LTS.
32 * \details This bisimulation partitioner applies the algorithm
33 * defined in J.F. Groote and F.W. Vaandrager. An efficient algorithm for branching bisimulation and stuttering
34 * equivalence. In M.S. Paterson, editor, Proceedings 17th ICALP, Warwick, volume 443 of Lecture Notes in Computer
35 * Science, pages 626-638. Springer-Verlag, 1990. The only difference is that this algorithm uses actions labels
36 * on transitions. Therefore, each list of non_inert_transitions is grouped such that all transitions with the
37 * same label are grouped together. Tau transitions (which have as label index the number of labels) occur at the
38 * beginning of this list.
39 *
40 * If branching is true, then branching bisimulation is used, otherwise strong bisimulation is applied.
41 * If preserve_divergence is true, then branching must be true. In this case states with an internal tau loop
42 * are considered to be different from states without a tau loop. In this way the divergences are preserved.
43 *
44 * The input transition system is not allowed to contain tau loops, except that if preserve_divergence is true
45 * tau loops on a single state are allowed as they indicate divergences. Using the scc partitioner the tau
46 * loops must first be removed before applying this algorithm.
47 * \warning Note that when compiled with optimisations, bisimulation partitioning
48 * is much faster than compiled without any optimisation. The difference can go up to a factor 10.
49 * \param[in] l Reference to the LTS. The LTS l is only changed if \ref replace_transition_system is called. */
50 bisim_partitioner(LTS_TYPE& l,
51 const bool branching = false,
52 const bool preserve_divergence = false,
53 const bool generate_counter_examples = false)
54 : aut(l),
55 branching(branching),
56 store_counter_info(generate_counter_examples)
57 {
58 assert(branching || !preserve_divergence);
59 mCRL2log(log::log_level_t::verbose) << (preserve_divergence?"Divergence preserving b":"B") <<
60 (branching?"ranching b":"") << "isimulation partitioner created for "
61 << l.num_states() << " states and " <<
62 l.num_transitions() << " transitions\n";
63
64 if (generate_counter_examples)
65 {
66 outgoing_transitions = transitions_per_outgoing_state_action_pair(aut.get_transitions(), aut.hidden_label_set());
67 }
68
69 create_initial_partition(branching,preserve_divergence);
70 refine_partition_until_it_becomes_stable(branching, preserve_divergence);
71 }
72
73
74 /** \brief Destroys this partitioner. */
76
77 /** \brief Replaces the transition relation of the current lts by the transitions
78 * of the bisimulation reduced transition system.
79 * \details Each transition (s,l,s') is replaced by a transition (t,l,t') where
80 * t and t' are the equivalence classes to which classes of the LTS. If the label l is
81 * internal, which is detected using the function is_tau, then it is only returned
82 * if t!=t' or preserve_divergence=true. This effectively removes all inert transitions.
83 * Duplicates are removed from the transitions in the new lts.
84 * Also the number of states and the initial state are adapted by this method.
85 *
86 * \pre The bisimulation equivalence classes have been computed.
87 * \param[in] branching Causes non internal transitions to be removed.
88 * \param[in] preserve_divergences Preserves tau loops on states. */
89 void replace_transition_system(const bool branching, const bool preserve_divergences)
90 {
91 // Put all the non inert transitions in a set. Add the transitions that form a self
92 // loop. Such transitions only exist in case divergence preserving branching bisimulation is
93 // used. A set is used to remove double occurrences of transitions.
94 std::set < transition > resulting_transitions;
95
96 for (const transition& i: aut.get_transitions())
97 {
98 const bool is_transition_i_hidden=aut.is_tau(aut.apply_hidden_label_map(i.label()));
99 if (!branching ||
100 !is_transition_i_hidden ||
101 get_eq_class(i.from())!=get_eq_class(i.to()) ||
102 (preserve_divergences && i.from()==i.to()))
103 {
104 resulting_transitions.insert(
105 transition(
106 get_eq_class(i.from()),
107// In the line below all hidden transitions are replaced by an explicit tau. It is possible to
108// always use i.label() where the resulting transition is only implicitly hidden. This has as effect that
109// the labels of non inert hidden transitions are preserved (e.g. for use in counter examples) but that
110// there are possibly multiple hidden transitions between states, leading to a larger number of
111// transitions than strictly necessary.
112 (is_transition_i_hidden?aut.tau_label_index():i.label()),
113 get_eq_class(i.to())));
114 }
115 }
116 // Remove the old transitions
117 aut.clear_transitions();
118
119 // Copy the transitions from the set into the transition system.
120 for (const transition& t: resulting_transitions)
121 {
122 aut.add_transition(t);
123 }
124
125 // Merge the states, by setting the state labels of each state to the concatenation of the state labels of its
126 // equivalence class.
127 if (aut.has_state_info()) /* If there are no state labels this step can be ignored */
128 {
129 /* Create a vector for the new labels */
130 std::vector<typename LTS_TYPE::state_label_t> new_labels(num_eq_classes());
131
132 for(std::size_t i=aut.num_states(); i>0; )
133 {
134 --i;
135 const std::size_t new_index=get_eq_class(i);
136 new_labels[new_index] = new_labels[new_index] + aut.state_label(i);
137 }
138
139 aut.set_num_states(num_eq_classes());
140 for(std::size_t i=0; i<num_eq_classes(); ++i)
141 {
142 aut.set_state_label(i,new_labels[i]);
143 }
144 }
145 else
146 {
147 aut.set_num_states(num_eq_classes());
148 }
149
150 aut.set_initial_state(get_eq_class(aut.initial_state()));
151 }
152
153 /** \brief Gives the number of bisimulation equivalence classes of the LTS.
154 * \return The number of bisimulation equivalence classes of the LTS.
155 */
157 {
158 return max_state_index;
159 }
160
161
162 /** \brief Gives the bisimulation equivalence class number of a state.
163 * \param[in] s A state number.
164 * \return The number of the bisimulation equivalence class to which \e s belongs. */
166 {
167 assert(s<block_index_of_a_state.size());
168 return blocks[block_index_of_a_state[s]].state_index;
169 }
170
171
172 /** \brief Returns whether two states are in the same bisimulation equivalence class.
173 * \param[in] s A state number.
174 * \param[in] t A state number.
175 * \retval true if \e s and \e t are in the same bisimulation equivalence class;
176 * \retval false otherwise. */
177 bool in_same_class(const std::size_t s, const std::size_t t) const
178 {
179 return get_eq_class(s)==get_eq_class(t);
180 }
181
182
183 /** \brief Creates a state formula that distinguishes state s from state t.
184 * \details The states s and t are non bisimilar states. If they are
185 * bisimilar an exception is raised. A counter state formula phi is
186 * returned, which has the property that s |= phi and not t |= phi.
187 * Based on "Computing Distinguishing Formulas for Branching Bisimulation", 1991 by Henri Korver
188 * \param[in] s The state number for which the resulting formula should be true
189 * \param[in] t The state number for which the resulting formula should be false
190 * \return A distinguishing state formula. */
191 mcrl2::state_formulas::state_formula counter_formula(std::size_t s, std::size_t t);
192
193 private:
194
198
200 LTS_TYPE& aut;
203
205 {
207 std::vector < state_type > inert_transitions; // Only the target state is interesting.
208
209 non_bottom_state(const state_type s)
210 : state(s)
211 {}
212 non_bottom_state(const state_type s, const std::vector < state_type > &it)
214 {}
215 };
216
217 struct block
218 {
219 state_type state_index = 0UL; // The state number that represent the states in this block
220 block_index_type block_index = 0UL; // The sequence number of this block.
221 block_index_type parent_block_index = 0UL; // Index of the parent block.
222 // If there is no parent block, this refers to the block
223 // itself.
224 std::vector < state_type > bottom_states; // The non bottom states must be ordered
225 // on tau reachability. The deepest
226 // states occur first in the vector.
228 // The non_inert transitions are grouped per label. The (non-inert) transitions
229 // with tau labels are at the end of this vector.
231
232 void swap(block& b) noexcept
233 {
234 state_type state_index1=b.state_index;
235 b.state_index=state_index;
236 state_index=state_index1;
237
238 block_index_type block_index1=b.block_index;
239 b.block_index=block_index;
240 block_index=block_index1;
241
242 block_index_type parent_block_index1=b.parent_block_index;
243 b.parent_block_index=parent_block_index;
244 parent_block_index=parent_block_index1;
245
246 bottom_states.swap(b.bottom_states);
247 non_bottom_states.swap(b.non_bottom_states);
248 non_inert_transitions.swap(b.non_inert_transitions);
249 }
250 };
251
253
254 // std::vector < bool > block_is_active; // Indicates whether this is still a block in the partition.
255 // Blocks that are split become inactive.
260
263
264 // Counter example information based on "Computing Distinguishing Formulas for Branching Bisimulation", 1991 by Henri Korver
265 // Given a block B that is split using action alpha and block B' into blocks L and R, with P as the partition just before this split, then
266 // right_child[B] = R
268 // split_by_action[B] = alpha,
270 // split_by_block[B] = B',
272 // r_alpha[R] = { C \in P | \exists_{s \in R, s' \in C} : s -a-> s'} and
274 // r_tau[R] = { C \in P | \exists_{s \in R, s' \in C} : s -tau-> s' && C != B}.
276 // map from source state and action to target state, makes generating counterexample info easier
278
279 // A counter for creating fresh variables
281
282
283 void create_initial_partition(const bool branching,
284 const bool preserve_divergences)
285
286 {
287 to_be_processed.clear();
288
289 block initial_partition;
290
291 // First store the bottom and non bottom states.
292 sort_transitions(aut.get_transitions(), aut.hidden_label_set(), mcrl2::lts::transition_sort_style::src_lbl_tgt);
293
294 state_type last_non_stored_state_number=0;
295 bool bottom_state=true;
296 std::vector < state_type > current_inert_transitions;
297
298 {
299 // Reserve enough space, such that no reallocations of the vector are required when adding transitions.
300 // For this purpose, first the number of inert transitions must be counted, to avoid reserving too much
301 // space. This for instance leads to a waste of memory (terabytes for reducing 30M states), especially,
302 // when calculating ia strong bisimulation reduction.
303 std::size_t initial_partition_non_inert_counter=0;
304 std::size_t current_inert_transition_counter=0;
305 const std::vector<transition> & trans=aut.get_transitions();
306 for (auto t: trans)
307 {
308 if (branching && aut.is_tau(aut.apply_hidden_label_map(t.label())))
309 {
310 if (preserve_divergences && t.from()==t.to())
311 {
312 initial_partition_non_inert_counter++;
313 }
314 else
315 {
316 current_inert_transition_counter++;
317 }
318 }
319 }
320 current_inert_transitions.reserve(initial_partition_non_inert_counter);
321 initial_partition.non_inert_transitions.reserve(current_inert_transition_counter);
322 }
323
324 const std::vector<transition>& trans=aut.get_transitions();
325 for (std::vector<transition>::const_iterator r=trans.begin(); r!=trans.end(); ++r)
326 {
327 const transition t=* r;
328
329 if (branching && aut.is_tau(aut.apply_hidden_label_map(t.label())))
330 {
331 if (preserve_divergences && t.from()==t.to())
332 {
333 initial_partition.non_inert_transitions.push_back(transition(t.from(),aut.tau_label_index(),t.to()));
334 }
335 else
336 {
337 current_inert_transitions.push_back(t.to());
338 bottom_state=false;
339 }
340 }
341 std::vector<transition>::const_iterator next_i=r;
342 ++next_i;
343 if (next_i==trans.end() || t.from()!=next_i->from())
344 {
345 // store the current from state
346 for (; last_non_stored_state_number<t.from(); ++last_non_stored_state_number)
347 {
348 initial_partition.bottom_states.push_back(last_non_stored_state_number);
349 }
350
351 if (bottom_state)
352 {
353 initial_partition.bottom_states.push_back(t.from());
354 }
355 else
356 {
357 initial_partition.non_bottom_states.push_back(non_bottom_state(t.from()));
358 current_inert_transitions.swap(initial_partition.non_bottom_states.back().inert_transitions);
359 bottom_state=true;
360 }
361 assert(last_non_stored_state_number==t.from());
362 last_non_stored_state_number++;
363 }
364 }
365
366 for (; last_non_stored_state_number<aut.num_states(); ++last_non_stored_state_number)
367 {
368 initial_partition.bottom_states.push_back(last_non_stored_state_number);
369 }
370
371 // Sort the non-bottom states such that the deepest states occur first.
372 // Raise an exception if there is a non trivial tau loop.
373
374 order_on_tau_reachability(initial_partition.non_bottom_states);
375
376 // Store the non-inert transitions (i.e. the non tau transitions)
377 sort_transitions(aut.get_transitions(), aut.hidden_label_set(), mcrl2::lts::transition_sort_style::lbl_tgt_src);
378 const std::vector<transition> & trans1=aut.get_transitions();
379 for (auto t: trans1)
380 {
381 if (!branching || !aut.is_tau(aut.apply_hidden_label_map(t.label())))
382 {
383 // Note that by sorting the transitions first, the non_inert_transitions are grouped per label.
384 initial_partition.non_inert_transitions.push_back(t);
385 }
386 }
387
388 initial_partition.block_index=0;
389 initial_partition.state_index=0;
390 max_state_index=1;
391 initial_partition.parent_block_index=0;
392 blocks.push_back(block());
393 blocks.back().swap(initial_partition);
394 block_index_of_a_state=std::vector < block_index_type >(aut.num_states(),0);
395 block_flags.push_back(false);
396 state_flags=std::vector < bool >(aut.num_states(),false);
397 block_is_in_to_be_processed.push_back(false);
398 to_be_processed.clear();
399 BL.clear();
400 } // end create_initial_partition
401
402// Refine the partition until the partition has become stable
403#ifndef NDEBUG
404 void refine_partition_until_it_becomes_stable(const bool branching, const bool preserve_divergence)
405#else
406 void refine_partition_until_it_becomes_stable(const bool, const bool)
407#endif
408 {
409#ifndef NDEBUG
410 std::size_t consistency_check_counter=1;
411 std::size_t consistency_check_barrier=1;
412#endif
413 bool partition_is_unstable=true; // This boolean indicates that the partition becomes unstable
414 // because an inert transition becomes non inert.
415 while (!to_be_processed.empty() || partition_is_unstable)
416 {
417#ifndef NDEBUG
418 // Avoid checking too often. This is too time consuming, even in debug mode.
419 consistency_check_counter++;
420 if (consistency_check_counter>=consistency_check_barrier)
421 {
422 consistency_check_counter=0;
423 consistency_check_barrier=consistency_check_barrier*2;
424 check_internal_consistency_of_the_partitioning_data_structure(branching, preserve_divergence);
425 }
426#endif
427 if (to_be_processed.empty() && partition_is_unstable)
428 {
429 // Put all blocks in to_be_processed;
430 for (block_index_type i=0; i< blocks.size(); ++i)
431 {
432 to_be_processed.push_back(i);
433 }
434 block_is_in_to_be_processed=std::vector < bool >(blocks.size(),true);
435 partition_is_unstable=false;
436 }
437
438 const block_index_type splitter_index=to_be_processed.back();
439 to_be_processed.pop_back();
440 block_is_in_to_be_processed[splitter_index]=false;
441
442 // Split with the splitter block, unless it is to_be_processed as we have to reconsider it
443 // completely anyhow at some later point.
444
445 // Walk through the non_inert_transitions via a natural number, because
446 // the blocks and the non_inert_transitions can move around in memory. This makes it
447 // unsafe to use interators.
448
449 for (std::size_t i=0; i<blocks[splitter_index].non_inert_transitions.size(); ++i)
450 {
451 // The flag of the starting state of *i is raised and its block is added to BL;
452
453 assert(blocks[splitter_index].non_inert_transitions[i].from()<aut.num_states());
454 state_flags[blocks[splitter_index].non_inert_transitions[i].from()]=true;
455 const block_index_type marked_block_index=block_index_of_a_state[blocks[splitter_index].non_inert_transitions[i].from()];
456 if (block_flags[marked_block_index]==false)
457 {
458 block_flags[marked_block_index]=true;
459 BL.push_back(marked_block_index);
460 }
461
462 // If the label of the next action is different, we must carry out the splitting.
463 if ((i+1)==blocks[splitter_index].non_inert_transitions.size() ||
464 aut.apply_hidden_label_map(blocks[splitter_index].non_inert_transitions[i].label())!=
465 aut.apply_hidden_label_map(blocks[splitter_index].non_inert_transitions[i+1].label()))
466 {
467 // We consider BL which contains references to all blocks from which a state from splitter
468 // can be reached. If not all flags of the non bottom states in a block in BL are set, the
469 // non flagged non bottom states are moved to a new block.
470
471 split_the_blocks_in_BL(partition_is_unstable,aut.apply_hidden_label_map(blocks[splitter_index].non_inert_transitions[i].label()),splitter_index);
472
473 }
474
475 }
476 }
477#ifndef NDEBUG
479#endif
480 block_flags.clear();
481 block_is_in_to_be_processed.clear();
482 state_flags.clear();
483 to_be_processed.clear();
484 BL.clear();
485 }
486
488
490 bool& partition_is_unstable,
491 const label_type splitter_label,
492 const block_index_type splitter_block)
493 {
494 for (block_index_type i1: BL)
495 {
496 block_flags[i1] = false;
497 std::vector < state_type > flagged_states;
498 std::vector < state_type > non_flagged_states;
499 std::vector < state_type > i1_bottom_states;
500 i1_bottom_states.swap(blocks[i1].bottom_states);
501
502 for (state_type bottom_state: i1_bottom_states)
503 {
504 if (state_flags[bottom_state])
505 {
506 // state is flagged.
507 flagged_states.push_back(bottom_state);
508 }
509 else
510 {
511 // state is not flagged. It will be moved to a new block.
512 non_flagged_states.push_back(bottom_state);
513 block_index_of_a_state[bottom_state] = blocks.size();
514 }
515 }
516 assert(!flagged_states.empty()||!blocks[i1].non_bottom_states.empty()||i1_bottom_states.empty());
517 block_index_type reset_state_flags_block = i1;
518
519 if (!non_flagged_states.empty())
520 {
521 // There are flagged and non flagged states. So, the block must be split.
522 // Move the unflagged states to the new block.
523
524 if (mCRL2logEnabled(log::log_level_t::debug))
525 {
526 const std::size_t new_block_count = (blocks.size()+1)/2;
527 const std::size_t m = static_cast<std::size_t>(std::pow(10.0, std::floor(std::log10(static_cast<double>(new_block_count)))));
528 if (new_block_count % m==0)
529 {
530 mCRL2log(log::log_level_t::debug) << "Bisimulation partitioner: create block " << new_block_count << std::endl;
531 }
532 }
533
534 // Create a first new block.
535 blocks.push_back(block());
536 block_index_type new_block1=blocks.size()-1;
537 blocks.back().state_index=max_state_index;
538 max_state_index++;
539 blocks.back().block_index=new_block1;
540 blocks.back().parent_block_index = i1;
541
542 non_flagged_states.swap(blocks.back().bottom_states);
543 // Put the indices of first split block to to_be_processed.
544 to_be_processed.push_back(blocks.back().block_index);
545 block_is_in_to_be_processed.push_back(true);
546
547 // Create a second new block.
548 blocks.push_back(block());
549 block_index_type new_block2=blocks.size()-1;
550 blocks.back().state_index = blocks[i1].state_index;
551 blocks.back().block_index=new_block2;
552 reset_state_flags_block=new_block2;
553 blocks.back().parent_block_index = i1;
554
555 // Move the flagged states to the second block, and let the block index of these states refer to this block.
556 flagged_states.swap(blocks.back().bottom_states);
557 std::vector < state_type > &reference_to_flagged_states_of_block2=blocks.back().bottom_states;
558 for (state_type j: reference_to_flagged_states_of_block2)
559 {
560 block_index_of_a_state[j] = new_block2;
561 }
562
563 // NOTES: new_block1 = R, new_block2 = L
564 // Store counter formula info
565 if (store_counter_info)
566 {
567 right_child[i1] = new_block1;
568 split_by_action[i1] = splitter_label;
569 split_by_block[i1] = splitter_block;
570
571 // compute r_alpha[new_block1]
572 std::set<block_index_type> reachable_blocks = {};
573 for (state_type source_state : blocks[new_block1].bottom_states)
574 {
575 for (outgoing_transitions_per_state_action_t::const_iterator i = outgoing_transitions.lower_bound(std::pair<state_type, label_type>(source_state, splitter_label));
576 i != outgoing_transitions.upper_bound(std::pair<state_type, label_type>(source_state, splitter_label)); ++i)
577 {
578 state_type target_state = to(i);
579 block_index_type target_block = block_index_of_a_state[target_state];
580 reachable_blocks.insert(target_block == new_block1 || target_block == new_block2 ? i1 : target_block);
581 }
582 }
583 r_alpha[new_block1] = reachable_blocks;
584
585 if (branching) {
586 // compute r_tau[new_block1]
587 std::set<block_index_type> tau_reachable_blocks = {};
588 for (state_type source_state : blocks[new_block1].bottom_states)
589 {
590 for (label_type lab = 0; lab < aut.num_action_labels(); ++lab)
591 {
592 if (aut.is_tau(aut.apply_hidden_label_map(lab)))
593 {
594 for (outgoing_transitions_per_state_action_t::const_iterator i = outgoing_transitions.lower_bound(std::pair<state_type, label_type>(source_state, lab));
595 i != outgoing_transitions.upper_bound(std::pair<state_type, label_type>(source_state, lab)); ++i)
596 {
597 state_type target_state = to(i);
598 block_index_type target_block = block_index_of_a_state[target_state];
599 if (!(target_block == i1 || target_block == new_block1 || target_block == new_block2))
600 {
601 tau_reachable_blocks.insert(target_block);
602 }
603 }
604 }
605 }
606 }
607 r_tauP[new_block1] = tau_reachable_blocks;
608 }
609 }
610
611 // Put the indices of second split block to to_be_processed.
612 to_be_processed.push_back(blocks.back().block_index);
613 block_is_in_to_be_processed.push_back(true);
614
615 // reset the flag of block *i1, which is being split.
616 block_is_in_to_be_processed[i1] = false;
617
618 // The flag fields of the new blocks is set to false;
619 block_flags.push_back(false);
620 block_flags.push_back(false);
621
622 // Declare already some space for transitions, such that we can
623 // put inert transitions that become non inert in there when investigating
624 // the non bottom states. After investigating the non bottom states,
625 // the transitions are split over the vectors below.
626
627 std::vector < transition > flagged_non_inert_transitions;
628 std::vector < transition > non_flagged_non_inert_transitions;
629 // Reserve enough space for transitions to be copied. Otherwise, resizing may lead to
630 // lot of unneccesary copying...
631 flagged_non_inert_transitions.reserve(blocks[i1].non_inert_transitions.size());
632 non_flagged_non_inert_transitions.reserve(blocks[i1].non_inert_transitions.size());
633
634 // Next we scan the non-bottom states of *i1. If for some non-bottom state the flag is not raised
635 // and if none of the outgoing P-inert transitions leads to a state in the old block then this
636 // state becomes a non bottom state of B2.
637
638 std::vector < non_bottom_state > flagged_non_bottom_states;
639 std::vector < non_bottom_state > non_flagged_non_bottom_states;
640 std::vector < non_bottom_state > i1_non_bottom_states;
641 i1_non_bottom_states.swap(blocks[i1].non_bottom_states);
642 for (typename std::vector < non_bottom_state >::iterator k=i1_non_bottom_states.begin();
643 k!=i1_non_bottom_states.end(); ++k)
644 {
645 const std::vector < state_type > &inert_transitions=k->inert_transitions;
646 if (!state_flags[k->state])
647 {
648 bool all_transitions_end_in_unflagged_block=true;
649 for (std::vector < state_type > :: const_iterator l=inert_transitions.begin();
650 all_transitions_end_in_unflagged_block && l!=inert_transitions.end(); ++l)
651 {
652 if (block_index_of_a_state[*l]!= new_block1)
653 {
654 block_index_of_a_state[*l]=new_block2;
655 all_transitions_end_in_unflagged_block=false;
656 }
657 }
658 if (all_transitions_end_in_unflagged_block)
659 {
660 // Move *k to the non flagged block. Swap the inert transitions to avoid copying.
661 non_bottom_state s(k->state);
662 s.inert_transitions.swap(k->inert_transitions);
663 non_flagged_non_bottom_states.push_back(s);
664 block_index_of_a_state[k->state]=new_block1;
665 continue;
666 }
667 }
668 // Move *k to the flagged block; note that the transitions can have become
669 // non-inert. So, investigate them separately.
670 std::vector < state_type > remaining_inert_transitions;
671 for (state_type inert_transition: inert_transitions)
672 {
673 if (block_index_of_a_state[inert_transition] == new_block1)
674 {
675 // The transition *l (*k,tau_label,*l) becomes a non inert transition in the new
676 // block.
677 non_flagged_non_inert_transitions.push_back(
678 transition(k->state, aut.tau_label_index(), inert_transition));
679 }
680 else
681 {
682 // The transition represented by *l remains an inert transition.
683 block_index_of_a_state[inert_transition] = new_block2;
684 remaining_inert_transitions.push_back(inert_transition);
685 }
686 }
687 if (remaining_inert_transitions.empty()) // The last outgoing inert tau transition just became non inert.
688 // k->state has become a bottom state. Otherwise it remains
689 // a non bottom state.
690 {
691 blocks[new_block2].bottom_states.push_back(k->state);
692 block_index_of_a_state[k->state]=new_block2;
693 partition_is_unstable=true;
694 }
695 else
696 {
697 flagged_non_bottom_states.push_back(non_bottom_state(k->state,remaining_inert_transitions));
698 block_index_of_a_state[k->state]=new_block2;
699 }
700 }
701 non_flagged_non_bottom_states.swap(blocks[new_block1].non_bottom_states);
702 flagged_non_bottom_states.swap(blocks[new_block2].non_bottom_states);
703
704 // Finally the non-inert transitions are distributed over both blocks in the obvious way.
705 // Note that this must be done after all states are properly put into a new block.
706
707 assert(i1 < blocks.size());
708 std::vector < transition > i1_non_inert_transitions;
709 i1_non_inert_transitions.swap(blocks[i1].non_inert_transitions);
710 for (auto& i1_non_inert_transition: i1_non_inert_transitions)
711 {
712 if (block_index_of_a_state[i1_non_inert_transition.to()] == new_block1)
713 {
714 non_flagged_non_inert_transitions.push_back(i1_non_inert_transition);
715 }
716 else
717 {
718 block_index_of_a_state[i1_non_inert_transition.to()] = new_block2;
719 flagged_non_inert_transitions.push_back(i1_non_inert_transition);
720 }
721 }
722
723 // Only put the parts that we need to store. Avoid that reserved space for
724 // current_inert_transition_counter and flagged_non_inert_transitions is stored
725 // in the non_inert_transitions in both blocks. Therefore, we do not use a copy
726 // constructor, but put elements in there one by one.
727 // The two lines below were old code, wasting memory bandwidth.
728 // non_flagged_non_inert_transitions.swap(blocks[new_block1].non_inert_transitions);
729 // flagged_non_inert_transitions.swap(blocks[new_block2].non_inert_transitions);
730
731 blocks[new_block1].non_inert_transitions.reserve(non_flagged_non_inert_transitions.size());
732 for (const auto& non_flagged_non_inert_transition: non_flagged_non_inert_transitions)
733 {
734 blocks[new_block1].non_inert_transitions.push_back(non_flagged_non_inert_transition);
735 }
736
737 blocks[new_block2].non_inert_transitions.reserve(flagged_non_inert_transitions.size());
738 for (const auto& flagged_non_inert_transition: flagged_non_inert_transitions)
739 {
740 blocks[new_block2].non_inert_transitions.push_back(flagged_non_inert_transition);
741 }
742 }
743 else
744 {
745 // Nothing changed, so put the bottom states back again.
746 i1_bottom_states.swap(blocks[i1].bottom_states);
747 }
748 // reset the state flags
749 std::vector < state_type > &flagged_states_to_be_unflagged=blocks[reset_state_flags_block].bottom_states;
750 for (state_type j: flagged_states_to_be_unflagged)
751 {
752 state_flags[j] = false;
753 }
754
755 std::vector < non_bottom_state > &flagged_states_to_be_unflagged1=blocks[reset_state_flags_block].non_bottom_states;
756 for (typename std::vector < non_bottom_state >::const_iterator j=flagged_states_to_be_unflagged1.begin();
757 j!=flagged_states_to_be_unflagged1.end(); ++j)
758 {
759 state_flags[j->state]=false;
760 }
761 }
762 BL.clear();
763 }
764
766 const state_type s,
770 {
771 if (inert_transition_map.count(s)>0) // The state s is a bottom state. We need not to investigate these.
772 {
773 if (visited.count(s)==0)
774 {
775 visited.insert(s);
776 std::vector < state_type> &inert_transitions=inert_transition_map[s];
777 for (state_type inert_transition: inert_transitions)
778 {
779 order_recursively_on_tau_reachability(inert_transition,
780 inert_transition_map,
781 new_non_bottom_states,
782 visited);
783 }
784 new_non_bottom_states.push_back(non_bottom_state(s));
785 inert_transitions.swap(new_non_bottom_states.back().inert_transitions);
786 }
787 }
788 }
789
790 void order_on_tau_reachability(std::vector < non_bottom_state > &non_bottom_states)
791 {
792 std::set < state_type > visited;
793 std::map < state_type, std::vector < state_type > > inert_transition_map;
794 for (typename std::vector < non_bottom_state >::iterator i=non_bottom_states.begin();
795 i!=non_bottom_states.end(); ++i)
796 {
797 i->inert_transitions.swap(inert_transition_map[i->state]);
798 }
799 std::vector < non_bottom_state > new_non_bottom_states;
800
801 for (typename std::vector < non_bottom_state >::const_iterator i=non_bottom_states.begin();
802 i!=non_bottom_states.end(); ++i)
803 {
804 order_recursively_on_tau_reachability(i->state, inert_transition_map, new_non_bottom_states, visited);
805 }
806 new_non_bottom_states.swap(non_bottom_states);
807 }
808
809 /**
810 * \brief conjunction Creates a conjunction of state formulas
811 * \param[in] terms The terms of the conjunction
812 * \return The conjunctive state formula
813 */
814 mcrl2::state_formulas::state_formula conjunction(std::set<mcrl2::state_formulas::state_formula> terms) const
815 {
816 return utilities::detail::join<mcrl2::state_formulas::state_formula>(terms.begin(), terms.end(),
817 [](mcrl2::state_formulas::state_formula a, mcrl2::state_formulas::state_formula b)
818 { return mcrl2::state_formulas::and_(a, b); }, mcrl2::state_formulas::true_());
819 }
820
821 /**
822 * \brief create_regular_formula Creates a regular formula that represents action a
823 * \details In case the action comes from an LTS in the lts format.
824 * \param[in] a The action for which to create a regular formula
825 * \return The created regular formula
826 */
828 {
830 }
831
832 /**
833 * \brief create_regular_formula Creates a regular formula that represents action a
834 * \details In case the action comes from an LTS in the aut or fsm format.
835 * \param[in] a The action for which to create a regular formula
836 * \return The created regular formula
837 */
838 regular_formulas::regular_formula create_regular_formula(const mcrl2::lts::action_label_string& a) const
839 {
841 process::action_list({ process::action(process::action_label(a, {}), {}) })));
842 }
843
844 /**
845 * \brief until_formula Creates a state formula that corresponds to the until operator phi1<a>phi2 from HMLU
846 * \details This operator intuitively means: "phi1 holds while stuttering until we can do an a-step after which phi2 holds"
847 * In the operators of the mu-calculus that mCRL2 supports we can define this as:
848 * phi2 || (mu X.phi1 && (<tau>X || <a>phi2)) if a = tau
849 * mu X.phi1 && (<tau>X || <a>phi2) else
850 * \param[in] phi1 The first state formula for the until operator
851 * \param[in] a The action for the until operator
852 * \param[in] phi2 The second state formula for the until operator
853 * \return A state formula that corresponds to the until operator phi1<a>phi2 from HMLU
854 */
855 mcrl2::state_formulas::state_formula until_formula(const mcrl2::state_formulas::state_formula& phi1, const label_type& a,
856 const mcrl2::state_formulas::state_formula& phi2)
857 {
858 std::string var = "X" + std::to_string(fresh_var_counter++);
859 mcrl2::state_formulas::state_formula tauStep =
860 mcrl2::state_formulas::may(regular_formulas::regular_formula(action_formulas::multi_action()), mcrl2::state_formulas::variable(var, {}));
861 mcrl2::state_formulas::state_formula lastStep = mcrl2::state_formulas::may(create_regular_formula(aut.action_label(a)), phi2);
862
863 mcrl2::state_formulas::state_formula until =
864 mcrl2::state_formulas::mu(var, {}, mcrl2::state_formulas::and_(phi1, mcrl2::state_formulas::or_(tauStep, lastStep)));
865
866 if (aut.is_tau(aut.apply_hidden_label_map(a)))
867 {
868 until = mcrl2::state_formulas::or_(phi2, until);
869 }
870 return until;
871 }
872
873 /* \brief Creates a state formula that distinguishes states in block B1 from state in block B2.
874 \details Based on "Computing Distinguishing Formulas for Branching Bisimulation", 1991 by Henri Korver.
875 Variable names used below correspond to definitions in this paper.
876 \param[in] B1 The block on which the resulting formula must be true.
877 \param[in] B2 The block on which the resulting formula must be false.
878 \return A distinguishing state formula.
879 */
880 mcrl2::state_formulas::state_formula counter_formula_aux(const block_index_type B1, const block_index_type B2)
881 {
882 // First find the smallest block containing both B1 and B2.
883 // Find all blocks containing B1.
884 std::set < block_index_type > blocks_containing_B1;
885 block_index_type B1p = B1;
886 blocks_containing_B1.insert(B1p);
887 while (blocks[B1p].parent_block_index != B1p)
888 {
889 B1p = blocks[B1p].parent_block_index;
890 blocks_containing_B1.insert(B1p);
891 }
892
893 // Go up the block tree from B2 until we find a block that contains B1.
894 block_index_type DB = B2;
895 while (blocks_containing_B1.count(DB) == 0)
896 {
897 assert(blocks[DB].parent_block_index != DB);
898 DB = blocks[DB].parent_block_index;
899 }
900
901 // Now DB is the smallest block containing both B1 and B2.
902
903 const block_index_type R = right_child.at(DB);
904 const label_type a = split_by_action.at(DB);
905 const block_index_type Bp = split_by_block.at(DB);
906
907 // We distinguish Bp with every block in r_alpha[R]
908 std::set<mcrl2::state_formulas::state_formula> Gamma2;
909 for (block_index_type PPB : r_alpha.at(R))
910 {
911 Gamma2.insert(counter_formula_aux(Bp, PPB));
912 }
913 mcrl2::state_formulas::state_formula Phi2 = conjunction(Gamma2);
914
915 mcrl2::state_formulas::state_formula Phi;
916 if (branching)
917 {
918 // We distinguish DB with every block in r_tauP[R]
919 std::set<mcrl2::state_formulas::state_formula> Gamma1;
920 for (block_index_type PPB : r_tauP.at(R))
921 {
922 Gamma1.insert(counter_formula_aux(DB, PPB));
923 }
924 mcrl2::state_formulas::state_formula Phi1 = conjunction(Gamma1);
925
926 // In case a == tau, we also need to distinguish Bp with R
927 if (aut.is_tau(aut.apply_hidden_label_map(a)))
928 {
929 Phi2 = mcrl2::state_formulas::and_(Phi2, counter_formula_aux(Bp, R));
930 }
931
932 Phi = until_formula(Phi1, a, Phi2);
933 }
934 else
935 {
936 Phi = mcrl2::state_formulas::may(create_regular_formula(aut.action_label(a)), Phi2);
937 }
938
939 if (blocks_containing_B1.count(R) == 0)
940 {
941 return Phi;
942 }
943 else
944 {
945 return mcrl2::state_formulas::not_(Phi);
946 }
947 }
948
949
950#ifndef NDEBUG
951 // The method below is intended to check the consistency of the internal data
952 // structure. Its sole purpose is to detect programming errors. It has no
953 // side effects on the data structure. If a problem occurs, execution halts with
954 // an assert.
955
957 const bool branching,
958 const bool preserve_divergence) const
959 {
960 state_type total_number_of_states=0;
961 std::size_t total_number_of_transitions=0;
962
963 assert(!blocks.empty());
964 std::set < block_index_type > block_indices;
965
966 assert(block_index_of_a_state.size()==aut.num_states());
967 for (typename std::vector < block >::const_iterator i=blocks.begin();
968 i!=blocks.end(); ++i)
969 {
970 // Check the block_index.
971 assert(i->block_index<blocks.size());
972 assert(block_indices.count(i->block_index)==0);
973 block_indices.insert(i->block_index);
974
975 // Check the bottom states.
976 const std::vector < state_type > &i_bottom_states=i->bottom_states;
977
978 for (state_type bottom_state: i_bottom_states)
979 {
980 total_number_of_states++;
981 assert(bottom_state < aut.num_states());
982 // Check that the block number of the state is maintained properly.
983 assert(block_index_of_a_state[bottom_state] == i->block_index);
984 }
985
986 // Check the non bottom states. In particular check that there is no tau loop
987 // in these non bottom states.
988 const std::vector < non_bottom_state > &i_non_bottom_states=i->non_bottom_states;
989 std::set < state_type > visited;
990 std::set < state_type > local_bottom_states;
991 for (typename std::vector < non_bottom_state >::const_iterator j=i_non_bottom_states.begin();
992 j!=i_non_bottom_states.end(); ++j)
993 {
994 local_bottom_states.insert(j->state);
995 }
996
997 for (typename std::vector < non_bottom_state >::const_iterator j=i_non_bottom_states.begin();
998 j!=i_non_bottom_states.end(); ++j)
999 {
1000 total_number_of_states++;
1001 assert(j->state<aut.num_states());
1002 // Check that the block number of the state is maintained properly.
1003 assert(block_index_of_a_state[j->state]==i->block_index);
1004 const std::vector < state_type > &j_inert_transitions=j->inert_transitions;
1005 for (state_type inert_transition: j_inert_transitions)
1006 {
1007 total_number_of_transitions++;
1008 assert(inert_transition < aut.num_states());
1009 // Check that the inert transitions are well ordered.
1010 assert(visited.count(inert_transition) > 0 || local_bottom_states.count(inert_transition) == 0);
1011 }
1012 visited.insert(j->state);
1013 }
1014
1015 // Check the non_inert_transitions. It is required that the transitions
1016 // are grouped per label, and that tau transitions must be inert.
1017
1018 const std::vector < transition > &i_non_inert_transitions=i->non_inert_transitions;
1019 std::set < label_type > observed_action_labels;
1020 for (std::vector < transition >::const_iterator j=i_non_inert_transitions.begin();
1021 j!=i_non_inert_transitions.end(); ++j)
1022 {
1023 total_number_of_transitions++;
1024 assert(j->to()<aut.num_states());
1025 assert(j->from()<aut.num_states());
1026
1027 // Check proper grouping of action labels.
1028 std::vector < transition >::const_iterator j_next=j;
1029 j_next++;
1030 if (j_next==i_non_inert_transitions.end() || (aut.apply_hidden_label_map(j->label())!=aut.apply_hidden_label_map(j_next->label())))
1031 {
1032 assert(observed_action_labels.count(aut.apply_hidden_label_map(j->label()))==0);
1033 observed_action_labels.insert(aut.apply_hidden_label_map(j->label()));
1034 }
1035
1036 // Check whether tau transition in non inert transition vector is inert.
1037 if (!preserve_divergence && branching && aut.is_tau(aut.apply_hidden_label_map(j->label())))
1038 {
1039 assert(j->to()!=j->from());
1040 }
1041
1042 // Check whether the target state of the transition is in the current block.
1043 assert(block_index_of_a_state[j->to()]==i->block_index);
1044 }
1045 }
1046
1047 // Check total number of states and transitions.
1048 assert(total_number_of_states==aut.num_states());
1049 assert(total_number_of_transitions==aut.num_transitions());
1050
1051 // Check block_index_of_a_state
1052 assert(block_index_of_a_state.size()==aut.num_states());
1053 for (block_index_type i: block_index_of_a_state)
1054 {
1055 assert(blocks[i].block_index == i);
1056 }
1057
1058 // Check block_flags that the block flags are all set to false
1059 for (bool block_flag: block_flags)
1060 {
1061 assert(!block_flag);
1062 }
1063
1064 // Check that state_flags are all false.
1065 for (bool state_flag: state_flags)
1066 {
1067 assert(!state_flag);
1068 }
1069
1070 // Check to_be_processed
1071 // Check block_is_in_to_be_processed
1072 std::vector < bool > temporary_block_is_in_to_be_processed(blocks.size(),false);
1073
1074 for (block_index_type i: to_be_processed)
1075 {
1076 temporary_block_is_in_to_be_processed[i] = true;
1077 }
1078 for (state_type i=0; i<blocks.size(); ++i)
1079 {
1080 assert(!block_is_in_to_be_processed[i] || temporary_block_is_in_to_be_processed[i]);
1081 }
1082
1083 // Check that BL is empty.
1084 assert(BL.empty());
1085
1086 }
1087
1088#endif // not NDEBUG
1089
1090};
1091
1092
1093/** \brief Reduce transition system l with respect to strong or (divergence preserving) branching bisimulation.
1094 * \param[in,out] l The transition system that is reduced.
1095 * \param[in] branching If true branching bisimulation is applied, otherwise strong bisimulation.
1096 * \param[in] preserve_divergences Indicates whether loops of internal actions on states must be preserved. If false
1097 * these are removed. If true these are preserved. */
1098template<class LTS_TYPE>
1099void bisimulation_reduce(LTS_TYPE& l, bool branching = false, bool preserve_divergences = false);
1100
1101/** \brief Checks whether the two initial states of two lts's are strong or branching bisimilar.
1102 * \details This lts and the lts l2 are not usable anymore after this call.
1103 * The space consumption is O(n) and time is O(nm). It uses the branching bisimulation
1104 * algorithm by Groote and Vaandrager from 1990.
1105 * \param[in,out] l1 A first transition system.
1106 * \param[in,out] l2 A second transition system.
1107 * \param[in] branching If true branching bisimulation is used, otherwise strong bisimulation is applied.
1108 * \param[in] preserve_divergences If true and branching is true, preserve tau loops on states.
1109 * \param[in] generate_counter_examples Whether to generate a counter example
1110 * \param[in] counter_example_file The file to store the counter example in
1111 * \param[in] structured_output
1112 * \retval True iff the initial states of the current transition system and l2 are (divergence preserving) (branching) bisimilar */
1113template<class LTS_TYPE>
1114bool destructive_bisimulation_compare(LTS_TYPE& l1,
1115 LTS_TYPE& l2,
1116 bool branching = false,
1117 bool preserve_divergences = false,
1118 bool generate_counter_examples = false,
1119 const std::string& counter_example_file = "",
1120 bool structured_output = false);
1121
1122/** \brief Checks whether the two initial states of two lts's are strong or branching bisimilar.
1123 * \details The current transitions system and the lts l2 are first duplicated and subsequently
1124 * reduced modulo bisimulation. If memory space is a concern, one could consider to
1125 * use destructive_bisimulation_compare. This routine uses the Groote-Vaandrager
1126 * branching bisimulation routine. It runs in O(mn) and uses O(n) memory where n is the
1127 * number of states and m is the number of transitions.
1128 * \param[in,out] l1 A first transition system.
1129 * \param[in,out] l2 A second transistion system.
1130 * \param[in] branching If true branching bisimulation is used, otherwise strong bisimulation is applied.
1131 * \param[in] preserve_divergences If true and branching is true, preserve tau loops on states.
1132 * \param[in] generate_counter_examples Whether to generate a counter example
1133 * \param[in] counter_example_file The file to store the counter example in
1134 * \param[in] structured_output
1135 * \retval True iff the initial states of the current transition system and l2 are (divergence preserving) (branching) bisimilar */
1136template<class LTS_TYPE>
1137bool bisimulation_compare(const LTS_TYPE& l1,
1138 const LTS_TYPE& l2,
1139 bool branching = false,
1140 bool preserve_divergences = false,
1141 bool generate_counter_examples = false,
1142 const std::string& counter_example_file = "",
1143 bool structured_output = false);
1144
1145template < class LTS_TYPE>
1146mcrl2::state_formulas::state_formula bisim_partitioner<LTS_TYPE>::counter_formula(
1147 const std::size_t s, const std::size_t t)
1148{
1149 if (get_eq_class(s)==get_eq_class(t))
1150 {
1151 throw mcrl2::runtime_error("Requesting a counter state formula for two bisimilar states. Such a state formula is not useful.");
1152 }
1153
1154 return counter_formula_aux(block_index_of_a_state[s], block_index_of_a_state[t]);
1155}
1156
1157
1158template < class LTS_TYPE>
1159void bisimulation_reduce(LTS_TYPE& l,
1160 const bool branching /*=false */,
1161 const bool preserve_divergences /*=false */)
1162{
1163 // First, remove tau loops in case of branching bisimulation.
1164 if (branching)
1165 {
1166 scc_reduce(l,preserve_divergences);
1167 }
1168
1169 // Secondly, apply the branching bisimulation reduction algorithm. If there are no tau's,
1170 // this will automatically yield strong bisimulation.
1171 detail::bisim_partitioner<LTS_TYPE> bisim_part(l, branching, preserve_divergences);
1172
1173 // Assign the reduced LTS
1174 bisim_part.replace_transition_system(branching,preserve_divergences);
1175}
1176
1177template < class LTS_TYPE>
1178bool bisimulation_compare(
1179 const LTS_TYPE& l1,
1180 const LTS_TYPE& l2,
1181 const bool branching /* =false*/,
1182 const bool preserve_divergences /*=false*/,
1183 const bool generate_counter_examples /*= false*/,
1184 const std::string& /* counter_example_file = ""*/,
1185 const bool structured_output /* = false */)
1186{
1187 LTS_TYPE l1_copy(l1);
1188 LTS_TYPE l2_copy(l2);
1189 return destructive_bisimulation_compare(l1_copy,l2_copy,branching,preserve_divergences,
1190 generate_counter_examples, structured_output);
1191}
1192
1193template < class LTS_TYPE>
1194bool destructive_bisimulation_compare(
1195 LTS_TYPE& l1,
1196 LTS_TYPE& l2,
1197 const bool branching /* =false*/,
1198 const bool preserve_divergences /*=false*/,
1199 const bool generate_counter_examples /* = false */,
1200 const std::string& counter_example_file /*= ""*/,
1201 const bool /*structured_output = false */)
1202{
1203 std::size_t init_l2 = l2.initial_state() + l1.num_states();
1204 mcrl2::lts::detail::merge(l1,l2);
1205 l2.clear(); // No use for l2 anymore.
1206
1207 // First remove tau loops in case of branching bisimulation.
1208 if (branching)
1209 {
1210 detail::scc_partitioner<LTS_TYPE> scc_part(l1);
1211 scc_part.replace_transition_system(preserve_divergences);
1212 init_l2 = scc_part.get_eq_class(init_l2);
1213 }
1214
1215 detail::bisim_partitioner<LTS_TYPE> bisim_part(l1, branching, preserve_divergences, generate_counter_examples);
1216 if (generate_counter_examples && !bisim_part.in_same_class(l1.initial_state(), init_l2))
1217 {
1218 mcrl2::state_formulas::state_formula counter_example_formula = bisim_part.counter_formula(l1.initial_state(), init_l2);
1219
1220 std::string filename = "Counterexample.mcf";
1221 if (!counter_example_file.empty())
1222 {
1223 filename = counter_example_file;
1224 }
1225 std::ofstream counter_file(filename);
1226 counter_file << mcrl2::state_formulas::pp(counter_example_formula);
1227 counter_file.close();
1228 mCRL2log(mcrl2::log::log_level_t::info) << "Saved counterexample to: \"" << filename << "\"" << std::endl;
1229
1230 }
1231 return bisim_part.in_same_class(l1.initial_state(),init_l2);
1232}
1233
1234
1235}
1236
1237#endif // MCRL2_LTS_DETAIL_LIBLTS_BISIM_H
\brief The multi action for action formulas
multi_action(const process::action_list &actions)
\brief Constructor Z14.
\brief A timed multi-action
const process::action_list & actions() const
mcrl2::state_formulas::state_formula conjunction(std::set< mcrl2::state_formulas::state_formula > terms) const
conjunction Creates a conjunction of state formulas
regular_formulas::regular_formula create_regular_formula(const mcrl2::lts::action_label_string &a) const
create_regular_formula Creates a regular formula that represents action a
regular_formulas::regular_formula create_regular_formula(const mcrl2::lps::multi_action &a) const
create_regular_formula Creates a regular formula that represents action a
std::vector< bool > block_is_in_to_be_processed
std::map< block_index_type, block_index_type > right_child
std::vector< block_index_type > BL
bool in_same_class(const std::size_t s, const std::size_t t) const
Returns whether two states are in the same bisimulation equivalence class.
mcrl2::state_formulas::state_formula until_formula(const mcrl2::state_formulas::state_formula &phi1, const label_type &a, const mcrl2::state_formulas::state_formula &phi2)
until_formula Creates a state formula that corresponds to the until operator phi1phi2 from HMLU
std::size_t get_eq_class(const std::size_t s) const
Gives the bisimulation equivalence class number of a state.
bisim_partitioner(LTS_TYPE &l, const bool branching=false, const bool preserve_divergence=false, const bool generate_counter_examples=false)
Creates a bisimulation partitioner for an LTS.
~bisim_partitioner()=default
Destroys this partitioner.
std::map< block_index_type, label_type > split_by_action
std::size_t num_eq_classes() const
Gives the number of bisimulation equivalence classes of the LTS.
mcrl2::state_formulas::state_formula counter_formula(std::size_t s, std::size_t t)
Creates a state formula that distinguishes state s from state t.
void order_recursively_on_tau_reachability(const state_type s, std::map< state_type, std::vector< state_type > > &inert_transition_map, std::vector< non_bottom_state > &new_non_bottom_states, std::set< state_type > &visited)
std::vector< block_index_type > to_be_processed
std::map< block_index_type, block_index_type > split_by_block
void replace_transition_system(const bool branching, const bool preserve_divergences)
Replaces the transition relation of the current lts by the transitions of the bisimulation reduced tr...
void order_on_tau_reachability(std::vector< non_bottom_state > &non_bottom_states)
void split_the_blocks_in_BL(bool &partition_is_unstable, const label_type splitter_label, const block_index_type splitter_block)
void refine_partition_until_it_becomes_stable(const bool branching, const bool preserve_divergence)
void create_initial_partition(const bool branching, const bool preserve_divergences)
std::vector< state_type > block_index_of_a_state
mcrl2::state_formulas::state_formula counter_formula_aux(const block_index_type B1, const block_index_type B2)
void check_internal_consistency_of_the_partitioning_data_structure(const bool branching, const bool preserve_divergence) const
outgoing_transitions_per_state_action_t outgoing_transitions
function object to compare two constln_t pointers based on their contents
A class that can be used to store counterexample trees and.
A class that contains a labelled transition system.
Class for computing the signature for strong bisimulation.
Definition sigref.h:76
Class for computing the signature for branching bisimulation.
Definition sigref.h:106
Class for computing the signature for divergence preserving branching bisimulation.
Definition sigref.h:185
Signature based reductions for labelled transition systems.
Definition sigref.h:351
regular_formula(const action_formulas::action_formula &x)
\brief Constructor Z6.
\brief The not operator for state formulas
not_(const state_formula &operand)
\brief Constructor Z14.
\brief The or operator for state formulas
or_(const state_formula &left, const state_formula &right)
\brief Constructor Z14.
state_formula & operator=(state_formula &&) noexcept=default
#define mCRL2log(LEVEL)
mCRL2log(LEVEL) provides the stream used to log.
Definition logger.h:435
The main namespace for the LPS library.
Definition constelm.h:18
bool bisimulation_compare(const LTS_TYPE &l1, const LTS_TYPE &l2, bool branching=false, bool preserve_divergences=false, bool generate_counter_examples=false, const std::string &counter_example_file="", bool structured_output=false)
Checks whether the two initial states of two lts's are strong or branching bisimilar.
lts_type guess_format(std::string const &s, const bool be_verbose)
Determines the LTS format from a filename by its extension.
Definition liblts.cpp:26
static const std::array< std::string, 5 > extension_strings
Definition liblts.cpp:73
std::string supported_lts_formats_text(const std::set< lts_type > &supported)
Gives a textual list describing supported LTS formats.
Definition liblts.cpp:185
std::string string_for_type(const lts_type type)
Gives a string representation of an LTS format.
Definition liblts.cpp:112
std::string mime_type_for_type(const lts_type type)
Gives the MIME type associated with an LTS format.
Definition liblts.cpp:122
void get_trans(const outgoing_transitions_per_state_t &begin, tree_set_store &tss, std::ptrdiff_t d, std::vector< transition > &d_trans, LTS_TYPE &aut)
lts_type parse_format(std::string const &s)
Determines the LTS format from a format specification string.
Definition liblts.cpp:91
static const std::array< std::string, 5 > type_strings
Definition liblts.cpp:71
std::string extension_for_type(const lts_type type)
Gives the filename extension associated with an LTS format.
Definition liblts.cpp:117
const std::set< lts_type > & supported_lts_formats()
Gives the set of all supported LTS formats.
Definition liblts.cpp:139
std::string lts_extensions_as_string(const std::set< lts_type > &supported)
Gives a list of extensions for supported LTS formats.
Definition liblts.cpp:221
std::string lts_extensions_as_string(const std::string &sep, const std::set< lts_type > &supported)
Gives a list of extensions for supported LTS formats.
Definition liblts.cpp:190
bool destructive_bisimulation_compare(LTS_TYPE &l1, LTS_TYPE &l2, bool branching=false, bool preserve_divergences=false, bool generate_counter_examples=false, const std::string &counter_example_file="", bool structured_output=false)
Checks whether the two initial states of two lts's are strong or branching bisimilar.
void bisimulation_reduce(LTS_TYPE &l, bool branching=false, bool preserve_divergences=false)
Reduce transition system l with respect to strong or (divergence preserving) branching bisimulation.
bool lts_named_cmp(const std::array< std::string, Size > &N, T a, T b)
Definition liblts.cpp:147
std::string supported_lts_formats_text(lts_type default_format, const std::set< lts_type > &supported)
Gives a textual list describing supported LTS formats.
Definition liblts.cpp:152
static const std::array< std::string, 5 > type_desc_strings
Definition liblts.cpp:75
static const std::array< std::string, 5 > mime_type_strings
Definition liblts.cpp:84
static const std::set< lts_type > & initialise_supported_lts_formats()
Definition liblts.cpp:127
bool destructive_compare(LTS_TYPE &l1, LTS_TYPE &l2, lts_preorder pre, bool generate_counter_example, const std::string &counter_example_file="", bool structured_output=false, lps::exploration_strategy strategy=lps::exploration_strategy::es_breadth, bool preprocess=true)
Checks whether this LTS is smaller than another LTS according to a preorder.
bool is_deterministic(const LTS_TYPE &l)
Checks whether this LTS is deterministic.
void determinise(LTS_TYPE &l)
Determinises this LTS.
bool compare(const LTS_TYPE &l1, const LTS_TYPE &l2, lts_preorder pre, bool generate_counter_example, const std::string &counter_example_file="", bool structured_output=false, lps::exploration_strategy strategy=lps::exploration_strategy::es_breadth, bool preprocess=true)
Checks whether this LTS is smaller than another LTS according to a preorder.
void reduce(LTS_TYPE &l, lts_equivalence eq)
Applies a reduction algorithm to this LTS.
bool compare(const LTS_TYPE &l1, const LTS_TYPE &l2, lts_equivalence eq, bool generate_counter_examples=false, const std::string &counter_example_file="", bool structured_output=false)
Checks whether this LTS is equivalent to another LTS.
bool destructive_compare(LTS_TYPE &l1, LTS_TYPE &l2, const lts_equivalence eq, const bool generate_counter_examples=false, const std::string &counter_example_file=std::string(), const bool structured_output=false)
Checks whether this LTS is equivalent to another LTS.
void merge(LTS_TYPE &l1, const LTS_TYPE &l2)
Merge the second lts into the first lts.
bool reachability_check(lts< SL, AL, BASE > &l, bool remove_unreachable=false)
Checks whether all states in this LTS are reachable from the initial state and remove unreachable sta...
bool reachability_check(probabilistic_lts< SL, AL, PROBABILISTIC_STATE, BASE > &l, bool remove_unreachable=false)
Checks whether all states in a probabilistic LTS are reachable from the initial state and remove unre...
std::vector< transition > non_inert_transitions
std::vector< non_bottom_state > non_bottom_states
non_bottom_state(const state_type s, const std::vector< state_type > &it)