From c7e64eb841461d60b970fd71efcb48793e22ff83 Mon Sep 17 00:00:00 2001 From: "KOKO\\Mimi" Date: Thu, 30 Jul 2026 16:38:05 +0900 Subject: [PATCH] =?UTF-8?q?feat(domain-and-input-skeleton):=20step=200=20?= =?UTF-8?q?=E2=80=94=20semantic-domain-and-origin-types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/fesa/model/beam_element.hpp | 18 ++++ include/fesa/model/beam_section.hpp | 28 ++++++ include/fesa/model/domain.hpp | 96 +++++++++++++++++++ include/fesa/model/entity_origin.hpp | 16 ++++ include/fesa/model/entity_set.hpp | 20 ++++ include/fesa/model/ids.hpp | 17 ++++ include/fesa/model/material.hpp | 16 ++++ include/fesa/model/node.hpp | 15 +++ include/fesa/model/step_definition.hpp | 29 ++++++ tests/CMakeLists.txt | 24 +++++ tests/unit/model/entity_origin_test.cpp | 55 +++++++++++ tests/unit/model/model_types_test.cpp | 121 ++++++++++++++++++++++++ 12 files changed, 455 insertions(+) create mode 100644 include/fesa/model/beam_element.hpp create mode 100644 include/fesa/model/beam_section.hpp create mode 100644 include/fesa/model/domain.hpp create mode 100644 include/fesa/model/entity_origin.hpp create mode 100644 include/fesa/model/entity_set.hpp create mode 100644 include/fesa/model/ids.hpp create mode 100644 include/fesa/model/material.hpp create mode 100644 include/fesa/model/node.hpp create mode 100644 include/fesa/model/step_definition.hpp create mode 100644 tests/unit/model/entity_origin_test.cpp create mode 100644 tests/unit/model/model_types_test.cpp diff --git a/include/fesa/model/beam_element.hpp b/include/fesa/model/beam_element.hpp new file mode 100644 index 0000000..8b08c0f --- /dev/null +++ b/include/fesa/model/beam_element.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include +#include + +namespace fesa { + +struct BeamElement final { + ElementId id; + EntityOrigin origin; + std::array nodes; + MaterialId material; + SectionId section; +}; + +} // namespace fesa diff --git a/include/fesa/model/beam_section.hpp b/include/fesa/model/beam_section.hpp new file mode 100644 index 0000000..9ddc8e4 --- /dev/null +++ b/include/fesa/model/beam_section.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include + +#include +#include + +namespace fesa { + +enum class ShearPropertySource { input, phase1_default }; + +struct BeamSection final { + SectionId id; + std::string name; + double area; + double iy; + double iz; + double torsion_j; + double shear_area_y; + double shear_area_z; + ShearPropertySource shear_source; + Vec3 orientation; + std::vector> recovery_points; +}; + +} // namespace fesa diff --git a/include/fesa/model/domain.hpp b/include/fesa/model/domain.hpp new file mode 100644 index 0000000..7ec46db --- /dev/null +++ b/include/fesa/model/domain.hpp @@ -0,0 +1,96 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace fesa { + +class Domain final { +public: + Domain( + std::vector nodes, + std::vector beam_elements, + std::vector materials, + std::vector sections, + std::vector node_sets, + std::vector element_sets, + StepDefinition step) + : nodes_{std::move(nodes)}, + beam_elements_{std::move(beam_elements)}, + materials_{std::move(materials)}, + sections_{std::move(sections)}, + node_sets_{std::move(node_sets)}, + element_sets_{std::move(element_sets)}, + step_{std::move(step)} {} + + [[nodiscard]] std::span nodes() const noexcept { + return nodes_; + } + + [[nodiscard]] std::span beam_elements() const noexcept { + return beam_elements_; + } + + [[nodiscard]] std::span materials() const noexcept { + return materials_; + } + + [[nodiscard]] std::span sections() const noexcept { + return sections_; + } + + [[nodiscard]] std::span node_sets() const noexcept { + return node_sets_; + } + + [[nodiscard]] std::span element_sets() const noexcept { + return element_sets_; + } + + [[nodiscard]] const StepDefinition& step() const noexcept { + return step_; + } + + [[nodiscard]] const Node& node(const NodeId id) const { + const auto found = std::find_if( + nodes_.begin(), nodes_.end(), + [id](const Node& candidate) { return candidate.id == id; }); + if (found == nodes_.end()) { + throw std::out_of_range{"Node ID is not present in the Domain."}; + } + return *found; + } + + [[nodiscard]] const Node& node(const EntityOrigin& origin) const { + const auto found = std::find_if( + nodes_.begin(), nodes_.end(), [&origin](const Node& candidate) { + return candidate.origin == origin; + }); + if (found == nodes_.end()) { + throw std::out_of_range{ + "Node origin is not present in the Domain."}; + } + return *found; + } + +private: + std::vector nodes_; + std::vector beam_elements_; + std::vector materials_; + std::vector sections_; + std::vector node_sets_; + std::vector element_sets_; + StepDefinition step_; +}; + +} // namespace fesa diff --git a/include/fesa/model/entity_origin.hpp b/include/fesa/model/entity_origin.hpp new file mode 100644 index 0000000..3d374a5 --- /dev/null +++ b/include/fesa/model/entity_origin.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +namespace fesa { + +struct EntityOrigin final { + std::string part_name; + std::string instance_name; + std::int64_t local_label; + + bool operator==(const EntityOrigin&) const = default; +}; + +} // namespace fesa diff --git a/include/fesa/model/entity_set.hpp b/include/fesa/model/entity_set.hpp new file mode 100644 index 0000000..48b7582 --- /dev/null +++ b/include/fesa/model/entity_set.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +#include + +namespace fesa { + +struct NodeSet final { + std::string name; + std::vector members; +}; + +struct ElementSet final { + std::string name; + std::vector members; +}; + +} // namespace fesa diff --git a/include/fesa/model/ids.hpp b/include/fesa/model/ids.hpp new file mode 100644 index 0000000..01bbe42 --- /dev/null +++ b/include/fesa/model/ids.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace fesa { + +struct NodeTag; +struct ElementTag; +struct MaterialTag; +struct SectionTag; + +using NodeId = EntityId; +using ElementId = EntityId; +using MaterialId = EntityId; +using SectionId = EntityId; + +} // namespace fesa diff --git a/include/fesa/model/material.hpp b/include/fesa/model/material.hpp new file mode 100644 index 0000000..e0c98f9 --- /dev/null +++ b/include/fesa/model/material.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +#include + +namespace fesa { + +struct IsotropicElastic final { + MaterialId id; + std::string name; + double young; + double poisson; +}; + +} // namespace fesa diff --git a/include/fesa/model/node.hpp b/include/fesa/model/node.hpp new file mode 100644 index 0000000..3002f58 --- /dev/null +++ b/include/fesa/model/node.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include +#include + +namespace fesa { + +struct Node final { + NodeId id; + EntityOrigin origin; + Vec3 position; +}; + +} // namespace fesa diff --git a/include/fesa/model/step_definition.hpp b/include/fesa/model/step_definition.hpp new file mode 100644 index 0000000..4c9b14b --- /dev/null +++ b/include/fesa/model/step_definition.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace fesa { + +struct PrescribedDof final { + NodeId node; + std::uint8_t dof; + double value; +}; + +struct NodalLoad final { + NodeId node; + std::array values; +}; + +struct StepDefinition final { + std::string name; + std::vector prescribed_dofs; + std::vector nodal_loads; +}; + +} // namespace fesa diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 94eff40..17e47b6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -75,3 +75,27 @@ add_test( NAME CoreValueTypes COMMAND "$" ) + +add_executable(fesa_model_value_tests + unit/model/entity_origin_test.cpp + unit/model/model_types_test.cpp +) + +target_compile_features(fesa_model_value_tests PRIVATE cxx_std_20) +target_compile_options(fesa_model_value_tests PRIVATE /W4 /permissive- /EHsc) + +target_link_libraries(fesa_model_value_tests + PRIVATE + fesa_core + GTest::gtest_main +) + +add_test( + NAME ModelTypes + COMMAND "$" --gtest_filter=ModelTypes.* +) + +add_test( + NAME EntityOrigin + COMMAND "$" --gtest_filter=EntityOrigin.* +) diff --git a/tests/unit/model/entity_origin_test.cpp b/tests/unit/model/entity_origin_test.cpp new file mode 100644 index 0000000..955c872 --- /dev/null +++ b/tests/unit/model/entity_origin_test.cpp @@ -0,0 +1,55 @@ +#include + +#include +#include +#include +#include + +#include + +namespace { + +static_assert(std::is_same_v< + decltype(std::declval().nodes()), + std::span>); +static_assert(std::is_same_v< + decltype(std::declval().node( + std::declval())), + const fesa::Node&>); + +TEST(EntityOrigin, UsesEmptyScopeNamesForFlatMeshEntities) { + const fesa::EntityOrigin origin{"", "", 17}; + + EXPECT_TRUE(origin.part_name.empty()); + EXPECT_TRUE(origin.instance_name.empty()); + EXPECT_EQ(origin.local_label, 17); +} + +TEST(EntityOrigin, FindsHierarchicalNodeByCompositeOrigin) { + const fesa::Node node{ + fesa::NodeId{0}, + fesa::EntityOrigin{"BeamPart", "Beam-1", 101}, + fesa::Vec3{1.0, 2.0, 3.0}, + }; + const fesa::Domain domain{ + std::vector{node}, + {}, + {}, + {}, + {}, + {}, + fesa::StepDefinition{"Load", {}, {}}, + }; + + const fesa::EntityOrigin lookup{"BeamPart", "Beam-1", 101}; + const fesa::Node& found_by_origin = domain.node(lookup); + const fesa::Node& found_by_id = domain.node(fesa::NodeId{0}); + + EXPECT_EQ(&found_by_origin, &domain.nodes().front()); + EXPECT_EQ(&found_by_id, &domain.nodes().front()); + EXPECT_EQ(found_by_origin.origin.part_name, "BeamPart"); + EXPECT_EQ(found_by_origin.origin.instance_name, "Beam-1"); + EXPECT_EQ(found_by_origin.origin.local_label, 101); +} + +} // namespace diff --git a/tests/unit/model/model_types_test.cpp b/tests/unit/model/model_types_test.cpp new file mode 100644 index 0000000..e1b9217 --- /dev/null +++ b/tests/unit/model/model_types_test.cpp @@ -0,0 +1,121 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace { + +TEST(ModelTypes, PreservesNodeAndBeamElementValues) { + const fesa::Node first_node{ + fesa::NodeId{0}, + fesa::EntityOrigin{"BeamPart", "Beam-1", 101}, + fesa::Vec3{1.0, 2.0, 3.0}, + }; + const fesa::BeamElement element{ + fesa::ElementId{4}, + fesa::EntityOrigin{"BeamPart", "Beam-1", 201}, + {fesa::NodeId{0}, fesa::NodeId{1}}, + fesa::MaterialId{2}, + fesa::SectionId{3}, + }; + + EXPECT_EQ(first_node.id.value(), 0); + EXPECT_EQ(first_node.origin.part_name, "BeamPart"); + EXPECT_EQ(first_node.origin.instance_name, "Beam-1"); + EXPECT_EQ(first_node.origin.local_label, 101); + EXPECT_DOUBLE_EQ(first_node.position.x, 1.0); + EXPECT_DOUBLE_EQ(first_node.position.y, 2.0); + EXPECT_DOUBLE_EQ(first_node.position.z, 3.0); + + EXPECT_EQ(element.id.value(), 4); + EXPECT_EQ(element.origin.local_label, 201); + EXPECT_EQ(element.nodes[0].value(), 0); + EXPECT_EQ(element.nodes[1].value(), 1); + EXPECT_EQ(element.material.value(), 2); + EXPECT_EQ(element.section.value(), 3); +} + +TEST(ModelTypes, PreservesMaterialAndBeamSectionValues) { + const fesa::IsotropicElastic material{ + fesa::MaterialId{2}, + "Steel", + 210.0e9, + 0.3, + }; + const fesa::BeamSection section{ + fesa::SectionId{3}, + "General", + 0.04, + 1.2e-4, + 1.4e-4, + 2.0e-4, + 0.03, + 0.031, + fesa::ShearPropertySource::input, + fesa::Vec3{0.0, 1.0, 0.0}, + {{-0.1, 0.0}, {0.1, 0.0}}, + }; + + EXPECT_EQ(material.id.value(), 2); + EXPECT_EQ(material.name, "Steel"); + EXPECT_DOUBLE_EQ(material.young, 210.0e9); + EXPECT_DOUBLE_EQ(material.poisson, 0.3); + + EXPECT_EQ(section.id.value(), 3); + EXPECT_EQ(section.name, "General"); + EXPECT_DOUBLE_EQ(section.area, 0.04); + EXPECT_DOUBLE_EQ(section.iy, 1.2e-4); + EXPECT_DOUBLE_EQ(section.iz, 1.4e-4); + EXPECT_DOUBLE_EQ(section.torsion_j, 2.0e-4); + EXPECT_DOUBLE_EQ(section.shear_area_y, 0.03); + EXPECT_DOUBLE_EQ(section.shear_area_z, 0.031); + EXPECT_EQ(section.shear_source, fesa::ShearPropertySource::input); + EXPECT_DOUBLE_EQ(section.orientation.y, 1.0); + ASSERT_EQ(section.recovery_points.size(), 2); + EXPECT_DOUBLE_EQ(section.recovery_points[0][0], -0.1); + EXPECT_DOUBLE_EQ(section.recovery_points[1][0], 0.1); +} + +TEST(ModelTypes, PreservesSetsAndLinearStaticStepValues) { + const fesa::NodeSet node_set{ + "Fixed", + {fesa::NodeId{0}, fesa::NodeId{1}}, + }; + const fesa::ElementSet element_set{ + "Beam", + {fesa::ElementId{4}}, + }; + const fesa::StepDefinition step{ + "Load", + {{fesa::NodeId{0}, 6, 0.25}}, + {{ + fesa::NodeId{1}, + {10.0, 20.0, 30.0, 40.0, 50.0, 60.0}, + }}, + }; + + EXPECT_EQ(node_set.name, "Fixed"); + ASSERT_EQ(node_set.members.size(), 2); + EXPECT_EQ(node_set.members[1].value(), 1); + EXPECT_EQ(element_set.name, "Beam"); + ASSERT_EQ(element_set.members.size(), 1); + EXPECT_EQ(element_set.members[0].value(), 4); + + EXPECT_EQ(step.name, "Load"); + ASSERT_EQ(step.prescribed_dofs.size(), 1); + EXPECT_EQ(step.prescribed_dofs[0].node.value(), 0); + EXPECT_EQ(step.prescribed_dofs[0].dof, 6); + EXPECT_DOUBLE_EQ(step.prescribed_dofs[0].value, 0.25); + ASSERT_EQ(step.nodal_loads.size(), 1); + EXPECT_EQ(step.nodal_loads[0].node.value(), 1); + EXPECT_DOUBLE_EQ(step.nodal_loads[0].values[5], 60.0); +} + +} // namespace