fix(equation-and-linear-solve): address review findings

This commit is contained in:
KOKO\Mimi
2026-07-31 16:36:07 +09:00
parent 2dd17be5d0
commit 741fc9eaea
12 changed files with 329 additions and 147 deletions
@@ -2,6 +2,7 @@
#include <algorithm>
#include <array>
#include <bit>
#include <cstddef>
#include <cstdint>
#include <limits>
@@ -158,6 +159,78 @@ fesa::Domain build_parallel_domain(
return finish_domain(std::move(builder), {"Load", {}, {}});
}
fesa::Domain build_rounding_domain(const bool large_element_first) {
fesa::DomainBuilder builder;
builder.add_node({
fesa::NodeId{4},
fesa::EntityOrigin{"BeamPart", "Beam-1", 1},
fesa::Vec3{0.0, 0.0, 0.0},
});
builder.add_node({
fesa::NodeId{10},
fesa::EntityOrigin{"BeamPart", "Beam-1", 2},
fesa::Vec3{1.0, 0.0, 0.0},
});
for (std::size_t index = 0; index < 3; ++index) {
fesa::IsotropicElastic material = unit_material();
material.id =
fesa::MaterialId{static_cast<std::int64_t>(index)};
material.name = "Material-" + std::to_string(index);
material.young = index == 2 ? 1.0e16 : 1.0;
builder.add_material(std::move(material));
}
builder.add_section(unit_section());
std::array<std::size_t, 3> storage_order{0, 1, 2};
if (large_element_first) {
storage_order = {2, 0, 1};
}
for (const std::size_t index : storage_order) {
builder.add_beam_element({
fesa::ElementId{static_cast<std::int64_t>(index)},
fesa::EntityOrigin{
"BeamPart",
"Beam-1",
static_cast<std::int64_t>((index + 1) * 10),
},
{fesa::NodeId{4}, fesa::NodeId{10}},
fesa::MaterialId{static_cast<std::int64_t>(index)},
fesa::SectionId{0},
});
}
return finish_domain(std::move(builder), {"Load", {}, {}});
}
fesa::Domain build_orphan_node_domain() {
fesa::DomainBuilder builder;
builder.add_node({
fesa::NodeId{0},
fesa::EntityOrigin{"BeamPart", "Beam-1", 1},
fesa::Vec3{0.0, 0.0, 0.0},
});
builder.add_node({
fesa::NodeId{1},
fesa::EntityOrigin{"BeamPart", "Beam-1", 2},
fesa::Vec3{1.0, 0.0, 0.0},
});
builder.add_node({
fesa::NodeId{2},
fesa::EntityOrigin{"BeamPart", "Beam-1", 3},
fesa::Vec3{2.0, 0.0, 0.0},
});
builder.add_material(unit_material());
builder.add_section(unit_section());
builder.add_beam_element({
fesa::ElementId{0},
fesa::EntityOrigin{"BeamPart", "Beam-1", 1},
{fesa::NodeId{0}, fesa::NodeId{1}},
fesa::MaterialId{0},
fesa::SectionId{0},
});
return finish_domain(std::move(builder), {"Load", {}, {}});
}
double csr_value(
const fesa::SymmetricCsr& matrix,
std::size_t row,
@@ -246,6 +319,29 @@ TEST(SymmetricCsr, StoresSortedUpperTriangleWithValidOffsets) {
}
}
TEST(SparsePattern, IncludesZeroDiagonalForEveryOrphanNodeDof) {
const fesa::Domain domain = build_orphan_node_domain();
const fesa::SymmetricCsr matrix =
fesa::assemble_serial(
domain, fesa::DofManager::build(domain))
.stiffness;
ASSERT_EQ(matrix.order, 18);
for (std::size_t row = 0; row < matrix.order; ++row) {
const auto begin =
matrix.column_indices.begin() + matrix.row_offsets[row];
const auto end =
matrix.column_indices.begin() + matrix.row_offsets[row + 1];
const auto diagonal = std::lower_bound(
begin, end, static_cast<std::int32_t>(row));
ASSERT_NE(diagonal, end);
EXPECT_EQ(*diagonal, static_cast<std::int32_t>(row));
}
for (std::size_t row = 12; row < 18; ++row) {
EXPECT_DOUBLE_EQ(csr_value(matrix, row, row), 0.0);
}
}
TEST(SerialAssembly, AssemblesHandCalculatedAxialChainAndFullLoad) {
const fesa::Domain domain = build_chain_domain(false);
const fesa::DofManager dofs = fesa::DofManager::build(domain);
@@ -307,6 +403,36 @@ TEST(SerialAssembly, MergesDuplicateElementContributions) {
}
}
TEST(SerialAssembly, ReducesByElementOriginNotDomainStorageOrder) {
const fesa::Domain origin_order_domain =
build_rounding_domain(false);
const fesa::Domain storage_order_domain =
build_rounding_domain(true);
const double origin_order_value = csr_value(
fesa::assemble_serial(
origin_order_domain,
fesa::DofManager::build(origin_order_domain))
.stiffness,
0,
0);
const double storage_order_value = csr_value(
fesa::assemble_serial(
storage_order_domain,
fesa::DofManager::build(storage_order_domain))
.stiffness,
0,
0);
const double expected = (1.0 + 1.0) + 1.0e16;
EXPECT_EQ(
std::bit_cast<std::uint64_t>(origin_order_value),
std::bit_cast<std::uint64_t>(expected));
EXPECT_EQ(
std::bit_cast<std::uint64_t>(storage_order_value),
std::bit_cast<std::uint64_t>(expected));
}
TEST(SerialAssembly, IsIndependentOfDomainStorageAndExternalLabelOrder) {
const fesa::Domain first_domain = build_chain_domain(false);
const fesa::Domain second_domain = build_chain_domain(true);