feat: strengthen dof manager foundation

This commit is contained in:
NINI
2026-05-04 13:18:28 +09:00
parent ac72f4ccd7
commit b9b0963d50
5 changed files with 223 additions and 22 deletions
+118
View File
@@ -139,6 +139,22 @@ fesa::Domain singleElementValidationDomain() {
return domain;
}
fesa::Domain noncontiguousDofDomain() {
fesa::Domain domain;
domain.nodes[30] = {30, {1, 1, 0}};
domain.nodes[10] = {10, {0, 0, 0}};
domain.nodes[40] = {40, {0, 1, 0}};
domain.nodes[20] = {20, {1, 0, 0}};
domain.elements[5] = {5, fesa::ElementType::MITC4, {10, 20, 30, 40}, "EALL"};
domain.element_sets["eall"] = {"EALL", {5}};
domain.node_sets["clamped"] = {"CLAMPED", {20}};
domain.materials["mat"] = {"MAT", 1000.0, 0.3};
domain.shell_sections.push_back({"EALL", "MAT", 0.1});
domain.boundary_conditions.push_back({"CLAMPED", 1, 6, 0.0});
domain.boundary_conditions.push_back({"30", 1, 2, 0.0});
return domain;
}
} // namespace
FESA_TEST(core_types_and_dof_mapping_are_stable) {
@@ -501,6 +517,108 @@ FESA_TEST(dof_manager_owns_equation_numbering_and_reconstruction) {
FESA_CHECK_NEAR(full[static_cast<std::size_t>(dofs.fullIndex(1, fesa::Dof::UX))], 0.0, 1.0e-15);
}
FESA_TEST(dof_manager_preserves_global_order_for_noncontiguous_nodes) {
auto domain = noncontiguousDofDomain();
fesa::DofManager dofs(domain);
FESA_CHECK(dofs.nodeIds() == std::vector<fesa::GlobalId>({10, 20, 30, 40}));
FESA_CHECK(dofs.fullDofCount() == 24);
for (std::size_t node_offset = 0; node_offset < dofs.nodeIds().size(); ++node_offset) {
const fesa::GlobalId node_id = dofs.nodeIds()[node_offset];
for (fesa::Dof dof : fesa::allDofs()) {
const fesa::LocalIndex expected = static_cast<fesa::LocalIndex>(6 * node_offset + fesa::dofIndex(dof));
FESA_CHECK(dofs.fullIndex(node_id, dof) == expected);
const auto address = dofs.fullDof(expected);
FESA_CHECK(address.node_id == node_id);
FESA_CHECK(address.dof == dof);
}
}
}
FESA_TEST(dof_manager_partitions_constraints_and_equations_deterministically) {
auto domain = noncontiguousDofDomain();
fesa::DofManager dofs(domain);
FESA_CHECK(dofs.constrainedDofCount() == 8);
FESA_CHECK(dofs.freeDofCount() == 16);
FESA_CHECK(dofs.constrainedFullIndices() == std::vector<fesa::LocalIndex>({6, 7, 8, 9, 10, 11, 12, 13}));
FESA_CHECK(dofs.freeFullIndices() ==
std::vector<fesa::LocalIndex>({0, 1, 2, 3, 4, 5, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}));
FESA_CHECK(dofs.equation(10, fesa::Dof::UX) == 0);
FESA_CHECK(dofs.equation(20, fesa::Dof::RZ) == -1);
FESA_CHECK(dofs.equation(30, fesa::Dof::UX) == -1);
FESA_CHECK(dofs.equation(30, fesa::Dof::UZ) == 6);
FESA_CHECK(dofs.equation(40, fesa::Dof::RZ) == 15);
}
FESA_TEST(dof_manager_reduces_and_reconstructs_full_vectors) {
auto domain = noncontiguousDofDomain();
fesa::DofManager dofs(domain);
std::vector<fesa::Real> full(static_cast<std::size_t>(dofs.fullDofCount()), 0.0);
for (std::size_t i = 0; i < full.size(); ++i) {
full[i] = static_cast<fesa::Real>(100 + i);
}
auto reduced = dofs.reduceFullVector(full);
FESA_CHECK(reduced.size() == static_cast<std::size_t>(dofs.freeDofCount()));
FESA_CHECK_NEAR(reduced[0], full[0], 1.0e-15);
FESA_CHECK_NEAR(reduced[6], full[14], 1.0e-15);
FESA_CHECK_NEAR(reduced.back(), full[23], 1.0e-15);
auto reconstructed = dofs.reconstructFullVector(reduced);
for (fesa::LocalIndex full_index = 0; full_index < dofs.fullDofCount(); ++full_index) {
const auto address = dofs.fullDof(full_index);
if (dofs.isConstrained(address.node_id, address.dof)) {
FESA_CHECK_NEAR(reconstructed[static_cast<std::size_t>(full_index)], 0.0, 1.0e-15);
} else {
FESA_CHECK_NEAR(reconstructed[static_cast<std::size_t>(full_index)], full[static_cast<std::size_t>(full_index)], 1.0e-15);
}
}
}
FESA_TEST(dof_manager_provides_element_sparse_connectivity_inputs) {
auto domain = parsedPhase1Domain();
fesa::DofManager dofs(domain);
const auto& element = domain.elements.at(1);
auto full_indices = dofs.elementFullDofIndices(element);
auto equation_ids = dofs.elementEquationIds(element);
for (fesa::LocalIndex i = 0; i < 24; ++i) {
FESA_CHECK(full_indices[static_cast<std::size_t>(i)] == i);
}
for (fesa::LocalIndex i = 0; i < 6; ++i) {
FESA_CHECK(equation_ids[static_cast<std::size_t>(i)] == -1);
FESA_CHECK(equation_ids[static_cast<std::size_t>(18 + i)] == -1);
}
FESA_CHECK(equation_ids[static_cast<std::size_t>(6 + fesa::dofIndex(fesa::Dof::UZ))] == 0);
FESA_CHECK(equation_ids[static_cast<std::size_t>(12 + fesa::dofIndex(fesa::Dof::UZ))] == 1);
FESA_CHECK(equation_ids[static_cast<std::size_t>(6 + fesa::dofIndex(fesa::Dof::UX))] == -1);
}
FESA_TEST(full_vector_reaction_recovery_uses_full_system_quantities) {
auto domain = noncontiguousDofDomain();
fesa::DofManager dofs(domain);
fesa::DenseMatrix k_full(dofs.fullDofCount(), dofs.fullDofCount());
std::vector<fesa::Real> u_full(static_cast<std::size_t>(dofs.fullDofCount()), 0.0);
std::vector<fesa::Real> f_full(static_cast<std::size_t>(dofs.fullDofCount()), 0.0);
const fesa::LocalIndex support_ux = dofs.fullIndex(20, fesa::Dof::UX);
const fesa::LocalIndex free_uz = dofs.fullIndex(30, fesa::Dof::UZ);
k_full(support_ux, support_ux) = 10.0;
k_full(support_ux, free_uz) = 2.0;
k_full(free_uz, support_ux) = 2.0;
k_full(free_uz, free_uz) = 5.0;
u_full[static_cast<std::size_t>(free_uz)] = 3.0;
f_full[static_cast<std::size_t>(support_ux)] = 1.0;
f_full[static_cast<std::size_t>(free_uz)] = 7.0;
auto reaction = fesa::recoverFullReaction(k_full, u_full, f_full);
FESA_CHECK_NEAR(reaction[static_cast<std::size_t>(support_ux)], 5.0, 1.0e-15);
FESA_CHECK_NEAR(reaction[static_cast<std::size_t>(free_uz)], 8.0, 1.0e-15);
}
FESA_TEST(gaussian_solver_solves_and_diagnoses_singular_systems) {
fesa::DenseMatrix a(2, 2);
a(0, 0) = 2.0;