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