You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

266 lines
19 KiB

#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 <class SparseModelType, typename GeometryValueType>
SparsePcaaQuery<SparseModelType, GeometryValueType>::SparsePcaaQuery(SparseMultiObjectivePreprocessorResult<SparseModelType>& preprocessorResult) :
originalModel(preprocessorResult.originalModel), originalFormula(preprocessorResult.originalFormula), objectives(preprocessorResult.objectives) {
this->weightVectorChecker = WeightVectorCheckerFactory<SparseModelType>::create(preprocessorResult);
this->diracWeightVectorsToBeChecked = storm::storage::BitVector(this->objectives.size(), true);
this->overApproximation = storm::storage::geometry::Polytope<GeometryValueType>::createUniversalPolytope();
this->underApproximation = storm::storage::geometry::Polytope<GeometryValueType>::createEmptyPolytope();
}
template <class SparseModelType, typename GeometryValueType>
typename SparsePcaaQuery<SparseModelType, GeometryValueType>::WeightVector SparsePcaaQuery<SparseModelType, GeometryValueType>::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<double>(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<GeometryValueType>());
result[objIndex] = storm::utility::one<GeometryValueType>();
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<storm::storage::geometry::Halfspace<GeometryValueType>> halfspaces = underApproximation->getHalfspaces();
uint_fast64_t farestHalfspaceIndex = halfspaces.size();
GeometryValueType farestDistance = -storm::utility::one<GeometryValueType>();
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<GeometryValueType>(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<GeometryValueType>(halfspaces[farestHalfspaceIndex].normalVector());
}
STORM_LOG_THROW(farestHalfspaceIndex<halfspaces.size(), storm::exceptions::UnexpectedException, "There is no seperating vector.");
STORM_LOG_DEBUG("Found separating weight vector: " << storm::utility::vector::toString(storm::utility::vector::convertNumericVector<double>(halfspaces[farestHalfspaceIndex].normalVector())) << ".");
return halfspaces[farestHalfspaceIndex].normalVector();
}
template <class SparseModelType, typename GeometryValueType>
void SparsePcaaQuery<SparseModelType, GeometryValueType>::performRefinementStep(WeightVector&& direction) {
// Normalize the direction vector so that the entries sum up to one
storm::utility::vector::scaleVectorInPlace(direction, storm::utility::one<GeometryValueType>() / std::accumulate(direction.begin(), direction.end(), storm::utility::zero<GeometryValueType>()));
weightVectorChecker->check(storm::utility::vector::convertNumericVector<typename SparseModelType::ValueType>(direction));
STORM_LOG_DEBUG("weighted objectives checker result (under approximation) is " << storm::utility::vector::toString(storm::utility::vector::convertNumericVector<double>(weightVectorChecker->getUnderApproximationOfInitialStateResults())));
RefinementStep step;
step.weightVector = direction;
step.lowerBoundPoint = storm::utility::vector::convertNumericVector<GeometryValueType>(weightVectorChecker->getUnderApproximationOfInitialStateResults());
step.upperBoundPoint = storm::utility::vector::convertNumericVector<GeometryValueType>(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<GeometryValueType>();
step.upperBoundPoint[objIndex] *= -storm::utility::one<GeometryValueType>();
}
}
refinementSteps.push_back(std::move(step));
updateOverApproximation();
updateUnderApproximation();
}
template <class SparseModelType, typename GeometryValueType>
void SparsePcaaQuery<SparseModelType, GeometryValueType>::updateOverApproximation() {
storm::storage::geometry::Halfspace<GeometryValueType> 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<double>(h.invert().euclideanDistance(refinementSteps.back().upperBoundPoint)) << ".");
}
overApproximation = overApproximation->intersection(h);
STORM_LOG_DEBUG("Updated OverApproximation to " << overApproximation->toString(true));
}
template <class SparseModelType, typename GeometryValueType>
void SparsePcaaQuery<SparseModelType, GeometryValueType>::updateUnderApproximation() {
std::vector<Point> paretoPoints;
paretoPoints.reserve(refinementSteps.size());
for(auto const& step : refinementSteps) {
paretoPoints.push_back(step.lowerBoundPoint);
}
underApproximation = storm::storage::geometry::Polytope<GeometryValueType>::createDownwardClosure(paretoPoints);
STORM_LOG_DEBUG("Updated UnderApproximation to " << underApproximation->toString(true));
}
template <class SparseModelType, typename GeometryValueType>
bool SparsePcaaQuery<SparseModelType, GeometryValueType>::maxStepsPerformed() const {
return storm::settings::getModule<storm::settings::modules::MultiObjectiveSettings>().isMaxStepsSet() &&
this->refinementSteps.size() >= storm::settings::getModule<storm::settings::modules::MultiObjectiveSettings>().getMaxSteps();
}
template<typename SparseModelType, typename GeometryValueType>
typename SparsePcaaQuery<SparseModelType, GeometryValueType>::Point SparsePcaaQuery<SparseModelType, GeometryValueType>::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<GeometryValueType>() - point[objIndex]);
} else {
result.push_back(point[objIndex]);
}
} else {
if (obj.considersComplementaryEvent) {
result.push_back(storm::utility::one<GeometryValueType>() + point[objIndex]);
} else {
result.push_back(-point[objIndex]);
}
}
}
return result;
}
template<typename SparseModelType, typename GeometryValueType>
std::shared_ptr<storm::storage::geometry::Polytope<GeometryValueType>> SparsePcaaQuery<SparseModelType, GeometryValueType>::transformPolytopeToOriginalModel(std::shared_ptr<storm::storage::geometry::Polytope<GeometryValueType>> const& polytope) const {
if(polytope->isEmpty()) {
return storm::storage::geometry::Polytope<GeometryValueType>::createEmptyPolytope();
}
if(polytope->isUniversal()) {
return storm::storage::geometry::Polytope<GeometryValueType>::createUniversalPolytope();
}
uint_fast64_t numObjectives = this->objectives.size();
std::vector<std::vector<GeometryValueType>> transformationMatrix(numObjectives, std::vector<GeometryValueType>(numObjectives, storm::utility::zero<GeometryValueType>()));
std::vector<GeometryValueType> 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<GeometryValueType>();
transformationVector.push_back(storm::utility::one<GeometryValueType>());
} else {
transformationMatrix[objIndex][objIndex] = storm::utility::one<GeometryValueType>();
transformationVector.push_back(storm::utility::zero<GeometryValueType>());
}
} else {
if (obj.considersComplementaryEvent) {
transformationMatrix[objIndex][objIndex] = storm::utility::one<GeometryValueType>();
transformationVector.push_back(storm::utility::one<GeometryValueType>());
} else {
transformationMatrix[objIndex][objIndex] = -storm::utility::one<GeometryValueType>();
transformationVector.push_back(storm::utility::zero<GeometryValueType>());
}
}
}
return polytope->affineTransformation(transformationMatrix, transformationVector);
}
template<typename SparseModelType, typename GeometryValueType>
void SparsePcaaQuery<SparseModelType, GeometryValueType>::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<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;
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(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<double>(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<double>(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<double>(v));
}
storm::utility::exportDataToCSVFile(destinationDir + "boundaries.csv", pointsForPlotting, columnHeaders);
}
#ifdef STORM_HAVE_CARL
template class SparsePcaaQuery<storm::models::sparse::Mdp<double>, storm::RationalNumber>;
template class SparsePcaaQuery<storm::models::sparse::MarkovAutomaton<double>, storm::RationalNumber>;
template class SparsePcaaQuery<storm::models::sparse::Mdp<storm::RationalNumber>, storm::RationalNumber>;
template class SparsePcaaQuery<storm::models::sparse::MarkovAutomaton<storm::RationalNumber>, storm::RationalNumber>;
#endif
}
}
}