673 lines
20 KiB
C++
673 lines
20 KiB
C++
#include <gtest/gtest.h>
|
|
#include <cmath>
|
|
#include <functional>
|
|
#include "dcalc/FindRoot.hh"
|
|
|
|
namespace sta {
|
|
|
|
class FindRootTest : public ::testing::Test {};
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Original 7 tests
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// Test finding root of f(x) = x^2 - 4 (root at x=2)
|
|
TEST_F(FindRootTest, QuadraticPositiveRoot) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x * x - 4.0;
|
|
dy = 2.0 * x;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 1.0, 3.0, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, 2.0, 1e-8);
|
|
}
|
|
|
|
// Test finding root of f(x) = x^2 - 4 (root at x=-2)
|
|
TEST_F(FindRootTest, QuadraticNegativeRoot) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x * x - 4.0;
|
|
dy = 2.0 * x;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, -3.0, -1.0, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, -2.0, 1e-8);
|
|
}
|
|
|
|
// Test finding root of f(x) = x - 1 (linear, root at x=1)
|
|
TEST_F(FindRootTest, LinearRoot) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x - 1.0;
|
|
dy = 1.0;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 0.0, 2.0, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, 1.0, 1e-8);
|
|
}
|
|
|
|
// Test finding root of f(x) = sin(x) near pi
|
|
TEST_F(FindRootTest, SinRoot) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = sin(x);
|
|
dy = cos(x);
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 2.5, 3.8, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, M_PI, 1e-6);
|
|
}
|
|
|
|
// Test finding root of f(x) = e^x - 2 (root at x=ln(2))
|
|
TEST_F(FindRootTest, ExponentialRoot) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = exp(x) - 2.0;
|
|
dy = exp(x);
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 0.0, 1.0, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, log(2.0), 1e-8);
|
|
}
|
|
|
|
// Test with tight tolerance
|
|
TEST_F(FindRootTest, TightTolerance) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x * x - 2.0;
|
|
dy = 2.0 * x;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 1.0, 2.0, 1e-14, 200, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, sqrt(2.0), 1e-12);
|
|
}
|
|
|
|
// Test the 4-argument version with pre-computed y values
|
|
TEST_F(FindRootTest, WithPrecomputedY) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x * x - 9.0;
|
|
dy = 2.0 * x;
|
|
};
|
|
bool fail = false;
|
|
// x1=2, y1=4-9=-5, x2=4, y2=16-9=7
|
|
double root = findRoot(func, 2.0, -5.0, 4.0, 7.0, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, 3.0, 1e-8);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Tolerance edge cases
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// Very tight tolerance: 1e-15 (near machine epsilon)
|
|
TEST_F(FindRootTest, VeryTightTolerance) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x - 5.0;
|
|
dy = 1.0;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 3.0, 7.0, 1e-15, 500, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, 5.0, 1e-13);
|
|
}
|
|
|
|
// Very loose tolerance: 1e-1
|
|
TEST_F(FindRootTest, LooseTolerance) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x * x - 25.0;
|
|
dy = 2.0 * x;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 3.0, 7.0, 1e-1, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
// With 10% relative tolerance, result should still be in the right ballpark
|
|
EXPECT_NEAR(root, 5.0, 0.6);
|
|
}
|
|
|
|
// Zero tolerance: convergence check becomes abs(dx) <= 0, which is only
|
|
// satisfied when dx is exactly 0. Likely hits max_iter and fails.
|
|
TEST_F(FindRootTest, ZeroTolerance) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x - 3.0;
|
|
dy = 1.0;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 1.0, 5.0, 0.0, 100, fail);
|
|
// May or may not converge -- for a linear function Newton converges in 1 step
|
|
// so dx can be exactly 0. Accept either outcome.
|
|
if (!fail) {
|
|
EXPECT_NEAR(root, 3.0, 1e-10);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Iteration limit edge cases
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// Only 1 iteration allowed
|
|
TEST_F(FindRootTest, OneIteration) {
|
|
ASSERT_NO_THROW(( [&](){
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x * x - 4.0;
|
|
dy = 2.0 * x;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 1.0, 3.0, 1e-10, 1, fail);
|
|
// With only 1 iteration, a quadratic likely won't converge to tight tol
|
|
// The algorithm may or may not fail depending on initial bisection step
|
|
// Root should still be a finite number within the bracket
|
|
EXPECT_GE(root, 1.0);
|
|
EXPECT_LE(root, 3.0);
|
|
|
|
}() ));
|
|
}
|
|
|
|
// Two iterations
|
|
TEST_F(FindRootTest, TwoIterations) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x - 7.0;
|
|
dy = 1.0;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 5.0, 9.0, 1e-10, 2, fail);
|
|
// Linear function: Newton should converge very fast
|
|
// After the initial midpoint (7.0), Newton step should nail it
|
|
if (!fail) {
|
|
EXPECT_NEAR(root, 7.0, 1e-6);
|
|
}
|
|
}
|
|
|
|
// Zero max iterations: the for-loop body never executes, so fail is set to true
|
|
TEST_F(FindRootTest, ZeroMaxIterations) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x - 1.0;
|
|
dy = 1.0;
|
|
};
|
|
bool fail = false;
|
|
findRoot(func, 0.0, 2.0, 1e-10, 0, fail);
|
|
EXPECT_TRUE(fail);
|
|
}
|
|
|
|
// Large max_iter (should still converge quickly and not hang)
|
|
TEST_F(FindRootTest, LargeMaxIter) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x * x - 16.0;
|
|
dy = 2.0 * x;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 1.0, 10.0, 1e-12, 10000, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, 4.0, 1e-10);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Special function types
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// Cubic: f(x) = x^3 - 8 (root at x=2)
|
|
TEST_F(FindRootTest, CubicRoot) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x * x * x - 8.0;
|
|
dy = 3.0 * x * x;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 1.0, 3.0, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, 2.0, 1e-8);
|
|
}
|
|
|
|
// Quartic: f(x) = x^4 - 16 (root at x=2)
|
|
TEST_F(FindRootTest, QuarticRoot) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x * x * x * x - 16.0;
|
|
dy = 4.0 * x * x * x;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 1.0, 3.0, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, 2.0, 1e-8);
|
|
}
|
|
|
|
// Exponential: f(x) = e^x - 10 (root at x=ln(10))
|
|
TEST_F(FindRootTest, ExponentialRoot2) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = exp(x) - 10.0;
|
|
dy = exp(x);
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 1.0, 4.0, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, log(10.0), 1e-8);
|
|
}
|
|
|
|
// Square root function: f(x) = sqrt(x) - 3, root at x=9
|
|
// Derivative: 1/(2*sqrt(x))
|
|
TEST_F(FindRootTest, SqrtFunctionRoot) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = sqrt(x) - 3.0;
|
|
dy = 0.5 / sqrt(x);
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 1.0, 20.0, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, 9.0, 1e-6);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Near-zero roots
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// f(x) = x - 1e-10 (root very close to zero)
|
|
// Note: convergence check is abs(dx) <= x_tol * abs(root).
|
|
// When root is near zero, the relative tolerance is very tight.
|
|
// This may require many iterations or not converge.
|
|
TEST_F(FindRootTest, NearZeroRootLinear) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x - 1e-10;
|
|
dy = 1.0;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, -1.0, 1.0, 1e-6, 200, fail);
|
|
// Newton on a linear function converges in 1-2 steps regardless of root location
|
|
if (!fail) {
|
|
EXPECT_NEAR(root, 1e-10, 1e-6);
|
|
}
|
|
}
|
|
|
|
// f(x) = x (root exactly at zero)
|
|
// Convergence test: abs(dx) <= x_tol * abs(root) = x_tol * 0 = 0
|
|
// Will likely hit max_iter because relative tolerance at root=0 requires dx=0 exactly
|
|
TEST_F(FindRootTest, RootExactlyAtZero) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x;
|
|
dy = 1.0;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, -1.0, 1.0, 1e-10, 200, fail);
|
|
// Even if fail is true, root should be very close to 0
|
|
EXPECT_NEAR(root, 0.0, 1e-6);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Negative domain
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// Root in deeply negative domain: f(x) = x + 100, root at x=-100
|
|
TEST_F(FindRootTest, NegativeDomainRoot) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x + 100.0;
|
|
dy = 1.0;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, -200.0, 0.0, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, -100.0, 1e-6);
|
|
}
|
|
|
|
// Both bracket endpoints negative: f(x) = x^2 - 1, root at x=-1
|
|
TEST_F(FindRootTest, NegativeBracketRoot) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x * x - 1.0;
|
|
dy = 2.0 * x;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, -2.0, -0.5, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, -1.0, 1e-8);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Trigonometric functions
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// sin(x) root at x=0 (bracket [-1, 1])
|
|
TEST_F(FindRootTest, SinRootAtZero) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = sin(x);
|
|
dy = cos(x);
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, -1.0, 1.0, 1e-10, 100, fail);
|
|
// Root at 0 has the relative-tolerance issue, but Newton converges fast for sin
|
|
EXPECT_NEAR(root, 0.0, 1e-4);
|
|
}
|
|
|
|
// sin(x) root at x=2*pi (bracket [5.5, 7.0])
|
|
TEST_F(FindRootTest, SinRootAt2Pi) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = sin(x);
|
|
dy = cos(x);
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 5.5, 7.0, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, 2.0 * M_PI, 1e-6);
|
|
}
|
|
|
|
// cos(x) root at x=pi/2
|
|
TEST_F(FindRootTest, CosRootAtPiOver2) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = cos(x);
|
|
dy = -sin(x);
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 1.0, 2.0, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, M_PI / 2.0, 1e-6);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Multiple roots nearby
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// f(x) = (x-1)(x-2) = x^2 - 3x + 2, roots at x=1 and x=2
|
|
// Bracket [0.5, 1.5] should find x=1
|
|
TEST_F(FindRootTest, MultipleRootsFindFirst) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = (x - 1.0) * (x - 2.0);
|
|
dy = 2.0 * x - 3.0;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 0.5, 1.5, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, 1.0, 1e-8);
|
|
}
|
|
|
|
// Same function, bracket [1.5, 2.5] should find x=2
|
|
TEST_F(FindRootTest, MultipleRootsFindSecond) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = (x - 1.0) * (x - 2.0);
|
|
dy = 2.0 * x - 3.0;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 1.5, 2.5, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, 2.0, 1e-8);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Discontinuous derivative (sharp corner)
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// f(x) = |x| - 1 with piecewise derivative.
|
|
// Root at x=1 (bracket [0.5, 2.0] avoids the corner at 0)
|
|
TEST_F(FindRootTest, AbsValueRoot) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = fabs(x) - 1.0;
|
|
dy = (x >= 0.0) ? 1.0 : -1.0;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 0.5, 2.0, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, 1.0, 1e-8);
|
|
}
|
|
|
|
// f(x) = |x| - 1, root at x=-1
|
|
TEST_F(FindRootTest, AbsValueNegativeRoot) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = fabs(x) - 1.0;
|
|
dy = (x >= 0.0) ? 1.0 : -1.0;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, -2.0, -0.5, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, -1.0, 1e-8);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Very flat function (slow convergence)
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// f(x) = (x - 3)^5 has a repeated root at x=3 where the derivative is also
|
|
// zero. Newton-Raphson divides by dy which becomes 0, producing NaN.
|
|
// The algorithm is expected to fail on this degenerate case.
|
|
TEST_F(FindRootTest, FlatFifthOrderRootFails) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
double d = x - 3.0;
|
|
double d2 = d * d;
|
|
double d4 = d2 * d2;
|
|
y = d4 * d; // (x-3)^5
|
|
dy = 5.0 * d4; // 5*(x-3)^4
|
|
};
|
|
bool fail = false;
|
|
findRoot(func, 2.0, 4.0, 1e-6, 500, fail);
|
|
// The algorithm is expected to fail because dy -> 0 at the root
|
|
EXPECT_TRUE(fail);
|
|
}
|
|
|
|
// A function that is very flat near the root but still has nonzero derivative
|
|
// at the root: f(x) = sinh(x - 3) which is ~0 near x=3 but never has dy=0.
|
|
// sinh is flat near 0 (sinh(e) ~ e for small e) but derivative cosh(e) >= 1.
|
|
TEST_F(FindRootTest, FlatSinhRoot) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = sinh(x - 3.0);
|
|
dy = cosh(x - 3.0);
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 2.0, 4.0, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, 3.0, 1e-6);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Very steep function (fast convergence)
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// f(x) = 1000*(x - 5), root at x=5. Very steep gradient.
|
|
TEST_F(FindRootTest, SteepLinearRoot) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = 1000.0 * (x - 5.0);
|
|
dy = 1000.0;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 3.0, 7.0, 1e-12, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, 5.0, 1e-10);
|
|
}
|
|
|
|
// f(x) = 1e6 * (x - 2), very steep
|
|
TEST_F(FindRootTest, VerySteepLinearRoot) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = 1e6 * (x - 2.0);
|
|
dy = 1e6;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 1.0, 3.0, 1e-14, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, 2.0, 1e-12);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Large bracket
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// f(x) = x - 42, bracket [-1000, 1000]
|
|
TEST_F(FindRootTest, LargeBracket) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x - 42.0;
|
|
dy = 1.0;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, -1000.0, 1000.0, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, 42.0, 1e-6);
|
|
}
|
|
|
|
// Quadratic with large bracket: f(x) = x^2 - 100, root at 10
|
|
TEST_F(FindRootTest, LargeBracketQuadratic) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x * x - 100.0;
|
|
dy = 2.0 * x;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 1.0, 1000.0, 1e-10, 200, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, 10.0, 1e-6);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Small bracket
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// f(x) = x - 1.0, bracket [0.999999, 1.000001] (very tight bracket around root)
|
|
TEST_F(FindRootTest, SmallBracket) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x - 1.0;
|
|
dy = 1.0;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 0.999999, 1.000001, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, 1.0, 1e-6);
|
|
}
|
|
|
|
// Quadratic with very small bracket around root=2
|
|
TEST_F(FindRootTest, SmallBracketQuadratic) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x * x - 4.0;
|
|
dy = 2.0 * x;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 1.9999, 2.0001, 1e-12, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, 2.0, 1e-8);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Both overloads tested together
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// Compare 2-arg and 4-arg overloads produce same result
|
|
TEST_F(FindRootTest, OverloadsProduceSameResult) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x * x * x - 27.0;
|
|
dy = 3.0 * x * x;
|
|
};
|
|
|
|
bool fail_2arg = false;
|
|
double root_2arg = findRoot(func, 2.0, 4.0, 1e-12, 100, fail_2arg);
|
|
|
|
// Pre-compute y values for 4-arg version
|
|
double y1 = 2.0 * 2.0 * 2.0 - 27.0; // 8 - 27 = -19
|
|
double y2 = 4.0 * 4.0 * 4.0 - 27.0; // 64 - 27 = 37
|
|
bool fail_4arg = false;
|
|
double root_4arg = findRoot(func, 2.0, y1, 4.0, y2, 1e-12, 100, fail_4arg);
|
|
|
|
EXPECT_FALSE(fail_2arg);
|
|
EXPECT_FALSE(fail_4arg);
|
|
EXPECT_NEAR(root_2arg, 3.0, 1e-10);
|
|
EXPECT_NEAR(root_4arg, 3.0, 1e-10);
|
|
EXPECT_NEAR(root_2arg, root_4arg, 1e-14);
|
|
}
|
|
|
|
// 4-arg overload: x1 endpoint is exact root (y1 == 0)
|
|
TEST_F(FindRootTest, FourArgX1IsRoot) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x - 5.0;
|
|
dy = 1.0;
|
|
};
|
|
bool fail = false;
|
|
// y1 = 5 - 5 = 0
|
|
double root = findRoot(func, 5.0, 0.0, 8.0, 3.0, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_DOUBLE_EQ(root, 5.0);
|
|
}
|
|
|
|
// 4-arg overload: x2 endpoint is exact root (y2 == 0)
|
|
TEST_F(FindRootTest, FourArgX2IsRoot) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x - 5.0;
|
|
dy = 1.0;
|
|
};
|
|
bool fail = false;
|
|
// y2 = 5 - 5 = 0
|
|
double root = findRoot(func, 2.0, -3.0, 5.0, 0.0, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_DOUBLE_EQ(root, 5.0);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Same-sign y values (should fail)
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// Both endpoints positive: should fail
|
|
TEST_F(FindRootTest, BothEndpointsPositiveFails) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x * x + 1.0; // Always positive, no real root
|
|
dy = 2.0 * x;
|
|
};
|
|
bool fail = false;
|
|
findRoot(func, 1.0, 3.0, 1e-10, 100, fail);
|
|
EXPECT_TRUE(fail);
|
|
}
|
|
|
|
// Both endpoints negative: should fail
|
|
TEST_F(FindRootTest, BothEndpointsNegativeFails) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = -x * x - 1.0; // Always negative
|
|
dy = -2.0 * x;
|
|
};
|
|
bool fail = false;
|
|
findRoot(func, -3.0, 3.0, 1e-10, 100, fail);
|
|
EXPECT_TRUE(fail);
|
|
}
|
|
|
|
// 4-arg version: same-sign y values
|
|
TEST_F(FindRootTest, FourArgSameSignFails) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x * x;
|
|
dy = 2.0 * x;
|
|
};
|
|
bool fail = false;
|
|
// Both y values positive
|
|
findRoot(func, 1.0, 1.0, 2.0, 4.0, 1e-10, 100, fail);
|
|
EXPECT_TRUE(fail);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Symmetry test
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// f(x) = x^2 - 4: bracket [0, 3] finds +2, bracket [-3, 0] finds -2
|
|
TEST_F(FindRootTest, SymmetryPositiveBracket) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x * x - 4.0;
|
|
dy = 2.0 * x;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, 0.5, 3.0, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, 2.0, 1e-8);
|
|
}
|
|
|
|
TEST_F(FindRootTest, SymmetryNegativeBracket) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x * x - 4.0;
|
|
dy = 2.0 * x;
|
|
};
|
|
bool fail = false;
|
|
double root = findRoot(func, -3.0, -0.5, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, -2.0, 1e-8);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Swapped bracket order (x1 > x2)
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// The algorithm should work regardless of bracket order
|
|
TEST_F(FindRootTest, SwappedBracketOrder) {
|
|
FindRootFunc func = [](double x, double &y, double &dy) {
|
|
y = x - 3.0;
|
|
dy = 1.0;
|
|
};
|
|
bool fail = false;
|
|
// x1=5 > x2=1 (reversed order)
|
|
double root = findRoot(func, 5.0, 1.0, 1e-10, 100, fail);
|
|
EXPECT_FALSE(fail);
|
|
EXPECT_NEAR(root, 3.0, 1e-8);
|
|
}
|
|
|
|
} // namespace sta
|