From 7f4ac583a56433ac32f9e7f5a0a7973d47f33e2f Mon Sep 17 00:00:00 2001 From: Jonas Date: Thu, 19 Feb 2026 01:12:15 +0100 Subject: [PATCH 01/10] Optimize std::isnan functions using spaceship operator --- include/pagmo/detail/custom_comparisons.hpp | 84 ++++++++++++++++++++- 1 file changed, 82 insertions(+), 2 deletions(-) diff --git a/include/pagmo/detail/custom_comparisons.hpp b/include/pagmo/detail/custom_comparisons.hpp index 58aff766c..156395432 100644 --- a/include/pagmo/detail/custom_comparisons.hpp +++ b/include/pagmo/detail/custom_comparisons.hpp @@ -54,7 +54,38 @@ namespace detail template inline bool less_than_f(T a, T b) { - static_assert(std::is_floating_point::value, "less_than_f can be used only with floating-point types."); + static_assert(std::is_floating_point_v, "less_than_f can be used only with floating-point types."); + +#if defined(__cpp_impl_three_way_comparison) + + const auto cmp = a <=> b; + // True or false returned immediately, if there's no NaN (std::isnan is very slow) + if (cmp == std::partial_ordering::less) { + return true; + } + if (cmp == std::partial_ordering::greater || cmp == std::partial_ordering::equivalent) { + return false; + } + + // Result was std::partial_ordering::unordered, one of the operands must be NaN + const bool a_nan = std::isnan(a); + const bool b_nan = std::isnan(b); + + // If only B is NaN, After must be returned + if (!a_nan && b_nan) { + return After; + } + // If only A is NaN, !After must be returned + if (a_nan && !b_nan) { + return !After; + } + + // If both are NaN, false must be returned + return false; + +#else + + // Pre-C++20 implementation if (!std::isnan(a)) { if (!std::isnan(b)) return a < b; // a < b @@ -66,6 +97,8 @@ inline bool less_than_f(T a, T b) else return false; // nan < nan } + +#endif } // Greater than compares floating point types placing nans after inf or before -inf @@ -74,7 +107,33 @@ inline bool less_than_f(T a, T b) template inline bool greater_than_f(T a, T b) { - static_assert(std::is_floating_point::value, "greater_than_f can be used only with floating-point types."); + static_assert(std::is_floating_point_v, "greater_than_f can be used only with floating-point types."); + +#if defined(__cpp_impl_three_way_comparison) + + auto cmp = a <=> b; + if (cmp == std::partial_ordering::greater) { + return true; + } + if (cmp == std::partial_ordering::less || cmp == std::partial_ordering::equivalent) { + return false; + } + + const bool a_nan = std::isnan(a); + const bool b_nan = std::isnan(b); + + if (!a_nan && b_nan) { + return !After; + } + if (a_nan && !b_nan) { + return After; + } + + return false; + +#else + + // Pre-C++20 implementation if (!std::isnan(a)) { if (!std::isnan(b)) return a > b; // a > b @@ -86,17 +145,38 @@ inline bool greater_than_f(T a, T b) else return false; // nan > nan } + +#endif } // equal_to than compares floating point types considering nan==nan template inline bool equal_to_f(T a, T b) { +#if defined(__cpp_impl_three_way_comparison) + + auto cmp = a <=> b; + if (cmp == std::partial_ordering::equivalent) { + return true; + } + + if (cmp != std::partial_ordering::unordered) { + return false; + } + + return std::isnan(a) && std::isnan(b); + +#else + + // Pre-C++20 implementation static_assert(std::is_floating_point::value, "equal_to_f can be used only with floating-point types."); if (!std::isnan(a) && !std::isnan(b)) { return a == b; } return std::isnan(a) && std::isnan(b); + +#endif + } // equal_to_vf than compares vectors of floating point types considering nan==nan From 2adb6eca16b600f25b3b520ba69c99347112b973 Mon Sep 17 00:00:00 2001 From: Jonas Date: Thu, 19 Feb 2026 19:30:43 +0100 Subject: [PATCH 02/10] Add fast_non_dominated_sorting_buffered Version of a hot-path function with pre-allocated vectors, reduced CPU time spent on vector allocation approximately from 7% to 1%. --- include/pagmo/utils/multi_objective.hpp | 5 +- src/utils/multi_objective.cpp | 89 +++++++++++++++++++++++-- 2 files changed, 89 insertions(+), 5 deletions(-) diff --git a/include/pagmo/utils/multi_objective.hpp b/include/pagmo/utils/multi_objective.hpp index 4c298f727..7869110ce 100644 --- a/include/pagmo/utils/multi_objective.hpp +++ b/include/pagmo/utils/multi_objective.hpp @@ -59,7 +59,7 @@ PAGMO_DLL_PUBLIC void reksum(std::vector> &, const std::vect } // namespace detail // Pareto-dominance -PAGMO_DLL_PUBLIC bool pareto_dominance(const vector_double &, const vector_double &); +PAGMO_DLL_PUBLIC inline bool pareto_dominance(const vector_double &, const vector_double &); // Non dominated front 2D (Kung's algorithm) PAGMO_DLL_PUBLIC std::vector non_dominated_front_2d(const std::vector &); @@ -71,6 +71,9 @@ using fnds_return_type = std::tuple>, std::v // Fast non dominated sorting PAGMO_DLL_PUBLIC fnds_return_type fast_non_dominated_sorting(const std::vector &); +// Fast non dominated sorting with pre-allocated buffers +PAGMO_DLL_PUBLIC void fast_non_dominated_sorting_buffered(const std::vector &, fnds_return_type&); + // Crowding distance PAGMO_DLL_PUBLIC vector_double crowding_distance(const std::vector &); diff --git a/src/utils/multi_objective.cpp b/src/utils/multi_objective.cpp index 585df9b42..644a9a114 100644 --- a/src/utils/multi_objective.cpp +++ b/src/utils/multi_objective.cpp @@ -94,7 +94,7 @@ void reksum(std::vector> &retval, const std::vector &po std::move(non_dom_rank)); } +/// Fast non dominated sorting with pre-allocated buffers +/** + * Improved version of fast_non_dominated_sorting(), doesn't allocate any vectors inside the function body, instead lets + * user pass an object of type fnds_return_type, as one of the parameters. This improves performance when called inside + * hot path loops. + * + * @param points An std::vector containing the objectives of different individuals. Example + * {{1,2,3},{-2,3,7},{-1,-2,-3},{0,0,0}} + * @param sorting_results Return values of this function, same as fast_non_dominated_sorting() + * + * @throws std::invalid_argument If the size of \p points is not at least 2 + */ +void fast_non_dominated_sorting_buffered(const std::vector &points, fnds_return_type& sorting_results) +{ + auto N = points.size(); + // We make sure to have two points at least (one could also be allowed) + if (N < 2u) { + pagmo_throw(std::invalid_argument, "At least two points are needed for fast_non_dominated_sorting: " + + std::to_string(N) + " detected."); + } + + // These are the return values, we need to invalidate previous content of these vectors + std::vector>& non_dom_fronts = std::get<0>(sorting_results); + std::vector>& dom_list = std::get<1>(sorting_results); + std::vector& dom_count = std::get<2>(sorting_results); + std::vector& non_dom_rank = std::get<3>(sorting_results); + non_dom_fronts.clear(); + non_dom_fronts.resize(1u); + + dom_count.clear(); + dom_list.resize(N); + + dom_count.assign(N, 0u); + non_dom_rank.assign(N, 0u); + + // Start the fast non dominated sort algorithm + for (decltype(N) i = 0u; i < N; ++i) { + dom_list[i].clear(); + dom_count[i] = 0u; + for (decltype(N) j = 0u; j < i; ++j) { + if (pareto_dominance(points[i], points[j])) { + dom_list[i].push_back(j); + ++dom_count[j]; + } else if (pareto_dominance(points[j], points[i])) { + dom_list[j].push_back(i); + ++dom_count[i]; + } + } + } + for (decltype(N) i = 0u; i < N; ++i) { + if (dom_count[i] == 0u) { + non_dom_rank[i] = 0u; + non_dom_fronts[0].push_back(i); + } + } + // we copy dom_count as we want to output its value at this point + auto dom_count_copy(dom_count); + auto current_front = non_dom_fronts[0]; + std::vector>::size_type front_counter(0u); + while (!current_front.empty()) { + std::vector next_front; + for (const decltype(current_front.size()) p : current_front) { + for (const decltype(dom_list[p].size()) q : dom_list[p]) { + --dom_count_copy[q]; + if (dom_count_copy[q] == 0u) { + non_dom_rank[q] = front_counter + 1u; + next_front.push_back(q); + } + } + } + ++front_counter; + current_front = next_front; + if (!current_front.empty()) { + non_dom_fronts.push_back(current_front); + } + } +} + /// Crowding distance /** * An implementation of the crowding distance. Complexity is \f$ O(MNlog(N))\f$ where \f$M\f$ is the number of @@ -343,6 +421,9 @@ vector_double crowding_distance(const std::vector &non_dom_front) */ std::vector select_best_N_mo(const std::vector &input_f, pop_size_t N) { + // Pre-allocated buffer for results of calling fast_non_dominated_sorting_buffered() + static fnds_return_type sorting_results{}; + if (N == 0u) { // corner case return {}; } @@ -360,9 +441,9 @@ std::vector select_best_N_mo(const std::vector &input std::vector retval; std::vector::size_type front_id(0u); // Run fast-non-dominated sorting - auto tuple = fast_non_dominated_sorting(input_f); + fast_non_dominated_sorting_buffered(input_f, sorting_results); // Insert all non dominated fronts if not more than N - for (const auto &front : std::get<0>(tuple)) { + for (const auto &front : std::get<0>(sorting_results)) { if (retval.size() + front.size() <= N) { for (auto i : front) { retval.push_back(i); @@ -375,7 +456,7 @@ std::vector select_best_N_mo(const std::vector &input break; } } - auto front = std::get<0>(tuple)[front_id]; + const auto front = std::get<0>(sorting_results)[front_id]; std::vector non_dom_fits(front.size()); // Run crowding distance for the front for (decltype(front.size()) i = 0u; i < front.size(); ++i) { From 6cc0a2a5c8eb42ddf4b9c4a0812d908fc6c3edf9 Mon Sep 17 00:00:00 2001 From: Jonas Date: Thu, 19 Feb 2026 20:40:31 +0100 Subject: [PATCH 03/10] Add select_best_N_mo_buffered Minor 0.1% / 0.2% performance increase Switch to passing struct via function params instead of static Static is not thread-safe Add another fnds_buffer inside of nsga2.cpp --- .idea/vcs.xml | 6 +++ include/pagmo/utils/multi_objective.hpp | 3 ++ src/algorithms/nsga2.cpp | 12 +++-- src/utils/multi_objective.cpp | 66 +++++++++++++++++++++++-- 4 files changed, 78 insertions(+), 9 deletions(-) create mode 100644 .idea/vcs.xml diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..94a25f7f4 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/include/pagmo/utils/multi_objective.hpp b/include/pagmo/utils/multi_objective.hpp index 7869110ce..515dcac1b 100644 --- a/include/pagmo/utils/multi_objective.hpp +++ b/include/pagmo/utils/multi_objective.hpp @@ -83,6 +83,9 @@ PAGMO_DLL_PUBLIC std::vector sort_population_mo(const std::vector select_best_N_mo(const std::vector &, pop_size_t); +// Selects the best N individuals in multi-objective optimization with pre-allocated buffers +PAGMO_DLL_PUBLIC std::vector select_best_N_mo_buffered(const std::vector &, pop_size_t, fnds_return_type&); + // Ideal point PAGMO_DLL_PUBLIC vector_double ideal(const std::vector &); diff --git a/src/algorithms/nsga2.cpp b/src/algorithms/nsga2.cpp index 4ddd6b198..2ec0d0f2a 100644 --- a/src/algorithms/nsga2.cpp +++ b/src/algorithms/nsga2.cpp @@ -136,6 +136,10 @@ population nsga2::evolve(population pop) const vector_double::size_type parent1_idx, parent2_idx; std::pair children; + // We're using select_best_N_mo_buffered(), which prevents re-allocation of this structure + fnds_return_type best_N_buffer{}; + fnds_return_type fnds_buffer{}; + std::iota(shuffle1.begin(), shuffle1.end(), vector_double::size_type(0)); std::iota(shuffle2.begin(), shuffle2.end(), vector_double::size_type(0)); @@ -181,10 +185,10 @@ population nsga2::evolve(population pop) const std::shuffle(shuffle2.begin(), shuffle2.end(), m_e); // 1 - We compute crowding distance and non dominated rank for the current population - auto fnds_res = fast_non_dominated_sorting(pop.get_f()); - auto ndf = std::get<0>(fnds_res); // non dominated fronts [[0,3,2],[1,5,6],[4],...] + fast_non_dominated_sorting_buffered(pop.get_f(), fnds_buffer); + auto& ndf = std::get<0>(fnds_buffer); // non dominated fronts [[0,3,2],[1,5,6],[4],...] vector_double pop_cd(NP); // crowding distances of the whole population - auto ndr = std::get<3>(fnds_res); // non domination rank [0,1,0,0,2,1,1, ... ] + auto& ndr = std::get<3>(fnds_buffer); // non domination rank [0,1,0,0,2,1,1, ... ] for (const auto &front_idxs : ndf) { if (front_idxs.size() == 1u) { // handles the case where the front has collapsed to one point pop_cd[front_idxs[0]] = std::numeric_limits::infinity(); @@ -297,7 +301,7 @@ population nsga2::evolve(population pop) const } // This method returns the sorted N best individuals in the population according to the crowded comparison // operator - best_idx = select_best_N_mo(popnew.get_f(), NP); + best_idx = select_best_N_mo_buffered(popnew.get_f(), NP, best_N_buffer); // We insert into the population for (population::size_type i = 0; i < NP; ++i) { pop.set_xf(i, popnew.get_x()[best_idx[i]], popnew.get_f()[best_idx[i]]); diff --git a/src/utils/multi_objective.cpp b/src/utils/multi_objective.cpp index 644a9a114..2594214ba 100644 --- a/src/utils/multi_objective.cpp +++ b/src/utils/multi_objective.cpp @@ -421,9 +421,64 @@ vector_double crowding_distance(const std::vector &non_dom_front) */ std::vector select_best_N_mo(const std::vector &input_f, pop_size_t N) { - // Pre-allocated buffer for results of calling fast_non_dominated_sorting_buffered() - static fnds_return_type sorting_results{}; + if (N == 0u) { // corner case + return {}; + } + if (input_f.size() == 0u) { // corner case + return {}; + } + if (input_f.size() == 1u) { // corner case + return {0u}; + } + if (N >= input_f.size()) { // corner case + std::vector retval(input_f.size()); + std::iota(retval.begin(), retval.end(), pop_size_t(0u)); + return retval; + } + std::vector retval; + std::vector::size_type front_id(0u); + // Run fast-non-dominated sorting + auto tuple = fast_non_dominated_sorting(input_f); + // Insert all non dominated fronts if not more than N + for (const auto &front : std::get<0>(tuple)) { + if (retval.size() + front.size() <= N) { + for (auto i : front) { + retval.push_back(i); + } + if (retval.size() == N) { + return retval; + } + ++front_id; + } else { + break; + } + } + auto front = std::get<0>(tuple)[front_id]; + std::vector non_dom_fits(front.size()); + // Run crowding distance for the front + for (decltype(front.size()) i = 0u; i < front.size(); ++i) { + non_dom_fits[i] = input_f[front[i]]; + } + vector_double cds(crowding_distance(non_dom_fits)); + // We now have front and crowding distance, we sort the front w.r.t. the crowding + std::vector idxs(front.size()); + std::iota(idxs.begin(), idxs.end(), pop_size_t(0u)); + std::sort(idxs.begin(), idxs.end(), [&cds](pop_size_t idx1, pop_size_t idx2) { + return detail::greater_than_f(cds[idx1], cds[idx2]); + }); // Descending order1 + auto remaining = N - retval.size(); + for (decltype(remaining) i = 0u; i < remaining; ++i) { + retval.push_back(front[idxs[i]]); + } + return retval; +} +/// Selects the best N individuals in multi-objective optimization +/** + * select_best_N_mo() with pre-allocated buffers. + */ +std::vector select_best_N_mo_buffered(const std::vector &input_f, pop_size_t N, fnds_return_type& fnds_buffer) +{ if (N == 0u) { // corner case return {}; } @@ -441,9 +496,9 @@ std::vector select_best_N_mo(const std::vector &input std::vector retval; std::vector::size_type front_id(0u); // Run fast-non-dominated sorting - fast_non_dominated_sorting_buffered(input_f, sorting_results); + fast_non_dominated_sorting_buffered(input_f, fnds_buffer); // Insert all non dominated fronts if not more than N - for (const auto &front : std::get<0>(sorting_results)) { + for (const auto &front : std::get<0>(fnds_buffer)) { if (retval.size() + front.size() <= N) { for (auto i : front) { retval.push_back(i); @@ -456,7 +511,7 @@ std::vector select_best_N_mo(const std::vector &input break; } } - const auto front = std::get<0>(sorting_results)[front_id]; + const auto& front = std::get<0>(fnds_buffer)[front_id]; std::vector non_dom_fits(front.size()); // Run crowding distance for the front for (decltype(front.size()) i = 0u; i < front.size(); ++i) { @@ -476,6 +531,7 @@ std::vector select_best_N_mo(const std::vector &input return retval; } + /// Sorts a population in multi-objective optimization /** * Sorts a population (intended here as an std::vector containing the objective vectors) From f98538d6f89846d8fd389ef0724800978b9d2988 Mon Sep 17 00:00:00 2001 From: Jonas Date: Sat, 21 Feb 2026 22:34:10 +0100 Subject: [PATCH 04/10] Move pop_cd outside of main NSGA2 loop --- src/algorithms/nsga2.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/algorithms/nsga2.cpp b/src/algorithms/nsga2.cpp index 2ec0d0f2a..685132a4b 100644 --- a/src/algorithms/nsga2.cpp +++ b/src/algorithms/nsga2.cpp @@ -135,6 +135,7 @@ population nsga2::evolve(population pop) const std::vector best_idx(NP), shuffle1(NP), shuffle2(NP); vector_double::size_type parent1_idx, parent2_idx; std::pair children; + vector_double pop_cd(NP); // crowding distances of the whole population // We're using select_best_N_mo_buffered(), which prevents re-allocation of this structure fnds_return_type best_N_buffer{}; @@ -187,7 +188,8 @@ population nsga2::evolve(population pop) const // 1 - We compute crowding distance and non dominated rank for the current population fast_non_dominated_sorting_buffered(pop.get_f(), fnds_buffer); auto& ndf = std::get<0>(fnds_buffer); // non dominated fronts [[0,3,2],[1,5,6],[4],...] - vector_double pop_cd(NP); // crowding distances of the whole population + // Reset the pop_cd to being empty - that is, set all values to zero + std::fill(pop_cd.begin(), pop_cd.end(), 0); auto& ndr = std::get<3>(fnds_buffer); // non domination rank [0,1,0,0,2,1,1, ... ] for (const auto &front_idxs : ndf) { if (front_idxs.size() == 1u) { // handles the case where the front has collapsed to one point From 10b02aeeb66a7cc9f560bf0e08a4b0d541ec145d Mon Sep 17 00:00:00 2001 From: Jonas Date: Sat, 21 Feb 2026 23:00:32 +0100 Subject: [PATCH 05/10] Move front vector outside of NSGA2 loop --- src/algorithms/nsga2.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/algorithms/nsga2.cpp b/src/algorithms/nsga2.cpp index 685132a4b..9525b5a7a 100644 --- a/src/algorithms/nsga2.cpp +++ b/src/algorithms/nsga2.cpp @@ -135,7 +135,8 @@ population nsga2::evolve(population pop) const std::vector best_idx(NP), shuffle1(NP), shuffle2(NP); vector_double::size_type parent1_idx, parent2_idx; std::pair children; - vector_double pop_cd(NP); // crowding distances of the whole population + vector_double pop_cd(NP); // crowding distances of the whole population + std::vector front_cd; // for the crowding distance loop // We're using select_best_N_mo_buffered(), which prevents re-allocation of this structure fnds_return_type best_N_buffer{}; @@ -188,9 +189,11 @@ population nsga2::evolve(population pop) const // 1 - We compute crowding distance and non dominated rank for the current population fast_non_dominated_sorting_buffered(pop.get_f(), fnds_buffer); auto& ndf = std::get<0>(fnds_buffer); // non dominated fronts [[0,3,2],[1,5,6],[4],...] - // Reset the pop_cd to being empty - that is, set all values to zero - std::fill(pop_cd.begin(), pop_cd.end(), 0); auto& ndr = std::get<3>(fnds_buffer); // non domination rank [0,1,0,0,2,1,1, ... ] + + // Reset the pop_cd to being empty - that is, set all values to zero + std::fill(pop_cd.begin(), pop_cd.end(), 0.0); + for (const auto &front_idxs : ndf) { if (front_idxs.size() == 1u) { // handles the case where the front has collapsed to one point pop_cd[front_idxs[0]] = std::numeric_limits::infinity(); @@ -199,11 +202,11 @@ population nsga2::evolve(population pop) const pop_cd[front_idxs[0]] = std::numeric_limits::infinity(); pop_cd[front_idxs[1]] = std::numeric_limits::infinity(); } else { - std::vector front; - for (auto idx : front_idxs) { - front.push_back(pop.get_f()[idx]); + front_cd.clear(); + for (const auto idx : front_idxs) { + front_cd.push_back(pop.get_f()[idx]); } - auto cd = crowding_distance(front); + const auto cd = crowding_distance(front_cd); for (decltype(cd.size()) i = 0u; i < cd.size(); ++i) { pop_cd[front_idxs[i]] = cd[i]; } From 01c18fb3a28bdfd76f6d53bc6be751ca167d8b8e Mon Sep 17 00:00:00 2001 From: Jonas Date: Sat, 21 Feb 2026 23:19:59 +0100 Subject: [PATCH 06/10] Move popnew out of NSGA2 loop --- src/algorithms/nsga2.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/algorithms/nsga2.cpp b/src/algorithms/nsga2.cpp index 9525b5a7a..fad127f13 100644 --- a/src/algorithms/nsga2.cpp +++ b/src/algorithms/nsga2.cpp @@ -135,8 +135,9 @@ population nsga2::evolve(population pop) const std::vector best_idx(NP), shuffle1(NP), shuffle2(NP); vector_double::size_type parent1_idx, parent2_idx; std::pair children; - vector_double pop_cd(NP); // crowding distances of the whole population - std::vector front_cd; // for the crowding distance loop + vector_double pop_cd(NP); // Crowding distances of the whole population + std::vector front_cd; // For the crowding distance loop + population popnew(pop); // Used to contain a copy of the old population // We're using select_best_N_mo_buffered(), which prevents re-allocation of this structure fnds_return_type best_N_buffer{}; @@ -180,7 +181,7 @@ population nsga2::evolve(population pop) const } // At each generation we make a copy of the population into popnew - population popnew(pop); + popnew = pop; // We create some pseudo-random permutation of the population indexes std::shuffle(shuffle1.begin(), shuffle1.end(), m_e); From 23a38d5654df5078fa709b75bbb1b54c86133706 Mon Sep 17 00:00:00 2001 From: Jonas Date: Sat, 21 Feb 2026 23:30:26 +0100 Subject: [PATCH 07/10] Revert "Move popnew out of NSGA2 loop" This reverts commit 5083cafcf483bb4a58399854667f348ce9f23e06. --- src/algorithms/nsga2.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/algorithms/nsga2.cpp b/src/algorithms/nsga2.cpp index fad127f13..9525b5a7a 100644 --- a/src/algorithms/nsga2.cpp +++ b/src/algorithms/nsga2.cpp @@ -135,9 +135,8 @@ population nsga2::evolve(population pop) const std::vector best_idx(NP), shuffle1(NP), shuffle2(NP); vector_double::size_type parent1_idx, parent2_idx; std::pair children; - vector_double pop_cd(NP); // Crowding distances of the whole population - std::vector front_cd; // For the crowding distance loop - population popnew(pop); // Used to contain a copy of the old population + vector_double pop_cd(NP); // crowding distances of the whole population + std::vector front_cd; // for the crowding distance loop // We're using select_best_N_mo_buffered(), which prevents re-allocation of this structure fnds_return_type best_N_buffer{}; @@ -181,7 +180,7 @@ population nsga2::evolve(population pop) const } // At each generation we make a copy of the population into popnew - popnew = pop; + population popnew(pop); // We create some pseudo-random permutation of the population indexes std::shuffle(shuffle1.begin(), shuffle1.end(), m_e); From 0e07e4bebdd613c4722fa10836283952e673e145 Mon Sep 17 00:00:00 2001 From: Jonas Date: Sat, 21 Feb 2026 23:30:32 +0100 Subject: [PATCH 08/10] Revert "Move front vector outside of NSGA2 loop" This reverts commit 7d81225cf7725545776337ae3974e58393f71042. --- src/algorithms/nsga2.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/algorithms/nsga2.cpp b/src/algorithms/nsga2.cpp index 9525b5a7a..685132a4b 100644 --- a/src/algorithms/nsga2.cpp +++ b/src/algorithms/nsga2.cpp @@ -135,8 +135,7 @@ population nsga2::evolve(population pop) const std::vector best_idx(NP), shuffle1(NP), shuffle2(NP); vector_double::size_type parent1_idx, parent2_idx; std::pair children; - vector_double pop_cd(NP); // crowding distances of the whole population - std::vector front_cd; // for the crowding distance loop + vector_double pop_cd(NP); // crowding distances of the whole population // We're using select_best_N_mo_buffered(), which prevents re-allocation of this structure fnds_return_type best_N_buffer{}; @@ -189,11 +188,9 @@ population nsga2::evolve(population pop) const // 1 - We compute crowding distance and non dominated rank for the current population fast_non_dominated_sorting_buffered(pop.get_f(), fnds_buffer); auto& ndf = std::get<0>(fnds_buffer); // non dominated fronts [[0,3,2],[1,5,6],[4],...] - auto& ndr = std::get<3>(fnds_buffer); // non domination rank [0,1,0,0,2,1,1, ... ] - // Reset the pop_cd to being empty - that is, set all values to zero - std::fill(pop_cd.begin(), pop_cd.end(), 0.0); - + std::fill(pop_cd.begin(), pop_cd.end(), 0); + auto& ndr = std::get<3>(fnds_buffer); // non domination rank [0,1,0,0,2,1,1, ... ] for (const auto &front_idxs : ndf) { if (front_idxs.size() == 1u) { // handles the case where the front has collapsed to one point pop_cd[front_idxs[0]] = std::numeric_limits::infinity(); @@ -202,11 +199,11 @@ population nsga2::evolve(population pop) const pop_cd[front_idxs[0]] = std::numeric_limits::infinity(); pop_cd[front_idxs[1]] = std::numeric_limits::infinity(); } else { - front_cd.clear(); - for (const auto idx : front_idxs) { - front_cd.push_back(pop.get_f()[idx]); + std::vector front; + for (auto idx : front_idxs) { + front.push_back(pop.get_f()[idx]); } - const auto cd = crowding_distance(front_cd); + auto cd = crowding_distance(front); for (decltype(cd.size()) i = 0u; i < cd.size(); ++i) { pop_cd[front_idxs[i]] = cd[i]; } From 90211a8c92e32e714db4b1ab6aa87863ce860bb3 Mon Sep 17 00:00:00 2001 From: Jonas Date: Sat, 21 Feb 2026 23:30:37 +0100 Subject: [PATCH 09/10] Revert "Move pop_cd outside of main NSGA2 loop" This reverts commit 1ecf79a19b54ed65915461442c98a3df9b8a934e. --- src/algorithms/nsga2.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/algorithms/nsga2.cpp b/src/algorithms/nsga2.cpp index 685132a4b..2ec0d0f2a 100644 --- a/src/algorithms/nsga2.cpp +++ b/src/algorithms/nsga2.cpp @@ -135,7 +135,6 @@ population nsga2::evolve(population pop) const std::vector best_idx(NP), shuffle1(NP), shuffle2(NP); vector_double::size_type parent1_idx, parent2_idx; std::pair children; - vector_double pop_cd(NP); // crowding distances of the whole population // We're using select_best_N_mo_buffered(), which prevents re-allocation of this structure fnds_return_type best_N_buffer{}; @@ -188,8 +187,7 @@ population nsga2::evolve(population pop) const // 1 - We compute crowding distance and non dominated rank for the current population fast_non_dominated_sorting_buffered(pop.get_f(), fnds_buffer); auto& ndf = std::get<0>(fnds_buffer); // non dominated fronts [[0,3,2],[1,5,6],[4],...] - // Reset the pop_cd to being empty - that is, set all values to zero - std::fill(pop_cd.begin(), pop_cd.end(), 0); + vector_double pop_cd(NP); // crowding distances of the whole population auto& ndr = std::get<3>(fnds_buffer); // non domination rank [0,1,0,0,2,1,1, ... ] for (const auto &front_idxs : ndf) { if (front_idxs.size() == 1u) { // handles the case where the front has collapsed to one point From 319adddd9b93cbf128def33807bb4efd6ff79baf Mon Sep 17 00:00:00 2001 From: Jonas Date: Thu, 26 Feb 2026 04:29:43 +0100 Subject: [PATCH 10/10] Clean up repo --- .idea/vcs.xml | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 .idea/vcs.xml diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7f4..000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file