Storm 1.13.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
NativePolytope.cpp
Go to the documentation of this file.
2
13
14namespace storm {
15namespace storage {
16namespace geometry {
17
18template<typename ValueType>
20 if (halfspaces.empty()) {
21 // The polytope is universal
22 emptyStatus = EmptyStatus::Nonempty;
23 } else {
24 Eigen::Index maxCol = halfspaces.front().normalVector().size();
25 Eigen::Index maxRow = halfspaces.size();
26 A = EigenMatrix(maxRow, maxCol);
27 b = EigenVector(maxRow);
28 for (Eigen::Index row = 0; row < A.rows(); ++row) {
29 assert((Eigen::Index)halfspaces[row].normalVector().size() == maxCol);
30 b(row) = halfspaces[row].offset();
31 A.row(row) = storm::adapters::EigenAdapter::toEigenVector(halfspaces[row].normalVector());
32 }
33 emptyStatus = EmptyStatus::Unknown;
34 }
35}
36
37template<typename ValueType>
38NativePolytope<ValueType>::NativePolytope(std::vector<Point> const& points) {
39 if (points.empty()) {
40 emptyStatus = EmptyStatus::Empty;
41 } else {
42 std::vector<EigenVector> eigenPoints;
43 eigenPoints.reserve(points.size());
44 for (auto const& p : points) {
45 eigenPoints.emplace_back(storm::adapters::EigenAdapter::toEigenVector(p));
46 }
47
49 qh.generateHalfspacesFromPoints(eigenPoints, false);
50 A = std::move(qh.getResultMatrix());
51 b = std::move(qh.getResultVector());
52 emptyStatus = EmptyStatus::Nonempty;
53 }
54}
55
56template<typename ValueType>
57std::shared_ptr<Polytope<ValueType>> NativePolytope<ValueType>::create(boost::optional<std::vector<Halfspace<ValueType>>> const& halfspaces,
58 boost::optional<std::vector<Point>> const& points) {
59 if (halfspaces) {
60 STORM_LOG_WARN_COND(!points, "Creating a NativePolytope where halfspaces AND points are given. The points will be ignored.");
61 return std::make_shared<NativePolytope<ValueType>>(*halfspaces);
62 } else if (points) {
63 return std::make_shared<NativePolytope<ValueType>>(*points);
64 }
65 STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Creating a NativePolytope but no representation was given.");
66 return nullptr;
67}
68
69template<typename ValueType>
70NativePolytope<ValueType>::NativePolytope(NativePolytope<ValueType> const& other) : emptyStatus(other.emptyStatus), A(other.A), b(other.b) {
71 // Intentionally left empty
72}
73
74template<typename ValueType>
76 : emptyStatus(std::move(other.emptyStatus)), A(std::move(other.A)), b(std::move(other.b)) {
77 // Intentionally left empty
78}
79
80template<typename ValueType>
81NativePolytope<ValueType>::NativePolytope(EmptyStatus const& emptyStatus, EigenMatrix const& halfspaceMatrix, EigenVector const& halfspaceVector)
82 : emptyStatus(emptyStatus), A(halfspaceMatrix), b(halfspaceVector) {
83 // Intentionally left empty
84}
85
86template<typename ValueType>
87NativePolytope<ValueType>::NativePolytope(EmptyStatus&& emptyStatus, EigenMatrix&& halfspaceMatrix, EigenVector&& halfspaceVector)
88 : emptyStatus(emptyStatus), A(halfspaceMatrix), b(halfspaceVector) {
89 // Intentionally left empty
90}
91
92template<typename ValueType>
94 // Intentionally left empty
95}
96
97template<typename ValueType>
98std::vector<typename Polytope<ValueType>::Point> NativePolytope<ValueType>::getVertices() const {
99 std::vector<EigenVector> eigenVertices = getEigenVertices();
100 std::vector<Point> result;
101 result.reserve(eigenVertices.size());
102 for (auto const& p : eigenVertices) {
104 }
105 return result;
106}
107
108template<typename ValueType>
109std::vector<Halfspace<ValueType>> NativePolytope<ValueType>::getHalfspaces() const {
110 std::vector<Halfspace<ValueType>> result;
111 result.reserve(A.rows());
112
113 for (Eigen::Index row = 0; row < A.rows(); ++row) {
114 result.emplace_back(storm::adapters::EigenAdapter::toStdVector(EigenVector(A.row(row))), b(row));
115 }
116 return result;
117}
118
119template<typename ValueType>
121 if (emptyStatus == EmptyStatus::Unknown) {
122 std::shared_ptr<storm::expressions::ExpressionManager> manager(new storm::expressions::ExpressionManager());
123 std::unique_ptr<storm::solver::SmtSolver> solver = storm::utility::solver::SmtSolverFactory().create(*manager);
124 std::vector<storm::expressions::Expression> constraints = getConstraints(*manager, declareVariables(*manager, "x"));
125 for (auto const& constraint : constraints) {
126 solver->add(constraint);
127 }
128 switch (solver->check()) {
130 emptyStatus = EmptyStatus::Nonempty;
131 break;
133 emptyStatus = EmptyStatus::Empty;
134 break;
135 default:
136 STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Unexpected result of SMT solver during emptyness-check of Polytope.");
137 break;
138 }
139 }
140 return emptyStatus == EmptyStatus::Empty;
141}
142
143template<typename ValueType>
145 return A.rows() == 0;
146}
147
148template<typename ValueType>
151 for (Eigen::Index row = 0; row < A.rows(); ++row) {
152 if ((A.row(row) * x)(0) > b(row)) {
153 return false;
154 }
155 }
156 return true;
157}
158
159template<typename ValueType>
160bool NativePolytope<ValueType>::contains(std::shared_ptr<Polytope<ValueType>> const& other) const {
161 STORM_LOG_THROW(other->isNativePolytope(), storm::exceptions::InvalidArgumentException,
162 "Invoked operation between a NativePolytope and a different polytope implementation. This is not supported");
163 if (this->isUniversal()) {
164 return true;
165 } else if (other->isUniversal()) {
166 return false;
167 } else {
168 // Check whether there is one point in other that is not in this
169 std::shared_ptr<storm::expressions::ExpressionManager> manager(new storm::expressions::ExpressionManager());
170 std::unique_ptr<storm::solver::SmtSolver> solver = storm::utility::solver::SmtSolverFactory().create(*manager);
171 std::vector<storm::expressions::Variable> variables = declareVariables(*manager, "x");
172 std::vector<storm::expressions::Expression> constraints = getConstraints(*manager, variables);
173 storm::expressions::Expression constraintsThis = manager->boolean(true);
174 for (auto const& constraint : constraints) {
175 constraintsThis = constraintsThis && constraint;
176 }
177 solver->add(!constraintsThis);
178 constraints = dynamic_cast<NativePolytope<ValueType> const&>(*other).getConstraints(*manager, variables);
179 for (auto const& constraint : constraints) {
180 solver->add(constraint);
181 }
182 switch (solver->check()) {
184 return false;
186 return true;
187 default:
188 STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Unexpected result of SMT solver during containment check of two polytopes.");
189 return false;
190 }
191 }
192}
193
194template<typename ValueType>
195std::shared_ptr<Polytope<ValueType>> NativePolytope<ValueType>::intersection(std::shared_ptr<Polytope<ValueType>> const& rhs) const {
196 STORM_LOG_THROW(rhs->isNativePolytope(), storm::exceptions::InvalidArgumentException,
197 "Invoked operation between a NativePolytope and a different polytope implementation. This is not supported");
198 if (this->isUniversal()) {
199 return rhs;
200 } else if (rhs->isUniversal()) {
201 return std::make_shared<NativePolytope<ValueType>>(*this);
202 } else {
203 NativePolytope<ValueType> const& nativeRhs = dynamic_cast<NativePolytope<ValueType> const&>(*rhs);
204 EigenMatrix resultA(A.rows() + nativeRhs.A.rows(), A.cols());
205 resultA << A, nativeRhs.A;
206 EigenVector resultb(resultA.rows());
207 resultb << b, nativeRhs.b;
208 return std::make_shared<NativePolytope<ValueType>>(EmptyStatus::Unknown, std::move(resultA), std::move(resultb));
209 }
210}
211
212template<typename ValueType>
213std::shared_ptr<Polytope<ValueType>> NativePolytope<ValueType>::intersection(Halfspace<ValueType> const& halfspace) const {
214 if (A.rows() == 0) {
215 // No constraints yet
217 EigenVector resultb(1);
218 resultb(0) = halfspace.offset();
219 return std::make_shared<NativePolytope<ValueType>>(EmptyStatus::Unknown, std::move(resultA), std::move(resultb));
220 }
221 EigenMatrix resultA(A.rows() + 1, A.cols());
222 resultA << A, storm::adapters::EigenAdapter::toEigenVector(halfspace.normalVector()).transpose();
223 EigenVector resultb(resultA.rows());
224 resultb << b, halfspace.offset();
225 return std::make_shared<NativePolytope<ValueType>>(EmptyStatus::Unknown, std::move(resultA), std::move(resultb));
226}
227
228template<typename ValueType>
229std::shared_ptr<Polytope<ValueType>> NativePolytope<ValueType>::convexUnion(std::shared_ptr<Polytope<ValueType>> const& rhs) const {
230 STORM_LOG_THROW(rhs->isNativePolytope(), storm::exceptions::InvalidArgumentException,
231 "Invoked operation between a NativePolytope and a different polytope implementation. This is not supported");
232 if (this->isEmpty()) {
233 return std::make_shared<NativePolytope<ValueType>>(dynamic_cast<NativePolytope<ValueType> const&>(*rhs));
234 } else if (rhs->isEmpty()) {
235 return std::make_shared<NativePolytope<ValueType>>(*this);
236 } else if (this->isUniversal() || rhs->isUniversal()) {
237 return std::make_shared<NativePolytope<ValueType>>(std::vector<Halfspace<ValueType>>());
238 }
239
240 STORM_LOG_WARN_COND_DEBUG(false, "Implementation of convex union of two polytopes only works if the polytopes are bounded. This is not checked.");
241
242 std::vector<EigenVector> rhsVertices = dynamic_cast<NativePolytope<ValueType> const&>(*rhs).getEigenVertices();
243 std::vector<EigenVector> resultVertices = this->getEigenVertices();
244 resultVertices.insert(resultVertices.end(), std::make_move_iterator(rhsVertices.begin()), std::make_move_iterator(rhsVertices.end()));
245
247 qh.generateHalfspacesFromPoints(resultVertices, false);
248 return std::make_shared<NativePolytope<ValueType>>(EmptyStatus::Nonempty, std::move(qh.getResultMatrix()), std::move(qh.getResultVector()));
249}
250
251template<typename ValueType>
252std::shared_ptr<Polytope<ValueType>> NativePolytope<ValueType>::minkowskiSum(std::shared_ptr<Polytope<ValueType>> const& rhs) const {
253 STORM_LOG_THROW(rhs->isNativePolytope(), storm::exceptions::InvalidArgumentException,
254 "Invoked operation between a NativePolytope and a different polytope implementation. This is not supported");
255 NativePolytope<ValueType> const& nativeRhs = dynamic_cast<NativePolytope<ValueType> const&>(*rhs);
256
257 if (this->isEmpty() || nativeRhs.isEmpty()) {
258 return std::make_shared<NativePolytope<ValueType>>(std::vector<Point>());
259 }
260
261 std::vector<std::pair<EigenVector, ValueType>> resultConstraints;
262 resultConstraints.reserve(A.rows() + nativeRhs.A.rows());
263
264 // evaluation of rhs in directions of lhs
265 for (Eigen::Index i = 0; i < A.rows(); ++i) {
266 auto optimizationRes = nativeRhs.optimize(A.row(i));
267 if (optimizationRes.second) {
268 resultConstraints.emplace_back(A.row(i), b(i) + (A.row(i) * optimizationRes.first)(0));
269 }
270 // If optimizationRes.second is false, it means that rhs is unbounded in this direction, i.e., the current constraint is not inserted
271 }
272
273 // evaluation of lhs in directions of rhs
274 for (Eigen::Index i = 0; i < nativeRhs.A.rows(); ++i) {
275 auto optimizationRes = optimize(nativeRhs.A.row(i));
276 if (optimizationRes.second) {
277 resultConstraints.emplace_back(nativeRhs.A.row(i), nativeRhs.b(i) + (nativeRhs.A.row(i) * optimizationRes.first)(0));
278 }
279 // If optimizationRes.second is false, it means that rhs is unbounded in this direction, i.e., the current constraint is not inserted
280 }
281
282 if (resultConstraints.empty()) {
283 return std::make_shared<NativePolytope<ValueType>>(std::vector<Halfspace<ValueType>>());
284 } else {
285 EigenMatrix newA(resultConstraints.size(), resultConstraints.front().first.rows());
286 EigenVector newb(resultConstraints.size());
287 for (Eigen::Index i = 0; i < newA.rows(); ++i) {
288 newA.row(i) = resultConstraints[i].first;
289 newb(i) = resultConstraints[i].second;
290 }
291 return std::make_shared<NativePolytope<ValueType>>(EmptyStatus::Nonempty, std::move(newA), std::move(newb));
292 }
293}
294
295template<typename ValueType>
296std::shared_ptr<Polytope<ValueType>> NativePolytope<ValueType>::affineTransformation(std::vector<Point> const& matrix, Point const& vector) const {
297 STORM_LOG_THROW(!matrix.empty(), storm::exceptions::InvalidArgumentException, "Invoked affine transformation with a matrix without rows.");
298 Eigen::Index rows = matrix.size();
299 Eigen::Index columns = matrix.front().size();
300 EigenMatrix eigenMatrix(rows, columns);
301 for (Eigen::Index row = 0; row < rows; ++row) {
302 eigenMatrix.row(row) = storm::adapters::EigenAdapter::toEigenVector(matrix[row]);
303 }
305
306 Eigen::FullPivLU<EigenMatrix> luMatrix(eigenMatrix);
307 STORM_LOG_THROW(luMatrix.isInvertible(), storm::exceptions::NotImplementedException,
308 "Affine Transformation of native polytope only implemented if the transformation matrix is invertable");
309 if (isUniversal()) {
310 return std::make_shared<NativePolytope<ValueType>>(std::vector<Halfspace<ValueType>>());
311 }
312 EigenMatrix newA = A * luMatrix.inverse();
313 EigenVector newb = b + (newA * eigenVector);
314 return std::make_shared<NativePolytope<ValueType>>(emptyStatus, std::move(newA), std::move(newb));
315}
316
317template<typename ValueType>
318std::pair<typename NativePolytope<ValueType>::Point, bool> NativePolytope<ValueType>::optimize(Point const& direction) const {
319 if (isUniversal()) {
320 return std::make_pair(Point(), false);
321 }
322
323 storm::solver::Z3LpSolver<ValueType> solver(storm::solver::OptimizationDirection::Maximize);
324 std::vector<storm::expressions::Variable> variables;
325 variables.reserve(A.cols());
326 for (Eigen::Index i = 0; i < A.cols(); ++i) {
327 variables.push_back(solver.addUnboundedContinuousVariable("x" + std::to_string(i), direction[i]));
328 }
329 std::vector<storm::expressions::Expression> constraints = getConstraints(solver.getManager(), variables);
330 for (auto const& constraint : constraints) {
331 solver.addConstraint("", constraint);
332 }
333 solver.update();
334 solver.optimize();
335 if (solver.isOptimal()) {
336 auto result = std::make_pair(Point(), true);
337 result.first.reserve(variables.size());
338 for (auto const& var : variables) {
339 result.first.push_back(solver.getContinuousValue(var));
340 }
341 return result;
342 } else {
343 // solution is infinity or infeasible
344 return std::make_pair(Point(), false);
345 }
346}
347
348template<typename ValueType>
349std::pair<typename NativePolytope<ValueType>::EigenVector, bool> NativePolytope<ValueType>::optimize(EigenVector const& direction) const {
350 if (isUniversal()) {
351 return std::make_pair(EigenVector(), false);
352 }
353
354 storm::solver::Z3LpSolver<ValueType> solver(storm::solver::OptimizationDirection::Maximize);
355 std::vector<storm::expressions::Variable> variables;
356 variables.reserve(A.cols());
357 for (Eigen::Index i = 0; i < A.cols(); ++i) {
358 variables.push_back(solver.addUnboundedContinuousVariable("x" + std::to_string(i), static_cast<ValueType>(direction(i))));
359 }
360 std::vector<storm::expressions::Expression> constraints = getConstraints(solver.getManager(), variables);
361 for (auto const& constraint : constraints) {
362 solver.addConstraint("", constraint);
363 }
364 solver.update();
365 solver.optimize();
366 if (solver.isOptimal()) {
367 auto result = std::make_pair(EigenVector(A.cols()), true);
368 for (Eigen::Index i = 0; i < A.cols(); ++i) {
369 result.first(i) = solver.getContinuousValue(variables[i]);
370 }
371 return result;
372 } else {
373 // solution is infinity or infeasible
374 return std::make_pair(EigenVector(), false);
375 }
376}
377
378template<typename ValueType>
380 return true;
381}
382template<typename ValueType>
383std::vector<typename NativePolytope<ValueType>::EigenVector> NativePolytope<ValueType>::getEigenVertices() const {
385 he.generateVerticesFromConstraints(A, b, false);
386 return he.getResultVertices();
387}
388
389template<typename ValueType>
391 std::string const& namePrefix) const {
392 std::vector<storm::expressions::Variable> result;
393 result.reserve(A.cols());
394 for (Eigen::Index col = 0; col < A.cols(); ++col) {
395 result.push_back(manager.declareVariable(namePrefix + std::to_string(col), manager.getRationalType()));
396 }
397 return result;
398}
399
400template<typename ValueType>
401std::vector<storm::expressions::Expression> NativePolytope<ValueType>::getConstraints(storm::expressions::ExpressionManager const& manager,
402 std::vector<storm::expressions::Variable> const& variables) const {
403 std::vector<storm::expressions::Expression> result;
404 for (Eigen::Index row = 0; row < A.rows(); ++row) {
405 storm::expressions::Expression lhs = manager.rational(A(row, 0)) * variables[0].getExpression();
406 for (Eigen::Index col = 1; col < A.cols(); ++col) {
407 lhs = lhs + manager.rational(A(row, col)) * variables[col].getExpression();
408 }
409 result.push_back(lhs <= manager.rational(b(row)));
410 }
411 return result;
412}
413
414template<typename ValueType>
415std::shared_ptr<Polytope<ValueType>> NativePolytope<ValueType>::clean() {
416 if (isEmpty()) {
417 return create(boost::none, {});
418 }
419 std::shared_ptr<storm::expressions::ExpressionManager> manager(new storm::expressions::ExpressionManager());
420 std::unique_ptr<storm::solver::SmtSolver> solver = storm::utility::solver::SmtSolverFactory().create(*manager);
421 auto variables = declareVariables(*manager, "x");
422 std::vector<storm::expressions::Expression> constraints = getConstraints(*manager, variables);
423 for (auto const& constraint : constraints) {
424 solver->add(constraint);
425 }
426 storm::storage::BitVector keptConstraints(A.rows(), false);
427 for (Eigen::Index row = 0; row < A.rows(); ++row) {
428 storm::expressions::Expression lhs = manager->rational(A(row, 0)) * variables[0].getExpression();
429 for (Eigen::Index col = 1; col < A.cols(); ++col) {
430 lhs = lhs + manager->rational(A(row, col)) * variables[col].getExpression();
431 }
432 solver->push();
433 solver->add(lhs >= manager->rational(b(row)));
434 switch (solver->check()) {
436 keptConstraints.set(row, true);
438 break;
439 default:
440 STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Unexpected result of SMT solver during emptyness-check of Polytope.");
441 break;
442 }
443 solver->pop();
444 }
445 std::vector<Halfspace<ValueType>> newHalfspaces;
446 newHalfspaces.reserve(keptConstraints.getNumberOfSetBits());
447 for (auto row : keptConstraints) {
448 newHalfspaces.emplace_back(storm::adapters::EigenAdapter::toStdVector(EigenVector(A.row(row))), b(row));
449 }
450 return create(newHalfspaces, boost::none);
451}
452
453template class NativePolytope<double>;
455} // namespace geometry
456} // namespace storage
457} // namespace storm
static Eigen::Matrix< ValueType, Eigen::Dynamic, 1 > toEigenVector(std::vector< ValueType > const &v)
static std::vector< ValueType > toStdVector(Eigen::Matrix< ValueType, Eigen::Dynamic, 1 > const &v)
This class is responsible for managing a set of typed variables and all expressions using these varia...
A class that implements the LpSolver interface using Z3.
Definition Z3LpSolver.h:23
A bit vector that is internally represented as a vector of 64-bit values.
Definition BitVector.h:16
uint64_t getNumberOfSetBits() const
Returns the number of bits that are set to true in this bit vector.
void set(uint64_t index, bool value=true)
Sets the given truth value at the given index.
std::vector< ValueType > const & normalVector() const
Definition Halfspace.h:112
ValueType const & offset() const
Definition Halfspace.h:120
void generateVerticesFromConstraints(EigenMatrix const &constraintMatrix, EigenVector const &constraintVector, bool generateRelevantHyperplanesAndVertexSets)
virtual bool contains(Point const &point) const override
Returns true iff the given point is inside of the polytope.
NativePolytope(std::vector< Halfspace< ValueType > > const &halfspaces)
Creates a NativePolytope from the given halfspaces The resulting polytope is defined as the intersect...
Eigen::Matrix< ValueType, Eigen::Dynamic, Eigen::Dynamic > EigenMatrix
virtual std::vector< storm::expressions::Variable > declareVariables(storm::expressions::ExpressionManager &manager, std::string const &namePrefix) const override
declares one variable for each dimension and returns the obtained variables.
virtual std::pair< Point, bool > optimize(Point const &direction) const override
Finds an optimal point inside this polytope w.r.t.
virtual std::shared_ptr< Polytope< ValueType > > clean() override
Performs cleaning operations, e.g., deleting redundant halfspaces.
virtual std::shared_ptr< Polytope< ValueType > > affineTransformation(std::vector< Point > const &matrix, Point const &vector) const override
Returns the affine transformation of this polytope P w.r.t.
virtual bool isNativePolytope() const override
Polytope< ValueType >::Point Point
virtual std::vector< Point > getVertices() const override
Returns the vertices of this polytope.
Eigen::Matrix< ValueType, Eigen::Dynamic, 1 > EigenVector
virtual bool isUniversal() const override
Returns whether this polytope is universal (i.e., equals R^n).
virtual std::shared_ptr< Polytope< ValueType > > intersection(std::shared_ptr< Polytope< ValueType > > const &rhs) const override
Intersects this polytope with rhs and returns the result.
virtual std::shared_ptr< Polytope< ValueType > > minkowskiSum(std::shared_ptr< Polytope< ValueType > > const &rhs) const override
Returns the minkowskiSum of this polytope and rhs.
static std::shared_ptr< Polytope< ValueType > > create(boost::optional< std::vector< Halfspace< ValueType > > > const &halfspaces, boost::optional< std::vector< Point > > const &points)
Creates a NativePolytope from the given halfspaces or points.
virtual bool isEmpty() const override
Returns whether this polytope is the empty set.
virtual std::vector< storm::expressions::Expression > getConstraints(storm::expressions::ExpressionManager const &manager, std::vector< storm::expressions::Variable > const &variables) const override
returns the constrains defined by this polytope as an expression over the given variables
virtual std::vector< Halfspace< ValueType > > getHalfspaces() const override
Returns the halfspaces of this polytope.
virtual std::shared_ptr< Polytope< ValueType > > convexUnion(std::shared_ptr< Polytope< ValueType > > const &rhs) const override
Returns the convex union of this polytope and rhs.
void generateHalfspacesFromPoints(std::vector< EigenVector > &points, bool generateRelevantVerticesAndVertexSets)
Definition QuickHull.cpp:19
virtual std::unique_ptr< storm::solver::SmtSolver > create(storm::expressions::ExpressionManager &manager) const
Creates a new SMT solver instance.
Definition solver.cpp:124
#define STORM_LOG_WARN_COND(cond, message)
Definition macros.h:38
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:30
#define STORM_LOG_WARN_COND_DEBUG(cond, message)
Definition macros.h:18