1 /* Copyright 2017-2021 PaGMO development team
2
3 This file is part of the PaGMO library.
4
5 The PaGMO library is free software; you can redistribute it and/or modify
6 it under the terms of either:
7
8 * the GNU Lesser General Public License as published by the Free
9 Software Foundation; either version 3 of the License, or (at your
10 option) any later version.
11
12 or
13
14 * the GNU General Public License as published by the Free Software
15 Foundation; either version 3 of the License, or (at your option) any
16 later version.
17
18 or both in parallel, as here.
19
20 The PaGMO library is distributed in the hope that it will be useful, but
21 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
22 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
23 for more details.
24
25 You should have received copies of the GNU General Public License and the
26 GNU Lesser General Public License along with the PaGMO library. If not,
27 see https://www.gnu.org/licenses/. */
28
29 #include <cmath>
30 #include <memory>
31 #include <stdexcept>
32 #include <vector>
33
34 #include <pagmo/exceptions.hpp>
35 #include <pagmo/population.hpp>
36 #include <pagmo/types.hpp>
37 #include <pagmo/utils/hv_algos/hv_algorithm.hpp>
38 #include <pagmo/utils/hv_algos/hv_hv2d.hpp>
39 #include <pagmo/utils/hv_algos/hv_hv3d.hpp>
40 #include <pagmo/utils/hv_algos/hv_hvwfg.hpp>
41 #include <pagmo/utils/hypervolume.hpp>
42
43 namespace pagmo
44 {
45
46 /// Default constructor
47 /**
48 * Initiates hypervolume with empty set of points.
49 * Used for serialization purposes.
50 */
hypervolume()51 hypervolume::hypervolume() : m_points(), m_copy_points(true), m_verify(false) {}
52
53 // Constructor from population
hypervolume(const pagmo::population & pop,bool verify)54 hypervolume::hypervolume(const pagmo::population &pop, bool verify) : m_copy_points(true), m_verify(verify)
55 {
56 if (pop.get_problem().get_nc() > 0u) {
57 pagmo_throw(std::invalid_argument,
58 "The problem of the population is not unconstrained."
59 "Only unconstrained populations can be used to construct hypervolume objects.");
60 }
61 if (pop.get_problem().get_nobj() < 2u) {
62 pagmo_throw(std::invalid_argument,
63 "The problem of the population is not multiobjective."
64 "Only multi-objective populations can be used to construct hypervolume objects.");
65 }
66 m_points = pop.get_f();
67 if (m_verify) {
68 verify_after_construct();
69 }
70 }
71
72 // Constructor from points
hypervolume(const std::vector<vector_double> & points,bool verify)73 hypervolume::hypervolume(const std::vector<vector_double> &points, bool verify)
74 : m_points(points), m_copy_points(true), m_verify(verify)
75 {
76 if (m_verify) {
77 verify_after_construct();
78 }
79 }
80
81 /// Default copy constructor.
82 /**
83 * The copy constructor will deep copy the input problem \p other.
84 *
85 * @throws unspecified any exception thrown by:
86 * - memory allocation errors in standard containers,
87 */
88 hypervolume::hypervolume(const hypervolume &) = default;
89
90 /// Default copy assignment operator
91 /**
92 * @return a reference to \p this.
93 */
94 hypervolume &hypervolume::operator=(const hypervolume &) = default;
95
96 /// Setter for 'copy_points' flag
97 /**
98 *
99 * It is used in cases where we are certain that we can alter the original set of points
100 * from the hypervolume object.
101 * This is useful when we don't want to make a copy of the points first, as most algorithms
102 * alter the original set, but may result in unexpected behaviour when used incorrectly
103 * (e.g. requesting the computation twice out of the same object)
104 *
105 * \verbatim embed:rst:leading-asterisk
106 * .. warning::
107 *
108 * When this flag is set to true the object can reliably be used only once to compute
109 * the hypervolume. Successive usages are undefined behaviour.
110 *
111 * \endverbatim
112 *
113 * @param copy_points boolean value stating whether the hypervolume computation may use original set
114 */
set_copy_points(bool copy_points)115 void hypervolume::set_copy_points(bool copy_points)
116 {
117 m_copy_points = copy_points;
118 }
119
120 /// Getter for 'copy_points' flag
121 /**
122 * Gets the copy_points flag
123 *
124 * @return the copy_points flag value
125 */
get_copy_points() const126 bool hypervolume::get_copy_points() const
127 {
128 return m_copy_points;
129 }
130
131 /// Setter for the 'verify' flag
132 /**
133 * Turns off the verification phase.
134 * By default, the hypervolume object verifies whether certain characteristics of the point set hold,
135 * such as valid dimension sizes or a reference point that suits the minimisation.
136 * In order to optimize the computation when the rules above are certain, we can turn off that phase.
137 *
138 * This may result in unexpected behaviour when used incorrectly (e.g. requesting the computation
139 * of empty set of points)
140 *
141 * @param verify boolean value stating whether the hypervolume computation is to be executed without verification
142 */
set_verify(bool verify)143 void hypervolume::set_verify(bool verify)
144 {
145 m_verify = verify;
146 }
147
148 /// Getter for the 'verify' flag
149 /**
150 * Gets the verify flag
151 *
152 * @return the verify flag value
153 */
get_verify() const154 bool hypervolume::get_verify() const
155 {
156 return m_verify;
157 }
158
159 // Calculate a default reference point
refpoint(double offset) const160 vector_double hypervolume::refpoint(double offset) const
161 {
162 // Corner case
163 if (m_points.size() == 0u) {
164 return {};
165 }
166
167 auto fdim = m_points[0].size();
168 vector_double ref_point(m_points[0].begin(), m_points[0].end());
169
170 for (decltype(fdim) f_idx = 0u; f_idx < fdim; ++f_idx) {
171 for (std::vector<vector_double>::size_type idx = 1u; idx < m_points.size(); ++idx) {
172 ref_point[f_idx] = std::max(ref_point[f_idx], m_points[idx][f_idx]);
173 }
174 }
175
176 for (auto &c : ref_point) {
177 c += offset;
178 }
179
180 return ref_point;
181 }
182
183 /// Get points
184 /**
185 * Will return a vector containing the points as they were set up during construction of the hypervolume object.
186 *
187 * @return const reference to the vector containing the fitness_vectors representing the points in the hyperspace.
188 */
get_points() const189 const std::vector<vector_double> &hypervolume::get_points() const
190 {
191 return m_points;
192 }
193
194 /// Compute hypervolume
195 /**
196 * Computes hypervolume for given reference point.
197 * This method chooses the hv_algorithm dynamically.
198 *
199 * @param r_point vector describing the reference point
200 *
201 * @return value representing the hypervolume
202 */
compute(const vector_double & r_point) const203 double hypervolume::compute(const vector_double &r_point) const
204 {
205 return compute(r_point, *get_best_compute(r_point));
206 }
207
208 /// Compute hypervolume
209 /**
210 * Computes hypervolume for given reference point, using given pagmo::hv_algorithm object.
211 *
212 * @param r_point fitness vector describing the reference point
213 * @param hv_algo instance of the algorithm object used for the computation
214 *
215 * @return the hypervolume
216 */
compute(const vector_double & r_point,hv_algorithm & hv_algo) const217 double hypervolume::compute(const vector_double &r_point, hv_algorithm &hv_algo) const
218 {
219 if (m_verify) {
220 verify_before_compute(r_point, hv_algo);
221 }
222 // copy the initial set of points, as the algorithm may alter its contents
223 if (m_copy_points) {
224 std::vector<vector_double> points_cpy(m_points.begin(), m_points.end());
225 return hv_algo.compute(points_cpy, r_point);
226 } else {
227 return hv_algo.compute(m_points, r_point);
228 }
229 }
230
231 /// Compute exclusive contribution
232 /**
233 * Computes exclusive hypervolume for given indivdual.
234 *
235 * @param p_idx index of the individual for whom we compute the exclusive contribution to the hypervolume
236 * @param r_point fitness vector describing the reference point
237 * @param hv_algo instance of the algorithm object used for the computation
238 *
239 * @return the exclusive contribution to the hypervolume
240 */
exclusive(unsigned p_idx,const vector_double & r_point,hv_algorithm & hv_algo) const241 double hypervolume::exclusive(unsigned p_idx, const vector_double &r_point, hv_algorithm &hv_algo) const
242 {
243 if (m_verify) {
244 verify_before_compute(r_point, hv_algo);
245 }
246
247 if (p_idx >= m_points.size()) {
248 pagmo_throw(std::invalid_argument, "Index of the individual is out of bounds.");
249 }
250
251 // copy the initial set of points, as the algorithm may alter its contents
252 if (m_copy_points) {
253 std::vector<vector_double> points_cpy(m_points.begin(), m_points.end());
254 return hv_algo.exclusive(p_idx, points_cpy, r_point);
255 } else {
256 return hv_algo.exclusive(p_idx, m_points, r_point);
257 }
258 }
259
260 /// Compute exclusive contribution
261 /**
262 * Computes exclusive hypervolume for given indivdual.
263 * This methods chooses the hv_algorithm dynamically.
264 *
265 * @param p_idx index of the individual for whom we compute the exclusive contribution to the hypervolume
266 * @param r_point fitness vector describing the reference point
267 *
268 * @return the exclusive contribution to the hypervolume
269 */
exclusive(unsigned p_idx,const vector_double & r_point) const270 double hypervolume::exclusive(unsigned p_idx, const vector_double &r_point) const
271 {
272 return exclusive(p_idx, r_point, *get_best_exclusive(p_idx, r_point));
273 }
274
275 /// Contributions method
276 /**
277 * This method returns the exclusive contribution to the hypervolume by every point.
278 * The concrete algorithm can implement this feature optimally (as opposed to calling for the exclusive contributor
279 * in a loop).
280 *
281 * @param r_point fitness vector describing the reference point
282 * @param hv_algo instance of the algorithm object used for the computation
283 *
284 * @return vector of exclusive contributions by every point
285 */
contributions(const vector_double & r_point,hv_algorithm & hv_algo) const286 std::vector<double> hypervolume::contributions(const vector_double &r_point, hv_algorithm &hv_algo) const
287 {
288 if (m_verify) {
289 verify_before_compute(r_point, hv_algo);
290 }
291
292 // Trivial case
293 if (m_points.size() == 1u) {
294 std::vector<double> c;
295 c.push_back(hv_algorithm::volume_between(m_points[0], r_point));
296 return c;
297 }
298
299 // copy the initial set of points, as the algorithm may alter its contents
300 if (m_copy_points) {
301 std::vector<vector_double> points_cpy(m_points.begin(), m_points.end());
302 return hv_algo.contributions(points_cpy, r_point);
303 } else {
304 return hv_algo.contributions(m_points, r_point);
305 }
306 }
307
308 /// Contributions method
309 /**
310 * This method returns the exclusive contribution to the hypervolume by every point.
311 * The concrete algorithm can implement this feature optimally (as opposed to calling for the exclusive contributor
312 * in a loop).
313 * The hv_algorithm itself is chosen dynamically, so the best performing method is employed for given task.
314 *
315 * @param r_point fitness vector describing the reference point
316 * @return vector of exclusive contributions by every point
317 */
contributions(const vector_double & r_point) const318 std::vector<double> hypervolume::contributions(const vector_double &r_point) const
319 {
320 return contributions(r_point, *get_best_contributions(r_point));
321 }
322
323 /// Find the least contributing individual
324 /**
325 * Establishes the individual contributing the least to the total hypervolume.
326 *
327 * @param r_point fitness vector describing the reference point
328 * @param hv_algo instance of the algorithm object used for the computation
329 *
330 * @return index of the least contributing point
331 */
least_contributor(const vector_double & r_point,hv_algorithm & hv_algo) const332 unsigned long long hypervolume::least_contributor(const vector_double &r_point, hv_algorithm &hv_algo) const
333 {
334 if (m_verify) {
335 verify_before_compute(r_point, hv_algo);
336 }
337
338 // Trivial case
339 if (m_points.size() == 1) {
340 return 0u;
341 }
342
343 // copy the initial set of points, as the algorithm may alter its contents
344 if (m_copy_points) {
345 std::vector<vector_double> points_cpy(m_points.begin(), m_points.end());
346 return hv_algo.least_contributor(points_cpy, r_point);
347 } else {
348 return hv_algo.least_contributor(m_points, r_point);
349 }
350 }
351
352 /// Find the least contributing individual
353 /**
354 * Establishes the individual contributing the least to the total hypervolume.
355 * This method chooses the best performing hv_algorithm dynamically
356 *
357 * @param r_point fitness vector describing the reference point
358 *
359 * @return index of the least contributing point
360 */
least_contributor(const vector_double & r_point) const361 unsigned long long hypervolume::least_contributor(const vector_double &r_point) const
362 {
363 return least_contributor(r_point, *get_best_contributions(r_point));
364 }
365
366 /// Find the most contributing individual
367 /**
368 * Establish the individual contributing the most to the total hypervolume.
369 *
370 * @param r_point fitness vector describing the reference point
371 * @param hv_algo instance of the algorithm object used for the computation
372 *
373 * @return index of the most contributing point
374 */
greatest_contributor(const vector_double & r_point,hv_algorithm & hv_algo) const375 unsigned long long hypervolume::greatest_contributor(const vector_double &r_point, hv_algorithm &hv_algo) const
376 {
377 if (m_verify) {
378 verify_before_compute(r_point, hv_algo);
379 }
380
381 // copy the initial set of points, as the algorithm may alter its contents
382 if (m_copy_points) {
383 std::vector<vector_double> points_cpy(m_points.begin(), m_points.end());
384 return hv_algo.greatest_contributor(points_cpy, r_point);
385 } else {
386 return hv_algo.greatest_contributor(m_points, r_point);
387 }
388 }
389
390 /// Find the most contributing individual
391 /**
392 * Establish the individual contributing the most to the total hypervolume.
393 * This method chooses the best performing hv_algorithm dynamically
394 *
395 * @param r_point fitness vector describing the reference point
396 *
397 * @return index of the most contributing point
398 */
greatest_contributor(const vector_double & r_point) const399 unsigned long long hypervolume::greatest_contributor(const vector_double &r_point) const
400 {
401 return greatest_contributor(r_point, *get_best_contributions(r_point));
402 }
403
404 // Verify after construct method
405 /**
406 * Verifies whether basic requirements are met for the initial set of points.
407 *
408 * @throws invalid_argument if point size is empty or when the dimensions among the points differ
409 */
verify_after_construct() const410 void hypervolume::verify_after_construct() const
411 {
412 if (m_points.size() == 0) {
413 pagmo_throw(std::invalid_argument, "Point set cannot be empty.");
414 }
415 auto f_dim = m_points[0].size();
416 if (f_dim <= 1) {
417 pagmo_throw(std::invalid_argument, "Points of dimension > 1 required.");
418 }
419 for (const auto &v : m_points) {
420 if (v.size() != f_dim) {
421 pagmo_throw(std::invalid_argument, "All point set dimensions must be equal.");
422 }
423 }
424 for (const auto &point : m_points) {
425 for (auto value : point) {
426 if (std::isnan(value)) {
427 pagmo_throw(std::invalid_argument, "A nan value has been encountered in the hypervolume points. Cannot "
428 "construct the hypervolume object");
429 }
430 }
431 }
432 }
433
434 // Verify before compute method
435 /**
436 * Verifies whether reference point and the hypervolume method meet certain criteria.
437 *
438 * @param r_point vector describing the reference point
439 * @param hv_algo instance of the algorithm object used for the computation
440 *
441 * @throws value_error if reference point's and point set dimension do not agree
442 */
verify_before_compute(const vector_double & r_point,hv_algorithm & hv_algo) const443 void hypervolume::verify_before_compute(const vector_double &r_point, hv_algorithm &hv_algo) const
444 {
445 if (m_points[0].size() != r_point.size()) {
446 pagmo_throw(std::invalid_argument, "Point set dimensions and reference point dimension must be equal.");
447 }
448 hv_algo.verify_before_compute(m_points, r_point);
449 }
450
451 namespace detail
452 {
453
454 // Expected number of operations
455 /**
456 * Returns the expected average amount of elementary operations necessary to compute the hypervolume
457 * for a given front size \f$n\f$ and dimension size \f$d\f$
458 * This method is used by the approximated algorithms that fall back to exact computation.
459 *
460 * @param n size of the front
461 * @param d dimension size
462 *
463 * @return expected number of operations
464 */
expected_hv_operations(vector_double::size_type n,vector_double::size_type d)465 double expected_hv_operations(vector_double::size_type n, vector_double::size_type d)
466 {
467 if (d <= 3u) {
468 return static_cast<double>(d) * static_cast<double>(n) * std::log(n); // hv3d
469 } else if (d == 4u) {
470 return 4.0 * static_cast<double>(n) * static_cast<double>(n); // hv4d
471 } else {
472 return 0.0005 * static_cast<double>(d)
473 * std::pow(static_cast<double>(n), static_cast<double>(d) * 0.5); // exponential complexity
474 }
475 }
476
477 } // namespace detail
478
479 /// Chooses the best algorithm to compute the hypervolume
480 /**
481 * Returns the best method for given hypervolume computation problem.
482 * As of yet, only the dimension size is taken into account.
483 *
484 * @param r_point reference point for the vector of points
485 *
486 * @return an std::shared_ptr to the selected algorithm
487 */
get_best_compute(const vector_double & r_point) const488 std::shared_ptr<hv_algorithm> hypervolume::get_best_compute(const vector_double &r_point) const
489 {
490 auto fdim = r_point.size();
491
492 if (fdim == 2u) {
493 return hv2d().clone();
494 } else if (fdim == 3u) {
495 return hv3d().clone();
496 } else {
497 return hvwfg().clone();
498 }
499 }
500
501 /// Chooses the best algorithm to compute the hypervolume
502 /**
503 * Returns the best method for given hypervolume computation problem.
504 * As of yet, only the dimension size is taken into account.
505 *
506 * @param p_idx index of the point for which the exclusive contribution is to be computed
507 * @param r_point reference point for the vector of points
508 *
509 * @return an std::shared_ptr to the selected algorithm
510 */
get_best_exclusive(const unsigned p_idx,const vector_double & r_point) const511 std::shared_ptr<hv_algorithm> hypervolume::get_best_exclusive(const unsigned p_idx, const vector_double &r_point) const
512 {
513 (void)p_idx;
514 // Exclusive contribution and compute method share the same "best" set of algorithms.
515 return hypervolume::get_best_compute(r_point);
516 }
517
518 /// Chooses the best algorithm to compute the hypervolume
519 /**
520 * Returns the best method for given hypervolume computation problem.
521 * As of yet, only the dimension size is taken into account.
522 *
523 * @param r_point reference point for the vector of points
524 *
525 * @return an std::shared_ptr to the selected algorithm
526 */
get_best_contributions(const vector_double & r_point) const527 std::shared_ptr<hv_algorithm> hypervolume::get_best_contributions(const vector_double &r_point) const
528 {
529 auto fdim = r_point.size();
530
531 if (fdim == 2u) {
532 return hv2d().clone();
533 } else if (fdim == 3u) {
534 return hv3d().clone();
535 } else {
536 return hvwfg().clone();
537 }
538 }
539
540 } // namespace pagmo
541