perf(shell): route local products through MKL and reduce Hessian storage work
This commit is contained in:
@@ -2,9 +2,11 @@
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <exception>
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -13,6 +15,7 @@
|
||||
#include "fesa/math/matrix.h"
|
||||
#include "fesa/solvers/linear/linear_solver.h"
|
||||
#include "fesa/solvers/linear/tangent_solver.h"
|
||||
#include "math/dense_blas_internal.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace hht_internal {
|
||||
@@ -132,13 +135,13 @@ Status CheckLocalMass(const Domain& domain, const SparseMatrix& matrix,
|
||||
const std::size_t begin, const std::size_t end) {
|
||||
if (begin == end) return Status::Ok();
|
||||
const std::size_t size = end - begin;
|
||||
std::array<std::array<double, 6>, 6> block{};
|
||||
std::array<std::array<double, 6>, 6> lower{};
|
||||
const dense_blas_internal::ScopedLocalDenseThreads local_threads{};
|
||||
Matrix block{size, size};
|
||||
double scale = 0.0;
|
||||
for (std::size_t i = 0; i < size; ++i) {
|
||||
for (std::size_t j = 0; j < size; ++j) {
|
||||
block[i][j] = Entry(matrix, begin + i, begin + j);
|
||||
scale = std::max(scale, std::abs(block[i][j]));
|
||||
block(i, j) = Entry(matrix, begin + i, begin + j);
|
||||
scale = std::max(scale, std::abs(block(i, j)));
|
||||
}
|
||||
}
|
||||
if (!std::isfinite(scale) || !(scale > 0.0)) {
|
||||
@@ -149,36 +152,34 @@ Status CheckLocalMass(const Domain& domain, const SparseMatrix& matrix,
|
||||
for (std::size_t i = 0; i < size; ++i) {
|
||||
double sum = 0.0;
|
||||
for (std::size_t j = 0; j < size; ++j) {
|
||||
block[i][j] /= scale;
|
||||
sum += std::abs(block[i][j]);
|
||||
block(i, j) /= scale;
|
||||
sum += std::abs(block(i, j));
|
||||
}
|
||||
norm = std::max(norm, sum);
|
||||
for (std::size_t j = 0; j <= i; ++j) {
|
||||
double value = block[i][j];
|
||||
for (std::size_t k = 0; k < j; ++k) value -= lower[i][k] * lower[j][k];
|
||||
if (!std::isfinite(value) || (i == j && !(value > 0.0))) {
|
||||
return Failure(
|
||||
domain, "dynamic-positive-mass",
|
||||
"Physical mass requires strictly positive Cholesky pivots.");
|
||||
}
|
||||
lower[i][j] = i == j ? std::sqrt(value) : value / lower[j][j];
|
||||
}
|
||||
}
|
||||
Matrix lower{size, size};
|
||||
Matrix inverse_lower{size, size};
|
||||
auto factor_failure = dense_blas_internal::CholeskyFailure::kNone;
|
||||
try {
|
||||
if (!dense_blas_internal::CholeskyLowerAndInverse(
|
||||
block, lower, inverse_lower, &factor_failure)) {
|
||||
if (factor_failure == dense_blas_internal::CholeskyFailure::kInverse)
|
||||
return Failure(
|
||||
domain, "dynamic-positive-mass-conditioning",
|
||||
"A nodal free physical block reciprocal condition is below 2^-32.");
|
||||
return Failure(
|
||||
domain, "dynamic-positive-mass",
|
||||
"Physical mass requires strictly positive Cholesky pivots.");
|
||||
}
|
||||
} catch (const std::exception& error) {
|
||||
return Failure(domain, "dynamic-positive-mass", error.what());
|
||||
}
|
||||
const Matrix inverse =
|
||||
dense_blas_internal::MultiplyTransposeLeft(inverse_lower, inverse_lower);
|
||||
std::array<double, 6> sums{};
|
||||
for (std::size_t column = 0; column < size; ++column) {
|
||||
std::array<double, 6> solution{};
|
||||
for (std::size_t i = 0; i < size; ++i) {
|
||||
double value = i == column ? 1.0 : 0.0;
|
||||
for (std::size_t k = 0; k < i; ++k) value -= lower[i][k] * solution[k];
|
||||
solution[i] = value / lower[i][i];
|
||||
}
|
||||
for (std::size_t reverse = size; reverse > 0U; --reverse) {
|
||||
const std::size_t i = reverse - 1U;
|
||||
double value = solution[i];
|
||||
for (std::size_t k = i + 1U; k < size; ++k)
|
||||
value -= lower[k][i] * solution[k];
|
||||
solution[i] = value / lower[i][i];
|
||||
sums[i] += std::abs(solution[i]);
|
||||
sums[i] += std::abs(inverse(i, column));
|
||||
}
|
||||
}
|
||||
const double inverse_norm = *std::max_element(sums.begin(), sums.end());
|
||||
@@ -212,19 +213,33 @@ Result<PhysicalSpace> BuildPhysicalSpace(
|
||||
double symmetry_error = 0.0;
|
||||
double null_error = 0.0;
|
||||
if (mass_scale > 0.0) {
|
||||
Vector normalized_mass{mass.Values().size()};
|
||||
Vector null_coefficients{mass.Values().size()};
|
||||
for (std::size_t index = 0U; index < mass.Values().size(); ++index) {
|
||||
normalized_mass[index] = mass.Values()[index] / mass_scale;
|
||||
const auto full = dofs.FreeDofs()[mass.ColumnIndices()[index]];
|
||||
if (full % 6U >= 3U && node_null[full / 6U] != no_null)
|
||||
null_coefficients[index] =
|
||||
assembly.rotations[full / 6U].drill_null[full % 6U - 3U];
|
||||
}
|
||||
Vector null_products{0U};
|
||||
{
|
||||
const dense_blas_internal::ScopedLocalDenseThreads local_threads{};
|
||||
null_products = dense_blas_internal::MultiplyDiagonal(normalized_mass,
|
||||
null_coefficients);
|
||||
}
|
||||
for (std::size_t row = 0; row < mass.Rows(); ++row) {
|
||||
std::vector<double> action(null_nodes.size(), 0.0);
|
||||
for (std::size_t index = mass.RowOffsets()[row];
|
||||
index < mass.RowOffsets()[row + 1U]; ++index) {
|
||||
const std::size_t column = mass.ColumnIndices()[index];
|
||||
const double entry = mass.Values()[index] / mass_scale;
|
||||
const double entry = normalized_mass[index];
|
||||
mass_norm = std::hypot(mass_norm, entry);
|
||||
symmetry_error = std::hypot(
|
||||
symmetry_error, entry - Entry(mass, column, row) / mass_scale);
|
||||
const std::size_t full = dofs.FreeDofs()[column];
|
||||
if (full % 6U >= 3U && node_null[full / 6U] != no_null) {
|
||||
action[node_null[full / 6U]] +=
|
||||
entry * assembly.rotations[full / 6U].drill_null[full % 6U - 3U];
|
||||
action[node_null[full / 6U]] += null_products[index];
|
||||
}
|
||||
}
|
||||
for (const double value : action)
|
||||
@@ -291,6 +306,7 @@ Result<PhysicalSpace> BuildPhysicalSpace(
|
||||
}
|
||||
}
|
||||
std::vector<CooContribution> entries;
|
||||
std::vector<std::pair<double, double>> mass_factors;
|
||||
node_starts.push_back(dimension);
|
||||
double complement_gram_error = 0.0;
|
||||
double null_gram_error = 0.0;
|
||||
@@ -350,8 +366,10 @@ Result<PhysicalSpace> BuildPhysicalSpace(
|
||||
domain, "dynamic-invalid-analytic-null",
|
||||
"The scaled physical and analytic null Gram identities failed."));
|
||||
}
|
||||
for (std::size_t i = 0; i < dimension; ++i)
|
||||
for (std::size_t i = 0; i < dimension; ++i) {
|
||||
entries.push_back({i, i, 0.0, 0U, entries.size()});
|
||||
mass_factors.push_back({0.0, 0.0});
|
||||
}
|
||||
for (std::size_t row = 0; row < mass.Rows(); ++row) {
|
||||
const auto& left = rows[dofs.FreeDofs()[row]];
|
||||
for (std::size_t index = mass.RowOffsets()[row];
|
||||
@@ -359,13 +377,54 @@ Result<PhysicalSpace> BuildPhysicalSpace(
|
||||
const auto& right = rows[dofs.FreeDofs()[mass.ColumnIndices()[index]]];
|
||||
for (const auto& a : left) {
|
||||
for (const auto& b : right) {
|
||||
entries.push_back({a.first, b.first,
|
||||
a.second * mass.Values()[index] * b.second, 0U,
|
||||
entries.size()});
|
||||
entries.push_back({a.first, b.first, a.second, 0U, entries.size()});
|
||||
mass_factors.push_back({mass.Values()[index], b.second});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vector coefficients{entries.size()};
|
||||
Vector mass_values{entries.size()};
|
||||
Vector right_values{entries.size()};
|
||||
for (std::size_t i = 0U; i < entries.size(); ++i) {
|
||||
coefficients[i] = entries[i].value;
|
||||
mass_values[i] = mass_factors[i].first;
|
||||
right_values[i] = mass_factors[i].second;
|
||||
}
|
||||
Vector projected{0U};
|
||||
{
|
||||
const dense_blas_internal::ScopedLocalDenseThreads local_threads{};
|
||||
projected =
|
||||
dense_blas_internal::MultiplyDiagonal(coefficients, mass_values);
|
||||
bool finite = true;
|
||||
for (std::size_t i = 0U; i < projected.Size(); ++i) {
|
||||
entries[i].value = projected[i];
|
||||
finite = finite && std::isfinite(projected[i]);
|
||||
}
|
||||
// A rejected intermediate cannot produce a finite original contribution.
|
||||
// Still compute the remaining finite entries so an earlier final-product
|
||||
// overflow keeps its diagnostic priority. No invalid operand reaches MKL.
|
||||
if (!finite) {
|
||||
std::vector<std::size_t> valid_positions;
|
||||
for (std::size_t i = 0U; i < projected.Size(); ++i)
|
||||
if (std::isfinite(projected[i])) valid_positions.push_back(i);
|
||||
Vector valid_left{valid_positions.size()};
|
||||
Vector valid_right{valid_positions.size()};
|
||||
for (std::size_t i = 0U; i < valid_positions.size(); ++i) {
|
||||
valid_left[i] = projected[valid_positions[i]];
|
||||
valid_right[i] = right_values[valid_positions[i]];
|
||||
}
|
||||
const Vector valid_products =
|
||||
dense_blas_internal::MultiplyDiagonal(valid_left, valid_right);
|
||||
for (std::size_t i = 0U; i < valid_positions.size(); ++i)
|
||||
entries[valid_positions[i]].value = valid_products[i];
|
||||
auto rejected = MakeSparse(dimension, dimension, std::move(entries));
|
||||
return Result<PhysicalSpace>::Failure(rejected.GetStatus());
|
||||
}
|
||||
projected = dense_blas_internal::MultiplyDiagonal(projected, right_values);
|
||||
}
|
||||
for (std::size_t i = 0U; i < entries.size(); ++i)
|
||||
entries[i].value = projected[i];
|
||||
auto block = MakeSparse(dimension, dimension, std::move(entries));
|
||||
if (!block.HasValue())
|
||||
return Result<PhysicalSpace>::Failure(block.GetStatus());
|
||||
@@ -436,15 +495,20 @@ Result<Regularity> CheckRegularity(const Domain& domain,
|
||||
for (std::size_t i = 0; i < nodes.size(); ++i) {
|
||||
node_null[nodes[i]] = i;
|
||||
const auto& n = assembly.rotations[nodes[i]].drill_null;
|
||||
double stiffness = 0.0;
|
||||
Matrix metric{3U, 3U};
|
||||
Vector direction{3U};
|
||||
for (std::size_t a = 0; a < 3; ++a) {
|
||||
direction[a] = n[a];
|
||||
for (std::size_t b = 0; b < 3; ++b) {
|
||||
stiffness += n[a] *
|
||||
Entry(assembly.drilling_metric, 6U * nodes[i] + 3U + a,
|
||||
6U * nodes[i] + 3U + b) *
|
||||
n[b];
|
||||
metric(a, b) = Entry(assembly.drilling_metric, 6U * nodes[i] + 3U + a,
|
||||
6U * nodes[i] + 3U + b);
|
||||
}
|
||||
}
|
||||
double stiffness = 0.0;
|
||||
{
|
||||
const dense_blas_internal::ScopedLocalDenseThreads local_threads{};
|
||||
stiffness = direction.Dot(metric.Multiply(direction));
|
||||
}
|
||||
if (!std::isfinite(stiffness) || !(stiffness > 0.0)) {
|
||||
return Result<Regularity>::Failure(
|
||||
Failure(domain, "dynamic-algebraic-regularity",
|
||||
@@ -454,22 +518,37 @@ Result<Regularity> CheckRegularity(const Domain& domain,
|
||||
weights[i] = std::sqrt(stiffness);
|
||||
}
|
||||
std::vector<CooContribution> entries;
|
||||
std::vector<double> null_coefficients;
|
||||
for (std::size_t row = 0; row < nodes.size(); ++row) {
|
||||
entries.push_back({row, row, 0.0, 0U, entries.size()});
|
||||
null_coefficients.push_back(0.0);
|
||||
for (std::size_t index = gradient.RowOffsets()[row];
|
||||
index < gradient.RowOffsets()[row + 1U]; ++index) {
|
||||
const std::size_t full = gradient.ColumnIndices()[index];
|
||||
if (full % 6U >= 3U && node_null[full / 6U] != no_null) {
|
||||
const std::size_t column = node_null[full / 6U];
|
||||
entries.push_back(
|
||||
{row, column,
|
||||
gradient.Values()[index] *
|
||||
assembly.rotations[full / 6U].drill_null[full % 6U - 3U] /
|
||||
weights[row] / weights[column],
|
||||
0U, entries.size()});
|
||||
{row, column, gradient.Values()[index], 0U, entries.size()});
|
||||
null_coefficients.push_back(
|
||||
assembly.rotations[full / 6U].drill_null[full % 6U - 3U]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vector gradient_values{entries.size()};
|
||||
Vector null_values{entries.size()};
|
||||
for (std::size_t i = 0U; i < entries.size(); ++i) {
|
||||
gradient_values[i] = entries[i].value;
|
||||
null_values[i] = null_coefficients[i];
|
||||
}
|
||||
Vector products{0U};
|
||||
{
|
||||
const dense_blas_internal::ScopedLocalDenseThreads local_threads{};
|
||||
products =
|
||||
dense_blas_internal::MultiplyDiagonal(gradient_values, null_values);
|
||||
}
|
||||
for (std::size_t i = 0U; i < entries.size(); ++i)
|
||||
entries[i].value =
|
||||
products[i] / weights[entries[i].row] / weights[entries[i].column];
|
||||
auto matrix = MakeSparse(nodes.size(), nodes.size(), std::move(entries));
|
||||
if (!matrix.HasValue())
|
||||
return Result<Regularity>::Failure(matrix.GetStatus());
|
||||
|
||||
Reference in New Issue
Block a user