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