feat: add thermal dof mapping

This commit is contained in:
KOKO\Mimi
2026-09-29 18:59:04 +09:00
parent 878a71bd96
commit e71f3ad9ac
4 changed files with 313 additions and 0 deletions
+72
View File
@@ -5,10 +5,12 @@
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <functional>
#include <optional>
#include <string>
#include <stdexcept>
#include <utility>
#include <vector>
@@ -143,6 +145,29 @@ fesa::ModelDefinition MakeShellDefinition() {
return definition;
}
fesa::ModelDefinition MakeThermalDefinition() {
const std::filesystem::path source{"models/thermal-dof-manager.inp"};
fesa::ModelDefinition definition{};
definition.source_path = source;
for (std::int64_t label = 1; label <= 6; ++label) {
definition.nodes.push_back({{"Plate", label, std::to_string(label)},
{static_cast<double>(label), 0.0, 0.0},
{source, static_cast<std::size_t>(label)}});
}
definition.thermal_materials = {
{{"", 1, "Thermal"}, "Thermal", 40.0, 7800.0, 500.0, {source, 20U}}};
definition.thermal_sections = {
{{"", 1, "Section"}, "Section", 0.01, 0U, {source, 21U}}};
definition.thermal_elements = {
{{"Plate", 10, "10"}, {0U, 1U, 2U, 3U}, 0U, {source, 30U}},
{{"Plate", 20, "20"}, {1U, 2U, 3U, 4U}, 0U, {source, 31U}}};
definition.thermal_model_boundaries = {{0U, 11, 1.0, {source, 40U}},
{1U, 15, 2.0, {source, 41U}}};
definition.thermal_step = {"Heat", 0.1, 1.0, 100U, false,
{{0U, 11, 3.0, {source, 50U}}}, {}, {source, 49U}};
return definition;
}
struct DofFixture {
fesa::DofManager dofs;
};
@@ -409,3 +434,50 @@ TEST(DofManager, ReconstructsFullReducedRoundTrip) {
EXPECT_DOUBLE_EQ(reconstructed[index], full[index]);
}
}
TEST(ThermalDofManager, MapsOnlyConnectedNodesAndOverridesModelBoundary) {
auto domain = fesa::Domain::Create(MakeThermalDefinition());
ASSERT_TRUE(domain.HasValue());
auto model = fesa::AnalysisModel::Create(domain.Value());
ASSERT_TRUE(model.HasValue());
auto result = fesa::DofManager::Create(model.Value());
ASSERT_TRUE(result.HasValue());
auto dofs = std::move(result.Value());
EXPECT_EQ(dofs.Profile(), fesa::DofProfile::kThermal);
EXPECT_EQ(dofs.FullDofCount(), 25U);
EXPECT_EQ(dofs.ConstrainedDofs(), (std::vector<std::size_t>{0U, 9U}));
ASSERT_EQ(dofs.PrescribedValues().Size(), 2U);
EXPECT_DOUBLE_EQ(dofs.PrescribedValues()[0U], 3.0);
EXPECT_DOUBLE_EQ(dofs.PrescribedValues()[1U], 2.0);
EXPECT_EQ(dofs.FullThermalDof(4U, fesa::ThermalDofComponent::kTop), 24U);
EXPECT_THROW(dofs.FullThermalDof(5U, fesa::ThermalDofComponent::kBottom),
std::out_of_range);
const auto first = dofs.ThermalElementScatter(
{{"Plate", 10, "10"}, {0U, 1U, 2U, 3U}});
ASSERT_TRUE(first.HasValue());
EXPECT_EQ(first.Value(),
(std::vector<std::size_t>{0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U,
9U, 10U, 11U, 12U, 13U, 14U, 15U, 16U,
17U, 18U, 19U}));
EXPECT_EQ(RowColumns(dofs.GetSparsePattern(), 0U),
(std::vector<std::size_t>{0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U,
9U, 10U, 11U, 12U, 13U, 14U, 15U, 16U,
17U, 18U, 19U}));
const auto free_before = dofs.FreeDofs();
const auto constrained_before = dofs.ConstrainedDofs();
const fesa::Status invalid = dofs.BuildThermal(
model.Value(), {{{"Plate", 10, "10"}, {0U, 1U, 2U, 3U},
{fesa::ThermalDofComponent::kBottom,
fesa::ThermalDofComponent::kBottom,
fesa::ThermalDofComponent::kMiddle,
fesa::ThermalDofComponent::kUpper,
fesa::ThermalDofComponent::kTop}},
{{"Plate", 20, "20"}, {1U, 2U, 3U, 4U}}});
ASSERT_FALSE(invalid.IsOk());
EXPECT_EQ(invalid.Diagnostics()[0U].code, "invalid-thermal-element-layout");
EXPECT_EQ(dofs.FreeDofs(), free_before);
EXPECT_EQ(dofs.ConstrainedDofs(), constrained_before);
}