feat(cpp-object-oriented-modular-refactoring): step 15 - generic-dof-manager
This commit is contained in:
@@ -4,13 +4,93 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace fesa {
|
||||
|
||||
class DofManagerTestAccess {
|
||||
public:
|
||||
static void DuplicateOwnership(DofManager& dofs) {
|
||||
dofs.constrained_dofs_[3U] = dofs.free_dofs_[7U];
|
||||
}
|
||||
|
||||
static void RemoveFullMapping(DofManager& dofs) {
|
||||
dofs.free_equations_.pop_back();
|
||||
}
|
||||
|
||||
static void ReverseFreeMapping(DofManager& dofs) {
|
||||
std::swap(dofs.free_dofs_[0U], dofs.free_dofs_[1U]);
|
||||
}
|
||||
|
||||
static void ReverseConstrainedMapping(DofManager& dofs) {
|
||||
std::swap(dofs.constrained_dofs_[0U], dofs.constrained_dofs_[1U]);
|
||||
}
|
||||
|
||||
static void CorruptEquationMapping(DofManager& dofs) {
|
||||
dofs.free_equations_[dofs.free_dofs_[0U]] = dofs.free_dofs_.size();
|
||||
}
|
||||
|
||||
static void RemoveSparsePatternEntry(DofManager& dofs) {
|
||||
const std::size_t position = dofs.sparse_pattern_.row_offsets[1U] - 1U;
|
||||
dofs.sparse_pattern_.column_indices.erase(
|
||||
dofs.sparse_pattern_.column_indices.begin() +
|
||||
static_cast<std::ptrdiff_t>(position));
|
||||
for (std::size_t row = 1U; row < dofs.sparse_pattern_.row_offsets.size();
|
||||
++row) {
|
||||
--dofs.sparse_pattern_.row_offsets[row];
|
||||
}
|
||||
}
|
||||
|
||||
static void AddSparsePatternEntry(DofManager& dofs) {
|
||||
const std::size_t position = dofs.sparse_pattern_.row_offsets[1U];
|
||||
dofs.sparse_pattern_.column_indices.insert(
|
||||
dofs.sparse_pattern_.column_indices.begin() +
|
||||
static_cast<std::ptrdiff_t>(position),
|
||||
12U);
|
||||
for (std::size_t row = 1U; row < dofs.sparse_pattern_.row_offsets.size();
|
||||
++row) {
|
||||
++dofs.sparse_pattern_.row_offsets[row];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
namespace {
|
||||
|
||||
class GenericLayoutElement final : public fesa::Element {
|
||||
public:
|
||||
explicit GenericLayoutElement(fesa::ElementDofLayout layout)
|
||||
: layout_{std::move(layout)} {}
|
||||
|
||||
const fesa::ElementDofLayout& DofLayout() const noexcept override {
|
||||
return layout_;
|
||||
}
|
||||
|
||||
fesa::Result<fesa::ElementStiffnessContribution> ComputeStiffness()
|
||||
const override {
|
||||
const std::size_t local_dof_count =
|
||||
layout_.node_indices.size() * layout_.components_per_node.size();
|
||||
return fesa::Result<fesa::ElementStiffnessContribution>::Success(
|
||||
{layout_, fesa::Matrix{local_dof_count, local_dof_count}});
|
||||
}
|
||||
|
||||
fesa::Result<fesa::ElementResultBundle> Recover(
|
||||
const fesa::Vector&) const override {
|
||||
return fesa::Result<fesa::ElementResultBundle>::Success(
|
||||
{layout_.source_id, fesa::BeamElementResultRows{}});
|
||||
}
|
||||
|
||||
private:
|
||||
fesa::ElementDofLayout layout_;
|
||||
};
|
||||
|
||||
fesa::ModelDefinition MakeDefinition() {
|
||||
const std::filesystem::path source{"models/dof-manager.inp"};
|
||||
fesa::ModelDefinition definition{};
|
||||
@@ -83,8 +163,102 @@ std::vector<std::size_t> RowColumns(const fesa::SparsePattern& pattern,
|
||||
pattern.column_indices.begin() + pattern.row_offsets[row + 1U]};
|
||||
}
|
||||
|
||||
void ExpectInvariantFailure(const fesa::DofManager& dofs,
|
||||
const std::string& code) {
|
||||
const fesa::Status status = dofs.ValidateInvariants();
|
||||
ASSERT_FALSE(status.IsOk());
|
||||
EXPECT_EQ(status.Category(), fesa::FailureCategory::kModel);
|
||||
ASSERT_EQ(status.Diagnostics().size(), 1U);
|
||||
EXPECT_EQ(status.Diagnostics()[0U].code, code);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// C-DOF-001
|
||||
TEST(DofManager, BuildsGenericRuntimeLayoutsInDeclaredSourceOrder) {
|
||||
auto domain = fesa::Domain::Create(MakeDefinition());
|
||||
ASSERT_TRUE(domain.HasValue());
|
||||
auto model = fesa::AnalysisModel::Create(domain.Value());
|
||||
ASSERT_TRUE(model.HasValue());
|
||||
|
||||
GenericLayoutElement first{fesa::ElementDofLayout{
|
||||
{"Beam-1", 100, "100"},
|
||||
{2U, 0U},
|
||||
{fesa::DofComponent::kUz, fesa::DofComponent::kUx}}};
|
||||
GenericLayoutElement second{
|
||||
fesa::ElementDofLayout{{"Beam-1", 200, "200"},
|
||||
{1U},
|
||||
{fesa::DofComponent::kUrz, fesa::DofComponent::kUy,
|
||||
fesa::DofComponent::kUrx}}};
|
||||
const fesa::ElementView elements{std::cref(first), std::cref(second)};
|
||||
|
||||
fesa::DofManager dofs;
|
||||
ASSERT_TRUE(dofs.Build(model.Value(), elements).IsOk());
|
||||
ASSERT_TRUE(dofs.ValidateInvariants().IsOk());
|
||||
|
||||
auto first_scatter = dofs.ElementScatter(first.DofLayout());
|
||||
auto second_scatter = dofs.ElementScatter(second.DofLayout());
|
||||
ASSERT_TRUE(first_scatter.HasValue());
|
||||
ASSERT_TRUE(second_scatter.HasValue());
|
||||
EXPECT_EQ(first_scatter.Value(),
|
||||
(std::vector<std::size_t>{14U, 12U, 2U, 0U}));
|
||||
EXPECT_EQ(second_scatter.Value(), (std::vector<std::size_t>{11U, 7U, 9U}));
|
||||
|
||||
const auto& pattern = dofs.GetSparsePattern();
|
||||
EXPECT_EQ(RowColumns(pattern, 14U),
|
||||
(std::vector<std::size_t>{0U, 2U, 12U, 14U}));
|
||||
EXPECT_EQ(RowColumns(pattern, 7U), (std::vector<std::size_t>{7U, 9U, 11U}));
|
||||
|
||||
const auto expected_free_dofs = dofs.FreeDofs();
|
||||
const auto expected_constrained_dofs = dofs.ConstrainedDofs();
|
||||
const auto expected_row_offsets = pattern.row_offsets;
|
||||
const auto expected_columns = pattern.column_indices;
|
||||
const fesa::ElementView reversed_elements{std::cref(second),
|
||||
std::cref(first)};
|
||||
const fesa::Status reversed_status =
|
||||
dofs.Build(model.Value(), reversed_elements);
|
||||
ASSERT_FALSE(reversed_status.IsOk());
|
||||
ASSERT_EQ(reversed_status.Diagnostics().size(), 1U);
|
||||
EXPECT_EQ(reversed_status.Diagnostics()[0U].code,
|
||||
"invalid-element-layout-order");
|
||||
EXPECT_EQ(dofs.FreeDofs(), expected_free_dofs);
|
||||
EXPECT_EQ(dofs.ConstrainedDofs(), expected_constrained_dofs);
|
||||
EXPECT_EQ(dofs.GetSparsePattern().row_offsets, expected_row_offsets);
|
||||
EXPECT_EQ(dofs.GetSparsePattern().column_indices, expected_columns);
|
||||
}
|
||||
|
||||
TEST(DofManager, RejectsEveryCorruptedOwnerMappingInvariant) {
|
||||
const auto fixture = MakeDofFixture();
|
||||
|
||||
auto duplicate = fixture.dofs;
|
||||
fesa::DofManagerTestAccess::DuplicateOwnership(duplicate);
|
||||
ExpectInvariantFailure(duplicate, "duplicate-dof-mapping");
|
||||
|
||||
auto full = fixture.dofs;
|
||||
fesa::DofManagerTestAccess::RemoveFullMapping(full);
|
||||
ExpectInvariantFailure(full, "invalid-dof-dimensions");
|
||||
|
||||
auto free = fixture.dofs;
|
||||
fesa::DofManagerTestAccess::ReverseFreeMapping(free);
|
||||
ExpectInvariantFailure(free, "invalid-free-dof-mapping");
|
||||
|
||||
auto constrained = fixture.dofs;
|
||||
fesa::DofManagerTestAccess::ReverseConstrainedMapping(constrained);
|
||||
ExpectInvariantFailure(constrained, "invalid-constrained-dof-mapping");
|
||||
|
||||
auto equation = fixture.dofs;
|
||||
fesa::DofManagerTestAccess::CorruptEquationMapping(equation);
|
||||
ExpectInvariantFailure(equation, "invalid-equation-mapping");
|
||||
|
||||
auto missing_pattern_entry = fixture.dofs;
|
||||
fesa::DofManagerTestAccess::RemoveSparsePatternEntry(missing_pattern_entry);
|
||||
ExpectInvariantFailure(missing_pattern_entry, "invalid-dof-sparse-pattern");
|
||||
|
||||
auto extra_pattern_entry = fixture.dofs;
|
||||
fesa::DofManagerTestAccess::AddSparsePatternEntry(extra_pattern_entry);
|
||||
ExpectInvariantFailure(extra_pattern_entry, "invalid-dof-sparse-pattern");
|
||||
}
|
||||
|
||||
TEST(DofManager, NumbersSixDofsAndFreeEquationsStably) {
|
||||
const auto fixture = MakeDofFixture();
|
||||
const auto& dofs = fixture.dofs;
|
||||
|
||||
Reference in New Issue
Block a user