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