#include "fesa/constraints/boundary_condition.h" #include #include #include #include #include #include #include #include #include #include #include #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 domain; std::unique_ptr model; std::unique_ptr dofs; }; fesa::ModelDefinition MakeDefinition( std::vector 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 boundaries = {}) { auto domain_result = fesa::Domain::Create(MakeDefinition(std::move(boundaries))); EXPECT_TRUE(domain_result.HasValue()); auto domain = std::make_unique(std::move(domain_result.Value())); auto model_result = fesa::AnalysisModel::Create(*domain); EXPECT_TRUE(model_result.HasValue()); auto model = std::make_unique(std::move(model_result.Value())); auto dof_result = fesa::DofManager::Create(*model); EXPECT_TRUE(dof_result.HasValue()); auto dofs = std::make_unique(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 definitions) : definitions_{std::move(definitions)} {} fesa::Result> ResolveConstraints( const fesa::BoundaryConditionContext&) const override { return fesa::Result>::Success( definitions_); } private: std::vector 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& constrained_dofs, const std::vector& 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); 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{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::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::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"); }