Files
FESADev/tests/unit/model/domain_test.cpp
T

278 lines
12 KiB
C++

#include "fesa/model/domain.h"
#include <gtest/gtest.h>
#include <filesystem>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
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<decltype(std::declval<const fesa::Domain&>().Nodes()),
const std::vector<fesa::Node>&>);
static_assert(
std::is_same_v<decltype(std::declval<const fesa::Domain&>().Elements()),
const std::vector<fesa::EulerBeam3DDefinition>&>);
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<const fesa::Domain&>().ShellElements()),
const std::vector<fesa::Mitc4ShellDefinition>&>);
static_assert(std::is_same_v<
decltype(std::declval<const fesa::Domain&>().ShellSections()),
const std::vector<fesa::ShellSection>&>);
static_assert(
std::is_same_v<decltype(std::declval<const fesa::Domain&>()
.ShellNodeInitialFrames()),
const std::vector<fesa::ShellNodeInitialFrame>&>);
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<double, 3>{0.0, 0.0, 1.0}));
EXPECT_EQ(domain.ShellNodeInitialFrames()[0].tangent_a,
(std::array<double, 3>{1.0, 0.0, 0.0}));
EXPECT_EQ(domain.ShellNodeInitialFrames()[0].tangent_b,
(std::array<double, 3>{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());
}