mCRL2
Loading...
Searching...
No Matches
liblts_bisim_dnj.h
Go to the documentation of this file.
1// Author(s): David N. Jansen, Institute of Software, Chinese Academy of
2// Sciences, Beijing, PR China
3//
4// Copyright: see the accompanying file COPYING or copy at
5// https://github.com/mCRL2org/mCRL2/blob/master/COPYING
6//
7// Distributed under the Boost Software License, Version 1.0.
8// (See accompanying file LICENSE_1_0.txt or copy at
9// http://www.boost.org/LICENSE_1_0.txt)
10
11/// \file lts/detail/liblts_bisim_dnj.h
12///
13/// \brief O(m log n)-time branching bisimulation algorithm
14///
15/// \details This file implements an efficient partition refinement algorithm
16/// for labelled transition systems inspired by Groote / Jansen / Keiren / Wijs
17/// (2017) and Valmari (2009) to calculate the branching bisimulation classes
18/// of a labelled transition system. Different from the 2017 article, it does
19/// not translate the LTS to a Kripke structure, but works on the LTS directly.
20/// In this way the memory use can be reduced. The algorithm is described in
21/// the conference publication
22///
23/// David N. Jansen, Jan Friso Groote, Jeroen J.A. Keiren, Anton Wijs:
24/// An O(m log n) algorithm for branching bisimilarity on labelled transition
25/// systems. In: Armin Biere, David Parker (Eds.): Tools and Algorithms for the
26/// Construction and Analysis of Systems: TACAS. (LNCS 12070). Springer: Cham,
27/// 2020. pp 3-20. https://doi.org/10.1007/978-3-030-45237-7_1
28///
29/// Partition refinement means that the algorithm maintains a partition of the
30/// state space of the LTS into ``blocks''. A block contains all states in one
31/// or several branching bisimulation equivalence classes. Blocks are being
32/// refined until every block contains exactly one branching bisimulation
33/// equivalence class.
34///
35/// The algorithm divides the non-inert transitions into *action_block-slices.*
36/// An action_block-slice contains all transitions with a given action label
37/// into one block. One or more action_block-slices are joined into a *bunch.*
38/// Bunches register which blocks have already been stabilised:
39///
40/// > Invariant: The blocks are stable under the bunches, i. e. if a block
41/// > has a transition in a bunch, then every bottom state in this block has
42/// > a transition in this bunch.
43///
44/// However, if a bunch is non-trivial (i. e., it contains more than one
45/// action_block-slice), its slices may split some blocks into finer parts:
46/// not all states may have a transition in the same action_block-slice. So, a
47/// refinement step consists of moving one small action_block-slice from a
48/// non-trivial bunch to a new bunch and then splitting blocks to restore the
49/// Invariant. Splitting is always done by finding a small subblock and moving
50/// the states in that subblock to a new block. Note that we always handle a
51/// small part of some larger unit; that ensures that each state and transition
52/// is only touched O(log n) times.
53///
54/// After splitting blocks, some inert transitions may have become non-inert.
55/// These transitions mostly need to be inserted into their own bunch. Also,
56/// some states may have lost all their inert transitions and become new bottom
57/// states. These states need to be handled as well to re-establish the
58/// Invariant.
59///
60/// Overall, we spend time as follows:
61/// - Every transition is moved to a new bunch at most
62/// log<SUB>2</SUB>(2 * n<SUP>2</SUP>) = 2*log<SUB>2</SUB>(n) + 1 times.
63/// Every move leads to O(1) work.
64/// - Every state is moved to a new block at most log<SUB>2</SUB>(n) times.
65/// Every move leads to work proportional to the number of its incoming and
66/// outgoing transitions.
67/// - Every state becomes a new bottom state at most once. When this happens,
68/// this leads to work proportional to the number of its outgoing
69/// transitions.
70/// When summing this up over all states and transitions, we get that the
71/// algorithm spends time in O(m log n), where m is the number of transitions
72/// and n ist the number of states.
73///
74/// \author David N. Jansen, Institute of Software, Chinese Academy of
75/// Sciences, Beijing, PR China
76
77// The file is best viewed on a screen or in a window that is 160 characters
78// wide. The left 80 columns contain the main text of the program. The right
79// 80 columns contain assertions and other code used for debugging.
80
81#ifndef LIBLTS_BISIM_DNJ_H
82#define LIBLTS_BISIM_DNJ_H
83
84#include <iomanip> // for std::fixed, std::setprecision(), std::setw()
85#include <ctime> // for std::clock_t, std::clock()
86#include "mcrl2/lts/detail/liblts_scc.h"
87#include "mcrl2/lts/detail/liblts_merge.h"
88#include "mcrl2/lts/detail/coroutine.h"
89#include "mcrl2/lts/detail/check_complexity.h"
90#include "mcrl2/lts/detail/fixed_vector.h"
91#include "mcrl2/lts/detail/simple_list.h"
92
93#include <cstddef> // for std::size_t
94#include <utility>
95
96namespace mcrl2::lts::detail
97{
98// The bisimulation algorithm below is hand-tuned and deliberately uses C-style
99// arrays, goto-based coroutine control flow, and helper macros. In addition,
100// misc-static-assert misfires on the many runtime assert() statements that are
101// expanded through macros. These checks are therefore suppressed for the whole
102// file.
103// NOLINTBEGIN(cppcoreguidelines-macro-usage,misc-static-assert,cppcoreguidelines-avoid-goto,cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays)
104 #ifndef NDEBUG
105 /// \brief include something in Debug mode
106 /// \details In a few places, we have to include an additional parameter to
107 /// a function in Debug mode. While it is in principle possible to use
108 /// #ifndef NDEBUG ... #endif, that would lead to distributing the code
109 /// over many code lines. This macro expands to its arguments in Debug
110// state_type and trans_type are defined in check_complexity.h. /// mode and to nothing otherwise.
111 #define ONLY_IF_DEBUG(...) __VA_ARGS__
112 #else
113 #define ONLY_IF_DEBUG(...)
114 #endif
115/// \brief type used to store label numbers and counts
116/// \details It would be better to define it as LTS_TYPE::labels_size_type, but
117/// that would require most classes to become templates.
118using label_type = std::size_t;
119
120template <class LTS_TYPE> class bisim_partitioner_dnj;
121
122namespace bisim_dnj
123{
124
125/// \class iterator_or_counter
126/// \brief union: either iterator or counter (for initialisation)
127/// \details During initialisation, we need some counters to count the number
128/// of transitions for each state. To avoid allocating separate memory for
129/// these counters, we store their value in the same place where we later
130/// store an iterator.
131///
132/// We assume that each such variable starts out as a counter and at
133/// some point becomes an iterator. That point is marked by calling
134/// convert_to_iterator(). The structure may only be destroyed after that
135/// call, as the destructor assumes it's an iterator.
136template <class Iterator>
137union iterator_or_counter
138{
139 /// \brief counter (used during initialisation)
140 trans_type count;
141
142 /// \brief iterator (used during main part of the algorithm)
143 Iterator begin;
144
145 /// \brief Construct the object as a counter
146 iterator_or_counter() { count = 0; }
147
148
149 /// \brief Convert the object from counter to iterator
150 void convert_to_iterator(const Iterator other)
151 {
152 new (&begin) Iterator(other);
153 }
154
155
156 /// \brief Destruct the object as an iterator
157 ~iterator_or_counter() { begin.~Iterator(); }
158};
159
160
161class block_bunch_entry;
162class action_block_entry;
163
164
165
166
167
168/* ************************************************************************* */
169/* */
170/* R E F I N A B L E P A R T I T I O N */
171/* */
172/* ************************************************************************* */
173
174
175
176
177
178/// \defgroup part_state
179/// \brief data structures for states
180/// \details States are stored in a refinable partition data structure. The
181/// actual state information will not be moved around, but only entries in
182/// a separate permutation array. Entries is this array are grouped per
183/// block, so that the states in a block can be described as a slice in the
184/// permutation array.
185///@{
186class state_info_entry;
187class permutation_entry;
188
189/// \class permutation_t
190/// \brief stores a permutation of the states, ordered by block
191/// \details This is the central concept of the _refinable partition_: the
192/// permutation of the states, such that states belonging to the same block are
193/// adjacent.
194///
195/// Iterating over the states of a block will
196/// therefore be done using the permutation_t array.
197using permutation_t = fixed_vector<permutation_entry>;
198
199class block_t;
200class bunch_t;
201
202class pred_entry;
203class succ_entry;
204
205class block_bunch_slice_t;
206using block_bunch_slice_iter_t = simple_list<block_bunch_slice_t>::iterator;
207using block_bunch_slice_const_iter_t = simple_list<block_bunch_slice_t>::const_iterator;
208using block_bunch_slice_iter_or_null_t = iterator_or_null_t<block_bunch_slice_t>;
209
210enum new_block_mode_t { new_block_is_U, new_block_is_R };
211
212
213/// \class state_info_entry
214/// \brief stores information about a single state
215/// \details This class stores all other information about a state that the
216/// partition needs. In particular: the block where the state belongs and the
217/// position in the permutation array (i. e. the inverse of the permutation).
218class state_info_entry
219{
220 public:
221 /// \brief iterator to first inert incoming transition
222 /// \details Non-inert incoming transitions of the state are stored just
223 /// before the element where this iterator points to.
224 ///
225 /// During initialisation, this field also doubles up as a counter for
226 /// the number of incoming transitions, and as the pointer to the first
227 /// incoming inert transition that already has been initialised.
228 iterator_or_counter<pred_entry*> pred_inert;
229
230 /// \brief iterator to first inert outgoing transition
231 /// \details Non-inert outgoing transitions of the state are stored just
232 /// before the element where this iterator points to.
233 ///
234 /// During initialisation, this field also doubles up as a counter for the
235 /// number of *inert* outgoing transitions, and as the pointer to the first
236 /// outgoing inert transition that already has been initialised.
237 iterator_or_counter<succ_entry*> succ_inert;
238
239 /// \brief block where the state belongs
240 /// \details During initialisation, this field is used to point at the
241 /// first unused slot of the (non-inert) bledecessors, ahem, predecessors.
242 /// Sorry for the mock-chinese ``typo''. So we always assume that it
243 /// starts as a pred_entry*, at some moment is converted to a block_t*, and
244 /// then stays that way until it is destroyed.
245 union bl_t {
246 pred_entry* ed_noninert_end;
247 block_t* ock;
248 } bl{};
249
250 /// \brief position of the state in the permutation array
251 permutation_entry* pos = nullptr;
252
253 /// \brief number of inert transitions to non-U-states
254 /// \details Actually, as we cannot easily count how many inert outgoing
255 /// transitions this state has, we initialize this pointer to
256 /// succ_inert.begin. Every time we find an outgoing transition to a
257 /// U-state, we increase this iterator; as soon as it no longer points to
258 /// an outgoing transition of this state, we have found all inert outgoing
259 /// transitions. This requires that after the inert outgoing transitions
260 /// there is a transition that starts in another state, or there is a dummy
261 /// transition.
262 ///
263 /// During initialisation, this field also doubles up as a counter for the
264 /// number of *non-inert* outgoing transitions, and as the pointer to the
265 /// first outgoing transition that already has been initialised. Therefore
266 /// it cannot be a `const succ_entry*`.
267 iterator_or_counter<succ_entry*> untested_to_U_eqv;
268 #ifndef NDEBUG
269 /// \brief print a short state identification for debugging
270 template<class LTS_TYPE>
271 std::string debug_id_short(const bisim_partitioner_dnj<LTS_TYPE>&
272 partitioner) const
273 { assert(partitioner.part_st.state_info.data() <= this);
274 assert(this < partitioner.part_st.state_info.data_end());
275 return std::to_string(this - partitioner.part_st.state_info.data());
276 }
277
278 /// \brief print a state identification for debugging
279 template<class LTS_TYPE>
280 std::string debug_id(const bisim_partitioner_dnj<LTS_TYPE>&
281 partitioner) const
282 {
283 return "state " + debug_id_short(partitioner);
284 }
285 #endif
286 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
287 mutable check_complexity::state_dnj_counter_t work_counter;
288 #endif
289};
290
291
292/// \brief entry in the permutation array
293class permutation_entry {
294 public:
295 /// \brief pointer to the state information data structure
296 state_info_entry* st;
297
298
299 /// \brief default constructor (should not be deleted)
300 permutation_entry() = default;
301
302
303 /// \brief move constructor
304 /// \details The move constructor is called when a temporary object is
305 /// created; in that case, it is not necessary to set the pos pointer.
306 permutation_entry(const permutation_entry&& other) noexcept
307 {
308 st = other.st;
309 }
310
311
312 /// \brief move assignment operator
313 /// \details The move assignment operator is called when an object is moved
314 /// to its final place. Therefore, we have to adapt the pos pointer. Note
315 /// that std::swap also uses move assignment, so we automatically get the
316 /// correct behaviour there.
317 permutation_entry& operator=(const permutation_entry& other) noexcept
318 {
319 if (this != &other)
320 {
321 st = other.st;
322 st->pos = this;
323 }
324 return *this;
325 }
326};
327
328
329/// \class block_t
330/// \brief stores information about a block
331/// \details A block corresponds to a slice of the permutation array. As the
332/// number of blocks is initially unknown, we will allocate instances of this
333/// class dynamically.
334///
335/// The slice in the permutation array containing the states of the block is
336/// subdivided into the following subslices (in this order):
337/// 1. unmarked bottom states
338/// 2. marked bottom states (initially empty)
339/// 3. unmarked non-bottom states
340/// 4. marked non-bottom states (initially empty)
341///
342/// A state should be marked iff it is a predecessor of the current splitter
343/// (through a strong transition). The marking is later extended to the
344/// R-states; that are the states with a weak transition to the splitter.
345///
346/// (During the execution of some functions, blocks are subdivided further;
347/// however, as these subdivisions are local to a single function, they are not
348/// stored here.)
349///
350/// Note that block_t uses the default destructor; therefore, if simple_list is
351/// trivially destructible, so is block_t.
352class block_t
353{
354 public:
355 /// \brief iterator to the first state of the block
356 permutation_entry* begin;
357
358 /// \brief iterator to the first marked bottom state of the block
359 permutation_entry* marked_bottom_begin;
360
361 /// \brief iterator to the first non-bottom state of the block
362 permutation_entry* nonbottom_begin;
363
364 /// \brief iterator to the first marked non-bottom state of the block
365 permutation_entry* marked_nonbottom_begin;
366
367 /// \brief iterator past the last state of the block
368 permutation_entry* end;
369
370 /// \brief list of stable block_bunch-slices with transitions from this
371 /// block
372 simple_list<block_bunch_slice_t> stable_block_bunch;
373
374 /// \brief unique sequence number of this block
375 /// \details After the stuttering equivalence algorithm has terminated,
376 /// this number is used as a state number in the quotient LTS.
377 const state_type seqnr;
378
379
380 /// \brief constructor
381 /// \details The constructor initialises the block to: all states are
382 /// bottom states, no state is marked.
383 /// \param new_begin initial iterator to the first state of the block
384 /// \param new_end initial iterator past the last state of the block
385 /// \param new_seqnr is the sequence number of the new block
386 block_t(permutation_entry* const new_begin,
387 permutation_entry* const new_end, state_type const new_seqnr)
388 : begin(new_begin),
389 marked_bottom_begin(new_end),
390 nonbottom_begin(new_end),
391 marked_nonbottom_begin(new_end),
392 end(new_end),
393 stable_block_bunch(),
394 seqnr(new_seqnr)
395 { assert(new_begin < new_end);
396 }
397
398
399 /// \brief provides the number of states in the block
400 state_type size() const
401 { assert(begin <= marked_bottom_begin); assert(marked_nonbottom_begin <= end);
402 assert(marked_bottom_begin <= nonbottom_begin);
403 assert(nonbottom_begin <= marked_nonbottom_begin);
404 return end - begin;
405 }
406
407
408 /// \brief provides the number of bottom states in the block
409 state_type bottom_size() const
410 { assert(begin <= marked_bottom_begin); assert(marked_nonbottom_begin <= end);
411 assert(marked_bottom_begin <= nonbottom_begin);
412 assert(nonbottom_begin <= marked_nonbottom_begin);
413 return nonbottom_begin - begin;
414 }
415
416
417 /// \brief provides the number of marked bottom states in the block
418 state_type marked_bottom_size() const
419 { assert(begin <= marked_bottom_begin); assert(marked_nonbottom_begin <= end);
420 assert(marked_bottom_begin <= nonbottom_begin);
421 assert(nonbottom_begin <= marked_nonbottom_begin);
422 return nonbottom_begin - marked_bottom_begin;
423 }
424
425
426 /// \brief provides the number of marked states in the block
427 state_type marked_size() const
428 {
429 return end - marked_nonbottom_begin + marked_bottom_size();
430 }
431
432
433 /// \brief provides the number of unmarked bottom states in the block
434 state_type unmarked_bottom_size() const
435 { assert(begin <= marked_bottom_begin); assert(marked_nonbottom_begin <= end);
436 assert(marked_bottom_begin <= nonbottom_begin);
437 assert(nonbottom_begin <= marked_nonbottom_begin);
438 return marked_bottom_begin - begin;
439 }
440
441
442 /// \brief provides the number of unmarked nonbottom states in the block
443 state_type unmarked_nonbottom_size() const
444 { assert(begin <= marked_bottom_begin); assert(marked_nonbottom_begin <= end);
445 assert(marked_bottom_begin <= nonbottom_begin);
446 assert(nonbottom_begin <= marked_nonbottom_begin);
447 return marked_nonbottom_begin - nonbottom_begin;
448 }
449
450
451 /// \brief mark a non-bottom state
452 /// \details Marking is done by moving the state to the slice of the marked
453 /// non-bottom states of the block.
454 /// \param s the non-bottom state that has to be marked
455 /// \returns true iff the state was not marked before
456 bool mark_nonbottom(permutation_entry* const s)
457 { assert(nonbottom_begin <= s); assert(s < end);
458 // assert(this == s->st->bl.ock); -- does not hold during initialisation
459 assert(begin <= marked_bottom_begin);
460 assert(marked_bottom_begin <= nonbottom_begin);
461 assert(nonbottom_begin <= marked_nonbottom_begin);
462 if (marked_nonbottom_begin <= s) { return false; } assert(marked_nonbottom_begin <= end);
463 std::swap(*s, *--marked_nonbottom_begin); assert(nonbottom_begin <= marked_nonbottom_begin);
464 return true;
465 }
466
467
468 /// \brief mark a state
469 /// \details Marking is done by moving the state to the slice of the marked
470 /// bottom or non-bottom states of the block.
471 /// \param s the state that has to be marked
472 /// \returns true iff the state was not marked before
473 bool mark(permutation_entry* const s)
474 { assert(begin <= s);
475 if (s < nonbottom_begin) // assert(this == s->st->bl.ock); -- does not hold during initialisation
476 { assert(begin <= marked_bottom_begin); assert(marked_nonbottom_begin <= end);
477 assert(nonbottom_begin <= marked_nonbottom_begin);
478 if (marked_bottom_begin <= s) { return false; } assert(marked_bottom_begin <= nonbottom_begin);
479 std::swap(*s, *--marked_bottom_begin); assert(begin <= marked_bottom_begin);
480 return true;
481 }
482 return mark_nonbottom(s);
483 }
484
485
486 /// \brief refine a block
487 /// \details This function is called after a refinement function has found
488 /// where to split the block into unmarked (U) and marked (R) states.
489 /// It creates a new block for the smaller subblock.
490 /// \param new_block_mode indicates whether the U- or the R-block
491 /// should be the new one. (This parameter is necessary in case
492 /// the two halves have exactly the same size.)
493 /// \param new_seqnr is the sequence number of the new block
494 /// \param new_block (if the pool allocator is used) a pointer to an
495 /// uninitialized block, where the new block will be stored.
496 /// \returns pointer to the new block
497 ONLY_IF_DEBUG( template<class LTS_TYPE> )
498 block_t* split_off_block(enum new_block_mode_t new_block_mode, ONLY_IF_DEBUG( const bisim_partitioner_dnj<LTS_TYPE>& partitioner, )
499 state_type new_seqnr);
500 #ifndef NDEBUG
501 /// \brief print a block identification for debugging
502 template<class LTS_TYPE>
503 inline std::string debug_id(const bisim_partitioner_dnj<LTS_TYPE>&
504 partitioner) const
505 { assert(partitioner.part_st.permutation.data() <= begin);
506 assert(begin < end); assert(begin <= marked_bottom_begin);
507 assert(marked_bottom_begin <= nonbottom_begin);
508 assert(nonbottom_begin <= marked_nonbottom_begin);
509 assert(marked_nonbottom_begin <= end);
510 assert(end <= partitioner.part_st.permutation.data_end());
511 return "block [" +
512 std::to_string(begin - partitioner.part_st.permutation.data()) +
513 "," + std::to_string(end - partitioner.part_st.permutation.data()) +
514 ") (#" + std::to_string(seqnr) + ")";
515 }
516 #endif
517 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
518 mutable check_complexity::block_dnj_counter_t work_counter;
519 #endif
520};
521
522
523/// \class part_state_t
524/// \brief refinable partition data structure
525/// \details This class collects all information about a partition of the
526/// states.
527class part_state_t
528{
529 public:
530 /// \brief permutation array
531 /// \details This is the central element of the data structure: In this
532 /// array, states that belong to the same block are stored in adjacent
533 /// elements.
534 permutation_t permutation;
535
536 /// \brief array with all other information about states
537 fixed_vector<state_info_entry> state_info;
538
539 /// \brief total number of blocks with unique sequence number allocated
540 /// \details Upon starting the stuttering equivalence algorithm, the number
541 /// of blocks must be zero.
542 state_type nr_of_blocks = 0;
543
544 /// \brief constructor
545 /// \details The constructor allocates memory and makes the permutation and
546 /// state_info arrays consistent with each other, but does not actually
547 /// initialise the partition. Immediately afterwards, the initialisation
548 /// will be done in `bisim_partitioner_dnj::create_initial_partition()`.
549 /// \param num_states number of states in the LTS
550 part_state_t(state_type const num_states)
551 : permutation(num_states),
552 state_info(num_states)
553 { assert(0 < num_states);
554 permutation_entry* perm_iter(permutation.data());
555 #ifdef USE_POOL_ALLOCATOR
556 static_assert(std::is_trivially_destructible_v<block_t>);
557 #endif
558 state_info_entry* state_iter(state_info.data()); assert(perm_iter < permutation.data_end());
559 do
560 {
561 state_iter->pos = perm_iter;
562 perm_iter->st = state_iter;
563 }
564 while (++state_iter, ++perm_iter < permutation.data_end()); assert(state_iter == state_info.data_end());
565 }
566
567
568 #ifndef USE_POOL_ALLOCATOR
569 /// \brief destructor
570 /// \details The destructor also deallocates the blocks, as they are
571 /// not directly referenced from anywhere. This is only necessary if
572 /// we do not use the pool allocator, as the latter will destroy the
573 /// blocks wholesale.
574 ~part_state_t()
575 { ONLY_IF_DEBUG( state_type deleted_blocks(0); )
576 permutation_entry* perm_iter(permutation.data_end()); assert(permutation.data() < perm_iter);
577 do
578 {
579 block_t* const B(perm_iter[-1].st->bl.ock); assert(B->end == perm_iter);
580 perm_iter = B->begin; ONLY_IF_DEBUG( ++deleted_blocks; )
581 delete B;
582 }
583 while (permutation.data() < perm_iter); assert(deleted_blocks == nr_of_blocks);
584 }
585 #endif
586
587
588 /// \brief calculate the size of the state space
589 /// \returns the number of states in the LTS
590 state_type state_size() const { return permutation.size(); }
591
592
593 /// \brief find the block of a state
594 /// \param s number of the state
595 /// \returns a pointer to the block where state s resides in
596 const block_t* block(state_type const s) const
597 {
598 return state_info[s].bl.ock;
599 }
600 #ifndef NDEBUG
601 private:
602 /// \brief print a slice of the partition (typically a block)
603 /// \details If the slice indicated by the parameters is not empty, the
604 /// message and the states in this slice will be printed.
605 /// \param message text printed as a title if the slice is not empty
606 /// \param begin_print iterator to the beginning of the slice
607 /// \param end_print iterator past the end of the slice
608 /// \param partitioner LTS partitioner (used to print more details)
609 template<class LTS_TYPE>
610 void print_block(const block_t* const B,
611 const char* const message,
612 const permutation_entry* begin_print,
613 const permutation_entry* const end_print,
614 const bisim_partitioner_dnj<LTS_TYPE>& partitioner) const
615 { assert(B->begin <= begin_print); assert(end_print <= B->end);
616 if (end_print == begin_print)
617 {
618 return;
619 }
620
621 mCRL2log(log::debug) << '\t' << message
622 << (1 < end_print - begin_print ? "s:\n" : ":\n");
623 assert(begin_print < end_print);
624 do
625 {
626 mCRL2log(log::debug) << "\t\t"
627 << begin_print->st->debug_id(partitioner);
628 if (B != begin_print->st->bl.ock)
629 {
630 mCRL2log(log::debug) << ", inconsistent: points "
631 "to " << begin_print->st->bl.ock->debug_id(partitioner);
632 }
633 if (begin_print != begin_print->st->pos)
634 {
635 mCRL2log(log::debug)
636 << ", inconsistent pointer to state_info_entry";
637 }
638 mCRL2log(log::debug) << '\n';
639 }
640 while (++begin_print < end_print);
641 }
642 public:
643 /// \brief print the partition per block
644 /// \details The function prints all blocks in order. For each block, it
645 /// lists its states, separated into nonbottom and bottom states.
646 /// \param partitioner LTS partitioner (used to print more details)
647 template<class LTS_TYPE>
648 void print_part(const bisim_partitioner_dnj<LTS_TYPE>& partitioner) const
649 {
650 if (!mCRL2logEnabled(log::debug))
651 {
652 return;
653 }
654 const block_t* B(permutation.front().st->bl.ock);
655 do
656 {
657 mCRL2log(log::debug)<<B->debug_id(partitioner)<<":\n";
658 print_block(B, "Bottom state",
659 B->begin, B->marked_bottom_begin, partitioner);
660 print_block(B, "Marked bottom state",
661 B->marked_bottom_begin, B->nonbottom_begin, partitioner);
662 print_block(B, "Non-bottom state",
663 B->nonbottom_begin, B->marked_nonbottom_begin, partitioner);
664 print_block(B, "Marked non-bottom state",
665 B->marked_nonbottom_begin, B->end, partitioner);
666 // go to next block
667 }
668 while(B->end<permutation.data_end() && (B = B->end->st->bl.ock, true));
669 }
670
671 /// \brief asserts that the partition of states is consistent
672 /// \details It also requires that no states are marked.
673 /// \param partitioner LTS partitioner (used to print more details)
674 template<class LTS_TYPE>
675 void assert_consistency(
676 const bisim_partitioner_dnj<LTS_TYPE>& partitioner) const
677 {
678 const permutation_entry* perm_iter(permutation.data());
679 state_type true_nr_of_blocks(0);
680 assert(perm_iter < permutation.data_end());
681 do
682 {
683 const block_t* const block(perm_iter->st->bl.ock);
684 // block is consistent:
685 assert(block->begin == perm_iter);
686 assert(block->begin < block->marked_bottom_begin);
687 assert(block->marked_bottom_begin == block->nonbottom_begin);
688 assert(block->nonbottom_begin <= block->marked_nonbottom_begin);
689 assert(block->marked_nonbottom_begin == block->end);
690 assert(partitioner.branching||block->nonbottom_begin==block->end);
691 assert(0 <= block->seqnr);
692 assert(block->seqnr < nr_of_blocks);
693 unsigned const max_block(check_complexity::log_n -
694 check_complexity::ilog2(block->size()));
695 mCRL2complexity(block, no_temporary_work(max_block), partitioner);
696
697 // states in the block are consistent:
698 do
699 {
700 const state_info_entry* const state(perm_iter->st);
701 // assert(part_tr.pred.data() < state->pred_inert.begin);
702 assert(&state_info.back() == state ||
703 state->pred_inert.begin <= state[1].pred_inert.begin);
704 // assert(state->pred_inert.begin < part_tr.pred.data_end());
705 // assert(state->succ_inert.begin < part_tr.succ.data_end());
706 if (perm_iter < block->nonbottom_begin)
707 {
708 assert(&state_info.back() == state || state->
709 succ_inert.begin <= state[1].succ_inert.begin);
710 // assert(state->succ_inert.begin==&part_tr.succ.back() ||
711 // state <
712 // state->succ_inert.begin->block_bunch->pred->source);
713 mCRL2complexity(state, no_temporary_work(max_block, true),
714 partitioner);
715 }
716 else
717 {
718 // assert(state->succ_inert.begin < &part_tr.succ.back());
719 assert(&state_info.back() == state || state->
720 succ_inert.begin < state[1].succ_inert.begin);
721 //assert(state ==
722 // state->succ_inert.begin->block_bunch->pred->source);
723 mCRL2complexity(state, no_temporary_work(max_block, false),
724 partitioner);
725 }
726 assert(block == state->bl.ock);
727 assert(perm_iter == state->pos);
728 }
729 while (++perm_iter < block->end);
730 assert(perm_iter == block->end);
731 ++true_nr_of_blocks;
732 }
733 while (perm_iter < permutation.data_end());
734 assert(nr_of_blocks == true_nr_of_blocks);
735 }
736 #endif
737};
738
739///@} (end of group part_state)
740
741
742
743
744 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
745/* ************************************************************************* */ static struct {
746/* */ bool operator()(const iterator_or_counter<action_block_entry*> p1,
747/* T R A N S I T I O N S */ const action_block_entry* const action_block) const
748/* */ {
749/* ************************************************************************* */ return p1.begin > action_block;
750 }
751 } const action_label_greater;
752 #endif
753
754
755/// \defgroup part_trans
756/// \brief data structures for transitions used during partition refinement
757/// \details These definitions provide a partition for transition data
758/// structure that can be used for the partition refinement algorithm.
759///
760/// Basically, transitions are stored in four arrays:
761/// - `pred`: transitions grouped by goal state, to allow finding all
762/// predecessors of a goal state.
763/// (At the beginning and the end of the pred array, there are dummy
764/// entries.)
765/// - `succ`: transitions grouped by source state and bunch, to allow finding
766/// all successors of a source state. Given a transition in this array, it
767/// is easy to find all transitions from the same source state in the same
768/// bunch.
769/// (At the beginning and the end of the succ array, there are dummy
770/// entries.)
771/// - `action_block`: a permutation of the transitions such that transitions
772/// in the same bunch are adjacent, and within each bunch transitions with
773/// the same action label and target block.
774/// (Between two action_block-slices with different actions, there is a dummy
775/// entry.)
776/// - `block_bunch`: a permutation of the transitions such that transitions
777/// from the same block in the same bunch are adjacent.
778/// (At the beginning of the block_bunch array, there is a dummy entry.)
779/// Entries in these four arrays are linked with each other with circular
780/// iterators, so that one can find the corresponding entry in another array.
781///
782/// Within this sort order, inert transitions are always placed after non-inert
783/// transitions.
784///
785/// state_info_entry and block_t (defined above) contain pointers to the slices
786/// of these arrays. For bunches and block_bunch-slices, we additionally
787/// create _descriptors_ that hold some information about the slice.
788
789///@{
790
791/// \brief information about a transition sorted per source state
792class succ_entry
793{
794 public:
795 /// \brief circular iterator to link the four transition arrays
796 block_bunch_entry* block_bunch;
797
798 /// \brief pointer to delimit the slice of transitions in the same bunch
799 /// \details For most transitions, this pointer points to the first
800 /// transition that starts in the same state and belongs to the same
801 /// bunch. But if this transition is the first such transition, the
802 /// pointer points to the last such transition (not one past the last, like
803 /// otherwise in C and C++).
804 ///
805 /// For inert transitions, the value is nullptr.
806 succ_entry* begin_or_before_end;
807
808
809 /// \brief find the beginning of the out-slice
810 succ_entry* out_slice_begin( ONLY_IF_DEBUG( const fixed_vector<succ_entry>& succ )
811 );
812
813
814 /// \brief find the bunch of the transition
815 bunch_t* bunch() const;
816 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
817 /// \brief assign work to the transitions in an out-slice (i.e. the
818 /// transitions from one state in a specific bunch)
819 /// \details This debugging function is called to account for work that
820 /// could be assigned to any transition in the out-slice. Just to make
821 /// sure, we therefore set the corresponding counter `ctr` for every
822 /// transition in the out-slice to `max_value`.
823 /// \param partitioner LTS partitioner
824 /// \param out_slice_begin pointer to the first transition in the
825 /// out-slice
826 /// \param ctr type of the counter that work is assigned to
827 /// \param max_value new value that the counter should get
828 template <class LTS_TYPE>
829 static inline void add_work_to_out_slice(
830 const bisim_partitioner_dnj<LTS_TYPE>& partitioner,
831 const succ_entry* out_slice_begin,
832 enum check_complexity::counter_type ctr, unsigned max_value);
833 #endif
834};
835
836
837/// \brief information about a transition grouped per (source block, bunch)
838/// pair
839class block_bunch_entry
840{
841 public:
842 /// \brief circular iterator to link the four transition arrays
843 pred_entry* pred = nullptr;
844
845 /// \brief block_bunch-slice of which this transition is part
846 /// \details The slice is null iff the transition is inert.
847 block_bunch_slice_iter_or_null_t slice;
848};
849
850
851/// \brief information about a transition sorted per target state
852/// \details As I expect the transitions in this array to be moved least often,
853/// I store the information on source and target state in this entry. (It
854/// could be stored in any of the four arrays describing the transition;
855/// through the circular iterators, the information would be available anyway.)
856class pred_entry
857{
858 public:
859 /// \brief circular iterator to link the four transition arrays
860 action_block_entry* action_block = nullptr;
861
862 /// \brief source state of the transition
863 state_info_entry* source = nullptr;
864
865 /// \brief target state of the transition
866 state_info_entry* target = nullptr;
867#ifndef NDEBUG
868 /// \brief print a short transition identification for debugging
869 template <class LTS_TYPE>
870 std::string debug_id_short(const bisim_partitioner_dnj<LTS_TYPE>&
871 partitioner) const
872 {
873 return "from " + source->debug_id_short(partitioner) +
874 " to " + target->debug_id_short(partitioner);
875 }
876
877 /// \brief print a transition identification for debugging
878 template <class LTS_TYPE>
879 std::string debug_id(const bisim_partitioner_dnj<LTS_TYPE>& partitioner)
880 const
881 {
882 // Search for the action label in partitioner.action_label
883 label_type const label(std::lower_bound(
884 partitioner.action_label.cbegin(), partitioner.action_label.cend(),
885 action_block, action_label_greater) -
886 partitioner.action_label.cbegin());
887 assert(label < partitioner.action_label.size());
888 assert(partitioner.action_label[label].begin <= action_block);
889 assert(0==label||action_block<partitioner.action_label[label-1].begin);
890 // class lts_lts_t uses a function pp() to transform the action label
891 // to a string.
892 return pp(partitioner.aut.action_label(label)) + "-transition " +
893 debug_id_short(partitioner);
894 }
895 #endif
896 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
897 mutable check_complexity::trans_dnj_counter_t work_counter;
898 #endif
899};
900
901
902/// \brief information about a transition sorted per (action, target block)
903/// pair
904class action_block_entry
905{
906 public:
907 /// \brief circular iterator to link the four transition arrays
908 /// \details This iterator can be nullptr because we need to insert dummy
909 /// elements between two action_block-slices during initialisation, to make
910 /// it easier for first_move_transition_to_new_action_block() to detect
911 /// whether two action_block-slices belong to the same action or not.
912 succ_entry* succ;
913
914 /// \brief pointer to delimit the slice of transitions in the same (action,
915 /// block) pair
916 /// \details For most transitions, this pointer points to the first
917 /// transition that has the same action and goes to the same block. But if
918 /// this transition is the first such transition, the pointer points to the
919 /// last such transition (not one past the last, like otherwise in C and
920 /// C++).
921 ///
922 /// For inert transitions and dummy entries, the value is nullptr.
923 action_block_entry* begin_or_before_end;
924
925
926 /// \brief find the beginning of the action_block-slice
927 action_block_entry* action_block_slice_begin( ONLY_IF_DEBUG( const action_block_entry* const action_block_begin,
928 const action_block_entry* const action_block_orig_inert_begin )
929 )
930 {
931 action_block_entry* result(begin_or_before_end); assert(nullptr != result);
932 if (this < result)
933 { assert(this == result->begin_or_before_end);
934 result = this; // The following assertion does not always hold: the function is called
935 } // immediately after a block is refined, so it may be the case that the
936 // transitions are still to be moved to different slices.
937 // assert(succ->block_bunch->pred->target->bl.ock ==
938 // result->succ->block_bunch->pred->target->bl.ock);
939 assert(nullptr != succ); assert(nullptr != result->succ);
940 assert(succ->bunch() == result->succ->bunch());
941 assert(result == action_block_begin || nullptr == result[-1].succ ||
942 action_block_orig_inert_begin <= result ||
943 result[-1].succ->block_bunch->pred->target->bl.ock !=
944 result->succ->block_bunch->pred->target->bl.ock);
945 // assert(this has the same action as result);
946 return result;
947 }
948};
949
950
951class part_trans_t;
952
953/// \brief bunch of transitions
954/// \details Like a slice, at the end of the algorithm there will be a bunch
955/// for every transition in the bisimulation quotient. Therefore, we should
956/// try to minimize the size of a bunch as much as possible.
957class bunch_t
958{
959 public:
960 /// \brief first transition in the bunch
961 action_block_entry* begin;
962
963 /// \brief pointer past the last transition in the bunch
964 action_block_entry* end;
965
966 /// \brief pointer to next non-trivial bunch (in the single-linked list) or
967 /// label
968 /// \details During refinement, this field stores a pointer to the next
969 /// nontrivial bunch. After refinement, it is set to the label.
970 union next_nontrivial_and_label_t
971 {
972 /// \brief pointer to the next non-trivial bunch in the single-linked
973 /// list
974 /// \details This pointer is == nullptr if the bunch is trivial. The
975 /// last entry in the list points to itself so its pointer is still
976 /// != nullptr.
977 bunch_t* next_nontrivial;
978
979 /// \brief action label of the transition
980 label_type label;
981
982
983 /// \brief constructor
984 next_nontrivial_and_label_t()
985 {
986 next_nontrivial = nullptr;
987 }
988 } next_nontrivial_and_label;
989
990
991 /// \brief constructor
992 bunch_t(action_block_entry* const new_begin,
993 action_block_entry* const new_end)
994 : begin(new_begin),
995 end(new_end),
996 next_nontrivial_and_label()
997 { }
998
999
1000 /// \brief returns true iff the bunch is trivial
1001 /// \details If this bunch is the last in the list of non-trivial bunches,
1002 /// the convention is that the next pointer points to this bunch itself (to
1003 /// distinguish it from nullptr).
1004 bool is_trivial() const
1005 {
1006 return nullptr == next_nontrivial_and_label.next_nontrivial;
1007 }
1008
1009
1010 /// \brief split off a single action_block-slice from the bunch
1011 /// \details The function splits the current bunch after its first
1012 /// action_block-slice or before its last action_block-slice, whichever
1013 /// is smaller. It creates a new bunch for the split-off slice and
1014 /// returns a pointer to the new bunch. The caller has to adapt the
1015 /// block_bunch-slices.
1016 bunch_t* split_off_small_action_block_slice(part_trans_t& part_tr);
1017 #ifndef NDEBUG
1018 /// \brief print a short bunch identification for debugging
1019 template <class LTS_TYPE>
1020 std::string debug_id_short(const bisim_partitioner_dnj<LTS_TYPE>&
1021 partitioner) const
1022 {
1023 assert(partitioner.part_tr.action_block.data() <= begin);
1024 assert(end <= partitioner.part_tr.action_block_inert_begin);
1025 return "bunch [" + std::to_string(begin -
1026 partitioner.part_tr.action_block.data()) + "," +
1027 std::to_string(end - partitioner.part_tr.action_block.data()) + ")";
1028 }
1029
1030 /// \brief print a long bunch identification for debugging
1031 template <class LTS_TYPE>
1032 std::string debug_id(const bisim_partitioner_dnj<LTS_TYPE>& partitioner)
1033 const
1034 { assert(nullptr != end[-1].succ);
1035 const action_block_entry* iter(begin); assert(iter < end);
1036 assert(nullptr != iter->succ);
1037 assert(iter == iter->succ->block_bunch->pred->action_block);
1038 std::string result(debug_id_short(partitioner));
1039 result += " containing transition";
1040 result += iter < end - 1 ? "s " : " ";
1041 result += iter->succ->block_bunch->pred->debug_id_short(partitioner);
1042 ++iter;
1043 if (end <= iter)
1044 {
1045 return result;
1046 }
1047 while (nullptr == iter->succ)
1048 {
1049 ++iter;
1050 }
1051 assert(iter < end);
1052 assert(iter == iter->succ->block_bunch->pred->action_block);
1053 result += ", ";
1054 result += iter->succ->block_bunch->pred->debug_id_short(partitioner);
1055 if (iter < end - 3)
1056 {
1057 result += ", ...";
1058 iter = end - 3;
1059 }
1060 while (++iter < end)
1061 {
1062 if (nullptr != iter->succ)
1063 { assert(iter == iter->succ->block_bunch->pred->action_block);
1064 result += ", ";
1065 result += iter->succ->block_bunch->pred->debug_id_short(
1066 partitioner);
1067 }
1068 }
1069 return result;
1070 }
1071 #endif
1072 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
1073 /// \brief calculates the maximal allowed value for work counters
1074 /// associated with this bunch
1075 /// \details Work counters may only be nonzero if this bunch is a
1076 /// single-action bunch, i. e. all its transitions have the same action
1077 /// label. Also, only then the size can be calculated as end - begin.
1078 template <class LTS_TYPE>
1079 int max_work_counter(const bisim_partitioner_dnj<LTS_TYPE>& partitioner)
1080 const
1081 {
1082 // verify that the bunch only has a single action label.
1083 // Search for the action label in partitioner.action_label
1084 label_type const label(std::lower_bound(
1085 partitioner.action_label.cbegin(), partitioner.action_label.cend(),
1086 begin, action_label_greater) -
1087 partitioner.action_label.cbegin());
1088 assert(label < partitioner.action_label.size());
1089 assert(partitioner.action_label[label].begin <= begin);
1090 assert(0 == label || begin < partitioner.action_label[label-1].begin);
1091 if (0 == label || end < partitioner.action_label[label - 1].begin)
1092 {
1093 assert(check_complexity::ilog2(end - begin) <=
1094 check_complexity::log_n);
1095 return check_complexity::log_n-check_complexity::ilog2(end-begin);
1096 }
1097 return 0;
1098 }
1099
1100 mutable check_complexity::bunch_dnj_counter_t work_counter;
1101 #endif
1102};
1103
1104
1105/// \brief Information about a set of transitions with the same source block,
1106/// in the same bunch
1107/// \details A block_bunch-slices contains the transitions in a single bunch
1108/// that start in the same block. In the end, we want each block to be stable
1109/// under its block_bunch-slices, i.e. every bottom state has a transition in
1110/// every block_bunch-slice of the block. Then, there will be one slice for
1111/// each transition in the minimised LTS, so we should try to minimize this
1112/// data structure as much as possible.
1113///
1114/// Also note that these slices are part of a doubly-linked list. We cannot
1115/// change this to a singly-linked list because we occasionally delete an
1116/// element from this list. This would be possible with a single-linked list
1117/// if we could infer the order of the list somehow, e.g. from the transitions
1118/// in a bottom state -- however, this does not work when a block loses all its
1119/// bottom states, i.e. when a block splits into a small U and large R but R
1120/// does not contain any bottom states.
1121class block_bunch_slice_t
1122{
1123 public:
1124 /// \brief pointer past the end of the transitions in the block_bunch array
1125 /// \details We do not need a begin pointer because we can always walk
1126 /// through the transitions in the slice from end to beginning.
1127 block_bunch_entry* end;
1128
1129 /// bunch to which this slice belongs
1130 bunch_t* bunch;
1131
1132 /// \brief pointer to the first marked transition in the block_bunch array
1133 /// \details If this pointer is nullptr, then the block_bunch_slice is
1134 /// stable.
1135 block_bunch_entry* marked_begin = nullptr;
1136
1137 /// \brief returns true iff the block_bunch-slice is registered as stable
1138 bool is_stable() const { return nullptr == marked_begin; }
1139
1140
1141 /// \brief register that the block_bunch-slice is stable
1142 void make_stable()
1143 { assert(!is_stable()); assert(!empty());
1144 marked_begin = nullptr;
1145 }
1146
1147
1148 /// \brief register that the block_bunch-slice is not stable
1149 void make_unstable()
1150 { assert(is_stable());
1151 marked_begin = end; assert(!is_stable());
1152 }
1153
1154
1155 /// \brief constructor
1156 block_bunch_slice_t(block_bunch_entry* const new_end,
1157 bunch_t* const new_bunch, bool const new_is_stable)
1158 : end(new_end),
1159 bunch(new_bunch)
1160 {
1161 if (!new_is_stable)
1162 {
1163 make_unstable();
1164 }
1165 }
1166
1167
1168 /// \brief returns true iff the block_bunch-slice is empty
1169 /// \details A block_bunch-slice should only become empty if it is
1170 /// unstable.
1171 bool empty() const
1172 { // assert(std::less(part_tr.block_bunch.data(), end));
1173 // assert(!std::less(part_tr.block_bunch_inert_begin, end));
1174 // assert(part_tr.block_bunch.front().slice != this);
1175 return end[-1].slice != this;
1176 }
1177
1178
1179 /// compute the source block of the transitions in this slice
1180 block_t* source_block() const
1181 { assert(!empty());
1182 return end[-1].pred->source->bl.ock;
1183 }
1184 #ifndef NDEBUG
1185 /// \brief print a block_bunch-slice identification for debugging
1186 template <class LTS_TYPE>
1187 std::string debug_id(const bisim_partitioner_dnj<LTS_TYPE>& partitioner)
1188 const
1189 {
1190 static struct {
1191 bool operator()(const block_bunch_entry& p1,
1192 const block_bunch_slice_t* const p2) const
1193 {
1194 return p1.slice != p2;
1195 }
1196 } const block_bunch_not_equal;
1197
1198 assert(partitioner.part_tr.block_bunch.data() < end);
1199 assert(end <= partitioner.part_tr.block_bunch_inert_begin);
1200 std::string const index_string(std::to_string(end -
1201 &partitioner.part_tr.block_bunch.cbegin()[1]));
1202 if (empty())
1203 { //assert(!is_stable());
1204 return "empty block_bunch_slice [" + index_string + "," +
1205 index_string + ")";
1206 }
1207 const block_bunch_entry* begin(
1208 &partitioner.part_tr.block_bunch.cbegin()[1]);
1209 if (trans_type bunch_size(
1210 bunch->end
1211 - bunch->begin);
1212 std::cmp_greater(
1213 (end - begin),
1214 bunch_size))
1215 {
1216 begin = end - bunch_size;
1217 }
1218 begin = std::lower_bound(begin, const_cast<const block_bunch_entry*>
1219 (is_stable() || marked_begin==end ? end-1 : marked_begin),
1220 this, block_bunch_not_equal);
1221 assert(begin->slice == this);
1222 assert(begin[-1].slice != this);
1223 return (is_stable() ? "stable block_bunch-slice ["
1224 : "unstable block_bunch_slice [") +
1225 std::to_string(begin-&partitioner.part_tr.block_bunch.cbegin()[1]) +
1226 "," + index_string + ") containing transitions from " +
1227 source_block()->debug_id(partitioner) +
1228 " in " + bunch->debug_id_short(partitioner);
1229 }
1230 #endif
1231 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
1232 /// \brief add work to transitions starting in bottom states
1233 /// \details Sometimes an action is done whose time could be accounted for
1234 /// by any transition starting in a bottom state of the block.
1235 /// \param ctr counter type to which work is assigned
1236 /// \param max_value new value of the counter
1237 /// \param partitioner LTS partitioner (to print error messages if
1238 /// necessary)
1239 template <class LTS_TYPE>
1240 bool add_work_to_bottom_transns(enum check_complexity::
1241 counter_type const ctr, unsigned const max_value,
1242 const bisim_partitioner_dnj<LTS_TYPE>& partitioner) const
1243 { assert(!empty());
1244 assert(1U == max_value);
1245 const block_t* const block(source_block());
1246 bool result(false);
1247 const block_bunch_entry* block_bunch(end);
1248 assert(partitioner.part_tr.block_bunch.front().slice != this);
1249 assert(block_bunch[-1].slice == this);
1250 do
1251 {
1252 --block_bunch;
1253 const state_info_entry* const source(block_bunch->pred->source);
1254 assert(source->bl.ock == block);
1255 if (source->pos < block->nonbottom_begin /*&&
1256 // the transition starts in a (new) bottom state
1257 block_bunch->pred->work_counter.counters[ctr -
1258 check_complexity::TRANS_dnj_MIN] != max_value*/)
1259 {
1260 mCRL2complexity(block_bunch->pred, add_work(ctr, max_value),
1261 partitioner);
1262 (void) partitioner; // avoid unused variable warning
1263 result = true;
1264 }
1265 }
1266 while (block_bunch[-1].slice == this);
1267 return result;
1268 }
1269
1270 mutable check_complexity::block_bunch_dnj_counter_t work_counter;
1271 #endif
1272};
1273
1274
1275/// \brief find the beginning of the out-slice
1276inline succ_entry* succ_entry::out_slice_begin( ONLY_IF_DEBUG( const fixed_vector<succ_entry>& succ )
1277 )
1278{ assert(nullptr != begin_or_before_end);
1279 succ_entry* result(begin_or_before_end); assert(result->block_bunch->pred->action_block->succ == result);
1280 if (this < result)
1281 { assert(nullptr != result->begin_or_before_end);
1282 assert(this == result->begin_or_before_end);
1283 result = this; assert(result->block_bunch->pred->action_block->succ == result);
1284 } assert(block_bunch->pred->source == result->block_bunch->pred->source);
1285 // assert(this <= result); //< holds always, based on the if() above
1286 assert(nullptr != result->begin_or_before_end);
1287 assert(this <= result->begin_or_before_end);
1288 assert(block_bunch->slice == result->block_bunch->slice);
1289 assert(&succ.cbegin()[1] == result ||
1290 result[-1].block_bunch->pred->source < block_bunch->pred->source ||
1291 result[-1].bunch() != block_bunch->slice->bunch);
1292 return result;
1293}
1294
1295
1296/// \brief find the bunch of a transition
1297inline bunch_t* succ_entry::bunch() const
1298{
1299 return block_bunch->slice->bunch;
1300}
1301 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
1302 /// \brief register that work has been done for the out-slice containing
1303 /// `out_slice_begin`
1304 /// \details This function should be used if work
1305 /// \param partitioner the partitioner data structure, used to write
1306 /// diagnostic messages
1307 /// \param out_slice_begin the first transition in the out-slice
1308 /// \param ctr counter type to which work has to be assigned
1309 /// \param max_value new value of the counter
1310 template <class LTS_TYPE>
1311 /* static */ inline void succ_entry::add_work_to_out_slice(
1312 const bisim_partitioner_dnj<LTS_TYPE>& partitioner,
1313 const succ_entry* out_slice_begin,
1314 enum check_complexity::counter_type const ctr,unsigned const max_value)
1315 {
1316 const succ_entry* const out_slice_before_end(
1317 out_slice_begin->begin_or_before_end);
1318 assert(nullptr != out_slice_before_end);
1319 assert(out_slice_begin <= out_slice_before_end);
1320 mCRL2complexity(out_slice_begin->block_bunch->pred,
1321 add_work(ctr, max_value), partitioner);
1322 #ifndef NDEBUG
1323 while (++out_slice_begin <= out_slice_before_end)
1324 {
1325 // treat temporary counters specially
1326 mCRL2complexity(out_slice_begin->block_bunch->pred,
1327 add_work_notemporary(ctr, max_value), partitioner);
1328 }
1329 #else
1330 (void) partitioner; (void) out_slice_before_end;
1331 // avoid unused variable warning
1332 #endif
1333 }
1334 #endif
1335class part_trans_t
1336{
1337 public:
1338 /// \brief array containing all successor entries
1339 /// \details The first and last entry are dummy entries, pointing to a
1340 /// transition from nullptr to nullptr, to make it easier to check whether
1341 /// there is another transition from the current state.
1342 fixed_vector<succ_entry> succ;
1343
1344 /// \brief array containing all block_bunch entries
1345 /// \details The first entry is a dummy entry, pointing to a transition not
1346 /// contained in any slice, to make it easier to check whether there is
1347 /// another transition in the current block_bunch.
1348 fixed_vector<block_bunch_entry> block_bunch;
1349
1350 /// \brief array containing all predecessor entries
1351 /// \details The first and last entry are dummy entries, pointing to a
1352 /// transition to nullptr, to make it easier to check whether there is
1353 /// another transition to the current state.
1354 fixed_vector<pred_entry> pred;
1355
1356 /// \brief array containing all action_block entries
1357 /// \details During initialisation, the transitions are sorted according to
1358 /// their label. Between transitions with different labels there is a dummy
1359 /// entry, to make it easier to check whether there is another transition
1360 /// in the current action_block slice.
1361 ///
1362 /// The array may be empty if there are no transitions.
1363 fixed_vector<action_block_entry> action_block;
1364
1365 /// \brief pointer to the first inert transition in block_bunch
1366 block_bunch_entry* block_bunch_inert_begin;
1367
1368 /// \brief pointer to the first inert transition in action_block
1369 action_block_entry* action_block_inert_begin;
1370 #ifndef NDEBUG
1371 /// \brief pointer to the first inert transition in the initial partition
1372 const action_block_entry* action_block_orig_inert_begin = nullptr;
1373#endif
1374 /// \brief list of unstable block_bunch-slices
1375 simple_list<block_bunch_slice_t> splitter_list;
1376 private:
1377 /// \brief pointer to first non-trivial bunch
1378 bunch_t* first_nontrivial = nullptr;
1379
1380 public:
1381 #ifdef USE_POOL_ALLOCATOR
1382 static_assert(std::is_trivially_destructible_v<bunch_t>);
1383#endif
1384
1385 /// \brief number of new bottom states found until now.
1386 state_type nr_of_new_bottom_states = 0;
1387
1388 /// \brief counters to measure progress
1389 trans_type nr_of_bunches = 0;
1390 trans_type nr_of_nontrivial_bunches = 0;
1391 trans_type nr_of_action_block_slices = 0;
1392 trans_type nr_of_block_bunch_slices = 0;
1393
1394 /// \brief constructor
1395 /// \details The constructor sets up the dummy transitions at the beginning
1396 /// and end of the succ, block_bunch and pred arrays. (Dummy transitions
1397 /// in action_block depend on the number of transitions per action label,
1398 /// so they cannot be set up without knowing details about how many
1399 /// transitions have which label.)
1400 /// \param num_transitions number of transitions of the LTS
1401 /// \param num_actions number of action labels of the LTS
1402 part_trans_t(trans_type num_transitions,
1403 trans_type num_actions)
1404 : succ(num_transitions + 2),
1405 block_bunch(num_transitions + 1),
1406 pred(num_transitions + 2),
1407 action_block(num_transitions + num_actions < 1
1408 ? 0 : num_transitions + num_actions - 1),
1409 block_bunch_inert_begin(block_bunch.data_end()),
1410 action_block_inert_begin(action_block.data_end()),
1411 splitter_list()
1412 {
1413 succ.front().block_bunch = block_bunch.data();
1414 succ.back() .block_bunch = block_bunch.data();
1415 block_bunch.front().pred = pred.data();
1416 block_bunch.front().slice = nullptr;
1417 pred.front().source = nullptr;
1418 pred.front().target = nullptr;
1419 pred.back() .source = nullptr;
1420 pred.back() .target = nullptr;
1421 }
1422
1423
1424 /// \brief destructor
1425 ~part_trans_t()
1426 {
1427 #ifndef USE_POOL_ALLOCATOR
1428 // The destructor also deallocates the bunches, as they are not
1429 // directly referenced from anywhere. This is only necessary if we
1430 // do not use the pool allocator, as the latter will destroy the
1431 // bunches wholesale.
1432 action_block_entry* action_block_iter(action_block.data());
1433 for (;;)
1434 {
1435 do
1436 {
1437 if (action_block_inert_begin <= action_block_iter)
1438 { assert(0 == nr_of_bunches);
1439 return;
1440 }
1441 }
1442 while (nullptr == action_block_iter->succ && ( assert(nullptr == action_block_iter->begin_or_before_end),
1443 ++action_block_iter, true)); assert(nullptr != action_block_iter->begin_or_before_end);
1444 bunch_t* const bunch(action_block_iter->succ->bunch()); assert(bunch->begin == action_block_iter);
1445 action_block_iter = bunch->end;
1446 delete bunch; ONLY_IF_DEBUG( --nr_of_bunches; )
1447 }
1448 /* unreachable */ assert(0);
1449 #endif
1450 }
1451
1452
1453 /// \brief provide some bunch from the list of non-trivial bunches
1454 /// \returns pointer to a bunch that is in the list of non-trivial bunches
1455 bunch_t* get_some_nontrivial()
1456 {
1457 return first_nontrivial;
1458 }
1459
1460
1461 /// \brief insert a bunch into the list of nontrivial bunches
1462 /// \param bunch the bunch that has become non-trivial
1463 void make_nontrivial(bunch_t* const bunch)
1464 { assert(1 < bunch->end - bunch->begin); assert(bunch->is_trivial());
1465 // The following assertions do not necessarily hold during initialisation:
1466 //assert(bunch->begin <= bunch->begin->begin_or_before_end);
1467 bunch->next_nontrivial_and_label.next_nontrivial =
1468 nullptr == first_nontrivial ? bunch : first_nontrivial; //assert(nullptr != bunch->begin->begin_or_before_end);
1469 //assert(nullptr != bunch->end[-1].begin_or_before_end);
1470 //assert(bunch->begin->begin_or_before_end <
1471 // bunch->end[-1].begin_or_before_end);
1472 //assert(nullptr != end[-1].begin_or_before_end);
1473 first_nontrivial = bunch; assert(nr_of_nontrivial_bunches < nr_of_bunches);
1474 ++nr_of_nontrivial_bunches; //assert(end[-1].begin_or_before_end <= end);
1475 }
1476
1477
1478 /// \brief remove a bunch from the list of nontrivial bunches
1479 /// \param bunch the bunch that has become trivial
1480 void make_trivial(bunch_t* const bunch)
1481 { assert(!bunch->is_trivial()); assert(first_nontrivial == bunch);
1482 first_nontrivial =
1483 bunch == bunch->next_nontrivial_and_label.next_nontrivial
1484 ? nullptr : bunch->next_nontrivial_and_label.next_nontrivial; assert(bunch->end - 1 == bunch->begin->begin_or_before_end);
1485 bunch->next_nontrivial_and_label.next_nontrivial = nullptr; assert(0 < nr_of_nontrivial_bunches);
1486 --nr_of_nontrivial_bunches; assert(bunch->begin == bunch->end[-1].begin_or_before_end);
1487 }
1488
1489
1490 /// \brief transition is moved to a new bunch
1491 /// \details This (and the next function) have to be called after a
1492 /// transition has changed its bunch. The member function will adapt the
1493 /// transition data structure. It assumes that the transition is
1494 /// non-inert.
1495 ///
1496 /// The work has to be done in two steps: We call the first step
1497 /// first_move_transition_to_new_bunch() for each transition in the new
1498 /// bunch, and then call second_move_transition_to_new_bunch() again for
1499 /// all these transitions. The reason is that some data structures need to
1500 /// be finalised in the second phase.
1501 ///
1502 /// The first phase moves all transitions to their correct position in
1503 /// the out-slices and block_bunch-slices, but it doesn't yet create
1504 /// a fully correct new out-slice and block_bunch-slice. It adapts
1505 /// current_out_slice of all states with a transition in the new bunch.
1506 /// \param action_block_iter_iter transition that has to be changed
1507 /// \param bunch_T_a_Bprime the new bunch in which the transition
1508 /// lies
1509 /// \param first_transition_of_state true iff this is the first transition
1510 /// of the state, so a new out-slice has
1511 /// to be allocated.
1512 void first_move_transition_to_new_bunch(
1513 action_block_entry* const action_block_iter,
1514 bunch_t* const bunch_T_a_Bprime,
1515 bool const first_transition_of_state)
1516 {
1517
1518 /* - - - - - - - - adapt part_tr.succ - - - - - - - - */
1519
1520 succ_entry* const old_succ_pos(action_block_iter->succ); assert(nullptr != old_succ_pos);
1521 assert(old_succ_pos->block_bunch->pred->action_block == action_block_iter);
1522 succ_entry* const out_slice_begin(old_succ_pos->out_slice_begin( ONLY_IF_DEBUG( succ )
1523 )); assert(out_slice_begin->block_bunch->pred->action_block->succ ==
1524 out_slice_begin);
1525 succ_entry* const new_succ_pos(out_slice_begin->begin_or_before_end); assert(nullptr != new_succ_pos);
1526 assert(out_slice_begin == new_succ_pos->begin_or_before_end);
1527 assert(new_succ_pos<old_succ_pos->block_bunch->pred->source->succ_inert.begin);
1528 /* move the transition to the end of its out-slice */ assert(new_succ_pos->block_bunch->pred->action_block->succ == new_succ_pos);
1529 if (old_succ_pos < new_succ_pos)
1530 {
1531 std::swap(old_succ_pos->block_bunch, new_succ_pos->block_bunch);
1532 old_succ_pos->block_bunch->pred->action_block->succ = old_succ_pos; assert(action_block_iter == new_succ_pos->block_bunch->pred->action_block);
1533 action_block_iter->succ = new_succ_pos;
1534 }
1535 else
1536 {
1537 assert(old_succ_pos == new_succ_pos);
1538 }
1539
1540 // adapt the old out-slice immediately
1541 // If the old out-slice becomes empty, then out_slice_begin ==
1542 // new_succ_pos, so the two following assignments will assign the
1543 // same variable. The second assignment is the relevant one.
1544 out_slice_begin->begin_or_before_end = new_succ_pos - 1;
1545
1546 // adapt the new out-slice, as far as is possible now:
1547 // make the begin_or_before_end pointers of the first and last
1548 // transition in the slice correct immediately. The other
1549 // begin_or_before_end pointers need to be corrected after all
1550 // transitions in the new bunch have been positioned correctly.
1551 if (first_transition_of_state)
1552 {
1553 new_succ_pos->begin_or_before_end = new_succ_pos;
1554 }
1555 else
1556 {
1557 succ_entry* const out_slice_before_end(
1558 new_succ_pos[1].begin_or_before_end); assert(nullptr != out_slice_before_end);
1559 assert(new_succ_pos < out_slice_before_end);
1560 assert(out_slice_before_end->block_bunch->pred->action_block->succ ==
1561 out_slice_before_end);
1562 assert(new_succ_pos + 1 == out_slice_before_end->begin_or_before_end);
1563 out_slice_before_end->begin_or_before_end = new_succ_pos; assert(out_slice_before_end <
1564 new_succ_pos->block_bunch->pred->source->succ_inert.begin);
1565 new_succ_pos->begin_or_before_end = out_slice_before_end; assert(bunch_T_a_Bprime == out_slice_before_end->bunch());
1566 }
1567
1568 /* - - - - - - - adapt part_tr.block_bunch - - - - - - - */ assert(new_succ_pos == action_block_iter->succ);
1569
1570 block_bunch_entry* const old_block_bunch_pos(
1571 new_succ_pos->block_bunch); assert(old_block_bunch_pos->pred->action_block == action_block_iter);
1572 block_t*const source_block = old_block_bunch_pos->pred->source->bl.ock; assert(!old_block_bunch_pos->slice.is_null());
1573 block_bunch_slice_iter_t const old_block_bunch_slice(
1574 old_block_bunch_pos->slice);
1575 block_bunch_entry* const new_block_bunch_pos(
1576 old_block_bunch_slice->end - 1); assert(nullptr != new_block_bunch_pos->pred->action_block->succ);
1577 assert(new_block_bunch_pos->pred->action_block->succ->block_bunch ==
1578 new_block_bunch_pos);
1579 // create or adapt the new block_bunch-slice
1580 block_bunch_slice_iter_t new_block_bunch_slice;
1581 if (new_block_bunch_pos + 1 >= block_bunch_inert_begin ||
1582 (new_block_bunch_slice = (block_bunch_slice_iter_t)
1583 new_block_bunch_pos[1].slice, assert(!new_block_bunch_pos[1].slice.is_null()),
1584 bunch_T_a_Bprime != new_block_bunch_slice->bunch ||
1585 source_block != new_block_bunch_slice->source_block()))
1586 { assert(first_transition_of_state);
1587 // This is the first transition in the block_bunch-slice.
1588 // The old block_bunch-slice becomes unstable, and the new
1589 // block_bunch-slice is created unstable.
1590
1591 // Note that the new block_bunch-slice should precede the old one.
1592
1593 #ifdef USE_SIMPLE_LIST
1594 new_block_bunch_slice = splitter_list.emplace_back(
1595 new_block_bunch_pos + 1, bunch_T_a_Bprime, false);
1596 #else
1597 splitter_list.emplace_back(new_block_bunch_pos + 1,
1598 bunch_T_a_Bprime, false);
1599 new_block_bunch_slice = std::prev(splitter_list.end());
1600 #endif
1601 ++nr_of_block_bunch_slices;
1602 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
1603 new_block_bunch_slice->work_counter = old_block_bunch_slice->work_counter;
1604 #endif
1605 splitter_list.splice(splitter_list.end(),
1606 source_block->stable_block_bunch, old_block_bunch_slice);
1607 old_block_bunch_slice->make_unstable();
1608 } assert(!new_block_bunch_slice->is_stable());
1609
1610 // move the transition to the end of its block_bunch-slice
1611 if (old_block_bunch_pos < new_block_bunch_pos)
1612 {
1613 std::swap(old_block_bunch_pos->pred, new_block_bunch_pos->pred); assert(nullptr != old_block_bunch_pos->pred->action_block->succ);
1614 old_block_bunch_pos->pred->action_block->succ->block_bunch =
1615 old_block_bunch_pos; assert(new_succ_pos == new_block_bunch_pos->pred->action_block->succ);
1616 new_succ_pos->block_bunch = new_block_bunch_pos;
1617 }
1618 else
1619 {
1620 assert(new_block_bunch_pos == old_block_bunch_pos);
1621 }
1622 assert(new_block_bunch_pos->slice == old_block_bunch_slice);
1623 new_block_bunch_pos->slice = new_block_bunch_slice;
1624
1625 /* adapt the old block_bunch-slice */ assert(new_block_bunch_pos + 1 == old_block_bunch_slice->marked_begin);
1626 old_block_bunch_slice->end = new_block_bunch_pos;
1627 old_block_bunch_slice->marked_begin = new_block_bunch_pos; assert(nullptr != new_block_bunch_pos);
1628 if (old_block_bunch_slice->empty())
1629 { assert(!old_block_bunch_slice->is_stable());
1630 splitter_list.erase(old_block_bunch_slice); assert(!new_block_bunch_slice->is_stable());
1631 --nr_of_block_bunch_slices;
1632
1633 // Because now every bottom state has a transition in the new
1634 // bunch, and no state has a transition in the old bunch, there
1635 // is no need to refine this block. So we make this
1636 // block_bunch-slice stable again.
1637 source_block->stable_block_bunch.splice(
1638 source_block->stable_block_bunch.end(),
1639 splitter_list, new_block_bunch_slice);
1640 new_block_bunch_slice->make_stable();
1641
1642 // unmark the states
1643 // (This transition must be the last transition from
1644 // source_block in the new bunch, so unmarking the states now
1645 // will not be undone by later markings of other states.)
1646 source_block->marked_nonbottom_begin = source_block->end; assert(source_block->marked_bottom_begin == source_block->begin);
1647 source_block->marked_bottom_begin = source_block->nonbottom_begin;
1648 }
1649 }
1650
1651
1652 /// \brief transition is moved to a new bunch, phase 2
1653 /// \details This (and the previous function) have to be called after a
1654 /// transition has changed its bunch. The member function will adapt the
1655 /// transition data structure. It assumes that the transition is
1656 /// non-inert.
1657 ///
1658 /// The work has to be done in two steps: We call the first step
1659 /// first_move_transition_to_new_bunch() for each transition in the new
1660 /// bunch, and then call second_move_transition_to_new_bunch() again for
1661 /// all these transitions. The reason is that some data structures need to
1662 /// be finalised/ in the second phase.
1663 ///
1664 /// The second phase finalizes the new out-slices and block_bunch-slices
1665 /// that were left half-finished by the first phase. It assumes that all
1666 /// block_bunch-slices are registered as stable.
1667 /// \param action_block_iter_iter transition that has to be changed
1668 /// \param large_splitter_bunch the large splitter_bunch that has
1669 /// been split; the transition has moved
1670 /// from `large_splitter_bunch` to a new,
1671 /// small bunch.
1672 ONLY_IF_DEBUG( template <class LTS_TYPE> )
1673 void second_move_transition_to_new_bunch(
1674 action_block_entry* const action_block_iter, ONLY_IF_DEBUG( const bisim_partitioner_dnj<LTS_TYPE>& partitioner,
1675 bunch_t* const bunch_T_a_Bprime, )
1676 bunch_t* const large_splitter_bunch)
1677 { assert(nullptr != bunch_T_a_Bprime);
1678
1679 /* - - - - - - - - adapt part_tr.succ - - - - - - - - */
1680
1681 // We already moved the transition in part_tr.succ to the correct
1682 // place in first_move_transition_to_new_bunch(); now we have to
1683 // set begin_or_before_end.
1684 succ_entry* const new_succ_pos(action_block_iter->succ); assert(nullptr != new_succ_pos);
1685 assert(new_succ_pos->block_bunch->pred->action_block == action_block_iter);
1686 state_info_entry* const source(
1687 new_succ_pos->block_bunch->pred->source); assert(source->pos->st == source);
1688 assert(new_succ_pos < source->succ_inert.begin);
1689 assert(source == partitioner.part_st.state_info.data() ||
1690 source[-1].succ_inert.begin <= new_succ_pos);
1691 assert(nullptr != new_succ_pos->begin_or_before_end);
1692 succ_entry* const new_begin_or_before_end(
1693 new_succ_pos->begin_or_before_end->begin_or_before_end); assert(nullptr != new_begin_or_before_end);
1694 assert(new_begin_or_before_end->block_bunch->pred->action_block->succ ==
1695 new_begin_or_before_end);
1696 if (new_begin_or_before_end < new_succ_pos)
1697 { assert(source == partitioner.part_st.state_info.data() ||
1698 source[-1].succ_inert.begin <= new_begin_or_before_end);
1699 new_succ_pos->begin_or_before_end = new_begin_or_before_end;
1700 }
1701 else
1702 { assert(new_begin_or_before_end == new_succ_pos);
1703 // This is the first or the last transition in the out-slice.
1704 const succ_entry* const new_before_end(
1705 new_begin_or_before_end->begin_or_before_end); assert(nullptr != new_before_end);
1706 if (new_begin_or_before_end <= new_before_end)
1707 { assert(&partitioner.part_tr.succ.cbegin()[1] == new_begin_or_before_end ||
1708 /* This is the first transition in the new out-slice. */ new_begin_or_before_end[-1].block_bunch->pred->source < source ||
1709 /* If there is still a transition in the old out-slice, */ new_begin_or_before_end[-1].bunch() != bunch_T_a_Bprime);
1710 /* we prepay for it. */ assert(new_before_end + 1 == source->succ_inert.begin ||
1711 bunch_T_a_Bprime != new_before_end[1].bunch());
1712 if (source == new_succ_pos[-1].block_bunch->pred->source &&
1713 new_succ_pos[-1].bunch() == large_splitter_bunch)
1714 {
1715 // Mark one transition in the large slice
1716 block_bunch_entry* const old_block_bunch_pos(
1717 new_succ_pos[-1].block_bunch); assert(!old_block_bunch_pos->slice.is_null());
1718 assert(old_block_bunch_pos->pred->action_block->succ == new_succ_pos - 1);
1719 block_bunch_slice_iter_t const large_splitter_slice(
1720 old_block_bunch_pos->slice);
1721 if (!large_splitter_slice->is_stable())
1722 {
1723 block_bunch_entry* const new_block_bunch_pos(
1724 large_splitter_slice->marked_begin - 1); assert(nullptr != new_block_bunch_pos->pred->action_block->succ);
1725 assert(new_block_bunch_pos->pred->action_block->succ->block_bunch ==
1726 new_block_bunch_pos);
1727 if (old_block_bunch_pos < new_block_bunch_pos)
1728 {
1729 std::swap(old_block_bunch_pos->pred,
1730 new_block_bunch_pos->pred); // assert(nullptr != old_block_bunch_pos->pred->action_block->succ);
1731 old_block_bunch_pos->pred->action_block->
1732 succ->block_bunch = old_block_bunch_pos; assert(new_block_bunch_pos->pred->action_block->succ == new_succ_pos - 1);
1733 new_succ_pos[-1].block_bunch = new_block_bunch_pos;
1734 }
1735 large_splitter_slice->marked_begin=new_block_bunch_pos; assert(nullptr != new_block_bunch_pos);
1736 }
1737 else
1738 {
1739 assert(1 >= source->bl.ock->size());
1740 }
1741 }
1742 }
1743 else
1744 {
1745 assert(source == partitioner.part_st.state_info.data() || source[-1].succ_inert.begin <= new_before_end);
1746 }
1747 }
1748 #ifndef NDEBUG
1749 /* - - - - - - - adapt part_tr.block_bunch - - - - - - - */ const block_bunch_entry* new_block_bunch_pos(new_succ_pos->block_bunch);
1750 assert(new_block_bunch_pos->pred->action_block->succ == new_succ_pos);
1751 assert(!new_block_bunch_pos->slice.is_null());
1752 /* Nothing needs to be done. */ block_bunch_slice_const_iter_t const new_block_bunch_slice(
1753 new_block_bunch_pos->slice);
1754 assert(new_block_bunch_pos < new_block_bunch_slice->end);
1755 assert(bunch_T_a_Bprime == new_block_bunch_slice->bunch);
1756 if (new_block_bunch_pos + 1
1757 < new_block_bunch_slice->end)
1758 {
1759 return;
1760 }
1761
1762 // This transition is the last in the block_bunch-slice. If there
1763 // were some task that would need to be done exactly once per
1764 // block_bunch-slice, this would be the moment.
1765 do {
1766 assert(
1767 source->bl.ock
1768 == new_block_bunch_pos->pred
1769 ->source->bl.ock);
1770 }
1771 while (
1772 (--new_block_bunch_pos)->slice
1773 == new_block_bunch_slice);
1774 assert(new_block_bunch_pos <= partitioner.part_tr.block_bunch.data() ||
1775 source->bl.ock != new_block_bunch_pos->pred->source->bl.ock ||
1776 bunch_T_a_Bprime != new_block_bunch_pos->slice->bunch);
1777 #endif
1778 }
1779
1780
1781 private:
1782 /// \brief Adapt the non-inert transitions in an out-slice to a new block
1783 /// \details After a block has been split, the outgoing transitions of the
1784 /// new block need to move to the respective block_bunch-slice of the new
1785 /// block.
1786 ///
1787 /// This function handles all transitions in the out-slice just before
1788 /// `out_slice_end`, as they all belong to the same block_bunch-slice and
1789 /// can be moved together. However, transitions in `splitter_T` are
1790 /// excepted: all transitions in `splitter_T` from all states are
1791 /// transitions of the R-subblock, so if the latter is the new block, then
1792 /// `splitter_T` can be moved as a whole instead of per-state. In this
1793 /// case, the caller should move `splitter_T` to the list of stable
1794 /// block_bunch-slices of the R-subblock.
1795 ///
1796 /// The function returns the beginning of this out-slice (which can become
1797 /// the next out_slice_end). It is meant to be called from the last
1798 /// out-slice back to the first because it inserts stable
1799 /// block_bunch-slices at the beginning of the list for the new block, so
1800 /// it would normally become ordered according to the bunch.
1801 /// \param out_slice_end The transition just after the out-slice that is
1802 /// adapted
1803 /// \param old_block The block in which the source state of the
1804 /// out-slice was before it was split (only needed if
1805 /// we do not use simple lists)
1806 /// \param splitter_T The splitter that made this block split
1807 /// \returns the beginning of this out-slice (which can become the next
1808 /// out_slice_end)
1809 ONLY_IF_DEBUG( template <class LTS_TYPE> )
1810 succ_entry* move_out_slice_to_new_block(
1811 succ_entry* out_slice_end, ONLY_IF_DEBUG( const bisim_partitioner_dnj<LTS_TYPE>& partitioner, )
1812 block_t* const old_block,
1813 block_bunch_slice_const_iter_t const splitter_T)
1814 { assert(&succ.cbegin()[1] < out_slice_end);
1815 succ_entry* const out_slice_begin(
1816 out_slice_end[-1].begin_or_before_end); assert(nullptr != out_slice_begin);
1817 assert(out_slice_begin < out_slice_end);
1818 assert(out_slice_begin->block_bunch->pred->action_block->succ ==
1819 out_slice_begin);
1820 block_bunch_entry* old_block_bunch_pos(out_slice_end[-1].block_bunch); assert(nullptr != old_block_bunch_pos->pred->action_block->succ);
1821 assert(!old_block_bunch_pos->slice.is_null());
1822 block_bunch_slice_iter_t const old_block_bunch_slice(
1823 old_block_bunch_pos->slice); assert(old_block_bunch_pos->pred->action_block->succ->block_bunch ==
1824 old_block_bunch_pos);
1825 if (&*splitter_T == &*old_block_bunch_slice)
1826 {
1827 return out_slice_begin;
1828 }
1829
1830 block_bunch_entry* old_block_bunch_slice_end(
1831 old_block_bunch_slice->end);
1832 state_info_entry* const source(old_block_bunch_pos->pred->source); assert(out_slice_end <= source->succ_inert.begin);
1833 assert(partitioner.part_st.state_info.data() == source ||
1834 source[-1].succ_inert.begin < out_slice_end);
1835 block_t* const new_block(source->bl.ock); assert(source == out_slice_begin->block_bunch->pred->source);
1836 block_bunch_slice_iter_t new_block_bunch_slice; assert(source->pos->st == source);
1837 if (old_block_bunch_slice_end >= block_bunch_inert_begin ||
1838 new_block != old_block_bunch_slice_end->pred->source->bl.ock ||
1839 (new_block_bunch_slice = (block_bunch_slice_iter_t)
1840 old_block_bunch_slice_end->slice, assert(!old_block_bunch_slice_end->slice.is_null()),
1841 old_block_bunch_slice->bunch != new_block_bunch_slice->bunch))
1842 {
1843 // the new block_bunch-slice is not suitable; create a new one and
1844 // insert it into the correct list.
1845 if (old_block_bunch_slice->is_stable())
1846 {
1847 // In most cases, but not always, the source is a bottom state.
1848 #ifdef USE_SIMPLE_LIST
1849 new_block_bunch_slice =
1850 new_block->stable_block_bunch.emplace_front(
1851 old_block_bunch_slice->end,
1852 old_block_bunch_slice->bunch, true);
1853 #else
1854 new_block->stable_block_bunch.emplace_front(
1855 old_block_bunch_slice->end,
1856 old_block_bunch_slice->bunch, true);
1857 new_block_bunch_slice =
1858 new_block->stable_block_bunch.begin();
1859 #endif
1860 }
1861 else
1862 {
1863 #ifdef USE_SIMPLE_LIST
1864 new_block_bunch_slice = splitter_list.emplace_after(
1865 old_block_bunch_slice,
1866 old_block_bunch_slice->end,
1867 old_block_bunch_slice->bunch, false);
1868 #else
1869 new_block_bunch_slice = splitter_list.emplace(
1870 std::next(old_block_bunch_slice),
1871 old_block_bunch_slice->end,
1872 old_block_bunch_slice->bunch, false);
1873 #endif
1874 }
1875 ++nr_of_block_bunch_slices;
1876 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
1877 new_block_bunch_slice->work_counter = old_block_bunch_slice->work_counter;
1878 #endif
1879 }
1880 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
1881 unsigned max_counter = check_complexity::log_n -
1882 check_complexity::ilog2(new_block->size());
1883 #endif
1884 /* move all transitions in this out-slice to the new block_bunch */ assert(out_slice_begin < out_slice_end);
1885 do
1886 { assert(old_block_bunch_pos == out_slice_end[-1].block_bunch);
1887 --out_slice_end; assert(old_block_bunch_pos->slice == old_block_bunch_slice);
1888 assert(source == out_slice_end->block_bunch->pred->source);
1889 --old_block_bunch_slice_end; // assign work already now because the transition may be moved to several
1890 // places:
1891 old_block_bunch_slice_end->slice = new_block_bunch_slice; mCRL2complexity(old_block_bunch_pos->pred, add_work(
1892 check_complexity::move_out_slice_to_new_block, max_counter), partitioner);
1893 if (old_block_bunch_slice->is_stable() || ( assert(!new_block_bunch_slice->is_stable()),
1894 old_block_bunch_slice->marked_begin >
1895 old_block_bunch_slice_end &&
1896 (/* As the old block_bunch-slice has no marked */ assert(nullptr != old_block_bunch_slice_end),
1897 // transitions, it is enough to adapt its marked_begin
1898 // and then do a simple (two-way) swap.
1899 old_block_bunch_slice->marked_begin =
1900 old_block_bunch_slice_end, true)))
1901 {
1902 // The old block_bunch-slice is stable, or it has no
1903 // marked transitions.
1904 std::swap(old_block_bunch_pos->pred,
1905 old_block_bunch_slice_end->pred);
1906 }
1907 else
1908 {
1909 // The old block_bunch-slice is unstable and has marked
1910 // transitions.
1911 pred_entry* const old_pred = old_block_bunch_pos->pred;
1912 if (old_block_bunch_pos < old_block_bunch_slice->marked_begin)
1913 {
1914 // The transition is not marked, but there are other
1915 // marked transitions in the old block_bunch-slice.
1916 // Move the transition to the non-marked part of the
1917 // new block_bunch-slice.
1918 block_bunch_entry* const old_marked_begin =
1919 old_block_bunch_slice->marked_begin - 1; assert(old_block_bunch_pos < old_block_bunch_slice_end);
1920 old_block_bunch_slice->marked_begin = old_marked_begin;
1921
1922 old_block_bunch_pos->pred = old_marked_begin->pred;
1923 old_marked_begin->pred = old_block_bunch_slice_end->pred;
1924 old_block_bunch_slice_end->pred = old_pred; assert(nullptr != old_marked_begin->pred->action_block->succ);
1925
1926 old_marked_begin->pred->action_block->succ->
1927 block_bunch = old_marked_begin;
1928 }
1929 else
1930 {
1931 // The transition is marked. Move to the marked part
1932 // of the new block_bunch-slice.
1933 block_bunch_entry* const new_marked_begin =
1934 new_block_bunch_slice->marked_begin - 1;
1935 new_block_bunch_slice->marked_begin = new_marked_begin; assert(old_block_bunch_pos < new_marked_begin ||
1936 old_block_bunch_pos == old_block_bunch_slice_end);
1937 old_block_bunch_pos->pred=old_block_bunch_slice_end->pred; assert(old_block_bunch_slice_end <= new_marked_begin);
1938 old_block_bunch_slice_end->pred = new_marked_begin->pred;
1939 new_marked_begin->pred = old_pred; assert(out_slice_end == new_marked_begin->pred->action_block->succ);
1940
1941 out_slice_end->block_bunch = new_marked_begin;
1942 }
1943 } assert(nullptr != old_block_bunch_slice_end->pred->action_block->succ);
1944 old_block_bunch_slice_end->pred->action_block->succ->block_bunch =
1945 old_block_bunch_slice_end; assert(nullptr != old_block_bunch_pos->pred->action_block->succ);
1946 old_block_bunch_pos->pred->action_block->succ->block_bunch =
1947 old_block_bunch_pos;
1948 }
1949 while (out_slice_begin < out_slice_end &&
1950 (old_block_bunch_pos = out_slice_end[-1].block_bunch, true));
1951 old_block_bunch_slice->end = old_block_bunch_slice_end;
1952
1953 if (old_block_bunch_slice->empty())
1954 {
1955 if (old_block_bunch_slice->is_stable())
1956 {
1957 // If the new block is R, then the old (U) block loses
1958 // exactly one stable block_bunch-slice, namely the one we
1959 // just stabilised for (`splitter_T`). We could perhaps
1960 // optimize this by moving that slice as a whole to the new
1961 // block -- perhaps later.
1962 //
1963 // If the new block is U, then the old (R) block loses
1964 // no stable block_bunch-slices if it contains any bottom
1965 // state. If it doesn't contain any bottom state, it will
1966 // definitely keep `splitter_T`, but nothing else can be
1967 // guaranteed.
1968 //
1969 // So old_block_bunch_slice may be deleted, in particular
1970 // if the new block is U, but not exclusively.
1971 old_block->stable_block_bunch.erase(old_block_bunch_slice);
1972 }
1973 else
1974 {
1975 splitter_list.erase(old_block_bunch_slice);
1976 }
1977 --nr_of_block_bunch_slices;
1978 }
1979 return out_slice_begin;
1980 }
1981
1982
1983 /// \brief handle one transition after a block has been split
1984 /// \details The main task of this method is to move the transition to the
1985 /// correct place in the action_block slice.
1986 ///
1987 /// This function handles phase 1. Because the new action_block-slice
1988 /// cannot be adapted completely until all transitions into the new block
1989 /// have been handled through phase 1, the next function handles them again
1990 /// in phase 2.
1991 /// \param pred_iter transition that has to be moved
1992 void first_move_transition_to_new_action_block(pred_entry* const pred_iter)
1993 {
1994 action_block_entry* const old_action_block_pos(
1995 pred_iter->action_block); assert(nullptr != old_action_block_pos->succ);
1996 assert(old_action_block_pos->succ->block_bunch->pred == pred_iter);
1997 action_block_entry* const action_block_slice_begin(
1998 old_action_block_pos->action_block_slice_begin( ONLY_IF_DEBUG( action_block.data(), action_block_orig_inert_begin )
1999 )); assert(nullptr != action_block_slice_begin->succ);
2000 assert(action_block_slice_begin->succ->block_bunch->pred->action_block ==
2001 action_block_slice_begin);
2002 action_block_entry* const new_action_block_pos(
2003 action_block_slice_begin->begin_or_before_end); assert(nullptr != new_action_block_pos);
2004 assert(action_block_slice_begin == new_action_block_pos->begin_or_before_end);
2005 assert(nullptr != new_action_block_pos->succ);
2006 assert(new_action_block_pos->succ->block_bunch->pred->action_block ==
2007 /* move the transition to the end of the action_block-slice */ new_action_block_pos);
2008 if (old_action_block_pos < new_action_block_pos)
2009 {
2010 succ_entry* const temp(new_action_block_pos->succ); assert(nullptr != temp); assert(nullptr != old_action_block_pos->succ);
2011 new_action_block_pos->succ = old_action_block_pos->succ;
2012 old_action_block_pos->succ = temp;
2013 temp->block_bunch->pred->action_block = old_action_block_pos; assert(pred_iter == new_action_block_pos->succ->block_bunch->pred);
2014 pred_iter->action_block = new_action_block_pos;
2015
2016 // adapt the old action_block-slice immediately
2017 action_block_slice_begin->begin_or_before_end =
2018 new_action_block_pos - 1;
2019 }
2020 else
2021 { assert(old_action_block_pos == new_action_block_pos);
2022 if (action_block_slice_begin < new_action_block_pos)
2023 {
2024 // The old action_block-slice is not empty, so we have to adapt
2025 // the pointer at the beginning. (If it is empty, it may
2026 // happen that `new_action_block_pos - 1` is an illegal value.)
2027 action_block_slice_begin->begin_or_before_end =
2028 new_action_block_pos - 1;
2029 }
2030 else
2031 {
2032 --nr_of_action_block_slices;
2033 }
2034 } assert(nullptr != new_action_block_pos->succ);
2035 assert(pred_iter == new_action_block_pos->succ->block_bunch->pred);
2036 // adapt the new action_block-slice, as far as is possible now
2037 // make the begin_or_before_end pointers of the first and last
2038 // transition in the slice correct immediately. The other
2039 // begin_or_before_end pointers need to be corrected after all
2040 // transitions in the new bunch have been positioned correctly.
2041 if (new_action_block_pos + 1 >= action_block_inert_begin ||
2042 nullptr == new_action_block_pos[1].succ ||
2043 new_action_block_pos[1].succ->bunch() !=
2044 new_action_block_pos->succ->bunch() ||
2045 new_action_block_pos[1].succ->block_bunch->pred->target->bl.ock !=
2046 pred_iter->target->bl.ock)
2047 {
2048 // This is the first transition that moves to this new
2049 // action_block-slice.
2050 new_action_block_pos->begin_or_before_end = new_action_block_pos;
2051 ++nr_of_action_block_slices;
2052 }
2053 else
2054 {
2055 action_block_entry* const action_block_slice_before_end(
2056 new_action_block_pos[1].begin_or_before_end); assert(nullptr != action_block_slice_before_end);
2057 assert(new_action_block_pos < action_block_slice_before_end);
2058 assert(nullptr != action_block_slice_before_end->succ);
2059 assert(action_block_slice_before_end->succ->block_bunch->pred->action_block ==
2060 action_block_slice_before_end);
2061 assert(new_action_block_pos + 1 ==
2062 action_block_slice_before_end->begin_or_before_end);
2063 action_block_slice_before_end->begin_or_before_end =
2064 new_action_block_pos; assert(action_block_slice_before_end->succ->block_bunch->
2065 pred->target->bl.ock == pred_iter->target->bl.ock);
2066 new_action_block_pos->begin_or_before_end =
2067 action_block_slice_before_end; assert(action_block_slice_before_end < action_block_inert_begin);
2068 }
2069 }
2070
2071
2072 /// \brief handle one transition after a block has been split, phase 2
2073 /// \details Because the new action_block-slice cannot be adapted
2074 /// completely until all transitions into the new block have been handled
2075 /// through phase 1 see the previous function), this function handles them
2076 /// again in phase 2.
2077 /// \param pred_iter transition that has to be moved
2078 void second_move_transition_to_new_action_block(
2079 pred_entry* const pred_iter)
2080 {
2081 action_block_entry* const new_action_block_pos(
2082 pred_iter->action_block); assert(nullptr != new_action_block_pos->succ);
2083 assert(new_action_block_pos->succ->block_bunch->pred == pred_iter);
2084 action_block_entry* const old_begin_or_before_end(
2085 new_action_block_pos->begin_or_before_end); assert(nullptr != old_begin_or_before_end);
2086 assert(nullptr != old_begin_or_before_end->succ);
2087 assert(old_begin_or_before_end->succ->block_bunch->pred->action_block ==
2088 old_begin_or_before_end);
2089 if (action_block_entry* const new_begin_or_before_end(
2090 old_begin_or_before_end->begin_or_before_end); assert(nullptr != new_begin_or_before_end),
2091 assert(nullptr != new_begin_or_before_end->succ),
2092 assert(new_begin_or_before_end->succ->block_bunch->pred->action_block ==
2093 new_begin_or_before_end),
2094 new_begin_or_before_end < new_action_block_pos)
2095 { assert(old_begin_or_before_end==new_begin_or_before_end->begin_or_before_end);
2096 new_action_block_pos->begin_or_before_end =
2097 new_begin_or_before_end; assert(new_action_block_pos <= old_begin_or_before_end);
2098 return;
2099 }
2100 else
2101 {
2102 assert(new_begin_or_before_end == new_action_block_pos);
2103 }
2104 if (old_begin_or_before_end < new_action_block_pos)
2105 {
2106 return;
2107 }
2108
2109 // this is the first transition in the new action_block-slice.
2110 // Check whether the bunch it belongs to has become nontrivial.
2111 bunch_t* const bunch(new_action_block_pos->succ->bunch());
2112 if (!bunch->is_trivial()) { return; } assert(old_begin_or_before_end + 1 == bunch->end);
2113 if (bunch->begin < new_action_block_pos)
2114 {
2115 make_nontrivial(bunch);
2116 }
2117 }
2118
2119
2120 /// \brief adapt data structures for a transition that has become non-inert
2121 /// \details If the action_block-slice and the block_bunch-slice that
2122 /// precede the inert transitions in the respective arrays fit, the
2123 /// transition is added to these arrays instead of creating a new one.
2124 /// This only works if:
2125 /// - the action_block-slice has the same target block and the same action
2126 /// as old_pred_pos
2127 /// - the block_bunch-slice has the same source block as old_pred_pos
2128 /// - the bunch must contain the action_block-slice.
2129 /// If only the last two conditions are fulfilled, we can start a new
2130 /// action_block-slice in the same bunch. (It would be best for this if
2131 /// the R-subblock's block_bunch-slice would be the new one, because that
2132 /// would generally allow to add the new non-inert transitions to the last
2133 /// splitter.)
2134 ///
2135 /// The state is only marked if is becomes a new bottom state. Otherwise,
2136 /// the marking/unmarking of the state is unchanged.
2137 /// \param old_pred_pos the transition that needs to be adapted.
2138 /// \param[in,out] new_noninert_block_bunch_ptr the bunch where new
2139 /// non-inert transitions have to be stored.
2140 /// If no such bunch has yet been created, it
2141 /// is nullptr; in that case, make_noninert()
2142 /// creates a new bunch.
2143 /// \returns true iff the state became a new bottom state
2144 bool make_noninert(pred_entry* const old_pred_pos,
2145 block_bunch_slice_iter_or_null_t* const new_noninert_block_bunch_ptr)
2146 {
2147 state_info_entry* const source(old_pred_pos->source); assert(source->pos->st == source);
2148 state_info_entry* const target(old_pred_pos->target); assert(target->pos->st == target);
2149
2150 action_block_entry* const new_action_block_pos(
2151 action_block_inert_begin++); assert(nullptr != new_action_block_pos->succ);
2152 assert(new_action_block_pos->succ->block_bunch->pred->action_block ==
2153 new_action_block_pos);
2154 succ_entry* const new_succ_pos(source->succ_inert.begin++); assert(new_succ_pos->block_bunch->pred->action_block->succ == new_succ_pos);
2155 block_bunch_entry* const new_block_bunch_pos(
2156 block_bunch_inert_begin++); assert(nullptr != new_block_bunch_pos->pred->action_block->succ);
2157 assert(new_block_bunch_pos->pred->action_block->succ->block_bunch ==
2158 new_block_bunch_pos);
2159 action_block_entry* const old_action_block_pos(
2160 old_pred_pos->action_block); assert(new_action_block_pos <= old_action_block_pos);
2161
2162 succ_entry* const old_succ_pos(old_action_block_pos->succ); assert(nullptr != old_succ_pos);
2163 block_bunch_entry* const old_block_bunch_pos(
2164 old_succ_pos->block_bunch); assert(old_pred_pos == old_block_bunch_pos->pred);
2165 pred_entry* const new_pred_pos(target->pred_inert.begin++); assert(nullptr != new_pred_pos->action_block->succ);
2166 assert(new_pred_pos->action_block->succ->block_bunch->pred == new_pred_pos);
2167
2168 /* adapt action_block */ assert(nullptr == new_action_block_pos->begin_or_before_end);
2169 if (new_action_block_pos < old_action_block_pos)
2170 { assert(nullptr == old_action_block_pos->begin_or_before_end);
2171 old_action_block_pos->succ = new_action_block_pos->succ; assert(nullptr != old_action_block_pos->succ);
2172 assert(old_action_block_pos->succ->block_bunch->pred->action_block ==
2173 new_action_block_pos);
2174 old_action_block_pos->succ->block_bunch->pred->action_block =
2175 old_action_block_pos;
2176 }
2177 else
2178 {
2179 assert(new_action_block_pos == old_action_block_pos);
2180 }
2181 new_action_block_pos->succ = new_succ_pos; assert(nullptr != new_succ_pos);
2182 // new_action_block_pos->begin_or_before_end = ...; -- see below
2183
2184 /* adapt succ */ assert(nullptr == new_succ_pos->begin_or_before_end);
2185 if (new_succ_pos < old_succ_pos)
2186 { assert(nullptr == old_succ_pos->begin_or_before_end);
2187 old_succ_pos->block_bunch = new_succ_pos->block_bunch; assert(old_succ_pos->block_bunch->pred->action_block->succ == new_succ_pos);
2188 old_succ_pos->block_bunch->pred->action_block->succ = old_succ_pos; assert(nullptr != old_succ_pos);
2189 }
2190 else
2191 {
2192 assert(new_succ_pos == old_succ_pos);
2193 }
2194 new_succ_pos->block_bunch = new_block_bunch_pos;
2195 // new_succ_pos->begin_or_before_end = ...; -- see below
2196
2197 /* adapt block_bunch */ assert(new_block_bunch_pos->slice.is_null());
2198 if (new_block_bunch_pos < old_block_bunch_pos)
2199 { assert(old_block_bunch_pos->slice.is_null());
2200 assert(nullptr != old_block_bunch_pos->pred->action_block->succ);
2201 old_block_bunch_pos->pred = new_block_bunch_pos->pred; assert(old_block_bunch_pos->pred->action_block->succ->block_bunch ==
2202 new_block_bunch_pos);
2203 assert(nullptr != old_block_bunch_pos->pred->action_block->succ);
2204 old_block_bunch_pos->pred->action_block->succ->block_bunch =
2205 old_block_bunch_pos;
2206 }
2207 else
2208 {
2209 assert(new_block_bunch_pos == old_block_bunch_pos);
2210 }
2211 new_block_bunch_pos->pred = new_pred_pos;
2212 // new_block_bunch_pos->slice = ...; -- see below
2213
2214 // adapt pred
2215 if (new_pred_pos < old_pred_pos)
2216 {
2217 // We need std::swap here to swap the whole content, including
2218 // work counters in case we measure the work.
2219 std::swap(*old_pred_pos, *new_pred_pos); assert(nullptr != old_pred_pos->action_block->succ);
2220 assert(old_pred_pos->action_block->succ->block_bunch->pred == new_pred_pos);
2221 old_pred_pos->action_block->succ->block_bunch->pred = old_pred_pos;
2222 }
2223 else
2224 {
2225 assert(new_pred_pos == old_pred_pos);
2226 }
2227 new_pred_pos->action_block = new_action_block_pos;
2228
2229 /* make the state a bottom state if necessary */ assert(source->bl.ock->nonbottom_begin <= source->pos);
2230 bool became_bottom(false); assert(succ.back().block_bunch->pred->source != source);
2231 if (source != source->succ_inert.begin->block_bunch->pred->source)
2232 {
2233 block_t* const source_block(source->bl.ock);
2234 // make the state a marked bottom state
2235 if (source->pos >= source_block->marked_nonbottom_begin)
2236 {
2237 std::swap(*source->pos,
2238 *source_block->marked_nonbottom_begin++);
2239 } assert(source->pos < source_block->marked_nonbottom_begin);
2240 std::swap(*source->pos, *source_block->nonbottom_begin++);
2241 ++nr_of_new_bottom_states;
2242 became_bottom = true;
2243 }
2244
2245 bunch_t* new_noninert_bunch; assert(nullptr != new_action_block_pos);
2246 if (!new_noninert_block_bunch_ptr->is_null())
2247 {
2248 // There is already some new non-inert transition from this block.
2249 // So we can reuse this block_bunch and its bunch.
2250 // (However, it may be the case that the current transition goes to
2251 // another block; in the latter case, we have to create a new
2252 // action_block-slice.)
2253
2254 // extend the bunch
2255 new_noninert_bunch = (*new_noninert_block_bunch_ptr)->bunch; assert(new_action_block_pos >= new_noninert_bunch->end);
2257 for (const action_block_entry*temp_action_block_pos=new_action_block_pos ;
2258 temp_action_block_pos > new_noninert_bunch->end ; )
2259 {
2260 assert(nullptr == (--temp_action_block_pos)->succ);
2261 }
2262 )
2263 new_noninert_bunch->end = action_block_inert_begin;
2264 /* extend the block_bunch-slice */ assert((*new_noninert_block_bunch_ptr)->end == new_block_bunch_pos);
2265 (*new_noninert_block_bunch_ptr)->end = block_bunch_inert_begin;
2266 if (!(*new_noninert_block_bunch_ptr)->is_stable())
2267 { assert((*new_noninert_block_bunch_ptr)->marked_begin == new_block_bunch_pos);
2268 (*new_noninert_block_bunch_ptr)->marked_begin =
2269 block_bunch_inert_begin;
2270 }
2271 new_block_bunch_pos->slice = *new_noninert_block_bunch_ptr;
2272
2273 /* adapt the action_block-slice */ assert(new_noninert_bunch->begin < new_action_block_pos);
2274 if (nullptr != new_action_block_pos[-1].succ &&
2275 target->bl.ock == new_action_block_pos[-1].
2276 succ->block_bunch->pred->target->bl.ock)
2277 {
2278 // the action_block-slice is suitable: extend it
2279 action_block_entry* const action_block_slice_begin(
2280 new_action_block_pos[-1].begin_or_before_end); assert(nullptr != action_block_slice_begin);
2281 assert(new_action_block_pos-1==action_block_slice_begin->begin_or_before_end);
2282 assert(nullptr != action_block_slice_begin->succ);
2283 assert(action_block_slice_begin->succ->block_bunch->pred->action_block ==
2284 action_block_slice_begin);
2285 action_block_slice_begin->begin_or_before_end =
2286 new_action_block_pos;
2287 new_action_block_pos->begin_or_before_end =
2288 action_block_slice_begin;
2289 }
2290 else
2291 {
2292 // create a new action_block-slice
2293 new_action_block_pos->begin_or_before_end=new_action_block_pos;
2294 if (new_noninert_bunch->is_trivial())
2295 { // Only during initialisation, it may happen that we add new non-inert
2296 make_nontrivial(new_noninert_bunch); // transitions to a nontrivial bunch:
2297 }
2298 #ifndef NDEBUG
2299 else
2300 {
2301 // We make sure that new_noninert_bunch is the first bunch in
2302 // action_block (and because it's always the last one, it will be the
2303 // only one, so there is only one bunch, as ).
2304 for (const action_block_entry* iter = action_block.data();
2305 iter < new_noninert_bunch->begin; ++iter)
2306 {
2307 assert(nullptr == iter->succ);
2308 assert(nullptr == iter->begin_or_before_end);
2309 }
2310 }
2311 #endif
2312 ++nr_of_action_block_slices;
2313 }
2314
2315 /* adapt the out-slice */ assert(source != succ.front().block_bunch->pred->source);
2316 if (source == new_succ_pos[-1].block_bunch->pred->source &&
2317 new_succ_pos[-1].bunch() == new_noninert_bunch)
2318 {
2319 // the out-slice is suitable: extend it.
2320 succ_entry* const out_slice_begin(
2321 new_succ_pos[-1].begin_or_before_end); assert(nullptr != out_slice_begin);
2322 assert(new_succ_pos - 1 == out_slice_begin->begin_or_before_end);
2323 out_slice_begin->begin_or_before_end = new_succ_pos; assert(out_slice_begin->block_bunch->pred->action_block->succ ==
2324 out_slice_begin);
2325 new_succ_pos->begin_or_before_end = out_slice_begin;
2326 return became_bottom;
2327 }
2328 }
2329 else
2330 {
2331 // create a new bunch for noninert transitions
2332 new_noninert_bunch =
2333 #ifdef USE_POOL_ALLOCATOR
2334 simple_list<block_bunch_slice_t>::get_pool().
2335 template construct<bunch_t>
2336 #else
2337 new bunch_t
2338 #endif
2339 (new_action_block_pos, action_block_inert_begin);
2340 ++nr_of_bunches;
2341
2342 // create a new block_bunch-slice
2343 #ifdef USE_SIMPLE_LIST
2344 block_bunch_slice_iter_t new_noninert_block_bunch(
2345 splitter_list.emplace_back(
2346 block_bunch_inert_begin, new_noninert_bunch, false));
2347 #else
2348 splitter_list.emplace_back(block_bunch_inert_begin,
2349 new_noninert_bunch, false);
2350 block_bunch_slice_iter_t new_noninert_block_bunch(
2351 std::prev(splitter_list.end()));
2352 #endif
2353 ++nr_of_block_bunch_slices;
2354 new_block_bunch_pos->slice = new_noninert_block_bunch;
2355 *new_noninert_block_bunch_ptr = new_noninert_block_bunch;
2356
2357 // create a new action_block-slice
2358 new_action_block_pos->begin_or_before_end = new_action_block_pos;
2359 ++nr_of_action_block_slices;
2360 } assert(&succ.cbegin()[1] == new_succ_pos ||
2361 new_succ_pos[-1].block_bunch->pred->source < source ||
2362 /* create a new out-slice */ new_succ_pos[-1].bunch() != new_noninert_bunch);
2363 new_succ_pos->begin_or_before_end = new_succ_pos;
2364 return became_bottom;
2365 }
2366
2367
2368 public:
2369 /// \brief Split all data structures after a new block has been created
2370 /// \details This function splits the block_bunch- and action_block-slices
2371 /// to reflect that some transitions now start or end in the new block.
2372 /// They can no longer be in the same slice as the transitions that start
2373 /// or end in the old block, respectively. It also marks the transitions
2374 /// that have become non-inert as such and finds new bottom states.
2375 ///
2376 /// Its time complexity is O(1 + |in(new_block)| + |out(new_block)|).
2377 /// \param new_block the new block
2378 /// \param old_block the old block (from which new_block was split
2379 /// off)
2380 /// \param add_new_noninert_to_splitter indicates to which
2381 /// block_bunch-slice new non-inert transitions
2382 /// should be added: if this parameter is `false`,
2383 /// a new slice is created; if it is `true`,
2384 /// new non-inert transitions are added to
2385 /// `splitter_T`. The latter can be done iff
2386 /// `splitter_T` is the last block_bunch-slice
2387 /// \param splitter_T the splitter that caused new_block and old_block
2388 /// to separate from each other
2389 /// \param new_block_mode indicates whether the new block is U or R
2390 ONLY_IF_DEBUG( template<class LTS_TYPE> )
2391 void adapt_transitions_for_new_block(
2392 block_t* const new_block,
2393 block_t* const old_block, ONLY_IF_DEBUG( const bisim_partitioner_dnj<LTS_TYPE>& partitioner, )
2394 bool const add_new_noninert_to_splitter,
2395 const block_bunch_slice_iter_t splitter_T,
2396 enum new_block_mode_t const new_block_mode)
2397 { assert(splitter_T->is_stable());
2398 // We begin with a bottom state so the new block gets a sorted list of
2399 // stable block_bunch-slices.
2400 permutation_entry* s_iter(new_block->begin); assert(s_iter < new_block->end);
2401 do
2402 {
2403 state_info_entry* const s(s_iter->st); assert(new_block == s->bl.ock);
2404 assert(s->pos == s_iter);
2405 /* - - - - - - adapt part_tr.block_bunch - - - - - - */
2406 assert(s != succ.front().block_bunch->pred->source);
2407 for (succ_entry* succ_iter(s->succ_inert.begin);
2408 s == succ_iter[-1].block_bunch->pred->source; )
2409 {
2410 succ_iter = move_out_slice_to_new_block(succ_iter, ONLY_IF_DEBUG( partitioner, )
2411 old_block, splitter_T); assert(succ_iter->block_bunch->pred->action_block->succ == succ_iter);
2412 assert(s == succ_iter->block_bunch->pred->source);
2413 // add_work_to_out_slice(succ_iter, ...) -- subsumed in the call below
2414 }
2415
2416 /*- - - - - - adapt part_tr.action_block - - - - - -*/
2417 assert(s != pred.front().target);
2418 for (pred_entry* pred_iter(s->pred_inert.begin);
2419 s == (--pred_iter)->target; )
2420 { assert(pred.data() < pred_iter);
2421 assert(nullptr != pred_iter->action_block->succ);
2422 assert(pred_iter->action_block->succ->block_bunch->pred == pred_iter);
2423 first_move_transition_to_new_action_block(pred_iter); // mCRL2complexity(pred_iter, ...) -- subsumed in the call below
2424 } // mCRL2complexity(s, ...) -- subsumed in the call at the end
2425 }
2426 while (++s_iter < new_block->end);
2427
2428 if (new_block_is_R == new_block_mode)
2429 { assert(splitter_T->source_block() == new_block);
2430 // The `splitter_T` slice moves completely from the old to the new
2431 // block. We move it as a whole to the new block_bunch list.
2432 new_block->stable_block_bunch.splice(
2433 new_block->stable_block_bunch.begin(),
2434 old_block->stable_block_bunch, splitter_T);
2435 }
2436 else
2437 {
2438 assert(splitter_T->source_block() == old_block);
2439 }
2440
2441 // We cannot join the loop above with the one below because transitions
2442 // in the action_block-slices need to be handled in two phases.
2443
2444 s_iter = new_block->begin; assert(s_iter < new_block->end);
2445 do
2446 {
2447 state_info_entry* const s(s_iter->st); assert(s->pos == s_iter); assert(s != pred.front().target);
2448 for (pred_entry* pred_iter(s->pred_inert.begin);
2449 s == (--pred_iter)->target; )
2450 { assert(pred.data() < pred_iter);
2451 assert(nullptr != pred_iter->action_block->succ);
2452 assert(pred_iter->action_block->succ->block_bunch->pred == pred_iter);
2453 second_move_transition_to_new_action_block(pred_iter); // mCRL2complexity(pred_iter, ...) -- subsumed in the call below
2454 } // mCRL2complexity(s, ...) -- subsumed in the call at the end
2455 }
2456 while (++s_iter < new_block->end);
2457 assert(0 == new_block->marked_size()); assert(0 == old_block->marked_size());
2458 /* - - - - - - find new non-inert transitions - - - - - - */ assert(block_bunch.data_end() - block_bunch_inert_begin ==
2459 action_block.data_end() - action_block_inert_begin);
2460 if (block_bunch_inert_begin < block_bunch.data_end())
2461 {
2462 block_bunch_slice_iter_or_null_t new_noninert_block_bunch;
2463 if (add_new_noninert_to_splitter)
2464 {
2465 new_noninert_block_bunch = splitter_T;
2466 }
2467 else
2468 {
2469 new_noninert_block_bunch = nullptr;
2470 }
2471 if (new_block_is_U == new_block_mode)
2472 { assert(old_block == new_block->end->st->bl.ock);
2473 assert(new_block->end < partitioner.part_st.permutation.data_end());
2474 permutation_entry* target_iter(new_block->begin); assert(target_iter < new_block->end);
2475 do
2476 {
2477 state_info_entry* const s(target_iter->st); assert(s->pos == target_iter);
2478 // check all incoming inert transitions of s, whether they
2479 /* still start in new_block */ assert(s != pred.back().target);
2480 for (pred_entry* pred_iter(s->pred_inert.begin);
2481 s == pred_iter->target; ++pred_iter)
2482 { assert(pred_iter < &pred.back());
2483 assert(nullptr != pred_iter->action_block->succ);
2484 state_info_entry* const t(pred_iter->source); assert(pred_iter->action_block->succ->block_bunch->pred == pred_iter);
2485 assert(t->pos->st == t);
2486 if (new_block != t->bl.ock)
2487 { assert(old_block == t->bl.ock);
2488 if (!make_noninert(pred_iter,
2489 &new_noninert_block_bunch))
2490 // make_noninert() may modify *pred_iter
2491 {
2492 old_block->mark_nonbottom(t->pos);
2493 }
2494 } // mCRL2complexity(old value of *pred_iter, ...) -- overapproximated by the
2495 // call below
2496 } // mCRL2complexity(s, ...) -- subsumed in the call at the end
2497 }
2498 while (++target_iter < new_block->end); assert(0 < old_block->bottom_size());
2499 }
2500 else
2501 { assert(new_block_is_R == new_block_mode);
2502 /* We have to be careful because make_noninert may move */ assert(partitioner.part_st.permutation.data() < new_block->begin);
2503 /* a state either forward (to the marked states) or */ assert(old_block == new_block->begin[-1].st->bl.ock);
2504 /* back (to the bottom states). */ assert(0 < old_block->bottom_size());
2505 for(permutation_entry* source_iter(new_block->nonbottom_begin);
2506 source_iter < new_block->marked_nonbottom_begin; )
2507 {
2508 state_info_entry* const s(source_iter->st); assert(s->pos == source_iter);
2509 // check all outgoing inert transitions of s, whether they
2510 /* still end in new_block. */ assert(succ.back().block_bunch->pred->source != s);
2511 succ_entry* succ_iter(s->succ_inert.begin); assert(succ_iter < &succ.back());
2512 bool dont_mark(true); assert(s == succ_iter->block_bunch->pred->source);
2513 do
2514 { assert(succ_iter->block_bunch->pred->action_block->succ == succ_iter);
2515 if (new_block !=
2516 succ_iter->block_bunch->pred->target->bl.ock)
2517 { assert(old_block == succ_iter->block_bunch->pred->target->bl.ock);
2518 dont_mark = make_noninert(
2519 succ_iter->block_bunch->pred,
2520 &new_noninert_block_bunch);
2521 } // mCRL2complexity(succ_iter->block_bunch->pred, ...) -- overapproximated by
2522 // the call below
2523 }
2524 while (s == (++succ_iter)->block_bunch->pred->source);
2525 if (dont_mark)
2526 {
2527 ++source_iter;
2528 }
2529 else
2530 { assert(s->pos == source_iter);
2531 new_block->mark_nonbottom(source_iter);
2532 } assert(new_block->nonbottom_begin <= source_iter);
2533 // mCRL2complexity(s, ...) -- overapproximated by the call at the end
2534 }
2535 }
2536 }
2537 else
2538 {
2539 assert(block_bunch_inert_begin == block_bunch.data_end());
2540 }
2541 mCRL2complexity(new_block, add_work(check_complexity::
2542 adapt_transitions_for_new_block, check_complexity::log_n -
2543 check_complexity::ilog2(new_block->size())), partitioner);
2544 }
2545 #ifndef NDEBUG
2546 /// \brief print all transitions
2547 /// \details Transitions are printed organised into bunches.
2548 template <class LTS_TYPE>
2549 void print_trans(const bisim_partitioner_dnj<LTS_TYPE>& partitioner) const
2550 {
2551 if (!mCRL2logEnabled(log::debug))
2552 {
2553 return;
2554 }
2555 // print all outgoing transitions grouped per successor and out-slice
2556 const succ_entry* succ_iter(&succ.cbegin()[1]);
2557 if (succ_iter >= &succ.back())
2558 {
2559 mCRL2log(log::debug) << "No transitions.\n";
2560 return;
2561 }
2562 const state_info_entry* source(succ_iter->block_bunch->pred->source);
2563 mCRL2log(log::debug) << source->debug_id(partitioner) << ":\n";
2564 if (succ_iter->block_bunch->slice.is_null())
2565 {
2566 mCRL2log(log::debug) << "\tInert successors:\n";
2567 }
2568 block_bunch_slice_iter_or_null_t current_out_bunch(nullptr);
2569 do
2570 {
2571 bool always_print=false;
2572 if (source != succ_iter->block_bunch->pred->source)
2573 { assert(source < succ_iter->block_bunch->pred->source);
2574 source = succ_iter->block_bunch->pred->source;
2575 mCRL2log(log::debug)
2576 << source->debug_id(partitioner) << ":\n";
2577 always_print=true;
2578 }
2579 if (always_print ||
2580 succ_iter->block_bunch->slice != current_out_bunch)
2581 { //assert(!current_out_bunch.is_null());
2582 if (succ_iter->block_bunch->slice.is_null())
2583 { assert(succ_iter == source->succ_inert.begin);
2584 mCRL2log(log::debug)<<"\tInert successors:\n";
2585 current_out_bunch = nullptr;
2586 }
2587 else
2588 { assert(succ_iter < source->succ_inert.begin);
2589 //assert(!current_out_bunch.is_null());
2590 //assert(current_out_bunch == splitter_list.end() ||
2591 // current_out_bunch->bunch != succ_iter->bunch());
2592 mCRL2log(log::debug) << "\tSuccessors in "
2593 <<succ_iter->bunch()->debug_id_short(partitioner)<<":\n";
2594 current_out_bunch = succ_iter->block_bunch->slice;
2595 }
2596 }
2597 mCRL2log(log::debug) << "\t\t"
2598 << succ_iter->block_bunch->pred->debug_id(partitioner) << '\n';
2599 }
2600 while (++succ_iter < &succ.back());
2601
2602 // print all transitions grouped per bunch and action_block-slice
2603 const action_block_entry* action_block_iter(action_block.data());
2604 do {
2605 assert(action_block_iter
2606 < action_block
2607 .data_end());
2608 }
2609 while (
2610 nullptr
2611 == action_block_iter
2612 ->succ
2613 && (assert(
2614 nullptr
2615 == action_block_iter
2616 ->begin_or_before_end),
2617 ++action_block_iter,
2618 true));
2619 do
2620 {
2621 const action_block_entry* bunch_end;
2622 const action_block_entry* action_block_slice_end;
2623 assert(nullptr != action_block_iter->succ);
2624 if (action_block_iter->succ->block_bunch->slice.is_null())
2625 { assert(action_block_iter == action_block_inert_begin);
2626 mCRL2log(log::debug) <<"Inert transition slice [";
2627 action_block_slice_end = bunch_end = action_block.data_end();
2628 }
2629 else
2630 {
2631 const bunch_t* const bunch(action_block_iter->succ->bunch());
2632 assert(nullptr != bunch);
2633 mCRL2log(log::debug) << bunch->debug_id_short(
2634 partitioner) << ":\n\taction_block-slice [";
2635 assert(bunch->begin == action_block_iter);
2636 bunch_end = bunch->end;
2637 assert(bunch_end <= action_block_inert_begin);
2638 assert(nullptr != action_block_iter->begin_or_before_end);
2639 action_block_slice_end =
2640 action_block_iter->begin_or_before_end + 1;
2641 }
2642 assert(action_block_slice_end <= bunch_end);
2643 // for all action_block-slices in bunch
2644 for (;;)
2645 {
2646 mCRL2log(log::debug) << (action_block_iter -
2647 action_block.data()) << ","
2648 << (action_block_slice_end - action_block.data()) << "):\n";
2649 // for all transitions in the action_block-slice
2650 assert(action_block_iter < action_block_slice_end);
2651 do
2652 {
2653 assert(nullptr != action_block_iter->succ);
2654 mCRL2log(log::debug) << "\t\t"
2655 << action_block_iter->succ->block_bunch->
2656 pred->debug_id(partitioner) << '\n';
2657 }
2658 while (++action_block_iter < action_block_slice_end);
2659 // go to next action_block-slice in the same bunch
2660 while (action_block_iter < bunch_end &&
2661 nullptr == action_block_iter->succ)
2662 {
2663 assert(nullptr == action_block_iter->begin_or_before_end);
2664 ++action_block_iter;
2665 assert(action_block_iter < bunch_end);
2666 }
2667 if (action_block_iter
2668 >= bunch_end)
2669 {
2670 break;
2671 }
2672 assert(nullptr != action_block_iter->begin_or_before_end);
2673 action_block_slice_end =
2674 action_block_iter->begin_or_before_end + 1;
2675 mCRL2log(log::debug) << "\taction_block-slice [";
2676 }
2677 // go to next bunch
2678 assert(action_block_iter == bunch_end);
2679 while (action_block_iter < action_block.data_end() &&
2680 nullptr == action_block_iter->succ)
2681 {
2682 assert(nullptr == action_block_iter->begin_or_before_end);
2683 ++action_block_iter;
2684 }
2685 }
2686 while (action_block_iter < action_block.data_end());
2687 }
2688 #endif
2689};
2690
2691
2692/// \brief refine a block
2693/// \details This function is called after a refinement function has found
2694/// where to split the block into unmarked (U) and marked (R) states.
2695/// It creates a new block for the smaller subblock.
2696/// \param new_block_mode indicates whether the U- or the R-block should be
2697/// the new one. (This parameter is necessary in case
2698/// the two halves have exactly the same size.)
2699/// \param new_seqnr is the sequence number of the new block
2700/// \returns pointer to the new block
2701 ONLY_IF_DEBUG( template<class LTS_TYPE> )
2702inline block_t* block_t::split_off_block(
2703 enum new_block_mode_t const new_block_mode, ONLY_IF_DEBUG( const bisim_partitioner_dnj<LTS_TYPE>& partitioner, )
2704 state_type const new_seqnr)
2705{ assert(0 < marked_size()); assert(0 < unmarked_bottom_size());
2706 // create a new block
2707 block_t* new_block;
2708 state_type swapcount(std::min(marked_bottom_size(),
2709 unmarked_nonbottom_size()));
2710 if (permutation_entry* const splitpoint(marked_bottom_begin +
2711 unmarked_nonbottom_size()); assert(begin < splitpoint), assert(splitpoint < end),
2712 assert(splitpoint->st->pos == splitpoint),
2713 new_block_is_U == new_block_mode)
2714 { assert((state_type) (splitpoint - begin) <= size()/2);
2715 new_block =
2716 #ifdef USE_POOL_ALLOCATOR
2717 simple_list<block_bunch_slice_t>::get_pool().
2718 template construct<block_t>
2719 #else
2720 new block_t
2721 #endif
2722 (begin, splitpoint, new_seqnr);
2723 new_block->nonbottom_begin = marked_bottom_begin;
2724
2725 // adapt the old block: it only keeps the R-states
2726 begin = splitpoint;
2727 nonbottom_begin = marked_nonbottom_begin;
2728 }
2729 else
2730 { assert(new_block_is_R == new_block_mode);
2731 new_block =
2732 #ifdef USE_POOL_ALLOCATOR
2733 simple_list<block_bunch_slice_t>::get_pool().
2734 template construct<block_t>
2735 #else
2736 new block_t
2737 #endif
2738 (splitpoint, end, new_seqnr);
2739 new_block->nonbottom_begin = marked_nonbottom_begin; assert((state_type) (end - splitpoint) <= size()/2);
2740
2741 // adapt the old block: it only keeps the U-states
2742 end = splitpoint;
2743 nonbottom_begin = marked_bottom_begin;
2744 }
2745 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
2746 /* swap contents */ new_block->work_counter = work_counter;
2747 #endif
2748 // The structure of a block is
2749 // | unmarked | marked | unmarked | marked |
2750 // | bottom | bottom | non-bottom | non-bottom |
2751 // We have to swap the marked bottom with the unmarked non-bottom
2752 // states.
2753 //
2754 // It is not necessary to reset the untested_to_U counters; these
2755 // counters are anyway only valid for the states in the respective
2756 // slice.
2757
2758 if (0 < swapcount)
2759 {
2760 // vector swap the states:
2761 permutation_entry* pos1(marked_bottom_begin);
2762 permutation_entry* pos2(marked_nonbottom_begin); assert(pos1 < pos2);
2763 permutation_entry const temp(std::move(*pos1));
2764 for (;;)
2765 {
2766 --pos2; assert(pos1 < pos2);
2767 *pos1 = std::move(*pos2);
2768 ++pos1;
2769 if (0 >= --swapcount) { break; } assert(pos1 < pos2);
2770 *pos2 = std::move(*pos1); // mCRL2complexity(new_block_is_U == new_block_mode ? pos1[-1] : *pos2, ...)
2771 } // -- overapproximated by the call at the end
2772 *pos2 = std::move(temp); // mCRL2complexity(new_block_is_U == new_block_mode ? pos1[-1] : *pos2, ...)
2773 } // -- overapproximated by the call at the end
2774 #ifndef NDEBUG
2775 { const permutation_entry* s_iter(begin); assert(s_iter < end);
2776 do {
2777 assert(
2778 s_iter->st->pos == s_iter);
2779 }
2780 while (++s_iter < end);
2781 }
2782#endif
2783 // unmark all states in both blocks
2784 marked_nonbottom_begin = end;
2785 marked_bottom_begin = nonbottom_begin;
2786 new_block->marked_bottom_begin = new_block->nonbottom_begin; assert(new_block->size() <= size());
2787
2788 /* set the block pointer of states in the new block */ assert(new_block->marked_nonbottom_begin == new_block->end);
2789 permutation_entry* s_iter(new_block->begin); assert(s_iter < new_block->end);
2790 do
2791 { assert(s_iter->st->pos == s_iter);
2792 s_iter->st->bl.ock = new_block; // mCRL2complexity (*s_iter, ...) -- subsumed in the call below
2793 }
2794 while (++s_iter < new_block->end); mCRL2complexity(new_block, add_work(check_complexity::split_off_block,
2795 check_complexity::log_n - check_complexity::ilog2(new_block->size())),
2796 partitioner);
2797 return new_block;
2798}
2799
2800
2801/// \brief split off a single action_block-slice from the bunch
2802/// \details The function splits the current bunch after its first
2803/// action_block-slice or before its last action_block-slice, whichever is
2804/// smaller. It creates a new bunch for the split-off slice and returns a
2805/// pointer to the new bunch. The caller has to adapt the block_bunch-slices.
2806/// \param part_tr the data structure containing information about the
2807/// partition of transitions (needed to find the list of
2808/// non-trivial bunches)
2809/// \returns pointer to a new bunch containing one small action_block-slice
2810/// that was originally in this bunch
2811inline bunch_t* bunch_t::split_off_small_action_block_slice(
2812 part_trans_t& part_tr)
2813{ assert(begin < end); assert(nullptr != begin->succ);
2814 assert(nullptr != begin->begin_or_before_end);
2815 action_block_entry* const first_slice_end(begin->begin_or_before_end + 1); assert(nullptr != end[-1].succ); assert(nullptr!=end[-1].begin_or_before_end);
2816 action_block_entry* const last_slice_begin(end[-1].begin_or_before_end); assert(begin < first_slice_end); assert(first_slice_end <= last_slice_begin);
2817 bunch_t* bunch_T_a_Bprime;
2818 /* Line 2.6: Select some a in Act and B' in Pi_s such that */ assert(last_slice_begin < end); assert(nullptr != first_slice_end[-1].succ);
2819 /* |T--a-->B'| < 1/2 |T| */ assert(nullptr != last_slice_begin->succ);
2820 if (first_slice_end - begin > end - last_slice_begin)
2821 {
2822 // Line 2.7: Pi_t := Pi_t \ {T} union { T--a-->B', T \ T--a-->B' }
2823 bunch_T_a_Bprime =
2824 #ifdef USE_POOL_ALLOCATOR
2825 simple_list<block_bunch_slice_t>::get_pool().
2826 template construct<bunch_t>
2827 #else
2828 new bunch_t
2829 #endif
2830 (last_slice_begin, end); assert(nullptr != bunch_T_a_Bprime);
2831 end = last_slice_begin;
2832 while (nullptr == end[-1].succ)
2833 {
2834 --end; assert(first_slice_end <= end); assert(nullptr == end->begin_or_before_end);
2835 } assert(nullptr != end[-1].begin_or_before_end);
2836 if (first_slice_end == end)
2837 {
2838 part_tr.make_trivial(this);
2839 }
2840 }
2841 else
2842 {
2843 // Line 2.7: Pi_t := Pi_t \ {T} union { T--a-->B', T \ T--a-->B' }
2844 bunch_T_a_Bprime =
2845 #ifdef USE_POOL_ALLOCATOR
2846 simple_list<block_bunch_slice_t>::get_pool().
2847 template construct<bunch_t>
2848 #else
2849 new bunch_t
2850 #endif
2851 (begin, first_slice_end); assert(nullptr != bunch_T_a_Bprime);
2852 begin = first_slice_end;
2853 while (nullptr == begin->succ)
2854 { assert(nullptr == begin->begin_or_before_end);
2855 ++begin; assert(begin <= last_slice_begin);
2856 } assert(nullptr != begin->begin_or_before_end);
2857 if (begin == last_slice_begin)
2858 {
2859 part_tr.make_trivial(this);
2860 }
2861 }
2862 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
2863 bunch_T_a_Bprime->work_counter = work_counter;
2864 #endif
2865 ++part_tr.nr_of_bunches;
2866 return bunch_T_a_Bprime;
2867}
2868///@} (end of group part_trans)
2869
2870} // end namespace bisim_dnj
2871
2872
2873
2874
2875
2876/* ************************************************************************* */
2877/* */
2878/* A L G O R I T H M S */
2879/* */
2880/* ************************************************************************* */
2881
2882
2883
2884
2885
2886/// \defgroup part_refine
2887/// \brief classes to calculate the stutter equivalence quotient of a LTS
2888///@{
2889
2890
2891
2892/*=============================================================================
2893= main class =
2894=============================================================================*/
2895
2896
2897
2898
2899
2900/// \class bisim_partitioner_dnj
2901/// \brief implements the main algorithm for the branching bisimulation
2902/// quotient
2903template <class LTS_TYPE>
2904class bisim_partitioner_dnj
2905{
2906 private:
2907 /// \brief modes that determine details of how split() should work
2908 enum refine_mode_t{extend_from_marked_states,
2909 extend_from_marked_states_add_new_noninert_to_splitter,
2910 extend_from_splitter };
2911
2912 /// \brief automaton that is being reduced
2913 LTS_TYPE& aut;
2914 ONLY_IF_DEBUG( public: )
2915 /// \brief partition of the state space into blocks
2916 bisim_dnj::part_state_t part_st;
2917
2918 /// \brief partitions of the transitions (with bunches and
2919 /// action_block-slices)
2920 bisim_dnj::part_trans_t part_tr;
2921 private:
2922 /// \brief action label slices
2923 /// \details In part_tr.action_block, no information about the action label
2924 /// is actually stored with the transitions, to save memory. Entry l of
2925 /// this array contains a pointer to the first entry in
2926 /// part_tr.action_block with label l.
2927 ///
2928 /// During initialisation, entry l of this array contains a counter to
2929 /// indicate how many non-inert transitions with action label l have been
2930 /// found.
2931 fixed_vector<bisim_dnj::iterator_or_counter<
2932 bisim_dnj::action_block_entry*> > action_label;
2933 ONLY_IF_DEBUG( public: )
2934 /// \brief true iff branching (not strong) bisimulation has been requested
2935 bool const branching;
2936 private:
2937 /// \brief true iff divergence-preserving branching bisimulation has been
2938 /// requested
2939 /// \details Note that this field must be false if strong bisimulation has
2940 /// been requested. There is no such thing as divergence-preserving strong
2941 /// bisimulation.
2942 bool const preserve_divergence;
2943 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
2944 friend class bisim_dnj::pred_entry;
2945 friend class bisim_dnj::bunch_t;
2946 #endif
2947 public:
2948 /// time measurement after the end of create_initial_partition()
2949 std::clock_t end_initial_part;
2950
2951 /// \brief constructor
2952 /// \details The constructor constructs the data structures and immediately
2953 /// calculates the partition corresponding with the bisimulation quotient.
2954 /// It destroys the transitions on the LTS (to save memory) but does not
2955 /// adapt the LTS to represent the quotient's transitions.
2956 /// \param new_aut LTS that needs to be reduced
2957 /// \param new_branching If true branching bisimulation is used,
2958 /// otherwise strong bisimulation is
2959 /// applied.
2960 /// \param new_preserve_divergence If true and branching is true, preserve
2961 /// tau loops on states.
2962 bisim_partitioner_dnj(LTS_TYPE& new_aut, bool const new_branching = false,
2963 bool const new_preserve_divergence = false)
2964 : aut(new_aut),
2965 part_st(new_aut.num_states()),
2966 part_tr(new_aut.num_transitions(), new_aut.num_action_labels()),
2967 action_label(new_aut.num_action_labels()),
2968 branching(new_branching),
2969 preserve_divergence(new_preserve_divergence)
2970 { assert(branching || !preserve_divergence);
2971
2972 create_initial_partition(); ONLY_IF_DEBUG( part_tr.action_block_orig_inert_begin =
2973 part_tr.action_block_inert_begin; )
2974 end_initial_part = std::clock();
2975 refine_partition_until_it_becomes_stable();
2976 }
2977
2978
2979 /// \brief Calculate the number of equivalence classes
2980 /// \details The number of equivalence classes (which is valid after the
2981 /// partition has been constructed) is equal to the number of states in the
2982 /// bisimulation quotient.
2983 state_type num_eq_classes() const
2984 {
2985 return part_st.nr_of_blocks;
2986 }
2987
2988
2989 /// \brief Get the equivalence class of a state
2990 /// \details After running the minimisation algorithm, this function
2991 /// produces the number of the equivalence class of a state. This number
2992 /// is the same as the number of the state in the minimised LTS to which
2993 /// the original state is mapped.
2994 /// \param s state whose equivalence class needs to be found
2995 /// \returns sequence number of the equivalence class of state s
2996 state_type get_eq_class(state_type const s) const
2997 {
2998 return part_st.block(s)->seqnr;
2999 }
3000
3001
3002 /// \brief Adapt the LTS after minimisation
3003 /// \details After the efficient branching bisimulation minimisation, the
3004 /// information about the quotient LTS is only stored in the partition data
3005 /// structure of the partitioner object. This function exports the
3006 /// information back to the LTS by adapting its states and transitions: it
3007 /// updates the number of states and adds those transitions that are
3008 /// mandated by the partition data structure. If desired, it also creates
3009 /// a vector containing an arbritrary (example) original state per
3010 /// equivalence class.
3011 ///
3012 /// The main parameter and return value are implicit with this function: a
3013 /// reference to the LTS was stored in the object by the constructor.
3014 void finalize_minimized_LTS()
3015 {
3016 // The labels have already been stored in
3017 // next_nontrivial_and_label.label by
3018 // refine_partition_until_it_becomes_stable().
3019
3020 // for all blocks
3021 const bisim_dnj::permutation_entry* s_iter(part_st.permutation.data()); assert(s_iter < part_st.permutation.data_end());
3022 do
3023 {
3024 const bisim_dnj::block_t* const B(s_iter->st->bl.ock);
3025 // for all block_bunch-slices of the block
3026 for (const bisim_dnj::block_bunch_slice_t& block_bunch :
3027 B->stable_block_bunch)
3028 { assert(block_bunch.is_stable()); assert(!block_bunch.empty());
3029 const bisim_dnj::pred_entry* const
3030 pred(block_bunch.end[-1].pred); assert(pred->source->bl.ock == B);
3031 assert(nullptr != pred->action_block->succ);
3032 /* add a transition from the source block to the goal block */ assert(pred->action_block->succ->block_bunch->pred == pred);
3033 /* with the indicated label. */ assert(pred->action_block->succ->block_bunch->slice == &block_bunch);
3034 label_type const
3035 label(block_bunch.bunch->next_nontrivial_and_label.label); assert(0 <= label); assert(label < action_label.size());
3036 aut.add_transition(transition(B->seqnr, label,
3037 pred->target->bl.ock->seqnr));
3038 }
3039 s_iter = B->end;
3040 }
3041 while (s_iter < part_st.permutation.data_end());
3042
3043 // Merge the states, by setting the state labels of each state to the
3044 // concatenation of the state labels of its equivalence class.
3045
3046 if (aut.has_state_info()) /* If there are no state labels
3047 this step can be ignored */
3048 {
3049 /* Create a vector for the new labels */
3050 std::remove_reference_t<decltype(aut.state_labels())> new_labels(num_eq_classes());
3051
3052 state_type i(0); assert(i < aut.num_states());
3053 do
3054 {
3055 const state_type new_index(get_eq_class(i));
3056 new_labels[new_index]=new_labels[new_index]+aut.state_label(i);
3057 }
3058 while (++i < aut.num_states());
3059
3060 aut.set_num_states(num_eq_classes(), false); assert(0 == aut.num_state_labels());
3061 new_labels.swap(aut.state_labels());
3062 }
3063 else
3064 {
3065 aut.set_num_states(num_eq_classes(), false);
3066 }
3067
3068 aut.set_initial_state(get_eq_class(aut.initial_state()));
3069 }
3070
3071
3072 /// \brief Check whether two states are in the same equivalence class.
3073 /// \param s first state that needs to be compared.
3074 /// \param t second state that needs to be compared.
3075 /// \returns true iff the two states are in the same equivalence class.
3076 bool in_same_class(state_type const s, state_type const t) const
3077 {
3078 return part_st.block(s) == part_st.block(t);
3079 }
3080 private:
3081
3082 /*--------------------------- main algorithm ----------------------------*/
3083
3084 /// \brief Create a partition satisfying the main invariant
3085 /// \details Before the actual bisimulation minimisation can start, this
3086 /// function needs to be called to create a partition that satisfies the
3087 /// main invariant of the efficient O(m log n) branching bisimulation
3088 /// minimisation.
3089 ///
3090 /// It puts all non-inert transitions into a single bunch, containing one
3091 /// action_block-slice for each action label. It creates a single block
3092 /// (or possibly two, if there are states that never will do any visible
3093 /// action). As a side effect, it deletes all transitions from the LTS
3094 /// that is stored with the partitioner; information about the transitions
3095 /// is kept in data structures that are suitable for the efficient
3096 /// algorithm.
3097 ///
3098 /// For divergence-preserving branching bisimulation, we only need to treat
3099 /// tau-self-loops as non-inert transitions. In other texts, this is
3100 /// sometimes described as temporarily renaming the tau-self-loops to
3101 /// self-loops with a special label. However, as there are no other
3102 /// non-inert tau transitions, we can simply put them in their own
3103 /// action_block-slice, separate from the inert tau transitions. (It would
3104 /// be an error to mix the inert transitions with the self-loops in the
3105 /// same slice.)
3106 void create_initial_partition()
3107 {
3108 mCRL2log(log::verbose) << "An O(m log n) "
3109 << (branching ? (preserve_divergence
3110 ? "divergence-preserving branching "
3111 : "branching ")
3112 : "")
3113 << "bisimulation partitioner created for " << part_st.state_size()
3114 << " states and " << aut.num_transitions() << " transitions.\n";
3115
3116 if (part_st.state_size() > 2 * aut.num_transitions() + 1)
3117 {
3118 mCRL2log(log::warning) << "There are several isolated states "
3119 "without incoming or outgoing transition. It is not "
3120 "guaranteed that branching bisimulation minimisation runs in "
3121 "time O(m log n).\n";
3122 }
3123
3124 sort_transitions(aut.get_transitions(), tgt_lbl_src);
3125 mCRL2log(log::verbose) << "Carried out sorting\n";
3126 // create one block for all states
3127 bisim_dnj::block_t* B(
3128 #ifdef USE_POOL_ALLOCATOR
3129 simple_list<bisim_dnj::block_bunch_slice_t>::get_pool().
3130 template construct<bisim_dnj::block_t>
3131 #else
3132 new bisim_dnj::block_t
3133 #endif
3134 (part_st.permutation.data(),
3135 part_st.permutation.data_end(), part_st.nr_of_blocks++));
3136
3137 // Iterate over the transitions to count how to order them in
3138 // part_trans_t
3139
3140 // counters for the non-inert outgoing and incoming transitions per
3141 // state are provided in part_st.state_info. These counters have been
3142 // initialised to zero in the constructor of part_state_t.
3143 // counters for the non-inert transition per label are stored in
3144 // action_label.
3145 assert(action_label.size() == aut.num_action_labels());
3146 // counter for the total number of inert transitions:
3147 trans_type inert_transitions(0);
3148 for (const transition& t: aut.get_transitions())
3149 {
3150 if (branching&&aut.is_tau(aut.apply_hidden_label_map(t.label()))&& ((
3151 t.from() != t.to()) || (assert(preserve_divergence), false)))
3152 {
3153 // The transition is inert.
3154 ++part_st.state_info[t.from()].succ_inert.count;
3155 ++inert_transitions;
3156
3157 // The source state should become non-bottom:
3158 if (part_st.state_info[t.from()].pos < B->nonbottom_begin)
3159 {
3160 std::swap(*part_st.state_info[t.from()].pos,
3161 *--B->nonbottom_begin);
3162 // we do not yet update the marked_bottom_begin pointer
3163 }
3164 }
3165 else
3166 {
3167 // The transition is non-inert. (It may be a self-loop).
3168 ++part_st.state_info[t.from()].untested_to_U_eqv.count;
3169 ++action_label[aut.apply_hidden_label_map(t.label())].count;
3170 }
3171 ++part_st.state_info[t.to()].pred_inert.count;
3172 }
3173 // Now we update the marked_bottom_begin pointer:
3174 B->marked_bottom_begin = B->nonbottom_begin;
3175
3176 // set the pointers to transition slices in the state info entries
3177
3178 // We set them all to the end of the respective slice here. Then, with
3179 // every transition, the pointer will be reduced by one, so that after
3180 // placing all transitions it will point to the beginning of the slice.
3181
3182 bisim_dnj::pred_entry* next_pred_begin(&part_tr.pred.begin()[1]);
3183 bisim_dnj::succ_entry* next_succ_begin(&part_tr.succ.begin()[1]);
3184 bisim_dnj::state_info_entry* state_iter(part_st.state_info.data()); assert(state_iter < part_st.state_info.data_end());
3185 do
3186 {
3187 state_iter->bl.ed_noninert_end = next_pred_begin;
3188 next_pred_begin += state_iter->pred_inert.count;
3189 state_iter->pred_inert.convert_to_iterator(next_pred_begin);
3190
3191 // create slice descriptors in part_tr.succ for each state with
3192 /* outgoing transitions. */ assert(nullptr != next_succ_begin);
3193 state_iter->untested_to_U_eqv.convert_to_iterator(
3194 next_succ_begin + state_iter->untested_to_U_eqv.count);
3195 if (next_succ_begin < state_iter->untested_to_U_eqv.begin)
3196 { assert(nullptr != state_iter->untested_to_U_eqv.begin);
3197 next_succ_begin->begin_or_before_end =
3198 state_iter->untested_to_U_eqv.begin - 1;
3199 for (bisim_dnj::succ_entry* const
3200 out_slice_begin(next_succ_begin);
3201 ++next_succ_begin < state_iter->untested_to_U_eqv.begin; )
3202 {
3203 next_succ_begin->begin_or_before_end = out_slice_begin; // mCRL2complexity(next_succ_begin->block_bunch->pred, ...) -- subsumed in the
3204 } // call below
3205
3206 B->mark(state_iter->pos);
3207 }
3208 state_iter->succ_inert.convert_to_iterator(next_succ_begin +
3209 state_iter->succ_inert.count);
3210 #ifndef NDEBUG
3211 while (next_succ_begin < state_iter->succ_inert.begin)
3212 { assert(nullptr == next_succ_begin->begin_or_before_end);
3213 ++next_succ_begin;
3214 }
3215 #endif
3216 next_succ_begin = state_iter->succ_inert.begin; // mCRL2complexity(*state_iter, ...) -- subsumed in the call at the end
3217 }
3218 while (++state_iter < part_st.state_info.data_end());
3219
3220 // Line 2.4: Pi_t := { { all non-inert transitions } }
3221 part_tr.action_block_inert_begin =
3222 part_tr.action_block.data_end() - inert_transitions; assert(part_tr.action_block.data() <= part_tr.action_block_inert_begin);
3223 part_tr.block_bunch_inert_begin =
3224 part_tr.block_bunch.data_end() - inert_transitions; assert(part_tr.block_bunch.data() < part_tr.block_bunch_inert_begin);
3225 bisim_dnj::bunch_t* bunch(nullptr);
3226
3227 if (1 + part_tr.block_bunch.data() < part_tr.block_bunch_inert_begin)
3228 { assert(part_tr.action_block.data() < part_tr.action_block_inert_begin);
3229 // create a single bunch containing all non-inert transitions
3230 bunch =
3231 #ifdef USE_POOL_ALLOCATOR
3232 simple_list<bisim_dnj::block_bunch_slice_t>::get_pool().
3233 template construct<bisim_dnj::bunch_t>
3234 #else
3235 new bisim_dnj::bunch_t
3236 #endif
3237 (part_tr.action_block.data(),
3238 part_tr.action_block_inert_begin); assert(nullptr != bunch); assert(part_tr.splitter_list.empty());
3239 ++part_tr.nr_of_bunches; assert(1 == part_tr.nr_of_bunches);
3240
3241 // create a single block_bunch entry for all non-inert transitions
3242 part_tr.splitter_list.emplace_front(
3243 part_tr.block_bunch_inert_begin, bunch, false); assert(!part_tr.splitter_list.empty());
3244 ++part_tr.nr_of_block_bunch_slices; assert(1 == part_tr.nr_of_block_bunch_slices);
3245 }
3246
3247 // create slice descriptors in part_tr.action_block for each label
3248
3249 // The action_block array shall have the tau transitions at the end:
3250 // first the non-inert tau transitions (during initialisation, that are
3251 // only the tau self-loops), then the tau transitions that have become
3252 // non-inert and finally the inert transitions.
3253 // Transitions with other labels are placed from beginning to end.
3254 // Every such transition block except the last one ends with a dummy
3255 /* entry. If there are transition labels without transitions, */ assert(part_tr.action_block.size() ==
3256 /* multiple dummy entries will be placed side-by-side. */ aut.num_transitions() + action_label.size() - 1);
3257 bisim_dnj::action_block_entry*
3258 next_action_label_begin(part_tr.action_block.data());
3259 trans_type const n_square(part_st.state_size() * part_st.state_size());
3260 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
3261 trans_type max_transitions = n_square;
3262 #endif
3263 label_type label(action_label.size()); assert(0 < label);
3264 do
3265 {
3266 --label;
3267 if (0 < action_label[label].count)
3268 { assert(nullptr != bunch);
3269 if (++part_tr.nr_of_action_block_slices == 2)
3270 {
3271 // This is the second action_block-slice, so the bunch is
3272 // not yet marked as nontrivial but it should be.
3273 part_tr.make_nontrivial(bunch);
3274 }
3275 if (n_square < action_label[label].count)
3276 {
3277 mCRL2log(log::warning) << "There are "
3278 << action_label[label].count << ' '
3279 << pp(aut.action_label(label)) << "-transitions. "
3280 "This is more than n^2 (= " << n_square << "). It is "
3281 "not guaranteed that branching bisimulation "
3282 "minimisation runs in time O(m log n).\n";
3283 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
3284 if (max_transitions < action_label[label].count)
3285 { max_transitions = action_label[label].count; }
3286 #endif
3287 }
3288 // initialise begin_or_before_end pointers for this
3289 // action_block-slice
3290 action_label[label].convert_to_iterator(
3291 next_action_label_begin + action_label[label].count);
3292 next_action_label_begin->begin_or_before_end =
3293 action_label[label].begin - 1; assert(nullptr != next_action_label_begin->begin_or_before_end);
3294 bisim_dnj::action_block_entry* const
3295 action_block_slice_begin(next_action_label_begin); assert(nullptr != action_block_slice_begin);
3296 while (++next_action_label_begin < action_label[label].begin)
3297 {
3298 next_action_label_begin->begin_or_before_end =
3299 action_block_slice_begin; // mCRL2complexity(next_action_label_begin->succ->block_bunch->pred, ...) --
3300 } // subsumed in the call at the end
3301 }
3302 else
3303 {
3304 action_label[label].convert_to_iterator(
3305 next_action_label_begin);
3306 if (0 != label && aut.num_transitions() < action_label.size())
3307 {
3308 mCRL2log(log::warning) << "Action label "
3309 << pp(aut.action_label(label)) << " has no "
3310 "transitions, and the number of action labels exceeds "
3311 "the number of transitions. It is not guaranteed that "
3312 "branching bisimulation minimisation runs in time "
3313 "O(m log n).\n";
3314 }
3315 }
3316 }
3317 while (0 < label && (/* insert a dummy entry */ assert(next_action_label_begin < part_tr.action_block_inert_begin),
3318 next_action_label_begin->succ = nullptr,
3319 next_action_label_begin->begin_or_before_end = nullptr,
3320 ++next_action_label_begin, true)); assert(next_action_label_begin == part_tr.action_block_inert_begin);
3321 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
3322 /* distribute the transitions over the data structures */ check_complexity::init(2 * max_transitions);
3323 #endif
3324 bisim_dnj::block_bunch_entry*
3325 next_block_bunch(1 + part_tr.block_bunch.data());
3326 for (const transition& t: aut.get_transitions())
3327 {
3328 bisim_dnj::state_info_entry* const
3329 source(part_st.state_info.data() + t.from());
3330 bisim_dnj::state_info_entry* const
3331 target(part_st.state_info.data() + t.to());
3332 bisim_dnj::succ_entry* succ_pos;
3333 bisim_dnj::block_bunch_entry* block_bunch_pos;
3334 bisim_dnj::pred_entry* pred_pos;
3335 bisim_dnj::action_block_entry* action_block_pos;
3336
3337 if (branching&&aut.is_tau(aut.apply_hidden_label_map(t.label()))&& ((
3338 t.from() != t.to()) || (assert(preserve_divergence), false)))
3339 {
3340 // It is a (normal) inert transition: place near the end of the
3341 // respective pred/succ slices, just before the other inert
3342 // transitions.
3343 succ_pos = --source->succ_inert.begin; assert(nullptr == succ_pos->begin_or_before_end);
3344 block_bunch_pos = part_tr.block_bunch.data_end() -
3345 inert_transitions; assert(block_bunch_pos >= part_tr.block_bunch_inert_begin);
3346 pred_pos = --target->pred_inert.begin; assert(block_bunch_pos->slice.is_null());
3347 action_block_pos = part_tr.action_block.data_end() -
3348 inert_transitions; assert(action_block_pos >= part_tr.action_block_inert_begin);
3349 action_block_pos->begin_or_before_end = nullptr;
3350 --inert_transitions;
3351 }
3352 else
3353 {
3354 // It is a non-inert transition (possibly a self-loop): place
3355 // at the end of the respective succ slice and at the beginning
3356 // of the respective pred slice.
3357 succ_pos =
3358 --part_st.state_info[t.from()].untested_to_U_eqv.begin; assert(nullptr != succ_pos->begin_or_before_end);
3359 assert(nullptr != succ_pos->begin_or_before_end->begin_or_before_end);
3360 assert(succ_pos->begin_or_before_end <= succ_pos ||
3361 succ_pos->begin_or_before_end->begin_or_before_end == succ_pos);
3362 block_bunch_pos = next_block_bunch++; assert(block_bunch_pos < part_tr.block_bunch_inert_begin);
3363 pred_pos = target->bl.ed_noninert_end++;
3364 action_block_pos =
3365 --action_label[aut.apply_hidden_label_map(t.label())].begin; assert(nullptr != action_block_pos->begin_or_before_end);
3366 assert(nullptr != action_block_pos->begin_or_before_end->begin_or_before_end);
3367 assert(action_block_pos->begin_or_before_end <= action_block_pos ||
3368 action_block_pos->begin_or_before_end->
3369 begin_or_before_end == action_block_pos);
3370 assert(!part_tr.splitter_list.empty());
3371 block_bunch_pos->slice = part_tr.splitter_list.begin(); assert(action_block_pos < part_tr.action_block_inert_begin);
3372 } assert(target->bl.ed_noninert_end <= target->pred_inert.begin);
3373 succ_pos->block_bunch = block_bunch_pos;
3374 block_bunch_pos->pred = pred_pos;
3375 pred_pos->action_block = action_block_pos;
3376 pred_pos->source = source;
3377 pred_pos->target = target; assert(nullptr != succ_pos);
3378 action_block_pos->succ = succ_pos; // mCRL2complexity(pred_pos, ...) -- subsumed in the call at the end
3379 } assert(0 == inert_transitions);
3380 /* delete transitions already -- they are no longer needed. We will */ assert(next_block_bunch == part_tr.block_bunch_inert_begin);
3381 // add new transitions at the end of minimisation.
3382 aut.clear_transitions();
3383
3384 state_iter = part_st.state_info.data(); assert(state_iter < part_st.state_info.data_end());
3385 do
3386 {
3387 state_iter->bl.ock = B;
3388 }
3389 while (++state_iter < part_st.state_info.data_end());
3390
3391 if (nullptr != bunch)
3392 {
3393 while (nullptr == bunch->begin->succ)
3394 { assert(nullptr == bunch->begin->begin_or_before_end);
3395 ++bunch->begin; assert(bunch->begin < bunch->end);
3396 } assert(nullptr != bunch->begin->begin_or_before_end);
3397 while (nullptr == bunch->end[-1].succ)
3398 { assert(nullptr == bunch->end[-1].begin_or_before_end);
3399 --bunch->end; assert(bunch->begin < bunch->end);
3400 } assert(nullptr != bunch->end[-1].begin_or_before_end);
3401
3402 /* Line 2.2: B_vis := { s in S | there exists a visible */ mCRL2complexity(B, add_work(check_complexity::
3403 /* transition that is reachable */ create_initial_partition, 1U), *this);
3404 // from s }
3405 // B_invis := S \ B_vis
3406 // Line 2.3: Pi_s := { B_vis, B_invis } \ { emptyset }
3407 // At this point, all states with a visible transition are
3408 // marked.
3409 if (0 < B->marked_size())
3410 { ONLY_IF_DEBUG( part_st.print_part(*this);
3411 part_tr.print_trans(*this); )
3412 bisim_dnj::block_bunch_slice_iter_t slice =
3413 part_tr.splitter_list.begin();
3414 if (1 < B->size())
3415 {
3416 B = split(B,
3417 /* splitter block_bunch */ slice,
3418 extend_from_marked_states_add_new_noninert_to_splitter);
3419 // We can ignore possible new non-inert transitions, as
3420 // every R-bottom state already has a transition in bunch.
3421 B->marked_nonbottom_begin = B->end;
3422 }
3423 else
3424 { assert(B->nonbottom_begin == B->end);
3425 /* A block with 1 state will not be split. However, we */ assert(B->marked_nonbottom_begin == B->end);
3426 // still have to make the splitter stable.
3427 B->stable_block_bunch.splice(B->stable_block_bunch.end(),
3428 part_tr.splitter_list, slice);
3429 slice->make_stable();
3430 } assert(!B->stable_block_bunch.empty()); assert(part_tr.splitter_list.empty());
3431 assert(B->stable_block_bunch.front().end <= part_tr.block_bunch_inert_begin);
3432 assert(1 + part_tr.block_bunch.data() < B->stable_block_bunch.front().end);
3433 B->marked_bottom_begin = B->nonbottom_begin; assert(!B->stable_block_bunch.front().empty());
3434 }
3435 }
3436 else
3437 {
3438 assert(0 == B->marked_size());
3439 }
3440 }
3441 #ifndef NDEBUG
3442 /// \brief assert that the data structure is consistent and stable
3443 /// \details The data structure is tested against a large number of
3444 /// assertions to ensure that everything is consistent, e. g. pointers that
3445 /// should point to successors of state s actually point to a transition
3446 /// that starts in s.
3447 ///
3448 /// Additionally, it is asserted that the partition is stable. i. e. every
3449 /// bottom state in every block can reach exactly every bunch in the list
3450 /// of bunches that should be reachable from it, and every nonbottom state
3451 /// can reach a subset of them.
3452 void assert_stability() const
3453 {
3454 part_st.assert_consistency(*this);
3455
3456 assert(part_tr.succ.size() == part_tr.block_bunch.size() + 1);
3457 assert(part_tr.pred.size() == part_tr.block_bunch.size() + 1);
3458 assert(part_tr.action_block.size() ==
3459 part_tr.block_bunch.size() + action_label.size() - 2);
3460 if (part_tr.block_bunch.empty())
3461 {
3462 return;
3463 }
3464
3465 assert(part_tr.splitter_list.empty());
3466 /* for (const block_bunch_slice_t& block_bunch : part_tr.splitter_list)
3467 {
3468 assert(!block_bunch.is_stable());
3469 } */
3470
3471 trans_type true_nr_of_block_bunch_slices(0);
3472 // for all blocks
3473 const bisim_dnj::permutation_entry*
3474 perm_iter(part_st.permutation.data());
3475 assert(perm_iter < part_st.permutation.data_end());
3476 do
3477 {
3478 const bisim_dnj::block_t* const block(perm_iter->st->bl.ock);
3479 unsigned const max_block(check_complexity::log_n -
3480 check_complexity::ilog2(block->size()));
3481 // iterators have no predefined hash, so we store pointers:
3482 std::unordered_set<const bisim_dnj::block_bunch_slice_t*>
3483 block_bunch_check_set;
3484 #ifndef USE_SIMPLE_LIST
3485 block_bunch_check_set.reserve(
3486 block->stable_block_bunch.size());
3487 #endif
3488
3489 // for all stable block_bunch-slices of the block
3490 for (const bisim_dnj::block_bunch_slice_t& block_bunch :
3491 block->stable_block_bunch)
3492 {
3493 assert(block_bunch.source_block() == block);
3494 assert(block_bunch.is_stable());
3495 block_bunch_check_set.insert(&block_bunch);
3496 mCRL2complexity(&block_bunch, no_temporary_work(
3497 block_bunch.bunch->max_work_counter(*this)), *this);
3498 ++true_nr_of_block_bunch_slices;
3499 }
3500
3501 // for all states in the block
3502 do
3503 {
3504 trans_type block_bunch_count(0);
3505 const bisim_dnj::state_info_entry* const state(perm_iter->st);
3506 assert(state!=part_tr.succ.front().block_bunch->pred->source);
3507 // for all out-slices of the state
3508 for (const bisim_dnj::succ_entry*
3509 out_slice_end(state->succ_inert.begin);
3510 state == out_slice_end[-1].block_bunch->pred->source; )
3511 { assert(!out_slice_end[-1].block_bunch->slice.is_null());
3512 bisim_dnj::block_bunch_slice_const_iter_t const
3513 block_bunch_slice(out_slice_end[-1].block_bunch->slice);
3514 const bisim_dnj::bunch_t* const bunch(
3515 block_bunch_slice->bunch);
3516 assert(block == block_bunch_slice->source_block());
3517 if (block_bunch_slice->is_stable())
3518 {
3519 assert(1 == block_bunch_check_set.count(
3520 &*block_bunch_slice));
3521 ++block_bunch_count;
3522 }
3523 else
3524 {
3525 assert(
3526 0); // i. e.
3527 // all
3528 // block_bunch-slices
3529 // should
3530 }
3531 // be stable
3532 const bisim_dnj::succ_entry* const out_slice_begin(
3533 out_slice_end[-1].begin_or_before_end);
3534 assert(nullptr != out_slice_begin);
3535 assert(out_slice_begin < out_slice_end);
3536 assert(nullptr != out_slice_begin->begin_or_before_end);
3537 assert(out_slice_begin->begin_or_before_end + 1 ==
3538 out_slice_end);
3539
3540 // for all transitions in the out-slice
3541 do
3542 {
3543 --out_slice_end;
3544 assert(bunch->begin <=
3545 out_slice_end->block_bunch->pred->action_block);
3546 assert(out_slice_end->block_bunch->pred->
3547 action_block < bunch->end);
3548 assert(out_slice_end->block_bunch->slice ==
3549 block_bunch_slice);
3550 assert(nullptr != out_slice_end->begin_or_before_end);
3551 if (out_slice_end->block_bunch + 1 !=
3552 block_bunch_slice->end)
3553 {
3554 assert(out_slice_end->block_bunch + 1 <
3555 block_bunch_slice->end);
3556 assert(out_slice_end->block_bunch[1].slice ==
3557 block_bunch_slice);
3558 }
3559 mCRL2complexity(out_slice_end->block_bunch->pred,
3560 no_temporary_work(max_block,
3561 check_complexity::log_n -
3562 check_complexity::ilog2(out_slice_end->
3563 block_bunch->pred->target->bl.ock->size()),
3564 perm_iter < block->nonbottom_begin),*this);
3565 }
3566 while (out_slice_begin < out_slice_end &&
3567 (assert(out_slice_begin ==
3568 out_slice_end->begin_or_before_end), true));
3569 }
3570 if (perm_iter < block->nonbottom_begin)
3571 {
3572 assert(block_bunch_check_set.size() == block_bunch_count);
3573 }
3574 }
3575 while (++perm_iter < block->end);
3576 }
3577 while (perm_iter < part_st.permutation.data_end());
3578 assert(part_tr.nr_of_block_bunch_slices ==
3579 true_nr_of_block_bunch_slices);
3580 assert(part_tr.action_block.data()<=part_tr.action_block_inert_begin);
3581 assert(part_tr.block_bunch.data() < part_tr.block_bunch_inert_begin);
3582 if (branching)
3583 { assert(part_tr.action_block_inert_begin <=
3584 part_tr.action_block.data_end());
3585 assert(part_tr.block_bunch_inert_begin <=
3586 part_tr.block_bunch.data_end());
3587 assert(part_tr.block_bunch.data_end() -
3588 part_tr.block_bunch_inert_begin ==
3589 part_tr.action_block.data_end()-part_tr.action_block_inert_begin);
3590
3591 // for all inert transitions
3592 for (const bisim_dnj::action_block_entry* action_block(
3593 part_tr.action_block_inert_begin);
3594 action_block < part_tr.action_block.data_end();
3595 ++action_block)
3596 { assert(nullptr == action_block->begin_or_before_end);
3597 const bisim_dnj::succ_entry* const
3598 succ_iter(action_block->succ);
3599 assert(nullptr != succ_iter);
3600 assert(succ_iter->block_bunch->slice.is_null());
3601 const bisim_dnj::pred_entry* const
3602 pred_iter(succ_iter->block_bunch->pred);
3603 assert(action_block == pred_iter->action_block);
3604 assert(part_tr.block_bunch_inert_begin <=
3605 succ_iter->block_bunch);
3606 assert(pred_iter->source != pred_iter->target);
3607 assert(pred_iter->source->bl.ock == pred_iter->target->bl.ock);
3608 assert(pred_iter->source->succ_inert.begin <= succ_iter);
3609 assert(pred_iter->source->succ_inert.begin == succ_iter ||
3610 succ_iter[-1].block_bunch->pred->source==pred_iter->source);
3611 assert(pred_iter->target->pred_inert.begin <= pred_iter);
3612 assert(pred_iter->target->pred_inert.begin == pred_iter ||
3613 pred_iter[-1].target == pred_iter->target);
3614 unsigned const max_block(check_complexity::log_n -
3615 check_complexity::ilog2(pred_iter->target->bl.ock->size()));
3616 mCRL2complexity(pred_iter, no_temporary_work(max_block,
3617 max_block, false), *this);
3618 }
3619 }
3620 else
3621 {
3622 assert(!preserve_divergence);
3623 assert(part_tr.action_block_inert_begin ==
3624 part_tr.action_block.data_end());
3625 assert(part_tr.block_bunch_inert_begin ==
3626 part_tr.block_bunch.data_end());
3627 }
3628 const bisim_dnj::action_block_entry*
3629 action_slice_end(part_tr.action_block_inert_begin);
3630 trans_type true_nr_of_bunches(0);
3631 trans_type true_nr_of_nontrivial_bunches(0);
3632 trans_type true_nr_of_action_block_slices(0);
3633 // for all action labels and bunches
3634 label_type label(0);
3635 assert(label < action_label.size());
3636 const bisim_dnj::bunch_t* previous_bunch(nullptr);
3637 do
3638 {
3639 assert(part_tr.action_block.data() <= action_label[label].begin);
3640 assert(action_label[label].begin <= action_slice_end);
3641 assert(action_slice_end <= part_tr.action_block_inert_begin);
3642 // for all action_block slices
3643 for (const bisim_dnj::action_block_entry*
3644 action_block_slice_end(action_slice_end);
3645 action_label[label].begin < action_block_slice_end; )
3646 {
3647 const bisim_dnj::action_block_entry* const
3648 action_block_slice_begin(
3649 action_block_slice_end[-1].begin_or_before_end);
3650 assert(nullptr != action_block_slice_begin);
3651 assert(action_block_slice_begin < action_block_slice_end);
3652 assert(action_block_slice_end <= action_slice_end);
3653 assert(nullptr != action_block_slice_begin->succ);
3654 const bisim_dnj::block_t* const
3655 target_block(action_block_slice_begin->
3656 succ->block_bunch->pred->target->bl.ock);
3657 const bisim_dnj::bunch_t* const
3658 bunch(action_block_slice_begin->succ->bunch());
3659 if (previous_bunch != bunch)
3660 {
3661 assert(nullptr == previous_bunch);
3662 previous_bunch = bunch;
3663 assert(bunch->end == action_block_slice_end);
3664 if (bunch->begin == action_block_slice_begin)
3665 {
3666 // Perhaps this does not always hold; sometimes, an
3667 // action_block slice disappears but the bunch cannot
3668 // be made trivial.
3669 assert(bunch->is_trivial());
3670 }
3671 else
3672 {
3673 assert(!bunch->is_trivial());
3674 ++true_nr_of_nontrivial_bunches;
3675 }
3676 mCRL2complexity(bunch, no_temporary_work(
3677 bunch->max_work_counter(*this)), *this);
3678 ++true_nr_of_bunches;
3679 }
3680 if(bunch->begin == action_block_slice_begin)
3681 {
3682 previous_bunch = nullptr;
3683 }
3684 else
3685 {
3686 assert(
3687 bunch->begin
3688 < action_block_slice_begin);
3689 }
3690
3691 assert(action_block_slice_begin->begin_or_before_end + 1 ==
3692 action_block_slice_end);
3693 // for all transitions in the action_block slice
3694 const bisim_dnj::action_block_entry*
3695 action_block(action_block_slice_end);
3696 do
3697 {
3698 --action_block;
3699 const bisim_dnj::succ_entry* const
3700 succ_iter(action_block->succ);
3701 assert(nullptr != succ_iter);
3702 const bisim_dnj::pred_entry* const
3703 pred_iter(succ_iter->block_bunch->pred);
3704 assert(action_block == pred_iter->action_block);
3705 assert(succ_iter->block_bunch <
3706 part_tr.block_bunch_inert_begin);
3707 assert(!branching || !aut.is_tau(label) ||
3708 pred_iter->source->bl.ock!=pred_iter->target->bl.ock ||
3709 (preserve_divergence &&
3710 pred_iter->source == pred_iter->target));
3711 assert(succ_iter < pred_iter->source->succ_inert.begin);
3712 assert(succ_iter+1==pred_iter->source->succ_inert.begin ||
3713 succ_iter[1].block_bunch->pred->source ==
3714 pred_iter->source);
3715 assert(pred_iter < pred_iter->target->pred_inert.begin);
3716 assert(pred_iter+1==pred_iter->target->pred_inert.begin ||
3717 pred_iter[1].target == pred_iter->target);
3718 assert(target_block == pred_iter->target->bl.ock);
3719 assert(bunch == succ_iter->bunch());
3720 }
3721 while (action_block_slice_begin < action_block &&
3722 (// some properties only need to be checked for states that
3723 // are not the first one:
3724 assert(action_block->begin_or_before_end ==
3725 action_block_slice_begin), true));
3726 action_block_slice_end = action_block_slice_begin;
3727 ++true_nr_of_action_block_slices;
3728 }
3729 if (action_slice_end < part_tr.action_block_inert_begin)
3730 {
3731 // there is a dummy transition between action labels
3732 assert(nullptr == action_slice_end->succ);
3733 assert(nullptr == action_slice_end->begin_or_before_end);
3734 }
3735 }
3736 while (++label < action_label.size() &&
3737 (action_slice_end = action_label[label - 1].begin - 1, true));
3738 assert(nullptr == previous_bunch);
3739 assert(part_tr.nr_of_bunches == true_nr_of_bunches);
3740 assert(part_tr.nr_of_nontrivial_bunches ==
3741 true_nr_of_nontrivial_bunches);
3742 assert(part_tr.nr_of_action_block_slices ==
3743 true_nr_of_action_block_slices);
3744 }
3745 #endif
3746 /// \brief Run (branching) bisimulation minimisation in time O(m log n)
3747 /// \details This function assumes that the partitioner object stores a LTS
3748 /// with a partition satisfying the invariant:
3749 ///
3750 /// > If a state contains a transition in a bunch, then every bottom state
3751 /// > in the same block contains a transition in that bunch.
3752 ///
3753 /// The function runs the efficient O(m log n) algorithm for branching
3754 /// bisimulation minimisation on the LTS that has been stored in the
3755 /// partitioner: As long as there are nontrivial bunches, it selects one,
3756 /// subdivides it into two bunches and then stabilises the partition for
3757 /// these bunches. As a result, the partition stored in the partitioner
3758 /// will become stable.
3759 ///
3760 /// Parameters and return value are implicit with this function: the LTS,
3761 /// the partition and the flags of the bisimulation algorithm are all
3762 /// stored in the partitioner object.
3763 void refine_partition_until_it_becomes_stable()
3764 {
3765 // Line 2.5: for all non-trivial bunches bunch_T in Pi_t do
3766 std::clock_t next_print_time = std::clock();
3767 const std::clock_t rounded_start_time=next_print_time-CLOCKS_PER_SEC/2;
3768 // const double log_initial_nr_of_action_block_slices =
3769 // 100 / std::log(part_tr.nr_of_action_block_slices);
3770 for (;;)
3771 { // mCRL2complexity(...) -- this loop will be ascribed to (the transitions in)
3772 // the new bunch below.
3773 /*------------------ find a non-trivial bunch -------------------*/ ONLY_IF_DEBUG( part_st.print_part(*this); part_tr.print_trans(*this);
3774 assert_stability(); )
3775 /* Line 2.6: Select some a in Act and B' in Pi_s such that */ assert(part_tr.nr_of_bunches + part_tr.nr_of_nontrivial_bunches <=
3776 /* |bunch_T_a_Bprime| <= 1/2 |bunch_T| */ part_tr.nr_of_action_block_slices);
3777 bisim_dnj::bunch_t* const bunch_T(part_tr.get_some_nontrivial());
3778 if (mCRL2logEnabled(log::verbose))
3779 {
3780 if (std::clock_t now = std::clock(); next_print_time <= now ||
3781 nullptr == bunch_T)
3782 {
3783
3784 /* - - - - -print progress information- - - - - */
3785
3786 // The formula below should ensure that `next_print_time`
3787 // increases by a whole number of minutes, so that the
3788 // progress information is printed every minute (or, if
3789 // one iteration takes more than one minute, after a whole
3790 // number of minutes).
3791 next_print_time+=((now-next_print_time)/(60*CLOCKS_PER_SEC)
3792 + 1) * (60*CLOCKS_PER_SEC);
3793 now = (now - rounded_start_time) / CLOCKS_PER_SEC;
3794 if (0 != now)
3795 {
3796 if (60 <= now)
3797 {
3798 if (3600 <= now)
3799 {
3800 mCRL2log(log::verbose)
3801 << now / 3600 << " h ";
3802 now %= 3600;
3803 }
3804 mCRL2log(log::verbose)
3805 << now / 60 << " min ";
3806 now %= 60;
3807 }
3808 mCRL2log(log::verbose) << now
3809 << " sec passed since starting the main loop.\n";
3810 }
3811 #define PRINT_SG_PL(counter, sg_string, pl_string)
3812 (counter)
3813 << (1 == (counter) ? (sg_string) : (pl_string))
3814 mCRL2log(log::verbose)
3815 << (nullptr == bunch_T ? "The reduced LTS contains "
3816 : "The reduced LTS contains at least ")
3817 << PRINT_SG_PL(part_st.nr_of_blocks,
3818 " state and ", " states and ")
3819 << PRINT_SG_PL(part_tr.nr_of_block_bunch_slices,
3820 " transition.", " transitions.");
3821 if (1 < part_tr.nr_of_action_block_slices)
3822 {
3823 #define PRINT_INT_PERCENTAGE(num,denom)
3824 (((num) * 200 + (denom)) / (denom) / 2)
3825 mCRL2log(log::verbose) << " Estimated "
3826 << PRINT_INT_PERCENTAGE(part_tr.nr_of_bunches - 1,
3827 part_tr.nr_of_action_block_slices - 1)
3828 << "% done.";
3829 #undef PRINT_INT_PERCENTAGE
3830 }
3831 mCRL2log(log::verbose)
3832 // << " Logarithmic estimate: "
3833 // << (int)(100.5+std::log((double) part_tr.nr_of_bunches/
3834 // part_tr.nr_of_action_block_slices)
3835 // *log_initial_nr_of_action_block_slices)
3836 // << "% done."
3837 << "\nThe current partition contains ";
3838 if (branching)
3839 {
3840 mCRL2log(log::verbose)
3841 << PRINT_SG_PL(part_tr.nr_of_new_bottom_states,
3842 " new bottom state, ", " new bottom states, ");
3843 }
3844 else
3845 {
3846 assert(0 == part_tr.nr_of_new_bottom_states);
3847 }
3848 mCRL2log(log::verbose)
3849 << PRINT_SG_PL(part_tr.nr_of_bunches,
3850 " bunch (of which ", " bunches (of which ")
3851 << PRINT_SG_PL(part_tr.nr_of_nontrivial_bunches,
3852 " is nontrivial), and ", " are nontrivial), and ")
3853 << PRINT_SG_PL(part_tr.nr_of_action_block_slices,
3854 " action-block-slice.\n", " action-block-slices.\n");
3855 #undef PRINT_SG_PL
3856 }
3857 }
3858 if (nullptr == bunch_T) { break; } ONLY_IF_DEBUG( mCRL2log(log::debug) << "Refining "
3859 /* Line 2.7: Pi_t := Pi_t \ { bunch_T } union */ << bunch_T->debug_id(*this) << '\n'; )
3860 /* { bunch_T_a_Bprime, bunch_T \ bunch_T_a_Bprime } */ assert(part_tr.nr_of_bunches < part_tr.nr_of_action_block_slices);
3861 bisim_dnj::bunch_t* const bunch_T_a_Bprime(
3862 bunch_T->split_off_small_action_block_slice(part_tr));
3863 ONLY_IF_DEBUG( mCRL2log(log::debug) << "Splitting off "
3864 /*------------ find predecessors of bunch_T_a_Bprime ------------*/ << bunch_T_a_Bprime->debug_id(*this) << '\n'; )
3865 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
3866 /* Line 2.8: for all B in splittableBlocks(bunch_T_a_Bprime) do */ unsigned const max_splitter_counter(
3867 /* we actually run through the transitions in T--a-->B' */ bunch_T_a_Bprime->max_work_counter(*this));
3868 #endif
3869 bisim_dnj::action_block_entry* splitter_iter(
3870 bunch_T_a_Bprime->begin); assert(splitter_iter < bunch_T_a_Bprime->end);
3871 do
3872 { assert(nullptr != splitter_iter->succ);
3873 bisim_dnj::state_info_entry* const
3874 source(splitter_iter->succ->block_bunch->pred->source); assert(splitter_iter->succ->block_bunch->pred->action_block == splitter_iter);
3875 // Line 2.11: Mark all transitions in bunch_T_a_Bprime
3876 // actually we mark the source state (i.e. register it's in
3877 // R)
3878 bool const first_transition_of_state(
3879 source->bl.ock->mark(source->pos));
3880 // Line 2.9: Add bunch_T_a_Bprime as primary to the splitter
3881 // list
3882 // Line 2.10: Add T_B--> \ bunch_T_a_Bprime as secondary to the
3883 // splitter list
3884 part_tr.first_move_transition_to_new_bunch(splitter_iter,
3885 bunch_T_a_Bprime, first_transition_of_state); // mCRL2complexity(splitter_iter->succ->block_bunch->pred, ...) -- subsumed
3886 // Line 2.13: end for // in the call below
3887 }
3888 while (++splitter_iter < bunch_T_a_Bprime->end);
3889
3890 // We cannot join the loop above with the loop below!
3891
3892 // Line 2.8: for all B in splittableBlocks(T--a-->B') do
3893 splitter_iter = bunch_T_a_Bprime->begin; assert(splitter_iter < bunch_T_a_Bprime->end);
3894 do
3895 {
3896 // Line 2.12: For every state with both marked outgoing
3897 // transitions and outgoing transitions in
3898 // T_B--> \ bunch_T_a_Bprime, mark one such
3899 // transition
3900 part_tr.second_move_transition_to_new_bunch(splitter_iter, ONLY_IF_DEBUG( *this, bunch_T_a_Bprime, )
3901 bunch_T); // mCRL2complexity(splitter_iter->succ->block_bunch->pred, ...) -- subsumed
3902 // Line 2.13: end for // in the call below
3903 }
3904 while (++splitter_iter < bunch_T_a_Bprime->end); mCRL2complexity(bunch_T_a_Bprime,
3905 add_work(check_complexity::refine_partition_until_stable_find_pred,
3906 /*----------------- stabilise the partition again ---------------*/ max_splitter_counter), *this);
3907 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
3908 /* Line 2.14: for all T'_B--> in the splitter list (in order) do */ bisim_dnj::block_bunch_slice_iter_or_null_t bbslice_T_a_Bprime_B(nullptr);
3909 #endif
3910 while (!part_tr.splitter_list.empty())
3911 {
3912 bisim_dnj::block_bunch_slice_iter_t splitter_Tprime_B( // We have to call mCRL2complexity here because `splitter_Tprime_B` may be
3913 part_tr.splitter_list.begin()); // split up later.
3914 bisim_dnj::block_t* block_B(splitter_Tprime_B->source_block()); assert(!splitter_Tprime_B->is_stable());
3915 bool const is_primary_splitter = 0 < block_B->marked_size(); assert(!splitter_Tprime_B->empty());
3916 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
3917 bool add_stabilize_to_bottom_transns_succeeded = true;
3918 if (is_primary_splitter)
3919 {
3920 assert(bbslice_T_a_Bprime_B.is_null());
3921 // assign work to this splitter bunch
3922 mCRL2complexity(splitter_Tprime_B, add_work(
3923 check_complexity::refine_partition_until_stable_stabilize,
3924 max_splitter_counter), *this);
3925 }
3926 else if (!bbslice_T_a_Bprime_B.is_null())
3927 {
3928 // assign work to this the corresponding block_bunch-slice of
3929 // bunch_T_a_Bprime
3930 mCRL2complexity(bbslice_T_a_Bprime_B,
3931 add_work(check_complexity::
3932 refine_partition_until_stable_stabilize_for_large_splitter,
3933 max_splitter_counter), *this);
3934 }
3935 else
3936 {
3937 // This must be a refinement to stabilize for new bottom states.
3938 // assign work to the new bottom states in this block_bunch-slice
3939 add_stabilize_to_bottom_transns_succeeded = splitter_Tprime_B->
3940 add_work_to_bottom_transns(check_complexity::
3941 refine_partition_until_stable_stabilize_new_noninert_a_priori,
3942 1U, *this);
3943 }
3944 #endif
3945 if (1 < block_B->size())
3946 {
3947 bisim_dnj::permutation_entry* const
3948 block_B_begin(block_B->begin); assert(block_B_begin->st->pos == block_B_begin);
3949 // Line 2.15: (R, U) := split(B, T'_B-->)
3950 // Line 2.16: Remove T'_B--> from the splitter list
3951 // Line 2.17: Pi_s := Pi_s \ { B } union { R, U } \ { {} }
3952 bisim_dnj::block_t*block_R=split(block_B,splitter_Tprime_B,
3953 is_primary_splitter ? extend_from_marked_states
3954 : extend_from_splitter);
3955 if (block_B_begin < block_R->begin)
3956 {
3957 // The refinement was non-trivial.
3958
3959 // Line 2.18: if T'_B--> was a primary splitter then
3960 if (is_primary_splitter)
3961 { assert(splitter_Tprime_B->bunch == bunch_T_a_Bprime);
3962 // Line 2.19: Remove T_U--> \ T_U--a-->B' from the
3963 // splitter list
3964 bisim_dnj::block_t* const
3965 block_U(block_B_begin->st->bl.ock); assert(block_U->end == block_R->begin);
3966 bisim_dnj::block_bunch_slice_iter_t U_splitter(
3967 part_tr.splitter_list.begin()); assert(0 == block_U->marked_size());
3968 if (part_tr.splitter_list.end() != U_splitter &&
3969 (U_splitter->source_block() == block_U ||
3970 (++U_splitter != part_tr.splitter_list.end() &&
3971 U_splitter->source_block() == block_U)))
3972 { assert(!U_splitter->is_stable());
3973 assert(U_splitter->bunch == bunch_T);
3974 block_U->stable_block_bunch.splice(
3975 block_U->stable_block_bunch.end(),
3976 part_tr.splitter_list, U_splitter);
3977 U_splitter->make_stable();
3978 }
3979 #ifndef NDEBUG
3980 // There should be no block-bunch-slice for the co-splitter that is
3981 // still unstable.
3982 for (bisim_dnj::block_bunch_slice_const_iter_t
3983 iter(part_tr.splitter_list.cbegin());
3984 part_tr.splitter_list.cend() != iter; ++iter)
3985 { assert(!iter->is_stable());
3986 assert(iter->source_block() != block_U);
3987 /* Line 2.20: end if */ }
3988 #endif
3989 }
3990 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
3991 else
3992 {
3993 // account for work that couldn't be accounted for earlier (because we
3994 // didn't know yet which state would become a new bottom state)
3995 if (!add_stabilize_to_bottom_transns_succeeded)
3996 { assert(splitter_Tprime_B->add_work_to_bottom_transns(
3997 check_complexity::
3998 refine_partition_until_stable_stabilize_new_noninert_a_posteriori,
3999 1U, *this));
4000 }
4001 if (splitter_Tprime_B->work_counter.has_temporary_work())
4002 { assert(splitter_Tprime_B->add_work_to_bottom_transns(
4003 check_complexity::
4004 handle_new_noninert_transns_make_unstable_a_posteriori,
4005 1U, *this));
4006 splitter_Tprime_B->work_counter.reset_temporary_work();
4007 /* Line 2.21: if R--tau-->U is not empty (i. e. R */ }
4008 /* has new non-inert transitions) then */ }
4009 #endif
4010 if (0 < block_R->marked_size())
4011 { ONLY_IF_DEBUG( const bisim_dnj::block_bunch_entry* const splitter_end =
4012 /* Line 2.22: Create a new bunch containing */ splitter_Tprime_B->end; )
4013 // exactly R--tau-->U, add R--tau-->U to
4014 // the splitter list, and mark all its
4015 // transitions
4016 // to
4017 // Line 2.28: For each bottom state, mark one of
4018 // its outgoing transitions in every
4019 // T_N--> where it has one
4020 block_R = handle_new_noninert_transns(
4021 block_R, splitter_Tprime_B);
4022 #ifndef NDEBUG
4023 if (splitter_end[-1].pred->source->bl.ock == block_R)
4024 { assert(!splitter_end[-1].slice.is_null());
4025 splitter_Tprime_B =
4026 (bisim_dnj::block_bunch_slice_iter_t) splitter_end[-1].slice;
4027 }
4028 /* Line 2.29: end if */ assert(nullptr == block_R || splitter_Tprime_B->source_block() == block_R);
4029 #endif
4030 }
4031 }
4032 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
4033 else
4034 { assert(0 == block_R->marked_size());
4035 assert(add_stabilize_to_bottom_transns_succeeded);
4036 // now splitter must have some transitions that start in bottom states:
4037 if (splitter_Tprime_B->work_counter.has_temporary_work())
4038 { assert(!is_primary_splitter);
4039 if (!splitter_Tprime_B->add_work_to_bottom_transns(check_complexity
4040 ::handle_new_noninert_transns_make_unstable_a_posteriori,
4041 1U, *this))
4042 { assert(0); }
4043 splitter_Tprime_B->work_counter.reset_temporary_work();
4044 }
4045 }
4046 block_B = block_R;
4047 #endif
4048 }
4049 else
4050 { assert(block_B->nonbottom_begin == block_B->end);
4051 /* A block with 1 state will not be split. However, we */ assert(block_B->marked_nonbottom_begin == block_B->end);
4052 // may have to unmark all states.
4053 block_B->marked_bottom_begin = block_B->end;
4054 block_B->stable_block_bunch.splice(
4055 block_B->stable_block_bunch.end(),
4056 part_tr.splitter_list, splitter_Tprime_B);
4057 splitter_Tprime_B->make_stable(); assert(add_stabilize_to_bottom_transns_succeeded);
4058 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
4059 // now splitter must have some transitions that start in bottom states:
4060 if (splitter_Tprime_B->work_counter.has_temporary_work())
4061 { assert(!is_primary_splitter);
4062 if (!splitter_Tprime_B->add_work_to_bottom_transns(check_complexity::
4063 handle_new_noninert_transns_make_unstable_a_posteriori,
4064 1U, *this))
4065 { assert(0); }
4066 splitter_Tprime_B->work_counter.reset_temporary_work();
4067 }
4068 #endif
4069 }
4070 #ifndef NDEBUG
4071 if (is_primary_splitter && !part_tr.splitter_list.empty() &&
4072 part_tr.splitter_list.front().bunch == bunch_T &&
4073 part_tr.splitter_list.front().source_block() == block_B)
4074 { // The next block_bunch-slice to be handled is the one in the large
4075 // splitter corresponding to the current splitter. In that iteration,
4076 // we will need the current splitter block_bunch-slice.
4077 assert(nullptr != block_B);
4078 assert(splitter_Tprime_B->source_block() == block_B);
4079 assert(splitter_Tprime_B->bunch == bunch_T_a_Bprime);
4080 bbslice_T_a_Bprime_B = splitter_Tprime_B;
4081 }
4082 /* Line 2.30: end for */ else
4083 {
4084 bbslice_T_a_Bprime_B = nullptr;
4085 }
4086#endif
4087 }
4088 // Line 2.31: end for
4089 } assert(part_tr.nr_of_bunches == part_tr.nr_of_action_block_slices);
4090 assert(0 == part_tr.nr_of_nontrivial_bunches);
4091
4092 // store the labels with the action_block-slices
4093 // As every action_block-slice is a (trivial) bunch at the same time,
4094 // we can reuse the field next_nontrivial_and_label.label (instead of
4095 // next_nontrivial_and_label.next_nontrivial) to store the label.
4096 const bisim_dnj::action_block_entry*
4097 action_block_iter_end(part_tr.action_block_inert_begin);
4098 label_type label(0); assert(label < action_label.size());
4099 do
4100 {
4101 for (bisim_dnj::action_block_entry*
4102 action_block_iter(action_label[label].begin);
4103 action_block_iter < action_block_iter_end;
4104 action_block_iter = action_block_iter->begin_or_before_end + 1)
4105 { assert(nullptr != action_block_iter->succ);
4106 assert(action_block_iter->succ->block_bunch->pred->action_block ==
4107 action_block_iter);
4108 assert(action_block_iter->succ->bunch()->is_trivial());
4109 action_block_iter->succ->bunch()->
4110 next_nontrivial_and_label.label = label; assert(nullptr != action_block_iter->begin_or_before_end);
4111 assert(action_block_iter <= action_block_iter->begin_or_before_end);
4112 }
4113 }
4114 while (++label < action_label.size() &&
4115 (action_block_iter_end = action_label[label - 1].begin - 1, true));
4116 }
4117
4118 /*----------------- Split -- Algorithm 3 of [JGKW 2020] -----------------*/
4119
4120 /// \brief Split a block according to a splitter
4121 /// \details The function splits `block_B` into the R-subblock (states
4122 /// with a transition in `splitter_T`) and the U-subblock (states without a
4123 /// transition in `splitter_T`). Depending on `mode`, the states are
4124 /// primed as follows:
4125 ///
4126 /// - If `mode == extend_from_marked_states`, then all states with strong
4127 /// transition(s) must have been marked already.
4128 /// - If `mode == extend_from_marked_states__add_new_noninert_to_splitter`,
4129 /// states are marked as above. The only difference is the handling of
4130 /// new non-inert transitions.
4131 /// - If `mode == extend_from_splitter`, then no states must be marked;
4132 /// the initial states with a transition in `splitter_T` are searched by
4133 /// `split()` itself. Every bottom state with strong transition(s)
4134 /// needs to have at least one marked strong transition.
4135 ///
4136 /// The function will also adapt all data structures and determine
4137 /// which transitions have changed from inert to non-inert. States
4138 /// with a new non-inert transition will be marked upon returning.
4139 /// Normally, the new non-inert transitions are moved to a new
4140 /// bunch, which will be specially created. However, if `mode ==
4141 /// extend_from_marked_states__add_new_noninert_to_splitter`, then the new
4142 /// non-inert transitions will be added to `splitter_T` (which must hold
4143 /// transitions that have just become non-inert before this call to
4144 /// `split()`). If the resulting block contains marked states, the caller
4145 /// has to call `handle_new_noninert_transns()` to stabilise the block
4146 /// because the new bunch may make the block unstable.
4147 /// \param block_B block that needs to be refined
4148 /// \param splitter_T transition set that makes the block unstable
4149 /// \param mode indicates how to find states with a transition in
4150 /// `splitter_T`, as described above
4151 /// \returns (a pointer to) the R-subblock. It is an error to call the
4152 /// function with settings that lead to an empty R-subblock. (An empty
4153 /// U-subblock is ok.)
4154 bisim_dnj::block_t* split(bisim_dnj::block_t* const block_B,
4155 const bisim_dnj::block_bunch_slice_iter_t splitter_T,
4156 enum refine_mode_t mode)
4157 { assert(block_B == splitter_T->source_block());
4158 #ifndef NDEBUG
4159 mCRL2log(log::debug) << "split("
4160 << block_B->debug_id(*this)
4161 << ',' << splitter_T->debug_id(*this)
4162 << (extend_from_marked_states_add_new_noninert_to_splitter == mode
4163 ? ",extend_from_marked_states_add_new_noninert_to_splitter)\n"
4164 : (extend_from_marked_states == mode
4165 ? ",extend_from_marked_states)\n"
4166 : (extend_from_splitter == mode
4167 ? ",extend_from_splitter)\n"
4168 : ",UNKNOWN MODE)\n")));
4169 #endif
4170 bisim_dnj::block_t* block_R=nullptr; assert(!splitter_T->is_stable()); assert(1 < block_B->size());
4171 union R_s_iter_t
4172 {
4173 bisim_dnj::block_bunch_entry* splitter_iter;
4174 bisim_dnj::permutation_entry* block;
4175 } R_s_iter{};
4176
4177 if (extend_from_splitter == mode)
4178 { assert(0 == block_B->marked_size());
4179 // Line 3.2: R := B--Marked(T)--> ; U := Bottom(B) \ R
4180 R_s_iter.splitter_iter = splitter_T->end; assert(splitter_T->marked_begin <= R_s_iter.splitter_iter);
4181 while (splitter_T->marked_begin < R_s_iter.splitter_iter)
4182 { assert(&part_tr.block_bunch.cbegin()[1] < R_s_iter.splitter_iter);
4183 --R_s_iter.splitter_iter;
4184 bisim_dnj::state_info_entry* const
4185 s(R_s_iter.splitter_iter->pred->source); assert(s->bl.ock == block_B); assert(s->pos->st == s);
4186 block_B->mark(s->pos);
4187 // We cannot stop, even if the R-subblock becomes too large,
4188 // because we need to mark all bottom states that are not in U.
4189 }
4190 } else { assert(0 < block_B->marked_size());
4191 assert(splitter_T->marked_begin == splitter_T->end); }
4192 block_B->stable_block_bunch.splice(block_B->stable_block_bunch.end(),
4193 part_tr.splitter_list, splitter_T);
4194 splitter_T->make_stable();
4195
4197 // shared variables of both coroutines
4198 bisim_dnj::permutation_entry*
4199 untested_to_U_defined_end(block_B->nonbottom_begin);
4200 bisim_dnj::permutation_entry*
4201 U_nonbottom_end(untested_to_U_defined_end);
4202
4203 // variable declarations of the U-coroutine
4204 bisim_dnj::permutation_entry* U_s_iter;
4205 bisim_dnj::pred_entry* U_t_iter;
4206 bisim_dnj::state_info_entry* U_t;
4207 const bisim_dnj::succ_entry* U_u_iter;
4208
4209 // variable declarations of the R-coroutine
4210 bisim_dnj::pred_entry* R_t_iter;
4211
4212 COROUTINE_LABELS( (SPLIT_R_PREDECESSOR_HANDLED)
4213 (SPLIT_U_PREDECESSOR_HANDLED)
4214 (SPLIT_R_STATE_HANDLED)
4215 (SPLIT_U_STATE_HANDLED)
4216 (SPLIT_U_TESTING)
4217 (SPLIT_R_COLLECT_SPLITTER))
4218
4219 /*------------------------ find U-states ------------------------*/
4220
4221 COROUTINE
4222 // Line 3.21l: if |U| > |B|/2 then
4223 if(block_B->size() / 2 < block_B->unmarked_bottom_size())
4224 {
4225 // Line 3.22l: Abort this coroutine
4227 // Line 3.23l: end if
4228 }
4229 if (0 == block_B->unmarked_bottom_size())
4230 {
4231 // all bottom states are in R, so there cannot be any
4232 // U-states. Unmark all states, as there are no
4233 // transitions that have become non-inert.
4234 block_B->marked_nonbottom_begin = block_B->end;
4235 block_B->marked_bottom_begin = block_B->nonbottom_begin;
4236 block_R = block_B;
4237 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
4238 finalise_U_is_smaller(nullptr, block_R, *this);
4239 #endif
4241 }
4242
4243 /* - - - - - - - visit U-states - - - - - - - */
4244
4245 if (U_nonbottom_end < block_B->marked_nonbottom_begin)
4246 {
4247 // Line 3.5l: for all s in U while |U| < |B|/2 do
4248 U_s_iter = block_B->begin;
4249 COROUTINE_DO_WHILE (SPLIT_U_STATE_HANDLED,
4250 U_s_iter < U_nonbottom_end)
4251 {
4252 /* Line 3.6l: for all inert transitions t--tau-->s do*/ assert(part_tr.pred.front().target != U_s_iter->st);
4253 COROUTINE_FOR(SPLIT_U_PREDECESSOR_HANDLED,
4254 U_t_iter = U_s_iter->st->pred_inert.begin,
4255 U_t_iter->target == U_s_iter->st, ++U_t_iter)
4256 {
4257 U_t = U_t_iter->source; assert(block_B->nonbottom_begin <= U_t->pos);
4258 /* Line 3.7l: if t in R then Skip state t */ assert(U_t->pos < block_B->end);
4259 if (block_B->marked_nonbottom_begin <= U_t->pos)
4260 {
4261 goto continuation;
4262 }
4263 // Line 3.8l: if untested[t] is undefined then
4264 if (untested_to_U_defined_end <= U_t->pos)
4265 {
4266 // Line 3.9l: untested[t] :=
4267 // |{ t--tau-->u | u in B }|
4268 U_t->untested_to_U_eqv.begin =
4269 U_t->succ_inert.begin;
4270 std::swap(*U_t->pos,
4271 *untested_to_U_defined_end++);
4272 // Line 3.10l: end if
4273 } assert(U_t != part_tr.succ.back().block_bunch->pred->source);
4274 // Line 3.11l: untested[t] := untested[t] − 1
4275 ++U_t->untested_to_U_eqv.begin;
4276 // Line 3.12l: if untested[t]>0 then Skip state t
4277 if (U_t == U_t->untested_to_U_eqv.
4278 begin->block_bunch->pred->source)
4279 {
4280 goto continuation;
4281 }
4282 // Line 3.13l: if not (B--T--> subset R) then
4283 if (extend_from_splitter == mode)
4284 { assert(U_t != part_tr.succ.front().block_bunch->pred->source);
4285 // Line 3.14l: for all non-inert
4286 // t --alpha--> u do
4287 U_u_iter = U_t->succ_inert.begin; assert(part_tr.succ.data() < U_u_iter);
4288 COROUTINE_WHILE(SPLIT_U_TESTING, U_t ==
4289 U_u_iter[-1].block_bunch->pred->source)
4290 {
4291 U_u_iter=U_u_iter[-1].begin_or_before_end; assert(nullptr != U_u_iter);
4292 /* Line 3.15l: if t --alpha--> u in T */ assert(U_u_iter->block_bunch->pred->source == U_t);
4293 /* then Skip t */ assert(!U_u_iter->block_bunch->slice.is_null());
4294 bisim_dnj::block_bunch_slice_const_iter_t
4295 const block_bunch(
4296 U_u_iter->block_bunch->slice);
4297 if (&*block_bunch == &*splitter_T)
4298 {
4299 goto continuation;
4300 // i. e. break and then continue
4301 }
4302 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
4303 bisim_dnj::succ_entry::add_work_to_out_slice(*this, U_u_iter,
4304 /* Line 3.16l: end for */ check_complexity::split_U_test_noninert_transitions, 1U);
4305 #endif
4306 }
4308 // Line 3.17l: end if
4309 } assert(U_nonbottom_end <= U_t->pos);
4310 /* Line 3.18l: Add t to U */ assert(U_t->pos < untested_to_U_defined_end);
4311 std::swap(*U_t->pos, *U_nonbottom_end++);
4312 // Line 3.21l: if |U| > |B|/2 then
4313 if (block_B->size() / 2 <
4314 U_nonbottom_end-block_B->nonbottom_begin +
4315 block_B->unmarked_bottom_size())
4316 {
4317 // Line 3.22l: Abort this coroutine
4318 // As the U-coroutine is now aborted, the
4319 // untested_to_U values are no longer relevant.
4320 // The assignment tells the R-coroutine that it
4321 // doesn't need to make complicated swaps any
4322 // more to keep untested properly initialized.
4323 untested_to_U_defined_end = U_nonbottom_end;
4325 // Line 3.23l: end if
4326 }
4327 // Line 3.19l: end for
4328 continuation: mCRL2complexity(U_t_iter, add_work(
4329 check_complexity::split_U_handle_transition_to_U_state, 1U), *this);
4330 }
4331 END_COROUTINE_FOR; mCRL2complexity(U_s_iter->st, add_work(
4332 /* Line 3.20l: end for */ check_complexity::split_U_find_predecessors_of_U_state, 1U), *this);
4333 ++U_s_iter;
4334 if(block_B->marked_bottom_begin == U_s_iter)
4335 {
4336 U_s_iter = block_B->nonbottom_begin;
4337 }
4338 }
4340 }
4341
4342 /*- - - - - - - split off U-block - - - - - - -*/
4343
4344 // Line 3.24l: Abort the other coroutine
4346 // Line 2.17: Pi_s := Pi_s \ { B } union ({ R, U } \ { {} })
4347 // All non-U states are in R.
4348 block_B->marked_nonbottom_begin = U_nonbottom_end;
4349 block_R = block_B;
4350 bisim_dnj::block_t* const block_U(
4351 block_R->split_off_block(bisim_dnj::new_block_is_U, ONLY_IF_DEBUG( *this, )
4352 part_st.nr_of_blocks++));
4353 // Line 2.16: Remove Tprime_B--> = Tprime_R--> from the
4354 // splitter list
4355 /* and the remainder of Line 2.17 */ assert(0 == block_U->marked_size()); assert(0 == block_R->marked_size());
4356 part_tr.adapt_transitions_for_new_block(block_U,
4357 block_R,
4358 ONLY_IF_DEBUG(*this, ) extend_from_marked_states_add_new_noninert_to_splitter == mode,
4359 splitter_T,
4360 bisim_dnj::new_block_is_U);
4361#if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
4362 finalise_U_is_smaller(block_U, block_R, *this);
4363 #endif
4365
4366 /*------------------------ find R-states ------------------------*/
4367
4368 COROUTINE
4369 // Line 3.21r: if |R| > |B|/2 then
4370 if (block_B->size() / 2 < block_B->marked_size())
4371 {
4372 // Line 3.22r: Abort this coroutine
4374 // Line 3.23r: end if
4375 }
4376
4377 /* - - - - - collect states from B--T--> - - - - - */
4378
4379 if (extend_from_splitter == mode)
4380 {
4381 // Line 3.4r: R := R union B--(T \ Marked(T))-->
4382 if (U_nonbottom_end < block_B->marked_nonbottom_begin)
4383 { assert(part_tr.block_bunch.front().slice != splitter_T);
4384 COROUTINE_WHILE (SPLIT_R_COLLECT_SPLITTER,
4385 R_s_iter.splitter_iter[-1].slice == splitter_T)
4386 { assert(&part_tr.block_bunch.cbegin()[1] < R_s_iter.splitter_iter);
4387 --R_s_iter.splitter_iter;
4388 bisim_dnj::state_info_entry* const s(
4389 R_s_iter.splitter_iter->pred->source); assert(s->bl.ock == block_B); assert(s->pos->st == s);
4390 if (block_B->nonbottom_begin <= s->pos)
4391 { assert(U_nonbottom_end <= s->pos);
4392 if (s->pos < untested_to_U_defined_end)
4393 {
4394 // The non-bottom state has a transition
4395 // to a visited U-state, so untested is
4396 // initialised; however, now it is
4397 // discovered to be in R anyway.
4398 std::swap(*s->pos,
4399 *--untested_to_U_defined_end);
4400 }
4401 if (block_B->mark_nonbottom(s->pos) &&
4402 // Line 3.21r: if |R| > |B|/2 then
4403 block_B->size()/2 < block_B->marked_size())
4404 {
4405 // Line 3.22r: Abort this coroutine
4407 // Line 3.23r: end if
4408 }
4409 }
4410 else
4411 {
4412 assert(block_B->marked_bottom_begin <= s->pos);
4413 }
4414 mCRL2complexity(R_s_iter.splitter_iter->pred, add_work(
4415 check_complexity::split_R_handle_transition_from_R_state, 1U), *this);
4416 }
4418
4419 // Indicate to the U-coroutine that all states in
4420 // B--T--> are now in R.
4421 // The shared variable `mode` is used
4422 // instead of a separate shared variable.
4423 mode = extend_from_marked_states;
4424 }
4425 #ifndef NDEBUG
4426 else
4427 {
4428 // assert that all non-marked transitions in `splitter_T` start in
4429 // marked states
4430 assert(part_tr.block_bunch.front().slice != splitter_T);
4431 while (R_s_iter.splitter_iter[-1].slice == splitter_T)
4432 {
4433 assert(&part_tr.block_bunch.cbegin()[1] < R_s_iter.splitter_iter);
4434 --R_s_iter.splitter_iter;
4435 bisim_dnj::state_info_entry* const
4436 s(R_s_iter.splitter_iter->pred->source);
4437 assert(s->bl.ock == block_B); assert(s->pos->st == s);
4438 assert(s->pos < block_B->nonbottom_begin ||
4439 block_B->marked_nonbottom_begin <= s->pos);
4440 assert(block_B->marked_bottom_begin <= s->pos);
4441 }
4442 }
4443 #endif
4444 }
4445
4446 /* - - - - - - - visit R-states - - - - - - - */ assert(0 != block_B->marked_size());
4447
4448 if (U_nonbottom_end < block_B->marked_nonbottom_begin)
4449 {
4450 // Line 3.5r: for all s in R while |R| < |B|/2 do
4451 R_s_iter.block = block_B->nonbottom_begin;
4452 if (block_B->marked_bottom_begin == R_s_iter.block)
4453 {
4454 // It may happen that all found states are non-bottom
4455 // states. (In that case, some of these states will
4456 // become new bottom states.)
4457 R_s_iter.block = block_B->end;
4458 } assert(block_B->marked_nonbottom_begin != R_s_iter.block);
4459 COROUTINE_DO_WHILE(SPLIT_R_STATE_HANDLED,
4460 block_B->marked_nonbottom_begin != R_s_iter.block)
4461 {
4462 --R_s_iter.block; assert(part_tr.pred.back().target != R_s_iter.block->st);
4463 // Line 3.7r: for all inert transitions t--tau-->s do
4464 COROUTINE_FOR (SPLIT_R_PREDECESSOR_HANDLED,
4465 R_t_iter = R_s_iter.block->st->pred_inert.begin,
4466 R_t_iter->target == R_s_iter.block->st, ++R_t_iter)
4467 {
4468 bisim_dnj::state_info_entry* const
4469 t(R_t_iter->source); assert(U_nonbottom_end <= t->pos);
4470 /* Line 3.18r: Add t to R */ assert(t->pos->st == t); assert(t->pos < block_B->end);
4471 if (t->pos < untested_to_U_defined_end)
4472 {
4473 // The state has a transition to a U-state, so
4474 // untested is initialised; however, now it is
4475 // discovered to be in R anyway.
4476 std::swap(*t->pos,
4477 *--untested_to_U_defined_end);
4478 }
4479 if (block_B->mark_nonbottom(t->pos) &&
4480 // Line 3.21r: if |R| > |B|/2 then
4481 block_B->size() / 2 < block_B->marked_size())
4482 {
4483 // Line 3.22r: Abort this coroutine
4485 // Line 3.23r: end if
4486 } mCRL2complexity(R_t_iter, add_work(
4487 /* Line 3.19r: end for */ check_complexity::split_R_handle_transition_to_R_state, 1U), *this);
4488 }
4489 END_COROUTINE_FOR; mCRL2complexity(R_s_iter.block->st, add_work(
4490 check_complexity::split_R_find_predecessors_of_R_state,
4491 /* Line 3.20r: end for */ 1U), *this);
4492 if (block_B->marked_bottom_begin == R_s_iter.block &&
4493 R_s_iter.block < block_B->nonbottom_begin)
4494 {
4495 R_s_iter.block = block_B->end;
4496 }
4497 }
4499 }
4500
4501 /*- - - - - - - split off R-block - - - - - - -*/
4502
4503 // Line 3.24r: Abort the other coroutine
4505 // Line 2.17: Pi_s := Pi_s \ { B } union ({ R, U } \ { {} })
4506 // All non-R states are in U.
4507 block_R = block_B->split_off_block(bisim_dnj::new_block_is_R, ONLY_IF_DEBUG( *this, )
4508 part_st.nr_of_blocks++);
4509 // Line 2.16: Remove Tprime_B--> = Tprime_R--> from the
4510 // splitter list
4511 /* and the remainder of Line 2.17 */ assert(0 == block_B->marked_size()); assert(0 == block_R->marked_size());
4512 part_tr.adapt_transitions_for_new_block(block_R,
4513 block_B,
4514 ONLY_IF_DEBUG(*this, ) extend_from_marked_states_add_new_noninert_to_splitter == mode,
4515 splitter_T,
4516 bisim_dnj::new_block_is_R);
4517#if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
4518 finalise_R_is_smaller(block_B, block_R, *this);
4519 #endif
4522 return block_R;
4523 }
4524
4525 /*-- Handle new non-inert transitions -- Lines 2.22-2.28 in [JGKW2020] --*/
4526
4527 /// \brief Handle a block with new non-inert transitions
4528 /// \details When this function starts, it assumes that the states with a
4529 /// new non-inert transition in `block_R` are marked. It is an error if it
4530 /// does not contain any marked states.
4531 ///
4532 /// The function separates the states with new non-inert transitions from
4533 /// those without; as a result, the N-subblock (which contains states
4534 /// with new non-inert transitions) will contain at least one new bottom
4535 /// state (and no old bottom states). It then registers almost all
4536 /// block_bunch-slices of this N-subblock as unstable and marks one
4537 /// transition per block_bunch-slice and new bottom state. Only the
4538 /// block_bunch-slice containing the new non-inert transitions and, if
4539 /// possible, `bbslice_Tprime_R` are not registered as unstable.
4540 /// \param block_R block containing states with new non-inert
4541 /// transitions that need to be stabilised
4542 /// \param bbslice_Tprime_R splitter of the last separation before, i.e.
4543 /// the splitter that made these transitions
4544 /// non-inert (`block_R` should already be stable
4545 /// w.r.t. `bbslice_Tprime_R`).
4546 /// \returns the block containing the old bottom states (and every state in
4547 /// `block_R` that cannot reach any new non-inert transition),
4548 /// i.e. the U-subblock of the separation
4549 bisim_dnj::block_t* handle_new_noninert_transns(
4550 bisim_dnj::block_t* const block_R,
4551 bisim_dnj::block_bunch_slice_const_iter_t bbslice_Tprime_R)
4552 { assert(block_R == bbslice_Tprime_R->source_block());
4553 bisim_dnj::block_t* block_Rprime; assert(&part_tr.block_bunch.cbegin()[1] < part_tr.block_bunch_inert_begin);
4554 bisim_dnj::block_t* block_N; assert(!part_tr.block_bunch_inert_begin[-1].slice.is_null());
4555 bisim_dnj::block_bunch_slice_iter_t const bbslice_R_tau_U(
4556 part_tr.block_bunch_inert_begin[-1].slice); assert(bbslice_Tprime_R->is_stable());
4557 /* Line 2.23: (N, R') := split(R, R--tau-->U) */ assert(!bbslice_R_tau_U->is_stable());
4558 /* Line 2.24: Remove R--tau-->U from the splitter list */ assert(block_R == bbslice_R_tau_U->source_block());
4559 /* Line 2.25: Pi_s := Pi_s \ { R } union { N, R' } \ { emptyset } */ assert(0 < block_R->marked_size());
4560 // Line 2.26: Add N--tau-->R' to the bunch containing R--tau-->U
4561 if (0 < block_R->unmarked_bottom_size())
4562 { assert(part_tr.splitter_list.begin() != part_tr.splitter_list.end());
4563 #ifndef NDEBUG
4564 bool const next_splitter_is_of_same_block =
4565 part_tr.splitter_list.begin() != bbslice_R_tau_U &&
4566 part_tr.splitter_list.front().source_block() == block_R;
4567 #endif
4568 block_N = split(block_R,
4569 bbslice_R_tau_U,
4570 extend_from_marked_states_add_new_noninert_to_splitter);
4571 assert(part_st.permutation.data()
4572 < block_N->begin);
4573 block_Rprime
4574 = block_N->begin[-1].st->bl.ock;
4575 #ifndef NDEBUG
4576 // If the first element of the splitter list was a block_bunch-slice of
4577 // block_N, it was split up. The condition below checks whether the
4578 // N-subblock's (= the block with new bottom states) slice is placed before
4579 // the R'-subblock's (= the block with old bottom states).
4580 if (next_splitter_is_of_same_block &&
4581 (assert(part_tr.splitter_list.begin() != part_tr.splitter_list.end()),
4582 part_tr.splitter_list.front().source_block()==block_N))
4583 {
4584 bisim_dnj::block_bunch_slice_iter_t const bbslice_T_Rprime(
4585 std::next(part_tr.splitter_list.begin()));
4586 if (part_tr.splitter_list.end() != bbslice_T_Rprime &&
4587 bbslice_T_Rprime->source_block() == block_Rprime)
4588 {
4589 // The R'-subblock's slice must be the first in the splitter list.
4590 // This is necessary in Debug-mode to ensure that the cost of
4591 // refining R' is accounted for correctly.
4592 part_tr.splitter_list.splice(part_tr.splitter_list.begin(),
4593 /* If more new noninert transitions are found, we do not need to */ part_tr.splitter_list, bbslice_T_Rprime);
4594 /* separate them further, as every bottom state already has a */ }
4595 /* transition in bbslice_R_tau_U->bunch. */ }
4596 #endif
4597 if (0 < block_N->marked_bottom_size())
4598 {
4599 // Not only new non-inert transitions, but also new bottom
4600 // states have been found. In that case, we also have to
4601 // refine w.r.t. the last splitter, as the new bottom states in
4602 // block_N may be unstable under it. We set the variable
4603 // `bbslice_Tprime_R` to `bbslice_R_tau_U` so it won't disturb
4604 // in the test below.
4605 bbslice_Tprime_R = bbslice_R_tau_U;
4606 block_N->marked_bottom_begin = block_N->nonbottom_begin;
4607 }
4608 else if (bbslice_Tprime_R->source_block() != block_N)
4609 { assert(bbslice_Tprime_R->source_block() == block_Rprime);
4610 // bbslice_Tprime_R contained transitions from every (old and
4611 // new) bottom state in the block. It has been split, and now
4612 // it contains transitions from the block with old bottom
4613 /* states; however, we need the block_bunch-slice with */ assert(!bbslice_Tprime_R->end->slice.is_null());
4614 /* transitions from the block with new bottom states. */ assert(bbslice_Tprime_R->end < part_tr.block_bunch_inert_begin);
4615 bbslice_Tprime_R = (bisim_dnj::block_bunch_slice_const_iter_t)
4616 bbslice_Tprime_R->end->slice; assert(bbslice_Tprime_R->source_block() == block_N);
4617 }
4618 }
4619 else
4620 {
4621 block_N = block_R;
4622 // make bbslice_R_tau_U stable
4623 block_N->stable_block_bunch.splice(
4624 block_N->stable_block_bunch.end(),
4625 part_tr.splitter_list, bbslice_R_tau_U);
4626 bbslice_R_tau_U->make_stable();
4627 block_N->marked_bottom_begin = block_N->nonbottom_begin;
4628 block_Rprime = nullptr;
4629 }
4630 block_N->marked_nonbottom_begin = block_N->end;
4631
4632 if (1 >= block_N->size())
4633 {
4634 return block_Rprime;
4635 }
4636
4637 // Line 2.27: Insert all T_N--> as secondary into the splitter list
4638 // However, the bunch of new noninert transitions and the bunch
4639 // that was the last splitter do not need to be handled (as long
4640 // as there are no further new bottom states).
4641 // We cannot do this in time O(1) because we need to call
4642 // `make_unstable()` for each block_bunch-slice individually.
4643 for (bisim_dnj::block_bunch_slice_iter_t bbslice_T_N(
4644 block_N->stable_block_bunch.begin());
4645 block_N->stable_block_bunch.end() != bbslice_T_N; )
4646 { assert(bbslice_T_N->is_stable());
4647 bisim_dnj::block_bunch_slice_iter_t const
4648 next_bbslice_T_N(std::next(bbslice_T_N));
4649 if (&*bbslice_T_N != &*bbslice_Tprime_R &&
4650 &*bbslice_T_N != &*bbslice_R_tau_U)
4651 {
4652 // In Debug mode, we have to place the new splitters at the end
4653 // of the splitter list -- after a refinement with a primary
4654 // splitter, the corresponding refinement with the large
4655 // splitter should follow immediately, to ensure that the cost
4656 // for refining `block_Rprime` is accounted for correctly.
4657 part_tr.splitter_list.splice(part_tr.splitter_list.end(),
4658 block_N->stable_block_bunch, bbslice_T_N);
4659 bbslice_T_N->make_unstable();
4660 }
4661 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
4662 // Try to assign this work to a transition from a bottom state in
4663 // bbslice_T_N.
4664 // If that does not succeed, temporarily assign it to the block_bunch
4665 // itself. Later, we shall find a bottom state to which this work can be
4666 // assigned.
4667 assert(!bbslice_T_N->work_counter.has_temporary_work());
4668 if (!bbslice_T_N->add_work_to_bottom_transns(check_complexity::
4669 handle_new_noninert_transns_make_unstable_a_priori, 1U, *this))
4670 { mCRL2complexity(bbslice_T_N, add_work(check_complexity::
4671 handle_new_noninert_transns_make_unstable_temp, 1U), *this);
4672 assert(bbslice_T_N->work_counter.has_temporary_work());
4673 assert(!bbslice_T_N->is_stable());
4674 }
4675 #endif
4676 bbslice_T_N = next_bbslice_T_N;
4677 }
4678
4679 // Line 2.28: For each bottom state, mark one of its outgoing
4680 // transitions in each in every T_N--> where it has one
4681 bisim_dnj::permutation_entry* s_iter(block_N->begin); assert(s_iter < block_N->nonbottom_begin);
4682 do
4683 {
4684 bisim_dnj::state_info_entry* const s(s_iter->st); assert(s->pos == s_iter);
4685 // for all out-slices of s do
4686 for (bisim_dnj::succ_entry* succ_iter(s->succ_inert.begin);
4687 s == succ_iter[-1].block_bunch->pred->source; )
4688 { assert(succ_iter[-1].begin_or_before_end < succ_iter);
4689 succ_iter = succ_iter[-1].begin_or_before_end; assert(nullptr != succ_iter);
4690 /* Mark the first transition in the out-slice in its */ assert(succ_iter->block_bunch->pred->action_block->succ == succ_iter);
4691 /* block_bunch-slice */ assert(s == succ_iter->block_bunch->pred->source);
4692 bisim_dnj::block_bunch_entry* const
4693 old_block_bunch_pos(succ_iter->block_bunch); assert(!old_block_bunch_pos->slice.is_null());
4694 bisim_dnj::block_bunch_slice_iter_t const
4695 bbslice_T_N((bisim_dnj::block_bunch_slice_iter_t)
4696 old_block_bunch_pos->slice);
4697 if (!bbslice_T_N->is_stable())
4698 { assert(&*bbslice_T_N != &*bbslice_Tprime_R && bbslice_T_N != bbslice_R_tau_U);
4699 bisim_dnj::block_bunch_entry* const
4700 new_block_bunch_pos(bbslice_T_N->marked_begin - 1);
4701 // It may happen that the transition was already paid
4702 // for earlier, namely if it once was in bunch_T
4703 if (old_block_bunch_pos <= new_block_bunch_pos)
4704 {
4705 bbslice_T_N->marked_begin = new_block_bunch_pos; assert(new_block_bunch_pos->slice == bbslice_T_N);
4706 std::swap(old_block_bunch_pos->pred,
4707 new_block_bunch_pos->pred); assert(nullptr != old_block_bunch_pos->pred->action_block->succ);
4708 old_block_bunch_pos->pred->action_block->succ->
4709 block_bunch = old_block_bunch_pos; assert(new_block_bunch_pos->pred->action_block->succ == succ_iter);
4710 succ_iter->block_bunch = new_block_bunch_pos; // add_work(succ_iter->block_bunch->pred, ...) -- subsumed in the call below
4711 }
4712 }
4713 else
4714 {
4715 assert(&*bbslice_T_N == &*bbslice_Tprime_R || bbslice_T_N == bbslice_R_tau_U);
4716 }
4717 } mCRL2complexity(s, add_work(
4718 check_complexity::handle_new_noninert_transns, 1U), *this);
4719 }
4720 while (++s_iter < block_N->nonbottom_begin);
4721
4722 return block_Rprime;
4723 }
4724};
4725
4726///@} (end of group part_refine)
4727 #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
4728 namespace bisim_dnj {
4729
4730 /// \brief moves temporary work counters to normal ones if the U-block is
4731 /// smaller
4732 /// \param block_U the smaller subblock containing the U-states (can
4733 /// be nullptr)
4734 /// \param block_R the larger subblock containing the R-states
4735 /// \param partitioner the partitioner data structure, used to write
4736 /// diagnostic messages
4737 template <class LTS_TYPE>
4738 static void finalise_U_is_smaller(const block_t* const block_U,
4739 const block_t* const block_R,
4740 const bisim_partitioner_dnj<LTS_TYPE>& partitioner)
4741 {
4742 if (nullptr != block_U)
4743 {
4744 unsigned const max_U_block(check_complexity::log_n -
4745 check_complexity::ilog2(block_U->size()));
4746 // finalise work counters for the U-states and their transitions
4747 const permutation_entry* s_iter(block_U->begin);
4748 assert(s_iter < block_U->end);
4749 do
4750 {
4751 const state_info_entry* const s(s_iter->st);
4752 mCRL2complexity(s, finalise_work(
4753 check_complexity::split_U_find_predecessors_of_U_state,
4754 check_complexity::split_find_predecessors_of_R_or_U_state,
4755 max_U_block), partitioner);
4756 assert(s != partitioner.part_tr.pred.back().target);
4757 for (const pred_entry* pred_iter(s->pred_inert.begin);
4758 s == pred_iter->target; ++pred_iter)
4759 {
4760 mCRL2complexity(pred_iter, finalise_work(check_complexity::
4761 split_U_handle_transition_to_U_state,
4762 check_complexity::
4763 split_handle_transition_to_R_or_U_state,
4764 max_U_block), partitioner);
4765 }
4766 // Sometimes, inert transitions become transitions from R- to
4767 // U-states; therefore, we also have to walk through the
4768 // noninert predecessors of U-states:
4769 assert(s != partitioner.part_tr.pred.front().target);
4770 for (const pred_entry* pred_iter(s->pred_inert.begin);
4771 s == (--pred_iter)->target; )
4772 {
4773 mCRL2complexity(pred_iter, finalise_work(check_complexity::
4774 split_U_handle_transition_to_U_state,
4775 check_complexity::
4776 split_handle_transition_to_R_or_U_state,
4777 max_U_block), partitioner);
4778 }
4779 assert(s != partitioner.part_tr.succ.front().
4780 block_bunch->pred->source);
4781 for (const succ_entry* succ_iter(s->succ_inert.begin);
4782 s == (--succ_iter)->block_bunch->pred->source; )
4783 {
4784 mCRL2complexity(succ_iter->block_bunch->pred,finalise_work(
4785 check_complexity::
4786 split_U_test_noninert_transitions,
4787 check_complexity::
4788 split_handle_transition_from_R_or_U_state,
4789 max_U_block), partitioner);
4790 }
4791 }
4792 while (++s_iter < block_U->end);
4793 }
4794 // cancel work counters for the R-states and their transitions, and
4795 // also account for work done in the U-coroutine on R-states
4796 const permutation_entry* s_iter(block_R->begin);
4797 assert(s_iter < block_R->end);
4798 do
4799 {
4800 const state_info_entry* const s(s_iter->st);
4801 mCRL2complexity(s, cancel_work(check_complexity::
4802 split_R_find_predecessors_of_R_state), partitioner);
4803 assert(s != partitioner.part_tr.pred.back().target);
4804 for (const pred_entry* pred_iter(s->pred_inert.begin);
4805 s == pred_iter->target; ++pred_iter)
4806 {
4807 mCRL2complexity(pred_iter, cancel_work(check_complexity::
4808 split_R_handle_transition_to_R_state), partitioner);
4809 }
4810 assert(s !=
4811 partitioner.part_tr.succ.front().block_bunch->pred->source);
4812 for (const succ_entry* succ_iter(s->succ_inert.begin);
4813 s == (--succ_iter)->block_bunch->pred->source; )
4814 {
4815 mCRL2complexity(succ_iter->block_bunch->pred, cancel_work(
4816 check_complexity::
4817 split_R_handle_transition_from_R_state), partitioner);
4818 // the following counter measures work done in the
4819 // U-coroutine that found R-states.
4820 mCRL2complexity(succ_iter->block_bunch->pred,finalise_work(
4821 check_complexity::
4822 split_U_test_noninert_transitions,
4823 check_complexity::
4824 split_test_noninert_transitions_found_new_bottom_state,
4825 1U), partitioner);
4826 }
4827 }
4828 while (++s_iter < block_R->end);
4829
4830 check_complexity::check_temporary_work();
4831 (void) partitioner; // avoid unused variable warning
4832 }
4833
4834 /// \brief moves temporary work counters to normal ones if the R-block is
4835 /// smaller
4836 /// \param block_U the larger subblock containing the U-states
4837 /// \param block_R the smaller but non-empty subblock containing the
4838 /// R-states
4839 /// \param partitioner the partitioner data structure, used to write
4840 /// diagnostic messages
4841 template <class LTS_TYPE>
4842 static void finalise_R_is_smaller(const block_t* const block_U,
4843 const block_t* const block_R,
4844 const bisim_partitioner_dnj<LTS_TYPE>& partitioner)
4845 {
4846 unsigned const max_R_block(check_complexity::log_n -
4847 check_complexity::ilog2(block_R->size()));
4848 // cancel work counters for the U-states and their transitions
4849 const permutation_entry* s_iter(block_U->begin);
4850 assert(s_iter < block_U->end);
4851 do
4852 {
4853 const state_info_entry* const s(s_iter->st);
4854 mCRL2complexity(s, cancel_work(check_complexity::
4855 split_U_find_predecessors_of_U_state), partitioner);
4856 assert(s != partitioner.part_tr.pred.back().target);
4857 for (const pred_entry* pred_iter(s->pred_inert.begin);
4858 s == pred_iter->target; ++pred_iter)
4859 {
4860 mCRL2complexity(pred_iter, cancel_work(check_complexity::
4861 split_U_handle_transition_to_U_state), partitioner);
4862 }
4863 // Sometimes, inert transitions become transitions from R- to
4864 // U-states; therefore, we also have to walk through the
4865 // noninert predecessors of U-states:
4866 assert(s != partitioner.part_tr.pred.front().target);
4867 for (const pred_entry* pred_iter(s->pred_inert.begin);
4868 s == (--pred_iter)->target; )
4869 {
4870 mCRL2complexity(pred_iter, cancel_work(check_complexity::
4871 split_U_handle_transition_to_U_state), partitioner);
4872 }
4873 assert(s !=
4874 partitioner.part_tr.succ.front().block_bunch->pred->source);
4875 for (const succ_entry* succ_iter(s->succ_inert.begin);
4876 s == (--succ_iter)->block_bunch->pred->source; )
4877 {
4878 mCRL2complexity(succ_iter->block_bunch->pred, cancel_work(
4879 check_complexity::
4880 split_U_test_noninert_transitions), partitioner);
4881 }
4882 }
4883 while (++s_iter < block_U->end);
4884 // finalise work counters for the R-states and their transitions
4885 s_iter = block_R->begin;
4886 assert(s_iter < block_R->end);
4887 do
4888 {
4889 const state_info_entry* const s(s_iter->st);
4890 mCRL2complexity(s, finalise_work(
4891 check_complexity::split_R_find_predecessors_of_R_state,
4892 check_complexity::split_find_predecessors_of_R_or_U_state,
4893 max_R_block), partitioner);
4894 assert(s != partitioner.part_tr.pred.back().target);
4895 for (const pred_entry* pred_iter(s->pred_inert.begin);
4896 s == pred_iter->target; ++pred_iter)
4897 {
4898 mCRL2complexity(pred_iter, finalise_work(
4899 check_complexity::split_R_handle_transition_to_R_state,
4900 check_complexity::split_handle_transition_to_R_or_U_state,
4901 max_R_block), partitioner);
4902 }
4903 assert(s !=
4904 partitioner.part_tr.succ.front().block_bunch->pred->source);
4905 for (const succ_entry* succ_iter(s->succ_inert.begin);
4906 s == (--succ_iter)->block_bunch->pred->source; )
4907 {
4908 mCRL2complexity(succ_iter->block_bunch->pred, finalise_work(
4909 check_complexity::
4910 split_R_handle_transition_from_R_state,
4911 check_complexity::
4912 split_handle_transition_from_R_or_U_state,
4913 max_R_block), partitioner);
4914 // the following counter actually is work done in the
4915 // U-coroutine that found R-states.
4916 mCRL2complexity(succ_iter->block_bunch->pred, cancel_work(
4917 check_complexity::
4918 split_U_test_noninert_transitions), partitioner);
4919 }
4920 }
4921 while (++s_iter < block_R->end);
4922 check_complexity::check_temporary_work();
4923 (void) partitioner; // avoid unused variable warning
4924 }
4925
4926 } // end namespace bisim_dnj
4927 #endif
4928
4929
4930
4931
4932
4933/* ************************************************************************* */
4934/* */
4935/* I N T E R F A C E */
4936/* */
4937/* ************************************************************************* */
4938
4939
4940
4941
4942
4943/// \defgroup part_interface
4944/// \brief nonmember functions serving as interface with the rest of mCRL2
4945/// \details These functions are copied, almost without changes, from
4946/// liblts_bisim_gw.h, which was written by Anton Wijs.
4947///@{
4948
4949/// \brief Reduce transition system l with respect to strong or
4950/// (divergence-preserving) branching bisimulation.
4951/// \param[in,out] l The transition system that is reduced.
4952/// \param branching If true branching bisimulation is
4953/// applied, otherwise strong bisimulation.
4954/// \param preserve_divergence Indicates whether loops of internal
4955/// actions on states must be preserved. If
4956/// false these are removed. If true these
4957/// are preserved.
4958template <class LTS_TYPE>
4959void bisimulation_reduce_dnj(LTS_TYPE& l, bool const branching = false,
4960 bool const preserve_divergence = false)
4961{
4962 if (1 >= l.num_states())
4963 {
4964 // LTSs with 1 state also need to be reduced because some users call
4965 // bisimulation minimisation just to remove duplicated transitions.
4966 mCRL2log(log::warning) << "There is only 1 state in the LTS. It is "
4967 "not guaranteed that branching bisimulation minimisation runs "
4968 "in time O(m log n).\n";
4969 }
4970 // Line 2.1: Find tau-SCCs and contract each of them to a single state
4971 const std::clock_t start_SCC=std::clock();
4972 if (branching)
4973 {
4974 scc_reduce(l, preserve_divergence);
4975 // If only 1 state remains after this contraction, we are already
4976 // finished because scc_reduce() also removes duplicated transitions.
4977 if (1 >= l.num_states())
4978 {
4979 return;
4980 }
4981 }
4982 // Now apply the branching bisimulation reduction algorithm. If there
4983 // are no taus, this will automatically yield strong bisimulation.
4984 const std::clock_t start_part=std::clock();
4985 bisim_partitioner_dnj<LTS_TYPE> bisim_part(l, branching,
4986 preserve_divergence);
4987
4988 // Assign the reduced LTS
4989 const std::clock_t end_part=std::clock();
4990 bisim_part.finalize_minimized_LTS();
4991
4992 if (mCRL2logEnabled(log::verbose))
4993 {
4994 const std::clock_t end_finalizing=std::clock();
4995 const int prec=static_cast<int>(std::lrint(std::log10(CLOCKS_PER_SEC)+0.19897000433602));
4996 // For example, if CLOCKS_PER_SEC>= 20: >=2 digits
4997 // If CLOCKS_PER_SEC>= 200: >=3 digits
4998 // If CLOCKS_PER_SEC>=2000000: >=7 digits
4999
5000 double runtime[5];
5001 runtime[0]=(double) (end_finalizing - start_SCC)/CLOCKS_PER_SEC; // total time
5002 runtime[1]=(double) ( start_part-start_SCC)/CLOCKS_PER_SEC;
5003 runtime[2]=(double) ( bisim_part.end_initial_part-start_part )/CLOCKS_PER_SEC;
5004 runtime[3]=(double) ( end_part-bisim_part.end_initial_part )/CLOCKS_PER_SEC;
5005 runtime[4]=(double) (end_finalizing-end_part )/CLOCKS_PER_SEC;
5006 if (runtime[0]>=60.0)
5007 {
5008 int min[sizeof(runtime)/sizeof(runtime[0])];
5009 for (unsigned i = 0; i < sizeof(runtime)/sizeof(runtime[0]); ++i)
5010 {
5011 min[i] = static_cast<int>(trunc(runtime[i] / 60.0));
5012 runtime[i] -= 60 * min[i];
5013 }
5014 if (min[0]>=60)
5015 {
5016 int h[sizeof(runtime)/sizeof(runtime[0])];
5017 for (unsigned i=0; i < sizeof(runtime)/sizeof(runtime[0]); ++i)
5018 {
5019 h[i] = min[i] / 60;
5020 min[i] %= 60;
5021 }
5022 int width = static_cast<int>(trunc(log10(h[0])) + 1);
5023
5024 mCRL2log(log::verbose) << std::fixed << std::setprecision(prec)
5025 << "Time spent on contracting SCCs: " << std::setw(width) << h[1] << "h " << std::setw(2) << min[1] << "min " << std::setw(prec+3) << runtime[1] << "s\n"
5026 "Time spent on initial partition:" << std::setw(width) << h[2] << "h " << std::setw(2) << min[2] << "min " << std::setw(prec+3) << runtime[2] << "s\n"
5027 "Time spent on refining: " << std::setw(width) << h[3] << "h " << std::setw(2) << min[3] << "min " << std::setw(prec+3) << runtime[3] << "s\n"
5028 "Time spent on finalizing: " << std::setw(width) << h[4] << "h " << std::setw(2) << min[4] << "min " << std::setw(prec+3) << runtime[4] << "s\n"
5029 "Total CPU time: " << std::setw(width) << h[0] << "h " << std::setw(2) << min[0] << "min " << std::setw(prec+3) << runtime[0] << "s\n"
5030 "BENCHMARK TIMEdnj: " << static_cast<double>(end_part-start_part)/CLOCKS_PER_SEC << "\n"
5031 << std::defaultfloat;
5032 }
5033 else
5034 {
5035 mCRL2log(log::verbose) << std::fixed << std::setprecision(prec)
5036 << "Time spent on contracting SCCs: " << std::setw(2) << min[1] << "min " << std::setw(prec+3) << runtime[1] << "s\n"
5037 "Time spent on initial partition:" << std::setw(2) << min[2] << "min " << std::setw(prec+3) << runtime[2] << "s\n"
5038 "Time spent on refining: " << std::setw(2) << min[3] << "min " << std::setw(prec+3) << runtime[3] << "s\n"
5039 "Time spent on finalizing: " << std::setw(2) << min[4] << "min " << std::setw(prec+3) << runtime[4] << "s\n"
5040 "Total CPU time: " << std::setw(2) << min[0] << "min " << std::setw(prec+3) << runtime[0] << "s\n"
5041 "BENCHMARK TIMEdnj: " << static_cast<double>(end_part-start_part)/CLOCKS_PER_SEC << "\n"
5042 << std::defaultfloat;
5043 }
5044 }
5045 else
5046 {
5047 mCRL2log(log::verbose) << std::fixed << std::setprecision(prec)
5048 << "Time spent on contracting SCCs: " << std::setw(prec+3) << runtime[1] << "s\n"
5049 "Time spent on initial partition:" << std::setw(prec+3) << runtime[2] << "s\n"
5050 "Time spent on refining: " << std::setw(prec+3) << runtime[3] << "s\n"
5051 "Time spent on finalizing: " << std::setw(prec+3) << runtime[4] << "s\n"
5052 "Total CPU time: " << std::setw(prec+3) << runtime[0] << "s\n"
5053 "BENCHMARK TIMEdnj: " << static_cast<double>(end_part-start_part)/CLOCKS_PER_SEC << "\n"
5054 << std::defaultfloat;
5055 }
5056 }
5057}
5058
5059
5060/// \brief Checks whether the two initial states of two LTSs are strong or
5061/// (divergence-preserving) branching bisimilar.
5062/// \details This routine uses the O(m log n) branching bisimulation algorithm
5063/// developed in 2018 by David N. Jansen. It runs in O(m log n) time and uses
5064/// O(m) memory, where n is the number of states and m is the number of
5065/// transitions.
5066///
5067/// The LTSs l1 and l2 are not usable anymore after this call.
5068/// \param[in,out] l1 A first transition system.
5069/// \param[in,out] l2 A second transistion system.
5070/// \param branching If true branching bisimulation is used,
5071/// otherwise strong bisimulation is
5072/// applied.
5073/// \param preserve_divergence If true and branching is true, preserve
5074/// tau loops on states.
5075/// \param generate_counter_examples (non-functional, only in the
5076/// interface for historical reasons)
5077/// \returns True iff the initial states of the transition systems l1 and l2
5078/// are ((divergence-preserving) branching) bisimilar.
5079template <class LTS_TYPE>
5080bool destructive_bisimulation_compare_dnj(LTS_TYPE& l1, LTS_TYPE& l2,
5081 bool const branching = false, bool const preserve_divergence = false,
5082 bool const generate_counter_examples = false,
5083 const std::string& /*counter_example_file*/ = "",
5084 bool /*structured_output*/ = false)
5085{
5086 if (generate_counter_examples)
5087 {
5088 mCRL2log(log::warning) << "The JGKW20 branching bisimulation "
5089 "algorithm does not generate counterexamples.\n";
5090 }
5091 std::size_t init_l2(l2.initial_state() + l1.num_states());
5092 detail::merge(l1, std::move(l2));
5093 l2.clear(); // No use for l2 anymore.
5094
5095 // Line 2.1: Find tau-SCCs and contract each of them to a single state
5096 if (branching)
5097 {
5098 scc_partitioner<LTS_TYPE> scc_part(l1);
5099 scc_part.replace_transition_system(preserve_divergence);
5100 init_l2 = scc_part.get_eq_class(init_l2);
5101 }
5102 else
5103 {
5104 assert(!preserve_divergence);
5105 }
5106 assert(1 < l1.num_states());
5107 bisim_partitioner_dnj<LTS_TYPE> bisim_part(l1, branching,
5108 preserve_divergence);
5109
5110 return bisim_part.in_same_class(l1.initial_state(), init_l2);
5111}
5112
5113
5114/// \brief Checks whether the two initial states of two LTSs are strong or
5115/// (divergence-preserving) branching bisimilar.
5116/// \details The LTSs l1 and l2 are first duplicated and subsequently reduced
5117/// modulo bisimulation. If memory is a concern, one could consider to use
5118/// destructive_bisimulation_compare(). This routine uses the O(m log n)
5119/// branching bisimulation algorithm developed in 2018 by David N. Jansen. It
5120/// runs in O(m log n) time and uses O(m) memory, where n is the number of
5121/// states and m is the number of transitions.
5122/// \param l1 A first transition system.
5123/// \param l2 A second transistion system.
5124/// \param branching If true branching bisimulation is used,
5125/// otherwise strong bisimulation is applied.
5126/// \param preserve_divergence If true and branching is true, preserve tau
5127/// loops on states.
5128/// \retval True iff the initial states of the transition systems l1 and l2
5129/// are ((divergence-preserving) branching) bisimilar.
5130template <class LTS_TYPE>
5131inline bool bisimulation_compare_dnj(const LTS_TYPE& l1, const LTS_TYPE& l2,
5132 bool const branching = false, bool const preserve_divergence = false)
5133{
5134 LTS_TYPE l1_copy(l1);
5135 LTS_TYPE l2_copy(l2);
5136 return destructive_bisimulation_compare_dnj(l1_copy, l2_copy, branching,
5137 preserve_divergence);
5138}
5139
5140///@} (end of group part_interface)
5141
5142// NOLINTEND(cppcoreguidelines-macro-usage,misc-static-assert,cppcoreguidelines-avoid-goto,cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays)
5143
5144} // end namespace detail
5145// end namespace lts
5146// end namespace mcrl2
5147
5148#endif // ifndef LIBLTS_BISIM_DNJ_H
#define mCRL2complexity(unit, call, info_for_debug)
Assigns work to a counter and checks for errors.
aterm & operator=(const aterm &other) noexcept=default
aterm(const aterm &other) noexcept=default
This class has user-declared copy constructor so declare default copy and move operators.
static constexpr std::size_t maximal_size_of_stack
std::array< unprotected_aterm_core, maximal_size_of_stack > m_stack
void initialise(const term_balanced_tree< Term > &tree)
const Term & dereference() const
Dereference operator.
bool equal(const iterator &other) const
Equality operator.
iterator(const term_balanced_tree< Term > &tree)
void increment()
Increments the iterator.
bool is_node() const
Returns true iff the tree is a node with a left and right subtree.
static void make_tree_helper(aterm &result, ForwardTraversalIterator &p, const std::size_t size, Transformer transformer)
term_balanced_tree & operator=(const term_balanced_tree &) noexcept=default
Assignment operator.
size_type size() const
Returns the size of the term_balanced_tree.
term_balanced_tree(term_balanced_tree &&) noexcept=default
Move constructor.
bool empty() const
Returns true if tree is empty.
static const aterm & empty_tree()
static void make_tree(aterm &result, ForwardTraversalIterator &p, const std::size_t size, Transformer transformer)
term_balanced_tree(ForwardTraversalIterator first, const std::size_t size)
Creates an term_balanced_tree with a copy of a range.
static const function_symbol & tree_single_node_function()
const aterm & left_branch() const
Get the left branch of the tree.
term_balanced_tree(const term_balanced_tree &) noexcept=default
Copy constructor.
term_balanced_tree(ForwardTraversalIterator first, const std::size_t size, Transformer transformer)
Creates an term_balanced_tree with a copy of a range, where a transformer is applied to each term bef...
static const function_symbol & tree_node_function()
const Term & operator[](std::size_t position) const
Element indexing operator.
iterator begin() const
Returns an iterator pointing to the beginning of the term_balanced_tree.
iterator end() const
Returns an iterator pointing to the end of the term_balanced_tree.
term_balanced_tree()
Default constructor. Creates an empty tree.
const aterm & right_branch() const
Get the left branch of the tree.
term_balanced_tree & operator=(term_balanced_tree &&) noexcept=default
Move assign operator.
term_balanced_tree(const aterm &tree)
Construction from aterm.
const Term & element_at(std::size_t position, std::size_t size) const
Get an element at the indicated position.
static const function_symbol & tree_empty_function()
friend void make_term_balanced_tree(term_balanced_tree< Term1 > &result, ForwardTraversalIterator p, std::size_t size, Transformer transformer)
term_balanced_tree(detail::_term_appl *t)
A list of aterm objects.
Definition aterm_list.h:26
A unordered_map class in which aterms can be stored.
action_formula(action_formula &&) noexcept=default
action_formula & operator=(const action_formula &) noexcept=default
action_formula(const atermpp::aterm &term)
action_formula(const data::data_expression &x)
\brief Constructor Z6.
action_formula(const action_formula &) noexcept=default
Move semantics.
action_formula & operator=(action_formula &&) noexcept=default
action_formula(const data::untyped_data_parameter &x)
\brief Constructor Z6.
action_formula()
\brief Default constructor X3.
action_formula(const process::untyped_multi_action &x)
\brief Constructor Z6.
\brief The and operator for action formulas
and_ & operator=(const and_ &) noexcept=default
and_ & operator=(and_ &&) noexcept=default
and_(const action_formula &left, const action_formula &right)
\brief Constructor Z14.
and_()
\brief Default constructor X3.
and_(and_ &&) noexcept=default
const action_formula & left() const
and_(const atermpp::aterm &term)
and_(const and_ &) noexcept=default
Move semantics.
const action_formula & right() const
\brief The at operator for action formulas
at(const atermpp::aterm &term)
const data::data_expression & time_stamp() const
at & operator=(at &&) noexcept=default
const action_formula & operand() const
at(const at &) noexcept=default
Move semantics.
at(at &&) noexcept=default
at()
\brief Default constructor X3.
at & operator=(const at &) noexcept=default
at(const action_formula &operand, const data::data_expression &time_stamp)
\brief Constructor Z14.
\brief The existential quantification operator for action formulas
exists(const atermpp::aterm &term)
exists & operator=(exists &&) noexcept=default
exists(exists &&) noexcept=default
exists(const exists &) noexcept=default
Move semantics.
exists()
\brief Default constructor X3.
const data::variable_list & variables() const
exists & operator=(const exists &) noexcept=default
const action_formula & body() const
exists(const data::variable_list &variables, const action_formula &body)
\brief Constructor Z14.
\brief The value false for action formulas
false_(const atermpp::aterm &term)
false_()
\brief Default constructor X3.
false_(false_ &&) noexcept=default
false_(const false_ &) noexcept=default
Move semantics.
false_ & operator=(const false_ &) noexcept=default
false_ & operator=(false_ &&) noexcept=default
\brief The universal quantification operator for action formulas
forall & operator=(const forall &) noexcept=default
const action_formula & body() const
forall & operator=(forall &&) noexcept=default
forall(const atermpp::aterm &term)
const data::variable_list & variables() const
forall()
\brief Default constructor X3.
forall(const data::variable_list &variables, const action_formula &body)
\brief Constructor Z14.
forall(const forall &) noexcept=default
Move semantics.
forall(forall &&) noexcept=default
\brief The implication operator for action formulas
const action_formula & left() const
imp(const imp &) noexcept=default
Move semantics.
imp(imp &&) noexcept=default
imp & operator=(imp &&) noexcept=default
imp(const action_formula &left, const action_formula &right)
\brief Constructor Z14.
imp()
\brief Default constructor X3.
imp & operator=(const imp &) noexcept=default
imp(const atermpp::aterm &term)
const action_formula & right() const
\brief The multi action for action formulas
multi_action(const multi_action &) noexcept=default
Move semantics.
multi_action(multi_action &&) noexcept=default
multi_action(const process::action_list &actions)
\brief Constructor Z14.
multi_action(const atermpp::aterm &term)
multi_action & operator=(const multi_action &) noexcept=default
multi_action()
\brief Default constructor X3.
const process::action_list & actions() const
multi_action & operator=(multi_action &&) noexcept=default
\brief The not operator for action formulas
not_(const action_formula &operand)
\brief Constructor Z14.
not_()
\brief Default constructor X3.
const action_formula & operand() const
not_(const atermpp::aterm &term)
not_(not_ &&) noexcept=default
not_(const not_ &) noexcept=default
Move semantics.
not_ & operator=(const not_ &) noexcept=default
not_ & operator=(not_ &&) noexcept=default
\brief The or operator for action formulas
or_ & operator=(const or_ &) noexcept=default
or_(or_ &&) noexcept=default
or_()
\brief Default constructor X3.
or_ & operator=(or_ &&) noexcept=default
or_(const action_formula &left, const action_formula &right)
\brief Constructor Z14.
or_(const atermpp::aterm &term)
or_(const or_ &) noexcept=default
Move semantics.
const action_formula & right() const
const action_formula & left() const
\brief The value true for action formulas
true_(true_ &&) noexcept=default
true_ & operator=(const true_ &) noexcept=default
true_()
\brief Default constructor X3.
true_(const true_ &) noexcept=default
Move semantics.
true_(const atermpp::aterm &term)
true_ & operator=(true_ &&) noexcept=default
data_expression & operator=(data_expression &&) noexcept=default
sort_expression sort() const
Returns the sort of the data expression.
Definition data.cpp:107
data_expression(const data_expression &) noexcept=default
Move semantics.
data_expression(data_expression &&) noexcept=default
Rewriter that operates on data expressions.
Definition rewriter.h:84
data_expression operator()(const data_expression &d) const
Rewrites a data expression.
Definition rewriter.h:161
void add_sort(const basic_sort &s)
Adds a sort to this specification.
\brief A data variable
Definition variable.h:25
Action rename specification.
\brief A timed multi-action
multi_action(const multi_action &) noexcept=default
Move semantics.
const process::action_list & actions() const
multi_action(const process::action_list &actions=process::action_list(), data::data_expression time=data::undefined_real())
Constructor. Actions are sorted to establish the sorted-storage invariant.
This class contains labels for probabilistic transistions, consisting of a numerator and a denumerato...
static probabilistic_data_expression one()
Constant one.
probabilistic_data_expression operator+(const probabilistic_data_expression &other) const
Standard addition operator. Note that the expression is not evaluated. For this the rewriter has to b...
probabilistic_data_expression(const data::data_expression &d)
Construct a probabilistic_data_expression from a data_expression, which must be of sort real.
bool operator==(const probabilistic_data_expression &other) const
probabilistic_data_expression(std::size_t enumerator, std::size_t denominator)
bool operator!=(const probabilistic_data_expression &other) const
bool operator>=(const probabilistic_data_expression &other) const
bool operator<(const probabilistic_data_expression &other) const
bool operator<=(const probabilistic_data_expression &other) const
bool operator>(const probabilistic_data_expression &other) const
probabilistic_data_expression(const std::string &enumerator, const std::string &denominator)
probabilistic_data_expression operator-(const probabilistic_data_expression &other) const
Standard subtraction operator.
static data::data_specification data_specification_with_real()
static probabilistic_data_expression zero()
Constant zero.
Linear process specification.
STATE & state()
Get the state in a state probability pair.
state_probability_pair(state_probability_pair &&p)=default
state_probability_pair & operator=(state_probability_pair &&p)=default
state_probability_pair(const state_probability_pair &p)=default
Copy constructor;.
state_probability_pair & operator=(const state_probability_pair &p)=default
Standard assignment.
const PROBABILITY & probability() const
get the probability from a state proability pair.
const STATE & state() const
Get the state from a state probability pair.
PROBABILITY & probability()
Set the probability in a state probability pair.
state_probability_pair(const STATE &state, const PROBABILITY &probability)
constructor.
bool operator==(const state_probability_pair &other) const
Standard equality operator.
A class containing the values for action labels for the .lts format.
Definition lts_lts.h:142
action_label_lts & operator=(const action_label_lts &)=default
Copy assignment.
void hide_actions(const std::vector< std::string > &tau_actions)
Hide the actions with labels in tau_actions.
Definition lts_lts.h:163
action_label_lts(const action_label_lts &)=default
Copy constructor.
static const action_label_lts & tau_action()
Definition lts_lts.h:179
action_label_lts(const mcrl2::lps::multi_action &a)
Constructor.
Definition lts_lts.h:155
action_label_lts()=default
Default constructor.
void set_truths(formula &f)
Compute and set the truth values of a formula f.
level_type gca_level(const block_index_type B1, const block_index_type B2)
Auxiliarry function that computes the level of the greatest common ancestor. In other words a lvl i s...
bisim_partitioner_minimal_depth(LTS_TYPE &l, const std::size_t init_l2)
Creates a bisimulation partitioner for an LTS.
mcrl2::state_formulas::state_formula dist_formula_mindepth(const std::size_t s, const std::size_t t)
Creates a state formula that distinguishes state s from state t.
formula distinguish(const block_index_type b1, const block_index_type b2)
Creates a formula that distinguishes a block b1 from the block b2.
~bisim_partitioner_minimal_depth()=default
Destroys this partitioner.
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
bool in_same_class(const std::size_t s, const std::size_t t)
block_index_type lift_block(const block_index_type B1, level_type goal)
mcrl2::state_formulas::state_formula conjunction(std::vector< formula > &conjunctions)
conjunction Creates a conjunction of state formulas
mcrl2::state_formulas::state_formula convert_formula(formula &f)
void split_BL(level_type lvl)
Performs the splits based on the blocks in Bsplit and the flags set in state_flags.
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
mcrl2::state_formulas::state_formula conjunction(std::vector< mcrl2::state_formulas::state_formula > &conjunctions)
conjunction Creates a conjunction of state formulas
regular_formulas::regular_formula make_tau_hat(regular_formulas::regular_formula &f)
void split_and_intersect(std::set< block_index_type > &truths, std::pair< block_index_type, block_index_type > liftedB1B2)
branching_bisim_partitioner_minimal_depth(LTS_TYPE &l, const std::size_t init_l2)
Creates a branching bisimulation partitioner for an LTS.
mcrl2::state_formulas::state_formula dist_formula(block_index_type block_index1, block_index_type block_index2)
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
bool is_dist(std::set< blockpair_type > &dist_blockpairs, std::set< block_index_type > &to_dist)
is_dist Checks if a given conjunction correctly exludes a set of blocks.
std::vector< mcrl2::state_formulas::state_formula > filtered_dist_conjunction(std::map< blockpair_type, mcrl2::state_formulas::state_formula > &Phi, std::set< block_index_type > &Tdist, std::set< block_index_type > &Truths)
mcrl2::state_formulas::state_formula dist_formula_mindepth(size_t s, size_t t)
Creates a state formula that distinguishes state s from state t.
bool is_dist(const std::set< blockpair_type > &dist_blockpairs, const std::set< block_index_type > &to_dist, std::set< block_index_type > &truths)
is_dist overloaded to also maintain the truth values computed at the end.
std::pair< block_index_type, block_index_type > min_split_blockpair(block_index_type b1, block_index_type b2)
function object to compare two constln_t pointers based on their contents
A class that can be used to store counterexample trees and.
lts_type type()
Provides the type of this lts, in casu lts_aut.
Definition lts_aut.h:39
bool operator==(const lts_aut_base &) const
Standard equality function.
Definition lts_aut.h:52
void swap(lts_aut_base &) noexcept
Standard swap function.
Definition lts_aut.h:45
void swap(lts_dot_base &) noexcept
The standard swap function.
Definition lts_dot.h:120
lts_type type() const
The lts_type of state_label_dot. In this case lts_dot.
Definition lts_dot.h:113
void clear()
Clear the transitions system.
Definition lts_fsm.h:134
const std::vector< std::string > & state_element_values(std::size_t idx) const
Provides the vector of strings that correspond to the values of the number at position idx in a vecto...
Definition lts_fsm.h:146
std::size_t add_state_element_value(std::size_t idx, const std::string &s)
Adds a string to the state element values for the idx-th position in a state vector....
Definition lts_fsm.h:178
void swap(lts_fsm_base &other) noexcept
Standard swap function.
Definition lts_fsm.h:123
bool operator==(const lts_fsm_base &other) const
Definition lts_fsm.h:108
lts_type type() const
The lts_type of this labelled transition system. In this case lts_fsm.
Definition lts_fsm.h:117
std::string state_element_value(std::size_t parameter_index, std::size_t element_index) const
Returns the element_index'th element for the parameter with index parameter_index.
Definition lts_fsm.h:193
std::string state_label_to_string(const state_label_fsm &l) const
Pretty print a state value of this FSM.
Definition lts_fsm.h:156
a base class for lts_lts_t and probabilistic_lts_t.
Definition lts_lts.h:268
static lts_type type()
Yields the type of this lts, in this case lts_lts.
Definition lts_lts.h:296
void set_process_parameters(const data::variable_list &params)
Set the state parameters for this LTS.
Definition lts_lts.h:354
lts_lts_base()=default
Default constructor.
bool operator==(const lts_lts_base &other) const
Standard equality function;.
Definition lts_lts.h:279
process::action_label_list m_action_decls
Definition lts_lts.h:272
void set_action_label_declarations(const process::action_label_list &decls)
Set the action label information for this LTS.
Definition lts_lts.h:318
const data::variable & process_parameter(std::size_t i) const
Returns the i-th parameter of the state vectors stored in this LTS.
Definition lts_lts.h:341
data::data_specification m_data_spec
Definition lts_lts.h:270
const data::variable_list & process_parameters() const
Return the process parameters stored in this LTS.
Definition lts_lts.h:333
void set_data(const data::data_specification &spec)
Set the mCRL2 data specification of this LTS.
Definition lts_lts.h:326
void swap(lts_lts_base &l) noexcept
Definition lts_lts.h:286
const process::action_label_list & action_label_declarations() const
Return action label declarations stored in this LTS.
Definition lts_lts.h:310
data::variable_list m_parameters
Definition lts_lts.h:271
A simple labelled transition format with only strings as action labels.
Definition lts_aut.h:67
void load(const std::string &filename)
Load the labelled transition system from a file.
void load(std::istream &is)
Load the labelled transition system from an input stream.
void save(const std::string &filename) const
Save the labelled transition system to file.
A class to contain labelled transition systems in graphviz format.
Definition lts_dot.h:132
void save(const std::string &filename) const
Save the labelled transition system to a file.
void save(std::ostream &os) const
Save the labelled transition system to a stream.
The class lts_fsm_t contains labelled transition systems in .fsm format.
Definition lts_fsm.h:254
void load(const std::string &filename)
Save the labelled transition system to file.
void save(const std::string &filename) const
Save the labelled transition system to file.
This class contains labelled transition systems in .lts format.
Definition lts_lts.h:370
lts_lts_t()=default
Creates an object containing no information.
void save(const std::string &filename) const
Save the labelled transition system to file.
void load(const std::string &filename)
Load the labelled transition system from file.
A simple labelled transition format with only strings as action labels.
Definition lts_aut.h:100
void load(const std::string &filename)
Load the labelled transition system from a file.
void load(std::istream &is)
Load the labelled transition system from an input stream.
void save(const std::string &filename) const
Save the labelled transition system to file.
A class to contain labelled transition systems in graphviz format.
Definition lts_dot.h:158
void save(std::ostream &os) const
Save the labelled transition system to a stream.
void save(const std::string &filename) const
Save the labelled transition system to a file.
The class lts_fsm_t contains labelled transition systems in .fsm format.
Definition lts_fsm.h:282
This class contains probabilistic labelled transition systems in .lts format.
Definition lts_lts.h:398
probabilistic_lts_lts_t()=default
Creates an object containing no information.
void load(const std::string &filename)
Load the labelled transition system from file.
void save(const std::string &filename) const
Save the labelled transition system to file.
A class that contains a labelled transition system.
probabilistic_lts(probabilistic_lts &&other)=default
Standard move constructor.
void set_initial_probabilistic_state(const PROBABILISTIC_STATE_T &state)
Sets the probabilistic initial state number of this LTS.
probabilistic_lts()=default
Creates an empty LTS.
const PROBABILISTIC_STATE_T & initial_probabilistic_state() const
Gets the initial state number of this LTS.
bool operator==(const probabilistic_lts &other) const
Standard equality operator.
labels_size_type num_probabilistic_states() const
Gets the number of probabilistic states of this LTS.
static constexpr bool is_probabilistic_lts
An indicator that this is a probabilistic lts.
void clear_probabilistic_states()
Clear the probabilistic states in this probabilistic transitions system.
states_size_type add_and_reset_probabilistic_state(PROBABILISTIC_STATE_T &s)
Adds a probabilistic state to this LTS and resets the state to empty.
void clear()
Clear the transitions system.
probabilistic_lts & operator=(probabilistic_lts &&other)=default
Standard assignment move operator.
void swap(probabilistic_lts &other) noexcept
Swap this lts with the supplied supplied LTS.
probabilistic_lts & operator=(const probabilistic_lts &other)=default
Standard assignment operator.
std::vector< PROBABILISTIC_STATE_T > m_probabilistic_states
probabilistic_lts(const probabilistic_lts &other)=default
Standard copy constructor.
states_size_type add_probabilistic_state(const PROBABILISTIC_STATE_T &s)
Adds a probabilistic state to this LTS.
states_size_type initial_state() const
PROBABILISTIC_STATE_T m_init_probabilistic_state
A class that contains a probabilistic state.
void set(const STATE &s)
Set this probabilistic state to a single state with probability one.
const_iterator begin() const
Gets an iterator over pairs of state and probability. This can only be used when the state is stored ...
void construct_internal_vector_representation()
Guarantee that this probabilistic state is internally stored as a vector, such that begin/end,...
probabilistic_state & operator=(const probabilistic_state &other)
Copy assignment constructor.
const_reverse_iterator rbegin() const
Gets a reverse iterator over pairs of state and probability. This can only be used when the state is ...
std::size_t size() const
Gets the number of probabilistic states in the vector representation of this state....
bool operator!=(const probabilistic_state &other) const
Standard equality operator.
iterator begin()
Gets an iterator over pairs of state and probability. This can only be used if the state is internall...
probabilistic_state & operator=(probabilistic_state &&other)=default
Move assignment operator.
STATE get() const
Get a probabilistic state if is is simple, i.e., consists of a single state.
void swap(probabilistic_state &other) noexcept
Swap this probabilistic state.
iterator end()
Gets the end iterator over pairs of state and probability.
reverse_iterator rbegin()
Gets a reverse iterator over pairs of state and probability. This can only be used if the state is in...
std::vector< state_probability_pair > m_probabilistic_state
const_iterator end() const
Gets the end iterator over pairs of state and probability.
reverse_iterator rend()
Gets the reverse end iterator over pairs of state and probability.
bool operator==(const probabilistic_state &other) const
Standard equality operator.
void clear()
Makes the probabilistic state empty.
probabilistic_state(probabilistic_state &&other)=default
Move constructor.
probabilistic_state(const STATE_PROBABILITY_PAIR_ITERATOR begin, const STATE_PROBABILITY_PAIR_ITERATOR end)
Creates a probabilistic state on the basis of state_probability_pairs.
STATE maximal_state() const
Provides the maximal state index in a probabilistic state.
probabilistic_state(const probabilistic_state &other)
Copy constructor.
void shrink_to_fit()
If a probabilistic state is ready, shrinking it to minimal size might be useful to reduce its memory ...
probabilistic_state()
Default constructor.
probabilistic_state(const STATE &s)
Constructor of a probabilistic state from a non probabilistic state.
void add(const STATE &s, const PROBABILITY &p)
Add a state with a probability to the probabilistic state.
const_reverse_iterator rend() const
Gets the reverse end iterator over pairs of state and probability.
Class for computing the signature for strong bisimulation.
Definition sigref.h:74
Class for computing the signature for branching bisimulation.
Definition sigref.h:104
Class for computing the signature for divergence preserving branching bisimulation.
Definition sigref.h:183
Signature based reductions for labelled transition systems.
Definition sigref.h:349
This class contains labels for states in dot format.
Definition lts_dot.h:34
void set_name(const std::string &s)
This method sets the name of the state label to the string s.
Definition lts_dot.h:53
std::string name() const
This method returns the string in the name field of a state label.
Definition lts_dot.h:60
std::string label() const
This method returns the label in the name field of a state label.
Definition lts_dot.h:74
void set_label(const std::string &s)
This method sets the label field of the state label to the string s.
Definition lts_dot.h:67
state_label_dot(const std::string &state_name, const std::string &state_label)
A constructor setting the name and label of this state label to the indicated values.
Definition lts_dot.h:47
std::string m_state_label
Definition lts_dot.h:37
bool operator==(const state_label_dot &l) const
Standard comparison operator, comparing both the string in the name field, as well as the one in the ...
Definition lts_dot.h:82
bool operator!=(const state_label_dot &l) const
Standard inequality operator. Just the negation of equality.
Definition lts_dot.h:89
state_label_dot()=default
The default constructor.
This class contains state labels for the fsm format.
Definition lts_fsm.h:36
state_label_fsm()=default
Default constructor. The label becomes an empty vector.
state_label_fsm(const state_label_fsm &)=default
Copy constructor.
state_label_fsm & operator=(const state_label_fsm &)=default
Copy assignment.
static state_label_fsm number_to_label(const std::size_t n)
Create a state label consisting of a number as the only list element.
Definition lts_fsm.h:67
state_label_fsm(const std::vector< std::size_t > &v)
Default constructor. The label is set to the vector v.
Definition lts_fsm.h:50
state_label_fsm operator+(const state_label_fsm &l) const
An operator to concatenate two state labels. Fsm labels cannot be concatenated. Therefore,...
Definition lts_fsm.h:56
This class contains state labels for an labelled transition system in .lts format.
Definition lts_lts.h:38
state_label_lts(const state_label_lts &)=default
Copy constructor.
state_label_lts operator+(const state_label_lts &l) const
An operator to concatenate two state labels.
Definition lts_lts.h:79
state_label_lts(const super &l)
Construct a state label out of list of balanced trees of data expressions, representing a state label...
Definition lts_lts.h:71
state_label_lts()=default
Default constructor.
state_label_lts(const lps::state &l)
Construct a state label out of a balanced tree of data expressions, representing a state label.
Definition lts_lts.h:64
state_label_lts & operator=(const state_label_lts &)=default
Copy assignment.
static state_label_lts number_to_label(const std::size_t n)
Create a state label consisting of a number as the only list element.
Definition lts_lts.h:94
state_label_lts(const CONTAINER &l)
Construct a single state label out of the elements in a container.
Definition lts_lts.h:55
Process specification consisting of a data specification, action labels, a sequence of process equati...
\brief An untyped multi action or data application
\brief The alt operator for regular formulas
alt(const atermpp::aterm &term)
alt()
\brief Default constructor X3.
alt & operator=(alt &&) noexcept=default
const regular_formula & right() const
alt(const regular_formula &left, const regular_formula &right)
\brief Constructor Z14.
alt(const alt &) noexcept=default
Move semantics.
alt(alt &&) noexcept=default
alt & operator=(const alt &) noexcept=default
const regular_formula & left() const
regular_formula()
\brief Default constructor X3.
regular_formula(const action_formulas::action_formula &x)
\brief Constructor Z6.
regular_formula(const atermpp::aterm &term)
regular_formula(const regular_formula &) noexcept=default
Move semantics.
regular_formula(const data::data_expression &x)
\brief Constructor Z6.
regular_formula & operator=(const regular_formula &) noexcept=default
regular_formula(regular_formula &&) noexcept=default
regular_formula & operator=(regular_formula &&) noexcept=default
\brief The seq operator for regular formulas
seq(const regular_formula &left, const regular_formula &right)
\brief Constructor Z14.
const regular_formula & right() const
seq & operator=(const seq &) noexcept=default
seq(const seq &) noexcept=default
Move semantics.
const regular_formula & left() const
seq(seq &&) noexcept=default
seq()
\brief Default constructor X3.
seq & operator=(seq &&) noexcept=default
seq(const atermpp::aterm &term)
\brief The 'trans or nil' operator for regular formulas
trans_or_nil & operator=(trans_or_nil &&) noexcept=default
trans_or_nil & operator=(const trans_or_nil &) noexcept=default
trans_or_nil(const trans_or_nil &) noexcept=default
Move semantics.
trans_or_nil(const regular_formula &operand)
\brief Constructor Z14.
trans_or_nil()
\brief Default constructor X3.
trans_or_nil(trans_or_nil &&) noexcept=default
trans_or_nil(const atermpp::aterm &term)
const regular_formula & operand() const
\brief The trans operator for regular formulas
trans(const atermpp::aterm &term)
trans(trans &&) noexcept=default
const regular_formula & operand() const
trans & operator=(const trans &) noexcept=default
trans & operator=(trans &&) noexcept=default
trans()
\brief Default constructor X3.
trans(const trans &) noexcept=default
Move semantics.
trans(const regular_formula &operand)
\brief Constructor Z14.
\brief An untyped regular formula or action formula
untyped_regular_formula()
\brief Default constructor X3.
untyped_regular_formula & operator=(untyped_regular_formula &&) noexcept=default
untyped_regular_formula & operator=(const untyped_regular_formula &) noexcept=default
untyped_regular_formula(const std::string &name, const regular_formula &left, const regular_formula &right)
\brief Constructor Z2.
untyped_regular_formula(const core::identifier_string &name, const regular_formula &left, const regular_formula &right)
\brief Constructor Z14.
const core::identifier_string & name() const
untyped_regular_formula(const untyped_regular_formula &) noexcept=default
Move semantics.
untyped_regular_formula(untyped_regular_formula &&) noexcept=default
\brief The and operator for state formulas
and_(and_ &&) noexcept=default
const state_formula & right() const
and_(const atermpp::aterm &term)
and_(const and_ &) noexcept=default
Move semantics.
and_(const state_formula &left, const state_formula &right)
\brief Constructor Z14.
and_ & operator=(const and_ &) noexcept=default
and_()
\brief Default constructor X3.
and_ & operator=(and_ &&) noexcept=default
const state_formula & left() const
\brief The multiply operator for state formulas with values
const_multiply_alt & operator=(const const_multiply_alt &) noexcept=default
const state_formula & left() const
const_multiply_alt(const state_formula &left, const data::data_expression &right)
\brief Constructor Z14.
const_multiply_alt(const const_multiply_alt &) noexcept=default
Move semantics.
const_multiply_alt(const_multiply_alt &&) noexcept=default
const data::data_expression & right() const
const_multiply_alt(const atermpp::aterm &term)
const_multiply_alt & operator=(const_multiply_alt &&) noexcept=default
const_multiply_alt()
\brief Default constructor X3.
\brief The multiply operator for state formulas with values
const data::data_expression & left() const
const_multiply(const const_multiply &) noexcept=default
Move semantics.
const_multiply(const data::data_expression &left, const state_formula &right)
\brief Constructor Z14.
const_multiply()
\brief Default constructor X3.
const_multiply(const_multiply &&) noexcept=default
const_multiply & operator=(const const_multiply &) noexcept=default
const_multiply & operator=(const_multiply &&) noexcept=default
const_multiply(const atermpp::aterm &term)
const state_formula & right() const
\brief The timed delay operator for state formulas
delay_timed(const atermpp::aterm &term)
delay_timed()
\brief Default constructor X3.
delay_timed & operator=(const delay_timed &) noexcept=default
const data::data_expression & time_stamp() const
delay_timed(const data::data_expression &time_stamp)
\brief Constructor Z14.
delay_timed(const delay_timed &) noexcept=default
Move semantics.
delay_timed(delay_timed &&) noexcept=default
delay_timed & operator=(delay_timed &&) noexcept=default
\brief The delay operator for state formulas
delay & operator=(delay &&) noexcept=default
delay()
\brief Default constructor X3.
delay(const delay &) noexcept=default
Move semantics.
delay(delay &&) noexcept=default
delay(const atermpp::aterm &term)
delay & operator=(const delay &) noexcept=default
\brief The existential quantification operator for state formulas
exists(const data::variable_list &variables, const state_formula &body)
\brief Constructor Z14.
const state_formula & body() const
exists(const exists &) noexcept=default
Move semantics.
exists(exists &&) noexcept=default
exists & operator=(const exists &) noexcept=default
exists & operator=(exists &&) noexcept=default
exists()
\brief Default constructor X3.
exists(const atermpp::aterm &term)
const data::variable_list & variables() const
\brief The value false for state formulas
false_(false_ &&) noexcept=default
false_ & operator=(const false_ &) noexcept=default
false_ & operator=(false_ &&) noexcept=default
false_(const atermpp::aterm &term)
false_(const false_ &) noexcept=default
Move semantics.
false_()
\brief Default constructor X3.
\brief The universal quantification operator for state formulas
const state_formula & body() const
forall(const atermpp::aterm &term)
const data::variable_list & variables() const
forall & operator=(const forall &) noexcept=default
forall & operator=(forall &&) noexcept=default
forall(const forall &) noexcept=default
Move semantics.
forall(const data::variable_list &variables, const state_formula &body)
\brief Constructor Z14.
forall(forall &&) noexcept=default
forall()
\brief Default constructor X3.
\brief The implication operator for state formulas
imp()
\brief Default constructor X3.
imp(imp &&) noexcept=default
imp(const state_formula &left, const state_formula &right)
\brief Constructor Z14.
imp & operator=(const imp &) noexcept=default
const state_formula & left() const
const state_formula & right() const
imp(const atermpp::aterm &term)
imp(const imp &) noexcept=default
Move semantics.
imp & operator=(imp &&) noexcept=default
\brief The infimum over a data type for state formulas
infimum(const infimum &) noexcept=default
Move semantics.
infimum()
\brief Default constructor X3.
infimum(const data::variable_list &variables, const state_formula &body)
\brief Constructor Z14.
infimum & operator=(infimum &&) noexcept=default
const data::variable_list & variables() const
const state_formula & body() const
infimum(const atermpp::aterm &term)
infimum(infimum &&) noexcept=default
infimum & operator=(const infimum &) noexcept=default
\brief The may operator for state formulas
const state_formula & operand() const
may()
\brief Default constructor X3.
const regular_formulas::regular_formula & formula() const
may & operator=(const may &) noexcept=default
may & operator=(may &&) noexcept=default
may(const regular_formulas::regular_formula &formula, const state_formula &operand)
\brief Constructor Z14.
may(may &&) noexcept=default
may(const atermpp::aterm &term)
may(const may &) noexcept=default
Move semantics.
\brief The minus operator for state formulas
minus & operator=(minus &&) noexcept=default
minus(minus &&) noexcept=default
minus(const minus &) noexcept=default
Move semantics.
minus(const atermpp::aterm &term)
minus(const state_formula &operand)
\brief Constructor Z14.
const state_formula & operand() const
minus & operator=(const minus &) noexcept=default
minus()
\brief Default constructor X3.
\brief The mu operator for state formulas
const core::identifier_string & name() const
const data::assignment_list & assignments() const
mu(const mu &) noexcept=default
Move semantics.
mu(const std::string &name, const data::assignment_list &assignments, const state_formula &operand)
\brief Constructor Z2.
mu(const core::identifier_string &name, const data::assignment_list &assignments, const state_formula &operand)
\brief Constructor Z14.
mu & operator=(const mu &) noexcept=default
mu(mu &&) noexcept=default
mu & operator=(mu &&) noexcept=default
mu(const atermpp::aterm &term)
mu()
\brief Default constructor X3.
const state_formula & operand() const
\brief The must operator for state formulas
must(must &&) noexcept=default
must & operator=(must &&) noexcept=default
must(const atermpp::aterm &term)
must(const regular_formulas::regular_formula &formula, const state_formula &operand)
\brief Constructor Z14.
const regular_formulas::regular_formula & formula() const
must(const must &) noexcept=default
Move semantics.
const state_formula & operand() const
must()
\brief Default constructor X3.
must & operator=(const must &) noexcept=default
\brief The not operator for state formulas
not_(not_ &&) noexcept=default
not_(const not_ &) noexcept=default
Move semantics.
not_ & operator=(const not_ &) noexcept=default
not_ & operator=(not_ &&) noexcept=default
not_()
\brief Default constructor X3.
not_(const atermpp::aterm &term)
const state_formula & operand() const
not_(const state_formula &operand)
\brief Constructor Z14.
\brief The nu operator for state formulas
nu(const atermpp::aterm &term)
nu(nu &&) noexcept=default
nu(const core::identifier_string &name, const data::assignment_list &assignments, const state_formula &operand)
\brief Constructor Z14.
nu()
\brief Default constructor X3.
nu & operator=(const nu &) noexcept=default
nu & operator=(nu &&) noexcept=default
const core::identifier_string & name() const
nu(const std::string &name, const data::assignment_list &assignments, const state_formula &operand)
\brief Constructor Z2.
const state_formula & operand() const
nu(const nu &) noexcept=default
Move semantics.
const data::assignment_list & assignments() const
\brief The or operator for state formulas
or_(or_ &&) noexcept=default
or_()
\brief Default constructor X3.
or_(const or_ &) noexcept=default
Move semantics.
or_(const state_formula &left, const state_formula &right)
\brief Constructor Z14.
or_ & operator=(const or_ &) noexcept=default
const state_formula & right() const
or_ & operator=(or_ &&) noexcept=default
or_(const atermpp::aterm &term)
const state_formula & left() const
\brief The plus operator for state formulas with values
plus & operator=(plus &&) noexcept=default
plus & operator=(const plus &) noexcept=default
plus(const plus &) noexcept=default
Move semantics.
const state_formula & left() const
plus(const atermpp::aterm &term)
plus()
\brief Default constructor X3.
const state_formula & right() const
plus(plus &&) noexcept=default
plus(const state_formula &left, const state_formula &right)
\brief Constructor Z14.
state_formula(const state_formula &) noexcept=default
Move semantics.
state_formula()
\brief Default constructor X3.
state_formula(state_formula &&) noexcept=default
bool has_time() const
Returns true if the formula is timed.
state_formula(const data::untyped_data_parameter &x)
\brief Constructor Z6.
state_formula & operator=(state_formula &&) noexcept=default
state_formula(const data::data_expression &x)
\brief Constructor Z6.
state_formula(const atermpp::aterm &term)
state_formula & operator=(const state_formula &) noexcept=default
\brief The sum over a data type for state formulas
sum(const sum &) noexcept=default
Move semantics.
sum(sum &&) noexcept=default
sum(const atermpp::aterm &term)
sum(const data::variable_list &variables, const state_formula &body)
\brief Constructor Z14.
sum & operator=(sum &&) noexcept=default
sum()
\brief Default constructor X3.
const data::variable_list & variables() const
const state_formula & body() const
sum & operator=(const sum &) noexcept=default
\brief The supremum over a data type for state formulas
supremum & operator=(supremum &&) noexcept=default
supremum(supremum &&) noexcept=default
supremum(const atermpp::aterm &term)
supremum()
\brief Default constructor X3.
supremum(const supremum &) noexcept=default
Move semantics.
supremum & operator=(const supremum &) noexcept=default
const state_formula & body() const
const data::variable_list & variables() const
supremum(const data::variable_list &variables, const state_formula &body)
\brief Constructor Z14.
\brief The value true for state formulas
true_()
\brief Default constructor X3.
true_ & operator=(const true_ &) noexcept=default
true_(true_ &&) noexcept=default
true_(const true_ &) noexcept=default
Move semantics.
true_(const atermpp::aterm &term)
true_ & operator=(true_ &&) noexcept=default
\brief The state formula variable
variable & operator=(const variable &) noexcept=default
variable(const core::identifier_string &name, const data::data_expression_list &arguments)
\brief Constructor Z14.
variable(const variable &) noexcept=default
Move semantics.
variable(const std::string &name, const data::data_expression_list &arguments)
\brief Constructor Z2.
variable()
\brief Default constructor X3.
variable & operator=(variable &&) noexcept=default
const core::identifier_string & name() const
const data::data_expression_list & arguments() const
variable(variable &&) noexcept=default
variable(const atermpp::aterm &term)
\brief The timed yaled operator for state formulas
yaled_timed(yaled_timed &&) noexcept=default
yaled_timed & operator=(const yaled_timed &) noexcept=default
yaled_timed()
\brief Default constructor X3.
yaled_timed & operator=(yaled_timed &&) noexcept=default
yaled_timed(const yaled_timed &) noexcept=default
Move semantics.
yaled_timed(const data::data_expression &time_stamp)
\brief Constructor Z14.
yaled_timed(const atermpp::aterm &term)
const data::data_expression & time_stamp() const
\brief The yaled operator for state formulas
yaled()
\brief Default constructor X3.
yaled(const atermpp::aterm &term)
yaled & operator=(const yaled &) noexcept=default
yaled(const yaled &) noexcept=default
Move semantics.
yaled(yaled &&) noexcept=default
yaled & operator=(yaled &&) noexcept=default
#define ABORT_THIS_COROUTINE()
indicates that this coroutine gives up control to the other one
Definition coroutine.h:366
#define END_COROUTINE
Ends the definition of code for a coroutine.
Definition coroutine.h:203
#define ABORT_OTHER_COROUTINE()
indicates that the other coroutine should give up control
Definition coroutine.h:381
#define COROUTINE_FOR(location, init, condition, update)
a for loop where every iteration incurs one unit of work
Definition coroutine.h:274
#define COROUTINE_WHILE(location, condition)
a while loop where every iteration incurs one unit of work
Definition coroutine.h:230
#define COROUTINES_SECTION
begin a section with two coroutines
Definition coroutine.h:145
#define COROUTINE_DO_WHILE(location, condition)
a do { } while loop where every iteration incurs one unit of work
Definition coroutine.h:317
#define END_COROUTINES_SECTION
Close a section containing coroutines.
Definition coroutine.h:211
#define COROUTINE
Define the code for a coroutine.
Definition coroutine.h:195
#define END_COROUTINE_WHILE
ends a loop started with COROUTINE_WHILE
Definition coroutine.h:255
#define END_COROUTINE_FOR
ends a loop started with COROUTINE_FOR
Definition coroutine.h:300
#define COROUTINE_LABELS(locations)
Declare the interrupt locations for the coroutines.
Definition coroutine.h:164
#define END_COROUTINE_DO_WHILE
ends a loop started with COROUTINE_DO_WHILE
Definition coroutine.h:339
#define TERMINATE_COROUTINE_SUCCESSFULLY()
terminate the pair of coroutines successfully
Definition coroutine.h:351
#define BLOCK_NO_SEQNR
#define PRINT_SG_PL(counter, sg_string, pl_string)
#define ONLY_IF_DEBUG(...)
include something in Debug mode
#define PRINT_INT_PERCENTAGE(num, denom)
#define INIT_WITHOUT_BLC_SETS
#define min_above_pivot
#define abort_if_non_bottom_size_too_large_NewBotSt(i)
#define bottom_size(coroutine)
#define linked_list
#define new_start_bottom_states(idx)
#define new_end_bottom_states(idx)
#define abort_if_size_too_large(coroutine, i)
#define non_bottom_states_NewBotSt
#define new_end_bottom_states_NewBotSt
#define abort_if_bottom_size_too_large(coroutine)
#define max_below_pivot
#define bottom_and_non_bottom_size(coroutine)
#define SPLIT_RIGHT
#define SPLIT_LEFT
#define SPLIT_SMALLER
#define mCRL2log(LEVEL)
mCRL2log(LEVEL) provides the stream used to log.
Definition logger.h:393
global_function_symbol g_tree_node("@node@", 2)
global_function_symbol g_empty("@empty@", 0)
global_function_symbol g_single_tree_node("@single_node@", 1)
std::string pp(const term_balanced_tree< Term > t)
bool is_aterm_balanced_tree(const aterm &t)
void make_term_balanced_tree(term_balanced_tree< Term > &result, ForwardTraversalIterator p, std::size_t size, Transformer transformer)
void make_exists(atermpp::aterm &t, const ARGUMENTS &... args)
void swap(or_ &t1, or_ &t2) noexcept
\brief swap overload
std::string pp(const action_formulas::exists &x, bool arg0)
bool is_at(const atermpp::aterm &x)
void swap(forall &t1, forall &t2) noexcept
\brief swap overload
std::string pp(const action_formulas::imp &x, bool arg0)
std::ostream & operator<<(std::ostream &out, const or_ &x)
std::string pp(const action_formulas::at &x, bool arg0)
std::ostream & operator<<(std::ostream &out, const action_formula &x)
std::string pp(const action_formulas::forall &x, bool arg0)
void make_not_(atermpp::aterm &t, const ARGUMENTS &... args)
std::string pp(const action_formulas::or_ &x, bool arg0)
std::string pp(const action_formulas::action_formula &x, bool arg0)
std::ostream & operator<<(std::ostream &out, const true_ &x)
std::ostream & operator<<(std::ostream &out, const exists &x)
std::ostream & operator<<(std::ostream &out, const at &x)
std::string pp(const action_formulas::true_ &x, bool arg0)
std::set< data::variable > find_all_variables(const action_formulas::action_formula &x)
bool is_or(const atermpp::aterm &x)
void swap(action_formula &t1, action_formula &t2) noexcept
\brief swap overload
bool is_true(const atermpp::aterm &x)
bool is_forall(const atermpp::aterm &x)
void swap(not_ &t1, not_ &t2) noexcept
\brief swap overload
std::string pp(const action_formulas::not_ &x, bool arg0)
std::ostream & operator<<(std::ostream &out, const and_ &x)
void swap(true_ &t1, true_ &t2) noexcept
\brief swap overload
void make_and_(atermpp::aterm &t, const ARGUMENTS &... args)
std::ostream & operator<<(std::ostream &out, const false_ &x)
bool is_false(const atermpp::aterm &x)
bool is_not(const atermpp::aterm &x)
void swap(false_ &t1, false_ &t2) noexcept
\brief swap overload
void swap(and_ &t1, and_ &t2) noexcept
\brief swap overload
void make_imp(atermpp::aterm &t, const ARGUMENTS &... args)
bool is_imp(const atermpp::aterm &x)
bool is_and(const atermpp::aterm &x)
void make_forall(atermpp::aterm &t, const ARGUMENTS &... args)
void swap(multi_action &t1, multi_action &t2) noexcept
\brief swap overload
void swap(imp &t1, imp &t2) noexcept
\brief swap overload
void make_or_(atermpp::aterm &t, const ARGUMENTS &... args)
std::ostream & operator<<(std::ostream &out, const forall &x)
void swap(exists &t1, exists &t2) noexcept
\brief swap overload
std::ostream & operator<<(std::ostream &out, const imp &x)
std::ostream & operator<<(std::ostream &out, const multi_action &x)
void make_multi_action(atermpp::aterm &t, const ARGUMENTS &... args)
bool is_multi_action(const atermpp::aterm &x)
std::ostream & operator<<(std::ostream &out, const not_ &x)
std::string pp(const action_formulas::multi_action &x, bool arg0)
void swap(at &t1, at &t2) noexcept
\brief swap overload
void make_at(atermpp::aterm &t, const ARGUMENTS &... args)
std::string pp(const action_formulas::false_ &x, bool arg0)
bool is_exists(const atermpp::aterm &x)
std::string pp(const action_formulas::and_ &x, bool arg0)
bool is_action_formula(const atermpp::aterm &x)
static data_specification const & default_specification()
Definition parse.h:28
Namespace for system defined sort bool_.
Definition bool.h:29
const function_symbol & false_()
Constructor for function symbol false.
Definition bool.h:106
const function_symbol & true_()
Constructor for function symbol true.
Definition bool.h:74
Namespace for system defined sort int_.
application cint(const data_expression &arg0)
Application of function symbol @cInt.
Definition int1.h:101
const basic_sort & int_()
Constructor for sort expression Int.
Definition int1.h:44
Namespace for system defined sort nat.
const basic_sort & nat()
Constructor for sort expression Nat.
Definition nat1.h:43
application cnat(const data_expression &arg0)
Application of function symbol @cNat.
Definition nat1.h:161
Namespace for system defined sort pos.
const basic_sort & pos()
Constructor for sort expression Pos.
Definition pos1.h:42
Namespace for system defined sort real_.
data_expression & real_one()
application creal(const data_expression &arg0, const data_expression &arg1)
Application of function symbol @cReal.
Definition real1.h:129
data_expression & real_zero()
const basic_sort & real_()
Constructor for sort expression Real.
Definition real1.h:45
application plus(const data_expression &arg0, const data_expression &arg1)
Application of function symbol +.
Definition real1.h:1112
application minus(const data_expression &arg0, const data_expression &arg1)
Application of function symbol -.
Definition real1.h:1197
bool is_data_expression(const atermpp::aterm &x)
Test for a data_expression expression.
application less(const data_expression &arg0, const data_expression &arg1)
Application of function symbol <.
Definition standard.h:254
bool is_untyped_data_parameter(const atermpp::aterm &x)
application equal_to(const data_expression &arg0, const data_expression &arg1)
Application of function symbol ==.
Definition standard.h:140
std::pair< std::set< data::variable >, std::set< data::variable > > read_write_parameters(const lps::action_summand &summand, const std::set< data::variable > &process_parameters)
Computes the read and written process parameters for the given summand.
A class that takes a linear process specification and checks all tau-summands of that LPS for conflue...
multi_action complete_multi_action(process::untyped_multi_action &x, const process::action_label_list &action_decls, const data::data_specification &data_spec=data::detail::default_specification())
Definition lps.cpp:148
void remove_common_divisor(std::size_t &enumerator, std::size_t &denominator)
void complete_action_rename_specification(action_rename_specification &x, const lps::stochastic_specification &spec)
Definition lps.cpp:166
process::untyped_multi_action parse_multi_action_new(const std::string &text)
Definition lps.cpp:130
multi_action complete_multi_action(process::untyped_multi_action &x, multi_action_type_checker &typechecker, const data::data_specification &data_spec=data::detail::default_specification())
Definition lps.cpp:140
std::size_t greatest_common_divisor(std::size_t x, std::size_t y)
action_rename_specification parse_action_rename_specification_new(const std::string &text)
Definition lps.cpp:156
The main namespace for the LPS library.
Definition constelm.h:18
specification parse_linear_process_specification(const std::string &text)
Parses a linear process specification from a string.
Definition parse.h:149
void complete_data_specification(stochastic_specification &spec)
Adds all sorts that appear in the process of l to the data specification of l.
multi_action parse_multi_action(const std::string &text, const process::action_label_list &action_decls, const data::data_specification &data_spec=data::detail::default_specification())
Parses a multi_action from a string.
Definition parse.h:67
void parse_lps(std::istream &, Specification &)
Definition parse.h:156
process::action parse_action(const std::string &text, const process::action_label_list &action_decls, const data::data_specification &data_spec=data::detail::default_specification())
Parses an action from a string.
Definition parse.h:208
void complete_data_specification(specification &spec)
Adds all sorts that appear in the process of l to the data specification of l.
std::string pp(const probabilistic_data_expression &l)
multi_action parse_multi_action(std::stringstream &in, multi_action_type_checker &typechecker, const data::data_specification &data_spec=data::detail::default_specification())
Parses a multi_action from an input stream.
Definition parse.h:53
action_rename_specification parse_action_rename_specification(std::istream &in, const lps::stochastic_specification &spec)
Parses a process specification from an input stream.
Definition parse.h:91
std::ostream & operator<<(std::ostream &out, const probabilistic_data_expression &x)
Pretty print to an outstream.
multi_action parse_multi_action(std::stringstream &in, const process::action_label_list &action_decls, const data::data_specification &data_spec=data::detail::default_specification())
Parses a multi_action from an input stream.
Definition parse.h:39
action_rename_specification parse_action_rename_specification(const std::string &spec_string, const lps::stochastic_specification &spec)
Parses an action rename specification. Parses an action rename specification. If the action rename sp...
Definition parse.h:107
void parse_lps< specification >(std::istream &from, specification &result)
Definition parse.h:163
void make_state(state &result, ForwardTraversalIterator p, const std::size_t size)
Definition state.h:33
void parse_lps< stochastic_specification >(std::istream &from, stochastic_specification &result)
Parses a stochastic linear process specification from an input stream.
Definition parse.h:180
std::string pp(const lps::state &x)
Definition state.h:44
multi_action parse_multi_action(const std::string &text, multi_action_type_checker &typechecker, const data::data_specification &data_spec=data::detail::default_specification())
Parses a multi_action from a string.
Definition parse.h:80
void parse_lps(const std::string &text, Specification &result)
Definition parse.h:194
specification parse_linear_process_specification(std::istream &spec_stream)
Parses a linear process specification from an input stream.
Definition parse.h:125
void make_state(state &result, ForwardTraversalIterator p, const std::size_t size, Transformer transformer)
Definition state.h:24
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(lts_type default_format, const std::set< lts_type > &supported)
Gives a textual list describing supported LTS formats.
Definition liblts.cpp:152
bool destructive_branching_bisimulation_compare_minimal_depth(LTS_TYPE &l1, LTS_TYPE &l2, const std::string &counter_example_file)
std::string supported_lts_formats_text(const std::set< lts_type > &supported)
Gives a textual list describing supported LTS formats.
Definition liblts.cpp:185
bool destructive_bisimulation_compare_minimal_depth(LTS_TYPE &l1, LTS_TYPE &l2, const std::string &counter_example_file)
std::string string_for_type(const lts_type type)
Gives a string representation of an LTS format.
Definition liblts.cpp:112
void unmark_explicit_divergence_transitions(LTS_TYPE &l, const std::size_t divergent_transition_label)
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
LABEL_TYPE make_divergence_label(const std::string &s)
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
std::size_t mark_explicit_divergence_transitions(LTS_TYPE &l)
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
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
std::string pp(const state_label_dot &l)
Pretty print function for a state_label_dot. Only prints the label field.
Definition lts_dot.h:97
std::string pp(const state_label_lts &label)
Pretty print a state value of this LTS.
Definition lts_lts.h:106
bool is_deterministic(const LTS_TYPE &l)
Checks whether this LTS is deterministic.
outgoing_transitions_per_state_action_t transitions_per_outgoing_state_action_pair_reversed(const std::vector< transition > &trans)
Provide the transitions as a multimap accessible per from state and label, ordered backwardly.
action_label_lts parse_lts_action(const std::string &multi_action_string, const data::data_specification &data_spec, lps::multi_action_type_checker &typechecker)
Parse a string into an action label.
Definition lts_lts.h:201
void group_transitions_on_label(std::vector< transition > &transitions, std::function< std::size_t(const transition &)> get_label, const std::size_t number_of_labels, const std::size_t tau_label_index)
std::size_t to(const outgoing_pair_t &p)
Target state of a label state pair.
std::string pp(const state_label_fsm &label)
Pretty print an fsm state label.
Definition lts_fsm.h:75
outgoing_transitions_per_state_action_t transitions_per_outgoing_state_action_pair(const std::vector< transition > &trans)
Provide the transitions as a multimap accessible per from state and label.
void sort_transitions(std::vector< transition > &transitions, const std::set< transition::size_type > &hidden_label_set, transition_sort_style ts=src_lbl_tgt)
Sorts the transitions using a sort style.
void determinise(LTS_TYPE &l)
Determinises this LTS.
std::string pp(const probabilistic_state< STATE, PROBABILITY > &l)
std::ostream & operator<<(std::ostream &out, const probabilistic_state< STATE, PROBABILITY > &l)
Pretty print to an outstream.
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.
outgoing_transitions_per_state_action_t transitions_per_outgoing_state_action_pair_reversed(const std::vector< transition > &trans, const std::set< transition::size_type > &hide_label_set)
Provide the transitions as a multimap accessible per from state and label, ordered backwardly.
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.
std::string pp(const action_label_lts &l)
Print the action label to string.
Definition lts_lts.h:188
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::es_breadth, bool preprocess=true)
Checks whether this LTS is smaller than another LTS according to a preorder.
outgoing_transitions_per_state_action_t transitions_per_outgoing_state_action_pair(const std::vector< transition > &trans, const std::set< transition::size_type > &hide_label_set)
Provide the transitions as a multimap accessible per from state and label.
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...
std::size_t label(const outgoing_pair_t &p)
Label of a pair of a label and target state.
std::size_t from(const outgoing_transitions_per_state_action_t::const_iterator &i)
From state of an iterator exploring transitions per outgoing state and action.
void group_transitions_on_label(const std::vector< transition >::iterator begin, const std::vector< transition >::iterator end, std::function< std::size_t(const transition &)> get_label, std::vector< std::pair< std::size_t, std::size_t > > &count_sum_transitions_per_action, const std::size_t tau_label_index=0, std::vector< std::size_t > &todo_stack=bogus_todo_stack)
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...
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::es_breadth, bool preprocess=true)
Checks whether this LTS is smaller than another LTS according to a preorder.
The main namespace for the Process library.
bool is_linear(const process_specification &p, bool verbose=false)
Returns true if the process specification is linear.
Definition is_linear.h:344
bool is_untyped_multi_action(const atermpp::aterm &x)
void swap(trans &t1, trans &t2) noexcept
\brief swap overload
bool is_alt(const atermpp::aterm &x)
bool is_untyped_regular_formula(const atermpp::aterm &x)
void make_trans(atermpp::aterm &t, const ARGUMENTS &... args)
std::ostream & operator<<(std::ostream &out, const regular_formula &x)
void make_seq(atermpp::aterm &t, const ARGUMENTS &... args)
void make_trans_or_nil(atermpp::aterm &t, const ARGUMENTS &... args)
bool is_trans(const atermpp::aterm &x)
std::string pp(const regular_formulas::trans &x, bool arg0)
void make_alt(atermpp::aterm &t, const ARGUMENTS &... args)
void make_untyped_regular_formula(atermpp::aterm &t, const ARGUMENTS &... args)
std::string pp(const regular_formulas::alt &x, bool arg0)
std::ostream & operator<<(std::ostream &out, const trans &x)
void swap(untyped_regular_formula &t1, untyped_regular_formula &t2) noexcept
\brief swap overload
bool is_trans_or_nil(const atermpp::aterm &x)
std::ostream & operator<<(std::ostream &out, const untyped_regular_formula &x)
bool is_regular_formula(const atermpp::aterm &x)
void swap(trans_or_nil &t1, trans_or_nil &t2) noexcept
\brief swap overload
std::ostream & operator<<(std::ostream &out, const trans_or_nil &x)
bool is_seq(const atermpp::aterm &x)
std::string pp(const regular_formulas::untyped_regular_formula &x, bool arg0)
std::string pp(const regular_formulas::seq &x, bool arg0)
std::string pp(const regular_formulas::trans_or_nil &x, bool arg0)
void swap(seq &t1, seq &t2) noexcept
\brief swap overload
std::ostream & operator<<(std::ostream &out, const seq &x)
void swap(regular_formula &t1, regular_formula &t2) noexcept
\brief swap overload
std::ostream & operator<<(std::ostream &out, const alt &x)
std::string pp(const regular_formulas::regular_formula &x, bool arg0)
void swap(alt &t1, alt &t2) noexcept
\brief swap overload
bool is_timed(const state_formula &x)
void swap(variable &t1, variable &t2) noexcept
\brief swap overload
bool is_infimum(const atermpp::aterm &x)
std::string pp(const state_formulas::nu &x, bool arg0)
std::string pp(const state_formulas::exists &x, bool arg0)
std::string pp(const state_formulas::not_ &x, bool arg0)
std::ostream & operator<<(std::ostream &out, const not_ &x)
bool is_and(const atermpp::aterm &x)
void swap(minus &t1, minus &t2) noexcept
\brief swap overload
std::ostream & operator<<(std::ostream &out, const sum &x)
std::string pp(const state_formulas::supremum &x, bool arg0)
bool is_delay_timed(const atermpp::aterm &x)
void swap(exists &t1, exists &t2) noexcept
\brief swap overload
bool is_const_multiply(const atermpp::aterm &x)
std::ostream & operator<<(std::ostream &out, const exists &x)
std::string pp(const state_formulas::must &x, bool arg0)
void swap(const_multiply_alt &t1, const_multiply_alt &t2) noexcept
\brief swap overload
bool is_minus(const atermpp::aterm &x)
void make_imp(atermpp::aterm &t, const ARGUMENTS &... args)
bool is_exists(const atermpp::aterm &x)
void swap(may &t1, may &t2) noexcept
\brief swap overload
void swap(mu &t1, mu &t2) noexcept
\brief swap overload
bool is_not(const atermpp::aterm &x)
std::string pp(const state_formulas::minus &x, bool arg0)
bool is_state_formula(const atermpp::aterm &x)
void swap(sum &t1, sum &t2) noexcept
\brief swap overload
std::ostream & operator<<(std::ostream &out, const const_multiply &x)
std::ostream & operator<<(std::ostream &out, const may &x)
void make_const_multiply(atermpp::aterm &t, const ARGUMENTS &... args)
std::ostream & operator<<(std::ostream &out, const nu &x)
void make_exists(atermpp::aterm &t, const ARGUMENTS &... args)
void swap(supremum &t1, supremum &t2) noexcept
\brief swap overload
bool is_supremum(const atermpp::aterm &x)
void swap(true_ &t1, true_ &t2) noexcept
\brief swap overload
std::ostream & operator<<(std::ostream &out, const minus &x)
bool is_must(const atermpp::aterm &x)
void swap(const_multiply &t1, const_multiply &t2) noexcept
\brief swap overload
std::set< data::variable > find_all_variables(const state_formulas::state_formula &x)
std::ostream & operator<<(std::ostream &out, const imp &x)
bool is_yaled(const atermpp::aterm &x)
std::ostream & operator<<(std::ostream &out, const mu &x)
void make_and_(atermpp::aterm &t, const ARGUMENTS &... args)
std::ostream & operator<<(std::ostream &out, const must &x)
std::ostream & operator<<(std::ostream &out, const supremum &x)
void swap(not_ &t1, not_ &t2) noexcept
\brief swap overload
std::set< data::variable > find_free_variables(const state_formulas::state_formula &x)
void swap(state_formula &t1, state_formula &t2) noexcept
\brief swap overload
bool is_true(const atermpp::aterm &x)
std::string pp(const state_formulas::true_ &x, bool arg0)
std::ostream & operator<<(std::ostream &out, const true_ &x)
std::string pp(const state_formulas::state_formula &x, bool arg0)
std::ostream & operator<<(std::ostream &out, const variable &x)
std::ostream & operator<<(std::ostream &out, const state_formula &x)
void swap(plus &t1, plus &t2) noexcept
\brief swap overload
std::string pp(const state_formulas::const_multiply &x, bool arg0)
void make_plus(atermpp::aterm &t, const ARGUMENTS &... args)
std::ostream & operator<<(std::ostream &out, const and_ &x)
std::string pp(const state_formulas::delay_timed &x, bool arg0)
void swap(yaled &t1, yaled &t2) noexcept
\brief swap overload
void swap(delay &t1, delay &t2) noexcept
\brief swap overload
bool is_variable(const atermpp::aterm &x)
void make_not_(atermpp::aterm &t, const ARGUMENTS &... args)
std::ostream & operator<<(std::ostream &out, const forall &x)
void make_infimum(atermpp::aterm &t, const ARGUMENTS &... args)
bool is_may(const atermpp::aterm &x)
std::ostream & operator<<(std::ostream &out, const yaled_timed &x)
bool is_yaled_timed(const atermpp::aterm &x)
bool is_imp(const atermpp::aterm &x)
void swap(yaled_timed &t1, yaled_timed &t2) noexcept
\brief swap overload
void make_delay_timed(atermpp::aterm &t, const ARGUMENTS &... args)
std::string pp(const state_formulas::imp &x, bool arg0)
std::ostream & operator<<(std::ostream &out, const or_ &x)
std::string pp(const state_formulas::mu &x, bool arg0)
void make_const_multiply_alt(atermpp::aterm &t, const ARGUMENTS &... args)
void make_may(atermpp::aterm &t, const ARGUMENTS &... args)
bool is_sum(const atermpp::aterm &x)
state_formulas::state_formula translate_user_notation(const state_formulas::state_formula &x)
void make_must(atermpp::aterm &t, const ARGUMENTS &... args)
state_formulas::state_formula normalize_sorts(const state_formulas::state_formula &x, const data::sort_specification &sortspec)
void swap(and_ &t1, and_ &t2) noexcept
\brief swap overload
bool is_nu(const atermpp::aterm &x)
void swap(false_ &t1, false_ &t2) noexcept
\brief swap overload
std::string pp(const state_formulas::delay &x, bool arg0)
std::ostream & operator<<(std::ostream &out, const false_ &x)
std::string pp(const state_formulas::forall &x, bool arg0)
void swap(forall &t1, forall &t2) noexcept
\brief swap overload
std::string pp(const state_formulas::sum &x, bool arg0)
void swap(delay_timed &t1, delay_timed &t2) noexcept
\brief swap overload
void swap(infimum &t1, infimum &t2) noexcept
\brief swap overload
std::ostream & operator<<(std::ostream &out, const plus &x)
std::string pp(const state_formulas::yaled &x, bool arg0)
bool is_delay(const atermpp::aterm &x)
std::ostream & operator<<(std::ostream &out, const infimum &x)
std::string pp(const state_formulas::infimum &x, bool arg0)
std::string pp(const state_formulas::or_ &x, bool arg0)
std::ostream & operator<<(std::ostream &out, const delay &x)
std::string pp(const state_formulas::may &x, bool arg0)
bool is_false(const atermpp::aterm &x)
void make_variable(atermpp::aterm &t, const ARGUMENTS &... args)
void make_nu(atermpp::aterm &t, const ARGUMENTS &... args)
void make_supremum(atermpp::aterm &t, const ARGUMENTS &... args)
void make_sum(atermpp::aterm &t, const ARGUMENTS &... args)
void swap(must &t1, must &t2) noexcept
\brief swap overload
bool is_plus(const atermpp::aterm &x)
std::ostream & operator<<(std::ostream &out, const delay_timed &x)
void swap(nu &t1, nu &t2) noexcept
\brief swap overload
std::string pp(const state_formulas::and_ &x, bool arg0)
void make_forall(atermpp::aterm &t, const ARGUMENTS &... args)
std::string pp(const state_formulas::false_ &x, bool arg0)
std::string pp(const state_formulas::const_multiply_alt &x, bool arg0)
bool is_mu(const atermpp::aterm &x)
bool is_forall(const atermpp::aterm &x)
void make_minus(atermpp::aterm &t, const ARGUMENTS &... args)
bool is_const_multiply_alt(const atermpp::aterm &x)
void swap(or_ &t1, or_ &t2) noexcept
\brief swap overload
std::string pp(const state_formulas::yaled_timed &x, bool arg0)
std::string pp(const state_formulas::plus &x, bool arg0)
bool is_or(const atermpp::aterm &x)
void make_or_(atermpp::aterm &t, const ARGUMENTS &... args)
void make_yaled_timed(atermpp::aterm &t, const ARGUMENTS &... args)
std::string pp(const state_formulas::variable &x, bool arg0)
void swap(imp &t1, imp &t2) noexcept
\brief swap overload
std::set< data::sort_expression > find_sort_expressions(const state_formulas::state_formula &x)
bool find_nil(const state_formulas::state_formula &x)
std::ostream & operator<<(std::ostream &out, const const_multiply_alt &x)
std::set< process::action_label > find_action_labels(const state_formulas::state_formula &x)
std::ostream & operator<<(std::ostream &out, const yaled &x)
void make_mu(atermpp::aterm &t, const ARGUMENTS &... args)
std::set< core::identifier_string > find_identifiers(const state_formulas::state_formula &x)
void swap(atermpp::term_balanced_tree< T > &t1, atermpp::term_balanced_tree< T > &t2) noexcept
Swaps two balanced trees.
#define USE_SIMPLE_LIST
Definition simple_list.h:60
#define USE_POOL_ALLOCATOR
Definition simple_list.h:66
static const atermpp::aterm StateMay
static const atermpp::aterm StateOr
static const atermpp::aterm UntypedRegFrm
static const atermpp::aterm StateFrm
static const atermpp::aterm StateYaled
static const atermpp::aterm RegAlt
static const atermpp::aterm ActNot
static const atermpp::aterm ActImp
static const atermpp::aterm ActTrue
static const atermpp::aterm StateInfimum
static const atermpp::aterm StateAnd
static const atermpp::aterm StateExists
static const atermpp::aterm RegTrans
static const atermpp::aterm ActOr
static const atermpp::aterm StateConstantMultiplyAlt
static const atermpp::aterm ActFrm
static const atermpp::aterm ActForall
static const atermpp::aterm StateYaledTimed
static const atermpp::aterm ActFalse
static const atermpp::aterm StateFalse
static const atermpp::aterm RegFrm
static const atermpp::aterm StateDelay
static const atermpp::aterm StatePlus
static const atermpp::aterm StateMinus
static const atermpp::aterm StateNu
static const atermpp::aterm ActAnd
static const atermpp::aterm StateDelayTimed
static const atermpp::aterm StateSupremum
static const atermpp::aterm StateSum
static const atermpp::aterm ActAt
static const atermpp::aterm ActExists
static const atermpp::aterm StateMu
static const atermpp::aterm RegTransOrNil
static const atermpp::aterm StateVar
static const atermpp::aterm StateImp
static const atermpp::aterm RegSeq
static const atermpp::aterm StateTrue
static const atermpp::aterm StateForall
static const atermpp::aterm StateMust
static const atermpp::aterm StateNot
static const atermpp::aterm ActMultAct
static const atermpp::aterm StateConstantMultiply
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)
Converts a process expression into linear process format. Use the convert member functions for this.
lps::specification convert(const process_specification &p)
Converts a process_specification into a specification. Throws non_linear_process if a non-linear sub-...
Converts a process expression into linear process format. Use the convert member functions for this.
lps::stochastic_specification convert(const process_specification &p)
Converts a process_specification into a stochastic_specification. Throws non_linear_process if a non-...
std::size_t operator()(const atermpp::term_balanced_tree< T > &t) const
std::size_t operator()(const mcrl2::lps::probabilistic_data_expression &p) const
std::size_t operator()(const mcrl2::lps::state_probability_pair< STATE, PROBABILITY > &p) const
std::size_t operator()(const mcrl2::lts::action_label_lts &as) const
Definition lts_lts.h:424
std::size_t operator()(const mcrl2::lts::probabilistic_state< STATE, PROBABILITY > &p) const