#include "fesa/analysis/analysis_model.hpp" #include "fesa/assembly/parallel_for.hpp" #include "fesa/assembly/sparse_assembler.hpp" #include "fesa/fem/dof_manager.hpp" #include "fesa/model/domain.hpp" #include #include #include #include #include #include namespace { fesa::ModelDefinition makeDefinition() { const std::filesystem::path source{"models/sparse-assembly.inp"}; fesa::ModelDefinition definition{}; definition.sourcePath = source; definition.sourceContentIdentity = "fnv1a64:0123456789abcdef"; definition.nodes = { {{"Beam-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 10U}}, {{"Beam-1", 2, "2"}, {2.0, 0.0, 0.0}, {source, 11U}}, {{"Beam-1", 3, "3"}, {5.0, 0.0, 0.0}, {source, 12U}}}; definition.materials = { {"Material", 120.0, 0.25, {source, 20U}}}; definition.sections = {{ "Section", 2.0, 1.5, 0.0, 0.75, 0.5, {0.0, 1.0, 0.0}, {}, {source, 30U}}}; definition.elements = { {{"Beam-1", 10, "10"}, {0U, 1U}, 0U, 0U, {source, 40U}}, {{"Beam-1", 20, "20"}, {1U, 2U}, 0U, 0U, {source, 41U}}}; definition.steps = {{ "Step-1", {}, {}, 0.1, 1.0, 0.01, 1.0, {source, 50U}}}; return definition; } template bool byteIdentical(const std::vector& left, const std::vector& right) { return left.size() == right.size() && (left.empty() || std::memcmp( left.data(), right.data(), left.size() * sizeof(T)) == 0); } double entry( const fesa::SparseMatrix& matrix, const std::size_t row, const std::size_t column) { const auto begin = matrix.columnIndices().begin() + matrix.rowOffsets()[row]; const auto end = matrix.columnIndices().begin() + matrix.rowOffsets()[row + 1U]; const auto found = std::lower_bound(begin, end, column); if (found == end || *found != column) { return 0.0; } return matrix.values()[static_cast( std::distance(matrix.columnIndices().begin(), found))]; } class ReverseParallelFor final : public fesa::ParallelFor { public: void execute( const std::size_t count, const std::function& body) const override { ++calls_; observedCount_ = count; for (std::size_t index = count; index > 0U; --index) { body(index - 1U); } } std::size_t calls() const noexcept { return calls_; } std::size_t observedCount() const noexcept { return observedCount_; } private: mutable std::size_t calls_{0U}; mutable std::size_t observedCount_{0U}; }; void expectByteIdentical( const fesa::SparseMatrix& actual, const fesa::SparseMatrix& expected) { EXPECT_TRUE(byteIdentical(actual.rowOffsets(), expected.rowOffsets())); EXPECT_TRUE(byteIdentical(actual.columnIndices(), expected.columnIndices())); EXPECT_TRUE(byteIdentical(actual.values(), expected.values())); } TEST(SparseAssembly, SerialTbbAndRepeatedRunsAreByteIdentical) { auto domainResult = fesa::Domain::create(makeDefinition()); ASSERT_TRUE(domainResult.hasValue()); auto modelResult = fesa::AnalysisModel::create(domainResult.value()); ASSERT_TRUE(modelResult.hasValue()); auto dofsResult = fesa::DofManager::create(modelResult.value()); ASSERT_TRUE(dofsResult.hasValue()); fesa::SerialParallelFor serialExecutor; fesa::TbbParallelFor tbbExecutor; ReverseParallelFor reverseExecutor; auto serial = fesa::SparseAssembler::assembleStiffness( modelResult.value(), dofsResult.value(), serialExecutor); auto tbb = fesa::SparseAssembler::assembleStiffness( modelResult.value(), dofsResult.value(), tbbExecutor); auto reversed = fesa::SparseAssembler::assembleStiffness( modelResult.value(), dofsResult.value(), reverseExecutor); ASSERT_TRUE(serial.hasValue()); ASSERT_TRUE(tbb.hasValue()); ASSERT_TRUE(reversed.hasValue()); EXPECT_EQ(reverseExecutor.calls(), 1U); EXPECT_EQ(reverseExecutor.observedCount(), 2U); EXPECT_EQ(serial.value().rows(), 18U); EXPECT_EQ(serial.value().columns(), 18U); EXPECT_EQ(serial.value().rowOffsets(), dofsResult.value().sparsePattern().rowOffsets); EXPECT_EQ( serial.value().columnIndices(), dofsResult.value().sparsePattern().columnIndices); EXPECT_TRUE(serial.value().validate().isOk()); expectByteIdentical(tbb.value(), serial.value()); expectByteIdentical(reversed.value(), serial.value()); for (std::size_t repetition = 0U; repetition < 8U; ++repetition) { auto repeated = fesa::SparseAssembler::assembleStiffness( modelResult.value(), dofsResult.value(), tbbExecutor); ASSERT_TRUE(repeated.hasValue()); expectByteIdentical(repeated.value(), serial.value()); } for (std::size_t row = 0U; row < serial.value().rows(); ++row) { for (std::size_t column = 0U; column < serial.value().columns(); ++column) { EXPECT_DOUBLE_EQ( entry(serial.value(), row, column), entry(serial.value(), column, row)); } } EXPECT_NEAR(entry(serial.value(), 0U, 0U), 120.0, 1.0e-12); EXPECT_NEAR(entry(serial.value(), 0U, 6U), -120.0, 1.0e-12); EXPECT_NEAR(entry(serial.value(), 6U, 6U), 200.0, 1.0e-12); EXPECT_NEAR(entry(serial.value(), 6U, 12U), -80.0, 1.0e-12); EXPECT_NEAR(entry(serial.value(), 12U, 12U), 80.0, 1.0e-12); } } // namespace