#include #include #include #include #include #include #include #include #include #include #include #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 domain; std::unique_ptr model; std::unique_ptr dofs; std::unique_ptr 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 locations{ fesa::ShellMidsurfaceLocation::kGp1, fesa::ShellMidsurfaceLocation::kGp2, fesa::ShellMidsurfaceLocation::kGp3, fesa::ShellMidsurfaceLocation::kGp4}; const std::array, 4> coordinates{ std::array{-gauss, -gauss}, std::array{gauss, -gauss}, std::array{gauss, gauss}, std::array{-gauss, gauss}}; const std::array positions{ fesa::ShellSectionPosition::kBottom, fesa::ShellSectionPosition::kMiddle, fesa::ShellSectionPosition::kTop}; constexpr std::array 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(point + component + 1U); row.section_resultant[component] = 10.0 * static_cast(point + component + 1U); } for (std::size_t position = 0U; position < positions.size(); ++position) { row.stress[position] = {positions[position], zeta[position], {static_cast(point + position + 1U), static_cast(point + position + 2U), static_cast(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(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(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(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(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(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 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 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 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::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 expected_nodes{MakeSourceId("1"), MakeSourceId("2")}; std::vector> 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{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(full_dof) + 0.25; state.ExternalForce()[full_dof] = -static_cast(full_dof) - 0.5; state.InternalForce()[full_dof] = 100.0 + static_cast(full_dof); state.Residual()[full_dof] = 200.0 + static_cast(full_dof); state.Reaction()[full_dof] = 300.0 + static_cast(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(full_dof); candidate.external_force[full_dof] = 20.0 + static_cast(full_dof); candidate.internal_force[full_dof] = 30.0 + static_cast(full_dof); candidate.residual[full_dof] = 40.0 + static_cast(full_dof); candidate.reaction[full_dof] = 50.0 + static_cast(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(full_dof) + 0.25; state.ExternalForce()[full_dof] = -static_cast(full_dof) - 0.5; state.InternalForce()[full_dof] = 100.0 + static_cast(full_dof); state.Residual()[full_dof] = 200.0 + static_cast(full_dof); state.Reaction()[full_dof] = 300.0 + static_cast(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::quiet_NaN(); const auto status = fesa::results_internal::CommitRecoveryCandidate( std::move(candidate), state); ExpectStatusCode(status, "nonfinite-recovery-value"); ExpectAnalysisStateEqual(state, prior); }