Files
FESADev/tests/unit/results/result_recovery_components_test.cpp

594 lines
27 KiB
C++

#include <gtest/gtest.h>
#include <array>
#include <cmath>
#include <cstddef>
#include <filesystem>
#include <limits>
#include <memory>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include "fesa/analysis/analysis_model.h"
#include "fesa/analysis/analysis_state.h"
#include "fesa/assembly/parallel_for.h"
#include "fesa/assembly/sparse_assembler.h"
#include "fesa/elements/element.h"
#include "fesa/fem/dof_manager.h"
#include "fesa/math/vector3.h"
#include "fesa/model/domain.h"
#include "results/analysis_state_commit.h"
#include "results/beam_result_recovery.h"
#include "results/global_equilibrium_recovery.h"
#include "results/recovery_candidate.h"
#include "results/shell_result_recovery.h"
namespace {
struct ShellFixture {
std::unique_ptr<fesa::Domain> domain;
std::unique_ptr<fesa::AnalysisModel> model;
std::unique_ptr<fesa::DofManager> dofs;
std::unique_ptr<fesa::SparseMatrix> stiffness;
};
fesa::SourceEntityId MakeSourceId(const std::string& text) {
return {"Part-1", 10, text};
}
fesa::BeamElementResultRows MakeBeamRows(const fesa::EntityIndex element,
const double value) {
fesa::BeamElementResultRows rows{};
rows.endpoint_rows = {{element,
0,
MakeSourceId("1"),
{-value, 0.0, 0.0, 0.0, 0.0, 0.0},
{value, 2.0 * value, 3.0 * value, 4.0 * value}},
{element,
1,
MakeSourceId("2"),
{value, 0.0, 0.0, 0.0, 0.0, 0.0},
{value, 2.0 * value, 3.0 * value, 4.0 * value}}};
rows.gauss_rows = {{element,
1,
{value, value + 1.0, value + 2.0, value + 3.0},
{value, 2.0 * value, 3.0 * value, 4.0 * value}},
{element,
2,
{value + 4.0, value + 5.0, value + 6.0, value + 7.0},
{value + 8.0, value + 9.0, value + 10.0, value + 11.0}}};
rows.stress_rows = {{element, 1, 0U, 0.0, 0.0, 5.0 * value, "fake"},
{element, 2, 1U, -0.25, 0.5, 6.0 * value, "input"}};
return rows;
}
fesa::ShellElementResultRows MakeShellRows(const fesa::EntityIndex element,
const double physical_energy) {
const double gauss = 1.0 / std::sqrt(3.0);
const std::array<fesa::ShellMidsurfaceLocation, 4> locations{
fesa::ShellMidsurfaceLocation::kGp1, fesa::ShellMidsurfaceLocation::kGp2,
fesa::ShellMidsurfaceLocation::kGp3, fesa::ShellMidsurfaceLocation::kGp4};
const std::array<std::array<double, 2>, 4> coordinates{
std::array<double, 2>{-gauss, -gauss},
std::array<double, 2>{gauss, -gauss}, std::array<double, 2>{gauss, gauss},
std::array<double, 2>{-gauss, gauss}};
const std::array<fesa::ShellSectionPosition, 3> positions{
fesa::ShellSectionPosition::kBottom, fesa::ShellSectionPosition::kMiddle,
fesa::ShellSectionPosition::kTop};
constexpr std::array<double, 3> zeta{-1.0, 0.0, 1.0};
fesa::ShellElementResultRows rows{};
rows.physical_strain_energy = physical_energy;
for (std::size_t point = 0U; point < locations.size(); ++point) {
fesa::ShellResultRow row{};
row.element = element;
row.location = locations[point];
row.natural_coordinates = coordinates[point];
row.local_frame = {{{1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}}};
for (std::size_t component = 0U; component < row.generalized_strain.size();
++component) {
row.generalized_strain[component] =
static_cast<double>(point + component + 1U);
row.section_resultant[component] =
10.0 * static_cast<double>(point + component + 1U);
}
for (std::size_t position = 0U; position < positions.size(); ++position) {
row.stress[position] = {positions[position],
zeta[position],
{static_cast<double>(point + position + 1U),
static_cast<double>(point + position + 2U),
static_cast<double>(point + position + 3U)}};
}
rows.rows.push_back(std::move(row));
}
return rows;
}
fesa::ModelDefinition MakeShellDefinition() {
const std::filesystem::path source{"models/recovery-components-shell.inp"};
fesa::ModelDefinition definition{};
definition.source_path = source;
definition.source_content_identity = "fnv1a64:1234567890abcdef";
definition.nodes = {{{"Shell-1", 1, "1"}, {-1.0, -1.0, 0.0}, {source, 10U}},
{{"Shell-1", 2, "2"}, {1.0, -1.0, 0.0}, {source, 11U}},
{{"Shell-1", 3, "3"}, {1.0, 1.0, 0.0}, {source, 12U}},
{{"Shell-1", 4, "4"}, {-1.0, 1.0, 0.0}, {source, 13U}}};
definition.materials = {{"Material", 120.0, 0.25, {source, 20U}}};
definition.shell_sections = {{"ShellSection", 2.0, 0U, {source, 30U}}};
definition.shell_elements = {{{"Shell-1", 10, "10"},
fesa::ShellSourceElementType::kS4,
{0U, 1U, 2U, 3U},
0U,
0U,
{source, 40U}}};
for (std::size_t node = 0U; node < definition.nodes.size(); ++node) {
definition.shell_node_initial_frames.push_back(
{static_cast<fesa::EntityIndex>(node),
{0.0, 0.0, 1.0},
{1.0, 0.0, 0.0},
{0.0, 1.0, 0.0}});
}
definition.steps = {{"Step-1", {}, {}, 0.1, 1.0, 0.01, 1.0, {source, 50U}}};
return definition;
}
ShellFixture MakeShellFixture(fesa::ModelDefinition definition) {
auto domain_result = fesa::Domain::Create(std::move(definition));
if (!domain_result.HasValue()) {
throw std::runtime_error{"Shell Domain construction failed."};
}
auto domain =
std::make_unique<fesa::Domain>(std::move(domain_result.Value()));
auto model_result = fesa::AnalysisModel::Create(*domain);
if (!model_result.HasValue()) {
throw std::runtime_error{"Shell AnalysisModel construction failed."};
}
auto model =
std::make_unique<fesa::AnalysisModel>(std::move(model_result.Value()));
auto dofs_result = fesa::DofManager::Create(*model);
if (!dofs_result.HasValue()) {
throw std::runtime_error{"Shell DofManager construction failed."};
}
auto dofs =
std::make_unique<fesa::DofManager>(std::move(dofs_result.Value()));
fesa::SerialParallelFor serial;
auto stiffness_result =
fesa::SparseAssembler::AssembleStiffness(*model, *dofs, serial);
if (!stiffness_result.HasValue()) {
throw std::runtime_error{"Shell stiffness assembly failed."};
}
auto stiffness =
std::make_unique<fesa::SparseMatrix>(std::move(stiffness_result.Value()));
return {std::move(domain), std::move(model), std::move(dofs),
std::move(stiffness)};
}
fesa::AnalysisState MakeShellPhysicalState(const ShellFixture& fixture) {
constexpr std::array<double, 8> generalized{0.1, -0.05, 0.2, 0.3,
-0.15, 0.25, 0.4, -0.3};
auto state = fesa::AnalysisState::Create(*fixture.dofs, {"Step-1", 0U});
for (std::size_t node = 0U; node < fixture.domain->Nodes().size(); ++node) {
const double x = fixture.domain->Nodes()[node].coordinates[0U];
const double y = fixture.domain->Nodes()[node].coordinates[1U];
const std::size_t offset = 6U * node;
state.Displacement()[offset] =
generalized[0U] * x + 0.5 * generalized[2U] * y;
state.Displacement()[offset + 1U] =
generalized[1U] * y + 0.5 * generalized[2U] * x;
state.Displacement()[offset + 2U] = generalized[6U] * x +
generalized[7U] * y -
0.5 * generalized[5U] * x * y;
state.Displacement()[offset + 3U] =
-generalized[4U] * y - 0.5 * generalized[5U] * x;
state.Displacement()[offset + 4U] =
generalized[3U] * x + 0.5 * generalized[5U] * y;
}
state.ExternalForce() = fixture.stiffness->Multiply(state.Displacement());
return state;
}
void ExpectStatusCode(const fesa::Status& status, const std::string& code) {
ASSERT_FALSE(status.IsOk());
EXPECT_EQ(status.Category(), fesa::FailureCategory::kModel);
ASSERT_EQ(status.Diagnostics().size(), 1U);
EXPECT_EQ(status.Diagnostics()[0U].code, code);
}
void ExpectVectorEqual(const fesa::Vector& actual,
const fesa::Vector& expected) {
ASSERT_EQ(actual.Size(), expected.Size());
for (std::size_t index = 0U; index < actual.Size(); ++index) {
EXPECT_DOUBLE_EQ(actual[index], expected[index]);
}
}
void ExpectAnalysisStateEqual(const fesa::AnalysisState& actual,
const fesa::AnalysisState& expected) {
EXPECT_EQ(actual.Identity().step_name, expected.Identity().step_name);
EXPECT_EQ(actual.Identity().frame_index, expected.Identity().frame_index);
ExpectVectorEqual(actual.Displacement(), expected.Displacement());
ExpectVectorEqual(actual.ExternalForce(), expected.ExternalForce());
ExpectVectorEqual(actual.InternalForce(), expected.InternalForce());
ExpectVectorEqual(actual.Residual(), expected.Residual());
ExpectVectorEqual(actual.Reaction(), expected.Reaction());
ASSERT_EQ(actual.EndpointResults().size(), expected.EndpointResults().size());
for (std::size_t row = 0U; row < actual.EndpointResults().size(); ++row) {
EXPECT_EQ(actual.EndpointResults()[row].element,
expected.EndpointResults()[row].element);
EXPECT_EQ(actual.EndpointResults()[row].endpoint,
expected.EndpointResults()[row].endpoint);
EXPECT_EQ(actual.EndpointResults()[row].node.instance_name,
expected.EndpointResults()[row].node.instance_name);
EXPECT_EQ(actual.EndpointResults()[row].node.source_label,
expected.EndpointResults()[row].node.source_label);
EXPECT_EQ(actual.EndpointResults()[row].node.source_label_text,
expected.EndpointResults()[row].node.source_label_text);
EXPECT_EQ(actual.EndpointResults()[row].end_action,
expected.EndpointResults()[row].end_action);
EXPECT_EQ(actual.EndpointResults()[row].section_resultant,
expected.EndpointResults()[row].section_resultant);
}
ASSERT_EQ(actual.GaussResults().size(), expected.GaussResults().size());
for (std::size_t row = 0U; row < actual.GaussResults().size(); ++row) {
EXPECT_EQ(actual.GaussResults()[row].element,
expected.GaussResults()[row].element);
EXPECT_EQ(actual.GaussResults()[row].gauss_point,
expected.GaussResults()[row].gauss_point);
EXPECT_EQ(actual.GaussResults()[row].generalized_strain,
expected.GaussResults()[row].generalized_strain);
EXPECT_EQ(actual.GaussResults()[row].generalized_resultant,
expected.GaussResults()[row].generalized_resultant);
}
ASSERT_EQ(actual.StressResults().size(), expected.StressResults().size());
for (std::size_t row = 0U; row < actual.StressResults().size(); ++row) {
EXPECT_EQ(actual.StressResults()[row].element,
expected.StressResults()[row].element);
EXPECT_EQ(actual.StressResults()[row].gauss_point,
expected.StressResults()[row].gauss_point);
EXPECT_EQ(actual.StressResults()[row].section_point,
expected.StressResults()[row].section_point);
EXPECT_DOUBLE_EQ(actual.StressResults()[row].x1,
expected.StressResults()[row].x1);
EXPECT_DOUBLE_EQ(actual.StressResults()[row].x2,
expected.StressResults()[row].x2);
EXPECT_DOUBLE_EQ(actual.StressResults()[row].s11,
expected.StressResults()[row].s11);
EXPECT_EQ(actual.StressResults()[row].source,
expected.StressResults()[row].source);
}
ASSERT_EQ(actual.ShellResults().size(), expected.ShellResults().size());
for (std::size_t row = 0U; row < actual.ShellResults().size(); ++row) {
EXPECT_EQ(actual.ShellResults()[row].element,
expected.ShellResults()[row].element);
EXPECT_EQ(actual.ShellResults()[row].location,
expected.ShellResults()[row].location);
EXPECT_EQ(actual.ShellResults()[row].natural_coordinates,
expected.ShellResults()[row].natural_coordinates);
EXPECT_EQ(actual.ShellResults()[row].local_frame,
expected.ShellResults()[row].local_frame);
EXPECT_EQ(actual.ShellResults()[row].generalized_strain,
expected.ShellResults()[row].generalized_strain);
EXPECT_EQ(actual.ShellResults()[row].section_resultant,
expected.ShellResults()[row].section_resultant);
}
EXPECT_DOUBLE_EQ(actual.PhysicalStrainEnergy(),
expected.PhysicalStrainEnergy());
EXPECT_EQ(actual.Equilibrium(), expected.Equilibrium());
EXPECT_EQ(actual.VerificationMetrics(), expected.VerificationMetrics());
}
} // namespace
// C-MODULE-002
TEST(ResultRecoveryComponents,
GlobalEquilibriumUsesFullResidualAndGlobalOriginMoment) {
auto centered_definition = MakeShellDefinition();
auto translated_definition = centered_definition;
constexpr fesa::Vector3 translation{7.0, 11.0, 0.0};
for (auto& node : translated_definition.nodes) {
for (std::size_t component = 0U;
component < translation.Components().size(); ++component) {
node.coordinates[component] += translation[component];
}
}
const auto centered_fixture =
MakeShellFixture(std::move(centered_definition));
const auto translated_fixture =
MakeShellFixture(std::move(translated_definition));
auto centered = MakeShellPhysicalState(centered_fixture);
auto translated = MakeShellPhysicalState(translated_fixture);
centered.ExternalForce()[0U] += 1.0e-9;
translated.ExternalForce()[0U] += 1.0e-9;
auto centered_global = fesa::results_internal::GlobalEquilibriumRecovery(
*centered_fixture.domain, *centered_fixture.dofs,
*centered_fixture.stiffness, centered.Displacement(),
centered.ExternalForce());
ASSERT_TRUE(centered_global.HasValue());
auto translated_global = fesa::results_internal::GlobalEquilibriumRecovery(
*translated_fixture.domain, *translated_fixture.dofs,
*translated_fixture.stiffness, translated.Displacement(),
translated.ExternalForce());
ASSERT_TRUE(translated_global.HasValue());
EXPECT_NEAR(centered_global.Value().residual[0U], -1.0e-9, 1.0e-14);
EXPECT_DOUBLE_EQ(centered_global.Value().reaction[0U],
centered_global.Value().residual[0U]);
fesa::results_internal::RecoveryCandidate centered_candidate{
centered_fixture.dofs->FullDofCount()};
const auto centered_status =
fesa::results_internal::RecoverShellGlobalEvidence(
*centered_fixture.domain, *centered_fixture.dofs,
centered.ExternalForce(), centered_global.Value(),
centered_candidate);
ASSERT_TRUE(centered_status.IsOk());
fesa::results_internal::RecoveryCandidate translated_candidate{
translated_fixture.dofs->FullDofCount()};
const auto translated_status =
fesa::results_internal::RecoverShellGlobalEvidence(
*translated_fixture.domain, *translated_fixture.dofs,
translated.ExternalForce(), translated_global.Value(),
translated_candidate);
ASSERT_TRUE(translated_status.IsOk());
std::array<double, 3> centered_force{};
for (std::size_t component = 0U; component < centered_force.size();
++component) {
centered_force[component] = centered_candidate.shell.equilibrium[component];
EXPECT_NEAR(translated_candidate.shell.equilibrium[component],
centered_force[component], 1.0e-12);
}
const fesa::Vector3 translated_moment_delta =
translation.Cross(fesa::Vector3{centered_force});
for (std::size_t component = 0U; component < centered_force.size();
++component) {
EXPECT_NEAR(translated_candidate.shell.equilibrium[3U + component] -
centered_candidate.shell.equilibrium[3U + component],
translated_moment_delta[component], 5.0e-12);
}
}
// C-MODULE-002
TEST(ResultRecoveryComponents,
BeamResultRecoveryKeepsEndpointGaussAndStressIdentities) {
constexpr fesa::EntityIndex element = 7U;
const std::array<fesa::SourceEntityId, 2> expected_nodes{MakeSourceId("1"),
MakeSourceId("2")};
fesa::results_internal::RecoveryCandidate candidate{0U};
const auto status = fesa::results_internal::BeamResultRecovery(
{}, MakeSourceId("10"), element, expected_nodes,
MakeBeamRows(element, 2.0), candidate);
ASSERT_TRUE(status.IsOk());
ASSERT_EQ(candidate.endpoint_rows.size(), 2U);
ASSERT_EQ(candidate.gauss_rows.size(), 2U);
ASSERT_EQ(candidate.stress_rows.size(), 2U);
EXPECT_TRUE(candidate.has_beam_results);
EXPECT_EQ(candidate.endpoint_rows[0U].endpoint, 0);
EXPECT_EQ(candidate.endpoint_rows[1U].endpoint, 1);
EXPECT_DOUBLE_EQ(candidate.endpoint_rows[0U].end_action[0U], -2.0);
EXPECT_DOUBLE_EQ(candidate.endpoint_rows[0U].section_resultant[0U], 2.0);
EXPECT_DOUBLE_EQ(candidate.endpoint_rows[1U].end_action[0U], 2.0);
EXPECT_DOUBLE_EQ(candidate.endpoint_rows[1U].section_resultant[0U], 2.0);
EXPECT_EQ(candidate.gauss_rows[0U].gauss_point, 1);
EXPECT_EQ(candidate.gauss_rows[1U].gauss_point, 2);
EXPECT_EQ(candidate.stress_rows[0U].section_point, 0U);
EXPECT_EQ(candidate.stress_rows[1U].section_point, 1U);
auto invalid = MakeBeamRows(element, 3.0);
invalid.stress_rows[1U].s11 = std::numeric_limits<double>::quiet_NaN();
fesa::results_internal::RecoveryCandidate rejected{0U};
ExpectStatusCode(
fesa::results_internal::BeamResultRecovery(
{}, MakeSourceId("10"), element, expected_nodes, invalid, rejected),
"nonfinite-recovery-value");
EXPECT_TRUE(rejected.endpoint_rows.empty());
EXPECT_TRUE(rejected.gauss_rows.empty());
EXPECT_TRUE(rejected.stress_rows.empty());
}
// C-MODULE-002
TEST(ResultRecoveryComponents,
BeamResultRecoveryRejectsMalformedB33IdentityBeforeAppending) {
constexpr fesa::EntityIndex element = 7U;
const std::array<fesa::SourceEntityId, 2> expected_nodes{MakeSourceId("1"),
MakeSourceId("2")};
std::vector<std::pair<std::string, fesa::BeamElementResultRows>> invalid;
auto wrong_endpoint = MakeBeamRows(element, 1.0);
wrong_endpoint.endpoint_rows[1U].endpoint = 2;
invalid.emplace_back("wrong endpoint", std::move(wrong_endpoint));
auto swapped_endpoints = MakeBeamRows(element, 1.0);
std::swap(swapped_endpoints.endpoint_rows[0U],
swapped_endpoints.endpoint_rows[1U]);
invalid.emplace_back("swapped endpoints", std::move(swapped_endpoints));
auto missing_endpoint = MakeBeamRows(element, 1.0);
missing_endpoint.endpoint_rows.pop_back();
invalid.emplace_back("missing endpoint", std::move(missing_endpoint));
auto duplicate_endpoint = MakeBeamRows(element, 1.0);
duplicate_endpoint.endpoint_rows[1U] = duplicate_endpoint.endpoint_rows[0U];
invalid.emplace_back("duplicate endpoint", std::move(duplicate_endpoint));
auto wrong_source_node = MakeBeamRows(element, 1.0);
wrong_source_node.endpoint_rows[0U].node = MakeSourceId("99");
invalid.emplace_back("wrong source node", std::move(wrong_source_node));
auto wrong_gauss = MakeBeamRows(element, 1.0);
wrong_gauss.gauss_rows[1U].gauss_point = 3;
invalid.emplace_back("wrong GP", std::move(wrong_gauss));
auto swapped_gauss = MakeBeamRows(element, 1.0);
std::swap(swapped_gauss.gauss_rows[0U], swapped_gauss.gauss_rows[1U]);
invalid.emplace_back("swapped GP", std::move(swapped_gauss));
auto missing_gauss = MakeBeamRows(element, 1.0);
missing_gauss.gauss_rows.pop_back();
invalid.emplace_back("missing GP", std::move(missing_gauss));
auto duplicate_gauss = MakeBeamRows(element, 1.0);
duplicate_gauss.gauss_rows[1U] = duplicate_gauss.gauss_rows[0U];
invalid.emplace_back("duplicate GP", std::move(duplicate_gauss));
auto malformed_stress_order = MakeBeamRows(element, 1.0);
std::swap(malformed_stress_order.stress_rows[0U],
malformed_stress_order.stress_rows[1U]);
invalid.emplace_back("malformed stress order",
std::move(malformed_stress_order));
for (const auto& [description, rows] : invalid) {
SCOPED_TRACE(description);
fesa::results_internal::RecoveryCandidate candidate{0U};
ExpectStatusCode(
fesa::results_internal::BeamResultRecovery(
{}, MakeSourceId("10"), element, expected_nodes, rows, candidate),
"invalid-element-result-identity");
EXPECT_TRUE(candidate.endpoint_rows.empty());
EXPECT_TRUE(candidate.gauss_rows.empty());
EXPECT_TRUE(candidate.stress_rows.empty());
EXPECT_FALSE(candidate.has_beam_results);
}
}
// C-MODULE-002
TEST(ResultRecoveryComponents,
ShellResultRecoveryKeepsGpStressOrderAndPhysicalEnergy) {
constexpr fesa::EntityIndex element = 4U;
fesa::results_internal::RecoveryCandidate candidate{0U};
const auto status = fesa::results_internal::ShellResultRecovery(
{}, MakeSourceId("20"), element, MakeShellRows(element, 12.5), candidate);
ASSERT_TRUE(status.IsOk());
EXPECT_TRUE(candidate.has_shell_results);
EXPECT_EQ(candidate.expected_shell_elements,
(std::vector<fesa::EntityIndex>{element}));
ASSERT_EQ(candidate.shell.rows.size(), 4U);
EXPECT_EQ(candidate.shell.rows[0U].location,
fesa::ShellMidsurfaceLocation::kGp1);
EXPECT_EQ(candidate.shell.rows[3U].location,
fesa::ShellMidsurfaceLocation::kGp4);
EXPECT_EQ(candidate.shell.rows[0U].stress[0U].position,
fesa::ShellSectionPosition::kBottom);
EXPECT_EQ(candidate.shell.rows[0U].stress[1U].position,
fesa::ShellSectionPosition::kMiddle);
EXPECT_EQ(candidate.shell.rows[0U].stress[2U].position,
fesa::ShellSectionPosition::kTop);
EXPECT_DOUBLE_EQ(candidate.shell.physical_strain_energy, 12.5);
auto invalid = MakeShellRows(element, 12.5);
invalid.rows[2U].element = element + 1U;
fesa::results_internal::RecoveryCandidate rejected{0U};
ExpectStatusCode(fesa::results_internal::ShellResultRecovery(
{}, MakeSourceId("20"), element, invalid, rejected),
"invalid-element-result-identity");
EXPECT_TRUE(rejected.shell.rows.empty());
EXPECT_TRUE(rejected.expected_shell_elements.empty());
EXPECT_DOUBLE_EQ(rejected.shell.physical_strain_energy, 0.0);
}
// C-MODULE-002
TEST(ResultRecoveryComponents,
CommitRecoveryCandidateRollsBackLaterInvalidShellBundle) {
const auto fixture = MakeShellFixture(MakeShellDefinition());
auto state = fesa::AnalysisState::Create(*fixture.dofs, {"Step-1", 0U});
for (std::size_t full_dof = 0U; full_dof < fixture.dofs->FullDofCount();
++full_dof) {
state.Displacement()[full_dof] = static_cast<double>(full_dof) + 0.25;
state.ExternalForce()[full_dof] = -static_cast<double>(full_dof) - 0.5;
state.InternalForce()[full_dof] = 100.0 + static_cast<double>(full_dof);
state.Residual()[full_dof] = 200.0 + static_cast<double>(full_dof);
state.Reaction()[full_dof] = 300.0 + static_cast<double>(full_dof);
}
state.EndpointResults() = MakeBeamRows(0U, 9.0).endpoint_rows;
auto prior_shell_rows = MakeShellRows(0U, 71.0);
fesa::ShellStateCandidate prior_shell{};
prior_shell.rows = prior_shell_rows.rows;
prior_shell.physical_strain_energy = prior_shell_rows.physical_strain_energy;
prior_shell.equilibrium = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
prior_shell.verification_metrics = {1.0e-11, 2.0e-11, 3.0e-11};
ASSERT_TRUE(state.CommitShellResults({0U}, std::move(prior_shell)).IsOk());
const fesa::AnalysisState prior = state;
fesa::results_internal::RecoveryCandidate candidate{
fixture.dofs->FullDofCount()};
for (std::size_t full_dof = 0U; full_dof < fixture.dofs->FullDofCount();
++full_dof) {
candidate.displacement[full_dof] = 10.0 + static_cast<double>(full_dof);
candidate.external_force[full_dof] = 20.0 + static_cast<double>(full_dof);
candidate.internal_force[full_dof] = 30.0 + static_cast<double>(full_dof);
candidate.residual[full_dof] = 40.0 + static_cast<double>(full_dof);
candidate.reaction[full_dof] = 50.0 + static_cast<double>(full_dof);
}
candidate.has_shell_results = true;
candidate.expected_shell_elements = {0U};
auto invalid_shell_rows = MakeShellRows(0U, 12.5);
candidate.shell.rows = invalid_shell_rows.rows;
candidate.shell.rows[3U].location = fesa::ShellMidsurfaceLocation::kGp1;
candidate.shell.physical_strain_energy =
invalid_shell_rows.physical_strain_energy;
candidate.shell.equilibrium = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
candidate.shell.verification_metrics = {0.0, 0.0, 0.0};
const auto status = fesa::results_internal::CommitRecoveryCandidate(
std::move(candidate), state);
ExpectStatusCode(status, "invalid-shell-state-inventory");
ExpectVectorEqual(state.Displacement(), prior.Displacement());
ExpectVectorEqual(state.ExternalForce(), prior.ExternalForce());
ExpectVectorEqual(state.InternalForce(), prior.InternalForce());
ExpectVectorEqual(state.Residual(), prior.Residual());
ExpectVectorEqual(state.Reaction(), prior.Reaction());
ASSERT_EQ(state.EndpointResults().size(), prior.EndpointResults().size());
EXPECT_EQ(state.EndpointResults()[0U].end_action,
prior.EndpointResults()[0U].end_action);
ASSERT_EQ(state.ShellResults().size(), prior.ShellResults().size());
EXPECT_EQ(state.ShellResults()[0U].location,
prior.ShellResults()[0U].location);
EXPECT_EQ(state.ShellResults()[0U].section_resultant,
prior.ShellResults()[0U].section_resultant);
EXPECT_DOUBLE_EQ(state.PhysicalStrainEnergy(), prior.PhysicalStrainEnergy());
EXPECT_EQ(state.Equilibrium(), prior.Equilibrium());
EXPECT_EQ(state.VerificationMetrics(), prior.VerificationMetrics());
}
// C-MODULE-002
TEST(ResultRecoveryComponents,
CommitRecoveryCandidateRejectsNonfiniteVectorAndPreservesWholeState) {
const auto fixture = MakeShellFixture(MakeShellDefinition());
auto state = fesa::AnalysisState::Create(*fixture.dofs, {"Prior-Step", 3U});
for (std::size_t full_dof = 0U; full_dof < fixture.dofs->FullDofCount();
++full_dof) {
state.Displacement()[full_dof] = static_cast<double>(full_dof) + 0.25;
state.ExternalForce()[full_dof] = -static_cast<double>(full_dof) - 0.5;
state.InternalForce()[full_dof] = 100.0 + static_cast<double>(full_dof);
state.Residual()[full_dof] = 200.0 + static_cast<double>(full_dof);
state.Reaction()[full_dof] = 300.0 + static_cast<double>(full_dof);
}
const auto prior_beam = MakeBeamRows(0U, 9.0);
state.EndpointResults() = prior_beam.endpoint_rows;
state.GaussResults() = prior_beam.gauss_rows;
state.StressResults() = prior_beam.stress_rows;
auto prior_shell_rows = MakeShellRows(0U, 71.0);
fesa::ShellStateCandidate prior_shell{};
prior_shell.rows = prior_shell_rows.rows;
prior_shell.physical_strain_energy = prior_shell_rows.physical_strain_energy;
prior_shell.equilibrium = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
prior_shell.verification_metrics = {1.0e-11, 2.0e-11, 3.0e-11};
ASSERT_TRUE(state.CommitShellResults({0U}, std::move(prior_shell)).IsOk());
const fesa::AnalysisState prior = state;
fesa::results_internal::RecoveryCandidate candidate{
fixture.dofs->FullDofCount()};
candidate.displacement[0U] = 10.0;
candidate.external_force[0U] = 20.0;
candidate.internal_force[0U] = 30.0;
candidate.residual[0U] = 40.0;
candidate.reaction[0U] = std::numeric_limits<double>::quiet_NaN();
const auto status = fesa::results_internal::CommitRecoveryCandidate(
std::move(candidate), state);
ExpectStatusCode(status, "nonfinite-recovery-value");
ExpectAnalysisStateEqual(state, prior);
}