This commit is contained in:
2026-01-20 17:30:34 +01:00
commit 650e5cc6b8
14 changed files with 968 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
# nix
result
# cmake
build
# direnv files
.direnv
.envrc
+20
View File
@@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 4.0)
project(autoopt)
set(CMAKE_CXX_STANDARD 23)
add_compile_options(-Wall -Werror -Wpedantic)
add_library(autoopt INTERFACE)
target_include_directories(autoopt INTERFACE include)
install(DIRECTORY include/autoopt DESTINATION include)
# add tests if gtest is found
find_library(GTestPackage gtest QUIET)
if(GTestPackage)
enable_testing()
add_subdirectory(tests)
endif()
Generated
+61
View File
@@ -0,0 +1,61 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1768564909,
"narHash": "sha256-Kell/SpJYVkHWMvnhqJz/8DqQg2b6PguxVWOuadbHCc=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "e4bae1bd10c9c57b2cf517953ab70060a828ee6f",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
+71
View File
@@ -0,0 +1,71 @@
{
description = "auto-opt-cpp";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs =
{
self,
nixpkgs,
flake-utils,
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
pkg =
{
stdenv,
cmake,
ninja,
lib,
gtest,
withTests ? false,
}:
let
fs = lib.fileset;
in
stdenv.mkDerivation {
pname = "autoopt-cpp";
version = "0.1.0";
src = fs.toSource {
root = ./.;
fileset = fs.unions [
./include
./CMakeLists.txt
./tests
];
};
buildInputs = lib.optionals withTests [ gtest ];
nativeBuildInputs = [
cmake
ninja
];
};
in
rec {
packages = rec {
autoopt-cpp = pkgs.callPackage pkg { };
autoopt-cpp-with-tests = pkgs.callPackage pkg { withTests = true; };
default = autoopt-cpp;
};
devShells = {
default = pkgs.mkShell {
inputsFrom = [
packages.autoopt-cpp-with-tests
];
shellHook = ''
echo "Development shell for autoopt-cpp"
'';
};
};
}
);
}
+61
View File
@@ -0,0 +1,61 @@
#pragma once
#include <array>
#include "autoopt/dual.hpp"
namespace autoopt {
template <typename T, class Func>
T derivative(Func&& f, const T& x) {
dual<T> a(x, T(1));
dual<T> b = f(a);
return b._dx;
}
template <typename T, std::size_t N, class Func>
std::array<T, N> gradient(Func&& f, const std::array<T, N>& x) {
std::array<T, N> grad{};
std::array<dual<T>, N> dual_x{};
for (std::size_t i = 0; i < N; ++i) {
dual_x[i] = dual<T>(x[i], T(0));
}
for (std::size_t i = 0; i < N; ++i) {
dual_x[i]._dx = T(1);
dual<T> dual_y = f(dual_x);
grad[i] = dual_y._dx;
dual_x[i]._dx = T(0);
}
return grad;
}
template <typename T, std::size_t N, std::size_t M>
using matrix_t = std::array<std::array<T, M>, N>;
template <typename T, std::size_t N, std::size_t M, class Func>
matrix_t<T, M, N> jacobian(Func&& f, const std::array<T, N>& x) {
matrix_t<T, M, N> jacob{};
std::array<dual<T>, N> dual_x{};
for (std::size_t i = 0; i < N; ++i) {
dual_x[i] = dual<T>(x[i], T(0));
}
for (std::size_t i = 0; i < N; ++i) {
dual_x[i]._dx = T(1);
std::array<dual<T>, M> dual_y = f(dual_x);
for (std::size_t j = 0; j < M; ++j) {
jacob[j][i] = dual_y[j]._dx;
}
dual_x[i]._dx = T(0);
}
return jacob;
}
template <typename T, std::size_t N, class Func>
matrix_t<T, N, N> hessian(Func&& f, const std::array<T, N>& x) {
auto helper_func = [&f]<typename U>(const std::array<U, N>& y) {
return gradient<U, N>(f, y);
};
return jacobian<T, N, N>(helper_func, x);
}
} // namespace autoopt
+241
View File
@@ -0,0 +1,241 @@
#pragma once
#include <cmath>
namespace autoopt {
template <typename T>
struct dual {
T _x;
T _dx;
constexpr dual(T x = T{0}, T dx = T{0}) : _x(x), _dx(dx) {}
// allow arbitrary deeply-nested dual construction
template <typename U>
constexpr dual(U x) : _x(T(x)), _dx(T(0)) {}
};
template <typename T>
constexpr dual<T> operator+(const dual<T>& a, const dual<T>& b) {
return dual<T>(a._x + b._x, a._dx + b._dx);
}
template <typename T>
constexpr dual<T> operator-(const dual<T>& a) {
return dual<T>(-a._x, -a._dx);
}
template <typename T>
constexpr dual<T> operator-(const dual<T>& a, const dual<T>& b) {
return dual<T>(a._x - b._x, a._dx - b._dx);
}
template <typename T>
constexpr dual<T> operator*(const dual<T>& a, const dual<T>& b) {
return dual<T>(a._x * b._x, a._x * b._dx + a._dx * b._x);
}
template <typename T>
constexpr dual<T> operator/(const dual<T>& a, const dual<T>& b) {
return dual<T>(a._x / b._x, (a._dx * b._x - a._x * b._dx) / (b._x * b._x));
}
template <typename T>
constexpr bool operator==(const dual<T>& a, const dual<T>& b) {
return a._x == b._x;
}
template <typename T>
constexpr bool operator!=(const dual<T>& a, const dual<T>& b) {
return a._x != b._x;
}
template <typename T>
constexpr bool operator<(const dual<T>& a, const dual<T>& b) {
return a._x < b._x;
}
template <typename T>
constexpr bool operator<=(const dual<T>& a, const dual<T>& b) {
return a._x <= b._x;
}
template <typename T>
constexpr bool operator>(const dual<T>& a, const dual<T>& b) {
return a._x > b._x;
}
template <typename T>
constexpr bool operator>=(const dual<T>& a, const dual<T>& b) {
return a._x >= b._x;
}
} // namespace autoopt
namespace std {
using autoopt::dual;
// forward declarations of standard functions
template <typename T>
constexpr dual<T> abs(const dual<T>& a);
template <typename T>
constexpr dual<T> exp(const dual<T>& a);
template <typename T>
constexpr dual<T> log(const dual<T>& a);
template <typename T>
constexpr dual<T> pow(const dual<T>& a, const dual<T>& b);
template <typename T>
constexpr dual<T> sqrt(const dual<T>& a);
// forward declarations of trigonometric functions
template <typename T>
constexpr dual<T> sin(const dual<T>& a);
template <typename T>
constexpr dual<T> cos(const dual<T>& a);
template <typename T>
constexpr dual<T> tan(const dual<T>& a);
template <typename T>
constexpr dual<T> asin(const dual<T>& a);
template <typename T>
constexpr dual<T> acos(const dual<T>& a);
template <typename T>
constexpr dual<T> atan(const dual<T>& a);
template <typename T>
constexpr dual<T> atan2(const dual<T>& y, const dual<T>& x);
// forward declarations of hyperbolic functions
template <typename T>
constexpr dual<T> sinh(const dual<T>& a);
template <typename T>
constexpr dual<T> cosh(const dual<T>& a);
template <typename T>
constexpr dual<T> tanh(const dual<T>& a);
template <typename T>
constexpr dual<T> asinh(const dual<T>& a);
template <typename T>
constexpr dual<T> acosh(const dual<T>& a);
template <typename T>
constexpr dual<T> atanh(const dual<T>& a);
// standard functions
template <typename T>
constexpr dual<T> abs(const dual<T>& a) {
return dual<T>(std::abs(a._x), (a._x >= T(0) ? T(1) : T(-1)) * a._dx);
}
template <typename T>
constexpr dual<T> exp(const dual<T>& a) {
T exp_x = std::exp(a._x);
return dual<T>(exp_x, exp_x * a._dx);
}
template <typename T>
constexpr dual<T> log(const dual<T>& a) {
return dual<T>(std::log(a._x), (T(1) / a._x) * a._dx);
}
template <typename T>
constexpr dual<T> pow(const dual<T>& a, const dual<T>& b) {
return std::exp(b * std::log(a));
}
template <typename T>
constexpr dual<T> sqrt(const dual<T>& a) {
T sqrt_x = std::sqrt(a._x);
return dual<T>(sqrt_x, (T(1) / (T(2) * sqrt_x)) * a._dx);
}
// trigonometric functions
template <typename T>
constexpr dual<T> sin(const dual<T>& a) {
return dual<T>(std::sin(a._x), std::cos(a._x) * a._dx);
}
template <typename T>
constexpr dual<T> cos(const dual<T>& a) {
return dual<T>(std::cos(a._x), -std::sin(a._x) * a._dx);
}
template <typename T>
constexpr dual<T> tan(const dual<T>& a) {
T cos_x = std::cos(a._x);
return dual<T>(std::tan(a._x), (T(1) / (cos_x * cos_x)) * a._dx);
}
template <typename T>
constexpr dual<T> asin(const dual<T>& a) {
return dual<T>(std::asin(a._x), (T(1) / std::sqrt(T(1) - a._x * a._x)) * a._dx);
}
template <typename T>
constexpr dual<T> acos(const dual<T>& a) {
return dual<T>(std::acos(a._x), (-T(1) / std::sqrt(T(1) - a._x * a._x)) * a._dx);
}
template <typename T>
constexpr dual<T> atan(const dual<T>& a) {
return dual<T>(std::atan(a._x), (T(1) / (T(1) + a._x * a._x)) * a._dx);
}
template <typename T>
constexpr dual<T> atan2(const dual<T>& y, const dual<T>& x) {
return dual<T>(std::atan2(y._x, x._x),
(x._x * y._dx - y._x * x._dx) / (x._x * x._x + y._x * y._x));
}
// hyperbolic functions
template <typename T>
constexpr dual<T> sinh(const dual<T>& a) {
return dual<T>(std::sinh(a._x), std::cosh(a._x) * a._dx);
}
template <typename T>
constexpr dual<T> cosh(const dual<T>& a) {
return dual<T>(std::cosh(a._x), std::sinh(a._x) * a._dx);
}
template <typename T>
constexpr dual<T> tanh(const dual<T>& a) {
T cosh_x = std::cosh(a._x);
return dual<T>(std::tanh(a._x), (T(1) / (cosh_x * cosh_x)) * a._dx);
}
template <typename T>
constexpr dual<T> asinh(const dual<T>& a) {
return dual<T>(std::asinh(a._x), (T(1) / std::sqrt(a._x * a._x + T(1))) * a._dx);
}
template <typename T>
constexpr dual<T> acosh(const dual<T>& a) {
return dual<T>(std::acosh(a._x), (T(1) / std::sqrt(a._x * a._x - T(1))) * a._dx);
}
template <typename T>
constexpr dual<T> atanh(const dual<T>& a) {
return dual<T>(std::atanh(a._x), (T(1) / (T(1) - a._x * a._x)) * a._dx);
}
} // namespace std
+50
View File
@@ -0,0 +1,50 @@
#pragma once
#include "autoopt/quadric.hpp"
namespace autoopt {
template <typename T>
struct ellipse {
T left_arm;
T right_arm;
T entrance_angle;
constexpr quadric<T> to_quadric() const {
T a = (left_arm + right_arm) / T{2};
T a2 = a * a;
T left_x = -left_arm * std::cos(entrance_angle);
T left_y = left_arm * std::sin(entrance_angle);
T right_x = right_arm * std::cos(entrance_angle);
T right_y = right_arm * std::sin(entrance_angle);
T c_x = (left_x + right_x) / T{2};
T c_y = (left_y + right_y) / T{2};
T c2 = (left_x - c_x) * (left_x - c_x) + (left_y - c_y) * (left_y - c_y);
T b2 = a2 - c2;
// source: https://en.wikipedia.org/wiki/Ellipse#General_ellipse
T theta = std::atan2(right_y - left_y, right_x - left_x);
T cos_t = std::cos(theta);
T sin_t = std::sin(theta);
T cos_t2 = cos_t * cos_t;
T sin_t2 = sin_t * sin_t;
T sico_t = sin_t * cos_t;
T A = a2 * sin_t2 + b2 * cos_t2;
T B = T{2} * (b2 - a2) * sico_t;
T C = a2 * cos_t2 + b2 * sin_t2;
T D = -T{2} * A * c_x - B * c_y;
T E = -B * c_x - T{2} * C * c_y;
T F = A * c_x * c_x + B * c_x * c_y + C * c_y * c_y - a2 * b2;
return quadric<T>(A, B, C, D, E, F);
}
};
} // namespace autoopt
+111
View File
@@ -0,0 +1,111 @@
#pragma once
#include <array>
#include <cstddef>
#include "autoopt/derivative.hpp"
namespace autoopt {
template <typename T, size_t N>
struct optimization_problem {
virtual std::array<T, N> initial_guess() = 0;
virtual T objective(const std::array<T, N>& params) = 0;
virtual std::array<T, N> gradient(const std::array<T, N>& params) = 0;
virtual matrix_t<T, N, N> hessian(const std::array<T, N>& params) = 0;
};
template <typename T, size_t N, class Func>
struct auto_diff_optimization_problem : public optimization_problem<T, N> {
Func _objective_func;
std::array<T, N> _initial_guess;
auto_diff_optimization_problem(
Func objective_func, std::array<T, N> initial_guess = std::array<T, N>{})
: _objective_func(objective_func), _initial_guess(initial_guess) {}
std::array<T, N> initial_guess() override { return _initial_guess; }
T objective(const std::array<T, N>& params) override {
return _objective_func(params);
}
std::array<T, N> gradient(const std::array<T, N>& params) override {
return autoopt::gradient<T, N>(_objective_func, params);
}
matrix_t<T, N, N> hessian(const std::array<T, N>& params) override {
return autoopt::hessian<T, N>(_objective_func, params);
}
};
template <typename T, size_t N>
struct log_barrier_optimization_problem
: public optimization_problem<T, N> {
optimization_problem<T, N>& _base_problem;
std::array<T, N> _delta;
T _barrier_strength;
log_barrier_optimization_problem(
optimization_problem<T, N>& base_problem,
std::array<T, N> delta,
T barrier_strength = T{1e-3})
: _base_problem(base_problem),
_delta(delta),
_barrier_strength(barrier_strength) {}
std::array<T, N> initial_guess() override {
return _base_problem.initial_guess();
}
T objective(const std::array<T, N>& params) override {
T base_obj = _base_problem.objective(params);
T barrier = barrier_term(params);
return base_obj + barrier;
}
std::array<T, N> gradient(const std::array<T, N>& params) override {
auto base_grad = _base_problem.gradient(params);
std::array<T, N> barrier_grad = autoopt::gradient<T, N>(
[this]<typename U>(const std::array<U, N>& p) {
return barrier_term<U>(p);
},
params);
std::array<T, N> total_grad;
for (size_t i = 0; i < N; ++i) {
total_grad[i] = base_grad[i] + barrier_grad[i];
}
return total_grad;
}
matrix_t<T, N, N> hessian(const std::array<T, N>& params) override {
auto base_hess = _base_problem.hessian(params);
matrix_t<T, N, N> barrier_hess = autoopt::hessian<T, N>(
[this]<typename U>(const std::array<U, N>& p) {
return barrier_term<U>(p);
},
params);
matrix_t<T, N, N> total_hess;
for (size_t i = 0; i < N; ++i) {
for (size_t j = 0; j < N; ++j) {
total_hess[i][j] = base_hess[i][j] + barrier_hess[i][j];
}
}
return total_hess;
}
private:
template <typename U>
U barrier_term(const std::array<U, N>& params) {
U barrier = U{0};
for (size_t i = 0; i < N; ++i) {
U lb = _base_problem.initial_guess()[i] - _delta[i];
U ub = _base_problem.initial_guess()[i] + _delta[i];
barrier = barrier + std::log(params[i] - lb) + std::log(ub - params[i]);
}
return -U{_barrier_strength} * barrier;
}
};
} // namespace autoopt
+60
View File
@@ -0,0 +1,60 @@
#pragma once
#include <cmath>
#include <autoopt/derivative.hpp>
namespace autoopt {
template <typename T>
struct quadric {
T _A; // x² coefficient
T _B; // xy coefficient
T _C; // y² coefficient
T _D; // x coefficient
T _E; // y coefficient
T _F; // constant term
constexpr quadric(T A = 0, T B = 0, T C = 0, T D = 0, T E = 0, T F = 0)
: _A(A), _B(B), _C(C), _D(D), _E(E), _F(F) {}
constexpr quadric rotated_by(T angle_rad) const {
T co = std::cos(angle_rad);
T si = std::sin(angle_rad);
T co_2 = co * co;
T si_2 = si * si;
T co_si = co * si;
T A_new = _A * co_2 - _B * co_si + _C * si_2;
T B_new = T{2} * (_A - _C) * co_si + _B * (co_2 - si_2);
T C_new = _A * si_2 + _B * co_si + _C * co_2;
T D_new = _D * co - _E * si;
T E_new = _D * si + _E * co;
T F_new = _F;
return quadric(A_new, B_new, C_new, D_new, E_new, F_new);
}
constexpr T at(T x) const {
T sign = T{-1};
T Bx_E = _B * x + _E;
T Ax2_Dx_F = _A * x * x + _D * x + _F;
if (_C == T{0}) {
return -Ax2_Dx_F / Bx_E;
}
T discriminant = Bx_E * Bx_E - T{4} * _C * Ax2_Dx_F;
T num = sign * std::sqrt(discriminant) - Bx_E;
T denom = T{2} * _C;
return num / denom;
}
constexpr T slope_at(T x) const {
return derivative([&]<typename U>(U x_val) {
quadric<U> q{U{_A}, U{_B}, U{_C}, U{_D}, U{_E}, U{_F}};
return q.at(x_val);
}, x);
}
};
} // namespace autoopt
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#include <cmath>
namespace autoopt {
template <typename T>
T rad2deg(T radians) {
return radians * (T{180} / M_PI);
}
template <typename T>
T deg2rad(T degrees) {
return degrees * (M_PI / T{180});
}
template <typename T>
T rad2arcmin(T radians) {
return radians * (T{180 * 60} / M_PI);
}
template <typename T>
T arcmin2rad(T arcminutes) {
return arcminutes * (M_PI / T{180 * 60});
}
template <typename T>
T rad2arcsec(T radians) {
return radians * (T{180 * 60 * 60} / M_PI);
}
template <typename T>
T arcsec2rad(T arcseconds) {
return arcseconds * (M_PI / T{180 * 60 * 60});
}
} // namespace autoopt
+10
View File
@@ -0,0 +1,10 @@
file(GLOB_RECURSE TEST_SOURCES *.cpp)
add_executable(autoopt-test ${TEST_SOURCES})
target_link_libraries(autoopt-test autoopt gtest gtest_main)
install(TARGETS autoopt-test DESTINATION bin)
include(GoogleTest)
gtest_discover_tests(autoopt-test)
+137
View File
@@ -0,0 +1,137 @@
#include "autoopt/dual.hpp"
#include <gtest/gtest.h>
#include "autoopt/derivative.hpp"
using namespace autoopt;
TEST(DualTest, BasicOperations) {
dual<double> a(2.0, 1.0); // a = 2.0, da/dx = 1.0
dual<double> b(3.0, 0.0); // b = 3.0, db/dx = 0.0
dual<double> c = a + b;
EXPECT_DOUBLE_EQ(c._x, 5.0);
EXPECT_DOUBLE_EQ(c._dx, 1.0);
dual<double> d = a * b;
EXPECT_DOUBLE_EQ(d._x, 6.0);
EXPECT_DOUBLE_EQ(d._dx, 3.0);
dual<double> e = a / b;
EXPECT_DOUBLE_EQ(e._x, 2.0 / 3.0);
EXPECT_DOUBLE_EQ(e._dx, 1.0 / 3.0);
}
TEST(DualTest, StandardFunctions) {
dual<double> a(0.5, 1.0); // a = 0.5, da/dx = 1.0
dual<double> b = std::sin(a);
EXPECT_DOUBLE_EQ(b._x, std::sin(0.5));
EXPECT_DOUBLE_EQ(b._dx, std::cos(0.5));
dual<double> c = std::exp(a);
EXPECT_DOUBLE_EQ(c._x, std::exp(0.5));
EXPECT_DOUBLE_EQ(c._dx, std::exp(0.5));
dual<double> d = std::log(a);
EXPECT_DOUBLE_EQ(d._x, std::log(0.5));
EXPECT_DOUBLE_EQ(d._dx, 1.0 / 0.5);
}
TEST(DualTest, DerivativeFunction) {
auto func = []<typename T>(const T& x) { return std::sin(x) * std::exp(x); };
for (double val : {0.0, 0.5, 1.0, 2.0}) {
double deriv = derivative(func, val);
double expected = (std::cos(val) + std::sin(val)) * std::exp(val);
EXPECT_DOUBLE_EQ(deriv, expected);
}
}
TEST(DualTest, GradientFunction) {
auto func = []<typename T>(const std::array<T, 2>& x) {
return x[0] * x[0] + std::sin(x[1]);
};
std::array<double, 2> point = {1.0, 0.0};
std::array<double, 2> grad = gradient(func, point);
EXPECT_DOUBLE_EQ(grad[0], 2.0 * point[0]); // d/dx1
EXPECT_DOUBLE_EQ(grad[1], std::cos(point[1])); // d/dx2
}
TEST(DualTest, JacobianFunction) {
auto func = []<typename T>(const std::array<T, 2>& x) {
return std::array<T, 2>{x[0] * x[0], std::sin(x[1])};
};
std::array<double, 2> point = {1.0, 0.0};
auto jacob = jacobian<double, 2, 2>(func, point);
EXPECT_DOUBLE_EQ(jacob[0][0], 2.0 * point[0]); // d(f1)/d(x1)
EXPECT_DOUBLE_EQ(jacob[0][1], 0.0); // d(f1)/d(x2)
EXPECT_DOUBLE_EQ(jacob[1][0], 0.0); // d(f2)/d(x1)
EXPECT_DOUBLE_EQ(jacob[1][1], std::cos(point[1])); // d(f2)/d(x2)
}
TEST(DualTest, HessianFunction) {
auto func = []<typename T>(const std::array<T, 2>& x) {
return x[0] * x[0] + x[1] * x[1];
};
std::array<double, 2> point = {1.0, 2.0};
auto hess = hessian<double, 2>(func, point);
EXPECT_DOUBLE_EQ(hess[0][0], 2.0); // d²f/dx1²
EXPECT_DOUBLE_EQ(hess[0][1], 0.0); // d²f/dx1dx2
EXPECT_DOUBLE_EQ(hess[1][0], 0.0); // d²f/dx2dx1
EXPECT_DOUBLE_EQ(hess[1][1], 2.0); // d²f/dx2²
}
struct opti_func {
std::vector<std::array<double, 2>> test_data;
// loss function
template <typename T>
T operator()(const std::array<T, 3>& params) const {
T sum = T(0);
for (const auto& data_point : test_data) {
T x = T{data_point[0]};
T y_true = T{data_point[1]};
T a = params[0];
T b = params[1];
T c = params[2];
T y_pred = a * x * x + b * x + c;
T error = y_pred - y_true;
sum = sum + error * error;
}
return sum / T(test_data.size());
}
};
TEST(DualTest, OptimizationFunction) {
opti_func f;
f.test_data = {
{0.0, 4.0},
{1.0, 1.0},
{2.0, 0.0},
{3.0, 1.0},
{4.0, 4.0},
};
std::array<double, 3> params = {1.0, -4.0, 4.0};
auto grad = gradient<double, 3>(f, params);
EXPECT_DOUBLE_EQ(grad[0], 0.0); // dL/da
EXPECT_DOUBLE_EQ(grad[1], 0.0); // dL/db
EXPECT_DOUBLE_EQ(grad[2], 0.0); // dL/dc
auto hess = hessian<double, 3>(f, params);
for (std::size_t i = 0; i < 3; ++i) {
for (std::size_t j = 0; j < 3; ++j) {
EXPECT_GE(hess[i][j], 0.0); // Hessian should be positive semi-definite
}
}
}
+78
View File
@@ -0,0 +1,78 @@
#include <gtest/gtest.h>
#include <autoopt/ellipse.hpp>
#include <autoopt/optimization_problem.hpp>
#include <autoopt/util.hpp>
#include <iomanip>
#include <iostream>
using namespace autoopt;
TEST(Ellipse, Slope) {
ellipse<double> e{100, 1000, deg2rad(1.0)}; // entrance angle 1 degree
quadric<double> q = e.to_quadric();
EXPECT_NEAR(q.slope_at(-10), -0.0010305116165301856, 1e-9);
EXPECT_NEAR(q.slope_at(0), 0.0, 1e-9);
EXPECT_NEAR(q.slope_at(10), 0.00090001261192696272, 1e-9);
}
TEST(Ellipse, ParamGradient) {
std::vector<std::pair<double, double>> data_points = {
{-10.0, -0.001}, {0.0, 0.0}, {10, 0.0009}};
std::array<double, 4> params = {100, 1000, deg2rad(1.0), 0.0};
auto loss_func = [&data_points]<typename T>(const std::array<T, 4>& p) {
ellipse<T> e{T{p[0]}, T{p[1]}, T{p[2]}};
quadric<T> q = e.to_quadric().rotated_by(T{p[3]});
T loss = T{0};
for (const auto& [x, y_true] : data_points) {
T y_pred = q.slope_at(T{x});
T error = y_pred - T{y_true};
loss = loss + error * error;
}
return loss / T(data_points.size());
};
auto_diff_optimization_problem<double, 4, decltype(loss_func)> problem(loss_func, params);
auto grad = problem.gradient(params);
EXPECT_NEAR(grad[0], -2.0789313126683308e-10, 1e-15); // d/d(left_arm)
EXPECT_NEAR(grad[1], -1.7464984353858657e-12, 1e-15); // d/d(right_arm)
EXPECT_NEAR(grad[2], 1.2013025455499119e-06, 1e-15); // d/d(entrance_angle)
EXPECT_NEAR(grad[3], -2.0332702665822054e-05, 1e-15); // d/d(rotation_angle)
std::cout << "Gradient:\n";
for (size_t i = 0; i < 4; ++i) {
std::cout << grad[i] << "\n";
}
auto hess = problem.hessian(params);
// set formatting for easier reading
std::cout << std::scientific;
// set field width for alignment
std::cout << "Hessian matrix:\n";
for (size_t i = 0; i < 4; ++i) {
;
for (size_t j = 0; j < 4; ++j) {
std::cout << std::setprecision(5) << std::setw(15) << hess[i][j];
}
std::cout << "\n";
}
// log barrier
log_barrier_optimization_problem<double, 4> log_barrier_problem(
problem,
{1.0, 1.0, deg2rad(0.1), deg2rad(0.1)},
1e-3);
auto log_barrier_grad = log_barrier_problem.gradient(params);
std::cout << "Log Barrier Gradient:\n";
for (size_t i = 0; i < 4; ++i) {
std::cout << log_barrier_grad[i] << "\n";
}
}
+21
View File
@@ -0,0 +1,21 @@
#include <gtest/gtest.h>
#include "autoopt/quadric.hpp"
#include <iostream>
using namespace autoopt;
TEST(QuadricTest, ParabolaRotation) {
quadric<double> q(1.0, 0.0, 0.0, 0.0, -1.0, 0.0); // y = x^2
EXPECT_DOUBLE_EQ(q.at(-1.0), 1.0); // At x=1, y=1
EXPECT_DOUBLE_EQ(q.at(0.0), 0.0); // At x=0, y=0
EXPECT_DOUBLE_EQ(q.at(1.0), 1.0); // At x=1, y=1
double angle = M_PI / 4; // 45 degrees
quadric<double> q_rotated = q.rotated_by(angle);
EXPECT_NEAR(q_rotated.at(-1.0), -0.11729096183611623, 1e-9);
EXPECT_NEAR(q_rotated.at(0.0), 0.0, 1e-9);
EXPECT_TRUE(std::isnan(q_rotated.at(1.0))); // Expect NaN due to no real solution
}