From c91d4c027472013a2e32b4ccde06f6568b96326b Mon Sep 17 00:00:00 2001 From: shuheitnk <32556009@fukuchiyama.ac.jp> Date: Thu, 20 Aug 2026 09:39:18 +0000 Subject: [PATCH] Improve MA-BBOB evaluation by skipping zero-weight BBOB instances MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Suggestions: Improving Existing functionalities Description in detail: This PR suggests a small optimization to the MA-BBOB evaluation by skipping BBOB instances with zero weights. The current implementation evaluates all 24 BBOB instances, including instances that do not contribute to the objective. Skipping these evaluations avoids unnecessary computation while preserving the MA-BBOB objective value. The benchmark measures the time to evaluate 20,000 solutions on each of the MA-BBOB instances {1..100}, across the dimensions {2, 5, 20, 40}. Runtime is reduced from 211.26 s to 33.61 s (6.29× speedup). Name of the function and file: ioh::problem::bbob::ManyAffine::evaluate include/ioh/problem/bbob/many_affine.hpp Additional information: No additional dependencies are required. The optimized implementation was verified against the original evaluation by comparing the 20,000 objective values of each instance bitwise, with no differences found across all instances. The test suite passes (99/99). The evaluation counters of skipped subproblems are not incremented. --- include/ioh/problem/bbob/many_affine.hpp | 4 ++ tests/cpp/problem/test_bbob_affine.cpp | 88 +++++++++++++++++++++++- 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/include/ioh/problem/bbob/many_affine.hpp b/include/ioh/problem/bbob/many_affine.hpp index ef55f1cde..b082c6944 100644 --- a/include/ioh/problem/bbob/many_affine.hpp +++ b/include/ioh/problem/bbob/many_affine.hpp @@ -95,6 +95,10 @@ namespace ioh::problem::bbob auto result = 0.0; for (int fi = 0; fi < 24; fi++) { + // A BBOB instance with zero weight contributes nothing to the weighted + // sum, so its evaluation can be skipped. + if (weights_[fi] == 0.0) + continue; // compute xopt shifted x std::vector x0 = x; for (size_t i = 0; i < x.size(); i++) diff --git a/tests/cpp/problem/test_bbob_affine.cpp b/tests/cpp/problem/test_bbob_affine.cpp index 5130f9a6f..7149cadc1 100644 --- a/tests/cpp/problem/test_bbob_affine.cpp +++ b/tests/cpp/problem/test_bbob_affine.cpp @@ -1,5 +1,9 @@ #include "../utils.hpp" +#include +#include +#include + #include "ioh/problem/bbob/many_affine.hpp" @@ -12,4 +16,86 @@ TEST_F(BaseTest, TestManyAffine) EXPECT_NEAR(affine(x0), 3.521076347, 1e-8); //TODO: test based on indiviudal problems -} \ No newline at end of file +} + +namespace +{ + //! True when two doubles have the same bit pattern, which is stricter than ==. + bool bitwise_equal(const double a, const double b) { return std::memcmp(&a, &b, sizeof(double)) == 0; } + + //! Reconstruct the original ManyAffine evaluation by summing all 24 BBOB instances. + //! This serves as a reference for the optimized implementation. + double evaluate_all_instances(ioh::problem::bbob::ManyAffine &affine, const std::vector &x) + { + auto problems = affine.get_problems(); + const auto weights = affine.get_weights(); + const auto scale_factors = affine.get_scale_factors(); + const auto xopt = affine.optimum().x; + + auto result = 0.0; + for (int fi = 0; fi < 24; fi++) + { + std::vector x0 = x; + for (size_t i = 0; i < x.size(); i++) + x0[i] = x[i] + problems[fi]->optimum().x[i] - xopt[i]; + + double f0 = (*problems[fi])(x0)-problems[fi]->optimum().y; + f0 = std::min(std::max(f0, 1e-12), 1e20); + f0 = (std::log10(f0) + 8) / scale_factors[fi]; + f0 = f0 * weights[fi]; + result += f0; + } + return pow(10, (10 * result - 8)); + } +} // namespace + +//! Skipping zero-weight BBOB instances must not change any objective value. +//! The reference implementation evaluates all 24 BBOB instances and is compared +//! with the optimized implementation using bitwise equality. +TEST_F(BaseTest, TestManyAffineSkipsZeroWeights) +{ + using namespace ioh::problem::bbob; + + for (const int n_variables : {2, 5, 10}) + { + for (int instance = 1; instance <= 10; instance++) + { + ManyAffine affine(instance, n_variables); + + const auto weights = affine.get_weights(); + const auto n_zero = std::count(weights.begin(), weights.end(), 0.0); + + // Ensure the test exercises the optimization target. + EXPECT_GT(n_zero, 0) << "instance " << instance << " in " << n_variables << "D has no zero weight"; + + // The optimum is computed inside the constructor, so it is covered too. + EXPECT_TRUE(bitwise_equal(affine.optimum().y, evaluate_all_instances(affine, affine.optimum().x))) + << "optimum of instance " << instance << " in " << n_variables << "D"; + + std::mt19937 gen(static_cast(instance * 100 + n_variables)); + std::uniform_real_distribution dis(-5.0, 5.0); + + std::vector> points{ + std::vector(static_cast(n_variables), 0.0), + std::vector(static_cast(n_variables), -5.0), + std::vector(static_cast(n_variables), 5.0), + }; + for (int k = 0; k < 10; k++) + { + std::vector x(static_cast(n_variables)); + for (auto &xi : x) + xi = dis(gen); + points.push_back(x); + } + + for (const auto &x : points) + { + const double expected = evaluate_all_instances(affine, x); + const double got = affine(x); + EXPECT_TRUE(bitwise_equal(expected, got)) + << "instance " << instance << " in " << n_variables << "D at x = " << format_vector(x) + << ": expected " << expected << " got " << got; + } + } + } +}