HypervolumeContributionApproximator.h
Go to the documentation of this file.
1 /*!
2  *
3  *
4  * \brief Approximately determines the individual contributing the least
5  * hypervolume.
6  *
7  *
8  *
9  * \author T.Voss, O.Krause
10  * \date 2010-2016
11  *
12  *
13  * \par Copyright 1995-2017 Shark Development Team
14  *
15  * <BR><HR>
16  * This file is part of Shark.
17  * <http://shark-ml.org/>
18  *
19  * Shark is free software: you can redistribute it and/or modify
20  * it under the terms of the GNU Lesser General Public License as published
21  * by the Free Software Foundation, either version 3 of the License, or
22  * (at your option) any later version.
23  *
24  * Shark is distributed in the hope that it will be useful,
25  * but WITHOUT ANY WARRANTY; without even the implied warranty of
26  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27  * GNU Lesser General Public License for more details.
28  *
29  * You should have received a copy of the GNU Lesser General Public License
30  * along with Shark. If not, see <http://www.gnu.org/licenses/>.
31  *
32  */
33 #ifndef SHARK_ALGORITHMS_DIRECTSEARCH_HYPERVOLUME_CONTRIBUTION_APPROXIMATOR_H
34 #define SHARK_ALGORITHMS_DIRECTSEARCH_HYPERVOLUME_CONTRIBUTION_APPROXIMATOR_H
35 
36 #include <boost/cstdint.hpp>
37 
40 #include <algorithm>
41 #include <limits>
42 #include <vector>
43 #include <cmath>
44 
45 namespace shark {
46 
47 /// \brief Approximately determines the point of a set contributing the least hypervolume.
48 ///
49 /// See K. Bringmann, T. Friedrich. Approximating the least hypervolume contributor: NP-hard in general, but fast in practice. Proc. of the 5th International Conference on Evolutionary Multi-Criterion Optimization (EMO 2009), Vol. 5467 of LNCS, pages 6-20, Springer-Verlag, 2009.
50 ///
51 /// The algorithm works by estimating a bounding box for every point that includes all its contribution volume and then draw
52 /// sample inside the box to estimate which fraction of the box is covered by other points in the set.
53 ///
54 /// The algorithm only implements the k=1 version of the smallest contribution. For the element A it returns holds the
55 /// following guarantue: with probability of 1-delta, Con(A) < (1+epsilon)Con(LC)
56 /// where LC is true least contributor. Note that there are no error guarantuees regarding the returned value of the contribution:
57 /// the algorithm stops when it is sure that the bound above holds, but depending on the setup, this might be very early.
58 ///
59 /// Note that, while on average the algorithm performs reasonable well, the upper run-time is not bounded by the number of elements or dimensionality.
60 /// When two points have nearly the same(or exactly the same) contribution
61 /// the algorithm will run for many iterations, until the bound above holds. The same holds if the point with the smallest contribution
62 /// has a very large potential contribution as many samples are required to establish that allmost all of the box is covered.
63 ///
64 ///\tparam random The type of the random for sampling random points.
66  /// \brief Models a point and associated information for book-keeping purposes.
67  template<typename VectorType>
68  struct Point {
69  Point( VectorType const& point, VectorType const& reference )
70  : point( point )
71  , sample( point.size() )
72  , boundingBox( reference )
73  , boundingBoxVolume( 0. )
77  , computedExactly(false)
78  , noSamples( 0 )
79  , noSuccessfulSamples( 0 )
80  {}
81 
85  std::vector< typename std::vector<Point>::const_iterator > influencingPoints;
86 
92 
93  std::size_t noSamples;
94  std::size_t noSuccessfulSamples;
95  };
96 
100 
101  double m_gamma;
102  double m_errorProbability; ///<The error probability.
103  double m_errorBound; ///<The error bound
104 
105  /// \brief C'tor
106  /// \param [in] delta the error probability of the least contributor
107  /// \param [in] eps the error bound of the least contributor
109  : m_startDeltaMultiplier( 0.1 )
110  , m_multiplierDelta( 0.775 )
111  , m_minimumMultiplierDelta( 0.2 )
112  , m_gamma( 0.25 )
113  , m_errorProbability(1.E-2)
114  , m_errorBound(1.E-2)
115  {}
116 
117  double delta()const{
118  return m_errorProbability;
119  }
120  double& delta(){
121  return m_errorProbability;
122  }
123 
124  double epsilon()const{
125  return m_errorBound;
126  }
127  double& epsilon(){
128  return m_errorBound;
129  }
130 
131  template<typename Archive>
132  void serialize( Archive & archive, const unsigned int version ) {
133  archive & BOOST_SERIALIZATION_NVP(m_startDeltaMultiplier);
134  archive & BOOST_SERIALIZATION_NVP(m_multiplierDelta);
135  archive & BOOST_SERIALIZATION_NVP(m_minimumMultiplierDelta);
136  archive & BOOST_SERIALIZATION_NVP(m_gamma);
137  archive & BOOST_SERIALIZATION_NVP(m_errorProbability);
138  archive & BOOST_SERIALIZATION_NVP(m_errorBound);
139  }
140 
141  /// \brief Determines the point contributing the least hypervolume to the overall set of points.
142  ///
143  /// \param [in] s pareto front of points
144  /// \param [in] reference The reference point to consider for calculating individual points' contributions.
145  template<class Set,class VectorType>
146  std::vector<KeyValuePair<double,std::size_t> > smallest(Set const& points, std::size_t k, VectorType const& reference)const{
147  SHARK_RUNTIME_CHECK(points.size() >= k, "There must be at least k points in the set");
148  SHARK_RUNTIME_CHECK(k == 1, "Not implemented for k != 1");
149 
150  std::vector< Point<VectorType> > front;
151  for(auto const& point: points) {
152  front.emplace_back( point, reference );
153  }
154  computeBoundingBoxes( front );
155 
156  std::vector< typename std::vector< Point<VectorType> >::iterator > activePoints;
157  for(auto it = front.begin(); it != front.end(); ++it ) {
158  activePoints.push_back( it );
159  }
160 
161  auto smallest = computeSmallest(activePoints, front.size());
162 
163  std::vector<KeyValuePair<double,std::size_t> > result(1);
164  result[0].key = smallest->approximatedContribution;
165  result[0].value = smallest-front.begin();
166  return result;
167  }
168 
169 
170  /// \brief Returns the index of the points with smallest contribution.
171  ///
172  /// As no reference point is given, the extremum points can not be computed and are never selected.
173  ///
174  /// \param [in] points The set \f$S\f$ of points from which to select the smallest contributor.
175  /// \param [in] k The number of points to select.
176  template<class Set>
177  std::vector<KeyValuePair<double,std::size_t> > smallest(Set const& points, std::size_t k)const{
178  SHARK_RUNTIME_CHECK(points.size() >= k, "There must be at least k points in the set");
179  SHARK_RUNTIME_CHECK(k == 1, "Not implemented for k != 1");
180 
181  //find reference point as well as points with lowest function value
182  std::vector<std::size_t> minIndex(points[0].size(),0);
183  RealVector minVal = points[0];
184  RealVector reference=points[0];
185  for(std::size_t i = 1; i != points.size(); ++i){
186  noalias(reference) = max(reference,points[i]);
187  for(std::size_t j = 0; j != minVal.size(); ++j){
188  if(points[i](j)< minVal[j]){
189  minVal[j] = points[i](j);
190  minIndex[j]=i;
191  }
192  }
193  }
194 
195  std::vector< Point<RealVector> > front;
196  front.reserve( points.size() );
197  for(auto const& point: points){
198  front.emplace_back( point, reference );
199  }
200  computeBoundingBoxes( front );
201 
202  std::vector<std::vector< Point<RealVector> >::iterator > activePoints;
203  for(auto it = front.begin(); it != front.end(); ++it ) {
204  if(std::find(minIndex.begin(),minIndex.end(),it-front.begin()) != minIndex.end())
205  continue;
206  //~ //check whether point is on the boundary -> least contributor
207  //~ for(std::size_t j = 0; j != minVal.size(); ++j){
208  //~ if(it->point[j] == reference[j]){
209  //~ return std::vector<KeyValuePair<double,std::size_t> >(1,{0.0,it-front.begin()});
210  //~ }
211  //~ }
212  activePoints.push_back( it );
213 
214  }
215 
216 
217  auto smallest = computeSmallest(activePoints, front.size());
218  std::vector<KeyValuePair<double,std::size_t> > result(1);
219  result[0].key = smallest->approximatedContribution;
220  result[0].value = smallest-front.begin();
221  return result;
222  }
223 
224 private:
225 
226  template<class Set>
227  typename Set::value_type computeSmallest(Set& activePoints, std::size_t n)const{
228  typedef typename Set::value_type SetIter;
229  //compute initial guess for delta
230  double delta = 0.;
231  for( auto it = activePoints.begin(); it != activePoints.end(); ++it )
232  delta = std::max( delta, (*it)->boundingBoxVolume );
233  delta *= m_startDeltaMultiplier;
234 
235  unsigned int round = 0;
236  while( true ) {
237  round++;
238 
239  //check whether we spent so much time on sampling that computing the real volume is not much more expensive any more.
240  //this guarantuees convergence even in cases that two points have the same hyper volume.
241  SHARK_PARALLEL_FOR(int i = 0; i < (int)activePoints.size(); ++i){
242  if(shouldCompute(*activePoints[i])){
243  computeExactly(*activePoints[i]);
244  }
245  }
246 
247  //sample all active points so that their individual deviations are smaller than delta
248  for( auto point: activePoints )
249  sample( *point, round, delta, n );
250 
251  //find the current least contributor
252  auto minimalElement = std::min_element(
253  activePoints.begin(),activePoints.end(),
254  [](SetIter const& a, SetIter const& b){return a->approximatedContribution < b->approximatedContribution;}
255  );
256 
257  //section 3.4.1: push the least contributor: decrease its delta further to have a chance to end earlier.
258  if( activePoints.size() > 2 ) {
259  sample( **minimalElement, round, m_minimumMultiplierDelta * delta, n );
260  minimalElement = std::min_element(
261  activePoints.begin(),activePoints.end(),
262  [](SetIter const& a, SetIter const& b){return a->approximatedContribution < b->approximatedContribution;}
263  );
264  }
265 
266  //remove all points whose confidence interval does not overlap with the current minimum any more.
267  double erase_level = (*minimalElement)->contributionUpperBound;
268  auto erase_start = std::remove_if(
269  activePoints.begin(),activePoints.end(),
270  [=](SetIter const& point){return point->contributionLowerBound > erase_level;}
271  );
272  activePoints.erase(erase_start,activePoints.end());
273 
274  //if the set only has one point left, we are done.
275  if(activePoints.size() == 1)
276  return activePoints.front();
277 
278  // stopping conditions: have we reached the desired accuracy?
279  // for this we need to know:
280  // 1. contribution for all points are bounded above 0
281  // 2. upperBound(LC) < (1+epsilon)*lowerBound(A) for all A
282  double d = 0;
283  for( auto it = activePoints.begin(); it != activePoints.end(); ++it ) {
284  if( it == minimalElement )
285  continue;
286  double nom = (*minimalElement)-> contributionUpperBound;
287  double den = (*it)->contributionLowerBound;
288  if( den <= 0. )
289  d = std::numeric_limits<double>::max();
290  else
291  d = std::max(d,nom/den);
292  }
293 
294  //if the stopping condition is fulfilled, return the minimal element
295  if(d < 1. + m_errorBound){
296  return *minimalElement;
297  }
298 
299  delta *= m_multiplierDelta;
300  }
301  }
302 
303  /// \brief Samples in the bounding box of the supplied point until a pre-defined threshold is reached.
304  ///
305  /// \param [in] point Iterator to the point that should be sampled.
306  /// \param [in] r The current round.
307  /// \param [in] delta The delta that should be reached.
308  /// \param [in] n the total number of points in the front. Required for proper calculation of bounds
309  template<class VectorType>
310  void sample( Point<VectorType>& point, unsigned int r, double delta, std::size_t n )const{
311  if(point.computedExactly) return;//spend no time on points that are computed exactly
312 
313  double logFactor = std::log( 2. * n * (1. + m_gamma) / (m_errorProbability * m_gamma) );
314  double logR = std::log( static_cast<double>( r ) );
315  //compute how many samples we need until the bound of the current box is smaller than delta
316  //this is formula (3) in the paper when used in an equality < delta and solving for noSamples
317  //we add +1 to ensure that the inequality holds.
318  double thresholdD= 1.0+0.5 * ( (1. + m_gamma) * logR + logFactor ) * sqr( point.boundingBoxVolume / delta );
319  std::size_t threshold = static_cast<std::size_t>(thresholdD);
320  //sample points inside the box of the current point
321  for( ; point.noSamples < threshold; point.noSamples++ ) {
322  //sample a point inside the box
323  point.sample.resize(point.point.size());
324  for( unsigned int i = 0; i < point.sample.size(); i++ ) {
325  point.sample[ i ] = random::uni(random::globalRng, point.point[ i ], point.boundingBox[ i ] );
326  }
327  ++point.noSamples;
328  //check if the point is not dominated by any of the influencing points
329  if( !isPointDominated( point.influencingPoints, point.sample ) )
330  point.noSuccessfulSamples++;
331  }
332 
333  //current best guess for volume: fraction of accepted points inside the box imes the volume of the box. (2) in the paper
335  //lower and upper bounds for the best guess: with high probability it will be in this region. (3) in the paper.
336  double deltaReached = std::sqrt( 0.5 * ((1. + m_gamma) * logR+ logFactor ) / point.noSamples ) * point.boundingBoxVolume;
337  point.contributionLowerBound = point.approximatedContribution - deltaReached;
338  point.contributionUpperBound = point.approximatedContribution + deltaReached;
339  }
340 
341  /// \brief Checks whether a point is dominated by any point in a given set.
342  ///
343  /// \tparam Set The type of the set of points.
344  ///
345  /// \param [in] set The set of individuals to check the sampled point against.
346  /// \param [in] point Point to test
347  ///
348  /// \returns true if the point was non-dominated
349  template<typename Set, typename VectorType>
350  bool isPointDominated( Set const& set, VectorType const& point )const{
351 
352  for( unsigned int i = 0; i < set.size(); i++ ) {
353  DominanceRelation rel = dominance(set[i]->point, point);
354  if (rel == LHS_DOMINATES_RHS)
355  return true;
356  }
357  return false;
358  }
359 
360  /// \brief Computes bounding boxes and their volume for the range of points defined by the iterators.
361  template<class Set>
362  void computeBoundingBoxes(Set& set )const{
363  for(auto it = set.begin(); it != set.end(); ++it ) {
364  auto& p1 = *it;
365  //first cut the bounding boxes on sides that are completely covered by other points
366  for(auto itt = set.begin(); itt != set.end(); ++itt ) {
367 
368  if( itt == it )
369  continue;
370 
371  auto& p2 = *itt;
372 
373  unsigned int coordCounter = 0;
374  unsigned int coord = 0;
375  for( unsigned int o = 0; o < p1.point.size(); o++ ) {
376  if( p2.point[ o ] > p1.point[ o ] ) {
377  coordCounter++;
378  if( coordCounter == 2 )
379  break;
380  coord = o;
381  }
382  }
383 
384  if( coordCounter == 1 && p1.boundingBox[ coord ] > p2.point[ coord ] )
385  p1.boundingBox[ coord ] = p2.point[ coord ];
386  }
387 
388  //compute volume of the box
389  it->boundingBoxVolume = 1.;
390  for(unsigned int i = 0 ; i < it->boundingBox.size(); i++ ) {
391  it->boundingBoxVolume *= it->boundingBox[ i ] - it->point[ i ];
392  }
393 
394  //find all points that are partially covering this box
395  for(auto itt = set.begin(); itt != set.end(); ++itt ) {
396  if( itt == it )
397  continue;
398 
399  bool isInfluencing = true;
400  for( unsigned int i = 0; i < it->point.size(); i++ ) {
401  if( itt->point[ i ] >= it->boundingBox[ i ] ) {
402  isInfluencing = false;
403  break;
404  }
405  }
406  if( isInfluencing ) {
407  it->influencingPoints.push_back( itt );
408  }
409  }
410  }
411  }
412  template<class VectorType>
413  bool shouldCompute(Point<VectorType> const& point)const{
414  //we do not compute if it is already computed
415  if(point.computedExactly) return false;
416  std::size_t numPoints = point.influencingPoints.size();
417  //point is on its own no need to sample.
418  if(numPoints == 0) return true;
419  std::size_t numObjectives = point.point.size();
420  //runtime already spend on point
421  double time = (double)point.noSamples * numObjectives;
422 
423  //estimate of algo run time
424  double algoRunTime = 0.03 * numObjectives * numObjectives * std::pow(numPoints, numObjectives * 0.5 );
425  return time > algoRunTime;
426  }
427 
428  template<class VectorType>
429  void computeExactly(Point<VectorType>& point)const{
430  std::size_t numPoints = point.influencingPoints.size();
431  //compute volume of the points inside the box
432  std::vector<VectorType> transformedPoints(numPoints);
433  for(std::size_t j = 0; j != numPoints; ++j){
434  transformedPoints[j] = max(point.influencingPoints[j]->point, point.point);
435  }
437  double volume = point.boundingBoxVolume - vol(transformedPoints, point.boundingBox);
438  point.computedExactly = true;
439  point.contributionLowerBound = volume;
440  point.contributionUpperBound = volume;
441  point.approximatedContribution = volume;
442  }
443 };
444 }
445 
446 #endif