Storm 1.14.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
Partition.h
Go to the documentation of this file.
1#pragma once
2
3#include <cstddef>
4#include <map>
5#include <ranges>
6#include <set>
7#include <span>
8
11
12namespace storm::bisimulation {
13
20class Partition {
21 public:
23 using ElementIndex = uint64_t;
24
27 using Block = std::span<ElementIndex const>;
28
33 struct BlockCompare {
34 bool operator()(Block const& lhs, Block const& rhs) const {
35 if (lhs.size() < rhs.size()) {
36 return true;
37 }
38 if (lhs.size() > rhs.size()) {
39 return false;
40 }
41 return lhs.data() < rhs.data();
42 }
43 };
44
48 using OrderedBlockSet = std::set<Block, BlockCompare>;
49
53 template<typename T>
54 using OrderedBlockMap = std::map<Block, T, BlockCompare>;
55
63 public:
67 explicit NonSuperBlockSet(Partition const& partition);
68
72 std::size_t size() const;
73
77 bool empty() const;
78
82 bool contains(Block const& block) const;
83
88 void insert(Block const& block);
89
94 Block pop();
95
96 private:
97 Partition const& partition;
98 std::vector<uint64_t> containedBlockIndices;
99 storm::storage::BitVector blockIndices;
100 };
101
102 Partition() = default;
103 Partition(Partition const& other) = default;
104 Partition& operator=(Partition const& other) = default;
105 Partition(Partition&& other) noexcept = default;
106 Partition& operator=(Partition&& other) = default;
107
113 explicit Partition(ElementIndex numElements);
114
118 std::size_t getNumberOfBlocks() const;
119
123 std::size_t getNumberOfElements() const;
124
128 Block getUniversalBlock() const;
129
136 Block getBlockOfElement(ElementIndex element) const;
137
142 bool contains(Block const& block, ElementIndex element) const;
143
148 bool isSubBlockOf(Block const& subblock, Block const& superblock) const;
149
154 bool isEqualBlock(Block const& lhs, Block const& rhs) const;
155
160 bool isSameBlock(ElementIndex const& element1, ElementIndex const& element2) const;
161
166 bool isBlockOfElement(Block const& block, ElementIndex const& element) const;
167
171 bool checkBlockValidity(Block const& block) const;
172
176 bool isProperSuperBlock(Block const& block) const;
177
182 template<typename Func>
183 requires std::invocable<Func, Block>
184 void forEachBlock(Func const& f) const {
186 }
187
194 template<typename Func>
195 requires std::invocable<Func, Block>
196 void forEachSubBlock(Block const& superBlock, Func const& f) const {
197 STORM_LOG_ASSERT(!superBlock.empty(), "Superblock is empty.");
198 auto superBlockEnd = getBlockIndex(superBlock) + superBlock.size();
199 for (BlockIndex blockStart = getBlockIndex(superBlock); blockStart < superBlockEnd;) {
200 auto const& subBlock = getBlockOfElement(blockContents[blockStart]);
201 blockStart += subBlock.size();
202 f(subBlock);
203 }
204 }
205
213 template<typename SplittingOrder>
214 requires std::invocable<SplittingOrder, ElementIndex, ElementIndex>
215 bool splitBlockByOrder(Block const& block, SplittingOrder const& less) {
216 return splitBlockByOrder(block, less, less);
217 }
218
230 template<typename Order, typename Condition>
231 requires std::invocable<Order, ElementIndex, ElementIndex> && std::invocable<Condition, ElementIndex, ElementIndex>
232 bool splitBlockByOrder(Block const& block, Order const& less, Condition const& splitCondition) {
233 if (block.size() <= 1) {
234 return false; // nothing to do
235 }
236
237 // Sort the contents of the block first
238 STORM_LOG_ASSERT(!isProperSuperBlock(block), "Tried to split a block that consists of multiple sub-blocks.");
239 auto const blockStart = getBlockIndex(block);
240
241 auto const blockEnd = blockStart + block.size();
242 std::sort(blockContents.begin() + blockStart, blockContents.begin() + blockEnd, less);
243
244 // update the inverse after sorting
245 for (ElementIndex i = blockStart; i < blockEnd; ++i) {
246 blockContentsInverse[blockContents[i]] = i;
247 }
248
249 // Catch the special case where there is no split
250 if (!splitCondition(blockContents[blockStart], blockContents[blockEnd - 1])) {
251 return false; // nothing to do
252 }
253
254 // helper function to find the end index of a current block
255 auto getEndOfBlock = [this, splitCondition, blockEnd](BlockIndex const currIndex) {
256 for (auto i = currIndex + 1; i < blockEnd; ++i) {
257 if (splitCondition(blockContents[currIndex], blockContents[i])) {
258 return i;
259 }
260 }
261 return blockEnd;
262 };
263
264 // Now create new blocks whenever the order changes
265 for (auto newBlockIndex = blockStart; newBlockIndex < blockEnd;) {
266 auto const newBlockEnd = getEndOfBlock(newBlockIndex);
267 registerNewBlock(newBlockIndex, newBlockEnd);
268 newBlockIndex = newBlockEnd;
269 ++numBlocks;
270 }
271 --numBlocks; // the old superblock is no longer a block as counted by this partition
272 STORM_LOG_ASSERT(isProperSuperBlock(block), "Partition in inconsistent state: Block was not split into multiple sub-blocks.");
273 STORM_LOG_ASSERT(checkBlockValidity(block), "Partition in inconsistent state: Block is not valid.");
274 return true; // there must have been a split because the case without a split is already caught above
275 }
276
286 template<typename Condition>
287 requires std::invocable<Condition, ElementIndex, ElementIndex>
288 bool splitBlockByClustering(Block const& block, Condition const& condition) {
289 STORM_LOG_ASSERT(!isProperSuperBlock(block), "Tried to split a block that consists of multiple sub-blocks.");
290 if (block.size() <= 1) {
291 return false; // nothing to do
292 }
293
294 auto const blockStart = getBlockIndex(block);
295 auto const blockEnd = blockStart + block.size();
296
297 // Swaps the elements in [start, blockEnd) so that those compatible (!condition) with the anchor at start directly follow it.
298 // @return mid such that all elements in [start, mid) are compatible with the anchor (located at start) and those at [mid, blockEnd) are not.
299 auto const partitionBlock = [this, &condition, &blockEnd](BlockIndex const start) {
300 // The anchor itself stays in place, so we only partition the elements after it.
301 auto const anchor = blockContents[start];
302 return partitionBlockContents(start + 1, blockEnd, [&condition, &anchor](ElementIndex const element) { return condition(anchor, element); });
303 };
304
305 for (uint64_t start = blockStart; start < blockEnd;) {
306 auto const currEnd = partitionBlock(start);
307 // Catch the special case where there is no split
308 if (start == blockStart && currEnd == blockEnd) {
309 return false; // nothing to do
310 }
311 registerNewBlock(start, currEnd);
312 start = currEnd;
313 ++numBlocks;
314 }
315 --numBlocks; // the old superblock is no longer a block as counted by this partition
316 STORM_LOG_ASSERT(isProperSuperBlock(block), "Partition in inconsistent state: Block was not split into multiple sub-blocks.");
317 STORM_LOG_ASSERT(checkBlockValidity(block), "Partition in inconsistent state: Block is not valid.");
318 return true; // there must have been a split because the case without a split is already caught above
319 }
320
327 template<typename SplittingPredicate>
328 requires std::predicate<SplittingPredicate, ElementIndex>
329 std::pair<Block, Block> splitBlockByPredicate(Block const& block, SplittingPredicate const& f) {
330 STORM_LOG_ASSERT(!isProperSuperBlock(block), "Tried to split a block that consists of multiple sub-blocks.");
331 STORM_LOG_ASSERT(!block.empty(), "Tried to split an empty block");
332
333 // swap the block contents so that all elements e with f(e) == false come first
334 auto const blockStart = getBlockIndex(block);
335 auto const blockEnd = blockStart + block.size();
336 auto const l = partitionBlockContents(blockStart, blockEnd, f);
337
338 // Handle cases where there is no split
339 if (l == blockStart) {
340 return {Block{}, block}; // all elements are true
341 } else if (l == blockEnd) {
342 return {block, Block{}}; // all elements are false
343 }
344
345 // Perform a split
346 Block noBlock = registerNewBlock(blockStart, l);
347 Block yesBlock = registerNewBlock(l, blockEnd);
348 ++numBlocks; // We have split a single block into two blocks, so the total number increments by 1
349 return {noBlock, yesBlock};
350 }
351
359 template<typename SplitRange>
360 requires std::ranges::input_range<std::remove_reference_t<SplitRange>> &&
361 std::same_as<std::ranges::range_value_t<std::remove_reference_t<SplitRange>>, ElementIndex>
362 std::pair<Block, Block> splitBlockByRange(Block const& block, SplitRange&& r) {
363 STORM_LOG_ASSERT(!isProperSuperBlock(block), "Tried to split a block that consists of multiple sub-blocks.");
364 STORM_LOG_ASSERT(!block.empty(), "Tried to split an empty block");
365
366 auto const blockStart = getBlockIndex(block);
367 auto const blockEnd = blockStart + block.size();
368
369 // We split the block into a "no"-part and a "yes"-part
370
371 auto yesBlockStart = blockEnd;
372 for (ElementIndex const element : r) {
373 auto const src = blockContentsInverse[element];
374 if (src < blockStart || src >= yesBlockStart) {
375 continue; // element is either not in the block or already occurred before (duplicate in r)
376 }
377 // swap the element to the end of the block, into the 'yes' part
378 --yesBlockStart;
379 std::swap(blockContents[src], blockContents[yesBlockStart]);
380 blockContentsInverse[blockContents[src]] = src;
381 blockContentsInverse[blockContents[yesBlockStart]] = yesBlockStart;
382 }
383
384 // Handle cases where there is no split
385 if (yesBlockStart == blockStart) {
386 return {Block{}, block}; // all elements are in the range
387 } else if (yesBlockStart == blockEnd) {
388 return {block, Block{}}; // no elements are in the range
389 }
390
391 // Perform a split
392 Block noBlock = registerNewBlock(blockStart, yesBlockStart);
393 Block yesBlock = registerNewBlock(yesBlockStart, blockEnd);
394 ++numBlocks; // We have split a single block into two blocks, so the total number increments by 1
395 return {noBlock, yesBlock};
396 }
397
398 private:
403 using BlockIndex = uint64_t;
404
409 template<typename SplittingPredicate>
410 requires std::predicate<SplittingPredicate, ElementIndex>
411 BlockIndex partitionBlockContents(BlockIndex const start, BlockIndex const end, SplittingPredicate const& f) {
412 STORM_LOG_ASSERT(start <= end, "Tried to partition an invalid range.");
413 if (start == end) {
414 return start; // empty range, nothing to swap
415 }
416 auto l = start;
417 auto r = end - 1;
418 // Loop invariant: all elements at position < l are false, all elements at position > r are true
419 while (l <= r) {
420 while (l <= r && !f(std::as_const(blockContents[l]))) {
421 ++l;
422 }
423 if (l > r) {
424 break; // no more elements to swap
425 }
426 // At this point we know that f(blockContents[l]) == true
427 while (l < r && f(std::as_const(blockContents[r]))) {
428 --r;
429 }
430 if (l == r) {
431 // We have f(blockContents[r]) == f(blockContents[l]) == true
432 --r;
433 break; // l > r holds now
434 }
435 std::swap(blockContents[l], blockContents[r]);
436 blockContentsInverse[blockContents[l]] = l;
437 blockContentsInverse[blockContents[r]] = r;
438 ++l;
439 --r;
440 }
441 STORM_LOG_ASSERT(l == r + 1, "Unexpected indices");
442 return l;
443 }
444
450 BlockIndex getBlockIndex(Block const& block) const;
451
456 Block getBlockFromIndex(BlockIndex blockIndex) const;
457
466 Block registerNewBlock(BlockIndex const start, BlockIndex const end);
467
470 std::vector<ElementIndex> blockContents;
471
474 std::vector<BlockIndex> blockContentsInverse;
475
477 std::vector<BlockIndex> elementToBlockIndex;
478
481 std::vector<std::size_t> blockSizes;
482
484 uint64_t numBlocks = 0;
485};
486
487std::ostream& operator<<(std::ostream& os, const Partition& partition);
488
489} // namespace storm::bisimulation
NonSuperBlockSet(Partition const &partition)
Creates an empty set associated with the given partition.
Definition Partition.cpp:20
void insert(Block const &block)
Inserts the given block into the set.
Definition Partition.cpp:34
Block pop()
Removes and returns an arbitrary block from the set.
Definition Partition.cpp:43
bool contains(Block const &block) const
Definition Partition.cpp:30
Represents a partition of a set of consecutive indices.
Definition Partition.h:20
bool contains(Block const &block, ElementIndex element) const
Definition Partition.cpp:79
bool splitBlockByClustering(Block const &block, Condition const &condition)
Splits the given block by anchor-based clustering: repeatedly picks the first not-yet-grouped element...
Definition Partition.h:288
std::map< Block, T, BlockCompare > OrderedBlockMap
A map of blocks, ordered by their size (smallest first).
Definition Partition.h:54
void forEachBlock(Func const &f) const
Applies the given function on each block in the partition.
Definition Partition.h:184
uint64_t ElementIndex
The type used to index the elements of the partition.
Definition Partition.h:23
std::size_t getNumberOfBlocks() const
Retrieves the number of blocks.
Definition Partition.cpp:61
void forEachSubBlock(Block const &superBlock, Func const &f) const
Applies the given function on each block in the given super block.
Definition Partition.h:196
bool isSameBlock(ElementIndex const &element1, ElementIndex const &element2) const
Definition Partition.cpp:97
Partition(Partition const &other)=default
bool isBlockOfElement(Block const &block, ElementIndex const &element) const
std::size_t getNumberOfElements() const
Retrieves the number of elements.
Definition Partition.cpp:66
bool splitBlockByOrder(Block const &block, SplittingOrder const &less)
Splits the given block according to the given order.
Definition Partition.h:215
std::span< ElementIndex const > Block
A contiguous range of elements that form a (possibly proper super-) block of the partition.
Definition Partition.h:27
bool isSubBlockOf(Block const &subblock, Block const &superblock) const
Definition Partition.cpp:84
Block getBlockOfElement(ElementIndex element) const
Definition Partition.cpp:74
Partition(Partition &&other) noexcept=default
bool splitBlockByOrder(Block const &block, Order const &less, Condition const &splitCondition)
Splits the given block according to the given order and splitting condition.
Definition Partition.h:232
std::pair< Block, Block > splitBlockByRange(Block const &block, SplitRange &&r)
Splits the given block according to the given range of elements.
Definition Partition.h:362
std::set< Block, BlockCompare > OrderedBlockSet
A set of blocks, ordered by their size (smallest first).
Definition Partition.h:48
bool isProperSuperBlock(Block const &block) const
Partition & operator=(Partition const &other)=default
Partition & operator=(Partition &&other)=default
bool isEqualBlock(Block const &lhs, Block const &rhs) const
Definition Partition.cpp:91
bool checkBlockValidity(Block const &block) const
Checks if the given block is a valid block in the partition.
std::pair< Block, Block > splitBlockByPredicate(Block const &block, SplittingPredicate const &f)
Splits the given block according to the given predicate.
Definition Partition.h:329
A bit vector that is internally represented as a vector of 64-bit values.
Definition BitVector.h:16
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:9
std::ostream & operator<<(std::ostream &os, const Partition &partition)
Orders blocks by their size (ascending) and, for equally sized blocks, by their location in memory.
Definition Partition.h:33
bool operator()(Block const &lhs, Block const &rhs) const
Definition Partition.h:34