#include "analysis/hht_development_probe.h" #include #if defined(FESA_HHT_DEVELOPMENT_PROBE) #include "fesa/math/sparse_matrix.h" #include "fesa/math/vector.h" #include "analysis/hht_reduced_internal.h" namespace fesa::hht_development_probe { class IdentityTangentSolver final : public TangentSolver { public: Status FactorizeFresh(const SparseMatrix&) override { return Status::Ok(); } Status SolveCorrection(const Vector& rhs, Vector& result) const override { result = rhs; return Status::Ok(); } }; TEST(HhtDevelopmentProbe, CountsActualInnerUpdatesForLinearGraphAndZeroRoot) { hht_reduced_internal::FixedChart chart{}; chart.full_dof_count = 1U; chart.z_dofs = {0U}; chart.z_columns = {{0U, 1U, false, {{0U, 1.0}}, {0.0}}}; IdentityTangentSolver solver; const auto evaluate = [](const Vector& q) { hht_reduced_internal::ConstraintOperators operators; operators.rows.resize(1U); operators.rows[0U].value = q[0U]; operators.rows[0U].gradient = {{0U, 1.0}}; return Result::Success( std::move(operators)); }; Statistics statistics; active = &statistics; const auto first = hht_reduced_internal::SolveEmbeddingRoot( chart, Vector{0U}, Vector{1U, 1.0}, evaluate, solver); active = nullptr; ASSERT_TRUE(first.HasValue()); EXPECT_EQ(statistics.counts[kInnerCorrection].load(), 1U); EXPECT_DOUBLE_EQ(first.Value().z[0U], 0.0); Statistics zero_statistics; active = &zero_statistics; const auto zero = hht_reduced_internal::SolveEmbeddingRoot( chart, Vector{0U}, Vector{1U, 0.0}, evaluate, solver); active = nullptr; ASSERT_TRUE(zero.HasValue()); EXPECT_EQ(zero_statistics.counts[kInnerCorrection].load(), 0U); } class FailingSolver final : public LinearSolver, public TangentSolver { public: Status Factorize(const SparseMatrix&) override { ++factor_calls; return Status::Failure(FailureCategory::kSolver, {}); } Status FactorizeFresh(const SparseMatrix& matrix) override { return Factorize(matrix); } Status Solve(const Vector&, Vector&) const override { ++solve_calls; return Status::Failure(FailureCategory::kSolver, {}); } Status SolveCorrection(const Vector& rhs, Vector& result) const override { return Solve(rhs, result); } int factor_calls = 0; mutable int solve_calls = 0; }; TEST(HhtDevelopmentProbe, ForwardsEveryLogicalCallAndPreservesFailure) { Statistics statistics; active = &statistics; FailingSolver backend; PositiveSolver positive{backend}; CorrectionSolver tangent{backend}; auto matrix = SparseMatrix::FromCoo(0U, 0U, {}, SparsePattern{{0U}, {}}); ASSERT_TRUE(matrix.HasValue()); const Vector rhs{0U}; Vector result{0U}; EXPECT_FALSE(positive.Factorize(matrix.Value()).IsOk()); EXPECT_FALSE(positive.Solve(rhs, result).IsOk()); EXPECT_FALSE(tangent.FactorizeFresh(matrix.Value()).IsOk()); EXPECT_FALSE(tangent.SolveCorrection(rhs, result).IsOk()); active = nullptr; EXPECT_EQ(backend.factor_calls, 2); EXPECT_EQ(backend.solve_calls, 2); EXPECT_EQ(statistics.counts[kFactorCall].load(), 2U); EXPECT_EQ(statistics.counts[kSolveCall].load(), 2U); EXPECT_EQ(statistics.counts[kRhs].load(), 2U); } TEST(HhtDevelopmentProbe, CountsOnlyWhileActiveAndPreservesMultiplicity) { Statistics statistics; active = &statistics; Count(kOuterCorrection); Count(kInternalEvaluation, 7U); active = nullptr; Count(kOuterCorrection); EXPECT_EQ(statistics.counts[kOuterCorrection].load(), 1U); EXPECT_EQ(statistics.counts[kInternalEvaluation].load(), 7U); EXPECT_EQ(statistics.counts[kInertiaEvaluation].load(), 0U); } TEST(HhtDevelopmentProbe, NestedTimesHaveNoDoubleCounting) { Statistics statistics; active = &statistics; { Scope parent{kProcedure}; { Scope child{kAssembly}; } } active = nullptr; EXPECT_EQ(statistics.stage_calls[kProcedure], 1U); EXPECT_EQ(statistics.stage_calls[kAssembly], 1U); EXPECT_GE(statistics.inclusive_ns[kProcedure], 0); EXPECT_GE(statistics.inclusive_ns[kAssembly], 0); EXPECT_EQ(statistics.exclusive_ns[kAssembly], statistics.inclusive_ns[kAssembly]); EXPECT_EQ(statistics.exclusive_ns[kProcedure] + statistics.exclusive_ns[kAssembly], statistics.inclusive_ns[kProcedure]); } } // namespace fesa::hht_development_probe #endif