PaGMO  1.1.5
bf_approx.cpp
1 /*****************************************************************************
2  * Copyright (C) 2004-2015 The PaGMO development team, *
3  * Advanced Concepts Team (ACT), European Space Agency (ESA) *
4  * *
5  * https://github.com/esa/pagmo *
6  * *
7  * act@esa.int *
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  * This program is distributed in the hope that it will be useful, *
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
17  * GNU General Public License for more details. *
18  * *
19  * You should have received a copy of the GNU General Public License *
20  * along with this program; if not, write to the *
21  * Free Software Foundation, Inc., *
22  * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
23  *****************************************************************************/
24 
25 
26 #include "bf_approx.h"
27 
28 namespace pagmo { namespace util { namespace hv_algorithm {
29 
31 
43 bf_approx::bf_approx(const bool use_exact, const unsigned int trivial_subcase_size, const double eps, const double delta, const double delta_multiplier, const double alpha, const double initial_delta_coeff, const double gamma)
44  : m_use_exact(use_exact), m_trivial_subcase_size(trivial_subcase_size), m_eps(eps), m_delta(delta), m_delta_multiplier(delta_multiplier), m_alpha(alpha), m_initial_delta_coeff(initial_delta_coeff), m_gamma(gamma) { }
45 
46 double bf_approx::lc_end_condition(unsigned int idx, unsigned int LC, std::vector<double> &approx_volume, std::vector<double> &point_delta)
47 {
48  return (approx_volume[LC] + point_delta[LC]) / (approx_volume[idx] - point_delta[idx]);
49 }
50 double bf_approx::gc_end_condition(unsigned int idx, unsigned int GC, std::vector<double> &approx_volume, std::vector<double> &point_delta)
51 {
52  return (approx_volume[idx] + point_delta[idx]) / (approx_volume[GC] - point_delta[GC]);
53 }
54 bool bf_approx::lc_erase_condition(unsigned int idx, unsigned int LC, std::vector<double> &approx_volume, std::vector<double> &point_delta)
55 {
56  return (approx_volume[idx] - point_delta[idx]) > (approx_volume[LC] + point_delta[LC]);
57 }
58 bool bf_approx::gc_erase_condition(unsigned int idx, unsigned int GC, std::vector<double> &approx_volume, std::vector<double> &point_delta)
59 {
60  return (approx_volume[idx] + point_delta[idx]) < (approx_volume[GC] - point_delta[GC]);
61 }
62 
64 
72 unsigned int bf_approx::least_contributor(std::vector<fitness_vector> &points, const fitness_vector &r_point) const
73 {
74  return approx_extreme_contributor(points, r_point, LEAST, base::cmp_least, lc_erase_condition, lc_end_condition);
75 }
76 
78 
86 unsigned int bf_approx::greatest_contributor(std::vector<fitness_vector> &points, const fitness_vector &r_point) const
87 {
88  return approx_extreme_contributor(points, r_point, GREATEST, base::cmp_greatest, gc_erase_condition, gc_end_condition);
89 }
90 
92 
113 unsigned int bf_approx::approx_extreme_contributor(std::vector<fitness_vector> &points, const fitness_vector &r_point, extreme_contrib_type ec_type, bool (*cmp_func)(double, double),
114  bool (*erase_condition)(unsigned int, unsigned int, std::vector<double> &, std::vector<double> &), double (*end_condition)(unsigned int, unsigned int, std::vector<double> &, std::vector<double> &)) const
115 {
116  m_no_samples = std::vector<unsigned long long>(points.size(), 0);
117  m_no_succ_samples = std::vector<unsigned long long>(points.size(), 0);
118  m_no_ops = std::vector<unsigned long long>(points.size(), 1);
119  m_point_set = std::vector<unsigned int>(points.size(), 0);
120  m_box_volume = std::vector<double>(points.size(), 0.0);
121  m_approx_volume = std::vector<double>(points.size(), 0.0);
122  m_point_delta = std::vector<double>(points.size(), 0.0);
123  m_boxes = std::vector<fitness_vector>(points.size());
124  m_box_points = std::vector<std::vector<unsigned int> >(points.size());
125 
126  // precomputed log factor for the point delta computation
127  const double log_factor = log (2. * points.size() * (1. + m_gamma) / (m_delta * m_gamma) );
128 
129  // round counter
130  unsigned int round_no = 0;
131 
132  // round delta
133  double r_delta = 0.0;
134 
135  bool stop_condition = false;
136 
137  // index of extreme contributor
138  unsigned int EC = 0;
139 
140  // put every point into the set
141  for(unsigned int i = 0 ; i < m_point_set.size() ; ++i) {
142  m_point_set[i] = i;
143  }
144 
145  // Initial computation
146  // - compute bounding boxes and their hypervolume
147  // - set round Delta as max of hypervolumes
148  // - determine points overlapping with each bounding box
149  for(std::vector<fitness_vector>::size_type idx = 0 ; idx < points.size() ; ++idx) {
150  m_boxes[idx] = compute_bounding_box(points, r_point, idx);
151  m_box_volume[idx] = base::volume_between(points[idx], m_boxes[idx]);
152  r_delta = std::max(r_delta, m_box_volume[idx]);
153 
154  for(std::vector<fitness_vector>::size_type idx2 = 0 ; idx2 < points.size() ; ++idx2) {
155  if (idx == idx2) {
156  continue;
157  }
158  int op = point_in_box(points[idx2], points[idx], m_boxes[idx]);
159  if (op == 1) {
160  m_box_points[idx].push_back(idx2);
161  } else if (ec_type == LEAST) {
162  // Execute extra checks specifically for the least contributor
163  switch(op) {
164  case 2:
165  // since contribution by idx is guaranteed to be 0.0 (as the point is dominated) we might as well return it as the least contributor right away
166  return idx;
167  case 3:
168  // points at idx and idx2 are equal, each of them will contribute 0.0 hypervolume, return idx as the least contributor
169  return idx;
170  default:
171  break;
172  }
173  }
174  }
175  }
176 
177  // decrease the initial maximum volume by a constant factor
178  r_delta *= m_initial_delta_coeff;
179 
180  // Main loop
181  do {
182  r_delta *= m_delta_multiplier;
183  ++round_no;
184 
185  for(unsigned int _i = 0 ; _i < m_point_set.size() ; ++_i) {
186  unsigned int idx = m_point_set[_i];
187  sampling_round(points, r_delta , round_no, idx, log_factor);
188  }
189 
190  // sample the extreme contributor
191  sampling_round(points, m_alpha * r_delta , round_no, EC, log_factor);
192 
193  // find the new extreme contributor
194  for(unsigned int _i = 0 ; _i < m_point_set.size() ; ++_i) {
195  unsigned int idx = m_point_set[_i];
196  if(cmp_func(m_approx_volume[idx], m_approx_volume[EC])) {
197  EC = idx;
198  }
199  }
200 
201  // erase known non-extreme contributors
202  std::vector<unsigned int>::iterator it = m_point_set.begin();
203  while(it != m_point_set.end()) {
204  unsigned int idx = *it;
205  if ((idx != EC) && erase_condition(idx, EC, m_approx_volume, m_point_delta)) {
206  it = m_point_set.erase(it);
207  }
208  else {
209  ++it;
210  }
211  }
212 
213  // check termination condition
214  stop_condition = false;
215  if (m_point_set.size() <= 1) {
216  stop_condition = true;
217  } else {
218  stop_condition = true;
219  for(unsigned int _i = 0 ; _i < m_point_set.size() ; ++_i) {
220  unsigned int idx = m_point_set[_i];
221  if (idx == EC) {
222  continue;
223  }
224  double d = end_condition(idx, EC, m_approx_volume, m_point_delta);
225  if( d <= 0 || d > 1 + m_eps ) {
226  stop_condition = false;
227  break;
228  }
229  }
230  }
231  } while (!stop_condition);
232 
233  return EC;
234 }
235 
237 void bf_approx::sampling_round(const std::vector<fitness_vector> &points, const double delta, const unsigned int round, const unsigned int idx, const double log_factor) const
238 {
239  if (m_use_exact) {
240  // if the sampling for given point was already resolved using exact method
241  if (m_no_ops[idx] == 0) {
242  return;
243  }
244 
245  // if the exact computation is trivial OR when the sampling takes too long in terms of elementary operations
246  if ( m_box_points[idx].size() <= m_trivial_subcase_size || static_cast<double>(m_no_ops[idx]) >= hypervolume::get_expected_operations(m_box_points[idx].size(), points[0].size()) ) {
247  const std::vector<unsigned int> &bp = m_box_points[idx];
248  if (bp.size() == 0) {
249  m_approx_volume[idx] = m_box_volume[idx];
250  } else {
251 
252  int f_dim = points[0].size();
253 
254  const fitness_vector &p = points[idx];
255 
256  std::vector<fitness_vector> sub_front(bp.size(), fitness_vector(f_dim, 0.0));
257 
258  for(unsigned int p_idx = 0 ; p_idx < sub_front.size() ; ++p_idx) {
259  for(unsigned int d_idx = 0 ; d_idx < sub_front[0].size() ; ++d_idx) {
260  sub_front[p_idx][d_idx] = std::max( p[d_idx], points[bp[p_idx]][d_idx] );
261  }
262  }
263 
264  const fitness_vector &refpoint = m_boxes[idx];
265  hypervolume hv_obj = hypervolume(sub_front, false);
266  hv_obj.set_copy_points(false);
267  double hv = hv_obj.compute(refpoint);
268  m_approx_volume[idx] = m_box_volume[idx] - hv;
269  }
270 
271  m_point_delta[idx] = 0.0;
272  m_no_ops[idx] = 0;
273 
274  return;
275  }
276  }
277 
278  double tmp = m_box_volume[idx] / delta;
279  double required_no_samples = 0.5 * ( (1. + m_gamma) * log( round ) + log_factor ) * tmp * tmp;
280 
281  while(m_no_samples[idx] < required_no_samples) {
282  ++m_no_samples[idx];
283  if (sample_successful(points, idx)) {
284  ++m_no_succ_samples[idx];
285  }
286  }
287 
288  m_approx_volume[idx] = static_cast<double>(m_no_succ_samples[idx]) / static_cast<double>(m_no_samples[idx]) * m_box_volume[idx];
289  m_point_delta[idx] = compute_point_delta(round, idx, log_factor) * m_box_volume[idx];
290 }
291 
293 bool bf_approx::sample_successful(const std::vector<fitness_vector> &points, const unsigned int idx) const
294 {
295  const fitness_vector &lb = points[idx];
296  const fitness_vector &ub = m_boxes[idx];
297  fitness_vector rnd_p(lb.size(), 0.0);
298  for(unsigned int i = 0 ; i < lb.size() ; ++ i) {
299  rnd_p[i] = lb[i] + m_drng()*(ub[i]-lb[i]);
300  }
301 
302  for(unsigned int i = 0 ; i < m_box_points[idx].size() ; ++i) {
303 
304  // box_p is a point overlapping the bounding box volume
305  const fitness_vector &box_p = points[m_box_points[idx][i]];
306 
307  // assume that box_p DOMINATES the random point and try to prove it otherwise below
308  bool dominates = true;
309 
310  // increase the number of operations by the dimension size
311  m_no_ops[idx] += box_p.size() + 1;
312  for(unsigned int d_idx = 0 ; d_idx < box_p.size() ; ++d_idx) {
313  if(rnd_p[d_idx] < box_p[d_idx]) { // box_p does not dominate rnd_p
314  dominates=false;
315  break;
316  }
317  }
318  // if the box_p dominated the rnd_p return the sample as false
319  if (dominates) {
320  return false;
321  }
322  }
323  return true;
324 }
325 
327 
331 double bf_approx::compute_point_delta(const unsigned int round_no, const unsigned int idx, const double log_factor) const
332 {
333  return sqrt(
334  0.5 * ((1. + m_gamma) * log(static_cast<double>(round_no)) + log_factor)
335  / (static_cast<double>(m_no_samples[idx]))
336  );
337 }
338 
340 
346 int bf_approx::point_in_box(const fitness_vector &p, const fitness_vector &a, const fitness_vector &b) const
347 {
348  int cmp_a_p = base::dom_cmp(a, p);
349 
350  // point a is equal to point p (duplicate)
351  if (cmp_a_p == 3) {
352  return 3;
353  // point a is dominated by p (a is the least contributor)
354  } else if (cmp_a_p == 1) {
355  return 2;
356  } else if(base::dom_cmp(b, p) == 1) {
357  return 1;
358  } else {
359  return 0;
360  }
361 }
362 
364 /* Find the MINIMAL (in terms of volume) bounding box that contains all the exclusive hypervolume contributed by point at index 'p_idx'
365  * Thus obtained point 'z' and the original point 'points[p_idx]' form a box in which we perform the monte carlo sampling
366  *
367  * @param[in] points pareto front points
368  * @param[in] r_point reference point
369  * @param[in] p_idx index of point for which we compute the bounding box
370  *
371  * @return fitness_vector describing the opposite corner of the bounding box
372  */
373 fitness_vector bf_approx::compute_bounding_box(const std::vector<fitness_vector> &points, const fitness_vector &r_point, const unsigned int p_idx) const
374 {
375  // z is the opposite corner of the bounding box (reference point as a 'safe' first candidate - this is the MAXIMAL bounding box as of yet)
376  fitness_vector z(r_point);
377 
378  // Below we perform a reduction to the minimal bounding box.
379  // We check whether given point at 'idx' is DOMINATED (strong domination) by 'p_idx' in exactly one objective, and DOMINATING (weak domination) in the remaining ones.
380  const fitness_vector& p = points[p_idx];
381  int worse_dim_idx;
382  unsigned int f_dim = r_point.size();
383  for(std::vector<fitness_vector>::size_type idx = 0 ; idx < points.size(); ++idx) {
384 
385  worse_dim_idx = -1; // initiate the possible opposite point dimension by -1 (no candidate)
386 
387  for(fitness_vector::size_type f_idx = 0; f_idx < f_dim; ++f_idx) {
388  if (points[idx][f_idx] >= p[f_idx]) { // if any point is worse by given dimension, it's the potential dimension in which we reduce the box
389  if (worse_dim_idx != -1) { // if given point is already worse in any previous dimension, skip to next point as it's a bad candidate
390  worse_dim_idx = -1; // set the result to "no candidate" and break
391  break;
392  }
393  worse_dim_idx = f_idx;
394  }
395  }
396  if (worse_dim_idx != -1){ // if given point was worse only in one dimension it's the potential candidate for the bouding box reductor
397  z[worse_dim_idx] = std::min(z[worse_dim_idx], points[idx][worse_dim_idx]); // reduce the bounding box
398  }
399  }
400  return z;
401 }
402 
404 
412 void bf_approx::verify_before_compute(const std::vector<fitness_vector> &points, const fitness_vector &r_point) const
413 {
414  base::assert_minimisation(points, r_point);
415 }
416 
418 
426 double bf_approx::compute(std::vector<fitness_vector> &points, const fitness_vector &r_point) const
427 {
428  (void)points;
429  (void)r_point;
430  pagmo_throw(value_error, "This algorithm can just approximate extreme contributions but not the hypervolume itself.");
431  return 0.0;
432 }
433 
436 {
437  return base_ptr(new bf_approx(*this));
438 }
439 
441 std::string bf_approx::get_name() const
442 {
443  return "Bringmann-Friedrich approximation method";
444 }
445 
446 } } }
447 
448 BOOST_CLASS_EXPORT_IMPLEMENT(pagmo::util::hv_algorithm::bf_approx)
Root PaGMO namespace.
Bringmann-Friedrich approximation method.
Definition: bf_approx.h:50
static bool cmp_least(const double, const double)
Comparison method for the least contributor.
static double volume_between(const fitness_vector &, const fitness_vector &, unsigned int=0)
Compute volume between two points.
void assert_minimisation(const std::vector< fitness_vector > &, const fitness_vector &) const
Assert that reference point dominates every other point from the set.
static int dom_cmp(double *, double *, unsigned int)
Dominance comparison method.
static bool cmp_greatest(const double, const double)
Comparison method for the least contributor.
void verify_before_compute(const std::vector< fitness_vector > &, const fitness_vector &) const
Verify before compute method.
Definition: bf_approx.cpp:412
bf_approx(const bool use_exact=true, const unsigned int trivial_subcase_size=1, const double eps=1e-2, const double delta=1e-6, const double delta_multiplier=0.775, const double m_alpha=0.2, const double initial_delta_coeff=0.1, const double gamma=0.25)
Constructor.
Definition: bf_approx.cpp:43
double compute(std::vector< fitness_vector > &, const fitness_vector &) const
Compute hypervolume.
Definition: bf_approx.cpp:426
static double get_expected_operations(const unsigned int n, const unsigned int d)
Get expected numer of operations.
std::vector< double > fitness_vector
Fitness vector type.
Definition: types.h:42
unsigned int least_contributor(std::vector< fitness_vector > &, const fitness_vector &) const
Least contributor method.
Definition: bf_approx.cpp:72
boost::shared_ptr< base > base_ptr
Base hypervolume algorithm class.
unsigned int greatest_contributor(std::vector< fitness_vector > &, const fitness_vector &) const
Greatest contributor method.
Definition: bf_approx.cpp:86
std::string get_name() const
Algorithm name.
Definition: bf_approx.cpp:441
base_ptr clone() const
Clone method.
Definition: bf_approx.cpp:435