feat(cpp-object-oriented-modular-refactoring): step 12 - material-property-hierarchy

This commit is contained in:
KOKO\Mimi
2026-08-16 08:48:18 +09:00
parent ec9c3e250a
commit 64a7071986
14 changed files with 746 additions and 31 deletions
+2
View File
@@ -29,6 +29,8 @@ add_executable(
unit/model/model_types_test.cpp
unit/model/shell_geometry_test.cpp
unit/model/source_target_resolver_test.cpp
unit/materials/material_test.cpp
unit/properties/element_property_test.cpp
unit/results/result_records_test.cpp
unit/results/result_recovery_test.cpp
unit/results/results_writer_test.cpp
+63
View File
@@ -0,0 +1,63 @@
#include <gtest/gtest.h>
#include <limits>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include "fesa/materials/isotropic_linear_elastic_material.h"
namespace {
fesa::SourceEntityId MaterialSourceId() { return {"", 7, "Steel"}; }
fesa::SourceLocation MaterialLocation() { return {"models/material.inp", 30U}; }
} // namespace
TEST(Material, OwnsIsotropicElasticityThroughVirtualBase) {
static_assert(std::has_virtual_destructor_v<fesa::Material>);
auto candidate = fesa::IsotropicLinearElasticMaterial::Create(
MaterialSourceId(), "Steel", 210.0e9, 0.3, MaterialLocation());
ASSERT_TRUE(candidate.HasValue());
std::unique_ptr<fesa::Material> material =
std::make_unique<fesa::IsotropicLinearElasticMaterial>(
std::move(candidate.Value()));
EXPECT_EQ(material->Kind(), fesa::MaterialKind::kIsotropicLinearElastic);
EXPECT_EQ(material->SourceId().source_label, 7);
EXPECT_EQ(material->SourceId().source_label_text, "Steel");
const auto& isotropic =
static_cast<const fesa::IsotropicLinearElasticMaterial&>(*material);
EXPECT_EQ(isotropic.Name(), "Steel");
EXPECT_DOUBLE_EQ(isotropic.YoungsModulus(), 210.0e9);
EXPECT_DOUBLE_EQ(isotropic.PoissonsRatio(), 0.3);
EXPECT_EQ(isotropic.Location().file, "models/material.inp");
EXPECT_EQ(isotropic.Location().line, 30U);
}
TEST(Material, RejectsInvalidCurrentElasticFields) {
const auto expect_rejected = [](double youngs_modulus,
double poissons_ratio) {
const auto result = fesa::IsotropicLinearElasticMaterial::Create(
MaterialSourceId(), "Steel", youngs_modulus, poissons_ratio,
MaterialLocation());
ASSERT_FALSE(result.HasValue());
EXPECT_EQ(result.GetStatus().Category(), fesa::FailureCategory::kModel);
ASSERT_EQ(result.GetStatus().Diagnostics().size(), 1U);
EXPECT_EQ(result.GetStatus().Diagnostics()[0].severity,
fesa::Severity::kError);
};
expect_rejected(0.0, 0.3);
expect_rejected(std::numeric_limits<double>::infinity(), 0.3);
expect_rejected(210.0e9, -1.0);
expect_rejected(210.0e9, std::numeric_limits<double>::quiet_NaN());
const auto beam_compatible = fesa::IsotropicLinearElasticMaterial::Create(
MaterialSourceId(), "Steel", 210.0e9, 0.75, MaterialLocation());
EXPECT_TRUE(beam_compatible.HasValue());
}
@@ -0,0 +1,99 @@
#include <gtest/gtest.h>
#include <array>
#include <limits>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "fesa/properties/general_beam_section.h"
#include "fesa/properties/shell_section.h"
namespace {
fesa::SourceLocation PropertyLocation() { return {"models/property.inp", 40U}; }
} // namespace
TEST(ElementProperty, OwnsGeneralBeamSectionThroughVirtualBase) {
static_assert(std::has_virtual_destructor_v<fesa::ElementProperty>);
const fesa::SourceEntityId source_id{"", 4, "BeamSection"};
auto candidate = fesa::GeneralBeamSection::Create(
source_id, "BeamSection", 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}}, PropertyLocation());
ASSERT_TRUE(candidate.HasValue());
std::unique_ptr<fesa::ElementProperty> property =
std::make_unique<fesa::GeneralBeamSection>(std::move(candidate.Value()));
EXPECT_EQ(property->Kind(), fesa::ElementPropertyKind::kGeneralBeamSection);
EXPECT_EQ(property->SourceId().source_label, 4);
EXPECT_EQ(property->SourceId().source_label_text, "BeamSection");
const auto& section = static_cast<const fesa::GeneralBeamSection&>(*property);
EXPECT_EQ(section.Name(), "BeamSection");
EXPECT_DOUBLE_EQ(section.Area(), 0.02);
EXPECT_DOUBLE_EQ(section.I11(), 1.0e-5);
EXPECT_DOUBLE_EQ(section.I12(), 0.0);
EXPECT_DOUBLE_EQ(section.I22(), 2.0e-5);
EXPECT_DOUBLE_EQ(section.TorsionalConstant(), 5.0e-6);
EXPECT_EQ(section.FirstAxis(), (std::array<double, 3>{0.0, 1.0, 0.0}));
EXPECT_EQ(section.SectionPoints(),
(std::vector<std::array<double, 2>>{{-0.1, 0.0}, {0.1, 0.0}}));
EXPECT_EQ(section.Location().line, 40U);
}
TEST(ElementProperty, OwnsShellSectionThroughVirtualBase) {
const fesa::SourceEntityId source_id{"", 8, "ShellSection"};
auto candidate =
fesa::ShellSection::Create(source_id, "ShellSection", 0.01,
fesa::EntityIndex{3}, PropertyLocation());
ASSERT_TRUE(candidate.HasValue());
std::unique_ptr<fesa::ElementProperty> property =
std::make_unique<fesa::ShellSection>(std::move(candidate.Value()));
EXPECT_EQ(property->Kind(), fesa::ElementPropertyKind::kShellSection);
EXPECT_EQ(property->SourceId().source_label, 8);
const auto& section = static_cast<const fesa::ShellSection&>(*property);
EXPECT_EQ(section.Name(), "ShellSection");
EXPECT_DOUBLE_EQ(section.Thickness(), 0.01);
EXPECT_EQ(section.MaterialIndex(), fesa::EntityIndex{3});
EXPECT_EQ(section.Location().file, "models/property.inp");
}
TEST(ElementProperty, PreservesEmptyOptionalSectionPointInventory) {
const auto section = fesa::GeneralBeamSection::Create(
{"", 4, "BeamSection"}, "BeamSection", 0.02, 1.0e-5, 0.0, 2.0e-5, 5.0e-6,
{0.0, 1.0, 0.0}, {}, PropertyLocation());
ASSERT_TRUE(section.HasValue());
EXPECT_TRUE(section.Value().SectionPoints().empty());
}
TEST(ElementProperty, RejectsInvalidCurrentSectionFields) {
const auto invalid_beam = fesa::GeneralBeamSection::Create(
{"", 4, "BeamSection"}, "BeamSection", 0.0, 1.0e-5, 0.0, 2.0e-5, 5.0e-6,
{0.0, 1.0, 0.0}, {}, PropertyLocation());
ASSERT_FALSE(invalid_beam.HasValue());
EXPECT_EQ(invalid_beam.GetStatus().Category(), fesa::FailureCategory::kModel);
const auto coupled_beam = fesa::GeneralBeamSection::Create(
{"", 4, "BeamSection"}, "BeamSection", 0.02, 1.0e-5, 1.0e-8, 2.0e-5,
5.0e-6, {0.0, 1.0, 0.0}, {}, PropertyLocation());
EXPECT_FALSE(coupled_beam.HasValue());
const auto nonfinite_point = fesa::GeneralBeamSection::Create(
{"", 4, "BeamSection"}, "BeamSection", 0.02, 1.0e-5, 0.0, 2.0e-5, 5.0e-6,
{0.0, 1.0, 0.0}, {{std::numeric_limits<double>::quiet_NaN(), 0.0}},
PropertyLocation());
EXPECT_FALSE(nonfinite_point.HasValue());
const auto invalid_shell =
fesa::ShellSection::Create({"", 8, "ShellSection"}, "ShellSection", 0.0,
fesa::EntityIndex{3}, PropertyLocation());
ASSERT_FALSE(invalid_shell.HasValue());
EXPECT_EQ(invalid_shell.GetStatus().Category(),
fesa::FailureCategory::kModel);
}