feat(cpp-object-oriented-modular-refactoring): step 19 - boundary-condition-policy
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
#include "fesa/constraints/boundary_condition.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/analysis/analysis_model.h"
|
||||
#include "fesa/constraints/prescribed_displacement.h"
|
||||
#include "fesa/fem/dof_manager.h"
|
||||
#include "fesa/model/domain.h"
|
||||
#include "fesa/model/source_target_resolver.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct BoundaryFixture {
|
||||
std::unique_ptr<fesa::Domain> domain;
|
||||
std::unique_ptr<fesa::AnalysisModel> model;
|
||||
std::unique_ptr<fesa::DofManager> dofs;
|
||||
};
|
||||
|
||||
fesa::ModelDefinition MakeDefinition(
|
||||
std::vector<fesa::PrescribedDisplacementDefinition> boundaries = {}) {
|
||||
const std::filesystem::path source{"models/boundary-condition.inp"};
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.source_path = source;
|
||||
definition.source_content_identity = "fnv1a64:boundary";
|
||||
definition.nodes = {{{"Beam-1", 10, "10"}, {0.0, 0.0, 0.0}, {source, 2U}},
|
||||
{{"Beam-1", 20, "20"}, {1.0, 0.0, 0.0}, {source, 3U}}};
|
||||
definition.node_sets = {
|
||||
{"Pair", std::string{"Beam-1"}, {1U, 0U}, {source, 4U}}};
|
||||
definition.steps = {{"Step-1",
|
||||
std::move(boundaries),
|
||||
{},
|
||||
0.1,
|
||||
1.0,
|
||||
0.01,
|
||||
1.0,
|
||||
{source, 10U}}};
|
||||
return definition;
|
||||
}
|
||||
|
||||
BoundaryFixture MakeFixture(
|
||||
std::vector<fesa::PrescribedDisplacementDefinition> boundaries = {}) {
|
||||
auto domain_result =
|
||||
fesa::Domain::Create(MakeDefinition(std::move(boundaries)));
|
||||
EXPECT_TRUE(domain_result.HasValue());
|
||||
auto domain =
|
||||
std::make_unique<fesa::Domain>(std::move(domain_result.Value()));
|
||||
|
||||
auto model_result = fesa::AnalysisModel::Create(*domain);
|
||||
EXPECT_TRUE(model_result.HasValue());
|
||||
auto model =
|
||||
std::make_unique<fesa::AnalysisModel>(std::move(model_result.Value()));
|
||||
|
||||
auto dof_result = fesa::DofManager::Create(*model);
|
||||
EXPECT_TRUE(dof_result.HasValue());
|
||||
auto dofs = std::make_unique<fesa::DofManager>(std::move(dof_result.Value()));
|
||||
return {std::move(domain), std::move(model), std::move(dofs)};
|
||||
}
|
||||
|
||||
class FakeBoundaryCondition final : public fesa::BoundaryCondition {
|
||||
public:
|
||||
explicit FakeBoundaryCondition(
|
||||
std::vector<fesa::ConstraintDefinition> definitions)
|
||||
: definitions_{std::move(definitions)} {}
|
||||
|
||||
fesa::Result<std::vector<fesa::ConstraintDefinition>> ResolveConstraints(
|
||||
const fesa::BoundaryConditionContext&) const override {
|
||||
return fesa::Result<std::vector<fesa::ConstraintDefinition>>::Success(
|
||||
definitions_);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<fesa::ConstraintDefinition> definitions_;
|
||||
};
|
||||
|
||||
void ExpectFailureCode(const fesa::Status& status, const std::string& code) {
|
||||
ASSERT_FALSE(status.IsOk());
|
||||
ASSERT_EQ(status.Diagnostics().size(), 1U);
|
||||
EXPECT_EQ(status.Diagnostics()[0U].code, code);
|
||||
}
|
||||
|
||||
void ExpectCandidate(const fesa::DofManager& dofs,
|
||||
const std::vector<std::size_t>& constrained_dofs,
|
||||
const std::vector<double>& prescribed_values) {
|
||||
EXPECT_EQ(dofs.ConstrainedDofs(), constrained_dofs);
|
||||
ASSERT_EQ(dofs.PrescribedValues().Size(), prescribed_values.size());
|
||||
for (std::size_t index = 0U; index < prescribed_values.size(); ++index) {
|
||||
EXPECT_DOUBLE_EQ(dofs.PrescribedValues()[index], prescribed_values[index]);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// C-BC-001
|
||||
TEST(BoundaryCondition, ResolvesStableNonzeroDefinitionsThroughBase) {
|
||||
static_assert(std::has_virtual_destructor_v<fesa::BoundaryCondition>);
|
||||
auto fixture = MakeFixture();
|
||||
const fesa::SourceTargetIndex target_index =
|
||||
fesa::SourceTargetIndex::FromDomain(*fixture.domain);
|
||||
const fesa::SourceTargetResolver resolver{target_index};
|
||||
const fesa::BoundaryConditionContext context{*fixture.domain, *fixture.dofs,
|
||||
resolver};
|
||||
const fesa::PrescribedDisplacementBoundaryCondition prescribed(
|
||||
{fesa::SourceEntityKind::kNode, "Beam-1", "pair"},
|
||||
fesa::DofComponent::kUz, 2.5, 4U);
|
||||
const fesa::BoundaryCondition& boundary = prescribed;
|
||||
|
||||
const auto definitions = boundary.ResolveConstraints(context);
|
||||
|
||||
ASSERT_TRUE(definitions.HasValue());
|
||||
ASSERT_EQ(definitions.Value().size(), 2U);
|
||||
EXPECT_EQ(definitions.Value()[0U].source_order, 4U);
|
||||
EXPECT_EQ(definitions.Value()[0U].full_dof_index, 8U);
|
||||
EXPECT_DOUBLE_EQ(definitions.Value()[0U].prescribed_value, 2.5);
|
||||
EXPECT_EQ(definitions.Value()[1U].source_order, 4U);
|
||||
EXPECT_EQ(definitions.Value()[1U].full_dof_index, 2U);
|
||||
EXPECT_DOUBLE_EQ(definitions.Value()[1U].prescribed_value, 2.5);
|
||||
}
|
||||
|
||||
TEST(BoundaryCondition, DomainOwnsExpandedPolymorphicDefinitionsStably) {
|
||||
const std::filesystem::path source{"models/boundary-condition.inp"};
|
||||
auto fixture = MakeFixture({{"Pair", 1, 2, 1.25, {source, 12U}}});
|
||||
|
||||
const auto& boundaries = fixture.model->Step().BoundaryConditions();
|
||||
ASSERT_EQ(boundaries.size(), 2U);
|
||||
EXPECT_EQ(fixture.model->ActiveBoundaryConditions(),
|
||||
(std::vector<fesa::EntityIndex>{0U, 1U}));
|
||||
|
||||
const fesa::SourceTargetIndex target_index =
|
||||
fesa::SourceTargetIndex::FromDomain(*fixture.domain);
|
||||
const fesa::SourceTargetResolver resolver{target_index};
|
||||
const fesa::BoundaryConditionContext context{*fixture.domain, *fixture.dofs,
|
||||
resolver};
|
||||
const auto first = boundaries[0U].get().ResolveConstraints(context);
|
||||
const auto second = boundaries[1U].get().ResolveConstraints(context);
|
||||
ASSERT_TRUE(first.HasValue());
|
||||
ASSERT_TRUE(second.HasValue());
|
||||
EXPECT_EQ(first.Value()[0U].source_order, 0U);
|
||||
EXPECT_EQ(first.Value()[0U].full_dof_index, 6U);
|
||||
EXPECT_EQ(second.Value()[0U].source_order, 1U);
|
||||
EXPECT_EQ(second.Value()[0U].full_dof_index, 7U);
|
||||
EXPECT_DOUBLE_EQ(first.Value()[0U].prescribed_value, 1.25);
|
||||
EXPECT_DOUBLE_EQ(second.Value()[0U].prescribed_value, 1.25);
|
||||
}
|
||||
|
||||
TEST(BoundaryCondition,
|
||||
RejectsDuplicateConflictAndNonfiniteBeforeCandidateCommit) {
|
||||
auto fixture = MakeFixture();
|
||||
const fesa::ElementView elements;
|
||||
FakeBoundaryCondition initial({{0U, 1U, 3.0}});
|
||||
ASSERT_TRUE(
|
||||
fixture.dofs->Build(*fixture.model, elements, {std::cref(initial)})
|
||||
.IsOk());
|
||||
ExpectCandidate(*fixture.dofs, {1U}, {3.0});
|
||||
|
||||
FakeBoundaryCondition duplicate({{0U, 2U, 4.0}, {0U, 2U, 4.0}});
|
||||
ExpectFailureCode(
|
||||
fixture.dofs->Build(*fixture.model, elements, {std::cref(duplicate)}),
|
||||
"duplicate-constraint-definition");
|
||||
ExpectCandidate(*fixture.dofs, {1U}, {3.0});
|
||||
|
||||
FakeBoundaryCondition first({{0U, 2U, 4.0}});
|
||||
FakeBoundaryCondition conflicting({{1U, 2U, -4.0}});
|
||||
ExpectFailureCode(
|
||||
fixture.dofs->Build(*fixture.model, elements,
|
||||
{std::cref(first), std::cref(conflicting)}),
|
||||
"conflicting-boundary-condition");
|
||||
ExpectCandidate(*fixture.dofs, {1U}, {3.0});
|
||||
|
||||
FakeBoundaryCondition nonfinite(
|
||||
{{0U, 2U, std::numeric_limits<double>::quiet_NaN()}});
|
||||
ExpectFailureCode(
|
||||
fixture.dofs->Build(*fixture.model, elements, {std::cref(nonfinite)}),
|
||||
"nonfinite-prescribed-value");
|
||||
ExpectCandidate(*fixture.dofs, {1U}, {3.0});
|
||||
}
|
||||
|
||||
TEST(BoundaryCondition, AcceptsIdenticalOverlapAcrossSourceDefinitions) {
|
||||
auto fixture = MakeFixture();
|
||||
FakeBoundaryCondition first({{0U, 4U, -2.0}});
|
||||
FakeBoundaryCondition second({{1U, 4U, -2.0}});
|
||||
|
||||
const fesa::Status status = fixture.dofs->Build(
|
||||
*fixture.model, {}, {std::cref(first), std::cref(second)});
|
||||
|
||||
ASSERT_TRUE(status.IsOk());
|
||||
ExpectCandidate(*fixture.dofs, {4U}, {-2.0});
|
||||
}
|
||||
|
||||
TEST(BoundaryCondition, ConcreteRejectsNonfinitePrescribedValue) {
|
||||
auto fixture = MakeFixture();
|
||||
const fesa::SourceTargetIndex target_index =
|
||||
fesa::SourceTargetIndex::FromDomain(*fixture.domain);
|
||||
const fesa::SourceTargetResolver resolver{target_index};
|
||||
const fesa::BoundaryConditionContext context{*fixture.domain, *fixture.dofs,
|
||||
resolver};
|
||||
const fesa::PrescribedDisplacementBoundaryCondition prescribed(
|
||||
{fesa::SourceEntityKind::kNode, "Beam-1", "10"}, fesa::DofComponent::kUx,
|
||||
std::numeric_limits<double>::infinity(), 0U);
|
||||
|
||||
const auto result = prescribed.ResolveConstraints(context);
|
||||
|
||||
ASSERT_FALSE(result.HasValue());
|
||||
ASSERT_EQ(result.GetStatus().Diagnostics().size(), 1U);
|
||||
EXPECT_EQ(result.GetStatus().Diagnostics()[0U].code,
|
||||
"nonfinite-prescribed-value");
|
||||
}
|
||||
Reference in New Issue
Block a user