Storm 1.11.1.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
SparsePcaaQuery.cpp
Go to the documentation of this file.
2
8#include "storm/io/export.h"
23
25
26template<class SparseModelType, typename GeometryValueType>
28 : initialStateOfOriginalModel(preprocessorResult.originalModel.getInitialStates().getNextSetIndex(0)), objectives(preprocessorResult.objectives) {
29 STORM_LOG_THROW(preprocessorResult.originalModel.getInitialStates().hasUniqueSetBit(), storm::exceptions::NotSupportedException,
30 "The input model does not have a unique initial state.");
31 this->weightVectorChecker = createWeightVectorChecker(preprocessorResult);
32}
33
34template<class SparseModelType, typename GeometryValueType>
35std::unique_ptr<CheckResult> SparsePcaaQuery<SparseModelType, GeometryValueType>::check(Environment const& env, bool produceScheduler) {
36 // Following Algorithm 3.1 of https://doi.org/10.18154/RWTH-2023-09669
37
38 // Ensure that we can handle the input
40 storm::exceptions::IllegalArgumentException, "Unhandled multiobjective precision type.");
41
42 // Auxiliary helper functions for better readability
43 auto abortIterations = [&env](uint64_t numRefinementSteps) {
44 if (env.modelchecker().multi().isMaxStepsSet() && numRefinementSteps >= env.modelchecker().multi().getMaxSteps()) {
45 STORM_LOG_WARN("Aborting multi-objective computation as the maximum number of refinement steps (" << env.modelchecker().multi().getMaxSteps()
46 << ") has been reached.");
47 return true;
49 STORM_LOG_WARN("Aborting multi-objective computation after " << numRefinementSteps << " refinement steps as termination has been requested.");
50 return true;
51 }
52 return false;
53 };
54 auto isMinimizingObjective = [this](uint64_t objIndex) { return storm::solver::minimize(this->objectives[objIndex].formula->getOptimalityType()); };
55
56 // Data maintained through the iterations
57 // The results in each iteration of the algorithm (including achievable points)
58 std::vector<RefinementStep> refinementSteps;
59 // Over-approximation of the set of achievable points
60 PolytopePtr overApproximation(Polytope::createUniversalPolytope());
61
62 // Start iterative refinement
63 while (!abortIterations(refinementSteps.size())) {
64 auto answerOrWeights = tryAnswerOrNextWeights(env, refinementSteps, overApproximation, produceScheduler);
65 if (answerOrWeights.index() == 0) {
66 if (env.modelchecker().multi().isExportPlotSet()) {
67 exportPlotOfCurrentApproximation(env, refinementSteps, overApproximation);
68 }
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");
72 }
73 return std::move(std::get<0>(answerOrWeights));
74 };
75 auto [weightVector, epsilonWso] = std::get<1>(answerOrWeights);
76 // Normalize the weight vector to make sure that its magnitude does not influence the accuracy of the weighted sum optimization
77 GeometryValueType normalizationFactor =
78 storm::utility::one<GeometryValueType>() / storm::utility::sqrt(storm::utility::vector::dotProduct(weightVector, weightVector));
79 storm::utility::vector::scaleVectorInPlace(weightVector, normalizationFactor);
80 STORM_LOG_INFO("Iteration #" << refinementSteps.size() << ": Processing new WSO instance with weight vector "
81 << storm::utility::vector::toString(storm::utility::vector::convertNumericVector<double>(weightVector))
82 << " and precision " << storm::utility::convertNumber<double>(epsilonWso) << ".");
83
84 // Solve WSO instance
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());
88 // Due to numerical issues, it might be that the found optimal weighted sum is smaller than the actual weighted sum of one of the achievable points.
89 // To avoid that our over-approximation does not contain all achievable points, we correct this here.
90 for (auto const& step : refinementSteps) {
91 optimalWeightedSum = std::max(optimalWeightedSum, storm::utility::vector::dotProduct(weightVector, step.achievablePoint));
92 }
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)) << ".");
97 }
98
99 // Store result of iteration
100 refinementSteps.push_back(
101 RefinementStep{.weightVector{std::move(weightVector)},
102 .achievablePoint{storm::utility::vector::convertNumericVector<GeometryValueType>(weightVectorChecker->getAchievablePoint())},
103 .optimalWeightedSum{optimalWeightedSum},
104 .scheduler{}});
105 auto& currentStep = refinementSteps.back();
107 "WSO found point " << storm::utility::vector::toString(storm::utility::vector::convertNumericVector<double>(currentStep.achievablePoint)));
108 // For the minimizing objectives, we need to scale the corresponding entries with -1 as we want to consider the downward closure
109 for (uint64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) {
110 if (isMinimizingObjective(objIndex)) {
111 currentStep.achievablePoint[objIndex] *= -storm::utility::one<GeometryValueType>();
112 }
113 }
114 if (produceScheduler) {
115 currentStep.scheduler = weightVectorChecker->computeScheduler();
116 }
117 overApproximation =
118 overApproximation->intersection(storm::storage::geometry::Halfspace<GeometryValueType>(currentStep.weightVector, currentStep.optimalWeightedSum));
119 }
120 // Reaching this means that we aborted the iterations
121 // Return a best-effort solution
122 std::vector<std::vector<ModelValueType>> achievablePoints;
123 achievablePoints.reserve(refinementSteps.size());
124 for (auto const& step : refinementSteps) {
125 achievablePoints.push_back(
126 storm::utility::vector::convertNumericVector<ModelValueType>(transformObjectiveValuesToOriginal(objectives, step.achievablePoint)));
127 }
128 return std::unique_ptr<CheckResult>(new ExplicitParetoCurveCheckResult<ModelValueType>(initialStateOfOriginalModel, std::move(achievablePoints)));
129}
130
131template<class SparseModelType, typename GeometryValueType>
132typename SparsePcaaQuery<SparseModelType, GeometryValueType>::AnswerOrWeights SparsePcaaQuery<SparseModelType, GeometryValueType>::tryAnswerOrNextWeights(
133 Environment const& env, std::vector<RefinementStep> const& refinementSteps, PolytopePtr overApproximation, bool produceScheduler) {
134 if (refinementSteps.size() < objectives.size()) {
135 // At least optimize each objective once
136 WeightVector weightVector(objectives.size(), storm::utility::zero<GeometryValueType>());
137 weightVector[refinementSteps.size()] = storm::utility::one<GeometryValueType>();
138
139 return WeightedSumOptimizationInput{
140 .weightVector{std::move(weightVector)},
141 .epsilonWso{getEpsilonWso(env)},
142 };
143 }
144 storm::storage::BitVector objectivesWithThreshold(objectives.size(), false);
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>();
151 if (storm::solver::minimize(formula.getOptimalityType())) {
152 // Values for minimizing objectives will be negated in order to convert them to maximizing objectives.
153 thresholds[objIndex] *= -storm::utility::one<GeometryValueType>();
154 }
156 !storm::logic::isStrict(formula.getBound().comparisonType),
157 "Strict bound in objective " << objectives[objIndex].originalFormula << " is not supported and will be treated as non-strict bound.");
158 }
159 }
160 if (objectivesWithThreshold.empty() && objectives.size() > 1) {
161 return tryAnswerOrNextWeightsPareto(env, refinementSteps, overApproximation, produceScheduler);
162 } else {
163 uint64_t const numObjectivesWithoutBound = objectives.size() - objectivesWithThreshold.getNumberOfSetBits();
164 std::optional<uint64_t> optObjIndex;
165 if (numObjectivesWithoutBound == 1) {
166 optObjIndex = objectivesWithThreshold.getNextUnsetIndex(0);
167 } else {
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.");
170 }
171 return tryAnswerOrNextWeightsAchievability(env, optObjIndex, thresholds, refinementSteps, overApproximation, produceScheduler);
172 }
173}
174
175template<typename GeometryValueType>
176auto findSeparatingHalfspace(auto const& refinementSteps, std::vector<GeometryValueType> const& point) {
177 // Build the LP from Figure 3.9 of https://doi.org/10.18154/RWTH-2023-09669
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>();
183
184 storm::solver::Z3LpSolver<GeometryValueType> solver(storm::solver::OptimizationDirection::Maximize);
185 std::vector<storm::expressions::Expression> weightVariableExpressions;
186 weightVariableExpressions.reserve(dim);
187 for (uint64_t i = 0; i < dim; ++i) {
188 weightVariableExpressions.push_back(solver.addBoundedContinuousVariable("w" + std::to_string(i), zero, one));
189 }
190 solver.addConstraint("", storm::expressions::sum(weightVariableExpressions) <= solver.getManager().rational(one));
191 auto distVar = solver.addUnboundedContinuousVariable("d", one);
192 for (auto const& step : refinementSteps) {
193 std::vector<storm::expressions::Expression> sum;
194 sum.reserve(dim);
195 for (uint64_t i = 0; i < dim; ++i) {
196 sum.push_back(solver.getManager().rational(point[i] - step.achievablePoint[i]) * weightVariableExpressions[i]);
197 }
198 solver.addConstraint("", distVar <= storm::expressions::sum(sum));
199 }
200 solver.update();
201 solver.optimize();
202 std::optional<storm::storage::geometry::Halfspace<GeometryValueType>> result;
203 if (solver.isOptimal()) {
204 std::vector<GeometryValueType> normalVector;
205 for (auto const& w_i : weightVariableExpressions) {
206 normalVector.push_back(solver.getContinuousValue(w_i.getBaseExpression().asVariableExpression().getVariable()));
207 }
208 GeometryValueType offset = storm::utility::vector::dotProduct(normalVector, point) - solver.getContinuousValue(distVar);
209 result.emplace(std::move(normalVector), std::move(offset));
210 } else {
211 STORM_LOG_THROW(solver.isInfeasible(), storm::exceptions::UnexpectedException, "Unexpected result of LP solver in separating halfspace computation.");
212 }
213 return result;
214}
215
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) {
222 // First use the overapproximation to either
223 // (1) decide that the thresholds are not achievable or
224 // (2) obtain a reference point that is in the over-approximation, respects the thresholds, and is epsilon-optimal for the objective without threshold (if
225 // any)
226 Point referencePoint;
227 if (!optObjIndex.has_value()) {
228 if (!overApproximation->contains(thresholds)) {
229 // The thresholds are not achievable
230 return std::unique_ptr<CheckResult>(new ExplicitQualitativeCheckResult(initialStateOfOriginalModel, false));
231 }
232 referencePoint = thresholds;
233 } else {
234 // Get the best value we can hope to achieve
235 std::vector<Halfspace> thresholdsHalfspaces;
236 for (uint64_t objIndex = 0; objIndex < objectives.size(); ++objIndex) {
237 if (objIndex == optObjIndex.value()) {
238 continue;
239 }
240 thresholdsHalfspaces.push_back(Halfspace(WeightVector(objectives.size(), storm::utility::zero<GeometryValueType>()), -thresholds[objIndex]));
241 thresholdsHalfspaces.back().normalVector()[objIndex] = -storm::utility::one<GeometryValueType>();
242 }
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) {
249 // The thresholds are not achievable
250 return std::unique_ptr<CheckResult>(new ExplicitQualitativeCheckResult(initialStateOfOriginalModel, false));
251 }
252 referencePoint = thresholds;
253 referencePoint[optObjIndex.value()] =
254 optRes.first[optObjIndex.value()] - storm::utility::convertNumber<GeometryValueType>(env.modelchecker().multi().getPrecision());
255 // The following assertion holds because optRes.first is in the over-approximation and satisfies all thresholds and the over-approximation is
256 // downward closed
257 STORM_LOG_ASSERT(overApproximation->contains(referencePoint), "Expected reference point to be contained in the over-approximation");
258 }
259
260 // Second, find a separating halfspace between the under-approximation and the reference point with maximal L1 distance (not Euclidean!) to the latter
261 auto separatingHalfspace = findSeparatingHalfspace(refinementSteps, referencePoint);
262 bool const referencePointInUnderApproximation = !separatingHalfspace.has_value() || separatingHalfspace->contains(referencePoint);
263 if (referencePointInUnderApproximation) {
264 // The reference point is achievable. We can assemble a result
265 STORM_LOG_THROW(!produceScheduler, storm::exceptions::NotSupportedException,
266 "Producing schedulers is currently not supported for (numerical) achievability queries.");
267 // TODO: to get a scheduler, we need to find a convex combination of the achievable points that yields the reference point (using LP)
268 if (optObjIndex.has_value()) {
269 // Return the middle-value of the result interval [ referencePoint[optObjIndex], referencePoint[optObjIndex] + multiPrecisoin ]
270 GeometryValueType result =
271 referencePoint[optObjIndex.value()] + (storm::utility::convertNumber<GeometryValueType>(env.modelchecker().multi().getPrecision()) /
272 storm::utility::convertNumber<GeometryValueType, uint64_t>(2));
273 auto resultForOriginalModel =
274 storm::utility::convertNumber<ModelValueType>(transformObjectiveValueToOriginal(objectives[optObjIndex.value()], result));
275
276 return std::unique_ptr<CheckResult>(new ExplicitQuantitativeCheckResult<ModelValueType>(initialStateOfOriginalModel, resultForOriginalModel));
277 } else {
278 return std::unique_ptr<CheckResult>(new ExplicitQualitativeCheckResult(initialStateOfOriginalModel, true));
279 }
280 }
281
282 // We found a separating halfspace that we can use to refine the approximation
283 GeometryValueType eps_wso = getEpsilonWso(env);
284 // Check if there is a need to increase the weighted sum optimization precision
285 if (separatingHalfspace->distance(referencePoint) < eps_wso) {
286 // The reference point is close to the under-approximation. We also check if the boundary of the over-approximation is close
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));
290 }
291 return WeightedSumOptimizationInput{
292 .weightVector{separatingHalfspace->normalVector()},
293 .epsilonWso{eps_wso},
294 };
295}
296
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) {
300 // First get the halfspaces whose intersection underapproximates the set of achievable points
301 std::vector<Point> achievablePoints;
302 achievablePoints.reserve(refinementSteps.size());
303 for (auto const& step : refinementSteps) {
304 achievablePoints.push_back(step.achievablePoint);
305 }
306 PolytopePtr underApproximation = Polytope::createDownwardClosure(achievablePoints);
307 auto achievableHalfspaces = underApproximation->getHalfspaces();
308 // Now check whether the over-approximation contains a point that is not close enough to the under-approximation
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)},
321 };
322 }
323 }
324 // If we reach this point, the over-approximation is close enough to the under-approximation
325 // obtain the data for the checkresult
326 // We take the paretoOptimalPoints as the vertices of the underApproximation.
327 // This is to filter out points found in a refinement step that are dominated by another point.
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(
334 storm::utility::vector::convertNumericVector<ModelValueType>(transformObjectiveValuesToOriginal(this->objectives, vertex)));
335 if (produceScheduler) {
336 // Find the refinement step in which we found the vertex
337 // This is guaranteed to work as long as GeometryValueType is exact, i.e.,
338 // there as long as there are no rounding errors when converting from set of points into a (H-)polytope and then back to a vertex set.
340 auto stepIt = std::find_if(refinementSteps.begin(), refinementSteps.end(), [&vertex](auto const& step) { return step.achievablePoint == vertex; });
341 STORM_LOG_ASSERT(stepIt != refinementSteps.end(),
342 "Scheduler for point " << storm::utility::vector::toString(paretoOptimalPoints.back()) << " not found.");
343 STORM_LOG_ASSERT(stepIt->scheduler.has_value(),
344 "Scheduler for point " << storm::utility::vector::toString(paretoOptimalPoints.back()) << " not generated.");
345 paretoOptimalSchedulers.push_back(std::move(stepIt->scheduler.value()));
346 }
347 }
348 return std::unique_ptr<CheckResult>(new ExplicitParetoCurveCheckResult<ModelValueType>(
349 initialStateOfOriginalModel, std::move(paretoOptimalPoints), std::move(paretoOptimalSchedulers),
350 transformObjectivePolytopeToOriginal(this->objectives, underApproximation)->template convertNumberRepresentation<ModelValueType>(),
351 transformObjectivePolytopeToOriginal(this->objectives, overApproximation)->template convertNumberRepresentation<ModelValueType>()));
352}
353
354template<typename SparseModelType, typename GeometryValueType>
355GeometryValueType SparsePcaaQuery<SparseModelType, GeometryValueType>::getEpsilonWso(Environment const& env, std::optional<GeometryValueType> approxDistance) {
356 // Determine heuristic parameter gamma for approximation tradeoff. We should have 0 < gamma < 1, where small values mean that weighted sum optimization
357 // needs to be done with high accuracy.
358 GeometryValueType gamma;
359 if (env.modelchecker().multi().isApproximationTradeoffSet()) {
360 // A value was set explicitly, so we use that.
361 gamma = storm::utility::convertNumber<GeometryValueType>(env.modelchecker().multi().getApproximationTradeoff());
362 } else {
363 // No value was set explicitly. We pick one heuristically
364 if (env.solver().isForceExact()) {
365 gamma = storm::utility::zero<GeometryValueType>(); // In exact mode, we don't expect any inaccuracies in the WSO solver
366 } else if (env.solver().isForceSoundness() || weightVectorChecker->smallPrecisionsAreChallenging()) {
367 // in sound mode and/or when WSO calls are challenging, we pick a middle-ground value
368 gamma = storm::utility::convertNumber<GeometryValueType>(0.5);
369 } else {
370 // In unsound mode with non-challenging WSO calls, we don't want too inaccurate precisions (e.g. standard value iteration with large epsilon becomes
371 // very unreliable). Hence, we pick a rather small value.
372 gamma = storm::utility::convertNumber<GeometryValueType>(1.0 / 64.0);
373 }
374 }
375
376 // Get the precision for multiobjective model checking. Further decrease it if the approximation is close.
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());
380 }
381
382 // We divide by sqrt(objectives.size()) to ensure that even for values of gamma close to 1, we can still achieve enough precision
383 // See Example 3.5 in https://doi.org/10.18154/RWTH-2023-09669 for an example why this is needed.
384 return gamma * eps_multi / storm::utility::convertNumber<GeometryValueType>(std::sqrt(objectives.size()));
385}
386
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.");
392
393 // Get achievable points as well as a hyperrectangle that is used to guarantee that the resulting polytopes are bounded.
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) {
400 achievablePoints.push_back(transformObjectiveValuesToOriginal(this->objectives, step.achievablePoint));
401 boundaries.enlarge(achievablePoints.back());
402 }
403
404 PolytopePtr underApproximation = Polytope::createDownwardClosure(achievablePoints);
405 auto transformedUnderApprox = transformObjectivePolytopeToOriginal(this->objectives, underApproximation);
406 auto transformedOverApprox = transformObjectivePolytopeToOriginal(this->objectives, overApproximation);
407
408 auto underApproxVertices = transformedUnderApprox->getVertices();
409 for (auto const& v : underApproxVertices) {
410 boundaries.enlarge(v);
411 }
412 auto overApproxVertices = transformedOverApprox->getVertices();
413 for (auto const& v : overApproxVertices) {
414 boundaries.enlarge(v);
415 }
416
417 // Further enlarge the boundaries a little
418 storm::utility::vector::scaleVectorInPlace(boundaries.lowerBounds(), GeometryValueType(15) / GeometryValueType(10));
419 storm::utility::vector::scaleVectorInPlace(boundaries.upperBounds(), GeometryValueType(15) / GeometryValueType(10));
420
421 auto boundariesAsPolytope = boundaries.asPolytope();
422 std::vector<std::string> columnHeaders = {"x", "y"};
423
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));
430 }
431 storm::io::exportDataToCSVFile<double, std::string>(env.modelchecker().multi().getPlotPathUnderApproximation().get(), pointsForPlotting, columnHeaders);
432 }
433
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));
440 }
441 storm::io::exportDataToCSVFile<double, std::string>(env.modelchecker().multi().getPlotPathOverApproximation().get(), pointsForPlotting, columnHeaders);
442 }
443
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));
449 }
450 storm::io::exportDataToCSVFile<double, std::string>(env.modelchecker().multi().getPlotPathParetoPoints().get(), pointsForPlotting, columnHeaders);
451 }
452}
453
454template class SparsePcaaQuery<storm::models::sparse::Mdp<double>, storm::RationalNumber>;
455template class SparsePcaaQuery<storm::models::sparse::MarkovAutomaton<double>, storm::RationalNumber>;
456
457template class SparsePcaaQuery<storm::models::sparse::Mdp<storm::RationalNumber>, storm::RationalNumber>;
458template class SparsePcaaQuery<storm::models::sparse::MarkovAutomaton<storm::RationalNumber>, storm::RationalNumber>;
459} // namespace storm::modelchecker::multiobjective
ModelCheckerEnvironment & modelchecker()
MultiObjectiveModelCheckerEnvironment & multi()
Expression rational(double value) const
Creates an expression that characterizes the given rational literal.
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.
Definition LpSolver.cpp:130
Variable addBoundedContinuousVariable(std::string const &name, ValueType lowerBound, ValueType upperBound, ValueType objectiveFunctionCoefficient=0)
Registers an upper- and lower-bounded continuous variable, i.e.
Definition LpSolver.cpp:37
Variable addUnboundedContinuousVariable(std::string const &name, ValueType objectiveFunctionCoefficient=0)
Registers a unbounded continuous variable, i.e.
Definition LpSolver.cpp:56
A class that implements the LpSolver interface using Z3.
Definition Z3LpSolver.h:24
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.
Definition BitVector.h:16
#define STORM_LOG_INFO(message)
Definition logging.h:24
#define STORM_LOG_WARN(message)
Definition logging.h:25
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:11
#define STORM_LOG_WARN_COND(cond, message)
Definition macros.h:38
#define STORM_LOG_ERROR_COND(cond, message)
Definition macros.h:52
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:30
#define STORM_PRINT_AND_LOG(message)
Definition macros.h:68
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.
Definition vector.h:473
std::string toString(std::vector< ValueType > const &vector)
Output vector as string.
Definition vector.h:1145
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...
Definition vector.h:447
ValueType sqrt(ValueType const &number)