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