Browse Source

WeighedObjectives model checking, first version for multi-objective achievability queries

Former-commit-id: 484795cc7c
main
TimQu 9 years ago
parent
commit
f529816df4
  1. 13
      src/modelchecker/multiobjective/SparseMdpMultiObjectiveModelChecker.cpp
  2. 42
      src/modelchecker/multiobjective/helper/SparseMdpMultiObjectivePreprocessingHelper.cpp
  3. 2
      src/modelchecker/multiobjective/helper/SparseMdpMultiObjectivePreprocessingHelper.h
  4. 137
      src/modelchecker/multiobjective/helper/SparseMultiObjectiveModelCheckerHelper.cpp
  5. 64
      src/modelchecker/multiobjective/helper/SparseMultiObjectiveModelCheckerHelper.h
  6. 16
      src/modelchecker/multiobjective/helper/SparseMultiObjectiveModelCheckerInformation.h
  7. 163
      src/modelchecker/multiobjective/helper/SparseWeightedObjectivesModelCheckerHelper.cpp
  8. 90
      src/modelchecker/multiobjective/helper/SparseWeightedObjectivesModelCheckerHelper.h
  9. 18
      src/storage/TotalScheduler.cpp
  10. 19
      src/storage/TotalScheduler.h
  11. 16
      src/storage/geometry/Polytope.cpp
  12. 21
      src/storage/geometry/Polytope.h
  13. 41
      src/utility/vector.h

13
src/modelchecker/multiobjective/SparseMdpMultiObjectiveModelChecker.cpp

@ -9,8 +9,9 @@
#include "src/modelchecker/results/ExplicitQualitativeCheckResult.h"
#include "src/modelchecker/results/ExplicitQuantitativeCheckResult.h"
#include "src/modelchecker/multiobjective/helper/SparseMdpMultiObjectivePreprocessingHelper.h"
#include "src/modelchecker/multiobjective/helper/SparseMultiObjectiveModelCheckerInformation.h"
#include "src/modelchecker/multiobjective/helper/SparseMdpMultiObjectivePreprocessingHelper.h"
#include "src/modelchecker/multiobjective/helper/SparseMultiObjectiveModelCheckerHelper.h"
#include "src/exceptions/NotImplementedException.h"
@ -35,11 +36,19 @@ namespace storm {
std::unique_ptr<CheckResult> SparseMdpMultiObjectiveModelChecker<SparseMdpModelType>::checkMultiObjectiveFormula(CheckTask<storm::logic::MultiObjectiveFormula> const& checkTask) {
helper::SparseMultiObjectiveModelCheckerInformation<SparseMdpModelType> info = helper::SparseMdpMultiObjectivePreprocessingHelper<SparseMdpModelType>::preprocess(checkTask.getFormula(), this->getModel());
std::cout << std::endl;
std::cout << "Preprocessed Information:" << std::endl;
info.printInformationToStream(std::cout);
#ifdef STORM_HAVE_CARL
helper::SparseMultiObjectiveModelCheckerHelper<SparseMdpModelType, storm::RationalNumber> helper(info);
helper.achievabilityQuery();
#else
STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Multi objective model checking currently requires carl.");
#endif
std::cout << "Information after helper call: " << std::endl;
info.printInformationToStream(std::cout);
return std::unique_ptr<CheckResult>(new ExplicitQualitativeCheckResult());
}

42
src/modelchecker/multiobjective/helper/SparseMdpMultiObjectivePreprocessingHelper.cpp

@ -22,10 +22,10 @@ namespace storm {
namespace helper {
template<typename SparseMdpModelType>
SparseMultiObjectiveModelCheckerInformation<SparseMdpModelType> SparseMdpMultiObjectivePreprocessingHelper<SparseMdpModelType>::preprocess(storm::logic::MultiObjectiveFormula const& originalFormula, SparseMdpModelType originalModel) {
SparseMultiObjectiveModelCheckerInformation<SparseMdpModelType> SparseMdpMultiObjectivePreprocessingHelper<SparseMdpModelType>::preprocess(storm::logic::MultiObjectiveFormula const& originalFormula, SparseMdpModelType const& originalModel) {
Information info(std::move(originalModel));
//Initialize the state mapping.
info.newToOldStateIndexMapping = storm::utility::vector::buildVectorForRange(0, info.model.getNumberOfStates());
info.newToOldStateIndexMapping = storm::utility::vector::buildVectorForRange(0, info.preprocessedModel.getNumberOfStates());
//Gather information regarding the individual objectives
for(auto const& subFormula : originalFormula.getSubFormulas()){
addObjective(subFormula, info);
@ -46,7 +46,7 @@ namespace storm {
//We can now remove all original reward models to save some memory
std::set<std::string> origRewardModels = originalFormula.getReferencedRewardModels();
for (auto const& rewModel : origRewardModels){
info.model.removeRewardModel(rewModel);
info.preprocessedModel.removeRewardModel(rewModel);
}
return info;
}
@ -70,7 +70,7 @@ namespace storm {
}
objective.rewardModelName = "objective" + std::to_string(info.objectives.size());
// Make sure the new reward model gets a unique name
while(info.model.hasRewardModel(objective.rewardModelName)){
while(info.preprocessedModel.hasRewardModel(objective.rewardModelName)){
objective.rewardModelName = "_" + objective.rewardModelName;
}
setStepBoundOfObjective(objective);
@ -145,8 +145,8 @@ namespace storm {
STORM_LOG_THROW(info.negatedRewardsConsidered == currentObjective.originalFormulaMinimizes, storm::exceptions::InvalidPropertyException, "Decided to consider " << (info.negatedRewardsConsidered ? "negated" : "non-negated") << "rewards, but the formula " << formula << (currentObjective.originalFormulaMinimizes ? " minimizes" : "maximizes") << ". Reward objectives should either all minimize or all maximize.");
// Check if the reward model is uniquely specified
STORM_LOG_THROW((formula.hasRewardModelName() && info.model.hasRewardModel(formula.getRewardModelName()))
|| info.model.hasUniqueRewardModel(), storm::exceptions::InvalidPropertyException, "The reward model is not unique and the formula " << formula << " does not specify a reward model.");
STORM_LOG_THROW((formula.hasRewardModelName() && info.preprocessedModel.hasRewardModel(formula.getRewardModelName()))
|| info.preprocessedModel.hasUniqueRewardModel(), storm::exceptions::InvalidPropertyException, "The reward model is not unique and the formula " << formula << " does not specify a reward model.");
if(info.negatedRewardsConsidered && currentObjective.threshold){
*(currentObjective.threshold) *= -storm::utility::one<ValueType>();
@ -166,13 +166,13 @@ namespace storm {
void SparseMdpMultiObjectivePreprocessingHelper<SparseMdpModelType>::preprocessFormula(storm::logic::UntilFormula const& formula, Information& info, typename Information::ObjectiveInformation& currentObjective) {
CheckTask<storm::logic::Formula> phiTask(formula.getLeftSubformula());
CheckTask<storm::logic::Formula> psiTask(formula.getRightSubformula());
storm::modelchecker::SparsePropositionalModelChecker<SparseMdpModelType> mc(info.model);
storm::modelchecker::SparsePropositionalModelChecker<SparseMdpModelType> mc(info.preprocessedModel);
STORM_LOG_THROW(mc.canHandle(phiTask) && mc.canHandle(psiTask), storm::exceptions::InvalidPropertyException, "The subformulas of " << formula << " should be propositional.");
storm::storage::BitVector phiStates = mc.check(phiTask)->asExplicitQualitativeCheckResult().getTruthValuesVector();
storm::storage::BitVector psiStates = mc.check(psiTask)->asExplicitQualitativeCheckResult().getTruthValuesVector();
auto duplicatorResult = storm::transformer::StateDuplicator<SparseMdpModelType>::transform(info.model, ~phiStates | psiStates);
info.model = std::move(*duplicatorResult.model);
auto duplicatorResult = storm::transformer::StateDuplicator<SparseMdpModelType>::transform(info.preprocessedModel, ~phiStates | psiStates);
info.preprocessedModel = std::move(*duplicatorResult.model);
// duplicatorResult.newToOldStateIndexMapping now reffers to the indices of the model we had before preprocessing this formula.
// This might not be the actual original model.
for(auto & originalModelStateIndex : duplicatorResult.newToOldStateIndexMapping){
@ -181,21 +181,21 @@ namespace storm {
info.newToOldStateIndexMapping = std::move(duplicatorResult.newToOldStateIndexMapping);
// build stateAction reward vector that gives (one*transitionProbability) reward whenever a transition leads from the firstCopy to a psiState
storm::storage::BitVector newPsiStates(info.model.getNumberOfStates(), false);
storm::storage::BitVector newPsiStates(info.preprocessedModel.getNumberOfStates(), false);
for(auto const& oldPsiState : psiStates){
//note that psiStates are always located in the second copy
newPsiStates.set(duplicatorResult.secondCopyOldToNewStateIndexMapping[oldPsiState], true);
}
std::vector<ValueType> objectiveRewards(info.model.getTransitionMatrix().getRowCount(), storm::utility::zero<ValueType>());
std::vector<ValueType> objectiveRewards(info.preprocessedModel.getTransitionMatrix().getRowCount(), storm::utility::zero<ValueType>());
for (auto const& firstCopyState : duplicatorResult.firstCopy) {
for (uint_fast64_t row = info.model.getTransitionMatrix().getRowGroupIndices()[firstCopyState]; row < info.model.getTransitionMatrix().getRowGroupIndices()[firstCopyState + 1]; ++row) {
objectiveRewards[row] = info.model.getTransitionMatrix().getConstrainedRowSum(row, newPsiStates);
for (uint_fast64_t row = info.preprocessedModel.getTransitionMatrix().getRowGroupIndices()[firstCopyState]; row < info.preprocessedModel.getTransitionMatrix().getRowGroupIndices()[firstCopyState + 1]; ++row) {
objectiveRewards[row] = info.preprocessedModel.getTransitionMatrix().getConstrainedRowSum(row, newPsiStates);
}
}
if(info.negatedRewardsConsidered){
storm::utility::vector::scaleVectorInPlace(objectiveRewards, -storm::utility::one<ValueType>());
}
info.model.addRewardModel(currentObjective.rewardModelName, RewardModelType(boost::none, objectiveRewards));
info.preprocessedModel.addRewardModel(currentObjective.rewardModelName, RewardModelType(boost::none, objectiveRewards));
}
template<typename SparseMdpModelType>
@ -218,13 +218,13 @@ namespace storm {
STORM_LOG_THROW(formula.isReachabilityRewardFormula(), storm::exceptions::InvalidPropertyException, "The formula " << formula << " neither considers reachability Probabilities nor reachability rewards");
CheckTask<storm::logic::Formula> targetTask(formula.getSubformula());
storm::modelchecker::SparsePropositionalModelChecker<SparseMdpModelType> mc(info.model);
storm::modelchecker::SparsePropositionalModelChecker<SparseMdpModelType> mc(info.preprocessedModel);
STORM_LOG_THROW(mc.canHandle(targetTask), storm::exceptions::InvalidPropertyException, "The subformula of " << formula << " should be propositional.");
storm::storage::BitVector targetStates = mc.check(targetTask)->asExplicitQualitativeCheckResult().getTruthValuesVector();
STORM_LOG_WARN("There is no check for reward finiteness yet"); //TODO
auto duplicatorResult = storm::transformer::StateDuplicator<SparseMdpModelType>::transform(info.model, targetStates);
info.model = std::move(*duplicatorResult.model);
auto duplicatorResult = storm::transformer::StateDuplicator<SparseMdpModelType>::transform(info.preprocessedModel, targetStates);
info.preprocessedModel = std::move(*duplicatorResult.model);
// duplicatorResult.newToOldStateIndexMapping now reffers to the indices of the model we had before preprocessing this formula.
// This might not be the actual original model.
for(auto & originalModelStateIndex : duplicatorResult.newToOldStateIndexMapping){
@ -233,21 +233,21 @@ namespace storm {
info.newToOldStateIndexMapping = std::move(duplicatorResult.newToOldStateIndexMapping);
// Add a reward model that gives zero reward to the states of the second copy.
std::vector<ValueType> objectiveRewards = info.model.getRewardModel(optionalRewardModelName ? optionalRewardModelName.get() : "").getTotalRewardVector(info.model.getTransitionMatrix());
std::vector<ValueType> objectiveRewards = info.preprocessedModel.getRewardModel(optionalRewardModelName ? optionalRewardModelName.get() : "").getTotalRewardVector(info.preprocessedModel.getTransitionMatrix());
storm::utility::vector::setVectorValues(objectiveRewards, duplicatorResult.secondCopy, storm::utility::zero<ValueType>());
if(info.negatedRewardsConsidered){
storm::utility::vector::scaleVectorInPlace(objectiveRewards, -storm::utility::one<ValueType>());
}
info.model.addRewardModel(currentObjective.rewardModelName, RewardModelType(boost::none, objectiveRewards));
info.preprocessedModel.addRewardModel(currentObjective.rewardModelName, RewardModelType(boost::none, objectiveRewards));
}
template<typename SparseMdpModelType>
void SparseMdpMultiObjectivePreprocessingHelper<SparseMdpModelType>::preprocessFormula(storm::logic::CumulativeRewardFormula const& formula, Information& info, typename Information::ObjectiveInformation& currentObjective, boost::optional<std::string> const& optionalRewardModelName) {
std::vector<ValueType> objectiveRewards = info.model.getRewardModel(optionalRewardModelName ? optionalRewardModelName.get() : "").getTotalRewardVector(info.model.getTransitionMatrix());
std::vector<ValueType> objectiveRewards = info.preprocessedModel.getRewardModel(optionalRewardModelName ? optionalRewardModelName.get() : "").getTotalRewardVector(info.preprocessedModel.getTransitionMatrix());
if(info.negatedRewardsConsidered){
storm::utility::vector::scaleVectorInPlace(objectiveRewards, -storm::utility::one<ValueType>());
}
info.model.addRewardModel(currentObjective.rewardModelName, RewardModelType(boost::none, objectiveRewards));
info.preprocessedModel.addRewardModel(currentObjective.rewardModelName, RewardModelType(boost::none, objectiveRewards));
}
template class SparseMdpMultiObjectivePreprocessingHelper<storm::models::sparse::Mdp<double>>;

2
src/modelchecker/multiobjective/helper/SparseMdpMultiObjectivePreprocessingHelper.h

@ -25,7 +25,7 @@ namespace storm {
* @param originalModel The considered model
* @param originalFormula the considered formula. The subformulas should only contain one OperatorFormula at top level, i.e., the formula is simple.
*/
static Information preprocess(storm::logic::MultiObjectiveFormula const& originalFormula, SparseMdpModelType originalModel);
static Information preprocess(storm::logic::MultiObjectiveFormula const& originalFormula, SparseMdpModelType const& originalModel);
private:

137
src/modelchecker/multiobjective/helper/SparseMultiObjectiveModelCheckerHelper.cpp

@ -0,0 +1,137 @@
#include "src/modelchecker/multiobjective/helper/SparseMultiObjectiveModelCheckerHelper.h"
#include "src/adapters/CarlAdapter.h"
#include "src/models/sparse/Mdp.h"
#include "src/models/sparse/StandardRewardModel.h"
#include "src/utility/constants.h"
#include "src/exceptions/UnexpectedException.h"
namespace storm {
namespace modelchecker {
namespace helper {
template <class SparseModelType, typename RationalNumberType>
SparseMultiObjectiveModelCheckerHelper<SparseModelType, RationalNumberType>::SparseMultiObjectiveModelCheckerHelper(Information& info) : info(info), weightedObjectivesChecker(info) {
overApproximation = storm::storage::geometry::Polytope<RationalNumberType>::createUniversalPolytope();
underApproximation = storm::storage::geometry::Polytope<RationalNumberType>::createEmptyPolytope();
}
template <class SparseModelType, typename RationalNumberType>
SparseMultiObjectiveModelCheckerHelper<SparseModelType, RationalNumberType>::~SparseMultiObjectiveModelCheckerHelper() {
// Intentionally left empty
}
template <class SparseModelType, typename RationalNumberType>
void SparseMultiObjectiveModelCheckerHelper<SparseModelType, RationalNumberType>::achievabilityQuery() {
Point queryPoint;
queryPoint.reserve(info.objectives.size());
for(auto const& obj : info.objectives) {
STORM_LOG_THROW(obj.threshold, storm::exceptions::UnexpectedException, "Can not perform achievabilityQuery: No threshold given for at least one objective.");
queryPoint.push_back(storm::utility::convertNumber<RationalNumberType>(*obj.threshold));
}
achievabilityQuery(queryPoint);
}
template <class SparseModelType, typename RationalNumberType>
void SparseMultiObjectiveModelCheckerHelper<SparseModelType, RationalNumberType>::achievabilityQuery(Point const& queryPoint) {
storm::storage::BitVector individualObjectivesToBeChecked(info.objectives.size(), true);
while(true) { //TODO introduce convergence criterion? (like max num of iterations)
storm::storage::geometry::Halfspace<RationalNumberType> separatingHalfspace = findSeparatingHalfspace(queryPoint, individualObjectivesToBeChecked);
weightedObjectivesChecker.check(storm::utility::vector::convertNumericVector<ModelValueType>(separatingHalfspace.normalVector()));
storm::storage::TotalScheduler scheduler = weightedObjectivesChecker.getScheduler();
Point weightedObjectivesResult = storm::utility::vector::convertNumericVector<RationalNumberType>(weightedObjectivesChecker.getInitialStateResultOfObjectives());
// Insert the computed scheduler and check whether we have already seen it before
auto schedulerInsertRes = schedulers.insert(std::make_pair(std::move(scheduler), paretoOptimalPoints.end()));
if(schedulerInsertRes.second){
// The scheduler is new, so insert the newly computed pareto optimal point.
// To each scheduler, we assign the (unique) pareto optimal point it induces.
schedulerInsertRes.first->second = paretoOptimalPoints.insert(std::make_pair(std::move(weightedObjectivesResult), std::vector<WeightVector>())).first;
}
// In the case where the scheduler is not new, we assume that the corresponding pareto optimal points for the old and new scheduler are equal
// Note that the values might not be exactly equal due to numerical issues.
Point const& paretoPoint = schedulerInsertRes.first->second->first;
std::vector<WeightVector>& weightVectors = schedulerInsertRes.first->second->second;
weightVectors.push_back(std::move(separatingHalfspace.normalVector()));
updateOverApproximation(paretoPoint, weightVectors.back());
if(!overApproximation->contains(queryPoint)){
std::cout << "PROPERTY VIOLATED" << std::endl;
return;
}
updateUnderApproximation();
if(underApproximation->contains(queryPoint)){
std::cout << "PROPERTY SATISFIED" << std::endl;
return;
}
}
}
template <class SparseModelType, typename RationalNumberType>
storm::storage::geometry::Halfspace<RationalNumberType> SparseMultiObjectiveModelCheckerHelper<SparseModelType, RationalNumberType>::findSeparatingHalfspace(Point const& pointToBeSeparated, storm::storage::BitVector& individualObjectivesToBeChecked) {
// First, we check 'simple' weight vectors that correspond to checking a single objective
while (!individualObjectivesToBeChecked.empty()){
uint_fast64_t objectiveIndex = individualObjectivesToBeChecked.getNextSetIndex(0);
individualObjectivesToBeChecked.set(objectiveIndex, false);
WeightVector normalVector; // = (0..0 1 0..0)
normalVector.reserve(info.objectives.size());
for(uint_fast64_t i = 0; i<info.objectives.size(); ++i){
normalVector.push_back( (i==objectiveIndex) ? storm::utility::one<RationalNumberType>() : storm::utility::zero<RationalNumberType>());
}
storm::storage::geometry::Halfspace<RationalNumberType> h(normalVector, storm::utility::vector::dotProduct(normalVector, pointToBeSeparated));
bool hIsSeparating = true;
for(auto const& paretoPoint : paretoOptimalPoints){
hIsSeparating &= h.contains(paretoPoint.first);
}
if(hIsSeparating) {
return h;
}
}
return underApproximation->findSeparatingHalfspace(pointToBeSeparated);
}
template <class SparseModelType, typename RationalNumberType>
void SparseMultiObjectiveModelCheckerHelper<SparseModelType, RationalNumberType>::updateOverApproximation(Point const& newPoint, WeightVector const& newWeightVector) {
storm::storage::geometry::Halfspace<RationalNumberType> h(newWeightVector, storm::utility::vector::dotProduct(newWeightVector, newPoint));
overApproximation = overApproximation->intersection(h);
STORM_LOG_DEBUG("Updated OverApproximation to polytope " << overApproximation->toString(true));
}
template <class SparseModelType, typename RationalNumberType>
void SparseMultiObjectiveModelCheckerHelper<SparseModelType, RationalNumberType>::updateUnderApproximation() {
std::vector<Point> paretoPointsVec;
paretoPointsVec.reserve(paretoOptimalPoints.size());
for(auto const& paretoPoint : paretoOptimalPoints) {
paretoPointsVec.push_back(paretoPoint.first);
}
boost::optional<Point> upperBounds;
if(!paretoPointsVec.empty()){
//Get the pointwise maximum of the pareto points
upperBounds = paretoPointsVec.front();
for(auto paretoPointIt = paretoPointsVec.begin()+1; paretoPointIt != paretoPointsVec.end(); ++paretoPointIt){
auto upperBoundIt = upperBounds->begin();
for(auto const& paretoPointCoordinate : *paretoPointIt){
if(paretoPointCoordinate>*upperBoundIt){
*upperBoundIt = paretoPointCoordinate;
}
++upperBoundIt;
}
}
}
underApproximation = storm::storage::geometry::Polytope<RationalNumberType>::create(paretoPointsVec)->downwardClosure(upperBounds);
STORM_LOG_DEBUG("Updated UnderApproximation to polytope " << overApproximation->toString(true));
}
#ifdef STORM_HAVE_CARL
template class SparseMultiObjectiveModelCheckerHelper<storm::models::sparse::Mdp<double>, storm::RationalNumber>;
#endif
}
}
}

64
src/modelchecker/multiobjective/helper/SparseMultiObjectiveModelCheckerHelper.h

@ -0,0 +1,64 @@
#ifndef STORM_MODELCHECKER_MULTIOBJECTIVE_HELPER_SPARSEMULTIOBJECTIVEMODELCHECKERHELPER_H_
#define STORM_MODELCHECKER_MULTIOBJECTIVE_HELPER_SPARSEMULTIOBJECTIVEMODELCHECKERHELPER_H_
#include <map>
#include <unordered_map>
#include "src/modelchecker/multiobjective/helper/SparseMultiObjectiveModelCheckerInformation.h"
#include "src/modelchecker/multiObjective/helper/SparseWeightedObjectivesModelCheckerHelper.h"
#include "src/storage/geometry/Polytope.h"
#include "src/storage/TotalScheduler.h"
namespace storm {
namespace modelchecker {
namespace helper {
template <class SparseModelType, typename RationalNumberType>
class SparseMultiObjectiveModelCheckerHelper {
public:
typedef typename SparseModelType::ValueType ModelValueType;
typedef typename SparseModelType::RewardModelType RewardModelType;
typedef SparseMultiObjectiveModelCheckerInformation<SparseModelType> Information;
typedef std::vector<RationalNumberType> Point;
typedef std::vector<RationalNumberType> WeightVector;
SparseMultiObjectiveModelCheckerHelper(Information& info);
~SparseMultiObjectiveModelCheckerHelper();
void achievabilityQuery();
private:
void achievabilityQuery(Point const& queryPoint);
/*
* Returns a halfspace h that separates the underapproximation from the given point p, i.e.,
* - p lies on the border of h and
* - for each x in the underApproximation, it holds that h contains x
*
* @param pointToBeSeparated the point that is to be seperated
* @param objectivesToBeCheckedIndividually stores for each objective whether it still makes sense to check for this objective individually (i.e., with weight vector given by w_{objIndex} = 1 )
*/
storm::storage::geometry::Halfspace<RationalNumberType> findSeparatingHalfspace(Point const& pointToBeSeparated, storm::storage::BitVector& individualObjectivesToBeChecked);
void updateOverApproximation(Point const& newPoint, WeightVector const& newWeightVector);
void updateUnderApproximation();
Information& info;
SparseWeightedObjectivesModelCheckerHelper<SparseModelType> weightedObjectivesChecker;
//TODO: sort points as needed
std::map<Point, std::vector<WeightVector>> paretoOptimalPoints;
std::unordered_map<storm::storage::TotalScheduler, typename std::map<Point, std::vector<WeightVector>>::iterator> schedulers;
std::shared_ptr<storm::storage::geometry::Polytope<RationalNumberType>> overApproximation;
std::shared_ptr<storm::storage::geometry::Polytope<RationalNumberType>> underApproximation;
};
}
}
}
#endif /* STORM_MODELCHECKER_MULTIOBJECTIVE_HELPER_SPARSEMULTIOBJECTIVEMODELCHECKERHELPER_H_ */

16
src/modelchecker/multiobjective/helper/SparseMultiObjectiveModelCheckerInformation.h

@ -49,15 +49,12 @@ namespace storm {
};
SparseMultiObjectiveModelCheckerInformation(SparseModelType const& model) : model(model) {
SparseMultiObjectiveModelCheckerInformation(SparseModelType const& model) : preprocessedModel(model), originalModel(model) {
//Intentionally left empty
}
SparseMultiObjectiveModelCheckerInformation(SparseModelType && model) : model(model) {
//Intentionally left empty
}
SparseModelType model;
SparseModelType preprocessedModel;
SparseModelType const& originalModel;
std::vector<uint_fast64_t> newToOldStateIndexMapping;
bool negatedRewardsConsidered;
@ -75,8 +72,11 @@ namespace storm {
}
out << "--------------------------------------------------------------" << std::endl;
out << std::endl;
out << "Original Model Information:" << std::endl;
originalModel.printModelInformationToStream(out);
out << std::endl;
out << "Preprocessed Model Information:" << std::endl;
model.printModelInformationToStream(out);
preprocessedModel.printModelInformationToStream(out);
out << std::endl;
if(negatedRewardsConsidered){
out << "The rewards in the preprocessed model are negated" << std::endl;

163
src/modelchecker/multiobjective/helper/SparseWeightedObjectivesModelCheckerHelper.cpp

@ -0,0 +1,163 @@
#include "src/modelchecker/multiobjective/helper/SparseWeightedObjectivesModelCheckerHelper.h"
#include "src/models/sparse/Mdp.h"
#include "src/models/sparse/StandardRewardModel.h"
#include "src/modelchecker/prctl/helper/SparseDtmcPrctlHelper.h"
#include "src/solver/LinearEquationSolver.h"
#include "src/solver/MinMaxLinearEquationSolver.h"
#include "src/utility/graph.h"
#include "src/utility/macros.h"
#include "src/utility/solver.h"
#include "src/utility/vector.h"
#include "src/exceptions/IllegalFunctionCallException.h"
namespace storm {
namespace modelchecker {
namespace helper {
template <class SparseModelType>
SparseWeightedObjectivesModelCheckerHelper<SparseModelType>::SparseWeightedObjectivesModelCheckerHelper(Information const& info) : info(info), checkHasBeenCalled(false) , objectiveResults(info.objectives.size()){
}
template <class SparseModelType>
void SparseWeightedObjectivesModelCheckerHelper<SparseModelType>::check(std::vector<ValueType> const& weightVector) {
unboundedWeightedPhase(weightVector);
unboundedIndividualPhase(weightVector);
boundedPhase(weightVector);
checkHasBeenCalled=true;
}
template <class SparseModelType>
std::vector<typename SparseWeightedObjectivesModelCheckerHelper<SparseModelType>::ValueType> SparseWeightedObjectivesModelCheckerHelper<SparseModelType>::getInitialStateResultOfObjectives() const {
STORM_LOG_THROW(checkHasBeenCalled, storm::exceptions::IllegalFunctionCallException, "Tried to retrieve results but check(..) has not been called before.");
STORM_LOG_ASSERT(info.preprocessedModel.getInitialStates().getNumberOfSetBits()==1, "The considered model has multiple initial states");
std::vector<ValueType> res;
res.reserve(objectiveResults.size());
for(auto const& objResult : objectiveResults) {
res.push_back(objResult[*info.preprocessedModel.getInitialStates().begin()]);
}
return res;
}
template <class SparseModelType>
storm::storage::TotalScheduler const& SparseWeightedObjectivesModelCheckerHelper<SparseModelType>::getScheduler() const {
STORM_LOG_THROW(checkHasBeenCalled, storm::exceptions::IllegalFunctionCallException, "Tried to retrieve results but check(..) has not been called before.");
return scheduler;
}
template <class SparseModelType>
void SparseWeightedObjectivesModelCheckerHelper<SparseModelType>::unboundedWeightedPhase(std::vector<ValueType> const& weightVector) {
std::vector<ValueType> weightedRewardVector(info.preprocessedModel.getTransitionMatrix().getRowCount(), storm::utility::zero<ValueType>());
for(uint_fast64_t objIndex = 0; objIndex < weightVector.size(); ++objIndex) {
if(!info.objectives[objIndex].stepBound){
storm::utility::vector::addScaledVector(weightedRewardVector, info.preprocessedModel.getRewardModel(info.objectives[objIndex].rewardModelName).getStateActionRewardVector(), weightVector[objIndex]);
}
}
// TODO check for +/- infty reward...
//Testing..
storm::storage::BitVector test(64);
test.set(63);
std::cout << "Test set index with 0: " << test.getNextSetIndex(0) << std::endl;
std::cout << "Test set index with 63: " << test.getNextSetIndex(63) << std::endl;
std::cout << "Test set index with 64: " << test.getNextSetIndex(64) << std::endl;
storm::storage::BitVector actionsWithRewards = storm::utility::vector::filter<ValueType>(weightedRewardVector, [&] (ValueType const& value) -> bool {return !storm::utility::isZero(value);});
storm::storage::BitVector statesWithRewards(info.preprocessedModel.getNumberOfStates(), false);
uint_fast64_t currActionIndex = actionsWithRewards.getNextSetIndex(0);
auto endOfRowGroup = info.preprocessedModel.getTransitionMatrix().getRowGroupIndices().begin() + 1;
for(uint_fast64_t state = 0; state<info.preprocessedModel.getNumberOfStates(); ++state){
if(currActionIndex < *endOfRowGroup){
statesWithRewards.set(state);
currActionIndex = actionsWithRewards.getNextSetIndex(*endOfRowGroup);
}
++endOfRowGroup;
}
STORM_LOG_WARN("REMOVE VALIDATION CODE");
for(uint_fast64_t state = 0; state<info.preprocessedModel.getNumberOfStates(); ++state){
bool stateHasReward=false;
for(uint_fast64_t row = info.preprocessedModel.getTransitionMatrix().getRowGroupIndices()[state]; row< info.preprocessedModel.getTransitionMatrix().getRowGroupIndices()[state+1]; ++row) {
stateHasReward |= actionsWithRewards.get(row);
}
STORM_LOG_ERROR_COND(stateHasReward == statesWithRewards.get(state), "statesWithRewardsVector is wrong!!!!");
}
//Get the states from which a state with reward can be reached.
storm::storage::BitVector maybeStates = storm::utility::graph::performProbGreater0E(info.preprocessedModel.getTransitionMatrix(),
info.preprocessedModel.getTransitionMatrix().getRowGroupIndices(),
info.preprocessedModel.getBackwardTransitions(),
storm::storage::BitVector(info.preprocessedModel.getNumberOfStates(), true),
statesWithRewards);
this->weightedResult = std::vector<ValueType>(info.preprocessedModel.getNumberOfStates());
this->scheduler = storm::storage::TotalScheduler(info.preprocessedModel.getNumberOfStates());
storm::storage::SparseMatrix<ValueType> submatrix = info.preprocessedModel.getTransitionMatrix().getSubmatrix(true, maybeStates, maybeStates, false);
std::vector<ValueType> b(submatrix.getRowCount());
storm::utility::vector::selectVectorValues(b, maybeStates, weightedRewardVector);
std::vector<ValueType> x(submatrix.getRowGroupCount(), storm::utility::zero<ValueType>());
storm::utility::solver::MinMaxLinearEquationSolverFactory<ValueType> solverFactory;
std::unique_ptr<storm::solver::MinMaxLinearEquationSolver<ValueType>> solver = solverFactory.create(submatrix, true);
solver->solveEquationSystem(x, b);
storm::utility::vector::setVectorValues(this->weightedResult, maybeStates, x);
storm::utility::vector::setVectorValues(this->weightedResult, ~maybeStates, storm::utility::zero<ValueType>());
uint_fast64_t currentSubState = 0;
for (auto maybeState : maybeStates) {
this->scheduler.setChoice(maybeState, solver->getScheduler().getChoice(currentSubState));
++currentSubState;
}
// Note that the choices for the ~maybeStates are arbitrary as no states with rewards are reachable any way.
}
template <class SparseModelType>
void SparseWeightedObjectivesModelCheckerHelper<SparseModelType>::unboundedIndividualPhase(std::vector<ValueType> const& weightVector) {
storm::storage::SparseMatrix<ValueType> deterministicMatrix = info.preprocessedModel.getTransitionMatrix().selectRowsFromRowGroups(this->scheduler.getChoices(), true);
storm::storage::SparseMatrix<ValueType> deterministicBackwardTransitions = deterministicMatrix.transpose();
std::vector<ValueType> deterministicStateRewards(deterministicMatrix.getRowCount());
storm::utility::solver::LinearEquationSolverFactory<ValueType> linearEquationSolverFactory;
//TODO check if all but one entry of weightVector is zero
for(uint_fast64_t objIndex = 0; objIndex < weightVector.size(); ++objIndex) {
if(!info.objectives[objIndex].stepBound){
storm::utility::vector::selectVectorValues(deterministicStateRewards, this->scheduler.getChoices(), info.preprocessedModel.getTransitionMatrix().getRowGroupIndices(), info.preprocessedModel.getRewardModel(info.objectives[objIndex].rewardModelName).getStateActionRewardVector());
storm::storage::BitVector statesWithRewards = storm::utility::vector::filter<ValueType>(deterministicStateRewards, [&] (ValueType const& value) -> bool {return !storm::utility::isZero(value);});
// As target states, we take the states from which no reward is reachable.
STORM_LOG_WARN("TODO: target state selection is currently only valid for reachability properties...");
//TODO: we should be able to give some hint to the solver..
storm::storage::BitVector targetStates = storm::utility::graph::performProbGreater0(deterministicBackwardTransitions, storm::storage::BitVector(deterministicMatrix.getRowCount(), true), statesWithRewards);
objectiveResults[objIndex] = storm::modelchecker::helper::SparseDtmcPrctlHelper<ValueType>::computeReachabilityRewards(deterministicMatrix,
deterministicBackwardTransitions,
deterministicStateRewards,
targetStates,
false, //no qualitative checking,
linearEquationSolverFactory);
}
}
}
template <class SparseModelType>
void SparseWeightedObjectivesModelCheckerHelper<SparseModelType>::boundedPhase(std::vector<ValueType> const& weightVector) {
STORM_LOG_WARN("bounded properties not yet implemented");
}
template class SparseWeightedObjectivesModelCheckerHelper<storm::models::sparse::Mdp<double>>;
}
}
}

90
src/modelchecker/multiobjective/helper/SparseWeightedObjectivesModelCheckerHelper.h

@ -0,0 +1,90 @@
#ifndef STORM_MODELCHECKER_MULTIOBJECTIVE_HELPER_SPARSEWEIGHTEDOBJECTIVESMODELCHECKERHELPER_H_
#define STORM_MODELCHECKER_MULTIOBJECTIVE_HELPER_SPARSEWEIGHTEDOBJECTIVESMODELCHECKERHELPER_H_
#include <vector>
#include "src/modelchecker/multiobjective/helper/SparseMultiObjectiveModelCheckerInformation.h"
#include "src/storage/TotalScheduler.h"
namespace storm {
namespace modelchecker {
namespace helper {
/*!
* Helper Class that takes a MultiObjectiveInformation and a weight vector and ...
* - computes the maximal expected reward w.r.t. the weighted sum of the rewards of the individual objectives
* - extracts the scheduler that induces this maximum
* - computes for each objective the value induced by this scheduler
*/
template <class SparseModelType>
class SparseWeightedObjectivesModelCheckerHelper {
public:
typedef typename SparseModelType::ValueType ValueType;
typedef typename SparseModelType::RewardModelType RewardModelType;
typedef SparseMultiObjectiveModelCheckerInformation<SparseModelType> Information;
SparseWeightedObjectivesModelCheckerHelper(Information const& info);
/*!
* - computes the maximal expected reward w.r.t. the weighted sum of the rewards of the individual objectives
* - extracts the scheduler that induces this maximum
* - computes for each objective the value induced by this scheduler
*/
void check(std::vector<ValueType> const& weightVector);
/*!
* Getter methods for the results of the most recent call of check(..)
* Note that check(..) has to be called before retrieving results. Otherwise, an exception is thrown.
*/
// The results of the individual objectives at the initial state of the given model
std::vector<ValueType> getInitialStateResultOfObjectives() const;
// A scheduler that induces the optimal values
storm::storage::TotalScheduler const& getScheduler() const;
private:
/*!
* Determines the scheduler that maximizes the weighted reward vector of the unbounded objectives
*
* @param weightVector the weight vector of the current check
*/
void unboundedWeightedPhase(std::vector<ValueType> const& weightVector);
/*!
* Computes the values of the objectives that do not have a stepBound w.r.t. the scheduler computed in the unboundedWeightedPhase
*
* @param weightVector the weight vector of the current check
*/
void unboundedIndividualPhase(std::vector<ValueType> const& weightVector);
/*!
* For each time epoch (starting with the maximal stepBound occurring in the objectives), this method
* - determines the objectives that are relevant in the current time epoch
* - determines the maximizing scheduler for the weighted reward vector of these objectives
* - computes the values of these objectives w.r.t. this scheduler
*
* @param weightVector the weight vector of the current check
*/
void boundedPhase(std::vector<ValueType> const& weightVector);
// stores the considered information of the multi-objective model checking problem
Information const& info;
// becomes true after the first call of check(..)
bool checkHasBeenCalled;
// The result for the weighted reward vector (for all states of the model)
std::vector<ValueType> weightedResult;
// The results for the individual objectives (for all states of the model)
std::vector<std::vector<ValueType>> objectiveResults;
// The scheduler that maximizes the weighted rewards
storm::storage::TotalScheduler scheduler;
};
}
}
}
#endif /* STORM_MODELCHECKER_MULTIOBJECTIVE_HELPER_SPARSEWEIGHTEDOBJECTIVESMODELCHECKERHELPER_H_ */

18
src/storage/TotalScheduler.cpp

@ -1,5 +1,6 @@
#include "src/storage/TotalScheduler.h"
#include "src/exceptions/InvalidArgumentException.h"
#include "src/utility/Hash.h"
namespace storm {
namespace storage {
@ -15,6 +16,10 @@ namespace storm {
// Intentionally left empty.
}
bool TotalScheduler::operator==(storm::storage::TotalScheduler const& other) const {
return this->choices == other.choices;
}
void TotalScheduler::setChoice(uint_fast64_t state, uint_fast64_t choice) {
if (state > choices.size()) {
throw storm::exceptions::InvalidArgumentException() << "Invalid call to TotalScheduler::setChoice: scheduler cannot not define a choice for state " << state << ".";
@ -34,6 +39,10 @@ namespace storm {
return choices[state];
}
std::vector<uint_fast64_t> const& TotalScheduler::getChoices() const {
return choices;
}
std::ostream& operator<<(std::ostream& out, TotalScheduler const& scheduler) {
out << "total scheduler (defined on " << scheduler.choices.size() << " states) [ ";
for (uint_fast64_t state = 0; state < scheduler.choices.size() - 1; ++state) {
@ -44,6 +53,11 @@ namespace storm {
}
return out;
}
}
}
}
namespace std {
std::size_t hash<storm::storage::TotalScheduler>::operator()(storm::storage::TotalScheduler const& totalScheduler) const {
return storm::utility::Hash<uint_fast64_t>::getHash(totalScheduler.choices);
}
}

19
src/storage/TotalScheduler.h

@ -11,6 +11,12 @@ namespace storm {
class TotalScheduler : public Scheduler {
public:
TotalScheduler(TotalScheduler const& other) = default;
TotalScheduler(TotalScheduler&& other) = default;
TotalScheduler& operator=(TotalScheduler const& other) = default;
TotalScheduler& operator=(TotalScheduler&& other) = default;
/*!
* Creates a total scheduler that defines a choice for the given number of states. By default, all choices
* are initialized to zero.
@ -32,6 +38,8 @@ namespace storm {
* @param choices A vector whose i-th entry defines the choice of state i.
*/
TotalScheduler(std::vector<uint_fast64_t>&& choices);
bool operator==(TotalScheduler const& other) const;
void setChoice(uint_fast64_t state, uint_fast64_t choice) override;
@ -39,7 +47,10 @@ namespace storm {
uint_fast64_t getChoice(uint_fast64_t state) const override;
std::vector<uint_fast64_t> const& getChoices() const;
friend std::ostream& operator<<(std::ostream& out, TotalScheduler const& scheduler);
friend struct std::hash<storm::storage::TotalScheduler>;
private:
// A vector that stores the choice for each state.
@ -48,4 +59,12 @@ namespace storm {
} // namespace storage
} // namespace storm
namespace std {
template <>
struct hash<storm::storage::TotalScheduler> {
std::size_t operator()(storm::storage::TotalScheduler const& totalScheduler) const;
};
}
#endif /* STORM_STORAGE_TOTALSCHEDULER_H_ */

16
src/storage/geometry/Polytope.cpp

@ -22,6 +22,16 @@ namespace storm {
return create(boost::none, points);
}
template <typename ValueType>
std::shared_ptr<Polytope<ValueType>> Polytope<ValueType>::createUniversalPolytope() {
return create(std::vector<Halfspace<ValueType>>(), boost::none);
}
template <typename ValueType>
std::shared_ptr<Polytope<ValueType>> Polytope<ValueType>::createEmptyPolytope() {
return create(boost::none, std::vector<Point>());
}
#ifdef STORM_HAVE_CARL
template <>
std::shared_ptr<Polytope<storm::RationalNumber>> Polytope<storm::RationalNumber>::create(boost::optional<std::vector<Halfspace<storm::RationalNumber>>> const& halfspaces,
@ -118,6 +128,12 @@ namespace storm {
return nullptr;
}
template <typename ValueType>
Halfspace<ValueType> Polytope<ValueType>::findSeparatingHalfspace(Point const& pointToBeSeparated) const {
STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Functionality not implemented.");
return Halfspace<ValueType>(Point(), storm::utility::zero<ValueType>());
}
template <typename ValueType>
std::string Polytope<ValueType>::toString(bool numbersAsDouble) const {
auto halfspaces = this->getHalfspaces();

21
src/storage/geometry/Polytope.h

@ -31,6 +31,16 @@ namespace storm {
*/
static std::shared_ptr<Polytope<ValueType>> create(std::vector<Point> const& points);
/*!
* Creates the universal polytope (i.e., the set R^n)
*/
static std::shared_ptr<Polytope<ValueType>> createUniversalPolytope();
/*!
* Creates the empty polytope (i.e., emptyset)
*/
static std::shared_ptr<Polytope<ValueType>> createEmptyPolytope();
/*!
* Returns the vertices of this polytope.
*/
@ -88,6 +98,17 @@ namespace storm {
*/
virtual std::shared_ptr<Polytope<ValueType>> downwardClosure(boost::optional<Point> const& upperBounds = boost::none) const;
/*!
* Returns a halfspace h that separates this polytope from the given point p, i.e.,
* - p lies on the border of h and
* - for each x in this polytope, it holds that h contains x
*
* A halfspace with maximal distance to the polytope is preferred.
*
* @param pointToBeSeparated the point that is to be seperated
*/
virtual Halfspace<ValueType> findSeparatingHalfspace(Point const& pointToBeSeparated) const;
/*
* Returns a string representation of this polytope.
* If the given flag is true, the occurring numbers are converted to double before printing to increase readability

41
src/utility/vector.h

@ -339,14 +339,26 @@ namespace storm {
/*!
* Multiplies each element of the given vector with the given factor and writes the result into the vector.
*
* @param target The first summand and target vector.
* @param summand The second summand.
* @param target The operand and target vector.
* @param factor The scaling factor
*/
template<class ValueType1, class ValueType2>
void scaleVectorInPlace(std::vector<ValueType1>& target, ValueType2 const& factor) {
applyPointwise<ValueType1, ValueType2>(target, target, [&] (ValueType1 const& argument) -> ValueType1 { return argument * factor; });
}
/*!
* Adds each element of the first vector and (the corresponding element of the second vector times the given factor) and writes the result into the first vector.
*
* @param firstOperand The first operand.
* @param secondOperand The second operand
* @param factor The factor for the elements of the second operand
*/
template<class InValueType1, class InValueType2, class InValueType3>
void addScaledVector(std::vector<InValueType1>& firstOperand, std::vector<InValueType2> const& secondOperand, InValueType3 const& factor) {
applyPointwise<InValueType1, InValueType2, InValueType1>(firstOperand, secondOperand, firstOperand, [&] (InValueType1 const& val1, InValueType2 const& val2) -> InValueType1 { return val1 + (factor * val2); });
}
/*!
* Computes the dot product (aka scalar product) and returns the result
*
@ -692,17 +704,30 @@ namespace storm {
return subVector;
}
/*!
* Converts the given vector to the given ValueType
* Assumes that both, TargetType and SourceType are numeric
*/
template<typename TargetType, typename SourceType>
std::vector<TargetType> convertNumericVector(std::vector<SourceType> const& oldVector) {
std::vector<TargetType> resultVector;
resultVector.reserve(oldVector.size());
for(auto const& oldValue : oldVector){
resultVector.push_back(storm::utility::convertNumber<TargetType>(oldValue));
}
return resultVector;
}
/*!
* Converts the given vector to the given ValueType
*/
template<typename NewValueType, typename ValueType>
std::vector<NewValueType> toValueType(std::vector<ValueType> const& oldVector) {
typename std::vector<NewValueType> toValueType(std::vector<ValueType> const& oldVector) {
std::vector<NewValueType> resultVector;
resultVector.resize(oldVector.size());
for (size_t i = 0, size = oldVector.size(); i < size; ++i) {
resultVector.at(i) = static_cast<NewValueType>(oldVector.at(i));
}
resultVector.reserve(oldVector.size());
for(auto const& oldValue : oldVector){
resultVector.push_back(static_cast<NewValueType>(oldValue));
}
return resultVector;
}

Loading…
Cancel
Save