완성된 HHT trial의 constraint 자료 중복 평가 제거
This commit is contained in:
@@ -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 반복은 하지 않았다.
|
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/비용 근거가 충분해진 경우의 비교 후보이며 현재 구현 범위를 확대하지 않는다.
|
다음 우선 후보는 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 fesa {
|
||||||
|
|
||||||
|
namespace nonlinear_mitc4_internal {
|
||||||
|
struct ConfigurationSnapshot;
|
||||||
|
} // namespace nonlinear_mitc4_internal
|
||||||
|
|
||||||
/// @brief Stores one four-node nonlinear shell trial in global coordinates.
|
/// @brief Stores one four-node nonlinear shell trial in global coordinates.
|
||||||
struct NonlinearMitc4TrialDofs {
|
struct NonlinearMitc4TrialDofs {
|
||||||
std::array<Vector3, 4> displacement;
|
std::array<Vector3, 4> displacement;
|
||||||
@@ -129,6 +133,8 @@ class NonlinearMitc4Shell {
|
|||||||
const Vector3& force_acceleration) const;
|
const Vector3& force_acceleration) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
friend struct nonlinear_mitc4_internal::ConfigurationSnapshot;
|
||||||
|
|
||||||
/// @brief Retains validated fixed-reference data without analysis ownership.
|
/// @brief Retains validated fixed-reference data without analysis ownership.
|
||||||
NonlinearMitc4Shell(std::array<Vector3, 4> coordinates,
|
NonlinearMitc4Shell(std::array<Vector3, 4> coordinates,
|
||||||
std::array<Matrix, 4> initial_frames,
|
std::array<Matrix, 4> initial_frames,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cfenv>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <map>
|
#include <map>
|
||||||
@@ -6,6 +7,10 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#if defined(_M_X64)
|
||||||
|
#include <xmmintrin.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "analysis/hht_dynamic_internal.h"
|
#include "analysis/hht_dynamic_internal.h"
|
||||||
#include "math/dense_blas_internal.h"
|
#include "math/dense_blas_internal.h"
|
||||||
#include "fesa/assembly/parallel_for.h"
|
#include "fesa/assembly/parallel_for.h"
|
||||||
@@ -14,6 +19,29 @@
|
|||||||
#include "fesa/model/source_target_resolver.h"
|
#include "fesa/model/source_target_resolver.h"
|
||||||
|
|
||||||
namespace fesa::hht_internal {
|
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 {
|
namespace {
|
||||||
|
|
||||||
/// @brief Reports a dynamic assembly or equilibrium failure.
|
/// @brief Reports a dynamic assembly or equilibrium failure.
|
||||||
@@ -388,6 +416,10 @@ Result<DynamicAssembly> EvaluateAssembly(
|
|||||||
std::vector<std::optional<NonlinearMitc4InertiaEvaluation>> inertia(
|
std::vector<std::optional<NonlinearMitc4InertiaEvaluation>> inertia(
|
||||||
shells.size());
|
shells.size());
|
||||||
std::vector<std::optional<Status>> failures(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) {
|
parallel_for.Execute(shells.size(), [&](const std::size_t e) {
|
||||||
const auto q = Gather(frame.generalized_coordinates, shells[e]);
|
const auto q = Gather(frame.generalized_coordinates, shells[e]);
|
||||||
const auto v = Gather(frame.velocity, 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.displacement[node] = Triple(q, 6U * node);
|
||||||
trial.rotation_vector[node] = Triple(q, 6U * node + 3U);
|
trial.rotation_vector[node] = Triple(q, 6U * node + 3U);
|
||||||
}
|
}
|
||||||
|
const auto before = retain_constraints ? ReadTrialEvaluationContext()
|
||||||
|
: TrialEvaluationContext{};
|
||||||
auto f = EvaluateInternalResidualAndTangent(
|
auto f = EvaluateInternalResidualAndTangent(
|
||||||
shells[e].internal, trial, directional_third ? &v : nullptr);
|
shells[e].internal, trial, directional_third ? &v : nullptr);
|
||||||
if (!f.HasValue()) {
|
if (!f.HasValue()) {
|
||||||
failures[e] = f.GetStatus();
|
failures[e] = f.GetStatus();
|
||||||
return;
|
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);
|
auto m = shells[e].inertia.Evaluate(trial, v, a);
|
||||||
if (!m.HasValue()) {
|
if (!m.HasValue()) {
|
||||||
failures[e] = m.GetStatus();
|
failures[e] = m.GetStatus();
|
||||||
@@ -605,13 +643,52 @@ Result<DynamicAssembly> EvaluateAssembly(
|
|||||||
return Result<DynamicAssembly>::Failure(gradient.GetStatus());
|
return Result<DynamicAssembly>::Failure(gradient.GetStatus());
|
||||||
hidden_gradient = std::move(gradient.Value());
|
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(
|
return Result<DynamicAssembly>::Success(
|
||||||
{std::move(frame), std::move(mass.Value()), std::move(position.Value()),
|
{std::move(frame), std::move(mass.Value()), std::move(position.Value()),
|
||||||
std::move(velocity.Value()), std::move(stiffness.Value()),
|
std::move(velocity.Value()), std::move(stiffness.Value()),
|
||||||
std::move(effective.Value()), std::move(metric.Value()),
|
std::move(effective.Value()), std::move(metric.Value()),
|
||||||
std::move(third), std::move(third_scale), std::move(rotations),
|
std::move(third), std::move(third_scale), std::move(rotations),
|
||||||
momentum_scale, angular_scale, std::move(hidden_second),
|
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,
|
Status CompleteEquilibrium(const Domain& domain, const DofManager& dofs,
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
#define FESA_ANALYSIS_HHT_DYNAMIC_INTERNAL_H_
|
#define FESA_ANALYSIS_HHT_DYNAMIC_INTERNAL_H_
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -10,6 +12,8 @@
|
|||||||
#include "fesa/elements/nonlinear_mitc4_inertia.h"
|
#include "fesa/elements/nonlinear_mitc4_inertia.h"
|
||||||
#include "fesa/elements/nonlinear_mitc4_shell.h"
|
#include "fesa/elements/nonlinear_mitc4_shell.h"
|
||||||
#include "fesa/math/sparse_matrix.h"
|
#include "fesa/math/sparse_matrix.h"
|
||||||
|
#include "elements/nonlinear_mitc4_configuration_internal.h"
|
||||||
|
#include "math/dense_blas_internal.h"
|
||||||
|
|
||||||
namespace fesa {
|
namespace fesa {
|
||||||
class LinearSolver;
|
class LinearSolver;
|
||||||
@@ -25,6 +29,49 @@ struct RuntimeShell {
|
|||||||
NonlinearMitc4Inertia inertia;
|
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.
|
/// @brief Stores a complete trial with independently assembled operators.
|
||||||
struct DynamicAssembly {
|
struct DynamicAssembly {
|
||||||
DynamicAcceptedFrameState frame;
|
DynamicAcceptedFrameState frame;
|
||||||
@@ -43,6 +90,7 @@ struct DynamicAssembly {
|
|||||||
Vector hidden_second_scale{0U};
|
Vector hidden_second_scale{0U};
|
||||||
// Canonical scalar drilling-constraint derivative, indexed by global node.
|
// Canonical scalar drilling-constraint derivative, indexed by global node.
|
||||||
std::optional<SparseMatrix> hidden_gradient;
|
std::optional<SparseMatrix> hidden_gradient;
|
||||||
|
std::optional<TrialConstraintData> trial_constraints;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// @brief Builds checked shell evaluators in stable source order.
|
/// @brief Builds checked shell evaluators in stable source order.
|
||||||
|
|||||||
@@ -60,6 +60,88 @@ Vector Local(const Vector& full,
|
|||||||
return local;
|
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(const Vector& local) {
|
||||||
NonlinearMitc4TrialDofs trial{};
|
NonlinearMitc4TrialDofs trial{};
|
||||||
for (std::size_t node = 0U; node < 4U; ++node) {
|
for (std::size_t node = 0U; node < 4U; ++node) {
|
||||||
@@ -443,32 +525,46 @@ Result<ConstraintOperators> EvaluateConstraintOperators(
|
|||||||
node_row[chart.null_nodes[row]] = row;
|
node_row[chart.null_nodes[row]] = row;
|
||||||
}
|
}
|
||||||
std::vector<bool> external_done(domain.Nodes().size(), false);
|
std::vector<bool> external_done(domain.Nodes().size(), false);
|
||||||
|
std::size_t shell_index = 0U;
|
||||||
for (const auto& shell : shells) {
|
for (const auto& shell : shells) {
|
||||||
const auto q = Local(assembly.frame.generalized_coordinates, shell);
|
const auto q = Local(assembly.frame.generalized_coordinates, shell);
|
||||||
const auto v = Local(assembly.frame.velocity, shell);
|
const auto v = Local(assembly.frame.velocity, shell);
|
||||||
const auto internal = EvaluateInternalResidualAndTangent(shell.internal,
|
const auto* saved = MatchingConstraints(domain, shell, assembly,
|
||||||
Trial(q), &v);
|
shell_index++, shells.size(), q, v);
|
||||||
|
std::optional<NonlinearMitc4InternalEquilibrium> fresh;
|
||||||
|
if (saved == nullptr) {
|
||||||
|
auto internal =
|
||||||
|
EvaluateInternalResidualAndTangent(shell.internal, Trial(q), &v);
|
||||||
if (!internal.HasValue())
|
if (!internal.HasValue())
|
||||||
return Result<ConstraintOperators>::Failure(internal.GetStatus());
|
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) {
|
for (std::size_t local_node = 0U; local_node < 4U; ++local_node) {
|
||||||
const auto global_node = shell.scatter[6U * local_node] / 6U;
|
const auto global_node = shell.scatter[6U * local_node] / 6U;
|
||||||
if (global_node >= node_row.size() || node_row[global_node] == rows)
|
if (global_node >= node_row.size() || node_row[global_node] == rows)
|
||||||
continue;
|
continue;
|
||||||
const auto row = node_row[global_node];
|
const auto row = node_row[global_node];
|
||||||
result.rows[row].value +=
|
result.rows[row].value += constraint[local_node];
|
||||||
internal.Value().drilling_constraint[local_node];
|
|
||||||
for (std::size_t i = 0U; i < 24U; ++i) {
|
for (std::size_t i = 0U; i < 24U; ++i) {
|
||||||
const auto global_i = shell.scatter[i];
|
const auto global_i = shell.scatter[i];
|
||||||
const double gradient =
|
const double gradient = gradient_data(local_node, i);
|
||||||
internal.Value().drilling_constraint_gradient(local_node, i);
|
const double third = third_data[local_node][i];
|
||||||
const double third = internal.Value()
|
|
||||||
.drilling_constraint_third_directional_gradient
|
|
||||||
[local_node][i];
|
|
||||||
if (gradient != 0.0) gradients[row][global_i] += gradient;
|
if (gradient != 0.0) gradients[row][global_i] += gradient;
|
||||||
if (third != 0.0) third_gradients[row][global_i] += third;
|
if (third != 0.0) third_gradients[row][global_i] += third;
|
||||||
for (std::size_t j = 0U; j < 24U; ++j) {
|
for (std::size_t j = 0U; j < 24U; ++j) {
|
||||||
const double hessian =
|
const double hessian = hessian_data[local_node](i, j);
|
||||||
internal.Value().drilling_constraint_hessian[local_node](i, j);
|
|
||||||
if (hessian != 0.0)
|
if (hessian != 0.0)
|
||||||
hessians[row][{global_i, shell.scatter[j]}] += hessian;
|
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 <utility>
|
||||||
|
|
||||||
#include "fesa/elements/finite_rotation.h"
|
#include "fesa/elements/finite_rotation.h"
|
||||||
|
#include "elements/nonlinear_mitc4_configuration_internal.h"
|
||||||
#include "math/dense_blas_internal.h"
|
#include "math/dense_blas_internal.h"
|
||||||
|
|
||||||
namespace fesa {
|
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 {
|
namespace {
|
||||||
|
|
||||||
constexpr std::size_t kNodeCount = 4U;
|
constexpr std::size_t kNodeCount = 4U;
|
||||||
|
|||||||
@@ -40,6 +40,11 @@ ScopedLocalDenseThreads::~ScopedLocalDenseThreads() {
|
|||||||
if (restore_default_) static_cast<void>(mkl_set_num_threads_local(0));
|
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,
|
bool CholeskyLowerAndInverse(const Matrix& matrix, Matrix& lower,
|
||||||
Matrix& inverse_lower, CholeskyFailure* failure) {
|
Matrix& inverse_lower, CholeskyFailure* failure) {
|
||||||
if (failure != nullptr) *failure = CholeskyFailure::kNone;
|
if (failure != nullptr) *failure = CholeskyFailure::kNone;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#ifndef FESA_SRC_FESA_MATH_DENSE_BLAS_INTERNAL_H_
|
#ifndef FESA_SRC_FESA_MATH_DENSE_BLAS_INTERNAL_H_
|
||||||
#define FESA_SRC_FESA_MATH_DENSE_BLAS_INTERNAL_H_
|
#define FESA_SRC_FESA_MATH_DENSE_BLAS_INTERNAL_H_
|
||||||
|
|
||||||
#include <mkl.h>
|
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
||||||
|
#include <mkl.h>
|
||||||
|
|
||||||
#include "fesa/core/status.h"
|
#include "fesa/core/status.h"
|
||||||
#include "fesa/math/matrix.h"
|
#include "fesa/math/matrix.h"
|
||||||
#include "fesa/math/vector.h"
|
#include "fesa/math/vector.h"
|
||||||
@@ -40,6 +40,16 @@ class ScopedLocalDenseThreads {
|
|||||||
bool restore_default_{false};
|
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.
|
/// @brief Converts a dense storage length to the private MKL integer type.
|
||||||
/// @return The converted length or an uncategorized overflow failure.
|
/// @return The converted length or an uncategorized overflow failure.
|
||||||
Result<MKL_INT> ToMklSize(std::size_t size);
|
Result<MKL_INT> ToMklSize(std::size_t size);
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cfenv>
|
#include <cfenv>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <cstring>
|
||||||
|
#include <functional>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
@@ -19,6 +21,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "analysis/hht_reduced_internal.h"
|
#include "analysis/hht_reduced_internal.h"
|
||||||
|
#include "math/dense_blas_internal.h"
|
||||||
#include "fesa/analysis/analysis_model.h"
|
#include "fesa/analysis/analysis_model.h"
|
||||||
#include "fesa/analysis/hht_time_integration.h"
|
#include "fesa/analysis/hht_time_integration.h"
|
||||||
#include "fesa/assembly/parallel_for.h"
|
#include "fesa/assembly/parallel_for.h"
|
||||||
@@ -26,6 +29,71 @@
|
|||||||
|
|
||||||
namespace {
|
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 {
|
struct Fixture {
|
||||||
std::unique_ptr<fesa::Domain> domain;
|
std::unique_ptr<fesa::Domain> domain;
|
||||||
fesa::DofManager dofs;
|
fesa::DofManager dofs;
|
||||||
@@ -394,6 +462,480 @@ TEST(HhtReducedChart, InvertibleGraphDoesNotCertifySelectorTransversality) {
|
|||||||
assembly, chart.Value(), solver).HasValue());
|
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(¤t.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) {
|
TEST(HhtReducedOperators, ScatterShellDerivativesByConstraintRow) {
|
||||||
const auto fixture = MakeFixture(false);
|
const auto fixture = MakeFixture(false);
|
||||||
const auto shells = fesa::hht_internal::BuildRuntimeShells(*fixture.domain,
|
const auto shells = fesa::hht_internal::BuildRuntimeShells(*fixture.domain,
|
||||||
|
|||||||
@@ -11,6 +11,39 @@
|
|||||||
namespace fesa::dense_blas_internal {
|
namespace fesa::dense_blas_internal {
|
||||||
namespace {
|
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,
|
TEST(DenseBlasInternal,
|
||||||
OuterProductUsesRectangularStorageAndPreservesRejection) {
|
OuterProductUsesRectangularStorageAndPreservesRejection) {
|
||||||
const std::array<double, 2U> left{2.0, -3.0};
|
const std::array<double, 2U> left{2.0, -3.0};
|
||||||
|
|||||||
Reference in New Issue
Block a user