987 lines
42 KiB
C++
987 lines
42 KiB
C++
#include "fesa/analysis/nonlinear_static_procedure.h"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <limits>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "fesa/analysis/analysis_model.h"
|
|
#include "fesa/analysis/newton_iteration_log.h"
|
|
#include "fesa/analysis/nonlinear_static_control.h"
|
|
#include "fesa/assembly/parallel_for.h"
|
|
#include "fesa/assembly/surface_pressure_assembler.h"
|
|
#include "fesa/constraints/essential_constraint_policy.h"
|
|
#include "fesa/elements/finite_rotation.h"
|
|
#include "fesa/elements/mitc4_shell.h"
|
|
#include "fesa/elements/nonlinear_mitc4_shell.h"
|
|
#include "fesa/fem/dof_manager.h"
|
|
#include "fesa/math/sparse_matrix.h"
|
|
#include "fesa/properties/shell_section.h"
|
|
#include "fesa/solvers/linear/tangent_solver.h"
|
|
|
|
namespace fesa {
|
|
namespace {
|
|
|
|
constexpr std::size_t kDofsPerNode = 6U;
|
|
constexpr std::size_t kTranslationsPerNode = 3U;
|
|
constexpr std::size_t kShellDofCount = 24U;
|
|
constexpr std::uint32_t kMaximumNewtonIterations = 100U;
|
|
|
|
struct RuntimeShell {
|
|
EntityIndex element_index;
|
|
std::array<std::size_t, kShellDofCount> scatter;
|
|
NonlinearMitc4Shell element;
|
|
};
|
|
|
|
struct InternalAssembly {
|
|
Vector force;
|
|
SparseMatrix tangent;
|
|
std::vector<ShellResultRow> shell_rows;
|
|
double physical_strain_energy;
|
|
};
|
|
|
|
Status ProcedureFailure(const FailureCategory category, const std::string& code,
|
|
const SourceLocation& location,
|
|
const std::string& keyword,
|
|
const std::string& identity,
|
|
const std::string& message) {
|
|
return Status::Failure(category, {{Severity::kError, code, location, keyword,
|
|
identity, message}});
|
|
}
|
|
|
|
std::string DiagnosticCode(const Status& status) {
|
|
if (status.Diagnostics().empty()) {
|
|
return "nonlinear-procedure-failure";
|
|
}
|
|
return status.Diagnostics().front().code;
|
|
}
|
|
|
|
std::string DiagnosticReason(const Status& status) {
|
|
if (status.Diagnostics().empty()) {
|
|
return "The nonlinear procedure returned no diagnostic.";
|
|
}
|
|
return status.Diagnostics().front().message;
|
|
}
|
|
|
|
NewtonChannelDecision UnevaluatedDecision(const std::string& identity,
|
|
const NewtonChannelUnit unit) {
|
|
return {identity,
|
|
0.0,
|
|
0.0,
|
|
NewtonConvergenceBranch::kZeroScale,
|
|
kNewtonZeroScaleThreshold,
|
|
unit,
|
|
false};
|
|
}
|
|
|
|
NewtonIterationLogRecord NewLogRecord(const StepDefinition& step,
|
|
const LoadFactorIncrement& increment,
|
|
const std::uint32_t iteration) {
|
|
return {step.Name(),
|
|
increment.increment_index,
|
|
increment.load_factor,
|
|
iteration,
|
|
NewtonIterationStage::kEntered,
|
|
{false, NewtonIterationStage::kEntered, {}, {}},
|
|
"entered",
|
|
"pending",
|
|
{},
|
|
UnevaluatedDecision("residual_force", NewtonChannelUnit::kForce),
|
|
UnevaluatedDecision("residual_moment",
|
|
NewtonChannelUnit::kForceLength),
|
|
UnevaluatedDecision("correction_translation",
|
|
NewtonChannelUnit::kLength),
|
|
UnevaluatedDecision("correction_rotation",
|
|
NewtonChannelUnit::kDimensionless),
|
|
false};
|
|
}
|
|
|
|
Status WriteAndFlush(NewtonIterationLogWriter& log,
|
|
const NewtonIterationLogRecord& record) {
|
|
const Status write_status = log.Write(record);
|
|
if (!write_status.IsOk()) {
|
|
return write_status;
|
|
}
|
|
return log.Flush();
|
|
}
|
|
|
|
Status RecordFailure(NewtonIterationLogWriter& log,
|
|
NewtonIterationLogRecord* record, const Status& failure,
|
|
const NewtonIterationStage stage) {
|
|
record->last_completed_stage = stage;
|
|
record->failure = {true, stage, DiagnosticCode(failure),
|
|
DiagnosticReason(failure)};
|
|
record->transition = "failed";
|
|
record->outcome = "failed";
|
|
record->diagnostic_code = DiagnosticCode(failure);
|
|
const Status log_status = WriteAndFlush(log, *record);
|
|
return log_status.IsOk() ? failure : log_status;
|
|
}
|
|
|
|
Matrix InitialFrame(const ShellNodeInitialFrame& frame) {
|
|
Matrix result{3U, 3U};
|
|
for (std::size_t row = 0U; row < kTranslationsPerNode; ++row) {
|
|
result(row, 0U) = frame.tangent_a[row];
|
|
result(row, 1U) = frame.tangent_b[row];
|
|
result(row, 2U) = frame.director[row];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Result<DofManager> BuildDofManager(const Domain& domain) {
|
|
Result<AnalysisModel> model = AnalysisModel::Create(domain);
|
|
if (!model.HasValue()) {
|
|
return Result<DofManager>::Failure(model.GetStatus());
|
|
}
|
|
return DofManager::Create(model.Value());
|
|
}
|
|
|
|
Result<NonlinearStateBaselineInput> BuildBaseline(const Domain& domain) {
|
|
if (domain.ShellNodeInitialFrames().size() != domain.Nodes().size()) {
|
|
return Result<NonlinearStateBaselineInput>::Failure(ProcedureFailure(
|
|
FailureCategory::kModel, "invalid-nonlinear-state",
|
|
{domain.SourcePath(), 0U},
|
|
"NODE", domain.SourceContentIdentity(),
|
|
"Nonlinear analysis requires one initial shell frame per node."));
|
|
}
|
|
|
|
NonlinearStateBaselineInput baseline;
|
|
baseline.node_baselines.reserve(domain.Nodes().size());
|
|
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
|
|
const ShellNodeInitialFrame& frame = domain.ShellNodeInitialFrames()[node];
|
|
if (frame.node_index != static_cast<EntityIndex>(node)) {
|
|
return Result<NonlinearStateBaselineInput>::Failure(ProcedureFailure(
|
|
FailureCategory::kModel, "invalid-nonlinear-state",
|
|
domain.Nodes()[node].location, "NODE",
|
|
domain.Nodes()[node].source_id.source_label_text,
|
|
"Initial shell frames must follow stable Domain node order."));
|
|
}
|
|
baseline.node_baselines.push_back(
|
|
{static_cast<EntityIndex>(node), domain.Nodes()[node].source_id,
|
|
{frame.tangent_a, frame.tangent_b, frame.director}, frame.director});
|
|
}
|
|
return Result<NonlinearStateBaselineInput>::Success(std::move(baseline));
|
|
}
|
|
|
|
Result<std::vector<RuntimeShell>> BuildRuntimeShells(const Domain& domain,
|
|
const DofManager& dofs) {
|
|
std::vector<RuntimeShell> shells;
|
|
shells.reserve(domain.Elements().Size());
|
|
for (std::size_t index = 0U; index < domain.Elements().Size(); ++index) {
|
|
const auto* definition =
|
|
dynamic_cast<const Mitc4ShellDefinition*>(&domain.Elements()[index]);
|
|
if (definition == nullptr) {
|
|
return Result<std::vector<RuntimeShell>>::Failure(ProcedureFailure(
|
|
FailureCategory::kModel, "unsupported-nonlinear-element",
|
|
{}, "ELEMENT",
|
|
domain.Elements()[index].SourceId().source_label_text,
|
|
"The nonlinear static procedure supports MITC4 shell elements "
|
|
"only."));
|
|
}
|
|
if (definition->section_index >= domain.Properties().Size() ||
|
|
definition->material_index >= domain.Materials().Size()) {
|
|
return Result<std::vector<RuntimeShell>>::Failure(ProcedureFailure(
|
|
FailureCategory::kModel, "invalid-nonlinear-static-data",
|
|
definition->location, "ELEMENT",
|
|
definition->source_id.source_label_text,
|
|
"The nonlinear shell element property or material index is "
|
|
"invalid."));
|
|
}
|
|
|
|
const auto* section =
|
|
dynamic_cast<const ShellSection*>(
|
|
&domain.Properties()[definition->section_index]);
|
|
const auto* material = dynamic_cast<const LinearElasticMaterial*>(
|
|
&domain.Materials()[definition->material_index]);
|
|
if (section == nullptr || material == nullptr) {
|
|
return Result<std::vector<RuntimeShell>>::Failure(ProcedureFailure(
|
|
FailureCategory::kModel, "incompatible-nonlinear-shell-property",
|
|
definition->location, "ELEMENT",
|
|
definition->source_id.source_label_text,
|
|
"A nonlinear MITC4 shell requires a shell section and linear "
|
|
"elastic material."));
|
|
}
|
|
|
|
std::array<const Node*, 4U> nodes{};
|
|
std::array<ShellNodeInitialFrame, 4U> frames{};
|
|
for (std::size_t node = 0U; node < definition->node_indices.size();
|
|
++node) {
|
|
const EntityIndex node_index = definition->node_indices[node];
|
|
if (node_index >= domain.Nodes().size() ||
|
|
node_index >= domain.ShellNodeInitialFrames().size()) {
|
|
return Result<std::vector<RuntimeShell>>::Failure(ProcedureFailure(
|
|
FailureCategory::kModel, "invalid-shell-director",
|
|
definition->location, "ELEMENT",
|
|
definition->source_id.source_label_text,
|
|
"A nonlinear shell node does not have a stable initial frame."));
|
|
}
|
|
nodes[node] = &domain.Nodes()[node_index];
|
|
frames[node] = domain.ShellNodeInitialFrames()[node_index];
|
|
if (frames[node].node_index != node_index) {
|
|
return Result<std::vector<RuntimeShell>>::Failure(ProcedureFailure(
|
|
FailureCategory::kModel, "invalid-shell-director",
|
|
definition->location, "ELEMENT",
|
|
definition->source_id.source_label_text,
|
|
"A nonlinear shell initial frame does not match its node "
|
|
"identity."));
|
|
}
|
|
}
|
|
|
|
Result<NonlinearMitc4Shell> shell =
|
|
NonlinearMitc4Shell::Create(nodes, frames, *section, *material);
|
|
if (!shell.HasValue()) {
|
|
return Result<std::vector<RuntimeShell>>::Failure(shell.GetStatus());
|
|
}
|
|
const EntityIndex element_index = static_cast<EntityIndex>(index);
|
|
shells.push_back(
|
|
{element_index, dofs.ShellElementScatter(element_index),
|
|
std::move(shell.Value())});
|
|
}
|
|
return Result<std::vector<RuntimeShell>>::Success(std::move(shells));
|
|
}
|
|
|
|
Status PopulateNodalKinematics(const Domain& domain,
|
|
NonlinearTrialState* trial) {
|
|
if (trial->generalized_coordinates.Size() !=
|
|
domain.Nodes().size() * kDofsPerNode) {
|
|
return ProcedureFailure(
|
|
FailureCategory::kSolver, "nonfinite-newton-data",
|
|
{domain.SourcePath(), 0U},
|
|
"NONLINEAR_STATIC_PROCEDURE", domain.SourceContentIdentity(),
|
|
"Nonlinear trial coordinates do not match the Domain node inventory.");
|
|
}
|
|
|
|
trial->displacement = trial->generalized_coordinates;
|
|
trial->rotation_vector = Vector{trial->generalized_coordinates.Size()};
|
|
trial->nodal_triad.clear();
|
|
trial->nodal_director.clear();
|
|
trial->nodal_triad.reserve(domain.Nodes().size());
|
|
trial->nodal_director.reserve(domain.Nodes().size());
|
|
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
|
|
const std::size_t first = node * kDofsPerNode;
|
|
const Vector3 phi{trial->generalized_coordinates[first + 3U],
|
|
trial->generalized_coordinates[first + 4U],
|
|
trial->generalized_coordinates[first + 5U]};
|
|
Result<FiniteRotationState> rotation = MakeFiniteRotationState(
|
|
phi, InitialFrame(domain.ShellNodeInitialFrames()[node]));
|
|
if (!rotation.HasValue()) {
|
|
return rotation.GetStatus();
|
|
}
|
|
for (std::size_t component = 0U; component < kTranslationsPerNode;
|
|
++component) {
|
|
trial->rotation_vector[first + 3U + component] = phi[component];
|
|
}
|
|
const Matrix& q = rotation.Value().q;
|
|
trial->nodal_triad.push_back(
|
|
{{{q(0U, 0U), q(1U, 0U), q(2U, 0U)},
|
|
{q(0U, 1U), q(1U, 1U), q(2U, 1U)},
|
|
{q(0U, 2U), q(1U, 2U), q(2U, 2U)}}});
|
|
trial->nodal_director.push_back(rotation.Value().director.Components());
|
|
}
|
|
return Status::Ok();
|
|
}
|
|
|
|
NonlinearMitc4TrialDofs ExtractElementTrial(
|
|
const RuntimeShell& shell, const Vector& coordinates) {
|
|
NonlinearMitc4TrialDofs trial{};
|
|
for (std::size_t node = 0U; node < 4U; ++node) {
|
|
const std::size_t first = node * kDofsPerNode;
|
|
trial.displacement[node] =
|
|
{coordinates[shell.scatter[first]],
|
|
coordinates[shell.scatter[first + 1U]],
|
|
coordinates[shell.scatter[first + 2U]]};
|
|
trial.rotation_vector[node] =
|
|
{coordinates[shell.scatter[first + 3U]],
|
|
coordinates[shell.scatter[first + 4U]],
|
|
coordinates[shell.scatter[first + 5U]]};
|
|
}
|
|
return trial;
|
|
}
|
|
|
|
bool IsFinite(const Vector& values) {
|
|
for (std::size_t index = 0U; index < values.Size(); ++index) {
|
|
if (!std::isfinite(values[index])) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IsFinite(const Matrix& values) {
|
|
for (std::size_t row = 0U; row < values.Rows(); ++row) {
|
|
for (std::size_t column = 0U; column < values.Columns(); ++column) {
|
|
if (!std::isfinite(values(row, column))) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Result<InternalAssembly> AssembleInternal(
|
|
const std::vector<RuntimeShell>& shells, const DofManager& dofs,
|
|
const Vector& coordinates, const ParallelFor& parallel_for) {
|
|
std::vector<std::optional<NonlinearMitc4InternalEquilibrium>> local(
|
|
shells.size());
|
|
std::vector<std::optional<Status>> failures(shells.size());
|
|
parallel_for.Execute(shells.size(), [&](const std::size_t index) {
|
|
Result<NonlinearMitc4InternalEquilibrium> result =
|
|
EvaluateInternalResidualAndTangent(shells[index].element,
|
|
ExtractElementTrial(shells[index],
|
|
coordinates));
|
|
if (!result.HasValue()) {
|
|
failures[index] = result.GetStatus();
|
|
return;
|
|
}
|
|
local[index] = std::move(result.Value());
|
|
});
|
|
|
|
Vector force{dofs.FullDofCount()};
|
|
std::vector<CooContribution> contributions;
|
|
contributions.reserve(shells.size() * kShellDofCount * kShellDofCount);
|
|
std::vector<ShellResultRow> rows;
|
|
rows.reserve(shells.size() * 4U);
|
|
double energy = 0.0;
|
|
for (std::size_t index = 0U; index < shells.size(); ++index) {
|
|
if (failures[index].has_value()) {
|
|
return Result<InternalAssembly>::Failure(*failures[index]);
|
|
}
|
|
if (!local[index].has_value() ||
|
|
!IsFinite(local[index]->internal_residual) ||
|
|
!IsFinite(local[index]->internal_tangent) ||
|
|
!std::isfinite(local[index]->physical_strain_energy)) {
|
|
return Result<InternalAssembly>::Failure(ProcedureFailure(
|
|
FailureCategory::kSolver, "nonfinite-newton-data", {},
|
|
"NONLINEAR_STATIC_PROCEDURE", std::to_string(index),
|
|
"A nonlinear shell internal residual or tangent is nonfinite."));
|
|
}
|
|
for (std::size_t row = 0U; row < kShellDofCount; ++row) {
|
|
force[shells[index].scatter[row]] += local[index]->internal_residual[row];
|
|
for (std::size_t column = 0U; column < kShellDofCount; ++column) {
|
|
contributions.push_back(
|
|
{shells[index].scatter[row], shells[index].scatter[column],
|
|
local[index]->internal_tangent(row, column), index,
|
|
row * kShellDofCount + column});
|
|
}
|
|
}
|
|
for (const NonlinearMitc4RecoveryPoint& point :
|
|
local[index]->recovery.gauss_points) {
|
|
rows.push_back({shells[index].element_index,
|
|
point.location,
|
|
point.natural_coordinates,
|
|
{point.local_frame.e1, point.local_frame.e2,
|
|
point.local_frame.e3},
|
|
point.generalized_strain,
|
|
point.section_resultant,
|
|
point.stress});
|
|
}
|
|
energy += local[index]->physical_strain_energy;
|
|
}
|
|
if (!IsFinite(force) || !std::isfinite(energy)) {
|
|
return Result<InternalAssembly>::Failure(ProcedureFailure(
|
|
FailureCategory::kSolver, "nonfinite-newton-data", {},
|
|
"NONLINEAR_STATIC_PROCEDURE", {},
|
|
"Global nonlinear internal force or energy is nonfinite."));
|
|
}
|
|
|
|
Result<SparseMatrix> tangent = SparseMatrix::FromCoo(
|
|
dofs.FullDofCount(), dofs.FullDofCount(), std::move(contributions),
|
|
dofs.GetSparsePattern());
|
|
if (!tangent.HasValue()) {
|
|
return Result<InternalAssembly>::Failure(tangent.GetStatus());
|
|
}
|
|
return Result<InternalAssembly>::Success(
|
|
{std::move(force), std::move(tangent.Value()), std::move(rows), energy});
|
|
}
|
|
|
|
Result<Vector> Difference(const Vector& left, const Vector& right) {
|
|
if (left.Size() != right.Size()) {
|
|
return Result<Vector>::Failure(ProcedureFailure(
|
|
FailureCategory::kSolver, "nonfinite-newton-data", {},
|
|
"NONLINEAR_STATIC_PROCEDURE", {},
|
|
"Nonlinear internal and external vectors must have equal dimensions."));
|
|
}
|
|
Vector result{left.Size()};
|
|
for (std::size_t index = 0U; index < result.Size(); ++index) {
|
|
result[index] = left[index] - right[index];
|
|
}
|
|
if (!IsFinite(result)) {
|
|
return Result<Vector>::Failure(ProcedureFailure(
|
|
FailureCategory::kSolver, "nonfinite-newton-data", {},
|
|
"NONLINEAR_STATIC_PROCEDURE", {},
|
|
"Nonlinear residual must remain finite."));
|
|
}
|
|
return Result<Vector>::Success(std::move(result));
|
|
}
|
|
|
|
Result<SparseMatrix> Difference(const SparseMatrix& internal,
|
|
const SparseMatrix& external,
|
|
const DofManager& dofs) {
|
|
if (internal.Rows() != external.Rows() ||
|
|
internal.Columns() != external.Columns() ||
|
|
internal.RowOffsets() != external.RowOffsets() ||
|
|
internal.ColumnIndices() != external.ColumnIndices()) {
|
|
return Result<SparseMatrix>::Failure(ProcedureFailure(
|
|
FailureCategory::kSolver, "nonfinite-newton-data", {},
|
|
"NONLINEAR_STATIC_PROCEDURE", {},
|
|
"Nonlinear internal and external tangent patterns must match."));
|
|
}
|
|
std::vector<CooContribution> contributions;
|
|
contributions.reserve(internal.Values().size());
|
|
for (std::size_t row = 0U; row < internal.Rows(); ++row) {
|
|
for (std::size_t offset = internal.RowOffsets()[row];
|
|
offset < internal.RowOffsets()[row + 1U]; ++offset) {
|
|
const double value =
|
|
internal.Values()[offset] - external.Values()[offset];
|
|
if (!std::isfinite(value)) {
|
|
return Result<SparseMatrix>::Failure(ProcedureFailure(
|
|
FailureCategory::kSolver, "nonfinite-newton-data", {},
|
|
"NONLINEAR_STATIC_PROCEDURE", {},
|
|
"Nonlinear tangent must remain finite."));
|
|
}
|
|
contributions.push_back(
|
|
{row, internal.ColumnIndices()[offset], value, row, offset});
|
|
}
|
|
}
|
|
return SparseMatrix::FromCoo(internal.Rows(), internal.Columns(),
|
|
std::move(contributions),
|
|
dofs.GetSparsePattern());
|
|
}
|
|
|
|
/// @brief Adds fixed and follower external candidates in stable CSR order.
|
|
Result<GlobalFixedLoadContribution> CombineExternalContributions(
|
|
const GlobalFixedLoadContribution& fixed,
|
|
const SurfacePressureAssemblyCandidate& pressure, const DofManager& dofs) {
|
|
if (fixed.generalized_external_force.Size() !=
|
|
pressure.full_external_force.Size() ||
|
|
fixed.generalized_external_tangent.Rows() !=
|
|
pressure.full_external_tangent.Rows() ||
|
|
fixed.generalized_external_tangent.Columns() !=
|
|
pressure.full_external_tangent.Columns() ||
|
|
fixed.generalized_external_tangent.RowOffsets() !=
|
|
pressure.full_external_tangent.RowOffsets() ||
|
|
fixed.generalized_external_tangent.ColumnIndices() !=
|
|
pressure.full_external_tangent.ColumnIndices()) {
|
|
return Result<GlobalFixedLoadContribution>::Failure(ProcedureFailure(
|
|
FailureCategory::kSolver, "nonfinite-newton-data", {},
|
|
"NONLINEAR_STATIC_PROCEDURE", {},
|
|
"Nonlinear fixed and pressure external candidates must share the "
|
|
"stable full-space dimension and pattern."));
|
|
}
|
|
|
|
Vector force{fixed.generalized_external_force.Size()};
|
|
for (std::size_t index = 0U; index < force.Size(); ++index) {
|
|
force[index] = fixed.generalized_external_force[index] +
|
|
pressure.full_external_force[index];
|
|
if (!std::isfinite(force[index])) {
|
|
return Result<GlobalFixedLoadContribution>::Failure(ProcedureFailure(
|
|
FailureCategory::kSolver, "nonfinite-newton-data", {},
|
|
"NONLINEAR_STATIC_PROCEDURE", std::to_string(index),
|
|
"Nonlinear fixed and pressure force composition is nonfinite."));
|
|
}
|
|
}
|
|
|
|
std::vector<CooContribution> contributions;
|
|
contributions.reserve(fixed.generalized_external_tangent.Values().size());
|
|
for (std::size_t row = 0U; row < fixed.generalized_external_tangent.Rows();
|
|
++row) {
|
|
for (std::size_t offset =
|
|
fixed.generalized_external_tangent.RowOffsets()[row];
|
|
offset < fixed.generalized_external_tangent.RowOffsets()[row + 1U];
|
|
++offset) {
|
|
const double tangent =
|
|
fixed.generalized_external_tangent.Values()[offset] +
|
|
pressure.full_external_tangent.Values()[offset];
|
|
if (!std::isfinite(tangent)) {
|
|
return Result<GlobalFixedLoadContribution>::Failure(ProcedureFailure(
|
|
FailureCategory::kSolver, "nonfinite-newton-data", {},
|
|
"NONLINEAR_STATIC_PROCEDURE", std::to_string(row),
|
|
"Nonlinear fixed and pressure tangent composition is nonfinite."));
|
|
}
|
|
contributions.push_back(
|
|
{row, fixed.generalized_external_tangent.ColumnIndices()[offset],
|
|
tangent, row, offset});
|
|
}
|
|
}
|
|
Result<SparseMatrix> tangent =
|
|
SparseMatrix::FromCoo(fixed.generalized_external_tangent.Rows(),
|
|
fixed.generalized_external_tangent.Columns(),
|
|
std::move(contributions), dofs.GetSparsePattern());
|
|
if (!tangent.HasValue()) {
|
|
return Result<GlobalFixedLoadContribution>::Failure(tangent.GetStatus());
|
|
}
|
|
|
|
std::vector<Diagnostic> diagnostics = fixed.diagnostics;
|
|
diagnostics.insert(diagnostics.end(), pressure.diagnostics.begin(),
|
|
pressure.diagnostics.end());
|
|
return Result<GlobalFixedLoadContribution>::Success(
|
|
{std::move(force), std::move(tangent.Value()), std::move(diagnostics)});
|
|
}
|
|
|
|
Vector SelectFreeComponents(const Vector& full, const DofManager& dofs,
|
|
const bool rotations) {
|
|
std::size_t count = 0U;
|
|
for (const std::size_t full_dof : dofs.FreeDofs()) {
|
|
if ((full_dof % kDofsPerNode >= kTranslationsPerNode) == rotations) {
|
|
++count;
|
|
}
|
|
}
|
|
Vector selected{count};
|
|
std::size_t output = 0U;
|
|
for (const std::size_t full_dof : dofs.FreeDofs()) {
|
|
if ((full_dof % kDofsPerNode >= kTranslationsPerNode) == rotations) {
|
|
selected[output++] = full[full_dof];
|
|
}
|
|
}
|
|
return selected;
|
|
}
|
|
|
|
Vector SelectCorrectionComponents(const Vector& correction,
|
|
const DofManager& dofs,
|
|
const bool rotations) {
|
|
std::size_t count = 0U;
|
|
for (const std::size_t full_dof : dofs.FreeDofs()) {
|
|
if ((full_dof % kDofsPerNode >= kTranslationsPerNode) == rotations) {
|
|
++count;
|
|
}
|
|
}
|
|
Vector selected{count};
|
|
std::size_t output = 0U;
|
|
for (std::size_t equation = 0U; equation < dofs.FreeDofs().size();
|
|
++equation) {
|
|
if ((dofs.FreeDofs()[equation] % kDofsPerNode >=
|
|
kTranslationsPerNode) == rotations) {
|
|
selected[output++] = correction[equation];
|
|
}
|
|
}
|
|
return selected;
|
|
}
|
|
|
|
Vector CurrentIncrementComponents(const Vector& current, const Vector& start,
|
|
const DofManager& dofs,
|
|
const bool rotations) {
|
|
Vector difference{current.Size()};
|
|
for (std::size_t index = 0U; index < difference.Size(); ++index) {
|
|
difference[index] = current[index] - start[index];
|
|
}
|
|
return SelectFreeComponents(difference, dofs, rotations);
|
|
}
|
|
|
|
Vector3 SpatialMoment(const Matrix& transform,
|
|
const std::array<double, 3>& generalized) {
|
|
return {transform(0U, 0U) * generalized[0U] +
|
|
transform(0U, 1U) * generalized[1U] +
|
|
transform(0U, 2U) * generalized[2U],
|
|
transform(1U, 0U) * generalized[0U] +
|
|
transform(1U, 1U) * generalized[1U] +
|
|
transform(1U, 2U) * generalized[2U],
|
|
transform(2U, 0U) * generalized[0U] +
|
|
transform(2U, 1U) * generalized[1U] +
|
|
transform(2U, 2U) * generalized[2U]};
|
|
}
|
|
|
|
/// @brief Computes accepted-state applied-plus-reaction balance evidence.
|
|
Result<std::array<double, 6>> GlobalEquilibrium(
|
|
const Domain& domain, const DofManager& dofs,
|
|
const NonlinearTrialState& trial) {
|
|
const std::size_t full_dof_count = domain.Nodes().size() * kDofsPerNode;
|
|
if (trial.displacement.Size() != full_dof_count ||
|
|
trial.rotation_vector.Size() != full_dof_count ||
|
|
trial.external_force.Size() != full_dof_count ||
|
|
trial.residual.Size() != full_dof_count) {
|
|
return Result<std::array<double, 6>>::Failure(ProcedureFailure(
|
|
FailureCategory::kSolver, "nonfinite-newton-data",
|
|
{domain.SourcePath(), 0U}, "NONLINEAR_STATIC_PROCEDURE",
|
|
domain.SourceContentIdentity(),
|
|
"Nonlinear equilibrium vectors do not match the Domain node inventory."));
|
|
}
|
|
|
|
std::vector<bool> constrained(full_dof_count, false);
|
|
for (const std::size_t full_dof : dofs.ConstrainedDofs()) {
|
|
if (full_dof >= constrained.size()) {
|
|
return Result<std::array<double, 6>>::Failure(ProcedureFailure(
|
|
FailureCategory::kSolver, "nonfinite-newton-data",
|
|
{domain.SourcePath(), 0U}, "NONLINEAR_STATIC_PROCEDURE",
|
|
domain.SourceContentIdentity(),
|
|
"Nonlinear constraint identity is outside the full DOF range."));
|
|
}
|
|
constrained[full_dof] = true;
|
|
}
|
|
|
|
Vector3 applied_force_resultant;
|
|
Vector3 applied_moment_resultant;
|
|
Vector3 reaction_force_resultant;
|
|
Vector3 reaction_moment_resultant;
|
|
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
|
|
const std::size_t first = node * kDofsPerNode;
|
|
const Vector3 position = Vector3{domain.Nodes()[node].coordinates} +
|
|
Vector3{trial.displacement[first],
|
|
trial.displacement[first + 1U],
|
|
trial.displacement[first + 2U]};
|
|
const Vector3 phi{trial.rotation_vector[first + 3U],
|
|
trial.rotation_vector[first + 4U],
|
|
trial.rotation_vector[first + 5U]};
|
|
const Vector3 applied_force{trial.external_force[first],
|
|
trial.external_force[first + 1U],
|
|
trial.external_force[first + 2U]};
|
|
const Vector3 reaction_force{
|
|
constrained[first] ? trial.residual[first] : 0.0,
|
|
constrained[first + 1U] ? trial.residual[first + 1U] : 0.0,
|
|
constrained[first + 2U] ? trial.residual[first + 2U] : 0.0};
|
|
const Matrix transform = InverseTransposeLeftJacobianSO3(phi);
|
|
const std::array<double, 3> applied_generalized{
|
|
trial.external_force[first + 3U], trial.external_force[first + 4U],
|
|
trial.external_force[first + 5U]};
|
|
const std::array<double, 3> reaction_generalized{
|
|
constrained[first + 3U] ? trial.residual[first + 3U] : 0.0,
|
|
constrained[first + 4U] ? trial.residual[first + 4U] : 0.0,
|
|
constrained[first + 5U] ? trial.residual[first + 5U] : 0.0};
|
|
const Vector3 applied_moment = SpatialMoment(transform, applied_generalized);
|
|
const Vector3 reaction_moment =
|
|
SpatialMoment(transform, reaction_generalized);
|
|
applied_force_resultant = applied_force_resultant + applied_force;
|
|
applied_moment_resultant =
|
|
applied_moment_resultant + position.Cross(applied_force) + applied_moment;
|
|
reaction_force_resultant = reaction_force_resultant + reaction_force;
|
|
reaction_moment_resultant = reaction_moment_resultant +
|
|
position.Cross(reaction_force) + reaction_moment;
|
|
}
|
|
const Vector3 force_balance =
|
|
applied_force_resultant + reaction_force_resultant;
|
|
const Vector3 moment_balance =
|
|
applied_moment_resultant + reaction_moment_resultant;
|
|
const std::array<double, 6> equilibrium{
|
|
force_balance.X(), force_balance.Y(), force_balance.Z(),
|
|
moment_balance.X(), moment_balance.Y(), moment_balance.Z()};
|
|
if (!std::all_of(equilibrium.begin(), equilibrium.end(),
|
|
[](const double value) { return std::isfinite(value); })) {
|
|
return Result<std::array<double, 6>>::Failure(ProcedureFailure(
|
|
FailureCategory::kSolver, "nonfinite-newton-data",
|
|
{domain.SourcePath(), 0U}, "NONLINEAR_STATIC_PROCEDURE",
|
|
domain.SourceContentIdentity(),
|
|
"Nonlinear global equilibrium balance is nonfinite."));
|
|
}
|
|
return Result<std::array<double, 6>>::Success(equilibrium);
|
|
}
|
|
|
|
Status ApplyIncrementConstraints(const StepDefinition& step,
|
|
const LoadFactorIncrement& increment,
|
|
const DofManager& dofs,
|
|
NonlinearTrialState* trial) {
|
|
Result<Vector> source_targets =
|
|
ApplyAbsolutePrescribedDisplacementTarget(step, increment.load_factor);
|
|
if (!source_targets.HasValue()) {
|
|
return source_targets.GetStatus();
|
|
}
|
|
for (std::size_t index = 0U; index < dofs.ConstrainedDofs().size();
|
|
++index) {
|
|
const double target =
|
|
increment.load_factor * dofs.PrescribedValues()[index];
|
|
if (!std::isfinite(target)) {
|
|
return ProcedureFailure(
|
|
FailureCategory::kSolver, "nonfinite-newton-data", step.Location(),
|
|
"BOUNDARY", step.Name(),
|
|
"A nonlinear prescribed displacement target is nonfinite.");
|
|
}
|
|
trial->generalized_coordinates[dofs.ConstrainedDofs()[index]] = target;
|
|
}
|
|
return Status::Ok();
|
|
}
|
|
|
|
AcceptedFrameState AcceptedFrame(const NonlinearAnalysisState& state,
|
|
const NonlinearTrialState& trial,
|
|
const LoadFactorIncrement& increment,
|
|
const std::uint32_t iteration) {
|
|
return {static_cast<std::uint32_t>(state.AcceptedFrames().size()),
|
|
increment.increment_index,
|
|
increment.load_factor,
|
|
iteration,
|
|
increment.is_factor_one,
|
|
trial.nodal_internal_order,
|
|
trial.nodal_source_ids,
|
|
trial.generalized_coordinates,
|
|
trial.displacement,
|
|
trial.rotation_vector,
|
|
trial.nodal_triad,
|
|
trial.nodal_director,
|
|
trial.internal_force,
|
|
trial.external_force,
|
|
trial.residual,
|
|
trial.reaction,
|
|
trial.shell_results,
|
|
trial.physical_strain_energy,
|
|
trial.global_equilibrium};
|
|
}
|
|
|
|
} // namespace
|
|
|
|
NonlinearStaticProcedureExecutor::NonlinearStaticProcedureExecutor(
|
|
const ParallelFor& parallel_for, TangentSolver& tangent_solver)
|
|
: parallel_for_(parallel_for), tangent_solver_(tangent_solver) {}
|
|
|
|
Result<NonlinearStaticExecutionResult>
|
|
NonlinearStaticProcedureExecutor::Execute(
|
|
const Domain& domain, NewtonIterationLogWriter& log) const {
|
|
if (domain.Steps().Size() != 1U || !domain.Steps()[0U].IsNonlinearStatic()) {
|
|
return Result<NonlinearStaticExecutionResult>::Failure(ProcedureFailure(
|
|
FailureCategory::kInput, "invalid-nonlinear-static-data",
|
|
{domain.SourcePath(), 0U}, "STATIC", domain.SourceContentIdentity(),
|
|
"Nonlinear procedure execution requires exactly one NLGEOM static "
|
|
"step."));
|
|
}
|
|
const StepDefinition& step = domain.Steps()[0U];
|
|
|
|
Result<DofManager> dofs = BuildDofManager(domain);
|
|
if (!dofs.HasValue()) {
|
|
return Result<NonlinearStaticExecutionResult>::Failure(dofs.GetStatus());
|
|
}
|
|
Result<AnalysisModel> analysis_model = AnalysisModel::Create(domain);
|
|
if (!analysis_model.HasValue()) {
|
|
return Result<NonlinearStaticExecutionResult>::Failure(
|
|
analysis_model.GetStatus());
|
|
}
|
|
Result<NonlinearStateBaselineInput> baseline = BuildBaseline(domain);
|
|
if (!baseline.HasValue()) {
|
|
return Result<NonlinearStaticExecutionResult>::Failure(
|
|
baseline.GetStatus());
|
|
}
|
|
Result<NonlinearAnalysisState> state =
|
|
NonlinearAnalysisState::Create(domain, dofs.Value(), baseline.Value());
|
|
if (!state.HasValue()) {
|
|
return Result<NonlinearStaticExecutionResult>::Failure(state.GetStatus());
|
|
}
|
|
Result<std::vector<RuntimeShell>> shells =
|
|
BuildRuntimeShells(domain, dofs.Value());
|
|
if (!shells.HasValue()) {
|
|
return Result<NonlinearStaticExecutionResult>::Failure(shells.GetStatus());
|
|
}
|
|
Result<std::vector<LoadFactorIncrement>> increments =
|
|
BuildFixedLoadFactorSequence(step);
|
|
if (!increments.HasValue()) {
|
|
return Result<NonlinearStaticExecutionResult>::Failure(
|
|
increments.GetStatus());
|
|
}
|
|
|
|
const EssentialConstraintPolicy constraints;
|
|
for (const LoadFactorIncrement& increment : increments.Value()) {
|
|
Result<NonlinearTrialState> trial = state.Value().BeginTrialFromCommitted();
|
|
if (!trial.HasValue()) {
|
|
return Result<NonlinearStaticExecutionResult>::Failure(trial.GetStatus());
|
|
}
|
|
const Vector increment_start = trial.Value().generalized_coordinates;
|
|
Status constraint_status =
|
|
ApplyIncrementConstraints(step, increment, dofs.Value(),
|
|
&trial.Value());
|
|
if (!constraint_status.IsOk()) {
|
|
return Result<NonlinearStaticExecutionResult>::Failure(constraint_status);
|
|
}
|
|
|
|
for (std::uint32_t iteration = 1U; iteration <= kMaximumNewtonIterations;
|
|
++iteration) {
|
|
NewtonIterationLogRecord record =
|
|
NewLogRecord(step, increment, iteration);
|
|
trial.Value().increment_index = increment.increment_index;
|
|
trial.Value().load_factor = increment.load_factor;
|
|
trial.Value().is_factor_zero_baseline = false;
|
|
|
|
Status kinematics = PopulateNodalKinematics(domain, &trial.Value());
|
|
if (!kinematics.IsOk()) {
|
|
return Result<NonlinearStaticExecutionResult>::Failure(
|
|
RecordFailure(log, &record, kinematics,
|
|
NewtonIterationStage::kKinematicsEvaluated));
|
|
}
|
|
record.last_completed_stage = NewtonIterationStage::kKinematicsEvaluated;
|
|
|
|
Result<InternalAssembly> internal = AssembleInternal(
|
|
shells.Value(), dofs.Value(), trial.Value().generalized_coordinates,
|
|
parallel_for_);
|
|
if (!internal.HasValue()) {
|
|
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
|
|
log, &record, internal.GetStatus(),
|
|
NewtonIterationStage::kInternalAssembled));
|
|
}
|
|
record.last_completed_stage = NewtonIterationStage::kInternalAssembled;
|
|
|
|
Result<GlobalFixedLoadContribution> fixed =
|
|
AssembleGlobalFixedLoadContribution(
|
|
domain, dofs.Value(), trial.Value().generalized_coordinates,
|
|
increment.load_factor);
|
|
if (!fixed.HasValue()) {
|
|
return Result<NonlinearStaticExecutionResult>::Failure(
|
|
RecordFailure(log, &record, fixed.GetStatus(),
|
|
NewtonIterationStage::kExternalAssembled));
|
|
}
|
|
Result<SurfacePressureAssemblyCandidate> pressure =
|
|
AssembleSurfacePressure(
|
|
analysis_model.Value(), dofs.Value(),
|
|
trial.Value().generalized_coordinates, increment.load_factor,
|
|
SurfacePressureConfiguration::kCurrent, parallel_for_);
|
|
if (!pressure.HasValue()) {
|
|
return Result<NonlinearStaticExecutionResult>::Failure(
|
|
RecordFailure(log, &record, pressure.GetStatus(),
|
|
NewtonIterationStage::kExternalAssembled));
|
|
}
|
|
Result<GlobalFixedLoadContribution> external =
|
|
CombineExternalContributions(fixed.Value(), pressure.Value(),
|
|
dofs.Value());
|
|
if (!external.HasValue()) {
|
|
return Result<NonlinearStaticExecutionResult>::Failure(
|
|
RecordFailure(log, &record, external.GetStatus(),
|
|
NewtonIterationStage::kExternalAssembled));
|
|
}
|
|
record.last_completed_stage = NewtonIterationStage::kExternalAssembled;
|
|
|
|
Result<Vector> residual =
|
|
Difference(internal.Value().force,
|
|
external.Value().generalized_external_force);
|
|
if (!residual.HasValue()) {
|
|
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
|
|
log, &record, residual.GetStatus(),
|
|
NewtonIterationStage::kExternalAssembled));
|
|
}
|
|
Result<SparseMatrix> tangent =
|
|
Difference(internal.Value().tangent,
|
|
external.Value().generalized_external_tangent,
|
|
dofs.Value());
|
|
if (!tangent.HasValue()) {
|
|
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
|
|
log, &record, tangent.GetStatus(),
|
|
NewtonIterationStage::kExternalAssembled));
|
|
}
|
|
trial.Value().internal_force = std::move(internal.Value().force);
|
|
trial.Value().external_force =
|
|
std::move(external.Value().generalized_external_force);
|
|
trial.Value().residual = std::move(residual.Value());
|
|
trial.Value().reaction = trial.Value().residual;
|
|
trial.Value().shell_results = std::move(internal.Value().shell_rows);
|
|
trial.Value().physical_strain_energy =
|
|
internal.Value().physical_strain_energy;
|
|
Result<std::array<double, 6>> global_equilibrium =
|
|
GlobalEquilibrium(domain, dofs.Value(), trial.Value());
|
|
if (!global_equilibrium.HasValue()) {
|
|
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
|
|
log, &record, global_equilibrium.GetStatus(),
|
|
NewtonIterationStage::kExternalAssembled));
|
|
}
|
|
trial.Value().global_equilibrium = global_equilibrium.Value();
|
|
|
|
Result<PartitionedStiffness> partition =
|
|
constraints.Partition(tangent.Value(), dofs.Value());
|
|
if (!partition.HasValue()) {
|
|
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
|
|
log, &record, partition.GetStatus(),
|
|
NewtonIterationStage::kExternalAssembled));
|
|
}
|
|
const Status factorization = tangent_solver_.FactorizeFresh(
|
|
partition.Value().k_ff);
|
|
if (!factorization.IsOk()) {
|
|
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
|
|
log, &record, factorization,
|
|
NewtonIterationStage::kSolvedCorrection));
|
|
}
|
|
|
|
Vector correction_rhs = constraints.GatherFree(trial.Value().residual,
|
|
dofs.Value());
|
|
for (std::size_t index = 0U; index < correction_rhs.Size(); ++index) {
|
|
correction_rhs[index] = -correction_rhs[index];
|
|
}
|
|
Vector correction{dofs.Value().FreeDofCount()};
|
|
const Status solve =
|
|
tangent_solver_.SolveCorrection(correction_rhs, correction);
|
|
if (!solve.IsOk()) {
|
|
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
|
|
log, &record, solve, NewtonIterationStage::kSolvedCorrection));
|
|
}
|
|
record.last_completed_stage = NewtonIterationStage::kSolvedCorrection;
|
|
|
|
const NewtonConvergenceResult convergence = EvaluateNewtonConvergence({
|
|
{"residual_force", SelectFreeComponents(trial.Value().residual,
|
|
dofs.Value(), false),
|
|
SelectFreeComponents(trial.Value().internal_force, dofs.Value(),
|
|
false),
|
|
SelectFreeComponents(trial.Value().external_force, dofs.Value(),
|
|
false)},
|
|
{"residual_moment", SelectFreeComponents(trial.Value().residual,
|
|
dofs.Value(), true),
|
|
SelectFreeComponents(trial.Value().internal_force, dofs.Value(),
|
|
true),
|
|
SelectFreeComponents(trial.Value().external_force, dofs.Value(),
|
|
true)},
|
|
{"correction_translation",
|
|
SelectCorrectionComponents(correction, dofs.Value(), false),
|
|
CurrentIncrementComponents(trial.Value().generalized_coordinates,
|
|
increment_start, dofs.Value(), false)},
|
|
{"correction_rotation",
|
|
SelectCorrectionComponents(correction, dofs.Value(), true),
|
|
CurrentIncrementComponents(trial.Value().generalized_coordinates,
|
|
increment_start, dofs.Value(), true)}});
|
|
record.force_residual = convergence.force_residual;
|
|
record.moment_residual = convergence.moment_residual;
|
|
record.translation_correction = convergence.translation_correction;
|
|
record.rotation_correction = convergence.rotation_correction;
|
|
record.combined_passed = convergence.combined_passed;
|
|
record.last_completed_stage = NewtonIterationStage::kConvergenceEvaluated;
|
|
|
|
if (convergence.combined_passed) {
|
|
const Status committed = state.Value().CommitAcceptedFrame(
|
|
AcceptedFrame(state.Value(), trial.Value(), increment, iteration));
|
|
if (!committed.IsOk()) {
|
|
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
|
|
log, &record, committed, NewtonIterationStage::kAccepted));
|
|
}
|
|
record.last_completed_stage = NewtonIterationStage::kAccepted;
|
|
record.transition = "accepted";
|
|
record.outcome = "accepted";
|
|
const Status log_status = WriteAndFlush(log, record);
|
|
if (!log_status.IsOk()) {
|
|
return Result<NonlinearStaticExecutionResult>::Failure(log_status);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (iteration == kMaximumNewtonIterations) {
|
|
const Status failure = ProcedureFailure(
|
|
FailureCategory::kSolver, "nonlinear-nonconvergence",
|
|
step.Location(), "STATIC", step.Name(),
|
|
"The nonlinear procedure did not converge within 100 iterations.");
|
|
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
|
|
log, &record, failure,
|
|
NewtonIterationStage::kConvergenceEvaluated));
|
|
}
|
|
|
|
for (std::size_t equation = 0U; equation < dofs.Value().FreeDofs().size();
|
|
++equation) {
|
|
const std::size_t full_dof = dofs.Value().FreeDofs()[equation];
|
|
trial.Value().generalized_coordinates[full_dof] += correction[equation];
|
|
if (!std::isfinite(trial.Value().generalized_coordinates[full_dof])) {
|
|
const Status failure = ProcedureFailure(
|
|
FailureCategory::kSolver, "nonfinite-newton-data", {},
|
|
"NONLINEAR_STATIC_PROCEDURE", std::to_string(full_dof),
|
|
"A nonlinear free-coordinate correction is nonfinite.");
|
|
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
|
|
log, &record, failure, NewtonIterationStage::kSolvedCorrection));
|
|
}
|
|
}
|
|
record.transition = "continue";
|
|
record.outcome = "not-converged";
|
|
const Status log_status = WriteAndFlush(log, record);
|
|
if (!log_status.IsOk()) {
|
|
return Result<NonlinearStaticExecutionResult>::Failure(log_status);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<Diagnostic> diagnostics = domain.Warnings();
|
|
SortDiagnostics(diagnostics);
|
|
return Result<NonlinearStaticExecutionResult>::Success(
|
|
{std::move(state.Value()), std::move(diagnostics)});
|
|
}
|
|
|
|
} // namespace fesa
|