mCRL2
Loading...
Searching...
No Matches
check_complexity.cpp
Go to the documentation of this file.
1// Author(s): David N. Jansen, Radboud Universiteit, Nijmegen, The Netherlands
2//
3// Copyright: see the accompanying file COPYING or copy at
4// https://github.com/mCRL2org/mCRL2/blob/master/COPYING
5//
6// Distributed under the Boost Software License, Version 1.0.
7// (See accompanying file LICENSE_1_0.txt or copy at
8// http://www.boost.org/LICENSE_1_0.txt)
9
10/// \file lts/source/check_complexity.cpp
11///
12/// \brief helper class for time complexity checks during test runs
13///
14/// \details We use the class in this file to check whether the overall time
15/// complexity fits in O(m log n). Although it is difficult to test this in
16/// general because of the constant factor in the definition of the O()
17/// notation, it is often possible to give a (rather tight) upper bound on the
18/// number of iterations of most loops.
19///
20/// The principle of time measurement with this file is: the work done in
21/// every loop body is assigned to a state or a transition. Every state and
22/// every transition gets a counter for every loop body which is assigned
23/// to it. When the loop body is executed, the corresponding counter is
24/// increased. When increasing a counter, a new value is assigned, based on
25/// the logarithm of the size of the corresponding block (or constellation).
26/// If the new value is not actually larger than the old one, an error is
27/// raised. The new value never is allowed to become larger than log2(n), so
28/// we have: For every counter, its value is increased at most log2(n) times,
29/// and therefore no more than log2(n) steps (of a certain kind) can be
30/// assigned to any single state or transition.
31///
32/// Note that an ``increase'' is by at least 1, but it may be more than 1. If
33/// one always increases to the maximal allowed value, it is ensured that a
34/// very small block found early will only incur work that corresponds to its
35/// own size (and not to the size of the block from which it was split off).
36///
37/// To assign work to some unit, write `mCRL2complexity(unit, add_work(counter
38/// type, new counter value));` The `unit` is a state or transition; the
39/// `counter type` is a value of `enum check_complexity::counter_type` defined
40/// below; the `new counter value` typically is `check_complexity::log_n -
41/// check_complexity::ilog2(block size)`.
42///
43/// For coroutines, there is an additional provision: work can temporarily be
44/// recorded in some special counters. As soon as it becomes clear which
45/// coroutine (the one handling red states or the one handling blue states, in
46/// our case) is faster, its counters are transferred to normal counters and
47/// the counters of the other coroutine are cancelled. It is checked that not
48/// too many counters are cancelled.
49///
50/// To transfer work from a temporary to a normal counter, one uses
51/// `finalise_work()`. To cancel counters, use `cancel_work()`. These
52/// functions are not normally called directly, but through `red_is_smaller()`
53/// or `blue_is_smaller()`: If the red subblock is smaller, call
54/// `red_is_smaller(temporary counter, normal counter, new counter value)` for
55/// each state and each transition of the block that was refined; if the blue
56/// subblock is smaller, call `blue_is_smaller(...)`.
57/// After all temporary work has been handled, call `check_temporary_work()` to
58/// compare the amount of sensible work with the amount of cancelled work.
59///
60/// If the work could be assigned to one of several counters, I recommend to
61/// assign it to all of them; otherwise, it may happen that a later excess of
62/// the time budget goes unnoticed because too few counters were advanced.
63/// This, however, poses some difficulties when using temporary counters: a
64/// single unit of work should be assigned to multiple counters, but added to
65/// the balance between sensible and superfluous work only once. A variant of
66/// `add_work()`, namely `add_work_notemporary()`, can be called in that case:
67/// it assigns a special value `DONT_COUNT_TEMPORARY` to a temporary counter
68/// meaning that it should be disregarded in the calculation of the balance.
69///
70/// \author David N. Jansen, Radboud Universiteit, Nijmegen, The Netherlands
71
72
73#include "mcrl2/lts/detail/check_complexity.h"
74
75namespace mcrl2
76{
77namespace lts
78{
79namespace detail
80{
81
82#if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
83
84/// \brief binary logarithm of the state space size, rounded down
85unsigned char check_complexity::log_n = '\0';
86
87/// \brief the number of useful steps in the course of the whole algorithm
89
90/// \brief the number of cancelled steps (in aborted coroutines) in the course of the whole algorithm
92
93/// \brief the number of waiting cycles in the course of the whole algorithm
95
96#ifndef NDEBUG
97
98/// \brief indicates whether waiting cycles are allowed
99/// \details During finalising all counters of an accounting period, there is
100/// a point when waiting cycles are measured (by calling the function
101/// `check_waiting_cycles()`); from that moment until the balance is reset
102/// (using `check_temporary_work()`) one cannot insert waiting cycles.
103/// This boolean is used to check for the restriction.
105
106/// \brief the number of useful steps in the last refinement
108
109/// \brief the number of waiting cycles that have been done in the current accounting period
110/// \details After finalising all counters of an accounting period, the
111/// number of waiting cycles should not exceed the sensible work. However, in
112/// contrast to cancelled work, it is not subtracted from the balance.
114
115/// \brief names for complexity counters
116/// \details Every complexity counter (defined in check_complexity.h)
117/// corresponds to a loop in one of the places of the algorithm. Here, an
118/// English description of that place is given, together with the line number
119/// in the pseudocode in the article [Groote/Jansen/Keiren/Wijs: An O(m log n)
120/// algorithm for computing stuttering equivalence and branching bisimulation.
121/// Accepted for publication in ACM TOCL 2017].
123{
124 // block counters
125 "2.4: while C contains a nontrivial constellation",
126 "2.10: for all s in SpB",
127 "3.29: Move Blue/Red to a new block NewB (set pointers to new block)",
128 "3.29: Move Blue/Red to a new block NewB (for all states s in NewB)",
129 "3.31: for all s in NewB",
130
131 // state counters
132 "3.29: Move Blue/Red to a new block NewB (swap states)",
133
134 "3.6l: refine bottom state",
135 "3.15: refine visited state",
136
137 // temporary state counters (blue):
138 "3.6l: while Test is not empty (3.11l: s is blue)",
139 "3.15l: while Blue contains unvisited states",
140
141 // temporary state counters (red):
142 "3.15r: while Red contains unvisited states",
143
144 // new bottom state counters: every state is visited once
145 "4.8: for all bottom states s in RfnB",
146 "4.15: for all old bottom states s in RedB (self-loop)",
147
148 // B_to_C_descriptor counters
149 "2.20: for all refinable blocks RfnB",
150 "2.17: Register that inert transitions from s go to NewC (B_to_C)",
151 "4.4: for all constellations C not in R reachable from RfnB",
152
153 // transition counters: every transition is visited O(log n) times
154 "2.11: for all s_prime in in(s)",
155 "2.17: Register that inert transitions from s go to NewC (out)",
156 "2.17: Register that inert transitions from s go to NewC (swap "
157 "transitions)",
158 "3.6l: refine outgoing transition to marked state",
159
160 "3.6/3.23l: refine outgoing transition",
161 "3.29: Move Blue/Red to a new block NewB (for all transitions in out(s))",
162 "3.32r: for all s_prime in out(s)",
163
164 "3.18: refine incoming transition",
165 "3.32l: for all s_prime in in(s)",
166
167 // temporary transition counters (blue):
168 "3.6l: while Test is not empty (3.9l: s is red)",
169 "3.6l: while Test is not empty (3.9l: s is red) during postprocessing",
170 "3.18l: for all s_prime in in(s) \\ Red",
171 "3.23l: if ... s_prime has no transition to SpC",
172
173 // temporary transition counters (red):
174 "3.6r: while FromRed is not empty",
175 "3.18r: for all s_prime in in(s)",
176
177 // new bottom transition counters: every transition is visited once
178 "3.6l: refine outgoing transition (called from PostprocessNewBottom)",
179 "3.23l: refine outgoing transition (state is new bottom state)",
180 "4.4: for all transitions from new bottom states (a priori)",
181 "4.4: for all transitions from new bottom states (a posteriori)",
182 "4.12: for all blocks B with transitions to SpC that need postprocessing "
183 "(a priori)",
184 "4.12: for all blocks B with transitions to SpC that need postprocessing "
185 "(a posteriori)",
186 "4.15: for all old bottom states s in RedB",
187
188 /*---------------- counters for the bisim_jgkw algorithm ----------------*/
189
190 // block counters
191 "split_off_block()",
192 "adapt_transitions_for_new_block()",
193 "create_initial_partition()",
194
195 // state counters
196 "split(), find predecessors of a state in the smaller subblock",
197 "split(), while U-coroutine runs, find predecessors of a U-state",
198 "split(), while R-coroutine runs, find predecessors of a R-state",
199 "handle_new_noninert_transns()",
200
201 // bunch counters (only for small bunches, i. e. bunches that have been
202 // split off from a large bunch)
203 "refine_partition_until_it_becomes_stable(), find predecessors",
204
205 // block_bunch-slice counters (only for block_bunch-slices that are
206 // part of a small bunch)
207 "refine_partition_until_it_becomes_stable(), stabilize",
208 "refine_partition_until_it_becomes_stable(), stabilize for large splitter",
209 "handle_new_noninert_transns(), make block_bunch-slice without bottom "
210 "states unstable (temporary counter)",
211
212 // transition counters
213 "move_out_slice_to_new_block()",
214 "split(), handle a transition from a state in the smaller subblock",
215 "split(), handle a transition to a state in the smaller subblock",
216 "split(), while U-coroutine runs, handle a transition to a U-state",
217 "split(), while U-coroutine runs, test noninert outgoing transitions",
218 "split(), while R-coroutine runs, handle a transition from a R-state",
219 "split(), while R-coroutine runs, handle a transition to a R-state",
220 "split(), the test for noninert transitions found a new bottom state",
221 "handle_new_noninert_transns(), make block_bunch-slice with bottom states "
222 "unstable",
223 "handle_new_noninert_transns(), make block_bunch-slice without bottom "
224 "states unstable (final counter)",
225 "refine_partition_until_it_becomes_stable(), stabilize block with bottom "
226 "states for new non-inert transitions",
227 "refine_partition_until_it_becomes_stable(), stabilize block without "
228 "bottom states for new non-inert transitions",
229
230 /*----------------- counters for the bisim_gj algorithm -----------------*/
231
232 // block counters
233 "refine_partition_until_it_becomes_stable(): find a splitter",
234 "splitB(): update BLC data structure of the smaller subblock",
235
236 // state counters
237 "split_block_B_into_R_and_BminR(): carry out a split",
238 "split_block_B_into_R_and_BminR(): skip over a state",
239 "simple_splitB(): find a bottom state in the smaller subblock (i.e. in U)",
240 "simple_splitB(): find predecessors of a state in the smaller subblock",
241 "multiple_swap_states_in_block(): account for swapping states in the aborted subblock",
242 "multiple_swap_states_in_block(): swap states in the smaller subblock",
243 "simple_splitB(): find predecessors of a state in R",
244 "simple_splitB(): find predecessors of a state in U",
245 "stabilizeB(): prepare a block with new bottom states",
246 "stabilizeB(): distribute states over Phat",
247 "create_initial_partition(): set start_incoming_transitions",
248
249 // BLC slice counters
250 "refine_partition_until_it_becomes_stable(): prepare tau-co-split",
251 "refine_partition_until_it_becomes_stable(): correct the end of calM",
252 "refine_partition_until_it_becomes_stable(): execute a main split",
253 "four_way_splitB(): handle a transition in the main splitter",
254
255 // transition counters
256 "simple_splitB(): "
257 "handle a transition from a state of the smaller subblock",
258 "simple_splitB(): handle a transition to a state of the smaller subblock",
259 "refine_partition_until_it_becomes_stable(): find a cotransition",
260 "order_BLC_transitions(): sort transition",
261 "simple_splitB(): handle a transition (in the splitter) from an R-state",
262 "simple_splitB(): handle an (inert) transition to an R-state",
263 "simple_splitB(): handle an (inert) transition to a U-state",
264 "simple_splitB(): handle a transition from a potential U-state",
265 "simple_splitB(): "
266 "the test for outgoing transitions found a new bottom state",
267 "splitB(): unmark transitions out of the new bottom block",
268 "splitB(): unmark transitions out of the new bottom block "
269 "(assigning work afterwards)",
270 "stabilizeB(): initialize Qhat",
271 "stabilizeB(): initialize Qhat (assigning work afterwards)",
272 "stabilizeB(): main loop",
273 "stabilizeB(): main loop (assigning work afterwards)",
274 "create_initial_partition(): refine block"
275};
276
277
278#ifdef TEST_WORK_COUNTER_NAMES
279/// \brief helper macro for check_complexity::test_work_names()
280#define test_work_name(var, ctr)
281 do
282 {
283 assert((var) == ctr);
284 mCRL2log(log::debug) << "work_names[" #ctr "] = \""
285 << work_names[ctr] << "\".\n";
286 (var) = (enum counter_type) ((var) + 1);
287 }
288 while (0)
289
290
291/// \brief prints a message for each counter, for debugging purposes
292/// \details The function prints, for each counter, the enum constant and the
293/// string associated with these counters. The function is intended to be used
294/// for debugging: a programmer can check the output of the function and see
295/// whether each enum constant is similar to the string stored in that
296/// position. If no, confusing error messages about time budget overruns may
297/// be printed.
298///
299/// The function is not currently called. A typical place where a call could
300/// be inserted is in check_complexity::init() in check_complexity.h.
302{
304 // block counters
312
313 // state counters
318
319 // temporary state counters (blue):
323
324 // temporary state counters (red):
327
328 // new bottom state counters
332
333 // B_to_C_descriptor counters
338 // temporary B_to_C_descriptor counters
343
344 // transition counters
352
356
359
360 // temporary transition counters (blue):
367
368 // temporary transition counters (red):
372
373 // new bottom transition counters
382
383 /*---------------- counters for the bisim_jgkw algorithm ----------------*/
384
385 // block counters
391
392 // state counters
401
402 // bunch counters (only for small bunches, i. e. bunches that have been
403 // split off from a large bunch)
407
408 // block_bunch-slice counters (only for block_bunch-slices that are
409 // part of a small bunch)
418
419 // transition counters
438
439 /*----------------- counters for the bisim_gj algorithm -----------------*/
440
441 // block counters
446
447 // state counters
464
465 // BLC slice counters
475
476 // transition counters
501
503}
504#endif // #ifdef TEST_WORK_COUNTER_NAMES
505
506#endif // #ifndef NDEBUG
507
508#endif // #if !defined(NDEBUG) || defined(COUNT_WORK_BALANCE)
509
510} // end namespace detail
511} // end namespace lts
512} // end namespace mcrl2
class for time complexity checks
static signed_trans_type sensible_work
counter to register the work balance for coroutines
static unsigned char log_n
value of floor(log2(n)) for easy access
static trans_type sensible_work_grand_total
the number of useful steps in the course of the whole algorithm
static trans_type no_of_waiting_cycles_grand_total
the number of waiting cycles in the course of the whole algorithm
static trans_type cancelled_work_grand_total
the number of cancelled steps (in aborted coroutines) in the course of the whole algorithm
static const char * work_names[TRANS_gj_MAX - BLOCK_MIN+1]
printable names of the counter types (for error messages)
static bool cannot_wait_before_reset
indicates whether waiting cycles are allowed
static trans_type no_of_waiting_cycles
the number of waiting cycles that have been done in the current accounting period
A base class for the lts_dot labelled transition system.
std::size_t trans_type
type used to store transition (numbers and) counts
std::make_signed< trans_type >::type signed_trans_type
type used to store differences between transition counters
The main LTS namespace.