Add boundary test coverage: stableNorm, LinSpaced, complex GEMV, triangular solve

libeigen/eigen!2291

Co-authored-by: Rasmus Munk Larsen <rmlarsen@gmail.com>
This commit is contained in:
Rasmus Munk Larsen
2026-03-12 18:15:30 -07:00
parent 6b9275d1a8
commit c1faa74738
4 changed files with 401 additions and 0 deletions

View File

@@ -293,6 +293,130 @@ void nullary_internal_logic() {
}
}
// Test LinSpaced at vectorization boundary sizes.
// The packetOp in linspaced_op_impl uses mask/select logic to handle
// the last partial packet (when vector size is not a multiple of PacketSize).
// This exercises those boundaries with element-by-element verification.
template <typename Scalar>
void linspaced_boundary() {
typedef typename NumTraits<Scalar>::Real RealScalar;
const Index PS = internal::packet_traits<Scalar>::size;
const Index sizes[] = {1, 2, 3, PS - 1, PS, PS + 1, 2 * PS - 1, 2 * PS, 2 * PS + 1, 4 * PS, 4 * PS + 1};
typedef Matrix<Scalar, Dynamic, 1> Vec;
for (int si = 0; si < 11; ++si) {
Index n = sizes[si];
if (n <= 0) continue;
Scalar low(1), high(100);
Vec v = Vec::LinSpaced(n, low, high);
// With n==1, LinSpaced returns [high] by design.
if (n == 1) {
VERIFY_IS_EQUAL(v(0), high);
} else {
VERIFY_IS_EQUAL(v(0), low);
VERIFY_IS_EQUAL(v(n - 1), high);
// Verify monotonicity.
for (Index k = 1; k < n; ++k) {
VERIFY(numext::real(v(k)) >= numext::real(v(k - 1)));
}
// Verify against scalar reference computation.
for (Index k = 0; k < n; ++k) {
Scalar ref = Scalar(low + (high - low) * RealScalar(k) / RealScalar(n - 1));
VERIFY_IS_APPROX(v(k), ref);
}
}
}
// Test the "flip" path: when |high| < |low|, the implementation uses
// a reversed computation for better precision. Verify at packet boundaries.
for (int si = 0; si < 11; ++si) {
Index n = sizes[si];
if (n <= 0 || n == 1) continue; // skip n=1, flip irrelevant for single element
Scalar low(1000), high(1); // |high| < |low| triggers flip
Vec v = Vec::LinSpaced(n, low, high);
VERIFY_IS_EQUAL(v(0), low);
VERIFY_IS_EQUAL(v(n - 1), high);
// Verify monotonicity (decreasing).
for (Index k = 1; k < n; ++k) {
VERIFY(numext::real(v(k)) <= numext::real(v(k - 1)));
}
// Verify against scalar reference.
for (Index k = 0; k < n; ++k) {
Scalar ref = Scalar(low + (high - low) * RealScalar(k) / RealScalar(n - 1));
VERIFY_IS_APPROX(v(k), ref);
}
}
}
// Test integer LinSpaced divisor path.
// When (abs(high - low) + 1) < num_steps, the integer LinSpaced uses
// a divisor-based formula instead of multiplication. This path is
// barely covered by existing tests which use random ranges.
template <int>
void linspaced_integer_divisor() {
typedef Matrix<int, Dynamic, 1> VecI;
// Case: num_steps much larger than range → triggers divisor path.
// LinSpaced(12, 0, 5): 12 steps over range [0,5], so range+1=6, 6 < 12.
{
VecI v = VecI::LinSpaced(12, 0, 5);
VERIFY_IS_EQUAL(v(0), 0);
// All values must be in [0, 5].
for (Index k = 0; k < 12; ++k) {
VERIFY(v(k) >= 0 && v(k) <= 5);
}
// Must be non-decreasing.
for (Index k = 1; k < 12; ++k) {
VERIFY(v(k) >= v(k - 1));
}
// Each integer 0-5 should appear at least once.
for (int val = 0; val <= 5; ++val) {
bool found = false;
for (Index k = 0; k < 12; ++k) {
if (v(k) == val) {
found = true;
break;
}
}
VERIFY(found);
}
}
// Case: range exactly divides steps → each value should appear equally.
// LinSpaced(20, 0, 3): range+1=4, 20%4==0, so each of 0,1,2,3 appears 5 times.
{
VecI v = VecI::LinSpaced(20, 0, 3);
VERIFY_IS_EQUAL(v(0), 0);
for (Index k = 0; k < 20; ++k) {
VERIFY(v(k) >= 0 && v(k) <= 3);
}
for (Index k = 1; k < 20; ++k) {
VERIFY(v(k) >= v(k - 1));
}
}
// Reverse: LinSpaced(12, 5, 0) should be reverse of LinSpaced(12, 0, 5).
{
VecI fwd = VecI::LinSpaced(12, 0, 5);
VecI rev = VecI::LinSpaced(12, 5, 0);
VERIFY_IS_APPROX(fwd, rev.reverse());
}
// Single step: always returns high.
{
VecI v = VecI::LinSpaced(1, 3, 7);
VERIFY_IS_EQUAL(v(0), 7);
}
}
EIGEN_DECLARE_TEST(nullary) {
CALL_SUBTEST_1(testMatrixType(Matrix2d()));
CALL_SUBTEST_2(testMatrixType(MatrixXcf(internal::random<int>(1, 300), internal::random<int>(1, 300))));
@@ -318,4 +442,11 @@ EIGEN_DECLARE_TEST(nullary) {
CALL_SUBTEST_6(bug1630<0>());
CALL_SUBTEST_9(nullary_overflow<0>());
CALL_SUBTEST_10(nullary_internal_logic<0>());
// LinSpaced at vectorization boundaries (deterministic, outside g_repeat).
CALL_SUBTEST_11(linspaced_boundary<float>());
CALL_SUBTEST_11(linspaced_boundary<double>());
// Integer LinSpaced divisor path tests.
CALL_SUBTEST_12(linspaced_integer_divisor<0>());
}