"""Tests for the BFGS fallback solver.""" import math import pytest from kindred_solver.bfgs import bfgs_solve from kindred_solver.expr import Const, Var from kindred_solver.params import ParamTable class TestBFGSBasic: def test_single_linear(self): """Solve x - 3 = 0.""" pt = ParamTable() x = pt.add("x", 0.0) assert bfgs_solve([x - Const(3.0)], pt) is True assert abs(pt.get_value("x") - 3.0) < 1e-8 def test_single_quadratic(self): """Solve x^2 - 4 = 0 from x=1 → x=2.""" pt = ParamTable() x = pt.add("x", 1.0) assert bfgs_solve([x * x - Const(4.0)], pt) is True assert abs(pt.get_value("x") - 2.0) < 1e-8 def test_two_variables(self): """Solve x + y = 5, x - y = 1.""" pt = ParamTable() x = pt.add("x", 0.0) y = pt.add("y", 0.0) assert bfgs_solve([x + y - Const(5.0), x - y - Const(1.0)], pt) is True assert abs(pt.get_value("x") - 3.0) < 1e-8 assert abs(pt.get_value("y") - 2.0) < 1e-8 def test_empty_system(self): pt = ParamTable() assert bfgs_solve([], pt) is True def test_with_quat_renorm(self): """Quaternion re-normalization during BFGS.""" pt = ParamTable() qw = pt.add("qw", 0.9) qx = pt.add("qx", 0.1) qy = pt.add("qy", 0.1) qz = pt.add("qz", 0.1) r = qw * qw + qx * qx + qy * qy + qz * qz - Const(1.0) groups = [("qw", "qx", "qy", "qz")] assert bfgs_solve([r], pt, quat_groups=groups) is True w, x, y, z = (pt.get_value(n) for n in ["qw", "qx", "qy", "qz"]) norm = math.sqrt(w**2 + x**2 + y**2 + z**2) assert abs(norm - 1.0) < 1e-8 class TestBFGSGeometric: def test_distance_constraint(self): """x^2 - 25 = 0 from x=3 → x=5.""" pt = ParamTable() x = pt.add("x", 3.0) assert bfgs_solve([x * x - Const(25.0)], pt) is True assert abs(pt.get_value("x") - 5.0) < 1e-8 def test_difficult_initial_guess(self): """BFGS should handle worse initial guesses than Newton.""" pt = ParamTable() x = pt.add("x", 100.0) y = pt.add("y", -50.0) residuals = [x + y - Const(5.0), x - y - Const(1.0)] assert bfgs_solve(residuals, pt) is True assert abs(pt.get_value("x") - 3.0) < 1e-6 assert abs(pt.get_value("y") - 2.0) < 1e-6