feat(cpp-object-oriented-modular-refactoring): step 17 - generic-result-recovery
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
@@ -19,6 +20,7 @@
|
||||
#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"
|
||||
@@ -43,6 +45,141 @@ struct ShellRecoveryFixture {
|
||||
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 = {},
|
||||
@@ -343,6 +480,158 @@ std::vector<fesa::EndpointResultRow> MakeStationRows(
|
||||
|
||||
} // 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);
|
||||
|
||||
Reference in New Issue
Block a user