Tim Quatmann
6 years ago
6 changed files with 520 additions and 62 deletions
-
387src/storm/modelchecker/multiobjective/deterministicScheds/DetSchedsSimplexChecker.h
-
1src/storm/modelchecker/multiobjective/deterministicScheds/DetSchedsWeightVectorChecker.cpp
-
157src/storm/modelchecker/multiobjective/deterministicScheds/DeterministicParetoExplorer.cpp
-
25src/storm/modelchecker/multiobjective/deterministicScheds/DeterministicParetoExplorer.h
-
11src/storm/modelchecker/multiobjective/deterministicScheds/MultiObjectiveSchedulerEvaluator.cpp
-
1src/storm/modelchecker/multiobjective/deterministicScheds/MultiObjectiveSchedulerEvaluator.h
@ -0,0 +1,387 @@ |
|||
#pragma once |
|||
|
|||
#include <vector> |
|||
|
|||
#include "storm/storage/geometry/Polytope.h" |
|||
#include "storm/modelchecker/multiobjective/deterministicScheds/MultiObjectiveSchedulerEvaluator.h" |
|||
#include "storm/storage/expressions/Expressions.h" |
|||
#include "storm/utility/solver.h" |
|||
#include "storm/solver/LpSolver.h" |
|||
#include "storm/modelchecker/results/ExplicitQuantitativeCheckResult.h" |
|||
#include "storm/modelchecker/results/ExplicitQualitativeCheckResult.h" |
|||
#include "storm/modelchecker/propositional/SparsePropositionalModelChecker.h" |
|||
#include "storm/modelchecker/prctl/SparseDtmcPrctlModelChecker.h" |
|||
#include "storm/modelchecker/csl/SparseCtmcCslModelChecker.h" |
|||
#include "storm/storage/BitVector.h" |
|||
#include "storm/utility/graph.h" |
|||
#include "storm/utility/vector.h" |
|||
#include "storm/utility/Stopwatch.h" |
|||
|
|||
namespace storm { |
|||
|
|||
class Environment; |
|||
|
|||
namespace modelchecker { |
|||
namespace multiobjective { |
|||
|
|||
/*! |
|||
* 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 is always 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. |
|||
* @tparam GeometryValueType |
|||
*/ |
|||
template <typename GeometryValueType> |
|||
class PolytopeTree { |
|||
typedef typename std::shared_ptr<storm::storage::geometry::Polytope<GeometryValueType>> Polytope; |
|||
typedef typename std::vector<GeometryValueType> Point; |
|||
|
|||
public: |
|||
PolytopeTree(Polytope const& polytope) : polytope(polytope) {} |
|||
|
|||
/*! |
|||
* Substracts the given rhs from this polytope. |
|||
*/ |
|||
void setMinus(Polytope 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<GeometryValueType>> newChildren; |
|||
Polytope 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); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void substractDownwardClosure(Point const& point, GeometryValueType const& eps) { |
|||
std::vector<GeometryValueType>(pointPlusEps); |
|||
for (auto const& coordinate : point) { |
|||
pointPlusEps.push_back(coordinate + eps); |
|||
} |
|||
auto downwardOfPoint = storm::storage::geometry::Polytope<GeometryValueType>::createDownwardClosure({pointPlusEps}); |
|||
setMinus(downwardOfPoint); |
|||
} |
|||
|
|||
bool isEmpty() const { |
|||
return polytope == nullptr; |
|||
} |
|||
|
|||
void clear() { |
|||
children.clear(); |
|||
polytope = nullptr; |
|||
} |
|||
|
|||
Polytope getPolytope() const { |
|||
return polytope; |
|||
} |
|||
|
|||
std::vector<PolytopeTree>& getChildren() { |
|||
return children; |
|||
} |
|||
|
|||
std::string toString() { |
|||
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: |
|||
|
|||
|
|||
|
|||
Polytope polytope; |
|||
std::vector<PolytopeTree<GeometryValueType>> children; |
|||
}; |
|||
|
|||
template <typename ModelType, typename GeometryValueType> |
|||
class DetSchedsSimplexChecker { |
|||
public: |
|||
|
|||
typedef typename ModelType::ValueType ValueType; |
|||
typedef typename std::shared_ptr<storm::storage::geometry::Polytope<GeometryValueType>> Polytope; |
|||
typedef typename std::vector<GeometryValueType> Point; |
|||
|
|||
DetSchedsSimplexChecker(std::shared_ptr<MultiObjectiveSchedulerEvaluator<ModelType>> const& schedulerEvaluator) : schedulerEvaluator(schedulerEvaluator) { |
|||
init(); |
|||
} |
|||
|
|||
~DetSchedsSimplexChecker() { |
|||
std::cout << "SIMPLEX CHECKER: " << swInit << " seconds for initialization" << std::endl; |
|||
std::cout << "SIMPLEX CHECKER: " << swCheck << " seconds for checking, including" << std::endl; |
|||
std::cout << "\t " << swLpBuild << " seconds for LP building" << std::endl; |
|||
std::cout << "\t " << swLpSolve << " seconds for LP solving" << std::endl; |
|||
std::cout << "SIMPLEX CHECKER: " << swAux << " seconds for aux stuff" << std::endl; |
|||
} |
|||
|
|||
std::pair<std::vector<Point>, std::vector<Polytope>> check(storm::Environment const& env, std::vector<GeometryValueType> const& weightVector, PolytopeTree<GeometryValueType>& polytopeTree, GeometryValueType const& eps) { |
|||
std::cout << "Checking a Simplex with weight vector " << storm::utility::vector::toString(weightVector) << std::endl << " and root " << polytopeTree.toString() << std::endl << "\t"; |
|||
if (polytopeTree.isEmpty()) { |
|||
return {{}, {}}; |
|||
} |
|||
swCheck.start(); |
|||
|
|||
swLpBuild.start(); |
|||
lpModel->push(); |
|||
currentObjectiveVariables.clear(); |
|||
|
|||
// 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(); |
|||
|
|||
auto result = checkRecursive(weightVector, polytopeTree, eps); |
|||
|
|||
swLpBuild.start(); |
|||
lpModel->pop(); |
|||
lpModel->update(); |
|||
swLpBuild.stop(); |
|||
swCheck.stop(); |
|||
std::cout << " done!" << std::endl; |
|||
return result; |
|||
} |
|||
|
|||
private: |
|||
|
|||
std::pair<std::vector<Point>, std::vector<Polytope>> checkRecursive(std::vector<GeometryValueType> const& weightVector, PolytopeTree<GeometryValueType>& polytopeTree, GeometryValueType const& eps) { |
|||
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()); |
|||
|
|||
auto vertices = polytopeTree.getPolytope()->getVertices(); |
|||
|
|||
std::vector<Point> foundPoints; |
|||
std::vector<Polytope> infeasableAreas; |
|||
|
|||
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(); |
|||
lpModel->optimize(); |
|||
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>(weightVector, storm::utility::vector::dotProduct(weightVector, 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()) { |
|||
auto childRes = checkRecursive(weightVector, polytopeTree, eps); |
|||
foundPoints.insert(foundPoints.end(), childRes.first.begin(), childRes.first.end()); |
|||
infeasableAreas.insert(infeasableAreas.end(), childRes.second.begin(), childRes.second.end()); |
|||
} |
|||
} |
|||
} else { |
|||
// Traverse all the children. |
|||
for (uint64_t childId = 0; childId < polytopeTree.getChildren().size(); ++childId) { |
|||
auto childRes = checkRecursive(weightVector, polytopeTree.getChildren()[childId], eps); |
|||
STORM_LOG_ASSERT(polytopeTree.getChildren()[childId].isEmpty(), "expected empty children."); |
|||
// Make the results known to the right siblings |
|||
for (auto const& newPoint : childRes.first) { |
|||
for (uint64_t siblingId = childId + 1; siblingId < polytopeTree.getChildren().size(); ++siblingId) { |
|||
polytopeTree.getChildren()[siblingId].substractDownwardClosure(newPoint, eps); |
|||
} |
|||
} |
|||
foundPoints.insert(foundPoints.end(), childRes.first.begin(), childRes.first.end()); |
|||
infeasableAreas.insert(infeasableAreas.end(), childRes.second.begin(), childRes.second.end()); |
|||
} |
|||
// All children are empty now, so this becomes empty. |
|||
polytopeTree.clear(); |
|||
} |
|||
swLpBuild.start(); |
|||
lpModel->pop(); |
|||
swLpBuild.stop(); |
|||
return {foundPoints, infeasableAreas}; |
|||
} |
|||
|
|||
/* Todo |
|||
ValueType getChoiceValueSummand(Objective<ValueType> const& objective, uint64_t choiceIndex) { |
|||
auto const& model = schedulerEvaluator->getModel(); |
|||
storm::modelchecker::SparsePropositionalModelChecker<ModelType> mc(model); |
|||
auto const& formula = *objective.formula; |
|||
if (formula.isProbabilityOperatorFormula() && formula.getSubformula().isUntilFormula()) { |
|||
return storm::utility::zero<ValueType>(); |
|||
} else if (formula.getSubformula().isEventuallyFormula() && (formula.isRewardOperatorFormula() || formula.isTimeOperatorFormula())) { |
|||
|
|||
storm::storage::BitVector rew0States = mc.check(formula.getSubformula().asEventuallyFormula().getSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector(); |
|||
if (formula.isRewardOperatorFormula()) { |
|||
auto const& rewModel = formula.asRewardOperatorFormula().hasRewardModelName() ? model.getRewardModel(formula.asRewardOperatorFormula().getRewardModelName()) : model.getUniqueRewardModel(); |
|||
storm::storage::BitVector statesWithoutReward = rewModel.getStatesWithZeroReward(model.getTransitionMatrix()); |
|||
rew0States = storm::utility::graph::performProb1A(model.getTransitionMatrix(), model.getNondeterministicChoiceIndices(), model.getBackwardTransitions(), statesWithoutReward, rew0States); |
|||
} |
|||
storm::utility::vector::setVectorValues(results[objIndex], rew0States, storm::utility::zero<ValueType>()); |
|||
schedulerIndependentStates.push_back(std::move(rew0States)); |
|||
} else if (formula.isRewardOperatorFormula() && formula.getSubformula().isTotalRewardFormula()) { |
|||
auto const& rewModel = formula.asRewardOperatorFormula().hasRewardModelName() ? model.getRewardModel(formula.asRewardOperatorFormula().getRewardModelName()) : model.getUniqueRewardModel(); |
|||
storm::storage::BitVector statesWithoutReward = rewModel.getStatesWithZeroReward(model.getTransitionMatrix()); |
|||
storm::storage::BitVector rew0States = storm::utility::graph::performProbGreater0E(model.getBackwardTransitions(), statesWithoutReward, ~statesWithoutReward); |
|||
rew0States.complement(); |
|||
storm::utility::vector::setVectorValues(results[objIndex], rew0States, storm::utility::zero<ValueType>()); |
|||
schedulerIndependentStates.push_back(std::move(rew0States)); |
|||
} else { |
|||
STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "The given formula " << formula << " is not supported."); |
|||
} |
|||
}*/ |
|||
|
|||
void init() { |
|||
swInit.start(); |
|||
auto const& model = schedulerEvaluator->getModel(); |
|||
auto const& objectives = schedulerEvaluator->getObjectives(); |
|||
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 < objectives.size(); ++objIndex) { |
|||
Objective<ValueType> const& objective = objectives[objIndex]; |
|||
storm::storage::BitVector const& schedulerIndependentStates = schedulerEvaluator->getSchedulerIndependentStates(objIndex); |
|||
// Create state variables |
|||
std::vector<storm::expressions::Expression> stateVars; |
|||
stateVars.reserve(numStates); |
|||
for (uint64_t state = 0; state < numStates; ++state) { |
|||
if (schedulerIndependentStates.get(state)) { |
|||
stateVars.push_back(lpModel->getConstant(schedulerEvaluator->getSchedulerIndependentStateResult(objIndex, state))); |
|||
} else { |
|||
stateVars.push_back(lpModel->addContinuousVariable("x" + std::to_string(objIndex) + "_" + std::to_string(state), objective.lowerResultBound, objective.upperResultBound).getExpression()); |
|||
} |
|||
if (state == *model.getInitialStates().begin()) { |
|||
initialStateResults.push_back(stateVars.back()); |
|||
} |
|||
} |
|||
|
|||
// Create and assert choice values |
|||
for (uint64_t state = 0; state < numStates; ++state) { |
|||
if (schedulerIndependentStates.get(state)) { |
|||
continue; |
|||
} |
|||
storm::expressions::Expression stateValue; |
|||
uint64_t numChoices = model.getNumberOfChoices(state); |
|||
for (uint64_t choice = 0; choice < numChoices; ++choice) { |
|||
storm::expressions::Expression choiceValue; |
|||
if (objective.formula) |
|||
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(objective.upperResultBound.get()) * (one - choiceVars[globalChoiceIndex]); |
|||
lpModel->addConstraint("", stateVars[state] - choiceValue <= maxDiff); |
|||
lpModel->addConstraint("", choiceValue - stateVars[state] <= maxDiff); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
lpModel->update(); |
|||
swInit.stop(); |
|||
} |
|||
|
|||
std::shared_ptr<MultiObjectiveSchedulerEvaluator<ModelType>> schedulerEvaluator; |
|||
|
|||
std::unique_ptr<storm::solver::LpSolver<ValueType>> lpModel; |
|||
std::vector<storm::expressions::Expression> initialStateResults; |
|||
std::vector<storm::expressions::Variable> currentObjectiveVariables; |
|||
|
|||
|
|||
storm::utility::Stopwatch swInit; |
|||
storm::utility::Stopwatch swCheck; |
|||
storm::utility::Stopwatch swLpSolve; |
|||
storm::utility::Stopwatch swLpBuild; |
|||
storm::utility::Stopwatch swAux; |
|||
}; |
|||
|
|||
} |
|||
} |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue