Files
FESADev/src/fesa/analysis/hht_dynamic_assembly.cpp
T

828 lines
38 KiB
C++

#include <algorithm>
#include <cfenv>
#include <cmath>
#include <limits>
#include <map>
#include <optional>
#include <string>
#include <utility>
#if defined(_M_X64)
#include <xmmintrin.h>
#endif
#include "analysis/hht_dynamic_internal.h"
#include "analysis/hht_development_probe.h"
#include "math/dense_blas_internal.h"
#include "fesa/assembly/parallel_for.h"
#include "fesa/elements/finite_rotation.h"
#include "fesa/loads/load.h"
#include "fesa/model/source_target_resolver.h"
#include "results/mitc4_drilling_evidence.h"
namespace fesa::hht_internal {
TrialEvaluationContext ReadTrialEvaluationContext() noexcept {
TrialEvaluationContext result{};
#if defined(_M_X64)
result.rounding = std::fegetround();
result.mxcsr_control = _mm_getcsr() & 0xFFC0U;
result.dense_policy = dense_blas_internal::ReadScopedLocalDensePolicy();
result.supported = result.rounding >= 0;
#endif
return result;
}
bool ReusableTrialContext(const TrialEvaluationContext& before,
const TrialEvaluationContext& after) noexcept {
return before.supported && after.supported &&
before.rounding == after.rounding &&
before.mxcsr_control == after.mxcsr_control &&
(before.mxcsr_control & 0x1F80U) == 0x1F80U &&
before.dense_policy.blas_max_threads == 1 &&
after.dense_policy.blas_max_threads == 1 &&
before.dense_policy.dynamic == 0 && after.dense_policy.dynamic == 0;
}
namespace {
/// @brief Reports a dynamic assembly or equilibrium failure.
Status Failure(const std::string& message) {
return Status::Failure(FailureCategory::kSolver, {{Severity::kError,
"dynamic-invalid-state",
{},
"DYNAMIC",
{},
message}});
}
/// @brief Uses exact zero semantics for a dimensionally consistent ratio.
double Ratio(const double numerator, const double denominator) {
return denominator > 0.0 ? numerator / denominator
: numerator == 0.0 ? 0.0
: (std::numeric_limits<double>::infinity)();
}
/// @brief Gathers one source-node Cartesian component triple.
Vector3 Triple(const Vector& vector, const std::size_t first) {
return {vector[first], vector[first + 1U], vector[first + 2U]};
}
/// @brief Computes one matrix-vector spatial transform.
Vector3 Transform(const Matrix& matrix, const Vector3& vector) {
return {matrix(0U, 0U) * vector[0U] + matrix(0U, 1U) * vector[1U] +
matrix(0U, 2U) * vector[2U],
matrix(1U, 0U) * vector[0U] + matrix(1U, 1U) * vector[1U] +
matrix(1U, 2U) * vector[2U],
matrix(2U, 0U) * vector[0U] + matrix(2U, 1U) * vector[1U] +
matrix(2U, 2U) * vector[2U]};
}
/// @brief Gathers a local 24-vector in the DOF-owner scatter order.
Vector Gather(const Vector& full, const RuntimeShell& shell) {
Vector local(24U);
for (std::size_t i = 0U; i < 24U; ++i) local[i] = full[shell.scatter[i]];
return local;
}
/// @brief Preserves rejected entries while batching finite products in MKL.
Vector EntryProducts(const Vector& left, const Vector& right) {
Vector result(left.Size(), std::numeric_limits<double>::quiet_NaN());
std::vector<std::size_t> positions;
positions.reserve(left.Size());
for (std::size_t i = 0U; i < left.Size(); ++i)
if (std::isfinite(left[i]) && std::isfinite(right[i])) positions.push_back(i);
Vector valid_left{positions.size()};
Vector valid_right{positions.size()};
for (std::size_t i = 0U; i < positions.size(); ++i) {
valid_left[i] = left[positions[i]];
valid_right[i] = right[positions[i]];
}
const dense_blas_internal::ScopedLocalDenseThreads local_threads{};
const Vector products =
dense_blas_internal::MultiplyDiagonal(valid_left, valid_right);
for (std::size_t i = 0U; i < positions.size(); ++i)
result[positions[i]] = products[i];
return result;
}
} // namespace
Result<std::vector<RuntimeShell>> BuildRuntimeShells(const Domain& domain,
const DofManager& dofs) {
std::vector<RuntimeShell> shells;
for (std::size_t index = 0U; index < domain.Elements().Size(); ++index) {
const auto* definition =
dynamic_cast<const Mitc4ShellDefinition*>(&domain.Elements()[index]);
if (definition == nullptr ||
definition->section_index >= domain.Properties().Size() ||
definition->material_index >= domain.Materials().Size()) {
return Result<std::vector<RuntimeShell>>::Failure(
Failure("HHT requires valid MITC4 shell definitions."));
}
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(
Failure("HHT requires homogeneous elastic shell properties."));
}
double density = 0.0;
for (const auto& record : domain.Densities()) {
if (record.material_index == definition->material_index)
density = record.rho;
}
std::array<const Node*, 4> nodes{};
std::array<ShellNodeInitialFrame, 4> frames{};
for (std::size_t local = 0U; local < 4U; ++local) {
const auto node = definition->node_indices[local];
if (node >= domain.Nodes().size() ||
node >= domain.ShellNodeInitialFrames().size()) {
return Result<std::vector<RuntimeShell>>::Failure(
Failure("A dynamic shell node has no initial frame."));
}
nodes[local] = &domain.Nodes()[node];
frames[local] = domain.ShellNodeInitialFrames()[node];
}
auto internal =
NonlinearMitc4Shell::Create(nodes, frames, *section, *material);
if (!internal.HasValue())
return Result<std::vector<RuntimeShell>>::Failure(internal.GetStatus());
auto inertia = NonlinearMitc4Inertia::Create(nodes, frames, *section,
*material, density);
if (!inertia.HasValue())
return Result<std::vector<RuntimeShell>>::Failure(inertia.GetStatus());
auto internal_kernel =
std::make_unique<NonlinearMitc4Shell>(std::move(internal.Value()));
auto inertia_kernel =
std::make_unique<NonlinearMitc4Inertia>(std::move(inertia.Value()));
ElementDofLayout layout{
definition->SourceId(), definition->NodeIndices(),
{DofComponent::kUx, DofComponent::kUy, DofComponent::kUz,
DofComponent::kUrx, DofComponent::kUry, DofComponent::kUrz}};
auto trial = std::make_unique<Mitc4TrialAdapter>(layout, *internal_kernel);
auto kinetic = std::make_unique<Mitc4InertiaAdapter>(layout, *inertia_kernel);
auto mapped = dofs.ElementScatter(kinetic->DofLayout());
if (!mapped.HasValue())
return Result<std::vector<RuntimeShell>>::Failure(mapped.GetStatus());
std::array<std::size_t, 24> scatter{};
std::copy(mapped.Value().begin(), mapped.Value().end(), scatter.begin());
shells.push_back({static_cast<EntityIndex>(index), scatter,
std::move(internal_kernel), std::move(inertia_kernel),
std::move(trial), std::move(kinetic)});
}
return Result<std::vector<RuntimeShell>>::Success(std::move(shells));
}
Result<double> ReferenceLength(const Domain& domain) {
double length = 0.0;
for (std::size_t i = 0U; i < domain.ShellElements().Size(); ++i) {
const auto& element = domain.ShellElements()[i];
for (std::size_t node = 0U; node < 4U; ++node) {
const Vector3 a(domain.Nodes()[element.node_indices[node]].coordinates);
const Vector3 b(
domain.Nodes()[element.node_indices[(node + 1U) % 4U]].coordinates);
length = (std::max)(length, (a - b).Norm());
}
}
if (!std::isfinite(length) || length <= 0.0)
return Result<double>::Failure(
Failure("The dynamic reference length must be positive."));
return Result<double>::Success(length);
}
Result<std::array<hht_field_convergence::FieldInput, 2>> MeasureEndpointFields(
const DofManager& dofs, const DynamicAcceptedFrameState& trial,
const DynamicAcceptedFrameState& accepted, const std::optional<Vector>& previous) {
using Fields = std::array<hht_field_convergence::FieldInput, 2>;
const auto count = dofs.FullDofCount();
if (trial.generalized_coordinates.Size() != count || trial.residual.Size() != count ||
accepted.generalized_coordinates.Size() != count || (previous && previous->Size() != count))
return Result<Fields>::Failure(Failure("Invalid full endpoint field inventory."));
Fields fields;
for (auto& field : fields) if (previous) field.correction = 0.0;
for (const auto full : dofs.FreeDofs()) {
auto& field = fields[full % 6U < 3U ? 0U : 1U];
const double residual = std::abs(trial.residual[full]);
const double increment = std::abs(trial.generalized_coordinates[full] - accepted.generalized_coordinates[full]);
const double correction = previous ? std::abs(trial.generalized_coordinates[full] - (*previous)[full]) : 0.0;
if (!std::isfinite(residual) || !std::isfinite(increment) || !std::isfinite(correction))
return Result<Fields>::Failure(Failure("Nonfinite full endpoint field arithmetic."));
field.active = true;
field.residual = (std::max)(field.residual, residual);
field.increment = (std::max)(field.increment, increment);
if (previous) field.correction = (std::max)(*field.correction, correction);
}
return Result<Fields>::Success(std::move(fields));
}
Result<std::array<Vector, 2>> AssembleLoads(const Domain& domain,
const DofManager& dofs,
std::array<std::vector<hht_field_convergence::FluxOccurrence>, 2>* field_flux) {
const auto index = SourceTargetIndex::FromDomain(domain);
const SourceTargetResolver resolver(index);
const LoadContext context{domain, dofs, resolver};
std::array<Vector, 2> result{Vector(dofs.FullDofCount()),
Vector(dofs.FullDofCount())};
for (const auto& load : domain.Steps()[0U].Loads()) {
const auto contributions = load.get().ComputeContributions(context);
if (!contributions.HasValue())
return Result<std::array<Vector, 2>>::Failure(contributions.GetStatus());
for (const auto& entry : contributions.Value()) {
if (field_flux != nullptr)
(*field_flux)[entry.full_dof_index % 6U < 3U ? 0U : 1U].push_back(
{entry.value, 0.0});
result[0U][entry.full_dof_index] += entry.value;
result[1U][entry.full_dof_index] += std::abs(entry.value);
if (!std::isfinite(result[0U][entry.full_dof_index]) ||
!std::isfinite(result[1U][entry.full_dof_index]))
return Result<std::array<Vector, 2>>::Failure(
Failure("Dynamic load reduction overflowed."));
}
}
return Result<std::array<Vector, 2>>::Success(std::move(result));
}
Result<SparseMatrix> MakeSparse(const std::size_t rows,
const std::size_t columns,
std::vector<CooContribution> contributions) {
std::vector<std::vector<std::size_t>> row_columns(rows);
for (const auto& entry : contributions) {
if (entry.row >= rows || entry.column >= columns)
return Result<SparseMatrix>::Failure(
Failure("Projected sparse index is invalid."));
row_columns[entry.row].push_back(entry.column);
}
SparsePattern pattern;
pattern.row_offsets.push_back(0U);
for (auto& row : row_columns) {
std::sort(row.begin(), row.end());
row.erase(std::unique(row.begin(), row.end()), row.end());
pattern.column_indices.insert(pattern.column_indices.end(), row.begin(),
row.end());
pattern.row_offsets.push_back(pattern.column_indices.size());
}
return SparseMatrix::FromCoo(rows, columns, std::move(contributions),
pattern);
}
Result<SparseMatrix> ScaledFree(const SparseMatrix& matrix,
const DofManager& dofs, const double length) {
std::vector<CooContribution> entries;
for (std::size_t i = 0U; i < dofs.FreeDofCount(); ++i) {
const auto row = dofs.FreeDofs()[i];
for (auto offset = matrix.RowOffsets()[row];
offset < matrix.RowOffsets()[row + 1U]; ++offset) {
const auto column = matrix.ColumnIndices()[offset];
const auto free_column = dofs.FreeEquation(column);
if (free_column.has_value()) {
const double factor =
(row % 6U < 3U ? length : 1.0) * (column % 6U < 3U ? length : 1.0);
entries.push_back(
{i, *free_column, factor * matrix.Values()[offset], row, offset});
}
}
}
return MakeSparse(dofs.FreeDofCount(), dofs.FreeDofCount(),
std::move(entries));
}
Status CheckLinearSolve(const SparseMatrix& matrix, const Vector& rhs,
const Vector& solution) {
if (matrix.Rows() != rhs.Size() || matrix.Columns() != solution.Size())
return Failure("Dynamic linear solve dimensions differ.");
double numerator = 0.0;
double denominator = 0.0;
for (std::size_t row = 0U; row < matrix.Rows(); ++row) {
double residual = -rhs[row];
double scale = std::abs(rhs[row]);
for (auto offset = matrix.RowOffsets()[row];
offset < matrix.RowOffsets()[row + 1U]; ++offset) {
const double term =
matrix.Values()[offset] * solution[matrix.ColumnIndices()[offset]];
residual += term;
scale += std::abs(term);
}
if (!std::isfinite(residual) || !std::isfinite(scale))
return Failure("Dynamic linear solve returned nonfinite data.");
numerator = (std::max)(numerator, std::abs(residual));
denominator = (std::max)(denominator, scale);
}
if (Ratio(numerator, denominator) > 1e-12) {
return Status::Failure(FailureCategory::kSolver,
{{Severity::kError,
"dynamic-linear-solve",
{},
"DYNAMIC",
{},
"Dynamic linear backward error exceeds 1e-12."}});
}
return Status::Ok();
}
Result<DynamicAssembly> EvaluateNewtonStart(
const Domain& domain, const DofManager& dofs,
const std::vector<RuntimeShell>& shells, DynamicAcceptedFrameState frame,
const HhtPredictor& predictor, const Vector& old_acceleration,
const Vector& load_scale, const double time_increment,
const HhtParameters& parameters, const ParallelFor& parallel_for) {
const Vector accepted_coordinates = frame.generalized_coordinates;
auto evaluate = [&](const Vector& coordinates) -> Result<DynamicAssembly> {
frame.generalized_coordinates = coordinates;
auto rates = EvaluateHhtRates(coordinates, predictor, old_acceleration,
time_increment, parameters);
if (!rates.HasValue())
return Result<DynamicAssembly>::Failure(rates.GetStatus());
frame.velocity = std::move(rates.Value().velocity);
frame.acceleration = std::move(rates.Value().acceleration);
frame.algorithmic_acceleration =
std::move(rates.Value().algorithmic_acceleration);
frame.iteration_count = 0U;
return EvaluateAssembly(domain, dofs, shells, frame, load_scale,
time_increment, parameters, parallel_for, false);
};
auto start = evaluate(predictor.coordinates);
if (start.HasValue()) {
bool same_coordinates =
accepted_coordinates.Size() == predictor.coordinates.Size();
for (std::size_t i = 0U; same_coordinates && i < accepted_coordinates.Size();
++i)
same_coordinates = accepted_coordinates[i] == predictor.coordinates[i];
if (same_coordinates) return start;
auto accepted_start = evaluate(accepted_coordinates);
if (!accepted_start.HasValue()) return accepted_start;
const auto length = ReferenceLength(domain);
if (!length.HasValue())
return Result<DynamicAssembly>::Failure(length.GetStatus());
double predictor_residual = 0.0;
double accepted_residual = 0.0;
for (const auto full : dofs.FreeDofs()) {
const double scale = full % 6U < 3U ? length.Value() : 1.0;
predictor_residual =
std::max(predictor_residual,
scale * std::abs(start.Value().frame.residual[full]));
accepted_residual =
std::max(accepted_residual,
scale * std::abs(accepted_start.Value().frame.residual[full]));
}
if (!std::isfinite(predictor_residual) || !std::isfinite(accepted_residual))
return Result<DynamicAssembly>::Failure(
Failure("The scaled initial Newton residual must remain finite."));
// A geometrically valid large DIRECT predictor may still be far from the
// endpoint. Compare only these two initial trials, in common energy units.
return predictor_residual <= accepted_residual ? std::move(start)
: std::move(accepted_start);
}
if (start.GetStatus().Diagnostics().empty()) return start;
const auto& code = start.GetStatus().Diagnostics().front().code;
bool invalid_geometry = code == "invalid-nonlinear-shell-geometry" ||
code == "invalid-nonlinear-shell-director" ||
code == "invalid-nonlinear-inertia-geometry" ||
code == "invalid-nonlinear-inertia-director";
if (code == "invalid-dynamic-rotation") {
// This diagnostic also covers rate overflow. Only a finite coordinate
// outside the existing principal margin permits another initial geometry.
const double pi = std::acos(-1.0);
const double margin = std::sqrt(std::numeric_limits<double>::epsilon());
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
const std::size_t offset = 6U * node + 3U;
const auto phi = Triple(predictor.coordinates, offset);
const auto rotation = EvaluateDynamicRotation(
phi, Vector3(domain.ShellNodeInitialFrames()[node].director),
Triple(frame.velocity, offset), Triple(frame.acceleration, offset));
if (rotation.HasValue()) continue;
// Match assembly's first failing node; an unrelated later chart error
// must not turn an earlier valid-chart rate failure into a fallback.
const double angle = phi.Norm();
invalid_geometry =
phi.IsFinite() &&
(!std::isfinite(angle) || !(pi - angle > margin));
break;
}
}
if (!invalid_geometry) return start;
// This is initial-iterate selection only: neither a Newton correction nor
// the frozen HHT predictor is changed, and accepted history is never mutated.
return evaluate(accepted_coordinates);
}
Result<DynamicAssembly> EvaluateAssembly(
const Domain& domain, const DofManager& dofs,
const std::vector<RuntimeShell>& shells, DynamicAcceptedFrameState frame,
const Vector& load_scale, const double time_increment,
const HhtParameters& parameters, const ParallelFor& parallel_for,
const bool directional_third) {
FESA_HHT_PROBE_SCOPE(kAssembly);
const std::size_t count = dofs.FullDofCount();
for (const auto* values : std::array<const Vector*, 6>{
&frame.generalized_coordinates, &frame.velocity, &frame.acceleration,
&frame.algorithmic_acceleration, &frame.external_force,
&load_scale}) {
if (values->Size() != count)
return Result<DynamicAssembly>::Failure(
Failure("Dynamic trial vector size is invalid."));
for (std::size_t i = 0U; i < count; ++i)
if (!std::isfinite((*values)[i]))
return Result<DynamicAssembly>::Failure(
Failure("Dynamic trial contains nonfinite values."));
}
frame.nodal_internal_order.clear();
frame.nodal_source_ids.clear();
frame.nodal_triad.clear();
frame.nodal_director.clear();
frame.angular_velocity = Vector(3U * domain.Nodes().size());
frame.angular_acceleration = Vector(3U * domain.Nodes().size());
std::vector<DynamicRotationKinematics> rotations;
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
const auto phi = Triple(frame.generalized_coordinates, 6U * node + 3U);
const auto& baseline = domain.ShellNodeInitialFrames()[node];
auto rotation = EvaluateDynamicRotation(
phi, Vector3(baseline.director), Triple(frame.velocity, 6U * node + 3U),
Triple(frame.acceleration, 6U * node + 3U));
if (!rotation.HasValue())
return Result<DynamicAssembly>::Failure(rotation.GetStatus());
const Matrix r = ExpSkew(phi);
frame.nodal_internal_order.push_back(static_cast<EntityIndex>(node));
frame.nodal_source_ids.push_back(domain.Nodes()[node].source_id);
frame.nodal_director.push_back(rotation.Value().director.Components());
frame.nodal_triad.push_back(
{Transform(r, Vector3(baseline.tangent_a)).Components(),
Transform(r, Vector3(baseline.tangent_b)).Components(),
rotation.Value().director.Components()});
for (std::size_t c = 0U; c < 3U; ++c) {
frame.angular_velocity[3U * node + c] =
rotation.Value().angular_velocity[c];
frame.angular_acceleration[3U * node + c] =
rotation.Value().angular_acceleration[c];
}
rotations.push_back(std::move(rotation.Value()));
}
std::vector<std::optional<NonlinearMitc4InternalEquilibrium>> internal(
shells.size());
std::vector<std::optional<NonlinearMitc4InertiaEvaluation>> inertia(
shells.size());
std::vector<std::optional<Status>> failures(shells.size());
const bool retain_constraints =
directional_third && !frame.is_initial && std::isfinite(frame.time);
std::vector<TrialEvaluationContext> evaluation_contexts(
retain_constraints ? shells.size() : 0U);
parallel_for.Execute(shells.size(), [&](const std::size_t e) {
const auto q = Gather(frame.generalized_coordinates, shells[e]);
const auto v = Gather(frame.velocity, shells[e]);
const auto a = Gather(frame.acceleration, shells[e]);
NonlinearMitc4TrialDofs trial{};
for (std::size_t node = 0U; node < 4U; ++node) {
trial.displacement[node] = Triple(q, 6U * node);
trial.rotation_vector[node] = Triple(q, 6U * node + 3U);
}
const auto before = retain_constraints ? ReadTrialEvaluationContext()
: TrialEvaluationContext{};
FESA_HHT_PROBE_COUNT(kInternalEvaluation);
auto f = shells[e].internal->EvaluateTrial(
trial, directional_third ? &v : nullptr);
if (!f.HasValue()) {
FESA_HHT_PROBE_COUNT(kInternalFailure);
failures[e] = f.GetStatus();
return;
}
if (retain_constraints) {
const auto after = ReadTrialEvaluationContext();
if (ReusableTrialContext(before, after)) evaluation_contexts[e] = after;
}
FESA_HHT_PROBE_COUNT(kInertiaEvaluation);
auto m = shells[e].inertia->Evaluate(trial, v, a);
if (!m.HasValue()) {
failures[e] = m.GetStatus();
return;
}
internal[e] = std::move(f.Value());
inertia[e] = std::move(m.Value());
});
if (shells.empty())
return Result<DynamicAssembly>::Failure(Failure("HHT requires a shell kernel."));
frame.mitc4_drilling =
Mitc4DrillingEvidence{shells.front().internal_kernel->DrillingMode()};
frame.inertia_force = Vector(count);
frame.internal_force = Vector(count);
frame.residual = Vector(count);
frame.reaction = Vector(count);
frame.residual_scale = load_scale;
frame.shell_results.clear();
frame.kinetic_energy = frame.physical_strain_energy = frame.drilling_energy =
0.0;
frame.linear_momentum = {};
frame.angular_momentum = {};
frame.linear_momentum_rate = {};
frame.angular_momentum_rate = {};
std::vector<std::map<std::pair<std::size_t, std::size_t>, double>>
constraint_hessians(domain.Nodes().size());
std::vector<std::map<std::size_t, double>>
constraint_gradients(domain.Nodes().size());
std::vector<bool> external_done(domain.Nodes().size(), false);
Vector hidden_constraint(directional_third ? domain.Nodes().size() : 0U);
Vector third(count);
Vector third_scale(count);
std::array<std::vector<CooContribution>, 6> entries;
std::array<std::vector<hht_field_convergence::FluxOccurrence>, 2> field_flux;
for (auto& values : entries) values.reserve(shells.size() * 24U * 24U);
double momentum_scale = 0.0;
double angular_scale = 0.0;
const double a_coefficient =
1.0 / ((1.0 + parameters.alpha) * parameters.beta * time_increment *
time_increment);
const double v_coefficient =
parameters.gamma / (parameters.beta * time_increment);
for (std::size_t e = 0U; e < shells.size(); ++e) {
if (failures[e].has_value())
return Result<DynamicAssembly>::Failure(*failures[e]);
const auto& f = *internal[e];
const auto& m = *inertia[e];
if (shells[e].internal_kernel->DrillingMode() != frame.mitc4_drilling->mode ||
f.drilling_force.Size() != 24U)
return Result<DynamicAssembly>::Failure(Failure("Invalid actual drilling mode or force."));
for (std::size_t node = 0U; node < 4U; ++node) {
const auto local = 6U * node;
const auto global = shells[e].scatter[local];
const Vector3 force = Triple(f.drilling_force, local);
const auto spatial = SpatialMomentSO3(
Triple(frame.generalized_coordinates, global + 3U),
Triple(f.drilling_force, local + 3U));
if (!spatial.HasValue())
return Result<DynamicAssembly>::Failure(spatial.GetStatus());
const Vector3 position = Vector3(domain.Nodes()[global / 6U].coordinates) +
Triple(frame.generalized_coordinates, global);
const Vector3 moment = position.Cross(force) + spatial.Value();
for (std::size_t c = 0U; c < 3U; ++c)
frame.mitc4_drilling->drilling_moment[c] += moment[c];
}
for (const double value : frame.mitc4_drilling->drilling_moment)
if (!std::isfinite(value))
return Result<DynamicAssembly>::Failure(Failure("Drilling spatial moment overflowed."));
if (directional_third) {
for (std::size_t local = 0U; local < 4U; ++local) {
const auto node = shells[e].scatter[6U * local] / 6U;
hidden_constraint[node] += f.drilling_constraint[local];
auto& gradient = constraint_gradients[node];
auto& hessian = constraint_hessians[node];
for (std::size_t i = 0U; i < 24U; ++i)
gradient[shells[e].scatter[i]] +=
f.drilling_constraint_gradient(local, i);
for (std::size_t i = 0U; i < 24U; ++i)
for (std::size_t j = 0U; j < 24U; ++j) {
hessian[{shells[e].scatter[i], shells[e].scatter[j]}] +=
f.drilling_constraint_hessian[local](i, j);
}
if (!external_done[node]) {
external_done[node] = true;
const auto first = 6U * node + 3U;
const auto external =
shells[e].internal->EvaluateExternalDrillingConstraint(
local, Triple(frame.generalized_coordinates, first),
Triple(frame.velocity, first),
Triple(frame.external_force, first), {}, {});
if (!external.HasValue())
return Result<DynamicAssembly>::Failure(external.GetStatus());
hidden_constraint[node] += external.Value().value;
for (std::size_t i = 0U; i < 3U; ++i) {
gradient[first + i] += external.Value().gradient[i];
for (std::size_t j = 0U; j < 3U; ++j)
hessian[{first + i, first + j}] +=
external.Value().hessian(i, j);
}
}
}
}
const auto a = Gather(frame.acceleration, shells[e]);
Vector mass_values{24U * 24U};
Vector acceleration_values{24U * 24U};
for (std::size_t row = 0U; row < 24U; ++row)
for (std::size_t column = 0U; column < 24U; ++column) {
const auto index = 24U * row + column;
mass_values[index] = m.mass(row, column);
acceleration_values[index] = a[column];
}
const Vector mass_action = EntryProducts(mass_values, acceleration_values);
for (std::size_t row = 0U; row < 24U; ++row) {
const auto global = shells[e].scatter[row];
field_flux[row % 6U < 3U ? 0U : 1U].push_back(
{f.internal_force[row], m.force[row]});
frame.internal_force[global] += f.internal_force[row];
frame.inertia_force[global] += m.force[row];
third[global] += f.force_directional_second[row];
third_scale[global] += std::abs(f.force_directional_second[row]);
frame.residual_scale[global] += std::abs(m.convective_force[row]) +
std::abs(f.physical_force[row]) +
std::abs(f.drilling_force[row]);
for (std::size_t column = 0U; column < 24U; ++column) {
frame.residual_scale[global] +=
std::abs(mass_action[24U * row + column]);
const std::array<double, 6> values{
m.mass(row, column), m.position_tangent(row, column),
m.velocity_tangent(row, column), f.internal_tangent(row, column),
f.internal_tangent(row, column) + m.position_tangent(row, column) +
v_coefficient * m.velocity_tangent(row, column) +
a_coefficient * m.mass(row, column),
f.drilling_metric(row, column)};
for (std::size_t k = 0U; k < 6U; ++k)
entries[k].push_back({global, shells[e].scatter[column], values[k], e,
row * 24U + column});
}
}
for (const auto& point : f.recovery.gauss_points) {
frame.shell_results.push_back(
{shells[e].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});
}
frame.kinetic_energy += m.kinetic_energy;
frame.physical_strain_energy += f.physical_strain_energy;
frame.drilling_energy += f.drilling_energy;
momentum_scale += m.momentum_rate_scale;
angular_scale += m.angular_momentum_rate_scale;
for (std::size_t c = 0U; c < 3U; ++c) {
frame.linear_momentum[c] += m.momentum[c];
frame.angular_momentum[c] += m.angular_momentum[c];
frame.linear_momentum_rate[c] += m.momentum_rate[c];
frame.angular_momentum_rate[c] += m.angular_momentum_rate[c];
}
}
for (std::size_t i = 0U; i < count; ++i) {
frame.residual[i] = frame.inertia_force[i] + frame.internal_force[i] -
frame.external_force[i];
if (!std::isfinite(frame.residual[i]) ||
!std::isfinite(frame.residual_scale[i]) || !std::isfinite(third[i]) ||
!std::isfinite(third_scale[i])) {
return Result<DynamicAssembly>::Failure(
Failure("Dynamic force reduction overflowed."));
}
}
for (const auto i : dofs.ConstrainedDofs())
frame.reaction[i] = frame.residual[i];
std::array<double, 2> residual_norm{};
std::array<double, 2> scale_norm{};
for (const auto i : dofs.FreeDofs()) {
const auto channel = i % 6U < 3U ? 0U : 1U;
residual_norm[channel] =
(std::max)(residual_norm[channel], std::abs(frame.residual[i]));
scale_norm[channel] =
(std::max)(scale_norm[channel], frame.residual_scale[i]);
}
for (std::size_t c = 0U; c < 2U; ++c)
frame.free_residual_metrics[c] = Ratio(residual_norm[c], scale_norm[c]);
auto mass = SparseMatrix::FromCoo(count, count, std::move(entries[0U]),
dofs.GetSparsePattern());
auto position = SparseMatrix::FromCoo(count, count, std::move(entries[1U]),
dofs.GetSparsePattern());
auto velocity = SparseMatrix::FromCoo(count, count, std::move(entries[2U]),
dofs.GetSparsePattern());
auto stiffness = SparseMatrix::FromCoo(count, count, std::move(entries[3U]),
dofs.GetSparsePattern());
auto effective = SparseMatrix::FromCoo(count, count, std::move(entries[4U]),
dofs.GetSparsePattern());
auto metric = SparseMatrix::FromCoo(count, count, std::move(entries[5U]),
dofs.GetSparsePattern());
for (const auto* result : {&mass, &position, &velocity, &stiffness,
&effective, &metric})
if (!result->HasValue())
return Result<DynamicAssembly>::Failure(result->GetStatus());
Vector hidden_second(domain.Nodes().size());
Vector hidden_second_scale(domain.Nodes().size());
for (std::size_t node = 0U; node < constraint_hessians.size(); ++node) {
Vector coefficients{constraint_hessians[node].size()};
Vector first_velocity{coefficients.Size()};
Vector second_velocity{coefficients.Size()};
std::size_t index = 0U;
for (const auto& entry : constraint_hessians[node]) {
coefficients[index] = entry.second;
first_velocity[index] = frame.velocity[entry.first.first];
second_velocity[index] = frame.velocity[entry.first.second];
++index;
}
const Vector products = EntryProducts(
EntryProducts(coefficients, first_velocity), second_velocity);
for (std::size_t i = 0U; i < products.Size(); ++i) {
const double term = products[i];
hidden_second[node] += term;
hidden_second_scale[node] += std::abs(term);
}
}
std::optional<SparseMatrix> hidden_gradient;
if (directional_third) {
std::vector<CooContribution> gradient_entries;
for (std::size_t node = 0U; node < constraint_gradients.size(); ++node)
for (const auto& entry : constraint_gradients[node])
gradient_entries.push_back(
{node, entry.first, entry.second, node, gradient_entries.size()});
auto gradient = MakeSparse(domain.Nodes().size(), count,
std::move(gradient_entries));
if (!gradient.HasValue())
return Result<DynamicAssembly>::Failure(gradient.GetStatus());
hidden_gradient = std::move(gradient.Value());
}
std::optional<TrialConstraintData> trial_constraints;
if (retain_constraints) {
TrialConstraintData data{};
data.source_path = domain.SourcePath();
data.time = frame.time;
data.frame_index = frame.frame_index;
data.increment_index = frame.increment_index;
data.iteration_count = frame.iteration_count;
data.shells.resize(shells.size());
for (std::size_t e = 0U; e < shells.size(); ++e) {
if (!ReusableTrialContext(evaluation_contexts[e], evaluation_contexts[e]))
continue;
auto& record = data.shells[e];
record.element_index = shells[e].element_index;
record.scatter = shells[e].scatter;
record.configuration =
nonlinear_mitc4_internal::ConfigurationSnapshot::Read(
*shells[e].internal_kernel);
record.evaluation_context = evaluation_contexts[e];
for (std::size_t i = 0U; i < 24U; ++i) {
record.q[i] = frame.generalized_coordinates[shells[e].scatter[i]];
record.v[i] = frame.velocity[shells[e].scatter[i]];
}
for (std::size_t node = 0U; node < 4U; ++node)
record.node_source_ids[node] =
domain.Nodes()[shells[e].scatter[6U * node] / 6U].source_id;
auto& response = *internal[e];
record.drilling_constraint = std::move(response.drilling_constraint);
record.drilling_constraint_gradient =
std::move(response.drilling_constraint_gradient);
record.drilling_constraint_hessian =
std::move(response.drilling_constraint_hessian);
record.drilling_constraint_third_directional_gradient =
std::move(response.drilling_constraint_third_directional_gradient);
record.directional_internal = true;
}
trial_constraints = std::move(data);
}
return Result<DynamicAssembly>::Success(
{std::move(frame), std::move(mass.Value()), std::move(position.Value()),
std::move(velocity.Value()), std::move(stiffness.Value()),
std::move(effective.Value()), std::move(metric.Value()),
std::move(third), std::move(third_scale), std::move(rotations),
momentum_scale, angular_scale, std::move(hidden_second),
std::move(hidden_second_scale), std::move(hidden_gradient),
std::move(trial_constraints), std::move(field_flux),
std::move(hidden_constraint)});
}
Status CompleteEquilibrium(const Domain& domain, const DofManager& dofs,
const DynamicAssembly& assembly,
DynamicAcceptedFrameState* frame) {
static_cast<void>(dofs);
Vector3 total_force;
Vector3 total_moment;
double force_scale = assembly.momentum_rate_scale;
double moment_scale = assembly.angular_momentum_rate_scale;
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
const auto first = 6U * node;
const Vector3 arm = Vector3(domain.Nodes()[node].coordinates) +
Triple(frame->generalized_coordinates, first);
const Vector3 phi = Triple(frame->generalized_coordinates, first + 3U);
for (const Vector* values : {&frame->external_force, &frame->reaction}) {
const Vector3 force = Triple(*values, first);
const auto spatial = SpatialMomentSO3(phi, Triple(*values, first + 3U));
if (!spatial.HasValue())
return Failure("Dynamic physical force or moment balance exceeds 1e-8.");
const Vector3 moment = spatial.Value();
total_force = total_force + force;
total_moment = total_moment + arm.Cross(force) + moment;
force_scale += force.Norm();
moment_scale += arm.Cross(force).Norm() + moment.Norm();
}
}
const Vector3 force_error =
total_force - Vector3(frame->linear_momentum_rate);
const Vector3 moment_error =
total_moment - Vector3(frame->angular_momentum_rate);
for (std::size_t c = 0U; c < 3U; ++c) {
frame->global_equilibrium[c] = force_error[c];
frame->global_equilibrium[3U + c] = moment_error[c];
}
Vector3 acceptance_moment = moment_error;
if (frame->mitc4_drilling) {
results_internal::CompleteDrillingEvidence(frame->global_equilibrium,
&*frame->mitc4_drilling);
if (!results_internal::ValidDrillingEvidence(frame->mitc4_drilling,
frame->global_equilibrium))
return Failure("Invalid dynamic drilling equilibrium evidence.");
if (frame->mitc4_drilling->mode == Mitc4DrillingMode::kDirectSpring)
acceptance_moment = moment_error - Vector3(frame->mitc4_drilling->drilling_moment);
}
if (!force_error.IsFinite() || !moment_error.IsFinite() ||
!std::isfinite(force_scale) || !std::isfinite(moment_scale) ||
Ratio(force_error.Norm(), force_scale) > 1e-8 ||
Ratio(acceptance_moment.Norm(), moment_scale) > 1e-8)
return Failure("Dynamic physical force or moment balance exceeds 1e-8.");
return Status::Ok();
}
} // namespace fesa::hht_internal