Storm 1.11.1.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
CommandAbstractor.cpp
Go to the documentation of this file.
2
3#include <boost/iterator/transform_iterator.hpp>
4#include <chrono>
5
15
16namespace storm::gbar {
17namespace abstraction {
18namespace prism {
19template<storm::dd::DdType DdType, typename ValueType>
21 std::shared_ptr<storm::utility::solver::SmtSolverFactory> const& smtSolverFactory,
22 bool useDecomposition, bool addPredicatesForValidBlocks, bool debug)
23 : smtSolver(smtSolverFactory->create(abstractionInformation.getExpressionManager())),
24 abstractionInformation(abstractionInformation),
25 command(command),
26 localExpressionInformation(abstractionInformation),
27 evaluator(abstractionInformation.getExpressionManager()),
28 relevantPredicatesAndVariables(),
29 cachedDd(abstractionInformation.getDdManager().getBddZero(), 0),
30 decisionVariables(),
31 useDecomposition(useDecomposition),
32 addPredicatesForValidBlocks(addPredicatesForValidBlocks),
33 skipBottomStates(false),
34 forceRecomputation(true),
35 abstractGuard(abstractionInformation.getDdManager().getBddZero()),
36 bottomStateAbstractor(abstractionInformation, {!command.getGuardExpression()}, smtSolverFactory),
37 debug(debug) {
38 // Make the second component of relevant predicates have the right size.
39 relevantPredicatesAndVariables.second.resize(command.getNumberOfUpdates());
40
41 // Assert all constraints to enforce legal variable values.
42 for (auto const& constraint : abstractionInformation.getConstraints()) {
43 smtSolver->add(constraint);
44 bottomStateAbstractor.constrain(constraint);
45 }
46
47 // Assert the guard of the command.
48 smtSolver->add(command.getGuardExpression());
49
50 // Construct assigned variables.
51 for (auto const& update : command.getUpdates()) {
52 for (auto const& assignment : update.getAssignments()) {
53 assignedVariables.insert(assignment.getVariable());
54 }
55 }
56
57 // Log whether or not predicates are added to ensure valid blocks.
58 if (this->addPredicatesForValidBlocks) {
59 STORM_LOG_DEBUG("Adding more predicates to ensure valid blocks.");
60 }
61}
62
63template<storm::dd::DdType DdType, typename ValueType>
64void CommandAbstractor<DdType, ValueType>::refine(std::vector<uint_fast64_t> const& predicates) {
65 // Add all predicates to the variable partition.
66 for (auto predicateIndex : predicates) {
67 localExpressionInformation.addExpression(predicateIndex);
68 }
69
70 // Next, we check whether there is work to be done by recomputing the relevant predicates and checking
71 // whether they changed.
72 std::pair<std::set<uint_fast64_t>, std::vector<std::set<uint_fast64_t>>> newRelevantPredicates = this->computeRelevantPredicates();
73
74 // Check whether we need to recompute the abstraction.
75 bool relevantPredicatesChanged = this->relevantPredicatesChanged(newRelevantPredicates);
76 if (relevantPredicatesChanged) {
77 addMissingPredicates(newRelevantPredicates);
78 }
79 forceRecomputation |= relevantPredicatesChanged;
80
81 // Refine bottom state abstractor. Note that this does not trigger a recomputation yet.
82 bottomStateAbstractor.refine(predicates);
83}
84
85template<storm::dd::DdType DdType, typename ValueType>
87 return command.get().getGuardExpression();
88}
89
90template<storm::dd::DdType DdType, typename ValueType>
91uint64_t CommandAbstractor<DdType, ValueType>::getNumberOfUpdates(uint64_t player1Choice) const {
92 return command.get().getNumberOfUpdates();
93}
94
95template<storm::dd::DdType DdType, typename ValueType>
96std::map<storm::expressions::Variable, storm::expressions::Expression> CommandAbstractor<DdType, ValueType>::getVariableUpdates(
97 uint64_t auxiliaryChoice) const {
98 return command.get().getUpdate(auxiliaryChoice).getAsVariableToExpressionMap();
99}
100
101template<storm::dd::DdType DdType, typename ValueType>
102std::set<storm::expressions::Variable> const& CommandAbstractor<DdType, ValueType>::getAssignedVariables() const {
103 return assignedVariables;
104}
105
106template<storm::dd::DdType DdType, typename ValueType>
108 if (useDecomposition) {
109 recomputeCachedBddWithDecomposition();
110 } else {
111 recomputeCachedBddWithoutDecomposition();
112 }
113}
114
115template<storm::dd::DdType DdType, typename ValueType>
116void CommandAbstractor<DdType, ValueType>::recomputeCachedBddWithDecomposition() {
117 STORM_LOG_TRACE("Recomputing BDD for command " << command.get() << " [with index " << command.get().getGlobalIndex() << "] using the decomposition.");
118 auto start = std::chrono::high_resolution_clock::now();
119
120 // compute a decomposition of the command
121 // * start with all relevant blocks: blocks of assignment variables and variables in the rhs of assignments
122 // * go through all assignments of all updates and merge relevant blocks that are related via an assignment
123 // * repeat this until nothing changes anymore
124 // * the resulting blocks are the decomposition
125
126 // Start by constructing the relevant blocks.
127 std::set<uint64_t> allRelevantBlocks;
128 std::map<storm::expressions::Variable, uint64_t> variableToBlockIndex;
129 for (auto const& update : command.get().getUpdates()) {
130 for (auto const& assignment : update.getAssignments()) {
131 allRelevantBlocks.insert(localExpressionInformation.getBlockIndexOfVariable(assignment.getVariable()));
132
133 auto rhsVariableBlocks = localExpressionInformation.getBlockIndicesOfVariables(assignment.getExpression().getVariables());
134 allRelevantBlocks.insert(rhsVariableBlocks.begin(), rhsVariableBlocks.end());
135 }
136 }
137 STORM_LOG_TRACE("Found " << allRelevantBlocks.size() << " relevant block(s).");
138
139 // Create a block partition.
140 std::vector<std::set<uint64_t>> relevantBlockPartition;
141 std::map<storm::expressions::Variable, uint64_t> variableToLocalBlockIndex;
142 uint64_t index = 0;
143 for (auto const& blockIndex : allRelevantBlocks) {
144 relevantBlockPartition.emplace_back(std::set<uint64_t>({blockIndex}));
145 for (auto const& variable : localExpressionInformation.getVariableBlockWithIndex(blockIndex)) {
146 variableToLocalBlockIndex[variable] = index;
147 }
148 ++index;
149 }
150
151 // Merge all blocks that are related via the right-hand side of assignments.
152 for (auto const& update : command.get().getUpdates()) {
153 for (auto const& assignment : update.getAssignments()) {
154 std::set<storm::expressions::Variable> rhsVariables = assignment.getExpression().getVariables();
155
156 if (!rhsVariables.empty()) {
157 uint64_t blockToKeep = variableToLocalBlockIndex.at(*rhsVariables.begin());
158 for (auto const& variable : rhsVariables) {
159 uint64_t block = variableToLocalBlockIndex.at(variable);
160 if (block != blockToKeep) {
161 for (auto const& blockIndex : relevantBlockPartition[block]) {
162 for (auto const& variable : localExpressionInformation.getVariableBlockWithIndex(blockIndex)) {
163 variableToLocalBlockIndex[variable] = blockToKeep;
164 }
165 }
166 relevantBlockPartition[blockToKeep].insert(relevantBlockPartition[block].begin(), relevantBlockPartition[block].end());
167 relevantBlockPartition[block].clear();
168 }
169 }
170 }
171 }
172 }
173
174 // Proceed by relating the blocks via assignment-variables and the expressions of their assigned expressions.
175 bool changed = false;
176 do {
177 changed = false;
178 for (auto const& update : command.get().getUpdates()) {
179 for (auto const& assignment : update.getAssignments()) {
180 std::set<storm::expressions::Variable> rhsVariables = assignment.getExpression().getVariables();
181
182 if (!rhsVariables.empty()) {
183 storm::expressions::Variable const& representativeVariable = *rhsVariables.begin();
184 uint64_t representativeBlock = variableToLocalBlockIndex.at(representativeVariable);
185 uint64_t assignmentVariableBlock = variableToLocalBlockIndex.at(assignment.getVariable());
186
187 // If the blocks are different, we merge them now
188 if (assignmentVariableBlock != representativeBlock) {
189 changed = true;
190
191 for (auto const& blockIndex : relevantBlockPartition[assignmentVariableBlock]) {
192 for (auto const& variable : localExpressionInformation.getVariableBlockWithIndex(blockIndex)) {
193 variableToLocalBlockIndex[variable] = representativeBlock;
194 }
195 }
196 relevantBlockPartition[representativeBlock].insert(relevantBlockPartition[assignmentVariableBlock].begin(),
197 relevantBlockPartition[assignmentVariableBlock].end());
198 relevantBlockPartition[assignmentVariableBlock].clear();
199 }
200 }
201 }
202 }
203 } while (changed);
204
205 // Now remove all blocks that are empty and obtain the partition.
206 std::vector<std::set<uint64_t>> cleanedRelevantBlockPartition;
207 for (auto& outerBlock : relevantBlockPartition) {
208 if (!outerBlock.empty()) {
209 cleanedRelevantBlockPartition.emplace_back();
210
211 for (auto const& innerBlock : outerBlock) {
212 if (!localExpressionInformation.getExpressionBlock(innerBlock).empty()) {
213 cleanedRelevantBlockPartition.back().insert(innerBlock);
214 }
215 }
216
217 if (cleanedRelevantBlockPartition.back().empty()) {
218 cleanedRelevantBlockPartition.pop_back();
219 }
220 }
221 }
222 relevantBlockPartition = std::move(cleanedRelevantBlockPartition);
223
224 STORM_LOG_TRACE("Decomposition into " << relevantBlockPartition.size() << " blocks.");
225 if (this->debug) {
226 uint64_t blockIndex = 0;
227 for (auto const& block : relevantBlockPartition) {
228 STORM_LOG_TRACE("Predicates of block " << blockIndex << ":");
229 std::set<uint64_t> blockPredicateIndices;
230 for (auto const& innerBlock : block) {
231 blockPredicateIndices.insert(localExpressionInformation.getExpressionBlock(innerBlock).begin(),
232 localExpressionInformation.getExpressionBlock(innerBlock).end());
233 }
234
235 for (auto const& predicateIndex : blockPredicateIndices) {
236 STORM_LOG_TRACE(abstractionInformation.get().getPredicateByIndex(predicateIndex));
237 }
238
239 ++blockIndex;
240 }
241 }
242
243 std::set<storm::expressions::Variable> variablesContainedInGuard = command.get().getGuardExpression().getVariables();
244
245 // Check whether we need to enumerate the guard. This is the case if the blocks related by the guard
246 // are not contained within a single block of our decomposition.
247 bool enumerateAbstractGuard = true;
248 std::set<uint64_t> guardBlocks = localExpressionInformation.getBlockIndicesOfVariables(variablesContainedInGuard);
249 for (auto const& block : relevantBlockPartition) {
250 bool allContained = true;
251 for (auto const& guardBlock : guardBlocks) {
252 if (block.find(guardBlock) == block.end()) {
253 allContained = false;
254 break;
255 }
256 }
257 if (allContained) {
258 enumerateAbstractGuard = false;
259 }
260 }
261
262 uint64_t numberOfSolutions = 0;
263 uint64_t numberOfTotalSolutions = 0;
264
265 // If we need to enumerate the guard, do it only once now.
266 if (enumerateAbstractGuard) {
267 std::set<uint64_t> relatedGuardPredicates = localExpressionInformation.getRelatedExpressions(variablesContainedInGuard);
268 std::vector<storm::expressions::Variable> guardDecisionVariables;
269 std::vector<std::pair<storm::expressions::Variable, uint_fast64_t>> guardVariablesAndPredicates;
270 for (auto const& element : relevantPredicatesAndVariables.first) {
271 if (relatedGuardPredicates.find(element.second) != relatedGuardPredicates.end()) {
272 guardDecisionVariables.push_back(element.first);
273 guardVariablesAndPredicates.push_back(element);
274 }
275 }
276 abstractGuard = this->getAbstractionInformation().getDdManager().getBddZero();
277 smtSolver->allSat(guardDecisionVariables,
278 [this, &guardVariablesAndPredicates, &numberOfSolutions](storm::solver::SmtSolver::ModelReference const& model) {
279 abstractGuard |= getSourceStateBdd(model, guardVariablesAndPredicates);
280 ++numberOfSolutions;
281 return true;
282 });
283 STORM_LOG_TRACE("Enumerated " << numberOfSolutions << " solutions for abstract guard.");
284
285 // Now that we have the abstract guard, we can add it as an assertion to the solver before enumerating
286 // the other solutions.
287
288 // Create a new backtracking point before adding the guard.
289 smtSolver->push();
290
291 // Create the guard constraint.
292 std::pair<std::vector<storm::expressions::Expression>, std::unordered_map<uint_fast64_t, storm::expressions::Variable>> result =
293 abstractGuard.toExpression(this->getAbstractionInformation().getExpressionManager());
294
295 // Then add it to the solver.
296 for (auto const& expression : result.first) {
297 smtSolver->add(expression);
298 }
299
300 // Finally associate the level variables with the predicates.
301 for (auto const& indexVariablePair : result.second) {
302 smtSolver->add(
303 storm::expressions::iff(indexVariablePair.second, this->getAbstractionInformation().getPredicateForDdVariableIndex(indexVariablePair.first)));
304 }
305 }
306
307 // Then enumerate the solutions for each of the blocks of the decomposition.
308 uint64_t usedNondeterminismVariables = 0;
309 uint64_t blockCounter = 0;
310 std::vector<storm::dd::Bdd<DdType>> blockBdds;
311 for (auto const& block : relevantBlockPartition) {
312 std::set<uint64_t> relevantPredicates;
313 for (auto const& innerBlock : block) {
314 relevantPredicates.insert(localExpressionInformation.getExpressionBlock(innerBlock).begin(),
315 localExpressionInformation.getExpressionBlock(innerBlock).end());
316 }
317
318 if (relevantPredicates.empty()) {
319 STORM_LOG_TRACE("Block does not contain relevant predicates, skipping it.");
320 continue;
321 }
322
323 std::vector<storm::expressions::Variable> transitionDecisionVariables;
324 std::vector<std::pair<storm::expressions::Variable, uint_fast64_t>> sourceVariablesAndPredicates;
325 for (auto const& element : relevantPredicatesAndVariables.first) {
326 if (relevantPredicates.find(element.second) != relevantPredicates.end()) {
327 transitionDecisionVariables.push_back(element.first);
328 sourceVariablesAndPredicates.push_back(element);
329 }
330 }
331
332 std::vector<std::vector<std::pair<storm::expressions::Variable, uint_fast64_t>>> destinationVariablesAndPredicates;
333 for (uint64_t updateIndex = 0; updateIndex < command.get().getNumberOfUpdates(); ++updateIndex) {
334 destinationVariablesAndPredicates.emplace_back();
335 for (auto const& assignment : command.get().getUpdate(updateIndex).getAssignments()) {
336 uint64_t assignmentVariableBlockIndex = localExpressionInformation.getBlockIndexOfVariable(assignment.getVariable());
337
338 if (block.find(assignmentVariableBlockIndex) != block.end()) {
339 std::set<uint64_t> const& assignmentVariableBlock = localExpressionInformation.getExpressionBlock(assignmentVariableBlockIndex);
340 for (auto const& element : relevantPredicatesAndVariables.second[updateIndex]) {
341 if (assignmentVariableBlock.find(element.second) != assignmentVariableBlock.end()) {
342 destinationVariablesAndPredicates.back().push_back(element);
343 transitionDecisionVariables.push_back(element.first);
344 }
345 }
346 }
347 }
348 }
349
350 std::unordered_map<storm::dd::Bdd<DdType>, std::vector<storm::dd::Bdd<DdType>>> sourceToDistributionsMap;
351 numberOfSolutions = 0;
352 smtSolver->allSat(transitionDecisionVariables, [&sourceToDistributionsMap, this, &numberOfSolutions, &sourceVariablesAndPredicates,
353 &destinationVariablesAndPredicates](storm::solver::SmtSolver::ModelReference const& model) {
354 sourceToDistributionsMap[getSourceStateBdd(model, sourceVariablesAndPredicates)].push_back(
355 getDistributionBdd(model, destinationVariablesAndPredicates));
356 ++numberOfSolutions;
357 return true;
358 });
359 STORM_LOG_TRACE("Enumerated " << numberOfSolutions << " solutions for block " << blockCounter << ".");
360 numberOfTotalSolutions += numberOfSolutions;
361
362 // Now we search for the maximal number of choices of player 2 to determine how many DD variables we
363 // need to encode the nondeterminism.
364 uint_fast64_t maximalNumberOfChoices = 0;
365 for (auto const& sourceDistributionsPair : sourceToDistributionsMap) {
366 maximalNumberOfChoices = std::max(maximalNumberOfChoices, static_cast<uint_fast64_t>(sourceDistributionsPair.second.size()));
367 }
368
369 // We now compute how many variables we need to encode the choices. We add one to the maximal number of
370 // choices to account for a possible transition to a bottom state.
371 uint_fast64_t numberOfVariablesNeeded = (maximalNumberOfChoices > 1)
372 ? (static_cast<uint_fast64_t>(std::ceil(std::log2(maximalNumberOfChoices + (blockCounter == 0 ? 1 : 0)))))
373 : (blockCounter == 0 ? 1 : 0);
374
375 // Finally, build overall result.
376 storm::dd::Bdd<DdType> resultBdd = this->getAbstractionInformation().getDdManager().getBddZero();
377
378 for (auto const& sourceDistributionsPair : sourceToDistributionsMap) {
379 STORM_LOG_ASSERT(!sourceDistributionsPair.first.isZero(), "The source BDD must not be empty.");
380 STORM_LOG_ASSERT(!sourceDistributionsPair.second.empty(), "The distributions must not be empty.");
381
382 // We start with the distribution index of 1, because 0 is reserved for a potential bottom choice.
383 uint_fast64_t distributionIndex = blockCounter == 0 ? 1 : 0;
384 storm::dd::Bdd<DdType> allDistributions = this->getAbstractionInformation().getDdManager().getBddZero();
385 for (auto const& distribution : sourceDistributionsPair.second) {
386 allDistributions |= distribution && this->getAbstractionInformation().encodePlayer2Choice(
387 distributionIndex, usedNondeterminismVariables, usedNondeterminismVariables + numberOfVariablesNeeded);
388 ++distributionIndex;
389 STORM_LOG_ASSERT(!allDistributions.isZero(), "The BDD must not be empty.");
390 }
391 resultBdd |= sourceDistributionsPair.first && allDistributions;
392 STORM_LOG_ASSERT(!resultBdd.isZero(), "The BDD must not be empty.");
393 }
394 usedNondeterminismVariables += numberOfVariablesNeeded;
395
396 blockBdds.push_back(resultBdd);
397 ++blockCounter;
398 }
399
400 if (enumerateAbstractGuard) {
401 smtSolver->pop();
402 }
403
404 // multiply the results
405 storm::dd::Bdd<DdType> resultBdd = getAbstractionInformation().getDdManager().getBddOne();
406 for (auto const& blockBdd : blockBdds) {
407 resultBdd &= blockBdd;
408 }
409
410 // If we did not explicitly enumerate the guard, we can construct it from the result BDD.
411 if (!enumerateAbstractGuard) {
412 std::set<storm::expressions::Variable> allVariables(getAbstractionInformation().getSuccessorVariables());
413 auto player2Variables = getAbstractionInformation().getPlayer2VariableSet(usedNondeterminismVariables);
414 allVariables.insert(player2Variables.begin(), player2Variables.end());
415 auto auxVariables = getAbstractionInformation().getAuxVariableSet(0, getAbstractionInformation().getAuxVariableCount());
416 allVariables.insert(auxVariables.begin(), auxVariables.end());
417
418 std::set<storm::expressions::Variable> variablesToAbstract;
419 std::set_intersection(allVariables.begin(), allVariables.end(), resultBdd.getContainedMetaVariables().begin(),
420 resultBdd.getContainedMetaVariables().end(), std::inserter(variablesToAbstract, variablesToAbstract.begin()));
421
422 abstractGuard = resultBdd.existsAbstract(variablesToAbstract);
423 } else {
424 // Multiply the abstract guard as it can contain predicates that are not mentioned in the blocks.
425 resultBdd &= abstractGuard;
426 }
427
428 // multiply with missing identities
429 resultBdd &= computeMissingUpdateIdentities();
430
431 // cache and return result
432 resultBdd &=
433 this->getAbstractionInformation().encodePlayer1Choice(command.get().getGlobalIndex(), this->getAbstractionInformation().getPlayer1VariableCount());
434
435 // Cache the result.
436 cachedDd = GameBddResult<DdType>(resultBdd, usedNondeterminismVariables);
437
438 auto end = std::chrono::high_resolution_clock::now();
439
440 STORM_LOG_TRACE("Enumerated " << numberOfTotalSolutions << " solutions in " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
441 << "ms.");
442 forceRecomputation = false;
443}
444
445template<storm::dd::DdType DdType, typename ValueType>
446void CommandAbstractor<DdType, ValueType>::recomputeCachedBddWithoutDecomposition() {
447 STORM_LOG_TRACE("Recomputing BDD for command " << command.get());
448 auto start = std::chrono::high_resolution_clock::now();
449
450 // Create a mapping from source state DDs to their distributions.
451 std::unordered_map<storm::dd::Bdd<DdType>, std::vector<storm::dd::Bdd<DdType>>> sourceToDistributionsMap;
452 uint64_t numberOfSolutions = 0;
453 smtSolver->allSat(decisionVariables, [&sourceToDistributionsMap, this, &numberOfSolutions](storm::solver::SmtSolver::ModelReference const& model) {
454 sourceToDistributionsMap[getSourceStateBdd(model, relevantPredicatesAndVariables.first)].push_back(
455 getDistributionBdd(model, relevantPredicatesAndVariables.second));
456 ++numberOfSolutions;
457 return true;
458 });
459
460 // Now we search for the maximal number of choices of player 2 to determine how many DD variables we
461 // need to encode the nondeterminism.
462 uint_fast64_t maximalNumberOfChoices = 0;
463 for (auto const& sourceDistributionsPair : sourceToDistributionsMap) {
464 maximalNumberOfChoices = std::max(maximalNumberOfChoices, static_cast<uint_fast64_t>(sourceDistributionsPair.second.size()));
465 }
466
467 // We now compute how many variables we need to encode the choices. We add one to the maximal number of
468 // choices to account for a possible transition to a bottom state.
469 uint_fast64_t numberOfVariablesNeeded = static_cast<uint_fast64_t>(std::ceil(std::log2(maximalNumberOfChoices + 1)));
470
471 // Finally, build overall result.
472 storm::dd::Bdd<DdType> resultBdd = this->getAbstractionInformation().getDdManager().getBddZero();
473 if (!skipBottomStates) {
474 abstractGuard = this->getAbstractionInformation().getDdManager().getBddZero();
475 }
476 for (auto const& sourceDistributionsPair : sourceToDistributionsMap) {
477 if (!skipBottomStates) {
478 abstractGuard |= sourceDistributionsPair.first;
479 }
480
481 STORM_LOG_ASSERT(!sourceDistributionsPair.first.isZero(), "The source BDD must not be empty.");
482 STORM_LOG_ASSERT(!sourceDistributionsPair.second.empty(), "The distributions must not be empty.");
483 // We start with the distribution index of 1, becase 0 is reserved for a potential bottom choice.
484 uint_fast64_t distributionIndex = 1;
485 storm::dd::Bdd<DdType> allDistributions = this->getAbstractionInformation().getDdManager().getBddZero();
486 for (auto const& distribution : sourceDistributionsPair.second) {
487 allDistributions |= distribution && this->getAbstractionInformation().encodePlayer2Choice(distributionIndex, 0, numberOfVariablesNeeded);
488 ++distributionIndex;
489 STORM_LOG_ASSERT(!allDistributions.isZero(), "The BDD must not be empty.");
490 }
491 resultBdd |= sourceDistributionsPair.first && allDistributions;
492 STORM_LOG_ASSERT(!resultBdd.isZero(), "The BDD must not be empty.");
493 }
494
495 resultBdd &= computeMissingUpdateIdentities();
496 resultBdd &=
497 this->getAbstractionInformation().encodePlayer1Choice(command.get().getGlobalIndex(), this->getAbstractionInformation().getPlayer1VariableCount());
498 STORM_LOG_ASSERT(sourceToDistributionsMap.empty() || !resultBdd.isZero(), "The BDD must not be empty, if there were distributions.");
499
500 // Cache the result.
501 cachedDd = GameBddResult<DdType>(resultBdd, numberOfVariablesNeeded);
502 auto end = std::chrono::high_resolution_clock::now();
503
504 STORM_LOG_TRACE("Enumerated " << numberOfSolutions << " solutions in " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
505 << "ms.");
506 forceRecomputation = false;
507}
508
509template<storm::dd::DdType DdType, typename ValueType>
510std::pair<std::set<uint_fast64_t>, std::set<uint_fast64_t>> CommandAbstractor<DdType, ValueType>::computeRelevantPredicates(
511 std::vector<storm::prism::Assignment> const& assignments) const {
512 std::pair<std::set<uint_fast64_t>, std::set<uint_fast64_t>> result;
513
514 std::set<storm::expressions::Variable> assignedVariables;
515 for (auto const& assignment : assignments) {
516 // Also, variables appearing on the right-hand side of an assignment are relevant for source state.
517 auto const& rightHandSidePredicates = localExpressionInformation.getExpressionsUsingVariables(assignment.getExpression().getVariables());
518 result.first.insert(rightHandSidePredicates.begin(), rightHandSidePredicates.end());
519
520 // Variables that are being assigned are relevant for the successor state.
521 storm::expressions::Variable const& assignedVariable = assignment.getVariable();
522 auto const& leftHandSidePredicates = localExpressionInformation.getExpressionsUsingVariable(assignedVariable);
523 result.second.insert(leftHandSidePredicates.begin(), leftHandSidePredicates.end());
524
525 // Predicates that are indirectly related to the assigned variables are relevant for the source state (if requested).
526 if (this->addPredicatesForValidBlocks) {
527 auto const& assignedVariableBlock = localExpressionInformation.getRelatedExpressions(assignedVariable);
528 result.first.insert(assignedVariableBlock.begin(), assignedVariableBlock.end());
529 }
530 }
531
532 return result;
533}
534
535template<storm::dd::DdType DdType, typename ValueType>
536std::pair<std::set<uint_fast64_t>, std::vector<std::set<uint_fast64_t>>> CommandAbstractor<DdType, ValueType>::computeRelevantPredicates() const {
537 std::pair<std::set<uint_fast64_t>, std::vector<std::set<uint_fast64_t>>> result;
538
539 // To start with, all predicates related to the guard are relevant source predicates.
540 result.first = localExpressionInformation.getExpressionsUsingVariables(command.get().getGuardExpression().getVariables());
541
542 // Then, we add the predicates that become relevant, because of some update.
543 for (auto const& update : command.get().getUpdates()) {
544 std::pair<std::set<uint_fast64_t>, std::set<uint_fast64_t>> relevantUpdatePredicates = computeRelevantPredicates(update.getAssignments());
545 result.first.insert(relevantUpdatePredicates.first.begin(), relevantUpdatePredicates.first.end());
546 result.second.push_back(relevantUpdatePredicates.second);
547 }
548
549 // std::cout << "relevant predicates for command " << command.get().getGlobalIndex() << '\n';
550 // std::cout << "source predicates\n";
551 // for (auto const& i : result.first) {
552 // std::cout << this->getAbstractionInformation().getPredicateByIndex(i) << '\n';
553 // }
554 // for (uint64_t i = 0; i < result.second.size(); ++i) {
555 // std::cout << "destination " << i << '\n';
556 // for (auto const& j : result.second[i]) {
557 // std::cout << this->getAbstractionInformation().getPredicateByIndex(j) << '\n';
558 // }
559 // }
560
561 return result;
562}
563
564template<storm::dd::DdType DdType, typename ValueType>
565bool CommandAbstractor<DdType, ValueType>::relevantPredicatesChanged(
566 std::pair<std::set<uint_fast64_t>, std::vector<std::set<uint_fast64_t>>> const& newRelevantPredicates) const {
567 if (newRelevantPredicates.first.size() > relevantPredicatesAndVariables.first.size()) {
568 return true;
569 }
570
571 for (uint_fast64_t index = 0; index < command.get().getNumberOfUpdates(); ++index) {
572 if (newRelevantPredicates.second[index].size() > relevantPredicatesAndVariables.second[index].size()) {
573 return true;
574 }
575 }
576
577 return false;
578}
579
580template<storm::dd::DdType DdType, typename ValueType>
581void CommandAbstractor<DdType, ValueType>::addMissingPredicates(
582 std::pair<std::set<uint_fast64_t>, std::vector<std::set<uint_fast64_t>>> const& newRelevantPredicates) {
583 // Determine and add new relevant source predicates.
584 std::vector<std::pair<storm::expressions::Variable, uint_fast64_t>> newSourceVariables =
585 this->getAbstractionInformation().declareNewVariables(relevantPredicatesAndVariables.first, newRelevantPredicates.first);
586 for (auto const& element : newSourceVariables) {
587 allRelevantPredicates.insert(element.second);
588 smtSolver->add(storm::expressions::iff(element.first, this->getAbstractionInformation().getPredicateByIndex(element.second)));
589 decisionVariables.push_back(element.first);
590 }
591
592 // Insert the new variables into the record of relevant source variables.
593 relevantPredicatesAndVariables.first.insert(relevantPredicatesAndVariables.first.end(), newSourceVariables.begin(), newSourceVariables.end());
594 std::sort(relevantPredicatesAndVariables.first.begin(), relevantPredicatesAndVariables.first.end(),
595 [](std::pair<storm::expressions::Variable, uint_fast64_t> const& first, std::pair<storm::expressions::Variable, uint_fast64_t> const& second) {
596 return first.second < second.second;
597 });
598
599 // Do the same for every update.
600 for (uint_fast64_t index = 0; index < command.get().getNumberOfUpdates(); ++index) {
601 std::vector<std::pair<storm::expressions::Variable, uint_fast64_t>> newSuccessorVariables =
602 this->getAbstractionInformation().declareNewVariables(relevantPredicatesAndVariables.second[index], newRelevantPredicates.second[index]);
603 for (auto const& element : newSuccessorVariables) {
604 allRelevantPredicates.insert(element.second);
605 smtSolver->add(storm::expressions::iff(element.first, this->getAbstractionInformation()
606 .getPredicateByIndex(element.second)
607 .substitute(command.get().getUpdate(index).getAsVariableToExpressionMap())));
608 decisionVariables.push_back(element.first);
609 }
610
611 relevantPredicatesAndVariables.second[index].insert(relevantPredicatesAndVariables.second[index].end(), newSuccessorVariables.begin(),
612 newSuccessorVariables.end());
613 std::sort(relevantPredicatesAndVariables.second[index].begin(), relevantPredicatesAndVariables.second[index].end(),
614 [](std::pair<storm::expressions::Variable, uint_fast64_t> const& first,
615 std::pair<storm::expressions::Variable, uint_fast64_t> const& second) { return first.second < second.second; });
616 }
617}
618
619template<storm::dd::DdType DdType, typename ValueType>
620storm::dd::Bdd<DdType> CommandAbstractor<DdType, ValueType>::getSourceStateBdd(
622 std::vector<std::pair<storm::expressions::Variable, uint_fast64_t>> const& variablePredicates) const {
623 storm::dd::Bdd<DdType> result = this->getAbstractionInformation().getDdManager().getBddOne();
624 for (auto variableIndexPairIt = variablePredicates.rbegin(), variableIndexPairIte = variablePredicates.rend(); variableIndexPairIt != variableIndexPairIte;
625 ++variableIndexPairIt) {
626 auto const& variableIndexPair = *variableIndexPairIt;
627 if (model.getBooleanValue(variableIndexPair.first)) {
628 result &= this->getAbstractionInformation().encodePredicateAsSource(variableIndexPair.second);
629 } else {
630 result &= !this->getAbstractionInformation().encodePredicateAsSource(variableIndexPair.second);
631 }
632 }
633
634 STORM_LOG_ASSERT(!result.isZero(), "Source must not be empty.");
635 return result;
636}
637
638template<storm::dd::DdType DdType, typename ValueType>
639storm::dd::Bdd<DdType> CommandAbstractor<DdType, ValueType>::getDistributionBdd(
641 std::vector<std::vector<std::pair<storm::expressions::Variable, uint_fast64_t>>> const& variablePredicates) const {
642 storm::dd::Bdd<DdType> result = this->getAbstractionInformation().getDdManager().getBddZero();
643
644 for (uint_fast64_t updateIndex = 0; updateIndex < command.get().getNumberOfUpdates(); ++updateIndex) {
645 storm::dd::Bdd<DdType> updateBdd = this->getAbstractionInformation().getDdManager().getBddOne();
646
647 // Translate block variables for this update into a successor block.
648 for (auto variableIndexPairIt = variablePredicates[updateIndex].rbegin(), variableIndexPairIte = variablePredicates[updateIndex].rend();
649 variableIndexPairIt != variableIndexPairIte; ++variableIndexPairIt) {
650 auto const& variableIndexPair = *variableIndexPairIt;
651 if (model.getBooleanValue(variableIndexPair.first)) {
652 updateBdd &= this->getAbstractionInformation().encodePredicateAsSuccessor(variableIndexPair.second);
653 } else {
654 updateBdd &= !this->getAbstractionInformation().encodePredicateAsSuccessor(variableIndexPair.second);
655 }
656 }
657
658 updateBdd &= this->getAbstractionInformation().encodeAux(updateIndex, 0, this->getAbstractionInformation().getAuxVariableCount());
659 result |= updateBdd;
660 }
661
662 STORM_LOG_ASSERT(!result.isZero(), "Distribution must not be empty.");
663 return result;
664}
665
666template<storm::dd::DdType DdType, typename ValueType>
667storm::dd::Bdd<DdType> CommandAbstractor<DdType, ValueType>::computeMissingUpdateIdentities() const {
668 storm::dd::Bdd<DdType> result = this->getAbstractionInformation().getDdManager().getBddZero();
669
670 for (uint_fast64_t updateIndex = 0; updateIndex < command.get().getNumberOfUpdates(); ++updateIndex) {
671 // Compute the identities that are missing for this update.
672 auto updateRelevantIt = relevantPredicatesAndVariables.second[updateIndex].rbegin();
673 auto updateRelevantIte = relevantPredicatesAndVariables.second[updateIndex].rend();
674
675 storm::dd::Bdd<DdType> updateIdentity = this->getAbstractionInformation().getDdManager().getBddOne();
676 for (uint_fast64_t predicateIndex = this->getAbstractionInformation().getNumberOfPredicates() - 1;; --predicateIndex) {
677 if (updateRelevantIt == updateRelevantIte || updateRelevantIt->second != predicateIndex) {
678 updateIdentity &= this->getAbstractionInformation().getPredicateIdentity(predicateIndex);
679 } else {
680 ++updateRelevantIt;
681 }
682
683 if (predicateIndex == 0) {
684 break;
685 }
686 }
687
688 result |= updateIdentity && this->getAbstractionInformation().encodeAux(updateIndex, 0, this->getAbstractionInformation().getAuxVariableCount());
689 }
690 return result;
691}
692
693template<storm::dd::DdType DdType, typename ValueType>
695 if (forceRecomputation) {
696 this->recomputeCachedBdd();
697 } else {
698 cachedDd.bdd &= computeMissingUpdateIdentities();
699 }
700
701 STORM_LOG_TRACE("Command produces " << cachedDd.bdd.getNonZeroCount() << " transitions.");
702
703 return cachedDd;
704}
705
706template<storm::dd::DdType DdType, typename ValueType>
708 uint_fast64_t numberOfPlayer2Variables) {
709 STORM_LOG_TRACE("Computing bottom state transitions of command " << command.get());
710 BottomStateResult<DdType> result(this->getAbstractionInformation().getDdManager().getBddZero(),
711 this->getAbstractionInformation().getDdManager().getBddZero());
712
713 // If the guard of this command is a predicate, there are not bottom states/transitions.
714 if (skipBottomStates) {
715 STORM_LOG_TRACE("Skipping bottom state computation for this command.");
716 return result;
717 }
718
719 storm::dd::Bdd<DdType> reachableStatesWithCommand = reachableStates && abstractGuard;
720
721 // Use the state abstractor to compute the set of abstract states that has this command enabled but
722 // still has a transition to a bottom state.
723 bottomStateAbstractor.constrain(reachableStatesWithCommand);
724 result.states = bottomStateAbstractor.getAbstractStates() && reachableStatesWithCommand;
725
726 // If the result is empty one time, we can skip the bottom state computation from now on.
727 if (result.states.isZero()) {
728 skipBottomStates = true;
729 }
730
731 // Now equip all these states with an actual transition to a bottom state.
732 result.transitions =
733 result.states && this->getAbstractionInformation().getAllPredicateIdentities() && this->getAbstractionInformation().getBottomStateBdd(false, false);
734
735 // Mark the states as bottom states.
736 result.states &= this->getAbstractionInformation().getBottomStateBdd(true, false);
737
738 // Add the command encoding and the next free player 2 encoding.
739 result.transitions &=
740 this->getAbstractionInformation().encodePlayer1Choice(command.get().getGlobalIndex(), this->getAbstractionInformation().getPlayer1VariableCount()) &&
741 this->getAbstractionInformation().encodePlayer2Choice(0, 0, numberOfPlayer2Variables) &&
742 this->getAbstractionInformation().encodeAux(0, 0, this->getAbstractionInformation().getAuxVariableCount());
743
744 return result;
745}
746
747template<storm::dd::DdType DdType, typename ValueType>
749 storm::dd::Add<DdType, ValueType> result = this->getAbstractionInformation().getDdManager().template getAddZero<ValueType>();
750 for (uint_fast64_t updateIndex = 0; updateIndex < command.get().getNumberOfUpdates(); ++updateIndex) {
751 result +=
752 this->getAbstractionInformation().encodeAux(updateIndex, 0, this->getAbstractionInformation().getAuxVariableCount()).template toAdd<ValueType>() *
753 this->getAbstractionInformation().getDdManager().getConstant(evaluator.asRational(command.get().getUpdate(updateIndex).getLikelihoodExpression()));
754 }
755 result *= this->getAbstractionInformation()
756 .encodePlayer1Choice(command.get().getGlobalIndex(), this->getAbstractionInformation().getPlayer1VariableCount())
757 .template toAdd<ValueType>();
758 return result;
759}
760
761template<storm::dd::DdType DdType, typename ValueType>
765
766template<storm::dd::DdType DdType, typename ValueType>
768 return abstractionInformation.get();
769}
770
771template<storm::dd::DdType DdType, typename ValueType>
772AbstractionInformation<DdType>& CommandAbstractor<DdType, ValueType>::getAbstractionInformation() {
773 return abstractionInformation.get();
774}
775
776template<storm::dd::DdType DdType, typename ValueType>
778 skipBottomStates = true;
779}
780
784} // namespace prism
785} // namespace abstraction
786} // namespace storm::gbar
Bdd< LibraryType > existsAbstract(std::set< storm::expressions::Variable > const &metaVariables) const
Existentially abstracts from the given meta variables.
Definition Bdd.cpp:172
bool isZero() const
Retrieves whether this DD represents the constant zero function.
Definition Bdd.cpp:541
Bdd< LibraryType > constrain(Bdd< LibraryType > const &constraint) const
Computes the constraint of the current BDD with the given constraint.
Definition Bdd.cpp:204
std::set< storm::expressions::Variable > const & getContainedMetaVariables() const
Retrieves the set of all meta variables contained in the DD.
Definition Dd.cpp:28
DdManager< LibraryType > & getDdManager() const
Retrieves the manager that is responsible for this DD.
Definition Dd.cpp:38
std::set< storm::expressions::Variable > const & getAssignedVariables() const
Retrieves the assigned variables.
storm::expressions::Expression const & getGuard() const
Retrieves the guard of this command.
CommandAbstractor(storm::prism::Command const &command, AbstractionInformation< DdType > &abstractionInformation, std::shared_ptr< storm::utility::solver::SmtSolverFactory > const &smtSolverFactory, bool useDecomposition, bool addPredicatesForValidBlocks, bool debug)
Constructs an abstract command from the given command and the initial predicates.
std::map< storm::expressions::Variable, storm::expressions::Expression > getVariableUpdates(uint64_t auxiliaryChoice) const
Retrieves a mapping from variables to expressions that define their updates wrt.
uint64_t getNumberOfUpdates(uint64_t player1Choice) const
Retrieves the number of updates of this command.
void refine(std::vector< uint_fast64_t > const &predicates)
Refines the abstract command with the given predicates.
storm::dd::Add< DdType, ValueType > getCommandUpdateProbabilitiesAdd() const
Retrieves an ADD that maps the encoding of the command and its updates to their probabilities.
storm::prism::Command const & getConcreteCommand() const
Retrieves the concrete command that is abstracted by this abstract command.
GameBddResult< DdType > abstract()
Computes the abstraction of the command wrt.
BottomStateResult< DdType > getBottomStateTransitions(storm::dd::Bdd< DdType > const &reachableStates, uint_fast64_t numberOfPlayer2Variables)
Retrieves the transitions to bottom states of this command.
storm::expressions::Expression const & getGuardExpression() const
Retrieves a reference to the guard of the command.
Definition Command.cpp:35
The base class for all model references.
Definition SmtSolver.h:31
virtual bool getBooleanValue(storm::expressions::Variable const &variable) const =0
#define STORM_LOG_DEBUG(message)
Definition logging.h:18
#define STORM_LOG_TRACE(message)
Definition logging.h:12
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:11
Expression iff(Expression const &first, Expression const &second)
std::pair< storm::RationalNumber, storm::RationalNumber > count(std::vector< storm::storage::BitVector > const &origSets, std::vector< storm::storage::BitVector > const &intersects, std::vector< storm::storage::BitVector > const &intersectsInfo, storm::RationalNumber val, bool plus, uint64_t remdepth)