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 * <https://shark-ml.github.io/Shark/>
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
45namespace 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 )
80 {}
81
85 std::vector< typename std::vector<Point>::const_iterator > influencingPoints;
86
92
93 std::size_t noSamples;
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
108 , m_multiplierDelta( 0.775 )
110 , m_gamma( 0.25 )
111 , m_errorProbability(1.E-2)
112 , m_errorBound(1.E-2)
113 {}
114
115 double delta()const{
116 return m_errorProbability;
117 }
118 double& delta(){
119 return m_errorProbability;
120 }
121
122 double epsilon()const{
123 return m_errorBound;
124 }
125 double& epsilon(){
126 return m_errorBound;
127 }
128
129 template<typename Archive>
130 void serialize( Archive & archive, const unsigned int version ) {
131 archive & BOOST_SERIALIZATION_NVP(m_startDeltaMultiplier);
132 archive & BOOST_SERIALIZATION_NVP(m_multiplierDelta);
133 archive & BOOST_SERIALIZATION_NVP(m_minimumMultiplierDelta);
134 archive & BOOST_SERIALIZATION_NVP(m_gamma);
135 archive & BOOST_SERIALIZATION_NVP(m_errorProbability);
136 archive & BOOST_SERIALIZATION_NVP(m_errorBound);
137 }
138
139 /// \brief Determines the point contributing the least hypervolume to the overall set of points.
140 ///
141 /// \param [in] points pareto front of points
142 /// \param [in] k The number of points to select.
143 /// \param [in] reference The reference point to consider for calculating individual points' contributions.
144 template<class Set,class VectorType>
145 std::vector<KeyValuePair<double,std::size_t> > smallest(Set const& points, std::size_t k, VectorType const& reference)const{
146 SHARK_RUNTIME_CHECK(points.size() >= k, "There must be at least k points in the set");
147 SHARK_RUNTIME_CHECK(k == 1, "Not implemented for k != 1");
148
149 std::vector< Point<VectorType> > front;
150 for(auto const& point: points) {
151 front.emplace_back( point, reference );
152 }
153 computeBoundingBoxes( front );
154
155 std::vector< typename std::vector< Point<VectorType> >::iterator > activePoints;
156 for(auto it = front.begin(); it != front.end(); ++it ) {
157 activePoints.push_back( it );
158 }
159
160 auto smallest = computeSmallest(activePoints, front.size());
161
162 std::vector<KeyValuePair<double,std::size_t> > result(1);
163 result[0].key = smallest->approximatedContribution;
164 result[0].value = smallest-front.begin();
165 return result;
166 }
167
168
169 /// \brief Returns the index of the points with smallest contribution.
170 ///
171 /// As no reference point is given, the extremum points can not be computed and are never selected.
172 ///
173 /// \param [in] points The set \f$S\f$ of points from which to select the smallest contributor.
174 /// \param [in] k The number of points to select.
175 template<class Set>
176 std::vector<KeyValuePair<double,std::size_t> > smallest(Set const& points, std::size_t k)const{
177 SHARK_RUNTIME_CHECK(points.size() >= k, "There must be at least k points in the set");
178 SHARK_RUNTIME_CHECK(k == 1, "Not implemented for k != 1");
179
180 //find reference point as well as points with lowest function value
181 std::vector<std::size_t> minIndex(points[0].size(),0);
182 RealVector minVal = points[0];
183 RealVector reference=points[0];
184 for(std::size_t i = 1; i != points.size(); ++i){
185 noalias(reference) = max(reference,points[i]);
186 for(std::size_t j = 0; j != minVal.size(); ++j){
187 if(points[i](j)< minVal[j]){
188 minVal[j] = points[i](j);
189 minIndex[j]=i;
190 }
191 }
192 }
193
194 std::vector< Point<RealVector> > front;
195 front.reserve( points.size() );
196 for(auto const& point: points){
197 front.emplace_back( point, reference );
198 }
199 computeBoundingBoxes( front );
200
201 std::vector<std::vector< Point<RealVector> >::iterator > activePoints;
202 for(auto it = front.begin(); it != front.end(); ++it ) {
203 if(std::find(minIndex.begin(),minIndex.end(),it-front.begin()) != minIndex.end())
204 continue;
205 //~ //check whether point is on the boundary -> least contributor
206 //~ for(std::size_t j = 0; j != minVal.size(); ++j){
207 //~ if(it->point[j] == reference[j]){
208 //~ return std::vector<KeyValuePair<double,std::size_t> >(1,{0.0,it-front.begin()});
209 //~ }
210 //~ }
211 activePoints.push_back( it );
212
213 }
214
215
216 auto smallest = computeSmallest(activePoints, front.size());
217 std::vector<KeyValuePair<double,std::size_t> > result(1);
218 result[0].key = smallest->approximatedContribution;
219 result[0].value = smallest-front.begin();
220 return result;
221 }
222
223private:
224
225 template<class Set>
226 typename Set::value_type computeSmallest(Set& activePoints, std::size_t n)const{
227 typedef typename Set::value_type SetIter;
228 //compute initial guess for delta
229 double delta = 0.;
230 for( auto it = activePoints.begin(); it != activePoints.end(); ++it )
231 delta = std::max( delta, (*it)->boundingBoxVolume );
233
234 unsigned int round = 0;
235 while( true ) {
236 round++;
237
238 //check whether we spent so much time on sampling that computing the real volume is not much more expensive any more.
239 //this guarantuees convergence even in cases that two points have the same hyper volume.
240 SHARK_PARALLEL_FOR(int i = 0; i < (int)activePoints.size(); ++i){
241 if(shouldCompute(*activePoints[i])){
242 computeExactly(*activePoints[i]);
243 }
244 }
245
246 //sample all active points so that their individual deviations are smaller than delta
247 for( auto point: activePoints )
248 sample( *point, round, delta, n );
249
250 //find the current least contributor
251 auto minimalElement = std::min_element(
252 activePoints.begin(),activePoints.end(),
253 [](SetIter const& a, SetIter const& b){return a->approximatedContribution < b->approximatedContribution;}
254 );
255
256 //section 3.4.1: push the least contributor: decrease its delta further to have a chance to end earlier.
257 if( activePoints.size() > 2 ) {
258 sample( **minimalElement, round, m_minimumMultiplierDelta * delta, n );
259 minimalElement = std::min_element(
260 activePoints.begin(),activePoints.end(),
261 [](SetIter const& a, SetIter const& b){return a->approximatedContribution < b->approximatedContribution;}
262 );
263 }
264
265 //remove all points whose confidence interval does not overlap with the current minimum any more.
266 double erase_level = (*minimalElement)->contributionUpperBound;
267 auto erase_start = std::remove_if(
268 activePoints.begin(),activePoints.end(),
269 [=](SetIter const& point){return point->contributionLowerBound > erase_level;}
270 );
271 activePoints.erase(erase_start,activePoints.end());
272
273 //if the set only has one point left, we are done.
274 if(activePoints.size() == 1)
275 return activePoints.front();
276
277 // stopping conditions: have we reached the desired accuracy?
278 // for this we need to know:
279 // 1. contribution for all points are bounded above 0
280 // 2. upperBound(LC) < (1+epsilon)*lowerBound(A) for all A
281 double d = 0;
282 for( auto it = activePoints.begin(); it != activePoints.end(); ++it ) {
283 if( it == minimalElement )
284 continue;
285 double nom = (*minimalElement)-> contributionUpperBound;
286 double den = (*it)->contributionLowerBound;
287 if( den <= 0. )
288 d = std::numeric_limits<double>::max();
289 else
290 d = std::max(d,nom/den);
291 }
292
293 //if the stopping condition is fulfilled, return the minimal element
294 if(d < 1. + m_errorBound){
295 return *minimalElement;
296 }
297
299 }
300 }
301
302 /// \brief Samples in the bounding box of the supplied point until a pre-defined threshold is reached.
303 ///
304 /// \param [in] point Iterator to the point that should be sampled.
305 /// \param [in] r The current round.
306 /// \param [in] delta The delta that should be reached.
307 /// \param [in] n the total number of points in the front. Required for proper calculation of bounds
308 template<class VectorType>
309 void sample( Point<VectorType>& point, unsigned int r, double delta, std::size_t n )const{
310 if(point.computedExactly) return;//spend no time on points that are computed exactly
311
312 double logFactor = std::log( 2. * n * (1. + m_gamma) / (m_errorProbability * m_gamma) );
313 double logR = std::log( static_cast<double>( r ) );
314 //compute how many samples we need until the bound of the current box is smaller than delta
315 //this is formula (3) in the paper when used in an equality < delta and solving for noSamples
316 //we add +1 to ensure that the inequality holds.
317 double thresholdD= 1.0+0.5 * ( (1. + m_gamma) * logR + logFactor ) * sqr( point.boundingBoxVolume / delta );
318 std::size_t threshold = static_cast<std::size_t>(thresholdD);
319 //sample points inside the box of the current point
320 for( ; point.noSamples < threshold; point.noSamples++ ) {
321 //sample a point inside the box
322 point.sample.resize(point.point.size());
323 for( unsigned int i = 0; i < point.sample.size(); i++ ) {
324 point.sample[ i ] = random::uni(random::globalRng, point.point[ i ], point.boundingBox[ i ] );
325 }
326 ++point.noSamples;
327 //check if the point is not dominated by any of the influencing points
328 if( !isPointDominated( point.influencingPoints, point.sample ) )
329 point.noSuccessfulSamples++;
330 }
331
332 //current best guess for volume: fraction of accepted points inside the box imes the volume of the box. (2) in the paper
333 point.approximatedContribution = (point.boundingBoxVolume * point.noSuccessfulSamples) / point.noSamples;
334 //lower and upper bounds for the best guess: with high probability it will be in this region. (3) in the paper.
335 double deltaReached = std::sqrt( 0.5 * ((1. + m_gamma) * logR+ logFactor ) / point.noSamples ) * point.boundingBoxVolume;
336 point.contributionLowerBound = point.approximatedContribution - deltaReached;
337 point.contributionUpperBound = point.approximatedContribution + deltaReached;
338 }
339
340 /// \brief Checks whether a point is dominated by any point in a given set.
341 ///
342 /// \tparam Set The type of the set of points.
343 ///
344 /// \param [in] set The set of individuals to check the sampled point against.
345 /// \param [in] point Point to test
346 ///
347 /// \returns true if the point was non-dominated
348 template<typename Set, typename VectorType>
349 bool isPointDominated( Set const& set, VectorType const& point )const{
350
351 for( unsigned int i = 0; i < set.size(); i++ ) {
352 DominanceRelation rel = dominance(set[i]->point, point);
353 if (rel == LHS_DOMINATES_RHS)
354 return true;
355 }
356 return false;
357 }
358
359 /// \brief Computes bounding boxes and their volume for the range of points defined by the iterators.
360 /// \param [in] set The set of individuals.
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 }
436 HypervolumeCalculator vol;
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