22 changed files with 1997 additions and 207 deletions
-
60src/modelchecker/multiobjective/Pcaa.cpp
-
20src/modelchecker/multiobjective/Pcaa.h
-
30src/modelchecker/multiobjective/SparseMaMultiObjectiveModelChecker.cpp
-
31src/modelchecker/multiobjective/SparseMdpMultiObjectiveModelChecker.cpp
-
11src/modelchecker/multiobjective/helper/SparseMaMultiObjectiveWeightVectorChecker.cpp
-
2src/modelchecker/multiobjective/helper/SparseMultiObjectiveWeightVectorChecker.cpp
-
4src/modelchecker/multiobjective/pcaa/PCAAObjective.h
-
423src/modelchecker/multiobjective/pcaa/SparseMaPcaaWeightVectorChecker.cpp
-
157src/modelchecker/multiobjective/pcaa/SparseMaPcaaWeightVectorChecker.h
-
122src/modelchecker/multiobjective/pcaa/SparseMdpPcaaWeightVectorChecker.cpp
-
48src/modelchecker/multiobjective/pcaa/SparseMdpPcaaWeightVectorChecker.h
-
130src/modelchecker/multiobjective/pcaa/SparsePCAAPreprocessor.cpp
-
42src/modelchecker/multiobjective/pcaa/SparsePCAAPreprocessor.h
-
33src/modelchecker/multiobjective/pcaa/SparsePCAAPreprocessorReturnType.h
-
169src/modelchecker/multiobjective/pcaa/SparsePcaaQuantitativeQuery.cpp
-
65src/modelchecker/multiobjective/pcaa/SparsePcaaQuantitativeQuery.h
-
205src/modelchecker/multiobjective/pcaa/SparsePcaaQuery.cpp
-
122src/modelchecker/multiobjective/pcaa/SparsePcaaQuery.h
-
311src/modelchecker/multiobjective/pcaa/SparsePcaaWeightVectorChecker.cpp
-
155src/modelchecker/multiobjective/pcaa/SparsePcaaWeightVectorChecker.h
-
60src/transformer/EndComponentEliminator.h
-
4test/functional/transformer/EndComponentEliminatorTest.cpp
@ -0,0 +1,60 @@ |
|||
#include "src/modelchecker/multiobjective/pcaa.h"
|
|||
|
|||
#include "src/utility/macros.h"
|
|||
|
|||
#include "src/models/sparse/Mdp.h"
|
|||
#include "src/models/sparse/MarkovAutomaton.h"
|
|||
#include "src/models/sparse/StandardRewardModel.h"
|
|||
#include "src/modelchecker/multiobjective/pcaa/SparsePcaaPreprocessor.h"
|
|||
#include "src/modelchecker/multiobjective/pcaa/SparsePcaaAchievabilityQuery.h"
|
|||
#include "src/modelchecker/multiobjective/pcaa/SparsePcaaQuantitativeQuery.h"
|
|||
#include "src/modelchecker/multiobjective/pcaa/SparsePcaaParetoQuery.h"
|
|||
|
|||
#include "src/exceptions/InvalidArgumentException.h"
|
|||
|
|||
|
|||
namespace storm { |
|||
namespace modelchecker { |
|||
namespace multiobjective { |
|||
|
|||
template<typename SparseModelType> |
|||
std::unique_ptr<CheckResult> performPcaa(SparseModelType const& model, storm::logic::MultiObjectiveFormula const& formula) { |
|||
STORM_LOG_ASSERT(model.getInitialStates().getNumberOfSetBits() == 1, "Multi-objective Model checking on model with multiple initial states is not supported."); |
|||
|
|||
#ifdef STORM_HAVE_CARL
|
|||
auto preprocessorResult = SparsePcaaPreprocessor<SparseModelType>::preprocess(model, formula); |
|||
STORM_LOG_DEBUG("Preprocessing done. Result: " << preprocessorResult); |
|||
|
|||
std::unique_ptr<SparsePcaaQuery<SparseModelType, storm::RationalNumber>> query; |
|||
switch (preprocessorResult.queryType) { |
|||
case SparsePcaaPreprocessorReturnType<SparseModelType>::QueryType::Achievability: |
|||
query = std::unique_ptr<SparsePcaaQuery<SparseModelType, storm::RationalNumber>> (new SparsePcaaAchievabilityQuery<SparseModelType, storm::RationalNumber>(preprocessorResult)); |
|||
break; |
|||
case SparsePcaaPreprocessorReturnType<SparseModelType>::QueryType::Quantitative: |
|||
query = std::unique_ptr<SparsePcaaQuery<SparseModelType, storm::RationalNumber>> (new SparsePcaaQuantitativeQuery<SparseModelType, storm::RationalNumber>(preprocessorResult)); |
|||
break; |
|||
case SparsePcaaPreprocessorReturnType<SparseModelType>::QueryType::Pareto: |
|||
query = std::unique_ptr<SparsePcaaQuery<SparseModelType, storm::RationalNumber>> (new SparsePcaaParetoQuery<SparseModelType, storm::RationalNumber>(preprocessorResult)); |
|||
break; |
|||
default: |
|||
STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Unsupported multi-objective Query Type."); |
|||
break; |
|||
} |
|||
|
|||
return query->check(); |
|||
#else
|
|||
STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Multi-objective model checking requires carl."); |
|||
return nullptr; |
|||
#endif
|
|||
} |
|||
|
|||
template std::unique_ptr<CheckResult> performPcaa<storm::models::sparse::Mdp<double>>(storm::models::sparse::Mdp<double> const& model, storm::logic::MultiObjectiveFormula const& formula); |
|||
template std::unique_ptr<CheckResult> performPcaa<storm::models::sparse::MarkovAutomaton<double>>(storm::models::sparse::MarkovAutomaton<double> const& model, storm::logic::MultiObjectiveFormula const& formula); |
|||
#ifdef STORM_HAVE_CARL
|
|||
template std::unique_ptr<CheckResult> performPcaa<storm::models::sparse::Mdp<storm::RationalNumber>>(storm::models::sparse::Mdp<storm::RationalNumber> const& model, storm::logic::MultiObjectiveFormula const& formula); |
|||
// template std::unique_ptr<CheckResult> performPcaa<storm::models::sparse::MarkovAutomaton<storm::RationalNumber>>(storm::models::sparse::MarkovAutomaton<storm::RationalNumber> const& model, storm::logic::MultiObjectiveFormula const& formula);
|
|||
#endif
|
|||
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,20 @@ |
|||
#ifndef STORM_MODELCHECKER_MULTIOBJECTIVE_PCAA_H_ |
|||
#define STORM_MODELCHECKER_MULTIOBJECTIVE_PCAA_H_ |
|||
|
|||
#include <memory> |
|||
|
|||
#include "src/modelchecker/results/CheckResult.h" |
|||
#include "src/logic/Formulas.h" |
|||
|
|||
namespace storm { |
|||
namespace modelchecker { |
|||
namespace multiobjective { |
|||
|
|||
template<typename SparseModelType> |
|||
std::unique_ptr<CheckResult> performPcaa(SparseModelType const& model, storm::logic::MultiObjectiveFormula const& formula); |
|||
|
|||
} |
|||
} |
|||
} |
|||
|
|||
#endif /* STORM_MODELCHECKER_MULTIOBJECTIVE_PCAA_H_ */ |
@ -0,0 +1,423 @@ |
|||
#include "src/modelchecker/multiobjective/pcaa/SparseMaPcaaWeightVectorChecker.h"
|
|||
|
|||
#include <cmath>
|
|||
|
|||
#include "src/adapters/CarlAdapter.h"
|
|||
#include "src/models/sparse/MarkovAutomaton.h"
|
|||
#include "src/models/sparse/StandardRewardModel.h"
|
|||
#include "src/transformer/EndComponentEliminator.h"
|
|||
#include "src/utility/macros.h"
|
|||
#include "src/utility/vector.h"
|
|||
|
|||
#include "src/exceptions/InvalidOperationException.h"
|
|||
|
|||
namespace storm { |
|||
namespace modelchecker { |
|||
namespace multiobjective { |
|||
|
|||
template <class SparseMaModelType> |
|||
SparseMaPcaaWeightVectorChecker<SparseMaModelType>::SparseMaPcaaWeightVectorChecker(SparseMaModelType const& model, |
|||
std::vector<PcaaObjective<ValueType>> const& objectives, |
|||
storm::storage::BitVector const& actionsWithNegativeReward, |
|||
storm::storage::BitVector const& ecActions, |
|||
storm::storage::BitVector const& possiblyRecurrentStates) : |
|||
SparsePcaaWeightVectorChecker<SparseMaModelType>(model, objectives, actionsWithNegativeReward, ecActions, possiblyRecurrentStates) { |
|||
// Set the (discretized) state action rewards.
|
|||
this->discreteActionRewards.resize(objectives.size()); |
|||
for(auto objIndex : this->objectivesWithNoUpperTimeBound) { |
|||
typename SparseMaModelType::RewardModelType const& rewModel = this->model.getRewardModel(this->objectives[objIndex].rewardModelName); |
|||
STORM_LOG_ASSERT(!rewModel.hasTransitionRewards(), "Preprocessed Reward model has transition rewards which is not expected."); |
|||
this->discreteActionRewards[objIndex] = rewModel.hasStateActionRewards() ? rewModel.getStateActionRewardVector() : std::vector<ValueType>(this->model.getTransitionMatrix().getRowCount(), 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->model.getMarkovianStates()) { |
|||
this->discreteActionRewards[objIndex][this->model.getTransitionMatrix().getRowGroupIndices()[markovianState]] += rewModel.getStateReward(markovianState) / this->model.getExitRate(markovianState); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
template <class SparseMaModelType> |
|||
void SparseMaPcaaWeightVectorChecker<SparseMaModelType>::boundedPhase(std::vector<ValueType> const& weightVector, std::vector<ValueType>& weightedRewardVector) { |
|||
|
|||
// Split the preprocessed model into transitions from/to probabilistic/Markovian states.
|
|||
SubModel MS = createSubModel(true, weightedRewardVector); |
|||
SubModel PS = createSubModel(false, weightedRewardVector); |
|||
|
|||
// Apply digitization to Markovian transitions
|
|||
ValueType digitizationConstant = getDigitizationConstant(); |
|||
digitize(MS, digitizationConstant); |
|||
|
|||
// Get for each occurring (digitized) timeBound the indices of the objectives with that bound.
|
|||
TimeBoundMap lowerTimeBounds; |
|||
TimeBoundMap upperTimeBounds; |
|||
digitizeTimeBounds(lowerTimeBounds, upperTimeBounds, digitizationConstant); |
|||
|
|||
// Initialize a minMaxSolver to compute an optimal scheduler (w.r.t. PS) for each epoch
|
|||
// No EC elimination is necessary as we assume non-zenoness
|
|||
std::unique_ptr<MinMaxSolverData> minMax = initMinMaxSolver(PS); |
|||
|
|||
// create a linear equation solver for the model induced by the optimal choice vector.
|
|||
// the solver will be updated whenever the optimal choice vector has changed.
|
|||
std::unique_ptr<LinEqSolverData> linEq = initLinEqSolver(PS); |
|||
|
|||
// Store the optimal choices of PS as computed by the minMax solver.
|
|||
std::vector<uint_fast64_t> optimalChoicesAtCurrentEpoch(PS.getNumberOfStates(), std::numeric_limits<uint_fast64_t>::max()); |
|||
|
|||
// Stores the objectives for which we need to compute values in the current time epoch.
|
|||
storm::storage::BitVector consideredObjectives = this->objectivesWithNoUpperTimeBound; |
|||
|
|||
auto lowerTimeBoundIt = lowerTimeBounds.begin(); |
|||
auto upperTimeBoundIt = upperTimeBounds.begin(); |
|||
uint_fast64_t currentEpoch = std::max(lowerTimeBounds.empty() ? 0 : lowerTimeBoundIt->first, upperTimeBounds.empty() ? 0 : upperTimeBoundIt->first); |
|||
while(true) { |
|||
// Update the objectives that are considered at the current time epoch as well as the (weighted) reward vectors.
|
|||
updateDataToCurrentEpoch(MS, PS, *minMax, consideredObjectives, currentEpoch, weightVector, lowerTimeBoundIt, lowerTimeBounds, upperTimeBoundIt, upperTimeBounds); |
|||
|
|||
// Compute the values that can be obtained at probabilistic states in the current time epoch
|
|||
performPSStep(PS, MS, *minMax, *linEq, optimalChoicesAtCurrentEpoch, consideredObjectives); |
|||
|
|||
// Compute values that can be obtained at Markovian states after letting one (digitized) time unit pass.
|
|||
// Only perform such a step if there is time left.
|
|||
if(currentEpoch>0) { |
|||
performMSStep(MS, PS, consideredObjectives); |
|||
--currentEpoch; |
|||
} else { |
|||
break; |
|||
} |
|||
} |
|||
|
|||
// compose the results from MS and PS
|
|||
storm::utility::vector::setVectorValues(this->weightedResult, MS.states, MS.weightedSolutionVector); |
|||
storm::utility::vector::setVectorValues(this->weightedResult, PS.states, PS.weightedSolutionVector); |
|||
for(uint_fast64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) { |
|||
storm::utility::vector::setVectorValues(this->objectiveResults[objIndex], MS.states, MS.objectiveSolutionVectors[objIndex]); |
|||
storm::utility::vector::setVectorValues(this->objectiveResults[objIndex], PS.states, PS.objectiveSolutionVectors[objIndex]); |
|||
} |
|||
} |
|||
|
|||
|
|||
template <class SparseMaModelType> |
|||
typename SparseMaPcaaWeightVectorChecker<SparseMaModelType>::SubModel SparseMaPcaaWeightVectorChecker<SparseMaModelType>::createSubModel(bool createMS, std::vector<ValueType> const& weightedRewardVector) const { |
|||
SubModel result; |
|||
|
|||
storm::storage::BitVector probabilisticStates = ~this->model.getMarkovianStates(); |
|||
result.states = createMS ? this->model.getMarkovianStates() : probabilisticStates; |
|||
result.choices = this->model.getTransitionMatrix().getRowIndicesOfRowGroups(result.states); |
|||
STORM_LOG_ASSERT(!createMS || result.states.getNumberOfSetBits() == result.choices.getNumberOfSetBits(), "row groups for Markovian states should consist of exactly one row"); |
|||
|
|||
//We need to add diagonal entries for selfloops on Markovian states.
|
|||
result.toMS = this->model.getTransitionMatrix().getSubmatrix(true, result.states, this->model.getMarkovianStates(), createMS); |
|||
result.toPS = this->model.getTransitionMatrix().getSubmatrix(true, result.states, probabilisticStates, false); |
|||
STORM_LOG_ASSERT(result.getNumberOfStates() == result.states.getNumberOfSetBits() && result.getNumberOfStates() == result.toMS.getRowGroupCount() && result.getNumberOfStates() == result.toPS.getRowGroupCount(), "Invalid state count for subsystem"); |
|||
STORM_LOG_ASSERT(result.getNumberOfChoices() == result.choices.getNumberOfSetBits() && result.getNumberOfChoices() == result.toMS.getRowCount() && result.getNumberOfChoices() == result.toPS.getRowCount(), "Invalid state count for subsystem"); |
|||
|
|||
result.weightedRewardVector.resize(result.getNumberOfChoices()); |
|||
storm::utility::vector::selectVectorValues(result.weightedRewardVector, result.choices, weightedRewardVector); |
|||
result.objectiveRewardVectors.resize(this->objectives.size()); |
|||
for(uint_fast64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) { |
|||
std::vector<ValueType>& objVector = result.objectiveRewardVectors[objIndex]; |
|||
objVector = std::vector<ValueType>(result.weightedRewardVector.size(), storm::utility::zero<ValueType>()); |
|||
if(this->objectivesWithNoUpperTimeBound.get(objIndex)) { |
|||
storm::utility::vector::selectVectorValues(objVector, result.choices, this->discreteActionRewards[objIndex]); |
|||
} else { |
|||
typename SparseMaModelType::RewardModelType const& rewModel = this->model.getRewardModel(this->objectives[objIndex].rewardModelName); |
|||
STORM_LOG_ASSERT(!rewModel.hasTransitionRewards(), "Preprocessed Reward model has transition rewards which is not expected."); |
|||
STORM_LOG_ASSERT(!rewModel.hasStateRewards(), "State rewards for bounded objectives for MAs are not expected (bounded rewards are not supported)."); |
|||
if(rewModel.hasStateActionRewards()) { |
|||
storm::utility::vector::selectVectorValues(objVector, result.choices, rewModel.getStateActionRewardVector()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
result.weightedSolutionVector.resize(result.getNumberOfStates()); |
|||
storm::utility::vector::selectVectorValues(result.weightedSolutionVector, result.states, this->weightedResult); |
|||
result.objectiveSolutionVectors.resize(this->objectives.size()); |
|||
for(uint_fast64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) { |
|||
result.objectiveSolutionVectors[objIndex].resize(result.weightedSolutionVector.size()); |
|||
storm::utility::vector::selectVectorValues(result.objectiveSolutionVectors[objIndex], result.states, this->objectiveResults[objIndex]); |
|||
} |
|||
|
|||
result.auxChoiceValues.resize(result.getNumberOfChoices()); |
|||
|
|||
return result; |
|||
} |
|||
|
|||
template <class SparseMaModelType> |
|||
template <typename VT, typename std::enable_if<storm::NumberTraits<VT>::SupportsExponential, int>::type> |
|||
VT SparseMaPcaaWeightVectorChecker<SparseMaModelType>::getDigitizationConstant() const { |
|||
STORM_LOG_DEBUG("Retrieving digitization constant"); |
|||
// We need to find a delta such that for each objective it holds that lowerbound/delta , upperbound/delta are natural numbers and
|
|||
// If there is a lower and an upper bound:
|
|||
// 1 - e^(-maxRate lowerbound) * (1 + maxRate delta) ^ (lowerbound / delta) + 1-e^(-maxRate upperbound) * (1 + maxRate delta) ^ (upperbound / delta) + (1-e^(-maxRate delta) <= maximumLowerUpperBoundGap
|
|||
// If there is only an upper bound:
|
|||
// 1-e^(-maxRate upperbound) * (1 + maxRate delta) ^ (upperbound / delta) <= maximumLowerUpperBoundGap
|
|||
|
|||
// Initialize some data for fast and easy access
|
|||
VT const maxRate = this->model.getMaximalExitRate(); |
|||
std::vector<std::pair<VT, VT>> eToPowerOfMinusMaxRateTimesBound; |
|||
VT smallestNonZeroBound = storm::utility::zero<VT>(); |
|||
for(auto const& obj : this->objectives) { |
|||
eToPowerOfMinusMaxRateTimesBound.emplace_back(); |
|||
if(obj.lowerTimeBound){ |
|||
STORM_LOG_ASSERT(!storm::utility::isZero(*obj.lowerTimeBound), "Got zero-valued lower bound."); // should have been handled in preprocessing
|
|||
STORM_LOG_ASSERT(!obj.upperTimeBound || *obj.lowerTimeBound < *obj.upperTimeBound, "Got point intervall or empty intervall on time bounded property which is not supported"); // should also have been handled in preprocessing
|
|||
eToPowerOfMinusMaxRateTimesBound.back().first = std::exp(-maxRate * (*obj.lowerTimeBound)); |
|||
smallestNonZeroBound = storm::utility::isZero(smallestNonZeroBound) ? *obj.lowerTimeBound : std::min(smallestNonZeroBound, *obj.lowerTimeBound); |
|||
} |
|||
if(obj.upperTimeBound){ |
|||
STORM_LOG_ASSERT(!storm::utility::isZero(*obj.upperTimeBound), "Got zero-valued upper bound."); // should have been handled in preprocessing
|
|||
eToPowerOfMinusMaxRateTimesBound.back().second = std::exp(-maxRate * (*obj.upperTimeBound)); |
|||
smallestNonZeroBound = storm::utility::isZero(smallestNonZeroBound) ? *obj.upperTimeBound : std::min(smallestNonZeroBound, *obj.upperTimeBound); |
|||
} |
|||
} |
|||
if(storm::utility::isZero(smallestNonZeroBound)) { |
|||
// There are no time bounds. In this case, one is a valid digitization constant.
|
|||
return storm::utility::one<VT>(); |
|||
} |
|||
|
|||
// We brute-force a delta, since a direct computation is apparently not easy.
|
|||
// Also note that the number of times this loop runs is a lower bound for the number of minMaxSolver invocations.
|
|||
// Hence, this brute-force approach will most likely not be a bottleneck.
|
|||
uint_fast64_t smallestStepBound = 1; |
|||
VT delta = smallestNonZeroBound / smallestStepBound; |
|||
while(true) { |
|||
bool deltaValid = true; |
|||
for(auto const& obj : this->objectives) { |
|||
if((obj.lowerTimeBound && *obj.lowerTimeBound/delta != std::floor(*obj.lowerTimeBound/delta)) || |
|||
(obj.upperTimeBound && *obj.upperTimeBound/delta != std::floor(*obj.upperTimeBound/delta))) { |
|||
deltaValid = false; |
|||
break; |
|||
} |
|||
} |
|||
if(deltaValid) { |
|||
for(uint_fast64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) { |
|||
auto const& obj = this->objectives[objIndex]; |
|||
VT precisionOfObj = storm::utility::zero<VT>(); |
|||
if(obj.lowerTimeBound) { |
|||
precisionOfObj += storm::utility::one<VT>() - (eToPowerOfMinusMaxRateTimesBound[objIndex].first * storm::utility::pow(storm::utility::one<VT>() + maxRate * delta, *obj.lowerTimeBound / delta) ) |
|||
+ storm::utility::one<VT>() - std::exp(-maxRate * delta); |
|||
} |
|||
if(obj.upperTimeBound) { |
|||
precisionOfObj += storm::utility::one<VT>() - (eToPowerOfMinusMaxRateTimesBound[objIndex].second * storm::utility::pow(storm::utility::one<VT>() + maxRate * delta, *obj.upperTimeBound / delta) ); |
|||
} |
|||
if(precisionOfObj > this->maximumLowerUpperBoundGap) { |
|||
deltaValid = false; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
if(deltaValid) { |
|||
break; |
|||
} |
|||
++smallestStepBound; |
|||
STORM_LOG_ASSERT(delta>smallestNonZeroBound / smallestStepBound, "Digitization constant is expected to become smaller in every iteration"); |
|||
delta = smallestNonZeroBound / smallestStepBound; |
|||
} |
|||
STORM_LOG_DEBUG("Found digitization constant: " << delta << ". At least " << smallestStepBound << " digitization steps will be necessarry"); |
|||
return delta; |
|||
} |
|||
|
|||
template <class SparseMaModelType> |
|||
template <typename VT, typename std::enable_if<!storm::NumberTraits<VT>::SupportsExponential, int>::type> |
|||
VT SparseMaPcaaWeightVectorChecker<SparseMaModelType>::getDigitizationConstant() const { |
|||
STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Computing bounded probabilities of MAs is unsupported for this value type."); |
|||
} |
|||
|
|||
template <class SparseMaModelType> |
|||
template <typename VT, typename std::enable_if<storm::NumberTraits<VT>::SupportsExponential, int>::type> |
|||
void SparseMaPcaaWeightVectorChecker<SparseMaModelType>::digitize(SubModel& MS, VT const& digitizationConstant) const { |
|||
std::vector<VT> rateVector(MS.getNumberOfChoices()); |
|||
storm::utility::vector::selectVectorValues(rateVector, MS.states, this->model.getExitRates()); |
|||
for(uint_fast64_t row = 0; row < rateVector.size(); ++row) { |
|||
VT const eToMinusRateTimesDelta = std::exp(-rateVector[row] * digitizationConstant); |
|||
for(auto& entry : MS.toMS.getRow(row)) { |
|||
entry.setValue((storm::utility::one<VT>() - eToMinusRateTimesDelta) * entry.getValue()); |
|||
if(entry.getColumn() == row) { |
|||
entry.setValue(entry.getValue() + eToMinusRateTimesDelta); |
|||
} |
|||
} |
|||
for(auto& entry : MS.toPS.getRow(row)) { |
|||
entry.setValue((storm::utility::one<VT>() - eToMinusRateTimesDelta) * entry.getValue()); |
|||
} |
|||
MS.weightedRewardVector[row] *= storm::utility::one<VT>() - eToMinusRateTimesDelta; |
|||
for(auto& objVector : MS.objectiveRewardVectors) { |
|||
objVector[row] *= storm::utility::one<VT>() - eToMinusRateTimesDelta; |
|||
} |
|||
} |
|||
} |
|||
|
|||
template <class SparseMaModelType> |
|||
template <typename VT, typename std::enable_if<!storm::NumberTraits<VT>::SupportsExponential, int>::type> |
|||
void SparseMaPcaaWeightVectorChecker<SparseMaModelType>::digitize(SubModel& subModel, VT const& digitizationConstant) const { |
|||
STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Computing bounded probabilities of MAs is unsupported for this value type."); |
|||
} |
|||
|
|||
template <class SparseMaModelType> |
|||
template <typename VT, typename std::enable_if<storm::NumberTraits<VT>::SupportsExponential, int>::type> |
|||
void SparseMaPcaaWeightVectorChecker<SparseMaModelType>::digitizeTimeBounds(TimeBoundMap& lowerTimeBounds, TimeBoundMap& upperTimeBounds, VT const& digitizationConstant) { |
|||
|
|||
VT const maxRate = this->model.getMaximalExitRate(); |
|||
for(uint_fast64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) { |
|||
auto const& obj = this->objectives[objIndex]; |
|||
VT errorTowardsZero; |
|||
VT errorAwayFromZero; |
|||
if(obj.lowerTimeBound) { |
|||
uint_fast64_t digitizedBound = storm::utility::convertNumber<uint_fast64_t>((*obj.lowerTimeBound)/digitizationConstant); |
|||
auto timeBoundIt = lowerTimeBounds.insert(std::make_pair(digitizedBound, storm::storage::BitVector(this->objectives.size(), false))).first; |
|||
timeBoundIt->second.set(objIndex); |
|||
VT digitizationError = storm::utility::one<VT>(); |
|||
digitizationError -= std::exp(-maxRate * (*obj.lowerTimeBound)) * storm::utility::pow(storm::utility::one<VT>() + maxRate * digitizationConstant, digitizedBound); |
|||
errorTowardsZero = -digitizationError; |
|||
errorAwayFromZero = storm::utility::one<VT>() - std::exp(-maxRate * digitizationConstant);; |
|||
} else { |
|||
errorTowardsZero = storm::utility::zero<VT>(); |
|||
errorAwayFromZero = storm::utility::zero<VT>(); |
|||
} |
|||
if(obj.upperTimeBound) { |
|||
uint_fast64_t digitizedBound = storm::utility::convertNumber<uint_fast64_t>((*obj.upperTimeBound)/digitizationConstant); |
|||
auto timeBoundIt = upperTimeBounds.insert(std::make_pair(digitizedBound, storm::storage::BitVector(this->objectives.size(), false))).first; |
|||
timeBoundIt->second.set(objIndex); |
|||
VT digitizationError = storm::utility::one<VT>(); |
|||
digitizationError -= std::exp(-maxRate * (*obj.upperTimeBound)) * storm::utility::pow(storm::utility::one<VT>() + maxRate * digitizationConstant, digitizedBound); |
|||
errorAwayFromZero += digitizationError; |
|||
} |
|||
STORM_LOG_ASSERT(errorTowardsZero + errorAwayFromZero <= this->maximumLowerUpperBoundGap, "Precision not sufficient."); |
|||
if (obj.rewardsArePositive) { |
|||
this->offsetsToLowerBound[objIndex] = -errorTowardsZero; |
|||
this->offsetsToUpperBound[objIndex] = errorAwayFromZero; |
|||
} else { |
|||
this->offsetsToLowerBound[objIndex] = -errorAwayFromZero; |
|||
this->offsetsToUpperBound[objIndex] = errorTowardsZero; |
|||
} |
|||
} |
|||
} |
|||
|
|||
template <class SparseMaModelType> |
|||
template <typename VT, typename std::enable_if<!storm::NumberTraits<VT>::SupportsExponential, int>::type> |
|||
void SparseMaPcaaWeightVectorChecker<SparseMaModelType>::digitizeTimeBounds(TimeBoundMap& lowerTimeBounds, TimeBoundMap& upperTimeBounds, VT const& digitizationConstant) { |
|||
STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Computing bounded probabilities of MAs is unsupported for this value type."); |
|||
} |
|||
|
|||
template <class SparseMaModelType> |
|||
std::unique_ptr<typename SparseMaPcaaWeightVectorChecker<SparseMaModelType>::MinMaxSolverData> SparseMaPcaaWeightVectorChecker<SparseMaModelType>::initMinMaxSolver(SubModel const& PS) const { |
|||
std::unique_ptr<MinMaxSolverData> result(new MinMaxSolverData()); |
|||
storm::solver::GeneralMinMaxLinearEquationSolverFactory<ValueType> minMaxSolverFactory; |
|||
result->solver = minMaxSolverFactory.create(PS.toPS); |
|||
result->solver->setOptimizationDirection(storm::solver::OptimizationDirection::Maximize); |
|||
result->solver->setTrackScheduler(true); |
|||
result->solver->allocateAuxMemory(storm::solver::MinMaxLinearEquationSolverOperation::SolveEquations); |
|||
|
|||
result->b.resize(PS.getNumberOfChoices()); |
|||
|
|||
return result; |
|||
} |
|||
|
|||
template <class SparseMaModelType> |
|||
std::unique_ptr<typename SparseMaPcaaWeightVectorChecker<SparseMaModelType>::LinEqSolverData> SparseMaPcaaWeightVectorChecker<SparseMaModelType>::initLinEqSolver(SubModel const& PS) const { |
|||
std::unique_ptr<LinEqSolverData> result(new LinEqSolverData()); |
|||
// We choose Jacobi since we call the solver very frequently on 'easy' inputs (note that jacobi without preconditioning has very little overhead).
|
|||
result->factory.getSettings().setSolutionMethod(storm::solver::GmmxxLinearEquationSolverSettings<ValueType>::SolutionMethod::Jacobi); |
|||
result->factory.getSettings().setPreconditioner(storm::solver::GmmxxLinearEquationSolverSettings<ValueType>::Preconditioner::None); |
|||
result->b.resize(PS.getNumberOfStates()); |
|||
return result; |
|||
} |
|||
|
|||
template <class SparseMaModelType> |
|||
void SparseMaPcaaWeightVectorChecker<SparseMaModelType>::updateDataToCurrentEpoch(SubModel& MS, SubModel& PS, MinMaxSolverData& minMax, storm::storage::BitVector& consideredObjectives, uint_fast64_t const& currentEpoch, std::vector<ValueType> const& weightVector, TimeBoundMap::iterator& lowerTimeBoundIt, TimeBoundMap const& lowerTimeBounds, TimeBoundMap::iterator& upperTimeBoundIt, TimeBoundMap const& upperTimeBounds) { |
|||
|
|||
//Note that lower time bounds are always strict. Hence, we need to react when the current epoch equals the stored bound.
|
|||
if(lowerTimeBoundIt != lowerTimeBounds.end() && currentEpoch == lowerTimeBoundIt->first) { |
|||
for(auto objIndex : lowerTimeBoundIt->second) { |
|||
// No more reward is earned for this objective.
|
|||
storm::utility::vector::addScaledVector(MS.weightedRewardVector, MS.objectiveRewardVectors[objIndex], -weightVector[objIndex]); |
|||
storm::utility::vector::addScaledVector(PS.weightedRewardVector, PS.objectiveRewardVectors[objIndex], -weightVector[objIndex]); |
|||
MS.objectiveRewardVectors[objIndex] = std::vector<ValueType>(MS.objectiveRewardVectors[objIndex].size(), storm::utility::zero<ValueType>()); |
|||
PS.objectiveRewardVectors[objIndex] = std::vector<ValueType>(PS.objectiveRewardVectors[objIndex].size(), storm::utility::zero<ValueType>()); |
|||
} |
|||
++lowerTimeBoundIt; |
|||
} |
|||
|
|||
if(upperTimeBoundIt != upperTimeBounds.end() && currentEpoch == upperTimeBoundIt->first) { |
|||
consideredObjectives |= upperTimeBoundIt->second; |
|||
for(auto objIndex : upperTimeBoundIt->second) { |
|||
// This objective now plays a role in the weighted sum
|
|||
storm::utility::vector::addScaledVector(MS.weightedRewardVector, MS.objectiveRewardVectors[objIndex], weightVector[objIndex]); |
|||
storm::utility::vector::addScaledVector(PS.weightedRewardVector, PS.objectiveRewardVectors[objIndex], weightVector[objIndex]); |
|||
} |
|||
++upperTimeBoundIt; |
|||
} |
|||
|
|||
// Update the solver data
|
|||
PS.toMS.multiplyWithVector(MS.weightedSolutionVector, minMax.b); |
|||
storm::utility::vector::addVectors(minMax.b, PS.weightedRewardVector, minMax.b); |
|||
} |
|||
|
|||
template <class SparseMaModelType> |
|||
void SparseMaPcaaWeightVectorChecker<SparseMaModelType>::performPSStep(SubModel& PS, SubModel const& MS, MinMaxSolverData& minMax, LinEqSolverData& linEq, std::vector<uint_fast64_t>& optimalChoicesAtCurrentEpoch, storm::storage::BitVector const& consideredObjectives) const { |
|||
// compute a choice vector for the probabilistic states that is optimal w.r.t. the weighted reward vector
|
|||
minMax.solver->solveEquations(PS.weightedSolutionVector, minMax.b); |
|||
auto newScheduler = minMax.solver->getScheduler(); |
|||
// check whether the linEqSolver needs to be updated, i.e., whether the scheduler has changed
|
|||
if(linEq.solver == nullptr || newScheduler->getChoices() != optimalChoicesAtCurrentEpoch) { |
|||
optimalChoicesAtCurrentEpoch = newScheduler->getChoices(); |
|||
linEq.solver = nullptr; |
|||
storm::storage::SparseMatrix<ValueType> linEqMatrix = PS.toPS.selectRowsFromRowGroups(optimalChoicesAtCurrentEpoch, true); |
|||
linEqMatrix.convertToEquationSystem(); |
|||
linEq.solver = linEq.factory.create(std::move(linEqMatrix)); |
|||
linEq.solver->allocateAuxMemory(storm::solver::LinearEquationSolverOperation::SolveEquations); |
|||
} |
|||
|
|||
// Get the results for the individual objectives.
|
|||
// Note that we do not consider an estimate for each objective (as done in the unbounded phase) since the results from the previous epoch are already pretty close
|
|||
for(auto objIndex : consideredObjectives) { |
|||
auto const& objectiveRewardVectorPS = PS.objectiveRewardVectors[objIndex]; |
|||
auto const& objectiveSolutionVectorMS = MS.objectiveSolutionVectors[objIndex]; |
|||
// compute rhs of equation system, i.e., PS.toMS * x + Rewards
|
|||
// To safe some time, only do this for the obtained optimal choices
|
|||
auto itGroupIndex = PS.toPS.getRowGroupIndices().begin(); |
|||
auto itChoiceOffset = optimalChoicesAtCurrentEpoch.begin(); |
|||
for(auto& bValue : linEq.b) { |
|||
uint_fast64_t row = (*itGroupIndex) + (*itChoiceOffset); |
|||
bValue = objectiveRewardVectorPS[row]; |
|||
for(auto const& entry : PS.toMS.getRow(row)){ |
|||
bValue += entry.getValue() * objectiveSolutionVectorMS[entry.getColumn()]; |
|||
} |
|||
++itGroupIndex; |
|||
++itChoiceOffset; |
|||
} |
|||
linEq.solver->solveEquations(PS.objectiveSolutionVectors[objIndex], linEq.b); |
|||
} |
|||
} |
|||
|
|||
template <class SparseMaModelType> |
|||
void SparseMaPcaaWeightVectorChecker<SparseMaModelType>::performMSStep(SubModel& MS, SubModel const& PS, storm::storage::BitVector const& consideredObjectives) const { |
|||
|
|||
MS.toMS.multiplyWithVector(MS.weightedSolutionVector, MS.auxChoiceValues); |
|||
storm::utility::vector::addVectors(MS.weightedRewardVector, MS.auxChoiceValues, MS.weightedSolutionVector); |
|||
MS.toPS.multiplyWithVector(PS.weightedSolutionVector, MS.auxChoiceValues); |
|||
storm::utility::vector::addVectors(MS.weightedSolutionVector, MS.auxChoiceValues, MS.weightedSolutionVector); |
|||
|
|||
for(auto objIndex : consideredObjectives) { |
|||
MS.toMS.multiplyWithVector(MS.objectiveSolutionVectors[objIndex], MS.auxChoiceValues); |
|||
storm::utility::vector::addVectors(MS.objectiveRewardVectors[objIndex], MS.auxChoiceValues, MS.objectiveSolutionVectors[objIndex]); |
|||
MS.toPS.multiplyWithVector(PS.objectiveSolutionVectors[objIndex], MS.auxChoiceValues); |
|||
storm::utility::vector::addVectors(MS.objectiveSolutionVectors[objIndex], MS.auxChoiceValues, MS.objectiveSolutionVectors[objIndex]); |
|||
} |
|||
} |
|||
|
|||
|
|||
template class SparseMaPcaaWeightVectorChecker<storm::models::sparse::MarkovAutomaton<double>>; |
|||
template double SparseMaPcaaWeightVectorChecker<storm::models::sparse::MarkovAutomaton<double>>::getDigitizationConstant<double>() const; |
|||
template void SparseMaPcaaWeightVectorChecker<storm::models::sparse::MarkovAutomaton<double>>::digitize<double>(SparseMaPcaaWeightVectorChecker<storm::models::sparse::MarkovAutomaton<double>>::SubModel& subModel, double const& digitizationConstant) const; |
|||
template void SparseMaPcaaWeightVectorChecker<storm::models::sparse::MarkovAutomaton<double>>::digitizeTimeBounds<double>(SparseMaPcaaWeightVectorChecker<storm::models::sparse::MarkovAutomaton<double>>::TimeBoundMap& lowerTimeBounds, SparseMaPcaaWeightVectorChecker<storm::models::sparse::MarkovAutomaton<double>>::TimeBoundMap& upperTimeBounds, double const& digitizationConstant); |
|||
#ifdef STORM_HAVE_CARL
|
|||
// template class SparseMaPcaaWeightVectorChecker<storm::models::sparse::MarkovAutomaton<storm::RationalNumber>>;
|
|||
// template storm::RationalNumber SparseMaPcaaWeightVectorChecker<storm::models::sparse::MarkovAutomaton<storm::RationalNumber>>::getDigitizationConstant<storm::RationalNumber>() const;
|
|||
// template void SparseMaPcaaWeightVectorChecker<storm::models::sparse::MarkovAutomaton<storm::RationalNumber>>::digitize<storm::RationalNumber>(SparseMaPcaaWeightVectorChecker<storm::models::sparse::MarkovAutomaton<storm::RationalNumber>>::SubModel& subModel, storm::RationalNumber const& digitizationConstant) const;
|
|||
// template void SparseMaPcaaWeightVectorChecker<storm::models::sparse::MarkovAutomaton<storm::RationalNumber>>::digitizeTimeBounds<storm::RationalNumber>(SparseMaPcaaWeightVectorChecker<storm::models::sparse::MarkovAutomaton<double>>::TimeBoundMap& lowerTimeBounds, SparseMaPcaaWeightVectorChecker<storm::models::sparse::MarkovAutomaton<double>>::TimeBoundMap& upperTimeBounds, storm::RationalNumber const& digitizationConstant);
|
|||
#endif
|
|||
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,157 @@ |
|||
#ifndef STORM_MODELCHECKER_MULTIOBJECTIVE_PCAA_SPARSEMAPCAAWEIGHTVECTORCHECKER_H_ |
|||
#define STORM_MODELCHECKER_MULTIOBJECTIVE_PCAA_SPARSEMAPCAAWEIGHTVECTORCHECKER_H_ |
|||
|
|||
#include <vector> |
|||
#include <type_traits> |
|||
|
|||
#include "src/modelchecker/multiobjective/pcaa/SparsePcaaWeightVectorChecker.h" |
|||
#include "src/solver/LinearEquationSolver.h" |
|||
#include "src/solver/GmmxxLinearEquationSolver.h" |
|||
#include "src/solver/MinMaxLinearEquationSolver.h" |
|||
#include "src/utility/NumberTraits.h" |
|||
|
|||
namespace storm { |
|||
namespace modelchecker { |
|||
namespace multiobjective { |
|||
|
|||
/*! |
|||
* Helper Class that takes preprocessed Pcaa data 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 SparseMaModelType> |
|||
class SparseMaPcaaWeightVectorChecker : public SparsePcaaWeightVectorChecker<SparseMaModelType> { |
|||
public: |
|||
typedef typename SparseMaModelType::ValueType ValueType; |
|||
|
|||
SparseMaPcaaWeightVectorChecker(SparseMaModelType const& model, |
|||
std::vector<PcaaObjective<ValueType>> const& objectives, |
|||
storm::storage::BitVector const& actionsWithNegativeReward, |
|||
storm::storage::BitVector const& ecActions, |
|||
storm::storage::BitVector const& possiblyRecurrentStates); |
|||
|
|||
private: |
|||
|
|||
/* |
|||
* Stores (digitized) time bounds in descending order |
|||
*/ |
|||
typedef std::map<uint_fast64_t, storm::storage::BitVector, std::greater<uint_fast64_t>> TimeBoundMap; |
|||
|
|||
/* |
|||
* Stores the ingredients of a sub model |
|||
*/ |
|||
struct SubModel { |
|||
storm::storage::BitVector states; // The states that are part of this sub model |
|||
storm::storage::BitVector choices; // The choices that are part of this sub model |
|||
|
|||
storm::storage::SparseMatrix<ValueType> toMS; // Transitions to Markovian states |
|||
storm::storage::SparseMatrix<ValueType> toPS; // Transitions to probabilistic states |
|||
|
|||
std::vector<ValueType> weightedRewardVector; |
|||
std::vector<std::vector<ValueType>> objectiveRewardVectors; |
|||
|
|||
std::vector<ValueType> weightedSolutionVector; |
|||
std::vector<std::vector<ValueType>> objectiveSolutionVectors; |
|||
|
|||
std::vector<ValueType> auxChoiceValues; //stores auxiliary values for every choice |
|||
|
|||
uint_fast64_t getNumberOfStates() const { return toMS.getRowGroupCount(); }; |
|||
uint_fast64_t getNumberOfChoices() const { return toMS.getRowCount(); }; |
|||
}; |
|||
|
|||
/* |
|||
* Stores the data that is relevant to invoke the minMaxSolver and retrieve the result. |
|||
*/ |
|||
struct MinMaxSolverData { |
|||
std::unique_ptr<storm::solver::MinMaxLinearEquationSolver<ValueType>> solver; |
|||
std::vector<ValueType> b; |
|||
}; |
|||
|
|||
struct LinEqSolverData { |
|||
storm::solver::GmmxxLinearEquationSolverFactory<ValueType> factory; |
|||
std::unique_ptr<storm::solver::LinearEquationSolver<ValueType>> solver; |
|||
std::vector<ValueType> b; |
|||
}; |
|||
|
|||
/*! |
|||
* |
|||
* @param weightVector the weight vector of the current check |
|||
* @param weightedRewardVector the weighted rewards considering the unbounded objectives. Will be invalidated after calling this. |
|||
*/ |
|||
virtual void boundedPhase(std::vector<ValueType> const& weightVector, std::vector<ValueType>& weightedRewardVector) override; |
|||
|
|||
/*! |
|||
* Retrieves the data for a submodel of the data->preprocessedModel |
|||
* @param createMS if true, the submodel containing the Markovian states is created. |
|||
* if false, the submodel containing the probabilistic states is created. |
|||
*/ |
|||
SubModel createSubModel(bool createMS, std::vector<ValueType> const& weightedRewardVector) const; |
|||
|
|||
/*! |
|||
* Retrieves the delta used for digitization |
|||
*/ |
|||
template <typename VT = ValueType, typename std::enable_if<storm::NumberTraits<VT>::SupportsExponential, int>::type = 0> |
|||
VT getDigitizationConstant() const; |
|||
template <typename VT = ValueType, typename std::enable_if<!storm::NumberTraits<VT>::SupportsExponential, int>::type = 0> |
|||
VT getDigitizationConstant() const; |
|||
|
|||
/*! |
|||
* Digitizes the given matrix and vectors w.r.t. the given digitization constant and the given rate vector. |
|||
*/ |
|||
template <typename VT = ValueType, typename std::enable_if<storm::NumberTraits<VT>::SupportsExponential, int>::type = 0> |
|||
void digitize(SubModel& subModel, VT const& digitizationConstant) const; |
|||
template <typename VT = ValueType, typename std::enable_if<!storm::NumberTraits<VT>::SupportsExponential, int>::type = 0> |
|||
void digitize(SubModel& subModel, VT const& digitizationConstant) const; |
|||
|
|||
/* |
|||
* Fills the given maps with the digitized time bounds. Also sets the offsetsToLowerBound / offsetsToUpperBound values |
|||
* according to the digitization error |
|||
*/ |
|||
template <typename VT = ValueType, typename std::enable_if<storm::NumberTraits<VT>::SupportsExponential, int>::type = 0> |
|||
void digitizeTimeBounds(TimeBoundMap& lowerTimeBounds, TimeBoundMap& upperTimeBounds, VT const& digitizationConstant); |
|||
template <typename VT = ValueType, typename std::enable_if<!storm::NumberTraits<VT>::SupportsExponential, int>::type = 0> |
|||
void digitizeTimeBounds(TimeBoundMap& lowerTimeBounds, TimeBoundMap& upperTimeBounds, VT const& digitizationConstant); |
|||
|
|||
|
|||
/*! |
|||
* Initializes the data for the MinMax solver |
|||
*/ |
|||
std::unique_ptr<MinMaxSolverData> initMinMaxSolver(SubModel const& PS) const; |
|||
|
|||
/*! |
|||
* Initializes the data for the LinEq solver |
|||
*/ |
|||
std::unique_ptr<LinEqSolverData> initLinEqSolver(SubModel const& PS) const; |
|||
|
|||
/* |
|||
* Updates the reward vectors within the split model, |
|||
* the reward vector of the reduced PStoPS model, and |
|||
* objectives that are considered at the current time epoch. |
|||
*/ |
|||
void updateDataToCurrentEpoch(SubModel& MS, SubModel& PS, MinMaxSolverData& minMax, storm::storage::BitVector& consideredObjectives, uint_fast64_t const& currentEpoch, std::vector<ValueType> const& weightVector, TimeBoundMap::iterator& lowerTimeBoundIt, TimeBoundMap const& lowerTimeBounds, TimeBoundMap::iterator& upperTimeBoundIt, TimeBoundMap const& upperTimeBounds); |
|||
|
|||
/* |
|||
* Performs a step for the probabilistic states, that is |
|||
* * Compute an optimal scheduler for the weighted reward sum |
|||
* * Compute the values for the individual objectives w.r.t. that scheduler |
|||
* |
|||
* The resulting values represent the rewards at probabilistic states that are obtained at the current time epoch. |
|||
*/ |
|||
void performPSStep(SubModel& PS, SubModel const& MS, MinMaxSolverData& minMax, LinEqSolverData& linEq, std::vector<uint_fast64_t>& optimalChoicesAtCurrentEpoch, storm::storage::BitVector const& consideredObjectives) const; |
|||
|
|||
/* |
|||
* Performs a step for the Markovian states, that is |
|||
* * Compute values for the weighted reward sum as well as for the individual objectives |
|||
* |
|||
* The resulting values represent the rewards at Markovian states that are obtained after one (digitized) time unit has passed. |
|||
*/ |
|||
void performMSStep(SubModel& MS, SubModel const& PS, storm::storage::BitVector const& consideredObjectives) const; |
|||
|
|||
}; |
|||
|
|||
} |
|||
} |
|||
} |
|||
|
|||
#endif /* STORM_MODELCHECKER_MULTIOBJECTIVE_PCAA_SPARSEMAPCAAWEIGHTVECTORCHECKER_H_ */ |
@ -0,0 +1,122 @@ |
|||
#include "src/modelchecker/multiobjective/pcaa/SparseMdpPcaaWeightVectorChecker.h"
|
|||
|
|||
#include "src/adapters/CarlAdapter.h"
|
|||
#include "src/models/sparse/Mdp.h"
|
|||
#include "src/models/sparse/StandardRewardModel.h"
|
|||
#include "src/utility/macros.h"
|
|||
#include "src/utility/vector.h"
|
|||
|
|||
|
|||
namespace storm { |
|||
namespace modelchecker { |
|||
namespace multiobjective { |
|||
|
|||
template <class SparseMdpModelType> |
|||
SparseMdpPcaaWeightVectorChecker<SparseMdpModelType>::SparseMdpPcaaWeightVectorChecker(SparseMdpModelType const& model, |
|||
std::vector<PcaaObjective<ValueType>> const& objectives, |
|||
storm::storage::BitVector const& actionsWithNegativeReward, |
|||
storm::storage::BitVector const& ecActions, |
|||
storm::storage::BitVector const& possiblyRecurrentStates) : |
|||
SparsePcaaWeightVectorChecker<SparseMdpModelType>(model, objectives, actionsWithNegativeReward, ecActions, possiblyRecurrentStates) { |
|||
// set the state action rewards
|
|||
for(uint_fast64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) { |
|||
typename SparseMdpModelType::RewardModelType const& rewModel = this->model.getRewardModel(this->objectives[objIndex].rewardModelName); |
|||
STORM_LOG_ASSERT(!rewModel.hasTransitionRewards(), "Reward model has transition rewards which is not expected."); |
|||
this->discreteActionRewards[objIndex] = rewModel.getTotalRewardVector(this->model.getTransitionMatrix()); |
|||
} |
|||
} |
|||
|
|||
template <class SparseMdpModelType> |
|||
void SparseMdpPcaaWeightVectorChecker<SparseMdpModelType>::boundedPhase(std::vector<ValueType> const& weightVector, std::vector<ValueType>& weightedRewardVector) { |
|||
// Allocate some memory so this does not need to happen for each time epoch
|
|||
std::vector<uint_fast64_t> optimalChoicesInCurrentEpoch(this->model.getNumberOfStates()); |
|||
std::vector<ValueType> choiceValues(weightedRewardVector.size()); |
|||
std::vector<ValueType> temporaryResult(this->model.getNumberOfStates()); |
|||
std::vector<ValueType> zeroReward(weightedRewardVector.size(), storm::utility::zero<ValueType>()); |
|||
// Get for each occurring timeBound the indices of the objectives with that bound.
|
|||
std::map<uint_fast64_t, storm::storage::BitVector, std::greater<uint_fast64_t>> lowerTimeBounds; |
|||
std::map<uint_fast64_t, storm::storage::BitVector, std::greater<uint_fast64_t>> upperTimeBounds; |
|||
for(uint_fast64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) { |
|||
auto const& obj = this->objectives[objIndex]; |
|||
if(obj.lowerTimeBound) { |
|||
auto timeBoundIt = lowerTimeBounds.insert(std::make_pair(storm::utility::convertNumber<uint_fast64_t>(*obj.lowerTimeBound), storm::storage::BitVector(this->objectives.size(), false))).first; |
|||
STORM_LOG_WARN_COND(storm::utility::convertNumber<ValueType>(timeBoundIt->first) == (*obj.lowerTimeBound), "Rounded non-integral bound " << *obj.lowerTimeBound << " to " << timeBoundIt->first << "."); |
|||
timeBoundIt->second.set(objIndex); |
|||
} |
|||
if(obj.upperTimeBound) { |
|||
auto timeBoundIt = upperTimeBounds.insert(std::make_pair(storm::utility::convertNumber<uint_fast64_t>(*obj.upperTimeBound), storm::storage::BitVector(this->objectives.size(), false))).first; |
|||
STORM_LOG_WARN_COND(storm::utility::convertNumber<ValueType>(timeBoundIt->first) == (*obj.upperTimeBound), "Rounded non-integral bound " << *obj.upperTimeBound << " to " << timeBoundIt->first << "."); |
|||
timeBoundIt->second.set(objIndex); |
|||
|
|||
// There is no error for the values of these objectives.
|
|||
this->offsetsToLowerBound[objIndex] = storm::utility::zero<ValueType>(); |
|||
this->offsetsToUpperBound[objIndex] = storm::utility::zero<ValueType>(); |
|||
} |
|||
} |
|||
|
|||
// Stores the objectives for which we need to compute values in the current time epoch.
|
|||
storm::storage::BitVector consideredObjectives = this->objectivesWithNoUpperTimeBound; |
|||
|
|||
// Stores objectives for which the current epoch passed their lower bound
|
|||
storm::storage::BitVector lowerBoundViolatedObjectives(consideredObjectives.size(), false); |
|||
|
|||
auto lowerTimeBoundIt = lowerTimeBounds.begin(); |
|||
auto upperTimeBoundIt = upperTimeBounds.begin(); |
|||
uint_fast64_t currentEpoch = std::max(lowerTimeBounds.empty() ? 0 : lowerTimeBoundIt->first - 1, upperTimeBounds.empty() ? 0 : upperTimeBoundIt->first); // consider lowerBound - 1 since we are interested in the first epoch that passes the bound
|
|||
|
|||
while(currentEpoch > 0) { |
|||
//For lower time bounds we need to react when the currentEpoch passed the bound
|
|||
// Hence, we substract 1 from the lower time bounds.
|
|||
if(lowerTimeBoundIt != lowerTimeBounds.end() && currentEpoch == lowerTimeBoundIt->first - 1) { |
|||
lowerBoundViolatedObjectives |= lowerTimeBoundIt->second; |
|||
for(auto objIndex : lowerTimeBoundIt->second) { |
|||
// No more reward is earned for this objective.
|
|||
storm::utility::vector::addScaledVector(weightedRewardVector, this->discreteActionRewards[objIndex], -weightVector[objIndex]); |
|||
} |
|||
++lowerTimeBoundIt; |
|||
} |
|||
|
|||
if(upperTimeBoundIt != upperTimeBounds.end() && currentEpoch == upperTimeBoundIt->first) { |
|||
consideredObjectives |= upperTimeBoundIt->second; |
|||
for(auto objIndex : upperTimeBoundIt->second) { |
|||
// This objective now plays a role in the weighted sum
|
|||
storm::utility::vector::addScaledVector(weightedRewardVector, this->discreteActionRewards[objIndex], weightVector[objIndex]); |
|||
} |
|||
++upperTimeBoundIt; |
|||
} |
|||
|
|||
// Get values and scheduler for weighted sum of objectives
|
|||
this->model.getTransitionMatrix().multiplyWithVector(this->weightedResult, choiceValues); |
|||
storm::utility::vector::addVectors(choiceValues, weightedRewardVector, choiceValues); |
|||
storm::utility::vector::reduceVectorMax(choiceValues, this->weightedResult, this->model.getTransitionMatrix().getRowGroupIndices(), &optimalChoicesInCurrentEpoch); |
|||
|
|||
// get values for individual objectives
|
|||
// TODO we could compute the result for one of the objectives from the weighted result, the given weight vector, and the remaining objective results.
|
|||
for(auto objIndex : consideredObjectives) { |
|||
std::vector<ValueType>& objectiveResult = this->objectiveResults[objIndex]; |
|||
std::vector<ValueType> const& objectiveRewards = lowerBoundViolatedObjectives.get(objIndex) ? zeroReward : this->discreteActionRewards[objIndex]; |
|||
auto rowGroupIndexIt = this->model.getTransitionMatrix().getRowGroupIndices().begin(); |
|||
auto optimalChoiceIt = optimalChoicesInCurrentEpoch.begin(); |
|||
for(ValueType& stateValue : temporaryResult){ |
|||
uint_fast64_t row = (*rowGroupIndexIt) + (*optimalChoiceIt); |
|||
++rowGroupIndexIt; |
|||
++optimalChoiceIt; |
|||
stateValue = objectiveRewards[row]; |
|||
for(auto const& entry : this->model.getTransitionMatrix().getRow(row)) { |
|||
stateValue += entry.getValue() * objectiveResult[entry.getColumn()]; |
|||
} |
|||
} |
|||
objectiveResult.swap(temporaryResult); |
|||
} |
|||
--currentEpoch; |
|||
} |
|||
} |
|||
|
|||
template class SparseMdpPcaaWeightVectorChecker<storm::models::sparse::Mdp<double>>; |
|||
#ifdef STORM_HAVE_CARL
|
|||
template class SparseMdpPcaaWeightVectorChecker<storm::models::sparse::Mdp<storm::RationalNumber>>; |
|||
#endif
|
|||
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,48 @@ |
|||
#ifndef STORM_MODELCHECKER_MULTIOBJECTIVE_PCAA_SPARSEMDPPCAAWEIGHTVECTORCHECKER_H_ |
|||
#define STORM_MODELCHECKER_MULTIOBJECTIVE_PCAA_SPARSEMDPPCAAWEIGHTVECTORCHECKER_H_ |
|||
|
|||
#include <vector> |
|||
|
|||
#include "src/modelchecker/multiobjective/pcaa/SparsePcaaWeightVectorChecker.h" |
|||
|
|||
namespace storm { |
|||
namespace modelchecker { |
|||
namespace multiobjective { |
|||
|
|||
/*! |
|||
* Helper Class that takes preprocessed Pcaa data 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 SparseMdpModelType> |
|||
class SparseMdpPcaaWeightVectorChecker : public SparsePcaaWeightVectorChecker<SparseMdpModelType> { |
|||
public: |
|||
typedef typename SparseMdpModelType::ValueType ValueType; |
|||
|
|||
SparseMdpPcaaWeightVectorChecker(SparseMdpModelType const& model, |
|||
std::vector<PcaaObjective<ValueType>> const& objectives, |
|||
storm::storage::BitVector const& actionsWithNegativeReward, |
|||
storm::storage::BitVector const& ecActions, |
|||
storm::storage::BitVector const& possiblyRecurrentStates); |
|||
|
|||
private: |
|||
|
|||
/*! |
|||
* 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 |
|||
* @param weightedRewardVector the weighted rewards considering the unbounded objectives. Will be invalidated after calling this. |
|||
*/ |
|||
virtual void boundedPhase(std::vector<ValueType> const& weightVector, std::vector<ValueType>& weightedRewardVector) override; |
|||
|
|||
}; |
|||
|
|||
} |
|||
} |
|||
} |
|||
|
|||
#endif /* STORM_MODELCHECKER_MULTIOBJECTIVE_PCAA_SPARSEMDPPCAAWEIGHTVECTORCHECKER_H_ */ |
@ -0,0 +1,169 @@ |
|||
#include "src/modelchecker/multiobjective/pcaa/SparsePcaaQuantitativeQuery.h"
|
|||
|
|||
#include "src/adapters/CarlAdapter.h"
|
|||
#include "src/models/sparse/Mdp.h"
|
|||
#include "src/models/sparse/MarkovAutomaton.h"
|
|||
#include "src/models/sparse/StandardRewardModel.h"
|
|||
#include "src/modelchecker/results/ExplicitQualitativeCheckResult.h"
|
|||
#include "src/modelchecker/results/ExplicitQuantitativeCheckResult.h"
|
|||
#include "src/utility/constants.h"
|
|||
#include "src/utility/vector.h"
|
|||
#include "src/settings//SettingsManager.h"
|
|||
#include "src/settings/modules/MultiObjectiveSettings.h"
|
|||
#include "src/settings/modules/GeneralSettings.h"
|
|||
|
|||
namespace storm { |
|||
namespace modelchecker { |
|||
namespace multiobjective { |
|||
|
|||
|
|||
template <class SparseModelType, typename GeometryValueType> |
|||
SparsePcaaQuantitativeQuery<SparseModelType, GeometryValueType>::SparsePcaaQuantitativeQuery(SparsePcaaPreprocessorReturnType<SparseModelType>& preprocessorResult) : SparsePcaaQuery<SparseModelType, GeometryValueType>(preprocessorResult) { |
|||
STORM_LOG_ASSERT(preprocessorResult.queryType==SparsePcaaPreprocessorReturnType<SparseModelType>::QueryType::Quantitative, "Invalid query Type"); |
|||
STORM_LOG_ASSERT(preprocessorResult.indexOfOptimizingObjective, "Detected quantitative query but index of optimizing objective is not set."); |
|||
indexOfOptimizingObjective = *preprocessorResult.indexOfOptimizingObjective; |
|||
initializeThresholdData(); |
|||
|
|||
// Set the maximum gap between lower and upper bound of the weightVectorChecker result.
|
|||
// This is the maximal edge length of the box we have to consider around each computed point
|
|||
// We pick the gap such that the maximal distance between two points within this box is less than the given precision divided by two.
|
|||
typename SparseModelType::ValueType gap = storm::utility::convertNumber<typename SparseModelType::ValueType>(storm::settings::getModule<storm::settings::modules::MultiObjectiveSettings>().getPrecision()); |
|||
gap /= (storm::utility::one<typename SparseModelType::ValueType>() + storm::utility::one<typename SparseModelType::ValueType>()); |
|||
gap /= storm::utility::sqrt(static_cast<typename SparseModelType::ValueType>(this->objectives.size())); |
|||
this->weightVectorChecker->setMaximumLowerUpperBoundGap(gap); |
|||
|
|||
} |
|||
|
|||
template <class SparseModelType, typename GeometryValueType> |
|||
void SparsePcaaQuantitativeQuery<SparseModelType, GeometryValueType>::initializeThresholdData() { |
|||
thresholds.reserve(this->objectives.size()); |
|||
strictThresholds = storm::storage::BitVector(this->objectives.size(), false); |
|||
std::vector<storm::storage::geometry::Halfspace<GeometryValueType>> thresholdConstraints; |
|||
thresholdConstraints.reserve(this->objectives.size()-1); |
|||
for(uint_fast64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) { |
|||
if(this->objectives[objIndex].threshold) { |
|||
thresholds.push_back(storm::utility::convertNumber<GeometryValueType>(*this->objectives[objIndex].threshold)); |
|||
WeightVector normalVector(this->objectives.size(), storm::utility::zero<GeometryValueType>()); |
|||
normalVector[objIndex] = -storm::utility::one<GeometryValueType>(); |
|||
thresholdConstraints.emplace_back(std::move(normalVector), -thresholds.back()); |
|||
strictThresholds.set(objIndex, this->objectives[objIndex].thresholdIsStrict); |
|||
} else { |
|||
thresholds.push_back(storm::utility::zero<GeometryValueType>()); |
|||
} |
|||
} |
|||
// Note: If we have a single objective (i.e., no objectives with thresholds), thresholdsAsPolytope gets no constraints
|
|||
thresholdsAsPolytope = storm::storage::geometry::Polytope<GeometryValueType>::create(thresholdConstraints); |
|||
} |
|||
|
|||
template <class SparseModelType, typename GeometryValueType> |
|||
std::unique_ptr<CheckResult> SparsePcaaQuantitativeQuery<SparseModelType, GeometryValueType>::check() { |
|||
|
|||
|
|||
// First find one solution that achieves the given thresholds ...
|
|||
if(this->checkAchievability()) { |
|||
// ... then improve it
|
|||
GeometryValueType result = this->improveSolution(); |
|||
|
|||
// transform the obtained result for the preprocessed model to a result w.r.t. the original model and return the checkresult
|
|||
typename SparseModelType::ValueType resultForOriginalModel = |
|||
storm::utility::convertNumber<typename SparseModelType::ValueType>(result) * |
|||
this->objectives[indexOfOptimizingObjective].toOriginalValueTransformationFactor + |
|||
this->objectives[indexOfOptimizingObjective].toOriginalValueTransformationOffset; |
|||
return std::unique_ptr<CheckResult>(new ExplicitQuantitativeCheckResult<typename SparseModelType::ValueType>(this->originalModel.getInitialStates().getNextSetIndex(0), resultForOriginalModel)); |
|||
} else { |
|||
return std::unique_ptr<CheckResult>(new ExplicitQualitativeCheckResult(this->originalModel.getInitialStates().getNextSetIndex(0), false)); |
|||
} |
|||
|
|||
|
|||
} |
|||
|
|||
template <class SparseModelType, typename GeometryValueType> |
|||
bool SparsePcaaQuantitativeQuery<SparseModelType, GeometryValueType>::checkAchievability() { |
|||
// We don't care for the optimizing objective at this point
|
|||
this->diracWeightVectorsToBeChecked.set(indexOfOptimizingObjective, false); |
|||
|
|||
while(!this->maxStepsPerformed()){ |
|||
WeightVector separatingVector = this->findSeparatingVector(thresholds); |
|||
this->performRefinementStep(std::move(separatingVector)); |
|||
//Pick the threshold for the optimizing objective low enough so valid solutions are not excluded
|
|||
thresholds[indexOfOptimizingObjective] = std::min(thresholds[indexOfOptimizingObjective], this->refinementSteps.back().lowerBoundPoint[indexOfOptimizingObjective]); |
|||
if(!checkIfThresholdsAreSatisfied(this->overApproximation)){ |
|||
return false; |
|||
} |
|||
if(checkIfThresholdsAreSatisfied(this->underApproximation)){ |
|||
return true; |
|||
} |
|||
} |
|||
STORM_LOG_ERROR("Could not check whether thresholds are achievable: Exceeded maximum number of refinement steps"); |
|||
return false; |
|||
} |
|||
|
|||
template <class SparseModelType, typename GeometryValueType> |
|||
GeometryValueType SparsePcaaQuantitativeQuery<SparseModelType, GeometryValueType>::improveSolution() { |
|||
this->diracWeightVectorsToBeChecked.clear(); // Only check weight vectors that can actually improve the solution
|
|||
|
|||
WeightVector directionOfOptimizingObjective(this->objectives.size(), storm::utility::zero<GeometryValueType>()); |
|||
directionOfOptimizingObjective[indexOfOptimizingObjective] = storm::utility::one<GeometryValueType>(); |
|||
|
|||
// Improve the found solution.
|
|||
// Note that we do not care whether a threshold is strict anymore, because the resulting optimum should be
|
|||
// the supremum over all strategies. Hence, one could combine a scheduler inducing the optimum value (but possibly violating strict
|
|||
// thresholds) and (with very low probability) a scheduler that satisfies all (possibly strict) thresholds.
|
|||
GeometryValueType result = storm::utility::zero<GeometryValueType>(); |
|||
while(!this->maxStepsPerformed()) { |
|||
std::pair<Point, bool> optimizationRes = this->underApproximation->intersection(thresholdsAsPolytope)->optimize(directionOfOptimizingObjective); |
|||
STORM_LOG_THROW(optimizationRes.second, storm::exceptions::UnexpectedException, "The underapproximation is either unbounded or empty."); |
|||
result = optimizationRes.first[indexOfOptimizingObjective]; |
|||
STORM_LOG_DEBUG("Best solution found so far is ~" << storm::utility::convertNumber<double>(result) << "."); |
|||
thresholds[indexOfOptimizingObjective] = result; |
|||
//Compute an upper bound for the optimum and check for convergence
|
|||
optimizationRes = this->overApproximation->intersection(thresholdsAsPolytope)->optimize(directionOfOptimizingObjective); |
|||
if(optimizationRes.second) { |
|||
GeometryValueType precisionOfResult = optimizationRes.first[indexOfOptimizingObjective] - result; |
|||
if(precisionOfResult < storm::utility::convertNumber<GeometryValueType>(storm::settings::getModule<storm::settings::modules::MultiObjectiveSettings>().getPrecision())) { |
|||
// Goal precision reached!
|
|||
return result; |
|||
} else { |
|||
STORM_LOG_DEBUG("Solution can be improved by at most " << storm::utility::convertNumber<double>(precisionOfResult)); |
|||
} |
|||
} |
|||
WeightVector separatingVector = this->findSeparatingVector(thresholds); |
|||
this->performRefinementStep(std::move(separatingVector)); |
|||
} |
|||
STORM_LOG_ERROR("Could not reach the desired precision: Exceeded maximum number of refinement steps"); |
|||
return result; |
|||
} |
|||
|
|||
template <class SparseModelType, typename GeometryValueType> |
|||
bool SparsePcaaQuantitativeQuery<SparseModelType, GeometryValueType>::checkIfThresholdsAreSatisfied(std::shared_ptr<storm::storage::geometry::Polytope<GeometryValueType>> const& polytope) { |
|||
std::vector<storm::storage::geometry::Halfspace<GeometryValueType>> halfspaces = polytope->getHalfspaces(); |
|||
for(auto const& h : halfspaces) { |
|||
GeometryValueType distance = h.distance(thresholds); |
|||
if(distance < storm::utility::zero<GeometryValueType>()) { |
|||
return false; |
|||
} |
|||
if(distance == storm::utility::zero<GeometryValueType>()) { |
|||
// In this case, the thresholds point is on the boundary of the polytope.
|
|||
// Check if this is problematic for the strict thresholds
|
|||
for(auto strictThreshold : strictThresholds) { |
|||
if(h.normalVector()[strictThreshold] > storm::utility::zero<GeometryValueType>()) { |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
|
|||
|
|||
#ifdef STORM_HAVE_CARL
|
|||
template class SparsePcaaQuantitativeQuery<storm::models::sparse::Mdp<double>, storm::RationalNumber>; |
|||
template class SparsePcaaQuantitativeQuery<storm::models::sparse::MarkovAutomaton<double>, storm::RationalNumber>; |
|||
|
|||
template class SparsePcaaQuantitativeQuery<storm::models::sparse::Mdp<storm::RationalNumber>, storm::RationalNumber>; |
|||
// template class SparsePcaaQuantitativeQuery<storm::models::sparse::MarkovAutomaton<storm::RationalNumber>, storm::RationalNumber>;
|
|||
#endif
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,65 @@ |
|||
#ifndef STORM_MODELCHECKER_MULTIOBJECTIVE_PCAA_SPARSEPCAAQUANTITATIVEQUERY_H_ |
|||
#define STORM_MODELCHECKER_MULTIOBJECTIVE_PCAA_SPARSEPCAAQUANTITATIVEQUERY_H_ |
|||
|
|||
#include "src/modelchecker/multiobjective/pcaa/SparsePcaaQuery.h" |
|||
|
|||
namespace storm { |
|||
namespace modelchecker { |
|||
namespace multiobjective { |
|||
|
|||
/* |
|||
* This class represents a query for the Pareto curve approximation algorithm (Pcaa). |
|||
* It implements the necessary computations for the different query types. |
|||
*/ |
|||
template <class SparseModelType, typename GeometryValueType> |
|||
class SparsePcaaQuantitativeQuery : public SparsePcaaQuery<SparseModelType, GeometryValueType> { |
|||
public: |
|||
|
|||
// Typedefs for simple geometric objects |
|||
typedef std::vector<GeometryValueType> Point; |
|||
typedef std::vector<GeometryValueType> WeightVector; |
|||
|
|||
/* |
|||
* Creates a new query for the Pareto curve approximation algorithm (Pcaa) |
|||
* @param preprocessorResult the result from preprocessing |
|||
*/ |
|||
SparsePcaaQuantitativeQuery(SparsePcaaPreprocessorReturnType<SparseModelType>& preprocessorResult); |
|||
|
|||
|
|||
/* |
|||
* Invokes the computation and retrieves the result |
|||
*/ |
|||
virtual std::unique_ptr<CheckResult> check() override; |
|||
|
|||
private: |
|||
|
|||
void initializeThresholdData(); |
|||
|
|||
/* |
|||
* Returns whether the given thresholds are achievable. |
|||
*/ |
|||
bool checkAchievability(); |
|||
|
|||
/* |
|||
* Given that the thresholds are achievable, this function further refines the approximations and returns the optimized value |
|||
*/ |
|||
GeometryValueType improveSolution(); |
|||
|
|||
/* |
|||
* Returns true iff there is one point in the given polytope that satisfies the given thresholds. |
|||
* It is assumed that the given polytope contains the downward closure of its vertices. |
|||
*/ |
|||
bool checkIfThresholdsAreSatisfied(std::shared_ptr<storm::storage::geometry::Polytope<GeometryValueType>> const& polytope); |
|||
|
|||
uint_fast64_t indexOfOptimizingObjective; |
|||
|
|||
Point thresholds; |
|||
storm::storage::BitVector strictThresholds; |
|||
std::shared_ptr<storm::storage::geometry::Polytope<GeometryValueType>> thresholdsAsPolytope; |
|||
}; |
|||
|
|||
} |
|||
} |
|||
} |
|||
|
|||
#endif /* STORM_MODELCHECKER_MULTIOBJECTIVE_PCAA_SPARSEPCAAQUANTITATIVEQUERY_H_ */ |
@ -0,0 +1,205 @@ |
|||
#include "src/modelchecker/multiobjective/pcaa/SparsePcaaQuery.h"
|
|||
|
|||
#include "src/adapters/CarlAdapter.h"
|
|||
#include "src/models/sparse/Mdp.h"
|
|||
#include "src/models/sparse/MarkovAutomaton.h"
|
|||
#include "src/models/sparse/StandardRewardModel.h"
|
|||
#include "src/modelchecker/multiobjective/pcaa/PcaaObjective.h"
|
|||
#include "src/modelchecker/multiobjective/pcaa/SparseMdpPcaaWeightVectorChecker.h"
|
|||
#include "src/modelchecker/multiobjective/pcaa/SparseMaPcaaWeightVectorChecker.h"
|
|||
#include "src/utility/constants.h"
|
|||
#include "src/utility/vector.h"
|
|||
#include "src/settings//SettingsManager.h"
|
|||
#include "src/settings/modules/MultiObjectiveSettings.h"
|
|||
|
|||
#include "src/exceptions/UnexpectedException.h"
|
|||
|
|||
namespace storm { |
|||
namespace modelchecker { |
|||
namespace multiobjective { |
|||
|
|||
|
|||
template <class SparseModelType, typename GeometryValueType> |
|||
SparsePcaaQuery<SparseModelType, GeometryValueType>::SparsePcaaQuery(SparsePcaaPreprocessorReturnType<SparseModelType>& preprocessorResult) : |
|||
originalModel(preprocessorResult.originalModel), originalFormula(preprocessorResult.originalFormula), |
|||
preprocessedModel(std::move(preprocessorResult.preprocessedModel)), objectives(std::move(preprocessorResult.objectives)) { |
|||
initializeWeightVectorChecker(preprocessedModel, objectives, preprocessorResult.actionsWithNegativeReward, preprocessorResult.ecActions, preprocessorResult.possiblyRecurrentStates); |
|||
this->diracWeightVectorsToBeChecked = storm::storage::BitVector(this->objectives.size(), true); |
|||
this->overApproximation = storm::storage::geometry::Polytope<GeometryValueType>::createUniversalPolytope(); |
|||
this->underApproximation = storm::storage::geometry::Polytope<GeometryValueType>::createEmptyPolytope(); |
|||
} |
|||
|
|||
template<> |
|||
void SparsePcaaQuery<storm::models::sparse::Mdp<double>, storm::RationalNumber>::initializeWeightVectorChecker(storm::models::sparse::Mdp<double> const& model, std::vector<PcaaObjective<double>> const& objectives, storm::storage::BitVector const& actionsWithNegativeReward, storm::storage::BitVector const& ecActions, storm::storage::BitVector const& possiblyRecurrentStates) { |
|||
this->weightVectorChecker = std::unique_ptr<SparsePcaaWeightVectorChecker<storm::models::sparse::Mdp<double>>>(new SparseMdpPcaaWeightVectorChecker<storm::models::sparse::Mdp<double>>(model, objectives, actionsWithNegativeReward, ecActions, possiblyRecurrentStates)); |
|||
} |
|||
|
|||
template<> |
|||
void SparsePcaaQuery<storm::models::sparse::Mdp<storm::RationalNumber>, storm::RationalNumber>::initializeWeightVectorChecker(storm::models::sparse::Mdp<storm::RationalNumber> const& model, std::vector<PcaaObjective<storm::RationalNumber>> const& objectives, storm::storage::BitVector const& actionsWithNegativeReward, storm::storage::BitVector const& ecActions, storm::storage::BitVector const& possiblyRecurrentStates) { |
|||
this->weightVectorChecker = std::unique_ptr<SparsePcaaWeightVectorChecker<storm::models::sparse::Mdp<storm::RationalNumber>>>(new SparseMdpPcaaWeightVectorChecker<storm::models::sparse::Mdp<storm::RationalNumber>>(model, objectives, actionsWithNegativeReward, ecActions, possiblyRecurrentStates)); |
|||
} |
|||
|
|||
template<> |
|||
void SparsePcaaQuery<storm::models::sparse::MarkovAutomaton<double>, storm::RationalNumber>::initializeWeightVectorChecker(storm::models::sparse::MarkovAutomaton<double> const& model, std::vector<PcaaObjective<double>> const& objectives, storm::storage::BitVector const& actionsWithNegativeReward, storm::storage::BitVector const& ecActions, storm::storage::BitVector const& possiblyRecurrentStates) { |
|||
this->weightVectorChecker = std::unique_ptr<SparsePcaaWeightVectorChecker<storm::models::sparse::MarkovAutomaton<double>>>(new SparseMaPcaaWeightVectorChecker<storm::models::sparse::MarkovAutomaton<double>>(model, objectives, actionsWithNegativeReward, ecActions, possiblyRecurrentStates)); |
|||
} |
|||
|
|||
// template<>
|
|||
// void SparsePcaaQuery<storm::models::sparse::MarkovAutomaton<storm::RationalNumber>, storm::RationalNumber>::initializeWeightVectorChecker(storm::models::sparse::MarkovAutomaton<storm::RationalNumber> const& model, std::vector<PcaaObjective<storm::RationalNumber>> const& objectives, storm::storage::BitVector const& actionsWithNegativeReward, storm::storage::BitVector const& ecActions, storm::storage::BitVector const& possiblyRecurrentStates) {
|
|||
// this->weightVectorChecker = std::unique_ptr<SparsePcaaWeightVectorChecker<storm::models::sparse::MarkovAutomaton<storm::RationalNumber>>>(new SparseMaPcaaWeightVectorChecker<storm::models::sparse::MarkovAutomaton<storm::RationalNumber>>(model, objectives, actionsWithNegativeReward, ecActions, possiblyRecurrentStates));
|
|||
// }
|
|||
|
|||
|
|||
template <class SparseModelType, typename GeometryValueType> |
|||
typename SparsePcaaQuery<SparseModelType, GeometryValueType>::WeightVector SparsePcaaQuery<SparseModelType, GeometryValueType>::findSeparatingVector(Point const& pointToBeSeparated) { |
|||
STORM_LOG_DEBUG("Searching a weight vector to seperate the point given by " << storm::utility::vector::toString(storm::utility::vector::convertNumericVector<double>(pointToBeSeparated)) << "."); |
|||
|
|||
if(underApproximation->isEmpty()) { |
|||
// In this case, every weight vector is separating
|
|||
uint_fast64_t objIndex = diracWeightVectorsToBeChecked.getNextSetIndex(0) % pointToBeSeparated.size(); |
|||
WeightVector result(pointToBeSeparated.size(), storm::utility::zero<GeometryValueType>()); |
|||
result[objIndex] = storm::utility::one<GeometryValueType>(); |
|||
diracWeightVectorsToBeChecked.set(objIndex, false); |
|||
return result; |
|||
} |
|||
|
|||
// Reaching this point means that the underApproximation contains halfspaces. The seperating vector has to be the normal vector of one of these halfspaces.
|
|||
// We pick one with maximal distance to the given point. However, Dirac weight vectors that only assign a non-zero weight to a single objective take precedence.
|
|||
std::vector<storm::storage::geometry::Halfspace<GeometryValueType>> halfspaces = underApproximation->getHalfspaces(); |
|||
uint_fast64_t farestHalfspaceIndex = halfspaces.size(); |
|||
GeometryValueType farestDistance = -storm::utility::one<GeometryValueType>(); |
|||
bool foundSeparatingDiracVector = false; |
|||
for(uint_fast64_t halfspaceIndex = 0; halfspaceIndex < halfspaces.size(); ++halfspaceIndex) { |
|||
GeometryValueType distance = -halfspaces[halfspaceIndex].euclideanDistance(pointToBeSeparated); |
|||
if(distance >= storm::utility::zero<GeometryValueType>()) { |
|||
storm::storage::BitVector nonZeroVectorEntries = ~storm::utility::vector::filterZero<GeometryValueType>(halfspaces[halfspaceIndex].normalVector()); |
|||
bool isSingleObjectiveVector = nonZeroVectorEntries.getNumberOfSetBits() == 1 && diracWeightVectorsToBeChecked.get(nonZeroVectorEntries.getNextSetIndex(0)); |
|||
// Check if this halfspace is a better candidate than the current one
|
|||
if((!foundSeparatingDiracVector && isSingleObjectiveVector ) || (foundSeparatingDiracVector==isSingleObjectiveVector && distance>farestDistance)) { |
|||
foundSeparatingDiracVector = foundSeparatingDiracVector || isSingleObjectiveVector; |
|||
farestHalfspaceIndex = halfspaceIndex; |
|||
farestDistance = distance; |
|||
} |
|||
} |
|||
} |
|||
if(foundSeparatingDiracVector) { |
|||
diracWeightVectorsToBeChecked &= storm::utility::vector::filterZero<GeometryValueType>(halfspaces[farestHalfspaceIndex].normalVector()); |
|||
} |
|||
|
|||
STORM_LOG_THROW(farestHalfspaceIndex<halfspaces.size(), storm::exceptions::UnexpectedException, "There is no seperating vector."); |
|||
STORM_LOG_DEBUG("Found separating weight vector: " << storm::utility::vector::toString(storm::utility::vector::convertNumericVector<double>(halfspaces[farestHalfspaceIndex].normalVector())) << "."); |
|||
return halfspaces[farestHalfspaceIndex].normalVector(); |
|||
} |
|||
|
|||
template <class SparseModelType, typename GeometryValueType> |
|||
void SparsePcaaQuery<SparseModelType, GeometryValueType>::performRefinementStep(WeightVector&& direction) { |
|||
// Normalize the direction vector so that the entries sum up to one
|
|||
storm::utility::vector::scaleVectorInPlace(direction, storm::utility::one<GeometryValueType>() / std::accumulate(direction.begin(), direction.end(), storm::utility::zero<GeometryValueType>())); |
|||
// Check if we already did a refinement step with that direction vector. If this is the case, we increase the precision.
|
|||
// We start with the most recent steps to consider the most recent result for this direction vector
|
|||
boost::optional<typename SparseModelType::ValueType> oldMaximumLowerUpperBoundGap; |
|||
for(auto stepIt = refinementSteps.rbegin(); stepIt != refinementSteps.rend(); ++stepIt) { |
|||
if(stepIt->weightVector == direction) { |
|||
STORM_LOG_WARN("Performing multiple refinement steps with the same direction vector."); |
|||
oldMaximumLowerUpperBoundGap = weightVectorChecker->getMaximumLowerUpperBoundGap(); |
|||
std::vector<GeometryValueType> lowerUpperDistances = stepIt->upperBoundPoint; |
|||
storm::utility::vector::subtractVectors(lowerUpperDistances, stepIt->lowerBoundPoint, lowerUpperDistances); |
|||
// shorten the distance between lower and upper bound for the new result by multiplying the current distance with 0.5
|
|||
// TODO: try other values/strategies?
|
|||
GeometryValueType distance = storm::utility::sqrt(storm::utility::vector::dotProduct(lowerUpperDistances, lowerUpperDistances)); |
|||
weightVectorChecker->setMaximumLowerUpperBoundGap(std::min(*oldMaximumLowerUpperBoundGap, storm::utility::convertNumber<typename SparseModelType::ValueType>(distance) * storm::utility::convertNumber<typename SparseModelType::ValueType>(0.5))); |
|||
break; |
|||
} |
|||
} |
|||
weightVectorChecker->check(storm::utility::vector::convertNumericVector<typename SparseModelType::ValueType>(direction)); |
|||
if(oldMaximumLowerUpperBoundGap) { |
|||
// Reset the precision back to the previous values
|
|||
weightVectorChecker->setMaximumLowerUpperBoundGap(*oldMaximumLowerUpperBoundGap); |
|||
} |
|||
STORM_LOG_DEBUG("weighted objectives checker result (lower bounds) is " << storm::utility::vector::toString(storm::utility::vector::convertNumericVector<double>(weightVectorChecker->getLowerBoundsOfInitialStateResults()))); |
|||
RefinementStep step; |
|||
step.weightVector = direction; |
|||
step.lowerBoundPoint = storm::utility::vector::convertNumericVector<GeometryValueType>(weightVectorChecker->getLowerBoundsOfInitialStateResults()); |
|||
step.upperBoundPoint = storm::utility::vector::convertNumericVector<GeometryValueType>(weightVectorChecker->getUpperBoundsOfInitialStateResults()); |
|||
refinementSteps.push_back(std::move(step)); |
|||
|
|||
updateOverApproximation(); |
|||
updateUnderApproximation(); |
|||
} |
|||
|
|||
template <class SparseModelType, typename GeometryValueType> |
|||
void SparsePcaaQuery<SparseModelType, GeometryValueType>::updateOverApproximation() { |
|||
storm::storage::geometry::Halfspace<GeometryValueType> h(refinementSteps.back().weightVector, storm::utility::vector::dotProduct(refinementSteps.back().weightVector, refinementSteps.back().upperBoundPoint)); |
|||
|
|||
// Due to numerical issues, it might be the case that the updated overapproximation does not contain the underapproximation,
|
|||
// e.g., when the new point is strictly contained in the underapproximation. Check if this is the case.
|
|||
GeometryValueType maximumOffset = h.offset(); |
|||
for(auto const& step : refinementSteps){ |
|||
maximumOffset = std::max(maximumOffset, storm::utility::vector::dotProduct(h.normalVector(), step.lowerBoundPoint)); |
|||
} |
|||
if(maximumOffset > h.offset()){ |
|||
// We correct the issue by shifting the halfspace such that it contains the underapproximation
|
|||
h.offset() = maximumOffset; |
|||
STORM_LOG_WARN("Numerical issues: The overapproximation would not contain the underapproximation. Hence, a halfspace is shifted by " << storm::utility::convertNumber<double>(h.euclideanDistance(refinementSteps.back().upperBoundPoint)) << "."); |
|||
} |
|||
overApproximation = overApproximation->intersection(h); |
|||
STORM_LOG_DEBUG("Updated OverApproximation to " << overApproximation->toString(true)); |
|||
} |
|||
|
|||
template <class SparseModelType, typename GeometryValueType> |
|||
void SparsePcaaQuery<SparseModelType, GeometryValueType>::updateUnderApproximation() { |
|||
std::vector<Point> paretoPoints; |
|||
paretoPoints.reserve(refinementSteps.size()); |
|||
for(auto const& step : refinementSteps) { |
|||
paretoPoints.push_back(step.lowerBoundPoint); |
|||
} |
|||
underApproximation = storm::storage::geometry::Polytope<GeometryValueType>::createDownwardClosure(paretoPoints); |
|||
STORM_LOG_DEBUG("Updated UnderApproximation to " << underApproximation->toString(true)); |
|||
} |
|||
|
|||
template <class SparseModelType, typename GeometryValueType> |
|||
bool SparsePcaaQuery<SparseModelType, GeometryValueType>::maxStepsPerformed() const { |
|||
return storm::settings::getModule<storm::settings::modules::MultiObjectiveSettings>().isMaxStepsSet() && |
|||
this->refinementSteps.size() >= storm::settings::getModule<storm::settings::modules::MultiObjectiveSettings>().getMaxSteps(); |
|||
} |
|||
|
|||
|
|||
template<typename SparseModelType, typename GeometryValueType> |
|||
typename SparsePcaaQuery<SparseModelType, GeometryValueType>::Point SparsePcaaQuery<SparseModelType, GeometryValueType>::transformPointToOriginalModel(Point const& point) const { |
|||
Point result; |
|||
result.reserve(point.size()); |
|||
for(uint_fast64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) { |
|||
result.push_back(point[objIndex] * storm::utility::convertNumber<GeometryValueType>(this->objectives[objIndex].toOriginalValueTransformationFactor) + storm::utility::convertNumber<GeometryValueType>(this->objectives[objIndex].toOriginalValueTransformationOffset)); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
template<typename SparseModelType, typename GeometryValueType> |
|||
std::shared_ptr<storm::storage::geometry::Polytope<GeometryValueType>> SparsePcaaQuery<SparseModelType, GeometryValueType>::transformPolytopeToOriginalModel(std::shared_ptr<storm::storage::geometry::Polytope<GeometryValueType>> const& polytope) const { |
|||
if(polytope->isEmpty()) { |
|||
return storm::storage::geometry::Polytope<GeometryValueType>::createEmptyPolytope(); |
|||
} |
|||
if(polytope->isUniversal()) { |
|||
return storm::storage::geometry::Polytope<GeometryValueType>::createUniversalPolytope(); |
|||
} |
|||
uint_fast64_t numObjectives = this->objectives.size(); |
|||
std::vector<std::vector<GeometryValueType>> transformationMatrix(numObjectives, std::vector<GeometryValueType>(numObjectives, storm::utility::zero<GeometryValueType>())); |
|||
std::vector<GeometryValueType> transformationVector; |
|||
transformationVector.reserve(numObjectives); |
|||
for(uint_fast64_t objIndex = 0; objIndex < numObjectives; ++objIndex) { |
|||
transformationMatrix[objIndex][objIndex] = storm::utility::convertNumber<GeometryValueType>(this->objectives[objIndex].toOriginalValueTransformationFactor); |
|||
transformationVector.push_back(storm::utility::convertNumber<GeometryValueType>(this->objectives[objIndex].toOriginalValueTransformationOffset)); |
|||
} |
|||
return polytope->affineTransformation(transformationMatrix, transformationVector); |
|||
} |
|||
|
|||
#ifdef STORM_HAVE_CARL
|
|||
template class SparsePcaaQuery<storm::models::sparse::Mdp<double>, storm::RationalNumber>; |
|||
template class SparsePcaaQuery<storm::models::sparse::MarkovAutomaton<double>, storm::RationalNumber>; |
|||
|
|||
template class SparsePcaaQuery<storm::models::sparse::Mdp<storm::RationalNumber>, storm::RationalNumber>; |
|||
// template class SparsePcaaQuery<storm::models::sparse::MarkovAutomaton<storm::RationalNumber>, storm::RationalNumber>;
|
|||
#endif
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,122 @@ |
|||
#ifndef STORM_MODELCHECKER_MULTIOBJECTIVE_PCAA_SPARSEPCAAQUERY_H_ |
|||
#define STORM_MODELCHECKER_MULTIOBJECTIVE_PCAA_SPARSEPCAAQUERY_H_ |
|||
|
|||
#include "src/modelchecker/results/CheckResult.h" |
|||
#include "src/modelchecker/multiobjective/pcaa/SparsePcaaPreprocessorReturnType.h" |
|||
#include "src/modelchecker/multiobjective/pcaa/SparsePcaaWeightVectorChecker.h" |
|||
#include "src/storage/geometry/Polytope.h" |
|||
|
|||
namespace storm { |
|||
namespace modelchecker { |
|||
namespace multiobjective { |
|||
|
|||
/* |
|||
* This class represents a query for the Pareto curve approximation algorithm (Pcaa). |
|||
* It implements the necessary computations for the different query types. |
|||
*/ |
|||
template <class SparseModelType, typename GeometryValueType> |
|||
class SparsePcaaQuery { |
|||
public: |
|||
|
|||
// Typedefs for simple geometric objects |
|||
typedef std::vector<GeometryValueType> Point; |
|||
typedef std::vector<GeometryValueType> WeightVector; |
|||
|
|||
/* |
|||
* Invokes the computation and retrieves the result |
|||
*/ |
|||
virtual std::unique_ptr<CheckResult> check() = 0; |
|||
|
|||
protected: |
|||
|
|||
/* |
|||
* Initializes the weight vector checker with the provided data from preprocessing |
|||
*/ |
|||
void initializeWeightVectorChecker(SparseModelType const& model, |
|||
std::vector<PcaaObjective<typename SparseModelType::ValueType>> const& objectives, |
|||
storm::storage::BitVector const& actionsWithNegativeReward, |
|||
storm::storage::BitVector const& ecActions, |
|||
storm::storage::BitVector const& possiblyRecurrentStates); |
|||
|
|||
/* |
|||
* Represents the information obtained in a single iteration of the algorithm |
|||
*/ |
|||
struct RefinementStep { |
|||
WeightVector weightVector; |
|||
Point lowerBoundPoint; |
|||
Point upperBoundPoint; |
|||
}; |
|||
|
|||
/* |
|||
* Creates a new query for the Pareto curve approximation algorithm (Pcaa) |
|||
* @param preprocessorResult the result from preprocessing |
|||
*/ |
|||
SparsePcaaQuery(SparsePcaaPreprocessorReturnType<SparseModelType>& preprocessorResult); |
|||
|
|||
/* |
|||
* Returns a weight vector w that separates the under approximation from the given point p, i.e., |
|||
* For each x in the under approximation, it holds that w*x <= w*p |
|||
* |
|||
* @param pointToBeSeparated the point that is to be seperated |
|||
*/ |
|||
WeightVector findSeparatingVector(Point const& pointToBeSeparated); |
|||
|
|||
/* |
|||
* Refines the current result w.r.t. the given direction vector. |
|||
*/ |
|||
void performRefinementStep(WeightVector&& direction); |
|||
|
|||
/* |
|||
* Updates the overapproximation after a refinement step has been performed |
|||
* |
|||
* @note The last entry of this->refinementSteps should be the newest step whose information is not yet included in the approximation. |
|||
*/ |
|||
void updateOverApproximation(); |
|||
|
|||
/* |
|||
* Updates the underapproximation after a refinement step has been performed |
|||
* |
|||
* @note The last entry of this->refinementSteps should be the newest step whose information is not yet included in the approximation. |
|||
*/ |
|||
void updateUnderApproximation(); |
|||
|
|||
/* |
|||
* Returns true iff the maximum number of refinement steps (as possibly specified in the settings) has been reached |
|||
*/ |
|||
bool maxStepsPerformed() const; |
|||
|
|||
/* |
|||
* Transforms the given point (or polytope) to values w.r.t. the original model (e.g. negates negative rewards for minimizing objectives). |
|||
*/ |
|||
Point transformPointToOriginalModel(Point const& polytope) const; |
|||
std::shared_ptr<storm::storage::geometry::Polytope<GeometryValueType>> transformPolytopeToOriginalModel(std::shared_ptr<storm::storage::geometry::Polytope<GeometryValueType>> const& polytope) const; |
|||
|
|||
|
|||
SparseModelType const& originalModel; |
|||
storm::logic::MultiObjectiveFormula const& originalFormula; |
|||
|
|||
SparseModelType preprocessedModel; |
|||
std::vector<PcaaObjective<typename SparseModelType::ValueType>> objectives; |
|||
|
|||
|
|||
// The corresponding weight vector checker |
|||
std::unique_ptr<SparsePcaaWeightVectorChecker<SparseModelType>> weightVectorChecker; |
|||
|
|||
//The results in each iteration of the algorithm |
|||
std::vector<RefinementStep> refinementSteps; |
|||
//Overapproximation of the set of achievable values |
|||
std::shared_ptr<storm::storage::geometry::Polytope<GeometryValueType>> overApproximation; |
|||
//Underapproximation of the set of achievable values |
|||
std::shared_ptr<storm::storage::geometry::Polytope<GeometryValueType>> underApproximation; |
|||
|
|||
// stores for each objective whether it still makes sense to check for this objective individually (i.e., with weight vector given by w_{i}>0 iff i=objIndex ) |
|||
storm::storage::BitVector diracWeightVectorsToBeChecked; |
|||
|
|||
|
|||
}; |
|||
|
|||
} |
|||
} |
|||
} |
|||
|
|||
#endif /* STORM_MODELCHECKER_MULTIOBJECTIVE_PCAA_SPARSEPCAAQUERY_H_ */ |
@ -0,0 +1,311 @@ |
|||
#include "src/modelchecker/multiobjective/pcaa/SparsePcaaWeightVectorChecker.h"
|
|||
|
|||
#include <map>
|
|||
|
|||
#include "src/adapters/CarlAdapter.h"
|
|||
#include "src/models/sparse/Mdp.h"
|
|||
#include "src/models/sparse/MarkovAutomaton.h"
|
|||
#include "src/models/sparse/StandardRewardModel.h"
|
|||
#include "src/modelchecker/prctl/helper/SparseDtmcPrctlHelper.h"
|
|||
#include "src/solver/MinMaxLinearEquationSolver.h"
|
|||
#include "src/transformer/EndComponentEliminator.h"
|
|||
#include "src/utility/graph.h"
|
|||
#include "src/utility/macros.h"
|
|||
#include "src/utility/vector.h"
|
|||
|
|||
#include "src/exceptions/IllegalFunctionCallException.h"
|
|||
#include "src/exceptions/NotImplementedException.h"
|
|||
|
|||
namespace storm { |
|||
namespace modelchecker { |
|||
namespace multiobjective { |
|||
|
|||
|
|||
template <class SparseModelType> |
|||
SparsePcaaWeightVectorChecker<SparseModelType>::SparsePcaaWeightVectorChecker(SparseModelType const& model, |
|||
std::vector<PcaaObjective<typename SparseModelType::ValueType>> const& objectives, |
|||
storm::storage::BitVector const& actionsWithNegativeReward, |
|||
storm::storage::BitVector const& ecActions, |
|||
storm::storage::BitVector const& possiblyRecurrentStates) : |
|||
model(model), |
|||
objectives(objectives), |
|||
actionsWithNegativeReward(actionsWithNegativeReward), |
|||
ecActions(ecActions), |
|||
possiblyRecurrentStates(possiblyRecurrentStates), |
|||
objectivesWithNoUpperTimeBound(objectives.size()), |
|||
discreteActionRewards(objectives.size()), |
|||
checkHasBeenCalled(false), |
|||
objectiveResults(objectives.size()), |
|||
offsetsToLowerBound(objectives.size()), |
|||
offsetsToUpperBound(objectives.size()) { |
|||
|
|||
// set the unbounded objectives
|
|||
for(uint_fast64_t objIndex = 0; objIndex < objectives.size(); ++objIndex) { |
|||
objectivesWithNoUpperTimeBound.set(objIndex, !objectives[objIndex].upperTimeBound); |
|||
} |
|||
} |
|||
|
|||
|
|||
template <class SparseModelType> |
|||
void SparsePcaaWeightVectorChecker<SparseModelType>::check(std::vector<ValueType> const& weightVector) { |
|||
checkHasBeenCalled=true; |
|||
STORM_LOG_DEBUG("Invoked WeightVectorChecker with weights " << std::endl << "\t" << storm::utility::vector::toString(storm::utility::vector::convertNumericVector<double>(weightVector))); |
|||
std::vector<ValueType> weightedRewardVector(model.getTransitionMatrix().getRowCount(), storm::utility::zero<ValueType>()); |
|||
for(auto objIndex : objectivesWithNoUpperTimeBound) { |
|||
storm::utility::vector::addScaledVector(weightedRewardVector, discreteActionRewards[objIndex], weightVector[objIndex]); |
|||
} |
|||
unboundedWeightedPhase(weightedRewardVector); |
|||
unboundedIndividualPhase(weightVector); |
|||
// Only invoke boundedPhase if necessarry, i.e., if there is at least one objective with a time bound
|
|||
for(auto const& obj : this->objectives) { |
|||
if(obj.lowerTimeBound || obj.upperTimeBound) { |
|||
boundedPhase(weightVector, weightedRewardVector); |
|||
break; |
|||
} |
|||
} |
|||
STORM_LOG_DEBUG("Weight vector check done. Lower bounds for results in initial state: " << storm::utility::vector::toString(storm::utility::vector::convertNumericVector<double>(getLowerBoundsOfInitialStateResults()))); |
|||
} |
|||
|
|||
template <class SparseModelType> |
|||
void SparsePcaaWeightVectorChecker<SparseModelType>::setMaximumLowerUpperBoundGap(ValueType const& value) { |
|||
this->maximumLowerUpperBoundGap = value; |
|||
} |
|||
|
|||
template <class SparseModelType> |
|||
typename SparsePcaaWeightVectorChecker<SparseModelType>::ValueType const& SparsePcaaWeightVectorChecker<SparseModelType>::getMaximumLowerUpperBoundGap() const { |
|||
return this->maximumLowerUpperBoundGap; |
|||
} |
|||
|
|||
template <class SparseModelType> |
|||
std::vector<typename SparsePcaaWeightVectorChecker<SparseModelType>::ValueType> SparsePcaaWeightVectorChecker<SparseModelType>::getLowerBoundsOfInitialStateResults() const { |
|||
STORM_LOG_THROW(checkHasBeenCalled, storm::exceptions::IllegalFunctionCallException, "Tried to retrieve results but check(..) has not been called before."); |
|||
uint_fast64_t initstate = *this->model.getInitialStates().begin(); |
|||
std::vector<ValueType> res; |
|||
res.reserve(this->objectives.size()); |
|||
for(uint_fast64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) { |
|||
res.push_back(this->objectiveResults[objIndex][initstate] + this->offsetsToLowerBound[objIndex]); |
|||
} |
|||
return res; |
|||
} |
|||
|
|||
template <class SparseModelType> |
|||
std::vector<typename SparsePcaaWeightVectorChecker<SparseModelType>::ValueType> SparsePcaaWeightVectorChecker<SparseModelType>::getUpperBoundsOfInitialStateResults() const { |
|||
STORM_LOG_THROW(checkHasBeenCalled, storm::exceptions::IllegalFunctionCallException, "Tried to retrieve results but check(..) has not been called before."); |
|||
uint_fast64_t initstate = *this->model.getInitialStates().begin(); |
|||
std::vector<ValueType> res; |
|||
res.reserve(this->objectives.size()); |
|||
for(uint_fast64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) { |
|||
res.push_back(this->objectiveResults[objIndex][initstate] + this->offsetsToUpperBound[objIndex]); |
|||
} |
|||
return res; |
|||
} |
|||
|
|||
template <class SparseModelType> |
|||
storm::storage::TotalScheduler const& SparsePcaaWeightVectorChecker<SparseModelType>::getScheduler() const { |
|||
STORM_LOG_THROW(this->checkHasBeenCalled, storm::exceptions::IllegalFunctionCallException, "Tried to retrieve results but check(..) has not been called before."); |
|||
for(auto const& obj : this->objectives) { |
|||
STORM_LOG_THROW(!obj.lowerTimeBound && !obj.upperTimeBound, storm::exceptions::NotImplementedException, "Scheduler retrival is not implemented for timeBounded objectives."); |
|||
} |
|||
return scheduler; |
|||
} |
|||
|
|||
template <class SparseModelType> |
|||
void SparsePcaaWeightVectorChecker<SparseModelType>::unboundedWeightedPhase(std::vector<ValueType> const& weightedRewardVector) { |
|||
if(this->objectivesWithNoUpperTimeBound.empty()) { |
|||
this->weightedResult = std::vector<ValueType>(model.getNumberOfStates(), storm::utility::zero<ValueType>()); |
|||
this->scheduler = storm::storage::TotalScheduler(model.getNumberOfStates()); |
|||
return; |
|||
} |
|||
|
|||
|
|||
// Only consider the states from which a transition with non-zero reward is reachable. (The remaining states always have reward zero).
|
|||
storm::storage::BitVector zeroRewardActions = storm::utility::vector::filterZero(weightedRewardVector); |
|||
storm::storage::BitVector nonZeroRewardActions = ~zeroRewardActions; |
|||
storm::storage::BitVector nonZeroRewardStates(model.getNumberOfStates(), false); |
|||
for(uint_fast64_t state = 0; state < model.getNumberOfStates(); ++state){ |
|||
if(nonZeroRewardActions.getNextSetIndex(model.getTransitionMatrix().getRowGroupIndices()[state]) < model.getTransitionMatrix().getRowGroupIndices()[state+1]) { |
|||
nonZeroRewardStates.set(state); |
|||
} |
|||
} |
|||
storm::storage::BitVector subsystemStates = storm::utility::graph::performProbGreater0E(model.getTransitionMatrix(), model.getTransitionMatrix().getRowGroupIndices(), model.getTransitionMatrix().transpose(true), storm::storage::BitVector(model.getNumberOfStates(), true), nonZeroRewardStates); |
|||
|
|||
// Remove neutral end components, i.e., ECs in which no reward is earned.
|
|||
auto ecEliminatorResult = storm::transformer::EndComponentEliminator<ValueType>::transform(model.getTransitionMatrix(), subsystemStates, ecActions & zeroRewardActions, possiblyRecurrentStates); |
|||
|
|||
std::vector<ValueType> subRewardVector(ecEliminatorResult.newToOldRowMapping.size()); |
|||
storm::utility::vector::selectVectorValues(subRewardVector, ecEliminatorResult.newToOldRowMapping, weightedRewardVector); |
|||
std::vector<ValueType> subResult(ecEliminatorResult.matrix.getRowGroupCount()); |
|||
|
|||
storm::solver::GeneralMinMaxLinearEquationSolverFactory<ValueType> solverFactory; |
|||
std::unique_ptr<storm::solver::MinMaxLinearEquationSolver<ValueType>> solver = solverFactory.create(ecEliminatorResult.matrix); |
|||
solver->setOptimizationDirection(storm::solver::OptimizationDirection::Maximize); |
|||
solver->setTrackScheduler(true); |
|||
solver->solveEquations(subResult, subRewardVector); |
|||
|
|||
this->weightedResult = std::vector<ValueType>(model.getNumberOfStates()); |
|||
std::vector<uint_fast64_t> optimalChoices(model.getNumberOfStates()); |
|||
|
|||
transformReducedSolutionToOriginalModel(ecEliminatorResult.matrix, subResult, solver->getScheduler()->getChoices(), ecEliminatorResult.newToOldRowMapping, ecEliminatorResult.oldToNewStateMapping, this->weightedResult, optimalChoices); |
|||
|
|||
this->scheduler = storm::storage::TotalScheduler(std::move(optimalChoices)); |
|||
} |
|||
|
|||
template <class SparseModelType> |
|||
void SparsePcaaWeightVectorChecker<SparseModelType>::unboundedIndividualPhase(std::vector<ValueType> const& weightVector) { |
|||
|
|||
storm::storage::SparseMatrix<ValueType> deterministicMatrix = model.getTransitionMatrix().selectRowsFromRowGroups(this->scheduler.getChoices(), true); |
|||
storm::storage::SparseMatrix<ValueType> deterministicBackwardTransitions = deterministicMatrix.transpose(); |
|||
std::vector<ValueType> deterministicStateRewards(deterministicMatrix.getRowCount()); |
|||
storm::solver::GeneralLinearEquationSolverFactory<ValueType> linearEquationSolverFactory; |
|||
|
|||
// We compute an estimate for the results of the individual objectives which is obtained from the weighted result and the result of the objectives computed so far.
|
|||
// Note that weightedResult = Sum_{i=1}^{n} w_i * objectiveResult_i.
|
|||
std::vector<ValueType> weightedSumOfUncheckedObjectives = weightedResult; |
|||
ValueType sumOfWeightsOfUncheckedObjectives = storm::utility::vector::sum_if(weightVector, objectivesWithNoUpperTimeBound); |
|||
|
|||
for(uint_fast64_t const& objIndex : storm::utility::vector::getSortedIndices(weightVector)) { |
|||
if(objectivesWithNoUpperTimeBound.get(objIndex)){ |
|||
offsetsToLowerBound[objIndex] = storm::utility::zero<ValueType>(); |
|||
offsetsToUpperBound[objIndex] = storm::utility::zero<ValueType>(); |
|||
storm::utility::vector::selectVectorValues(deterministicStateRewards, this->scheduler.getChoices(), model.getTransitionMatrix().getRowGroupIndices(), discreteActionRewards[objIndex]); |
|||
storm::storage::BitVector statesWithRewards = ~storm::utility::vector::filterZero(deterministicStateRewards); |
|||
// As target states, we pick the states from which no reward is reachable.
|
|||
storm::storage::BitVector targetStates = ~storm::utility::graph::performProbGreater0(deterministicBackwardTransitions, storm::storage::BitVector(deterministicMatrix.getRowCount(), true), statesWithRewards); |
|||
|
|||
// Compute the estimate for this objective
|
|||
if(!storm::utility::isZero(weightVector[objIndex])) { |
|||
objectiveResults[objIndex] = weightedSumOfUncheckedObjectives; |
|||
storm::utility::vector::scaleVectorInPlace(objectiveResults[objIndex], storm::utility::one<ValueType>() / sumOfWeightsOfUncheckedObjectives); |
|||
} |
|||
|
|||
// Make sure that the objectiveResult is initialized in some way
|
|||
objectiveResults[objIndex].resize(model.getNumberOfStates(), storm::utility::zero<ValueType>()); |
|||
|
|||
// Invoke the linear equation solver
|
|||
objectiveResults[objIndex] = storm::modelchecker::helper::SparseDtmcPrctlHelper<ValueType>::computeReachabilityRewards(deterministicMatrix, |
|||
deterministicBackwardTransitions, |
|||
deterministicStateRewards, |
|||
targetStates, |
|||
false, //no qualitative checking,
|
|||
linearEquationSolverFactory, |
|||
objectiveResults[objIndex]); |
|||
// Update the estimate for the next objectives.
|
|||
if(!storm::utility::isZero(weightVector[objIndex])) { |
|||
storm::utility::vector::addScaledVector(weightedSumOfUncheckedObjectives, objectiveResults[objIndex], -weightVector[objIndex]); |
|||
sumOfWeightsOfUncheckedObjectives -= weightVector[objIndex]; |
|||
} |
|||
} else { |
|||
objectiveResults[objIndex] = std::vector<ValueType>(model.getNumberOfStates(), storm::utility::zero<ValueType>()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
template <class SparseModelType> |
|||
void SparsePcaaWeightVectorChecker<SparseModelType>::transformReducedSolutionToOriginalModel(storm::storage::SparseMatrix<ValueType> const& reducedMatrix, |
|||
std::vector<ValueType> const& reducedSolution, |
|||
std::vector<uint_fast64_t> const& reducedOptimalChoices, |
|||
std::vector<uint_fast64_t> const& reducedToOriginalChoiceMapping, |
|||
std::vector<uint_fast64_t> const& originalToReducedStateMapping, |
|||
std::vector<ValueType>& originalSolution, |
|||
std::vector<uint_fast64_t>& originalOptimalChoices) const { |
|||
|
|||
storm::storage::BitVector recurrentStates(model.getTransitionMatrix().getRowGroupCount(), false); |
|||
storm::storage::BitVector statesThatShouldStayInTheirEC(model.getTransitionMatrix().getRowGroupCount(), false); |
|||
storm::storage::BitVector statesWithUndefSched(model.getTransitionMatrix().getRowGroupCount(), false); |
|||
|
|||
// Handle all the states for which the choice in the original model is uniquely given by the choice in the reduced model
|
|||
// Also store some information regarding the remaining states
|
|||
for(uint_fast64_t state = 0; state < model.getTransitionMatrix().getRowGroupCount(); ++state) { |
|||
// Check if the state exists in the reduced model, i.e., the mapping retrieves a valid index
|
|||
uint_fast64_t stateInReducedModel = originalToReducedStateMapping[state]; |
|||
if(stateInReducedModel < reducedMatrix.getRowGroupCount()) { |
|||
originalSolution[state] = reducedSolution[stateInReducedModel]; |
|||
uint_fast64_t chosenRowInReducedModel = reducedMatrix.getRowGroupIndices()[stateInReducedModel] + reducedOptimalChoices[stateInReducedModel]; |
|||
uint_fast64_t chosenRowInOriginalModel = reducedToOriginalChoiceMapping[chosenRowInReducedModel]; |
|||
// Check if the state is recurrent, i.e., the chosen row stays inside this EC.
|
|||
bool stateIsRecurrent = possiblyRecurrentStates.get(state); |
|||
for(auto const& entry : model.getTransitionMatrix().getRow(chosenRowInOriginalModel)) { |
|||
stateIsRecurrent &= originalToReducedStateMapping[entry.getColumn()] == stateInReducedModel; |
|||
} |
|||
if(stateIsRecurrent) { |
|||
recurrentStates.set(state); |
|||
statesThatShouldStayInTheirEC.set(state); |
|||
} else { |
|||
// Check if the chosen row originaly belonged to the current state (and not to another state of the EC)
|
|||
if(chosenRowInOriginalModel >= model.getTransitionMatrix().getRowGroupIndices()[state] && |
|||
chosenRowInOriginalModel < model.getTransitionMatrix().getRowGroupIndices()[state+1]) { |
|||
originalOptimalChoices[state] = chosenRowInOriginalModel - model.getTransitionMatrix().getRowGroupIndices()[state]; |
|||
} else { |
|||
statesWithUndefSched.set(state); |
|||
statesThatShouldStayInTheirEC.set(state); |
|||
} |
|||
} |
|||
} else { |
|||
// if the state does not exist in the reduced model, it means that the (weighted) result is always zero, independent of the scheduler.
|
|||
originalSolution[state] = storm::utility::zero<ValueType>(); |
|||
// However, it might be the case that infinite reward is induced for an objective with weight 0.
|
|||
// To avoid this, all possibly recurrent states are made recurrent and the remaining states have to reach a recurrent state with prob. one
|
|||
if(possiblyRecurrentStates.get(state)) { |
|||
recurrentStates.set(state); |
|||
} else { |
|||
statesWithUndefSched.set(state); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Handle recurrent states
|
|||
for(auto state : recurrentStates) { |
|||
bool foundRowForState = false; |
|||
// Find a row with zero rewards that only leads to recurrent states.
|
|||
// If the state should stay in its EC, we also need to make sure that all successors map to the same state in the reduced model
|
|||
uint_fast64_t stateInReducedModel = originalToReducedStateMapping[state]; |
|||
for(uint_fast64_t row = model.getTransitionMatrix().getRowGroupIndices()[state]; row < model.getTransitionMatrix().getRowGroupIndices()[state+1]; ++row) { |
|||
bool rowOnlyLeadsToRecurrentStates = true; |
|||
bool rowStaysInEC = true; |
|||
for( auto const& entry : model.getTransitionMatrix().getRow(row)) { |
|||
rowOnlyLeadsToRecurrentStates &= recurrentStates.get(entry.getColumn()); |
|||
rowStaysInEC &= originalToReducedStateMapping[entry.getColumn()] == stateInReducedModel; |
|||
} |
|||
if(rowOnlyLeadsToRecurrentStates && (rowStaysInEC || !statesThatShouldStayInTheirEC.get(state)) && !actionsWithNegativeReward.get(row)) { |
|||
foundRowForState = true; |
|||
originalOptimalChoices[state] = row - model.getTransitionMatrix().getRowGroupIndices()[state]; |
|||
break; |
|||
} |
|||
} |
|||
STORM_LOG_ASSERT(foundRowForState, "Could not find a suitable choice for a recurrent state."); |
|||
} |
|||
|
|||
// Handle remaining states with still undef. scheduler (either EC states or non-subsystem states)
|
|||
while(!statesWithUndefSched.empty()) { |
|||
for(auto state : statesWithUndefSched) { |
|||
// Try to find a choice such that at least one successor has a defined scheduler.
|
|||
// This way, a non-recurrent state will never become recurrent
|
|||
uint_fast64_t stateInReducedModel = originalToReducedStateMapping[state]; |
|||
for(uint_fast64_t row = model.getTransitionMatrix().getRowGroupIndices()[state]; row < model.getTransitionMatrix().getRowGroupIndices()[state+1]; ++row) { |
|||
bool rowStaysInEC = true; |
|||
bool rowLeadsToDefinedScheduler = false; |
|||
for(auto const& entry : model.getTransitionMatrix().getRow(row)) { |
|||
rowStaysInEC &= ( stateInReducedModel == originalToReducedStateMapping[entry.getColumn()]); |
|||
rowLeadsToDefinedScheduler |= !statesWithUndefSched.get(entry.getColumn()); |
|||
} |
|||
if(rowLeadsToDefinedScheduler && (rowStaysInEC || !statesThatShouldStayInTheirEC.get(state))) { |
|||
originalOptimalChoices[state] = row - model.getTransitionMatrix().getRowGroupIndices()[state]; |
|||
statesWithUndefSched.set(state, false); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
template class SparsePcaaWeightVectorChecker<storm::models::sparse::Mdp<double>>; |
|||
template class SparsePcaaWeightVectorChecker<storm::models::sparse::MarkovAutomaton<double>>; |
|||
#ifdef STORM_HAVE_CARL
|
|||
template class SparsePcaaWeightVectorChecker<storm::models::sparse::Mdp<storm::RationalNumber>>; |
|||
template class SparsePcaaWeightVectorChecker<storm::models::sparse::MarkovAutomaton<storm::RationalNumber>>; |
|||
#endif
|
|||
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,155 @@ |
|||
#ifndef STORM_MODELCHECKER_MULTIOBJECTIVE_PCAA_SPARSEPCAAWEIGHTVECTORCHECKER_H_ |
|||
#define STORM_MODELCHECKER_MULTIOBJECTIVE_PCAA_SPARSEPCAAWEIGHTVECTORCHECKER_H_ |
|||
|
|||
|
|||
#include "src/storage/BitVector.h" |
|||
#include "src/storage/SparseMatrix.h" |
|||
#include "src/storage/TotalScheduler.h" |
|||
#include "src/modelchecker/multiobjective/pcaa/PcaaObjective.h" |
|||
#include "src/utility/vector.h" |
|||
|
|||
namespace storm { |
|||
namespace modelchecker { |
|||
namespace multiobjective { |
|||
|
|||
/*! |
|||
* Helper Class that takes preprocessed Pcaa data 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 SparsePcaaWeightVectorChecker { |
|||
public: |
|||
typedef typename SparseModelType::ValueType ValueType; |
|||
|
|||
/* |
|||
* Creates a weight vextor checker. |
|||
* |
|||
* @param model The (preprocessed) model |
|||
* @param objectives The (preprocessed) objectives |
|||
* @param actionsWithNegativeReward The actions that have negative reward assigned for at least one objective |
|||
* @param ecActions The actions that are part of an EC |
|||
* @param possiblyRecurrentStates The states for which it is posible to visit them infinitely often (without inducing inf. reward) |
|||
* |
|||
*/ |
|||
|
|||
SparsePcaaWeightVectorChecker(SparseModelType const& model, |
|||
std::vector<PcaaObjective<ValueType>> const& objectives, |
|||
storm::storage::BitVector const& actionsWithNegativeReward, |
|||
storm::storage::BitVector const& ecActions, |
|||
storm::storage::BitVector const& possiblyRecurrentStates); |
|||
|
|||
/*! |
|||
* - 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); |
|||
|
|||
/*! |
|||
* Sets the maximum gap that is allowed between the lower and upper bound of the result of some objective. |
|||
*/ |
|||
void setMaximumLowerUpperBoundGap(ValueType const& value); |
|||
|
|||
/*! |
|||
* Retrieves the maximum gap that is allowed between the lower and upper bound of the result of some objective. |
|||
*/ |
|||
ValueType const& getMaximumLowerUpperBoundGap() const; |
|||
|
|||
/*! |
|||
* Retrieves the results of the individual objectives at the initial state of the given model. |
|||
* Note that check(..) has to be called before retrieving results. Otherwise, an exception is thrown. |
|||
* Also note that there is no guarantee that the lower/upper bounds are sound |
|||
* as long as the underlying solution methods are unsound (e.g., standard value iteration). |
|||
*/ |
|||
std::vector<ValueType> getLowerBoundsOfInitialStateResults() const; |
|||
std::vector<ValueType> getUpperBoundsOfInitialStateResults() const; |
|||
|
|||
/*! |
|||
* Retrieves a scheduler that induces the current values |
|||
* Note that check(..) has to be called before retrieving the scheduler. Otherwise, an exception is thrown. |
|||
*/ |
|||
storm::storage::TotalScheduler const& getScheduler() const; |
|||
|
|||
|
|||
protected: |
|||
|
|||
/*! |
|||
* Determines the scheduler that maximizes the weighted reward vector of the unbounded objectives |
|||
* |
|||
* @param weightedRewardVector the weighted rewards (only considering the unbounded objectives) |
|||
*/ |
|||
void unboundedWeightedPhase(std::vector<ValueType> const& weightedRewardVector); |
|||
|
|||
/*! |
|||
* 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 |
|||
* @param weightedRewardVector the weighted rewards considering the unbounded objectives. Will be invalidated after calling this. |
|||
*/ |
|||
virtual void boundedPhase(std::vector<ValueType> const& weightVector, std::vector<ValueType>& weightedRewardVector) = 0; |
|||
|
|||
/*! |
|||
* Transforms the results of a min-max-solver that considers a reduced model (without end components) to a result for the original (unreduced) model |
|||
*/ |
|||
void transformReducedSolutionToOriginalModel(storm::storage::SparseMatrix<ValueType> const& reducedMatrix, |
|||
std::vector<ValueType> const& reducedSolution, |
|||
std::vector<uint_fast64_t> const& reducedOptimalChoices, |
|||
std::vector<uint_fast64_t> const& reducedToOriginalChoiceMapping, |
|||
std::vector<uint_fast64_t> const& originalToReducedStateMapping, |
|||
std::vector<ValueType>& originalSolution, |
|||
std::vector<uint_fast64_t>& originalOptimalChoices) const; |
|||
|
|||
// The (preprocessed) model |
|||
SparseModelType const& model; |
|||
// The (preprocessed) objectives |
|||
std::vector<PcaaObjective<ValueType>> const& objectives; |
|||
|
|||
// The actions that have negative reward assigned for at least one objective |
|||
storm::storage::BitVector actionsWithNegativeReward; |
|||
// The actions that are part of an EC |
|||
storm::storage::BitVector ecActions; |
|||
// The states for which it is allowed to visit them infinitely often |
|||
// Put differently, if one of the states is part of a neutral EC, it is possible to |
|||
// stay in this EC forever (withoud inducing infinite reward for some objective). |
|||
storm::storage::BitVector possiblyRecurrentStates; |
|||
// stores the indices of the objectives for which there is no upper time bound |
|||
storm::storage::BitVector objectivesWithNoUpperTimeBound; |
|||
// stores the (discretized) state action rewards for each objective. |
|||
std::vector<std::vector<ValueType>> discreteActionRewards; |
|||
// stores the maximum gap that is allowed between the lower and upper bound of the result of some objective. |
|||
ValueType maximumLowerUpperBoundGap; |
|||
|
|||
// Memory for the solution of the most recent call of check(..) |
|||
// 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 lower bounds of the results for the individual objectives (w.r.t. all states of the model) |
|||
std::vector<std::vector<ValueType>> objectiveResults; |
|||
// Stores for each objective the distance between the computed result (w.r.t. the initial state) and a lower/upper bound for the actual result. |
|||
// The distances are stored as a (possibly negative) offset that has to be added to to the objectiveResults. |
|||
// Note that there is no guarantee that the lower/upper bounds are sound as long as the underlying solution method is not sound (e.g. standard value iteration). |
|||
std::vector<ValueType> offsetsToLowerBound; |
|||
std::vector<ValueType> offsetsToUpperBound; |
|||
// The scheduler that maximizes the weighted rewards |
|||
storm::storage::TotalScheduler scheduler; |
|||
|
|||
}; |
|||
|
|||
} |
|||
} |
|||
} |
|||
|
|||
#endif /* STORM_MODELCHECKER_MULTIOBJECTIVE_PCAA_SPARSEPCAAWEIGHTVECTORCHECKER_H_ */ |
Write
Preview
Loading…
Cancel
Save
Reference in new issue