TimQu
8 years ago
12 changed files with 1661 additions and 1 deletions
-
19src/storm/adapters/EigenAdapter.cpp
-
6src/storm/adapters/EigenAdapter.h
-
290src/storm/storage/geometry/NativePolytope.cpp
-
157src/storm/storage/geometry/NativePolytope.h
-
80src/storm/storage/geometry/nativepolytopeconversion/HyperplaneCollector.cpp
-
63src/storm/storage/geometry/nativepolytopeconversion/HyperplaneCollector.h
-
147src/storm/storage/geometry/nativepolytopeconversion/HyperplaneEnumeration.cpp
-
68src/storm/storage/geometry/nativepolytopeconversion/HyperplaneEnumeration.h
-
505src/storm/storage/geometry/nativepolytopeconversion/QuickHull.cpp
-
165src/storm/storage/geometry/nativepolytopeconversion/QuickHull.h
-
93src/storm/storage/geometry/nativepolytopeconversion/SubsetEnumerator.cpp
-
69src/storm/storage/geometry/nativepolytopeconversion/SubsetEnumerator.h
@ -0,0 +1,290 @@ |
|||
#include "storm/storage/geometry/NativePolytope.h"
|
|||
|
|||
#ifdef STasdf
|
|||
|
|||
#include "storm/utility/macros.h"
|
|||
#include "storm/exceptions/InvalidArgumentException.h"
|
|||
#include "storm/exceptions/UnexpectedException.h"
|
|||
#include "storm/utility/solver.h"
|
|||
|
|||
namespace storm { |
|||
namespace storage { |
|||
namespace geometry { |
|||
|
|||
template <typename ValueType> |
|||
NativePolytope<ValueType>::NativePolytope(std::vector<Halfspace<ValueType>> const& halfspaces) { |
|||
if(halfspaces.empty()){ |
|||
// The polytope is universal
|
|||
emptyStatus = EmptyStatus::Nonempty; |
|||
} else { |
|||
uint_fast64_t maxCol = halfspaces.front().normalVector().size(); |
|||
uint_fast64_t maxRow = halfspaces.size(); |
|||
A = EigenMatrix(maxRow, maxCol); |
|||
b = EigenVector(maxRow); |
|||
for ( uint_fast64_t row = 0; row < maxRow; ++row ){ |
|||
assert(halfspaces[row].normal().rows() == maxCol); |
|||
b(row) = halfspaces[row].offset(); |
|||
A(row) = storm::adapters::EigenAdapter<ValueType>::toEigenVector(halfspaces[row].normal()); |
|||
} |
|||
emptyStatus = EmptyStatus::Unknown; |
|||
} |
|||
} |
|||
|
|||
template <typename ValueType> |
|||
NativePolytope<ValueType>::NativePolytope(std::vector<Point> const& points) { |
|||
if(points.empty()){ |
|||
emptyStatus = EmptyStatus::Empty; |
|||
} else { |
|||
std::vector<EigenVector> eigenPoints; |
|||
eigenPoints.reserve(points.size()); |
|||
for(auto const& p : points){ |
|||
eigenPoints.emplace_back(storm::adapters::EigenAdapter<ValueType>::toEigenVector(p)); |
|||
} |
|||
|
|||
// todo: quickhull(eigenPoints)
|
|||
|
|||
emptyStatus = EmptyStatus::Nonempty; |
|||
} |
|||
} |
|||
|
|||
template <typename ValueType> |
|||
std::shared_ptr<Polytope<ValueType>> NativePolytope<ValueType>::create(boost::optional<std::vector<Halfspace<ValueType>>> const& halfspaces, |
|||
boost::optional<std::vector<Point>> const& points) { |
|||
if(halfspaces) { |
|||
STORM_LOG_WARN_COND(!points, "Creating a NativePolytope where halfspaces AND points are given. The points will be ignored."); |
|||
return std::make_shared<NativePolytope<ValueType>>(*halfspaces); |
|||
} else if(points) { |
|||
return std::make_shared<NativePolytope<ValueType>>(*points); |
|||
} |
|||
STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Creating a NativePolytope but no representation was given."); |
|||
return nullptr; |
|||
} |
|||
|
|||
template <typename ValueType> |
|||
NativePolytope<ValueType>::NativePolytope(NativePolytope<ValueType> const& other) : emptyStatus(other.emptyStatus), A(other.A), b(other.b) { |
|||
// Intentionally left empty
|
|||
} |
|||
|
|||
template <typename ValueType> |
|||
NativePolytope<ValueType>::NativePolytope(NativePolytope<ValueType>&& other) : emptyStatus(std::move(other.emptyStatus)), A(std::move(other.A)), b(std::move(other.b)) { |
|||
// Intentionally left empty
|
|||
} |
|||
|
|||
template <typename ValueType> |
|||
NativePolytope<ValueType>::NativePolytope(EmptyStatus const& emptyStatus, EigenMatrix const& halfspaceMatrix, EigenVector const& halfspaceVector) : emptyStatus(emptyStatus), A(halfspaceMatrix), b(halfspaceVector) { |
|||
// Intentionally left empty
|
|||
} |
|||
|
|||
template <typename ValueType> |
|||
NativePolytope<ValueType>::NativePolytope(EmptyStatus&& emptyStatus, EigenMatrix&& halfspaceMatrix, EigenVector&& halfspaceVector) : emptyStatus(emptyStatus), A(halfspaceMatrix), b(halfspaceVector) { |
|||
// Intentionally left empty
|
|||
} |
|||
|
|||
template <typename ValueType> |
|||
NativePolytope<ValueType>::~NativePolytope() { |
|||
// Intentionally left empty
|
|||
} |
|||
|
|||
template <typename ValueType> |
|||
std::vector<typename Polytope<ValueType>::Point> NativePolytope<ValueType>::getVertices() const { |
|||
std::vector<hypro::Point<ValueType>> eigenVertices = getEigenVertices(); |
|||
std::vector<Point> result; |
|||
result.reserve(eigenVertices.size()); |
|||
for(auto const& p : eigenVertices) { |
|||
result.push_back(storm::adapters::EigenAdapter<ValueType>::toStdVector(p)); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
template <typename ValueType> |
|||
std::vector<Halfspace<ValueType>> NativePolytope<ValueType>::getHalfspaces() const { |
|||
std::vector<Halfspace<ValueType>> result; |
|||
result.reserve(A.rows()); |
|||
|
|||
for(uint_fast64_t row=0; row < A.rows(); ++row){ |
|||
result.emplace_back(storm::adapters::EigenAdapter<ValueType>::toStdVector(A.row(row)), b.row(row)); |
|||
} |
|||
} |
|||
|
|||
template <typename ValueType> |
|||
bool NativePolytope<ValueType>::isEmpty() const { |
|||
if(emptyStatus == emptyStatus::Unknown) { |
|||
storm::expressions::ExpressionManager manager; |
|||
std::unique_ptr<storm::solver::SmtSolver> solver = storm::utility::solver::SmtSolverFactory().create(manager); |
|||
storm::expressions::Expression constraints = getConstraints(manager, declareVariables(manager, "x")); |
|||
solver->add(constraints); |
|||
switch(solver->check()) { |
|||
case storm::solver::SmtSolver::CheckResult::Sat: |
|||
emptyStatus = emptyStatus::Nonempty; |
|||
break; |
|||
case storm::solver::SmtSolver::CheckResult::Unsat: |
|||
emptyStatus = emptyStatus::Empty; |
|||
break; |
|||
default: |
|||
STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Unexpected result of SMT solver during emptyness-check of Polytope."); |
|||
break; |
|||
} |
|||
} |
|||
return emptyStatus == emptyStatus::Empty; |
|||
} |
|||
|
|||
template <typename ValueType> |
|||
bool NativePolytope<ValueType>::isUniversal() const { |
|||
return A.rows()==0; |
|||
} |
|||
|
|||
template <typename ValueType> |
|||
bool NativePolytope<ValueType>::contains(Point const& point) const{ |
|||
EigenVector x = storm::adapters::EigenAdapter<ValueType>::toEigenVector(point); |
|||
for(uint_fast64_t row=0; row < A.rows(); ++row){ |
|||
if((A.row(row) * x)(0) > b(row)){ |
|||
return false; |
|||
} |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
template <typename ValueType> |
|||
bool NativePolytope<ValueType>::contains(std::shared_ptr<Polytope<ValueType>> const& other) const { |
|||
STORM_LOG_THROW(other->isNativePolytope(), storm::exceptions::InvalidArgumentException, "Invoked operation between a NativePolytope and a different polytope implementation. This is not supported"); |
|||
// Check whether there is one point in other that is not in this
|
|||
storm::expressions::ExpressionManager manager; |
|||
std::unique_ptr<storm::solver::SmtSolver> solver = storm::utility::solver::SmtSolverFactory().create(manager); |
|||
storm::expressions::Expression constraintsThis = this->getConstraints(manager, declareVariables(manager, "x")); |
|||
solver->add(!constraintsThis); |
|||
storm::expressions::Expression constraintsOther = dynamic_cast<NativePolytope<ValueType> const&>(*other).getConstraints(manager, declareVariables(manager, "y")); |
|||
solver->add(constraintsOther); |
|||
|
|||
switch(solver->check()) { |
|||
case storm::solver::SmtSolver::CheckResult::Sat: |
|||
return false; |
|||
case storm::solver::SmtSolver::CheckResult::Unsat: |
|||
return true; |
|||
default: |
|||
STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Unexpected result of SMT solver during containment check of two polytopes."); |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
template <typename ValueType> |
|||
std::shared_ptr<Polytope<ValueType>> NativePolytope<ValueType>::intersection(std::shared_ptr<Polytope<ValueType>> const& rhs) const{ |
|||
STORM_LOG_THROW(rhs->isNativePolytope(), storm::exceptions::InvalidArgumentException, "Invoked operation between a NativePolytope and a different polytope implementation. This is not supported"); |
|||
NativePolytope<ValueType> const& nativeRhs = dynamic_cast<NativePolytope<ValueType> const&>(*rhs); |
|||
EigenMatrix resultA(A.rows() + nativeRhs.A.rows(), A.cols()); |
|||
resultA << A, |
|||
nativeRhs.A; |
|||
EigenVector resultb(resultA.rows()); |
|||
resultb << b, |
|||
nativeRhs.b; |
|||
return std::make_shared<NativePolytope<ValueType>>(std::move(resultA), std::move(resultb)); |
|||
} |
|||
|
|||
template <typename ValueType> |
|||
std::shared_ptr<Polytope<ValueType>> NativePolytope<ValueType>::intersection(Halfspace<ValueType> const& halfspace) const{ |
|||
EigenMatrix resultA(A.rows() + 1, A.cols()); |
|||
resultA << A, |
|||
storm::adapters::EigenAdapter<ValueType>::toEigenVector(halfspace.normal()); |
|||
EigenVector resultb(resultA.rows()); |
|||
resultb << b, |
|||
halfspace.offset(); |
|||
return std::make_shared<NativePolytope<ValueType>>(std::move(resultA), std::move(resultb)); |
|||
} |
|||
|
|||
template <typename ValueType> |
|||
std::shared_ptr<Polytope<ValueType>> NativePolytope<ValueType>::convexUnion(std::shared_ptr<Polytope<ValueType>> const& rhs) const{ |
|||
STORM_LOG_THROW(rhs->isNativePolytope(), storm::exceptions::InvalidArgumentException, "Invoked operation between a NativePolytope and a different polytope implementation. This is not supported"); |
|||
if(this->isEmpty()) { |
|||
return std::make_shared<NativePolytope<ValueType>>(dynamic_cast<NativePolytope<ValueType> const&>(*rhs)); |
|||
} else if (rhs->isEmpty()) { |
|||
return std::make_shared<NativePolytope<ValueType>>(*this); |
|||
} |
|||
if(this->isUniversal() || rhs->isUniversal) { |
|||
return std::make_shared<NativePolytope<ValueType>>(*this); |
|||
} |
|||
|
|||
STORM_LOG_WARN("Implementation of convex union of two polytopes only works if the polytopes are bounded."); |
|||
|
|||
std::vector<EigenVector> rhsVertices = dynamic_cast<NativePolytope<ValueType> const&>(*rhs).getEigenVertices(); |
|||
std::vector<EigenVector> resultVertices = this->getEigenVertices(); |
|||
resultVertices.insert(resultVertices.end(), std::make_move_iterator(rhsVertices.begin()), std::make_move_iterator(rhsVertices.end())); |
|||
|
|||
// todo invoke quickhull
|
|||
|
|||
return nullptr; |
|||
} |
|||
|
|||
template <typename ValueType> |
|||
std::shared_ptr<Polytope<ValueType>> NativePolytope<ValueType>::minkowskiSum(std::shared_ptr<Polytope<ValueType>> const& rhs) const{ |
|||
STORM_LOG_THROW(rhs->isNativePolytope(), storm::exceptions::InvalidArgumentException, "Invoked operation between a NativePolytope and a different polytope implementation. This is not supported"); |
|||
NativePolytope<ValueType> const& nativeRhs = dynamic_cast<NativePolytope<ValueType> const&>(*rhs); |
|||
|
|||
return std::make_shared<NativePolytope<ValueType>>(internPolytope.minkowskiSum(dynamic_cast<NativePolytope<ValueType> const&>(*rhs).internPolytope)); |
|||
} |
|||
|
|||
template <typename ValueType> |
|||
std::shared_ptr<Polytope<ValueType>> NativePolytope<ValueType>::affineTransformation(std::vector<Point> const& matrix, Point const& vector) const{ |
|||
STORM_LOG_THROW(!matrix.empty(), storm::exceptions::InvalidArgumentException, "Invoked affine transformation with a matrix without rows."); |
|||
hypro::matrix_t<ValueType> hyproMatrix(matrix.size(), matrix.front().size()); |
|||
for(uint_fast64_t row = 0; row < matrix.size(); ++row) { |
|||
hyproMatrix.row(row) = storm::adapters::toNative(matrix[row]); |
|||
} |
|||
return std::make_shared<NativePolytope<ValueType>>(internPolytope.affineTransformation(std::move(hyproMatrix), storm::adapters::toNative(vector))); |
|||
} |
|||
|
|||
template <typename ValueType> |
|||
std::pair<typename NativePolytope<ValueType>::Point, bool> NativePolytope<ValueType>::optimize(Point const& direction) const { |
|||
hypro::EvaluationResult<ValueType> evalRes = internPolytope.evaluate(storm::adapters::toNative(direction)); |
|||
switch (evalRes.errorCode) { |
|||
case hypro::SOLUTION::FEAS: |
|||
return std::make_pair(storm::adapters::fromNative(evalRes.optimumValue), true); |
|||
case hypro::SOLUTION::UNKNOWN: |
|||
STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Unexpected eror code for Polytope evaluation"); |
|||
return std::make_pair(Point(), false); |
|||
default: |
|||
//solution is infinity or infeasible
|
|||
return std::make_pair(Point(), false); |
|||
} |
|||
} |
|||
|
|||
template <typename ValueType> |
|||
bool NativePolytope<ValueType>::isNativePolytope() const { |
|||
return true; |
|||
} |
|||
template <typename ValueType> |
|||
std::vector<typename NativePolytope::ValueType>::EigenVector> NativePolytope<ValueType>::getEigenVertices() const { |
|||
// todo: invoke conversion
|
|||
return std::vector<EigenVector>(); |
|||
} |
|||
|
|||
template <typename ValueType> |
|||
std::vector<storm::expressions::Variable> NativePolytope<ValueType>::declareVariables(storm::expressions::ExpressionManager& manager, std::string const& namePrefix) const { |
|||
std::vector<storm::expressions::Variable> result; |
|||
result.reserve(A.cols()); |
|||
for(uint_fast64_t col=0; col < A.cols(); ++col){ |
|||
result.push_back(manager->declareVariable(namePrefix + std::to_string(col), manager->getRationalType())); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
template <typename ValueType> |
|||
storm::expressions::Expression NativePolytope<ValueType>::getConstraints(storm::expressions::ExpressionManager& manager, std::vector<storm::expressions::Variable> const& variables) cons { |
|||
storm::expressions::Expression result = manager.boolean(true); |
|||
for(uint_fast64_t row=0; row < A.rows(); ++row) { |
|||
storm::expressions::Expression lhs = manager.rational(A(row,0)) * variables[0].getExpression(); |
|||
for(uint_fast64_t col=1; col < A.cols(); ++col) { |
|||
lhs = lhs + manager.rational(A(row,col)) * variables[col].getExpression(); |
|||
} |
|||
result = result && (lhs <= manager.rational(b(row))); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
|
|||
template class NativePolytope<double>; |
|||
#ifdef STORM_HAVE_CARL
|
|||
template class NativePolytope<storm::RationalNumber>; |
|||
#endif
|
|||
} |
|||
} |
|||
} |
|||
#endif
|
@ -0,0 +1,157 @@ |
|||
#ifndef STORM_STORAGE_GEOMETRY_NATIVEPOLYTOPE_H_ |
|||
#define STORM_STORAGE_GEOMETRY_NATIVEPOLYTOPE_H_ |
|||
|
|||
#include "storm/storage/geometry/Polytope.h" |
|||
#include "storm/storage/expressions/Expressions.h" |
|||
#include "storm/utility/eigen.h" |
|||
|
|||
#ifdef asdf |
|||
|
|||
namespace storm { |
|||
namespace storage { |
|||
namespace geometry { |
|||
|
|||
template <typename ValueType> |
|||
class NativePolytope : public Polytope<ValueType> { |
|||
public: |
|||
|
|||
typedef typename Polytope<ValueType>::Point Point; |
|||
|
|||
|
|||
/*! |
|||
* Creates a NativePolytope from the given halfspaces or points. |
|||
* If both representations are given, one of them might be ignored |
|||
*/ |
|||
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 |
|||
* The resulting polytope is defined as the intersection of the halfspaces. |
|||
*/ |
|||
NativePolytope(std::vector<Halfspace<ValueType>> const& halfspaces); |
|||
|
|||
/*! |
|||
* Creates a NativePolytope from the given points. |
|||
* The resulting polytope is defined as the convex hull of the points' |
|||
*/ |
|||
NativePolytope(std::vector<Point> const& points); |
|||
|
|||
/*! |
|||
* Copy and move constructors |
|||
*/ |
|||
NativePolytope(NativePolytope<ValueType> const& other); |
|||
NativePolytope(NativePolytope<ValueType>&& other); |
|||
|
|||
/*! |
|||
* Construction from intern data |
|||
*/ |
|||
NativePolytope(EmptyStatus const& emptyStatus, EigenMatrix const& halfspaceMatrix, EigenVector const& halfspaceVector); |
|||
NativePolytope(EmptyStatus&& emptyStatus, EigenMatrix&& halfspaceMatrix, EigenVector&& halfspaceVector); |
|||
|
|||
~NativePolytope(); |
|||
|
|||
/*! |
|||
* Returns the vertices of this polytope. |
|||
*/ |
|||
virtual std::vector<Point> getVertices() const override; |
|||
|
|||
/*! |
|||
* Returns the halfspaces of this polytope. |
|||
*/ |
|||
virtual std::vector<Halfspace<ValueType>> getHalfspaces() const override; |
|||
|
|||
|
|||
/*! |
|||
* Returns whether this polytope is the empty set. |
|||
*/ |
|||
virtual bool isEmpty() const override; |
|||
|
|||
/*! |
|||
* Returns whether this polytope is universal (i.e., equals R^n). |
|||
*/ |
|||
virtual bool isUniversal() const override; |
|||
|
|||
/*! |
|||
* Returns true iff the given point is inside of the polytope. |
|||
*/ |
|||
virtual bool contains(Point const& point) const override; |
|||
|
|||
/*! |
|||
* Returns true iff the given polytope is a subset of this polytope. |
|||
*/ |
|||
virtual bool contains(std::shared_ptr<Polytope<ValueType>> const& other) const override; |
|||
|
|||
/*! |
|||
* Intersects this polytope with rhs and returns the result. |
|||
*/ |
|||
virtual std::shared_ptr<Polytope<ValueType>> intersection(std::shared_ptr<Polytope<ValueType>> const& rhs) const override; |
|||
virtual std::shared_ptr<Polytope<ValueType>> intersection(Halfspace<ValueType> const& halfspace) const override; |
|||
|
|||
/*! |
|||
* Returns the convex union of this polytope and rhs. |
|||
*/ |
|||
virtual std::shared_ptr<Polytope<ValueType>> convexUnion(std::shared_ptr<Polytope<ValueType>> const& rhs) const override; |
|||
|
|||
/*! |
|||
* Returns the minkowskiSum of this polytope and rhs. |
|||
*/ |
|||
virtual std::shared_ptr<Polytope<ValueType>> minkowskiSum(std::shared_ptr<Polytope<ValueType>> const& rhs) const override; |
|||
|
|||
/*! |
|||
* Returns the affine transformation of this polytope P w.r.t. the given matrix A and vector b. |
|||
* The result is the set {A*x+b | x \in P} |
|||
* |
|||
* @param matrix the transformation matrix, given as vector of rows |
|||
* @param vector the transformation offset |
|||
*/ |
|||
virtual std::shared_ptr<Polytope<ValueType>> affineTransformation(std::vector<Point> const& matrix, Point const& vector) const override; |
|||
|
|||
/*! |
|||
* Finds an optimal point inside this polytope w.r.t. the given direction, i.e., |
|||
* a point that maximizes dotPorduct(point, direction). |
|||
* If such a point does not exist, the returned bool is false. There are two reasons for this: |
|||
* - The polytope is empty |
|||
* - The polytope is not bounded in the given direction |
|||
*/ |
|||
virtual std::pair<Point, bool> optimize(Point const& direction) const override; |
|||
|
|||
virtual bool isNativePolytope() const override; |
|||
|
|||
private: |
|||
typedef StormEigen::Matrix<ValueType, StormEigen::Dynamic, StormEigen::Dynamic> EigenMatrix; |
|||
typedef StormEigen::Matrix<ValueType, StormEigen::Dynamic, 1> EigenVector; |
|||
|
|||
// returns the vertices of this polytope as EigenVectors |
|||
std::vector<EigenVector> getEigenVertices() const; |
|||
|
|||
// declares one variable for each constraint and returns the obtained variables. |
|||
std::vector<storm::expressions::Variable> declareVariables(storm::expressions::ExpressionManager& manager, std::string const& namePrefix) const; |
|||
|
|||
// returns the constrains defined by this polytope as an expresseion |
|||
storm::expressions::Expression getConstraints(storm::expressions::ExpressionManager& manager, std::vector<storm::expressions::Variable> const& variables) const; |
|||
|
|||
|
|||
enum class EmptyStatus{ |
|||
Unknown, //It is unknown whether the polytope is empty or not |
|||
Empty, //The polytope is empty |
|||
Nonempty //the polytope is not empty |
|||
}; |
|||
//Stores whether the polytope is empty or not |
|||
mutable EmptyStatus emptyStatus; |
|||
|
|||
// Intern representation of the polytope as { x | Ax<=b } |
|||
EigenMatrix A; |
|||
EigenVector b; |
|||
|
|||
|
|||
|
|||
}; |
|||
|
|||
} |
|||
} |
|||
} |
|||
|
|||
#endif /* asdfsafO */ |
|||
|
|||
#endif /* STORM_STORAGE_GEOMETRY_NATIVEPOLYTOPE_H_ */ |
@ -0,0 +1,80 @@ |
|||
/*
|
|||
* File: HyperplaneCollector.cpp |
|||
* Author: tim quatmann |
|||
* |
|||
* Created on December 10, 2015, 6:06 PM |
|||
*/ |
|||
|
|||
#include "HyperplaneCollector.h"
|
|||
namespace hypro { |
|||
namespace pterm{ |
|||
|
|||
template< typename Number> |
|||
bool HyperplaneCollector<Number>::insert(hypro::Hyperplane<Number>const & hyperplane, std::vector<std::size_t>const* indexList) { |
|||
return this->insert(hyperplane.normal(), hyperplane.offset(), indexList); |
|||
} |
|||
|
|||
template< typename Number> |
|||
bool HyperplaneCollector<Number>::insert(hypro::vector_t<Number> const& normal, Number const& offset, std::vector<std::size_t>const* indexList) { |
|||
hypro::vector_t<Number> copyNormal(normal); |
|||
Number copyOffset(offset); |
|||
return this->insert(std::move(copyNormal), std::move(copyOffset), indexList); |
|||
} |
|||
|
|||
template< typename Number> |
|||
bool HyperplaneCollector<Number>::insert(hypro::vector_t<Number> && normal, Number && offset, std::vector<std::size_t>const* indexList) { |
|||
//Normalize
|
|||
Number infinityNorm = normal.template lpNorm<Eigen::Infinity>(); |
|||
if(infinityNorm != (Number)0 ){ |
|||
normal /= infinityNorm; |
|||
offset /= infinityNorm; |
|||
} |
|||
|
|||
if(indexList == nullptr){ |
|||
//insert with empty list
|
|||
return this->mMap.insert(ValueType(KeyType(normal, offset), std::vector<std::size_t>())).second; |
|||
} else { |
|||
auto inserted = this->mMap.insert(ValueType(KeyType(normal, offset), *indexList)); |
|||
if(!inserted.second){ |
|||
//Append vertex list
|
|||
inserted.first->second.insert(inserted.first->second.end(), indexList->begin(), indexList->end()); |
|||
} |
|||
return inserted.second; |
|||
} |
|||
} |
|||
|
|||
template< typename Number> |
|||
std::pair<hypro::matrix_t<Number>, hypro::vector_t<Number>> HyperplaneCollector<Number>::getCollectedHyperplanesAsMatrixVector() const{ |
|||
assert(!this->mMap.empty()); |
|||
hypro::matrix_t<Number> A(this->mMap.size(), this->mMap.begin()->first.first.rows()); |
|||
hypro::vector_t<Number> b(this->mMap.size()); |
|||
|
|||
std::size_t row = 0; |
|||
for(auto const& mapEntry : this->mMap){ |
|||
A.row(row) = mapEntry.first.first; |
|||
b(row) = mapEntry.first.second; |
|||
++row; |
|||
} |
|||
return std::pair<hypro::matrix_t<Number>, hypro::vector_t<Number>>(std::move(A), std::move(b)); |
|||
} |
|||
|
|||
template< typename Number> |
|||
std::vector<std::vector<std::size_t>> HyperplaneCollector<Number>::getIndexLists() const{ |
|||
assert(!this->mMap.empty()); |
|||
std::vector<std::vector<std::size_t>> result(this->mMap.size()); |
|||
|
|||
auto resultIt = result.begin(); |
|||
for(auto const& mapEntry : this->mMap){ |
|||
*resultIt = mapEntry.second; |
|||
++resultIt; |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
template< typename Number> |
|||
std::size_t HyperplaneCollector<Number>::numOfCollectedHyperplanes() const { |
|||
return this->mMap.size(); |
|||
} |
|||
} |
|||
} |
|||
|
@ -0,0 +1,63 @@ |
|||
/* |
|||
* File: HyperplaneCollector.h |
|||
* Author: tim quatmann |
|||
* |
|||
* Created on December 10, 2015, 6:06 PM |
|||
*/ |
|||
|
|||
#pragma once |
|||
#include <unordered_map> |
|||
#include "../../config.h" |
|||
#include "../../datastructures/Hyperplane.h" |
|||
|
|||
namespace hypro{ |
|||
namespace pterm{ |
|||
/*! |
|||
* This class can be used to collect a set of hyperplanes (without duplicates). |
|||
* The inserted hyperplanes are normalized, i.e. devided by the infinity norm of the normal vector |
|||
*/ |
|||
template< typename Number> |
|||
class HyperplaneCollector { |
|||
public: |
|||
HyperplaneCollector() = default; |
|||
HyperplaneCollector(const HyperplaneCollector& orig) = default; |
|||
virtual ~HyperplaneCollector() = default; |
|||
|
|||
/* |
|||
* inserts the given hyperplane. |
|||
* For every (unique) hyperplane, there is a list of indices which can be used e.g. to obtain the set of vertices that lie on each hyperplane. |
|||
* If indexList is given (i.e. not nullptr), the given indices are appended to that list. |
|||
* Returns true iff the hyperplane was inserted (i.e. the hyperplane was not already contained in this) |
|||
*/ |
|||
bool insert(hypro::Hyperplane<Number> const& hyperplane, std::vector<std::size_t>const* indexList = nullptr); |
|||
bool insert(hypro::vector_t<Number> const& normal, Number const& offset, std::vector<std::size_t>const* indexList = nullptr); |
|||
bool insert(hypro::vector_t<Number>&& normal, Number && offset, std::vector<std::size_t>const* indexList = nullptr); |
|||
|
|||
std::pair<hypro::matrix_t<Number>, hypro::vector_t<Number>> getCollectedHyperplanesAsMatrixVector() const; |
|||
//Note that the returned lists might contain dublicates. |
|||
std::vector<std::vector<std::size_t>> getIndexLists() const; |
|||
|
|||
std::size_t numOfCollectedHyperplanes() const; |
|||
|
|||
private: |
|||
typedef std::pair<hypro::vector_t<Number>, Number> NormalOffset; |
|||
class NormalOffsetHash{ |
|||
public: |
|||
std::size_t operator()(NormalOffset const& ns) const { |
|||
std::size_t seed = std::hash<hypro::vector_t<Number>>()(ns.first); |
|||
carl::hash_add(seed, std::hash<Number>()(ns.second)); |
|||
return seed; |
|||
} |
|||
}; |
|||
typedef typename std::unordered_map<NormalOffset, std::vector<std::size_t>, NormalOffsetHash>::key_type KeyType; |
|||
typedef typename std::unordered_map<NormalOffset, std::vector<std::size_t>, NormalOffsetHash>::value_type ValueType; |
|||
|
|||
std::unordered_map<NormalOffset, std::vector<std::size_t>, NormalOffsetHash> mMap; |
|||
|
|||
|
|||
}; |
|||
} |
|||
} |
|||
|
|||
|
|||
#include "HyperplaneCollector.tpp" |
@ -0,0 +1,147 @@ |
|||
/*
|
|||
* File: HyperplaneEnumeration.tpp |
|||
* Author: tim quatmann |
|||
* Author: phillip florian |
|||
* |
|||
* Created on December 28, 2015, 1:06 PM |
|||
*/ |
|||
|
|||
#include "HyperplaneEnumeration.h"
|
|||
|
|||
#include "../SubsetEnumerator.h"
|
|||
#include "../HyperplaneCollector.h"
|
|||
|
|||
namespace hypro { |
|||
namespace pterm{ |
|||
|
|||
template<typename Number> |
|||
bool HyperplaneEnumeration<Number>::generateVerticesFromHalfspaces(PTermHPolytope<Number> const& hPoly, bool generateRelevantHyperplanesAndVertexSets){ |
|||
PTERM_DEBUG("Invoked generateVerticesFromHalfspaces with " << hPoly.getMatrix().rows() << " hyperplanes. Dimension is " << hPoly.dimension()); |
|||
std::unordered_map<Point<Number>, std::set<std::size_t>> vertexCollector; |
|||
hypro::pterm::SubsetEnumerator<hypro::matrix_t<Number>> subsetEnum(hPoly.getMatrix().rows(), hPoly.dimension(), hPoly.getMatrix(), this->linearDependenciesFilter); |
|||
if(subsetEnum.setToFirstSubset()){ |
|||
do{ |
|||
std::vector<std::size_t> const& subset = subsetEnum.getCurrentSubset(); |
|||
std::pair<hypro::matrix_t<Number>, hypro::vector_t<Number>> subHPolytope(hPoly.getSubHPolytope(subset)); |
|||
Point<Number> point(subHPolytope.first.fullPivLu().solve(subHPolytope.second)); |
|||
//Point<Number> point(hypro::gauss(subHPolytope.first, subHPolytope.second));
|
|||
if(hPoly.contains(point)){ |
|||
//Note that the map avoids duplicates.
|
|||
auto hyperplaneIndices = vertexCollector.insert(typename std::unordered_map<Point<Number>, std::set<std::size_t>>::value_type(std::move(point), std::set<std::size_t>())).first; |
|||
if(generateRelevantHyperplanesAndVertexSets){ |
|||
hyperplaneIndices->second.insert(subset.begin(), subset.end()); |
|||
} |
|||
} |
|||
} while (subsetEnum.incrementSubset()); |
|||
} else { |
|||
std::cout << "Could not generate any vertex while converting from H Polytope. TODO: implement this case (we get some unbounded thing here)" << std::endl; |
|||
return false; |
|||
} |
|||
if(generateRelevantHyperplanesAndVertexSets){ |
|||
//For each hyperplane, get the number of (unique) vertices that lie on it.
|
|||
std::vector<std::size_t> verticesOnHyperplaneCounter(hPoly.getMatrix().rows(), 0); |
|||
for(auto const& mapEntry : vertexCollector){ |
|||
for(auto const& hyperplaneIndex : mapEntry.second){ |
|||
++verticesOnHyperplaneCounter[hyperplaneIndex]; |
|||
} |
|||
} |
|||
|
|||
//Only keep the hyperplanes on which at least dimension() vertices lie.
|
|||
//Note that this will change the indices of the Hyperplanes.
|
|||
//Therefore, we additionally store the old indices for every hyperplane to be able to translate from old to new indices
|
|||
hypro::pterm::HyperplaneCollector<Number> hyperplaneCollector; |
|||
for(std::size_t hyperplaneIndex = 0; hyperplaneIndex < verticesOnHyperplaneCounter.size(); ++hyperplaneIndex){ |
|||
if(verticesOnHyperplaneCounter[hyperplaneIndex] >= hPoly.dimension()){ |
|||
std::vector<std::size_t> oldIndex; |
|||
oldIndex.push_back(hyperplaneIndex); |
|||
hyperplaneCollector.insert(hPoly.getMatrix().row(hyperplaneIndex), hPoly.getVector()(hyperplaneIndex), &oldIndex); |
|||
} |
|||
} |
|||
auto matrixVector = hyperplaneCollector.getCollectedHyperplanesAsMatrixVector(); |
|||
this->mRelevantMatrix = std::move(matrixVector.first); |
|||
this->mRelevantVector = std::move(matrixVector.second); |
|||
|
|||
//Get the mapping from old to new indices
|
|||
std::vector<std::size_t> oldToNewIndexMapping (hPoly.getMatrix().rows(), hPoly.getMatrix().rows()); //Initialize with some illegal value
|
|||
std::vector<std::vector<std::size_t>> newToOldIndexMapping(hyperplaneCollector.getIndexLists()); |
|||
for(std::size_t newIndex = 0; newIndex < newToOldIndexMapping.size(); ++newIndex){ |
|||
for(auto const& oldIndex : newToOldIndexMapping[newIndex]){ |
|||
oldToNewIndexMapping[oldIndex] = newIndex; |
|||
} |
|||
} |
|||
|
|||
//Insert the resulting vertices and get the set of vertices that lie on each hyperplane
|
|||
std::vector<std::set<std::size_t>> vertexSets(this->mRelevantMatrix.rows()); |
|||
this->mResultVertices.clear(); |
|||
this->mResultVertices.reserve(vertexCollector.size()); |
|||
for(auto const& mapEntry : vertexCollector){ |
|||
for(auto const& oldHyperplaneIndex : mapEntry.second){ |
|||
//ignore the hyperplanes which are redundant, i.e. for which there is no new index
|
|||
if(oldToNewIndexMapping[oldHyperplaneIndex] < this->mRelevantVector.rows()){ |
|||
vertexSets[oldToNewIndexMapping[oldHyperplaneIndex]].insert(this->mResultVertices.size()); |
|||
} |
|||
} |
|||
this->mResultVertices.push_back(mapEntry.first); |
|||
} |
|||
this->mVertexSets.clear(); |
|||
this->mVertexSets.reserve(vertexSets.size()); |
|||
for(auto const& vertexSet : vertexSets){ |
|||
this->mVertexSets.emplace_back(vertexSet.begin(), vertexSet.end()); |
|||
} |
|||
|
|||
} else { |
|||
this->mResultVertices.clear(); |
|||
this->mResultVertices.reserve(vertexCollector.size()); |
|||
for(auto const& mapEntry : vertexCollector){ |
|||
this->mResultVertices.push_back(mapEntry.first); |
|||
} |
|||
this->mVertexSets.clear(); |
|||
this->mRelevantMatrix = hypro::matrix_t<Number>(); |
|||
this->mRelevantVector = hypro::vector_t<Number>(); |
|||
} |
|||
PTERM_DEBUG("Invoked generateVerticesFromHalfspaces with " << hPoly.getMatrix().rows() << " hyperplanes and " << this->mResultVertices.size() << " vertices and " << this->mRelevantMatrix.rows() << " relevant hyperplanes. Dimension is " << hPoly.dimension()); |
|||
return true; |
|||
} |
|||
|
|||
|
|||
template <typename Number> |
|||
bool HyperplaneEnumeration<Number>::linearDependenciesFilter(std::vector<std::size_t> const& subset, std::size_t const& item, hypro::matrix_t<Number> const& A) { |
|||
hypro::matrix_t<Number> subMatrix(subset.size() +1, A.cols()); |
|||
for (std::size_t i = 0; i < subset.size(); ++i){ |
|||
subMatrix.row(i) = A.row(subset[i]); |
|||
} |
|||
subMatrix.row(subset.size()) = A.row(item); |
|||
Eigen::FullPivLU<matrix_t<Number>> lUMatrix( subMatrix ); |
|||
// std::cout << "The rank is " << lUMatrix.rank() << " and the matrix has " << subMatrix.rows() << " rows" << std::endl;
|
|||
if (lUMatrix.rank() < subMatrix.rows()){ |
|||
//Linear dependent!
|
|||
return false; |
|||
} else { |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
|
|||
template<typename Number> |
|||
std::vector<Point<Number>>& HyperplaneEnumeration<Number>::getResultVertices() { |
|||
return this->mResultVertices; |
|||
} |
|||
|
|||
template<typename Number> |
|||
hypro::matrix_t<Number>& HyperplaneEnumeration<Number>::getRelevantMatrix() { |
|||
return this->mRelevantMatrix; |
|||
} |
|||
|
|||
template<typename Number> |
|||
hypro::vector_t<Number>& HyperplaneEnumeration<Number>::getRelevantVector() { |
|||
return this->mRelevantVector; |
|||
} |
|||
|
|||
template<typename Number> |
|||
std::vector<std::vector<std::size_t>>& HyperplaneEnumeration<Number>::getVertexSets() { |
|||
return this->mVertexSets; |
|||
} |
|||
|
|||
} |
|||
} |
|||
|
@ -0,0 +1,68 @@ |
|||
/* |
|||
* File: HyperplaneEnumeration.h |
|||
* Author: tim quatmann |
|||
* Author: phillip florian |
|||
* |
|||
* Created on December 27, 2015, 1:06 PM |
|||
*/ |
|||
|
|||
#pragma once |
|||
#include "../macros.h" |
|||
#include "../../../datastructures/Hyperplane.h" |
|||
#include "../../../datastructures/Point.h" |
|||
|
|||
namespace hypro{ |
|||
namespace pterm{ |
|||
|
|||
template< typename Number> |
|||
class HyperplaneEnumeration { |
|||
public: |
|||
HyperplaneEnumeration() = default; |
|||
virtual ~HyperplaneEnumeration() = default; |
|||
|
|||
/* |
|||
* Generates the vertices of the given polytope by enumerating all intersection points generated by subsets of hyperplanes of size hPoly.dimension(). |
|||
* If the given flag is true, this method will also compute |
|||
* * the minimal set of hyperplanes which represent the given hPoly (can be used to remove redundant hyperplanes), and |
|||
* * for each hyperplane, the set of (non-redundant) vertices that lie on that hyperplane. |
|||
* |
|||
* Use the provided getter methods to retrieve the results |
|||
* |
|||
* @return true iff conversion was successful. |
|||
*/ |
|||
bool generateVerticesFromHalfspaces(PTermHPolytope<Number> const& hPoly, bool generateRelevantHyperplanesAndVertexSets); |
|||
|
|||
std::vector<Point<Number>>& getResultVertices(); |
|||
|
|||
/*! |
|||
* Returns the set of halfspaces which are not redundant |
|||
* @note the returned matrix and vector are empty if the corresponding flag was false |
|||
*/ |
|||
hypro::matrix_t<Number>& getRelevantMatrix(); |
|||
hypro::vector_t<Number>& getRelevantVector(); |
|||
|
|||
/*! |
|||
* Returns for each hyperplane the set of vertices that lie on that hyperplane. |
|||
* A vertex is given as an index in the relevantVertices vector. |
|||
* @note the returned vector is empty if the corresponding flag was false |
|||
*/ |
|||
std::vector<std::vector<std::size_t>>& getVertexSets(); |
|||
|
|||
|
|||
/* |
|||
* Returns true if the hyperplanes with indices of subset and item are all linear independent |
|||
* Note that this is also used by the hybrid polytope. |
|||
*/ |
|||
static bool linearDependenciesFilter(std::vector<std::size_t> const& subset, std::size_t const& item, hypro::matrix_t<Number> const& A); |
|||
|
|||
private: |
|||
std::vector<Point<Number>> mResultVertices; |
|||
hypro::matrix_t<Number> mRelevantMatrix; |
|||
hypro::vector_t<Number> mRelevantVector; |
|||
std::vector<std::vector<std::size_t>> mVertexSets; |
|||
}; |
|||
} |
|||
} |
|||
|
|||
|
|||
#include "HyperplaneEnumeration.tpp" |
@ -0,0 +1,505 @@ |
|||
#include "storm/storage/geometry/nativepolytopeconversion/QuickHull.h"
|
|||
|
|||
#include <algorithm>
|
|||
|
|||
#include "storm/utility/macros.h"
|
|||
|
|||
#include "storm/storage/geometry/nativepolytopeconversion/SubsetEnumerator.h"
|
|||
#include "storm/storage/geometry/nativepolytopeconversion/HyperplaneCollector.h"
|
|||
|
|||
namespace hypro { |
|||
namespace pterm{ |
|||
|
|||
template<typename ValueType> |
|||
void QuickHull<Number>::generateHalfspacesFromVertices(std::vector<EigenVector> const& points, bool generateRelevantVerticesAndVertexSets){ |
|||
STORM_LOG_ASSERT(!points.empty(), "Invoked QuickHull with empty set of points."); |
|||
STORM_LOG_DEBUG("Invoked QuickHull on " << points.size() << " points"); |
|||
const uint_fast64_t dimension = points.front().rows(); |
|||
|
|||
// Generate initial set of d+1 affine independent points (if such a set exists)
|
|||
std::vector<uint_fast64_t> vertexIndices; |
|||
uint_fast64_t minMaxVertexNumber; |
|||
if(!this->findInitialVertices(points, vertexIndices, minMaxVertexNumber)) { |
|||
// todo deal with this
|
|||
std::cout << "QuickHull: Could not find d+1 affine independend points. TODO: implement this case (we get some degenerated thing here)" << std::endl; |
|||
} |
|||
|
|||
// compute point inside initial facet
|
|||
EigenVector insidePoint(EigenVector::Zero(dimension)); |
|||
for(uint_fast64_t vertexIndex : vertexIndices){ |
|||
insidePoint += points[vertexIndex]; |
|||
} |
|||
insidePoint /= storm::utility::convertNumber<ValueType>(vertexIndices.size()); |
|||
|
|||
// Create the initial facets from the found vertices.
|
|||
std::vector<Facet> facets = computeInitialFacets(vertexIndices, insidePoint); |
|||
|
|||
// Enlarge the mesh by adding by first considering all points that are min (or max) in at least one dimension
|
|||
storm::storage::BitVector currentFacets(facets.size(), true); |
|||
storm::storage::BitVector consideredPoints(points.size(), false); |
|||
uint_fast64_t currentNumOfVertices = vertexIndices.size(); |
|||
for(uint_fast64_t i = 0; i < minMaxVertexNumber; ++i) { |
|||
consideredPoints.set(i); |
|||
} |
|||
this->extendMesh(points, consideredPoints, facets, currentFacets, insidePoint, currentNumOfVertices); |
|||
for(auto & facet : facets){ |
|||
facet.maxOutsidePointIndex = 0; |
|||
facet.outsideSet.clear(); |
|||
} |
|||
|
|||
consideredPoints = storm::storage::BitVector(points.size(), true); |
|||
this->extendMesh(points, consideredPoints, facets, currentFacets, insidePoint, currentNumOfVertices); |
|||
this->getPolytopeFromMesh(points, facets, currentFacets, success && generateRelevantVerticesAndVertexSets); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
|
|||
template<typename ValueType> |
|||
void QuickHull<Number>::extendMesh(std::vector<EigenVector>& points, |
|||
storm::storage::BitVector& consideredPoints, |
|||
std::vector<Facet>& facets, |
|||
storm::storage::BitVector& currentFacets, |
|||
vector_t<Number>& insidePoint, |
|||
uint_fast64_t& currentNumOfVertices) const { |
|||
|
|||
storm::storage::BitVector currentOutsidePoints = consideredPoints; |
|||
// Compute initial outside Sets
|
|||
for(uint_fast64_t facetIndex : currentFacets){ |
|||
computeOutsideSetOfFacet(facets[facetIndex], currentOutsidePoints, points); |
|||
} |
|||
|
|||
for(uint_fast64_t facetCount = currentFacets.getNextSetIndex(0); facetCount != currentFacets.size(); facetCount = currentFacets.getNextSetIndex(facetCount+1)) { |
|||
// set all points to false to get rid of points that lie within the polytope after each iteration
|
|||
currentOutsidePoints.reset(); |
|||
// Find a facet with a non-empty outside set
|
|||
if(!facets[facetCount].outsideSet.empty()) { |
|||
uint_fast64_t numberOfNewFacets = 0; |
|||
// Now we compute the enlarged mesh
|
|||
uint_fast64_t farAwayPointIndex = facets[facetCount].maxOutsidePointIndex; |
|||
assert(consideredPoints.get(farAwayPointIndex)); |
|||
// get Visible set from maxOutsidePoint of the current facet
|
|||
std::set<uint_fast64_t> visibleSet = getVisibleSet(facets, facetCount, points[farAwayPointIndex]); |
|||
std::set<uint_fast64_t> invisibleSet = getInvisibleNeighbors(facets, visibleSet); |
|||
for(auto invisFacetIt = invisibleSet.begin(); invisFacetIt != invisibleSet.end(); ++invisFacetIt) { |
|||
for(auto visFacetIt = visibleSet.begin(); visFacetIt != visibleSet.end(); ++visFacetIt) { |
|||
if (facetHasNeighborWithIndex(facets[*invisFacetIt], *visFacetIt)) { |
|||
Facet newFacet; |
|||
// Set points of Facet
|
|||
newFacet.points = getCommonPoints(facets[*invisFacetIt], facets[*visFacetIt]); |
|||
// replace old facet index by new facet index in the current neighbor
|
|||
newFacet.points.push_back(farAwayPointIndex); |
|||
replaceFacetNeighbor(facets, *visFacetIt, facets.size(), *invisFacetIt); |
|||
newFacet.neighbors.push_back(*invisFacetIt); |
|||
// get new hyperplane from common points and new point
|
|||
std::vector<EigenVector> vectorSet; |
|||
vectorSet.reserve(points[0].dimension()); |
|||
EigenVector refPoint(points[farAwayPointIndex].rawCoordinates()); |
|||
for (uint_fast64_t pointCount = 0; pointCount + 1 < points[0].dimension(); ++pointCount){ |
|||
vectorSet.emplace_back(points[newFacet.points[pointCount]].rawCoordinates() - refPoint); |
|||
} |
|||
assert(linearDependenciesFilter(vectorSet)); |
|||
newFacet.hyperplane = Hyperplane<Number>(std::move(refPoint), std::move(vectorSet)); |
|||
// check orientation of hyperplane
|
|||
// To avoid multiple matrix multiplications we need a point that is known to lie well within the polytope
|
|||
if (!newFacet.hyperplane.holds(insidePoint)){ |
|||
newFacet.hyperplane.invert(); |
|||
} |
|||
facets.push_back(newFacet); |
|||
// avoid using replaced facets and add new ones
|
|||
currentFacets.push_back(true); |
|||
// Set Points in outsideSet free
|
|||
// increase Number Of new Facets
|
|||
++numberOfNewFacets; |
|||
} |
|||
} |
|||
} |
|||
|
|||
for(auto visFacetIt = visibleSet.begin(); visFacetIt != visibleSet.end(); ++visFacetIt){ |
|||
for(uint_fast64_t m = 0; m < facets[*visFacetIt].outsideSet.size(); ++m){ |
|||
currentOutsidePoints.set(facets[*visFacetIt].outsideSet[m], true); |
|||
} |
|||
} |
|||
for(auto visFacetIt = visibleSet.begin(); visFacetIt != visibleSet.end(); ++visFacetIt){ |
|||
currentFacets.set(*visFacetIt, false); |
|||
} |
|||
// compute new outside sets
|
|||
for(uint_fast64_t fIndex = facets.size()-numberOfNewFacets; fIndex != facets.size(); ++fIndex){ |
|||
computeOutsideSetOfFacet(facets[fIndex], currentOutsidePoints, points); |
|||
} |
|||
|
|||
++currentNumOfVertices; |
|||
|
|||
#ifdef PTERM_DEBUG_OUTPUT
|
|||
numOfIncludedPoints += currentOutsidePoints.count(); |
|||
PTERM_DEBUG("Mesh currently contains " << numOfIncludedPoints << " of " << consideredPoints.count() << "points"); |
|||
#endif
|
|||
|
|||
// find neighbors in new facets
|
|||
setNeighborhoodOfNewFacets(facets, facets.size() - numberOfNewFacets, points[0].dimension()); |
|||
|
|||
} |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
template<typename ValueType> |
|||
void QuickHull<Number>::getPolytopeFromMesh(std::vector<EigenVector> const& points, std::vector<Facet> const& facets, storm::storage::BitVector const& currentFacets, bool generateRelevantVerticesAndVertexSets){ |
|||
|
|||
hypro::pterm::HyperplaneCollector<Number> hyperplaneCollector; |
|||
for(uint_fast64_t facetCount = 0; facetCount < facets.size(); ++facetCount){ |
|||
if(currentFacets[facetCount]){ |
|||
hyperplaneCollector.insert(std::move(facets[facetCount].hyperplane), generateRelevantVerticesAndVertexSets ? &facets[facetCount].points : nullptr); |
|||
} |
|||
} |
|||
|
|||
if(generateRelevantVerticesAndVertexSets){ |
|||
//Get the mapping from a hyperplane to the set of vertices that lie on that plane, erase the duplicates, and count for each vertex the number of hyperplanes on which that vertex lies
|
|||
this->mVertexSets = hyperplaneCollector.getIndexLists(); |
|||
std::vector<uint_fast64_t> hyperplanesOnVertexCounter(points.size(), 0); |
|||
for(auto& vertexVector : this->mVertexSets){ |
|||
std::set<uint_fast64_t> vertexSet; |
|||
for(auto const& i : vertexVector){ |
|||
if(vertexSet.insert(i).second){ |
|||
++hyperplanesOnVertexCounter[i]; |
|||
} |
|||
} |
|||
vertexVector.assign( vertexSet.begin(), vertexSet.end()); |
|||
} |
|||
//Now, we can erase all vertices which do not lie on at least dimension() hyperplanes.
|
|||
//Note that the indices of the HyperplaneToVerticesMapping needs to be refreshed according to the new set of vertices
|
|||
//Therefore, we additionally store the old indices for every vertex to be able to translate from old to new indices
|
|||
std::unordered_map<hypro::EigenVector, std::vector<uint_fast64_t>> relevantVerticesMap; |
|||
relevantVerticesMap.reserve(points.size()); |
|||
for(uint_fast64_t vertexIndex = 0; vertexIndex < hyperplanesOnVertexCounter.size(); ++vertexIndex){ |
|||
if(hyperplanesOnVertexCounter[vertexIndex] >= points[0].dimension()){ |
|||
auto mapEntry = relevantVerticesMap.insert(typename std::unordered_map<hypro::EigenVector, std::vector<uint_fast64_t>>::value_type(points[vertexIndex], std::vector<uint_fast64_t>())).first; |
|||
mapEntry->second.push_back(vertexIndex); |
|||
} |
|||
} |
|||
//Fill in the relevant vertices and create a translation map from old to new indices
|
|||
std::vector<uint_fast64_t> oldToNewIndexMapping (points.size(), points.size()); //Initialize with some illegal value
|
|||
this->mRelevantVertices.clear(); |
|||
this->mRelevantVertices.reserve(relevantVerticesMap.size()); |
|||
for(auto const& mapEntry : relevantVerticesMap){ |
|||
for(auto const& oldIndex : mapEntry.second){ |
|||
oldToNewIndexMapping[oldIndex] = this->mRelevantVertices.size(); |
|||
} |
|||
this->mRelevantVertices.push_back(mapEntry.first); |
|||
} |
|||
//Actually translate and erase duplicates
|
|||
for(auto& vertexVector : this->mVertexSets){ |
|||
std::set<uint_fast64_t> vertexSet; |
|||
for(auto const& oldIndex : vertexVector){ |
|||
if(hyperplanesOnVertexCounter[oldIndex] >= points[0].dimension()){ |
|||
vertexSet.insert(oldToNewIndexMapping[oldIndex]); |
|||
} |
|||
} |
|||
vertexVector.assign( vertexSet.begin(), vertexSet.end()); |
|||
} |
|||
} else { |
|||
this->mRelevantVertices.clear(); |
|||
this->mVertexSets.clear(); |
|||
} |
|||
auto matrixVector = hyperplaneCollector.getCollectedHyperplanesAsMatrixVector(); |
|||
this->mResultMatrix = std::move(matrixVector.first); |
|||
this->mResultVector = std::move(matrixVector.second); |
|||
|
|||
PTERM_DEBUG("Computed H representation from " << points.size() << " vertices and " << this->mResultVector.rows() << " hyperplanes and " << this->mRelevantVertices.size() << " relevant vertices. Dimension is " << points[0].dimension()); |
|||
PTERM_DEBUG("Total number of considered facets: " << facets.size() << " where " << currentFacets.count() << " are enabled."); |
|||
} |
|||
|
|||
template <typename ValueType> |
|||
bool QuickHull<ValueType>::affineFilter(std::vector<uint_fast64_t> const& subset, uint_fast64_t const& item, std::vector<EigenVector<ValueType>> const& points){ |
|||
EigenMatrix vectorMatrix(vertices[item].dimension()+1, subset.size() + 1); |
|||
for (uint_fast64_t i = 0; i < subset.size(); ++i){ |
|||
vectorMatrix.col(i) << vertices[subset[i]], storm::utility::one<ValueType>(); |
|||
} |
|||
vectorMatrix.col(subset.size()) << vertices[item], storm::utility::one<ValueType>(); |
|||
return (vectorMatrix.fullPivLu().rank() > subset.size()); |
|||
} |
|||
|
|||
|
|||
template<typename ValueType> |
|||
bool QuickHull<Number>::findInitialVertices(std::vector<hypro::EigenVector>& points, std::vector<uint_fast64_t>& verticesOfInitialPolytope, uint_fast64_t& minMaxVertices) const{ |
|||
const uint_fast64_t dimension = points[0].dimension(); |
|||
if(points.size() < dimension + 1){ |
|||
//not enough points to obtain a (non-degenerated) polytope
|
|||
return false; |
|||
} |
|||
const uint_fast64_t candidatesToFind = std::min(2*dimension, points.size()); |
|||
uint_fast64_t candidatesFound = 0; |
|||
storm::storage::BitVector consideredPoints(points.size(), true); |
|||
while(candidatesFound < candidatesToFind && !consideredPoints.empty()) { |
|||
for(uint_fast64_t currDim=0; currDim<dimension; ++currDim) { |
|||
uint_fast64_t minIndex = *consideredPoints.begin(); |
|||
uint_fast64_t maxIndex = minIndex; |
|||
for(uint_fast64_t pointIndex : consideredPoints){ |
|||
//Check if the current point is a new minimum or maximum at the current dimension
|
|||
if(points[minIndex](currDim) > points[pointIndex](currDim)){ |
|||
minIndex = pointIndex; |
|||
} |
|||
if(points[maxIndex](currDim) < points[pointIndex](currDim)){ |
|||
maxIndex = pointIndex; |
|||
} |
|||
} |
|||
consideredPoints.set(minIndex, false); |
|||
consideredPoints.set(maxIndex, false); |
|||
} |
|||
//Found candidates. Now swap them to the front.
|
|||
consideredPoints = ~consideredPoints; |
|||
const uint_fast64_t newNumberOfCandidates = consideredPoints.getNumberOfSetBits(); |
|||
assert(newNumberOfCandidates > 0); |
|||
if(newNumberOfCandidates < points.size()){ |
|||
uint_fast64_t nextPointToMove = consideredPoints.getNextSetIndex(newNumberOfCandidates); |
|||
for(uint_fast64_t indexAtFront = candidatesFound; indexAtFront < newNumberOfCandidates; ++indexAtFront){ |
|||
if(!consideredPoints.get(indexAtFront)) { |
|||
assert(nextPointToMove != consideredPoints.size()); |
|||
std::swap(points[indexAtFront], points[nextPointToMove]); |
|||
nextPointToMove = consideredPoints.getNextSetIndex(nextPointToMove+1); |
|||
consideredPoints.set(indexAtFront); |
|||
} |
|||
} |
|||
assert(nextPointToMove == consideredPoints.size()); |
|||
} |
|||
if(candidatesFound==0){ |
|||
//We are in the first iteration. It holds that the first newNumberOfCandidates points will be vertices of the final polytope
|
|||
minMaxVertices = newNumberOfCandidates; |
|||
} |
|||
candidatesFound = newNumberOfCandidates; |
|||
consideredPoints = ~consideredPoints; |
|||
} |
|||
|
|||
storm::storage::geometry::SubsetEnumerator<std::vector<EigenVector<ValueType>>> subsetEnum(points.size(), dimension+1, points, affineFilter); |
|||
if(subsetEnum.setToFirstSubset()){ |
|||
verticesOfInitialPolytope = subsetEnum.getCurrentSubset(); |
|||
return true; |
|||
} else { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
template<typename ValueType> |
|||
std::vector<typename QuickHull<ValueType>::Facet> computeInitialFacets(std::vector<EigenVector> const& points, std::vector<uint_fast64_t> const& verticesOfInitialPolytope, EigenVector const& insidePoint) const { |
|||
const uint_fast64_t dimension = points.front().rows(); |
|||
assert(verticesOfInitialPolytope.size() == dimension + 1); |
|||
std::vector<Facet> result; |
|||
result.reserve(dimension + 1); |
|||
storm::storage::geometry::SubsetEnumerator<> subsetEnum(verticesOfInitialPolytope.size(), dimension); |
|||
if(!subsetEnum.setToFirstSubset()){ |
|||
STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Could not find an initial subset."); |
|||
} |
|||
do{ |
|||
Facet newFacet; |
|||
// set the points that lie on the new facet
|
|||
std::vector<uint_fast64_t> const& subset(subsetEnum.getCurrentSubset()); |
|||
newFacet.points.reserve(subset.size()); |
|||
for(uint_fast64_t i : subset){ |
|||
newFacet.points.push_back(verticesOfInitialPolytope[i]); |
|||
} |
|||
//neighbors: these are always the remaining facets
|
|||
newFacet.neighbors.reserve(dimension); |
|||
for(uint_fast64_t i = 0; i < dimension+1; ++i){ |
|||
if(i != facets.size()){ //initFacets.size() will be the index of this new facet!
|
|||
newFacet.neighbors.push_back(i); |
|||
} |
|||
} |
|||
// normal and offset:
|
|||
computeNormalAndOffsetOfFacet(points, insidePoint, newFacet); |
|||
|
|||
facets.push_back(std::move(newFacet)); |
|||
} while(subsetEnum.incrementSubset()); |
|||
assert(facets.size()==dimension+1); |
|||
} |
|||
|
|||
template<typename ValueType> |
|||
void computeNormalAndOffsetOfFacet(std::vector<EigenVector> const& points, EigenVector const& insidePoint, Facet& facet) const { |
|||
const uint_fast64_t dimension = points.front().rows() |
|||
assert(facet.points.size() == dimension); |
|||
EigenVector refPoint(facet.points.back()); |
|||
EigenMatrix constraints(dimension-1, dimension); |
|||
for(unsigned row = 0; row < dimension-1; ++row) { |
|||
constraints.row(row) = points[facet.points[row]] - refPoint; |
|||
} |
|||
facet.normal = constraints.fullPivLu().kernel(); |
|||
facet.offset = facet.normal.dot(refPoint); |
|||
|
|||
// invert the plane if the insidePoint is not contained in it
|
|||
if(facet.normal.dot(insidePoint) > facet.offset) { |
|||
facet.normal = -facet.normal; |
|||
facet.offset = -facet.offset; |
|||
} |
|||
} |
|||
|
|||
|
|||
template<typename ValueType> |
|||
std::set<uint_fast64_t> QuickHull<Number>::getVisibleSet(std::vector<Facet> const& facets, uint_fast64_t const& startIndex, EigenVector const& point) const { |
|||
std::set<uint_fast64_t> facetsToCheck; |
|||
std::set<uint_fast64_t> facetsChecked; |
|||
std::set<uint_fast64_t> visibleSet; |
|||
facetsChecked.insert(startIndex); |
|||
visibleSet.insert(startIndex); |
|||
for(uint_fast64_t i = 0; i < facets[startIndex].neighbors.size(); ++i){ |
|||
facetsToCheck.insert(facets[startIndex].neighbors[i]); |
|||
} |
|||
while (!facetsToCheck.empty()){ |
|||
auto elementIt = facetsToCheck.begin(); |
|||
if ( point.dot(facets[*elementIt].normal) > facets[*elementIt].offset) { |
|||
visibleSet.insert(*elementIt); |
|||
for(uint_fast64_t i = 0; i < facets[*elementIt].neighbors.size(); ++i){ |
|||
if (facetsChecked.find(facets[*elementIt].neighbors[i]) == facetsChecked.end()){ |
|||
facetsToCheck.insert(facets[*elementIt].neighbors[i]); |
|||
} |
|||
} |
|||
} |
|||
facetsChecked.insert(*elementIt); |
|||
facetsToCheck.erase(elementIt); |
|||
} |
|||
return visibleSet; |
|||
} |
|||
|
|||
template<typename ValueType> |
|||
void QuickHull<Number>::setNeighborhoodOfNewFacets(std::vector<Facet>& facets, uint_fast64_t firstNewFacet, uint_fast64_t dimension) const{ |
|||
for(uint_fast64_t currentFacet = firstNewFacet; currentFacet < facets.size(); ++currentFacet){ |
|||
for(uint_fast64_t otherFacet = currentFacet + 1; otherFacet < facets.size(); ++otherFacet){ |
|||
std::vector<uint_fast64_t> commonPoints = getCommonPoints(facets[currentFacet], facets[otherFacet]); |
|||
if (commonPoints.size() >= dimension-1){ |
|||
facets[currentFacet].neighbors.push_back(otherFacet); |
|||
facets[otherFacet].neighbors.push_back(currentFacet); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
template<typename ValueType> |
|||
void QuickHull<Number>::replaceFacetNeighbor(std::vector<Facet>& facets, uint_fast64_t oldFacetIndex, uint_fast64_t newFacetIndex, uint_fast64_t neighborIndex) const { |
|||
uint_fast64_t index = 0; |
|||
while(facets[neighborIndex].neighbors[index] != oldFacetIndex && index < facets[neighborIndex].neighbors.size()){ |
|||
++index; |
|||
} |
|||
if (index < facets[neighborIndex].neighbors.size()){ |
|||
facets[neighborIndex].neighbors[index] = newFacetIndex; |
|||
} |
|||
} |
|||
|
|||
template<typename ValueType> |
|||
void QuickHull<Number>::computeOutsideSetOfFacet(Facet& facet, storm::storage::BitVector& currentOutsidePoints, std::vector<EigenVector> const& points) const { |
|||
Number maxMultiplicationResult = facet.hyperplane.offset(); |
|||
for(uint_fast64_t pointIndex = currentOutsidePoints) { |
|||
ValueType multiplicationResult = points[pointIndex].dot(facet.normal); |
|||
if( multiplicationResult > facet.hyperplane.offset() ) { |
|||
currentOutsidePoints.set(pointIndex, false); // we already know that the point lies outside so it can be ignored for future facets
|
|||
facet.outsideSet.push_back(pointIndex); |
|||
if (multiplicationResult > maxMultiplicationResult) { |
|||
maxMultiplicationResult = multiplicationResult; |
|||
facet.maxOutsidePointIndex = pointIndex; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
template<typename ValueType> |
|||
std::vector<uint_fast64_t> QuickHull<Number>::getCommonPoints(Facet const& lhs, Facet const& rhs) const{ |
|||
std::vector<uint_fast64_t> commonPoints; |
|||
for(uint_fast64_t refPoint = 0; refPoint < lhs.points.size(); ++refPoint){ |
|||
for(uint_fast64_t currentPoint = 0; currentPoint < rhs.points.size(); ++currentPoint){ |
|||
if (lhs.points[refPoint] == rhs.points[currentPoint]){ |
|||
commonPoints.push_back(lhs.points[refPoint]); |
|||
} |
|||
} |
|||
} |
|||
return commonPoints; |
|||
} |
|||
|
|||
template<typename ValueType> |
|||
std::set<uint_fast64_t> QuickHull<Number>::getInvisibleNeighbors( std::vector<Facet>& facets, std::set<uint_fast64_t> const& visibleSet) const { |
|||
std::set<uint_fast64_t> invisibleNeighbors; |
|||
for(auto currentFacetIt = visibleSet.begin(); currentFacetIt != visibleSet.end(); ++currentFacetIt){ |
|||
for(uint_fast64_t currentNeighbor = 0; currentNeighbor < facets[*currentFacetIt].neighbors.size(); ++currentNeighbor){ |
|||
if (visibleSet.find(facets[*currentFacetIt].neighbors[currentNeighbor]) == visibleSet.end()){ |
|||
invisibleNeighbors.insert(facets[*currentFacetIt].neighbors[currentNeighbor]); |
|||
} |
|||
} |
|||
} |
|||
return invisibleNeighbors; |
|||
} |
|||
|
|||
template<typename ValueType> |
|||
void QuickHull<Number>::enlargeIncompleteResult(std::vector<Facet> const& facets, storm::storage::BitVector const& currentFacets, std::vector<EigenVector> const& points, bool generateRelevantVerticesAndVertexSets){ |
|||
PTERM_TRACE("Enlarging incomplete Result of QuickHull"); |
|||
//Obtain the set of outside points
|
|||
std::vector<uint_fast64_t> outsidePoints; |
|||
for(uint_fast64_t facetIndex = currentFacets.find_first(); facetIndex != currentFacets.npos; facetIndex = currentFacets.find_next(facetIndex)){ |
|||
outsidePoints.insert(outsidePoints.end(), facets[facetIndex].outsideSet.begin(), facets[facetIndex].outsideSet.end()); |
|||
} |
|||
|
|||
//Now adjust the offsets of all the hyperplanes such that they contain each outside point
|
|||
for(uint_fast64_t planeIndex=0; planeIndex < this->mResultMatrix.rows(); ++planeIndex){ |
|||
Number maxValue = this->mResultVector(planeIndex); |
|||
for (uint_fast64_t outsidePointIndex : outsidePoints){ |
|||
Number currValue = this->mResultMatrix.row(planeIndex) * (points[outsidePointIndex].rawCoordinates()); |
|||
maxValue = std::max(maxValue, currValue); |
|||
} |
|||
if (pterm::Settings::instance().getQuickHullRoundingMode() != pterm::Settings::QuickHullRoundingMode::NONE) { |
|||
maxValue = NumberReduction<Number>::instance().roundUp(maxValue); |
|||
} |
|||
this->mResultVector(planeIndex) = maxValue; |
|||
} |
|||
|
|||
if(generateRelevantVerticesAndVertexSets){ |
|||
/* Note: The adjustment of the offset might introduce redundant halfspaces.
|
|||
* It is also not clear if it suffices to consider intersection points of hyperplanes that intersected in the original polytope |
|||
* |
|||
* Our solution is to convert the resulting h polytope back to a v poly |
|||
*/ |
|||
|
|||
PTermHPolytope<Number> enlargedPoly(std::move(this->mResultMatrix), std::move(this->mResultVector)); |
|||
HyperplaneEnumeration<Number> he; |
|||
if(!he.generateVerticesFromHalfspaces(enlargedPoly, true)){ |
|||
PTERM_ERROR("Could not find the vertices of the enlarged Polytope"); |
|||
} |
|||
this->mResultMatrix = std::move(he.getRelevantMatrix()); |
|||
this->mResultVector = std::move(he.getRelevantVector()); |
|||
this->mRelevantVertices = std::move(he.getResultVertices()); |
|||
this->mVertexSets = std::move(he.getVertexSets()); |
|||
|
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/*
|
|||
* File: AbstractVtoHConverter.tpp |
|||
* Author: tim quatmann |
|||
* |
|||
* Created on Februrary 2, 2016, 1:06 PM |
|||
*/ |
|||
|
|||
#include "AbstractVtoHConverter.h"
|
|||
|
|||
namespace hypro { |
|||
namespace pterm{ |
|||
|
|||
template<typename ValueType> |
|||
EigenMatrix& AbstractVtoHConverter<Number>::getResultMatrix() { |
|||
return this->mResultMatrix; |
|||
} |
|||
|
|||
template<typename ValueType> |
|||
EigenVector& AbstractVtoHConverter<Number>::getResultVector() { |
|||
return this->mResultVector; |
|||
} |
|||
|
|||
template<typename ValueType> |
|||
std::vector<EigenVector>& AbstractVtoHConverter<Number>::getRelevantVertices() { |
|||
return this->mRelevantVertices; |
|||
} |
|||
|
|||
template<typename ValueType> |
|||
std::vector<std::vector<uint_fast64_t>>& AbstractVtoHConverter<Number>::getVertexSets() { |
|||
return this->mVertexSets; |
|||
} |
|||
|
|||
} |
|||
} |
|||
|
@ -0,0 +1,165 @@ |
|||
#ifndef STORM_STORAGE_GEOMETRY_NATIVEPOLYTOPECONVERSION_QUICKHULL_H_ |
|||
#define STORM_STORAGE_GEOMETRY_NATIVEPOLYTOPECONVERSION_QUICKHULL_H_ |
|||
|
|||
#include "storm/storage/BitVector.h" |
|||
|
|||
namespace storm { |
|||
namespace storage { |
|||
namespace geometry { |
|||
|
|||
template< typename ValueType> |
|||
class QuickHull { |
|||
public: |
|||
|
|||
typedef StormEigen::Matrix<ValueType, StormEigen::Dynamic, StormEigen::Dynamic> EigenMatrix; |
|||
typedef StormEigen::Matrix<ValueType, StormEigen::Dynamic, 1> EigenVector; |
|||
|
|||
QuickHull() = default; |
|||
~QuickHull() = default; |
|||
|
|||
|
|||
/* |
|||
* Generates the halfspaces of the given set of Points by the QuickHull-algorithm |
|||
* If the given flag is true, this method will also compute |
|||
* * the minimal set of vertices which represent the given polytope (can be used to remove redundant vertices), and |
|||
* * for each hyperplane, the set of (non-redundant) vertices that lie on each hyperplane. |
|||
* |
|||
* Use the provided getter methods to retrieve the results |
|||
* |
|||
* @return true iff conversion was successful. |
|||
*/ |
|||
void generateHalfspacesFromPoints(std::vector<EigenVector> const& points, bool generateRelevantVerticesAndVertexSets); |
|||
|
|||
|
|||
EigenMatrix& getResultMatrix(); |
|||
|
|||
EigenVector& getResultVector(); |
|||
|
|||
/*! |
|||
* Returns the set of vertices which are not redundant |
|||
* @note the returned vector is empty if the corresponding flag was false |
|||
*/ |
|||
std::vector <EigenVector>& getRelevantVertices(); |
|||
|
|||
/*! |
|||
* Returns for each hyperplane the set of vertices that lie on that hyperplane. |
|||
* A vertex is given as an index in the relevantVertices vector. |
|||
* @note the returned vector is empty if the corresponding flag was false |
|||
*/ |
|||
std::vector<std::vector<std::uint_fast64_t>>& getVertexSets(); |
|||
|
|||
|
|||
private: |
|||
|
|||
struct Facet { |
|||
EigenVector normal; |
|||
ValueType offset; |
|||
std::vector<uint_fast64_t> points; |
|||
std::vector<uint_fast64_t> neighbors; |
|||
// maxOutsidePointIndex and outsideSet will be set in Quickhull algorithm |
|||
std::vector<uint_fast64_t> outsideSet; |
|||
uint_fast64_t maxOutsidePointIndex; |
|||
}; |
|||
|
|||
/* |
|||
* Returns true if the vertices with index of subset and item are affine independent |
|||
* Note that this filter also works for dimension()+1 many vertices |
|||
*/ |
|||
static bool affineFilter(std::vector<uint_fast64_t> const& subset, uint_fast64_t const& item, std::vector<EigenVector> const& vertices); |
|||
|
|||
|
|||
/*! |
|||
* finds a set of vertices that correspond to a (hopefully) large V polytope. |
|||
* |
|||
* @param points The set of points from which vertices are picked. Note that the order of the points might be changed when calling this!! |
|||
* @param verticesOfInitialPolytope Will be set to the resulting vertices (represented by indices w.r.t. the given points) |
|||
* @param minMaxVertices after calling this, the first 'minMaxVertices' points will have an extreme value in at least one dimension |
|||
* @return true if the method was successful. False if the given points are affine dependend, i.e. the polytope is degenerated. |
|||
*/ |
|||
bool findInitialVertices(std::vector<EigenVector>& points, std::vector<uint_fast64_t>& verticesOfInitialPolytope, uint_fast64_t& minMaxVertices) const; |
|||
|
|||
/*! |
|||
* Computes the initial facets out of the given dimension+1 initial vertices |
|||
*/ |
|||
std::vector<Facet> computeInitialFacets(std::vector<EigenVector> const& points, std::vector<uint_fast64_t> const& verticesOfInitialPolytope, EigenVector const& insidePoint) const; |
|||
|
|||
|
|||
// Computes the normal vector and the offset of the given facet from the (dimension many) points specified in the facet. |
|||
// The insidePoint specifies the orientation of the facet. |
|||
void computeNormalAndOffsetOfFacet(std::vector<EigenVector> const& points, EigenVector const& insidePoint, Facet& facet) const; |
|||
|
|||
/* |
|||
* Extends the given mesh using the QuickHull-algorithm |
|||
* For optimization reasons a point thats inside of the initial polytope but on none of the facets has to be provided. |
|||
|
|||
* @return true iff all consideredPoints are now contained by the mesh |
|||
*/ |
|||
void extendMesh(std::vector<EigenVector>& points, |
|||
storm::storage::BitVector& consideredPoints, |
|||
std::vector<Facet<Number>>& facets, |
|||
storm::storage::BitVector& currentFacets, |
|||
vector_t<Number>& insidePoint, |
|||
uint_fast64_t& currentNumOfVertices) const; |
|||
|
|||
/*! |
|||
* Uses the provided mesh to generate a HPolytope (in form of a matrix and a vector) |
|||
* If the given flag is true, this method will also compute |
|||
* * the minimal set of vertices which represent the given vPoly (can be used to remove redundant vertices), and |
|||
* * for each hyperplane, the set of (non-redundant) vertices that lie on each hyperplane. |
|||
* |
|||
*/ |
|||
void getPolytopeFromMesh(std::vector<EigenVector> const& points, std::vector<Facet<Number>> const& facets, storm::storage::BitVector const& currentFacets, bool generateRelevantVerticesAndVertexSets); |
|||
|
|||
|
|||
/* |
|||
* Returns the set of facets visible from point starting with the facet with index startIndex and recursively testing all neighbors |
|||
*/ |
|||
std::set<uint_fast64_t> getVisibleSet(std::vector<Facet<Number>> const& facets, uint_fast64_t const& startIndex, EigenVector const& point) const; |
|||
|
|||
/* |
|||
* Sets neighborhood for all facets with index >= firstNewFacet in facets |
|||
*/ |
|||
void setNeighborhoodOfNewFacets(std::vector<Facet<Number>>& facets, uint_fast64_t firstNewFacet, uint_fast64_t dimension) const; |
|||
|
|||
/* |
|||
* replaces oldFacet by newFacet in the neighborhood of neighbor |
|||
*/ |
|||
void replaceFacetNeighbor(std::vector<Facet<Number>>& facets, uint_fast64_t oldFacetIndex, uint_fast64_t newFacetIndex, uint_fast64_t neighborIndex) const; |
|||
|
|||
/* |
|||
* computes the outside set of the given facet |
|||
*/ |
|||
void computeOutsideSetOfFacet(Facet<Number>& facet, storm::storage::BitVector& currentOutsidePoints, std::vector<EigenVector> const& points) const; |
|||
|
|||
/* |
|||
* returns common points of lhs and rhs |
|||
*/ |
|||
std::vector<uint_fast64_t> getCommonPoints(Facet<Number> const& lhs, Facet<Number> const& rhs) const; |
|||
|
|||
/* |
|||
* computes all neighbors that are not in the visibleSet |
|||
*/ |
|||
std::set<uint_fast64_t> getInvisibleNeighbors( std::vector<Facet<Number>>& facets, std::set<uint_fast64_t> const& visibleSet) const; |
|||
|
|||
bool facetHasNeighborWithIndex(Facet<Number> const& facet, uint_fast64_t searchIndex) const; |
|||
|
|||
/* |
|||
* Enlarges the result such that all points that are still outside will be contained in the resulting polytope. |
|||
*/ |
|||
void enlargeIncompleteResult(std::vector<Facet<Number>> const& facets, storm::storage::BitVector const& currentFacets, std::vector<EigenVector> const& points, bool generateRelevantVerticesAndVertexSets); |
|||
|
|||
|
|||
hypro::matrix_t<Number> resultMatrix; |
|||
hypro::vector_t<Number> resultVector; |
|||
std::vector <EigenVector> relevantVertices; |
|||
std::vector <std::vector<uint_fast64_t>> vertexSets; |
|||
|
|||
}; |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
#endif STORM_STORAGE_GEOMETRY_NATIVEPOLYTOPECONVERSION_QUICKHULL_H_ |
@ -0,0 +1,93 @@ |
|||
#include "SubsetEnumerator.h"
|
|||
|
|||
|
|||
#include "storm/utility/eigen.h"
|
|||
|
|||
namespace storm { |
|||
namespace storage { |
|||
namespace geometry { |
|||
|
|||
template< typename DataType> |
|||
SubsetEnumerator<DataType>::SubsetEnumerator(uint_fast64_t n, uint_fast64_t k, DataType const& data, SubsetFilter subsetFilter) : n(n), k(k), data(data), filter(subsetFilter){ |
|||
//Intentionally left empty
|
|||
} |
|||
|
|||
template< typename DataType> |
|||
SubsetEnumerator<DataType>::~SubsetEnumerator(){ |
|||
//Intentionally left empty
|
|||
} |
|||
|
|||
template< typename DataType> |
|||
std::vector<uint_fast64_t> const& SubsetEnumerator<DataType>::getCurrentSubset(){ |
|||
return this->current; |
|||
} |
|||
|
|||
template< typename DataType> |
|||
bool SubsetEnumerator<DataType>::setToFirstSubset(){ |
|||
if(n < k) return false; |
|||
//set the upper boundaries first.
|
|||
upperBoundaries.clear(); |
|||
upperBoundaries.reserve(k); |
|||
for(uint_fast64_t bound = (n - k); bound < n; ++bound){ |
|||
upperBoundaries.push_back(bound); |
|||
} |
|||
//now set the current subset to the very first one
|
|||
current.clear(); |
|||
current.reserve(k); |
|||
uint_fast64_t newItem = 0; |
|||
while(current.size()!= k && newItem<=upperBoundaries[current.size()]){ |
|||
//Check if it is okay to add the new item...
|
|||
if(filter(current, newItem, data)){ |
|||
current.push_back(newItem); |
|||
} |
|||
++newItem; |
|||
} |
|||
//Note that we only insert things into the vector if it is "okay" to do so.
|
|||
//Hence, we have failed iff we were not able to insert k elements.
|
|||
return current.size() == k; |
|||
} |
|||
|
|||
template< typename DataType> |
|||
bool SubsetEnumerator<DataType>::incrementSubset(){ |
|||
//The currentSelection will be the numbers that are already inside of our new subset.
|
|||
std::vector<uint_fast64_t> currentSelection(current); |
|||
currentSelection.pop_back(); |
|||
uint_fast64_t pos = k-1; |
|||
while (true){ |
|||
//check whether we can increment at the current position
|
|||
if(current[pos] == upperBoundaries[pos]){ |
|||
if(pos==0){ |
|||
//we already moved to the very left and still can not increment.
|
|||
//Hence, we are already at the last subset
|
|||
return false; |
|||
} |
|||
currentSelection.pop_back(); |
|||
--pos; |
|||
} else { |
|||
++current[pos]; |
|||
//check if the new subset is inside our filter
|
|||
if(filter(currentSelection, current[pos], data)){ |
|||
//it is, so add it and go on with the position on the right
|
|||
currentSelection.push_back(current[pos]); |
|||
++pos; |
|||
if(pos == k){ |
|||
//we are already at the very right.
|
|||
//Hence, we have found our new subset of size k
|
|||
return true; |
|||
} |
|||
//initialize the value at the new position
|
|||
current[pos] = current[pos-1]; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
template< typename DataType> |
|||
bool SubsetEnumerator<DataType>::trueFilter(std::vector<uint_fast64_t> const& subset, uint_fast64_t const& item, DataType const& data){ |
|||
return true; |
|||
} |
|||
|
|||
template class SubsetEnumerator<StormEigen::Matrix<ValueType, StormEigen::Dynamic, 1>; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,69 @@ |
|||
#ifndef STORM_STORAGE_GEOMETRY_NATIVEPOLYTOPECONVERSION_SUBSETENUMERATOR_H_ |
|||
#define STORM_STORAGE_GEOMETRY_NATIVEPOLYTOPECONVERSION_SUBSETENUMERATOR_H_ |
|||
|
|||
#include <vector> |
|||
|
|||
|
|||
namespace storm { |
|||
namespace storage { |
|||
namespace geometry { |
|||
/*! |
|||
* This class can be used to enumerate all k-sized subsets of {0,...,n-1}. |
|||
* A subset is represented as a vector of ascending numbers. |
|||
* Example: (n=5, k=3) |
|||
* [0,1,2] --> [0,1,3] --> [0,1,4] --> [0,2,3] --> [0,2,4] --> [0,3,4] --> |
|||
* [1,2,3] --> [1,2,4] --> [1,3,4] --> [2,3,4] |
|||
* A filter can be given which should answer true iff it is ok to add a given |
|||
* item to a given subset. |
|||
* Example: (n=5, k=3, filter answers false iff 0 and 2 would be in the |
|||
* resulting subset): |
|||
* [0,1,3] --> [0,1,4] --> [0,3,4] --> [1,2,3] --> [1,2,4] --> [1,3,4] --> [2,3,4] |
|||
*/ |
|||
template<typename DataType = std::nullptr_t> |
|||
class SubsetEnumerator { |
|||
|
|||
public: |
|||
|
|||
//A typedef for the filter. |
|||
//Note that the function will be called with subset.size() in {0, ..., k-1}. |
|||
typedef bool (*SubsetFilter)(std::vector<uint_fast64_t> const& subset, uint_fast64_t const& item, DataType const& data); |
|||
|
|||
/* |
|||
* Constructs a subset enumerator that can enumerate all k-sized Subsets of {0,...,n-1} |
|||
* The given filter can be used to skip certain subsets. |
|||
* @note call "setToFirstSubset()" before retrieving the first subset |
|||
*/ |
|||
SubsetEnumerator(uint_fast64_t n, uint_fast64_t k, DataType const& data = DataType(), SubsetFilter subsetFilter = trueFilter); |
|||
|
|||
~SubsetEnumerator(); |
|||
|
|||
//returns the current subset of size k. |
|||
//Arbitrary behavior if setToFirstSubset or incrementSubset returned false or have never been executed |
|||
std::vector<uint_fast64_t> const& getCurrentSubset(); |
|||
|
|||
// Sets the current subset to the very first one. |
|||
// @note Needs to be called initially. |
|||
// Returns true iff there actually is a first subset and false if not (e.g. when n<k or the filter answers false in all cases). |
|||
bool setToFirstSubset(); |
|||
|
|||
//Increments the current subset. |
|||
//Returns true if there is a new subset and false if the current subset is already the last one. |
|||
bool incrementSubset(); |
|||
|
|||
// Default filter that returns always true. |
|||
static bool |
|||
trueFilter(std::vector<uint_fast64_t> const& subset, uint_fast64_t const& item, DataType const& data); |
|||
|
|||
private: |
|||
uint_fast64_t n; //the size of the source set |
|||
uint_fast64_t k; //the size of the desired subsets |
|||
DataType const &data; //The data which is given as additional information when invoking the filter |
|||
SubsetFilter filter; //returns true iff it is okay to insert a new element |
|||
std::vector<uint_fast64_t> current; //the current subset |
|||
std::vector<uint_fast64_t> upperBoundaries; //will always be [n-k, ..., n-1]. Used to easily check whether we can increment the subset at a given position |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
|
|||
#endif /* STORM_STORAGE_GEOMETRY_NATIVEPOLYTOPECONVERSION_SUBSETENUMERATOR_H_ */ |
Write
Preview
Loading…
Cancel
Save
Reference in new issue