feat(deterministic-parallel-assembly): step 0 — canonical-contribution-order

This commit is contained in:
KOKO\Mimi
2026-08-01 23:06:21 +09:00
parent 6ac474f19b
commit 40a7e6c3af
6 changed files with 251 additions and 2 deletions
+12
View File
@@ -397,6 +397,18 @@ add_test(
--gtest_filter=SymmetricCsr.*
)
add_test(
NAME CanonicalContribution
COMMAND "$<TARGET_FILE:fesa_serial_assembly_tests>"
--gtest_filter=CanonicalContribution.*
)
add_test(
NAME DeterministicMerge
COMMAND "$<TARGET_FILE:fesa_serial_assembly_tests>"
--gtest_filter=DeterministicMerge.*
)
add_executable(fesa_constraint_tests
unit/constraints/essential_bc_test.cpp
)
+77 -1
View File
@@ -1,3 +1,4 @@
#include <fesa/assembly/contribution.hpp>
#include <fesa/assembly/serial_assembler.hpp>
#include <algorithm>
@@ -187,8 +188,9 @@ fesa::Domain build_rounding_domain(const bool large_element_first) {
storage_order = {2, 0, 1};
}
for (const std::size_t index : storage_order) {
constexpr std::array<std::int64_t, 3> element_ids{1, 2, 0};
builder.add_beam_element({
fesa::ElementId{static_cast<std::int64_t>(index)},
fesa::ElementId{element_ids[index]},
fesa::EntityOrigin{
"BeamPart",
"Beam-1",
@@ -250,6 +252,80 @@ double csr_value(
std::distance(matrix.column_indices.begin(), found))];
}
std::vector<std::uint64_t> value_bits(
const fesa::SymmetricCsr& matrix) {
std::vector<std::uint64_t> bits;
bits.reserve(matrix.values.size());
for (const double value : matrix.values) {
bits.push_back(std::bit_cast<std::uint64_t>(value));
}
return bits;
}
TEST(CanonicalContribution, OrdersByCoordinateElementAndLocalOrder) {
const std::vector<fesa::MatrixContribution> contributions{
{1, 2, fesa::ElementId{9}, 4, 9.0},
{0, 2, fesa::ElementId{3}, 8, 3.8},
{0, 2, fesa::ElementId{3}, 2, 3.2},
{0, 2, fesa::ElementId{1}, 7, 1.7},
{0, 0, fesa::ElementId{5}, 0, 5.0},
};
const std::vector<fesa::MatrixContribution> canonical =
fesa::canonicalize_contributions(contributions);
ASSERT_EQ(canonical.size(), contributions.size());
EXPECT_EQ(canonical[0].row, 0);
EXPECT_EQ(canonical[0].column, 0);
EXPECT_EQ(canonical[0].element, fesa::ElementId{5});
EXPECT_EQ(canonical[1].element, fesa::ElementId{1});
EXPECT_EQ(canonical[2].element, fesa::ElementId{3});
EXPECT_EQ(canonical[2].local_order, 2);
EXPECT_EQ(canonical[3].element, fesa::ElementId{3});
EXPECT_EQ(canonical[3].local_order, 8);
EXPECT_EQ(canonical[4].row, 1);
EXPECT_EQ(canonical[4].column, 2);
}
TEST(DeterministicMerge, IsBitwiseStableAcrossInputPermutations) {
const std::vector<fesa::MatrixContribution> contributions{
{0, 1, fesa::ElementId{2}, 0, 1.0},
{0, 1, fesa::ElementId{0}, 0, 1.0e16},
{0, 1, fesa::ElementId{1}, 0, -1.0e16},
{1, 1, fesa::ElementId{1}, 1, -0.0},
{1, 1, fesa::ElementId{0}, 1, +0.0},
};
const std::vector<fesa::MatrixContribution> canonical =
fesa::canonicalize_contributions(contributions);
const fesa::SymmetricCsr expected =
fesa::merge_contributions(2, canonical);
std::array<std::size_t, 5> permutation{0, 1, 2, 3, 4};
do {
std::vector<fesa::MatrixContribution> shuffled;
shuffled.reserve(contributions.size());
for (const std::size_t index : permutation) {
shuffled.push_back(contributions[index]);
}
const std::vector<fesa::MatrixContribution> shuffled_canonical =
fesa::canonicalize_contributions(shuffled);
const fesa::SymmetricCsr actual =
fesa::merge_contributions(2, shuffled_canonical);
EXPECT_EQ(actual.order, expected.order);
EXPECT_EQ(actual.row_offsets, expected.row_offsets);
EXPECT_EQ(actual.column_indices, expected.column_indices);
EXPECT_EQ(value_bits(actual), value_bits(expected));
} while (std::ranges::next_permutation(permutation).found);
EXPECT_EQ(
std::bit_cast<std::uint64_t>(csr_value(expected, 0, 1)),
std::bit_cast<std::uint64_t>(1.0));
EXPECT_EQ(
std::bit_cast<std::uint64_t>(csr_value(expected, 1, 1)),
std::bit_cast<std::uint64_t>(+0.0));
}
TEST(SparsePattern, BuildsExpectedTwoElementChainStructure) {
const fesa::Domain domain = build_chain_domain(false);
const fesa::DofManager dofs = fesa::DofManager::build(domain);