#include "fesa/solvers/eigen/mkl_feast_eigen_solver.h" #include #include #include #include #include #include #include #include #include #include "fesa/fem/dof_manager.h" #include "fesa/math/sparse_matrix.h" #include "solvers/eigen/mkl_feast_eigen_solver_test_seam.h" namespace { fesa::SparseMatrix MakeDenseCsr(const std::size_t size, const std::vector& values) { EXPECT_EQ(values.size(), size * size); fesa::SparsePattern pattern; std::vector contributions; pattern.row_offsets.push_back(0U); for (std::size_t row = 0U; row < size; ++row) { for (std::size_t column = 0U; column < size; ++column) { pattern.column_indices.push_back(column); contributions.push_back( {row, column, values[row * size + column], row, column}); } pattern.row_offsets.push_back(pattern.column_indices.size()); } auto matrix = fesa::SparseMatrix::FromCoo(size, size, std::move(contributions), pattern); EXPECT_TRUE(matrix.HasValue()); return std::move(matrix.Value()); } void ExpectUnitGram(const fesa::Matrix& vectors) { for (std::size_t left = 0U; left < vectors.Columns(); ++left) { for (std::size_t right = 0U; right < vectors.Columns(); ++right) { double dot = 0.0; for (std::size_t row = 0U; row < vectors.Rows(); ++row) { dot += vectors(row, left) * vectors(row, right); } EXPECT_NEAR(dot, left == right ? 1.0 : 0.0, 1.0e-10); } } } void RequireSuccess(const fesa::Result& result) { if (result.HasValue()) return; const auto& diagnostics = result.GetStatus().Diagnostics(); ASSERT_FALSE(diagnostics.empty()); ADD_FAILURE() << diagnostics.front().code << " " << diagnostics.front().entity_identity << " " << diagnostics.front().message; } void ExpectFailureCode(const fesa::Result& result, const char* code) { ASSERT_FALSE(result.HasValue()); ASSERT_FALSE(result.GetStatus().Diagnostics().empty()); EXPECT_EQ(result.GetStatus().Diagnostics().front().code, code); } void ExpectSameBackendCalls(const std::vector& first, const std::vector& second) { ASSERT_EQ(first.size(), second.size()); for (std::size_t index = 0U; index < first.size(); ++index) { EXPECT_EQ(first[index].ordinal, second[index].ordinal); EXPECT_EQ(first[index].kind, second[index].kind); EXPECT_EQ(first[index].purpose, second[index].purpose); EXPECT_DOUBLE_EQ(first[index].lower, second[index].lower); EXPECT_DOUBLE_EQ(first[index].upper, second[index].upper); EXPECT_EQ(first[index].allocated_m0, second[index].allocated_m0); EXPECT_EQ(first[index].info, second[index].info); EXPECT_EQ(first[index].count_available, second[index].count_available); EXPECT_EQ(first[index].count, second[index].count); EXPECT_EQ(first[index].outcome, second[index].outcome); EXPECT_EQ(first[index].decision_reason, second[index].decision_reason); } } void ExpectSameProjector(const fesa::Matrix& first, const fesa::Matrix& second, const std::size_t columns) { ASSERT_EQ(first.Rows(), second.Rows()); double squared_difference = 0.0; for (std::size_t row = 0U; row < first.Rows(); ++row) { for (std::size_t column = 0U; column < first.Rows(); ++column) { double first_value = 0.0; double second_value = 0.0; for (std::size_t mode = 0U; mode < columns; ++mode) { first_value += first(row, mode) * first(column, mode); second_value += second(row, mode) * second(column, mode); } const double difference = first_value - second_value; squared_difference += difference * difference; } } EXPECT_LE(std::sqrt(squared_difference / (2.0 * static_cast(columns))), 1.0e-6); } void ExpectThreadSettingRestored( const fesa::Result& result, const char* code) { ExpectFailureCode(result, code); const int restored = mkl_set_num_threads_local(2); EXPECT_EQ(restored, 2); } } // namespace TEST(MklFeastEigenSolver, SolvesN1AndRetainsBackendEvidence) { const auto matrix = MakeDenseCsr(1U, {4.0}); fesa::MklFeastEigenSolver solver; const auto result = solver.Solve(matrix, 1U, 1.0); RequireSuccess(result); if (!result.HasValue()) return; EXPECT_EQ(result.Value().requested_modes, 1U); ASSERT_EQ(result.Value().eigenvalues.size(), 1U); EXPECT_NEAR(result.Value().eigenvalues[0U], 4.0, 1.0e-10); EXPECT_DOUBLE_EQ(result.Value().operator_scale, 4.0); EXPECT_EQ(result.Value().eigenvectors.Rows(), 1U); EXPECT_EQ(result.Value().eigenvectors.Columns(), 1U); EXPECT_EQ(result.Value().thread_count, 1U); ASSERT_EQ(result.Value().base_fpm.size(), 128U); EXPECT_EQ(result.Value().base_fpm[5U], 0); ASSERT_FALSE(result.Value().backend_calls.empty()); EXPECT_EQ(result.Value().backend_calls.front().purpose, "total-enclosure"); EXPECT_EQ(result.Value().backend_calls.back().kind, "solve"); EXPECT_TRUE(result.Value().backend_calls.back().count_available); EXPECT_EQ(result.Value().backend_calls.back().count, result.Value().eigenvalues.size()); EXPECT_TRUE(result.Value().backend_calls.back().loop_available); EXPECT_TRUE(result.Value().backend_calls.back().epsout_available); EXPECT_EQ(result.Value().backend_calls.back().outcome, "accepted"); EXPECT_EQ(result.Value().backend_calls.back().decision_reason, "accepted"); EXPECT_FALSE(result.Value().upper_search_termination.empty()); ExpectUnitGram(result.Value().eigenvectors); } TEST(MklFeastEigenSolver, SolvesAllZeroWithExplicitPhysicalScale) { const auto matrix = MakeDenseCsr(2U, {0.0, 0.0, 0.0, 0.0}); fesa::MklFeastEigenSolver solver; const auto result = solver.Solve(matrix, 2U, 7.0); RequireSuccess(result); if (!result.HasValue()) return; ASSERT_EQ(result.Value().eigenvalues.size(), 2U); EXPECT_DOUBLE_EQ(result.Value().operator_scale, 7.0); EXPECT_DOUBLE_EQ(result.Value().eigenvalues[0U], 0.0); EXPECT_DOUBLE_EQ(result.Value().eigenvalues[1U], 0.0); ExpectUnitGram(result.Value().eigenvectors); } TEST(MklFeastEigenSolver, PreservesRepeatedMultiplicityAndStableOrder) { const auto matrix = MakeDenseCsr(3U, {2.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 5.0}); fesa::MklFeastEigenSolver solver; const auto first = solver.Solve(matrix, 3U, 1.0); const auto second = solver.Solve(matrix, 3U, 1.0); const auto third = solver.Solve(matrix, 3U, 1.0); RequireSuccess(first); RequireSuccess(second); RequireSuccess(third); if (!first.HasValue() || !second.HasValue() || !third.HasValue()) return; ASSERT_EQ(first.Value().eigenvalues.size(), 3U); EXPECT_NEAR(first.Value().eigenvalues[0U], 2.0, 1.0e-10); EXPECT_NEAR(first.Value().eigenvalues[1U], 2.0, 1.0e-10); EXPECT_NEAR(first.Value().eigenvalues[2U], 5.0, 1.0e-10); EXPECT_EQ(first.Value().column_ids, second.Value().column_ids); EXPECT_EQ(first.Value().column_ids, third.Value().column_ids); EXPECT_EQ(first.Value().eigenvalues, second.Value().eigenvalues); EXPECT_EQ(first.Value().eigenvalues, third.Value().eigenvalues); ExpectSameBackendCalls(first.Value().backend_calls, second.Value().backend_calls); ExpectSameBackendCalls(first.Value().backend_calls, third.Value().backend_calls); ExpectSameProjector(first.Value().eigenvectors, second.Value().eigenvectors, 2U); ExpectSameProjector(first.Value().eigenvectors, third.Value().eigenvectors, 2U); ExpectUnitGram(first.Value().eigenvectors); } TEST(MklFeastEigenSolver, UsesGuardedPrefixWithoutDroppingRepeatedCluster) { constexpr std::size_t kSize = 64U; std::vector values(kSize * kSize, 0.0); for (std::size_t index = 0U; index < kSize; ++index) { values[index * kSize + index] = index < 40U ? 1.0 : index - 38U; } const auto matrix = MakeDenseCsr(kSize, values); fesa::MklFeastEigenSolver solver; const auto one_mode = solver.Solve(matrix, 1U, 1.0); const auto forty_first = solver.Solve(matrix, 41U, 1.0); RequireSuccess(one_mode); RequireSuccess(forty_first); if (!one_mode.HasValue() || !forty_first.HasValue()) return; EXPECT_GE(one_mode.Value().eigenvalues.size(), 40U); EXPECT_GE(forty_first.Value().eigenvalues.size(), 41U); for (std::size_t index = 0U; index < 40U; ++index) { EXPECT_NEAR(one_mode.Value().eigenvalues[index], 1.0, 1.0e-10); } EXPECT_NEAR(forty_first.Value().eigenvalues[40U], 2.0, 1.0e-10); EXPECT_EQ(one_mode.Value().backend_calls.back().kind, "solve"); EXPECT_EQ(forty_first.Value().backend_calls.back().kind, "solve"); } TEST(MklFeastEigenSolver, RejectsSignificantNegativeSpectrum) { const auto matrix = MakeDenseCsr(1U, {-2.0}); fesa::MklFeastEigenSolver solver; const auto result = solver.Solve(matrix, 1U, 1.0); ASSERT_FALSE(result.HasValue()); EXPECT_EQ(result.GetStatus().Category(), fesa::FailureCategory::kSolver); ASSERT_EQ(result.GetStatus().Diagnostics().size(), 1U); EXPECT_EQ(result.GetStatus().Diagnostics()[0U].code, "negative-eigenvalue-spectrum"); } TEST(MklFeastEigenSolver, KeepsCloseDistinctModesSeparated) { const auto matrix = MakeDenseCsr(2U, {1.0, 0.0, 0.0, 1.001}); fesa::MklFeastEigenSolver solver; const auto result = solver.Solve(matrix, 2U, 1.0); RequireSuccess(result); if (!result.HasValue()) return; ASSERT_EQ(result.Value().eigenvalues.size(), 2U); EXPECT_NEAR(result.Value().eigenvalues[0U], 1.0, 1.0e-10); EXPECT_NEAR(result.Value().eigenvalues[1U], 1.001, 1.0e-10); EXPECT_NEAR(std::abs(result.Value().eigenvectors(0U, 0U)), 1.0, 1.0e-10); EXPECT_NEAR(std::abs(result.Value().eigenvectors(1U, 1U)), 1.0, 1.0e-10); } TEST(MklFeastEigenSolver, RestoresCallingThreadLocalSetting) { const auto matrix = MakeDenseCsr(1U, {4.0}); const int prior = mkl_set_num_threads_local(2); fesa::MklFeastEigenSolver solver; const auto result = solver.Solve(matrix, 1U, 1.0); const int restored = mkl_set_num_threads_local(2); static_cast(mkl_set_num_threads_local(prior)); RequireSuccess(result); if (!result.HasValue()) return; EXPECT_EQ(restored, 2); } TEST(MklFeastEigenSolver, RejectsInvalidRequestAndZeroScale) { const auto matrix = MakeDenseCsr(1U, {0.0}); fesa::MklFeastEigenSolver solver; const auto zero_request = solver.Solve(matrix, 0U, 1.0); ASSERT_FALSE(zero_request.HasValue()); EXPECT_EQ(zero_request.GetStatus().Diagnostics()[0U].code, "feast-invalid-dimension"); const auto invalid_scale = solver.Solve(matrix, 1U, 0.0); ASSERT_FALSE(invalid_scale.HasValue()); EXPECT_EQ(invalid_scale.GetStatus().Diagnostics()[0U].code, "feast-invalid-zero-scale"); } TEST(MklFeastEigenSolver, PreservesLowestPrefixForZeroAndPositiveSpectra) { const std::vector> spectra{{2.0}, {0.0, 0.0, 0.0, 2.0, 5.0, 9.0}, {1.0, 2.0, 4.0, 8.0}, {0.0, 0.0, 0.0, 0.0}}; const std::vector> requests{ {1U}, {1U, 4U, 6U}, {1U, 2U, 4U}, {1U, 4U}}; const fesa::MklFeastEigenSolver solver; for (std::size_t case_index = 0U; case_index < spectra.size(); ++case_index) { const auto& spectrum = spectra[case_index]; const auto n = spectrum.size(); std::vector values(n * n, 0.0); for (std::size_t i = 0U; i < n; ++i) values[i * n + i] = spectrum[i]; const auto matrix = MakeDenseCsr(n, values); for (const auto requested : requests[case_index]) { const auto result = solver.Solve(matrix, requested, 1.0); RequireSuccess(result); ASSERT_TRUE(result.HasValue()); ASSERT_GE(result.Value().eigenvalues.size(), requested); for (std::size_t i = 0U; i < requested; ++i) EXPECT_NEAR(result.Value().eigenvalues[i], spectrum[i], 1e-10); ExpectUnitGram(result.Value().eigenvectors); } } } TEST(MklFeastEigenSolver, ResolvesCloseDistinctSpectrumAfterGivensCongruence) { const std::array spectrum{1.0, 1.0 + std::ldexp(1.0, -28), 1.0 + std::ldexp(1.0, -27), 8.0}; const std::array, 4> q{{{{0.6, 0.0, 0.0, -0.8}}, {{0.0, 0.6, -0.8, 0.0}}, {{0.0, 0.8, 0.6, 0.0}}, {{0.8, 0.0, 0.0, 0.6}}}}; std::vector values(16U, 0.0); for (std::size_t i = 0U; i < 4U; ++i) for (std::size_t j = 0U; j < 4U; ++j) for (std::size_t k = 0U; k < 4U; ++k) values[4U * i + j] += q[i][k] * spectrum[k] * q[j][k]; const auto result = fesa::MklFeastEigenSolver{}.Solve(MakeDenseCsr(4U, values), 2U, 1.0); RequireSuccess(result); ASSERT_TRUE(result.HasValue()); ASSERT_EQ(result.Value().eigenvalues.size(), 4U); for (std::size_t i = 0U; i < 4U; ++i) EXPECT_NEAR(result.Value().eigenvalues[i], spectrum[i], 1e-12); EXPECT_GT(result.Value().eigenvalues[1U] - result.Value().eigenvalues[0U], 1e-9); ExpectUnitGram(result.Value().eigenvectors); } TEST(MklFeastEigenSolver, RejectsFatalCountStatusWithoutRetry) { std::size_t hook_calls = 0U; fesa::internal::ScopedFeastBackendTestHook hook( [&](const fesa::internal::FeastBackendTestCall& call, fesa::internal::FeastBackendTestOutputs& outputs) { ++hook_calls; if (call.count && call.ordinal == 0U) outputs.info = -1; }); const auto result = fesa::MklFeastEigenSolver{}.Solve( MakeDenseCsr(1U, {2.0}), 1U, 1.0); ExpectFailureCode(result, "eigen-feast-failed"); EXPECT_EQ(hook_calls, 1U); } TEST(MklFeastEigenSolver, BoundsCountCallsAndUpperSearchReservations) { constexpr std::size_t kMaximum = (std::numeric_limits::max)(); EXPECT_TRUE(fesa::internal::CanCallFeast(511U)); EXPECT_FALSE(fesa::internal::CanCallFeast(512U)); EXPECT_FALSE(fesa::internal::CanCallFeast(kMaximum)); EXPECT_TRUE(fesa::internal::CanStartUpperCandidate(363U)); EXPECT_FALSE(fesa::internal::CanStartUpperCandidate(364U)); EXPECT_FALSE(fesa::internal::CanStartUpperCandidate(kMaximum)); } TEST(MklFeastEigenSolver, BoundsPrivateWorkspaceProductsBeforeAllocation) { constexpr std::size_t kMaximum = (std::numeric_limits::max)(); EXPECT_TRUE(fesa::internal::ProductFits(0U, kMaximum)); EXPECT_TRUE(fesa::internal::ProductFits(1U, kMaximum)); EXPECT_FALSE(fesa::internal::ProductFits(kMaximum, 2U)); const std::size_t byte_limit = static_cast( (std::numeric_limits::max)() / sizeof(double)); const std::size_t storage_limit = (std::min)(byte_limit, std::vector{}.max_size()); EXPECT_TRUE(fesa::internal::FitsDoubleStorage(0U)); EXPECT_TRUE(fesa::internal::FitsDoubleStorage(storage_limit)); EXPECT_FALSE(fesa::internal::FitsDoubleStorage(kMaximum)); if (storage_limit < kMaximum) EXPECT_FALSE(fesa::internal::FitsDoubleStorage(storage_limit + 1U)); } TEST(MklFeastEigenSolver, TranslatesCountWorkspaceAllocationFailure) { const int prior = mkl_set_num_threads_local(2); fesa::internal::ScopedFeastWorkspaceTestHook hook([](const bool count) { if (count) throw std::bad_alloc(); }); const auto result = fesa::MklFeastEigenSolver{}.Solve( MakeDenseCsr(1U, {2.0}), 1U, 1.0); ExpectThreadSettingRestored(result, "eigen-allocation-failed"); static_cast(mkl_set_num_threads_local(prior)); } TEST(MklFeastEigenSolver, TranslatesSolveWorkspaceAllocationFailure) { const int prior = mkl_set_num_threads_local(2); fesa::internal::ScopedFeastWorkspaceTestHook hook([](const bool count) { if (!count) throw std::bad_alloc(); }); const auto result = fesa::MklFeastEigenSolver{}.Solve( MakeDenseCsr(1U, {2.0}), 1U, 1.0); ExpectThreadSettingRestored(result, "eigen-allocation-failed"); static_cast(mkl_set_num_threads_local(prior)); } TEST(MklFeastEigenSolver, StopsInitialInfoOneAfterThreeCountCalls) { std::size_t count_calls = 0U; fesa::internal::ScopedFeastBackendTestHook hook( [&](const fesa::internal::FeastBackendTestCall& call, fesa::internal::FeastBackendTestOutputs& outputs) { if (call.count) { ++count_calls; outputs.info = 1; } }); const auto result = fesa::MklFeastEigenSolver{}.Solve( MakeDenseCsr(1U, {2.0}), 1U, 1.0); ExpectFailureCode(result, "eigen-feast-failed"); EXPECT_EQ(count_calls, 3U); } TEST(MklFeastEigenSolver, RejectsFinalCountHistoryMismatch) { fesa::internal::ScopedFeastBackendTestHook hook( [](const fesa::internal::FeastBackendTestCall& call, fesa::internal::FeastBackendTestOutputs& outputs) { // n=1 has total enclosure at ordinal 0 and final count at ordinal 1. if (call.count && call.ordinal == 1U) outputs.m0 = 0; }); const auto result = fesa::MklFeastEigenSolver{}.Solve( MakeDenseCsr(1U, {2.0}), 1U, 1.0); ExpectFailureCode(result, "eigen-count-inconsistent"); } TEST(MklFeastEigenSolver, RejectsDecreasingCountAcrossDistinctUpperGuards) { constexpr std::size_t kSize = 11U; std::vector values(kSize * kSize, 0.0); for (std::size_t index = 0U; index < kSize; ++index) values[index * kSize + index] = static_cast(index + 1U); fesa::internal::ScopedFeastBackendTestHook hook( [kSize](const fesa::internal::FeastBackendTestCall& call, fesa::internal::FeastBackendTestOutputs& outputs) { // Ordinals 1 and 2 are the first candidate's increasing upper guards. if (call.count && call.ordinal == 1U) outputs.m0 = kSize; if (call.count && call.ordinal == 2U) outputs.m0 = kSize - 1U; }); const auto result = fesa::MklFeastEigenSolver{}.Solve(MakeDenseCsr(kSize, values), 1U, 1.0); ExpectFailureCode(result, "eigen-count-inconsistent"); } TEST(MklFeastEigenSolver, PreservesRawNegativeBoundaryAndRejectsBelowIt) { const double threshold = -1.0e-9; const std::vector accepted_negatives{ -1.0e-12, threshold}; for (const double negative : accepted_negatives) { fesa::internal::ScopedFeastBackendTestHook hook( [negative](const fesa::internal::FeastBackendTestCall& call, fesa::internal::FeastBackendTestOutputs& outputs) { if (!call.count) { ASSERT_EQ(outputs.eigenvalues.size(), 3U); outputs.eigenvalues = {negative, 0.0, 1.0}; } }); const auto result = fesa::MklFeastEigenSolver{}.Solve( MakeDenseCsr(3U, {negative, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0}), 3U, 1.0); RequireSuccess(result); if (!result.HasValue()) continue; ASSERT_EQ(result.Value().eigenvalues.size(), 3U); EXPECT_DOUBLE_EQ(result.Value().eigenvalues[0U], negative); EXPECT_DOUBLE_EQ(result.Value().eigenvalues[1U], 0.0); EXPECT_DOUBLE_EQ(result.Value().eigenvalues[2U], 1.0); } const double below_threshold = std::nextafter(threshold, -(std::numeric_limits::infinity)()); fesa::internal::ScopedFeastBackendTestHook hook( [below_threshold](const fesa::internal::FeastBackendTestCall& call, fesa::internal::FeastBackendTestOutputs& outputs) { if (!call.count) outputs.eigenvalues = {below_threshold, 0.0, 1.0}; }); const auto rejected = fesa::MklFeastEigenSolver{}.Solve( MakeDenseCsr(3U, {below_threshold, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0}), 3U, 1.0); ExpectFailureCode(rejected, "negative-eigenvalue-spectrum"); } TEST(MklFeastEigenSolver, DoesNotRepeatUnchangedSolveAfterReturnedMismatch) { std::size_t solve_calls = 0U; fesa::internal::ScopedFeastBackendTestHook hook( [&](const fesa::internal::FeastBackendTestCall& call, fesa::internal::FeastBackendTestOutputs& outputs) { if (!call.count) { ++solve_calls; outputs.returned = 0; } }); const auto result = fesa::MklFeastEigenSolver{}.Solve( MakeDenseCsr(1U, {2.0}), 1U, 1.0); ExpectFailureCode(result, "eigen-feast-failed"); EXPECT_EQ(solve_calls, 1U); } TEST(MklFeastEigenSolver, RetriesSolveInfoTwoOnlyWithLargerInterval) { constexpr std::size_t kSize = 11U; std::vector values(kSize * kSize, 0.0); for (std::size_t index = 0U; index < kSize; ++index) values[index * kSize + index] = static_cast(index + 1U); std::vector solve_uppers; fesa::internal::ScopedFeastBackendTestHook hook( [&](const fesa::internal::FeastBackendTestCall& call, fesa::internal::FeastBackendTestOutputs& outputs) { if (!call.count) { solve_uppers.push_back(call.upper); if (solve_uppers.size() == 1U) outputs.info = 2; } }); const auto result = fesa::MklFeastEigenSolver{}.Solve(MakeDenseCsr(kSize, values), 1U, 1.0); RequireSuccess(result); if (!result.HasValue()) return; ASSERT_EQ(solve_uppers.size(), 2U); EXPECT_LT(solve_uppers[0U], solve_uppers[1U]); } TEST(MklFeastEigenSolver, RejectsNonfiniteSolveOutputWithoutRetry) { std::size_t solve_calls = 0U; fesa::internal::ScopedFeastBackendTestHook hook( [&](const fesa::internal::FeastBackendTestCall& call, fesa::internal::FeastBackendTestOutputs& outputs) { if (!call.count) { ++solve_calls; outputs.eigenvalues[0U] = (std::numeric_limits::quiet_NaN)(); } }); const auto result = fesa::MklFeastEigenSolver{}.Solve( MakeDenseCsr(1U, {2.0}), 1U, 1.0); ExpectFailureCode(result, "invalid-eigenvalue-result"); EXPECT_EQ(solve_calls, 1U); } TEST(MklFeastEigenSolver, RejectsResidualFailureWithoutSolveRetry) { std::size_t solve_calls = 0U; fesa::internal::ScopedFeastBackendTestHook hook( [&](const fesa::internal::FeastBackendTestCall& call, fesa::internal::FeastBackendTestOutputs& outputs) { if (!call.count) { ++solve_calls; // N=1 still requires validation of the returned all-c=2 modes. outputs.vectors[2U] = std::sqrt(0.5); outputs.vectors[3U] = std::sqrt(0.5); } }); const auto result = fesa::MklFeastEigenSolver{}.Solve( MakeDenseCsr(2U, {1.0, 0.0, 0.0, 2.0}), 1U, 1.0); ExpectFailureCode(result, "eigenpair-validation-failed"); EXPECT_NE(result.GetStatus().Diagnostics().front().message.find( "Independent whitened residual exceeds"), std::string::npos); EXPECT_EQ(solve_calls, 1U); } TEST(MklFeastEigenSolver, RejectsNonfiniteVectorAndEpsoutWithoutRetry) { for (const bool nonfinite_vector : {true, false}) { std::size_t solve_calls = 0U; fesa::internal::ScopedFeastBackendTestHook hook( [&](const fesa::internal::FeastBackendTestCall& call, fesa::internal::FeastBackendTestOutputs& outputs) { if (!call.count) { ++solve_calls; if (nonfinite_vector) outputs.vectors[0U] = (std::numeric_limits::quiet_NaN)(); else outputs.epsout = (std::numeric_limits::quiet_NaN)(); } }); const auto result = fesa::MklFeastEigenSolver{}.Solve( MakeDenseCsr(1U, {2.0}), 1U, 1.0); ExpectFailureCode(result, "invalid-eigenvalue-result"); EXPECT_EQ(solve_calls, 1U); } } TEST(MklFeastEigenSolver, RejectsGramFailureWithoutSolveRetry) { std::size_t solve_calls = 0U; fesa::internal::ScopedFeastBackendTestHook hook( [&](const fesa::internal::FeastBackendTestCall& call, fesa::internal::FeastBackendTestOutputs& outputs) { if (!call.count) { ++solve_calls; ASSERT_EQ(outputs.vectors.size(), 4U); outputs.vectors = {1.0, 0.0, 1.0, 0.0}; } }); const auto result = fesa::MklFeastEigenSolver{}.Solve( MakeDenseCsr(2U, {0.0, 0.0, 0.0, 0.0}), 2U, 1.0); ExpectFailureCode(result, "eigenpair-validation-failed"); EXPECT_EQ(solve_calls, 1U); } TEST(MklFeastEigenSolver, UsesCertifiedEnclosureAfterOptionalInfoOne) { constexpr std::size_t kSize = 11U; std::vector values(kSize * kSize, 0.0); for (std::size_t index = 0U; index < kSize; ++index) values[index * kSize + index] = static_cast(index + 1U); fesa::internal::ScopedFeastBackendTestHook hook( [](const fesa::internal::FeastBackendTestCall& call, fesa::internal::FeastBackendTestOutputs& outputs) { // The first upper guard follows the total enclosing count. if (call.count && call.ordinal == 1U) outputs.info = 1; }); const auto result = fesa::MklFeastEigenSolver{}.Solve(MakeDenseCsr(kSize, values), 1U, 1.0); RequireSuccess(result); if (!result.HasValue()) return; EXPECT_EQ(result.Value().upper_search_termination, "optional-status"); ASSERT_GE(result.Value().backend_calls.size(), 2U); EXPECT_EQ(result.Value().backend_calls[1U].outcome, "optional-abandoned"); }