8 changed files with 1596 additions and 2 deletions
-
253src/storm/modelchecker/multiobjective/deterministicScheds/DeterministicSchedsLpChecker.cpp
-
71src/storm/modelchecker/multiobjective/deterministicScheds/DeterministicSchedsLpChecker.h
-
159src/storm/modelchecker/multiobjective/deterministicScheds/DeterministicSchedsObjectiveHelper.cpp
-
44src/storm/modelchecker/multiobjective/deterministicScheds/DeterministicSchedsObjectiveHelper.h
-
699src/storm/modelchecker/multiobjective/deterministicScheds/DeterministicSchedsParetoExplorer.cpp
-
226src/storm/modelchecker/multiobjective/deterministicScheds/DeterministicSchedsParetoExplorer.h
-
4src/storm/modelchecker/multiobjective/multiObjectiveModelChecking.cpp
-
142src/storm/storage/geometry/PolytopeTree.h
@ -0,0 +1,253 @@ |
|||||
|
#include "storm/modelchecker/multiobjective/deterministicScheds/DeterministicSchedsLpChecker.h"
|
||||
|
#include "storm/models/sparse/MarkovAutomaton.h"
|
||||
|
#include "storm/models/sparse/Mdp.h"
|
||||
|
#include "storm/models/sparse/StandardRewardModel.h"
|
||||
|
#include "storm/utility/solver.h"
|
||||
|
|
||||
|
namespace storm { |
||||
|
namespace modelchecker { |
||||
|
namespace multiobjective { |
||||
|
|
||||
|
template <typename ModelType, typename GeometryValueType> |
||||
|
DeterministicSchedsLpChecker<ModelType, GeometryValueType>::DeterministicSchedsLpChecker(ModelType const& model, std::vector<storm::modelchecker::multiobjective::Objective<ValueType>> const& objectives) : model(model) { |
||||
|
swInit.start(); |
||||
|
initializeObjectiveHelper(objectives); |
||||
|
initializeLpModel(); |
||||
|
swInit.stop(); |
||||
|
} |
||||
|
|
||||
|
template <typename ModelType, typename GeometryValueType> |
||||
|
DeterministicSchedsLpChecker<ModelType, GeometryValueType>::~DeterministicSchedsLpChecker() { |
||||
|
std::cout << "Deterministic Scheds LP CHECKER STATISTICS: " << std::endl; |
||||
|
std::cout << "\t" << swInit << " seconds for initialization" << std::endl; |
||||
|
std::cout << "\t" << swCheck << " seconds for checking, including" << std::endl; |
||||
|
std::cout << "\t\t" << swLpBuild << " seconds for LP building" << std::endl; |
||||
|
std::cout << "\t\t" << swLpSolve << " seconds for LP solving" << std::endl; |
||||
|
std::cout << "\t\t" << swCheckVertices << " seconds for checking the vertices of the convex hull." << std::endl; |
||||
|
std::cout << "\t" << swAux << " seconds for aux stuff" << std::endl; |
||||
|
} |
||||
|
|
||||
|
template <typename ModelType, typename GeometryValueType> |
||||
|
void DeterministicSchedsLpChecker<ModelType, GeometryValueType>::setCurrentWeightVector(std::vector<GeometryValueType> const& weightVector) { |
||||
|
STORM_LOG_ASSERT(!weightVector.empty(), "Setting an empty weight vector is not supported."); |
||||
|
swLpBuild.start(); |
||||
|
if (!currentWeightVector.empty()) { |
||||
|
// Pop information of the current weight vector.
|
||||
|
lpModel->pop(); |
||||
|
lpModel->update(); |
||||
|
currentObjectiveVariables.clear(); |
||||
|
} |
||||
|
currentWeightVector = weightVector; |
||||
|
|
||||
|
lpModel->push(); |
||||
|
// set up objective function for the given weight vector
|
||||
|
for (uint64_t objIndex = 0; objIndex < initialStateResults.size(); ++objIndex) { |
||||
|
currentObjectiveVariables.push_back(lpModel->addUnboundedContinuousVariable("w_" + std::to_string(objIndex), storm::utility::convertNumber<ValueType>(weightVector[objIndex]))); |
||||
|
lpModel->addConstraint("", currentObjectiveVariables.back().getExpression() == initialStateResults[objIndex]); |
||||
|
} |
||||
|
lpModel->update(); |
||||
|
swLpBuild.stop(); |
||||
|
} |
||||
|
|
||||
|
template <typename ModelType, typename GeometryValueType> |
||||
|
std::vector<GeometryValueType> DeterministicSchedsLpChecker<ModelType, GeometryValueType>::check(storm::Environment const& env) { |
||||
|
STORM_LOG_ASSERT(!currentWeightVector.empty(), "Checking invoked before specifying a weight vector."); |
||||
|
STORM_LOG_TRACE("Checking a vertex..."); |
||||
|
swCheck.start(); swCheckVertices.start(); swLpSolve.start(); |
||||
|
lpModel->optimize(); |
||||
|
swLpSolve.stop(); |
||||
|
STORM_LOG_ASSERT(!lpModel->isInfeasible(), "LP result is infeasable."); |
||||
|
STORM_LOG_ASSERT(!lpModel->isUnbounded(), "LP result is unbounded."); |
||||
|
|
||||
|
Point newPoint; |
||||
|
for (auto const& objVar : currentObjectiveVariables) { |
||||
|
newPoint.push_back(storm::utility::convertNumber<GeometryValueType>(lpModel->getContinuousValue(objVar))); |
||||
|
} |
||||
|
|
||||
|
swCheckVertices.stop(); swCheck.stop(); |
||||
|
return newPoint; |
||||
|
} |
||||
|
|
||||
|
template <typename ModelType, typename GeometryValueType> |
||||
|
std::pair<std::vector<std::vector<GeometryValueType>>, std::vector<std::shared_ptr<storm::storage::geometry::Polytope<GeometryValueType>>>> DeterministicSchedsLpChecker<ModelType, GeometryValueType>::check(storm::Environment const& env, storm::storage::geometry::PolytopeTree<GeometryValueType>& polytopeTree, GeometryValueType const& eps) { |
||||
|
std::cout << "Checking " << polytopeTree.toString() << std::endl << "\t"; |
||||
|
swCheck.start(); |
||||
|
STORM_LOG_ASSERT(!currentWeightVector.empty(), "Checking invoked before specifying a weight vector."); |
||||
|
if (polytopeTree.isEmpty()) { |
||||
|
return {{}, {}}; |
||||
|
} |
||||
|
|
||||
|
std::vector<Point> foundPoints; |
||||
|
std::vector<Polytope> infeasableAreas; |
||||
|
checkRecursive(polytopeTree, eps, foundPoints, infeasableAreas); |
||||
|
|
||||
|
swCheck.stop(); |
||||
|
std::cout << " done!" << std::endl; |
||||
|
return {foundPoints, infeasableAreas}; |
||||
|
} |
||||
|
|
||||
|
template <typename ModelType, typename GeometryValueType> |
||||
|
void DeterministicSchedsLpChecker<ModelType, GeometryValueType>::initializeObjectiveHelper(std::vector<storm::modelchecker::multiobjective::Objective<ValueType>> const& objectives) { |
||||
|
objectiveHelper.reserve(objectives.size()); |
||||
|
for (auto const& obj : objectives) { |
||||
|
objectiveHelper.emplace_back(model, obj); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
template <typename ModelType, typename GeometryValueType> |
||||
|
void DeterministicSchedsLpChecker<ModelType, GeometryValueType>::initializeLpModel() { |
||||
|
uint64_t numStates = model.getNumberOfStates(); |
||||
|
lpModel = storm::utility::solver::getLpSolver<ValueType>("model"); |
||||
|
lpModel->setOptimizationDirection(storm::solver::OptimizationDirection::Maximize); |
||||
|
initialStateResults.clear(); |
||||
|
|
||||
|
auto one = lpModel->getConstant(storm::utility::one<ValueType>()); |
||||
|
|
||||
|
// Create choice variables and assert that at least one choice is taken at each state.
|
||||
|
std::vector<storm::expressions::Expression> choiceVars; |
||||
|
choiceVars.reserve(model.getNumberOfChoices()); |
||||
|
for (uint64_t state = 0; state < numStates; ++state) { |
||||
|
uint64_t numChoices = model.getNumberOfChoices(state); |
||||
|
if (numChoices == 1) { |
||||
|
choiceVars.emplace_back(); |
||||
|
} else { |
||||
|
std::vector<storm::expressions::Expression> localChoices; |
||||
|
for (uint64_t choice = 0; choice < numChoices; ++choice) { |
||||
|
localChoices.push_back(lpModel->addBoundedIntegerVariable("c" + std::to_string(state) + "_" + std::to_string(choice), 0, 1).getExpression()); |
||||
|
} |
||||
|
lpModel->addConstraint("", storm::expressions::sum(localChoices).reduceNesting() >= one); |
||||
|
choiceVars.insert(choiceVars.end(), localChoices.begin(), localChoices.end()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (uint64_t objIndex = 0; objIndex < objectiveHelper.size(); ++objIndex) { |
||||
|
auto const& schedulerIndependentStates = objectiveHelper[objIndex].getSchedulerIndependentStateValues(); |
||||
|
// Create state variables
|
||||
|
std::vector<storm::expressions::Expression> stateVars; |
||||
|
stateVars.reserve(numStates); |
||||
|
for (uint64_t state = 0; state < numStates; ++state) { |
||||
|
auto valIt = schedulerIndependentStates.find(state); |
||||
|
if (valIt == schedulerIndependentStates.end()) { |
||||
|
stateVars.push_back(lpModel->addBoundedContinuousVariable("x" + std::to_string(objIndex) + "_" + std::to_string(state), objectiveHelper[objIndex].getLowerValueBoundAtState(state), objectiveHelper[objIndex].getUpperValueBoundAtState(state)).getExpression()); |
||||
|
} else { |
||||
|
stateVars.push_back(lpModel->getConstant(valIt->second)); |
||||
|
} |
||||
|
if (state == *model.getInitialStates().begin()) { |
||||
|
initialStateResults.push_back(stateVars.back()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Create and assert choice values
|
||||
|
auto const& choiceValueOffsets = objectiveHelper[objIndex].getChoiceValueOffsets(); |
||||
|
for (uint64_t state = 0; state < numStates; ++state) { |
||||
|
if (schedulerIndependentStates.find(state) != schedulerIndependentStates.end()) { |
||||
|
continue; |
||||
|
} |
||||
|
uint64_t numChoices = model.getNumberOfChoices(state); |
||||
|
uint64_t choiceOffset = model.getTransitionMatrix().getRowGroupIndices()[state]; |
||||
|
for (uint64_t choice = 0; choice < numChoices; ++choice) { |
||||
|
storm::expressions::Expression choiceValue; |
||||
|
auto valIt = choiceValueOffsets.find(choiceOffset + choice); |
||||
|
if (valIt != choiceValueOffsets.end()) { |
||||
|
choiceValue = lpModel->getConstant(valIt->second); |
||||
|
} |
||||
|
for (auto const& transition : model.getTransitionMatrix().getRow(state, choice)) { |
||||
|
storm::expressions::Expression transitionValue = lpModel->getConstant(transition.getValue()) * stateVars[transition.getColumn()]; |
||||
|
if (choiceValue.isInitialized()) { |
||||
|
choiceValue = choiceValue + transitionValue; |
||||
|
} else { |
||||
|
choiceValue = transitionValue; |
||||
|
} |
||||
|
} |
||||
|
choiceValue = choiceValue.simplify().reduceNesting(); |
||||
|
if (numChoices == 1) { |
||||
|
lpModel->addConstraint("", stateVars[state] == choiceValue); |
||||
|
} else { |
||||
|
uint64_t globalChoiceIndex = model.getTransitionMatrix().getRowGroupIndices()[state] + choice; |
||||
|
storm::expressions::Expression maxDiff = lpModel->getConstant(objectiveHelper[objIndex].getUpperValueBoundAtState(state) - objectiveHelper[objIndex].getLowerValueBoundAtState(state)) * (one - choiceVars[globalChoiceIndex]); |
||||
|
lpModel->addConstraint("", stateVars[state] - choiceValue <= maxDiff); |
||||
|
lpModel->addConstraint("", choiceValue - stateVars[state] <= maxDiff); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
lpModel->update(); |
||||
|
} |
||||
|
|
||||
|
template <typename ModelType, typename GeometryValueType> |
||||
|
void DeterministicSchedsLpChecker<ModelType, GeometryValueType>::checkRecursive(storm::storage::geometry::PolytopeTree <GeometryValueType>& polytopeTree, GeometryValueType const& eps, std::vector<Point>& foundPoints, std::vector<Polytope>& infeasableAreas) { |
||||
|
std::cout << "."; |
||||
|
std::cout.flush(); |
||||
|
STORM_LOG_ASSERT(!polytopeTree.isEmpty(), "Tree node is empty"); |
||||
|
STORM_LOG_ASSERT(!polytopeTree.getPolytope()->isEmpty(), "Tree node is empty."); |
||||
|
STORM_LOG_TRACE("Checking " << polytopeTree.toString()); |
||||
|
|
||||
|
swLpBuild.start(); |
||||
|
lpModel->push(); |
||||
|
// Assert the constraints of the current polytope
|
||||
|
auto nodeConstraints = polytopeTree.getPolytope()->getConstraints(lpModel->getManager(), currentObjectiveVariables); |
||||
|
for (auto const& constr : nodeConstraints) { |
||||
|
lpModel->addConstraint("", constr); |
||||
|
} |
||||
|
lpModel->update(); |
||||
|
swLpBuild.stop(); |
||||
|
|
||||
|
if (polytopeTree.getChildren().empty()) { |
||||
|
// At leaf nodes we need to perform the actual check.
|
||||
|
swLpSolve.start(); |
||||
|
STORM_LOG_TRACE("\tSolving MILP..."); |
||||
|
lpModel->optimize(); |
||||
|
STORM_LOG_TRACE("\tDone solving MILP..."); |
||||
|
swLpSolve.stop(); |
||||
|
|
||||
|
if (lpModel->isInfeasible()) { |
||||
|
infeasableAreas.push_back(polytopeTree.getPolytope()); |
||||
|
polytopeTree.clear(); |
||||
|
} else { |
||||
|
STORM_LOG_ASSERT(!lpModel->isUnbounded(), "LP result is unbounded."); |
||||
|
Point newPoint; |
||||
|
for (auto const& objVar : currentObjectiveVariables) { |
||||
|
newPoint.push_back(storm::utility::convertNumber<GeometryValueType>(lpModel->getContinuousValue(objVar))); |
||||
|
} |
||||
|
auto halfspace = storm::storage::geometry::Halfspace<GeometryValueType>(currentWeightVector, storm::utility::vector::dotProduct(currentWeightVector, newPoint)).invert(); |
||||
|
infeasableAreas.push_back(polytopeTree.getPolytope()->intersection(halfspace)); |
||||
|
if (infeasableAreas.back()->isEmpty()) { |
||||
|
infeasableAreas.pop_back(); |
||||
|
} |
||||
|
swAux.start(); |
||||
|
polytopeTree.setMinus(storm::storage::geometry::Polytope<GeometryValueType>::create({halfspace})); |
||||
|
foundPoints.push_back(newPoint); |
||||
|
polytopeTree.substractDownwardClosure(newPoint, eps); |
||||
|
swAux.stop(); |
||||
|
if (!polytopeTree.isEmpty()) { |
||||
|
checkRecursive(polytopeTree, eps, foundPoints, infeasableAreas); |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
// Traverse all the children.
|
||||
|
for (uint64_t childId = 0; childId < polytopeTree.getChildren().size(); ++childId) { |
||||
|
uint64_t newPointIndex = foundPoints.size(); |
||||
|
checkRecursive(polytopeTree.getChildren()[childId], eps, foundPoints, infeasableAreas); |
||||
|
STORM_LOG_ASSERT(polytopeTree.getChildren()[childId].isEmpty(), "expected empty children."); |
||||
|
// Make the new points known to the right siblings
|
||||
|
for (; newPointIndex < foundPoints.size(); ++newPointIndex) { |
||||
|
for (uint64_t siblingId = childId + 1; siblingId < polytopeTree.getChildren().size(); ++siblingId) { |
||||
|
polytopeTree.getChildren()[siblingId].substractDownwardClosure(foundPoints[newPointIndex], eps); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
// All children are empty now, so this node becomes empty.
|
||||
|
polytopeTree.clear(); |
||||
|
} |
||||
|
swLpBuild.start(); |
||||
|
lpModel->pop(); |
||||
|
swLpBuild.stop(); |
||||
|
} |
||||
|
|
||||
|
template class DeterministicSchedsLpChecker<storm::models::sparse::Mdp<double>, storm::RationalNumber>; |
||||
|
template class DeterministicSchedsLpChecker<storm::models::sparse::Mdp<storm::RationalNumber>, storm::RationalNumber>; |
||||
|
template class DeterministicSchedsLpChecker<storm::models::sparse::MarkovAutomaton<double>, storm::RationalNumber>; |
||||
|
template class DeterministicSchedsLpChecker<storm::models::sparse::MarkovAutomaton<storm::RationalNumber>, storm::RationalNumber>; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,71 @@ |
|||||
|
#pragma once |
||||
|
|
||||
|
#include <vector> |
||||
|
|
||||
|
#include "storm/modelchecker/multiobjective/Objective.h" |
||||
|
#include "storm/modelchecker/multiobjective/deterministicScheds/DeterministicSchedsObjectiveHelper.h" |
||||
|
#include "storm/storage/geometry/Polytope.h" |
||||
|
#include "storm/storage/geometry/PolytopeTree.h" |
||||
|
#include "storm/solver/LpSolver.h" |
||||
|
#include "storm/utility/Stopwatch.h" |
||||
|
|
||||
|
namespace storm { |
||||
|
|
||||
|
class Environment; |
||||
|
|
||||
|
namespace modelchecker { |
||||
|
namespace multiobjective { |
||||
|
|
||||
|
template <typename ModelType, typename GeometryValueType> |
||||
|
class DeterministicSchedsLpChecker { |
||||
|
public: |
||||
|
|
||||
|
typedef typename ModelType::ValueType ValueType; |
||||
|
typedef typename std::shared_ptr<storm::storage::geometry::Polytope<GeometryValueType>> Polytope; |
||||
|
typedef typename std::vector<GeometryValueType> Point; |
||||
|
|
||||
|
DeterministicSchedsLpChecker(ModelType const& model, std::vector<storm::modelchecker::multiobjective::Objective<ValueType>> const& objectives); |
||||
|
~DeterministicSchedsLpChecker(); |
||||
|
|
||||
|
/*! |
||||
|
* Specifies the current direction. |
||||
|
*/ |
||||
|
void setCurrentWeightVector(std::vector<GeometryValueType> const& weightVector); |
||||
|
|
||||
|
/*! |
||||
|
* Optimizes in the currently given direction |
||||
|
* @return some optimal point found in that direction. |
||||
|
*/ |
||||
|
Point check(storm::Environment const& env); |
||||
|
|
||||
|
/*! |
||||
|
* Optimizes in the currently given direction, recursively checks for points in the given area. |
||||
|
* @return all pareto optimal points in the area given by polytopeTree as well as a set of area in which no solution lies (the points might be achievable via some point outside of this area, though) |
||||
|
*/ |
||||
|
std::pair<std::vector<Point>, std::vector<Polytope>> check(storm::Environment const& env, storm::storage::geometry::PolytopeTree<GeometryValueType>& polytopeTree, GeometryValueType const& eps); |
||||
|
|
||||
|
private: |
||||
|
void initializeObjectiveHelper(std::vector<storm::modelchecker::multiobjective::Objective<ValueType>> const& objectives); |
||||
|
void initializeLpModel(); |
||||
|
|
||||
|
void checkRecursive(storm::storage::geometry::PolytopeTree<GeometryValueType>& polytopeTree, GeometryValueType const& eps, std::vector<Point>& foundPoints, std::vector<Polytope>& infeasableAreas); |
||||
|
|
||||
|
ModelType const& model; |
||||
|
std::vector<DeterministicSchedsObjectiveHelper<ModelType>> objectiveHelper; |
||||
|
|
||||
|
std::unique_ptr<storm::solver::LpSolver<ValueType>> lpModel; |
||||
|
std::vector<storm::expressions::Expression> initialStateResults; |
||||
|
std::vector<storm::expressions::Variable> currentObjectiveVariables; |
||||
|
std::vector<GeometryValueType> currentWeightVector; |
||||
|
|
||||
|
storm::utility::Stopwatch swInit; |
||||
|
storm::utility::Stopwatch swCheck; |
||||
|
storm::utility::Stopwatch swCheckVertices; |
||||
|
storm::utility::Stopwatch swLpSolve; |
||||
|
storm::utility::Stopwatch swLpBuild; |
||||
|
storm::utility::Stopwatch swAux; |
||||
|
}; |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,159 @@ |
|||||
|
#include "storm/modelchecker/multiobjective/deterministicScheds/DeterministicSchedsObjectiveHelper.h"
|
||||
|
|
||||
|
#include "storm/models/sparse/MarkovAutomaton.h"
|
||||
|
#include "storm/models/sparse/Mdp.h"
|
||||
|
#include "storm/models/sparse/StandardRewardModel.h"
|
||||
|
#include "storm/modelchecker/propositional/SparsePropositionalModelChecker.h"
|
||||
|
#include "storm/modelchecker/results/ExplicitQualitativeCheckResult.h"
|
||||
|
#include "storm/storage/BitVector.h"
|
||||
|
#include "storm/utility/graph.h"
|
||||
|
#include "storm/utility/FilteredRewardModel.h"
|
||||
|
#include "storm/logic/Formulas.h"
|
||||
|
|
||||
|
#include "storm/exceptions/UnexpectedException.h"
|
||||
|
namespace storm { |
||||
|
namespace modelchecker { |
||||
|
namespace multiobjective { |
||||
|
|
||||
|
|
||||
|
template <typename ModelType> |
||||
|
DeterministicSchedsObjectiveHelper<ModelType>::DeterministicSchedsObjectiveHelper(ModelType const& model, storm::modelchecker::multiobjective::Objective<ValueType> const& objective) : model(model), objective(objective) { |
||||
|
// Intentionally left empty
|
||||
|
} |
||||
|
|
||||
|
template <typename ModelType> |
||||
|
storm::storage::BitVector evaluatePropositionalFormula(ModelType const& model, storm::logic::Formula const& formula) { |
||||
|
storm::modelchecker::SparsePropositionalModelChecker<ModelType> mc(model); |
||||
|
auto checkResult = mc.check(formula); |
||||
|
STORM_LOG_THROW(checkResult && checkResult->isExplicitQualitativeCheckResult(), storm::exceptions::UnexpectedException, "Unexpected type of check result for subformula " << formula << "."); |
||||
|
return checkResult->asExplicitQualitativeCheckResult().getTruthValuesVector(); |
||||
|
} |
||||
|
|
||||
|
template <typename ModelType> |
||||
|
std::map<uint64_t, typename ModelType::ValueType> const& DeterministicSchedsObjectiveHelper<ModelType>::getSchedulerIndependentStateValues() const { |
||||
|
if (!schedulerIndependentStateValues) { |
||||
|
auto const& formula = *objective.formula; |
||||
|
std::map<uint64_t, ValueType> result; |
||||
|
if (formula.isProbabilityOperatorFormula() && formula.getSubformula().isUntilFormula()) { |
||||
|
storm::storage::BitVector phiStates = evaluatePropositionalFormula(model, formula.getSubformula().asUntilFormula().getLeftSubformula()); |
||||
|
storm::storage::BitVector psiStates = evaluatePropositionalFormula(model, formula.getSubformula().asUntilFormula().getRightSubformula()); |
||||
|
auto backwardTransitions = model.getBackwardTransitions(); |
||||
|
{ |
||||
|
storm::storage::BitVector prob1States = storm::utility::graph::performProb1A(model.getTransitionMatrix(), model.getNondeterministicChoiceIndices(), backwardTransitions, phiStates, psiStates); |
||||
|
for (auto const& prob1State : prob1States) { |
||||
|
result[prob1State] = storm::utility::one<ValueType>(); |
||||
|
} |
||||
|
} |
||||
|
{ |
||||
|
storm::storage::BitVector prob0States = storm::utility::graph::performProb0A(backwardTransitions, phiStates, psiStates); |
||||
|
for (auto const& prob0State : prob0States) { |
||||
|
result[prob0State] = storm::utility::zero<ValueType>(); |
||||
|
} |
||||
|
} |
||||
|
} else if (formula.getSubformula().isEventuallyFormula() && (formula.isRewardOperatorFormula() || formula.isTimeOperatorFormula())) { |
||||
|
storm::storage::BitVector rew0States = evaluatePropositionalFormula(model, formula.getSubformula().asEventuallyFormula().getSubformula()); |
||||
|
if (formula.isRewardOperatorFormula()) { |
||||
|
auto const& baseRewardModel = formula.asRewardOperatorFormula().hasRewardModelName() ? model.getRewardModel(formula.asRewardOperatorFormula().getRewardModelName()) : model.getUniqueRewardModel(); |
||||
|
auto rewardModel = storm::utility::createFilteredRewardModel(baseRewardModel, model.isDiscreteTimeModel(), formula.getSubformula().asEventuallyFormula()); |
||||
|
storm::storage::BitVector statesWithoutReward = rewardModel.get().getStatesWithZeroReward(model.getTransitionMatrix()); |
||||
|
rew0States = storm::utility::graph::performProb1A(model.getTransitionMatrix(), model.getNondeterministicChoiceIndices(), model.getBackwardTransitions(), statesWithoutReward, rew0States); |
||||
|
} |
||||
|
for (auto const& rew0State : rew0States) { |
||||
|
result[rew0State] = storm::utility::zero<ValueType>(); |
||||
|
} |
||||
|
} else if (formula.isRewardOperatorFormula() && formula.getSubformula().isTotalRewardFormula()) { |
||||
|
auto const& baseRewardModel = formula.asRewardOperatorFormula().hasRewardModelName() ? model.getRewardModel(formula.asRewardOperatorFormula().getRewardModelName()) : model.getUniqueRewardModel(); |
||||
|
auto rewardModel = storm::utility::createFilteredRewardModel(baseRewardModel, model.isDiscreteTimeModel(), formula.getSubformula().asTotalRewardFormula()); |
||||
|
storm::storage::BitVector statesWithoutReward = rewardModel.get().getStatesWithZeroReward(model.getTransitionMatrix()); |
||||
|
storm::storage::BitVector rew0States = storm::utility::graph::performProbGreater0E(model.getBackwardTransitions(), statesWithoutReward, ~statesWithoutReward); |
||||
|
rew0States.complement(); |
||||
|
for (auto const& rew0State : rew0States) { |
||||
|
result[rew0State] = storm::utility::zero<ValueType>(); |
||||
|
} |
||||
|
} else { |
||||
|
STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "The given formula " << formula << " is not supported."); |
||||
|
} |
||||
|
schedulerIndependentStateValues = std::move(result); |
||||
|
} |
||||
|
return schedulerIndependentStateValues.get(); |
||||
|
} |
||||
|
|
||||
|
template <typename ModelType> |
||||
|
std::map<uint64_t, typename ModelType::ValueType> const& DeterministicSchedsObjectiveHelper<ModelType>::getChoiceValueOffsets() const { |
||||
|
if (!choiceValueOffsets) { |
||||
|
auto const& formula = *objective.formula; |
||||
|
auto const& subformula = formula.getSubformula(); |
||||
|
std::map<uint64_t, ValueType> result; |
||||
|
if (formula.isProbabilityOperatorFormula() && subformula.isUntilFormula()) { |
||||
|
// In this case, there is nothing to be done.
|
||||
|
} else if (formula.isRewardOperatorFormula() && (subformula.isTotalRewardFormula() || subformula.isEventuallyFormula())) { |
||||
|
auto const& baseRewardModel = formula.asRewardOperatorFormula().hasRewardModelName() ? model.getRewardModel(formula.asRewardOperatorFormula().getRewardModelName()) : model.getUniqueRewardModel(); |
||||
|
auto rewardModel = subformula.isEventuallyFormula() ? storm::utility::createFilteredRewardModel(baseRewardModel, model.isDiscreteTimeModel(), subformula.asEventuallyFormula()) : storm::utility::createFilteredRewardModel(baseRewardModel, model.isDiscreteTimeModel(), subformula.asTotalRewardFormula()); |
||||
|
std::vector<ValueType> choiceBasedRewards = rewardModel.get().getTotalRewardVector(model.getTransitionMatrix()); |
||||
|
// Set entries for all non-zero reward choices at states whose value is not already known.
|
||||
|
// This relies on the fact that for goal states in reachability reward formulas, getSchedulerIndependentStateValues()[state] is set to zero.
|
||||
|
auto const& rowGroupIndices = model.getTransitionMatrix().getRowGroupIndices(); |
||||
|
auto const& stateValues = getSchedulerIndependentStateValues(); |
||||
|
for (uint64_t state = 0; state < model.getNumberOfStates(); ++state) { |
||||
|
if (stateValues.find(state) == stateValues.end()) { |
||||
|
for (uint64_t choice = rowGroupIndices[state]; choice < rowGroupIndices[state + 1]; ++choice) { |
||||
|
if (!storm::utility::isZero(choiceBasedRewards[choice])) { |
||||
|
result[choice] = choiceBasedRewards[choice]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} else if (formula.isTimeOperatorFormula() && subformula.isEventuallyFormula()) { |
||||
|
auto const& rowGroupIndices = model.getTransitionMatrix().getRowGroupIndices(); |
||||
|
auto const& stateValues = getSchedulerIndependentStateValues(); |
||||
|
std::vector<ValueType> const* rates = nullptr; |
||||
|
storm::storage::BitVector const* ms = nullptr; |
||||
|
if (model.isOfType(storm::models::ModelType::MarkovAutomaton)) { |
||||
|
auto ma = model.template as<storm::models::sparse::MarkovAutomaton<ValueType>>(); |
||||
|
rates = &ma->getExitRates(); |
||||
|
ms = &ma->getMarkovianStates(); |
||||
|
} |
||||
|
if (model.isOfType(storm::models::ModelType::Mdp)) { |
||||
|
// Set all choice offsets to one, except for the ones at states in scheduerIndependentStateValues.
|
||||
|
for (uint64_t state = 0; state < model.getNumberOfStates(); ++state) { |
||||
|
if (stateValues.find(state) == stateValues.end()) { |
||||
|
ValueType value = storm::utility::one<ValueType>(); |
||||
|
if (rates) { |
||||
|
if (ms->get(state)) { |
||||
|
value /= (*rates)[state]; |
||||
|
} else { |
||||
|
// Nothing to be done for probabilistic states
|
||||
|
continue; |
||||
|
} |
||||
|
} |
||||
|
for (uint64_t choice = rowGroupIndices[state]; choice < rowGroupIndices[state + 1]; ++choice) { |
||||
|
result[choice] = value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "The given formula " << formula << " is not supported."); |
||||
|
} |
||||
|
choiceValueOffsets = std::move(result); |
||||
|
} |
||||
|
return choiceValueOffsets.get(); |
||||
|
} |
||||
|
|
||||
|
template <typename ModelType> |
||||
|
typename ModelType::ValueType const& DeterministicSchedsObjectiveHelper<ModelType>::getUpperValueBoundAtState(uint64_t state) const{ |
||||
|
return objective.upperResultBound.get(); |
||||
|
} |
||||
|
|
||||
|
template <typename ModelType> |
||||
|
typename ModelType::ValueType const& DeterministicSchedsObjectiveHelper<ModelType>::getLowerValueBoundAtState(uint64_t state) const{ |
||||
|
return objective.lowerResultBound.get(); |
||||
|
} |
||||
|
|
||||
|
template class DeterministicSchedsObjectiveHelper<storm::models::sparse::Mdp<double>>; |
||||
|
template class DeterministicSchedsObjectiveHelper<storm::models::sparse::Mdp<storm::RationalNumber>>; |
||||
|
template class DeterministicSchedsObjectiveHelper<storm::models::sparse::MarkovAutomaton<double>>; |
||||
|
template class DeterministicSchedsObjectiveHelper<storm::models::sparse::MarkovAutomaton<storm::RationalNumber>>; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,44 @@ |
|||||
|
#pragma once |
||||
|
|
||||
|
#include <map> |
||||
|
#include <boost/optional.hpp> |
||||
|
|
||||
|
#include "storm/modelchecker/multiobjective/Objective.h" |
||||
|
|
||||
|
|
||||
|
namespace storm { |
||||
|
namespace modelchecker { |
||||
|
namespace multiobjective { |
||||
|
|
||||
|
template <typename ModelType> |
||||
|
class DeterministicSchedsObjectiveHelper { |
||||
|
public: |
||||
|
|
||||
|
typedef typename ModelType::ValueType ValueType; |
||||
|
DeterministicSchedsObjectiveHelper(ModelType const& model, Objective<ValueType> const& objective); |
||||
|
|
||||
|
/*! |
||||
|
* Returns states and values for states that are independent of the scheduler. |
||||
|
*/ |
||||
|
std::map<uint64_t, ValueType> const& getSchedulerIndependentStateValues() const; |
||||
|
|
||||
|
/*! |
||||
|
* Returns offsets of each choice value (e.g., the reward) if non-zero. |
||||
|
* This does not include choices of states with independent state values |
||||
|
*/ |
||||
|
std::map<uint64_t, ValueType> const& getChoiceValueOffsets() const; |
||||
|
|
||||
|
ValueType const& getUpperValueBoundAtState(uint64_t state) const; |
||||
|
ValueType const& getLowerValueBoundAtState(uint64_t state) const; |
||||
|
private: |
||||
|
|
||||
|
mutable boost::optional<std::map<uint64_t, ValueType>> schedulerIndependentStateValues; |
||||
|
mutable boost::optional<std::map<uint64_t, ValueType>> choiceValueOffsets; |
||||
|
|
||||
|
ModelType const& model; |
||||
|
Objective<ValueType> const& objective; |
||||
|
|
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,699 @@ |
|||||
|
#include <sstream>
|
||||
|
#include <algorithm>
|
||||
|
|
||||
|
|
||||
|
#include "storm/modelchecker/multiobjective/deterministicScheds/DeterministicSchedsParetoExplorer.h"
|
||||
|
#include "storm/storage/geometry/coordinates.h"
|
||||
|
#include "storm/models/sparse/MarkovAutomaton.h"
|
||||
|
#include "storm/models/sparse/Mdp.h"
|
||||
|
#include "storm/models/sparse/StandardRewardModel.h"
|
||||
|
#include "storm/modelchecker/multiobjective/MultiObjectivePostprocessing.h"
|
||||
|
#include "storm/modelchecker/results/ExplicitParetoCurveCheckResult.h"
|
||||
|
#include "storm/environment/modelchecker/MultiObjectiveModelCheckerEnvironment.h"
|
||||
|
|
||||
|
#include "storm/utility/export.h"
|
||||
|
#include "storm/utility/solver.h"
|
||||
|
|
||||
|
#include "storm/exceptions/UnexpectedException.h"
|
||||
|
#include "storm/exceptions/InvalidOperationException.h"
|
||||
|
|
||||
|
|
||||
|
namespace storm { |
||||
|
namespace modelchecker { |
||||
|
namespace multiobjective { |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Point::Point(std::vector<GeometryValueType> const& coordinates) : coordinates(coordinates), paretoOptimal(false), onFacet(false) { |
||||
|
STORM_LOG_ASSERT(!this->coordinates.empty(), "Points with dimension 0 are not supported"); |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Point::Point(std::vector<GeometryValueType>&& coordinates) : coordinates(std::move(coordinates)), paretoOptimal(false), onFacet(false) { |
||||
|
STORM_LOG_ASSERT(!this->coordinates.empty(), "Points with dimension 0 are not supported"); |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
std::vector<GeometryValueType>& DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Point::get() { |
||||
|
return coordinates; |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
std::vector<GeometryValueType> const& DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Point::get() const { |
||||
|
return coordinates; |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
uint64_t DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Point::dimension() const { |
||||
|
STORM_LOG_ASSERT(!coordinates.empty(), "Points with dimension 0 are not supported"); |
||||
|
return coordinates.size(); |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
typename DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Point::DominanceResult DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Point::getDominance(Point const& other) const { |
||||
|
STORM_LOG_ASSERT(this->dimension() == other.dimension(), "Non-Equal dimensions of points: [" << this->toString() << "] vs. [" << other.toString() << "]"); |
||||
|
auto thisIt = this->get().begin(); |
||||
|
auto otherIt = other.get().begin(); |
||||
|
auto thisItE = this->get().end(); |
||||
|
|
||||
|
// Find the first entry where the points differ
|
||||
|
while (*thisIt == *otherIt) { |
||||
|
++thisIt; |
||||
|
++otherIt; |
||||
|
if (thisIt == thisItE) { |
||||
|
return DominanceResult::Equal; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (*thisIt > *otherIt) { |
||||
|
// *this might dominate other
|
||||
|
for (++thisIt, ++otherIt; thisIt != thisItE; ++thisIt, ++otherIt) { |
||||
|
if (*thisIt < *otherIt) { |
||||
|
return DominanceResult::Incomparable; |
||||
|
} |
||||
|
} |
||||
|
return DominanceResult::Dominates; |
||||
|
} else { |
||||
|
assert(*thisIt < *otherIt); |
||||
|
// *this might be dominated by other
|
||||
|
for (++thisIt, ++otherIt; thisIt != thisItE; ++thisIt, ++otherIt) { |
||||
|
if (*thisIt > *otherIt) { |
||||
|
return DominanceResult::Incomparable; |
||||
|
} |
||||
|
} |
||||
|
return DominanceResult::Dominated; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
void DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Point::setParetoOptimal(bool value) { |
||||
|
paretoOptimal = value; |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
bool DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Point::isParetoOptimal() const { |
||||
|
return paretoOptimal; |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
void DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Point::setOnFacet(bool value) { |
||||
|
onFacet = value; |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
bool DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Point::liesOnFacet() const { |
||||
|
return onFacet; |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
std::string DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Point::toString(bool convertToDouble) const { |
||||
|
std::stringstream out; |
||||
|
bool first = true; |
||||
|
for (auto const& pi : this->get()) { |
||||
|
if (first) { |
||||
|
first = false; |
||||
|
} else { |
||||
|
out << ", "; |
||||
|
} |
||||
|
if (convertToDouble) { |
||||
|
out << storm::utility::convertNumber<double>(pi); |
||||
|
} else { |
||||
|
out << pi; |
||||
|
} |
||||
|
} |
||||
|
return out.str(); |
||||
|
} |
||||
|
|
||||
|
// template <class SparseModelType, typename GeometryValueType>
|
||||
|
// bool operator<(typename DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Point const& lhs, typename DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Point const& rhs) {
|
||||
|
// STORM_LOG_ASSERT(lhs.dimension() == rhs.dimension(), "Non-Equal dimensions of points: " << lhs << " vs. " << rhs);
|
||||
|
// for (uint64_t i = 0; i < lhs.dimension(); ++i) {
|
||||
|
// if (lhs.get()[i] < rhs.get()[i]) {
|
||||
|
// return true;
|
||||
|
// } else if (lhs.get()[i] != rhs.get()[i]) {
|
||||
|
// return false;
|
||||
|
// }
|
||||
|
// }
|
||||
|
// return false;
|
||||
|
// }
|
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Pointset::Pointset() : currId(1) { |
||||
|
// Intentionally left empty
|
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
boost::optional<typename DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::PointId> DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Pointset::addPoint(Environment const& env, Point&& point) { |
||||
|
|
||||
|
// Find dominated and dominating points
|
||||
|
auto pointsIt = points.begin(); |
||||
|
while (pointsIt != points.end()) { |
||||
|
switch (point.getDominance(pointsIt->second)) { |
||||
|
case Point::DominanceResult::Incomparable: |
||||
|
// Nothing to be done for this point
|
||||
|
++pointsIt; |
||||
|
break; |
||||
|
case Point::DominanceResult::Dominates: |
||||
|
// Found a point in the set that is dominated by the new point, so we erase it
|
||||
|
if (pointsIt->second.isParetoOptimal()) { |
||||
|
STORM_LOG_WARN("Potential precision issues: Found a point that dominates another point which was flagged as pareto optimal. Distance of points is " << std::sqrt(storm::utility::convertNumber<double>(storm::storage::geometry::squaredEuclideanDistance(pointsIt->second.get(), point.get())))); |
||||
|
point.setParetoOptimal(true); |
||||
|
} |
||||
|
if (pointsIt->second.liesOnFacet()) { |
||||
|
// Do not erase points that lie on a facet
|
||||
|
++pointsIt; |
||||
|
} else { |
||||
|
pointsIt = points.erase(pointsIt); |
||||
|
} |
||||
|
break; |
||||
|
case Point::DominanceResult::Dominated: |
||||
|
// The new point is dominated by another point.
|
||||
|
return boost::none; |
||||
|
case Point::DominanceResult::Equal: |
||||
|
if (point.isParetoOptimal()) { |
||||
|
pointsIt->second.setParetoOptimal(); |
||||
|
} |
||||
|
if (point.liesOnFacet()) { |
||||
|
pointsIt->second.setOnFacet(); |
||||
|
} |
||||
|
return pointsIt->first; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (env.modelchecker().multi().isPrintResultsSet()) { |
||||
|
std::cout << "## achievable point: [" << point.toString(true) << "]" << std::endl; |
||||
|
} |
||||
|
|
||||
|
points.emplace_hint(points.end(), currId, std::move(point)); |
||||
|
return currId++; |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
typename DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Point const& DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Pointset::getPoint(PointId const& id) const { |
||||
|
return points.at(id); |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
typename DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Pointset::iterator_type DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Pointset::begin() const { |
||||
|
return points.begin(); |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
typename DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Pointset::iterator_type DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Pointset::end() const { |
||||
|
return points.end(); |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
uint64_t DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Pointset::size() const { |
||||
|
return points.size(); |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
typename DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Polytope DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Pointset::downwardClosure() const { |
||||
|
std::vector<std::vector<GeometryValueType>> pointsAsVector; |
||||
|
pointsAsVector.reserve(size()); |
||||
|
for (auto const& p : points) { |
||||
|
pointsAsVector.push_back(p.second.get()); |
||||
|
} |
||||
|
return storm::storage::geometry::Polytope<GeometryValueType>::createDownwardClosure(std::move(pointsAsVector)); |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
void DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Pointset::collectPointsInPolytope(std::set<PointId>& collectedPoints, Polytope const& polytope) { |
||||
|
for (auto const& p : points) { |
||||
|
if (polytope->contains(p.second.get())) { |
||||
|
collectedPoints.insert(p.first); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
void DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Pointset::printToStream(std::ostream& out, bool includeIDs, bool convertToDouble) { |
||||
|
for (auto const& p : this->points) { |
||||
|
if (includeIDs) { |
||||
|
out << p.first << ": [" << p.second.toString(convertToDouble) << "]" << std::endl; |
||||
|
} else { |
||||
|
out << p.second.toString(convertToDouble) << std::endl; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Facet::Facet(storm::storage::geometry::Halfspace<GeometryValueType> const& halfspace) : halfspace(halfspace) { |
||||
|
// Intentionally left empty
|
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Facet::Facet(storm::storage::geometry::Halfspace<GeometryValueType>&& halfspace) : halfspace(std::move(halfspace)) { |
||||
|
// Intentionally left empty
|
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
storm::storage::geometry::Halfspace<GeometryValueType> const& DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Facet::getHalfspace() const { |
||||
|
return halfspace; |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
void DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Facet::addPoint(PointId const& pointId, Point const& point) { |
||||
|
inducedSimplex = nullptr; |
||||
|
GeometryValueType product = storm::utility::vector::dotProduct(getHalfspace().normalVector(), point.get()); |
||||
|
if (product != getHalfspace().offset()) { |
||||
|
if (product < getHalfspace().offset()) { |
||||
|
STORM_LOG_DEBUG("The point on the facet actually has distance " << storm::utility::convertNumber<double>(getHalfspace().euclideanDistance(point.get()))); |
||||
|
} else { |
||||
|
STORM_LOG_DEBUG("Halfspace of facet is shifted by " << storm::utility::convertNumber<double>(getHalfspace().euclideanDistance(point.get())) << " to capture all points that are supposed to lie on the facet."); |
||||
|
halfspace.offset() = product; |
||||
|
} |
||||
|
} |
||||
|
paretoPointsOnFacet.push_back(pointId); |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
std::vector<typename DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::PointId> const& DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Facet::getPoints() const { |
||||
|
return paretoPointsOnFacet; |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
uint64_t DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Facet::getNumberOfPoints() const { |
||||
|
return paretoPointsOnFacet.size(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
typename DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Polytope const& DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::Facet::getInducedSimplex(Pointset const& pointset, std::vector<GeometryValueType> const& referenceCoordinates) { |
||||
|
if (!inducedSimplex) { |
||||
|
std::vector<std::vector<GeometryValueType>> vertices = {referenceCoordinates}; |
||||
|
for (auto const& pId : paretoPointsOnFacet) { |
||||
|
vertices.push_back(pointset.getPoint(pId).get()); |
||||
|
} |
||||
|
// This facet might lie at the 'border', which means that the downward closure has to be taken in some directions
|
||||
|
storm::storage::BitVector dimensionsForDownwardClosure = storm::utility::vector::filterZero(this->halfspace.normalVector()); |
||||
|
STORM_LOG_ASSERT(dimensionsForDownwardClosure.getNumberOfSetBits() + vertices.size() >= halfspace.normalVector().size() + 1, "The number of points on the facet is insufficient"); |
||||
|
if (dimensionsForDownwardClosure.empty()) { |
||||
|
inducedSimplex = storm::storage::geometry::Polytope<GeometryValueType>::create(vertices); |
||||
|
} else { |
||||
|
inducedSimplex = storm::storage::geometry::Polytope<GeometryValueType>::createSelectiveDownwardClosure(vertices, dimensionsForDownwardClosure); |
||||
|
} |
||||
|
} |
||||
|
return inducedSimplex; |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::FacetAnalysisContext::FacetAnalysisContext(Facet& f) : facet(f) { |
||||
|
// Intentionally left empty
|
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::DeterministicSchedsParetoExplorer(preprocessing::SparseMultiObjectivePreprocessorResult<SparseModelType>& preprocessorResult) : model(preprocessorResult.preprocessedModel), objectives(preprocessorResult.objectives) { |
||||
|
originalModelInitialState = *preprocessorResult.originalModel.getInitialStates().begin(); |
||||
|
lpChecker = std::make_shared<DeterministicSchedsLpChecker<SparseModelType, GeometryValueType>>(*model, objectives); |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
std::unique_ptr<CheckResult> DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::check(Environment const& env) { |
||||
|
|
||||
|
clean(); |
||||
|
initializeFacets(env); |
||||
|
while (!unprocessedFacets.empty()) { |
||||
|
Facet f = std::move(unprocessedFacets.front()); |
||||
|
unprocessedFacets.pop(); |
||||
|
processFacet(env, f); |
||||
|
} |
||||
|
|
||||
|
std::vector<std::vector<ModelValueType>>paretoPoints; |
||||
|
paretoPoints.reserve(pointset.size()); |
||||
|
for (auto const& p : pointset) { |
||||
|
paretoPoints.push_back(storm::utility::vector::convertNumericVector<ModelValueType>(transformObjectiveValuesToOriginal(objectives, p.second.get()))); |
||||
|
} |
||||
|
return std::make_unique<storm::modelchecker::ExplicitParetoCurveCheckResult<ModelValueType>>(originalModelInitialState, std::move(paretoPoints), |
||||
|
nullptr, nullptr); |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
void DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::clean() { |
||||
|
pointset = Pointset(); |
||||
|
unprocessedFacets = std::queue<Facet>(); |
||||
|
overApproximation = storm::storage::geometry::Polytope<GeometryValueType>::createUniversalPolytope(); |
||||
|
unachievableAreas.clear(); |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
void DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::addHalfspaceToOverApproximation(Environment const& env, std::vector<GeometryValueType> const& normalVector, Point const& pointOnHalfspace) { |
||||
|
GeometryValueType offset = storm::utility::vector::dotProduct(normalVector, pointOnHalfspace.get()); |
||||
|
if (env.modelchecker().multi().isPrintResultsSet()) { |
||||
|
std::cout << "## unachievable halfspace: ["; |
||||
|
bool first = true; |
||||
|
for (auto const& xi : normalVector) { |
||||
|
if (first) { |
||||
|
first = false; |
||||
|
} else { |
||||
|
std::cout << ","; |
||||
|
} |
||||
|
std::cout << storm::utility::convertNumber<double>(xi); |
||||
|
} |
||||
|
std::cout << "];[" << storm::utility::convertNumber<double>(offset) << "]" << std::endl; |
||||
|
} |
||||
|
storm::storage::geometry::Halfspace<GeometryValueType> overApproxHalfspace(normalVector, offset); |
||||
|
overApproximation = overApproximation->intersection(overApproxHalfspace); |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
void DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::addUnachievableArea(Environment const& env, Polytope const& area) { |
||||
|
if (env.modelchecker().multi().isPrintResultsSet()) { |
||||
|
std::vector<std::vector<GeometryValueType>> vertices; |
||||
|
if (objectives.size() == 2) { |
||||
|
vertices = area->getVerticesInClockwiseOrder(); |
||||
|
} else { |
||||
|
vertices = area->getVertices(); |
||||
|
} |
||||
|
std::cout << "## unachievable polytope: "; |
||||
|
bool firstVertex = true; |
||||
|
for (auto const& v : vertices) { |
||||
|
if (firstVertex) { |
||||
|
firstVertex = false; |
||||
|
} else { |
||||
|
std::cout << ";"; |
||||
|
} |
||||
|
std::cout << "["; |
||||
|
bool firstEntry = true; |
||||
|
for (auto const& vi : v) { |
||||
|
if (firstEntry) { |
||||
|
firstEntry = false; |
||||
|
} else { |
||||
|
std::cout << ","; |
||||
|
} |
||||
|
std::cout << storm::utility::convertNumber<double>(vi); |
||||
|
} |
||||
|
std::cout << "]"; |
||||
|
} |
||||
|
std::cout << std::endl; |
||||
|
} |
||||
|
unachievableAreas.push_back(area); |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
void DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::initializeFacets(Environment const& env) { |
||||
|
for (uint64_t objIndex = 0; objIndex < objectives.size(); ++objIndex) { |
||||
|
std::vector<GeometryValueType> weightVector(objectives.size(), storm::utility::zero<ModelValueType>()); |
||||
|
if (storm::solver::minimize(objectives[objIndex].formula->getOptimalityType())) { |
||||
|
weightVector[objIndex] = -storm::utility::one<GeometryValueType>(); |
||||
|
} else { |
||||
|
weightVector[objIndex] = storm::utility::one<GeometryValueType>(); |
||||
|
} |
||||
|
lpChecker->setCurrentWeightVector(weightVector); |
||||
|
auto point = lpChecker->check(env); |
||||
|
for (uint64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) { |
||||
|
if (storm::solver::minimize(objectives[objIndex].formula->getOptimalityType())) { |
||||
|
point[objIndex] *= -storm::utility::one<ModelValueType>(); |
||||
|
} |
||||
|
Point p(storm::utility::vector::convertNumericVector<GeometryValueType>(point)); |
||||
|
p.setOnFacet(); |
||||
|
// Adapt the overapproximation
|
||||
|
std::vector<GeometryValueType> normalVector(objectives.size(), storm::utility::zero<GeometryValueType>()); |
||||
|
normalVector[objIndex] = storm::utility::one<GeometryValueType>(); |
||||
|
addHalfspaceToOverApproximation(env, normalVector, p); |
||||
|
pointset.addPoint(env, std::move(p)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
auto initialHalfspaces = pointset.downwardClosure()->getHalfspaces(); |
||||
|
for (auto& h : initialHalfspaces) { |
||||
|
Facet f(std::move(h)); |
||||
|
for (auto const& p : pointset) { |
||||
|
if (f.getHalfspace().isPointOnBoundary(p.second.get())) { |
||||
|
f.addPoint(p.first, p.second); |
||||
|
} |
||||
|
} |
||||
|
STORM_LOG_ASSERT(std::count(f.getHalfspace().normalVector().begin(), f.getHalfspace().normalVector().end(), storm::utility::zero<GeometryValueType>()) + f.getNumberOfPoints() == objectives.size(), "Unexpected number of points on facet."); |
||||
|
if (!checkFacetPrecision(env, f)) { |
||||
|
unprocessedFacets.push(std::move(f)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
std::vector<GeometryValueType> DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::getReferenceCoordinates() const { |
||||
|
std::vector<GeometryValueType> result; |
||||
|
for (auto const& obj : objectives) { |
||||
|
ModelValueType value = storm::solver::minimize(obj.formula->getOptimalityType()) ? obj.upperResultBound.get() : obj.lowerResultBound.get(); |
||||
|
result.push_back(storm::utility::convertNumber<GeometryValueType>(value)); |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
bool DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::checkFacetPrecision(Environment const& env, Facet& f) { |
||||
|
// TODO:
|
||||
|
return false; |
||||
|
/*
|
||||
|
auto const& inducedSimplex = f.getInducedSimplex(pointset); |
||||
|
|
||||
|
GeometryValueType eps = storm::utility::convertNumber<GeometryValueType>(env.modelchecker().multi().getPrecision()); |
||||
|
// get a polytope that contains exactly the points y, such that y+eps is in the induced simplex
|
||||
|
std::vector<GeometryValueType> offsetVector(objectives.size(), -eps); |
||||
|
auto shiftedSimplex = inducedSimplex->shift(offsetVector); |
||||
|
|
||||
|
// If the intersection of both polytopes is empty, it means that there can not be a point y in the simplex
|
||||
|
// such that y-eps is also in the simplex, i.e., the facet is already precise enough.
|
||||
|
return inducedSimplex->intersection(shiftedSimplex)->isEmpty(); |
||||
|
*/ |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
bool DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::checkFacetPrecision(Environment const& env, Facet& f, std::set<PointId> const& collectedSimplexPoints) { |
||||
|
assert(false); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
void DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::processFacet(Environment const& env, Facet& f) { |
||||
|
lpChecker->setCurrentWeightVector(f.getHalfspace().normalVector()); |
||||
|
|
||||
|
if (optimizeAndSplitFacet(env,f)) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
GeometryValueType eps = storm::utility::convertNumber<GeometryValueType>(env.modelchecker().multi().getPrecision()); |
||||
|
eps += eps; // The unknown area (box) can actually have size 2*eps
|
||||
|
storm::storage::geometry::PolytopeTree<GeometryValueType> polytopeTree(f.getInducedSimplex(pointset, getReferenceCoordinates())); |
||||
|
for (auto const& point : pointset) { |
||||
|
polytopeTree.substractDownwardClosure(point.second.get(), eps); |
||||
|
if (polytopeTree.isEmpty()) { |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
if (!polytopeTree.isEmpty()) { |
||||
|
auto res = lpChecker->check(env, polytopeTree, eps); |
||||
|
for (auto const& infeasableArea : res.second) { |
||||
|
addUnachievableArea(env, infeasableArea); |
||||
|
} |
||||
|
for (auto& achievablePoint : res.first) { |
||||
|
pointset.addPoint(env, Point(std::move(achievablePoint))); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
typename DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::FacetAnalysisContext DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::createAnalysisContext(Environment const& env, Facet& f) { |
||||
|
|
||||
|
FacetAnalysisContext res(f); |
||||
|
/*
|
||||
|
res.expressionManager = std::make_shared<storm::expressions::ExpressionManager>(); |
||||
|
res.smtSolver = storm::utility::solver::SmtSolverFactory().create(*res.expressionManager); |
||||
|
|
||||
|
Polytope const& inducedPoly = res.facet.getInducedSimplex(pointset); |
||||
|
|
||||
|
res.x = inducedPoly->declareVariables(*res.expressionManager, "x"); |
||||
|
for (auto const& c : inducedPoly->getConstraints(*res.expressionManager, res.x)) { |
||||
|
res.smtSolver->add(c); |
||||
|
} |
||||
|
|
||||
|
res.xMinusEps = inducedPoly->declareVariables(*res.expressionManager, "y"); |
||||
|
for (auto const& c : inducedPoly->getConstraints(*res.expressionManager, res.xMinusEps)) { |
||||
|
res.smtSolver->add(c); |
||||
|
} |
||||
|
|
||||
|
auto eps = res.expressionManager->rational(env.modelchecker().multi().getPrecision()); |
||||
|
storm::expressions::Expression xme; |
||||
|
for (uint64_t i = 0; i < res.x.size(); ++i) { |
||||
|
storm::expressions::Expression subExpr = (res.xMinusEps[i].getExpression() == res.x[i].getExpression() - eps); |
||||
|
if (i == 0) { |
||||
|
xme = subExpr; |
||||
|
} else { |
||||
|
xme = xme && subExpr; |
||||
|
} |
||||
|
} |
||||
|
res.smtSolver->add(xme); |
||||
|
*/ |
||||
|
return res; |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
bool DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::optimizeAndSplitFacet(Environment const& env, Facet& f) { |
||||
|
// Obtain the correct weight vector
|
||||
|
auto weightVector = storm::utility::vector::convertNumericVector<ModelValueType>(f.getHalfspace().normalVector()); |
||||
|
bool weightVectorYieldsParetoOptimalPoint = !storm::utility::vector::hasZeroEntry(weightVector); |
||||
|
for (uint64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) { |
||||
|
if (storm::solver::minimize(objectives[objIndex].formula->getOptimalityType())) { |
||||
|
weightVector[objIndex] *= -storm::utility::one<ModelValueType>(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Invoke optimization and insert the explored points
|
||||
|
boost::optional<PointId> optPointId; |
||||
|
auto point = lpChecker->check(env); |
||||
|
for (uint64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) { |
||||
|
if (storm::solver::minimize(objectives[objIndex].formula->getOptimalityType())) { |
||||
|
point[objIndex] *= -storm::utility::one<ModelValueType>(); |
||||
|
} |
||||
|
} |
||||
|
Point p(point); |
||||
|
p.setParetoOptimal(weightVectorYieldsParetoOptimalPoint); |
||||
|
p.setOnFacet(); |
||||
|
addHalfspaceToOverApproximation(env, f.getHalfspace().normalVector(), p); |
||||
|
optPointId = pointset.addPoint(env, std::move(p)); |
||||
|
|
||||
|
// Potentially generate new facets
|
||||
|
if (optPointId) { |
||||
|
auto const& optPoint = pointset.getPoint(*optPointId); |
||||
|
// TODO: this check might suffer from numerical errors. Check how much this would hurt us.
|
||||
|
if (f.getHalfspace().contains(optPoint.get())) { |
||||
|
// The point is contained in the halfspace which means that no more splitting is possible.
|
||||
|
return false; |
||||
|
} else { |
||||
|
// Found a new Pareto optimal point -> generate new facets
|
||||
|
std::vector<std::vector<GeometryValueType>> vertices; |
||||
|
vertices.push_back(optPoint.get()); |
||||
|
for (auto const& pId : f.getPoints()) { |
||||
|
vertices.push_back(pointset.getPoint(pId).get()); |
||||
|
} |
||||
|
auto newHalfspaceCandidates = storm::storage::geometry::Polytope<GeometryValueType>::createSelectiveDownwardClosure(vertices, storm::utility::vector::filterZero(f.getHalfspace().normalVector()))->getHalfspaces(); |
||||
|
for (auto& h : newHalfspaceCandidates) { |
||||
|
if (!storm::utility::vector::hasNegativeEntry(h.normalVector())) { |
||||
|
STORM_LOG_ASSERT(h.isPointOnBoundary(optPoint.get()), "Unexpected facet found while splitting."); |
||||
|
Facet fNew(std::move(h)); |
||||
|
fNew.addPoint(optPointId.get(), optPoint); |
||||
|
auto vertexIt = vertices.begin(); |
||||
|
++vertexIt; |
||||
|
for (auto const& pId : f.getPoints()) { |
||||
|
assert(pointset.getPoint(pId).get() == *vertexIt); |
||||
|
if (fNew.getHalfspace().isPointOnBoundary(*vertexIt)) { |
||||
|
fNew.addPoint(pId, pointset.getPoint(pId)); |
||||
|
} |
||||
|
++vertexIt; |
||||
|
} |
||||
|
assert(vertexIt == vertices.end()); |
||||
|
if (!checkFacetPrecision(env, fNew)) { |
||||
|
unprocessedFacets.push(std::move(fNew)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
} else { |
||||
|
// If the 'optimal point' was dominated by an existing point, we can not split the facet any further.
|
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
bool DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::addNewSimplexPoint(FacetAnalysisContext& context, PointId const& pointId, bool performCheck) { |
||||
|
auto const& coordinates = pointset.getPoint(pointId).get(); |
||||
|
storm::expressions::Expression pointAchievesXMinusEps; |
||||
|
for (uint64_t i = 0; i < coordinates.size(); ++i) { |
||||
|
storm::expressions::Expression subExpr = context.xMinusEps[i] <= context.expressionManager->rational(coordinates[i]); |
||||
|
if (i == 0) { |
||||
|
pointAchievesXMinusEps = subExpr; |
||||
|
} else { |
||||
|
pointAchievesXMinusEps = pointAchievesXMinusEps && subExpr; |
||||
|
} |
||||
|
} |
||||
|
context.smtSolver->add(!pointAchievesXMinusEps); |
||||
|
if (performCheck) { |
||||
|
auto smtCheckResult = context.smtSolver->check(); |
||||
|
if (smtCheckResult == storm::solver::SmtSolver::CheckResult::Unsat) { |
||||
|
// For all points x, there is a cached point that dominates or is equal to (x-eps).
|
||||
|
// (we have a constraint pointAchievesXminusEps that does not not hold (double negation)
|
||||
|
return true; |
||||
|
} else { |
||||
|
STORM_LOG_THROW(smtCheckResult == storm::solver::SmtSolver::CheckResult::Sat, storm::exceptions::UnexpectedException, "The smt solver did not yield sat or unsat."); |
||||
|
// there is a point x such that (x-eps) is not dominated by or equal to a cached point.
|
||||
|
return false; |
||||
|
} |
||||
|
} else { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
void DeterministicSchedsParetoExplorer<SparseModelType, GeometryValueType>::exportPlotOfCurrentApproximation(Environment const& env) { |
||||
|
/*
|
||||
|
STORM_LOG_ERROR_COND(objectives.size()==2, "Exporting plot requested but this is only implemented for the two-dimensional case."); |
||||
|
|
||||
|
auto transformedUnderApprox = transformPolytopeToOriginalModel(underApproximation); |
||||
|
auto transformedOverApprox = transformPolytopeToOriginalModel(overApproximation); |
||||
|
|
||||
|
// Get pareto points as well as a hyperrectangle that is used to guarantee that the resulting polytopes are bounded.
|
||||
|
storm::storage::geometry::Hyperrectangle<GeometryValueType> boundaries(std::vector<GeometryValueType>(objectives.size(), storm::utility::zero<GeometryValueType>()), std::vector<GeometryValueType>(objectives.size(), storm::utility::zero<GeometryValueType>())); |
||||
|
std::vector<std::vector<GeometryValueType>> paretoPoints; |
||||
|
paretoPoints.reserve(refinementSteps.size()); |
||||
|
for(auto const& step : refinementSteps) { |
||||
|
paretoPoints.push_back(transformPointToOriginalModel(step.lowerBoundPoint)); |
||||
|
boundaries.enlarge(paretoPoints.back()); |
||||
|
} |
||||
|
auto underApproxVertices = transformedUnderApprox->getVertices(); |
||||
|
for(auto const& v : underApproxVertices) { |
||||
|
boundaries.enlarge(v); |
||||
|
} |
||||
|
auto overApproxVertices = transformedOverApprox->getVertices(); |
||||
|
for(auto const& v : overApproxVertices) { |
||||
|
boundaries.enlarge(v); |
||||
|
} |
||||
|
|
||||
|
//Further enlarge the boundaries a little
|
||||
|
storm::utility::vector::scaleVectorInPlace(boundaries.lowerBounds(), GeometryValueType(15) / GeometryValueType(10)); |
||||
|
storm::utility::vector::scaleVectorInPlace(boundaries.upperBounds(), GeometryValueType(15) / GeometryValueType(10)); |
||||
|
|
||||
|
auto boundariesAsPolytope = boundaries.asPolytope(); |
||||
|
std::vector<std::string> columnHeaders = {"x", "y"}; |
||||
|
|
||||
|
std::vector<std::vector<double>> pointsForPlotting; |
||||
|
if (env.modelchecker().multi().getPlotPathUnderApproximation()) { |
||||
|
underApproxVertices = transformedUnderApprox->intersection(boundariesAsPolytope)->getVerticesInClockwiseOrder(); |
||||
|
pointsForPlotting.reserve(underApproxVertices.size()); |
||||
|
for(auto const& v : underApproxVertices) { |
||||
|
pointsForPlotting.push_back(storm::utility::vector::convertNumericVector<double>(v)); |
||||
|
} |
||||
|
storm::utility::exportDataToCSVFile<double, std::string>(env.modelchecker().multi().getPlotPathUnderApproximation().get(), pointsForPlotting, columnHeaders); |
||||
|
} |
||||
|
|
||||
|
if (env.modelchecker().multi().getPlotPathOverApproximation()) { |
||||
|
pointsForPlotting.clear(); |
||||
|
overApproxVertices = transformedOverApprox->intersection(boundariesAsPolytope)->getVerticesInClockwiseOrder(); |
||||
|
pointsForPlotting.reserve(overApproxVertices.size()); |
||||
|
for(auto const& v : overApproxVertices) { |
||||
|
pointsForPlotting.push_back(storm::utility::vector::convertNumericVector<double>(v)); |
||||
|
} |
||||
|
storm::utility::exportDataToCSVFile<double, std::string>(env.modelchecker().multi().getPlotPathOverApproximation().get(), pointsForPlotting, columnHeaders); |
||||
|
} |
||||
|
|
||||
|
if (env.modelchecker().multi().getPlotPathParetoPoints()) { |
||||
|
pointsForPlotting.clear(); |
||||
|
pointsForPlotting.reserve(paretoPoints.size()); |
||||
|
for(auto const& v : paretoPoints) { |
||||
|
pointsForPlotting.push_back(storm::utility::vector::convertNumericVector<double>(v)); |
||||
|
} |
||||
|
storm::utility::exportDataToCSVFile<double, std::string>(env.modelchecker().multi().getPlotPathParetoPoints().get(), pointsForPlotting, columnHeaders); |
||||
|
} |
||||
|
}; |
||||
|
*/ |
||||
|
} |
||||
|
|
||||
|
template class DeterministicSchedsParetoExplorer<storm::models::sparse::Mdp<double>, storm::RationalNumber>; |
||||
|
template class DeterministicSchedsParetoExplorer<storm::models::sparse::Mdp<storm::RationalNumber>, storm::RationalNumber>; |
||||
|
template class DeterministicSchedsParetoExplorer<storm::models::sparse::MarkovAutomaton<double>, storm::RationalNumber>; |
||||
|
template class DeterministicSchedsParetoExplorer<storm::models::sparse::MarkovAutomaton<storm::RationalNumber>, storm::RationalNumber>; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,226 @@ |
|||||
|
#pragma once |
||||
|
|
||||
|
#include <memory> |
||||
|
#include <queue> |
||||
|
|
||||
|
#include "storm/modelchecker/multiobjective/preprocessing/SparseMultiObjectivePreprocessorResult.h" |
||||
|
#include "storm/modelchecker/multiobjective/deterministicScheds/DeterministicSchedsLpChecker.h" |
||||
|
|
||||
|
#include "storm/storage/geometry/Polytope.h" |
||||
|
#include "storm/storage/geometry/Halfspace.h" |
||||
|
#include "storm/modelchecker/results/CheckResult.h" |
||||
|
#include "storm/storage/expressions/ExpressionManager.h" |
||||
|
#include "storm/solver/SmtSolver.h" |
||||
|
|
||||
|
namespace storm { |
||||
|
|
||||
|
class Environment; |
||||
|
|
||||
|
namespace modelchecker { |
||||
|
namespace multiobjective { |
||||
|
|
||||
|
template <class SparseModelType, typename GeometryValueType> |
||||
|
class DeterministicSchedsParetoExplorer { |
||||
|
public: |
||||
|
typedef uint64_t PointId; |
||||
|
typedef typename std::shared_ptr<storm::storage::geometry::Polytope<GeometryValueType>> Polytope; |
||||
|
typedef typename SparseModelType::ValueType ModelValueType; |
||||
|
|
||||
|
class Point { |
||||
|
public: |
||||
|
Point(std::vector<GeometryValueType> const& coordinates); |
||||
|
Point(std::vector<GeometryValueType>&& coordinates); |
||||
|
|
||||
|
std::vector<GeometryValueType> const& get() const; |
||||
|
std::vector<GeometryValueType>& get(); |
||||
|
|
||||
|
uint64_t dimension() const; |
||||
|
|
||||
|
enum class DominanceResult { |
||||
|
Incomparable, |
||||
|
Dominates, |
||||
|
Dominated, |
||||
|
Equal |
||||
|
}; |
||||
|
DominanceResult getDominance(Point const& other) const; |
||||
|
|
||||
|
void setParetoOptimal(bool value = true); |
||||
|
bool isParetoOptimal() const; |
||||
|
void setOnFacet(bool value = true); |
||||
|
bool liesOnFacet() const; |
||||
|
|
||||
|
std::string toString(bool convertToDouble = false) const; |
||||
|
|
||||
|
private: |
||||
|
std::vector<GeometryValueType> coordinates; |
||||
|
bool paretoOptimal; |
||||
|
bool onFacet; |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
class Pointset { |
||||
|
public: |
||||
|
|
||||
|
typedef typename std::map<PointId, Point>::const_iterator iterator_type; |
||||
|
|
||||
|
|
||||
|
Pointset(); |
||||
|
|
||||
|
/*! |
||||
|
* If the given point is not dominated by another point in the set, it is added |
||||
|
* to the set and its ID is returned. |
||||
|
* If the point is dominated by another point, boost::none is returned. |
||||
|
* Erases all points in the set, that are dominated by the given point. |
||||
|
* If the same point is already contained in the set, its id is returned |
||||
|
*/ |
||||
|
boost::optional<PointId> addPoint(Environment const& env, Point&& point); |
||||
|
|
||||
|
/*! |
||||
|
* Returns the point with the given ID |
||||
|
*/ |
||||
|
Point const& getPoint(PointId const& id) const; |
||||
|
|
||||
|
iterator_type begin() const; |
||||
|
iterator_type end() const; |
||||
|
|
||||
|
/*! |
||||
|
* Returns the number of points currently contained in the set |
||||
|
*/ |
||||
|
uint64_t size() const; |
||||
|
|
||||
|
/*! |
||||
|
* Returns the downward closure of the contained points. |
||||
|
*/ |
||||
|
Polytope downwardClosure() const; |
||||
|
|
||||
|
void collectPointsInPolytope(std::set<PointId>& collectedPoints, Polytope const& polytope); |
||||
|
|
||||
|
void printToStream(std::ostream& out, bool includeIDs = true, bool convertToDouble = false); |
||||
|
|
||||
|
private: |
||||
|
std::map<PointId, Point> points; |
||||
|
PointId currId; |
||||
|
}; |
||||
|
|
||||
|
class Facet { |
||||
|
public: |
||||
|
Facet(storm::storage::geometry::Halfspace<GeometryValueType> const& halfspace); |
||||
|
Facet(storm::storage::geometry::Halfspace<GeometryValueType>&& halfspace); |
||||
|
storm::storage::geometry::Halfspace<GeometryValueType> const& getHalfspace() const; |
||||
|
void addPoint(PointId const& pointId, Point const& point); |
||||
|
std::vector<PointId> const& getPoints() const; |
||||
|
uint64_t getNumberOfPoints() const; |
||||
|
|
||||
|
/*! |
||||
|
* Creates a polytope that captures all points that lie 'under' the facet |
||||
|
*/ |
||||
|
Polytope const& getInducedSimplex(Pointset const& pointset, std::vector<GeometryValueType> const& referenceCoordinates); |
||||
|
|
||||
|
|
||||
|
|
||||
|
private: |
||||
|
storm::storage::geometry::Halfspace<GeometryValueType> halfspace; |
||||
|
std::vector<PointId> paretoPointsOnFacet; |
||||
|
Polytope inducedSimplex; |
||||
|
}; |
||||
|
|
||||
|
struct FacetAnalysisContext { |
||||
|
FacetAnalysisContext(Facet& f); |
||||
|
|
||||
|
Facet& facet; |
||||
|
std::set<PointId> collectedPoints; |
||||
|
std::unique_ptr<storm::solver::SmtSolver> smtSolver; |
||||
|
std::shared_ptr<storm::expressions::ExpressionManager> expressionManager; |
||||
|
|
||||
|
// Variables that encode two points that lie in the induced simplex of the analyzed facet |
||||
|
// xMinusEps = (x_1-eps, x_m-eps) |
||||
|
std::vector<storm::expressions::Variable> x, xMinusEps; |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
DeterministicSchedsParetoExplorer(preprocessing::SparseMultiObjectivePreprocessorResult<SparseModelType>& preprocessorResult); |
||||
|
|
||||
|
virtual std::unique_ptr<CheckResult> check(Environment const& env); |
||||
|
|
||||
|
void exportPlotOfCurrentApproximation(Environment const& env); |
||||
|
|
||||
|
private: |
||||
|
|
||||
|
/*! |
||||
|
* Cleans up all cached results from a previous check call |
||||
|
*/ |
||||
|
void clean(); |
||||
|
|
||||
|
/*! |
||||
|
* Intersects the overapproximation with the given halfspace |
||||
|
*/ |
||||
|
void addHalfspaceToOverApproximation(Environment const& env, std::vector<GeometryValueType> const& normalVector, Point const& pointOnHalfspace); |
||||
|
|
||||
|
/*! |
||||
|
* Adds a polytope which consists of unachievable points |
||||
|
*/ |
||||
|
void addUnachievableArea(Environment const& env, Polytope const& area); |
||||
|
|
||||
|
/*! |
||||
|
* Builds the initial facets by optimizing the objectives individually. |
||||
|
* Adds the facets that need further processing to unprocessedFacets |
||||
|
*/ |
||||
|
void initializeFacets(Environment const& env); |
||||
|
|
||||
|
/*! |
||||
|
* Gets reference coordinates used to subdividing the downwardclosure |
||||
|
*/ |
||||
|
std::vector<GeometryValueType> getReferenceCoordinates() const; |
||||
|
|
||||
|
/*! |
||||
|
* Checks the precision of the given Facet and returns true, if no further processing of the facet is necessary |
||||
|
*/ |
||||
|
bool checkFacetPrecision(Environment const& env, Facet& f); |
||||
|
|
||||
|
/*! |
||||
|
* Checks the precision of the given Facet and returns true, if no further processing of the facet is necessary. |
||||
|
* Also takes the given points within the simplex of the facet into account |
||||
|
*/ |
||||
|
bool checkFacetPrecision(Environment const& env, Facet& f, std::set<PointId> const& collectedSimplexPoints); |
||||
|
|
||||
|
/*! Processes the given facet as follows: |
||||
|
* 1. Optimize in the facet direction. Potentially, this adds new, unprocessed facets |
||||
|
* 2. Find points that have already been collected so far such that they lie in the induced simplex of the facet. |
||||
|
* 3. Find more points that lie on the facet |
||||
|
* 4. Find all points that lie in the induced simplex or prove that there are none |
||||
|
*/ |
||||
|
void processFacet(Environment const& env, Facet& f); |
||||
|
|
||||
|
FacetAnalysisContext createAnalysisContext(Environment const& env, Facet& f); |
||||
|
|
||||
|
/*! |
||||
|
* Optimizes in the facet direction. If this results in a point that does not lie on the facet, |
||||
|
* 1. The new Pareto optimal point is added |
||||
|
* 2. New facets are generated and (if not already precise enough) added to unprocessedFacets |
||||
|
* 3. true is returned |
||||
|
*/ |
||||
|
bool optimizeAndSplitFacet(Environment const& env, Facet& f); |
||||
|
|
||||
|
/*! |
||||
|
* Adds a new point that lies within the induced simplex of the given facet to the analysis context. |
||||
|
* @param context the analysis context |
||||
|
* @param pointId the id of the given point. |
||||
|
* @param performCheck if true, it is checked whether the facet is sufficiently precise now. If false, no check is performed. |
||||
|
* @return true iff performCheck is true and the facet is sufficiently precise. |
||||
|
*/ |
||||
|
bool addNewSimplexPoint(FacetAnalysisContext& context, PointId const& pointId, bool performCheck); |
||||
|
|
||||
|
Pointset pointset; |
||||
|
std::queue<Facet> unprocessedFacets; |
||||
|
Polytope overApproximation; |
||||
|
std::vector<Polytope> unachievableAreas; |
||||
|
|
||||
|
std::shared_ptr<DeterministicSchedsLpChecker<SparseModelType, GeometryValueType>> lpChecker; |
||||
|
std::shared_ptr<SparseModelType> const& model; |
||||
|
uint64_t originalModelInitialState; |
||||
|
std::vector<Objective<ModelValueType>> const& objectives; |
||||
|
}; |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,142 @@ |
|||||
|
#pragma once |
||||
|
|
||||
|
#include <vector> |
||||
|
#include <memory> |
||||
|
#include "storm/storage/geometry/Polytope.h" |
||||
|
|
||||
|
namespace storm { |
||||
|
namespace storage { |
||||
|
namespace geometry { |
||||
|
|
||||
|
/*! |
||||
|
* Represents a set of points in Euclidean space. |
||||
|
* The set is defined as the union of the polytopes at the leafs of the tree. |
||||
|
* The polytope at inner nodes should always be the convex union of its children. |
||||
|
* The sets described by the children of a node are disjoint. |
||||
|
* A child is always non-empty, i.e., isEmpty() should only hold for the root node. |
||||
|
*/ |
||||
|
template <typename ValueType> |
||||
|
class PolytopeTree { |
||||
|
|
||||
|
public: |
||||
|
PolytopeTree(std::shared_ptr<Polytope<ValueType>> const& polytope = nullptr) : polytope(polytope) { |
||||
|
// Intentionally left empty |
||||
|
} |
||||
|
|
||||
|
/*! |
||||
|
* Substracts the given rhs from this polytope. |
||||
|
*/ |
||||
|
void setMinus(std::shared_ptr<Polytope<ValueType>> const& rhs) { |
||||
|
// This operation only has an effect if the intersection of this and rhs is non-empty. |
||||
|
if (!isEmpty() && !polytope->intersection(rhs)->isEmpty()) { |
||||
|
if (children.empty()) { |
||||
|
// This is a leaf node. |
||||
|
// Apply splitting. |
||||
|
auto newChildren = polytope->setMinus(rhs); |
||||
|
if (newChildren.empty()) { |
||||
|
// Delete this node. |
||||
|
polytope = nullptr; |
||||
|
} else if (newChildren.size() == 1) { |
||||
|
// Replace this node with its only child |
||||
|
polytope = newChildren.front()->clean(); |
||||
|
} else { |
||||
|
// Add the new children to this node. There is no need to traverse them. |
||||
|
for (auto& c : newChildren) { |
||||
|
children.push_back(c->clean()); |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
// This is an inner node. Traverse the children and set this to the convex union of its children. |
||||
|
std::vector<PolytopeTree<ValueType>> newChildren; |
||||
|
std::shared_ptr<Polytope<ValueType>> newPolytope = nullptr; |
||||
|
for (auto& c : children) { |
||||
|
c.setMinus(rhs); |
||||
|
if (c.polytope != nullptr) { |
||||
|
newChildren.push_back(c); |
||||
|
if (newPolytope) { |
||||
|
newPolytope->convexUnion(c.polytope); |
||||
|
} else { |
||||
|
newPolytope = c.polytope; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
polytope = newPolytope; // nullptr, if no children left |
||||
|
children = std::move(newChildren); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/*! |
||||
|
* Substracts the downward closure of the given point from this set. |
||||
|
* @param point the given point |
||||
|
* @param offset |
||||
|
*/ |
||||
|
void substractDownwardClosure(std::vector<ValueType> const& point, ValueType const& offset = storm::utility::zero<ValueType>()) { |
||||
|
if (storm::utility::isZero(offset)) { |
||||
|
setMinus(Polytope<ValueType>::createDownwardClosure({point})); |
||||
|
} else { |
||||
|
std::vector<ValueType> pointPrime; |
||||
|
pointPrime.reserve(point.size()); |
||||
|
for (auto const& coordinate : point) { |
||||
|
pointPrime.push_back(coordinate + offset); |
||||
|
} |
||||
|
setMinus(Polytope<ValueType>::createDownwardClosure({pointPrime})); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/*! |
||||
|
* Returns true if this is the empty set. |
||||
|
*/ |
||||
|
bool isEmpty() const { |
||||
|
return polytope == nullptr; |
||||
|
} |
||||
|
|
||||
|
/*! |
||||
|
* Clears all contents of this set, making it the empty set. |
||||
|
*/ |
||||
|
void clear() { |
||||
|
children.clear(); |
||||
|
polytope = nullptr; |
||||
|
} |
||||
|
|
||||
|
/*! |
||||
|
* Gets the polytope at this node |
||||
|
*/ |
||||
|
std::shared_ptr<Polytope<ValueType>>& getPolytope() { |
||||
|
return polytope; |
||||
|
} |
||||
|
|
||||
|
/*! |
||||
|
* Gets the children at this node. |
||||
|
*/ |
||||
|
std::vector<PolytopeTree>& getChildren() { |
||||
|
return children; |
||||
|
} |
||||
|
|
||||
|
/*! |
||||
|
* Returns a string representation of this node (for debugging purposes) |
||||
|
*/ |
||||
|
std::string toString() { |
||||
|
if (isEmpty()) { |
||||
|
return "Empty PolytopeTree"; |
||||
|
} |
||||
|
std::stringstream s; |
||||
|
s << "PolytopeTree node with " << getChildren().size() << " children: " << getPolytope()->toString(true) << std::endl << "Vertices: "; |
||||
|
auto vertices = getPolytope()->getVertices(); |
||||
|
for (auto const& v : vertices) { |
||||
|
s << "["; |
||||
|
for (auto const& vi : v) { |
||||
|
s << storm::utility::convertNumber<double>(vi) << ","; |
||||
|
} |
||||
|
s << "]\t"; |
||||
|
} |
||||
|
s << std::endl; |
||||
|
return s.str(); |
||||
|
} |
||||
|
private: |
||||
|
std::shared_ptr<Polytope<ValueType>> polytope; |
||||
|
std::vector<PolytopeTree<ValueType>> children; |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue