// Copyright Maksym Zhelyenzyakov 2025-2026. // Copyright Matt Borland 2026. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // https://www.boost.org/LICENSE_1_0.txt) // // Module build check for boost::math::optimization::gradient_descent. When // BOOST_MATH_BUILD_MODULE is defined the component is consumed through // `import boost.math;` and the standard library through `import std;` (performed // by math_unit_test.hpp). Nothing standard or Boost is included textually in // that mode. // // The optimizer minimizes f(x) = sum_i x_i^2, whose gradient is 2 x_i. With a // fixed learning rate lr a single descent step maps each coordinate x_i to // x_i * (1 - 2*lr), and repeated steps drive the minimizer to the origin. Both // the one-step and the limiting values are mathematically certain; the setup // mirrors the analytic_derivative_policies case in // test_gradient_descent_optimizer.cpp. #ifndef BOOST_MATH_BUILD_MODULE #include #else import boost.math; #endif #ifndef BOOST_MATH_BUILD_MODULE #include #include #endif #include "math_unit_test.hpp" namespace bopt = boost::math::optimization; // f(x) = sum_i x_i^2. Its gradient, 2 x_i, is supplied analytically by the // gradient policy below, so one descent step scales each x_i by (1 - 2*lr). template Real sum_of_squares(const std::vector& x) { Real s {0}; for (const auto& xi : x) { s += xi * xi; } return s; } // Leave the caller-supplied starting point untouched. template struct no_init_policy { void operator()(std::vector&) const noexcept {} }; // Evaluate the objective directly. template struct analytic_objective_eval_pol { template Real operator()(Objective&& objective, ArgumentContainer& x) { return objective(x); } }; // Supply the analytic gradient of sum_of_squares: grad_i = 2 x_i. template struct analytic_gradient_eval_pol { template void operator()(Objective&& obj_f, ArgumentContainer& x, FunctionEvaluationPolicy&& f_eval_pol, Real& obj_v, std::vector& grad_container) { obj_v = f_eval_pol(obj_f, x); grad_container.resize(x.size()); for (std::size_t i = 0; i < x.size(); ++i) { grad_container[i] = 2 * x[i]; } } }; template void test_gradient_descent(Real, const char* type_name) { std::cout << "Testing gradient_descent for type " << type_name << std::endl; // One step with lr = 0.01 scales each coordinate by (1 - 2*lr) = 0.98: // {3, -4, 5} -> {2.94, -3.92, 4.9}. { Real lr = Real {1} / Real {100}; std::vector x = {Real {3}, Real {-4}, Real {5}}; auto gdopt = bopt::make_gradient_descent(&sum_of_squares, x, lr, no_init_policy {}, analytic_objective_eval_pol {}, analytic_gradient_eval_pol {}); gdopt.step(); CHECK_ULP_CLOSE(2.94, x[0], 4); CHECK_ULP_CLOSE(-3.92, x[1], 4); CHECK_ULP_CLOSE(4.9, x[2], 4); } // Repeated steps drive the minimizer of f(x) = sum_i x_i^2 to the origin. { Real lr = Real {1} / Real {100}; std::vector x = {Real {3}, Real {-4}, Real {5}}; auto gdopt = bopt::make_gradient_descent(&sum_of_squares, x, lr, no_init_policy {}, analytic_objective_eval_pol {}, analytic_gradient_eval_pol {}); for (std::size_t i = 0; i < 2000; ++i) { gdopt.step(); } CHECK_ABSOLUTE_ERROR(Real(0), x[0], Real(1e-3)); CHECK_ABSOLUTE_ERROR(Real(0), x[1], Real(1e-3)); CHECK_ABSOLUTE_ERROR(Real(0), x[2], Real(1e-3)); } } int main() { test_gradient_descent(0.0, "double"); test_gradient_descent(0.0F, "float"); return boost::math::test::report_errors(); }