#include #include #include #include #include #include #include #include #include #include #include "fesa/analysis/analysis_model.h" #include "fesa/fem/dof_manager.h" #include "fesa/loads/concentrated_nodal_load.h" #include "fesa/model/domain.h" #include "fesa/model/source_target_resolver.h" namespace { fesa::Domain MakeDomain() { const std::filesystem::path source{"models/load-hierarchy.inp"}; fesa::ModelDefinition definition{}; definition.source_path = source; definition.source_content_identity = "fnv1a64:loadhierarchy"; definition.nodes = {{{"Part-1-1", 10, "10"}, {0.0, 0.0, 0.0}, {source, 2U}}, {{"Part-1-1", 20, "20"}, {1.0, 0.0, 0.0}, {source, 3U}}}; definition.node_sets = { {"Pair", std::string{"Part-1-1"}, {0U, 1U}, {source, 4U}}}; definition.steps = {{"Step-1", {}, {}, 0.1, 1.0, 0.01, 1.0, {source, 5U}}}; auto domain = fesa::Domain::Create(std::move(definition)); if (!domain.HasValue()) { throw std::runtime_error{"Load hierarchy Domain construction failed."}; } return std::move(domain.Value()); } void ExpectFailureCode( const fesa::Result>& result, const std::string& code) { ASSERT_FALSE(result.HasValue()); EXPECT_EQ(result.GetStatus().Category(), fesa::FailureCategory::kModel); ASSERT_EQ(result.GetStatus().Diagnostics().size(), 1U); EXPECT_EQ(result.GetStatus().Diagnostics()[0U].code, code); } } // namespace // C-LOAD-001 TEST(Load, ConcentratedNodalLoadEmitsStableFullDofContributions) { static_assert(std::has_virtual_destructor_v); static_assert(!std::is_copy_constructible_v); static_assert(std::is_move_constructible_v); fesa::Domain domain = MakeDomain(); auto model = fesa::AnalysisModel::Create(domain); ASSERT_TRUE(model.HasValue()); auto dofs = fesa::DofManager::Create(model.Value()); ASSERT_TRUE(dofs.HasValue()); const fesa::SourceTargetIndex target_index = fesa::SourceTargetIndex::FromDomain(domain); const fesa::SourceTargetResolver resolver{target_index}; const fesa::LoadContext context{domain, dofs.Value(), resolver}; const std::array components{1.0, -2.0, 0.0, 4.0, 0.0, -6.0}; const fesa::ConcentratedNodalLoad load( {fesa::SourceEntityKind::kNode, "Part-1-1", "pair"}, components, 7U); const auto result = load.ComputeContributions(context); ASSERT_TRUE(result.HasValue()); ASSERT_EQ(result.Value().size(), 12U); for (std::size_t node = 0U; node < 2U; ++node) { for (std::size_t component = 0U; component < components.size(); ++component) { const auto& contribution = result.Value()[node * components.size() + component]; EXPECT_EQ(contribution.source_order, 7U); EXPECT_EQ(contribution.full_dof_index, node * components.size() + component); EXPECT_DOUBLE_EQ(contribution.value, components[component]); } } } TEST(Load, ConcentratedNodalLoadRejectsInvalidInputWithoutContributions) { fesa::Domain domain = MakeDomain(); auto model = fesa::AnalysisModel::Create(domain); ASSERT_TRUE(model.HasValue()); auto dofs = fesa::DofManager::Create(model.Value()); ASSERT_TRUE(dofs.HasValue()); const fesa::SourceTargetIndex target_index = fesa::SourceTargetIndex::FromDomain(domain); const fesa::SourceTargetResolver resolver{target_index}; const fesa::LoadContext context{domain, dofs.Value(), resolver}; std::array nonfinite{}; nonfinite[2U] = std::numeric_limits::infinity(); const fesa::ConcentratedNodalLoad invalid_value( {fesa::SourceEntityKind::kNode, "Part-1-1", "10"}, nonfinite, 0U); ExpectFailureCode(invalid_value.ComputeContributions(context), "nonfinite-load-value"); const fesa::ConcentratedNodalLoad invalid_target( {fesa::SourceEntityKind::kNode, "Part-1-1", "Missing"}, {}, 0U); ExpectFailureCode(invalid_target.ComputeContributions(context), "invalid-load-target"); }