Storm 1.14.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
GameBasedMdpModelChecker.cpp
Go to the documentation of this file.
2
3#include <sstream>
4
14#include "storm/io/file.h"
39
40namespace storm::gbar {
41namespace modelchecker {
42
44using storm::gbar::abstraction::ExplicitQuantitativeResult;
45using storm::gbar::abstraction::ExplicitQuantitativeResultMinMax;
46using storm::gbar::abstraction::SymbolicQuantitativeGameResult;
47using storm::gbar::abstraction::SymbolicQuantitativeGameResultMinMax;
48
49template<storm::dd::DdType Type, typename ModelType>
52 std::shared_ptr<storm::utility::solver::SmtSolverFactory> const& smtSolverFactory)
53 : options(options),
54 smtSolverFactory(smtSolverFactory),
55 comparator(storm::utility::convertNumber<ValueType>(options.precision), options.relativeTerminationCriterion),
56 reuseQualitativeResults(false),
57 reuseQuantitativeResults(false),
58 solveMode(options.solveMode),
59 debug(options.debug) {
60 if (model.hasUndefinedConstants()) {
61 auto undefinedConstants = model.getUndefinedConstants();
62 std::vector<std::string> undefinedConstantNames;
63 for (auto undefinedConstant : undefinedConstants) {
64 undefinedConstantNames.emplace_back(undefinedConstant.getName());
65 }
66
68 "Model contains undefined constants ("
69 << boost::algorithm::join(undefinedConstantNames, ",")
70 << "). Game-based abstraction can treat such models, but you should make sure that you did not simply forget to define these "
71 "constants. In particular, it may be necessary to constrain the values of the undefined constants.");
72 }
73
74 if (model.isPrismProgram()) {
75 storm::prism::Program const& originalProgram = model.asPrismProgram();
76 STORM_LOG_THROW(
77 originalProgram.getModelType() == storm::prism::Program::ModelType::DTMC || originalProgram.getModelType() == storm::prism::Program::ModelType::MDP,
78 storm::exceptions::NotSupportedException, "Currently only DTMCs/MDPs are supported by the game-based model checker.");
79
80 auto flattenStart = std::chrono::high_resolution_clock::now();
81 // Flatten the modules if there is more than one.
82 if (originalProgram.getNumberOfModules() > 1) {
83 preprocessedModel = originalProgram.substituteFormulas().flattenModules(this->smtSolverFactory);
84 } else {
85 preprocessedModel = originalProgram;
86 }
87 auto flattenEnd = std::chrono::high_resolution_clock::now();
88 STORM_LOG_INFO("Flattened model in " << std::chrono::duration_cast<std::chrono::milliseconds>(flattenEnd - flattenStart).count() << "ms.");
89
90 STORM_LOG_TRACE("Game-based model checker got program " << preprocessedModel.asPrismProgram());
91 } else {
92 storm::jani::Model const& originalModel = model.asJaniModel();
94 storm::exceptions::NotSupportedException, "Currently only DTMCs/MDPs are supported by the game-based model checker.");
95
96 // Flatten the parallel composition.
97 preprocessedModel = model.asJaniModel().flattenComposition();
98 }
99
100 reuseQualitativeResults = options.reuseMode == abstraction::ReuseMode::All || options.reuseMode == abstraction::ReuseMode::Qualitative;
101 reuseQuantitativeResults = options.reuseMode == abstraction::ReuseMode::All || options.reuseMode == abstraction::ReuseMode::Quantitative;
102 maximalNumberOfAbstractions = options.maximalNumberOfAbstractions;
103 fixPlayer1Strategy = options.fixPlayer1Strategy;
104 fixPlayer2Strategy = options.fixPlayer2Strategy;
105}
106
107template<storm::dd::DdType Type, typename ModelType>
113
114template<storm::dd::DdType Type, typename ModelType>
115std::unique_ptr<storm::modelchecker::CheckResult> GameBasedMdpModelChecker<Type, ModelType>::computeUntilProbabilities(
117 storm::logic::UntilFormula const& pathFormula = checkTask.getFormula();
118 std::map<std::string, storm::expressions::Expression> labelToExpressionMapping;
119 if (preprocessedModel.isPrismProgram()) {
120 labelToExpressionMapping = preprocessedModel.asPrismProgram().getLabelToExpressionMapping();
121 } else {
122 storm::jani::Model const& janiModel = preprocessedModel.asJaniModel();
123 for (auto const& variable : janiModel.getGlobalVariables().getBooleanVariables()) {
124 if (variable.isTransient()) {
125 labelToExpressionMapping[variable.getName()] = janiModel.getLabelExpression(variable);
126 }
127 }
128 }
129
130 storm::expressions::Expression constraintExpression =
131 pathFormula.getLeftSubformula().toExpression(preprocessedModel.getManager(), labelToExpressionMapping);
132 storm::expressions::Expression targetStateExpression =
133 pathFormula.getRightSubformula().toExpression(preprocessedModel.getManager(), labelToExpressionMapping);
134
135 return performGameBasedAbstractionRefinement(env, checkTask.template substituteFormula<storm::logic::Formula>(pathFormula), constraintExpression,
136 targetStateExpression);
137}
138
139template<storm::dd::DdType Type, typename ModelType>
142 storm::logic::EventuallyFormula const& pathFormula = checkTask.getFormula();
143 std::map<std::string, storm::expressions::Expression> labelToExpressionMapping;
144 if (preprocessedModel.isPrismProgram()) {
145 labelToExpressionMapping = preprocessedModel.asPrismProgram().getLabelToExpressionMapping();
146 } else {
147 storm::jani::Model const& janiModel = preprocessedModel.asJaniModel();
148 for (auto const& variable : janiModel.getGlobalVariables().getBooleanVariables()) {
149 if (variable.isTransient()) {
150 labelToExpressionMapping[variable.getName()] = janiModel.getLabelExpression(variable);
151 }
152 }
153 }
154
155 storm::expressions::Expression constraintExpression = preprocessedModel.getManager().boolean(true);
156 storm::expressions::Expression targetStateExpression = pathFormula.getSubformula().toExpression(preprocessedModel.getManager(), labelToExpressionMapping);
157
158 return performGameBasedAbstractionRefinement(env, checkTask.template substituteFormula<storm::logic::Formula>(pathFormula), constraintExpression,
159 targetStateExpression);
160}
161
162template<storm::dd::DdType Type, typename ValueType>
163std::unique_ptr<storm::modelchecker::CheckResult> checkForResultAfterQualitativeCheck(
165 storm::dd::Bdd<Type> const& initialStates, storm::dd::Bdd<Type> const& prob0, storm::dd::Bdd<Type> const& prob1) {
166 std::unique_ptr<storm::modelchecker::CheckResult> result;
167
168 if (checkTask.isBoundSet()) {
169 // Despite having a bound, we create a quantitative result so that the next layer can perform the comparison.
170
171 if (player2Direction == storm::OptimizationDirection::Minimize) {
173 if ((prob1 && initialStates) == initialStates) {
174 result = std::make_unique<storm::modelchecker::ExplicitQuantitativeCheckResult<ValueType>>(storm::storage::sparse::state_type(0),
176 }
177 } else {
178 if (!(prob1 && initialStates).isZero()) {
179 result = std::make_unique<storm::modelchecker::ExplicitQuantitativeCheckResult<ValueType>>(storm::storage::sparse::state_type(0),
181 }
182 }
183 } else if (player2Direction == storm::OptimizationDirection::Maximize) {
185 if ((prob0 && initialStates) == initialStates) {
186 result = std::make_unique<storm::modelchecker::ExplicitQuantitativeCheckResult<ValueType>>(storm::storage::sparse::state_type(0),
188 }
189 } else {
190 if (!(prob0 && initialStates).isZero()) {
191 result = std::make_unique<storm::modelchecker::ExplicitQuantitativeCheckResult<ValueType>>(storm::storage::sparse::state_type(0),
193 }
194 }
195 }
196 } else {
197 if (player2Direction == storm::OptimizationDirection::Minimize && (prob1 && initialStates) == initialStates) {
198 result = std::make_unique<storm::modelchecker::ExplicitQuantitativeCheckResult<ValueType>>(storm::storage::sparse::state_type(0),
200 } else if (player2Direction == storm::OptimizationDirection::Maximize && (prob0 && initialStates) == initialStates) {
201 result = std::make_unique<storm::modelchecker::ExplicitQuantitativeCheckResult<ValueType>>(storm::storage::sparse::state_type(0),
203 }
204 }
205
206 return result;
207}
208
209template<storm::dd::DdType Type, typename ValueType>
210std::unique_ptr<storm::modelchecker::CheckResult> checkForResultAfterQualitativeCheck(
212 SymbolicQualitativeGameResultMinMax<Type> const& qualitativeResult) {
213 // Check whether we can already give the answer based on the current information.
214 std::unique_ptr<storm::modelchecker::CheckResult> result =
215 checkForResultAfterQualitativeCheck<Type, ValueType>(checkTask, storm::OptimizationDirection::Minimize, initialStates,
216 qualitativeResult.prob0Min.getPlayer1States(), qualitativeResult.prob1Min.getPlayer1States());
217 if (result) {
218 return result;
219 }
220 result = checkForResultAfterQualitativeCheck<Type, ValueType>(checkTask, storm::OptimizationDirection::Maximize, initialStates,
221 qualitativeResult.prob0Max.getPlayer1States(), qualitativeResult.prob1Max.getPlayer1States());
222 if (result) {
223 return result;
224 }
225 return result;
226}
227
228template<typename ValueType>
229std::unique_ptr<storm::modelchecker::CheckResult> checkForResultAfterQualitativeCheck(
231 storm::storage::BitVector const& initialStates, storm::storage::BitVector const& prob0, storm::storage::BitVector const& prob1) {
232 std::unique_ptr<storm::modelchecker::CheckResult> result;
233
234 if (checkTask.isBoundSet()) {
235 // Despite having a bound, we create a quantitative result so that the next layer can perform the comparison.
236
237 if (player2Direction == storm::OptimizationDirection::Minimize) {
239 if (initialStates.isSubsetOf(prob1)) {
240 result = std::make_unique<storm::modelchecker::ExplicitQuantitativeCheckResult<ValueType>>(storm::storage::sparse::state_type(0),
242 }
243 } else {
244 if (!initialStates.isDisjointFrom(prob1)) {
245 result = std::make_unique<storm::modelchecker::ExplicitQuantitativeCheckResult<ValueType>>(storm::storage::sparse::state_type(0),
247 }
248 }
249 } else if (player2Direction == storm::OptimizationDirection::Maximize) {
251 if (initialStates.isSubsetOf(prob0)) {
252 result = std::make_unique<storm::modelchecker::ExplicitQuantitativeCheckResult<ValueType>>(storm::storage::sparse::state_type(0),
254 }
255 } else {
256 if (!initialStates.isDisjointFrom(prob0)) {
257 result = std::make_unique<storm::modelchecker::ExplicitQuantitativeCheckResult<ValueType>>(storm::storage::sparse::state_type(0),
259 }
260 }
261 }
262 } else {
263 if (player2Direction == storm::OptimizationDirection::Minimize && initialStates.isSubsetOf(prob1)) {
264 result = std::make_unique<storm::modelchecker::ExplicitQuantitativeCheckResult<ValueType>>(storm::storage::sparse::state_type(0),
266 } else if (player2Direction == storm::OptimizationDirection::Maximize && initialStates.isSubsetOf(prob0)) {
267 result = std::make_unique<storm::modelchecker::ExplicitQuantitativeCheckResult<ValueType>>(storm::storage::sparse::state_type(0),
269 }
270 }
271
272 return result;
273}
274
275template<typename ValueType>
276std::unique_ptr<storm::modelchecker::CheckResult> checkForResultAfterQualitativeCheck(
278 ExplicitQualitativeGameResultMinMax const& qualitativeResult) {
279 // Check whether we can already give the answer based on the current information.
280 std::unique_ptr<storm::modelchecker::CheckResult> result =
281 checkForResultAfterQualitativeCheck<ValueType>(checkTask, storm::OptimizationDirection::Minimize, initialStates,
282 qualitativeResult.prob0Min.getPlayer1States(), qualitativeResult.prob1Min.getPlayer1States());
283 if (result) {
284 return result;
285 }
286 result = checkForResultAfterQualitativeCheck<ValueType>(checkTask, storm::OptimizationDirection::Maximize, initialStates,
287 qualitativeResult.prob0Max.getPlayer1States(), qualitativeResult.prob1Max.getPlayer1States());
288 if (result) {
289 return result;
290 }
291 return result;
292}
293
294template<typename ValueType>
295std::unique_ptr<storm::modelchecker::CheckResult> checkForResultAfterQuantitativeCheck(
297 std::pair<ValueType, ValueType> const& initialValueRange) {
298 std::unique_ptr<storm::modelchecker::CheckResult> result;
299
300 // If the minimum value exceeds an upper threshold or the maximum value is below a lower threshold, we can
301 // return the value because the property will definitely hold. Vice versa, if the minimum value exceeds an
302 // upper bound or the maximum value is below a lower bound, the property will definitely not hold and we can
303 // return the value.
304 if (!checkTask.isBoundSet()) {
305 return result;
306 }
307
308 ValueType const& lowerValue = initialValueRange.first;
309 ValueType const& upperValue = initialValueRange.second;
310
311 storm::logic::ComparisonType comparisonType = checkTask.getBoundComparisonType();
312 ValueType threshold = checkTask.getBoundThreshold();
313
314 if (storm::logic::isLowerBound(comparisonType)) {
315 if (player2Direction == storm::OptimizationDirection::Minimize) {
316 if ((storm::logic::isStrict(comparisonType) && lowerValue > threshold) || (!storm::logic::isStrict(comparisonType) && lowerValue >= threshold)) {
317 result = std::make_unique<storm::modelchecker::ExplicitQuantitativeCheckResult<ValueType>>(storm::storage::sparse::state_type(0), lowerValue);
318 }
319 } else {
320 if ((storm::logic::isStrict(comparisonType) && upperValue <= threshold) || (!storm::logic::isStrict(comparisonType) && upperValue < threshold)) {
321 result = std::make_unique<storm::modelchecker::ExplicitQuantitativeCheckResult<ValueType>>(storm::storage::sparse::state_type(0), upperValue);
322 }
323 }
324 } else {
325 if (player2Direction == storm::OptimizationDirection::Maximize) {
326 if ((storm::logic::isStrict(comparisonType) && upperValue < threshold) || (!storm::logic::isStrict(comparisonType) && upperValue <= threshold)) {
327 result = std::make_unique<storm::modelchecker::ExplicitQuantitativeCheckResult<ValueType>>(storm::storage::sparse::state_type(0), upperValue);
328 }
329 } else {
330 if ((storm::logic::isStrict(comparisonType) && lowerValue >= threshold) || (!storm::logic::isStrict(comparisonType) && lowerValue > threshold)) {
331 result = std::make_unique<storm::modelchecker::ExplicitQuantitativeCheckResult<ValueType>>(storm::storage::sparse::state_type(0), lowerValue);
332 }
333 }
334 }
335
336 return result;
337}
338
339template<typename ValueType>
340std::unique_ptr<storm::modelchecker::CheckResult> checkForResultAfterQuantitativeCheck(ValueType const& minValue, ValueType const& maxValue,
342 std::unique_ptr<storm::modelchecker::CheckResult> result;
343
344 // If the lower and upper bounds are close enough, we can return the result.
345 if (comparator.isEqual(minValue, maxValue)) {
346 result = std::make_unique<storm::modelchecker::ExplicitQuantitativeCheckResult<ValueType>>(storm::storage::sparse::state_type(0),
347 ValueType((minValue + maxValue) / ValueType(2)));
348 }
349
350 return result;
351}
352
353template<storm::dd::DdType Type, typename ValueType>
355 Environment const& env, storm::OptimizationDirection const& player1Direction, storm::OptimizationDirection const& player2Direction,
357 boost::optional<SymbolicQuantitativeGameResult<Type, ValueType>> const& startInfo = boost::none) {
358 STORM_LOG_TRACE("Performing quantative solution step. Player 1: " << player1Direction << ", player 2: " << player2Direction << ".");
359
360 // Compute the ingredients of the equation system.
361 storm::dd::Add<Type, ValueType> maybeStatesAdd = maybeStates.template toAdd<ValueType>();
362 storm::dd::Add<Type, ValueType> submatrix = maybeStatesAdd * game.getTransitionMatrix();
363 storm::dd::Add<Type, ValueType> prob1StatesAsColumn = prob1States.template toAdd<ValueType>().swapVariables(game.getRowColumnMetaVariablePairs());
364 storm::dd::Add<Type, ValueType> subvector = submatrix * prob1StatesAsColumn;
365 subvector = subvector.sumAbstract(game.getColumnVariables());
366
367 // Cut away all columns targeting non-maybe states.
368 submatrix *= maybeStatesAdd.swapVariables(game.getRowColumnMetaVariablePairs());
369
370 // Cut the starting vector to the maybe states of this query.
372 if (startInfo) {
373 startVector = startInfo.get().values * maybeStatesAdd;
374 } else {
375 startVector = game.getManager().template getAddZero<ValueType>();
376 }
377
378 // Create the solver and solve the equation system.
380 std::unique_ptr<storm::solver::SymbolicGameSolver<Type, ValueType>> solver =
381 solverFactory.create(submatrix, maybeStates, game.getIllegalPlayer1Mask(), game.getIllegalPlayer2Mask(), game.getRowVariables(),
383 solver->setGeneratePlayersStrategies(true);
384 auto values = solver->solveGame(env, player1Direction, player2Direction, startVector, subvector,
385 startInfo ? boost::make_optional(startInfo.get().getPlayer1Strategy()) : boost::none,
386 startInfo ? boost::make_optional(startInfo.get().getPlayer2Strategy()) : boost::none);
388 solver->getPlayer1Strategy(), solver->getPlayer2Strategy());
389}
390
391template<storm::dd::DdType Type, typename ValueType>
393 Environment const& env, storm::OptimizationDirection player1Direction, storm::OptimizationDirection player2Direction,
395 storm::dd::Add<Type, ValueType> const& initialStatesAdd, storm::dd::Bdd<Type> const& maybeStates,
396 boost::optional<SymbolicQuantitativeGameResult<Type, ValueType>> const& startInfo = boost::none) {
397 bool min = player2Direction == storm::OptimizationDirection::Minimize;
399
400 // We fix the strategies. That is, we take the decisions of the strategies obtained in the qualitiative
401 // preprocessing if possible.
402 storm::dd::Bdd<Type> combinedPlayer1QualitativeStrategies;
403 storm::dd::Bdd<Type> combinedPlayer2QualitativeStrategies;
404 if (min) {
405 combinedPlayer1QualitativeStrategies = (qualitativeResult.prob0Min.getPlayer1Strategy() || qualitativeResult.prob1Min.getPlayer1Strategy());
406 combinedPlayer2QualitativeStrategies = (qualitativeResult.prob0Min.getPlayer2Strategy() || qualitativeResult.prob1Min.getPlayer2Strategy());
407 } else {
408 combinedPlayer1QualitativeStrategies = (qualitativeResult.prob0Max.getPlayer1Strategy() || qualitativeResult.prob1Max.getPlayer1Strategy());
409 combinedPlayer2QualitativeStrategies = (qualitativeResult.prob0Max.getPlayer2Strategy() || qualitativeResult.prob1Max.getPlayer2Strategy());
410 }
411
412 result.player1Strategy = combinedPlayer1QualitativeStrategies;
413 result.player2Strategy = combinedPlayer2QualitativeStrategies;
414 result.values = game.getManager().template getAddZero<ValueType>();
415
416 auto start = std::chrono::high_resolution_clock::now();
417 if (!maybeStates.isZero()) {
418 STORM_LOG_TRACE("Solving " << maybeStates.getNonZeroCount() << " maybe states.");
419
420 // Solve the quantitative values of maybe states.
421 result = solveMaybeStates(env, player1Direction, player2Direction, game, maybeStates,
422 min ? qualitativeResult.prob1Min.getPlayer1States() : qualitativeResult.prob1Max.getPlayer1States(), startInfo);
423
424 // Cut the obtained strategies to the reachable states of the game.
425 result.getPlayer1Strategy() &= game.getReachableStates();
426 result.getPlayer2Strategy() &= game.getReachableStates();
427
428 // Extend the values of the maybe states by the qualitative values.
429 result.values += min ? qualitativeResult.prob1Min.getPlayer1States().template toAdd<ValueType>()
430 : qualitativeResult.prob1Max.getPlayer1States().template toAdd<ValueType>();
431 } else {
432 STORM_LOG_TRACE("No " << (player2Direction == storm::OptimizationDirection::Minimize ? "min" : "max") << " maybe states.");
433
434 // Extend the values of the maybe states by the qualitative values.
435 result.values += min ? qualitativeResult.prob1Min.getPlayer1States().template toAdd<ValueType>()
436 : qualitativeResult.prob1Max.getPlayer1States().template toAdd<ValueType>();
437 }
438
439 // Construct an ADD holding the initial values of initial states and extract the bound on the initial states.
440 storm::dd::Add<Type, ValueType> initialStateValueAdd = initialStatesAdd * result.values;
441
442 ValueType maxValueOverInitialStates = initialStateValueAdd.getMax();
443 initialStateValueAdd += (!game.getInitialStates()).template toAdd<ValueType>();
444 ValueType minValueOverInitialStates = initialStateValueAdd.getMin();
445
446 result.initialStatesRange = std::make_pair(minValueOverInitialStates, maxValueOverInitialStates);
447
448 result.player1Strategy =
449 combinedPlayer1QualitativeStrategies.existsAbstract(game.getPlayer1Variables()).ite(combinedPlayer1QualitativeStrategies, result.getPlayer1Strategy());
450 result.player2Strategy =
451 combinedPlayer2QualitativeStrategies.existsAbstract(game.getPlayer2Variables()).ite(combinedPlayer2QualitativeStrategies, result.getPlayer2Strategy());
452
453 auto end = std::chrono::high_resolution_clock::now();
454 STORM_LOG_TRACE("Obtained quantitative " << (min ? "lower" : "upper") << " bound "
455 << (min ? result.getInitialStatesRange().first : result.getInitialStatesRange().second) << " in "
456 << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms.");
457
458 return result;
459}
460
461template<typename ValueType>
463 Environment const& env, storm::OptimizationDirection player1Direction, storm::OptimizationDirection player2Direction,
464 storm::storage::SparseMatrix<ValueType> const& transitionMatrix, std::vector<uint64_t> const& player1Groups,
465 ExplicitQualitativeGameResultMinMax const& qualitativeResult, storm::storage::BitVector const& maybeStates, storage::ExplicitGameStrategyPair& strategyPair,
466 storm::dd::Odd const& odd, ExplicitQuantitativeResult<ValueType> const* startingQuantitativeResult = nullptr,
467 storage::ExplicitGameStrategyPair const* startingStrategyPair = nullptr,
468 boost::optional<PreviousExplicitResult<ValueType>> const& previousResult = boost::none) {
469 bool player2Min = player2Direction == storm::OptimizationDirection::Minimize;
470 auto const& player1Prob1States = player2Min ? qualitativeResult.getProb1Min().asExplicitQualitativeGameResult().getPlayer1States()
472 auto const& player2Prob0States = player2Min ? qualitativeResult.getProb0Min().asExplicitQualitativeGameResult().getPlayer2States()
474 auto const& player2Prob1States = player2Min ? qualitativeResult.getProb1Min().asExplicitQualitativeGameResult().getPlayer2States()
476
477 ExplicitQuantitativeResult<ValueType> result(maybeStates.size());
479
480 // If there are no maybe states, there is nothing we need to solve.
481 if (maybeStates.empty()) {
482 return result;
483 }
484
485 // If there is a previous result, unpack the previous values with respect to the new ODD.
486 if (previousResult) {
487 STORM_LOG_ASSERT(player2Min, "Can only reuse previous values when minimizing.");
488 previousResult.get().odd.oldToNewIndex(odd, [&previousResult, &result, player2Min, player1Prob1States](uint64_t oldOffset, uint64_t newOffset) {
489 if (!player1Prob1States.get(newOffset)) {
490 result.getValues()[newOffset] =
491 player2Min ? previousResult.get().values.getValues()[oldOffset] : previousResult.get().values.getValues()[oldOffset];
492 }
493 });
494 }
495
496 // Otherwise, we need to solve a (sub)game.
497 STORM_LOG_TRACE("[" << player1Direction << ", " << player2Direction << "]: Solving " << maybeStates.getNumberOfSetBits() << " maybe states.");
498
499 // Create the game by selecting all maybe player 2 states (non-prob0/1) of all maybe player 1 states.
500 std::vector<uint64_t> subPlayer1Groups(maybeStates.getNumberOfSetBits() + 1);
501 uint64_t position = 0;
502 uint64_t previousPlayer2States = 0;
503 storm::storage::BitVector player2MaybeStates(transitionMatrix.getRowGroupCount());
504 for (uint64_t state : maybeStates) {
505 subPlayer1Groups[position] = previousPlayer2States;
506
507 [[maybe_unused]] bool hasMaybePlayer2Successor = false;
508 for (uint64_t player2State = player1Groups[state]; player2State < player1Groups[state + 1]; ++player2State) {
509 if (!player2Prob0States.get(player2State) && !player2Prob1States.get(player2State)) {
510 player2MaybeStates.set(player2State);
511 hasMaybePlayer2Successor = true;
512 ++previousPlayer2States;
513 }
514 }
515 STORM_LOG_ASSERT(hasMaybePlayer2Successor, "Player 1 maybe state has no player2 maybe successor.");
516 ++position;
517 }
518 subPlayer1Groups.back() = previousPlayer2States;
519
520 // Create the player 2 matrix using the maybe player 2 states.
521 storm::storage::SparseMatrix<ValueType> submatrix = transitionMatrix.getSubmatrix(true, player2MaybeStates, maybeStates);
522 std::vector<ValueType> b = transitionMatrix.getConstrainedRowGroupSumVector(player2MaybeStates, player1Prob1States);
523
524 // Set up game solver.
525 auto gameSolver = storm::solver::GameSolverFactory<ValueType>().create(env, subPlayer1Groups, submatrix);
526
527 // Prepare the value storage for the maybe states. If the starting values were given, extract them now.
528 std::vector<ValueType> values(maybeStates.getNumberOfSetBits());
529 if (startingQuantitativeResult) {
530 storm::utility::vector::selectVectorValues(values, maybeStates, startingQuantitativeResult->getValues());
531 }
532 if (previousResult) {
533 STORM_LOG_ASSERT(!startingQuantitativeResult, "Cannot take two different hints.");
534 storm::utility::vector::selectVectorValues(values, maybeStates, result.getValues());
535 }
536
537 // Prepare scheduler storage.
538 std::vector<uint64_t> player1Scheduler(subPlayer1Groups.size() - 1);
539 std::vector<uint64_t> player2Scheduler(submatrix.getRowGroupCount());
540 if (startingStrategyPair) {
541 // If the starting strategy pair was provided, we need to extract the choices of the maybe states here.
542 uint64_t maybeStatePosition = 0;
543 previousPlayer2States = 0;
544 for (uint64_t state : maybeStates) {
545 uint64_t chosenPlayer2State = startingStrategyPair->getPlayer1Strategy().getChoice(state);
546
547 uint64_t previousPlayer2MaybeStatesForState = 0;
548 for (uint64_t player2State = player1Groups[state]; player2State < player1Groups[state + 1]; ++player2State) {
549 if (player2MaybeStates.get(player2State)) {
550 if (player2State == chosenPlayer2State) {
551 player1Scheduler[maybeStatePosition] = previousPlayer2MaybeStatesForState;
552 }
553
554 // Copy over the player 2 action (modulo making it local) as all rows for the player 2 state are taken.
555 if (startingStrategyPair->getPlayer2Strategy().hasDefinedChoice(player2State)) {
556 player2Scheduler[previousPlayer2States] =
557 startingStrategyPair->getPlayer2Strategy().getChoice(player2State) - transitionMatrix.getRowGroupIndices()[player2State];
558 } else {
559 player2Scheduler[previousPlayer2States] = 0;
560 }
561
562 ++previousPlayer2MaybeStatesForState;
563 ++previousPlayer2States;
564 }
565 }
566
567 ++maybeStatePosition;
568 }
569 STORM_LOG_ASSERT(previousPlayer2States == submatrix.getRowGroupCount(), "Expected correct number of player 2 states.");
570 }
571
572 // Solve actual game and track schedulers.
573 gameSolver->solveGame(env, player1Direction, player2Direction, values, b, &player1Scheduler, &player2Scheduler);
574
575 // Set values according to quantitative result (qualitative result has already been taken care of).
576 storm::utility::vector::setVectorValues(result.getValues(), maybeStates, values);
577
578 // Obtain strategies from solver and fuse them with the pre-existing strategy pair for the qualitative result.
579 uint64_t previousPlayer1MaybeStates = 0;
580 uint64_t previousPlayer2MaybeStates = 0;
581 for (uint64_t state : maybeStates) {
582 uint64_t previousPlayer2MaybeStatesForState = 0;
583 [[maybe_unused]] bool madePlayer1Choice = false;
584 for (uint64_t player2State = player1Groups[state]; player2State < player1Groups[state + 1]; ++player2State) {
585 if (player1Scheduler[previousPlayer1MaybeStates] == previousPlayer2MaybeStatesForState) {
586 strategyPair.getPlayer1Strategy().setChoice(state, player2State);
587 madePlayer1Choice = true;
588 }
589
590 if (player2MaybeStates.get(player2State)) {
591 strategyPair.getPlayer2Strategy().setChoice(player2State,
592 transitionMatrix.getRowGroupIndices()[player2State] + player2Scheduler[previousPlayer2MaybeStates]);
593
594 ++previousPlayer2MaybeStatesForState;
595 ++previousPlayer2MaybeStates;
596 }
597 }
598 STORM_LOG_ASSERT(madePlayer1Choice, "[" << player1Direction << "]: player 1 state " << state
599 << " did not make a choice, scheduler: " << player1Scheduler[previousPlayer1MaybeStates] << ".");
600
601 ++previousPlayer1MaybeStates;
602 }
603
604 return result;
605}
606
607template<storm::dd::DdType Type, typename ModelType>
608std::unique_ptr<storm::modelchecker::CheckResult> GameBasedMdpModelChecker<Type, ModelType>::performGameBasedAbstractionRefinement(
610 storm::expressions::Expression const& constraintExpression, storm::expressions::Expression const& targetStateExpression) {
611 STORM_LOG_THROW(checkTask.isOnlyInitialStatesRelevantSet(), storm::exceptions::InvalidPropertyException,
612 "The game-based abstraction refinement model checker can only compute the result for the initial states.");
613
614 // Optimization: do not compute both bounds if not necessary (e.g. if bound given and exceeded, etc.)
615 totalWatch.start();
616
617 // Set up initial predicates.
618 setupWatch.start();
619 std::vector<storm::expressions::Expression> initialPredicates = getInitialPredicates(constraintExpression, targetStateExpression);
620
621 // Derive the optimization direction for player 1 (assuming menu-game abstraction).
622 storm::OptimizationDirection player1Direction = getPlayer1Direction(checkTask);
623
624 // Create the abstractor.
625 if (preprocessedModel.isPrismProgram()) {
626 abstractor = std::make_shared<storm::gbar::abstraction::prism::PrismMenuGameAbstractor<Type, ValueType>>(env, preprocessedModel.asPrismProgram(),
627 smtSolverFactory, options);
628 } else {
629 abstractor = std::make_shared<storm::gbar::abstraction::jani::JaniMenuGameAbstractor<Type, ValueType>>(env, preprocessedModel.asJaniModel(),
630 smtSolverFactory, options);
631 }
632 std::unique_ptr<storm::modelchecker::CheckResult> result;
633 abstractor->getDdManager().execute([&]() {
634 if (!constraintExpression.isTrue()) {
635 abstractor->addTerminalStates(!constraintExpression);
636 }
637 abstractor->addTerminalStates(targetStateExpression);
638 abstractor->setTargetStates(targetStateExpression);
639
640 // Create a refiner that can be used to refine the abstraction when needed.
641 storm::gbar::abstraction::MenuGameRefiner<Type, ValueType> refiner(*abstractor, smtSolverFactory->create(preprocessedModel.getManager()), options);
642 refiner.refine(initialPredicates, false);
643
644 storm::dd::Bdd<Type> globalConstraintStates = abstractor->getStates(constraintExpression);
645 storm::dd::Bdd<Type> globalTargetStates = abstractor->getStates(targetStateExpression);
646 setupWatch.stop();
647
648 // Enter the main-loop of abstraction refinement.
649 boost::optional<SymbolicQualitativeGameResultMinMax<Type>> previousSymbolicQualitativeResult = boost::none;
650 boost::optional<SymbolicQuantitativeGameResult<Type, ValueType>> previousSymbolicMinQuantitativeResult = boost::none;
651 boost::optional<PreviousExplicitResult<ValueType>> previousExplicitResult = boost::none;
652 uint64_t peakPlayer1States = 0;
653 uint64_t peakTransitions = 0;
654 for (iteration = 0; iteration < maximalNumberOfAbstractions; ++iteration) {
655 auto iterationStart = std::chrono::high_resolution_clock::now();
656 STORM_LOG_TRACE("Starting iteration " << iteration << ".");
657
658 // (1) build the abstraction.
659 storm::utility::Stopwatch abstractionWatch(true);
660 storm::gbar::abstraction::MenuGame<Type, ValueType> game = abstractor->abstract();
661 abstractionWatch.stop();
662 totalAbstractionWatch.add(abstractionWatch);
663
664 uint64_t numberOfPlayer1States = game.getNumberOfStates();
665 peakPlayer1States = std::max(peakPlayer1States, numberOfPlayer1States);
666 uint64_t numberOfTransitions = game.getNumberOfTransitions();
667 peakTransitions = std::max(peakTransitions, numberOfTransitions);
668 STORM_LOG_INFO("Abstraction in iteration "
669 << iteration << " has " << numberOfPlayer1States << " player 1 states (" << game.getInitialStates().getNonZeroCount()
670 << " initial), " << game.getNumberOfPlayer2States() << " player 2 states, " << numberOfTransitions << " transitions, "
671 << game.getBottomStates().getNonZeroCount() << " bottom states, " << abstractor->getNumberOfPredicates() << " predicate(s), "
672 << game.getTransitionMatrix().getNodeCount() << " nodes (transition matrix) (computed in "
673 << abstractionWatch.getTimeInMilliseconds() << "ms).");
674
675 // (2) Prepare initial, constraint and target state BDDs for later use.
676 storm::dd::Bdd<Type> initialStates = game.getInitialStates();
677 // STORM_LOG_THROW(initialStates.getNonZeroCount() == 1 || checkTask.isBoundSet(), storm::exceptions::InvalidPropertyException,
678 // "Game-based abstraction refinement requires a bound on the formula for model with " << initialStates.getNonZeroCount() << "
679 // initial states.");
680 storm::dd::Bdd<Type> constraintStates = globalConstraintStates && game.getReachableStates();
681 storm::dd::Bdd<Type> targetStates = globalTargetStates && game.getReachableStates();
682 if (player1Direction == storm::OptimizationDirection::Minimize) {
683 targetStates |= game.getBottomStates();
684 }
685
686 // #ifdef LOCAL_DEBUG
687 // initialStates.template toAdd<ValueType>().exportToDot("init" + std::to_string(iteration) + ".dot");
688 // targetStates.template toAdd<ValueType>().exportToDot("target" + std::to_string(iteration) + ".dot");
689 // abstractor->exportToDot("game" + std::to_string(iteration) + ".dot", targetStates, game.getManager().getBddOne());
690 // game.getReachableStates().template toAdd<ValueType>().exportToDot("reach" + std::to_string(iteration) + ".dot");
691 // #endif
692
693 if (solveMode == abstraction::SolveMode::Dd) {
694 result = performSymbolicAbstractionSolutionStep(env, checkTask, game, player1Direction, initialStates, constraintStates, targetStates, refiner,
695 previousSymbolicQualitativeResult, previousSymbolicMinQuantitativeResult);
696 } else {
697 result = performExplicitAbstractionSolutionStep(env, checkTask, game, player1Direction, initialStates, constraintStates, targetStates, refiner,
698 previousExplicitResult);
699 }
700
701 if (result) {
702 totalWatch.stop();
703 printStatistics(*abstractor, game, iteration, peakPlayer1States, peakTransitions);
704 return;
705 }
706
707 auto iterationEnd = std::chrono::high_resolution_clock::now();
708 STORM_LOG_INFO("Iteration " << iteration << " took " << std::chrono::duration_cast<std::chrono::milliseconds>(iterationEnd - iterationStart).count()
709 << "ms.");
710 }
711 });
712 if (result) {
713 return result;
714 }
715 totalWatch.stop();
716
717 // If this point is reached, we have given up on abstraction.
718 STORM_LOG_WARN("Could not derive result, maximal number of abstractions exceeded.");
719 return nullptr;
720}
721
722template<storm::dd::DdType Type, typename ModelType>
723std::unique_ptr<storm::modelchecker::CheckResult> GameBasedMdpModelChecker<Type, ModelType>::performSymbolicAbstractionSolutionStep(
724 Environment const& env, storm::modelchecker::CheckTask<storm::logic::Formula, ValueType> const& checkTask,
725 storm::gbar::abstraction::MenuGame<Type, ValueType> const& game, storm::OptimizationDirection player1Direction, storm::dd::Bdd<Type> const& initialStates,
726 storm::dd::Bdd<Type> const& constraintStates, storm::dd::Bdd<Type> const& targetStates,
727 storm::gbar::abstraction::MenuGameRefiner<Type, ValueType> const& refiner,
728 boost::optional<SymbolicQualitativeGameResultMinMax<Type>>& previousQualitativeResult,
729 boost::optional<SymbolicQuantitativeGameResult<Type, ValueType>>& previousMinQuantitativeResult) {
730 STORM_LOG_TRACE("Using dd-based solving.");
731
732 // Prepare transition matrix BDD.
733 storm::dd::Bdd<Type> transitionMatrixBdd = game.getTransitionMatrix().toBdd();
734
735 // (1) compute all states with probability 0/1 wrt. to the two different player 2 goals (min/max).
736 storm::utility::Stopwatch qualitativeWatch(true);
737 SymbolicQualitativeGameResultMinMax<Type> qualitativeResult =
738 computeProb01States(previousQualitativeResult, game, player1Direction, transitionMatrixBdd, constraintStates, targetStates);
739 std::unique_ptr<storm::modelchecker::CheckResult> result =
740 checkForResultAfterQualitativeCheck<Type, ValueType>(checkTask, initialStates, qualitativeResult);
741 if (result) {
742 return result;
743 }
744 previousQualitativeResult = qualitativeResult;
745 qualitativeWatch.stop();
746 totalSolutionWatch.add(qualitativeWatch);
747 STORM_LOG_INFO("Qualitative computation completed in " << qualitativeWatch.getTimeInMilliseconds() << "ms.");
748
749 // (2) compute the states for which we have to determine quantitative information.
750 storm::dd::Bdd<Type> maybeMin =
751 !(qualitativeResult.prob0Min.getPlayer1States() || qualitativeResult.prob1Min.getPlayer1States()) && game.getReachableStates();
752 storm::dd::Bdd<Type> maybeMax =
753 !(qualitativeResult.prob0Max.getPlayer1States() || qualitativeResult.prob1Max.getPlayer1States()) && game.getReachableStates();
754
755 // (3) if the initial states are not maybe states, then we can refine at this point.
756 storm::dd::Bdd<Type> initialMaybeStates = (initialStates && maybeMin) || (initialStates && maybeMax);
757 bool qualitativeRefinement = false;
758 if (initialMaybeStates.isZero()) {
759 // In this case, we know the result for the initial states for both player 2 minimizing and maximizing.
760 STORM_LOG_TRACE("No initial state is a 'maybe' state.");
761
762 STORM_LOG_INFO("Obtained qualitative bounds [0, 1] on the actual value for the initial states (after "
763 << totalWatch.getTimeInMilliseconds() << "ms in iteration " << this->iteration << "). Refining abstraction based on qualitative check.");
764
765 // If we get here, the initial states were all identified as prob0/1 states, but the value (0 or 1)
766 // depends on whether player 2 is minimizing or maximizing. Therefore, we need to find a place to refine.
767 storm::utility::Stopwatch refinementWatch(true);
768 qualitativeRefinement = refiner.refine(game, transitionMatrixBdd, qualitativeResult);
769 refinementWatch.stop();
770 totalRefinementWatch.add(refinementWatch);
771 STORM_LOG_INFO("Qualitative refinement completed in " << refinementWatch.getTimeInMilliseconds() << "ms.");
772 }
773
774 // (4) if we arrived at this point and no refinement was made, we need to compute the quantitative solution.
775 if (!qualitativeRefinement) {
776 // At this point, we know that we cannot answer the query without further numeric computation.
777 STORM_LOG_TRACE("Starting numerical solution step.");
778
779 storm::dd::Add<Type, ValueType> initialStatesAdd = initialStates.template toAdd<ValueType>();
780
781 SymbolicQuantitativeGameResultMinMax<Type, ValueType> quantitativeResult;
782
783 // (7) Solve the min values and check whether we can give the answer already.
784 storm::utility::Stopwatch quantitativeWatch(true);
785 quantitativeResult.min = computeQuantitativeResult(env, player1Direction, storm::OptimizationDirection::Minimize, game, qualitativeResult,
786 initialStatesAdd, maybeMin, reuseQuantitativeResults ? previousMinQuantitativeResult : boost::none);
787 quantitativeWatch.stop();
788 previousMinQuantitativeResult = quantitativeResult.min;
789 result =
790 checkForResultAfterQuantitativeCheck<ValueType>(checkTask, storm::OptimizationDirection::Minimize, quantitativeResult.min.getInitialStatesRange());
791 if (result) {
792 totalSolutionWatch.add(quantitativeWatch);
793 return result;
794 }
795
796 // (8) Solve the max values and check whether we can give the answer already.
797 quantitativeWatch.start();
798 quantitativeResult.max = computeQuantitativeResult(env, player1Direction, storm::OptimizationDirection::Maximize, game, qualitativeResult,
799 initialStatesAdd, maybeMax, boost::make_optional(quantitativeResult.min));
800 quantitativeWatch.stop();
801 result =
802 checkForResultAfterQuantitativeCheck<ValueType>(checkTask, storm::OptimizationDirection::Maximize, quantitativeResult.max.getInitialStatesRange());
803 totalSolutionWatch.add(quantitativeWatch);
804 if (result) {
805 return result;
806 }
807
808 ValueType minVal = quantitativeResult.min.getInitialStatesRange().first;
809 ValueType maxVal = quantitativeResult.max.getInitialStatesRange().second;
810 ValueType difference = maxVal - minVal;
811 if (std::is_same<ValueType, double>::value) {
812 std::stringstream differenceStream;
813 differenceStream.setf(std::ios::fixed, std::ios::floatfield);
814 differenceStream.precision(15);
815 differenceStream << difference;
816 STORM_LOG_INFO("Obtained quantitative bounds [" << minVal << ", " << maxVal << "] (difference " << differenceStream.str()
817 << ") on the actual value for the initial states in " << quantitativeWatch.getTimeInMilliseconds()
818 << "ms (after " << totalWatch.getTimeInMilliseconds() << "ms in iteration " << this->iteration
819 << ").");
820 } else {
821 STORM_LOG_INFO("Obtained quantitative bounds [" << minVal << ", " << maxVal << "] (approx. [" << storm::utility::convertNumber<double>(minVal)
822 << ", " << storm::utility::convertNumber<double>(maxVal) << "], difference " << difference
823 << ") on the actual value for the initial states in " << quantitativeWatch.getTimeInMilliseconds()
824 << "ms (after " << totalWatch.getTimeInMilliseconds() << "ms in iteration " << this->iteration
825 << ").");
826 }
827
828 // (9) Check whether the lower and upper bounds are close enough to terminate with an answer.
829 result = checkForResultAfterQuantitativeCheck<ValueType>(quantitativeResult.min.getInitialStatesRange().first,
830 quantitativeResult.max.getInitialStatesRange().second, comparator);
831 if (result) {
832 return result;
833 }
834
835 // Make sure that all strategies are still valid strategies.
836 STORM_LOG_ASSERT(quantitativeResult.min.getPlayer1Strategy().isZero() ||
837 quantitativeResult.min.getPlayer1Strategy().template toAdd<ValueType>().sumAbstract(game.getPlayer1Variables()).getMax() <= 1,
838 "Player 1 strategy for min is illegal.");
839 STORM_LOG_ASSERT(quantitativeResult.max.getPlayer1Strategy().isZero() ||
840 quantitativeResult.max.getPlayer1Strategy().template toAdd<ValueType>().sumAbstract(game.getPlayer1Variables()).getMax() <= 1,
841 "Player 1 strategy for max is illegal.");
842 STORM_LOG_ASSERT(quantitativeResult.min.getPlayer2Strategy().isZero() ||
843 quantitativeResult.min.getPlayer2Strategy().template toAdd<ValueType>().sumAbstract(game.getPlayer2Variables()).getMax() <= 1,
844 "Player 2 strategy for min is illegal.");
845 STORM_LOG_ASSERT(quantitativeResult.max.getPlayer2Strategy().isZero() ||
846 quantitativeResult.max.getPlayer2Strategy().template toAdd<ValueType>().sumAbstract(game.getPlayer2Variables()).getMax() <= 1,
847 "Player 2 strategy for max is illegal.");
848
849 // (10) If we arrived at this point, it means that we have all qualitative and quantitative
850 // information about the game, but we could not yet answer the query. In this case, we need to refine.
851 storm::utility::Stopwatch refinementWatch(true);
852 refiner.refine(game, transitionMatrixBdd, quantitativeResult);
853 refinementWatch.stop();
854 totalRefinementWatch.add(refinementWatch);
855 STORM_LOG_INFO("Quantitative refinement completed in " << refinementWatch.getTimeInMilliseconds() << "ms.");
856 }
857
858 // Return null to indicate no result has been found yet.
859 return nullptr;
860}
861
862template<typename ValueType>
864 storage::ExplicitGameStrategyPair& maxStrategyPair, std::vector<uint64_t> const& player1Groups,
865 std::vector<uint64_t> const& player2Groups, storm::storage::SparseMatrix<ValueType> const& transitionMatrix,
866 storm::storage::BitVector const& constraintStates, storm::storage::BitVector const& targetStates,
867 ExplicitQualitativeGameResultMinMax const& qualitativeResult, bool redirectPlayer1, bool redirectPlayer2, bool sanityCheck) {
868 if (!redirectPlayer1 && !redirectPlayer2) {
869 return;
870 }
871
872 for (uint64_t state = 0; state < player1Groups.size() - 1; ++state) {
873 bool isProb0Min = qualitativeResult.getProb0Min().getStates().get(state);
874
875 bool hasMinPlayer1Choice = false;
876 uint64_t lowerPlayer1Choice = 0;
877 bool hasMaxPlayer1Choice = false;
878 uint64_t upperPlayer1Choice = 0;
879
880 if (minStrategyPair.getPlayer1Strategy().hasDefinedChoice(state)) {
881 hasMinPlayer1Choice = true;
882 lowerPlayer1Choice = minStrategyPair.getPlayer1Strategy().getChoice(state);
883
884 if (maxStrategyPair.getPlayer2Strategy().hasDefinedChoice(lowerPlayer1Choice)) {
885 uint64_t lowerPlayer2Choice = minStrategyPair.getPlayer2Strategy().getChoice(lowerPlayer1Choice);
886 uint64_t upperPlayer2Choice = maxStrategyPair.getPlayer2Strategy().getChoice(lowerPlayer1Choice);
887
888 if (lowerPlayer2Choice == upperPlayer2Choice) {
889 continue;
890 }
891
892 bool redirect = true;
893 if (isProb0Min) {
894 for (auto const& entry : transitionMatrix.getRow(upperPlayer2Choice)) {
895 if (!qualitativeResult.getProb0Min().getStates().get(entry.getColumn())) {
896 redirect = false;
897 break;
898 }
899 }
900 }
901
902 if (redirectPlayer2 && redirect) {
903 minStrategyPair.getPlayer2Strategy().setChoice(lowerPlayer1Choice, upperPlayer2Choice);
904 }
905 }
906 }
907
908 bool lowerChoiceUnderUpperIsProb0 = false;
909 if (maxStrategyPair.getPlayer1Strategy().hasDefinedChoice(state)) {
910 upperPlayer1Choice = maxStrategyPair.getPlayer1Strategy().getChoice(state);
911
912 if (lowerPlayer1Choice != upperPlayer1Choice && minStrategyPair.getPlayer2Strategy().hasDefinedChoice(upperPlayer1Choice)) {
913 hasMaxPlayer1Choice = true;
914
915 uint64_t lowerPlayer2Choice = minStrategyPair.getPlayer2Strategy().getChoice(upperPlayer1Choice);
916 uint64_t upperPlayer2Choice = maxStrategyPair.getPlayer2Strategy().getChoice(upperPlayer1Choice);
917
918 if (lowerPlayer2Choice == upperPlayer2Choice) {
919 continue;
920 }
921
922 lowerChoiceUnderUpperIsProb0 = true;
923 for (auto const& entry : transitionMatrix.getRow(lowerPlayer2Choice)) {
924 if (!qualitativeResult.getProb0Min().getStates().get(entry.getColumn())) {
925 lowerChoiceUnderUpperIsProb0 = false;
926 break;
927 }
928 }
929
930 bool redirect = true;
931 if (lowerChoiceUnderUpperIsProb0) {
932 for (auto const& entry : transitionMatrix.getRow(upperPlayer2Choice)) {
933 if (!qualitativeResult.getProb0Min().getStates().get(entry.getColumn())) {
934 redirect = false;
935 break;
936 }
937 }
938 }
939
940 if (redirectPlayer2 && redirect) {
941 minStrategyPair.getPlayer2Strategy().setChoice(lowerPlayer1Choice, upperPlayer2Choice);
942 }
943 }
944 }
945
946 if (redirectPlayer1 && player1Direction == storm::OptimizationDirection::Minimize) {
947 if (hasMinPlayer1Choice && hasMaxPlayer1Choice && lowerPlayer1Choice != upperPlayer1Choice) {
948 if (!isProb0Min || lowerChoiceUnderUpperIsProb0) {
949 minStrategyPair.getPlayer1Strategy().setChoice(state, upperPlayer1Choice);
950 }
951 }
952 }
953 }
954}
955
956template<typename ValueType>
958 public:
959 ExplicitGameExporter() : showNonStrategyAlternatives(false) {
960 // Intentionally left empty.
961 }
962
963 void exportToJson(std::string const& filename, std::vector<uint64_t> const& player1Groups, std::vector<uint64_t> const& player2Groups,
964 storm::storage::SparseMatrix<ValueType> const& transitionMatrix, storm::storage::BitVector const& initialStates,
965 storm::storage::BitVector const& constraintStates, storm::storage::BitVector const& targetStates,
966 ExplicitQuantitativeResultMinMax<ValueType> const& quantitativeResult, storage::ExplicitGameStrategyPair const* minStrategyPair,
967 storage::ExplicitGameStrategyPair const* maxStrategyPair) {
968 // Export game as json.
969 std::ofstream outfile;
970 storm::io::openFile(filename, outfile);
971 exportGame(outfile, player1Groups, player2Groups, transitionMatrix, initialStates, constraintStates, targetStates, quantitativeResult, minStrategyPair,
972 maxStrategyPair);
973 storm::io::closeFile(outfile);
974 }
975
977 showNonStrategyAlternatives = value;
978 }
979
980 private:
981 struct NodeData {
982 NodeData(uint64_t id, uint64_t player, bool initial, bool target) : id(id), player(player), initial(initial), target(target) {
983 // Intentionally left empty.
984 }
985
986 uint64_t id;
987 uint64_t player; // 0 = probabilistic player, 1 = player 1, 2 = player 2
988 bool initial;
989 bool target;
990 };
991
992 struct EdgeData {
993 EdgeData(uint64_t id, uint64_t source, uint64_t target, ValueType probability, uint64_t label, bool min, bool max)
994 : id(id), source(source), target(target), probability(probability), label(label), min(min), max(max) {
995 // Intentionally left empty.
996 }
997
998 uint64_t id;
999 uint64_t source;
1000 uint64_t target;
1001 ValueType probability;
1002 uint64_t label;
1003 bool min;
1004 bool max;
1005 };
1006
1007 void exportEdge(std::ofstream& out, EdgeData const& data, bool& first) {
1008 if (!first) {
1009 out << ",\n";
1010 } else {
1011 first = false;
1012 }
1013 out << "\t\t{\n";
1014 out << "\t\t\t\"data\": {\n";
1015 out << "\t\t\t\t\"id\": \"" << data.id << "\",\n";
1016 if (data.probability != storm::utility::zero<ValueType>()) {
1017 out << "\t\t\t\t\"name\": \"" << data.probability << "\",\n";
1018 } else {
1019 out << "\t\t\t\t\"name\": \"" << data.label << "\",\n";
1020 }
1021 out << "\t\t\t\t\"source\": \"" << data.source << "\",\n";
1022 out << "\t\t\t\t\"target\": \"" << data.target << "\"\n";
1023 out << "\t\t\t},\n";
1024 out << "\t\t\t\"classes\": \"";
1025 if (data.min && data.max) {
1026 out << "minMaxEdge";
1027 } else if (data.min) {
1028 out << "minEdge";
1029 } else if (data.max) {
1030 out << "maxEdge";
1031 } else {
1032 out << "edge";
1033 }
1034 out << "\"\n";
1035 out << "\t\t}";
1036 }
1037
1038 void exportNode(std::ofstream& out, NodeData const& data, ExplicitQuantitativeResultMinMax<ValueType> const* quantitativeResult, bool& first) {
1039 if (!first) {
1040 out << ",\n";
1041 } else {
1042 first = false;
1043 }
1044 out << "\t\t{\n";
1045 out << "\t\t\t\"data\": {\n";
1046 out << "\t\t\t\t\"id\": \"" << data.id << "\",\n";
1047 out << "\t\t\t\t\"name\": \"" << data.id;
1048 if (quantitativeResult && data.player == 1) {
1049 out << " [" << quantitativeResult->getMin().getValues()[data.id] << ", " << quantitativeResult->getMax().getValues()[data.id] << "]";
1050 }
1051 out << "\"\n";
1052 out << "\t\t\t},\n";
1053 out << "\t\t\t\"group\": \"nodes\",\n";
1054 out << "\t\t\t\"classes\": \"";
1055 if (data.player == 1) {
1056 if (data.initial) {
1057 out << "initialNode";
1058 } else if (data.target) {
1059 out << "targetNode";
1060 } else {
1061 out << "node";
1062 }
1063 } else if (data.player == 2) {
1064 out << "pl2node";
1065 } else if (data.player == 0) {
1066 out << "plpnode";
1067 }
1068 out << "\"\n";
1069 out << "\t\t}";
1070 }
1071
1072 void exportGame(std::ofstream& out, std::vector<uint64_t> const& player1Groups, std::vector<uint64_t> const& player2Groups,
1073 storm::storage::SparseMatrix<ValueType> const& transitionMatrix, storm::storage::BitVector const& initialStates,
1074 storm::storage::BitVector const& constraintStates, storm::storage::BitVector const& targetStates,
1075 ExplicitQuantitativeResultMinMax<ValueType> const& quantitativeResult, storage::ExplicitGameStrategyPair const* minStrategyPair,
1076 storage::ExplicitGameStrategyPair const* maxStrategyPair) {
1077 // To export the game as JSON, we build some data structures through a traversal and then emit them.
1078 std::vector<NodeData> nodes;
1079 std::vector<EdgeData> edges;
1080
1081 std::vector<uint64_t> stack;
1082 for (uint64_t state : initialStates) {
1083 stack.push_back(state);
1084 }
1085 storm::storage::BitVector reachablePlayer1(player1Groups.size() - 1);
1086
1087 uint64_t edgeId = 0;
1088 while (!stack.empty()) {
1089 uint64_t currentState = stack.back();
1090 stack.pop_back();
1091
1092 nodes.emplace_back(currentState, 1, initialStates.get(currentState), targetStates.get(currentState));
1093
1094 for (uint64_t player2State = player1Groups[currentState]; player2State < player1Groups[currentState + 1]; ++player2State) {
1095 bool emit = (minStrategyPair || maxStrategyPair) ? this->showNonStrategyAlternatives : true;
1096 bool min = false;
1097 bool max = false;
1098
1099 if (minStrategyPair && minStrategyPair->getPlayer1Strategy().hasDefinedChoice(currentState) &&
1100 minStrategyPair->getPlayer1Strategy().getChoice(currentState) == player2State) {
1101 emit = true;
1102 min = true;
1103 }
1104 if (maxStrategyPair && maxStrategyPair->getPlayer1Strategy().hasDefinedChoice(currentState) &&
1105 maxStrategyPair->getPlayer1Strategy().getChoice(currentState) == player2State) {
1106 emit = true;
1107 max = true;
1108 }
1109
1110 if (emit) {
1111 nodes.emplace_back(player2State, 2, false, false);
1112 edges.emplace_back(edgeId++, currentState, player2State, storm::utility::zero<ValueType>(), player2State - player1Groups[currentState], min,
1113 max);
1114
1115 for (uint64_t playerPState = player2Groups[player2State]; playerPState < player2Groups[player2State + 1]; ++playerPState) {
1116 emit = (minStrategyPair || maxStrategyPair) ? this->showNonStrategyAlternatives : true;
1117 min = false;
1118 max = false;
1119
1120 if (minStrategyPair && minStrategyPair->getPlayer2Strategy().hasDefinedChoice(player2State) &&
1121 minStrategyPair->getPlayer2Strategy().getChoice(player2State) == playerPState) {
1122 emit = true;
1123 min = true;
1124 }
1125 if (maxStrategyPair && maxStrategyPair->getPlayer2Strategy().hasDefinedChoice(player2State) &&
1126 maxStrategyPair->getPlayer2Strategy().getChoice(player2State) == playerPState) {
1127 emit = true;
1128 max = true;
1129 }
1130
1131 if (emit) {
1132 nodes.emplace_back(playerPState, 0, false, false);
1133 edges.emplace_back(edgeId++, player2State, playerPState, storm::utility::zero<ValueType>(),
1134 playerPState - player2Groups[player2State], min, max);
1135
1136 for (auto const& entry : transitionMatrix.getRow(playerPState)) {
1137 auto player1Successor = entry.getColumn();
1138 if (!reachablePlayer1.get(player1Successor)) {
1139 reachablePlayer1.set(player1Successor);
1140 stack.push_back(player1Successor);
1141 }
1142
1143 edges.emplace_back(edgeId++, playerPState, player1Successor, entry.getValue(), 0, false, false);
1144 }
1145 }
1146 }
1147 }
1148 }
1149 }
1150
1151 // Finally, export the data structures we built.
1152
1153 // Export nodes.
1154 out << "{\n\t\"nodes\": [\n";
1155 bool first = true;
1156 for (auto const& node : nodes) {
1157 exportNode(out, node, &quantitativeResult, first);
1158 }
1159 out << "\n\t],\n";
1160
1161 // Export edges.
1162 first = true;
1163 out << "\t\"edges\": [\n";
1164 for (auto const& edge : edges) {
1165 exportEdge(out, edge, first);
1166 }
1167 out << "\n\t]\n}\n";
1168 }
1169
1170 bool showNonStrategyAlternatives;
1171};
1172
1173template<typename ValueType>
1174void postProcessStrategies(Environment const& env, uint64_t iteration, storm::OptimizationDirection const& player1Direction,
1176 std::vector<uint64_t> const& player1Groups, std::vector<uint64_t> const& player2Groups,
1177 storm::storage::SparseMatrix<ValueType> const& transitionMatrix, storm::storage::BitVector const& initialStates,
1178 storm::storage::BitVector const& constraintStates, storm::storage::BitVector const& targetStates,
1179 ExplicitQuantitativeResultMinMax<ValueType> const& quantitativeResult, bool redirectPlayer1, bool redirectPlayer2,
1180 bool sanityCheck) {
1181 if (redirectPlayer1 || redirectPlayer2) {
1182 for (uint64_t state = 0; state < player1Groups.size() - 1; ++state) {
1183 STORM_LOG_ASSERT(targetStates.get(state) || minStrategyPair.getPlayer1Strategy().hasDefinedChoice(state),
1184 "Expected lower player 1 choice in state " << state << ".");
1185 STORM_LOG_ASSERT(targetStates.get(state) || maxStrategyPair.getPlayer1Strategy().hasDefinedChoice(state),
1186 "Expected upper player 1 choice in state " << state << ".");
1187
1188 bool hasMinPlayer1Choice = false;
1189 uint64_t lowerPlayer1Choice = 0;
1190 ValueType lowerValueUnderMinChoicePlayer1 = storm::utility::zero<ValueType>();
1191 bool hasMaxPlayer1Choice = false;
1192 uint64_t upperPlayer1Choice = 0;
1193 ValueType lowerValueUnderMaxChoicePlayer1 = storm::utility::zero<ValueType>();
1194
1195 if (minStrategyPair.getPlayer1Strategy().hasDefinedChoice(state)) {
1196 hasMinPlayer1Choice = true;
1197 lowerPlayer1Choice = minStrategyPair.getPlayer1Strategy().getChoice(state);
1198
1199 STORM_LOG_ASSERT(minStrategyPair.getPlayer2Strategy().hasDefinedChoice(lowerPlayer1Choice),
1200 "Expected lower player 2 choice for state " << state << " (lower player 1 choice " << lowerPlayer1Choice << ").");
1201 uint64_t lowerPlayer2Choice = minStrategyPair.getPlayer2Strategy().getChoice(lowerPlayer1Choice);
1202
1203 ValueType lowerValueUnderLowerChoicePlayer2 =
1204 transitionMatrix.multiplyRowWithVector(lowerPlayer2Choice, quantitativeResult.getMin().getValues());
1205 lowerValueUnderMinChoicePlayer1 = lowerValueUnderLowerChoicePlayer2;
1206
1207 if (maxStrategyPair.getPlayer2Strategy().hasDefinedChoice(lowerPlayer1Choice)) {
1208 uint64_t upperPlayer2Choice = maxStrategyPair.getPlayer2Strategy().getChoice(lowerPlayer1Choice);
1209
1210 if (lowerPlayer2Choice != upperPlayer2Choice) {
1211 ValueType lowerValueUnderUpperChoicePlayer2 =
1212 transitionMatrix.multiplyRowWithVector(upperPlayer2Choice, quantitativeResult.getMin().getValues());
1213
1214 if (redirectPlayer2 && lowerValueUnderUpperChoicePlayer2 <= lowerValueUnderLowerChoicePlayer2) {
1215 lowerValueUnderMinChoicePlayer1 = lowerValueUnderUpperChoicePlayer2;
1216 minStrategyPair.getPlayer2Strategy().setChoice(lowerPlayer1Choice, upperPlayer2Choice);
1217 }
1218 }
1219 }
1220 }
1221
1222 if (maxStrategyPair.getPlayer1Strategy().hasDefinedChoice(state)) {
1223 upperPlayer1Choice = maxStrategyPair.getPlayer1Strategy().getChoice(state);
1224
1225 if (upperPlayer1Choice != lowerPlayer1Choice && minStrategyPair.getPlayer2Strategy().hasDefinedChoice(upperPlayer1Choice)) {
1226 hasMaxPlayer1Choice = true;
1227
1228 uint64_t lowerPlayer2Choice = minStrategyPair.getPlayer2Strategy().getChoice(upperPlayer1Choice);
1229
1230 ValueType lowerValueUnderLowerChoicePlayer2 =
1231 transitionMatrix.multiplyRowWithVector(lowerPlayer2Choice, quantitativeResult.getMin().getValues());
1232 lowerValueUnderMaxChoicePlayer1 = lowerValueUnderLowerChoicePlayer2;
1233
1234 STORM_LOG_ASSERT(maxStrategyPair.getPlayer2Strategy().hasDefinedChoice(upperPlayer1Choice),
1235 "Expected upper player 2 choice for state " << state << " (upper player 1 choice " << upperPlayer1Choice << ").");
1236 uint64_t upperPlayer2Choice = maxStrategyPair.getPlayer2Strategy().getChoice(upperPlayer1Choice);
1237
1238 if (lowerPlayer2Choice != upperPlayer2Choice) {
1239 ValueType lowerValueUnderUpperChoicePlayer2 =
1240 transitionMatrix.multiplyRowWithVector(upperPlayer2Choice, quantitativeResult.getMin().getValues());
1241
1242 if (redirectPlayer2 && lowerValueUnderUpperChoicePlayer2 <= lowerValueUnderLowerChoicePlayer2) {
1243 minStrategyPair.getPlayer2Strategy().setChoice(upperPlayer1Choice, upperPlayer2Choice);
1244 }
1245 }
1246 }
1247 }
1248
1249 if (redirectPlayer1 && player1Direction == storm::OptimizationDirection::Minimize) {
1250 if (hasMinPlayer1Choice && hasMaxPlayer1Choice && lowerPlayer1Choice != upperPlayer1Choice) {
1251 if (lowerValueUnderMaxChoicePlayer1 <= lowerValueUnderMinChoicePlayer1) {
1252 minStrategyPair.getPlayer1Strategy().setChoice(state, upperPlayer1Choice);
1253 }
1254 }
1255 }
1256 }
1257 }
1258
1259 if (sanityCheck) {
1261
1264 storm::storage::SparseMatrixBuilder<ValueType> dtmcMatrixBuilder(player1Groups.size() - 1, player1Groups.size() - 1);
1265 for (uint64_t state = 0; state < player1Groups.size() - 1; ++state) {
1266 if (targetStates.get(state)) {
1267 dtmcMatrixBuilder.addNextValue(state, state, storm::utility::one<ValueType>());
1268 } else {
1269 STORM_LOG_ASSERT(minStrategyPair.getPlayer1Strategy().hasDefinedChoice(state), "Expected min player 1 choice in state " << state << ".");
1270 STORM_LOG_ASSERT(minStrategyPair.getPlayer2Strategy().hasDefinedChoice(minStrategyPair.getPlayer1Strategy().getChoice(state)),
1271 "Expected max player 2 choice in state " << state << " with player 2 choice "
1272 << maxStrategyPair.getPlayer1Strategy().getChoice(state) << ".");
1273 uint64_t player2Choice = minStrategyPair.getPlayer2Strategy().getChoice(minStrategyPair.getPlayer1Strategy().getChoice(state));
1274 for (auto const& entry : transitionMatrix.getRow(player2Choice)) {
1275 dtmcMatrixBuilder.addNextValue(state, entry.getColumn(), entry.getValue());
1276 }
1277 }
1278 }
1279 auto dtmcMatrix = dtmcMatrixBuilder.build();
1280 std::vector<ValueType> sanityValues =
1282 env, storm::solver::SolveGoal<ValueType>(), dtmcMatrix, dtmcMatrix.transpose(), constraintStates, targetStates, false)
1283 .values;
1284
1285 ValueType maxDiff = storm::utility::zero<ValueType>();
1286 uint64_t maxState = 0;
1287 for (uint64_t state = 0; state < player1Groups.size() - 1; ++state) {
1288 ValueType diff = storm::utility::abs(ValueType(sanityValues[state] - quantitativeResult.getMin().getValues()[state]));
1289 if (diff > maxDiff) {
1290 maxState = state;
1291 maxDiff = diff;
1292 }
1293 }
1294 STORM_LOG_TRACE("Got maximal deviation of " << maxDiff << ".");
1295 STORM_LOG_WARN_COND(sanityComparator.isZero(maxDiff),
1296 "Deviation " << maxDiff << " between computed value (" << quantitativeResult.getMin().getValues()[maxState]
1297 << ") and sanity check value (" << sanityValues[maxState] << ") in state " << maxState
1298 << " appears to be too high. (Obtained bounds were [" << quantitativeResult.getMin().getValues()[maxState] << ", "
1299 << quantitativeResult.getMax().getValues()[maxState] << "].)");
1300
1303 dtmcMatrixBuilder = storm::storage::SparseMatrixBuilder<ValueType>(player1Groups.size() - 1, player1Groups.size() - 1);
1304 for (uint64_t state = 0; state < player1Groups.size() - 1; ++state) {
1305 if (targetStates.get(state)) {
1306 dtmcMatrixBuilder.addNextValue(state, state, storm::utility::one<ValueType>());
1307 } else {
1308 STORM_LOG_ASSERT(maxStrategyPair.getPlayer1Strategy().hasDefinedChoice(state), "Expected max player 1 choice in state " << state << ".");
1309 STORM_LOG_ASSERT(maxStrategyPair.getPlayer2Strategy().hasDefinedChoice(maxStrategyPair.getPlayer1Strategy().getChoice(state)),
1310 "Expected max player 2 choice in state " << state << " with player 2 choice "
1311 << maxStrategyPair.getPlayer1Strategy().getChoice(state) << ".");
1312 uint64_t player2Choice = maxStrategyPair.getPlayer2Strategy().getChoice(maxStrategyPair.getPlayer1Strategy().getChoice(state));
1313
1314 for (auto const& entry : transitionMatrix.getRow(player2Choice)) {
1315 dtmcMatrixBuilder.addNextValue(state, entry.getColumn(), entry.getValue());
1316 }
1317 }
1318 }
1319 dtmcMatrix = dtmcMatrixBuilder.build();
1321 env, storm::solver::SolveGoal<ValueType>(), dtmcMatrix, dtmcMatrix.transpose(), constraintStates, targetStates, false)
1322 .values;
1323
1325 maxState = 0;
1326 for (uint64_t state = 0; state < player1Groups.size() - 1; ++state) {
1327 ValueType diff = storm::utility::abs(ValueType(sanityValues[state] - quantitativeResult.getMax().getValues()[state]));
1328 if (diff > maxDiff) {
1329 maxState = state;
1330 maxDiff = diff;
1331 }
1332 }
1333 STORM_LOG_TRACE("Got maximal deviation of " << maxDiff << ".");
1334 STORM_LOG_WARN_COND(sanityComparator.isZero(maxDiff),
1335 "Deviation " << maxDiff << " between computed value (" << quantitativeResult.getMax().getValues()[maxState]
1336 << ") and sanity check value (" << sanityValues[maxState] << ") in state " << maxState
1337 << " appears to be too high. (Obtained bounds were [" << quantitativeResult.getMin().getValues()[maxState] << ", "
1338 << quantitativeResult.getMax().getValues()[maxState] << "].)");
1339 }
1340}
1341
1342template<storm::dd::DdType Type, typename ModelType>
1343std::unique_ptr<storm::modelchecker::CheckResult> GameBasedMdpModelChecker<Type, ModelType>::performExplicitAbstractionSolutionStep(
1346 storm::dd::Bdd<Type> const& initialStatesBdd, storm::dd::Bdd<Type> const& constraintStatesBdd, storm::dd::Bdd<Type> const& targetStatesBdd,
1348 STORM_LOG_TRACE("Using sparse solving.");
1349
1350 // (0) Start by transforming the necessary symbolic elements to explicit ones.
1351 storm::utility::Stopwatch translationWatch(true);
1353
1354 std::vector<std::set<storm::expressions::Variable>> labelingVariableSets = {game.getPlayer1Variables(), game.getPlayer2Variables()};
1356 game.getRowVariables(), game.getColumnVariables(), game.getNondeterminismVariables(), odd, odd, labelingVariableSets);
1357 auto& transitionMatrix = matrixAndLabeling.matrix;
1358 auto& player1Labeling = matrixAndLabeling.labelings.front();
1359 auto& player2Labeling = matrixAndLabeling.labelings.back();
1360
1361 // Create the player 2 row grouping from the labeling.
1362 std::vector<uint64_t> tmpPlayer2RowGrouping;
1363 for (uint64_t player1State = 0; player1State < transitionMatrix.getRowGroupCount(); ++player1State) {
1364 uint64_t lastLabel = std::numeric_limits<uint64_t>::max();
1365 for (uint64_t row = transitionMatrix.getRowGroupIndices()[player1State]; row < transitionMatrix.getRowGroupIndices()[player1State + 1]; ++row) {
1366 if (player1Labeling[row] != lastLabel) {
1367 tmpPlayer2RowGrouping.emplace_back(row);
1368 lastLabel = player1Labeling[row];
1369 }
1370 }
1371 }
1372 tmpPlayer2RowGrouping.emplace_back(player1Labeling.size());
1373
1374 std::vector<uint64_t> player1RowGrouping = transitionMatrix.swapRowGroupIndices(std::move(tmpPlayer2RowGrouping));
1375 auto const& player2RowGrouping = transitionMatrix.getRowGroupIndices();
1376
1377 // Create the player 1 groups and backward transitions (for both players).
1378 std::vector<uint64_t> player1Groups(player1RowGrouping.size());
1379 storm::storage::SparseMatrix<ValueType> player1BackwardTransitions = transitionMatrix.transpose(true);
1380 std::vector<uint64_t> player2BackwardTransitions(transitionMatrix.getRowGroupCount());
1381
1382 uint64_t player2State = 0;
1383 for (uint64_t player1State = 0; player1State < player1RowGrouping.size() - 1; ++player1State) {
1384 while (player1RowGrouping[player1State + 1] > player2RowGrouping[player2State]) {
1385 player2BackwardTransitions[player2State] = player1State;
1386 ++player2State;
1387 }
1388
1389 player1Groups[player1State + 1] = player2State;
1390 }
1391
1392 // Lift the player 1 labeling from rows to row groups (player 2 states).
1393 for (uint64_t player1State = 0; player1State < player1Groups.size() - 1; ++player1State) {
1394 for (uint64_t player2State = player1Groups[player1State]; player2State < player1Groups[player1State + 1]; ++player2State) {
1395 player1Labeling[player2State] = player1Labeling[player2RowGrouping[player2State]];
1396 }
1397 }
1398 player1Labeling.resize(player2RowGrouping.size() - 1);
1399
1400 // Create explicit representations of important state sets.
1401 storm::storage::BitVector initialStates = initialStatesBdd.toVector(odd);
1402 storm::storage::BitVector constraintStates = constraintStatesBdd.toVector(odd);
1403 storm::storage::BitVector targetStates = targetStatesBdd.toVector(odd);
1404 translationWatch.stop();
1405 totalTranslationWatch.add(translationWatch);
1406 STORM_LOG_INFO("Translation to explicit representation completed in " << translationWatch.getTimeInMilliseconds() << "ms.");
1407
1408 // Prepare the two strategies.
1409 storage::ExplicitGameStrategyPair minStrategyPair(initialStates.size(), transitionMatrix.getRowGroupCount());
1410 storage::ExplicitGameStrategyPair maxStrategyPair(initialStates.size(), transitionMatrix.getRowGroupCount());
1411
1412 // (1) compute all states with probability 0/1 wrt. to the two different player 2 goals (min/max).
1413 storm::utility::Stopwatch qualitativeWatch(true);
1414 ExplicitQualitativeGameResultMinMax qualitativeResult =
1415 computeProb01States(previousResult, odd, player1Direction, transitionMatrix, player1Groups, player1BackwardTransitions, player2BackwardTransitions,
1416 constraintStates, targetStates, minStrategyPair, maxStrategyPair);
1417 qualitativeWatch.stop();
1418 totalSolutionWatch.add(qualitativeWatch);
1419 STORM_LOG_INFO("Qualitative computation completed in " << qualitativeWatch.getTimeInMilliseconds() << "ms.");
1420
1421 std::unique_ptr<storm::modelchecker::CheckResult> result = checkForResultAfterQualitativeCheck<ValueType>(checkTask, initialStates, qualitativeResult);
1422 if (result) {
1423 return result;
1424 }
1425
1426 // (2) compute the states for which we have to determine quantitative information.
1427 storm::storage::BitVector maybeMin = ~(qualitativeResult.getProb0Min().getStates() | qualitativeResult.getProb1Min().getStates());
1428 storm::storage::BitVector maybeMax = ~(qualitativeResult.getProb0Max().getStates() | qualitativeResult.getProb1Max().getStates());
1429
1430 // (3) if the initial states are not maybe states, then we can refine at this point.
1431 storm::storage::BitVector initialMaybeStates = initialStates & (maybeMin | maybeMax);
1432 bool qualitativeRefinement = false;
1433 if (initialMaybeStates.empty()) {
1434 // In this case, we know the result for the initial states for both player 2 minimizing and maximizing.
1435 STORM_LOG_TRACE("No initial state is a 'maybe' state.");
1436
1437 STORM_LOG_INFO("Obtained qualitative bounds [0, 1] on the actual value for the initial states (after "
1438 << totalWatch.getTimeInMilliseconds() << "ms in iteration " << this->iteration << "). Refining abstraction based on qualitative check.");
1439
1440 // Post-process strategies for better refinements.
1441 storm::utility::Stopwatch strategyProcessingWatch(true);
1442 postProcessStrategies(player1Direction, minStrategyPair, maxStrategyPair, player1Groups, player2RowGrouping, transitionMatrix, constraintStates,
1443 targetStates, qualitativeResult, this->fixPlayer1Strategy, this->fixPlayer2Strategy, this->debug);
1444 strategyProcessingWatch.stop();
1445 totalStrategyProcessingWatch.add(strategyProcessingWatch);
1446 STORM_LOG_DEBUG("Postprocessed strategies in " << strategyProcessingWatch.getTimeInMilliseconds() << "ms.");
1447
1448 // If we get here, the initial states were all identified as prob0/1 states, but the value (0 or 1)
1449 // depends on whether player 2 is minimizing or maximizing. Therefore, we need to find a place to refine.
1450 storm::utility::Stopwatch refinementWatch(true);
1451 qualitativeRefinement = refiner.refine(game, odd, transitionMatrix, player1Groups, player1Labeling, player2Labeling, initialStates, constraintStates,
1452 targetStates, qualitativeResult, minStrategyPair, maxStrategyPair);
1453 refinementWatch.stop();
1454 totalRefinementWatch.add(refinementWatch);
1455 STORM_LOG_INFO("Qualitative refinement completed in " << refinementWatch.getTimeInMilliseconds() << "ms.");
1456 }
1457
1458 ExplicitQuantitativeResultMinMax<ValueType> quantitativeResult;
1459
1460 // (4) if we arrived at this point and no refinement was made, we need to compute the quantitative solution.
1461 if (!qualitativeRefinement) {
1462 // At this point, we know that we cannot answer the query without further numeric computation.
1463 STORM_LOG_TRACE("Starting numerical solution step.");
1464
1465 // (7) Solve the min values and check whether we can give the answer already.
1466 storm::utility::Stopwatch quantitativeWatch(true);
1467 quantitativeResult.setMin(computeQuantitativeResult<ValueType>(env, player1Direction, storm::OptimizationDirection::Minimize, transitionMatrix,
1468 player1Groups, qualitativeResult, maybeMin, minStrategyPair, odd, nullptr, nullptr,
1469 this->reuseQuantitativeResults ? previousResult : boost::none));
1470
1471 // Dispose of previous result as we now reused it.
1472 if (previousResult) {
1473 previousResult.get().clear();
1474 }
1475 quantitativeWatch.stop();
1476 result = checkForResultAfterQuantitativeCheck<ValueType>(checkTask, storm::OptimizationDirection::Minimize,
1477 quantitativeResult.getMin().getRange(initialStates));
1478 if (result) {
1479 totalSolutionWatch.add(quantitativeWatch);
1480 return result;
1481 }
1482
1483 // (8) Solve the max values and check whether we can give the answer already.
1484 quantitativeWatch.start();
1485 quantitativeResult.setMax(computeQuantitativeResult(env, player1Direction, storm::OptimizationDirection::Maximize, transitionMatrix, player1Groups,
1486 qualitativeResult, maybeMax, maxStrategyPair, odd, &quantitativeResult.getMin(), &minStrategyPair));
1487 quantitativeWatch.stop();
1488 result = checkForResultAfterQuantitativeCheck<ValueType>(checkTask, storm::OptimizationDirection::Maximize,
1489 quantitativeResult.getMax().getRange(initialStates));
1490 totalSolutionWatch.add(quantitativeWatch);
1491 if (result) {
1492 return result;
1493 }
1494
1495 ValueType minVal = quantitativeResult.getMin().getRange(initialStates).first;
1496 ValueType maxVal = quantitativeResult.getMax().getRange(initialStates).second;
1497 ValueType difference = maxVal - minVal;
1498 if (std::is_same<ValueType, double>::value) {
1499 std::stringstream differenceStream;
1500 differenceStream.setf(std::ios::fixed, std::ios::floatfield);
1501 differenceStream.precision(15);
1502 differenceStream << difference;
1503 STORM_LOG_INFO("Obtained quantitative bounds [" << minVal << ", " << maxVal << "] (difference " << differenceStream.str()
1504 << ") on the actual value for the initial states in " << quantitativeWatch.getTimeInMilliseconds()
1505 << "ms (after " << totalWatch.getTimeInMilliseconds() << "ms in iteration " << this->iteration
1506 << ").");
1507 } else {
1508 STORM_LOG_INFO("Obtained quantitative bounds [" << minVal << ", " << maxVal << "] (approx. [" << storm::utility::convertNumber<double>(minVal)
1509 << ", " << storm::utility::convertNumber<double>(maxVal) << "], difference " << difference
1510 << ") on the actual value for the initial states in " << quantitativeWatch.getTimeInMilliseconds()
1511 << "ms (after " << totalWatch.getTimeInMilliseconds() << "ms in iteration " << this->iteration
1512 << ").");
1513 }
1514
1515 // (9) Check whether the lower and upper bounds are close enough to terminate with an answer.
1516 result = checkForResultAfterQuantitativeCheck<ValueType>(quantitativeResult.getMin().getRange(initialStates).first,
1517 quantitativeResult.getMax().getRange(initialStates).second, comparator);
1518 if (result) {
1519 return result;
1520 }
1521
1522 // Post-process strategies for better refinements.
1523 storm::utility::Stopwatch strategyProcessingWatch(true);
1524 postProcessStrategies(env, this->iteration, player1Direction, minStrategyPair, maxStrategyPair, player1Groups, player2RowGrouping, transitionMatrix,
1525 initialStates, constraintStates, targetStates, quantitativeResult, this->fixPlayer1Strategy, this->fixPlayer2Strategy,
1526 this->debug);
1527 strategyProcessingWatch.stop();
1528 totalStrategyProcessingWatch.add(strategyProcessingWatch);
1529 STORM_LOG_DEBUG("Postprocessed strategies in " << strategyProcessingWatch.getTimeInMilliseconds() << "ms.");
1530
1531 // Make sure that all strategies are still valid strategies.
1532 STORM_LOG_ASSERT(minStrategyPair.getNumberOfUndefinedPlayer1States() <= targetStates.getNumberOfSetBits(),
1533 "Expected at most " << targetStates.getNumberOfSetBits() << " (number of target states) player 1 states with undefined choice but got "
1534 << minStrategyPair.getNumberOfUndefinedPlayer1States() << ".");
1535 STORM_LOG_ASSERT(maxStrategyPair.getNumberOfUndefinedPlayer1States() <= targetStates.getNumberOfSetBits(),
1536 "Expected at most " << targetStates.getNumberOfSetBits() << " (number of target states) player 1 states with undefined choice but got "
1537 << maxStrategyPair.getNumberOfUndefinedPlayer1States() << ".");
1538
1539 // (10) If we arrived at this point, it means that we have all qualitative and quantitative
1540 // information about the game, but we could not yet answer the query. In this case, we need to refine.
1541 storm::utility::Stopwatch refinementWatch(true);
1542 refiner.refine(game, odd, transitionMatrix, player1Groups, player1Labeling, player2Labeling, initialStates, constraintStates, targetStates,
1543 quantitativeResult, minStrategyPair, maxStrategyPair);
1544 refinementWatch.stop();
1545 totalRefinementWatch.add(refinementWatch);
1546 STORM_LOG_INFO("Quantitative refinement completed in " << refinementWatch.getTimeInMilliseconds() << "ms.");
1547
1548 if (this->reuseQuantitativeResults) {
1549 PreviousExplicitResult<ValueType> nextPreviousResult;
1550 nextPreviousResult.values = std::move(quantitativeResult.getMin());
1551 nextPreviousResult.odd = odd;
1552 previousResult = std::move(nextPreviousResult);
1553 STORM_LOG_TRACE("Prepared next previous result to reuse values.");
1554 }
1555 }
1556
1557 return nullptr;
1558}
1559
1560template<storm::dd::DdType Type, typename ModelType>
1561std::vector<storm::expressions::Expression> GameBasedMdpModelChecker<Type, ModelType>::getInitialPredicates(
1562 storm::expressions::Expression const& constraintExpression, storm::expressions::Expression const& targetStateExpression) {
1563 std::vector<storm::expressions::Expression> initialPredicates;
1564 if (preprocessedModel.isJaniModel()) {
1565 storm::expressions::VariableSetPredicateSplitter splitter(preprocessedModel.asJaniModel().getAllLocationExpressionVariables());
1566
1567 std::vector<storm::expressions::Expression> splitExpressions = splitter.split(targetStateExpression);
1568 initialPredicates.insert(initialPredicates.end(), splitExpressions.begin(), splitExpressions.end());
1569
1570 splitExpressions = splitter.split(constraintExpression);
1571 initialPredicates.insert(initialPredicates.end(), splitExpressions.begin(), splitExpressions.end());
1572 } else {
1573 if (!targetStateExpression.isTrue() && !targetStateExpression.isFalse()) {
1574 initialPredicates.push_back(targetStateExpression);
1575 }
1576 if (!constraintExpression.isTrue() && !constraintExpression.isFalse()) {
1577 initialPredicates.push_back(constraintExpression);
1578 }
1579 }
1580 return initialPredicates;
1581}
1582
1583template<storm::dd::DdType Type, typename ModelType>
1584storm::OptimizationDirection GameBasedMdpModelChecker<Type, ModelType>::getPlayer1Direction(
1585 storm::modelchecker::CheckTask<storm::logic::Formula, ValueType> const& checkTask) {
1586 if (preprocessedModel.getModelType() == storm::storage::SymbolicModelDescription::ModelType::DTMC) {
1587 return storm::OptimizationDirection::Maximize;
1588 } else if (checkTask.isOptimizationDirectionSet()) {
1589 return checkTask.getOptimizationDirection();
1590 } else if (checkTask.isBoundSet() && preprocessedModel.getModelType() != storm::storage::SymbolicModelDescription::ModelType::DTMC) {
1591 return storm::logic::isLowerBound(checkTask.getBoundComparisonType()) ? storm::OptimizationDirection::Minimize : storm::OptimizationDirection::Maximize;
1592 }
1593 STORM_LOG_THROW(false, storm::exceptions::InvalidPropertyException, "Could not derive player 1 optimization direction.");
1594 return storm::OptimizationDirection::Maximize;
1595}
1596
1597template<storm::dd::DdType Type>
1599 if (prob0) {
1601 "Unable to proceed without strategy.");
1602 } else {
1603 STORM_LOG_ASSERT(result.hasPlayer1Strategy() && ((result.getPlayer1States() && !targetStates).isZero() || !result.getPlayer1Strategy().isZero()),
1604 "Unable to proceed without strategy.");
1605 }
1606
1608 "Unable to proceed without strategy.");
1609
1610 return true;
1611}
1612
1613template<storm::dd::DdType Type>
1615 bool result = true;
1616 result &= checkQualitativeStrategies(true, qualitativeResult.prob0Min, targetStates);
1617 result &= checkQualitativeStrategies(false, qualitativeResult.prob1Min, targetStates);
1618 result &= checkQualitativeStrategies(true, qualitativeResult.prob0Max, targetStates);
1619 result &= checkQualitativeStrategies(false, qualitativeResult.prob1Max, targetStates);
1620 return result;
1621}
1622
1623template<storm::dd::DdType Type, typename ModelType>
1624ExplicitQualitativeGameResultMinMax GameBasedMdpModelChecker<Type, ModelType>::computeProb01States(
1625 boost::optional<PreviousExplicitResult<ValueType>> const& previousResult, storm::dd::Odd const& odd, storm::OptimizationDirection player1Direction,
1626 storm::storage::SparseMatrix<ValueType> const& transitionMatrix, std::vector<uint64_t> const& player1Groups,
1627 storm::storage::SparseMatrix<ValueType> const& player1BackwardTransitions, std::vector<uint64_t> const& player2BackwardTransitions,
1628 storm::storage::BitVector const& constraintStates, storm::storage::BitVector const& targetStates, storage::ExplicitGameStrategyPair& minStrategyPair,
1629 storage::ExplicitGameStrategyPair& maxStrategyPair) {
1630 ExplicitQualitativeGameResultMinMax result;
1631
1632 // ExplicitQualitativeGameResult problematicStates = storm::utility::graph::performProb0(transitionMatrix, player1Groups,
1633 // player1BackwardTransitions, player2BackwardTransitions, constraintStates, targetStates, storm::OptimizationDirection::Minimize,
1634 // storm::OptimizationDirection::Minimize);
1635
1636 result.prob0Min =
1637 storm::utility::graph::performProb0(transitionMatrix, player1Groups, player1BackwardTransitions, player2BackwardTransitions, constraintStates,
1638 targetStates, player1Direction, storm::OptimizationDirection::Minimize, &minStrategyPair);
1639 result.prob1Min =
1640 storm::utility::graph::performProb1(transitionMatrix, player1Groups, player1BackwardTransitions, player2BackwardTransitions, constraintStates,
1641 targetStates, player1Direction, storm::OptimizationDirection::Minimize, &minStrategyPair);
1642 result.prob0Max =
1643 storm::utility::graph::performProb0(transitionMatrix, player1Groups, player1BackwardTransitions, player2BackwardTransitions, constraintStates,
1644 targetStates, player1Direction, storm::OptimizationDirection::Maximize, &maxStrategyPair);
1645 result.prob1Max =
1646 storm::utility::graph::performProb1(transitionMatrix, player1Groups, player1BackwardTransitions, player2BackwardTransitions, constraintStates,
1647 targetStates, player1Direction, storm::OptimizationDirection::Maximize, &maxStrategyPair);
1648
1649 STORM_LOG_INFO("[" << player1Direction << ", " << storm::OptimizationDirection::Minimize << "]: " << result.prob0Min.player1States.getNumberOfSetBits()
1650 << " 'no', " << result.prob1Min.player1States.getNumberOfSetBits() << " 'yes'.");
1651 STORM_LOG_INFO("[" << player1Direction << ", " << storm::OptimizationDirection::Maximize << "]: " << result.prob0Max.player1States.getNumberOfSetBits()
1652 << " 'no', " << result.prob1Max.player1States.getNumberOfSetBits() << " 'yes'.");
1653
1654 return result;
1655}
1656
1657template<storm::dd::DdType Type, typename ModelType>
1658SymbolicQualitativeGameResultMinMax<Type> GameBasedMdpModelChecker<Type, ModelType>::computeProb01States(
1659 boost::optional<SymbolicQualitativeGameResultMinMax<Type>> const& previousQualitativeResult,
1661 storm::dd::Bdd<Type> const& transitionMatrixBdd, storm::dd::Bdd<Type> const& constraintStates, storm::dd::Bdd<Type> const& targetStates) {
1662 SymbolicQualitativeGameResultMinMax<Type> result;
1663
1664 if (reuseQualitativeResults) {
1665 // Depending on the player 1 direction, we choose a different order of operations.
1666 if (player1Direction == storm::OptimizationDirection::Minimize) {
1667 // (1) min/min: compute prob0 using the game functions
1668 result.prob0Min = storm::utility::graph::performProb0(game, transitionMatrixBdd, constraintStates, targetStates, player1Direction,
1669 storm::OptimizationDirection::Minimize, true, true);
1670
1671 // (2) min/min: compute prob1 using the MDP functions
1672 storm::dd::Bdd<Type> candidates = game.getReachableStates() && !result.prob0Min.player1States;
1674 game, transitionMatrixBdd, previousQualitativeResult ? previousQualitativeResult.get().prob1Min.player1States : targetStates, candidates);
1675
1676 // (3) min/min: compute prob1 using the game functions
1677 result.prob1Min = storm::utility::graph::performProb1(game, transitionMatrixBdd, constraintStates, targetStates, player1Direction,
1678 storm::OptimizationDirection::Minimize, true, true, boost::make_optional(prob1MinMinMdp));
1679
1680 // (4) min/max: compute prob 0 using the game functions
1681 result.prob0Max = storm::utility::graph::performProb0(game, transitionMatrixBdd, constraintStates, targetStates, player1Direction,
1682 storm::OptimizationDirection::Maximize, true, true);
1683
1684 // (5) min/max: compute prob 1 using the game functions
1685 // We know that only previous prob1 states can now be prob 1 states again, because the upper bound
1686 // values can only decrease over iterations.
1687 boost::optional<storm::dd::Bdd<Type>> prob1Candidates;
1688 if (previousQualitativeResult) {
1689 prob1Candidates = previousQualitativeResult.get().prob1Max.player1States;
1690 }
1691 result.prob1Max = storm::utility::graph::performProb1(game, transitionMatrixBdd, constraintStates, targetStates, player1Direction,
1692 storm::OptimizationDirection::Maximize, true, true, prob1Candidates);
1693 } else {
1694 // (1) max/max: compute prob0 using the game functions
1695 result.prob0Max = storm::utility::graph::performProb0(game, transitionMatrixBdd, constraintStates, targetStates, player1Direction,
1696 storm::OptimizationDirection::Maximize, true, true);
1697
1698 // (2) max/max: compute prob1 using the MDP functions, reuse prob1 states of last iteration to constrain the candidate states.
1699 storm::dd::Bdd<Type> candidates = game.getReachableStates() && !result.prob0Max.player1States;
1700 if (previousQualitativeResult) {
1701 candidates &= previousQualitativeResult.get().prob1Max.player1States;
1702 }
1703 storm::dd::Bdd<Type> prob1MaxMaxMdp = storm::utility::graph::performProb1E(game, transitionMatrixBdd, constraintStates, targetStates, candidates);
1704
1705 // (3) max/max: compute prob1 using the game functions, reuse prob1 states from the MDP precomputation
1706 result.prob1Max = storm::utility::graph::performProb1(game, transitionMatrixBdd, constraintStates, targetStates, player1Direction,
1707 storm::OptimizationDirection::Maximize, true, true, boost::make_optional(prob1MaxMaxMdp));
1708
1709 // (4) max/min: compute prob0 using the game functions
1710 result.prob0Min = storm::utility::graph::performProb0(game, transitionMatrixBdd, constraintStates, targetStates, player1Direction,
1711 storm::OptimizationDirection::Minimize, true, true);
1712
1713 // (5) max/min: compute prob1 using the game functions, use prob1 from max/max as the candidate set
1714 result.prob1Min = storm::utility::graph::performProb1(game, transitionMatrixBdd, constraintStates, targetStates, player1Direction,
1715 storm::OptimizationDirection::Minimize, true, true, boost::make_optional(prob1MaxMaxMdp));
1716 }
1717 } else {
1718 result.prob0Min = storm::utility::graph::performProb0(game, transitionMatrixBdd, constraintStates, targetStates, player1Direction,
1719 storm::OptimizationDirection::Minimize, true, true);
1720 result.prob1Min = storm::utility::graph::performProb1(game, transitionMatrixBdd, constraintStates, targetStates, player1Direction,
1721 storm::OptimizationDirection::Minimize, true, true);
1722 result.prob0Max = storm::utility::graph::performProb0(game, transitionMatrixBdd, constraintStates, targetStates, player1Direction,
1723 storm::OptimizationDirection::Maximize, true, true);
1724 result.prob1Max = storm::utility::graph::performProb1(game, transitionMatrixBdd, constraintStates, targetStates, player1Direction,
1725 storm::OptimizationDirection::Maximize, true, true);
1726 }
1727
1728 STORM_LOG_INFO("[" << player1Direction << ", " << storm::OptimizationDirection::Minimize << "]: " << result.prob0Min.player1States.getNonZeroCount()
1729 << " 'no', " << result.prob1Min.player1States.getNonZeroCount() << " 'yes'.");
1730 STORM_LOG_INFO("[" << player1Direction << ", " << storm::OptimizationDirection::Maximize << "]: " << result.prob0Max.player1States.getNonZeroCount()
1731 << " 'no', " << result.prob1Max.player1States.getNonZeroCount() << " 'yes'.");
1732
1733 STORM_LOG_ASSERT(checkQualitativeStrategies(result, targetStates), "Qualitative strategies appear to be broken.");
1734 return result;
1735}
1736
1737template<storm::dd::DdType Type, typename ModelType>
1738void GameBasedMdpModelChecker<Type, ModelType>::printStatistics(storm::gbar::abstraction::MenuGameAbstractor<Type, ValueType> const& abstractor,
1739 storm::gbar::abstraction::MenuGame<Type, ValueType> const& game, uint64_t refinements,
1740 uint64_t peakPlayer1States, uint64_t peakTransitions) const {
1741 storm::gbar::abstraction::AbstractionInformation<Type> const& abstractionInformation = abstractor.getAbstractionInformation();
1742
1743 std::ostringstream oss;
1744 oss << std::fixed << std::setprecision(2);
1745
1746 oss << '\n';
1747 oss << "Statistics:\n";
1748 oss << " * size of final game: " << game.getReachableStates().getNonZeroCount() << " player 1 states, " << game.getTransitionMatrix().getNonZeroCount()
1749 << " transitions\n";
1750 oss << " * peak size of game: " << peakPlayer1States << " player 1 states, " << peakTransitions << " transitions\n";
1751 oss << " * refinements: " << refinements << '\n';
1752 oss << " * predicates: " << abstractionInformation.getNumberOfPredicates() << "\n\n";
1753
1754 uint64_t totalAbstractionTimeMillis = totalAbstractionWatch.getTimeInMilliseconds();
1755 uint64_t totalTranslationTimeMillis = totalTranslationWatch.getTimeInMilliseconds();
1756 uint64_t totalStrategyProcessingTimeMillis = totalStrategyProcessingWatch.getTimeInMilliseconds();
1757 uint64_t totalSolutionTimeMillis = totalSolutionWatch.getTimeInMilliseconds();
1758 uint64_t totalRefinementTimeMillis = totalRefinementWatch.getTimeInMilliseconds();
1759 uint64_t setupTime = setupWatch.getTimeInMilliseconds();
1760 uint64_t totalTimeMillis = totalWatch.getTimeInMilliseconds();
1761
1762 oss << "Time breakdown:\n";
1763 oss << " * setup: " << setupTime << "ms (" << 100 * static_cast<double>(setupTime) / totalTimeMillis << "%)\n";
1764 oss << " * abstraction: " << totalAbstractionTimeMillis << "ms (" << 100 * static_cast<double>(totalAbstractionTimeMillis) / totalTimeMillis << "%)\n";
1765 if (this->solveMode == abstraction::SolveMode::Sparse) {
1766 oss << " * translation: " << totalTranslationTimeMillis << "ms (" << 100 * static_cast<double>(totalTranslationTimeMillis) / totalTimeMillis
1767 << "%)\n";
1768 if (fixPlayer1Strategy || fixPlayer2Strategy) {
1769 oss << " * strategy processing: " << totalStrategyProcessingTimeMillis << "ms ("
1770 << 100 * static_cast<double>(totalStrategyProcessingTimeMillis) / totalTimeMillis << "%)\n";
1771 }
1772 }
1773 oss << " * solution: " << totalSolutionTimeMillis << "ms (" << 100 * static_cast<double>(totalSolutionTimeMillis) / totalTimeMillis << "%)\n";
1774 oss << " * refinement: " << totalRefinementTimeMillis << "ms (" << 100 * static_cast<double>(totalRefinementTimeMillis) / totalTimeMillis << "%)\n";
1775 oss << " ---------------------------------------------\n";
1776 oss << " * total: " << totalTimeMillis << "ms\n\n";
1777
1778 STORM_LOG_STATISTICS(oss.str());
1779}
1780
1781template<storm::dd::DdType Type, typename ModelType>
1782storm::expressions::Expression GameBasedMdpModelChecker<Type, ModelType>::getExpression(storm::logic::Formula const& formula) {
1784 storm::exceptions::InvalidPropertyException, "The target states have to be given as label or an expression.");
1785 storm::expressions::Expression result;
1786 if (formula.isAtomicLabelFormula()) {
1787 result = preprocessedModel.asPrismProgram().getLabelExpression(formula.asAtomicLabelFormula().getLabel());
1788 } else if (formula.isAtomicExpressionFormula()) {
1789 result = formula.asAtomicExpressionFormula().getExpression();
1790 } else {
1791 result =
1792 formula.asBooleanLiteralFormula().isTrueFormula() ? preprocessedModel.getManager().boolean(true) : preprocessedModel.getManager().boolean(false);
1793 }
1794 return result;
1795}
1796
1797template class GameBasedMdpModelChecker<storm::dd::DdType::CUDD, storm::models::symbolic::Dtmc<storm::dd::DdType::CUDD, double>>;
1798template class GameBasedMdpModelChecker<storm::dd::DdType::CUDD, storm::models::symbolic::Mdp<storm::dd::DdType::CUDD, double>>;
1799template class GameBasedMdpModelChecker<storm::dd::DdType::Sylvan, storm::models::symbolic::Dtmc<storm::dd::DdType::Sylvan, double>>;
1800template class GameBasedMdpModelChecker<storm::dd::DdType::Sylvan, storm::models::symbolic::Mdp<storm::dd::DdType::Sylvan, double>>;
1801
1802template class GameBasedMdpModelChecker<storm::dd::DdType::Sylvan, storm::models::symbolic::Dtmc<storm::dd::DdType::Sylvan, storm::RationalNumber>>;
1803template class GameBasedMdpModelChecker<storm::dd::DdType::Sylvan, storm::models::symbolic::Mdp<storm::dd::DdType::Sylvan, storm::RationalNumber>>;
1804} // namespace modelchecker
1805} // namespace storm::gbar
Add< LibraryType, ValueType > swapVariables(std::vector< std::pair< storm::expressions::Variable, storm::expressions::Variable > > const &metaVariablePairs) const
Swaps the given pairs of meta variables in the ADD.
Definition Add.cpp:285
MatrixAndLabeling toLabeledMatrix(std::set< storm::expressions::Variable > const &rowMetaVariables, std::set< storm::expressions::Variable > const &columnMetaVariables, std::set< storm::expressions::Variable > const &groupMetaVariables, storm::dd::Odd const &rowOdd, storm::dd::Odd const &columnOdd, std::vector< std::set< storm::expressions::Variable > > const &labelMetaVariables=std::vector< std::set< storm::expressions::Variable > >()) const
Definition Add.cpp:745
ValueType getMax() const
Retrieves the highest function value of any encoding.
Definition Add.cpp:468
ValueType getMin() const
Retrieves the lowest function value of any encoding.
Definition Add.cpp:463
virtual uint_fast64_t getNonZeroCount() const override
Retrieves the number of encodings that are mapped to a non-zero value.
Definition Add.cpp:444
Add< LibraryType, ValueType > sumAbstract(std::set< storm::expressions::Variable > const &metaVariables) const
Sum-abstracts from the given meta variables.
Definition Add.cpp:171
virtual uint_fast64_t getNodeCount() const override
Retrieves the number of nodes necessary to represent the DD.
Definition Add.cpp:458
Bdd< LibraryType > toBdd() const
Converts the ADD to a BDD by mapping all values unequal to zero to 1.
Definition Add.cpp:1180
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
virtual uint_fast64_t getNonZeroCount() const override
Retrieves the number of encodings that are mapped to a non-zero value.
Definition Bdd.cpp:507
Bdd< LibraryType > swapVariables(std::vector< std::pair< storm::expressions::Variable, storm::expressions::Variable > > const &metaVariablePairs) const
Swaps the given pairs of meta variables in the BDD.
Definition Bdd.cpp:296
Odd createOdd() const
Creates an ODD based on the current BDD.
Definition Bdd.cpp:565
storm::storage::BitVector toVector(storm::dd::Odd const &rowOdd) const
Converts the BDD to a bit vector.
Definition Bdd.cpp:491
bool isFalse() const
Checks if the expression is equal to the boolean literal false.
ExpressionManager const & getManager() const
Retrieves the manager responsible for this expression.
bool isTrue() const
Checks if the expression is equal to the boolean literal true.
Expression boolean(bool value) const
Creates an expression that characterizes the given boolean literal.
std::size_t getNumberOfPredicates() const
Retrieves the number of predicates.
virtual storm::storage::BitVector const & getStates() const =0
ExplicitQuantitativeResult< ValueType > const & getMin() const
ExplicitQuantitativeResult< ValueType > const & getMax() const
virtual AbstractionInformation< DdType > const & getAbstractionInformation() const =0
Retrieves information about the abstraction.
This class represents a discrete-time stochastic two-player game.
Definition MenuGame.h:14
storm::dd::Bdd< Type > getBottomStates() const
Retrieves the bottom states of the model.
Definition MenuGame.cpp:64
void refine(std::vector< storm::expressions::Expression > const &predicates, bool allowInjection=true) const
Refines the abstractor with the given predicates.
boost::optional< std::pair< ValueType, ValueType > > initialStatesRange
std::pair< ValueType, ValueType > const & getInitialStatesRange() const
void exportToJson(std::string const &filename, std::vector< uint64_t > const &player1Groups, std::vector< uint64_t > const &player2Groups, storm::storage::SparseMatrix< ValueType > const &transitionMatrix, storm::storage::BitVector const &initialStates, storm::storage::BitVector const &constraintStates, storm::storage::BitVector const &targetStates, ExplicitQuantitativeResultMinMax< ValueType > const &quantitativeResult, storage::ExplicitGameStrategyPair const *minStrategyPair, storage::ExplicitGameStrategyPair const *maxStrategyPair)
void setMin(ExplicitQuantitativeResult< ValueType > &&newMin)
ExplicitQuantitativeResult< ValueType > const & getMin() const
void setMax(ExplicitQuantitativeResult< ValueType > &&newMax)
ExplicitQuantitativeResult< ValueType > const & getMax() const
virtual std::unique_ptr< storm::modelchecker::CheckResult > computeReachabilityProbabilities(Environment const &env, storm::modelchecker::CheckTask< storm::logic::EventuallyFormula, ValueType > const &checkTask) override
virtual std::unique_ptr< storm::modelchecker::CheckResult > computeUntilProbabilities(Environment const &env, storm::modelchecker::CheckTask< storm::logic::UntilFormula, ValueType > const &checkTask) override
GameBasedMdpModelChecker(storm::storage::SymbolicModelDescription const &model, abstraction::AbstractionOptions const &options=abstraction::AbstractionOptions(), std::shared_ptr< storm::utility::solver::SmtSolverFactory > const &smtSolverFactory=std::make_shared< storm::utility::solver::MathsatSmtSolverFactory >())
Constructs a model checker whose underlying model is implicitly given by the provided program.
virtual bool canHandle(storm::modelchecker::CheckTask< storm::logic::Formula, ValueType > const &checkTask) const override
Overridden methods from super class.
VariableSet & getGlobalVariables()
Retrieves the variables of this automaton.
Definition Model.cpp:717
ModelType const & getModelType() const
Retrieves the type of the model.
Definition Model.cpp:117
storm::expressions::Expression getLabelExpression(Variable const &transientVariable, std::vector< std::reference_wrapper< Automaton const > > const &automata) const
Creates the expression that characterizes all states in which the provided transient boolean variable...
Definition Model.cpp:1431
detail::Variables< Variable > getBooleanVariables()
Retrieves the boolean variables in this set.
storm::expressions::Expression const & getExpression() const
std::string const & getLabel() const
Formula const & getRightSubformula() const
Formula const & getLeftSubformula() const
virtual bool isTrueFormula() const override
virtual bool isBooleanLiteralFormula() const
Definition Formula.cpp:66
storm::expressions::Expression toExpression(storm::expressions::ExpressionManager const &manager, std::map< std::string, storm::expressions::Expression > const &labelToExpressionMapping={}) const
Takes the formula and converts it to an equivalent expression.
Definition Formula.cpp:591
BooleanLiteralFormula & asBooleanLiteralFormula()
Definition Formula.cpp:307
AtomicExpressionFormula & asAtomicExpressionFormula()
Definition Formula.cpp:315
bool isInFragment(FragmentSpecification const &fragment) const
Definition Formula.cpp:218
AtomicLabelFormula & asAtomicLabelFormula()
Definition Formula.cpp:323
virtual bool isAtomicLabelFormula() const
Definition Formula.cpp:82
virtual bool isAtomicExpressionFormula() const
Definition Formula.cpp:78
Formula const & getSubformula() const
bool isBoundSet() const
Retrieves whether there is a bound with which the values for the states will be compared.
Definition CheckTask.h:220
ValueType getBoundThreshold() const
Retrieves the value of the bound (if set).
Definition CheckTask.h:227
storm::logic::ComparisonType const & getBoundComparisonType() const
Retrieves the comparison type of the bound (if set).
Definition CheckTask.h:236
bool isOptimizationDirectionSet() const
Retrieves whether an optimization direction was set.
Definition CheckTask.h:148
FormulaType const & getFormula() const
Retrieves the formula from this task.
Definition CheckTask.h:141
storm::OptimizationDirection const & getOptimizationDirection() const
Retrieves the optimization direction (if set).
Definition CheckTask.h:155
bool isOnlyInitialStatesRelevantSet() const
Retrieves whether only the initial states are relevant in the computation.
Definition CheckTask.h:205
static DTMCSparseModelCheckingHelperReturnType< SolutionType > computeUntilProbabilities(Environment const &env, storm::solver::SolveGoal< ValueType, SolutionType > &&goal, storm::storage::SparseMatrix< ValueType > const &transitionMatrix, storm::storage::SparseMatrix< ValueType > const &backwardTransitions, storm::storage::BitVector const &phiStates, storm::storage::BitVector const &psiStates, bool qualitative, ModelCheckerHint const &hint=ModelCheckerHint())
storm::dd::DdManager< Type > & getManager() const
Retrieves the manager responsible for the DDs that represent this model.
Definition Model.cpp:89
storm::dd::Add< Type, ValueType > const & getTransitionMatrix() const
Retrieves the matrix representing the transitions of the model.
Definition Model.cpp:174
std::set< storm::expressions::Variable > const & getColumnVariables() const
Retrieves the meta variables used to encode the columns of the transition matrix and the vector indic...
Definition Model.cpp:194
storm::dd::Bdd< Type > const & getInitialStates() const
Retrieves the initial states of the model.
Definition Model.cpp:104
std::vector< std::pair< storm::expressions::Variable, storm::expressions::Variable > > const & getRowColumnMetaVariablePairs() const
Retrieves the pairs of row and column meta variables.
Definition Model.cpp:220
std::set< storm::expressions::Variable > const & getRowVariables() const
Retrieves the meta variables used to encode the rows of the transition matrix and the vector indices.
Definition Model.cpp:189
virtual uint_fast64_t getNumberOfTransitions() const override
Returns the number of (non-zero) transitions of the model.
Definition Model.cpp:79
storm::dd::Bdd< Type > const & getReachableStates() const
Retrieves the reachable states of the model.
Definition Model.cpp:99
virtual uint_fast64_t getNumberOfStates() const override
Returns the number of states of the model.
Definition Model.cpp:74
virtual std::set< storm::expressions::Variable > const & getNondeterminismVariables() const override
Retrieves the meta variables used to encode the nondeterminism in the model.
std::set< storm::expressions::Variable > const & getPlayer1Variables() const
Retrieeves the set of meta variables used to encode the nondeterministic choices of player 1.
std::set< storm::expressions::Variable > const & getPlayer2Variables() const
Retrieeves the set of meta variables used to encode the nondeterministic choices of player 2.
storm::dd::Bdd< Type > getIllegalPlayer1Mask() const
Retrieves a BDD characterizing all illegal player 1 choice encodings in the model.
uint64_t getNumberOfPlayer2States() const
Retrieves the number of player 2 states in the game.
storm::dd::Bdd< Type > getIllegalPlayer2Mask() const
Retrieves a BDD characterizing all illegal player 2 choice encodings in the model.
virtual std::unique_ptr< GameSolver< ValueType > > create(Environment const &env, storm::storage::SparseMatrix< storm::storage::sparse::state_type > const &player1Matrix, storm::storage::SparseMatrix< ValueType > const &player2Matrix) const
virtual std::unique_ptr< storm::solver::SymbolicGameSolver< Type, ValueType > > create(storm::dd::Add< Type, ValueType > const &A, storm::dd::Bdd< Type > const &allRows, storm::dd::Bdd< Type > const &illegalPlayer1Mask, storm::dd::Bdd< Type > const &illegalPlayer2Mask, std::set< storm::expressions::Variable > const &rowMetaVariables, std::set< storm::expressions::Variable > const &columnMetaVariables, std::vector< std::pair< storm::expressions::Variable, storm::expressions::Variable > > const &rowColumnMetaVariablePairs, std::set< storm::expressions::Variable > const &player1Variables, std::set< storm::expressions::Variable > const &player2Variables) const
A bit vector that is internally represented as a vector of 64-bit values.
Definition BitVector.h:16
bool isDisjointFrom(BitVector const &other) const
Checks whether none of the bits that are set in the current bit vector are also set in the given bit ...
bool empty() const
Retrieves whether no bits are set to true in this bit vector.
bool isSubsetOf(BitVector const &other) const
Checks whether all bits that are set in the current bit vector are also set in the given bit vector.
uint64_t getNumberOfSetBits() const
Returns the number of bits that are set to true in this bit vector.
void set(uint64_t index, bool value=true)
Sets the given truth value at the given index.
size_t size() const
Retrieves the number of bits this bit vector can store.
bool get(uint64_t index) const
Retrieves the truth value of the bit at the given index and performs a bound check.
bool hasDefinedChoice(uint64_t state) const
void setChoice(uint64_t state, uint64_t choice)
uint64_t getChoice(uint64_t state) const
A class that can be used to build a sparse matrix by adding value by value.
void addNextValue(index_type row, index_type column, value_type const &value)
Sets the matrix entry at the given row and column to the given value.
SparseMatrix< value_type > build(index_type overriddenRowCount=0, index_type overriddenColumnCount=0, index_type overriddenRowGroupCount=0)
A class that holds a possibly non-square matrix in the compressed row storage format.
const_rows getRow(index_type row) const
Returns an object representing the given row.
SparseMatrix getSubmatrix(bool useGroups, storm::storage::BitVector const &rowConstraint, storm::storage::BitVector const &columnConstraint, bool insertDiagonalEntries=false, storm::storage::BitVector const &makeZeroColumns=storm::storage::BitVector()) const
Creates a submatrix of the current matrix by dropping all rows and columns whose bits are not set to ...
std::vector< index_type > swapRowGroupIndices(std::vector< index_type > &&newRowGrouping)
Swaps the grouping of rows of this matrix.
value_type multiplyRowWithVector(index_type row, std::vector< value_type > const &vector) const
Multiplies a single row of the matrix with the given vector and returns the result.
index_type getRowGroupCount() const
Returns the number of row groups in the matrix.
std::vector< index_type > const & getRowGroupIndices() const
Returns the grouping of rows of this matrix.
std::vector< value_type > getConstrainedRowGroupSumVector(storm::storage::BitVector const &rowGroupConstraint, storm::storage::BitVector const &columnConstraint) const
Computes a vector whose entries represent the sums of selected columns for all rows in selected row g...
storm::storage::SparseMatrix< value_type > transpose(bool joinGroups=false, bool keepZeros=false) const
Transposes the matrix.
bool isZero(ValueType const &value) const
bool isEqual(ValueType const &value1, ValueType const &value2) const
A class that provides convenience operations to display run times.
Definition Stopwatch.h:13
#define STORM_LOG_INFO(message)
Definition logging.h:34
#define STORM_LOG_WARN(message)
Definition logging.h:35
#define STORM_LOG_DEBUG(message)
Definition logging.h:28
#define STORM_LOG_STATISTICS(message)
Definition logging.h:48
#define STORM_LOG_TRACE(message)
Definition logging.h:22
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:9
#define STORM_LOG_WARN_COND(cond, message)
Definition macros.h:54
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:39
SFTBDDChecker::ValueType ValueType
std::unique_ptr< storm::modelchecker::CheckResult > checkForResultAfterQuantitativeCheck(storm::modelchecker::CheckTask< storm::logic::Formula, ValueType > const &checkTask, storm::OptimizationDirection const &player2Direction, std::pair< ValueType, ValueType > const &initialValueRange)
void postProcessStrategies(storm::OptimizationDirection const &player1Direction, storage::ExplicitGameStrategyPair &minStrategyPair, storage::ExplicitGameStrategyPair &maxStrategyPair, std::vector< uint64_t > const &player1Groups, std::vector< uint64_t > const &player2Groups, storm::storage::SparseMatrix< ValueType > const &transitionMatrix, storm::storage::BitVector const &constraintStates, storm::storage::BitVector const &targetStates, ExplicitQualitativeGameResultMinMax const &qualitativeResult, bool redirectPlayer1, bool redirectPlayer2, bool sanityCheck)
std::unique_ptr< storm::modelchecker::CheckResult > checkForResultAfterQualitativeCheck(storm::modelchecker::CheckTask< storm::logic::Formula, ValueType > const &checkTask, storm::OptimizationDirection player2Direction, storm::dd::Bdd< Type > const &initialStates, storm::dd::Bdd< Type > const &prob0, storm::dd::Bdd< Type > const &prob1)
SymbolicQuantitativeGameResult< Type, ValueType > computeQuantitativeResult(Environment const &env, storm::OptimizationDirection player1Direction, storm::OptimizationDirection player2Direction, storm::gbar::abstraction::MenuGame< Type, ValueType > const &game, SymbolicQualitativeGameResultMinMax< Type > const &qualitativeResult, storm::dd::Add< Type, ValueType > const &initialStatesAdd, storm::dd::Bdd< Type > const &maybeStates, boost::optional< SymbolicQuantitativeGameResult< Type, ValueType > > const &startInfo=boost::none)
bool checkQualitativeStrategies(bool prob0, SymbolicQualitativeGameResult< Type > const &result, storm::dd::Bdd< Type > const &targetStates)
SymbolicQuantitativeGameResult< Type, ValueType > solveMaybeStates(Environment const &env, storm::OptimizationDirection const &player1Direction, storm::OptimizationDirection const &player2Direction, storm::gbar::abstraction::MenuGame< Type, ValueType > const &game, storm::dd::Bdd< Type > const &maybeStates, storm::dd::Bdd< Type > const &prob1States, boost::optional< SymbolicQuantitativeGameResult< Type, ValueType > > const &startInfo=boost::none)
void closeFile(std::ofstream &stream)
Close the given file after writing.
Definition file.h:47
void openFile(std::string const &filepath, std::ofstream &filestream, bool append=false, bool silent=false)
Open the given file for writing.
Definition file.h:18
bool isLowerBound(ComparisonType t)
bool isStrict(ComparisonType t)
FragmentSpecification reachability()
storm::storage::BitVector getStates(storm::logic::Formula const &propositionalFormula, bool formulaInverted, PomdpType const &pomdp)
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)
ExplicitGameProb01Result performProb0(storm::storage::SparseMatrix< ValueType > const &transitionMatrix, std::vector< uint64_t > const &player1Groups, storm::storage::SparseMatrix< ValueType > const &player1BackwardTransitions, std::vector< uint64_t > const &player2BackwardTransitions, storm::storage::BitVector const &phiStates, storm::storage::BitVector const &psiStates, storm::OptimizationDirection const &player1Direction, storm::OptimizationDirection const &player2Direction, storm::storage::ExplicitGameStrategyPair *strategyPair)
Computes the set of states that have probability 0 given the strategies of the two players.
Definition graph.cpp:1330
storm::storage::BitVector performProb1A(storm::models::sparse::NondeterministicModel< T, RM > const &model, storm::storage::SparseMatrix< T > const &backwardTransitions, storm::storage::BitVector const &phiStates, storm::storage::BitVector const &psiStates)
Computes the sets of states that have probability 1 of satisfying phi until psi under all possible re...
Definition graph.cpp:1019
storm::storage::BitVector performProb1(storm::storage::SparseMatrix< T > const &backwardTransitions, storm::storage::BitVector const &, storm::storage::BitVector const &psiStates, storm::storage::BitVector const &statesWithProbabilityGreater0)
Computes the set of states of the given model for which all paths lead to the given set of target sta...
Definition graph.cpp:377
storm::storage::BitVector performProb1E(storm::storage::SparseMatrix< T > const &transitionMatrix, std::vector< uint_fast64_t > const &nondeterministicChoiceIndices, storm::storage::SparseMatrix< T > const &backwardTransitions, storm::storage::BitVector const &phiStates, storm::storage::BitVector const &psiStates, boost::optional< storm::storage::BitVector > const &choiceConstraint)
Computes the sets of states that have probability 1 of satisfying phi until psi under at least one po...
Definition graph.cpp:740
void setVectorValues(std::vector< T > &vector, storm::storage::BitVector const &positions, std::vector< S > const &values)
Sets the provided values at the provided positions in the given vector.
Definition vector.h:79
void selectVectorValues(std::vector< T > &vector, storm::storage::BitVector const &positions, std::vector< T > const &values)
Selects the elements from a vector at the specified positions and writes them consecutively into anot...
Definition vector.h:185
ValueType max(ValueType const &first, ValueType const &second)
ValueType min(ValueType const &first, ValueType const &second)
ValueType abs(ValueType const &number)
ValueType zero()
Definition constants.cpp:26
ValueType one()
Definition constants.cpp:21
TargetType convertNumber(SourceType const &number)
solver::OptimizationDirection OptimizationDirection
Converts the ADD to a row-grouped (sparse) matrix.
Definition Add.h:668
std::vector< std::vector< uint64_t > > labelings
Definition Add.h:680
storm::storage::SparseMatrix< ValueType > matrix
Definition Add.h:679
Options that configure the game-based abstraction-refinement procedure.
storm::storage::BitVector const & getPlayer1States() const
Definition graph.h:755
storm::storage::BitVector const & getPlayer2States() const
Definition graph.h:759
storm::dd::Bdd< Type > const & getPlayer2States() const
Definition graph.h:698
storm::dd::Bdd< Type > const & getPlayer1Strategy() const
Definition graph.h:674
storm::dd::Bdd< Type > const & getPlayer2Strategy() const
Definition graph.h:686
storm::dd::Bdd< Type > const & getPlayer1States() const
Definition graph.h:694