24template<
class SparseModelType,
typename GeometryValueType>
26 : initialStateOfOriginalModel(preprocessorResult.originalModel.getInitialStates().getNextSetIndex(0)), objectives(preprocessorResult.objectives) {
28 "The input model does not have a unique initial state.");
32template<
class SparseModelType,
typename GeometryValueType>
38 storm::exceptions::IllegalArgumentException,
"Unhandled multiobjective precision type.");
41 auto abortIterations = [&env](uint64_t numRefinementSteps) {
44 <<
") has been reached.");
47 STORM_LOG_WARN(
"Aborting multi-objective computation after " << numRefinementSteps <<
" refinement steps as termination has been requested.");
52 auto isMinimizingObjective = [
this](uint64_t objIndex) {
return storm::solver::minimize(this->objectives[objIndex].formula->getOptimalityType()); };
56 std::vector<RefinementStep> refinementSteps;
61 while (!abortIterations(refinementSteps.size())) {
62 auto answerOrWeights = tryAnswerOrNextWeights(env, refinementSteps, overApproximation, produceScheduler);
63 if (answerOrWeights.index() == 0) {
65 exportPlotOfCurrentApproximation(env, refinementSteps, overApproximation);
67 STORM_LOG_STATISTICS(
"Multi-objective Pareto Curve Approximation algorithm terminated after " << refinementSteps.size() <<
" refinement steps.\n");
68 return std::move(std::get<0>(answerOrWeights));
70 auto [weightVector, epsilonWso] = std::get<1>(answerOrWeights);
72 GeometryValueType normalizationFactor =
75 STORM_LOG_INFO(
"Iteration #" << refinementSteps.size() <<
": Processing new WSO instance with weight vector "
85 for (
auto const& step : refinementSteps) {
89 diff > epsilonWso / 10) {
90 STORM_LOG_WARN(
"Numerical issues: The overapproximation would not contain the underapproximation. Hence, a halfspace is shifted by "
95 refinementSteps.push_back(
96 RefinementStep{.weightVector{std::move(weightVector)},
98 .optimalWeightedSum{optimalWeightedSum},
100 auto& currentStep = refinementSteps.back();
104 for (uint64_t objIndex = 0; objIndex < this->objectives.size(); ++objIndex) {
105 if (isMinimizingObjective(objIndex)) {
109 if (produceScheduler) {
110 currentStep.scheduler = weightVectorChecker->computeScheduler();
117 std::vector<std::vector<ModelValueType>> achievablePoints;
118 achievablePoints.reserve(refinementSteps.size());
119 for (
auto const& step : refinementSteps) {
120 achievablePoints.push_back(
126template<
class SparseModelType,
typename GeometryValueType>
127typename SparsePcaaQuery<SparseModelType, GeometryValueType>::AnswerOrWeights SparsePcaaQuery<SparseModelType, GeometryValueType>::tryAnswerOrNextWeights(
128 Environment const& env, std::vector<RefinementStep>
const& refinementSteps, PolytopePtr overApproximation,
bool produceScheduler) {
129 if (refinementSteps.size() < objectives.size()) {
134 return WeightedSumOptimizationInput{
135 .weightVector{std::move(weightVector)},
136 .epsilonWso{getEpsilonWso(env)},
139 storm::storage::BitVector objectivesWithThreshold(objectives.size(),
false);
141 for (uint64_t objIndex = 0; objIndex < objectives.size(); ++objIndex) {
142 auto const& formula = *objectives[objIndex].formula;
143 if (formula.hasBound()) {
144 objectivesWithThreshold.set(objIndex);
145 thresholds[objIndex] = formula.template getThresholdAs<GeometryValueType>();
152 "Strict bound in objective " << objectives[objIndex].originalFormula <<
" is not supported and will be treated as non-strict bound.");
155 if (objectivesWithThreshold.empty() && objectives.size() > 1) {
156 return tryAnswerOrNextWeightsPareto(env, refinementSteps, overApproximation, produceScheduler);
158 uint64_t
const numObjectivesWithoutBound = objectives.size() - objectivesWithThreshold.getNumberOfSetBits();
159 std::optional<uint64_t> optObjIndex;
160 if (numObjectivesWithoutBound == 1) {
161 optObjIndex = objectivesWithThreshold.getNextUnsetIndex(0);
163 STORM_LOG_THROW(numObjectivesWithoutBound == 0, storm::exceptions::NotSupportedException,
164 "The type of query is not supported: There are multiple objectives with and without a value bound.");
166 return tryAnswerOrNextWeightsAchievability(env, optObjIndex, thresholds, refinementSteps, overApproximation, produceScheduler);
170template<
typename GeometryValueType>
173 uint64_t
const dim = point.size();
174 STORM_LOG_ASSERT(dim > 0,
"Expected at least one dimension for separating halfspace computation.");
175 STORM_LOG_ASSERT(!refinementSteps.empty(),
"Expected at least one refinement step for separating halfspace computation.");
180 std::vector<storm::expressions::Expression> weightVariableExpressions;
181 weightVariableExpressions.reserve(dim);
182 for (uint64_t i = 0; i < dim; ++i) {
183 weightVariableExpressions.push_back(
solver.addBoundedContinuousVariable(
"w" + std::to_string(i), zero, one));
186 auto distVar =
solver.addUnboundedContinuousVariable(
"d", one);
187 for (
auto const& step : refinementSteps) {
188 std::vector<storm::expressions::Expression> sum;
190 for (uint64_t i = 0; i < dim; ++i) {
191 sum.push_back(
solver.getManager().rational(point[i] - step.achievablePoint[i]) * weightVariableExpressions[i]);
197 std::optional<storm::storage::geometry::Halfspace<GeometryValueType>> result;
199 std::vector<GeometryValueType> normalVector;
200 for (
auto const& w_i : weightVariableExpressions) {
201 normalVector.push_back(
solver.getContinuousValue(w_i.getBaseExpression().asVariableExpression().getVariable()));
204 result.emplace(std::move(normalVector), std::move(offset));
206 STORM_LOG_THROW(
solver.isInfeasible(), storm::exceptions::UnexpectedException,
"Unexpected result of LP solver in separating halfspace computation.");
211template<
class SparseModelType,
typename GeometryValueType>
212typename SparsePcaaQuery<SparseModelType, GeometryValueType>::AnswerOrWeights
213SparsePcaaQuery<SparseModelType, GeometryValueType>::tryAnswerOrNextWeightsAchievability(
Environment const& env, std::optional<uint64_t>
const optObjIndex,
214 std::vector<GeometryValueType>
const& thresholds,
215 std::vector<RefinementStep>
const& refinementSteps,
216 PolytopePtr overApproximation,
bool produceScheduler) {
221 Point referencePoint;
222 if (!optObjIndex.has_value()) {
223 if (!overApproximation->contains(thresholds)) {
227 referencePoint = thresholds;
230 std::vector<Halfspace> thresholdsHalfspaces;
231 for (uint64_t objIndex = 0; objIndex < objectives.size(); ++objIndex) {
232 if (objIndex == optObjIndex.value()) {
238 auto thresholdPolytope = Polytope::create(thresholdsHalfspaces);
239 auto intersection = overApproximation->intersection(thresholdPolytope);
242 auto optRes = overApproximation->intersection(thresholdPolytope)->optimize(optDirVector);
243 if (!optRes.second) {
245 return std::unique_ptr<CheckResult>(
new ExplicitQualitativeCheckResult<ModelValueType>(initialStateOfOriginalModel,
false));
247 referencePoint = thresholds;
248 referencePoint[optObjIndex.value()] =
252 STORM_LOG_ASSERT(overApproximation->contains(referencePoint),
"Expected reference point to be contained in the over-approximation.");
257 bool const referencePointInUnderApproximation = !separatingHalfspace.has_value() || separatingHalfspace->contains(referencePoint);
258 if (referencePointInUnderApproximation) {
260 STORM_LOG_THROW(!produceScheduler, storm::exceptions::NotSupportedException,
261 "Producing schedulers is currently not supported for (numerical) achievability queries.");
263 if (optObjIndex.has_value()) {
265 GeometryValueType result =
268 auto resultForOriginalModel =
271 return std::unique_ptr<CheckResult>(
new ExplicitQuantitativeCheckResult<ModelValueType>(initialStateOfOriginalModel, resultForOriginalModel));
273 return std::unique_ptr<CheckResult>(
new ExplicitQualitativeCheckResult<ModelValueType>(initialStateOfOriginalModel,
true));
278 GeometryValueType eps_wso = getEpsilonWso(env);
280 if (separatingHalfspace->distance(referencePoint) < eps_wso) {
282 auto optResPair = overApproximation->optimize(separatingHalfspace->normalVector());
283 STORM_LOG_ASSERT(optResPair.second,
"Expected optimization to be successful as the over-approximation is non-empty.");
284 eps_wso = getEpsilonWso(env, separatingHalfspace->distance(optResPair.first));
286 return WeightedSumOptimizationInput{
287 .weightVector{separatingHalfspace->normalVector()},
288 .epsilonWso{eps_wso},
292template<
class SparseModelType,
typename GeometryValueType>
293typename SparsePcaaQuery<SparseModelType, GeometryValueType>::AnswerOrWeights SparsePcaaQuery<SparseModelType, GeometryValueType>::tryAnswerOrNextWeightsPareto(
294 Environment
const& env, std::vector<RefinementStep>
const& refinementSteps, PolytopePtr overApproximation,
bool produceScheduler) {
296 std::vector<Point> achievablePoints;
297 achievablePoints.reserve(refinementSteps.size());
298 for (
auto const& step : refinementSteps) {
299 achievablePoints.push_back(step.achievablePoint);
301 PolytopePtr underApproximation = Polytope::createDownwardClosure(achievablePoints);
302 auto achievableHalfspaces = underApproximation->getHalfspaces();
306 for (
auto const& halfspace : achievableHalfspaces) {
307 GeometryValueType
const sumOfWeights =
309 auto invertedShiftedHalfspace = halfspace.invert();
310 invertedShiftedHalfspace.offset() -= delta * sumOfWeights;
311 auto intersection = overApproximation->intersection(invertedShiftedHalfspace);
312 if (!intersection->isEmpty()) {
313 return WeightedSumOptimizationInput{
314 .weightVector{halfspace.normalVector()},
315 .epsilonWso{getEpsilonWso(env)},
323 std::vector<std::vector<ModelValueType>> paretoOptimalPoints;
324 std::vector<storm::storage::Scheduler<ModelValueType>> paretoOptimalSchedulers;
325 std::vector<Point> vertices = underApproximation->getVertices();
326 paretoOptimalPoints.reserve(vertices.size());
327 for (
auto const& vertex : vertices) {
328 paretoOptimalPoints.push_back(
330 if (produceScheduler) {
335 auto stepIt = std::find_if(refinementSteps.begin(), refinementSteps.end(), [&vertex](
auto const& step) { return step.achievablePoint == vertex; });
340 paretoOptimalSchedulers.push_back(std::move(stepIt->scheduler.value()));
343 return std::unique_ptr<CheckResult>(
new ExplicitParetoCurveCheckResult<ModelValueType>(
344 initialStateOfOriginalModel, std::move(paretoOptimalPoints), std::move(paretoOptimalSchedulers),
349template<
typename SparseModelType,
typename GeometryValueType>
350GeometryValueType SparsePcaaQuery<SparseModelType, GeometryValueType>::getEpsilonWso(Environment
const& env, std::optional<GeometryValueType> approxDistance) {
353 GeometryValueType gamma;
354 if (env.modelchecker().multi().isApproximationTradeoffSet()) {
359 if (env.solver().isForceExact()) {
361 }
else if (env.solver().isForceSoundness() || weightVectorChecker->smallPrecisionsAreChallenging()) {
373 if (approxDistance.has_value()) {
374 eps_multi = std::min<GeometryValueType>(eps_multi, approxDistance.value());
382template<
typename SparseModelType,
typename GeometryValueType>
383void SparsePcaaQuery<SparseModelType, GeometryValueType>::exportPlotOfCurrentApproximation(Environment
const& env,
384 std::vector<RefinementStep>
const& refinementSteps,
385 PolytopePtr overApproximation)
const {
386 STORM_LOG_ERROR_COND(objectives.size() == 2,
"Exporting plot requested but this is only implemented for the two-dimensional case.");
389 storm::storage::geometry::Hyperrectangle<GeometryValueType> boundaries(
392 std::vector<std::vector<GeometryValueType>> achievablePoints;
393 achievablePoints.reserve(refinementSteps.size());
394 for (
auto const& step : refinementSteps) {
396 boundaries.enlarge(achievablePoints.back());
399 PolytopePtr underApproximation = Polytope::createDownwardClosure(achievablePoints);
403 auto underApproxVertices = transformedUnderApprox->getVertices();
404 for (
auto const& v : underApproxVertices) {
405 boundaries.enlarge(v);
407 auto overApproxVertices = transformedOverApprox->getVertices();
408 for (
auto const& v : overApproxVertices) {
409 boundaries.enlarge(v);
416 auto boundariesAsPolytope = boundaries.asPolytope();
417 std::vector<std::string> columnHeaders = {
"x",
"y"};
419 std::vector<std::vector<double>> pointsForPlotting;
420 if (env.modelchecker().multi().getPlotPathUnderApproximation()) {
421 underApproxVertices = transformedUnderApprox->intersection(boundariesAsPolytope)->getVerticesInClockwiseOrder();
422 pointsForPlotting.reserve(underApproxVertices.size());
423 for (
auto const& v : underApproxVertices) {
429 if (env.modelchecker().multi().getPlotPathOverApproximation()) {
430 pointsForPlotting.clear();
431 overApproxVertices = transformedOverApprox->intersection(boundariesAsPolytope)->getVerticesInClockwiseOrder();
432 pointsForPlotting.reserve(overApproxVertices.size());
433 for (
auto const& v : overApproxVertices) {
439 if (env.modelchecker().multi().getPlotPathParetoPoints()) {
440 pointsForPlotting.clear();
441 pointsForPlotting.reserve(achievablePoints.size());
442 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_STATISTICS(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)
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)
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