AIToolbox
A library that offers tools for AI problem solving.
Prune.hpp
Go to the documentation of this file.
1 #ifndef AI_TOOLBOX_UTILS_PRUNE_HEADER_FILE
2 #define AI_TOOLBOX_UTILS_PRUNE_HEADER_FILE
3 
4 #include <algorithm>
5 
6 #include <AIToolbox/Types.hpp>
10 
11 namespace AIToolbox {
29  template <typename Iterator, typename P = std::identity>
30  Iterator extractDominated(Iterator begin, Iterator end, P p = P{}) {
31  if ( std::distance(begin, end) < 2 ) return end;
32 
33  auto optEnd = begin;
34  while (optEnd < end) {
35  auto target = end - 1; // The one we are checking whether it is dominated.
36  // Check against proven non-dominated vectors
37  for (auto iter = begin; iter < optEnd; ++iter) {
38  if (dominates(std::invoke(p, *iter), std::invoke(p, *target))) {
39  --end;
40  goto next;
41  }
42  }
43  {
44  // Check against others and find another non-dominated. Here
45  // we go from the back so that we only swap with vectors we
46  // have already checked.
47  auto helper = target;
48  while (helper != optEnd) {
49  --helper;
50  // If dominated, remove it and continue from there.
51  if (dominates(std::invoke(p, *helper), std::invoke(p, *target))) {
52  std::iter_swap(target, --end);
53  target = helper;
54  }
55  }
56  // Add vector we found in the non-dominated group
57  std::iter_swap(target, optEnd);
58  ++optEnd;
59  }
60 next:;
61  }
62  return end;
63  }
64 
95  template <typename Iterator, typename P = std::identity>
96  std::tuple<Iterator, Iterator, Iterator> extractDominatedIncremental(Iterator begin, Iterator newBegin, Iterator end, P p = P{}) {
97  // Make sure new entries don't dominate each other. This simplifies the
98  // checks and swaps we need to do later.
99  end = extractDominated(newBegin, end, p);
100  // Here we juggle the entries in the following way: we're going to have 4 separate ranges.
101  //
102  // begin oldEnd newBegin target end original end (discarded)
103  // * <old good> * <old bad> * <new to check> * <new good> * <new bad> *
104  //
105  // New entries which are dominated by the still good old vectors get
106  // moved to the "new bad" range. Old entries that are dominated by a
107  // new entry are moved to the "old bad" range. In the end, the "new to
108  // check" range shrinks until it's gone.
109  //
110  // begin oldEnd newBegin end original end (discarded)
111  // * <old good> * <old bad> * <new good> * <new bad> *
112  //
113  // Once we are done we shuffle the "old bad" and "new good" ranges so
114  // that we get:
115  //
116  // begin oldEnd mid end original end (discarded)
117  // * <old good> * <new good> * <old bad> * <new bad> *
118  //
119  // And we return the pointers to oldEnd, mid and end.
120  //
121  // Note that we make *NO* attempt at preserving the original ordering.
122  auto oldEnd = newBegin;
123  auto target = end;
124  while (target > newBegin) {
125  // Check new entries backwards so if they are bad we can swap them
126  // with provenly good entries.
127  --target;
128  // For each pre-existing Hyperplane, we check whether we dominate or we are dominated.
129  // - If we are dominated, we are done, as we don't belong in the
130  // good set.
131  // - If we dominate, then we have to check whether we dominate
132  // anyone else, and remove all of them.
133  // - If by the end no check was true, then we get placed in the
134  // good set.
135  bool isDominating = false;
136  auto old = oldEnd;
137  while (old > begin) {
138  --old;
139  // First check that the new entry is not dominated
140  if (!isDominating && dominates(std::invoke(p, *old), std::invoke(p, *target))) {
141  // If it is, we remove it by swapping it with the good
142  // new entry next to the bad range.
143  std::iter_swap(target, --end);
144  goto next;
145  }
146  // Now whether we dominate
147  if (dominates(std::invoke(p, *target), std::invoke(p, *old))) {
148  // We are dominating, so we are sure we can't be dominated,
149  // so we can skip those checks from now on. We still have
150  // to check against all old entries since we may dominate
151  // other ones.
152  isDominating = true;
153  // If we dominate, we put the old eliminated entries in a
154  // subrange between old and new. We are going to put them
155  // at the end afterwards. Note that we swap against an old
156  // we have already checked.
157  std::iter_swap(old, --oldEnd);
158  }
159  }
160 next:;
161  }
162  // Finally, we swap the "new good" and "old bad" ranges. We go forward
163  // for the old bad, and backwards for the new good, so we can stop as
164  // soon as the shortest of the two ranges is moved.
165  auto oldSwap = oldEnd, newSwap = end;
166  while (newSwap > newBegin && oldSwap < newBegin)
167  std::iter_swap(--newSwap, oldSwap++);
168 
169  return {oldEnd, newSwap == newBegin ? oldSwap : newSwap, end};
170  }
171 
180  class Pruner {
181  public:
187  Pruner(const size_t s) : S(s), lp_(S) {}
188 
196  template <typename It, typename P = std::identity>
197  It operator()(It begin, It end, P p = P{});
198 
199  private:
200  size_t S;
201 
202  WitnessLP lp_;
203  };
204 
205  // The idea is that the input thing already has all the best vectors,
206  // thus we only need to find them and discard the others.
207  template <typename It, typename P>
208  It Pruner::operator()(It begin, It end, P p) {
209  // Remove easy ValueFunctions to avoid doing more work later.
210  end = extractDominated(begin, end, p);
211 
212  const size_t size = std::distance(begin, end);
213  if ( size < 2 ) return end;
214 
215  // Initialize the new best list with some easy finds, and remove them from
216  // the old list.
217  It bound = begin;
218 
219  bound = extractBestAtSimplexCorners(S, begin, bound, end, p);
220 
221  // Here we could do some random belief lookups..
222 
223  // If we actually have still work to do..
224  if ( bound < end ) {
225  // We setup the lp preparing for a max of size rows.
226  lp_.reset();
227  lp_.allocate(size);
228 
229  // Setup initial LP rows. Note that best can't be empty, since we have
230  // at least one best for the simplex corners.
231  for ( auto it = begin; it != bound; ++it )
232  lp_.addOptimalRow(std::invoke(p, *it));
233  }
234 
235  // For each of the remaining points now we try to find a witness
236  // point with respect to the best ones. If there is, there is
237  // something we need to extract to best.
238  //
239  // What we are going to do is to push each 'best' constraint into
240  // the lp, and then push/pop the 'v' constraint every time we need
241  // to try out a new one.
242  //
243  // That we do in the findWitnessPoint function.
244  while ( bound < end ) {
245  const auto witness = lp_.findWitness(std::invoke(p, *(end-1)));
246  // If we get a belief point, we search for the actual vector that provides
247  // the best value on the belief point, we move it into the best vector.
248  if ( witness ) {
249  // Advance bound with the next best
250  bound = extractBestAtPoint(*witness, bound, bound, end, p);
251  // Add the newly found vector to our lp.
252  lp_.addOptimalRow(std::invoke(p, *(bound-1)));
253  }
254  // We only advance if we did not find anything. Otherwise, we may have found a
255  // witness point for the current value, but since we are not guaranteed to have
256  // put into best that value, it may still keep witness to other belief points!
257  else
258  --end;
259  }
260 
261  return bound;
262  }
263 }
264 
265 #endif
Core.hpp
AIToolbox::extractDominated
Iterator extractDominated(Iterator begin, Iterator end, P p=P{})
This function finds and moves all Hyperplanes in the range that are dominated by others.
Definition: Prune.hpp:30
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::Pruner
This class offers pruning facilities for non-parsimonious ValueFunction sets.
Definition: Prune.hpp:180
AIToolbox::WitnessLP::findWitness
std::optional< Point > findWitness(const Hyperplane &v)
This function solves the currently set LP.
AIToolbox::extractDominatedIncremental
std::tuple< Iterator, Iterator, Iterator > extractDominatedIncremental(Iterator begin, Iterator newBegin, Iterator end, P p=P{})
This function finds and moves all Hyperplanes in the range that are dominated by others.
Definition: Prune.hpp:96
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
TypeTraits.hpp
AIToolbox::Pruner::operator()
It operator()(It begin, It end, P p=P{})
This function prunes all non useful hyperplanes from the provided list.
Definition: Prune.hpp:208
AIToolbox
Definition: Experience.hpp:6
AIToolbox::Pruner::Pruner
Pruner(const size_t s)
Basic constructor.
Definition: Prune.hpp:187
Types.hpp
AIToolbox::WitnessLP::reset
void reset()
This function resets the internal LP to only the simplex constraint.
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::dominates
bool dominates(const Hyperplane &lhs, const Hyperplane &rhs)
This function checks whether an Hyperplane dominates another.
Definition: Polytope.hpp:45
Polytope.hpp