#include #include #include #include #include #include #include #include #include #include #include namespace { fesa::DofManager build_dofs( std::vector prescribed) { fesa::DomainBuilder builder; builder.add_node({ fesa::NodeId{0}, fesa::EntityOrigin{"", "", 1}, fesa::Vec3{0.0, 0.0, 0.0}, }); builder.set_step({"Load", std::move(prescribed), {}}); auto result = std::move(builder).build(); if (!result.domain.has_value()) { throw std::runtime_error{"Test Domain failed validation."}; } return fesa::DofManager::build(*result.domain); } fesa::EquationSystem hand_system() { return { { 6, {0, 3, 5, 6, 7, 8, 9}, {0, 1, 2, 1, 2, 2, 3, 4, 5}, {4.0, 1.0, 2.0, 3.0, 6.0, 5.0, 1.0, 1.0, 1.0}, }, {7.0, 8.0, 9.0, 0.0, 0.0, 0.0}, }; } TEST(EssentialBc, ZeroPrescribedValueDoesNotShiftReducedForce) { const std::vector prescribed{ {fesa::NodeId{0}, 2, 0.0}, }; const fesa::DofManager dofs = build_dofs(prescribed); const fesa::ConstraintResult result = fesa::eliminate_essential_bcs(hand_system(), dofs); ASSERT_TRUE(result.reduced_system.has_value()); EXPECT_TRUE(result.diagnostics.empty()); EXPECT_EQ( result.reduced_system->force, (std::vector{7.0, 9.0, 0.0, 0.0, 0.0})); } TEST( ConstraintElimination, ShiftsNonzeroPrescribedValueAndPreservesOriginalSystem) { const std::vector prescribed{ {fesa::NodeId{0}, 2, 2.0}, }; const fesa::DofManager dofs = build_dofs(prescribed); const fesa::EquationSystem original = hand_system(); const fesa::EquationSystem before = original; const fesa::ConstraintResult result = fesa::eliminate_essential_bcs(original, dofs); ASSERT_TRUE(result.reduced_system.has_value()); EXPECT_TRUE(result.diagnostics.empty()); const fesa::ReducedSystem& reduced = *result.reduced_system; EXPECT_EQ(reduced.stiffness.order, 5); EXPECT_EQ( reduced.stiffness.row_offsets, (std::vector{0, 2, 3, 4, 5, 6})); EXPECT_EQ( reduced.stiffness.column_indices, (std::vector{0, 1, 1, 2, 3, 4})); EXPECT_EQ( reduced.stiffness.values, (std::vector{4.0, 2.0, 5.0, 1.0, 1.0, 1.0})); EXPECT_EQ( reduced.force, (std::vector{5.0, -3.0, 0.0, 0.0, 0.0})); EXPECT_EQ(original.stiffness.order, before.stiffness.order); EXPECT_EQ( original.stiffness.row_offsets, before.stiffness.row_offsets); EXPECT_EQ( original.stiffness.column_indices, before.stiffness.column_indices); EXPECT_EQ(original.stiffness.values, before.stiffness.values); EXPECT_EQ(original.force, before.force); EXPECT_EQ( dofs.reconstruct_full( std::vector{1.0, 3.0, 0.0, 0.0, 0.0}), (std::vector{1.0, 2.0, 3.0, 0.0, 0.0, 0.0})); } TEST(ConstraintElimination, AcceptsAllDofsConstrained) { const std::vector prescribed{ {fesa::NodeId{0}, 1, 1.0}, {fesa::NodeId{0}, 2, 2.0}, {fesa::NodeId{0}, 3, 3.0}, {fesa::NodeId{0}, 4, 4.0}, {fesa::NodeId{0}, 5, 5.0}, {fesa::NodeId{0}, 6, 6.0}, }; const fesa::DofManager dofs = build_dofs(prescribed); const fesa::ConstraintResult result = fesa::eliminate_essential_bcs(hand_system(), dofs); ASSERT_TRUE(result.reduced_system.has_value()); const fesa::ReducedSystem& reduced = *result.reduced_system; EXPECT_EQ(reduced.stiffness.order, 0); EXPECT_EQ( reduced.stiffness.row_offsets, (std::vector{0})); EXPECT_TRUE(reduced.stiffness.column_indices.empty()); EXPECT_TRUE(reduced.stiffness.values.empty()); EXPECT_TRUE(reduced.force.empty()); EXPECT_EQ( dofs.reconstruct_full({}), (std::vector{1.0, 2.0, 3.0, 4.0, 5.0, 6.0})); } TEST(ConstraintElimination, RejectsNonfiniteSystemData) { const fesa::DofManager dofs = build_dofs({}); fesa::EquationSystem nonfinite_matrix = hand_system(); nonfinite_matrix.stiffness.values[0] = std::numeric_limits::quiet_NaN(); fesa::EquationSystem nonfinite_force = hand_system(); nonfinite_force.force[0] = std::numeric_limits::infinity(); const fesa::ConstraintResult matrix_result = fesa::eliminate_essential_bcs(nonfinite_matrix, dofs); const fesa::ConstraintResult force_result = fesa::eliminate_essential_bcs(nonfinite_force, dofs); ASSERT_FALSE(matrix_result.diagnostics.empty()); EXPECT_EQ( matrix_result.diagnostics.front().code, "equation.invalid_system"); ASSERT_FALSE(force_result.diagnostics.empty()); EXPECT_EQ( force_result.diagnostics.front().code, "equation.invalid_system"); } TEST(ConstraintElimination, RejectsNonfiniteReducedForce) { const fesa::DofManager dofs = build_dofs({ {fesa::NodeId{0}, 2, 2.0}, }); fesa::EquationSystem overflowing = hand_system(); overflowing.stiffness.values[1] = std::numeric_limits::max(); const fesa::ConstraintResult result = fesa::eliminate_essential_bcs(overflowing, dofs); EXPECT_FALSE(result.reduced_system.has_value()); ASSERT_FALSE(result.diagnostics.empty()); EXPECT_EQ( result.diagnostics.front().code, "equation.nonfinite_result"); } TEST(Reaction, UsesOriginalFullEquilibriumEquation) { const std::vector reaction = fesa::recover_reaction( hand_system(), std::vector{1.0, 2.0, 3.0, 0.0, 0.0, 0.0}); EXPECT_EQ( reaction, (std::vector{5.0, 17.0, 20.0, 0.0, 0.0, 0.0})); } TEST(Reaction, RejectsNonfiniteFullDisplacement) { std::vector displacement(6, 0.0); displacement[0] = std::numeric_limits::quiet_NaN(); EXPECT_THROW( static_cast( fesa::recover_reaction(hand_system(), displacement)), std::invalid_argument); } TEST(Reaction, RejectsNonfiniteRecoveredValue) { fesa::EquationSystem overflowing = hand_system(); overflowing.stiffness.values[0] = std::numeric_limits::max(); EXPECT_THROW( static_cast(fesa::recover_reaction( overflowing, std::vector{2.0, 0.0, 0.0, 0.0, 0.0, 0.0})), std::invalid_argument); } } // namespace