26template<
class SparseModelType,
typename GeometryValueType>
28 : initialStateOfOriginalModel(preprocessorResult.originalModel.getInitialStates().getNextSetIndex(0)), objectives(preprocessorResult.objectives) {
30 "The input model does not have a unique initial state.");
34template<
class SparseModelType,
typename GeometryValueType>
40 storm::exceptions::IllegalArgumentException,
"Unhandled multiobjective precision type.");
43 auto abortIterations = [&env](uint64_t numRefinementSteps) {
46 <<
") has been reached.");
49 STORM_LOG_WARN(
"Aborting multi-objective computation after " << numRefinementSteps <<
" refinement steps as termination has been requested.");
54 auto isMinimizingObjective = [
this](uint64_t objIndex) {
return storm::solver::minimize(this->objectives[objIndex].formula->getOptimalityType()); };
58 std::vector<RefinementStep> refinementSteps;
60 PolytopePtr overApproximation(Polytope::createUniversalPolytope());
63 while (!abortIterations(refinementSteps.size())) {
64 auto answerOrWeights = tryAnswerOrNextWeights(env, refinementSteps, overApproximation, produceScheduler);
65 if (answerOrWeights.index() == 0) {
67 exportPlotOfCurrentApproximation(env, refinementSteps, overApproximation);
69 if (storm::settings::getModule<storm::settings::modules::CoreSettings>().isShowStatisticsSet()) {
70 STORM_PRINT_AND_LOG(
"Multi-objective Pareto Curve Approximation algorithm terminated after " << refinementSteps.size()
71 <<
" refinement steps.\n");
73 return std::move(std::get<0>(answerOrWeights));
75 auto [weightVector, epsilonWso] = std::get<1>(answerOrWeights);
77 GeometryValueType normalizationFactor =
80 STORM_LOG_INFO(
"Iteration #" << refinementSteps.size() <<
": Processing new WSO instance with weight vector "
82 <<
" and precision " << storm::utility::convertNumber<double>(epsilonWso) <<
".");
85 this->weightVectorChecker->setWeightedPrecision(storm::utility::convertNumber<ModelValueType>(epsilonWso));
86 weightVectorChecker->check(env, storm::utility::vector::convertNumericVector<ModelValueType>(weightVector));
87 GeometryValueType optimalWeightedSum = storm::utility::convertNumber<GeometryValueType>(weightVectorChecker->getOptimalWeightedSum());
90 for (
auto const& step : refinementSteps) {
93 if (GeometryValueType
const diff = optimalWeightedSum - storm::utility::convertNumber<GeometryValueType>(weightVectorChecker->getOptimalWeightedSum());
94 diff > epsilonWso / 10) {
95 STORM_LOG_WARN(
"Numerical issues: The overapproximation would not contain the underapproximation. Hence, a halfspace is shifted by "
96 << (storm::utility::convertNumber<double>(diff)) <<
".");
100 refinementSteps.push_back(
101 RefinementStep{.weightVector{std::move(weightVector)},
102 .achievablePoint{storm::utility::vector::convertNumericVector<GeometryValueType>(weightVectorChecker->getAchievablePoint())},
103 .optimalWeightedSum{optimalWeightedSum},
105 auto& currentStep = refinementSteps.back();
109 for (uint64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) {
110 if (isMinimizingObjective(objIndex)) {
111 currentStep.achievablePoint[objIndex] *= -storm::utility::one<GeometryValueType>();
114 if (produceScheduler) {
115 currentStep.scheduler = weightVectorChecker->computeScheduler();
122 std::vector<std::vector<ModelValueType>> achievablePoints;
123 achievablePoints.reserve(refinementSteps.size());
124 for (
auto const& step : refinementSteps) {
125 achievablePoints.push_back(
131template<
class SparseModelType,
typename GeometryValueType>
133 Environment const& env, std::vector<RefinementStep>
const& refinementSteps, PolytopePtr overApproximation,
bool produceScheduler) {
134 if (refinementSteps.size() < objectives.size()) {
136 WeightVector weightVector(objectives.size(), storm::utility::zero<GeometryValueType>());
137 weightVector[refinementSteps.size()] = storm::utility::one<GeometryValueType>();
139 return WeightedSumOptimizationInput{
140 .weightVector{std::move(weightVector)},
141 .epsilonWso{getEpsilonWso(env)},
145 std::vector<GeometryValueType> thresholds(objectives.size(), storm::utility::zero<GeometryValueType>());
146 for (uint64_t objIndex = 0; objIndex < objectives.size(); ++objIndex) {
147 auto const& formula = *objectives[objIndex].formula;
148 if (formula.hasBound()) {
149 objectivesWithThreshold.set(objIndex);
150 thresholds[objIndex] = formula.template getThresholdAs<GeometryValueType>();
153 thresholds[objIndex] *= -storm::utility::one<GeometryValueType>();
157 "Strict bound in objective " << objectives[objIndex].originalFormula <<
" is not supported and will be treated as non-strict bound.");
160 if (objectivesWithThreshold.empty() && objectives.size() > 1) {
161 return tryAnswerOrNextWeightsPareto(env, refinementSteps, overApproximation, produceScheduler);
163 uint64_t
const numObjectivesWithoutBound = objectives.size() - objectivesWithThreshold.getNumberOfSetBits();
164 std::optional<uint64_t> optObjIndex;
165 if (numObjectivesWithoutBound == 1) {
166 optObjIndex = objectivesWithThreshold.getNextUnsetIndex(0);
168 STORM_LOG_THROW(numObjectivesWithoutBound == 0, storm::exceptions::NotSupportedException,
169 "The type of query is not supported: There are multiple objectives with and without a value bound.");
171 return tryAnswerOrNextWeightsAchievability(env, optObjIndex, thresholds, refinementSteps, overApproximation, produceScheduler);
175template<
typename GeometryValueType>
178 uint64_t
const dim = point.size();
179 STORM_LOG_ASSERT(dim > 0,
"Expected at least one dimension for separating halfspace computation.");
180 STORM_LOG_ASSERT(!refinementSteps.empty(),
"Expected at least one refinement step for separating halfspace computation.");
181 auto const zero = storm::utility::zero<GeometryValueType>();
182 auto const one = storm::utility::one<GeometryValueType>();
185 std::vector<storm::expressions::Expression> weightVariableExpressions;
186 weightVariableExpressions.reserve(dim);
187 for (uint64_t i = 0; i < dim; ++i) {
192 for (
auto const& step : refinementSteps) {
193 std::vector<storm::expressions::Expression> sum;
195 for (uint64_t i = 0; i < dim; ++i) {
196 sum.push_back(solver.
getManager().
rational(point[i] - step.achievablePoint[i]) * weightVariableExpressions[i]);
202 std::optional<storm::storage::geometry::Halfspace<GeometryValueType>> result;
204 std::vector<GeometryValueType> normalVector;
205 for (
auto const& w_i : weightVariableExpressions) {
206 normalVector.push_back(solver.
getContinuousValue(w_i.getBaseExpression().asVariableExpression().getVariable()));
209 result.emplace(std::move(normalVector), std::move(offset));
211 STORM_LOG_THROW(solver.
isInfeasible(), storm::exceptions::UnexpectedException,
"Unexpected result of LP solver in separating halfspace computation.");
216template<
class SparseModelType,
typename GeometryValueType>
217typename SparsePcaaQuery<SparseModelType, GeometryValueType>::AnswerOrWeights
218SparsePcaaQuery<SparseModelType, GeometryValueType>::tryAnswerOrNextWeightsAchievability(
Environment const& env, std::optional<uint64_t>
const optObjIndex,
219 std::vector<GeometryValueType>
const& thresholds,
220 std::vector<RefinementStep>
const& refinementSteps,
221 PolytopePtr overApproximation,
bool produceScheduler) {
226 Point referencePoint;
227 if (!optObjIndex.has_value()) {
228 if (!overApproximation->contains(thresholds)) {
232 referencePoint = thresholds;
235 std::vector<Halfspace> thresholdsHalfspaces;
236 for (uint64_t objIndex = 0; objIndex < objectives.size(); ++objIndex) {
237 if (objIndex == optObjIndex.value()) {
240 thresholdsHalfspaces.push_back(Halfspace(WeightVector(objectives.size(), storm::utility::zero<GeometryValueType>()), -thresholds[objIndex]));
241 thresholdsHalfspaces.back().normalVector()[objIndex] = -storm::utility::one<GeometryValueType>();
243 auto thresholdPolytope = Polytope::create(thresholdsHalfspaces);
244 auto intersection = overApproximation->intersection(thresholdPolytope);
245 WeightVector optDirVector(objectives.size(), storm::utility::zero<GeometryValueType>());
246 optDirVector[optObjIndex.value()] = storm::utility::one<GeometryValueType>();
247 auto optRes = overApproximation->intersection(thresholdPolytope)->optimize(optDirVector);
248 if (!optRes.second) {
250 return std::unique_ptr<CheckResult>(
new ExplicitQualitativeCheckResult(initialStateOfOriginalModel,
false));
252 referencePoint = thresholds;
253 referencePoint[optObjIndex.value()] =
257 STORM_LOG_ASSERT(overApproximation->contains(referencePoint),
"Expected reference point to be contained in the over-approximation");
262 bool const referencePointInUnderApproximation = !separatingHalfspace.has_value() || separatingHalfspace->contains(referencePoint);
263 if (referencePointInUnderApproximation) {
265 STORM_LOG_THROW(!produceScheduler, storm::exceptions::NotSupportedException,
266 "Producing schedulers is currently not supported for (numerical) achievability queries.");
268 if (optObjIndex.has_value()) {
270 GeometryValueType result =
272 storm::utility::convertNumber<GeometryValueType, uint64_t>(2));
273 auto resultForOriginalModel =
276 return std::unique_ptr<CheckResult>(
new ExplicitQuantitativeCheckResult<ModelValueType>(initialStateOfOriginalModel, resultForOriginalModel));
278 return std::unique_ptr<CheckResult>(
new ExplicitQualitativeCheckResult(initialStateOfOriginalModel,
true));
283 GeometryValueType eps_wso = getEpsilonWso(env);
285 if (separatingHalfspace->distance(referencePoint) < eps_wso) {
287 auto optResPair = overApproximation->optimize(separatingHalfspace->normalVector());
288 STORM_LOG_ASSERT(optResPair.second,
"Expected optimization to be successful as the over-approximation is non-empty.");
289 eps_wso = getEpsilonWso(env, separatingHalfspace->distance(optResPair.first));
291 return WeightedSumOptimizationInput{
292 .weightVector{separatingHalfspace->normalVector()},
293 .epsilonWso{eps_wso},
297template<
class SparseModelType,
typename GeometryValueType>
298typename SparsePcaaQuery<SparseModelType, GeometryValueType>::AnswerOrWeights SparsePcaaQuery<SparseModelType, GeometryValueType>::tryAnswerOrNextWeightsPareto(
299 Environment
const& env, std::vector<RefinementStep>
const& refinementSteps, PolytopePtr overApproximation,
bool produceScheduler) {
301 std::vector<Point> achievablePoints;
302 achievablePoints.reserve(refinementSteps.size());
303 for (
auto const& step : refinementSteps) {
304 achievablePoints.push_back(step.achievablePoint);
306 PolytopePtr underApproximation = Polytope::createDownwardClosure(achievablePoints);
307 auto achievableHalfspaces = underApproximation->getHalfspaces();
309 GeometryValueType delta = storm::utility::convertNumber<GeometryValueType>(env.modelchecker().multi().getPrecision()) /
310 storm::utility::convertNumber<GeometryValueType>(std::sqrt(objectives.size()));
311 for (
auto const& halfspace : achievableHalfspaces) {
312 GeometryValueType
const sumOfWeights =
313 std::accumulate(halfspace.normalVector().begin(), halfspace.normalVector().end(), storm::utility::zero<GeometryValueType>());
314 auto invertedShiftedHalfspace = halfspace.invert();
315 invertedShiftedHalfspace.offset() -= delta * sumOfWeights;
316 auto intersection = overApproximation->intersection(invertedShiftedHalfspace);
317 if (!intersection->isEmpty()) {
318 return WeightedSumOptimizationInput{
319 .weightVector{halfspace.normalVector()},
320 .epsilonWso{getEpsilonWso(env)},
328 std::vector<std::vector<ModelValueType>> paretoOptimalPoints;
329 std::vector<storm::storage::Scheduler<ModelValueType>> paretoOptimalSchedulers;
330 std::vector<Point> vertices = underApproximation->getVertices();
331 paretoOptimalPoints.reserve(vertices.size());
332 for (
auto const& vertex : vertices) {
333 paretoOptimalPoints.push_back(
335 if (produceScheduler) {
340 auto stepIt = std::find_if(refinementSteps.begin(), refinementSteps.end(), [&vertex](
auto const& step) { return step.achievablePoint == vertex; });
345 paretoOptimalSchedulers.push_back(std::move(stepIt->scheduler.value()));
348 return std::unique_ptr<CheckResult>(
new ExplicitParetoCurveCheckResult<ModelValueType>(
349 initialStateOfOriginalModel, std::move(paretoOptimalPoints), std::move(paretoOptimalSchedulers),
354template<
typename SparseModelType,
typename GeometryValueType>
355GeometryValueType SparsePcaaQuery<SparseModelType, GeometryValueType>::getEpsilonWso(Environment
const& env, std::optional<GeometryValueType> approxDistance) {
358 GeometryValueType gamma;
359 if (env.modelchecker().multi().isApproximationTradeoffSet()) {
361 gamma = storm::utility::convertNumber<GeometryValueType>(env.modelchecker().multi().getApproximationTradeoff());
364 if (env.solver().isForceExact()) {
365 gamma = storm::utility::zero<GeometryValueType>();
366 }
else if (env.solver().isForceSoundness() || weightVectorChecker->smallPrecisionsAreChallenging()) {
368 gamma = storm::utility::convertNumber<GeometryValueType>(0.5);
372 gamma = storm::utility::convertNumber<GeometryValueType>(1.0 / 64.0);
377 GeometryValueType eps_multi = storm::utility::convertNumber<GeometryValueType>(env.modelchecker().multi().getPrecision());
378 if (approxDistance.has_value()) {
379 eps_multi = std::min<GeometryValueType>(eps_multi, approxDistance.value());
384 return gamma * eps_multi / storm::utility::convertNumber<GeometryValueType>(std::sqrt(objectives.size()));
387template<
typename SparseModelType,
typename GeometryValueType>
388void SparsePcaaQuery<SparseModelType, GeometryValueType>::exportPlotOfCurrentApproximation(Environment
const& env,
389 std::vector<RefinementStep>
const& refinementSteps,
390 PolytopePtr overApproximation)
const {
391 STORM_LOG_ERROR_COND(objectives.size() == 2,
"Exporting plot requested but this is only implemented for the two-dimensional case.");
395 std::vector<GeometryValueType>(objectives.size(), storm::utility::zero<GeometryValueType>()),
396 std::vector<GeometryValueType>(objectives.size(), storm::utility::zero<GeometryValueType>()));
397 std::vector<std::vector<GeometryValueType>> achievablePoints;
398 achievablePoints.reserve(refinementSteps.size());
399 for (
auto const& step : refinementSteps) {
401 boundaries.enlarge(achievablePoints.back());
404 PolytopePtr underApproximation = Polytope::createDownwardClosure(achievablePoints);
408 auto underApproxVertices = transformedUnderApprox->getVertices();
409 for (
auto const& v : underApproxVertices) {
410 boundaries.enlarge(v);
412 auto overApproxVertices = transformedOverApprox->getVertices();
413 for (
auto const& v : overApproxVertices) {
414 boundaries.enlarge(v);
421 auto boundariesAsPolytope = boundaries.asPolytope();
422 std::vector<std::string> columnHeaders = {
"x",
"y"};
424 std::vector<std::vector<double>> pointsForPlotting;
425 if (env.modelchecker().multi().getPlotPathUnderApproximation()) {
426 underApproxVertices = transformedUnderApprox->intersection(boundariesAsPolytope)->getVerticesInClockwiseOrder();
427 pointsForPlotting.reserve(underApproxVertices.size());
428 for (
auto const& v : underApproxVertices) {
429 pointsForPlotting.push_back(storm::utility::vector::convertNumericVector<double>(v));
431 storm::io::exportDataToCSVFile<double, std::string>(env.modelchecker().multi().getPlotPathUnderApproximation().get(), pointsForPlotting, columnHeaders);
434 if (env.modelchecker().multi().getPlotPathOverApproximation()) {
435 pointsForPlotting.clear();
436 overApproxVertices = transformedOverApprox->intersection(boundariesAsPolytope)->getVerticesInClockwiseOrder();
437 pointsForPlotting.reserve(overApproxVertices.size());
438 for (
auto const& v : overApproxVertices) {
439 pointsForPlotting.push_back(storm::utility::vector::convertNumericVector<double>(v));
441 storm::io::exportDataToCSVFile<double, std::string>(env.modelchecker().multi().getPlotPathOverApproximation().get(), pointsForPlotting, columnHeaders);
444 if (env.modelchecker().multi().getPlotPathParetoPoints()) {
445 pointsForPlotting.clear();
446 pointsForPlotting.reserve(achievablePoints.size());
447 for (
auto const& v : achievablePoints) {
448 pointsForPlotting.push_back(storm::utility::vector::convertNumericVector<double>(v));
450 storm::io::exportDataToCSVFile<double, std::string>(env.modelchecker().multi().getPlotPathParetoPoints().get(), pointsForPlotting, columnHeaders);
454template class SparsePcaaQuery<storm::models::sparse::Mdp<double>, storm::RationalNumber>;
455template class SparsePcaaQuery<storm::models::sparse::MarkovAutomaton<double>, storm::RationalNumber>;
457template class SparsePcaaQuery<storm::models::sparse::Mdp<storm::RationalNumber>, storm::RationalNumber>;
458template class SparsePcaaQuery<storm::models::sparse::MarkovAutomaton<storm::RationalNumber>, storm::RationalNumber>;
ModelCheckerEnvironment & modelchecker()
MultiObjectiveModelCheckerEnvironment & multi()
uint64_t const & getMaxSteps() const
bool isExportPlotSet() const
storm::RationalNumber const & getPrecision() const
bool isMaxStepsSet() const
PrecisionType const & getPrecisionType() const
Expression rational(double value) const
Creates an expression that characterizes the given rational literal.
std::shared_ptr< Polytope > PolytopePtr
SparsePcaaQuery(PreprocessorResult &preprocessorResult)
Creates a new query for the Pareto curve approximation algorithm (Pcaa)
std::unique_ptr< CheckResult > check(Environment const &env, bool produceScheduler)
Invokes the computation and retrieves the result.
storm::expressions::ExpressionManager const & getManager() const
Retrieves the manager for the variables created for this solver.
Variable addBoundedContinuousVariable(std::string const &name, ValueType lowerBound, ValueType upperBound, ValueType objectiveFunctionCoefficient=0)
Registers an upper- and lower-bounded continuous variable, i.e.
Variable addUnboundedContinuousVariable(std::string const &name, ValueType objectiveFunctionCoefficient=0)
Registers a unbounded continuous variable, i.e.
A class that implements the LpSolver interface using Z3.
virtual void update() const override
Updates the model to make the variables that have been declared since the last call to update usable.
virtual ValueType getContinuousValue(Variable const &variable) const override
Retrieves the value of the continuous variable with the given name.
virtual void addConstraint(std::string const &name, Constraint const &constraint) override
Adds a the given constraint to the LP problem.
virtual bool isOptimal() const override
Retrieves whether the model was found to be optimal, i.e.
virtual bool isInfeasible() const override
Retrieves whether the model was found to be infeasible.
virtual void optimize() const override
Optimizes the LP problem previously constructed.
A bit vector that is internally represented as a vector of 64-bit values.
#define STORM_LOG_INFO(message)
#define STORM_LOG_WARN(message)
#define STORM_LOG_ASSERT(cond, message)
#define STORM_LOG_WARN_COND(cond, message)
#define STORM_LOG_ERROR_COND(cond, message)
#define STORM_LOG_THROW(cond, exception, message)
#define STORM_PRINT_AND_LOG(message)
Expression sum(std::vector< storm::expressions::Expression > const &expressions)
bool isStrict(ComparisonType t)
auto findSeparatingHalfspace(auto const &refinementSteps, std::vector< GeometryValueType > const &point)
std::shared_ptr< storm::storage::geometry::Polytope< GeometryValueType > > transformObjectivePolytopeToOriginal(std::vector< Objective< ValueType > > const &objectives, std::shared_ptr< storm::storage::geometry::Polytope< GeometryValueType > > const &polytope)
std::unique_ptr< PcaaWeightVectorChecker< ModelType > > createWeightVectorChecker(preprocessing::SparseMultiObjectivePreprocessorResult< ModelType > const &preprocessorResult)
std::vector< GeometryValueType > transformObjectiveValuesToOriginal(std::vector< Objective< ValueType > > const &objectives, std::vector< GeometryValueType > const &point)
GeometryValueType transformObjectiveValueToOriginal(Objective< ValueType > const &objective, GeometryValueType const &value)
bool constexpr minimize(OptimizationDirection d)
bool isTerminate()
Check whether the program should terminate (due to some abort signal).
T dotProduct(std::vector< T > const &firstOperand, std::vector< T > const &secondOperand)
Computes the dot product (aka scalar product) and returns the result.
std::string toString(std::vector< ValueType > const &vector)
Output vector as string.
void scaleVectorInPlace(std::vector< ValueType1 > &target, ValueType2 const &factor)
Multiplies each element of the given vector with the given factor and writes the result into the vect...
ValueType sqrt(ValueType const &number)
SparseModelType const & originalModel