#include "storm/modelchecker/multiobjective/pcaa/SparsePcaaQuantitativeQuery.h" #include "storm/adapters/CarlAdapter.h" #include "storm/models/sparse/Mdp.h" #include "storm/models/sparse/MarkovAutomaton.h" #include "storm/models/sparse/StandardRewardModel.h" #include "storm/modelchecker/results/ExplicitQualitativeCheckResult.h" #include "storm/modelchecker/results/ExplicitQuantitativeCheckResult.h" #include "storm/utility/constants.h" #include "storm/utility/vector.h" #include "storm/settings//SettingsManager.h" #include "storm/settings/modules/MultiObjectiveSettings.h" #include "storm/settings/modules/GeneralSettings.h" namespace storm { namespace modelchecker { namespace multiobjective { template SparsePcaaQuantitativeQuery::SparsePcaaQuantitativeQuery(SparsePcaaPreprocessorReturnType& preprocessorResult) : SparsePcaaQuery(preprocessorResult) { STORM_LOG_ASSERT(preprocessorResult.queryType==SparsePcaaPreprocessorReturnType::QueryType::Quantitative, "Invalid query Type"); STORM_LOG_ASSERT(preprocessorResult.indexOfOptimizingObjective, "Detected quantitative query but index of optimizing objective is not set."); indexOfOptimizingObjective = *preprocessorResult.indexOfOptimizingObjective; initializeThresholdData(); // Set the maximum distance between lower and upper bound of the weightVectorChecker result. this->weightVectorChecker->setWeightedPrecision(storm::utility::convertNumber(0.1)); } template void SparsePcaaQuantitativeQuery::initializeThresholdData() { thresholds.reserve(this->objectives.size()); strictThresholds = storm::storage::BitVector(this->objectives.size(), false); std::vector> thresholdConstraints; thresholdConstraints.reserve(this->objectives.size()-1); for(uint_fast64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) { if(this->objectives[objIndex].threshold) { thresholds.push_back(storm::utility::convertNumber(*this->objectives[objIndex].threshold)); WeightVector normalVector(this->objectives.size(), storm::utility::zero()); normalVector[objIndex] = -storm::utility::one(); thresholdConstraints.emplace_back(std::move(normalVector), -thresholds.back()); strictThresholds.set(objIndex, this->objectives[objIndex].thresholdIsStrict); } else { thresholds.push_back(storm::utility::zero()); } } // Note: If we have a single objective (i.e., no objectives with thresholds), thresholdsAsPolytope gets no constraints thresholdsAsPolytope = storm::storage::geometry::Polytope::create(thresholdConstraints); } template std::unique_ptr SparsePcaaQuantitativeQuery::check() { // First find one solution that achieves the given thresholds ... if(this->checkAchievability()) { // ... then improve it GeometryValueType result = this->improveSolution(); // transform the obtained result for the preprocessed model to a result w.r.t. the original model and return the checkresult typename SparseModelType::ValueType resultForOriginalModel = storm::utility::convertNumber(result) * this->objectives[indexOfOptimizingObjective].toOriginalValueTransformationFactor + this->objectives[indexOfOptimizingObjective].toOriginalValueTransformationOffset; return std::unique_ptr(new ExplicitQuantitativeCheckResult(this->originalModel.getInitialStates().getNextSetIndex(0), resultForOriginalModel)); } else { return std::unique_ptr(new ExplicitQualitativeCheckResult(this->originalModel.getInitialStates().getNextSetIndex(0), false)); } } template bool SparsePcaaQuantitativeQuery::checkAchievability() { if(this->objectives.size()>1) { // We don't care for the optimizing objective at this point this->diracWeightVectorsToBeChecked.set(indexOfOptimizingObjective, false); while(!this->maxStepsPerformed()){ WeightVector separatingVector = this->findSeparatingVector(thresholds); this->updateWeightedPrecisionInAchievabilityPhase(separatingVector); this->performRefinementStep(std::move(separatingVector)); //Pick the threshold for the optimizing objective low enough so valid solutions are not excluded thresholds[indexOfOptimizingObjective] = std::min(thresholds[indexOfOptimizingObjective], this->refinementSteps.back().lowerBoundPoint[indexOfOptimizingObjective]); if(!checkIfThresholdsAreSatisfied(this->overApproximation)){ return false; } if(checkIfThresholdsAreSatisfied(this->underApproximation)){ return true; } } } else { // If there is only one objective than its the optimizing one. Thus the query has to be achievable. return true; } STORM_LOG_ERROR("Could not check whether thresholds are achievable: Exceeded maximum number of refinement steps"); return false; } template void SparsePcaaQuantitativeQuery::updateWeightedPrecisionInAchievabilityPhase(WeightVector const& weights) { // Our heuristic considers the distance between the under- and the over approximation w.r.t. the given direction std::pair optimizationResOverApprox = this->overApproximation->optimize(weights); if(optimizationResOverApprox.second) { std::pair optimizationResUnderApprox = this->underApproximation->optimize(weights); if(optimizationResUnderApprox.second) { GeometryValueType distance = storm::utility::vector::dotProduct(optimizationResOverApprox.first, weights) - storm::utility::vector::dotProduct(optimizationResUnderApprox.first, weights); STORM_LOG_ASSERT(distance >= storm::utility::zero(), "Negative distance between under- and over approximation was not expected"); // Normalize the distance by dividing it with the Euclidean Norm of the weight-vector distance /= storm::utility::sqrt(storm::utility::vector::dotProduct(weights, weights)); distance /= GeometryValueType(2); this->weightVectorChecker->setWeightedPrecision(storm::utility::convertNumber(distance)); } } // do not update the precision if one of the approximations is unbounded in the provided direction } template GeometryValueType SparsePcaaQuantitativeQuery::improveSolution() { this->diracWeightVectorsToBeChecked.clear(); // Only check weight vectors that can actually improve the solution WeightVector directionOfOptimizingObjective(this->objectives.size(), storm::utility::zero()); directionOfOptimizingObjective[indexOfOptimizingObjective] = storm::utility::one(); // Improve the found solution. // Note that we do not care whether a threshold is strict anymore, because the resulting optimum should be // the supremum over all strategies. Hence, one could combine a scheduler inducing the optimum value (but possibly violating strict // thresholds) and (with very low probability) a scheduler that satisfies all (possibly strict) thresholds. GeometryValueType result = storm::utility::zero(); while(!this->maxStepsPerformed()) { if(this->refinementSteps.empty()) { // We did not make any refinement steps during the checkAchievability phase (e.g., because there is only one objective). this->weightVectorChecker->setWeightedPrecision(storm::utility::convertNumber(storm::settings::getModule().getPrecision())); WeightVector separatingVector = directionOfOptimizingObjective; this->performRefinementStep(std::move(separatingVector)); } std::pair optimizationRes = this->underApproximation->intersection(thresholdsAsPolytope)->optimize(directionOfOptimizingObjective); STORM_LOG_THROW(optimizationRes.second, storm::exceptions::UnexpectedException, "The underapproximation is either unbounded or empty."); result = optimizationRes.first[indexOfOptimizingObjective]; STORM_LOG_DEBUG("Best solution found so far is ~" << storm::utility::convertNumber(result) << "."); //Compute an upper bound for the optimum and check for convergence optimizationRes = this->overApproximation->intersection(thresholdsAsPolytope)->optimize(directionOfOptimizingObjective); if(optimizationRes.second) { GeometryValueType precisionOfResult = optimizationRes.first[indexOfOptimizingObjective] - result; if(precisionOfResult < storm::utility::convertNumber(storm::settings::getModule().getPrecision())) { // Goal precision reached! return result; } else { STORM_LOG_DEBUG("Solution can be improved by at most " << storm::utility::convertNumber(precisionOfResult)); thresholds[indexOfOptimizingObjective] = optimizationRes.first[indexOfOptimizingObjective]; } } else { thresholds[indexOfOptimizingObjective] = result + storm::utility::one(); } WeightVector separatingVector = this->findSeparatingVector(thresholds); this->updateWeightedPrecisionInImprovingPhase(separatingVector); this->performRefinementStep(std::move(separatingVector)); } STORM_LOG_ERROR("Could not reach the desired precision: Exceeded maximum number of refinement steps"); return result; } template void SparsePcaaQuantitativeQuery::updateWeightedPrecisionInImprovingPhase(WeightVector const& weights) { STORM_LOG_THROW(!storm::utility::isZero(weights[this->indexOfOptimizingObjective]), exceptions::UnexpectedException, "The chosen weight-vector gives zero weight for the objective that is to be optimized."); // If weighs[indexOfOptimizingObjective] is low, the computation of the weightVectorChecker needs to be more precise. // Our heuristic ensures that if p is the new vertex of the under-approximation, then max{ eps | p' = p + (0..0 eps 0..0) is in the over-approximation } <= multiobjective_precision/0.9 GeometryValueType weightedPrecision = weights[this->indexOfOptimizingObjective] * storm::utility::convertNumber(storm::settings::getModule().getPrecision()); // Normalize by division with the Euclidean Norm of the weight-vector weightedPrecision /= storm::utility::sqrt(storm::utility::vector::dotProduct(weights, weights)); weightedPrecision *= storm::utility::convertNumber(0.9); this->weightVectorChecker->setWeightedPrecision(storm::utility::convertNumber(weightedPrecision)); } template bool SparsePcaaQuantitativeQuery::checkIfThresholdsAreSatisfied(std::shared_ptr> const& polytope) { std::vector> halfspaces = polytope->getHalfspaces(); for(auto const& h : halfspaces) { if(storm::utility::isZero(h.distance(thresholds))) { // Check if the threshold point is on the boundary of the halfspace and whether this is violates strict thresholds if(h.isPointOnBoundary(thresholds)) { for(auto strictThreshold : strictThresholds) { if(h.normalVector()[strictThreshold] > storm::utility::zero()) { return false; } } } } else { return false; } } return true; } #ifdef STORM_HAVE_CARL template class SparsePcaaQuantitativeQuery, storm::RationalNumber>; template class SparsePcaaQuantitativeQuery, storm::RationalNumber>; template class SparsePcaaQuantitativeQuery, storm::RationalNumber>; template class SparsePcaaQuantitativeQuery, storm::RationalNumber>; #endif } } }