#include "storm-pars/modelchecker/region/SparseDtmcParameterLiftingModelChecker.h" #include "storm-pars/transformer/SparseParametricDtmcSimplifier.h" #include "storm/adapters/RationalFunctionAdapter.h" #include "storm/modelchecker/propositional/SparsePropositionalModelChecker.h" #include "storm/modelchecker/results/ExplicitQualitativeCheckResult.h" #include "storm/modelchecker/results/ExplicitQuantitativeCheckResult.h" #include "storm/models/sparse/Dtmc.h" #include "storm/models/sparse/StandardRewardModel.h" #include "storm/solver/StandardMinMaxLinearEquationSolver.h" #include "storm/utility/vector.h" #include "storm/utility/graph.h" #include "storm/utility/NumberTraits.h" #include "storm/exceptions/InvalidArgumentException.h" #include "storm/exceptions/InvalidPropertyException.h" #include "storm/exceptions/NotSupportedException.h" #include "storm/exceptions/UnexpectedException.h" namespace storm { namespace modelchecker { template SparseDtmcParameterLiftingModelChecker::SparseDtmcParameterLiftingModelChecker() : SparseDtmcParameterLiftingModelChecker(std::make_unique>()) { // Intentionally left empty } template SparseDtmcParameterLiftingModelChecker::SparseDtmcParameterLiftingModelChecker(std::unique_ptr>&& solverFactory) : solverFactory(std::move(solverFactory)) { // Intentionally left empty } template bool SparseDtmcParameterLiftingModelChecker::canHandle(std::shared_ptr parametricModel, CheckTask const& checkTask) const { bool result = parametricModel->isOfType(storm::models::ModelType::Dtmc); result &= parametricModel->isSparseModel(); result &= parametricModel->supportsParameters(); auto dtmc = parametricModel->template as(); result &= static_cast(dtmc); result &= checkTask.getFormula().isInFragment(storm::logic::reachability().setRewardOperatorsAllowed(true).setReachabilityRewardFormulasAllowed(true).setBoundedUntilFormulasAllowed(true).setCumulativeRewardFormulasAllowed(true).setStepBoundedUntilFormulasAllowed(true).setTimeBoundedUntilFormulasAllowed(true)); return result; } template void SparseDtmcParameterLiftingModelChecker::specify(std::shared_ptr parametricModel, CheckTask const& checkTask) { auto dtmc = parametricModel->template as(); specify(dtmc, checkTask, false); } template void SparseDtmcParameterLiftingModelChecker::specify(std::shared_ptr parametricModel, CheckTask const& checkTask, bool skipModelSimplification) { STORM_LOG_ASSERT(this->canHandle(parametricModel, checkTask), "specified model and formula can not be handled by this."); reset(); if (skipModelSimplification) { this->parametricModel = parametricModel; this->specifyFormula(checkTask); } else { auto simplifier = storm::transformer::SparseParametricDtmcSimplifier(*parametricModel); if (!simplifier.simplify(checkTask.getFormula())) { STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Simplifying the model was not successfull."); } this->parametricModel = simplifier.getSimplifiedModel(); this->specifyFormula(checkTask.substituteFormula(*simplifier.getSimplifiedFormula())); } } template void SparseDtmcParameterLiftingModelChecker::specifyBoundedUntilFormula(CheckTask const& checkTask) { // get the step bound STORM_LOG_THROW(!checkTask.getFormula().hasLowerBound(), storm::exceptions::NotSupportedException, "Lower step bounds are not supported."); STORM_LOG_THROW(checkTask.getFormula().hasUpperBound(), storm::exceptions::NotSupportedException, "Expected a bounded until formula with an upper bound."); STORM_LOG_THROW(checkTask.getFormula().getTimeBoundReference().isStepBound(), storm::exceptions::NotSupportedException, "Expected a bounded until formula with step bounds."); stepBound = checkTask.getFormula().getUpperBound().evaluateAsInt(); STORM_LOG_THROW(*stepBound > 0, storm::exceptions::NotSupportedException, "Can not apply parameter lifting on step bounded formula: The step bound has to be positive."); if (checkTask.getFormula().isUpperBoundStrict()) { STORM_LOG_THROW(*stepBound > 0, storm::exceptions::NotSupportedException, "Expected a strict upper step bound that is greater than zero."); --(*stepBound); } STORM_LOG_THROW(*stepBound > 0, storm::exceptions::NotSupportedException, "Can not apply parameter lifting on step bounded formula: The step bound has to be positive."); // get the results for the subformulas storm::modelchecker::SparsePropositionalModelChecker propositionalChecker(*this->parametricModel); STORM_LOG_THROW(propositionalChecker.canHandle(checkTask.getFormula().getLeftSubformula()) && propositionalChecker.canHandle(checkTask.getFormula().getRightSubformula()), storm::exceptions::NotSupportedException, "Parameter lifting with non-propositional subformulas is not supported"); storm::storage::BitVector phiStates = std::move(propositionalChecker.check(checkTask.getFormula().getLeftSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector()); storm::storage::BitVector psiStates = std::move(propositionalChecker.check(checkTask.getFormula().getRightSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector()); // get the maybeStates maybeStates = storm::utility::graph::performProbGreater0(this->parametricModel->getBackwardTransitions(), phiStates, psiStates, true, *stepBound); maybeStates &= ~psiStates; // set the result for all non-maybe states resultsForNonMaybeStates = std::vector(this->parametricModel->getNumberOfStates(), storm::utility::zero()); storm::utility::vector::setVectorValues(resultsForNonMaybeStates, psiStates, storm::utility::one()); // if there are maybestates, create the parameterLifter if (!maybeStates.empty()) { // Create the vector of one-step probabilities to go to target states. std::vector b = this->parametricModel->getTransitionMatrix().getConstrainedRowSumVector(storm::storage::BitVector(this->parametricModel->getTransitionMatrix().getRowCount(), true), psiStates); parameterLifter = std::make_unique>(this->parametricModel->getTransitionMatrix(), b, maybeStates, maybeStates); } // We know some bounds for the results so set them lowerResultBound = storm::utility::zero(); upperResultBound = storm::utility::one(); } template void SparseDtmcParameterLiftingModelChecker::specifyUntilFormula(CheckTask const& checkTask) { // get the results for the subformulas storm::modelchecker::SparsePropositionalModelChecker propositionalChecker(*this->parametricModel); STORM_LOG_THROW(propositionalChecker.canHandle(checkTask.getFormula().getLeftSubformula()) && propositionalChecker.canHandle(checkTask.getFormula().getRightSubformula()), storm::exceptions::NotSupportedException, "Parameter lifting with non-propositional subformulas is not supported"); storm::storage::BitVector phiStates = std::move(propositionalChecker.check(checkTask.getFormula().getLeftSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector()); storm::storage::BitVector psiStates = std::move(propositionalChecker.check(checkTask.getFormula().getRightSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector()); // get the maybeStates std::pair statesWithProbability01 = storm::utility::graph::performProb01(this->parametricModel->getBackwardTransitions(), phiStates, psiStates); maybeStates = ~(statesWithProbability01.first | statesWithProbability01.second); // set the result for all non-maybe states resultsForNonMaybeStates = std::vector(this->parametricModel->getNumberOfStates(), storm::utility::zero()); storm::utility::vector::setVectorValues(resultsForNonMaybeStates, statesWithProbability01.second, storm::utility::one()); // if there are maybestates, create the parameterLifter if (!maybeStates.empty()) { // Create the vector of one-step probabilities to go to target states. std::vector b = this->parametricModel->getTransitionMatrix().getConstrainedRowSumVector(storm::storage::BitVector(this->parametricModel->getTransitionMatrix().getRowCount(), true), psiStates); parameterLifter = std::make_unique>(this->parametricModel->getTransitionMatrix(), b, maybeStates, maybeStates); } // We know some bounds for the results so set them lowerResultBound = storm::utility::zero(); upperResultBound = storm::utility::one(); } template void SparseDtmcParameterLiftingModelChecker::specifyReachabilityRewardFormula(CheckTask const& checkTask) { // get the results for the subformula storm::modelchecker::SparsePropositionalModelChecker propositionalChecker(*this->parametricModel); STORM_LOG_THROW(propositionalChecker.canHandle(checkTask.getFormula().getSubformula()), storm::exceptions::NotSupportedException, "Parameter lifting with non-propositional subformulas is not supported"); storm::storage::BitVector targetStates = std::move(propositionalChecker.check(checkTask.getFormula().getSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector()); // get the maybeStates storm::storage::BitVector infinityStates = storm::utility::graph::performProb1(this->parametricModel->getBackwardTransitions(), storm::storage::BitVector(this->parametricModel->getNumberOfStates(), true), targetStates); infinityStates.complement(); maybeStates = ~(targetStates | infinityStates); // set the result for all the non-maybe states resultsForNonMaybeStates = std::vector(this->parametricModel->getNumberOfStates(), storm::utility::zero()); storm::utility::vector::setVectorValues(resultsForNonMaybeStates, infinityStates, storm::utility::infinity()); // if there are maybestates, create the parameterLifter if (!maybeStates.empty()) { // Create the reward vector STORM_LOG_THROW((checkTask.isRewardModelSet() && this->parametricModel->hasRewardModel(checkTask.getRewardModel())) || (!checkTask.isRewardModelSet() && this->parametricModel->hasUniqueRewardModel()), storm::exceptions::InvalidPropertyException, "The reward model specified by the CheckTask is not available in the given model."); typename SparseModelType::RewardModelType const& rewardModel = checkTask.isRewardModelSet() ? this->parametricModel->getRewardModel(checkTask.getRewardModel()) : this->parametricModel->getUniqueRewardModel(); std::vector b = rewardModel.getTotalRewardVector(this->parametricModel->getTransitionMatrix()); parameterLifter = std::make_unique>(this->parametricModel->getTransitionMatrix(), b, maybeStates, maybeStates); } // We only know a lower bound for the result lowerResultBound = storm::utility::zero(); } template void SparseDtmcParameterLiftingModelChecker::specifyCumulativeRewardFormula(CheckTask const& checkTask) { // Obtain the stepBound stepBound = checkTask.getFormula().getBound().evaluateAsInt(); if (checkTask.getFormula().isBoundStrict()) { STORM_LOG_THROW(*stepBound > 0, storm::exceptions::NotSupportedException, "Expected a strict upper step bound that is greater than zero."); --(*stepBound); } STORM_LOG_THROW(*stepBound > 0, storm::exceptions::NotSupportedException, "Can not apply parameter lifting on step bounded formula: The step bound has to be positive."); //Every state is a maybeState maybeStates = storm::storage::BitVector(this->parametricModel->getTransitionMatrix().getColumnCount(), true); resultsForNonMaybeStates = std::vector(this->parametricModel->getNumberOfStates()); // Create the reward vector STORM_LOG_THROW((checkTask.isRewardModelSet() && this->parametricModel->hasRewardModel(checkTask.getRewardModel())) || (!checkTask.isRewardModelSet() && this->parametricModel->hasUniqueRewardModel()), storm::exceptions::InvalidPropertyException, "The reward model specified by the CheckTask is not available in the given model."); typename SparseModelType::RewardModelType const& rewardModel = checkTask.isRewardModelSet() ? this->parametricModel->getRewardModel(checkTask.getRewardModel()) : this->parametricModel->getUniqueRewardModel(); std::vector b = rewardModel.getTotalRewardVector(this->parametricModel->getTransitionMatrix()); parameterLifter = std::make_unique>(this->parametricModel->getTransitionMatrix(), b, maybeStates, maybeStates); // We only know a lower bound for the result lowerResultBound = storm::utility::zero(); } template storm::modelchecker::SparseInstantiationModelChecker& SparseDtmcParameterLiftingModelChecker::getInstantiationChecker() { if (!instantiationChecker) { instantiationChecker = std::make_unique>(*this->parametricModel); instantiationChecker->specifyFormula(this->currentCheckTask->template convertValueType()); instantiationChecker->setInstantiationsAreGraphPreserving(true); } return *instantiationChecker; } template std::unique_ptr SparseDtmcParameterLiftingModelChecker::computeQuantitativeValues(storm::storage::ParameterRegion const& region, storm::solver::OptimizationDirection const& dirForParameters) { if(maybeStates.empty()) { return std::make_unique>(resultsForNonMaybeStates); } parameterLifter->specifyRegion(region, dirForParameters); // Set up the solver if (storm::NumberTraits::IsExact && solverFactory->getMinMaxMethod() == storm::solver::MinMaxMethod::ValueIteration) { STORM_LOG_INFO("Parameter Lifting: Setting solution method for exact MinMaxSolver to policy iteration"); solverFactory->setMinMaxMethod(storm::solver::MinMaxMethod::PolicyIteration); } auto solver = solverFactory->create(parameterLifter->getMatrix()); if (lowerResultBound) solver->setLowerBound(lowerResultBound.get()); if (upperResultBound) solver->setUpperBound(upperResultBound.get()); if (!stepBound) solver->setTrackScheduler(true); if (storm::solver::minimize(dirForParameters) && minSchedChoices && !stepBound) solver->setSchedulerHint(std::move(minSchedChoices.get())); if (storm::solver::maximize(dirForParameters) && maxSchedChoices && !stepBound) solver->setSchedulerHint(std::move(maxSchedChoices.get())); if (this->currentCheckTask->isBoundSet() && solver->hasSchedulerHint()) { // If we reach this point, we know that after applying the hint, the x-values can only become larger (if we maximize) or smaller (if we minimize). std::unique_ptr> termCond; storm::storage::BitVector relevantStatesInSubsystem = this->currentCheckTask->isOnlyInitialStatesRelevantSet() ? this->parametricModel->getInitialStates() % maybeStates : storm::storage::BitVector(maybeStates.getNumberOfSetBits(), true); if (storm::solver::minimize(dirForParameters)) { // Terminate if the value for ALL relevant states is already below the threshold termCond = std::make_unique> (relevantStatesInSubsystem, this->currentCheckTask->getBoundThreshold(), true, false); } else { // Terminate if the value for ALL relevant states is already above the threshold termCond = std::make_unique> (relevantStatesInSubsystem, true, this->currentCheckTask->getBoundThreshold(), true); } solver->setTerminationCondition(std::move(termCond)); } // Invoke the solver if(stepBound) { assert(*stepBound > 0); x = std::vector(maybeStates.getNumberOfSetBits(), storm::utility::zero()); solver->repeatedMultiply(dirForParameters, x, ¶meterLifter->getVector(), *stepBound); } else { x.resize(maybeStates.getNumberOfSetBits(), storm::utility::zero()); solver->solveEquations(dirForParameters, x, parameterLifter->getVector()); if(storm::solver::minimize(dirForParameters)) { minSchedChoices = solver->getSchedulerChoices(); } else { maxSchedChoices = solver->getSchedulerChoices(); } } // Get the result for the complete model (including maybestates) std::vector result = resultsForNonMaybeStates; auto maybeStateResIt = x.begin(); for(auto const& maybeState : maybeStates) { result[maybeState] = *maybeStateResIt; ++maybeStateResIt; } return std::make_unique>(std::move(result)); } template void SparseDtmcParameterLiftingModelChecker::reset() { maybeStates.resize(0); resultsForNonMaybeStates.clear(); stepBound = boost::none; instantiationChecker = nullptr; parameterLifter = nullptr; minSchedChoices = boost::none; maxSchedChoices = boost::none; x.clear(); lowerResultBound = boost::none; upperResultBound = boost::none; } template boost::optional> SparseDtmcParameterLiftingModelChecker::getCurrentMinScheduler() { if (!minSchedChoices) { return boost::none; } storm::storage::Scheduler result(minSchedChoices->size()); uint_fast64_t state = 0; for (auto const& schedulerChoice : minSchedChoices.get()) { result.setChoice(schedulerChoice, state); ++state; } return result; } template boost::optional> SparseDtmcParameterLiftingModelChecker::getCurrentMaxScheduler() { if (!maxSchedChoices) { return boost::none; } storm::storage::Scheduler result(maxSchedChoices->size()); uint_fast64_t state = 0; for (auto const& schedulerChoice : maxSchedChoices.get()) { result.setChoice(schedulerChoice, state); ++state; } return result; } template class SparseDtmcParameterLiftingModelChecker, double>; template class SparseDtmcParameterLiftingModelChecker, storm::RationalNumber>; } }