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

1152 lines
50 KiB
C++

#include "fesa/results/result_recovery.h"
#include <gtest/gtest.h>
#include <array>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <functional>
#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/load_assembler.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"
namespace {
constexpr double kYoungsModulus = 100.0;
constexpr double kPoissonRatio = 0.25;
constexpr double kLength = 2.0;
struct RecoveryFixture {
std::unique_ptr<fesa::Domain> domain;
std::unique_ptr<fesa::AnalysisModel> model;
std::unique_ptr<fesa::DofManager> dofs;
std::unique_ptr<fesa::SparseMatrix> stiffness;
};
struct ShellRecoveryFixture {
std::unique_ptr<fesa::Domain> domain;
std::unique_ptr<fesa::AnalysisModel> model;
std::unique_ptr<fesa::DofManager> dofs;
std::unique_ptr<fesa::SparseMatrix> stiffness;
};
class FakeRecoveryElement final : public fesa::Element {
public:
FakeRecoveryElement(fesa::ElementDofLayout layout,
fesa::ElementResultBundle bundle,
const bool fail_recovery = false)
: layout_{std::move(layout)},
bundle_{std::move(bundle)},
fail_recovery_{fail_recovery} {}
const fesa::ElementDofLayout& DofLayout() const noexcept override {
return layout_;
}
fesa::Result<fesa::ElementStiffnessContribution> ComputeStiffness()
const override {
const std::size_t local_dof_count =
layout_.node_indices.size() * layout_.components_per_node.size();
return fesa::Result<fesa::ElementStiffnessContribution>::Success(
{layout_, fesa::Matrix{local_dof_count, local_dof_count}});
}
fesa::Result<fesa::ElementResultBundle> Recover(
const fesa::Vector& element_displacement) const override {
observed_displacement_ = element_displacement;
if (fail_recovery_) {
return fesa::Result<fesa::ElementResultBundle>::Failure(
fesa::Status::Failure(
fesa::FailureCategory::kModel,
{{fesa::Severity::kError,
"fake-recovery-failure",
{},
"RESULT_RECOVERY",
layout_.source_id.source_label_text,
"The fake runtime element rejected recovery."}}));
}
return fesa::Result<fesa::ElementResultBundle>::Success(bundle_);
}
const fesa::Vector& ObservedDisplacement() const noexcept {
return observed_displacement_;
}
private:
fesa::ElementDofLayout layout_;
fesa::ElementResultBundle bundle_;
bool fail_recovery_;
mutable fesa::Vector observed_displacement_{0U};
};
std::vector<fesa::DofComponent> FullNodeComponents() {
return {fesa::DofComponent::kUx, fesa::DofComponent::kUy,
fesa::DofComponent::kUz, fesa::DofComponent::kUrx,
fesa::DofComponent::kUry, fesa::DofComponent::kUrz};
}
fesa::ElementDofLayout MakeRuntimeLayout(const fesa::Domain& domain,
const fesa::EntityIndex element) {
const auto& definition = domain.Elements()[element];
return {definition.SourceId(), definition.NodeIndices(),
FullNodeComponents()};
}
fesa::ElementResultBundle MakeFakeBeamBundle(const fesa::Domain& domain,
const fesa::EntityIndex element,
const double value) {
const auto& definition = domain.Elements()[element];
fesa::BeamElementResultRows rows{};
rows.endpoint_rows = {{element,
0,
domain.Nodes()[definition.NodeIndices()[0U]].source_id,
{-value, 0.0, 0.0, 0.0, 0.0, 0.0},
{value, 2.0 * value, 3.0 * value, 4.0 * value}},
{element,
1,
domain.Nodes()[definition.NodeIndices()[1U]].source_id,
{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, 0.0, 0.0, 0.0},
{value, 2.0 * value, 3.0 * value, 4.0 * value}},
{element,
2,
{value + 0.5, 0.0, 0.0, 0.0},
{value, 2.0 * value, 3.0 * value, 4.0 * value}}};
rows.stress_rows = {{element, 1, 0U, 0.0, 0.0, 5.0 * value, "fake"},
{element, 2, 0U, 0.0, 0.0, 6.0 * value, "fake"}};
return {definition.SourceId(), std::move(rows)};
}
fesa::ElementResultBundle MakeFakeShellBundle(const fesa::Domain& domain,
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}}};
row.generalized_strain[0U] = static_cast<double>(point + 1U);
row.section_resultant[0U] = 10.0 * static_cast<double>(point + 1U);
for (std::size_t position = 0U; position < positions.size(); ++position) {
row.stress[position] = {
positions[position],
zeta[position],
{static_cast<double>(point + position + 1U), 0.0, 0.0}};
}
rows.rows.push_back(std::move(row));
}
return {domain.Elements()[element].SourceId(), std::move(rows)};
}
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]);
}
}
fesa::ModelDefinition MakeDefinition(
const bool two_elements = false,
std::vector<std::array<double, 2>> section_points = {},
std::vector<fesa::NodalLoad> loads = {}, const bool reverse_second = false,
const bool section_jump = false, const bool nonzero_prescription = true) {
const std::filesystem::path source{"models/result-recovery.inp"};
fesa::ModelDefinition definition{};
definition.source_path = source;
definition.source_content_identity = "fnv1a64:0123456789abcdef";
definition.nodes = {{{"Beam-1", 1, "1"}, {0.0, 0.0, 0.0}, {source, 10U}},
{{"Beam-1", 2, "2"}, {kLength, 0.0, 0.0}, {source, 11U}}};
if (two_elements) {
definition.nodes.push_back(
{{"Beam-1", 3, "3"}, {2.0 * kLength, 0.0, 0.0}, {source, 12U}});
}
definition.materials = {
{"Material", kYoungsModulus, kPoissonRatio, {source, 20U}}};
definition.sections = {{"Section",
2.0,
3.0,
0.0,
4.0,
5.0,
{0.0, 1.0, 0.0},
std::move(section_points),
{source, 30U}}};
if (section_jump) {
auto second_section = definition.sections.front();
second_section.name = "Section-2";
second_section.area = 2.5;
second_section.location.line = 31U;
definition.sections.push_back(std::move(second_section));
}
definition.elements = {
{{"Beam-1", 10, "10"}, {0U, 1U}, 0U, 0U, {source, 40U}}};
if (two_elements) {
definition.elements.push_back(
{{"Beam-1", 20, "20"},
reverse_second ? std::array<fesa::EntityIndex, 2>{2U, 1U}
: std::array<fesa::EntityIndex, 2>{1U, 2U},
0U,
section_jump ? 1U : 0U,
{source, 41U}});
}
definition.steps = {
{"Step-1",
{{"1", 1, 1, nonzero_prescription ? 0.1 : 0.0, {source, 50U}},
{"1", 2, 6, 0.0, {source, 51U}}},
std::move(loads),
0.1,
1.0,
0.01,
1.0,
{source, 49U}}};
return definition;
}
RecoveryFixture MakeFixture(
const bool two_elements = false,
std::vector<std::array<double, 2>> section_points = {},
std::vector<fesa::NodalLoad> loads = {}, const bool reverse_second = false,
const bool section_jump = false, const bool nonzero_prescription = true) {
auto domain_result = fesa::Domain::Create(
MakeDefinition(two_elements, std::move(section_points), std::move(loads),
reverse_second, section_jump, nonzero_prescription));
if (!domain_result.HasValue()) {
throw std::runtime_error{"Recovery fixture 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{
"Recovery fixture 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{
"Recovery fixture 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{"Recovery fixture 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::ModelDefinition MakeShellDefinition(
const bool two_elements = false, const bool constrain_all = false,
std::vector<fesa::NodalLoad> loads = {}) {
const std::filesystem::path source{"models/shell-result-recovery.inp"};
fesa::ModelDefinition definition{};
definition.source_path = source;
definition.source_content_identity = "fnv1a64:fedcba9876543210";
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"}, {3.0, -1.0, 0.0}, {source, 12U}},
{{"Shell-1", 4, "4"}, {-1.0, 1.0, 0.0}, {source, 13U}},
{{"Shell-1", 5, "5"}, {1.0, 1.0, 0.0}, {source, 14U}},
{{"Shell-1", 6, "6"}, {3.0, 1.0, 0.0}, {source, 15U}}};
if (!two_elements) {
definition.nodes.erase(definition.nodes.begin() + 2,
definition.nodes.begin() + 3);
definition.nodes.erase(definition.nodes.begin() + 4);
}
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, two_elements ? 4U : 3U, two_elements ? 3U : 2U},
0U,
0U,
{source, 40U}}};
if (two_elements) {
definition.shell_elements.push_back({{"Shell-1", 20, "20"},
fesa::ShellSourceElementType::kS4r,
{1U, 2U, 5U, 4U},
0U,
0U,
{source, 41U}});
}
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}});
}
if (constrain_all) {
std::vector<fesa::EntityIndex> all_nodes;
all_nodes.reserve(definition.nodes.size());
for (std::size_t node = 0U; node < definition.nodes.size(); ++node) {
all_nodes.push_back(static_cast<fesa::EntityIndex>(node));
}
definition.node_sets.push_back(
{"All", {}, std::move(all_nodes), {source, 50U}});
}
definition.steps = {
{"Step-1",
constrain_all
? std::vector<fesa::PrescribedDisplacementDefinition>{{"All",
1,
6,
0.0,
{source,
60U}}}
: std::vector<fesa::PrescribedDisplacementDefinition>{},
std::move(loads),
0.1,
1.0,
0.01,
1.0,
{source, 59U}}};
return definition;
}
ShellRecoveryFixture MakeShellFixture(fesa::ModelDefinition definition) {
auto domain_result = fesa::Domain::Create(std::move(definition));
if (!domain_result.HasValue()) {
throw std::runtime_error{
"Shell recovery fixture 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 recovery fixture 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 recovery fixture 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 recovery fixture 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 ShellRecoveryFixture& 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;
}
fesa::AnalysisState MakeAxialEquilibriumState(const RecoveryFixture& fixture) {
auto state = fesa::AnalysisState::Create(*fixture.dofs, {"Step-1", 0U});
state.Displacement()[0U] = 0.1;
state.Displacement()[6U] = 0.3;
const fesa::Vector internal =
fixture.stiffness->Multiply(state.Displacement());
for (const std::size_t full_dof : fixture.dofs->FreeDofs()) {
state.ExternalForce()[full_dof] = internal[full_dof];
}
return state;
}
fesa::AnalysisState MakePatchState(const RecoveryFixture& fixture,
const double epsilon, const double twist,
const double kappa_y, const double kappa_z) {
auto state = fesa::AnalysisState::Create(*fixture.dofs, {"Step-1", 0U});
state.Displacement()[0U] = 0.1;
state.Displacement()[6U] = 0.1 + epsilon * kLength;
state.Displacement()[7U] = 0.5 * kappa_z * kLength * kLength;
state.Displacement()[8U] = -0.5 * kappa_y * kLength * kLength;
state.Displacement()[9U] = twist * kLength;
state.Displacement()[10U] = kappa_y * kLength;
state.Displacement()[11U] = kappa_z * kLength;
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 ExpectScaledNear(const double actual, const double expected,
const double relative_tolerance = 1.0e-12) {
ASSERT_TRUE(std::isfinite(actual));
ASSERT_TRUE(std::isfinite(expected));
EXPECT_LE(std::abs(actual - expected),
relative_tolerance * (std::max)(std::abs(expected), 1.0));
}
std::vector<fesa::EndpointResultRow> MakeStationRows(
const RecoveryFixture& fixture) {
const auto& nodes = fixture.domain->Nodes();
const auto& elements = fixture.domain->BeamElements();
return {{0U,
0,
nodes[elements[0U].node_indices[0U]].source_id,
{0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
{1.0, 2.0, 3.0, 4.0}},
{0U,
1,
nodes[elements[0U].node_indices[1U]].source_id,
{0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
{5.0, 6.0, 7.0, 8.0}},
{1U,
0,
nodes[elements[1U].node_indices[0U]].source_id,
{0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
{5.0, 6.0, 7.0, 8.0}},
{1U,
1,
nodes[elements[1U].node_indices[1U]].source_id,
{0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
{9.0, 10.0, 11.0, 12.0}}};
}
} // namespace
// C-RECOVERY-001
TEST(ResultRecovery, AggregatesFakeBeamBundlesInStableRuntimeOrder) {
const auto fixture = MakeFixture(true);
const auto input = MakeAxialEquilibriumState(fixture);
auto state = fesa::AnalysisState::Create(*fixture.dofs, {"Step-1", 0U});
FakeRecoveryElement first{MakeRuntimeLayout(*fixture.domain, 0U),
MakeFakeBeamBundle(*fixture.domain, 0U, 1.0)};
FakeRecoveryElement second{MakeRuntimeLayout(*fixture.domain, 1U),
MakeFakeBeamBundle(*fixture.domain, 1U, 2.0)};
const fesa::ElementView elements{std::cref(first), std::cref(second)};
const fesa::Status status = fesa::ResultRecovery::Recover(
*fixture.model, elements, *fixture.dofs, *fixture.stiffness,
input.Displacement(), input.ExternalForce(), state);
ASSERT_TRUE(status.IsOk());
ExpectVectorEqual(state.Displacement(), input.Displacement());
ExpectVectorEqual(state.ExternalForce(), input.ExternalForce());
ASSERT_EQ(first.ObservedDisplacement().Size(), 12U);
EXPECT_DOUBLE_EQ(first.ObservedDisplacement()[0U], 0.1);
EXPECT_DOUBLE_EQ(first.ObservedDisplacement()[6U], 0.3);
ASSERT_EQ(second.ObservedDisplacement().Size(), 12U);
EXPECT_DOUBLE_EQ(second.ObservedDisplacement()[0U], 0.3);
EXPECT_DOUBLE_EQ(second.ObservedDisplacement()[6U], 0.0);
ASSERT_EQ(state.EndpointResults().size(), 4U);
ASSERT_EQ(state.GaussResults().size(), 4U);
ASSERT_EQ(state.StressResults().size(), 4U);
for (std::size_t element = 0U; element < 2U; ++element) {
const double value = static_cast<double>(element + 1U);
const auto& negative_endpoint = state.EndpointResults()[2U * element];
const auto& positive_endpoint = state.EndpointResults()[2U * element + 1U];
EXPECT_EQ(negative_endpoint.element, element);
EXPECT_EQ(negative_endpoint.endpoint, 0);
EXPECT_DOUBLE_EQ(negative_endpoint.end_action[0U], -value);
EXPECT_DOUBLE_EQ(negative_endpoint.section_resultant[0U], value);
EXPECT_EQ(positive_endpoint.element, element);
EXPECT_EQ(positive_endpoint.endpoint, 1);
EXPECT_DOUBLE_EQ(positive_endpoint.end_action[0U], value);
EXPECT_DOUBLE_EQ(positive_endpoint.section_resultant[0U], value);
EXPECT_EQ(state.GaussResults()[2U * element].element, element);
EXPECT_EQ(state.GaussResults()[2U * element].gauss_point, 1);
EXPECT_EQ(state.GaussResults()[2U * element + 1U].element, element);
EXPECT_EQ(state.GaussResults()[2U * element + 1U].gauss_point, 2);
EXPECT_EQ(state.StressResults()[2U * element].element, element);
EXPECT_EQ(state.StressResults()[2U * element + 1U].element, element);
}
EXPECT_TRUE(state.ShellResults().empty());
}
// C-RECOVERY-001
TEST(ResultRecovery, AggregatesFakeShellBundleAndPhysicalEnergy) {
const auto fixture = MakeShellFixture(MakeShellDefinition());
const auto input = MakeShellPhysicalState(fixture);
auto state = fesa::AnalysisState::Create(*fixture.dofs, {"Step-1", 0U});
constexpr double physical_energy = 37.5;
FakeRecoveryElement shell{
MakeRuntimeLayout(*fixture.domain, 0U),
MakeFakeShellBundle(*fixture.domain, 0U, physical_energy)};
const fesa::ElementView elements{std::cref(shell)};
const fesa::Status status = fesa::ResultRecovery::Recover(
*fixture.model, elements, *fixture.dofs, *fixture.stiffness,
input.Displacement(), input.ExternalForce(), state);
ASSERT_TRUE(status.IsOk());
ExpectVectorEqual(state.Displacement(), input.Displacement());
ExpectVectorEqual(state.ExternalForce(), input.ExternalForce());
EXPECT_TRUE(state.EndpointResults().empty());
EXPECT_TRUE(state.GaussResults().empty());
EXPECT_TRUE(state.StressResults().empty());
ASSERT_EQ(state.ShellResults().size(), 4U);
EXPECT_EQ(state.ShellResults()[0U].location,
fesa::ShellMidsurfaceLocation::kGp1);
EXPECT_EQ(state.ShellResults()[3U].location,
fesa::ShellMidsurfaceLocation::kGp4);
EXPECT_DOUBLE_EQ(state.ShellResults()[0U].generalized_strain[0U], 1.0);
EXPECT_DOUBLE_EQ(state.ShellResults()[3U].section_resultant[0U], 40.0);
EXPECT_DOUBLE_EQ(state.PhysicalStrainEnergy(), physical_energy);
}
// C-RECOVERY-001
TEST(ResultRecovery, FailingFakeBundleRollsBackTheWholeState) {
const auto fixture = MakeFixture(true);
const auto input = MakeAxialEquilibriumState(fixture);
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);
}
auto stale_beam_bundle = MakeFakeBeamBundle(*fixture.domain, 0U, 9.0);
auto stale_beam_rows =
std::get<fesa::BeamElementResultRows>(stale_beam_bundle.payload);
state.EndpointResults() = std::move(stale_beam_rows.endpoint_rows);
state.GaussResults() = std::move(stale_beam_rows.gauss_rows);
state.StressResults() = std::move(stale_beam_rows.stress_rows);
auto stale_shell_bundle = MakeFakeShellBundle(*fixture.domain, 0U, 71.0);
const auto& stale_shell_rows =
std::get<fesa::ShellElementResultRows>(stale_shell_bundle.payload);
fesa::ShellStateCandidate stale_shell_candidate{};
stale_shell_candidate.rows = stale_shell_rows.rows;
stale_shell_candidate.physical_strain_energy =
stale_shell_rows.physical_strain_energy;
stale_shell_candidate.equilibrium = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
stale_shell_candidate.verification_metrics = {1.0e-11, 2.0e-11, 3.0e-11};
ASSERT_TRUE(
state.CommitShellResults({0U}, std::move(stale_shell_candidate)).IsOk());
const fesa::AnalysisState prior = state;
FakeRecoveryElement first{MakeRuntimeLayout(*fixture.domain, 0U),
MakeFakeBeamBundle(*fixture.domain, 0U, 1.0)};
FakeRecoveryElement failing{MakeRuntimeLayout(*fixture.domain, 1U),
MakeFakeBeamBundle(*fixture.domain, 1U, 2.0),
true};
const fesa::ElementView elements{std::cref(first), std::cref(failing)};
const fesa::Status status = fesa::ResultRecovery::Recover(
*fixture.model, elements, *fixture.dofs, *fixture.stiffness,
input.Displacement(), input.ExternalForce(), state);
ExpectStatusCode(status, "fake-recovery-failure");
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());
EXPECT_EQ(state.Identity().step_name, prior.Identity().step_name);
EXPECT_EQ(state.Identity().frame_index, prior.Identity().frame_index);
ASSERT_EQ(state.EndpointResults().size(), prior.EndpointResults().size());
EXPECT_EQ(state.EndpointResults()[0U].element,
prior.EndpointResults()[0U].element);
EXPECT_EQ(state.EndpointResults()[0U].end_action,
prior.EndpointResults()[0U].end_action);
ASSERT_EQ(state.GaussResults().size(), prior.GaussResults().size());
EXPECT_EQ(state.GaussResults()[0U].generalized_resultant,
prior.GaussResults()[0U].generalized_resultant);
ASSERT_EQ(state.StressResults().size(), prior.StressResults().size());
EXPECT_DOUBLE_EQ(state.StressResults()[0U].s11,
prior.StressResults()[0U].s11);
ASSERT_EQ(state.ShellResults().size(), prior.ShellResults().size());
EXPECT_EQ(state.ShellResults()[0U].generalized_strain,
prior.ShellResults()[0U].generalized_strain);
EXPECT_DOUBLE_EQ(state.PhysicalStrainEnergy(), prior.PhysicalStrainEnergy());
EXPECT_EQ(state.Equilibrium(), prior.Equilibrium());
EXPECT_EQ(state.VerificationMetrics(), prior.VerificationMetrics());
}
TEST(ResultRecovery, ComputesResidualReactionForNonzeroPrescription) {
const auto fixture = MakeFixture();
auto state = MakeAxialEquilibriumState(fixture);
fesa::ShellStateCandidate stale_shell_evidence{};
stale_shell_evidence.physical_strain_energy = 123.0;
stale_shell_evidence.equilibrium = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
stale_shell_evidence.verification_metrics = {1.0e-11, 2.0e-11, 3.0e-11};
ASSERT_TRUE(state.CommitShellResults({}, stale_shell_evidence).IsOk());
const fesa::Status status = fesa::ResultRecovery::Recover(
*fixture.model, *fixture.dofs, *fixture.stiffness, state);
ASSERT_TRUE(status.IsOk());
EXPECT_DOUBLE_EQ(state.InternalForce()[0U], -20.0);
EXPECT_DOUBLE_EQ(state.InternalForce()[6U], 20.0);
EXPECT_DOUBLE_EQ(state.Residual()[0U], -20.0);
EXPECT_DOUBLE_EQ(state.Residual()[6U], 0.0);
EXPECT_DOUBLE_EQ(state.Reaction()[0U], -20.0);
for (const std::size_t full_dof : fixture.dofs->FreeDofs()) {
EXPECT_DOUBLE_EQ(state.Reaction()[full_dof], 0.0);
}
EXPECT_TRUE(state.ShellResults().empty());
EXPECT_DOUBLE_EQ(state.PhysicalStrainEnergy(), 0.0);
EXPECT_EQ(state.Equilibrium(),
(std::array<double, 6>{0.0, 0.0, 0.0, 0.0, 0.0, 0.0}));
EXPECT_EQ(state.VerificationMetrics(),
(std::array<double, 3>{0.0, 0.0, 0.0}));
}
TEST(ResultRecovery, EnforcesNormalizedFreeResidual) {
const auto fixture = MakeFixture();
auto failed = MakeAxialEquilibriumState(fixture);
failed.InternalForce()[0U] = 91.0;
failed.Residual()[0U] = 92.0;
failed.Reaction()[0U] = 93.0;
failed.Reaction()[6U] = 94.0;
failed.EndpointResults().push_back({});
failed.ExternalForce()[6U] += 1.0e-7;
ExpectStatusCode(fesa::ResultRecovery::Recover(*fixture.model, *fixture.dofs,
*fixture.stiffness, failed),
"free-residual-tolerance-failure");
EXPECT_DOUBLE_EQ(failed.InternalForce()[0U], 91.0);
EXPECT_DOUBLE_EQ(failed.Residual()[0U], 92.0);
EXPECT_DOUBLE_EQ(failed.Reaction()[0U], 93.0);
EXPECT_DOUBLE_EQ(failed.Reaction()[6U], 94.0);
EXPECT_EQ(failed.EndpointResults().size(), 1U);
auto threshold_pass = MakeAxialEquilibriumState(fixture);
threshold_pass.ExternalForce()[6U] += 1.0e-10 * 20.0 * 0.5;
const fesa::Status threshold_status = fesa::ResultRecovery::Recover(
*fixture.model, *fixture.dofs, *fixture.stiffness, threshold_pass);
ASSERT_TRUE(threshold_status.IsOk());
EXPECT_NE(threshold_pass.Residual()[6U], 0.0);
EXPECT_DOUBLE_EQ(threshold_pass.Reaction()[6U],
threshold_pass.Residual()[6U]);
const auto zero_fixture = MakeFixture(false, {}, {}, false, false, false);
auto zero_equilibrium =
fesa::AnalysisState::Create(*zero_fixture.dofs, {"Step-1", 0U});
EXPECT_TRUE(
fesa::ResultRecovery::Recover(*zero_fixture.model, *zero_fixture.dofs,
*zero_fixture.stiffness, zero_equilibrium)
.IsOk());
auto wrong_prescription = MakeAxialEquilibriumState(fixture);
wrong_prescription.Displacement()[0U] = 0.0;
ExpectStatusCode(
fesa::ResultRecovery::Recover(*fixture.model, *fixture.dofs,
*fixture.stiffness, wrong_prescription),
"invalid-recovery-state");
auto nonfinite = MakeAxialEquilibriumState(fixture);
nonfinite.Displacement()[6U] = std::numeric_limits<double>::quiet_NaN();
ExpectStatusCode(fesa::ResultRecovery::Recover(*fixture.model, *fixture.dofs,
*fixture.stiffness, nonfinite),
"nonfinite-recovery-value");
const auto wrong_fixture = MakeFixture(true);
auto wrong_state =
fesa::AnalysisState::Create(*wrong_fixture.dofs, {"Step-1", 0U});
ExpectStatusCode(
fesa::ResultRecovery::Recover(*fixture.model, *fixture.dofs,
*fixture.stiffness, wrong_state),
"invalid-recovery-dimensions");
}
TEST(ResultRecovery, KeepsEndActionSectionAndGaussResultsDistinct) {
const auto fixture = MakeFixture();
auto state = MakePatchState(fixture, 0.02, 0.03, -0.04, 0.05);
ASSERT_TRUE(fesa::ResultRecovery::Recover(*fixture.model, *fixture.dofs,
*fixture.stiffness, state)
.IsOk());
ASSERT_EQ(state.EndpointResults().size(), 2U);
ASSERT_EQ(state.GaussResults().size(), 2U);
EXPECT_EQ(state.EndpointResults()[0U].endpoint, 0);
EXPECT_EQ(state.EndpointResults()[1U].endpoint, 1);
EXPECT_EQ(state.GaussResults()[0U].gauss_point, 1);
EXPECT_EQ(state.GaussResults()[1U].gauss_point, 2);
EXPECT_DOUBLE_EQ(state.EndpointResults()[0U].end_action[0U],
-state.EndpointResults()[0U].section_resultant[0U]);
EXPECT_DOUBLE_EQ(state.EndpointResults()[1U].end_action[0U],
state.EndpointResults()[1U].section_resultant[0U]);
EXPECT_DOUBLE_EQ(state.GaussResults()[0U].generalized_resultant[0U],
state.EndpointResults()[0U].section_resultant[0U]);
}
TEST(ResultRecovery, MatchesAxialTorsionAndTwoPlaneEndSigns) {
const auto fixture = MakeFixture();
const double epsilon = 0.02;
const double twist = -0.03;
const double kappa_y = 0.04;
const double kappa_z = -0.05;
auto state = MakePatchState(fixture, epsilon, twist, kappa_y, kappa_z);
ASSERT_TRUE(fesa::ResultRecovery::Recover(*fixture.model, *fixture.dofs,
*fixture.stiffness, state)
.IsOk());
const double shear_modulus = kYoungsModulus / (2.0 * (1.0 + kPoissonRatio));
const std::array<double, 4> expected = {
kYoungsModulus * 2.0 * epsilon, shear_modulus * 5.0 * twist,
kYoungsModulus * 3.0 * kappa_y, kYoungsModulus * 4.0 * kappa_z};
const std::array<std::size_t, 4> end_components = {0U, 3U, 4U, 5U};
for (std::size_t component = 0U; component < expected.size(); ++component) {
ExpectScaledNear(state.EndpointResults()[0U].section_resultant[component],
expected[component]);
ExpectScaledNear(state.EndpointResults()[1U].section_resultant[component],
expected[component]);
ExpectScaledNear(
state.EndpointResults()[0U].end_action[end_components[component]],
-expected[component]);
ExpectScaledNear(
state.EndpointResults()[1U].end_action[end_components[component]],
expected[component]);
}
}
TEST(ResultRecovery, OrdersStressPointsAndDefaultCentroid) {
const std::vector<std::array<double, 2>> section_points = {{0.25, -0.5},
{-0.4, 0.3}};
const auto fixture = MakeFixture(false, section_points);
auto state = MakePatchState(fixture, 0.01, 0.0, 0.02, -0.03);
ASSERT_TRUE(fesa::ResultRecovery::Recover(*fixture.model, *fixture.dofs,
*fixture.stiffness, state)
.IsOk());
ASSERT_EQ(state.StressResults().size(), 4U);
for (std::size_t gauss = 0U; gauss < 2U; ++gauss) {
for (std::size_t point = 0U; point < section_points.size(); ++point) {
const auto& row = state.StressResults()[gauss * 2U + point];
EXPECT_EQ(row.element, 0U);
EXPECT_EQ(row.gauss_point, static_cast<int>(gauss + 1U));
EXPECT_EQ(row.section_point, point + 1U);
EXPECT_DOUBLE_EQ(row.x1, section_points[point][0U]);
EXPECT_DOUBLE_EQ(row.x2, section_points[point][1U]);
EXPECT_EQ(row.source, "input");
ExpectScaledNear(
row.s11, kYoungsModulus * (0.01 + row.x2 * 0.02 - row.x1 * -0.03));
}
}
const auto default_fixture = MakeFixture();
auto default_state = MakePatchState(default_fixture, 0.01, 0.0, 0.0, 0.0);
ASSERT_TRUE(fesa::ResultRecovery::Recover(
*default_fixture.model, *default_fixture.dofs,
*default_fixture.stiffness, default_state)
.IsOk());
ASSERT_EQ(default_state.StressResults().size(), 2U);
for (const auto& row : default_state.StressResults()) {
EXPECT_EQ(row.section_point, 0U);
EXPECT_DOUBLE_EQ(row.x1, 0.0);
EXPECT_DOUBLE_EQ(row.x2, 0.0);
EXPECT_EQ(row.source, "fesa-default");
}
}
TEST(ResultRecovery, RequiresInteriorEndpointConsistencyWithoutAveraging) {
const auto fixture = MakeFixture(true);
const std::array<double, 4> tolerances = {1.0e-6, 1.0e-6, 1.0e-6, 1.0e-6};
auto rows = MakeStationRows(fixture);
rows[2U].section_resultant[0U] += 0.5e-6;
auto normalized =
fesa::ResultRecovery::NormalizeSectionResultantsToNodeStations(
*fixture.model, rows, tolerances);
ASSERT_TRUE(normalized.HasValue());
ASSERT_EQ(normalized.Value().size(), 3U);
EXPECT_EQ(normalized.Value()[1U].representative_element, 0U);
EXPECT_DOUBLE_EQ(normalized.Value()[1U].section_resultant[0U], 5.0);
rows[2U].section_resultant[0U] = 5.0 + 2.0e-6;
auto mismatch =
fesa::ResultRecovery::NormalizeSectionResultantsToNodeStations(
*fixture.model, rows, tolerances);
ASSERT_FALSE(mismatch.HasValue());
ExpectStatusCode(mismatch.GetStatus(), "node-station-tolerance-failure");
rows = MakeStationRows(fixture);
rows[2U].section_resultant[1U] = std::numeric_limits<double>::infinity();
auto nonfinite =
fesa::ResultRecovery::NormalizeSectionResultantsToNodeStations(
*fixture.model, rows, tolerances);
ASSERT_FALSE(nonfinite.HasValue());
ExpectStatusCode(nonfinite.GetStatus(), "nonfinite-node-station-value");
auto invalid_tolerance =
fesa::ResultRecovery::NormalizeSectionResultantsToNodeStations(
*fixture.model, MakeStationRows(fixture),
{1.0e-6, -1.0, 1.0e-6, 1.0e-6});
ASSERT_FALSE(invalid_tolerance.HasValue());
ExpectStatusCode(invalid_tolerance.GetStatus(),
"invalid-node-station-tolerance");
const std::filesystem::path source{"models/result-recovery.inp"};
const auto loaded_fixture =
MakeFixture(true, {}, {{"2", 2, 1.0, {source, 60U}}});
auto loaded = fesa::ResultRecovery::NormalizeSectionResultantsToNodeStations(
*loaded_fixture.model, MakeStationRows(loaded_fixture), tolerances);
ASSERT_FALSE(loaded.HasValue());
ExpectStatusCode(loaded.GetStatus(), "ineligible-node-station");
const auto reversed_fixture = MakeFixture(true, {}, {}, true);
auto reversed =
fesa::ResultRecovery::NormalizeSectionResultantsToNodeStations(
*reversed_fixture.model, MakeStationRows(reversed_fixture),
tolerances);
ASSERT_FALSE(reversed.HasValue());
ExpectStatusCode(reversed.GetStatus(), "ineligible-node-station");
const auto jump_fixture = MakeFixture(true, {}, {}, false, true);
auto jumped = fesa::ResultRecovery::NormalizeSectionResultantsToNodeStations(
*jump_fixture.model, MakeStationRows(jump_fixture), tolerances);
ASSERT_FALSE(jumped.HasValue());
ExpectStatusCode(jumped.GetStatus(), "ineligible-node-station");
}
// MITC4-REC-001
TEST(ResultRecovery, RecoversShellRowsInStableElementAndGpOrder) {
const auto fixture = MakeShellFixture(MakeShellDefinition(true));
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];
state.Displacement()[node * 6U] = 0.1 * x + 0.1 * y;
state.Displacement()[node * 6U + 1U] = -0.05 * y + 0.1 * x;
}
state.ExternalForce() = fixture.stiffness->Multiply(state.Displacement());
const auto status = fesa::ResultRecovery::Recover(
*fixture.model, *fixture.dofs, *fixture.stiffness, state);
ASSERT_TRUE(status.IsOk());
ASSERT_EQ(state.ShellResults().size(), 8U);
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}};
constexpr std::array<double, 8> expected_strain{0.1, -0.05, 0.2, 0.0,
0.0, 0.0, 0.0, 0.0};
constexpr std::array<double, 8> expected_resultant{22.4, -6.4, 19.2, 0.0,
0.0, 0.0, 0.0, 0.0};
const std::array<std::array<double, 3>, 3> expected_frame{
{{1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}}};
for (std::size_t element = 0U; element < 2U; ++element) {
for (std::size_t point = 0U; point < locations.size(); ++point) {
const auto& row = state.ShellResults()[element * 4U + point];
EXPECT_EQ(row.element, element);
EXPECT_EQ(row.location, locations[point]);
EXPECT_EQ(row.natural_coordinates, coordinates[point]);
EXPECT_EQ(row.local_frame, expected_frame);
for (std::size_t component = 0U; component < expected_strain.size();
++component) {
EXPECT_NEAR(row.generalized_strain[component],
expected_strain[component], 1.0e-12);
EXPECT_NEAR(row.section_resultant[component],
expected_resultant[component], 1.0e-12);
}
}
}
}
// MITC4-REC-002
TEST(ResultRecovery, RecoversDirectBottomMiddleTopShellStress) {
const auto fixture = MakeShellFixture(MakeShellDefinition());
auto state = MakeShellPhysicalState(fixture);
ASSERT_TRUE(fesa::ResultRecovery::Recover(*fixture.model, *fixture.dofs,
*fixture.stiffness, state)
.IsOk());
constexpr 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};
constexpr std::array<std::array<double, 3>, 3> expected_stress{
{{-22.4, 6.4, -2.4}, {11.2, -3.2, 9.6}, {44.8, -12.8, 21.6}}};
ASSERT_EQ(state.ShellResults().size(), 4U);
for (const auto& row : state.ShellResults()) {
for (std::size_t position = 0U; position < positions.size(); ++position) {
EXPECT_EQ(row.stress[position].position, positions[position]);
EXPECT_DOUBLE_EQ(row.stress[position].zeta, zeta[position]);
for (std::size_t component = 0U;
component < expected_stress[position].size(); ++component) {
EXPECT_NEAR(row.stress[position].components[component],
expected_stress[position][component], 1.0e-12);
}
}
}
}
// MITC4-REC-003
TEST(ResultRecovery, SumsOnlyPhysicalShellEnergyInSourceOrder) {
const auto fixture = MakeShellFixture(MakeShellDefinition());
auto state = MakeShellPhysicalState(fixture);
constexpr std::array<double, 4> drill{1.0, -1.0, 1.0, -1.0};
for (std::size_t node = 0U; node < drill.size(); ++node) {
state.Displacement()[node * 6U + 5U] = drill[node];
}
state.ExternalForce() = fixture.stiffness->Multiply(state.Displacement());
const double stabilized_energy =
0.5 * state.Displacement().Dot(
fixture.stiffness->Multiply(state.Displacement()));
ASSERT_TRUE(fesa::ResultRecovery::Recover(*fixture.model, *fixture.dofs,
*fixture.stiffness, state)
.IsOk());
EXPECT_NEAR(state.PhysicalStrainEnergy(), 72.16, 1.0e-12);
EXPECT_GT(stabilized_energy, state.PhysicalStrainEnergy());
}
// MITC4-REC-004
TEST(ResultRecovery, KeepsFullResidualAndComputesGlobalShellEquilibrium) {
const std::filesystem::path source{"models/shell-result-recovery.inp"};
const std::vector<fesa::NodalLoad> loads{{"1", 1, 5.0, {source, 70U}},
{"5", 1, -5.0, {source, 71U}},
{"1", 4, 2.0, {source, 72U}},
{"5", 4, -2.0, {source, 73U}}};
const auto fixture =
MakeShellFixture(MakeShellDefinition(false, true, loads));
auto state = fesa::AnalysisState::Create(*fixture.dofs, {"Step-1", 0U});
auto full_load =
fesa::LoadAssembler::AssembleFullNodalLoad(*fixture.model, *fixture.dofs);
ASSERT_TRUE(full_load.HasValue());
state.ExternalForce() = std::move(full_load.Value());
ASSERT_TRUE(fesa::ResultRecovery::Recover(*fixture.model, *fixture.dofs,
*fixture.stiffness, state)
.IsOk());
ASSERT_EQ(state.ShellResults().size(), 4U);
for (std::size_t full_dof = 0U; full_dof < fixture.dofs->FullDofCount();
++full_dof) {
EXPECT_DOUBLE_EQ(state.InternalForce()[full_dof], 0.0);
EXPECT_DOUBLE_EQ(state.Residual()[full_dof],
-state.ExternalForce()[full_dof]);
EXPECT_DOUBLE_EQ(state.Reaction()[full_dof], state.Residual()[full_dof]);
}
EXPECT_EQ(state.Equilibrium(),
(std::array<double, 6>{0.0, 0.0, 0.0, 0.0, 0.0, 0.0}));
EXPECT_EQ(state.VerificationMetrics(),
(std::array<double, 3>{0.0, 0.0, 0.0}));
const auto free_fixture = MakeShellFixture(MakeShellDefinition());
auto perturbed = MakeShellPhysicalState(free_fixture);
perturbed.ExternalForce()[0U] += 1.0e-9;
ASSERT_TRUE(fesa::ResultRecovery::Recover(*free_fixture.model,
*free_fixture.dofs,
*free_fixture.stiffness, perturbed)
.IsOk());
for (const double metric : perturbed.VerificationMetrics()) {
EXPECT_GT(metric, 0.0);
EXPECT_LE(metric, 1.0e-10);
}
}
// MITC4-REC-004, C-DUP-002
TEST(ResultRecovery, UsesGlobalOriginForShellMomentBalance) {
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;
ASSERT_TRUE(fesa::ResultRecovery::Recover(
*centered_fixture.model, *centered_fixture.dofs,
*centered_fixture.stiffness, centered)
.IsOk());
ASSERT_TRUE(fesa::ResultRecovery::Recover(
*translated_fixture.model, *translated_fixture.dofs,
*translated_fixture.stiffness, translated)
.IsOk());
std::array<double, 3> centered_force{};
for (std::size_t component = 0U; component < 3U; ++component) {
centered_force[component] = centered.Equilibrium()[component];
EXPECT_NEAR(translated.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 < 3U; ++component) {
EXPECT_NEAR(translated.Equilibrium()[3U + component] -
centered.Equilibrium()[3U + component],
translated_moment_delta[component], 5.0e-12);
}
EXPECT_GT(std::abs(translated_moment_delta[2U]), 1.0e-9);
}
// MITC4-REC-004
TEST(ResultRecovery, UsesScaleAwareShellMetricsAndRejectsExcess) {
const auto fixture = MakeShellFixture(MakeShellDefinition());
auto subunit = MakeShellPhysicalState(fixture);
auto large = MakeShellPhysicalState(fixture);
constexpr double subunit_scale = 1.0e-6;
constexpr double large_scale = 1.0e6;
subunit.Displacement().Scale(subunit_scale);
subunit.ExternalForce().Scale(subunit_scale);
subunit.ExternalForce()[0U] += 1.0e-9 * subunit_scale;
large.Displacement().Scale(large_scale);
large.ExternalForce().Scale(large_scale);
large.ExternalForce()[0U] += 1.0e-9 * large_scale;
ASSERT_TRUE(fesa::ResultRecovery::Recover(*fixture.model, *fixture.dofs,
*fixture.stiffness, subunit)
.IsOk());
ASSERT_TRUE(fesa::ResultRecovery::Recover(*fixture.model, *fixture.dofs,
*fixture.stiffness, large)
.IsOk());
for (std::size_t metric = 0U; metric < 3U; ++metric) {
EXPECT_GT(subunit.VerificationMetrics()[metric], 0.0);
EXPECT_GT(large.VerificationMetrics()[metric], 0.0);
EXPECT_NEAR(subunit.VerificationMetrics()[metric],
large.VerificationMetrics()[metric], 1.0e-13);
}
auto constrained_definition = MakeShellDefinition(false, true);
constrained_definition.steps[0U].boundaries[0U].value = 1.0;
const auto constrained_fixture =
MakeShellFixture(std::move(constrained_definition));
auto rejected =
fesa::AnalysisState::Create(*constrained_fixture.dofs, {"Step-1", 0U});
for (std::size_t full_dof = 0U;
full_dof < constrained_fixture.dofs->FullDofCount(); ++full_dof) {
rejected.Displacement()[full_dof] = 1.0;
}
const std::vector<fesa::CooContribution> unbalanced_entry{
{0U, 0U, 1.0, 0U, 0U}};
auto unbalanced_stiffness = fesa::SparseMatrix::FromCoo(
constrained_fixture.dofs->FullDofCount(),
constrained_fixture.dofs->FullDofCount(), unbalanced_entry,
constrained_fixture.dofs->GetSparsePattern());
ASSERT_TRUE(unbalanced_stiffness.HasValue());
ExpectStatusCode(fesa::ResultRecovery::Recover(
*constrained_fixture.model, *constrained_fixture.dofs,
unbalanced_stiffness.Value(), rejected),
"global-equilibrium-tolerance-failure");
}
// MITC4-REC-005
TEST(ResultRecovery, InvalidLaterShellLeavesEntirePriorStateUnchanged) {
const auto valid_fixture = MakeShellFixture(MakeShellDefinition(true));
auto state = MakeShellPhysicalState(valid_fixture);
ASSERT_TRUE(fesa::ResultRecovery::Recover(*valid_fixture.model,
*valid_fixture.dofs,
*valid_fixture.stiffness, state)
.IsOk());
ASSERT_EQ(state.ShellResults().size(), 8U);
const auto prior_first_row = state.ShellResults().front();
const double prior_energy = state.PhysicalStrainEnergy();
const auto prior_equilibrium = state.Equilibrium();
const auto prior_metrics = state.VerificationMetrics();
state.InternalForce()[0U] = 91.0;
state.Residual()[0U] = 92.0;
state.Reaction()[0U] = 93.0;
state.EndpointResults().push_back({});
state.Displacement()[2U * 6U] = (std::numeric_limits<double>::max)();
state.Displacement()[5U * 6U] = (std::numeric_limits<double>::max)();
state.ExternalForce() = fesa::Vector{valid_fixture.dofs->FullDofCount()};
auto zero_stiffness = fesa::SparseMatrix::FromCoo(
valid_fixture.dofs->FullDofCount(), valid_fixture.dofs->FullDofCount(),
{}, valid_fixture.dofs->GetSparsePattern());
ASSERT_TRUE(zero_stiffness.HasValue());
const auto status = fesa::ResultRecovery::Recover(
*valid_fixture.model, *valid_fixture.dofs, zero_stiffness.Value(), state);
ExpectStatusCode(status, "invalid-shell-recovery");
EXPECT_DOUBLE_EQ(state.InternalForce()[0U], 91.0);
EXPECT_DOUBLE_EQ(state.Residual()[0U], 92.0);
EXPECT_DOUBLE_EQ(state.Reaction()[0U], 93.0);
EXPECT_EQ(state.EndpointResults().size(), 1U);
ASSERT_EQ(state.ShellResults().size(), 8U);
EXPECT_EQ(state.ShellResults().front().element, prior_first_row.element);
EXPECT_EQ(state.ShellResults().front().location, prior_first_row.location);
EXPECT_EQ(state.ShellResults().front().generalized_strain,
prior_first_row.generalized_strain);
EXPECT_EQ(state.ShellResults().front().section_resultant,
prior_first_row.section_resultant);
EXPECT_DOUBLE_EQ(state.PhysicalStrainEnergy(), prior_energy);
EXPECT_EQ(state.Equilibrium(), prior_equilibrium);
EXPECT_EQ(state.VerificationMetrics(), prior_metrics);
}