완성된 HHT trial의 constraint 자료 중복 평가 제거

This commit is contained in:
KOKO\Mimi
2026-10-14 21:46:11 +09:00
parent 2850ceaa89
commit 0e5b3e05dd
11 changed files with 939 additions and 16 deletions
@@ -230,3 +230,60 @@ Source commit c397a147b0e885384da8dee81ef645b9d0b17adb에서 corrected-prefix cl
Release exe SHA7d3c48351a8b76dbc5274468fc9f205386d28521cde79633dcd970a861321143. Build/native/parent PATH, environment/identity 전후 및 timer facts는 parent constant-shell-native.json/development-constant-shell-before.json/development-constant-shell-after.json/development-constant-shell.json과 해당 run-1/native.json/run.json/stdout/solver log에 보존한다. 최종 HDF5가 없어서 reader/precheck와 blocking U comparator 및 UR warning 비교는 모두 미실행이다. 729node/101frame/schema2 또는 required reference PASS로 표시하지 않는다. 기존 원본이나 최종 H5를 수정하지 않고 현재 partial 파일과 timeout 자료를 보존했다. 추가 micro/profile/whole-run 반복은 하지 않았다.
다음 우선 후보는 profile에서 큰 EvaluateAssembly/EvaluateConstraintOperators 중복을 줄이는 exact true-trial internal constraint subset 재사용이다. False 위치/root 경로 및 external force/rate/acceleration primitive는 유지한다. 구체 context/q/v/direction/request/gate/lifetime 계획과 최소 private friend 범위를 먼저 검토한다. Node-local support6 AD는 support/비용 근거가 충분해진 경우의 비교 후보이며 현재 구현 범위를 확대하지 않는다.
## 완성된 true trial의 내부 constraint 자료 재사용
이 후보는 기존 SW profile의 Assembly/ConstraintOperators 중복 경로를 근거로 한다.
완성된 true assembly가 이미 계산한 constraint value4, gradient4×24, Hessian24×24
4개와 방향3차 gradient24 4개만 trial-owned optional에 보관한다. 기존 internal,
inertia, ordered external/recovery/finite/CSR/hidden-gradient gate가 모두 성공한
마지막 위치에서 기존 자료를 move한다. False와 initial frame은 저장하지 않는다.
Accepted frame, reduced history, HDF5에는 이 자료가 붙지 않아 증분마다 누적되는
구조가 아니다. 기존 Matrix/Vector deep-copy와 move 수명을 사용한다.
소비자는 기존 inventory/null/transverse/node-row gate 뒤의 원래 internal 평가
자리에서만 재사용을 확인한다. 구성64개 값과 source identity, element/scatter,
node IDs, q/v, time/frame/increment/iteration 및 directional 요청이 일치해야 한다.
현재/저장 q/v·구성·time은 finite 확인 뒤 representation 비교하여 ±0도 구별한다.
Worker 평가 전후와 소비 thread의 masked FP control 및 기존 scope 내부 BLAS1,
dynamic0이 안정적인 현재 Intel-thread 빌드에 한정한다. Private metadata getter는
전역 policy를 바꾸지 않고 기존 caller TLS를 원복한다. 불일치/비유한/불명확 key,
shape/payload 오류는 그 자리의 원래 full 평가와 Status로 돌아간다. 외부 force,
rate/acceleration primitive, 누적 순서, physics/gates/diagnostic은 그대로 수행한다.
Private element configuration header와 public header의 forward/private friend만
추가했다. Element가 analysis에 의존하지 않고 vendor 타입을 public에 노출하지 않는다.
Density는 내부 네 자료의 함수/energy 입력이 아니고 inertia는 매번 평가하므로 키에서
제외한다. 약13.59MiB는676 shell의 payload 일부 산술 추정이며 객체/allocator/source
문자열/추가 metadata 및 실제 peak 메모리 실측을 포함하는 상한이 아니다.
독립 source 검토는 최초 bit-only key가 동일 NaN/Inf current+saved key를 수용해
원래 실패를 생략할 수 있음을 발견했다(needs-fix). 해당 자료와 manifest를 보존한 뒤
finite key miss를 추가하여 재검토 PASS를 받았다. Source-derived 반례의 native RED는
미실행이며 실제 RED 관측이라고 기록하지 않는다. 최초 Debug build는 새 raw 자료
fixture의 Vector3 read-only component 대입 오류로 cmake native2/외부 PowerShell1,
13.2472474초에 실패했다. 원래 세 좌표를 생성자로 대입하는 test-only 수정으로
production hash를 유지했고 실패 로그/native/source 자료도 보존했다.
수정 source manifest C4EBD4169D44918E56DECDB0AB1D4AAFABDBA866612874C788DD883CF460C266에서
정식 Debug 재빌드7 edges native0/6.30초, 새 cache/getter6/6 PASS(native0,
CTest11.04초/native11.08초), 기존 reduced/initial Newton27/27 PASS(native0,
CTest6.40초/native6.43초)를 확인했다. 실제 CTest 합33개이며 기존 Constant의33-case
oracle와 다른 수다. Jet 수식/저장/provenance와 기존 element scope body는 바뀌지
않아 그 oracle는 이번 슬롯에서 반복하지 않았다. Raw 네 자료/forced-fresh의 strict
bits, deep copy/move, actual FP/TLS/dynamic miss, serial/TBB, 외부 rates, 동일
NaN/±Inf q/v key의 실패 진단 전필드와 비유한 time의 fresh 성공을 검사했다.
Corrected-prefix 실제 Ninja deps는 assembly41/reduced operators42/shell26이며
모두 새 private configuration header를 포함한다. Library SHA
5108045D88A2439E57934B8D66317DE1B545AFE122BFCF082D60DDB732D20DFE,
unit SHA CF6D180565FCFDF5AD19EEA26EF627A38C5C8603AE331607031411828700F8BD다.
Parent artifact의 true-trial-source-vector3.json/patch,
true-trial-vector3-debug-native.json/built-hashes.json 및 단계별 로그에 실제
source/compiler/argv/cwd/native/deps/thread/hook을 보존했다. Source10 hash 일치와
관련 프로세스0 확인 후 CPU를 반환했다.
현재 cache 후보의 Release/원본 shell 실행/실제 call 감소/비용 및 전체 Debug,
reference/C08/최종100초 합격은 미실행이다. 부분 실행 자료나 이전 source의 PASS를
이 후보의 전체 성능·물리 검증 완료로 바꾸지 않는다. 다음 bounded 개발 실행은
0warmup/1회만 수행하고 결과에 따라 다음 최소 후보를 검토한다.
@@ -15,6 +15,10 @@
namespace fesa {
namespace nonlinear_mitc4_internal {
struct ConfigurationSnapshot;
} // namespace nonlinear_mitc4_internal
/// @brief Stores one four-node nonlinear shell trial in global coordinates.
struct NonlinearMitc4TrialDofs {
std::array<Vector3, 4> displacement;
@@ -129,6 +133,8 @@ class NonlinearMitc4Shell {
const Vector3& force_acceleration) const;
private:
friend struct nonlinear_mitc4_internal::ConfigurationSnapshot;
/// @brief Retains validated fixed-reference data without analysis ownership.
NonlinearMitc4Shell(std::array<Vector3, 4> coordinates,
std::array<Matrix, 4> initial_frames,
+78 -1
View File
@@ -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,
+48
View File
@@ -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.
+109 -13
View File
@@ -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;
}
@@ -0,0 +1,24 @@
#ifndef FESA_SRC_FESA_ELEMENTS_NONLINEAR_MITC4_CONFIGURATION_INTERNAL_H_
#define FESA_SRC_FESA_ELEMENTS_NONLINEAR_MITC4_CONFIGURATION_INTERNAL_H_
#include <array>
#include <string>
#include "fesa/elements/nonlinear_mitc4_shell.h"
namespace fesa::nonlinear_mitc4_internal {
/// @brief Owns reference values without depending on an analysis or identity
/// key.
struct ConfigurationSnapshot {
std::array<double, 64> values{};
SourceLocation source{};
std::string identity;
/// @brief Copies all immutable inputs of the internal equilibrium evaluator.
static ConfigurationSnapshot Read(const NonlinearMitc4Shell& element);
};
} // namespace fesa::nonlinear_mitc4_internal
#endif // FESA_SRC_FESA_ELEMENTS_NONLINEAR_MITC4_CONFIGURATION_INTERNAL_H_
@@ -10,9 +10,34 @@
#include <utility>
#include "fesa/elements/finite_rotation.h"
#include "elements/nonlinear_mitc4_configuration_internal.h"
#include "math/dense_blas_internal.h"
namespace fesa {
nonlinear_mitc4_internal::ConfigurationSnapshot
nonlinear_mitc4_internal::ConfigurationSnapshot::Read(
const NonlinearMitc4Shell& element) {
ConfigurationSnapshot result{};
std::size_t index = 0U;
for (const auto& coordinates : element.coordinates_)
for (std::size_t c = 0U; c < 3U; ++c)
result.values[index++] = coordinates[c];
for (const auto& frame : element.initial_frames_)
for (std::size_t i = 0U; i < 3U; ++i)
for (std::size_t j = 0U; j < 3U; ++j)
result.values[index++] = frame(i, j);
for (const auto& director : element.initial_directors_)
for (std::size_t c = 0U; c < 3U; ++c) result.values[index++] = director[c];
result.values[index++] = element.thickness_;
result.values[index++] = element.youngs_modulus_;
result.values[index++] = element.poisson_ratio_;
result.values[index] = element.drilling_stiffness_;
result.source = element.source_location_;
result.identity = element.identity_;
return result;
}
namespace {
constexpr std::size_t kNodeCount = 4U;
+5
View File
@@ -40,6 +40,11 @@ ScopedLocalDenseThreads::~ScopedLocalDenseThreads() {
if (restore_default_) static_cast<void>(mkl_set_num_threads_local(0));
}
LocalDensePolicy ReadScopedLocalDensePolicy() noexcept {
const ScopedLocalDenseThreads scope{};
return {mkl_domain_get_max_threads(MKL_DOMAIN_BLAS), mkl_get_dynamic()};
}
bool CholeskyLowerAndInverse(const Matrix& matrix, Matrix& lower,
Matrix& inverse_lower, CholeskyFailure* failure) {
if (failure != nullptr) *failure = CholeskyFailure::kNone;
+12 -2
View File
@@ -1,10 +1,10 @@
#ifndef FESA_SRC_FESA_MATH_DENSE_BLAS_INTERNAL_H_
#define FESA_SRC_FESA_MATH_DENSE_BLAS_INTERNAL_H_
#include <mkl.h>
#include <cstddef>
#include <mkl.h>
#include "fesa/core/status.h"
#include "fesa/math/matrix.h"
#include "fesa/math/vector.h"
@@ -40,6 +40,16 @@ class ScopedLocalDenseThreads {
bool restore_default_{false};
};
/// @brief Records requested backend policy, without claiming actual threads.
struct LocalDensePolicy {
int blas_max_threads;
int dynamic;
};
/// @brief Reads BLAS policy inside the same restored scope as local elements.
/// @note No process-wide policy is changed; positive caller TLS is retained.
LocalDensePolicy ReadScopedLocalDensePolicy() noexcept;
/// @brief Converts a dense storage length to the private MKL integer type.
/// @return The converted length or an uncategorized overflow failure.
Result<MKL_INT> ToMklSize(std::size_t size);
@@ -5,6 +5,8 @@
#include <cmath>
#include <cfenv>
#include <cstddef>
#include <cstring>
#include <functional>
#include <iostream>
#include <memory>
#include <limits>
@@ -19,6 +21,7 @@
#endif
#include "analysis/hht_reduced_internal.h"
#include "math/dense_blas_internal.h"
#include "fesa/analysis/analysis_model.h"
#include "fesa/analysis/hht_time_integration.h"
#include "fesa/assembly/parallel_for.h"
@@ -26,6 +29,71 @@
namespace {
class RestoreTrialEnvironment {
public:
RestoreTrialEnvironment()
: rounding_(std::fegetround()),
dynamic_(mkl_get_dynamic()),
tls_(mkl_set_num_threads_local(0)) {
#if defined(_M_X64)
mxcsr_ = _mm_getcsr();
_mm_setcsr((_mm_getcsr() | 0x1F80U) & ~0x8040U);
#endif
static_cast<void>(std::fesetround(FE_TONEAREST));
mkl_set_dynamic(0);
}
~RestoreTrialEnvironment() {
mkl_set_dynamic(dynamic_);
static_cast<void>(mkl_set_num_threads_local(tls_));
static_cast<void>(std::fesetround(rounding_));
#if defined(_M_X64)
_mm_setcsr(mxcsr_);
#endif
}
private:
int rounding_;
int dynamic_;
int tls_;
#if defined(_M_X64)
unsigned int mxcsr_;
#endif
};
void ExpectSameBits(const double left, const double right) {
EXPECT_EQ(std::memcmp(&left, &right, sizeof(double)), 0);
}
void ExpectSameOperators(
const fesa::hht_reduced_internal::ConstraintOperators& left,
const fesa::hht_reduced_internal::ConstraintOperators& right) {
ASSERT_EQ(left.rows.size(), right.rows.size());
for (std::size_t row = 0U; row < left.rows.size(); ++row) {
const auto& a = left.rows[row];
const auto& b = right.rows[row];
ExpectSameBits(a.value, b.value);
ExpectSameBits(a.value_scale, b.value_scale);
ExpectSameBits(a.time_first, b.time_first);
ExpectSameBits(a.time_second, b.time_second);
for (const auto& pair : {std::make_pair(&a.gradient, &b.gradient),
std::make_pair(&a.time_gradient, &b.time_gradient),
std::make_pair(&a.third_directional_gradient,
&b.third_directional_gradient)}) {
ASSERT_EQ(pair.first->size(), pair.second->size());
for (std::size_t i = 0U; i < pair.first->size(); ++i) {
EXPECT_EQ((*pair.first)[i].dof, (*pair.second)[i].dof);
ExpectSameBits((*pair.first)[i].value, (*pair.second)[i].value);
}
}
ASSERT_EQ(a.hessian.size(), b.hessian.size());
for (std::size_t i = 0U; i < a.hessian.size(); ++i) {
EXPECT_EQ(a.hessian[i].row, b.hessian[i].row);
EXPECT_EQ(a.hessian[i].column, b.hessian[i].column);
ExpectSameBits(a.hessian[i].value, b.hessian[i].value);
}
}
}
struct Fixture {
std::unique_ptr<fesa::Domain> domain;
fesa::DofManager dofs;
@@ -394,6 +462,480 @@ TEST(HhtReducedChart, InvertibleGraphDoesNotCertifySelectorTransversality) {
assembly, chart.Value(), solver).HasValue());
}
TEST(HhtReducedOperators,
CompleteTrialConstraintDataMatchesFreshAndOwnsCopies) {
RestoreTrialEnvironment restore;
const auto fixture = MakeFixture(false, true);
const auto shells =
fesa::hht_internal::BuildRuntimeShells(*fixture.domain, fixture.dofs);
ASSERT_TRUE(shells.HasValue());
const fesa::Vector zero(fixture.dofs.FullDofCount());
auto q = zero;
auto v = zero;
auto force = zero;
auto rate = zero;
auto acceleration = zero;
for (std::size_t i = 0U; i < zero.Size(); ++i) {
q[i] = 0.001 * static_cast<double>((i % 5U) + 1U);
v[i] = 0.002 * static_cast<double>((i % 7U) + 1U);
if (i % 6U >= 3U) {
force[i] = 0.03;
rate[i] = -0.02;
acceleration[i] = 0.01;
}
}
auto assembled = Assemble(fixture, shells.Value(), q, v, zero, force, true);
ASSERT_TRUE(assembled.HasValue());
ASSERT_TRUE(assembled.Value().trial_constraints.has_value());
ASSERT_EQ(assembled.Value().trial_constraints->shells.size(),
shells.Value().size());
const auto chart = fesa::hht_reduced_internal::BuildFixedChart(
fixture.dofs, assembled.Value().rotations);
ASSERT_TRUE(chart.HasValue());
for (std::size_t e = 0U; e < shells.Value().size(); ++e) {
const auto& saved = assembled.Value().trial_constraints->shells[e];
ASSERT_TRUE(saved.directional_internal);
fesa::NonlinearMitc4TrialDofs trial{};
fesa::Vector direction{24U};
for (std::size_t node = 0U; node < 4U; ++node) {
const auto first = 6U * node;
trial.displacement[node] =
fesa::Vector3{saved.q[first], saved.q[first + 1U], saved.q[first + 2U]};
trial.rotation_vector[node] = fesa::Vector3{
saved.q[first + 3U], saved.q[first + 4U], saved.q[first + 5U]};
}
for (std::size_t i = 0U; i < 24U; ++i) direction[i] = saved.v[i];
const auto response = fesa::EvaluateInternalResidualAndTangent(
shells.Value()[e].internal, trial, &direction);
ASSERT_TRUE(response.HasValue());
for (std::size_t node = 0U; node < 4U; ++node) {
ExpectSameBits(saved.drilling_constraint[node],
response.Value().drilling_constraint[node]);
for (std::size_t i = 0U; i < 24U; ++i) {
ExpectSameBits(saved.drilling_constraint_gradient(node, i),
response.Value().drilling_constraint_gradient(node, i));
ExpectSameBits(
saved.drilling_constraint_third_directional_gradient[node][i],
response.Value()
.drilling_constraint_third_directional_gradient[node][i]);
for (std::size_t j = 0U; j < 24U; ++j)
ExpectSameBits(
saved.drilling_constraint_hessian[node](i, j),
response.Value().drilling_constraint_hessian[node](i, j));
}
}
}
const auto evaluate = [&](const fesa::hht_internal::DynamicAssembly& value) {
return fesa::hht_reduced_internal::EvaluateConstraintOperators(
*fixture.domain, fixture.dofs, shells.Value(), value, chart.Value(),
rate, acceleration);
};
auto fresh = assembled.Value();
fresh.trial_constraints.reset();
const auto reference = evaluate(fresh);
const auto reused = evaluate(assembled.Value());
ASSERT_TRUE(reference.HasValue());
ASSERT_TRUE(reused.HasValue());
ExpectSameOperators(reused.Value(), reference.Value());
auto copy = assembled.Value();
copy.trial_constraints->shells[0U].drilling_constraint[0U] += 7.0;
// Test-only finite marker proves the selected route actually reads its owner.
const auto marker = evaluate(copy);
ASSERT_TRUE(marker.HasValue());
EXPECT_NE(marker.Value().rows[0U].value, reused.Value().rows[0U].value);
EXPECT_NE(
copy.trial_constraints->shells[0U].drilling_constraint[0U],
assembled.Value().trial_constraints->shells[0U].drilling_constraint[0U]);
auto moved = std::move(assembled.Value());
const auto moved_result = evaluate(moved);
ASSERT_TRUE(moved_result.HasValue());
ExpectSameOperators(moved_result.Value(), reference.Value());
fesa::DynamicAcceptedFrameState frame{};
frame.generalized_coordinates = q;
frame.velocity = v;
frame.acceleration = zero;
frame.algorithmic_acceleration = zero;
frame.external_force = force;
const auto parameters = fesa::MakeHhtParameters(-0.05);
ASSERT_TRUE(parameters.HasValue());
fesa::TbbParallelFor parallel;
const auto tbb = fesa::hht_internal::EvaluateAssembly(
*fixture.domain, fixture.dofs, shells.Value(), frame, zero, 0.1,
parameters.Value(), parallel, true);
ASSERT_TRUE(tbb.HasValue());
const auto tbb_result = evaluate(tbb.Value());
ASSERT_TRUE(tbb_result.HasValue());
ExpectSameOperators(tbb_result.Value(), reference.Value());
}
TEST(HhtReducedOperators,
ChangedTrialConstraintContextFallsBackInOriginalPosition) {
RestoreTrialEnvironment restore;
const auto fixture = MakeFixture(false);
const auto shells =
fesa::hht_internal::BuildRuntimeShells(*fixture.domain, fixture.dofs);
ASSERT_TRUE(shells.HasValue());
const fesa::Vector zero(fixture.dofs.FullDofCount());
const auto assembled =
Assemble(fixture, shells.Value(), zero, zero, zero, zero, true);
ASSERT_TRUE(assembled.HasValue());
ASSERT_TRUE(assembled.Value().trial_constraints.has_value());
ASSERT_TRUE(
assembled.Value().trial_constraints->shells[0U].directional_internal);
const auto chart = fesa::hht_reduced_internal::BuildFixedChart(
fixture.dofs, assembled.Value().rotations);
ASSERT_TRUE(chart.HasValue());
const auto evaluate = [&](const fesa::hht_internal::DynamicAssembly& value) {
return fesa::hht_reduced_internal::EvaluateConstraintOperators(
*fixture.domain, fixture.dofs, shells.Value(), value, chart.Value(),
zero, zero);
};
using Assembly = fesa::hht_internal::DynamicAssembly;
std::vector<std::function<void(Assembly&)>> changes{
[](Assembly& a) { a.frame.generalized_coordinates[2U] = 0.001; },
[](Assembly& a) { a.frame.velocity[3U] = 0.003; },
[](Assembly& a) { a.frame.time = 0.1; },
[](Assembly& a) { ++a.frame.frame_index; },
[](Assembly& a) { ++a.frame.increment_index; },
[](Assembly& a) { ++a.frame.iteration_count; },
[](Assembly& a) { a.frame.is_initial = true; },
[](Assembly& a) { a.trial_constraints->source_path = "different.inp"; },
[](Assembly& a) { ++a.trial_constraints->shells[0U].element_index; },
[](Assembly& a) { ++a.trial_constraints->shells[0U].scatter[0U]; },
[](Assembly& a) {
a.trial_constraints->shells[0U].directional_internal = false;
},
[](Assembly& a) {
a.trial_constraints->shells[0U].node_source_ids[0U].instance_name +=
"x";
},
[](Assembly& a) {
++a.trial_constraints->shells[0U].node_source_ids[0U].source_label;
},
[](Assembly& a) {
a.trial_constraints->shells[0U].node_source_ids[0U].source_label_text +=
"x";
},
[](Assembly& a) {
++a.trial_constraints->shells[0U].configuration.source.line;
},
[](Assembly& a) {
a.trial_constraints->shells[0U].configuration.source.file =
"different.inp";
},
[](Assembly& a) {
a.trial_constraints->shells[0U].configuration.identity += "x";
},
[](Assembly& a) { a.trial_constraints->shells[0U].q[0U] = -0.0; },
[](Assembly& a) { a.trial_constraints->shells[0U].v[0U] = -0.0; },
[](Assembly& a) {
a.trial_constraints->shells[0U].drilling_constraint_gradient =
fesa::Matrix{0U, 0U};
},
[](Assembly& a) {
a.trial_constraints->shells[0U].drilling_constraint_hessian[2U] =
fesa::Matrix{0U, 0U};
},
[](Assembly& a) {
a.trial_constraints->shells[0U]
.drilling_constraint_third_directional_gradient[1U] =
fesa::Vector{0U};
},
[](Assembly& a) {
a.trial_constraints->shells[0U].drilling_constraint_hessian[0U](
0U, 0U) = std::numeric_limits<double>::infinity();
},
[](Assembly& a) {
a.trial_constraints->shells[0U]
.evaluation_context.dense_policy.blas_max_threads = 2;
},
[](Assembly& a) {
a.trial_constraints->shells[0U]
.evaluation_context.dense_policy.dynamic = 1;
},
[](Assembly& a) {
a.trial_constraints->shells[0U]
.evaluation_context.dense_policy.dynamic = -1;
},
[](Assembly& a) {
a.trial_constraints->shells[0U].evaluation_context.mxcsr_control ^=
0x8000U;
},
[](Assembly& a) {
a.trial_constraints->shells[0U].evaluation_context.supported = false;
}};
for (std::size_t i = 0U; i < 64U; ++i)
changes.push_back([i](Assembly& a) {
a.trial_constraints->shells[0U].configuration.values[i] += 0.125;
});
for (const double invalid : {std::numeric_limits<double>::quiet_NaN(),
std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity()})
changes.push_back([invalid](Assembly& a) {
a.trial_constraints->shells[0U].configuration.values[60U] = invalid;
});
for (std::size_t i = 0U; i < changes.size(); ++i) {
SCOPED_TRACE(i);
auto candidate = assembled.Value();
candidate.trial_constraints->shells[0U].drilling_constraint[0U] = 17.0;
changes[i](candidate);
auto fresh = candidate;
fresh.trial_constraints.reset();
const auto reference = evaluate(fresh);
const auto actual = evaluate(candidate);
ASSERT_TRUE(reference.HasValue());
ASSERT_TRUE(actual.HasValue());
ExpectSameOperators(actual.Value(), reference.Value());
}
for (std::size_t kind = 0U; kind < 2U; ++kind) {
auto candidate = assembled.Value();
if (kind == 0U) candidate.trial_constraints->shells.clear();
if (kind == 1U) candidate.trial_constraints.reset();
auto fresh = assembled.Value();
fresh.trial_constraints.reset();
const auto reference = evaluate(fresh);
const auto actual = evaluate(candidate);
ASSERT_TRUE(reference.HasValue());
ASSERT_TRUE(actual.HasValue());
ExpectSameOperators(actual.Value(), reference.Value());
}
// Change the actual consumer environment, preserving the original fallback.
for (std::size_t mode = 0U; mode < 5U; ++mode) {
SCOPED_TRACE(mode);
auto candidate = assembled.Value();
candidate.trial_constraints->shells[0U].drilling_constraint[0U] = 17.0;
auto fresh = candidate;
fresh.trial_constraints.reset();
#if defined(_M_X64)
const auto csr = _mm_getcsr();
if (mode == 0U) _mm_setcsr(csr ^ 0x8000U);
if (mode == 1U) _mm_setcsr(csr ^ 0x0040U);
#endif
const int rounding = std::fegetround();
if (mode == 2U) static_cast<void>(std::fesetround(FE_DOWNWARD));
if (mode == 3U) static_cast<void>(mkl_set_num_threads_local(2));
if (mode == 4U) mkl_set_dynamic(1);
const auto reference = evaluate(fresh);
const auto actual = evaluate(candidate);
if (mode == 4U) mkl_set_dynamic(0);
if (mode == 3U) EXPECT_EQ(mkl_set_num_threads_local(0), 2);
static_cast<void>(std::fesetround(rounding));
#if defined(_M_X64)
_mm_setcsr(csr);
#endif
ASSERT_TRUE(reference.HasValue());
ASSERT_TRUE(actual.HasValue());
ExpectSameOperators(actual.Value(), reference.Value());
}
auto failed_trial = assembled.Value();
failed_trial.frame.generalized_coordinates[0U] =
std::numeric_limits<double>::infinity();
auto fresh_failure = failed_trial;
fresh_failure.trial_constraints.reset();
const auto failed = evaluate(failed_trial);
const auto original = evaluate(fresh_failure);
ASSERT_FALSE(failed.HasValue());
ASSERT_FALSE(original.HasValue());
ASSERT_EQ(failed.GetStatus().Diagnostics().size(),
original.GetStatus().Diagnostics().size());
EXPECT_EQ(failed.GetStatus().Diagnostics()[0U].code,
original.GetStatus().Diagnostics()[0U].code);
EXPECT_EQ(failed.GetStatus().Diagnostics()[0U].message,
original.GetStatus().Diagnostics()[0U].message);
const auto rerun = evaluate(assembled.Value());
auto fresh = assembled.Value();
fresh.trial_constraints.reset();
const auto reference = evaluate(fresh);
ASSERT_TRUE(rerun.HasValue());
ASSERT_TRUE(reference.HasValue());
ExpectSameOperators(rerun.Value(), reference.Value());
}
TEST(HhtReducedOperators, TrialContextControlsAndInitialFalseFailureLifetime) {
RestoreTrialEnvironment restore;
const auto context = fesa::hht_internal::ReadTrialEvaluationContext();
ASSERT_TRUE(fesa::hht_internal::ReusableTrialContext(context, context));
for (const unsigned int bit : {0x0040U, 0x8000U, 0x2000U, 0x0080U}) {
auto changed = context;
changed.mxcsr_control ^= bit;
EXPECT_FALSE(fesa::hht_internal::ReusableTrialContext(context, changed));
}
auto unmasked = context;
unmasked.mxcsr_control &= ~0x0080U;
EXPECT_FALSE(fesa::hht_internal::ReusableTrialContext(unmasked, unmasked));
static_cast<void>(mkl_set_num_threads_local(2));
const auto positive = fesa::hht_internal::ReadTrialEvaluationContext();
EXPECT_FALSE(fesa::hht_internal::ReusableTrialContext(positive, positive));
EXPECT_EQ(mkl_set_num_threads_local(0), 2);
mkl_set_dynamic(1);
const auto dynamic = fesa::hht_internal::ReadTrialEvaluationContext();
EXPECT_FALSE(fesa::hht_internal::ReusableTrialContext(dynamic, dynamic));
mkl_set_dynamic(0);
const auto fixture = MakeFixture(false);
const auto shells =
fesa::hht_internal::BuildRuntimeShells(*fixture.domain, fixture.dofs);
ASSERT_TRUE(shells.HasValue());
const fesa::Vector zero(fixture.dofs.FullDofCount());
const auto position =
Assemble(fixture, shells.Value(), zero, zero, zero, zero, false);
ASSERT_TRUE(position.HasValue());
EXPECT_FALSE(position.Value().trial_constraints.has_value());
mkl_set_dynamic(1);
const auto dynamic_trial =
Assemble(fixture, shells.Value(), zero, zero, zero, zero, true);
mkl_set_dynamic(0);
ASSERT_TRUE(dynamic_trial.HasValue());
ASSERT_TRUE(dynamic_trial.Value().trial_constraints.has_value());
EXPECT_FALSE(
dynamic_trial.Value().trial_constraints->shells[0U].directional_internal);
static_cast<void>(mkl_set_num_threads_local(2));
const auto positive_trial =
Assemble(fixture, shells.Value(), zero, zero, zero, zero, true);
EXPECT_EQ(mkl_set_num_threads_local(0), 2);
ASSERT_TRUE(positive_trial.HasValue());
ASSERT_TRUE(positive_trial.Value().trial_constraints.has_value());
EXPECT_FALSE(positive_trial.Value()
.trial_constraints->shells[0U]
.directional_internal);
fesa::DynamicAcceptedFrameState frame{};
frame.is_initial = true;
for (auto* vector :
{&frame.generalized_coordinates, &frame.velocity, &frame.acceleration,
&frame.algorithmic_acceleration, &frame.external_force})
*vector = zero;
fesa::SerialParallelFor parallel;
const auto parameters = fesa::MakeHhtParameters(-0.05);
ASSERT_TRUE(parameters.HasValue());
const auto initial = fesa::hht_internal::EvaluateAssembly(
*fixture.domain, fixture.dofs, shells.Value(), frame, zero, 0.1,
parameters.Value(), parallel, true);
ASSERT_TRUE(initial.HasValue());
EXPECT_FALSE(initial.Value().trial_constraints.has_value());
auto invalid = zero;
invalid[0U] = std::numeric_limits<double>::quiet_NaN();
const auto failure =
Assemble(fixture, shells.Value(), invalid, zero, zero, zero, true);
EXPECT_FALSE(failure.HasValue());
const auto current =
Assemble(fixture, shells.Value(), zero, zero, zero, zero, true);
const auto next =
Assemble(fixture, shells.Value(), zero, zero, zero, zero, true);
ASSERT_TRUE(current.HasValue());
ASSERT_TRUE(next.HasValue());
ASSERT_EQ(current.Value().trial_constraints->shells.size(),
shells.Value().size());
ASSERT_EQ(next.Value().trial_constraints->shells.size(),
shells.Value().size());
EXPECT_NE(&current.Value().trial_constraints->shells[0U],
&next.Value().trial_constraints->shells[0U]);
}
TEST(HhtReducedOperators, NonfiniteMatchingTrialKeysPreserveOriginalFailure) {
RestoreTrialEnvironment restore;
const auto fixture = MakeFixture(false);
const auto shells =
fesa::hht_internal::BuildRuntimeShells(*fixture.domain, fixture.dofs);
ASSERT_TRUE(shells.HasValue());
const fesa::Vector zero(fixture.dofs.FullDofCount());
const auto assembled =
Assemble(fixture, shells.Value(), zero, zero, zero, zero, true);
ASSERT_TRUE(assembled.HasValue());
ASSERT_TRUE(assembled.Value().trial_constraints.has_value());
ASSERT_TRUE(
assembled.Value().trial_constraints->shells[0U].directional_internal);
const auto chart = fesa::hht_reduced_internal::BuildFixedChart(
fixture.dofs, assembled.Value().rotations);
ASSERT_TRUE(chart.HasValue());
const auto evaluate = [&](const fesa::hht_internal::DynamicAssembly& value) {
return fesa::hht_reduced_internal::EvaluateConstraintOperators(
*fixture.domain, fixture.dofs, shells.Value(), value, chart.Value(),
zero, zero);
};
const std::array<double, 3> invalid{std::numeric_limits<double>::quiet_NaN(),
std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity()};
for (std::size_t channel = 0U; channel < 2U; ++channel)
for (std::size_t i = 0U; i < invalid.size(); ++i) {
SCOPED_TRACE(channel);
SCOPED_TRACE(i);
auto candidate = assembled.Value();
auto& saved = candidate.trial_constraints->shells[0U];
if (channel == 0U) {
candidate.frame.generalized_coordinates[0U] = invalid[i];
saved.q[0U] = invalid[i];
} else {
candidate.frame.velocity[0U] = invalid[i];
saved.v[0U] = invalid[i];
}
auto fresh = candidate;
fresh.trial_constraints.reset();
const auto original = evaluate(fresh);
const auto actual = evaluate(candidate);
ASSERT_FALSE(original.HasValue());
ASSERT_FALSE(actual.HasValue());
EXPECT_EQ(actual.GetStatus().Category(), original.GetStatus().Category());
const auto& a = actual.GetStatus().Diagnostics();
const auto& b = original.GetStatus().Diagnostics();
ASSERT_EQ(a.size(), b.size());
for (std::size_t j = 0U; j < a.size(); ++j) {
EXPECT_EQ(a[j].severity, b[j].severity);
EXPECT_EQ(a[j].code, b[j].code);
EXPECT_EQ(a[j].location.file, b[j].location.file);
EXPECT_EQ(a[j].location.line, b[j].location.line);
EXPECT_EQ(a[j].keyword, b[j].keyword);
EXPECT_EQ(a[j].entity_identity, b[j].entity_identity);
EXPECT_EQ(a[j].message, b[j].message);
}
}
}
TEST(HhtReducedOperators, NonfiniteMatchingTimeKeyMissPreservesFreshSuccess) {
RestoreTrialEnvironment restore;
const auto fixture = MakeFixture(false);
const auto shells =
fesa::hht_internal::BuildRuntimeShells(*fixture.domain, fixture.dofs);
ASSERT_TRUE(shells.HasValue());
const fesa::Vector zero(fixture.dofs.FullDofCount());
const auto assembled =
Assemble(fixture, shells.Value(), zero, zero, zero, zero, true);
ASSERT_TRUE(assembled.HasValue());
ASSERT_TRUE(assembled.Value().trial_constraints.has_value());
const auto chart = fesa::hht_reduced_internal::BuildFixedChart(
fixture.dofs, assembled.Value().rotations);
ASSERT_TRUE(chart.HasValue());
const auto evaluate = [&](const fesa::hht_internal::DynamicAssembly& value) {
return fesa::hht_reduced_internal::EvaluateConstraintOperators(
*fixture.domain, fixture.dofs, shells.Value(), value, chart.Value(),
zero, zero);
};
const auto parameters = fesa::MakeHhtParameters(-0.05);
ASSERT_TRUE(parameters.HasValue());
fesa::SerialParallelFor parallel;
for (const double bad_time : {std::numeric_limits<double>::quiet_NaN(),
std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity()}) {
auto candidate = assembled.Value();
candidate.frame.time = bad_time;
candidate.trial_constraints->time = bad_time;
candidate.trial_constraints->shells[0U].drilling_constraint[0U] = 17.0;
auto fresh = candidate;
fresh.trial_constraints.reset();
const auto reference = evaluate(fresh);
const auto actual = evaluate(candidate);
ASSERT_TRUE(reference.HasValue());
ASSERT_TRUE(actual.HasValue());
ExpectSameOperators(actual.Value(), reference.Value());
const auto producer = fesa::hht_internal::EvaluateAssembly(
*fixture.domain, fixture.dofs, shells.Value(), candidate.frame, zero,
0.1, parameters.Value(), parallel, true);
ASSERT_TRUE(producer.HasValue());
EXPECT_FALSE(producer.Value().trial_constraints.has_value());
const auto producer_result = evaluate(producer.Value());
ASSERT_TRUE(producer_result.HasValue());
ExpectSameOperators(producer_result.Value(), reference.Value());
}
}
TEST(HhtReducedOperators, ScatterShellDerivativesByConstraintRow) {
const auto fixture = MakeFixture(false);
const auto shells = fesa::hht_internal::BuildRuntimeShells(*fixture.domain,
@@ -11,6 +11,39 @@
namespace fesa::dense_blas_internal {
namespace {
TEST(DenseBlasInternal, PolicyQueryPreservesDefaultPositiveAndNestedTls) {
const int previous = mkl_set_num_threads_local(0);
struct Restore {
int previous;
~Restore() { static_cast<void>(mkl_set_num_threads_local(previous)); }
} restore{previous};
const int global = mkl_get_max_threads();
const int domain = mkl_domain_get_max_threads(MKL_DOMAIN_BLAS);
const int dynamic = mkl_get_dynamic();
for (int repeat = 0; repeat < 2; ++repeat) {
const auto policy = ReadScopedLocalDensePolicy();
EXPECT_EQ(policy.blas_max_threads, 1);
EXPECT_EQ(policy.dynamic, dynamic);
EXPECT_EQ(mkl_set_num_threads_local(0), 0);
EXPECT_EQ(mkl_get_max_threads(), global);
EXPECT_EQ(mkl_domain_get_max_threads(MKL_DOMAIN_BLAS), domain);
EXPECT_EQ(mkl_get_dynamic(), dynamic);
}
static_cast<void>(mkl_set_num_threads_local(2));
EXPECT_EQ(ReadScopedLocalDensePolicy().blas_max_threads, 2);
EXPECT_EQ(mkl_set_num_threads_local(2), 2);
static_cast<void>(mkl_set_num_threads_local(0));
try {
const ScopedLocalDenseThreads outer{};
EXPECT_EQ(ReadScopedLocalDensePolicy().blas_max_threads, 1);
EXPECT_EQ(mkl_set_num_threads_local(1), 1);
throw std::runtime_error{"policy-query-unwind"};
} catch (const std::runtime_error&) {
}
EXPECT_EQ(mkl_set_num_threads_local(0), 0);
EXPECT_EQ(mkl_get_dynamic(), dynamic);
}
TEST(DenseBlasInternal,
OuterProductUsesRectangularStorageAndPreservesRejection) {
const std::array<double, 2U> left{2.0, -3.0};