feat(cpp-object-oriented-modular-refactoring): step 17 - generic-result-recovery

This commit is contained in:
KOKO\Mimi
2026-08-16 10:40:49 +09:00
parent 31b6129cf8
commit d711e6d4fd
3 changed files with 543 additions and 273 deletions
+233 -272
View File
@@ -4,15 +4,17 @@
#include <array>
#include <cmath>
#include <cstddef>
#include <functional>
#include <limits>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "fesa/elements/euler_beam_3d.h"
#include "fesa/elements/mitc4_shell.h"
#include "fesa/elements/element_factory.h"
#include "fesa/math/vector3.h"
#include "fesa/model/source_target_resolver.h"
@@ -20,9 +22,6 @@ namespace fesa {
namespace {
constexpr std::size_t kDofsPerNode = 6U;
constexpr std::size_t kElementDofCount = 12U;
constexpr std::size_t kShellElementDofCount = 24U;
constexpr std::size_t kShellLocationCount = 4U;
constexpr double kFreeResidualTolerance = 1.0e-10;
constexpr double kGlobalEquilibriumTolerance = 1.0e-10;
constexpr double kAxisTolerance = 1.0e-12;
@@ -118,8 +117,11 @@ bool StrictlyIncreasing(const std::vector<std::size_t>& values) {
/// @brief Validates complete recovery inputs before creating candidate results.
Status ValidateRecoveryInputs(const AnalysisModel& model,
const ElementView& elements,
const DofManager& dofs,
const SparseMatrix& full_stiffness,
const Vector& full_displacement,
const Vector& full_external_force,
const AnalysisState& state) {
const Domain& domain = model.GetDomain();
if (domain.Nodes().size() >
@@ -133,6 +135,8 @@ Status ValidateRecoveryInputs(const AnalysisModel& model,
if (dofs.FullDofCount() != full_count ||
full_stiffness.Rows() != full_count ||
full_stiffness.Columns() != full_count ||
full_displacement.Size() != full_count ||
full_external_force.Size() != full_count ||
state.Displacement().Size() != full_count ||
state.ExternalForce().Size() != full_count ||
state.InternalForce().Size() != full_count ||
@@ -148,7 +152,11 @@ Status ValidateRecoveryInputs(const AnalysisModel& model,
if (!matrix_status.IsOk()) {
return matrix_status;
}
if (!IsFinite(state.Displacement()) || !IsFinite(state.ExternalForce())) {
const Status dof_status = dofs.ValidateInvariants();
if (!dof_status.IsOk()) {
return dof_status;
}
if (!IsFinite(full_displacement) || !IsFinite(full_external_force)) {
return RecoveryFailure(
"nonfinite-recovery-value", {domain.SourcePath(), 0U},
domain.SourceContentIdentity(),
@@ -191,8 +199,7 @@ Status ValidateRecoveryInputs(const AnalysisModel& model,
"Constrained DOFs must be unique and absent from free equations.");
}
if (!std::isfinite(dofs.PrescribedValues()[constrained]) ||
state.Displacement()[full_dof] !=
dofs.PrescribedValues()[constrained]) {
full_displacement[full_dof] != dofs.PrescribedValues()[constrained]) {
return RecoveryFailure("invalid-recovery-state",
{domain.SourcePath(), 0U},
std::to_string(full_dof),
@@ -214,111 +221,125 @@ Status ValidateRecoveryInputs(const AnalysisModel& model,
"Free and constrained DOFs must partition the full range.");
}
EntityIndex previous_element = 0U;
bool first_element = true;
for (const EntityIndex element : model.ActiveBeamElements()) {
if (element >= domain.BeamElements().Size() ||
(!first_element && element <= previous_element)) {
if (elements.size() != model.ActiveElements().size()) {
return RecoveryFailure(
"invalid-recovery-entity", {domain.SourcePath(), 0U},
std::to_string(elements.size()),
"Runtime elements must match the active recovery inventory.");
}
EntityIndex previous_definition = 0U;
bool first_definition = true;
for (std::size_t source_order = 0U; source_order < elements.size();
++source_order) {
const EntityIndex definition_index = model.ActiveElements()[source_order];
if (definition_index >= domain.Elements().Size() ||
(!first_definition && definition_index <= previous_definition)) {
return RecoveryFailure(
"invalid-recovery-entity", {domain.SourcePath(), 0U},
std::to_string(element),
std::to_string(definition_index),
"Active elements must be unique in stable internal-index order.");
}
first_element = false;
previous_element = element;
const auto& definition = domain.BeamElements()[element];
if (definition.node_indices[0U] >= domain.Nodes().size() ||
definition.node_indices[1U] >= domain.Nodes().size() ||
definition.material_index >= domain.LinearElasticMaterials().Size() ||
definition.section_index >= domain.Sections().Size()) {
first_definition = false;
previous_definition = definition_index;
const auto& definition = domain.Elements()[definition_index];
const auto& layout = elements[source_order].get().DofLayout();
if (!SameSourceIdentity(layout.source_id, definition.SourceId()) ||
layout.node_indices != definition.NodeIndices()) {
return RecoveryFailure(
"invalid-recovery-entity", definition.location,
definition.source_id.source_label_text,
"Active beam references must resolve before recovery.");
"invalid-recovery-entity", {domain.SourcePath(), 0U},
definition.SourceId().source_label_text,
"Runtime recovery layouts must preserve active source identity and "
"topology order.");
}
try {
const auto& scatter = dofs.ElementScatter(element);
for (std::size_t endpoint = 0U; endpoint < 2U; ++endpoint) {
for (std::size_t component = 0U; component < kDofsPerNode;
++component) {
const std::size_t expected =
static_cast<std::size_t>(definition.node_indices[endpoint]) *
kDofsPerNode +
component;
if (scatter[endpoint * kDofsPerNode + component] != expected ||
expected >= full_count) {
return RecoveryFailure("invalid-recovery-order",
definition.location,
definition.source_id.source_label_text,
"Element scatter must preserve "
"endpoint/component full-DOF order.");
}
}
}
} catch (const std::out_of_range&) {
return RecoveryFailure(
"invalid-recovery-entity", definition.location,
definition.source_id.source_label_text,
"Every active element requires one twelve-DOF scatter map.");
auto scatter = dofs.ElementScatter(layout);
if (!scatter.HasValue()) {
return scatter.GetStatus();
}
}
return Status::Ok();
}
if (!model.ActiveBeamElements().empty() && !domain.ShellElements().Empty()) {
return RecoveryFailure(
"unsupported-mixed-element-model", {domain.SourcePath(), 0U},
"B33:FESA-MITC4",
"Result recovery does not support mixed beam and shell models.");
}
if (domain.ShellElements().Size() >
static_cast<std::size_t>((std::numeric_limits<EntityIndex>::max)())) {
return RecoveryFailure("invalid-recovery-dimensions",
{domain.SourcePath(), 0U},
domain.SourceContentIdentity(),
"The shell element count cannot be represented by "
"stable element identities.");
}
for (std::size_t element_order = 0U;
element_order < domain.ShellElements().Size(); ++element_order) {
const auto& definition = domain.ShellElements()[element_order];
if (definition.material_index >= domain.LinearElasticMaterials().Size() ||
definition.section_index >= domain.ShellSections().Size()) {
return RecoveryFailure("invalid-recovery-entity", definition.location,
definition.source_id.source_label_text,
"Active shell material and section references "
"must resolve before recovery.");
}
try {
const auto& scatter =
dofs.ShellElementScatter(static_cast<EntityIndex>(element_order));
for (std::size_t node_position = 0U;
node_position < definition.node_indices.size(); ++node_position) {
const EntityIndex node = definition.node_indices[node_position];
if (node >= domain.Nodes().size()) {
return RecoveryFailure(
"invalid-recovery-entity", definition.location,
definition.source_id.source_label_text,
"Active shell node references must resolve before recovery.");
}
for (std::size_t component = 0U; component < kDofsPerNode;
++component) {
const std::size_t local = node_position * kDofsPerNode + component;
const std::size_t expected =
static_cast<std::size_t>(node) * kDofsPerNode + component;
if (scatter[local] != expected || expected >= full_count) {
return RecoveryFailure(
"invalid-recovery-order", definition.location,
definition.source_id.source_label_text,
"Shell scatter must preserve node/component full-DOF order.");
}
}
}
} catch (const std::out_of_range&) {
/// @brief Appends one typed beam bundle without changing row identity or sign.
Status AppendBeamRows(const SourceLocation& location,
const SourceEntityId& source_id,
const EntityIndex expected_element,
const BeamElementResultRows& rows,
std::vector<EndpointResultRow>& endpoint_rows,
std::vector<GaussResultRow>& gauss_rows,
std::vector<StressS11Row>& stress_rows) {
for (const auto& row : rows.endpoint_rows) {
if (row.element != expected_element) {
return RecoveryFailure(
"invalid-recovery-entity", definition.location,
definition.source_id.source_label_text,
"Every active shell requires one twenty-four-DOF scatter map.");
"invalid-element-result-identity", location,
source_id.source_label_text,
"Beam endpoint rows must retain their active element identity.");
}
if (!IsFinite(row.end_action) || !IsFinite(row.section_resultant)) {
return RecoveryFailure(
"nonfinite-recovery-value", location, source_id.source_label_text,
"Beam endpoint actions and section resultants must remain finite.");
}
endpoint_rows.push_back(row);
}
for (const auto& row : rows.gauss_rows) {
if (row.element != expected_element) {
return RecoveryFailure(
"invalid-element-result-identity", location,
source_id.source_label_text,
"Beam Gauss rows must retain their active element identity.");
}
if (!IsFinite(row.generalized_strain) ||
!IsFinite(row.generalized_resultant)) {
return RecoveryFailure(
"nonfinite-recovery-value", location, source_id.source_label_text,
"Beam generalized recovery values must remain finite.");
}
gauss_rows.push_back(row);
}
for (const auto& row : rows.stress_rows) {
if (row.element != expected_element) {
return RecoveryFailure(
"invalid-element-result-identity", location,
source_id.source_label_text,
"Beam stress rows must retain their active element identity.");
}
if (!std::isfinite(row.x1) || !std::isfinite(row.x2) ||
!std::isfinite(row.s11)) {
return RecoveryFailure(
"nonfinite-recovery-value", location, source_id.source_label_text,
"Beam section-point stress values must remain finite.");
}
stress_rows.push_back(row);
}
return Status::Ok();
}
/// @brief Appends one physical shell bundle in stable runtime order.
Status AppendShellRows(const SourceLocation& location,
const SourceEntityId& source_id,
const EntityIndex expected_element,
const ShellElementResultRows& rows,
std::vector<EntityIndex>& expected_elements,
ShellStateCandidate& candidate) {
const double accumulated_energy =
candidate.physical_strain_energy + rows.physical_strain_energy;
if (!std::isfinite(rows.physical_strain_energy) ||
!std::isfinite(accumulated_energy)) {
return RecoveryFailure(
"nonfinite-recovery-value", location, source_id.source_label_text,
"Source-order physical shell energy reduction must remain finite.");
}
for (const auto& row : rows.rows) {
if (row.element != expected_element) {
return RecoveryFailure(
"invalid-element-result-identity", location,
source_id.source_label_text,
"Shell rows must retain their active element identity.");
}
candidate.rows.push_back(row);
}
expected_elements.push_back(expected_element);
candidate.physical_strain_energy = accumulated_energy;
return Status::Ok();
}
@@ -476,16 +497,18 @@ Status PopulateShellGlobalEvidence(const Domain& domain, const DofManager& dofs,
}
std::optional<AxisSet> LocalAxes(const Domain& domain,
const EulerBeam3DDefinition& element) {
const auto& first = domain.Nodes()[element.node_indices[0U]].coordinates;
const auto& second = domain.Nodes()[element.node_indices[1U]].coordinates;
const EntityIndex first_node,
const EntityIndex second_node,
const std::array<double, 3>& first_axis) {
const auto& first = domain.Nodes()[first_node].coordinates;
const auto& second = domain.Nodes()[second_node].coordinates;
const Vector3 delta = Vector3{second} - Vector3{first};
const double length = delta.Norm();
if (!std::isfinite(length) || !(length > 0.0)) {
return std::nullopt;
}
const Vector3 ex = delta / length;
const Vector3 guide{domain.Sections()[element.section_index].first_axis};
const Vector3 guide{first_axis};
const double projection = guide.Dot(ex);
const Vector3 ey_trial = guide - projection * ex;
const double ey_norm = ey_trial.Norm();
@@ -519,16 +542,24 @@ bool SameAxes(const AxisSet& left, const AxisSet& right) {
} // namespace
Status ResultRecovery::Recover(const AnalysisModel& model,
const ElementView& elements,
const DofManager& dofs,
const SparseMatrix& full_stiffness,
const Vector& full_displacement,
const Vector& full_external_force,
AnalysisState& state) {
// Copy aliased AnalysisState inputs before candidate commit can replace the
// caller-owned state.
const Vector displacement = full_displacement;
const Vector external_force = full_external_force;
const Status input_status =
ValidateRecoveryInputs(model, dofs, full_stiffness, state);
ValidateRecoveryInputs(model, elements, dofs, full_stiffness,
displacement, external_force, state);
if (!input_status.IsOk()) {
return input_status;
}
Vector internal_force = full_stiffness.Multiply(state.Displacement());
Vector internal_force = full_stiffness.Multiply(displacement);
if (!IsFinite(internal_force)) {
return RecoveryFailure(
"nonfinite-recovery-value", {model.GetDomain().SourcePath(), 0U},
@@ -537,8 +568,7 @@ Status ResultRecovery::Recover(const AnalysisModel& model,
}
Vector residual{dofs.FullDofCount()};
for (std::size_t full_dof = 0U; full_dof < residual.Size(); ++full_dof) {
residual[full_dof] =
internal_force[full_dof] - state.ExternalForce()[full_dof];
residual[full_dof] = internal_force[full_dof] - external_force[full_dof];
if (!std::isfinite(residual[full_dof])) {
return RecoveryFailure(
"nonfinite-recovery-value", {model.GetDomain().SourcePath(), 0U},
@@ -549,9 +579,8 @@ Status ResultRecovery::Recover(const AnalysisModel& model,
const double residual_norm = IndexedNorm(residual, dofs.FreeDofs());
const auto internal_term_norms =
FreeEquationInternalTermNorms(full_stiffness, state.Displacement(), dofs);
const double external_norm =
IndexedNorm(state.ExternalForce(), dofs.FreeDofs());
FreeEquationInternalTermNorms(full_stiffness, displacement, dofs);
const double external_norm = IndexedNorm(external_force, dofs.FreeDofs());
// Normalize against the three terms of
// Kff*df + Kfc*dc - Ff. Using only the already-cancelled K*d term would
// classify prescribed-only equilibrium roundoff as a unit residual.
@@ -586,171 +615,68 @@ Status ResultRecovery::Recover(const AnalysisModel& model,
std::vector<EndpointResultRow> endpoint_rows;
std::vector<GaussResultRow> gauss_rows;
std::vector<StressS11Row> stress_rows;
endpoint_rows.reserve(model.ActiveBeamElements().size() * 2U);
gauss_rows.reserve(model.ActiveBeamElements().size() * 2U);
const Domain& domain = model.GetDomain();
for (const EntityIndex element_index : model.ActiveBeamElements()) {
const auto& definition = domain.BeamElements()[element_index];
auto beam = EulerBeam3D::Create(
domain.Nodes()[definition.node_indices[0U]],
domain.Nodes()[definition.node_indices[1U]],
domain.Sections()[definition.section_index],
domain.LinearElasticMaterials()[definition.material_index]);
if (!beam.HasValue()) {
return beam.GetStatus();
}
Vector element_displacement{kElementDofCount};
const auto& scatter = dofs.ElementScatter(element_index);
for (std::size_t local_dof = 0U; local_dof < kElementDofCount;
++local_dof) {
element_displacement[local_dof] =
state.Displacement()[scatter[local_dof]];
}
const BeamRecovery recovered =
beam.Value().RecoverBeam(element_displacement);
for (std::size_t endpoint = 0U; endpoint < 2U; ++endpoint) {
if (!IsFinite(recovered.equilibrium_end_actions[endpoint]) ||
!IsFinite(recovered.endpoint_section_resultants[endpoint])) {
return RecoveryFailure("nonfinite-recovery-value", definition.location,
definition.source_id.source_label_text,
"Endpoint recovery values must be finite.");
}
endpoint_rows.push_back(
{element_index, static_cast<int>(endpoint),
domain.Nodes()[definition.node_indices[endpoint]].source_id,
recovered.equilibrium_end_actions[endpoint],
recovered.endpoint_section_resultants[endpoint]});
}
for (std::size_t gauss = 0U; gauss < 2U; ++gauss) {
if (!IsFinite(recovered.gauss_generalized_strains[gauss]) ||
!IsFinite(recovered.gauss_generalized_resultants[gauss])) {
return RecoveryFailure("nonfinite-recovery-value", definition.location,
definition.source_id.source_label_text,
"Gauss recovery values must be finite.");
}
gauss_rows.push_back({element_index, static_cast<int>(gauss + 1U),
recovered.gauss_generalized_strains[gauss],
recovered.gauss_generalized_resultants[gauss]});
}
for (const auto& point : recovered.stress_points) {
if ((point.gauss_point != 1 && point.gauss_point != 2) ||
!std::isfinite(point.x1) || !std::isfinite(point.x2) ||
!std::isfinite(point.s11)) {
return RecoveryFailure(
"nonfinite-recovery-value", definition.location,
definition.source_id.source_label_text,
"Stress recovery identity and values must be finite and ordered.");
}
stress_rows.push_back({element_index, point.gauss_point,
point.section_point, point.x1, point.x2, point.s11,
point.source});
}
}
ShellStateCandidate shell_candidate{};
std::vector<EntityIndex> expected_shell_elements;
if (!domain.ShellElements().Empty()) {
if (domain.ShellElements().Size() >
(std::numeric_limits<std::size_t>::max)() / kShellLocationCount) {
bool has_beam_results = false;
bool has_shell_results = false;
for (std::size_t source_order = 0U; source_order < elements.size();
++source_order) {
const EntityIndex element_index = model.ActiveElements()[source_order];
const Element& element = elements[source_order].get();
auto scatter = dofs.ElementScatter(element.DofLayout());
if (!scatter.HasValue()) {
return scatter.GetStatus();
}
Vector element_displacement{scatter.Value().size()};
for (std::size_t local_dof = 0U; local_dof < scatter.Value().size();
++local_dof) {
element_displacement[local_dof] =
displacement[scatter.Value()[local_dof]];
}
auto recovered = element.Recover(element_displacement);
if (!recovered.HasValue()) {
return recovered.GetStatus();
}
if (!SameSourceIdentity(recovered.Value().source_id,
element.DofLayout().source_id)) {
return RecoveryFailure(
"invalid-recovery-dimensions", {domain.SourcePath(), 0U},
domain.SourceContentIdentity(),
"The shell result-row inventory exceeds the addressable range.");
}
std::vector<std::optional<std::array<double, 3>>> directors_by_node(
domain.Nodes().size());
for (const auto& frame : domain.ShellNodeInitialFrames()) {
if (frame.node_index >= directors_by_node.size() ||
directors_by_node[frame.node_index].has_value()) {
return RecoveryFailure(
"invalid-recovery-entity", {domain.SourcePath(), 0U},
std::to_string(frame.node_index),
"Shell initial directors must map uniquely to model nodes.");
}
directors_by_node[frame.node_index] = frame.director;
"invalid-element-result-identity", {domain.SourcePath(), 0U},
element.DofLayout().source_id.source_label_text,
"A runtime result bundle must retain its element source identity.");
}
shell_candidate.rows.reserve(domain.ShellElements().Size() *
kShellLocationCount);
expected_shell_elements.reserve(domain.ShellElements().Size());
constexpr std::array<ShellMidsurfaceLocation, kShellLocationCount>
locations{ShellMidsurfaceLocation::kGp1, ShellMidsurfaceLocation::kGp2,
ShellMidsurfaceLocation::kGp3, ShellMidsurfaceLocation::kGp4};
constexpr std::array<ShellSectionPosition, 3> positions{
ShellSectionPosition::kBottom, ShellSectionPosition::kMiddle,
ShellSectionPosition::kTop};
constexpr std::array<double, 3> zeta{-1.0, 0.0, 1.0};
for (std::size_t element_order = 0U;
element_order < domain.ShellElements().Size(); ++element_order) {
const EntityIndex element_index = static_cast<EntityIndex>(element_order);
const auto& definition = domain.ShellElements()[element_order];
std::array<const Node*, 4> nodes{};
std::array<std::array<double, 3>, 4> directors{};
for (std::size_t node_position = 0U;
node_position < definition.node_indices.size(); ++node_position) {
const EntityIndex node = definition.node_indices[node_position];
if (!directors_by_node[node].has_value()) {
return RecoveryFailure(
"invalid-recovery-entity", definition.location,
definition.source_id.source_label_text,
"Shell recovery requires one initial director per element node.");
}
nodes[node_position] = &domain.Nodes()[node];
directors[node_position] = *directors_by_node[node];
}
auto shell = Mitc4Shell::Create(
nodes, directors, domain.ShellSections()[definition.section_index],
domain.LinearElasticMaterials()[definition.material_index]);
if (!shell.HasValue()) {
return shell.GetStatus();
}
Vector element_displacement{kShellElementDofCount};
const auto& scatter = dofs.ShellElementScatter(element_index);
for (std::size_t local_dof = 0U; local_dof < kShellElementDofCount;
++local_dof) {
element_displacement[local_dof] =
state.Displacement()[scatter[local_dof]];
}
auto recovered = shell.Value().RecoverPhysical(element_displacement);
if (!recovered.HasValue()) {
return recovered.GetStatus();
}
const double accumulated_energy = shell_candidate.physical_strain_energy +
recovered.Value().strain_energy;
if (!std::isfinite(accumulated_energy)) {
return RecoveryFailure(
"nonfinite-recovery-value", definition.location,
definition.source_id.source_label_text,
"Source-order physical shell energy reduction must remain finite.");
}
shell_candidate.physical_strain_energy = accumulated_energy;
expected_shell_elements.push_back(element_index);
for (std::size_t point = 0U; point < recovered.Value().points.size();
++point) {
const auto& physical_point = recovered.Value().points[point];
ShellResultRow row{};
row.element = element_index;
row.location = locations[point];
row.natural_coordinates = physical_point.natural_coordinates;
row.local_frame = {physical_point.local_frame.e1,
physical_point.local_frame.e2,
physical_point.local_frame.e3};
row.generalized_strain = physical_point.generalized_strain;
row.section_resultant = physical_point.section_resultant;
for (std::size_t position = 0U; position < positions.size();
++position) {
row.stress[position] = {positions[position], zeta[position],
physical_point.in_plane_stress[position]};
}
shell_candidate.rows.push_back(std::move(row));
}
const Status aggregation_status = std::visit(
[&](const auto& payload) -> Status {
using Payload = std::decay_t<decltype(payload)>;
if constexpr (std::is_same_v<Payload, BeamElementResultRows>) {
has_beam_results = true;
return AppendBeamRows(
{domain.SourcePath(), 0U}, recovered.Value().source_id,
element_index, payload, endpoint_rows, gauss_rows, stress_rows);
} else {
has_shell_results = true;
return AppendShellRows({domain.SourcePath(), 0U},
recovered.Value().source_id, element_index,
payload, expected_shell_elements,
shell_candidate);
}
},
recovered.Value().payload);
if (!aggregation_status.IsOk()) {
return aggregation_status;
}
const Status evidence_status = PopulateShellGlobalEvidence(
domain, dofs, state.ExternalForce(), residual, normalized_residual,
shell_candidate);
}
if (has_beam_results && has_shell_results) {
return RecoveryFailure(
"unsupported-mixed-element-model", {domain.SourcePath(), 0U},
"mixed-runtime-results",
"Result recovery does not support mixed beam and shell models.");
}
if (has_shell_results) {
const Status evidence_status =
PopulateShellGlobalEvidence(domain, dofs, external_force, residual,
normalized_residual, shell_candidate);
if (!evidence_status.IsOk()) {
return evidence_status;
}
@@ -760,6 +686,8 @@ Status ResultRecovery::Recover(const AnalysisModel& model,
// prior full residual, beam rows, and shell rows if any later shell or
// candidate-inventory validation fails.
AnalysisState candidate_state = state;
candidate_state.Displacement() = displacement;
candidate_state.ExternalForce() = external_force;
candidate_state.InternalForce() = std::move(internal_force);
candidate_state.Residual() = std::move(residual);
candidate_state.Reaction() = std::move(reaction);
@@ -775,6 +703,34 @@ Status ResultRecovery::Recover(const AnalysisModel& model,
return Status::Ok();
}
Status ResultRecovery::Recover(const AnalysisModel& model,
const DofManager& dofs,
const SparseMatrix& full_stiffness,
AnalysisState& state) {
const Domain& domain = model.GetDomain();
std::vector<std::unique_ptr<Element>> owned_elements;
ElementView elements;
owned_elements.reserve(model.ActiveElements().size());
elements.reserve(model.ActiveElements().size());
const ElementFactory factory;
for (const EntityIndex element_index : model.ActiveElements()) {
if (element_index >= domain.Elements().Size()) {
return RecoveryFailure(
"invalid-recovery-entity", {domain.SourcePath(), 0U},
std::to_string(element_index),
"An active recovery element is outside the Domain.");
}
auto candidate = factory.Create(domain.Elements()[element_index], domain);
if (!candidate.HasValue()) {
return candidate.GetStatus();
}
owned_elements.push_back(std::move(candidate.Value()));
elements.push_back(std::cref(*owned_elements.back()));
}
return Recover(model, elements, dofs, full_stiffness, state.Displacement(),
state.ExternalForce(), state);
}
Result<std::vector<NodeStationResultRow>>
ResultRecovery::NormalizeSectionResultantsToNodeStations(
const AnalysisModel& model,
@@ -893,8 +849,13 @@ ResultRecovery::NormalizeSectionResultantsToNodeStations(
incident[0U]->endpoint != incident[1U]->endpoint &&
((incident[0U]->endpoint == 1 && incident[1U]->endpoint == 0) ||
(incident[0U]->endpoint == 0 && incident[1U]->endpoint == 1));
const auto first_axes = LocalAxes(domain, first_element);
const auto second_axes = LocalAxes(domain, second_element);
const auto first_axes = LocalAxes(
domain, first_element.node_indices[0U], first_element.node_indices[1U],
domain.Sections()[first_element.section_index].first_axis);
const auto second_axes =
LocalAxes(domain, second_element.node_indices[0U],
second_element.node_indices[1U],
domain.Sections()[second_element.section_index].first_axis);
if (!chain_orientation ||
first_element.section_index != second_element.section_index ||
!first_axes.has_value() || !second_axes.has_value() ||