fix: enforce thermal boundary dof endpoints

This commit is contained in:
KOKO\Mimi
2026-09-29 18:47:23 +09:00
parent 254247efc1
commit 283628ecda
6 changed files with 188 additions and 5 deletions
+125
View File
@@ -4,6 +4,7 @@
#include <array>
#include <limits>
#include <stdexcept>
#include <type_traits>
#include <utility>
@@ -41,6 +42,14 @@ fesa::ModelDefinition ValidDefinition() {
} // namespace
TEST(ThermalDomain, StructuralDefinitionHasNoThermalStep) {
fesa::ModelDefinition definition{};
definition.steps = {{"Static", {}, {}, 0.1, 1.0, 0.01, 1.0, Location()}};
const auto result = fesa::Domain::Create(std::move(definition));
ASSERT_TRUE(result.HasValue());
EXPECT_EQ(result.Value().ThermalStep(), nullptr);
}
TEST(IsotropicThermalMaterial, RequiresFinitePositivePropertiesAndIdentity) {
const auto valid = fesa::IsotropicThermalMaterial::Create(
{"", 7, "Thermal-Steel"}, "Thermal-Steel", 45.0, 7800.0, 500.0,
@@ -56,6 +65,15 @@ TEST(IsotropicThermalMaterial, RequiresFinitePositivePropertiesAndIdentity) {
std::numeric_limits<double>::infinity(), 7800.0, 500.0, Location());
ASSERT_FALSE(invalid.HasValue());
EXPECT_EQ(invalid.GetStatus().Category(), fesa::FailureCategory::kModel);
EXPECT_FALSE(fesa::IsotropicThermalMaterial::Create(
{"", 7, "Thermal-Steel"}, "Thermal-Steel", 45.0, 0.0,
500.0, Location())
.HasValue());
EXPECT_FALSE(fesa::IsotropicThermalMaterial::Create(
{"", 7, "Thermal-Steel"}, "Thermal-Steel", 45.0, 7800.0,
std::numeric_limits<double>::quiet_NaN(), Location())
.HasValue());
}
TEST(ThermalDomain, OwnsFiveStationDefinitionsWithoutEquationState) {
@@ -70,12 +88,16 @@ TEST(ThermalDomain, OwnsFiveStationDefinitionsWithoutEquationState) {
EXPECT_EQ(domain.ThermalElements().Size(), 1U);
EXPECT_EQ(domain.ThermalElements()[0U].Kind(),
fesa::ElementDefinitionKind::kDs4Thermal);
EXPECT_EQ(domain.ThermalElements()[0U].PropertyIndex(),
fesa::Ds4ThermalDefinition::kNoPropertyIndex);
EXPECT_EQ(domain.ThermalElements()[0U].NodeIndices(),
(std::vector<fesa::EntityIndex>{0U, 1U, 2U, 3U}));
EXPECT_EQ(domain.ThermalInitialConditions()[0U].station_values,
(std::array<double, 5>{12.5, 12.5, 12.5, 12.5, 12.5}));
EXPECT_EQ(domain.ThermalStep()->Boundaries()[0U].value, 20.0);
EXPECT_EQ(domain.ThermalStep()->Loads()[0U].source_order, 0U);
EXPECT_EQ(static_cast<int>(fesa::ThermalDofComponent::kBottom), 11);
EXPECT_EQ(static_cast<int>(fesa::ThermalDofComponent::kTop), 15);
}
TEST(ThermalDomain, RejectsInactiveTargetsConflictsAndMixedElements) {
@@ -97,6 +119,13 @@ TEST(ThermalDomain, RejectsInactiveTargetsConflictsAndMixedElements) {
const auto mixed = fesa::Domain::Create(std::move(invalid));
ASSERT_FALSE(mixed.HasValue());
EXPECT_EQ(mixed.GetStatus().Category(), fesa::FailureCategory::kModel);
invalid = ValidDefinition();
invalid.thermal_sections[0U].thickness = 0.0;
const auto invalid_thickness = fesa::Domain::Create(std::move(invalid));
ASSERT_FALSE(invalid_thickness.HasValue());
EXPECT_EQ(invalid_thickness.GetStatus().Category(),
fesa::FailureCategory::kModel);
}
TEST(ThermalDomain, AllowsEqualInitialTargetsAndStepBoundaryOverride) {
@@ -118,6 +147,87 @@ TEST(ThermalDomain, AllowsEqualInitialTargetsAndStepBoundaryOverride) {
EXPECT_TRUE(overriding_step.HasValue());
}
TEST(ThermalDomain, RejectsInteriorThermalDofsAndInactiveFluxTargets) {
for (const int dof : {12, 13, 14}) {
auto definition = ValidDefinition();
definition.thermal_step->boundaries = {{0U, dof, 1.0, Location()}};
const auto boundary = fesa::Domain::Create(std::move(definition));
EXPECT_FALSE(boundary.HasValue());
definition = ValidDefinition();
definition.thermal_step->loads = {{0U, dof, 1.0, Location()}};
const auto flux = fesa::Domain::Create(std::move(definition));
EXPECT_FALSE(flux.HasValue());
}
auto definition = ValidDefinition();
definition.thermal_step->loads = {{4U, 11, 1.0, Location()}};
const auto inactive_flux = fesa::Domain::Create(std::move(definition));
EXPECT_FALSE(inactive_flux.HasValue());
definition = ValidDefinition();
definition.thermal_model_boundaries = {{4U, 11, 1.0, Location()}};
const auto inactive_boundary = fesa::Domain::Create(std::move(definition));
EXPECT_FALSE(inactive_boundary.HasValue());
}
TEST(ThermalDomain, PreservesAdditiveFluxSourceOrder) {
auto definition = ValidDefinition();
definition.thermal_step->loads = {{0U, 11, -5.0, Location()},
{1U, 11, 3.0, Location()}};
const auto result = fesa::Domain::Create(std::move(definition));
ASSERT_TRUE(result.HasValue());
const auto& loads = result.Value().ThermalStep()->Loads();
ASSERT_EQ(loads.size(), 2U);
EXPECT_EQ(loads[0U].source_order, 0U);
EXPECT_EQ(loads[1U].source_order, 1U);
EXPECT_DOUBLE_EQ(loads[0U].power, -5.0);
EXPECT_DOUBLE_EQ(loads[1U].power, 3.0);
}
TEST(ThermalAnalysisModel,
KeepsThermalSectionMaterialIndicesSeparateFromDomainMaterials) {
auto definition = ValidDefinition();
definition.materials = {{"Unused-Structural", 210.0e9, 0.3, Location()}};
definition.thermal_materials.push_back(
{{"", 2, "Thermal-Aluminum"}, "Thermal-Aluminum", 120.0, 2700.0,
900.0, Location()});
definition.thermal_sections = {
{{"", 1, "Section-A"}, "Section-A", 0.01, 1U, Location()},
{{"", 2, "Section-B"}, "Section-B", 0.02, 0U, Location()}};
definition.nodes.push_back(
{{"Plate", 6, "6"}, {3.0, 0.0, 0.0}, Location()});
definition.nodes.push_back(
{{"Plate", 7, "7"}, {4.0, 0.0, 0.0}, Location()});
definition.nodes.push_back(
{{"Plate", 8, "8"}, {4.0, 1.0, 0.0}, Location()});
definition.nodes.push_back(
{{"Plate", 9, "9"}, {3.0, 1.0, 0.0}, Location()});
definition.thermal_elements = {
{{"Plate", 8, "8"}, {0U, 1U, 2U, 3U}, 0U, Location()},
{{"Plate", 9, "9"}, {5U, 6U, 7U, 8U}, 1U, Location()}};
const auto domain_result = fesa::Domain::Create(std::move(definition));
ASSERT_TRUE(domain_result.HasValue());
const fesa::Domain& domain = domain_result.Value();
ASSERT_EQ(domain.Materials().Size(), 3U);
ASSERT_EQ(domain.ThermalMaterials().Size(), 2U);
EXPECT_EQ(domain.ThermalSections()[0U].material_index, 1U);
EXPECT_EQ(domain.ThermalSections()[1U].material_index, 0U);
EXPECT_EQ(domain.ThermalElements()[0U].MaterialIndex(), 2U);
EXPECT_EQ(domain.ThermalElements()[1U].MaterialIndex(), 1U);
EXPECT_EQ(domain.ThermalElements()[0U].PropertyIndex(),
fesa::Ds4ThermalDefinition::kNoPropertyIndex);
const auto model_result = fesa::AnalysisModel::Create(domain);
ASSERT_TRUE(model_result.HasValue());
EXPECT_EQ(model_result.Value().ActiveThermalMaterials(),
(std::vector<fesa::EntityIndex>{0U, 1U}));
EXPECT_EQ(model_result.Value().ActiveThermalSections(),
(std::vector<fesa::EntityIndex>{0U, 1U}));
EXPECT_TRUE(model_result.Value().ActiveProperties().empty());
}
TEST(ThermalAnalysisModel, ViewsConnectedDs4EntitiesWithoutOwningDomain) {
auto domain_result = fesa::Domain::Create(ValidDefinition());
ASSERT_TRUE(domain_result.HasValue());
@@ -128,9 +238,24 @@ TEST(ThermalAnalysisModel, ViewsConnectedDs4EntitiesWithoutOwningDomain) {
const fesa::AnalysisModel& model = model_result.Value();
EXPECT_TRUE(model.IsThermal());
EXPECT_EQ(model.ThermalStep(), domain.ThermalStep());
EXPECT_THROW(model.Step(), std::logic_error);
EXPECT_EQ(model.ActiveThermalElements(),
(std::vector<fesa::EntityIndex>{0U}));
EXPECT_EQ(model.ActiveThermalNodes(),
(std::vector<fesa::EntityIndex>{0U, 1U, 2U, 3U}));
EXPECT_EQ(model.ActiveThermalMaterials(),
(std::vector<fesa::EntityIndex>{0U}));
EXPECT_EQ(model.ActiveThermalSections(),
(std::vector<fesa::EntityIndex>{0U}));
EXPECT_TRUE(model.ActiveProperties().empty());
EXPECT_EQ(domain.ThermalSections()[0U].material_index, 0U);
EXPECT_EQ(domain.ThermalElements()[0U].MaterialIndex(), 0U);
EXPECT_EQ(&model.GetDomain(), &domain);
fesa::Domain moved_domain = std::move(domain_result.Value());
const auto moved_model = fesa::AnalysisModel::Create(moved_domain);
ASSERT_TRUE(moved_model.HasValue());
EXPECT_EQ(&moved_model.Value().GetDomain(), &moved_domain);
EXPECT_DOUBLE_EQ(moved_domain.ThermalMaterials()[0U].Conductivity(), 45.0);
EXPECT_EQ(moved_domain.ThermalElements()[0U].SourceId().source_label, 8);
}