perf(shell): route local products through MKL and reduce Hessian storage work
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "fesa/elements/finite_rotation.h"
|
||||
#include "math/dense_blas_internal.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
@@ -394,22 +395,63 @@ constexpr std::size_t kHessianSize = kGlobalDofCount * kGlobalDofCount;
|
||||
/// @brief Carries exact first and second trial-coordinate variations.
|
||||
class Hessian {
|
||||
public:
|
||||
Hessian() : values_{std::make_shared<std::array<double, kHessianSize>>()} {}
|
||||
Hessian() = default;
|
||||
|
||||
double& operator[](const std::size_t index) { return (*values_)[index]; }
|
||||
double& operator[](const std::size_t index) { return Data()[index]; }
|
||||
|
||||
const double& operator[](const std::size_t index) const {
|
||||
return (*values_)[index];
|
||||
return Storage()[index];
|
||||
}
|
||||
|
||||
auto begin() const { return values_->begin(); }
|
||||
auto begin() const { return Storage().begin(); }
|
||||
|
||||
auto end() const { return values_->end(); }
|
||||
auto end() const { return Storage().end(); }
|
||||
|
||||
double* Data() {
|
||||
if (!values_) {
|
||||
values_ = std::make_shared<Values>();
|
||||
values_->entries.fill(0.0);
|
||||
}
|
||||
return values_->entries.data();
|
||||
}
|
||||
|
||||
const double* ReadData() const { return Storage().data(); }
|
||||
|
||||
/// @brief Gets fresh output storage without redundant zero initialization.
|
||||
/// @note Only fresh operator results use this pointer; all 576 entries must
|
||||
/// be written before any read or copy of the result.
|
||||
double* OverwriteData() {
|
||||
if (!values_) values_ = std::make_shared<Values>();
|
||||
return values_->entries.data();
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<std::array<double, kHessianSize>> values_;
|
||||
struct Values {
|
||||
// User-provided construction leaves entries uninitialized for a complete
|
||||
// output overwrite while make_shared retains the single allocation.
|
||||
Values() noexcept {}
|
||||
std::array<double, kHessianSize> entries;
|
||||
};
|
||||
|
||||
const std::array<double, kHessianSize>& Storage() const {
|
||||
// Constants borrow only this immutable zero. Mutable coefficients retain
|
||||
// the existing shared ownership; no trial-dependent cache is introduced.
|
||||
static const std::array<double, kHessianSize> kZero{};
|
||||
return values_ ? values_->entries : kZero;
|
||||
}
|
||||
|
||||
std::shared_ptr<Values> values_;
|
||||
};
|
||||
|
||||
/// @brief Forms an ordinary derivative outer product without a Matrix copy.
|
||||
/// @note Rejected nonfinite operands propagate to the existing Jet finite gate.
|
||||
void Outer(const std::array<double, kGlobalDofCount>& left,
|
||||
const std::array<double, kGlobalDofCount>& right, double* result) {
|
||||
if (!dense_blas_internal::OuterProduct(left.data(), left.size(), right.data(),
|
||||
right.size(), result))
|
||||
std::fill_n(result, kHessianSize, std::numeric_limits<double>::quiet_NaN());
|
||||
}
|
||||
|
||||
/// @brief Carries exact first and second trial-coordinate variations.
|
||||
struct Jet {
|
||||
double value{};
|
||||
@@ -455,8 +497,11 @@ Jet operator+(const Jet& left, const Jet& right) {
|
||||
result.mixed_second[index] =
|
||||
left.mixed_second[index] + right.mixed_second[index];
|
||||
}
|
||||
const double* left_second = left.second.ReadData();
|
||||
const double* right_second = right.second.ReadData();
|
||||
double* result_second = result.second.OverwriteData();
|
||||
for (std::size_t index = 0U; index < kHessianSize; ++index) {
|
||||
result.second[index] = left.second[index] + right.second[index];
|
||||
result_second[index] = left_second[index] + right_second[index];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -475,8 +520,11 @@ Jet operator-(const Jet& left, const Jet& right) {
|
||||
result.mixed_second[index] =
|
||||
left.mixed_second[index] - right.mixed_second[index];
|
||||
}
|
||||
const double* left_second = left.second.ReadData();
|
||||
const double* right_second = right.second.ReadData();
|
||||
double* result_second = result.second.OverwriteData();
|
||||
for (std::size_t index = 0U; index < kHessianSize; ++index) {
|
||||
result.second[index] = left.second[index] - right.second[index];
|
||||
result_second[index] = left_second[index] - right_second[index];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -506,13 +554,18 @@ Jet operator*(const Jet& left, const Jet& right) {
|
||||
2.0 * left.directional_first * right.mixed_first[index] +
|
||||
left.value * right.mixed_second[index];
|
||||
}
|
||||
const double* left_second = left.second.ReadData();
|
||||
const double* right_second = right.second.ReadData();
|
||||
double* result_second = result.second.OverwriteData();
|
||||
Outer(left.first, right.first, result_second);
|
||||
std::array<double, kHessianSize> reverse;
|
||||
Outer(right.first, left.first, reverse.data());
|
||||
for (std::size_t row = 0U; row < kGlobalDofCount; ++row) {
|
||||
for (std::size_t column = 0U; column < kGlobalDofCount; ++column) {
|
||||
const std::size_t index = row * kGlobalDofCount + column;
|
||||
result.second[index] = left.second[index] * right.value +
|
||||
left.first[row] * right.first[column] +
|
||||
left.first[column] * right.first[row] +
|
||||
left.value * right.second[index];
|
||||
result_second[index] = left_second[index] * right.value +
|
||||
result_second[index] + reverse[index] +
|
||||
left.value * right_second[index];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -529,8 +582,10 @@ Jet operator*(const double scalar, const Jet& value) {
|
||||
result.mixed_first[index] = scalar * value.mixed_first[index];
|
||||
result.mixed_second[index] = scalar * value.mixed_second[index];
|
||||
}
|
||||
const double* value_second = value.second.ReadData();
|
||||
double* result_second = result.second.OverwriteData();
|
||||
for (std::size_t index = 0U; index < kHessianSize; ++index) {
|
||||
result.second[index] = scalar * value.second[index];
|
||||
result_second[index] = scalar * value_second[index];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -559,11 +614,16 @@ Jet Compose(const Jet& input, const double value, const double first,
|
||||
input.first[index] * input.directional_second) +
|
||||
first * input.mixed_second[index];
|
||||
}
|
||||
std::array<double, kGlobalDofCount> scaled_first{};
|
||||
for (std::size_t row = 0U; row < kGlobalDofCount; ++row)
|
||||
scaled_first[row] = second * input.first[row];
|
||||
const double* input_second = input.second.ReadData();
|
||||
double* result_second = result.second.OverwriteData();
|
||||
Outer(scaled_first, input.first, result_second);
|
||||
for (std::size_t row = 0U; row < kGlobalDofCount; ++row) {
|
||||
for (std::size_t column = 0U; column < kGlobalDofCount; ++column) {
|
||||
const std::size_t index = row * kGlobalDofCount + column;
|
||||
result.second[index] = first * input.second[index] +
|
||||
second * input.first[row] * input.first[column];
|
||||
result_second[index] = first * input_second[index] + result_second[index];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -613,19 +673,32 @@ Jet Atan2(const Jet& y, const Jet& x) {
|
||||
const double derivative_yx =
|
||||
(y.value * y.value - x.value * x.value) / radius_fourth;
|
||||
const double derivative_xx = 2.0 * x.value * y.value / radius_fourth;
|
||||
std::array<double, kGlobalDofCount> scaled_y{};
|
||||
std::array<double, kGlobalDofCount> scaled_x{};
|
||||
for (std::size_t i = 0U; i < kGlobalDofCount; ++i) {
|
||||
scaled_y[i] = derivative_yy * y.first[i];
|
||||
scaled_x[i] = derivative_xx * x.first[i];
|
||||
}
|
||||
std::array<std::array<double, kHessianSize>, 4U> products;
|
||||
Outer(scaled_y, y.first, products[0U].data());
|
||||
Outer(y.first, x.first, products[1U].data());
|
||||
Outer(x.first, y.first, products[2U].data());
|
||||
Outer(scaled_x, x.first, products[3U].data());
|
||||
for (std::size_t index = 0U; index < kGlobalDofCount; ++index) {
|
||||
result.first[index] =
|
||||
derivative_y * y.first[index] + derivative_x * x.first[index];
|
||||
}
|
||||
const double* y_second = y.second.ReadData();
|
||||
const double* x_second = x.second.ReadData();
|
||||
double* result_second = result.second.OverwriteData();
|
||||
for (std::size_t row = 0U; row < kGlobalDofCount; ++row) {
|
||||
for (std::size_t column = 0U; column < kGlobalDofCount; ++column) {
|
||||
const std::size_t index = row * kGlobalDofCount + column;
|
||||
result.second[index] = derivative_y * y.second[index] +
|
||||
derivative_x * x.second[index] +
|
||||
derivative_yy * y.first[row] * y.first[column] +
|
||||
derivative_yx * (y.first[row] * x.first[column] +
|
||||
x.first[row] * y.first[column]) +
|
||||
derivative_xx * x.first[row] * x.first[column];
|
||||
result_second[index] =
|
||||
derivative_y * y_second[index] + derivative_x * x_second[index] +
|
||||
products[0U][index] +
|
||||
derivative_yx * (products[1U][index] + products[2U][index]) +
|
||||
products[3U][index];
|
||||
}
|
||||
}
|
||||
// Use a bounded ratio for the directional third derivative only. Retain
|
||||
@@ -1181,10 +1254,14 @@ Jet DrillingEnergy(const std::array<Vector3, kNodeCount>& coordinates,
|
||||
DrillSpinRate(trial.rotation[node], initial_directors[node]) * gamma;
|
||||
}
|
||||
energy = energy + 0.5 * drilling_stiffness * gamma * gamma;
|
||||
std::array<double, kGlobalDofCount> scaled_gradient{};
|
||||
for (std::size_t row = 0U; row < kGlobalDofCount; ++row)
|
||||
scaled_gradient[row] = drilling_stiffness * gamma.first[row];
|
||||
std::array<double, kHessianSize> contribution;
|
||||
Outer(scaled_gradient, gamma.first, contribution.data());
|
||||
for (std::size_t row = 0U; row < kGlobalDofCount; ++row) {
|
||||
for (std::size_t column = 0U; column < kGlobalDofCount; ++column) {
|
||||
(*metric)(row, column) +=
|
||||
drilling_stiffness * gamma.first[row] * gamma.first[column];
|
||||
(*metric)(row, column) += contribution[row * kGlobalDofCount + column];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1505,6 +1582,7 @@ NonlinearMitc4Shell::EvaluateExternalDrillingConstraint(
|
||||
const std::size_t node, const Vector3& phi, const Vector3& phi_dot,
|
||||
const Vector3& force, const Vector3& force_rate,
|
||||
const Vector3& force_acceleration) const {
|
||||
const dense_blas_internal::ScopedLocalDenseThreads local_threads{};
|
||||
if (node >= kNodeCount || !phi_dot.IsFinite() || !force.IsFinite() ||
|
||||
!force_rate.IsFinite() || !force_acceleration.IsFinite()) {
|
||||
return Failure<NonlinearMitc4ExternalDrillingConstraint>(
|
||||
@@ -1594,6 +1672,9 @@ NonlinearMitc4Shell::EvaluateExternalDrillingConstraint(
|
||||
Result<NonlinearMitc4InternalEquilibrium> EvaluateInternalResidualAndTangent(
|
||||
const NonlinearMitc4Shell& element, const NonlinearMitc4TrialDofs& trial,
|
||||
const Vector* coordinate_direction) {
|
||||
// Element-local MKL work uses one worker only when the caller has no TLS
|
||||
// override. The guard restores that request before returning to global solve.
|
||||
const dense_blas_internal::ScopedLocalDenseThreads local_threads{};
|
||||
if (coordinate_direction != nullptr) {
|
||||
if (coordinate_direction->Size() != kGlobalDofCount) {
|
||||
return Failure<NonlinearMitc4InternalEquilibrium>(
|
||||
|
||||
Reference in New Issue
Block a user