#include "storm/modelchecker/multiobjective/pcaa/SparsePcaaQuery.h" #include "storm/adapters/RationalFunctionAdapter.h" #include "storm/models/sparse/Mdp.h" #include "storm/models/sparse/MarkovAutomaton.h" #include "storm/models/sparse/StandardRewardModel.h" #include "storm/modelchecker/multiobjective/Objective.h" #include "storm/settings/SettingsManager.h" #include "storm/settings/modules/MultiObjectiveSettings.h" #include "storm/storage/geometry/Hyperrectangle.h" #include "storm/utility/constants.h" #include "storm/utility/vector.h" #include "storm/utility/export.h" #include "storm/exceptions/UnexpectedException.h" namespace storm { namespace modelchecker { namespace multiobjective { template SparsePcaaQuery::SparsePcaaQuery(SparseMultiObjectivePreprocessorResult& preprocessorResult) : originalModel(preprocessorResult.originalModel), originalFormula(preprocessorResult.originalFormula), objectives(preprocessorResult.objectives) { this->weightVectorChecker = WeightVectorCheckerFactory::create(preprocessorResult); this->diracWeightVectorsToBeChecked = storm::storage::BitVector(this->objectives.size(), true); this->overApproximation = storm::storage::geometry::Polytope::createUniversalPolytope(); this->underApproximation = storm::storage::geometry::Polytope::createEmptyPolytope(); } template typename SparsePcaaQuery::WeightVector SparsePcaaQuery::findSeparatingVector(Point const& pointToBeSeparated) { STORM_LOG_DEBUG("Searching a weight vector to seperate the point given by " << storm::utility::vector::toString(storm::utility::vector::convertNumericVector(pointToBeSeparated)) << "."); if(underApproximation->isEmpty()) { // In this case, every weight vector is separating uint_fast64_t objIndex = diracWeightVectorsToBeChecked.getNextSetIndex(0) % pointToBeSeparated.size(); WeightVector result(pointToBeSeparated.size(), storm::utility::zero()); result[objIndex] = storm::utility::one(); diracWeightVectorsToBeChecked.set(objIndex, false); return result; } // Reaching this point means that the underApproximation contains halfspaces. The seperating vector has to be the normal vector of one of these halfspaces. // We pick one with maximal distance to the given point. However, Dirac weight vectors that only assign a non-zero weight to a single objective take precedence. STORM_LOG_ASSERT(!underApproximation->contains(pointToBeSeparated), "Tried to find a separating point but the point is already contained in the underApproximation"); std::vector> halfspaces = underApproximation->getHalfspaces(); uint_fast64_t farestHalfspaceIndex = halfspaces.size(); GeometryValueType farestDistance = -storm::utility::one(); bool foundSeparatingDiracVector = false; for(uint_fast64_t halfspaceIndex = 0; halfspaceIndex < halfspaces.size(); ++halfspaceIndex) { GeometryValueType distance = halfspaces[halfspaceIndex].euclideanDistance(pointToBeSeparated); if(!storm::utility::isZero(distance)) { storm::storage::BitVector nonZeroVectorEntries = ~storm::utility::vector::filterZero(halfspaces[halfspaceIndex].normalVector()); bool isSingleObjectiveVector = nonZeroVectorEntries.getNumberOfSetBits() == 1 && diracWeightVectorsToBeChecked.get(nonZeroVectorEntries.getNextSetIndex(0)); // Check if this halfspace is a better candidate than the current one if((!foundSeparatingDiracVector && isSingleObjectiveVector ) || (foundSeparatingDiracVector==isSingleObjectiveVector && distance>farestDistance)) { foundSeparatingDiracVector = foundSeparatingDiracVector || isSingleObjectiveVector; farestHalfspaceIndex = halfspaceIndex; farestDistance = distance; } } } if(foundSeparatingDiracVector) { diracWeightVectorsToBeChecked &= storm::utility::vector::filterZero(halfspaces[farestHalfspaceIndex].normalVector()); } STORM_LOG_THROW(farestHalfspaceIndex(halfspaces[farestHalfspaceIndex].normalVector())) << "."); return halfspaces[farestHalfspaceIndex].normalVector(); } template void SparsePcaaQuery::performRefinementStep(WeightVector&& direction) { // Normalize the direction vector so that the entries sum up to one storm::utility::vector::scaleVectorInPlace(direction, storm::utility::one() / std::accumulate(direction.begin(), direction.end(), storm::utility::zero())); weightVectorChecker->check(storm::utility::vector::convertNumericVector(direction)); STORM_LOG_DEBUG("weighted objectives checker result (under approximation) is " << storm::utility::vector::toString(storm::utility::vector::convertNumericVector(weightVectorChecker->getUnderApproximationOfInitialStateResults()))); RefinementStep step; step.weightVector = direction; step.lowerBoundPoint = storm::utility::vector::convertNumericVector(weightVectorChecker->getUnderApproximationOfInitialStateResults()); step.upperBoundPoint = storm::utility::vector::convertNumericVector(weightVectorChecker->getOverApproximationOfInitialStateResults()); // For the minimizing objectives, we need to scale the corresponding entries with -1 as we want to consider the downward closure for (uint_fast64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) { if (storm::solver::minimize(this->objectives[objIndex].formula->getOptimalityType())) { step.lowerBoundPoint[objIndex] *= -storm::utility::one(); step.upperBoundPoint[objIndex] *= -storm::utility::one(); } } refinementSteps.push_back(std::move(step)); updateOverApproximation(); updateUnderApproximation(); } template void SparsePcaaQuery::updateOverApproximation() { storm::storage::geometry::Halfspace h(refinementSteps.back().weightVector, storm::utility::vector::dotProduct(refinementSteps.back().weightVector, refinementSteps.back().upperBoundPoint)); // Due to numerical issues, it might be the case that the updated overapproximation does not contain the underapproximation, // e.g., when the new point is strictly contained in the underapproximation. Check if this is the case. GeometryValueType maximumOffset = h.offset(); for(auto const& step : refinementSteps){ maximumOffset = std::max(maximumOffset, storm::utility::vector::dotProduct(h.normalVector(), step.lowerBoundPoint)); } if(maximumOffset > h.offset()){ // We correct the issue by shifting the halfspace such that it contains the underapproximation h.offset() = maximumOffset; STORM_LOG_WARN("Numerical issues: The overapproximation would not contain the underapproximation. Hence, a halfspace is shifted by " << storm::utility::convertNumber(h.invert().euclideanDistance(refinementSteps.back().upperBoundPoint)) << "."); } overApproximation = overApproximation->intersection(h); STORM_LOG_DEBUG("Updated OverApproximation to " << overApproximation->toString(true)); } template void SparsePcaaQuery::updateUnderApproximation() { std::vector paretoPoints; paretoPoints.reserve(refinementSteps.size()); for(auto const& step : refinementSteps) { paretoPoints.push_back(step.lowerBoundPoint); } underApproximation = storm::storage::geometry::Polytope::createDownwardClosure(paretoPoints); STORM_LOG_DEBUG("Updated UnderApproximation to " << underApproximation->toString(true)); } template bool SparsePcaaQuery::maxStepsPerformed() const { return storm::settings::getModule().isMaxStepsSet() && this->refinementSteps.size() >= storm::settings::getModule().getMaxSteps(); } template typename SparsePcaaQuery::Point SparsePcaaQuery::transformPointToOriginalModel(Point const& point) const { Point result; result.reserve(point.size()); for(uint_fast64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) { auto const& obj = this->objectives[objIndex]; if (storm::solver::maximize(obj.formula->getOptimalityType())) { if (obj.considersComplementaryEvent) { result.push_back(storm::utility::one() - point[objIndex]); } else { result.push_back(point[objIndex]); } } else { if (obj.considersComplementaryEvent) { result.push_back(storm::utility::one() + point[objIndex]); } else { result.push_back(-point[objIndex]); } } } return result; } template std::shared_ptr> SparsePcaaQuery::transformPolytopeToOriginalModel(std::shared_ptr> const& polytope) const { if(polytope->isEmpty()) { return storm::storage::geometry::Polytope::createEmptyPolytope(); } if(polytope->isUniversal()) { return storm::storage::geometry::Polytope::createUniversalPolytope(); } uint_fast64_t numObjectives = this->objectives.size(); std::vector> transformationMatrix(numObjectives, std::vector(numObjectives, storm::utility::zero())); std::vector transformationVector; transformationVector.reserve(numObjectives); for(uint_fast64_t objIndex = 0; objIndex < numObjectives; ++objIndex) { auto const& obj = this->objectives[objIndex]; if (storm::solver::maximize(obj.formula->getOptimalityType())) { if (obj.considersComplementaryEvent) { transformationMatrix[objIndex][objIndex] = -storm::utility::one(); transformationVector.push_back(storm::utility::one()); } else { transformationMatrix[objIndex][objIndex] = storm::utility::one(); transformationVector.push_back(storm::utility::zero()); } } else { if (obj.considersComplementaryEvent) { transformationMatrix[objIndex][objIndex] = storm::utility::one(); transformationVector.push_back(storm::utility::one()); } else { transformationMatrix[objIndex][objIndex] = -storm::utility::one(); transformationVector.push_back(storm::utility::zero()); } } } return polytope->affineTransformation(transformationMatrix, transformationVector); } template void SparsePcaaQuery::exportPlotOfCurrentApproximation(std::string const& destinationDir) const { 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; 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(destinationDir + "underapproximation.csv", pointsForPlotting, columnHeaders); 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(destinationDir + "overapproximation.csv", pointsForPlotting, columnHeaders); pointsForPlotting.clear(); pointsForPlotting.reserve(paretoPoints.size()); for(auto const& v : paretoPoints) { pointsForPlotting.push_back(storm::utility::vector::convertNumericVector(v)); } storm::utility::exportDataToCSVFile(destinationDir + "paretopoints.csv", pointsForPlotting, columnHeaders); pointsForPlotting.clear(); auto boundVertices = boundariesAsPolytope->getVerticesInClockwiseOrder(); pointsForPlotting.reserve(4); for(auto const& v : boundVertices) { pointsForPlotting.push_back(storm::utility::vector::convertNumericVector(v)); } storm::utility::exportDataToCSVFile(destinationDir + "boundaries.csv", pointsForPlotting, columnHeaders); } #ifdef STORM_HAVE_CARL template class SparsePcaaQuery, storm::RationalNumber>; template class SparsePcaaQuery, storm::RationalNumber>; template class SparsePcaaQuery, storm::RationalNumber>; template class SparsePcaaQuery, storm::RationalNumber>; #endif } } }