diff --git a/src/storm/modelchecker/rpatl/SparseSmgRpatlModelChecker.cpp b/src/storm/modelchecker/rpatl/SparseSmgRpatlModelChecker.cpp index 35464513d..e3f5d46fd 100644 --- a/src/storm/modelchecker/rpatl/SparseSmgRpatlModelChecker.cpp +++ b/src/storm/modelchecker/rpatl/SparseSmgRpatlModelChecker.cpp @@ -12,6 +12,7 @@ #include "storm/modelchecker/results/ExplicitQuantitativeCheckResult.h" #include "storm/modelchecker/results/ExplicitParetoCurveCheckResult.h" +#include "storm/modelchecker/rpatl/helper/SparseSmgRpatlHelper.h" #include "storm/modelchecker/helper/infinitehorizon/SparseNondeterministicGameInfiniteHorizonHelper.h" #include "storm/modelchecker/helper/utility/SetInformationFromCheckTask.h" @@ -54,8 +55,6 @@ namespace storm { } } - - template std::unique_ptr SparseSmgRpatlModelChecker::checkGameFormula(Environment const& env, CheckTask const& checkTask) { Environment solverEnv = env; @@ -69,10 +68,25 @@ namespace storm { return this->checkRewardOperatorFormula(solverEnv, checkTask.substituteFormula(subFormula.asRewardOperatorFormula())); } else if (subFormula.isLongRunAverageOperatorFormula()) { return this->checkLongRunAverageOperatorFormula(solverEnv, checkTask.substituteFormula(subFormula.asLongRunAverageOperatorFormula())); + } else if (subFormula.isProbabilityOperatorFormula()) { + return this->checkProbabilityOperatorFormula(solverEnv, checkTask.substituteFormula(subFormula.asProbabilityOperatorFormula())); } STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Cannot check this property (yet)."); } + template + std::unique_ptr SparseSmgRpatlModelChecker::checkProbabilityOperatorFormula(Environment const& env, CheckTask const& checkTask) { + storm::logic::ProbabilityOperatorFormula const& stateFormula = checkTask.getFormula(); + std::unique_ptr result = this->computeProbabilities(env, checkTask.substituteFormula(stateFormula.getSubformula())); + + if (checkTask.isBoundSet()) { + STORM_LOG_THROW(result->isQuantitative(), storm::exceptions::InvalidOperationException, "Unable to perform comparison operation on non-quantitative result."); + return result->asQuantitativeCheckResult().compareAgainstBound(checkTask.getBoundComparisonType(), checkTask.getBoundThreshold()); + } else { + return result; + } + } + template std::unique_ptr SparseSmgRpatlModelChecker::checkRewardOperatorFormula(Environment const& env, CheckTask const& checkTask) { storm::logic::RewardOperatorFormula const& formula = checkTask.getFormula(); @@ -89,7 +103,14 @@ namespace storm { return result; } - + template + std::unique_ptr SparseSmgRpatlModelChecker::computeProbabilities(Environment const& env, CheckTask const& checkTask) { + storm::logic::Formula const& formula = checkTask.getFormula(); + if (formula.isReachabilityProbabilityFormula()) { + return this->computeReachabilityProbabilities(env, checkTask.substituteFormula(formula.asReachabilityProbabilityFormula())); + } + STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "The given formula '" << formula << "' is invalid."); + } template std::unique_ptr SparseSmgRpatlModelChecker::computeRewards(Environment const& env, storm::logic::RewardMeasureType rewardMeasureType, CheckTask const& checkTask) { @@ -100,6 +121,25 @@ namespace storm { STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "The given formula '" << rewardFormula << "' cannot (yet) be handled."); } + template + std::unique_ptr SparseSmgRpatlModelChecker::computeUntilProbabilities(Environment const& env, CheckTask const& checkTask) { + // Currently we only support computation of reachability probabilities, i.e. the left subformula will always be 'true' (for now). + storm::logic::UntilFormula const& pathFormula = checkTask.getFormula(); + STORM_LOG_THROW(checkTask.isOptimizationDirectionSet(), storm::exceptions::InvalidPropertyException, "Formula needs to specify whether minimal or maximal values are to be computed on nondeterministic model."); + std::unique_ptr leftResultPointer = this->check(env, pathFormula.getLeftSubformula()); + std::unique_ptr rightResultPointer = this->check(env, pathFormula.getRightSubformula()); + ExplicitQualitativeCheckResult const& leftResult = leftResultPointer->asExplicitQualitativeCheckResult(); + ExplicitQualitativeCheckResult const& rightResult = rightResultPointer->asExplicitQualitativeCheckResult(); + storm::solver::SolveGoal foo(this->getModel(), checkTask); + + auto ret = storm::modelchecker::helper::SparseSmgRpatlHelper::computeUntilProbabilities(env, storm::solver::SolveGoal(this->getModel(), checkTask), this->getModel().getTransitionMatrix(), this->getModel().getBackwardTransitions(), leftResult.getTruthValuesVector(), rightResult.getTruthValuesVector(), checkTask.isQualitativeSet(), statesOfCoalition, checkTask.isProduceSchedulersSet(), checkTask.getHint()); + std::unique_ptr result(new ExplicitQuantitativeCheckResult(std::move(ret.values))); + if (checkTask.isProduceSchedulersSet() && ret.scheduler) { + result->asExplicitQuantitativeCheckResult().setScheduler(std::move(ret.scheduler)); + } + return result; + } + template std::unique_ptr SparseSmgRpatlModelChecker::computeLongRunAverageProbabilities(Environment const& env, CheckTask const& checkTask) { STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "NYI"); @@ -119,41 +159,6 @@ namespace storm { return result; } - - - //template - //void SparseSmgRpatlModelChecker::coalitionIndicator(Environment& env, CheckTask const& checkTask) { - // storm::storage::BitVector coalitionIndicators(this->getModel().getTransitionMatrix().getRowGroupCount()); - - // std::vector> formulaPlayerIds = checkTask.getFormula().getCoalition().getPlayers(); - // std::vector playerIds; - // std::vector> playerActionIndices = this->getModel().getStatePlayerIndications(); - - // for(auto const& player : formulaPlayerIds) { - // // If the player is given via the player name we have to look up its index - // if(player.type() == typeid(std::string)) { - // auto it = std::find_if(playerActionIndices.begin(), playerActionIndices.end(), - // [&player](const std::pair& element){ return element.first == boost::get(player); }); - // playerIds.push_back(it->second); - // // If the player is given by its index we have to shift it to match internal mappings - // } else if(player.type() == typeid(uint_fast64_t)) { - // playerIds.push_back(boost::get(player) - 1); - // } - // } - // //for(auto const& p : playerActionIndices) std::cout << p.first << " - " << p.second << ", "; std::cout << std::endl; - // //for(auto const& p : playerIds) std::cout << p << ", "; std::cout << std::endl; - - // for(uint i = 0; i < playerActionIndices.size(); i++) { - // if(std::find(playerIds.begin(), playerIds.end(), playerActionIndices.at(i).second) != playerIds.end()) { - // coalitionIndicators.set(i); - // } - // } - // coalitionIndicators.complement(); - - // //std::cout << "MINMAX OVERRIDE: " << coalitionIndicators << std::endl; - // env.solver().multiplier().setOptimizationDirectionOverride(coalitionIndicators); - //} - template class SparseSmgRpatlModelChecker>; #ifdef STORM_HAVE_CARL template class SparseSmgRpatlModelChecker>; diff --git a/src/storm/modelchecker/rpatl/SparseSmgRpatlModelChecker.h b/src/storm/modelchecker/rpatl/SparseSmgRpatlModelChecker.h index 9f5889caf..dfbd47762 100644 --- a/src/storm/modelchecker/rpatl/SparseSmgRpatlModelChecker.h +++ b/src/storm/modelchecker/rpatl/SparseSmgRpatlModelChecker.h @@ -29,10 +29,14 @@ namespace storm { // The implemented methods of the AbstractModelChecker interface. bool canHandle(CheckTask const& checkTask) const override; std::unique_ptr checkGameFormula(Environment const& env, CheckTask const& checkTask) override; + std::unique_ptr checkProbabilityOperatorFormula(Environment const& env, CheckTask const& checkTask) override; std::unique_ptr checkRewardOperatorFormula(Environment const& env, CheckTask const& checkTask) override; std::unique_ptr checkLongRunAverageOperatorFormula(Environment const& env, CheckTask const& checkTask) override; + std::unique_ptr computeProbabilities(Environment const& env, CheckTask const& checkTask) override; std::unique_ptr computeRewards(Environment const& env, storm::logic::RewardMeasureType rewardMeasureType, CheckTask const& checkTask) override; + + std::unique_ptr computeUntilProbabilities(Environment const& env, CheckTask const& checkTask) override; std::unique_ptr computeLongRunAverageProbabilities(Environment const& env, CheckTask const& checkTask) override; std::unique_ptr computeLongRunAverageRewards(Environment const& env, storm::logic::RewardMeasureType rewardMeasureType, CheckTask const& checkTask) override; diff --git a/src/storm/modelchecker/rpatl/helper/SparseSmgRpatlHelper.cpp b/src/storm/modelchecker/rpatl/helper/SparseSmgRpatlHelper.cpp new file mode 100644 index 000000000..9bf5a10e8 --- /dev/null +++ b/src/storm/modelchecker/rpatl/helper/SparseSmgRpatlHelper.cpp @@ -0,0 +1,97 @@ +#include "SparseSmgRpatlHelper.h" + +#include "storm/environment/Environment.h" +#include "storm/environment/solver/MultiplierEnvironment.h" +#include "storm/environment/solver/MinMaxSolverEnvironment.h" +#include "storm/solver/MinMaxLinearEquationSolver.h" +#include "storm/utility/vector.h" + +#include "storm/modelchecker/rpatl/helper/internal/GameViHelper.h" + + +namespace storm { + namespace modelchecker { + namespace helper { + + template + MDPSparseModelCheckingHelperReturnType SparseSmgRpatlHelper::computeUntilProbabilities(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, bool qualitative, storm::storage::BitVector statesOfCoalition, bool produceScheduler, ModelCheckerHint const& hint) { + // TODO add Kwiatkowska original reference + // TODO FIX solver min max mess + + auto solverEnv = env; + solverEnv.solver().minMax().setMethod(storm::solver::MinMaxMethod::ValueIteration, false); + + // Initialize the solution vector. + std::vector x = std::vector(transitionMatrix.getRowGroupCount() - psiStates.getNumberOfSetBits(), storm::utility::zero()); + std::vector b = transitionMatrix.getConstrainedRowGroupSumVector(~psiStates, psiStates); + + // Reduce matrix to ~Prob1 states- + //STORM_LOG_DEBUG("\n" << transitionMatrix); + storm::storage::SparseMatrix submatrix = transitionMatrix.getSubmatrix(true, ~psiStates, ~psiStates, false); + //STORM_LOG_DEBUG("\n" << submatrix); + + + //STORM_LOG_DEBUG("x = " << storm::utility::vector::toString(x)); + //STORM_LOG_DEBUG("b = " << storm::utility::vector::toString(b)); + + storm::storage::BitVector clippedStatesOfCoalition(statesOfCoalition.size() - psiStates.getNumberOfSetBits()); + //STORM_LOG_DEBUG(psiStates); + //STORM_LOG_DEBUG(statesOfCoalition); + //STORM_LOG_DEBUG(clippedStatesOfCoalition); + + // TODO move this to BitVector-class + auto clippedStatesCounter = 0; + for(uint i = 0; i < psiStates.size(); i++) { + std::cout << i << " : " << psiStates.get(i) << " -> " << statesOfCoalition[i] << std::endl; + if(!psiStates.get(i)) { + clippedStatesOfCoalition.set(clippedStatesCounter, statesOfCoalition[i]); + clippedStatesCounter++; + } + } + //STORM_LOG_DEBUG(clippedStatesOfCoalition); + clippedStatesOfCoalition.complement(); + //STORM_LOG_DEBUG(clippedStatesOfCoalition); + + storm::modelchecker::helper::internal::GameViHelper viHelper(submatrix, clippedStatesOfCoalition); + std::unique_ptr> scheduler; + if (produceScheduler) { + viHelper.setProduceScheduler(true); + } + + viHelper.performValueIteration(env, x, b, goal.direction()); + + STORM_LOG_DEBUG(storm::utility::vector::toString(x)); + if (produceScheduler) { + scheduler = std::make_unique>(expandScheduler(viHelper.extractScheduler(), psiStates)); + STORM_LOG_DEBUG("IsPartial?" << scheduler->isPartialScheduler()); + } + return MDPSparseModelCheckingHelperReturnType(std::move(x), std::move(scheduler)); + } + + template + storm::storage::Scheduler SparseSmgRpatlHelper::expandScheduler(storm::storage::Scheduler scheduler, storm::storage::BitVector psiStates) { + //STORM_LOG_DEBUG(psiStates.size()); + for(uint i = 0; i < 2; i++) { + //STORM_LOG_DEBUG(scheduler.getChoice(i)); + } + storm::storage::Scheduler completeScheduler(psiStates.size()); + uint_fast64_t maybeStatesCounter = 0; + for(uint stateCounter = 0; stateCounter < psiStates.size(); stateCounter++) { + //STORM_LOG_DEBUG(stateCounter << " : " << psiStates.get(stateCounter)); + if(psiStates.get(stateCounter)) { + completeScheduler.setChoice(0, stateCounter); + } else { + completeScheduler.setChoice(scheduler.getChoice(maybeStatesCounter), stateCounter); + maybeStatesCounter++; + } + } + return completeScheduler; + } + + template class SparseSmgRpatlHelper; +#ifdef STORM_HAVE_CARL + template class SparseSmgRpatlHelper; +#endif + } + } +} diff --git a/src/storm/modelchecker/rpatl/helper/SparseSmgRpatlHelper.h b/src/storm/modelchecker/rpatl/helper/SparseSmgRpatlHelper.h new file mode 100644 index 000000000..9505bd51f --- /dev/null +++ b/src/storm/modelchecker/rpatl/helper/SparseSmgRpatlHelper.h @@ -0,0 +1,47 @@ +#pragma once + +#include + +#include "storm/modelchecker/hints/ModelCheckerHint.h" +#include "storm/modelchecker/prctl/helper/SolutionType.h" +#include "storm/storage/SparseMatrix.h" + +#include "storm/utility/solver.h" +#include "storm/solver/SolveGoal.h" + +#include "storm/modelchecker/prctl/helper/MDPModelCheckingHelperReturnType.h" + +namespace storm { + + class Environment; + + namespace storage { + class BitVector; + } + + namespace models { + namespace sparse { + template + class StandardRewardModel; + } + } + + namespace modelchecker { + class CheckResult; + + namespace helper { + + template + class SparseSmgRpatlHelper { + public: + // TODO should probably be renamed in the future: + + static MDPSparseModelCheckingHelperReturnType computeUntilProbabilities(Environment const& env, storm::solver::SolveGoal&& goal, storm::storage::SparseMatrix const& transitionMatrix, storm::storage::SparseMatrix const& backwardTransitions, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, bool qualitative, storm::storage::BitVector statesOfCoalition, bool produceScheduler, ModelCheckerHint const& hint = ModelCheckerHint()); + + private: + static storm::storage::Scheduler expandScheduler(storm::storage::Scheduler scheduler, storm::storage::BitVector psiStates); + + }; + } + } +} diff --git a/src/storm/modelchecker/rpatl/helper/internal/GameViHelper.cpp b/src/storm/modelchecker/rpatl/helper/internal/GameViHelper.cpp new file mode 100644 index 000000000..4f6fc1e4e --- /dev/null +++ b/src/storm/modelchecker/rpatl/helper/internal/GameViHelper.cpp @@ -0,0 +1,198 @@ +#include "GameViHelper.h" + +#include "storm/environment/Environment.h" +#include "storm/environment/solver/SolverEnvironment.h" +#include "storm/environment/solver/GameSolverEnvironment.h" + + +#include "storm/utility/SignalHandler.h" +#include "storm/utility/vector.h" + +// TODO this will undergo major refactoring as soon as we implement model checking of further properties + +namespace storm { + namespace modelchecker { + namespace helper { + namespace internal { + + template + GameViHelper::GameViHelper(storm::storage::SparseMatrix const& transitionMatrix, storm::storage::BitVector statesOfCoalition) : _transitionMatrix(transitionMatrix), _statesOfCoalition(statesOfCoalition) { + } + + template + void GameViHelper::prepareSolversAndMultipliersReachability(const Environment& env) { + // TODO we get whole transitionmatrix and psistates DONE IN smgrpatlhelper + // -> clip statesofcoalition + // -> compute b vector from psiStates + // -> clip transitionmatrix and create multiplier + _multiplier = storm::solver::MultiplierFactory().create(env, _transitionMatrix); + _multiplier->setOptimizationDirectionOverride(_statesOfCoalition); + + _x1IsCurrent = false; + } + + template + void GameViHelper::performValueIteration(Environment const& env, std::vector& x, std::vector b, storm::solver::OptimizationDirection const dir) { + prepareSolversAndMultipliersReachability(env); + ValueType precision = storm::utility::convertNumber(env.solver().game().getPrecision()); + uint64_t maxIter = env.solver().game().getMaximalNumberOfIterations(); + _b = b; + + _x1.assign(_transitionMatrix.getRowGroupCount(), storm::utility::zero()); + _x2 = _x1; + + if (this->isProduceSchedulerSet()) { + if (!this->_producedOptimalChoices.is_initialized()) { + this->_producedOptimalChoices.emplace(); + } + this->_producedOptimalChoices->resize(this->_transitionMatrix.getRowGroupCount()); + } + + uint64_t iter = 0; + std::vector result = x; + while (iter < maxIter) { + ++iter; + performIterationStep(env, dir); + + if (checkConvergence(precision)) { + break; + } + if (storm::utility::resources::isTerminate()) { + break; + } + } + x = xNew(); + + if (isProduceSchedulerSet()) { + // We will be doing one more iteration step and track scheduler choices this time. + performIterationStep(env, dir, &_producedOptimalChoices.get()); + } + } + + template + void GameViHelper::performIterationStep(Environment const& env, storm::solver::OptimizationDirection const dir, std::vector* choices) { + if (!_multiplier) { + prepareSolversAndMultipliersReachability(env); + } + _x1IsCurrent = !_x1IsCurrent; + + // multiplyandreducegaussseidel + // Ax + b + if (choices == nullptr) { + //STORM_LOG_DEBUG("\n" << _transitionMatrix); + //STORM_LOG_DEBUG("xOld = " << storm::utility::vector::toString(xOld())); + //STORM_LOG_DEBUG("b = " << storm::utility::vector::toString(_b)); + //STORM_LOG_DEBUG("xNew = " << storm::utility::vector::toString(xNew())); + _multiplier->multiplyAndReduce(env, dir, xOld(), &_b, xNew()); + //std::cin.get(); + } else { + // Also keep track of the choices made. + _multiplier->multiplyAndReduce(env, dir, xOld(), &_b, xNew(), choices); + } + + // compare applyPointwise + storm::utility::vector::applyPointwise(xOld(), xNew(), xNew(), [&dir] (ValueType const& a, ValueType const& b) -> ValueType { + if(storm::solver::maximize(dir)) { + if(a > b) return a; + else return b; + } else { + if(a > b) return a; + else return b; + } + }); + } + + template + bool GameViHelper::checkConvergence(ValueType threshold) const { + STORM_LOG_ASSERT(_multiplier, "tried to check for convergence without doing an iteration first."); + + // Now check whether the currently produced results are precise enough + STORM_LOG_ASSERT(threshold > storm::utility::zero(), "Did not expect a non-positive threshold."); + auto x1It = xOld().begin(); + auto x1Ite = xOld().end(); + auto x2It = xNew().begin(); + ValueType maxDiff = (*x2It - *x1It); + ValueType minDiff = maxDiff; + // The difference between maxDiff and minDiff is zero at this point. Thus, it doesn't make sense to check the threshold now. + for (++x1It, ++x2It; x1It != x1Ite; ++x1It, ++x2It) { + ValueType diff = (*x2It - *x1It); + // Potentially update maxDiff or minDiff + bool skipCheck = false; + if (maxDiff < diff) { + maxDiff = diff; + } else if (minDiff > diff) { + minDiff = diff; + } else { + skipCheck = true; + } + // Check convergence + if (!skipCheck && (maxDiff - minDiff) > threshold) { + return false; + } + } + // TODO needs checking + return true; + } + + template + void GameViHelper::setProduceScheduler(bool value) { + _produceScheduler = value; + } + + template + bool GameViHelper::isProduceSchedulerSet() const { + return _produceScheduler; + } + + template + std::vector const& GameViHelper::getProducedOptimalChoices() const { + STORM_LOG_ASSERT(this->isProduceSchedulerSet(), "Trying to get the produced optimal choices although no scheduler was requested."); + STORM_LOG_ASSERT(this->_producedOptimalChoices.is_initialized(), "Trying to get the produced optimal choices but none were available. Was there a computation call before?"); + return this->_producedOptimalChoices.get(); + } + + template + std::vector& GameViHelper::getProducedOptimalChoices() { + STORM_LOG_ASSERT(this->isProduceSchedulerSet(), "Trying to get the produced optimal choices although no scheduler was requested."); + STORM_LOG_ASSERT(this->_producedOptimalChoices.is_initialized(), "Trying to get the produced optimal choices but none were available. Was there a computation call before?"); + return this->_producedOptimalChoices.get(); + } + + template + storm::storage::Scheduler GameViHelper::extractScheduler() const{ + auto const& optimalChoices = getProducedOptimalChoices(); + storm::storage::Scheduler scheduler(optimalChoices.size()); + for (uint64_t state = 0; state < optimalChoices.size(); ++state) { + scheduler.setChoice(optimalChoices[state], state); + } + return scheduler; + } + + template + std::vector& GameViHelper::xNew() { + return _x1IsCurrent ? _x1 : _x2; + } + + template + std::vector const& GameViHelper::xNew() const { + return _x1IsCurrent ? _x1 : _x2; + } + + template + std::vector& GameViHelper::xOld() { + return _x1IsCurrent ? _x2 : _x1; + } + + template + std::vector const& GameViHelper::xOld() const { + return _x1IsCurrent ? _x2 : _x1; + } + + template class GameViHelper; +#ifdef STORM_HAVE_CARL + template class GameViHelper; +#endif + } + } + } +} diff --git a/src/storm/modelchecker/rpatl/helper/internal/GameViHelper.h b/src/storm/modelchecker/rpatl/helper/internal/GameViHelper.h new file mode 100644 index 000000000..92f963851 --- /dev/null +++ b/src/storm/modelchecker/rpatl/helper/internal/GameViHelper.h @@ -0,0 +1,77 @@ +#pragma once + +#include "storm/storage/SparseMatrix.h" +#include "storm/solver/LinearEquationSolver.h" +#include "storm/solver/MinMaxLinearEquationSolver.h" +#include "storm/solver/Multiplier.h" + +namespace storm { + class Environment; + + namespace storage { + template class Scheduler; + } + + namespace modelchecker { + namespace helper { + namespace internal { + + template + class GameViHelper { + public: + GameViHelper(storm::storage::SparseMatrix const& transitionMatrix, storm::storage::BitVector statesOfCoalition); + + void prepareSolversAndMultipliersReachability(const Environment& env); + + void performValueIteration(Environment const& env, std::vector& x, std::vector b, storm::solver::OptimizationDirection const dir); + + /*h + * Sets whether an optimal scheduler shall be constructed during the computation + */ + void setProduceScheduler(bool value); + + /*! + * @return whether an optimal scheduler shall be constructed during the computation + */ + bool isProduceSchedulerSet() const; + + storm::storage::Scheduler extractScheduler() const; + private: + void performIterationStep(Environment const& env, storm::solver::OptimizationDirection const dir, std::vector* choices = nullptr); + + /*! + * Checks whether the curently computed value achieves the desired precision + */ + bool checkConvergence(ValueType precision) const; + + std::vector& xNew(); + std::vector const& xNew() const; + + std::vector& xOld(); + std::vector const& xOld() const; + bool _x1IsCurrent; + + /*! + * @pre before calling this, a computation call should have been performed during which scheduler production was enabled. + * @return the produced scheduler of the most recent call. + */ + std::vector const& getProducedOptimalChoices() const; + + /*! + * @pre before calling this, a computation call should have been performed during which scheduler production was enabled. + * @return the produced scheduler of the most recent call. + */ + std::vector& getProducedOptimalChoices(); + + storm::storage::SparseMatrix _transitionMatrix; + storm::storage::BitVector _statesOfCoalition; + std::vector _x1, _x2, _b; + std::unique_ptr> _multiplier; + + bool _produceScheduler = false; + boost::optional> _producedOptimalChoices; + }; + } + } + } +}