#include #include #include #include #include #include #include #include #if defined(_M_X64) #include #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::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::quiet_NaN()); std::vector 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> BuildRuntimeShells(const Domain& domain, const DofManager& dofs) { std::vector shells; for (std::size_t index = 0U; index < domain.Elements().Size(); ++index) { const auto* definition = dynamic_cast(&domain.Elements()[index]); if (definition == nullptr || definition->section_index >= domain.Properties().Size() || definition->material_index >= domain.Materials().Size()) { return Result>::Failure( Failure("HHT requires valid MITC4 shell definitions.")); } const auto* section = dynamic_cast( &domain.Properties()[definition->section_index]); const auto* material = dynamic_cast( &domain.Materials()[definition->material_index]); if (section == nullptr || material == nullptr) { return Result>::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 nodes{}; std::array 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>::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>::Failure(internal.GetStatus()); auto inertia = NonlinearMitc4Inertia::Create(nodes, frames, *section, *material, density); if (!inertia.HasValue()) return Result>::Failure(inertia.GetStatus()); auto internal_kernel = std::make_unique(std::move(internal.Value())); auto inertia_kernel = std::make_unique(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(layout, *internal_kernel); auto kinetic = std::make_unique(layout, *inertia_kernel); auto mapped = dofs.ElementScatter(kinetic->DofLayout()); if (!mapped.HasValue()) return Result>::Failure(mapped.GetStatus()); std::array scatter{}; std::copy(mapped.Value().begin(), mapped.Value().end(), scatter.begin()); shells.push_back({static_cast(index), scatter, std::move(internal_kernel), std::move(inertia_kernel), std::move(trial), std::move(kinetic)}); } return Result>::Success(std::move(shells)); } Result 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::Failure( Failure("The dynamic reference length must be positive.")); return Result::Success(length); } Result> MeasureEndpointFields( const DofManager& dofs, const DynamicAcceptedFrameState& trial, const DynamicAcceptedFrameState& accepted, const std::optional& previous) { using Fields = std::array; 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::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::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::Success(std::move(fields)); } Result> AssembleLoads(const Domain& domain, const DofManager& dofs, std::array, 2>* field_flux) { const auto index = SourceTargetIndex::FromDomain(domain); const SourceTargetResolver resolver(index); const LoadContext context{domain, dofs, resolver}; std::array 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>::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>::Failure( Failure("Dynamic load reduction overflowed.")); } } return Result>::Success(std::move(result)); } Result MakeSparse(const std::size_t rows, const std::size_t columns, std::vector contributions) { std::vector> row_columns(rows); for (const auto& entry : contributions) { if (entry.row >= rows || entry.column >= columns) return Result::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 ScaledFree(const SparseMatrix& matrix, const DofManager& dofs, const double length) { std::vector 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 EvaluateNewtonStart( const Domain& domain, const DofManager& dofs, const std::vector& 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 { frame.generalized_coordinates = coordinates; auto rates = EvaluateHhtRates(coordinates, predictor, old_acceleration, time_increment, parameters); if (!rates.HasValue()) return Result::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::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::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::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 EvaluateAssembly( const Domain& domain, const DofManager& dofs, const std::vector& 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{ &frame.generalized_coordinates, &frame.velocity, &frame.acceleration, &frame.algorithmic_acceleration, &frame.external_force, &load_scale}) { if (values->Size() != count) return Result::Failure( Failure("Dynamic trial vector size is invalid.")); for (std::size_t i = 0U; i < count; ++i) if (!std::isfinite((*values)[i])) return Result::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 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::Failure(rotation.GetStatus()); const Matrix r = ExpSkew(phi); frame.nodal_internal_order.push_back(static_cast(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> internal( shells.size()); std::vector> inertia( shells.size()); std::vector> failures(shells.size()); const bool retain_constraints = directional_third && !frame.is_initial && std::isfinite(frame.time); std::vector 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::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, double>> constraint_hessians(domain.Nodes().size()); std::vector> constraint_gradients(domain.Nodes().size()); std::vector external_done(domain.Nodes().size(), false); Vector hidden_constraint(directional_third ? domain.Nodes().size() : 0U); Vector third(count); Vector third_scale(count); std::array, 6> entries; std::array, 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::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::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::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::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::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 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::Failure( Failure("Dynamic force reduction overflowed.")); } } for (const auto i : dofs.ConstrainedDofs()) frame.reaction[i] = frame.residual[i]; std::array residual_norm{}; std::array 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::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 hidden_gradient; if (directional_third) { std::vector 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::Failure(gradient.GetStatus()); hidden_gradient = std::move(gradient.Value()); } std::optional 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::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(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