feat(equation-and-linear-solve): step 1 — essential-bc-elimination

This commit is contained in:
KOKO\Mimi
2026-07-31 16:00:45 +09:00
parent 30dcb05dd1
commit 1bf277cbf1
7 changed files with 546 additions and 0 deletions
+37
View File
@@ -308,3 +308,40 @@ add_test(
COMMAND "$<TARGET_FILE:fesa_serial_assembly_tests>"
--gtest_filter=SymmetricCsr.*
)
add_executable(fesa_constraint_tests
unit/constraints/essential_bc_test.cpp
)
target_compile_features(fesa_constraint_tests PRIVATE cxx_std_20)
target_compile_options(
fesa_constraint_tests
PRIVATE
/W4
/permissive-
/EHsc
)
target_link_libraries(fesa_constraint_tests
PRIVATE
fesa_core
GTest::gtest_main
)
add_test(
NAME EssentialBc
COMMAND "$<TARGET_FILE:fesa_constraint_tests>"
--gtest_filter=EssentialBc.*
)
add_test(
NAME ConstraintElimination
COMMAND "$<TARGET_FILE:fesa_constraint_tests>"
--gtest_filter=ConstraintElimination.*
)
add_test(
NAME Reaction
COMMAND "$<TARGET_FILE:fesa_constraint_tests>"
--gtest_filter=Reaction.*
)
@@ -0,0 +1,178 @@
#include <fesa/constraints/essential_bc.hpp>
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <stdexcept>
#include <utility>
#include <vector>
#include <fesa/model/domain_builder.hpp>
#include <gtest/gtest.h>
namespace {
fesa::DofManager build_dofs(
std::vector<fesa::PrescribedDof> 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<fesa::PrescribedDof> 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, prescribed);
ASSERT_TRUE(result.reduced_system.has_value());
EXPECT_TRUE(result.diagnostics.empty());
EXPECT_EQ(
result.reduced_system->force,
(std::vector<double>{7.0, 9.0, 0.0, 0.0, 0.0}));
}
TEST(
ConstraintElimination,
ShiftsNonzeroPrescribedValueAndPreservesOriginalSystem) {
const std::vector<fesa::PrescribedDof> 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, prescribed);
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<std::int32_t>{0, 2, 3, 4, 5, 6}));
EXPECT_EQ(
reduced.stiffness.column_indices,
(std::vector<std::int32_t>{0, 1, 1, 2, 3, 4}));
EXPECT_EQ(
reduced.stiffness.values,
(std::vector<double>{4.0, 2.0, 5.0, 1.0, 1.0, 1.0}));
EXPECT_EQ(
reduced.force,
(std::vector<double>{5.0, -3.0, 0.0, 0.0, 0.0}));
EXPECT_EQ(
reduced.free_to_full,
(std::vector<std::size_t>{0, 2, 3, 4, 5}));
EXPECT_EQ(
reduced.prescribed_full,
(std::vector<double>{0.0, 2.0, 0.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<double>{1.0, 3.0, 0.0, 0.0, 0.0}),
(std::vector<double>{1.0, 2.0, 3.0, 0.0, 0.0, 0.0}));
}
TEST(ConstraintElimination, AcceptsAllDofsConstrained) {
const std::vector<fesa::PrescribedDof> 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, prescribed);
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<std::int32_t>{0}));
EXPECT_TRUE(reduced.stiffness.column_indices.empty());
EXPECT_TRUE(reduced.stiffness.values.empty());
EXPECT_TRUE(reduced.force.empty());
EXPECT_TRUE(reduced.free_to_full.empty());
EXPECT_EQ(
reduced.prescribed_full,
(std::vector<double>{1.0, 2.0, 3.0, 4.0, 5.0, 6.0}));
EXPECT_EQ(
dofs.reconstruct_full({}),
reduced.prescribed_full);
}
TEST(ConstraintElimination, RejectsConflictingPrescribedValues) {
const fesa::DofManager dofs = build_dofs({
{fesa::NodeId{0}, 2, 2.0},
});
const std::vector<fesa::PrescribedDof> conflicting{
{fesa::NodeId{0}, 2, 2.0},
{fesa::NodeId{0}, 2, 3.0},
};
const fesa::ConstraintResult result =
fesa::eliminate_essential_bcs(
hand_system(), dofs, conflicting);
EXPECT_FALSE(result.reduced_system.has_value());
ASSERT_FALSE(result.diagnostics.empty());
EXPECT_NE(
std::ranges::find(
result.diagnostics,
"equation.conflicting_prescribed_dof",
&fesa::Diagnostic::code),
result.diagnostics.end());
}
TEST(Reaction, UsesOriginalFullEquilibriumEquation) {
const std::vector<double> reaction = fesa::recover_reaction(
hand_system(),
std::vector<double>{1.0, 2.0, 3.0, 0.0, 0.0, 0.0});
EXPECT_EQ(
reaction,
(std::vector<double>{5.0, 17.0, 20.0, 0.0, 0.0, 0.0}));
}
} // namespace