230 lines
15 KiB
230 lines
15 KiB
#include "storm/modelchecker/multiobjective/constraintbased/SparseCbAchievabilityQuery.h"
|
|
|
|
#include "storm/adapters/RationalFunctionAdapter.h"
|
|
#include "storm/models/sparse/Mdp.h"
|
|
#include "storm/models/sparse/MarkovAutomaton.h"
|
|
#include "storm/models/sparse/StandardRewardModel.h"
|
|
#include "storm/modelchecker/results/ExplicitQualitativeCheckResult.h"
|
|
#include "storm/utility/constants.h"
|
|
#include "storm/utility/vector.h"
|
|
#include "storm/utility/solver.h"
|
|
#include "storm/utility/Stopwatch.h"
|
|
#include "storm/settings/SettingsManager.h"
|
|
#include "storm/settings/modules/GeneralSettings.h"
|
|
#include "storm/settings/modules/CoreSettings.h"
|
|
#include "storm/settings/modules/MultiObjectiveSettings.h"
|
|
#include "storm/storage/expressions/Expressions.h"
|
|
|
|
#include "storm/exceptions/InvalidOperationException.h"
|
|
#include "storm/exceptions/NotSupportedException.h"
|
|
#include "storm/exceptions/UnexpectedException.h"
|
|
|
|
namespace storm {
|
|
namespace modelchecker {
|
|
namespace multiobjective {
|
|
|
|
|
|
template <class SparseModelType>
|
|
SparseCbAchievabilityQuery<SparseModelType>::SparseCbAchievabilityQuery(SparseMultiObjectivePreprocessorResult<SparseModelType> const& preprocessorResult) : SparseCbQuery<SparseModelType>(preprocessorResult) {
|
|
STORM_LOG_ASSERT(preprocessorResult.queryType == SparseMultiObjectivePreprocessorResult<SparseModelType>::QueryType::Achievability, "Invalid query Type");
|
|
solver = storm::utility::solver::SmtSolverFactory().create(*this->expressionManager);
|
|
}
|
|
|
|
template <class SparseModelType>
|
|
std::unique_ptr<CheckResult> SparseCbAchievabilityQuery<SparseModelType>::check(Environment const& env) {
|
|
|
|
bool result = this->checkAchievability();
|
|
|
|
return std::unique_ptr<CheckResult>(new ExplicitQualitativeCheckResult(this->originalModel.getInitialStates().getNextSetIndex(0), result));
|
|
|
|
}
|
|
|
|
template <class SparseModelType>
|
|
bool SparseCbAchievabilityQuery<SparseModelType>::checkAchievability() {
|
|
STORM_LOG_INFO("Building constraint system to check achievability.");
|
|
//this->preprocessedModel->writeDotToStream(std::cout);
|
|
storm::utility::Stopwatch swInitialization(true);
|
|
initializeConstraintSystem();
|
|
STORM_LOG_INFO("Constraint system consists of " << expectedChoiceVariables.size() << " + " << bottomStateVariables.size() << " variables");
|
|
addObjectiveConstraints();
|
|
swInitialization.stop();
|
|
|
|
storm::utility::Stopwatch swCheck(true);
|
|
STORM_LOG_INFO("Invoking SMT Solver.");
|
|
storm::solver::SmtSolver::CheckResult result = solver->check();
|
|
swCheck.stop();
|
|
|
|
if (storm::settings::getModule<storm::settings::modules::CoreSettings>().isShowStatisticsSet()) {
|
|
STORM_PRINT_AND_LOG("Building the constraintsystem took " << swInitialization << " seconds and checking the SMT formula took " << swCheck << " seconds." << std::endl);
|
|
}
|
|
|
|
switch(result) {
|
|
case storm::solver::SmtSolver::CheckResult::Sat:
|
|
// std::cout << std::endl << "Satisfying assignment: " << std::endl << solver->getModelAsValuation().toString(true) << std::endl;
|
|
return true;
|
|
case storm::solver::SmtSolver::CheckResult::Unsat:
|
|
// std::cout << std::endl << "Unsatisfiability core: {" << std::endl;
|
|
// for (auto const& expr : solver->getUnsatCore()) {
|
|
// std::cout << "\t " << expr << std::endl;
|
|
// }
|
|
// std::cout << "}" << std::endl;
|
|
return false;
|
|
default:
|
|
STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "SMT solver yielded an unexpected result");
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
template <class SparseModelType>
|
|
void SparseCbAchievabilityQuery<SparseModelType>::initializeConstraintSystem() {
|
|
uint_fast64_t numStates = this->preprocessedModel->getNumberOfStates();
|
|
uint_fast64_t numChoices = this->preprocessedModel->getNumberOfChoices();
|
|
uint_fast64_t numBottomStates = this->reward0EStates.getNumberOfSetBits();
|
|
|
|
storm::expressions::Expression zero = this->expressionManager->rational(storm::utility::zero<ValueType>());
|
|
storm::expressions::Expression one = this->expressionManager->rational(storm::utility::one<ValueType>());
|
|
|
|
// Declare the variables for the choices and bottom states
|
|
expectedChoiceVariables.reserve(numChoices);
|
|
for (uint_fast64_t choice = 0; choice < numChoices; ++choice) {
|
|
expectedChoiceVariables.push_back(this->expressionManager->declareRationalVariable("y" + std::to_string(choice)));
|
|
}
|
|
bottomStateVariables.reserve(numBottomStates);
|
|
for (uint_fast64_t bottomState = 0; bottomState < numBottomStates; ++bottomState) {
|
|
bottomStateVariables.push_back(this->expressionManager->declareRationalVariable("z" + std::to_string(bottomState)));
|
|
}
|
|
|
|
// assert that the values are greater zero and that the bottom state values sum up to one
|
|
for (auto& var : expectedChoiceVariables) {
|
|
solver->add(var.getExpression() >= zero);
|
|
}
|
|
storm::expressions::Expression bottomStateSum = zero;
|
|
for (auto& var : bottomStateVariables) {
|
|
solver->add(var.getExpression() >= zero);
|
|
bottomStateSum = bottomStateSum + var.getExpression();
|
|
}
|
|
solver->add(bottomStateSum == one);
|
|
|
|
// assert that the "incoming" value of each state equals the "outgoing" value
|
|
storm::storage::SparseMatrix<ValueType> backwardsTransitions = this->preprocessedModel->getTransitionMatrix().transpose();
|
|
auto bottomStateVariableIt = bottomStateVariables.begin();
|
|
for (uint_fast64_t state = 0; state < numStates; ++state) {
|
|
// get the "incomming" value
|
|
storm::expressions::Expression value = this->preprocessedModel->getInitialStates().get(state) ? one : zero;
|
|
for (auto const& backwardsEntry : backwardsTransitions.getRow(state)) {
|
|
value = value + (this->expressionManager->rational(backwardsEntry.getValue()) * expectedChoiceVariables[backwardsEntry.getColumn()].getExpression());
|
|
}
|
|
|
|
// subtract the "outgoing" value
|
|
for (uint_fast64_t choice = this->preprocessedModel->getTransitionMatrix().getRowGroupIndices()[state]; choice < this->preprocessedModel->getTransitionMatrix().getRowGroupIndices()[state + 1]; ++choice) {
|
|
value = value - expectedChoiceVariables[choice];
|
|
}
|
|
if (this->reward0EStates.get(state)) {
|
|
value = value - (*bottomStateVariableIt);
|
|
++bottomStateVariableIt;
|
|
}
|
|
solver->add(value == zero);
|
|
}
|
|
assert(bottomStateVariableIt == bottomStateVariables.end());
|
|
|
|
}
|
|
|
|
template <class SparseModelType>
|
|
void SparseCbAchievabilityQuery<SparseModelType>::addObjectiveConstraints() {
|
|
storm::expressions::Expression zero = this->expressionManager->rational(storm::utility::zero<ValueType>());
|
|
for (Objective<ValueType> const& obj : this->objectives) {
|
|
STORM_LOG_THROW(obj.formula->isRewardOperatorFormula() && obj.formula->getSubformula().isTotalRewardFormula(), storm::exceptions::InvalidOperationException, "Constraint-based solver only supports total-reward objectives. Got " << *obj.formula << " instead.");
|
|
STORM_LOG_THROW(obj.formula->hasBound(), storm::exceptions::InvalidOperationException, "Invoked achievability query but no bound was specified for at least one objective.");
|
|
STORM_LOG_THROW(obj.formula->asRewardOperatorFormula().hasRewardModelName(), storm::exceptions::InvalidOperationException, "Expected reward operator with a reward model name. Got " << *obj.formula << " instead.");
|
|
std::vector<ValueType> rewards = getActionBasedExpectedRewards(obj.formula->asRewardOperatorFormula().getRewardModelName());
|
|
std::vector<storm::expressions::Expression> objectiveValues;
|
|
for (uint_fast64_t choice = 0; choice < rewards.size(); ++choice) {
|
|
if (!storm::utility::isZero(rewards[choice])) {
|
|
objectiveValues.push_back(this->expressionManager->rational(rewards[choice]) * expectedChoiceVariables[choice].getExpression());
|
|
}
|
|
}
|
|
|
|
// Get the sum of all objective values
|
|
// As the sum can potentially have many summands, we want to make sure that the formula tree is (roughly balanced)
|
|
auto vIt = objectiveValues.begin();
|
|
while (objectiveValues.size() > 1) {
|
|
if (vIt == objectiveValues.end() || vIt == objectiveValues.end() - 1) {
|
|
vIt = objectiveValues.begin();
|
|
}
|
|
*vIt = *vIt + objectiveValues.back();
|
|
objectiveValues.pop_back();
|
|
++vIt;
|
|
}
|
|
storm::expressions::Expression objValue = objectiveValues.empty() ? zero : objectiveValues.front();
|
|
|
|
// We need to actually evaluate the threshold as rational number. Otherwise a threshold like '<=16/9' might be considered as 1 due to integer division
|
|
storm::expressions::Expression threshold = this->expressionManager->rational(obj.formula->getThreshold().evaluateAsRational());
|
|
switch (obj.formula->getBound().comparisonType) {
|
|
case storm::logic::ComparisonType::Greater:
|
|
solver->add( objValue > threshold);
|
|
break;
|
|
case storm::logic::ComparisonType::GreaterEqual:
|
|
solver->add( objValue >= threshold);
|
|
break;
|
|
case storm::logic::ComparisonType::Less:
|
|
solver->add( objValue < threshold);
|
|
break;
|
|
case storm::logic::ComparisonType::LessEqual:
|
|
solver->add( objValue <= threshold);
|
|
break;
|
|
default:
|
|
STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "One or more objectives have an invalid comparison type");
|
|
}
|
|
}
|
|
}
|
|
|
|
template <>
|
|
std::vector<double> SparseCbAchievabilityQuery<storm::models::sparse::Mdp<double>>::getActionBasedExpectedRewards(std::string const& rewardModelName) const {
|
|
return this->preprocessedModel->getRewardModel(rewardModelName).getTotalRewardVector(this->preprocessedModel->getTransitionMatrix());
|
|
}
|
|
|
|
template <>
|
|
std::vector<double> SparseCbAchievabilityQuery<storm::models::sparse::MarkovAutomaton<double>>::getActionBasedExpectedRewards(std::string const& rewardModelName) const {
|
|
auto const& rewModel = this->preprocessedModel->getRewardModel(rewardModelName);
|
|
STORM_LOG_ASSERT(!rewModel.hasTransitionRewards(), "Preprocessed Reward model has transition rewards which is not expected.");
|
|
std::vector<double> result = rewModel.hasStateActionRewards() ? rewModel.getStateActionRewardVector() : std::vector<double>(this->preprocessedModel->getNumberOfChoices(), storm::utility::zero<ValueType>());
|
|
if(rewModel.hasStateRewards()) {
|
|
// Note that state rewards are earned over time and thus play no role for probabilistic states
|
|
for(auto markovianState : this->preprocessedModel->getMarkovianStates()) {
|
|
result[this->preprocessedModel->getTransitionMatrix().getRowGroupIndices()[markovianState]] += rewModel.getStateReward(markovianState) / this->preprocessedModel->getExitRate(markovianState);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
template <>
|
|
std::vector<storm::RationalNumber> SparseCbAchievabilityQuery<storm::models::sparse::MarkovAutomaton<storm::RationalNumber>>::getActionBasedExpectedRewards(std::string const& rewardModelName) const {
|
|
auto const& rewModel = this->preprocessedModel->getRewardModel(rewardModelName);
|
|
STORM_LOG_ASSERT(!rewModel.hasTransitionRewards(), "Preprocessed Reward model has transition rewards which is not expected.");
|
|
std::vector<storm::RationalNumber> result = rewModel.hasStateActionRewards() ? rewModel.getStateActionRewardVector() : std::vector<storm::RationalNumber>(this->preprocessedModel->getNumberOfChoices(), storm::utility::zero<ValueType>());
|
|
if(rewModel.hasStateRewards()) {
|
|
// Note that state rewards are earned over time and thus play no role for probabilistic states
|
|
for(auto markovianState : this->preprocessedModel->getMarkovianStates()) {
|
|
result[this->preprocessedModel->getTransitionMatrix().getRowGroupIndices()[markovianState]] += rewModel.getStateReward(markovianState) / this->preprocessedModel->getExitRate(markovianState);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
template <>
|
|
std::vector<storm::RationalNumber> SparseCbAchievabilityQuery<storm::models::sparse::Mdp<storm::RationalNumber>>::getActionBasedExpectedRewards(std::string const& rewardModelName) const {
|
|
return this->preprocessedModel->getRewardModel(rewardModelName).getTotalRewardVector(this->preprocessedModel->getTransitionMatrix());
|
|
}
|
|
|
|
|
|
#ifdef STORM_HAVE_CARL
|
|
template class SparseCbAchievabilityQuery<storm::models::sparse::Mdp<double>>;
|
|
template class SparseCbAchievabilityQuery<storm::models::sparse::MarkovAutomaton<double>>;
|
|
|
|
template class SparseCbAchievabilityQuery<storm::models::sparse::Mdp<storm::RationalNumber>>;
|
|
template class SparseCbAchievabilityQuery<storm::models::sparse::MarkovAutomaton<storm::RationalNumber>>;
|
|
#endif
|
|
}
|
|
}
|
|
}
|