Storm
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
EdgeAbstractor.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 jani {
25template<storm::dd::DdType DdType, typename ValueType>
27 std::shared_ptr<storm::utility::solver::SmtSolverFactory> const& smtSolverFactory, bool useDecomposition,
28 bool addPredicatesForValidBlocks, bool debug)
29 : smtSolver(smtSolverFactory->create(abstractionInformation.getExpressionManager())),
30 abstractionInformation(abstractionInformation),
31 edgeId(edgeId),
32 edge(edge),
33 localExpressionInformation(abstractionInformation),
34 evaluator(abstractionInformation.getExpressionManager()),
35 relevantPredicatesAndVariables(),
36 cachedDd(abstractionInformation.getDdManager().getBddZero(), 0),
37 decisionVariables(),
38 useDecomposition(useDecomposition),
39 addPredicatesForValidBlocks(addPredicatesForValidBlocks),
40 skipBottomStates(false),
41 forceRecomputation(true),
42 abstractGuard(abstractionInformation.getDdManager().getBddZero()),
43 bottomStateAbstractor(abstractionInformation, {!edge.getGuard()}, smtSolverFactory),
44 debug(debug) {
45 // Make the second component of relevant predicates have the right size.
46 relevantPredicatesAndVariables.second.resize(edge.getNumberOfDestinations());
47
48 // Assert all constraints to enforce legal variable values.
49 for (auto const& constraint : abstractionInformation.getConstraints()) {
50 smtSolver->add(constraint);
51 bottomStateAbstractor.constrain(constraint);
52 }
53
54 // Assert the guard of the command.
55 smtSolver->add(edge.getGuard());
56
57 // Construct assigned variables.
58 for (auto const& destination : edge.getDestinations()) {
59 for (auto const& assignment : destination.getOrderedAssignments()) {
60 assignedVariables.insert(assignment.getExpressionVariable());
61 }
62 }
63
64 // Log whether or not predicates are added to ensure valid blocks.
65 if (this->addPredicatesForValidBlocks) {
66 STORM_LOG_DEBUG("Adding more predicates to ensure valid blocks.");
67 }
68}
69
70template<storm::dd::DdType DdType, typename ValueType>
71void EdgeAbstractor<DdType, ValueType>::refine(std::vector<uint_fast64_t> const& predicates) {
72 // Add all predicates to the variable partition.
73 for (auto predicateIndex : predicates) {
74 localExpressionInformation.addExpression(predicateIndex);
75 }
76
77 // Next, we check whether there is work to be done by recomputing the relevant predicates and checking
78 // whether they changed.
79 std::pair<std::set<uint_fast64_t>, std::vector<std::set<uint_fast64_t>>> newRelevantPredicates = this->computeRelevantPredicates();
80
81 // Check whether we need to recompute the abstraction.
82 bool relevantPredicatesChanged = this->relevantPredicatesChanged(newRelevantPredicates);
83 if (relevantPredicatesChanged) {
84 addMissingPredicates(newRelevantPredicates);
85 }
86 forceRecomputation |= relevantPredicatesChanged;
87
88 // Refine bottom state abstractor. Note that this does not trigger a recomputation yet.
89 bottomStateAbstractor.refine(predicates);
90}
91
92template<storm::dd::DdType DdType, typename ValueType>
94 return edge.get().getGuard();
95}
96
97template<storm::dd::DdType DdType, typename ValueType>
98uint64_t EdgeAbstractor<DdType, ValueType>::getNumberOfUpdates(uint64_t player1Choice) const {
99 return edge.get().getNumberOfDestinations();
100}
101
102template<storm::dd::DdType DdType, typename ValueType>
103std::map<storm::expressions::Variable, storm::expressions::Expression> EdgeAbstractor<DdType, ValueType>::getVariableUpdates(uint64_t auxiliaryChoice) const {
104 return edge.get().getDestination(auxiliaryChoice).getAsVariableToExpressionMap();
105}
106
107template<storm::dd::DdType DdType, typename ValueType>
108std::set<storm::expressions::Variable> const& EdgeAbstractor<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 EdgeAbstractor<DdType, ValueType>::recomputeCachedBddWithDecomposition() {
123 STORM_LOG_TRACE("Recomputing BDD for edge with id " << edgeId << " and guard " << edge.get().getGuard() << " 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& destination : edge.get().getDestinations()) {
136 for (auto const& assignment : destination.getOrderedAssignments().getAllAssignments()) {
137 allRelevantBlocks.insert(localExpressionInformation.getBlockIndexOfVariable(assignment.getExpressionVariable()));
138
139 auto rhsVariableBlocks = localExpressionInformation.getBlockIndicesOfVariables(assignment.getAssignedExpression().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& destination : edge.get().getDestinations()) {
159 for (auto const& assignment : destination.getOrderedAssignments().getAllAssignments()) {
160 std::set<storm::expressions::Variable> rhsVariables = assignment.getAssignedExpression().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& destination : edge.get().getDestinations()) {
185 for (auto const& assignment : destination.getOrderedAssignments().getAllAssignments()) {
186 std::set<storm::expressions::Variable> rhsVariables = assignment.getAssignedExpression().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.getExpressionVariable());
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 = edge.get().getGuard().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 destinationIndex = 0; destinationIndex < edge.get().getNumberOfDestinations(); ++destinationIndex) {
340 destinationVariablesAndPredicates.emplace_back();
341 for (auto const& assignment : edge.get().getDestination(destinationIndex).getOrderedAssignments().getAllAssignments()) {
342 uint64_t assignmentVariableBlockIndex = localExpressionInformation.getBlockIndexOfVariable(assignment.getVariable().getExpressionVariable());
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[destinationIndex]) {
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 &= computeMissingDestinationIdentities();
436
437 // cache and return result
438 resultBdd &= this->getAbstractionInformation().encodePlayer1Choice(edgeId, this->getAbstractionInformation().getPlayer1VariableCount());
439
440 // Cache the result.
441 cachedDd = GameBddResult<DdType>(resultBdd, usedNondeterminismVariables);
442
443 auto end = std::chrono::high_resolution_clock::now();
444
445 STORM_LOG_TRACE("Enumerated " << numberOfTotalSolutions << " solutions in " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
446 << "ms.");
447 forceRecomputation = false;
448}
449
450template<storm::dd::DdType DdType, typename ValueType>
451void EdgeAbstractor<DdType, ValueType>::recomputeCachedBddWithoutDecomposition() {
452 STORM_LOG_TRACE("Recomputing BDD for edge with id " << edgeId << " and guard " << edge.get().getGuard());
453 auto start = std::chrono::high_resolution_clock::now();
454
455 // Create a mapping from source state DDs to their distributions.
456 std::unordered_map<storm::dd::Bdd<DdType>, std::vector<storm::dd::Bdd<DdType>>> sourceToDistributionsMap;
457 uint64_t numberOfSolutions = 0;
458 smtSolver->allSat(decisionVariables, [&sourceToDistributionsMap, this, &numberOfSolutions](storm::solver::SmtSolver::ModelReference const& model) {
459 sourceToDistributionsMap[getSourceStateBdd(model, relevantPredicatesAndVariables.first)].push_back(
460 getDistributionBdd(model, relevantPredicatesAndVariables.second));
461 ++numberOfSolutions;
462 return true;
463 });
464
465 // Now we search for the maximal number of choices of player 2 to determine how many DD variables we
466 // need to encode the nondeterminism.
467 uint_fast64_t maximalNumberOfChoices = 0;
468 for (auto const& sourceDistributionsPair : sourceToDistributionsMap) {
469 maximalNumberOfChoices = std::max(maximalNumberOfChoices, static_cast<uint_fast64_t>(sourceDistributionsPair.second.size()));
470 }
471
472 // We now compute how many variables we need to encode the choices. We add one to the maximal number of
473 // choices to account for a possible transition to a bottom state.
474 uint_fast64_t numberOfVariablesNeeded = static_cast<uint_fast64_t>(std::ceil(std::log2(maximalNumberOfChoices + 1)));
475
476 // Finally, build overall result.
477 storm::dd::Bdd<DdType> resultBdd = this->getAbstractionInformation().getDdManager().getBddZero();
478 if (!skipBottomStates) {
479 abstractGuard = this->getAbstractionInformation().getDdManager().getBddZero();
480 }
481 for (auto const& sourceDistributionsPair : sourceToDistributionsMap) {
482 if (!skipBottomStates) {
483 abstractGuard |= sourceDistributionsPair.first;
484 }
485
486 STORM_LOG_ASSERT(!sourceDistributionsPair.first.isZero(), "The source BDD must not be empty.");
487 STORM_LOG_ASSERT(!sourceDistributionsPair.second.empty(), "The distributions must not be empty.");
488 // We start with the distribution index of 1, becase 0 is reserved for a potential bottom choice.
489 uint_fast64_t distributionIndex = 1;
490 storm::dd::Bdd<DdType> allDistributions = this->getAbstractionInformation().getDdManager().getBddZero();
491 for (auto const& distribution : sourceDistributionsPair.second) {
492 allDistributions |= distribution && this->getAbstractionInformation().encodePlayer2Choice(distributionIndex, 0, numberOfVariablesNeeded);
493 ++distributionIndex;
494 STORM_LOG_ASSERT(!allDistributions.isZero(), "The BDD must not be empty.");
495 }
496 resultBdd |= sourceDistributionsPair.first && allDistributions;
497 STORM_LOG_ASSERT(!resultBdd.isZero(), "The BDD must not be empty.");
498 }
499
500 resultBdd &= computeMissingDestinationIdentities();
501 resultBdd &= this->getAbstractionInformation().encodePlayer1Choice(edgeId, this->getAbstractionInformation().getPlayer1VariableCount());
502 STORM_LOG_ASSERT(sourceToDistributionsMap.empty() || !resultBdd.isZero(), "The BDD must not be empty, if there were distributions.");
503
504 // Cache the result.
505 cachedDd = GameBddResult<DdType>(resultBdd, numberOfVariablesNeeded);
506 auto end = std::chrono::high_resolution_clock::now();
507
508 STORM_LOG_TRACE("Enumerated " << numberOfSolutions << " solutions in " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()
509 << "ms.");
510 forceRecomputation = false;
511}
512
513template<storm::dd::DdType DdType, typename ValueType>
514std::pair<std::set<uint_fast64_t>, std::set<uint_fast64_t>> EdgeAbstractor<DdType, ValueType>::computeRelevantPredicates(
515 storm::jani::OrderedAssignments const& assignments) const {
516 std::pair<std::set<uint_fast64_t>, std::set<uint_fast64_t>> result;
517
518 std::set<storm::expressions::Variable> assignedVariables;
519 for (auto const& assignment : assignments.getAllAssignments()) {
520 // Also, variables appearing on the right-hand side of an assignment are relevant for source state.
521 auto const& rightHandSidePredicates = localExpressionInformation.getExpressionsUsingVariables(assignment.getAssignedExpression().getVariables());
522 result.first.insert(rightHandSidePredicates.begin(), rightHandSidePredicates.end());
523
524 // Variables that are being assigned are relevant for the successor state.
525 storm::expressions::Variable const& assignedVariable = assignment.getExpressionVariable();
526 auto const& leftHandSidePredicates = localExpressionInformation.getExpressionsUsingVariable(assignedVariable);
527 result.second.insert(leftHandSidePredicates.begin(), leftHandSidePredicates.end());
528
529 // Predicates that are indirectly related to the assigned variables are relevant for the source state (if requested).
530 if (this->addPredicatesForValidBlocks) {
531 auto const& assignedVariableBlock = localExpressionInformation.getRelatedExpressions(assignedVariable);
532 result.first.insert(assignedVariableBlock.begin(), assignedVariableBlock.end());
533 }
534 }
535
536 return result;
537}
538
539template<storm::dd::DdType DdType, typename ValueType>
540std::pair<std::set<uint_fast64_t>, std::vector<std::set<uint_fast64_t>>> EdgeAbstractor<DdType, ValueType>::computeRelevantPredicates() const {
541 std::pair<std::set<uint_fast64_t>, std::vector<std::set<uint_fast64_t>>> result;
542
543 // To start with, all predicates related to the guard are relevant source predicates.
544 result.first = localExpressionInformation.getExpressionsUsingVariables(edge.get().getGuard().getVariables());
545
546 // Then, we add the predicates that become relevant, because of some update.
547 for (auto const& destination : edge.get().getDestinations()) {
548 std::pair<std::set<uint_fast64_t>, std::set<uint_fast64_t>> relevantUpdatePredicates = computeRelevantPredicates(destination.getOrderedAssignments());
549 result.first.insert(relevantUpdatePredicates.first.begin(), relevantUpdatePredicates.first.end());
550 result.second.push_back(relevantUpdatePredicates.second);
551 }
552
553 return result;
554}
555
556template<storm::dd::DdType DdType, typename ValueType>
557bool EdgeAbstractor<DdType, ValueType>::relevantPredicatesChanged(
558 std::pair<std::set<uint_fast64_t>, std::vector<std::set<uint_fast64_t>>> const& newRelevantPredicates) const {
559 if (newRelevantPredicates.first.size() > relevantPredicatesAndVariables.first.size()) {
560 return true;
561 }
562
563 for (uint_fast64_t index = 0; index < edge.get().getNumberOfDestinations(); ++index) {
564 if (newRelevantPredicates.second[index].size() > relevantPredicatesAndVariables.second[index].size()) {
565 return true;
566 }
567 }
568
569 return false;
570}
571
572template<storm::dd::DdType DdType, typename ValueType>
573void EdgeAbstractor<DdType, ValueType>::addMissingPredicates(
574 std::pair<std::set<uint_fast64_t>, std::vector<std::set<uint_fast64_t>>> const& newRelevantPredicates) {
575 // Determine and add new relevant source predicates.
576 std::vector<std::pair<storm::expressions::Variable, uint_fast64_t>> newSourceVariables =
577 this->getAbstractionInformation().declareNewVariables(relevantPredicatesAndVariables.first, newRelevantPredicates.first);
578 for (auto const& element : newSourceVariables) {
579 allRelevantPredicates.insert(element.second);
580 smtSolver->add(storm::expressions::iff(element.first, this->getAbstractionInformation().getPredicateByIndex(element.second)));
581 decisionVariables.push_back(element.first);
582 }
583
584 // Insert the new variables into the record of relevant source variables.
585 relevantPredicatesAndVariables.first.insert(relevantPredicatesAndVariables.first.end(), newSourceVariables.begin(), newSourceVariables.end());
586 std::sort(relevantPredicatesAndVariables.first.begin(), relevantPredicatesAndVariables.first.end(),
587 [](std::pair<storm::expressions::Variable, uint_fast64_t> const& first, std::pair<storm::expressions::Variable, uint_fast64_t> const& second) {
588 return first.second < second.second;
589 });
590
591 // Do the same for every update.
592 for (uint_fast64_t index = 0; index < edge.get().getNumberOfDestinations(); ++index) {
593 std::vector<std::pair<storm::expressions::Variable, uint_fast64_t>> newSuccessorVariables =
594 this->getAbstractionInformation().declareNewVariables(relevantPredicatesAndVariables.second[index], newRelevantPredicates.second[index]);
595 for (auto const& element : newSuccessorVariables) {
596 allRelevantPredicates.insert(element.second);
597 smtSolver->add(storm::expressions::iff(element.first, this->getAbstractionInformation()
598 .getPredicateByIndex(element.second)
599 .substitute(edge.get().getDestination(index).getAsVariableToExpressionMap())));
600 decisionVariables.push_back(element.first);
601 }
602
603 relevantPredicatesAndVariables.second[index].insert(relevantPredicatesAndVariables.second[index].end(), newSuccessorVariables.begin(),
604 newSuccessorVariables.end());
605 std::sort(relevantPredicatesAndVariables.second[index].begin(), relevantPredicatesAndVariables.second[index].end(),
606 [](std::pair<storm::expressions::Variable, uint_fast64_t> const& first,
607 std::pair<storm::expressions::Variable, uint_fast64_t> const& second) { return first.second < second.second; });
608 }
609}
610
611template<storm::dd::DdType DdType, typename ValueType>
612storm::dd::Bdd<DdType> EdgeAbstractor<DdType, ValueType>::getSourceStateBdd(
614 std::vector<std::pair<storm::expressions::Variable, uint_fast64_t>> const& variablePredicates) const {
615 storm::dd::Bdd<DdType> result = this->getAbstractionInformation().getDdManager().getBddOne();
616 for (auto variableIndexPairIt = variablePredicates.rbegin(), variableIndexPairIte = variablePredicates.rend(); variableIndexPairIt != variableIndexPairIte;
617 ++variableIndexPairIt) {
618 auto const& variableIndexPair = *variableIndexPairIt;
619 if (model.getBooleanValue(variableIndexPair.first)) {
620 result &= this->getAbstractionInformation().encodePredicateAsSource(variableIndexPair.second);
621 } else {
622 result &= !this->getAbstractionInformation().encodePredicateAsSource(variableIndexPair.second);
623 }
624 }
625
626 STORM_LOG_ASSERT(!result.isZero(), "Source must not be empty.");
627 return result;
628}
629
630template<storm::dd::DdType DdType, typename ValueType>
631storm::dd::Bdd<DdType> EdgeAbstractor<DdType, ValueType>::getDistributionBdd(
633 std::vector<std::vector<std::pair<storm::expressions::Variable, uint_fast64_t>>> const& variablePredicates) const {
634 storm::dd::Bdd<DdType> result = this->getAbstractionInformation().getDdManager().getBddZero();
635
636 for (uint_fast64_t destinationIndex = 0; destinationIndex < edge.get().getNumberOfDestinations(); ++destinationIndex) {
637 storm::dd::Bdd<DdType> updateBdd = this->getAbstractionInformation().getDdManager().getBddOne();
638
639 // Translate block variables for this update into a successor block.
640 for (auto variableIndexPairIt = variablePredicates[destinationIndex].rbegin(), variableIndexPairIte = variablePredicates[destinationIndex].rend();
641 variableIndexPairIt != variableIndexPairIte; ++variableIndexPairIt) {
642 auto const& variableIndexPair = *variableIndexPairIt;
643 if (model.getBooleanValue(variableIndexPair.first)) {
644 updateBdd &= this->getAbstractionInformation().encodePredicateAsSuccessor(variableIndexPair.second);
645 } else {
646 updateBdd &= !this->getAbstractionInformation().encodePredicateAsSuccessor(variableIndexPair.second);
647 }
648 }
649
650 updateBdd &= this->getAbstractionInformation().encodeAux(destinationIndex, 0, this->getAbstractionInformation().getAuxVariableCount());
651 result |= updateBdd;
652 }
653
654 STORM_LOG_ASSERT(!result.isZero(), "Distribution must not be empty.");
655 return result;
656}
657
658template<storm::dd::DdType DdType, typename ValueType>
659storm::dd::Bdd<DdType> EdgeAbstractor<DdType, ValueType>::computeMissingDestinationIdentities() const {
660 storm::dd::Bdd<DdType> result = this->getAbstractionInformation().getDdManager().getBddZero();
661
662 for (uint_fast64_t destinationIndex = 0; destinationIndex < edge.get().getNumberOfDestinations(); ++destinationIndex) {
663 // Compute the identities that are missing for this update.
664 auto updateRelevantIt = relevantPredicatesAndVariables.second[destinationIndex].rbegin();
665 auto updateRelevantIte = relevantPredicatesAndVariables.second[destinationIndex].rend();
666
667 storm::dd::Bdd<DdType> updateIdentity = this->getAbstractionInformation().getDdManager().getBddOne();
668 for (uint_fast64_t predicateIndex = this->getAbstractionInformation().getNumberOfPredicates() - 1;; --predicateIndex) {
669 if (updateRelevantIt == updateRelevantIte || updateRelevantIt->second != predicateIndex) {
670 updateIdentity &= this->getAbstractionInformation().getPredicateIdentity(predicateIndex);
671 } else {
672 ++updateRelevantIt;
673 }
674
675 if (predicateIndex == 0) {
676 break;
677 }
678 }
679
680 result |= updateIdentity && this->getAbstractionInformation().encodeAux(destinationIndex, 0, this->getAbstractionInformation().getAuxVariableCount());
681 }
682 return result;
683}
684
685template<storm::dd::DdType DdType, typename ValueType>
687 if (forceRecomputation) {
688 this->recomputeCachedBdd();
689 } else {
690 cachedDd.bdd &= computeMissingDestinationIdentities();
691 }
692
693 STORM_LOG_TRACE("Edge produces " << cachedDd.bdd.getNonZeroCount() << " transitions.");
694
695 return cachedDd;
696}
697
698template<storm::dd::DdType DdType, typename ValueType>
700 storm::dd::Bdd<DdType> const& reachableStates, uint_fast64_t numberOfPlayer2Variables,
701 boost::optional<std::pair<storm::expressions::Variable, storm::expressions::Variable>> const& locationVariables) {
702 STORM_LOG_TRACE("Computing bottom state transitions of edge with index " << edgeId << ".");
703 BottomStateResult<DdType> result(this->getAbstractionInformation().getDdManager().getBddZero(),
704 this->getAbstractionInformation().getDdManager().getBddZero());
705
706 // If the guard of this command is a predicate, there are not bottom states/transitions.
707 if (skipBottomStates) {
708 STORM_LOG_TRACE("Skipping bottom state computation for this edge.");
709 return result;
710 }
711
712 storm::dd::Bdd<DdType> reachableStatesWithEdge = reachableStates && abstractGuard;
713 // needed?
714 // if (locationVariables) {
715 // reachableStatesWithEdge = (reachableStates && abstractGuard &&
716 // this->getAbstractionInformation().encodeLocation(locationVariables.get().first,
717 // edge.get().getSourceLocationIndex())).existsAbstract(this->getAbstractionInformation().getSourceLocationVariables());
718 // } else {
719 // reachableStatesWithEdge = (reachableStates &&
720 // abstractGuard).existsAbstract(this->getAbstractionInformation().getSourceLocationVariables());
721 // }
722
723 // Use the state abstractor to compute the set of abstract states that has this command enabled but
724 // still has a transition to a bottom state.
725 bottomStateAbstractor.constrain(reachableStatesWithEdge);
726 if (locationVariables) {
727 result.states = bottomStateAbstractor.getAbstractStates() && reachableStatesWithEdge &&
728 this->getAbstractionInformation().encodeLocation(locationVariables.get().first, edge.get().getSourceLocationIndex());
729 } else {
730 result.states = bottomStateAbstractor.getAbstractStates() && reachableStatesWithEdge;
731 }
732
733 // If the result is empty one time, we can skip the bottom state computation from now on.
734 if (result.states.isZero()) {
735 skipBottomStates = true;
736 }
737
738 // Now equip all these states with an actual transition to a bottom state.
739 result.transitions =
740 result.states && this->getAbstractionInformation().getAllPredicateIdentities() && this->getAbstractionInformation().getBottomStateBdd(false, false);
741
742 // Mark the states as bottom states.
743 result.states &= this->getAbstractionInformation().getBottomStateBdd(true, false);
744
745 // Add the command encoding and the next free player 2 encoding.
746 result.transitions &= this->getAbstractionInformation().encodePlayer1Choice(edgeId, 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 boost::optional<std::pair<storm::expressions::Variable, storm::expressions::Variable>> const& locationVariables) const {
756 storm::dd::Add<DdType, ValueType> result = this->getAbstractionInformation().getDdManager().template getAddZero<ValueType>();
757 for (uint_fast64_t destinationIndex = 0; destinationIndex < edge.get().getNumberOfDestinations(); ++destinationIndex) {
759 this->getAbstractionInformation()
760 .encodeAux(destinationIndex, 0, this->getAbstractionInformation().getAuxVariableCount())
761 .template toAdd<ValueType>() *
762 this->getAbstractionInformation().getDdManager().getConstant(evaluator.asRational(edge.get().getDestination(destinationIndex).getProbability()));
763 if (locationVariables) {
764 tmp *= this->getAbstractionInformation()
765 .encodeLocation(locationVariables.get().second, edge.get().getDestination(destinationIndex).getLocationIndex())
766 .template toAdd<ValueType>();
767 }
768 result += tmp;
769 }
770
771 storm::dd::Add<DdType, ValueType> tmp = this->getAbstractionInformation().getDdManager().template getAddOne<ValueType>();
772 if (locationVariables) {
773 tmp *= this->getAbstractionInformation().encodeLocation(locationVariables.get().first, edge.get().getSourceLocationIndex()).template toAdd<ValueType>();
774 }
775 tmp *=
776 this->getAbstractionInformation().encodePlayer1Choice(edgeId, this->getAbstractionInformation().getPlayer1VariableCount()).template toAdd<ValueType>();
777
778 result *= tmp;
779
780 return result;
781}
782
783template<storm::dd::DdType DdType, typename ValueType>
787
788template<storm::dd::DdType DdType, typename ValueType>
790 return abstractionInformation.get();
791}
792
793template<storm::dd::DdType DdType, typename ValueType>
794AbstractionInformation<DdType>& EdgeAbstractor<DdType, ValueType>::getAbstractionInformation() {
795 return abstractionInformation.get();
796}
797
798template<storm::dd::DdType DdType, typename ValueType>
800 skipBottomStates = true;
801}
802
805#ifdef STORM_HAVE_CARL
807#endif
808} // namespace jani
809} // namespace abstraction
810} // 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
void refine(std::vector< uint_fast64_t > const &predicates)
Refines the abstract edge with the given predicates.
BottomStateResult< DdType > getBottomStateTransitions(storm::dd::Bdd< DdType > const &reachableStates, uint_fast64_t numberOfPlayer2Variables, boost::optional< std::pair< storm::expressions::Variable, storm::expressions::Variable > > const &locationVariables)
Retrieves the transitions to bottom states of this edge.
EdgeAbstractor(uint64_t edgeId, storm::jani::Edge const &edge, AbstractionInformation< DdType > &abstractionInformation, std::shared_ptr< storm::utility::solver::SmtSolverFactory > const &smtSolverFactory, bool useDecomposition, bool addPredicatesForValidBlocks, bool debug)
Constructs an abstract edge 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.
storm::jani::Edge const & getConcreteEdge() const
Retrieves the concrete edge that is abstracted by this abstract edge.
std::set< storm::expressions::Variable > const & getAssignedVariables() const
Retrieves the assigned variables.
storm::expressions::Expression const & getGuard() const
Retrieves the guard of this edge.
uint64_t getNumberOfUpdates(uint64_t player1Choice) const
Retrieves the number of updates of this edge.
GameBddResult< DdType > abstract()
Computes the abstraction of the edge wrt.
storm::dd::Add< DdType, ValueType > getEdgeDecoratorAdd(boost::optional< std::pair< storm::expressions::Variable, storm::expressions::Variable > > const &locationVariables) const
Retrieves an ADD that maps the encoding of the edge, source/target locations and its updates to their...
storm::expressions::Expression const & getGuard() const
Retrieves the guard of this edge.
Definition Edge.cpp:65
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)