You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
260 lines
21 KiB
260 lines
21 KiB
#include "src/modelchecker/multiobjective/helper/SparseMultiObjectivePreprocessor.h"
|
|
|
|
#include "src/models/sparse/Mdp.h"
|
|
#include "src/models/sparse/StandardRewardModel.h"
|
|
#include "src/modelchecker/propositional/SparsePropositionalModelChecker.h"
|
|
#include "src/modelchecker/results/ExplicitQualitativeCheckResult.h"
|
|
#include "src/transformer/StateDuplicator.h"
|
|
#include "src/transformer/SubsystemBuilder.h"
|
|
#include "src/utility/macros.h"
|
|
#include "src/utility/vector.h"
|
|
#include "src/utility/graph.h"
|
|
|
|
#include "src/exceptions/InvalidPropertyException.h"
|
|
#include "src/exceptions/UnexpectedException.h"
|
|
|
|
namespace storm {
|
|
namespace modelchecker {
|
|
namespace helper {
|
|
|
|
template<typename SparseModelType>
|
|
typename SparseMultiObjectivePreprocessor<SparseModelType>::PreprocessorData SparseMultiObjectivePreprocessor<SparseModelType>::preprocess(storm::logic::MultiObjectiveFormula const& originalFormula, SparseModelType const& originalModel, storm::storage::BitVector const& subsystem) {
|
|
|
|
auto subsystemBuilderResult = storm::transformer::SubsystemBuilder<SparseModelType>::transform(originalModel, subsystem);
|
|
PreprocessorData data(originalModel, std::move(*subsystemBuilderResult.model), std::move(subsystemBuilderResult.newToOldStateIndexMapping));
|
|
|
|
//Invoke preprocessing on the individual objectives
|
|
for(auto const& subFormula : originalFormula.getSubFormulas()){
|
|
STORM_LOG_DEBUG("Preprocessing objective " << *subFormula<< ".");
|
|
data.objectives.emplace_back();
|
|
ObjectiveInformation& currentObjective = data.objectives.back();
|
|
currentObjective.originalFormula = subFormula;
|
|
if(currentObjective.originalFormula->isOperatorFormula()) {
|
|
preprocessFormula(currentObjective.originalFormula->asOperatorFormula(), data, currentObjective);
|
|
} else {
|
|
STORM_LOG_THROW(false, storm::exceptions::InvalidPropertyException, "Could not preprocess the subformula " << *subFormula << " of " << originalFormula << " because it is not supported");
|
|
}
|
|
}
|
|
|
|
//We can now remove all original reward models to save some memory
|
|
std::set<std::string> origRewardModels = originalFormula.getReferencedRewardModels();
|
|
for (auto const& rewModel : origRewardModels){
|
|
data.preprocessedModel.removeRewardModel(rewModel);
|
|
}
|
|
|
|
//Set the query type. In case of a numerical query, also set the index of the objective to be optimized.
|
|
storm::storage::BitVector objectivesWithoutThreshold(data.objectives.size());
|
|
for(uint_fast64_t objIndex = 0; objIndex < data.objectives.size(); ++objIndex) {
|
|
objectivesWithoutThreshold.set(objIndex, !data.objectives[objIndex].threshold);
|
|
}
|
|
uint_fast64_t numOfObjectivesWithoutThreshold = objectivesWithoutThreshold.getNumberOfSetBits();
|
|
if(numOfObjectivesWithoutThreshold == 0) {
|
|
data.queryType = PreprocessorData::QueryType::Achievability;
|
|
} else if (numOfObjectivesWithoutThreshold == 1) {
|
|
data.queryType = PreprocessorData::QueryType::Numerical;
|
|
data.indexOfOptimizingObjective = objectivesWithoutThreshold.getNextSetIndex(0);
|
|
} else if (numOfObjectivesWithoutThreshold == data.objectives.size()) {
|
|
data.queryType = PreprocessorData::QueryType::Pareto;
|
|
} else {
|
|
STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "The number of objecties without threshold is not valid. It should be either 0 (achievabilityQuery), 1 (numericalQuery), or " << data.objectives.size() << " (paretoQuery). Got " << numOfObjectivesWithoutThreshold << " instead.");
|
|
}
|
|
return data;
|
|
}
|
|
|
|
template<typename SparseModelType>
|
|
void SparseMultiObjectivePreprocessor<SparseModelType>::preprocessFormula(storm::logic::OperatorFormula const& formula, PreprocessorData& data, ObjectiveInformation& currentObjective) {
|
|
|
|
// Get a unique name for the new reward model.
|
|
currentObjective.rewardModelName = "objective" + std::to_string(data.objectives.size());
|
|
while(data.preprocessedModel.hasRewardModel(currentObjective.rewardModelName)){
|
|
currentObjective.rewardModelName = "_" + currentObjective.rewardModelName;
|
|
}
|
|
|
|
currentObjective.toOriginalValueTransformationFactor = storm::utility::one<ValueType>();
|
|
currentObjective.toOriginalValueTransformationOffset = storm::utility::zero<ValueType>();
|
|
currentObjective.rewardsArePositive = true;
|
|
|
|
bool formulaMinimizes = false;
|
|
if(formula.hasBound()) {
|
|
currentObjective.threshold = storm::utility::convertNumber<ValueType>(formula.getBound().threshold);
|
|
currentObjective.thresholdIsStrict = storm::logic::isStrict(formula.getBound().comparisonType);
|
|
//Note that we minimize for upper bounds since we are looking for the EXISTENCE of a satisfying scheduler
|
|
formulaMinimizes = !storm::logic::isLowerBound(formula.getBound().comparisonType);
|
|
} else if (formula.hasOptimalityType()){
|
|
formulaMinimizes = storm::solver::minimize(formula.getOptimalityType());
|
|
} else {
|
|
STORM_LOG_THROW(false, storm::exceptions::InvalidPropertyException, "Current objective " << formula << " does not specify whether to minimize or maximize");
|
|
}
|
|
if(formulaMinimizes) {
|
|
// We negate all the values so we can consider the maximum for this objective
|
|
// Thus, all objectives will be maximized.
|
|
currentObjective.rewardsArePositive = false;
|
|
currentObjective.toOriginalValueTransformationFactor = -storm::utility::one<ValueType>();
|
|
}
|
|
|
|
if(formula.isProbabilityOperatorFormula()){
|
|
preprocessFormula(formula.asProbabilityOperatorFormula(), data, currentObjective);
|
|
} else if(formula.isRewardOperatorFormula()){
|
|
preprocessFormula(formula.asRewardOperatorFormula(), data, currentObjective);
|
|
} else {
|
|
STORM_LOG_THROW(false, storm::exceptions::InvalidPropertyException, "Could not preprocess the objective " << formula << " because it is not supported");
|
|
}
|
|
|
|
if(currentObjective.threshold) {
|
|
currentObjective.threshold = (currentObjective.threshold.get() - currentObjective.toOriginalValueTransformationOffset) / currentObjective.toOriginalValueTransformationFactor;
|
|
}
|
|
}
|
|
|
|
template<typename SparseModelType>
|
|
void SparseMultiObjectivePreprocessor<SparseModelType>::preprocessFormula(storm::logic::ProbabilityOperatorFormula const& formula, PreprocessorData& data, ObjectiveInformation& currentObjective) {
|
|
if(formula.getSubformula().isUntilFormula()){
|
|
preprocessFormula(formula.getSubformula().asUntilFormula(), data, currentObjective);
|
|
} else if(formula.getSubformula().isBoundedUntilFormula()){
|
|
preprocessFormula(formula.getSubformula().asBoundedUntilFormula(), data, currentObjective);
|
|
} else if(formula.getSubformula().isGloballyFormula()){
|
|
preprocessFormula(formula.getSubformula().asGloballyFormula(), data, currentObjective);
|
|
} else if(formula.getSubformula().isEventuallyFormula()){
|
|
preprocessFormula(formula.getSubformula().asEventuallyFormula(), data, currentObjective);
|
|
} else {
|
|
STORM_LOG_THROW(false, storm::exceptions::InvalidPropertyException, "The subformula of " << formula << " is not supported.");
|
|
}
|
|
}
|
|
|
|
template<typename SparseModelType>
|
|
void SparseMultiObjectivePreprocessor<SparseModelType>::preprocessFormula(storm::logic::RewardOperatorFormula const& formula, PreprocessorData& data, ObjectiveInformation& currentObjective) {
|
|
// Check if the reward model is uniquely specified
|
|
STORM_LOG_THROW((formula.hasRewardModelName() && data.preprocessedModel.hasRewardModel(formula.getRewardModelName()))
|
|
|| data.preprocessedModel.hasUniqueRewardModel(), storm::exceptions::InvalidPropertyException, "The reward model is not unique and the formula " << formula << " does not specify a reward model.");
|
|
if(formula.getSubformula().isEventuallyFormula()){
|
|
preprocessFormula(formula.getSubformula().asEventuallyFormula(), data, currentObjective, formula.getOptionalRewardModelName());
|
|
} else if(formula.getSubformula().isCumulativeRewardFormula()) {
|
|
preprocessFormula(formula.getSubformula().asCumulativeRewardFormula(), data, currentObjective, formula.getOptionalRewardModelName());
|
|
} else if(formula.getSubformula().isTotalRewardFormula()) {
|
|
preprocessFormula(formula.getSubformula().asTotalRewardFormula(), data, currentObjective, formula.getOptionalRewardModelName());
|
|
} else {
|
|
STORM_LOG_THROW(false, storm::exceptions::InvalidPropertyException, "The subformula of " << formula << " is not supported.");
|
|
}
|
|
}
|
|
|
|
template<typename SparseModelType>
|
|
void SparseMultiObjectivePreprocessor<SparseModelType>::preprocessFormula(storm::logic::UntilFormula const& formula, PreprocessorData& data, ObjectiveInformation& currentObjective) {
|
|
CheckTask<storm::logic::Formula> phiTask(formula.getLeftSubformula());
|
|
CheckTask<storm::logic::Formula> psiTask(formula.getRightSubformula());
|
|
storm::modelchecker::SparsePropositionalModelChecker<SparseModelType> mc(data.preprocessedModel);
|
|
STORM_LOG_THROW(mc.canHandle(phiTask) && mc.canHandle(psiTask), storm::exceptions::InvalidPropertyException, "The subformulas of " << formula << " should be propositional.");
|
|
storm::storage::BitVector phiStates = mc.check(phiTask)->asExplicitQualitativeCheckResult().getTruthValuesVector();
|
|
storm::storage::BitVector psiStates = mc.check(psiTask)->asExplicitQualitativeCheckResult().getTruthValuesVector();
|
|
|
|
auto duplicatorResult = storm::transformer::StateDuplicator<SparseModelType>::transform(data.preprocessedModel, ~phiStates | psiStates);
|
|
data.preprocessedModel = std::move(*duplicatorResult.model);
|
|
// duplicatorResult.newToOldStateIndexMapping now reffers to the indices of the model we had before preprocessing this formula.
|
|
// This might not be the actual original model.
|
|
for(auto & originalModelStateIndex : duplicatorResult.newToOldStateIndexMapping){
|
|
originalModelStateIndex = data.newToOldStateIndexMapping[originalModelStateIndex];
|
|
}
|
|
data.newToOldStateIndexMapping = std::move(duplicatorResult.newToOldStateIndexMapping);
|
|
|
|
// build stateAction reward vector that gives (one*transitionProbability) reward whenever a transition leads from the firstCopy to a psiState
|
|
storm::storage::BitVector newPsiStates(data.preprocessedModel.getNumberOfStates(), false);
|
|
for(auto const& oldPsiState : psiStates){
|
|
//note that psiStates are always located in the second copy
|
|
newPsiStates.set(duplicatorResult.secondCopyOldToNewStateIndexMapping[oldPsiState], true);
|
|
}
|
|
std::vector<ValueType> objectiveRewards(data.preprocessedModel.getTransitionMatrix().getRowCount(), storm::utility::zero<ValueType>());
|
|
for (auto const& firstCopyState : duplicatorResult.firstCopy) {
|
|
for (uint_fast64_t row = data.preprocessedModel.getTransitionMatrix().getRowGroupIndices()[firstCopyState]; row < data.preprocessedModel.getTransitionMatrix().getRowGroupIndices()[firstCopyState + 1]; ++row) {
|
|
objectiveRewards[row] = data.preprocessedModel.getTransitionMatrix().getConstrainedRowSum(row, newPsiStates);
|
|
}
|
|
}
|
|
if(!currentObjective.rewardsArePositive) {
|
|
storm::utility::vector::scaleVectorInPlace(objectiveRewards, -storm::utility::one<ValueType>());
|
|
}
|
|
data.preprocessedModel.addRewardModel(currentObjective.rewardModelName, RewardModelType(boost::none, objectiveRewards));
|
|
}
|
|
|
|
template<typename SparseModelType>
|
|
void SparseMultiObjectivePreprocessor<SparseModelType>::preprocessFormula(storm::logic::BoundedUntilFormula const& formula, PreprocessorData& data, ObjectiveInformation& currentObjective) {
|
|
STORM_LOG_THROW(formula.hasDiscreteTimeBound(), storm::exceptions::InvalidPropertyException, "Expected a boundedUntilFormula with a discrete time bound but got " << formula << ".");
|
|
currentObjective.stepBound = formula.getDiscreteTimeBound();
|
|
preprocessFormula(storm::logic::UntilFormula(formula.getLeftSubformula().asSharedPointer(), formula.getRightSubformula().asSharedPointer()), data, currentObjective);
|
|
}
|
|
|
|
template<typename SparseModelType>
|
|
void SparseMultiObjectivePreprocessor<SparseModelType>::preprocessFormula(storm::logic::GloballyFormula const& formula, PreprocessorData& data, ObjectiveInformation& currentObjective) {
|
|
// The formula will be transformed to an until formula for the complementary event.
|
|
// If the original formula minimizes, the complementary one will maximize and vice versa.
|
|
// Hence, the decision whether to consider positive or negative rewards flips.
|
|
currentObjective.rewardsArePositive = !currentObjective.rewardsArePositive;
|
|
// To transform from the value of the preprocessed model back to the value of the original model, we have to add 1 to the result.
|
|
// The transformation factor has already been set correctly
|
|
currentObjective.toOriginalValueTransformationOffset = storm::utility::one<ValueType>();
|
|
if(currentObjective.threshold) {
|
|
// Strict thresholds become non-strict and vice versa
|
|
currentObjective.thresholdIsStrict = !currentObjective.thresholdIsStrict;
|
|
}
|
|
auto negatedSubformula = std::make_shared<storm::logic::UnaryBooleanStateFormula>(storm::logic::UnaryBooleanStateFormula::OperatorType::Not, formula.getSubformula().asSharedPointer());
|
|
preprocessFormula(storm::logic::UntilFormula(storm::logic::Formula::getTrueFormula(), negatedSubformula), data, currentObjective);
|
|
}
|
|
|
|
template<typename SparseModelType>
|
|
void SparseMultiObjectivePreprocessor<SparseModelType>::preprocessFormula(storm::logic::EventuallyFormula const& formula, PreprocessorData& data, ObjectiveInformation& currentObjective, boost::optional<std::string> const& optionalRewardModelName) {
|
|
if(formula.isReachabilityProbabilityFormula()){
|
|
preprocessFormula(storm::logic::UntilFormula(storm::logic::Formula::getTrueFormula(), formula.getSubformula().asSharedPointer()), data, currentObjective);
|
|
return;
|
|
}
|
|
STORM_LOG_THROW(formula.isReachabilityRewardFormula(), storm::exceptions::InvalidPropertyException, "The formula " << formula << " neither considers reachability Probabilities nor reachability rewards");
|
|
|
|
CheckTask<storm::logic::Formula> targetTask(formula.getSubformula());
|
|
storm::modelchecker::SparsePropositionalModelChecker<SparseModelType> mc(data.preprocessedModel);
|
|
STORM_LOG_THROW(mc.canHandle(targetTask), storm::exceptions::InvalidPropertyException, "The subformula of " << formula << " should be propositional.");
|
|
storm::storage::BitVector targetStates = mc.check(targetTask)->asExplicitQualitativeCheckResult().getTruthValuesVector();
|
|
|
|
STORM_LOG_WARN("There is no check for reward finiteness yet"); //TODO
|
|
auto duplicatorResult = storm::transformer::StateDuplicator<SparseModelType>::transform(data.preprocessedModel, targetStates);
|
|
data.preprocessedModel = std::move(*duplicatorResult.model);
|
|
// duplicatorResult.newToOldStateIndexMapping now reffers to the indices of the model we had before preprocessing this formula.
|
|
// This might not be the actual original model.
|
|
for(auto & originalModelStateIndex : duplicatorResult.newToOldStateIndexMapping){
|
|
originalModelStateIndex = data.newToOldStateIndexMapping[originalModelStateIndex];
|
|
}
|
|
data.newToOldStateIndexMapping = std::move(duplicatorResult.newToOldStateIndexMapping);
|
|
|
|
// Add a reward model that gives zero reward to the states of the second copy.
|
|
std::vector<ValueType> objectiveRewards = data.preprocessedModel.getRewardModel(optionalRewardModelName ? optionalRewardModelName.get() : "").getTotalRewardVector(data.preprocessedModel.getTransitionMatrix());
|
|
storm::utility::vector::setVectorValues(objectiveRewards, duplicatorResult.secondCopy, storm::utility::zero<ValueType>());
|
|
storm::storage::BitVector positiveRewards = storm::utility::vector::filterGreaterZero(objectiveRewards);
|
|
storm::storage::BitVector nonNegativeRewards = positiveRewards | storm::utility::vector::filterZero(objectiveRewards);
|
|
STORM_LOG_THROW(nonNegativeRewards.full() || positiveRewards.empty(), storm::exceptions::InvalidPropertyException, "The reward model for the formula " << formula << " has positive and negative rewards which is not supported.");
|
|
if(!currentObjective.rewardsArePositive){
|
|
storm::utility::vector::scaleVectorInPlace(objectiveRewards, -storm::utility::one<ValueType>());
|
|
}
|
|
data.preprocessedModel.addRewardModel(currentObjective.rewardModelName, RewardModelType(boost::none, objectiveRewards));
|
|
}
|
|
|
|
template<typename SparseModelType>
|
|
void SparseMultiObjectivePreprocessor<SparseModelType>::preprocessFormula(storm::logic::CumulativeRewardFormula const& formula, PreprocessorData& data, ObjectiveInformation& currentObjective, boost::optional<std::string> const& optionalRewardModelName) {
|
|
STORM_LOG_THROW(formula.hasDiscreteTimeBound(), storm::exceptions::InvalidPropertyException, "Expected a cumulativeRewardFormula with a discrete time bound but got " << formula << ".");
|
|
currentObjective.stepBound = formula.getDiscreteTimeBound();
|
|
|
|
std::vector<ValueType> objectiveRewards = data.preprocessedModel.getRewardModel(optionalRewardModelName ? optionalRewardModelName.get() : "").getTotalRewardVector(data.preprocessedModel.getTransitionMatrix());
|
|
if(!currentObjective.rewardsArePositive){
|
|
storm::utility::vector::scaleVectorInPlace(objectiveRewards, -storm::utility::one<ValueType>());
|
|
}
|
|
data.preprocessedModel.addRewardModel(currentObjective.rewardModelName, RewardModelType(boost::none, objectiveRewards));
|
|
}
|
|
|
|
template<typename SparseModelType>
|
|
void SparseMultiObjectivePreprocessor<SparseModelType>::preprocessFormula(storm::logic::TotalRewardFormula const& formula, PreprocessorData& data, ObjectiveInformation& currentObjective, boost::optional<std::string> const& optionalRewardModelName) {
|
|
std::vector<ValueType> objectiveRewards = data.preprocessedModel.getRewardModel(optionalRewardModelName ? optionalRewardModelName.get() : "").getTotalRewardVector(data.preprocessedModel.getTransitionMatrix());
|
|
if(!currentObjective.rewardsArePositive){
|
|
storm::utility::vector::scaleVectorInPlace(objectiveRewards, -storm::utility::one<ValueType>());
|
|
}
|
|
data.preprocessedModel.addRewardModel(currentObjective.rewardModelName, RewardModelType(boost::none, objectiveRewards));
|
|
}
|
|
|
|
template class SparseMultiObjectivePreprocessor<storm::models::sparse::Mdp<double>>;
|
|
|
|
|
|
}
|
|
}
|
|
}
|