AIToolbox
A library that offers tools for AI problem solving.
Polytope.hpp
Go to the documentation of this file.
1 #ifndef AI_TOOLBOX_UTILS_POLYTOPE_HEADER_FILE
2 #define AI_TOOLBOX_UTILS_POLYTOPE_HEADER_FILE
3 
4 #include <array>
5 #include <optional>
6 
7 #include <Eigen/Dense>
8 
10 #include <AIToolbox/Utils/Core.hpp>
13 
14 #include <AIToolbox/Utils/LP.hpp>
15 
16 namespace AIToolbox {
20  using Hyperplane = Vector;
21 
26 
30  using PointSurface = std::pair<std::vector<Point>, std::vector<double>>;
31 
36 
45  inline bool dominates(const Hyperplane & lhs, const Hyperplane & rhs) {
46  return (lhs.array() - rhs.array() >= -equalToleranceSmall).minCoeff() ||
47  (lhs.array() - rhs.array() >= -lhs.array().min(rhs.array()) * equalToleranceGeneral).minCoeff();
48  };
49 
64  template <typename Iterator, typename P = std::identity>
65  Iterator findBestAtPoint(const Point & point, Iterator begin, Iterator end, double * value = nullptr, P p = P{}) {
66  auto bestMatch = begin;
67  double bestValue = point.dot(std::invoke(p, *bestMatch));
68 
69  while ( (++begin) < end ) {
70  const double currValue = point.dot(std::invoke(p, *begin));
71  if ( currValue > bestValue || ( currValue == bestValue && veccmp(std::invoke(p, *begin), std::invoke(p, *bestMatch)) > 0 ) ) {
72  bestMatch = begin;
73  bestValue = currValue;
74  }
75  }
76  if ( value ) *value = bestValue;
77  return bestMatch;
78  }
79 
94  template <typename Iterator, typename P = std::identity>
95  Iterator findBestAtSimplexCorner(const size_t corner, Iterator begin, Iterator end, double * value = nullptr, P p = P{}) {
96  auto bestMatch = begin;
97  double bestValue = std::invoke(p, *bestMatch)[corner];
98 
99  while ( (++begin) < end ) {
100  const double currValue = std::invoke(p, *begin)[corner];
101  if ( currValue > bestValue || ( currValue == bestValue && veccmp(std::invoke(p, *begin), std::invoke(p, *bestMatch)) > 0 ) ) {
102  bestMatch = begin;
103  bestValue = currValue;
104  }
105  }
106  if ( value ) *value = bestValue;
107  return bestMatch;
108  }
109 
137  template <typename Iterator, typename P = std::identity>
138  Iterator findBestDeltaDominated(const Point & point, const Hyperplane & plane, double delta, Iterator begin, Iterator end, P p = P{}) {
139  auto retval = end;
140 
141  const Hyperplane * maxPlane = &plane;
142  double maxVal = point.dot(*maxPlane);
143 
144  for (auto it = begin; it < end; ++it) {
145  const Hyperplane * newPlane = &std::invoke(p, *it);
146  const double newVal = point.dot(*newPlane);
147  if (newVal > maxVal) {
148  const double deltaValue = (newVal - maxVal) / (*newPlane - *maxPlane).norm();
149  if (deltaValue > delta) {
150  maxVal = newVal;
151  maxPlane = newPlane;
152  retval = it;
153  }
154  }
155  }
156  return retval;
157  }
158 
176  template <typename Iterator, typename P = std::identity>
177  Iterator extractBestAtPoint(const Point & point, Iterator begin, Iterator bound, Iterator end, P p = P{}) {
178  auto bestMatch = findBestAtPoint(point, begin, end, nullptr, p);
179 
180  if ( bestMatch >= bound )
181  std::iter_swap(bestMatch, bound++);
182 
183  return bound;
184  }
185 
208  template <typename Iterator, typename P = std::identity>
209  Iterator extractBestAtSimplexCorners(const size_t S, Iterator begin, Iterator bound, Iterator end, P p = P{}) {
210  if ( end == bound ) return bound;
211 
212  // For each corner
213  for ( size_t s = 0; s < S; ++s ) {
214  auto bestMatch = findBestAtSimplexCorner(s, begin, end, nullptr, p);
215 
216  if ( bestMatch >= bound )
217  std::iter_swap(bestMatch, bound++);
218  }
219  return bound;
220  }
221 
247  template <typename PIterator, typename VIterator, typename P = std::identity>
248  PIterator extractBestUsefulPoints(PIterator pbegin, PIterator pend, VIterator begin, VIterator end, P p = P{}) {
249  const auto pointsN = std::distance(pbegin, pend);
250  const auto entriesN = std::distance(begin, end);
251 
252  std::vector<std::pair<PIterator, double>> bestValues(entriesN, {pend, std::numeric_limits<double>::lowest()});
253  const auto maxBound = pointsN < entriesN ? pend : pbegin + entriesN;
254 
255  // So the idea here is that we advance IT only if we found a Point
256  // which supports a previously unsupported Hyperplane. This allows us
257  // to avoid doing later work for compacting the points before the
258  // bound.
259  //
260  // If instead the found Point takes into consideration an already
261  // supported Hyperplane, then it either is better or not. If it's
262  // better, we swap it with whatever was before. In both cases, the
263  // Point to discard ends up at the end and we decrease the bound.
264  auto it = pbegin;
265  auto bound = pend;
266  while (it < bound && it < maxBound) {
267  double value;
268  const auto vId = std::distance(begin, findBestAtPoint(*it, begin, end, &value, p));
269  if (bestValues[vId].second < value) {
270  if (bestValues[vId].first == pend) {
271  bestValues[vId] = {it++, value};
272  continue;
273  } else {
274  bestValues[vId].second = value;
275  std::iter_swap(bestValues[vId].first, it);
276  }
277  }
278  std::iter_swap(it, --bound);
279  }
280  if (it == bound) return it;
281 
282  // If all Hyperplanes have been supported by at least one Point, then
283  // we can finish up the rest with less swaps and checks. Here we only
284  // swap with the best if needed, otherwise we don't have to do
285  // anything.
286  //
287  // This is because we can return one Point per Hyperplane at the most,
288  // so if we're here the bound is not going to move anyway.
289  while (it < bound) {
290  double value;
291  const auto vId = std::distance(begin, findBestAtPoint(*it, begin, end, &value, p));
292  if (bestValues[vId].second < value) {
293  bestValues[vId].second = value;
294  std::iter_swap(bestValues[vId].first, it);
295  }
296  ++it;
297  }
298  return maxBound;
299  }
300 
339  template <typename NewIt, typename OldIt, typename P1 = std::identity, typename P2 = std::identity>
340  PointSurface findVerticesNaive(NewIt beginNew, NewIt endNew, OldIt alphasBegin, OldIt alphasEnd, P1 p1 = P1{}, P2 p2 = P2{}) {
341  PointSurface vertices;
342 
343  const size_t alphasSize = std::distance(alphasBegin, alphasEnd);
344  if (alphasSize == 0) return vertices;
345  const size_t S = std::invoke(p2, *alphasBegin).size();
346 
347  // This enumerator allows us to compute all possible subsets of S-1
348  // elements. We use it on both the alphas, and the boundaries, thus the
349  // number of elements we iterate over is alphasSize + S.
350  SubsetEnumerator enumerator(S - 1, 0ul, alphasSize + S);
351 
352  // This is the matrix on the left side of Ax = b (where A is m)
353  Matrix2D m(S + 1, S + 1);
354  m.row(0)[S] = -1; // First row is always a vector
355 
356  Vector boundary(S+1);
357  boundary[S] = 0.0; // The boundary doesn't care about the value
358 
359  // This is the vector on the right side of Ax = b
360  Vector b(S+1); b.setZero();
361 
362  Vector result(S+1);
363 
364  // Common matrix/vector setups
365 
366  for (auto newVIt = beginNew; newVIt != endNew; ++newVIt) {
367  m.row(0).head(S) = std::invoke(p1, *newVIt);
368 
369  enumerator.reset();
370 
371  // Get subset of planes, find corner with LU
372  size_t last = 0;
373  while (enumerator.isValid()) {
374  // Reset boundaries to care about all dimensions
375  boundary.head(S).fill(1.0);
376  size_t counter = last + 1;
377  // Note that we start from last to avoid re-copying vectors
378  // that are already in the matrix in their correct place.
379  for (auto i = last; i < enumerator->size(); ++i) {
380  // For each value in the enumerator, if it is less than
381  // alphasSize it is referring to an alphaVector we need to
382  // take into account.
383  const auto index = (*enumerator)[i];
384  if (index < alphasSize) {
385  // Copy the right vector in the matrix.
386  m.row(counter).head(S) = std::invoke(p2, *std::next(alphasBegin, index));
387  m.row(counter)[S] = -1;
388  ++counter;
389  } else {
390  // We limit the index-th dimension (minus alphasSize to scale in a 0-S range)
391  boundary[index - alphasSize] = 0.0;
392  }
393  }
394  m.row(counter) = boundary;
395  b[counter] = 1.0;
396  ++counter;
397 
398  // Note that we only need to consider the first "counter" rows,
399  // as the boundaries get merged in a single one.
400  result = m.topRows(counter).colPivHouseholderQr().solve(b.head(counter));
401 
402  b[counter-1] = 0.0;
403 
404  // Add to found only if valid, otherwise skip.
405  const double max = result.head(S).maxCoeff();
406  if ((result.head(S).array() >= 0).all() && (max < 1.0) && checkDifferentSmall(max, 1.0)) {
407  vertices.first.emplace_back(result.head(S));
408  vertices.second.emplace_back(result[S]);
409  }
410 
411  // Advance, and take the id of the first index changed in the
412  // next combination.
413  last = enumerator.advance();
414 
415  // If the index went over the alpha list, then we'd only have
416  // boundaries, but we don't care about those cases (since we
417  // assume we already have the corners of the simplex computed).
418  // Thus, terminate.
419  if ((*enumerator)[0] >= alphasSize) break;
420  }
421  }
422  return vertices;
423  }
424 
436  template <typename Range, typename P = std::identity>
437  PointSurface findVerticesNaive(const Range & range, P p = P{}) {
438  PointSurface retval;
439 
440  std::array<size_t, 1> indexToSkip;
441 
442  auto goodBegin = range.cbegin();
443  for (size_t i = 0; i < range.size(); ++i, ++goodBegin) {
444  // For each alpha, we find its vertices against the others.
445  indexToSkip[0] = i;
446  IndexSkipMap map(&indexToSkip, range);
447 
448  const auto cbegin = map.cbegin();
449  const auto cend = map.cend();
450 
451  // Note that the range we pass here is made by a single vector.
452  auto newVertices = findVerticesNaive(goodBegin, goodBegin + 1, cbegin, cend, p, p);
453 
454  retval.first.insert(std::end(retval.first),
455  std::make_move_iterator(std::begin(newVertices.first)),
456  std::make_move_iterator(std::end(newVertices.first))
457  );
458  retval.second.insert(std::end(retval.second),
459  std::make_move_iterator(std::begin(newVertices.second)),
460  std::make_move_iterator(std::end(newVertices.second))
461  );
462  }
463  return retval;
464  }
465 
489  double computeOptimisticValue(const Point & p, const std::vector<Point> & points, const std::vector<double> & values);
490 
510  std::tuple<double, Vector> LPInterpolation(const Point & p, const CompactHyperplanes & ubQ, const PointSurface & ubV);
511 
534  std::tuple<double, Vector> sawtoothInterpolation(const Point & p, const CompactHyperplanes & ubQ, const PointSurface & ubV);
535 
551  class WitnessLP {
552  public:
560  WitnessLP(size_t S);
561 
569  void addOptimalRow(const Hyperplane & v);
570 
582  std::optional<Point> findWitness(const Hyperplane & v);
583 
589  void reset();
590 
596  void allocate(size_t rows);
597 
598  private:
599  size_t S;
600  LP lp_;
601  };
602 }
603 
604 #endif
AIToolbox::checkDifferentSmall
bool checkDifferentSmall(const double a, const double b)
This function checks if two doubles near [0,1] are reasonably different.
Definition: Core.hpp:60
IndexMap.hpp
Core.hpp
AIToolbox::WitnessLP::WitnessLP
WitnessLP(size_t S)
Basic constructor.
AIToolbox::CompactHyperplanes
Matrix2D CompactHyperplanes
A compact set of (probably |A|) hyperplanes, one per column (probably |S| rows). This is generally us...
Definition: Polytope.hpp:35
AIToolbox::findBestDeltaDominated
Iterator findBestDeltaDominated(const Point &point, const Hyperplane &plane, double delta, Iterator begin, Iterator end, P p=P{})
This function returns, if it exists, an iterator to the highest Hyperplane that delta-dominates the i...
Definition: Polytope.hpp:138
AIToolbox::findVerticesNaive
PointSurface findVerticesNaive(NewIt beginNew, NewIt endNew, OldIt alphasBegin, OldIt alphasEnd, P1 p1=P1{}, P2 p2=P2{})
This function implements a naive vertex enumeration algorithm.
Definition: Polytope.hpp:340
AIToolbox::veccmp
std::strong_ordering veccmp(const V &lhs, const V &rhs)
This function compares two general vectors of equal size lexicographically.
Definition: Core.hpp:158
AIToolbox::findBestAtSimplexCorner
Iterator findBestAtSimplexCorner(const size_t corner, Iterator begin, Iterator end, double *value=nullptr, P p=P{})
This function returns an iterator pointing to the best Hyperplane for the specified corner of the sim...
Definition: Polytope.hpp:95
AIToolbox::Matrix2D
Eigen::Matrix< double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor|Eigen::AutoAlign > Matrix2D
Definition: Types.hpp:18
AIToolbox::WitnessLP::addOptimalRow
void addOptimalRow(const Hyperplane &v)
This function adds a new optimal constraint to the LP, which will not be removed unless the LP is res...
AIToolbox::extractBestAtPoint
Iterator extractBestAtPoint(const Point &point, Iterator begin, Iterator bound, Iterator end, P p=P{})
This function finds and moves the Hyperplane with the highest value for the given point at the beginn...
Definition: Polytope.hpp:177
AIToolbox::WitnessLP::findWitness
std::optional< Point > findWitness(const Hyperplane &v)
This function solves the currently set LP.
AIToolbox::Vector
Eigen::Matrix< double, Eigen::Dynamic, 1 > Vector
Definition: Types.hpp:16
AIToolbox::LP
This class presents a common interface for solving Linear Programming problems.
Definition: LP.hpp:23
AIToolbox::extractBestAtSimplexCorners
Iterator extractBestAtSimplexCorners(const size_t S, Iterator begin, Iterator bound, Iterator end, P p=P{})
This function finds and moves all best Hyperplanes in the simplex corners at the beginning of the spe...
Definition: Polytope.hpp:209
AIToolbox::Hyperplane
Vector Hyperplane
Defines a plane in a simplex where each value is the height at that corner.
Definition: Polytope.hpp:20
TypeTraits.hpp
AIToolbox::equalToleranceSmall
constexpr auto equalToleranceSmall
This is the max absolute difference for which two values can be considered equal.
Definition: Core.hpp:14
AIToolbox::Point
ProbabilityVector Point
Defines a point inside a simplex. Coordinates sum to 1.
Definition: Polytope.hpp:25
AIToolbox::equalToleranceGeneral
constexpr auto equalToleranceGeneral
Definition: Core.hpp:18
AIToolbox::sawtoothInterpolation
std::tuple< double, Vector > sawtoothInterpolation(const Point &p, const CompactHyperplanes &ubQ, const PointSurface &ubV)
This function computes an approximate, but quick, upper bound on the surface value at the input point...
AIToolbox
Definition: Experience.hpp:6
LP.hpp
AIToolbox::PointSurface
std::pair< std::vector< Point >, std::vector< double > > PointSurface
A surface within a simplex defined by points and their height. Should not contain the corners.
Definition: Polytope.hpp:30
Combinatorics.hpp
AIToolbox::WitnessLP::reset
void reset()
This function resets the internal LP to only the simplex constraint.
AIToolbox::computeOptimisticValue
double computeOptimisticValue(const Point &p, const std::vector< Point > &points, const std::vector< double > &values)
This function computes the optimistic value of a point given known vertices and values.
AIToolbox::WitnessLP
This class implements an easy interface to do Witness discovery through linear programming.
Definition: Polytope.hpp:551
AIToolbox::extractBestUsefulPoints
PIterator extractBestUsefulPoints(PIterator pbegin, PIterator pend, VIterator begin, VIterator end, P p=P{})
This function finds and moves all non-useful points at the end of the input range.
Definition: Polytope.hpp:248
AIToolbox::WitnessLP::allocate
void allocate(size_t rows)
This function reserves space for a certain amount of rows (not counting the simplex) to avoid realloc...
AIToolbox::findBestAtPoint
Iterator findBestAtPoint(const Point &point, Iterator begin, Iterator end, double *value=nullptr, P p=P{})
This function returns an iterator pointing to the best Hyperplane for the specified point.
Definition: Polytope.hpp:65
AIToolbox::ProbabilityVector
Vector ProbabilityVector
Definition: Types.hpp:34
AIToolbox::IndexSkipMap
IndexSkipMap(std::initializer_list< T > i, Container c) -> IndexSkipMap< std::vector< T >, Container >
AIToolbox::LPInterpolation
std::tuple< double, Vector > LPInterpolation(const Point &p, const CompactHyperplanes &ubQ, const PointSurface &ubV)
This function computes the exact value of the input Point w.r.t. the given surfaces.
AIToolbox::dominates
bool dominates(const Hyperplane &lhs, const Hyperplane &rhs)
This function checks whether an Hyperplane dominates another.
Definition: Polytope.hpp:45