#include "fesa/model/domain.h" #include #include #include #include #include #include namespace { fesa::ModelDefinition MakeOwnedDefinition() { fesa::ModelDefinition definition{}; definition.source_path = "models/owned.inp"; definition.source_content_identity = "fnv1a64:fedcba9876543210"; definition.heading = "Stable declaration order"; definition.nodes = { {{"Instance-A", 20, "0020"}, {2.0, 0.0, 0.0}, {"models/owned.inp", 12U}}, {{"Instance-A", 10, "0010"}, {1.0, 0.0, 0.0}, {"models/owned.inp", 11U}}}; definition.elements = {{{"Instance-A", 5, "0005"}, {fesa::EntityIndex{1}, fesa::EntityIndex{0}}, fesa::EntityIndex{0}, fesa::EntityIndex{0}, {"models/owned.inp", 20U}}}; definition.materials = {{"Steel", 210.0e9, 0.3, {"models/owned.inp", 30U}}}; definition.sections = {{"Section-1", 0.02, 1.0e-5, 0.0, 2.0e-5, 5.0e-6, {0.0, 1.0, 0.0}, {{-0.1, 0.0}, {0.1, 0.0}}, {"models/owned.inp", 40U}}}; definition.node_sets = { {"Tip", std::string{"Instance-A"}, {0U}, {"models/owned.inp", 45U}}}; definition.element_sets = { {"Beam", std::string{"Instance-A"}, {0U}, {"models/owned.inp", 46U}}}; definition.parts = { {"BeamPart", {20, 10}, {5}, {"Tip"}, {"Beam"}, {"models/owned.inp", 2U}}}; definition.instances = {{"Instance-A", "BeamPart", {{20, 0U}, {10, 1U}}, {{5, 0U}}, {"models/owned.inp", 50U}}}; definition.steps = {{"Step-1", {{"Root", 1, 6, 0.0, {"models/owned.inp", 60U}}}, {{"Tip", 3, -1000.0, {"models/owned.inp", 61U}}}, 0.1, 1.0, 1.0e-5, 1.0, {"models/owned.inp", 55U}}}; definition.warnings = {{fesa::Severity::kWarning, "ignored-output-request", {"models/owned.inp", 70U}, "*OUTPUT", "", "Ignored output request."}}; return definition; } } // namespace TEST(DomainModel, ImmutableOwnershipPreservesStableOrder) { auto definition = MakeOwnedDefinition(); auto result = fesa::Domain::Create(definition); ASSERT_TRUE(result.HasValue()); definition.source_path = "mutated.inp"; definition.source_content_identity = "mutated"; definition.nodes[0].source_id.source_label_text = "mutated"; definition.nodes[0].coordinates[0] = -99.0; definition.sections[0].section_points[0][0] = -99.0; definition.steps[0].loads[0].magnitude = 99.0; definition.warnings[0].code = "mutated"; const fesa::Domain& domain = result.Value(); const fesa::Node* const first_node_address = domain.Nodes().data(); static_assert( std::is_same_v().Nodes()), const std::vector&>); static_assert( std::is_same_v().Elements()), const std::vector&>); EXPECT_EQ(domain.SourcePath(), std::filesystem::path{"models/owned.inp"}); EXPECT_EQ(domain.SourceContentIdentity(), "fnv1a64:fedcba9876543210"); ASSERT_EQ(domain.Nodes().size(), 2U); EXPECT_EQ(domain.Nodes()[0].source_id.source_label, 20); EXPECT_EQ(domain.Nodes()[0].source_id.source_label_text, "0020"); EXPECT_DOUBLE_EQ(domain.Nodes()[0].coordinates[0], 2.0); EXPECT_EQ(domain.Nodes()[1].source_id.source_label, 10); EXPECT_EQ(domain.Nodes().data(), first_node_address); ASSERT_EQ(domain.Elements().size(), 1U); EXPECT_EQ(domain.Elements()[0].node_indices[0], 1U); EXPECT_EQ(domain.Elements()[0].node_indices[1], 0U); ASSERT_EQ(domain.Materials().size(), 1U); ASSERT_EQ(domain.Sections().size(), 1U); EXPECT_DOUBLE_EQ(domain.Sections()[0].section_points[0][0], -0.1); ASSERT_EQ(domain.NodeSets().size(), 1U); EXPECT_EQ(domain.NodeSets()[0].node_indices[0], 0U); ASSERT_EQ(domain.ElementSets().size(), 1U); EXPECT_EQ(domain.ElementSets()[0].element_indices[0], 0U); ASSERT_EQ(domain.Steps().size(), 1U); EXPECT_EQ(domain.Steps()[0].name, "Step-1"); EXPECT_DOUBLE_EQ(domain.Steps()[0].initial_increment, 0.1); EXPECT_DOUBLE_EQ(domain.Steps()[0].time_period, 1.0); EXPECT_DOUBLE_EQ(domain.Steps()[0].minimum_increment, 1.0e-5); EXPECT_DOUBLE_EQ(domain.Steps()[0].maximum_increment, 1.0); ASSERT_EQ(domain.Steps()[0].boundaries.size(), 1U); ASSERT_EQ(domain.Steps()[0].loads.size(), 1U); EXPECT_DOUBLE_EQ(domain.Steps()[0].loads[0].magnitude, -1000.0); ASSERT_EQ(domain.Warnings().size(), 1U); EXPECT_EQ(domain.Warnings()[0].code, "ignored-output-request"); } TEST(DomainModel, MultipleIdentityInstancesDoNotMerge) { fesa::ModelDefinition definition{}; definition.source_path = "models/two-instances.inp"; definition.source_content_identity = "fnv1a64:0011223344556677"; definition.nodes = {{{"Instance-A", 1, "1"}, {0.0, 0.0, 0.0}, {"models/two-instances.inp", 10U}}, {{"Instance-A", 2, "2"}, {1.0, 0.0, 0.0}, {"models/two-instances.inp", 11U}}, {{"Instance-B", 1, "1"}, {0.0, 0.0, 0.0}, {"models/two-instances.inp", 10U}}, {{"Instance-B", 2, "2"}, {1.0, 0.0, 0.0}, {"models/two-instances.inp", 11U}}}; definition.elements = {{{"Instance-A", 1, "1"}, {0U, 1U}, 0U, 0U, {"models/two-instances.inp", 20U}}, {{"Instance-B", 1, "1"}, {2U, 3U}, 0U, 0U, {"models/two-instances.inp", 20U}}}; definition.materials = { {"Steel", 210.0e9, 0.3, {"models/two-instances.inp", 30U}}}; definition.sections = {{"Section-1", 0.02, 1.0e-5, 0.0, 2.0e-5, 5.0e-6, {0.0, 1.0, 0.0}, {}, {"models/two-instances.inp", 40U}}}; definition.parts = { {"BeamPart", {1, 2}, {1}, {}, {}, {"models/two-instances.inp", 2U}}}; definition.instances = {{"Instance-A", "BeamPart", {{1, 0U}, {2, 1U}}, {{1, 0U}}, {"models/two-instances.inp", 50U}}, {"Instance-B", "BeamPart", {{1, 2U}, {2, 3U}}, {{1, 1U}}, {"models/two-instances.inp", 51U}}}; definition.steps = {{"Step-1", {}, {}, 0.1, 1.0, 1.0e-5, 1.0, {"models/two-instances.inp", 60U}}}; auto result = fesa::Domain::Create(std::move(definition)); ASSERT_TRUE(result.HasValue()); const fesa::Domain& domain = result.Value(); ASSERT_EQ(domain.Nodes().size(), 4U); EXPECT_EQ(domain.Nodes()[0].source_id.source_label, 1); EXPECT_EQ(domain.Nodes()[2].source_id.source_label, 1); EXPECT_EQ(domain.Nodes()[0].source_id.source_label_text, "1"); EXPECT_EQ(domain.Nodes()[2].source_id.source_label_text, "1"); EXPECT_NE(domain.Nodes()[0].source_id.instance_name, domain.Nodes()[2].source_id.instance_name); ASSERT_EQ(domain.Elements().size(), 2U); EXPECT_EQ(domain.Elements()[0].source_id.source_label, 1); EXPECT_EQ(domain.Elements()[1].source_id.source_label, 1); EXPECT_EQ(domain.Elements()[0].source_id.instance_name, "Instance-A"); EXPECT_EQ(domain.Elements()[1].source_id.instance_name, "Instance-B"); EXPECT_EQ(domain.Elements()[0].node_indices[0], 0U); EXPECT_EQ(domain.Elements()[1].node_indices[0], 2U); } // MITC4-MODEL-002 TEST(DomainModel, ShellOwnershipPreservesResolvedAssignmentsAndOptionalFrames) { fesa::ModelDefinition definition{}; definition.source_path = "models/shell-owned.inp"; definition.source_content_identity = "fnv1a64:1234567890abcdef"; definition.materials = { {"Material-B", 70.0e9, 0.33, {"models/shell-owned.inp", 31U}}, {"Material-A", 210.0e9, 0.3, {"models/shell-owned.inp", 30U}}}; definition.shell_sections = { {"Section-B", 0.02, 0U, {"models/shell-owned.inp", 41U}}, {"Section-A", 0.01, 1U, {"models/shell-owned.inp", 40U}}}; definition.shell_elements = {{{"Shell-Instance", 20, "0020"}, fesa::ShellSourceElementType::kS4r, {4U, 5U, 6U, 7U}, 0U, 0U, {"models/shell-owned.inp", 21U}}, {{"Shell-Instance", 10, "0010"}, fesa::ShellSourceElementType::kS4, {0U, 1U, 2U, 3U}, 1U, 1U, {"models/shell-owned.inp", 20U}}}; definition.shell_node_initial_frames = { {4U, {0.0, 0.0, 1.0}, {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}}, {0U, {0.0, 1.0, 0.0}, {1.0, 0.0, 0.0}, {0.0, 0.0, -1.0}}}; auto result = fesa::Domain::Create(definition); ASSERT_TRUE(result.HasValue()); definition.shell_sections[0].thickness = -1.0; definition.shell_elements[0].source_id.source_label_text = "mutated"; definition.shell_node_initial_frames[0].director[2] = -1.0; const fesa::Domain& domain = result.Value(); static_assert(std::is_same_v< decltype(std::declval().ShellElements()), const std::vector&>); static_assert(std::is_same_v< decltype(std::declval().ShellSections()), const std::vector&>); static_assert( std::is_same_v() .ShellNodeInitialFrames()), const std::vector&>); ASSERT_EQ(domain.ShellElements().size(), 2U); EXPECT_EQ(domain.ShellElements()[0].source_id.source_label, 20); EXPECT_EQ(domain.ShellElements()[0].source_id.source_label_text, "0020"); EXPECT_EQ(domain.ShellElements()[1].source_id.source_label, 10); EXPECT_EQ(domain.ShellElements()[0].source_type, fesa::ShellSourceElementType::kS4r); EXPECT_EQ(domain.ShellElements()[1].source_type, fesa::ShellSourceElementType::kS4); EXPECT_EQ(domain.ShellElements()[0].node_indices[3], 7U); EXPECT_EQ(domain.ShellElements()[0].material_index, 0U); EXPECT_EQ(domain.ShellElements()[0].section_index, 0U); ASSERT_EQ(domain.ShellSections().size(), 2U); EXPECT_EQ(domain.ShellSections()[0].name, "Section-B"); EXPECT_DOUBLE_EQ(domain.ShellSections()[0].thickness, 0.02); EXPECT_EQ(domain.ShellSections()[0].material_index, 0U); EXPECT_EQ(domain.ShellSections()[1].name, "Section-A"); ASSERT_EQ(domain.ShellNodeInitialFrames().size(), 2U); EXPECT_EQ(domain.ShellNodeInitialFrames()[0].node_index, 4U); EXPECT_EQ(domain.ShellNodeInitialFrames()[0].director, (std::array{0.0, 0.0, 1.0})); EXPECT_EQ(domain.ShellNodeInitialFrames()[0].tangent_a, (std::array{1.0, 0.0, 0.0})); EXPECT_EQ(domain.ShellNodeInitialFrames()[0].tangent_b, (std::array{0.0, 1.0, 0.0})); auto no_frames_result = fesa::Domain::Create(fesa::ModelDefinition{}); ASSERT_TRUE(no_frames_result.HasValue()); EXPECT_TRUE(no_frames_result.Value().ShellNodeInitialFrames().empty()); }