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;
63 while (!abortIterations(refinementSteps.size())) {
64 auto answerOrWeights = tryAnswerOrNextWeights(env, refinementSteps, overApproximation, produceScheduler);
65 if (answerOrWeights.index() == 0) {
67 exportPlotOfCurrentApproximation(env, refinementSteps, overApproximation);
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 "
90 for (
auto const& step : refinementSteps) {
94 diff > epsilonWso / 10) {
95 STORM_LOG_WARN(
"Numerical issues: The overapproximation would not contain the underapproximation. Hence, a halfspace is shifted by "
100 refinementSteps.push_back(
101 RefinementStep{.weightVector{std::move(weightVector)},
103 .optimalWeightedSum{optimalWeightedSum},
105 auto& currentStep = refinementSteps.back();
109 for (uint64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) {
110 if (isMinimizingObjective(objIndex)) {
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>
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()) {
139 return WeightedSumOptimizationInput{
140 .weightVector{std::move(weightVector)},
141 .epsilonWso{getEpsilonWso(env)},
144 storm::storage::BitVector objectivesWithThreshold(objectives.size(),
false);
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>();
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.");
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));
191 auto distVar =
solver.addUnboundedContinuousVariable(
"d", one);
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()) {
243 auto thresholdPolytope = Polytope::create(thresholdsHalfspaces);
244 auto intersection = overApproximation->intersection(thresholdPolytope);
247 auto optRes = overApproximation->intersection(thresholdPolytope)->optimize(optDirVector);
248 if (!optRes.second) {
250 return std::unique_ptr<CheckResult>(
new ExplicitQualitativeCheckResult<ModelValueType>(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 =
273 auto resultForOriginalModel =
276 return std::unique_ptr<CheckResult>(
new ExplicitQuantitativeCheckResult<ModelValueType>(initialStateOfOriginalModel, resultForOriginalModel));
278 return std::unique_ptr<CheckResult>(
new ExplicitQualitativeCheckResult<ModelValueType>(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();
311 for (
auto const& halfspace : achievableHalfspaces) {
312 GeometryValueType
const sumOfWeights =
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()) {
364 if (env.solver().isForceExact()) {
366 }
else if (env.solver().isForceSoundness() || weightVectorChecker->smallPrecisionsAreChallenging()) {
378 if (approxDistance.has_value()) {
379 eps_multi = std::min<GeometryValueType>(eps_multi, approxDistance.value());
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.");
394 storm::storage::geometry::Hyperrectangle<GeometryValueType> boundaries(
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) {
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) {
444 if (env.modelchecker().multi().getPlotPathParetoPoints()) {
445 pointsForPlotting.clear();
446 pointsForPlotting.reserve(achievablePoints.size());
447 for (
auto const& v : achievablePoints) {
ModelCheckerEnvironment & modelchecker()
MultiObjectiveModelCheckerEnvironment & multi()
uint64_t const & getMaxSteps() const
bool isExportPlotSet() const
storm::RationalNumber const & getPrecision() const
bool isMaxStepsSet() const
PrecisionType const & getPrecisionType() const
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.
A class that implements the LpSolver interface using Z3.
static std::shared_ptr< Polytope< GeometryValueType > > createUniversalPolytope()
#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)
void exportDataToCSVFile(std::string filepath, std::vector< std::vector< DataType > > const &data, boost::optional< std::vector< Header1Type > > const &header1=boost::none, boost::optional< std::vector< Header2Type > > const &header2=boost::none)
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)
SettingsType const & getModule()
Get module.
bool constexpr minimize(OptimizationDirection d)
bool isTerminate()
Check whether the program should terminate (due to some abort signal).
std::vector< TargetType > convertNumericVector(std::vector< SourceType > const &oldVector)
Converts the given vector to the given ValueType Assumes that both, TargetType and SourceType are num...
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)
TargetType convertNumber(SourceType const &number)
static const bool IsExact
SparseModelType const & originalModel