Storm 1.11.1.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
NondeterministicModelBisimulationDecomposition.cpp
Go to the documentation of this file.
2
8
9namespace storm {
10namespace storage {
11
12using namespace bisimulation;
13
14template<typename ModelType>
16 ModelType const& model,
18 : BisimulationDecomposition<ModelType, NondeterministicModelBisimulationDecomposition::BlockDataType>(model, model.getTransitionMatrix().transpose(false),
19 options),
20 choiceToStateMapping(model.getNumberOfChoices()),
21 quotientDistributions(model.getNumberOfChoices()),
22 orderedQuotientDistributions(model.getNumberOfChoices()) {
23 STORM_LOG_THROW(options.getType() == BisimulationType::Strong, storm::exceptions::IllegalFunctionCallException,
24 "Weak bisimulation is currently not supported for nondeterministic models.");
25}
26
27template<typename ModelType>
28std::pair<storm::storage::BitVector, storm::storage::BitVector> NondeterministicModelBisimulationDecomposition<ModelType>::getStatesWithProbability01() {
29 STORM_LOG_THROW(this->options.isOptimizationDirectionSet(), storm::exceptions::IllegalFunctionCallException,
30 "Can only compute states with probability 0/1 with an optimization direction (min/max).");
31 if (this->options.getOptimizationDirection() == OptimizationDirection::Minimize) {
32 return storm::utility::graph::performProb01Min(this->model.getTransitionMatrix(), this->model.getTransitionMatrix().getRowGroupIndices(),
33 this->model.getBackwardTransitions(), this->options.phiStates.get(), this->options.psiStates.get());
34 } else {
35 return storm::utility::graph::performProb01Max(this->model.getTransitionMatrix(), this->model.getTransitionMatrix().getRowGroupIndices(),
36 this->model.getBackwardTransitions(), this->options.phiStates.get(), this->options.psiStates.get());
37 }
38}
39
40template<typename ModelType>
42 this->createChoiceToStateMapping();
43 this->initializeQuotientDistributions();
44}
45
46template<typename ModelType>
48 std::vector<uint_fast64_t> nondeterministicChoiceIndices = this->model.getTransitionMatrix().getRowGroupIndices();
49 for (storm::storage::sparse::state_type state = 0; state < this->model.getNumberOfStates(); ++state) {
50 for (uint_fast64_t choice = nondeterministicChoiceIndices[state]; choice < nondeterministicChoiceIndices[state + 1]; ++choice) {
51 choiceToStateMapping[choice] = state;
52 }
53 }
54}
55
56template<typename ModelType>
57void NondeterministicModelBisimulationDecomposition<ModelType>::initializeQuotientDistributions() {
58 std::vector<uint_fast64_t> nondeterministicChoiceIndices = this->model.getTransitionMatrix().getRowGroupIndices();
59
60 for (auto const& block : this->partition.getBlocks()) {
61 if (block->data().absorbing()) {
62 // If the block is marked as absorbing, we need to create the corresponding distributions.
63 for (auto stateIt = this->partition.begin(*block), stateIte = this->partition.end(*block); stateIt != stateIte; ++stateIt) {
64 for (uint_fast64_t choice = nondeterministicChoiceIndices[*stateIt]; choice < nondeterministicChoiceIndices[*stateIt + 1]; ++choice) {
65 this->quotientDistributions[choice].addProbability(block->getId(), storm::utility::one<ValueType>());
66 orderedQuotientDistributions[choice] = &this->quotientDistributions[choice];
67 }
68 }
69 } else {
70 // Otherwise, we compute the probabilities from the transition matrix.
71 for (auto stateIt = this->partition.begin(*block), stateIte = this->partition.end(*block); stateIt != stateIte; ++stateIt) {
72 for (uint_fast64_t choice = nondeterministicChoiceIndices[*stateIt]; choice < nondeterministicChoiceIndices[*stateIt + 1]; ++choice) {
73 if (this->options.getKeepRewards() && this->model.hasRewardModel()) {
74 auto const& rewardModel = this->model.getUniqueRewardModel();
75 if (rewardModel.hasStateActionRewards()) {
76 this->quotientDistributions[choice].setReward(rewardModel.getStateActionReward(choice));
77 }
78 }
79 for (auto entry : this->model.getTransitionMatrix().getRow(choice)) {
80 if (!this->comparator.isZero(entry.getValue())) {
81 this->quotientDistributions[choice].addProbability(this->partition.getBlock(entry.getColumn()).getId(), entry.getValue());
82 }
83 }
84 orderedQuotientDistributions[choice] = &this->quotientDistributions[choice];
85 }
86 }
87 }
88 }
89
90 for (decltype(this->model.getNumberOfStates()) state = 0; state < this->model.getNumberOfStates(); ++state) {
91 updateOrderedQuotientDistributions(state);
92 }
93}
94
95template<typename ModelType>
96void NondeterministicModelBisimulationDecomposition<ModelType>::updateOrderedQuotientDistributions(storm::storage::sparse::state_type state) {
97 std::vector<uint_fast64_t> nondeterministicChoiceIndices = this->model.getTransitionMatrix().getRowGroupIndices();
98 std::sort(this->orderedQuotientDistributions.begin() + nondeterministicChoiceIndices[state],
99 this->orderedQuotientDistributions.begin() + nondeterministicChoiceIndices[state + 1],
101 return dist1->less(*dist2, this->comparator);
102 });
103}
104
105template<typename ModelType>
107 // In order to create the quotient model, we need to construct
108 // (a) the new transition matrix,
109 // (b) the new labeling,
110 // (c) the new reward structures.
111
112 // Prepare a matrix builder for (a).
113 storm::storage::SparseMatrixBuilder<ValueType> builder(0, this->size(), 0, false, true, this->size());
114
115 // Prepare the new state labeling for (b).
116 storm::models::sparse::StateLabeling newLabeling(this->size());
117 std::set<std::string> atomicPropositionsSet = this->options.respectedAtomicPropositions.get();
118 atomicPropositionsSet.insert("init");
119 std::vector<std::string> atomicPropositions = std::vector<std::string>(atomicPropositionsSet.begin(), atomicPropositionsSet.end());
120 for (auto const& ap : atomicPropositions) {
121 newLabeling.addLabel(ap);
122 }
123
124 // If the model had state (action) rewards, we need to build the state rewards for the quotient as well.
125 std::optional<std::vector<ValueType>> stateRewards;
126 std::optional<std::vector<ValueType>> stateActionRewards;
127 if (this->options.getKeepRewards() && this->model.hasRewardModel()) {
128 if (this->model.getUniqueRewardModel().hasStateRewards()) {
129 stateRewards = std::vector<ValueType>(this->blocks.size());
130 }
131 if (this->model.getUniqueRewardModel().hasStateActionRewards()) {
132 stateActionRewards = std::vector<ValueType>();
133 }
134 }
135
136 // Now build (a) and (b) by traversing all blocks.
137 uint_fast64_t currentRow = 0;
138 std::vector<uint_fast64_t> nondeterministicChoiceIndices = this->model.getTransitionMatrix().getRowGroupIndices();
139 for (uint_fast64_t blockIndex = 0; blockIndex < this->blocks.size(); ++blockIndex) {
140 auto const& block = this->blocks[blockIndex];
141
142 // Open new row group for the new meta state.
143 builder.newRowGroup(currentRow);
144
145 // Pick one representative state. For strong bisimulation it doesn't matter which state it is, because
146 // they all behave equally.
147 storm::storage::sparse::state_type representativeState = *block.begin();
148 Block<BlockDataType> const& oldBlock = this->partition.getBlock(representativeState);
149
150 // If the block is absorbing, we simply add a self-loop.
151 if (oldBlock.data().absorbing()) {
152 builder.addNextValue(currentRow, blockIndex, storm::utility::one<ValueType>());
153 ++currentRow;
154
155 // If the block has a special representative state, we retrieve it now.
156 if (oldBlock.data().hasRepresentativeState()) {
157 representativeState = oldBlock.data().representativeState();
158 }
159
160 // Give the choice a reward of zero as we artificially introduced that the block is absorbing.
161 if (this->options.getKeepRewards() && this->model.hasRewardModel() && this->model.getUniqueRewardModel().hasStateActionRewards()) {
162 stateActionRewards.value().push_back(storm::utility::zero<ValueType>());
163 }
164
165 // Add all of the selected atomic propositions that hold in the representative state to the state
166 // representing the block.
167 for (auto const& ap : atomicPropositions) {
168 if (this->model.getStateLabeling().getStateHasLabel(ap, representativeState)) {
169 newLabeling.addLabelToState(ap, blockIndex);
170 }
171 }
172 } else {
173 // Add the outgoing choices of the block.
174 for (uint_fast64_t choice = nondeterministicChoiceIndices[representativeState]; choice < nondeterministicChoiceIndices[representativeState + 1];
175 ++choice) {
176 // If the choice is the same as the last one, we do not need to add it.
177 if (choice > nondeterministicChoiceIndices[representativeState] &&
178 quotientDistributions[choice - 1].equals(quotientDistributions[choice], this->comparator)) {
179 continue;
180 }
181
182 for (auto entry : quotientDistributions[choice]) {
183 builder.addNextValue(currentRow, entry.first, entry.second);
184 }
185 if (this->options.getKeepRewards() && this->model.hasRewardModel() && this->model.getUniqueRewardModel().hasStateActionRewards()) {
186 stateActionRewards.value().push_back(quotientDistributions[choice].getReward());
187 }
188 ++currentRow;
189 }
190
191 // Otherwise add all atomic propositions to the equivalence class that the representative state
192 // satisfies.
193 for (auto const& ap : atomicPropositions) {
194 if (this->model.getStateLabeling().getStateHasLabel(ap, representativeState)) {
195 newLabeling.addLabelToState(ap, blockIndex);
196 }
197 }
198 }
199
200 // If the model has state rewards, we simply copy the state reward of the representative state, because
201 // all states in a block are guaranteed to have the same state reward.
202 if (this->options.getKeepRewards() && this->model.hasRewardModel() && this->model.getUniqueRewardModel().hasStateRewards()) {
203 stateRewards.value()[blockIndex] = this->model.getUniqueRewardModel().getStateRewardVector()[representativeState];
204 }
205 }
206
207 // Now check which of the blocks of the partition contain at least one initial state.
208 for (auto initialState : this->model.getInitialStates()) {
209 Block<BlockDataType> const& initialBlock = this->partition.getBlock(initialState);
210 newLabeling.addLabelToState("init", initialBlock.getId());
211 }
212
213 // Construct the reward model mapping.
214 std::unordered_map<std::string, typename ModelType::RewardModelType> rewardModels;
215 if (this->options.getKeepRewards() && this->model.hasRewardModel()) {
216 STORM_LOG_THROW(this->model.hasUniqueRewardModel(), storm::exceptions::IllegalFunctionCallException, "Cannot preserve more than one reward model.");
217 typename std::unordered_map<std::string, typename ModelType::RewardModelType>::const_iterator nameRewardModelPair =
218 this->model.getRewardModels().begin();
219 rewardModels.insert(std::make_pair(nameRewardModelPair->first, typename ModelType::RewardModelType(stateRewards, stateActionRewards)));
220 }
221
222 // Finally construct the quotient model.
223 this->quotient = std::make_shared<ModelType>(builder.build(0, this->size(), this->size()), std::move(newLabeling), std::move(rewardModels));
224}
225
226template<typename ModelType>
228 return block.getNumberOfStates() > 1 && !block.data().absorbing();
229}
230
231template<typename ModelType>
232void NondeterministicModelBisimulationDecomposition<ModelType>::updateQuotientDistributionsOfPredecessors(Block<BlockDataType> const& newBlock,
233 Block<BlockDataType> const& oldBlock,
234 std::vector<Block<BlockDataType>*>& splitterQueue) {
235 uint_fast64_t lastState = 0;
236 bool lastStateInitialized = false;
237
238 for (auto stateIt = this->partition.begin(newBlock), stateIte = this->partition.end(newBlock); stateIt != stateIte; ++stateIt) {
239 for (auto predecessorEntry : this->backwardTransitions.getRow(*stateIt)) {
240 if (this->comparator.isZero(predecessorEntry.getValue())) {
241 continue;
242 }
243
244 storm::storage::sparse::state_type predecessorChoice = predecessorEntry.getColumn();
245 storm::storage::sparse::state_type predecessorState = choiceToStateMapping[predecessorChoice];
246 Block<BlockDataType>& predecessorBlock = this->partition.getBlock(predecessorState);
247
248 // If the predecessor block is marked as absorbing, we do not need to update anything.
249 if (predecessorBlock.data().absorbing()) {
250 continue;
251 }
252
253 // If the predecessor block is not marked as to-be-refined, we do so now.
254 if (!predecessorBlock.data().splitter()) {
255 predecessorBlock.data().setSplitter();
256 splitterQueue.push_back(&predecessorBlock);
257 }
258
259 if (lastStateInitialized) {
260 // If we have skipped to the choices of the next state, we need to repair the order of the
261 // distributions for the last state.
262 if (lastState != predecessorState) {
263 updateOrderedQuotientDistributions(lastState);
264 lastState = predecessorState;
265 }
266 } else {
267 lastStateInitialized = true;
268 lastState = choiceToStateMapping[predecessorChoice];
269 }
270
271 // Now shift the probability from this transition from the old block to the new one.
272 this->quotientDistributions[predecessorChoice].shiftProbability(oldBlock.getId(), newBlock.getId(), predecessorEntry.getValue(), this->comparator);
273 }
274 }
275
276 if (lastStateInitialized) {
277 updateOrderedQuotientDistributions(lastState);
278 }
279}
280
281template<typename ModelType>
282bool NondeterministicModelBisimulationDecomposition<ModelType>::checkQuotientDistributions() const {
283 std::vector<uint_fast64_t> nondeterministicChoiceIndices = this->model.getTransitionMatrix().getRowGroupIndices();
284 for (decltype(this->model.getNumberOfStates()) state = 0; state < this->model.getNumberOfStates(); ++state) {
285 for (auto choice = nondeterministicChoiceIndices[state]; choice < nondeterministicChoiceIndices[state + 1]; ++choice) {
287 if (this->options.getKeepRewards() && this->model.hasRewardModel()) {
288 auto const& rewardModel = this->model.getUniqueRewardModel();
289 if (rewardModel.hasStateActionRewards()) {
290 distribution.setReward(rewardModel.getStateActionReward(choice));
291 }
292 }
293 for (auto const& element : this->model.getTransitionMatrix().getRow(choice)) {
294 distribution.addProbability(this->partition.getBlock(element.getColumn()).getId(), element.getValue());
295 }
296
297 if (!distribution.equals(quotientDistributions[choice], this->comparator)) {
298 std::cout << "the distributions for choice " << choice << " of state " << state << " do not match.\n";
299 std::cout << "is: " << quotientDistributions[choice] << " but should be " << distribution << '\n';
300 exit(-1);
301 }
302
303 bool less1 = distribution.less(quotientDistributions[choice], this->comparator);
304 bool less2 = quotientDistributions[choice].less(distribution, this->comparator);
305
306 if (distribution.equals(quotientDistributions[choice], this->comparator) && (less1 || less2)) {
307 std::cout << "mismatch of equality and less for \n";
308 std::cout << quotientDistributions[choice] << " vs " << distribution << '\n';
309 exit(-1);
310 }
311 }
312
313 for (auto choice = nondeterministicChoiceIndices[state]; choice < nondeterministicChoiceIndices[state + 1] - 1; ++choice) {
314 if (orderedQuotientDistributions[choice + 1]->less(*orderedQuotientDistributions[choice], this->comparator)) {
315 std::cout << "choice " << (choice + 1) << " is less than predecessor\n";
316 std::cout << *orderedQuotientDistributions[choice] << " should be less than " << *orderedQuotientDistributions[choice + 1] << '\n';
317 exit(-1);
318 }
319 }
320 }
321 return true;
322}
323
324template<typename ModelType>
325bool NondeterministicModelBisimulationDecomposition<ModelType>::printDistributions(uint_fast64_t state) const {
326 std::vector<uint_fast64_t> nondeterministicChoiceIndices = this->model.getTransitionMatrix().getRowGroupIndices();
327 for (auto choice = nondeterministicChoiceIndices[state]; choice < nondeterministicChoiceIndices[state + 1]; ++choice) {
328 std::cout << quotientDistributions[choice] << '\n';
329 }
330 return true;
331}
332
333template<typename ModelType>
334bool NondeterministicModelBisimulationDecomposition<ModelType>::checkBlockStable(bisimulation::Block<BlockDataType> const& newBlock) const {
335 std::cout << "checking stability of new block " << newBlock.getId() << " of size " << newBlock.getNumberOfStates() << '\n';
336 for (auto stateIt1 = this->partition.begin(newBlock), stateIte1 = this->partition.end(newBlock); stateIt1 != stateIte1; ++stateIt1) {
337 for (auto stateIt2 = this->partition.begin(newBlock), stateIte2 = this->partition.end(newBlock); stateIt2 != stateIte2; ++stateIt2) {
338 bool less1 = quotientDistributionsLess(*stateIt1, *stateIt2);
339 bool less2 = quotientDistributionsLess(*stateIt2, *stateIt1);
340 if (less1 || less2) {
341 std::cout << "the partition is not stable for the states " << *stateIt1 << " and " << *stateIt2 << '\n';
342 std::cout << "less1 " << less1 << " and less2 " << less2 << '\n';
343
344 std::cout << "distributions of state " << *stateIt1 << '\n';
345 this->printDistributions(*stateIt1);
346 std::cout << "distributions of state " << *stateIt2 << '\n';
347 this->printDistributions(*stateIt2);
348 exit(-1);
349 }
350 }
351 }
352 return true;
353}
354
355template<typename ModelType>
356bool NondeterministicModelBisimulationDecomposition<ModelType>::checkDistributionsDifferent(bisimulation::Block<BlockDataType> const& block,
358 for (auto stateIt1 = this->partition.begin(block), stateIte1 = this->partition.end(block); stateIt1 != stateIte1; ++stateIt1) {
359 for (auto stateIt2 = this->partition.begin() + block.getEndIndex(), stateIte2 = this->partition.begin() + end; stateIt2 != stateIte2; ++stateIt2) {
360 if (!quotientDistributionsLess(*stateIt1, *stateIt2)) {
361 std::cout << "distributions are not less, even though they should be!\n";
362 exit(-3);
363 } else {
364 std::cout << "less:\n";
365 this->printDistributions(*stateIt1);
366 std::cout << "and\n";
367 this->printDistributions(*stateIt2);
368 }
369 }
370 }
371 return true;
372}
373
374template<typename ModelType>
375bool NondeterministicModelBisimulationDecomposition<ModelType>::splitBlockAccordingToCurrentQuotientDistributions(
376 Block<BlockDataType>& block, std::vector<Block<BlockDataType>*>& splitterQueue) {
377 std::list<Block<BlockDataType>*> newBlocks;
378 bool split = this->partition.splitBlock(
379 block,
381 bool result = quotientDistributionsLess(state1, state2);
382 return result;
383 },
384 [&newBlocks](Block<BlockDataType>& newBlock) { newBlocks.push_back(&newBlock); });
385
386 // Defer updating the quotient distributions until *after* all splits, because
387 // it otherwise influences the subsequent splits!
388 for (auto el : newBlocks) {
389 this->updateQuotientDistributionsOfPredecessors(*el, block, splitterQueue);
390 }
391
392 return split;
393}
394
395template<typename ModelType>
396bool NondeterministicModelBisimulationDecomposition<ModelType>::quotientDistributionsLess(storm::storage::sparse::state_type state1,
398 STORM_LOG_TRACE("Comparing the quotient distributions of state " << state1 << " and " << state2 << ".");
399 std::vector<uint_fast64_t> nondeterministicChoiceIndices = this->model.getTransitionMatrix().getRowGroupIndices();
400
401 auto firstIt = orderedQuotientDistributions.begin() + nondeterministicChoiceIndices[state1];
402 auto firstIte = orderedQuotientDistributions.begin() + nondeterministicChoiceIndices[state1 + 1];
403 auto secondIt = orderedQuotientDistributions.begin() + nondeterministicChoiceIndices[state2];
404 auto secondIte = orderedQuotientDistributions.begin() + nondeterministicChoiceIndices[state2 + 1];
405
406 for (; firstIt != firstIte && secondIt != secondIte; ++firstIt, ++secondIt) {
407 // If the current distributions are in a less-than relationship, we can return a result.
408 if ((*firstIt)->less(**secondIt, this->comparator)) {
409 return true;
410 } else if ((*secondIt)->less(**firstIt, this->comparator)) {
411 return false;
412 }
413
414 // If the distributions matched, we need to advance both distribution iterators to the next distribution
415 // that is larger.
416 while (firstIt != firstIte && std::next(firstIt) != firstIte && !(*firstIt)->less(**std::next(firstIt), this->comparator)) {
417 ++firstIt;
418 }
419 while (secondIt != secondIte && std::next(secondIt) != secondIte && !(*secondIt)->less(**std::next(secondIt), this->comparator)) {
420 ++secondIt;
421 }
422 }
423
424 if (firstIt == firstIte && secondIt != secondIte) {
425 return true;
426 }
427 return false;
428}
429
430template<typename ModelType>
432 bisimulation::Block<BlockDataType>& splitter, std::vector<bisimulation::Block<BlockDataType>*>& splitterQueue) {
433 if (!possiblyNeedsRefinement(splitter)) {
434 return;
435 }
436
437 STORM_LOG_TRACE("Refining block " << splitter.getId());
438
439 splitBlockAccordingToCurrentQuotientDistributions(splitter, splitterQueue);
440}
441
443
446} // namespace storage
447} // namespace storm
void addLabel(std::string const &label)
Adds a new label to the labelings.
This class manages the labeling of the state space with a number of (atomic) labels.
void addLabelToState(std::string const &label, storm::storage::sparse::state_type state)
Adds a label to a given state.
This class is the superclass of all decompositions of a sparse model into its bisimulation quotient.
void addProbability(StateType const &state, ValueType const &probability)
Assigns the given state the given probability under this distribution.
bool less(DistributionWithReward< ValueType, StateType > const &other, storm::utility::ConstantsComparator< ValueType > const &comparator) const
bool equals(DistributionWithReward< ValueType, StateType > const &other, storm::utility::ConstantsComparator< ValueType > const &comparator) const
Checks whether the two distributions specify the same probabilities to go to the same states.
void setReward(ValueType const &reward)
Sets the reward of this distribution.
This class represents the decomposition of a nondeterministic model into its bisimulation quotient.
virtual void refinePartitionBasedOnSplitter(bisimulation::Block< BlockDataType > &splitter, std::vector< bisimulation::Block< BlockDataType > * > &splitterQueue) override
virtual void buildQuotient() override
Builds the quotient model based on the previously computed equivalence classes (stored in the blocks ...
virtual std::pair< storm::storage::BitVector, storm::storage::BitVector > getStatesWithProbability01() override
Computes the set of states with probability 0/1 for satisfying phi until psi.
NondeterministicModelBisimulationDecomposition(ModelType const &model, typename BisimulationDecomposition< ModelType, BlockDataType >::Options const &options=typename BisimulationDecomposition< ModelType, BlockDataType >::Options())
Computes the bisimulation relation for the given model.
virtual void initialize() override
A function that can initialize auxiliary data structures.
A class that can be used to build a sparse matrix by adding value by value.
void addNextValue(index_type row, index_type column, value_type const &value)
Sets the matrix entry at the given row and column to the given value.
void newRowGroup(index_type startingRow)
Starts a new row group in the matrix.
SparseMatrix< value_type > build(index_type overriddenRowCount=0, index_type overriddenColumnCount=0, index_type overriddenRowGroupCount=0)
std::size_t getNumberOfStates() const
Definition Block.cpp:127
std::size_t getId() const
Definition Block.cpp:64
#define STORM_LOG_TRACE(message)
Definition logging.h:12
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:30
std::pair< storm::storage::BitVector, storm::storage::BitVector > performProb01Max(storm::storage::SparseMatrix< T > const &transitionMatrix, std::vector< uint_fast64_t > const &nondeterministicChoiceIndices, storm::storage::SparseMatrix< T > const &backwardTransitions, storm::storage::BitVector const &phiStates, storm::storage::BitVector const &psiStates)
Definition graph.cpp:819
std::pair< storm::storage::BitVector, storm::storage::BitVector > performProb01Min(storm::storage::SparseMatrix< T > const &transitionMatrix, std::vector< uint_fast64_t > const &nondeterministicChoiceIndices, storm::storage::SparseMatrix< T > const &backwardTransitions, storm::storage::BitVector const &phiStates, storm::storage::BitVector const &psiStates)
Definition graph.cpp:1063