#include #include #include "storm/modelchecker/multiobjective/deterministicScheds/DeterministicParetoExplorer.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 DeterministicParetoExplorer::Point::Point(std::vector const& coordinates) : coordinates(coordinates), paretoOptimal(false), onFacet(false) { STORM_LOG_ASSERT(!this->coordinates.empty(), "Points with dimension 0 are not supported"); } template DeterministicParetoExplorer::Point::Point(std::vector&& coordinates) : coordinates(std::move(coordinates)), paretoOptimal(false), onFacet(false) { STORM_LOG_ASSERT(!this->coordinates.empty(), "Points with dimension 0 are not supported"); } template std::vector& DeterministicParetoExplorer::Point::get() { return coordinates; } template std::vector const& DeterministicParetoExplorer::Point::get() const { return coordinates; } template uint64_t DeterministicParetoExplorer::Point::dimension() const { STORM_LOG_ASSERT(!coordinates.empty(), "Points with dimension 0 are not supported"); return coordinates.size(); } template typename DeterministicParetoExplorer::Point::DominanceResult DeterministicParetoExplorer::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 void DeterministicParetoExplorer::Point::setParetoOptimal(bool value) { paretoOptimal = value; } template bool DeterministicParetoExplorer::Point::isParetoOptimal() const { return paretoOptimal; } template void DeterministicParetoExplorer::Point::setOnFacet(bool value) { onFacet = value; } template bool DeterministicParetoExplorer::Point::liesOnFacet() const { return onFacet; } template std::string DeterministicParetoExplorer::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(pi); } else { out << pi; } } return out.str(); } // template // bool operator<(typename DeterministicParetoExplorer::Point const& lhs, typename DeterministicParetoExplorer::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 DeterministicParetoExplorer::Pointset::Pointset() : currId(1) { // Intentionally left empty } template boost::optional::PointId> DeterministicParetoExplorer::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(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 typename DeterministicParetoExplorer::Point const& DeterministicParetoExplorer::Pointset::getPoint(PointId const& id) const { return points.at(id); } template typename DeterministicParetoExplorer::Pointset::iterator_type DeterministicParetoExplorer::Pointset::begin() const { return points.begin(); } template typename DeterministicParetoExplorer::Pointset::iterator_type DeterministicParetoExplorer::Pointset::end() const { return points.end(); } template uint64_t DeterministicParetoExplorer::Pointset::size() const { return points.size(); } template typename DeterministicParetoExplorer::Polytope DeterministicParetoExplorer::Pointset::downwardClosure() const { std::vector> pointsAsVector; pointsAsVector.reserve(size()); for (auto const& p : points) { pointsAsVector.push_back(p.second.get()); } return storm::storage::geometry::Polytope::createDownwardClosure(std::move(pointsAsVector)); } template void DeterministicParetoExplorer::Pointset::collectPointsInPolytope(std::set& collectedPoints, Polytope const& polytope) { for (auto const& p : points) { if (polytope->contains(p.second.get())) { collectedPoints.insert(p.first); } } } template void DeterministicParetoExplorer::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 DeterministicParetoExplorer::Facet::Facet(storm::storage::geometry::Halfspace const& halfspace) : halfspace(halfspace) { // Intentionally left empty } template DeterministicParetoExplorer::Facet::Facet(storm::storage::geometry::Halfspace&& halfspace) : halfspace(std::move(halfspace)) { // Intentionally left empty } template storm::storage::geometry::Halfspace const& DeterministicParetoExplorer::Facet::getHalfspace() const { return halfspace; } template void DeterministicParetoExplorer::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(getHalfspace().euclideanDistance(point.get()))); } else { STORM_LOG_DEBUG("Halfspace of facet is shifted by " << storm::utility::convertNumber(getHalfspace().euclideanDistance(point.get())) << " to capture all points that are supposed to lie on the facet."); halfspace.offset() = product; } } paretoPointsOnFacet.push_back(pointId); } template std::vector::PointId> const& DeterministicParetoExplorer::Facet::getPoints() const { return paretoPointsOnFacet; } template uint64_t DeterministicParetoExplorer::Facet::getNumberOfPoints() const { return paretoPointsOnFacet.size(); } template typename DeterministicParetoExplorer::Polytope const& DeterministicParetoExplorer::Facet::getInducedSimplex(Pointset const& pointset, std::vector const& referenceCoordinates) { if (!inducedSimplex) { std::vector> 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::create(vertices); } else { inducedSimplex = storm::storage::geometry::Polytope::createSelectiveDownwardClosure(vertices, dimensionsForDownwardClosure); } } return inducedSimplex; } template DeterministicParetoExplorer::FacetAnalysisContext::FacetAnalysisContext(Facet& f) : facet(f) { // Intentionally left empty } template DeterministicParetoExplorer::DeterministicParetoExplorer(preprocessing::SparseMultiObjectivePreprocessorResult& preprocessorResult) : model(preprocessorResult.preprocessedModel), objectives(preprocessorResult.objectives) { originalModelInitialState = *preprocessorResult.originalModel.getInitialStates().begin(); schedulerEvaluator = std::make_shared>(preprocessorResult); weightVectorChecker = std::make_shared>(schedulerEvaluator); simplexChecker = std::make_shared>(schedulerEvaluator); } template std::unique_ptr DeterministicParetoExplorer::check(Environment const& env) { clean(); initializeFacets(env); while (!unprocessedFacets.empty()) { Facet f = std::move(unprocessedFacets.front()); unprocessedFacets.pop(); processFacet(env, f); } std::vector>paretoPoints; paretoPoints.reserve(pointset.size()); for (auto const& p : pointset) { paretoPoints.push_back(storm::utility::vector::convertNumericVector(transformObjectiveValuesToOriginal(objectives, p.second.get()))); } return std::make_unique>(originalModelInitialState, std::move(paretoPoints), nullptr, nullptr); } template void DeterministicParetoExplorer::clean() { pointset = Pointset(); unprocessedFacets = std::queue(); overApproximation = storm::storage::geometry::Polytope::createUniversalPolytope(); unachievableAreas.clear(); } template void DeterministicParetoExplorer::addHalfspaceToOverApproximation(Environment const& env, std::vector 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(xi); } std::cout << "];[" << storm::utility::convertNumber(offset) << "]" << std::endl; } storm::storage::geometry::Halfspace overApproxHalfspace(normalVector, offset); overApproximation = overApproximation->intersection(overApproxHalfspace); } template void DeterministicParetoExplorer::addUnachievableArea(Environment const& env, Polytope const& area) { if (env.modelchecker().multi().isPrintResultsSet()) { std::vector> 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(vi); } std::cout << "]"; } std::cout << std::endl; } unachievableAreas.push_back(area); } template void DeterministicParetoExplorer::initializeFacets(Environment const& env) { for (uint64_t objIndex = 0; objIndex < objectives.size(); ++objIndex) { std::vector weightVector(objectives.size(), storm::utility::zero()); if (storm::solver::minimize(objectives[objIndex].formula->getOptimalityType())) { weightVector[objIndex] = -storm::utility::one(); } else { weightVector[objIndex] = storm::utility::one(); } auto points = weightVectorChecker->check(env, weightVector); bool last = true; for (auto pIt = points.rbegin(); pIt != points.rend(); ++pIt) { for (uint64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) { if (storm::solver::minimize(objectives[objIndex].formula->getOptimalityType())) { (*pIt)[objIndex] *= -storm::utility::one(); } } Point p(storm::utility::vector::convertNumericVector(*pIt)); if (last) { last = false; p.setOnFacet(); // Adapt the overapproximation std::vector normalVector(objectives.size(), storm::utility::zero()); normalVector[objIndex] = storm::utility::one(); 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()) + f.getNumberOfPoints() == objectives.size(), "Unexpected number of points on facet."); if (!checkFacetPrecision(env, f)) { unprocessedFacets.push(std::move(f)); } } } template std::vector DeterministicParetoExplorer::getReferenceCoordinates() const { std::vector result; for (auto const& obj : schedulerEvaluator->getObjectives()) { ModelValueType value = storm::solver::minimize(obj.formula->getOptimalityType()) ? obj.upperResultBound.get() : obj.lowerResultBound.get(); result.push_back(storm::utility::convertNumber(value)); } return result; } template bool DeterministicParetoExplorer::checkFacetPrecision(Environment const& env, Facet& f) { // TODO: return false; /* auto const& inducedSimplex = f.getInducedSimplex(pointset); GeometryValueType eps = storm::utility::convertNumber(env.modelchecker().multi().getPrecision()); // get a polytope that contains exactly the points y, such that y+eps is in the induced simplex std::vector 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 bool DeterministicParetoExplorer::checkFacetPrecision(Environment const& env, Facet& f, std::set const& collectedSimplexPoints) { assert(false); return false; } template void DeterministicParetoExplorer::processFacet(Environment const& env, Facet& f) { if (optimizeAndSplitFacet(env,f)) { return; } GeometryValueType eps = storm::utility::convertNumber(env.modelchecker().multi().getPrecision()); eps += eps; // The unknown area (box) can actually have size 2*eps PolytopeTree 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 = simplexChecker->check(env, f.getHalfspace().normalVector(), polytopeTree, eps); for (auto const& infeasableArea : res.second) { addUnachievableArea(env, infeasableArea); } for (auto& achievablePoint : res.first) { pointset.addPoint(env, Point(std::move(achievablePoint))); } } /* FacetAnalysisContext context = createAnalysisContext(env, f); if (findAndCheckCachedPoints(env, context)) { return; } if (analyzePointsOnFacet(env, context)) { return; } if (analyzePointsInSimplex(env, context)) { return; } */ // Reaching this point means that the facet could not be analyzed completely. //STORM_LOG_ERROR("Facet " << f.getHalfspace().toString(true) << " could not be analyzed completely."); } template typename DeterministicParetoExplorer::FacetAnalysisContext DeterministicParetoExplorer::createAnalysisContext(Environment const& env, Facet& f) { FacetAnalysisContext res(f); /* res.expressionManager = std::make_shared(); 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 bool DeterministicParetoExplorer::optimizeAndSplitFacet(Environment const& env, Facet& f) { // Obtain the correct weight vector auto weightVector = storm::utility::vector::convertNumericVector(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(); } } // Invoke optimization and insert the explored points boost::optional optPointId; auto points = weightVectorChecker->check(env, weightVector); bool last = true; for (auto pIt = points.rbegin(); pIt != points.rend(); ++pIt) { for (uint64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) { if (storm::solver::minimize(objectives[objIndex].formula->getOptimalityType())) { (*pIt)[objIndex] *= -storm::utility::one(); } } Point p(storm::utility::vector::convertNumericVector(*pIt)); if (last) { last = false; p.setParetoOptimal(weightVectorYieldsParetoOptimalPoint); p.setOnFacet(); addHalfspaceToOverApproximation(env, f.getHalfspace().normalVector(), p); optPointId = pointset.addPoint(env, std::move(p)); } else { pointset.addPoint(env, std::move(p)); } } // Potentially generate new facets if (optPointId) { auto const& optPoint = pointset.getPoint(*optPointId); 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> 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::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 bool DeterministicParetoExplorer::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 bool DeterministicParetoExplorer::findAndCheckCachedPoints(Environment const& env, FacetAnalysisContext& context) { /* Polytope inducedPoly = context.facet.getInducedSimplex(pointset); pointset.collectPointsInPolytope(context.collectedPoints, inducedPoly); uint64_t numNewPoints = context.collectedPoints.size(); STORM_LOG_ASSERT(numNewPoints >= context.facet.getNumberOfPoints(), "Did not find all points on the facet"); // return true iff for all points x there is a cached point that dominates or is equal to (x-eps). for (auto const& pId : context.collectedPoints) { --numNewPoints; if (numNewPoints == 0) { return addNewSimplexPoint(context, pId, true); } else { addNewSimplexPoint(context, pId, false); } } */ STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Reached code that should be unreachable..."); return false; } template bool DeterministicParetoExplorer::analyzePointsOnFacet(Environment const& env, FacetAnalysisContext& context) { // Enumerate all points on the facet by creating a sub-MDP // TODO: Enumerate them using the scheduler evaluator, ie create a class similar to the weight vector checker return false; } template bool DeterministicParetoExplorer::analyzePointsInSimplex(Environment const& env, FacetAnalysisContext& context) { auto const& pointIds = context.facet.getPoints(); std::vector::Point> pointsOnFacet; pointsOnFacet.reserve(pointIds.size()); for (auto const& pId : pointIds) { pointsOnFacet.push_back(pointset.getPoint(pId).get()); } // simplexChecker->setSimplex(context.facet.getInducedSimplex(pointset), context.facet.getHalfspace().normalVector(), pointsOnFacet); //for (auto const& pointInSimplex : context.collectedPoints) { // simplexChecker->addAchievablePoint(pointset.getPoint(pointInSimplex).get()); //} return false; } template void DeterministicParetoExplorer::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 boundaries(std::vector(objectives.size(), storm::utility::zero()), std::vector(objectives.size(), storm::utility::zero())); std::vector> 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 columnHeaders = {"x", "y"}; std::vector> 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(v)); } storm::utility::exportDataToCSVFile(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(v)); } storm::utility::exportDataToCSVFile(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(v)); } storm::utility::exportDataToCSVFile(env.modelchecker().multi().getPlotPathParetoPoints().get(), pointsForPlotting, columnHeaders); } }; */ } template class DeterministicParetoExplorer, storm::RationalNumber>; template class DeterministicParetoExplorer, storm::RationalNumber>; template class DeterministicParetoExplorer, storm::RationalNumber>; template class DeterministicParetoExplorer, storm::RationalNumber>; } } }