완성된 HHT trial의 constraint 자료 중복 평가 제거
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
#include <algorithm>
|
||||
#include <cfenv>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
@@ -6,6 +7,10 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#if defined(_M_X64)
|
||||
#include <xmmintrin.h>
|
||||
#endif
|
||||
|
||||
#include "analysis/hht_dynamic_internal.h"
|
||||
#include "math/dense_blas_internal.h"
|
||||
#include "fesa/assembly/parallel_for.h"
|
||||
@@ -14,6 +19,29 @@
|
||||
#include "fesa/model/source_target_resolver.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.
|
||||
@@ -388,6 +416,10 @@ Result<DynamicAssembly> EvaluateAssembly(
|
||||
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]);
|
||||
@@ -397,12 +429,18 @@ Result<DynamicAssembly> EvaluateAssembly(
|
||||
trial.displacement[node] = Triple(q, 6U * node);
|
||||
trial.rotation_vector[node] = Triple(q, 6U * node + 3U);
|
||||
}
|
||||
const auto before = retain_constraints ? ReadTrialEvaluationContext()
|
||||
: TrialEvaluationContext{};
|
||||
auto f = EvaluateInternalResidualAndTangent(
|
||||
shells[e].internal, trial, directional_third ? &v : nullptr);
|
||||
if (!f.HasValue()) {
|
||||
failures[e] = f.GetStatus();
|
||||
return;
|
||||
}
|
||||
if (retain_constraints) {
|
||||
const auto after = ReadTrialEvaluationContext();
|
||||
if (ReusableTrialContext(before, after)) evaluation_contexts[e] = after;
|
||||
}
|
||||
auto m = shells[e].inertia.Evaluate(trial, v, a);
|
||||
if (!m.HasValue()) {
|
||||
failures[e] = m.GetStatus();
|
||||
@@ -605,13 +643,52 @@ Result<DynamicAssembly> EvaluateAssembly(
|
||||
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);
|
||||
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(hidden_second_scale), std::move(hidden_gradient),
|
||||
std::move(trial_constraints)});
|
||||
}
|
||||
|
||||
Status CompleteEquilibrium(const Domain& domain, const DofManager& dofs,
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#define FESA_ANALYSIS_HHT_DYNAMIC_INTERNAL_H_
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
@@ -10,6 +12,8 @@
|
||||
#include "fesa/elements/nonlinear_mitc4_inertia.h"
|
||||
#include "fesa/elements/nonlinear_mitc4_shell.h"
|
||||
#include "fesa/math/sparse_matrix.h"
|
||||
#include "elements/nonlinear_mitc4_configuration_internal.h"
|
||||
#include "math/dense_blas_internal.h"
|
||||
|
||||
namespace fesa {
|
||||
class LinearSolver;
|
||||
@@ -25,6 +29,49 @@ struct RuntimeShell {
|
||||
NonlinearMitc4Inertia inertia;
|
||||
};
|
||||
|
||||
/// @brief Captures only controls used by the current MSVC x64 local evaluator.
|
||||
struct TrialEvaluationContext {
|
||||
int rounding{-1};
|
||||
unsigned int mxcsr_control{0U};
|
||||
dense_blas_internal::LocalDensePolicy dense_policy{0, -1};
|
||||
bool supported{false};
|
||||
};
|
||||
|
||||
/// @brief Queries metadata without changing FP controls or the restored TLS.
|
||||
TrialEvaluationContext ReadTrialEvaluationContext() noexcept;
|
||||
|
||||
/// @brief Allows only masked, stable, single-worker nondynamic local policy.
|
||||
bool ReusableTrialContext(const TrialEvaluationContext& before,
|
||||
const TrialEvaluationContext& after) noexcept;
|
||||
|
||||
/// @brief Owns only complete directional constraint data for one local trial.
|
||||
struct DirectionalShellConstraintData {
|
||||
EntityIndex element_index{0U};
|
||||
std::array<std::size_t, 24> scatter{};
|
||||
std::array<SourceEntityId, 4> node_source_ids{};
|
||||
nonlinear_mitc4_internal::ConfigurationSnapshot configuration;
|
||||
std::array<double, 24> q{};
|
||||
std::array<double, 24> v{};
|
||||
TrialEvaluationContext evaluation_context;
|
||||
bool directional_internal{false};
|
||||
Vector drilling_constraint{0U};
|
||||
Matrix drilling_constraint_gradient{0U, 0U};
|
||||
std::array<Matrix, 4> drilling_constraint_hessian{
|
||||
Matrix{0U, 0U}, Matrix{0U, 0U}, Matrix{0U, 0U}, Matrix{0U, 0U}};
|
||||
std::array<Vector, 4> drilling_constraint_third_directional_gradient{
|
||||
Vector{0U}, Vector{0U}, Vector{0U}, Vector{0U}};
|
||||
};
|
||||
|
||||
/// @brief Travels with one complete assembly, never accepted frame/history.
|
||||
struct TrialConstraintData {
|
||||
std::filesystem::path source_path;
|
||||
double time{0.0};
|
||||
std::uint32_t frame_index{0U};
|
||||
std::uint32_t increment_index{0U};
|
||||
std::uint32_t iteration_count{0U};
|
||||
std::vector<DirectionalShellConstraintData> shells;
|
||||
};
|
||||
|
||||
/// @brief Stores a complete trial with independently assembled operators.
|
||||
struct DynamicAssembly {
|
||||
DynamicAcceptedFrameState frame;
|
||||
@@ -43,6 +90,7 @@ struct DynamicAssembly {
|
||||
Vector hidden_second_scale{0U};
|
||||
// Canonical scalar drilling-constraint derivative, indexed by global node.
|
||||
std::optional<SparseMatrix> hidden_gradient;
|
||||
std::optional<TrialConstraintData> trial_constraints;
|
||||
};
|
||||
|
||||
/// @brief Builds checked shell evaluators in stable source order.
|
||||
|
||||
@@ -60,6 +60,88 @@ Vector Local(const Vector& full,
|
||||
return local;
|
||||
}
|
||||
|
||||
/// @brief Preserves the exact floating representation, including signed zero.
|
||||
bool SameBits(const double left, const double right) {
|
||||
return std::memcmp(&left, &right, sizeof(double)) == 0;
|
||||
}
|
||||
|
||||
/// @brief Compares semantic and original source identity without object keys.
|
||||
bool SameSource(const SourceEntityId& left, const SourceEntityId& right) {
|
||||
return left.instance_name == right.instance_name &&
|
||||
left.source_label == right.source_label &&
|
||||
left.source_label_text == right.source_label_text;
|
||||
}
|
||||
|
||||
/// @brief Finds only the current complete directional response, otherwise miss.
|
||||
const hht_internal::DirectionalShellConstraintData* MatchingConstraints(
|
||||
const Domain& domain, const hht_internal::RuntimeShell& shell,
|
||||
const hht_internal::DynamicAssembly& assembly, const std::size_t index,
|
||||
const std::size_t shell_count, const Vector& q, const Vector& v) {
|
||||
if (assembly.frame.is_initial || !assembly.trial_constraints.has_value())
|
||||
return nullptr;
|
||||
const auto& owner = *assembly.trial_constraints;
|
||||
const auto& frame = assembly.frame;
|
||||
if (owner.shells.size() != shell_count || index >= owner.shells.size() ||
|
||||
owner.source_path != domain.SourcePath() ||
|
||||
!std::isfinite(owner.time) || !std::isfinite(frame.time) ||
|
||||
!SameBits(owner.time, frame.time) ||
|
||||
owner.frame_index != frame.frame_index ||
|
||||
owner.increment_index != frame.increment_index ||
|
||||
owner.iteration_count != frame.iteration_count)
|
||||
return nullptr;
|
||||
const auto& record = owner.shells[index];
|
||||
if (!record.directional_internal ||
|
||||
record.element_index != shell.element_index ||
|
||||
record.scatter != shell.scatter ||
|
||||
!hht_internal::ReusableTrialContext(
|
||||
record.evaluation_context,
|
||||
hht_internal::ReadTrialEvaluationContext()))
|
||||
return nullptr;
|
||||
const auto configuration =
|
||||
nonlinear_mitc4_internal::ConfigurationSnapshot::Read(shell.internal);
|
||||
if (record.configuration.source.file != configuration.source.file ||
|
||||
record.configuration.source.line != configuration.source.line ||
|
||||
record.configuration.identity != configuration.identity)
|
||||
return nullptr;
|
||||
for (std::size_t i = 0U; i < configuration.values.size(); ++i)
|
||||
if (!std::isfinite(record.configuration.values[i]) ||
|
||||
!std::isfinite(configuration.values[i]) ||
|
||||
!SameBits(record.configuration.values[i], configuration.values[i]))
|
||||
return nullptr;
|
||||
for (std::size_t i = 0U; i < 24U; ++i)
|
||||
if (!std::isfinite(record.q[i]) || !std::isfinite(q[i]) ||
|
||||
!std::isfinite(record.v[i]) || !std::isfinite(v[i]) ||
|
||||
!SameBits(record.q[i], q[i]) || !SameBits(record.v[i], v[i]))
|
||||
return nullptr;
|
||||
for (std::size_t node = 0U; node < 4U; ++node) {
|
||||
const auto global_node = shell.scatter[6U * node] / 6U;
|
||||
if (global_node >= domain.Nodes().size() ||
|
||||
!SameSource(record.node_source_ids[node],
|
||||
domain.Nodes()[global_node].source_id))
|
||||
return nullptr;
|
||||
}
|
||||
if (record.drilling_constraint.Size() != 4U ||
|
||||
record.drilling_constraint_gradient.Rows() != 4U ||
|
||||
record.drilling_constraint_gradient.Columns() != 24U)
|
||||
return nullptr;
|
||||
for (std::size_t node = 0U; node < 4U; ++node) {
|
||||
const auto& hessian = record.drilling_constraint_hessian[node];
|
||||
const auto& third =
|
||||
record.drilling_constraint_third_directional_gradient[node];
|
||||
if (hessian.Rows() != 24U || hessian.Columns() != 24U ||
|
||||
third.Size() != 24U || !std::isfinite(record.drilling_constraint[node]))
|
||||
return nullptr;
|
||||
for (std::size_t i = 0U; i < 24U; ++i) {
|
||||
if (!std::isfinite(record.drilling_constraint_gradient(node, i)) ||
|
||||
!std::isfinite(third[i]))
|
||||
return nullptr;
|
||||
for (std::size_t j = 0U; j < 24U; ++j)
|
||||
if (!std::isfinite(hessian(i, j))) return nullptr;
|
||||
}
|
||||
}
|
||||
return &record;
|
||||
}
|
||||
|
||||
NonlinearMitc4TrialDofs Trial(const Vector& local) {
|
||||
NonlinearMitc4TrialDofs trial{};
|
||||
for (std::size_t node = 0U; node < 4U; ++node) {
|
||||
@@ -443,32 +525,46 @@ Result<ConstraintOperators> EvaluateConstraintOperators(
|
||||
node_row[chart.null_nodes[row]] = row;
|
||||
}
|
||||
std::vector<bool> external_done(domain.Nodes().size(), false);
|
||||
std::size_t shell_index = 0U;
|
||||
for (const auto& shell : shells) {
|
||||
const auto q = Local(assembly.frame.generalized_coordinates, shell);
|
||||
const auto v = Local(assembly.frame.velocity, shell);
|
||||
const auto internal = EvaluateInternalResidualAndTangent(shell.internal,
|
||||
Trial(q), &v);
|
||||
if (!internal.HasValue())
|
||||
return Result<ConstraintOperators>::Failure(internal.GetStatus());
|
||||
const auto* saved = MatchingConstraints(domain, shell, assembly,
|
||||
shell_index++, shells.size(), q, v);
|
||||
std::optional<NonlinearMitc4InternalEquilibrium> fresh;
|
||||
if (saved == nullptr) {
|
||||
auto internal =
|
||||
EvaluateInternalResidualAndTangent(shell.internal, Trial(q), &v);
|
||||
if (!internal.HasValue())
|
||||
return Result<ConstraintOperators>::Failure(internal.GetStatus());
|
||||
fresh = std::move(internal.Value());
|
||||
}
|
||||
const auto& constraint = saved != nullptr ? saved->drilling_constraint
|
||||
: fresh->drilling_constraint;
|
||||
const auto& gradient_data = saved != nullptr
|
||||
? saved->drilling_constraint_gradient
|
||||
: fresh->drilling_constraint_gradient;
|
||||
const auto& third_data =
|
||||
saved != nullptr
|
||||
? saved->drilling_constraint_third_directional_gradient
|
||||
: fresh->drilling_constraint_third_directional_gradient;
|
||||
const auto& hessian_data = saved != nullptr
|
||||
? saved->drilling_constraint_hessian
|
||||
: fresh->drilling_constraint_hessian;
|
||||
for (std::size_t local_node = 0U; local_node < 4U; ++local_node) {
|
||||
const auto global_node = shell.scatter[6U * local_node] / 6U;
|
||||
if (global_node >= node_row.size() || node_row[global_node] == rows)
|
||||
continue;
|
||||
const auto row = node_row[global_node];
|
||||
result.rows[row].value +=
|
||||
internal.Value().drilling_constraint[local_node];
|
||||
result.rows[row].value += constraint[local_node];
|
||||
for (std::size_t i = 0U; i < 24U; ++i) {
|
||||
const auto global_i = shell.scatter[i];
|
||||
const double gradient =
|
||||
internal.Value().drilling_constraint_gradient(local_node, i);
|
||||
const double third = internal.Value()
|
||||
.drilling_constraint_third_directional_gradient
|
||||
[local_node][i];
|
||||
const double gradient = gradient_data(local_node, i);
|
||||
const double third = third_data[local_node][i];
|
||||
if (gradient != 0.0) gradients[row][global_i] += gradient;
|
||||
if (third != 0.0) third_gradients[row][global_i] += third;
|
||||
for (std::size_t j = 0U; j < 24U; ++j) {
|
||||
const double hessian =
|
||||
internal.Value().drilling_constraint_hessian[local_node](i, j);
|
||||
const double hessian = hessian_data[local_node](i, j);
|
||||
if (hessian != 0.0)
|
||||
hessians[row][{global_i, shell.scatter[j]}] += hessian;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user