perf(math): route ordinary products through MKL and preserve arithmetic

This commit is contained in:
KOKO\Mimi
2026-10-14 15:09:53 +09:00
parent b4fc1bb54b
commit 59c8e93b07
18 changed files with 793 additions and 169 deletions
@@ -12,11 +12,13 @@
#include <new>
#include <numeric>
#include <sstream>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include "fesa/math/sparse_matrix.h"
#include "fesa/fem/dof_manager.h"
#include "fesa/math/vector.h"
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
#include "solvers/eigen/mkl_feast_eigen_solver_test_seam.h"
@@ -186,6 +188,37 @@ Result<Csr> CopyUpperOneBased(const SparseMatrix& matrix, const double scale) {
return Result<Csr>::Success(std::move(copy));
}
/// @brief Keeps the independently scaled full or upper-mirrored operator.
Result<SparseMatrix> ScaledActionMatrix(const SparseMatrix& matrix,
const double scale,
const bool mirror_upper = false) {
std::vector<CooContribution> contributions;
std::vector<std::vector<std::size_t>> columns(matrix.Rows());
for (std::size_t row = 0; row < matrix.Rows(); ++row) {
for (auto position = matrix.RowOffsets()[row];
position < matrix.RowOffsets()[row + 1U]; ++position) {
const auto column = matrix.ColumnIndices()[position];
if (mirror_upper && column < row) continue;
const auto value = matrix.Values()[position] / scale;
columns[row].push_back(column);
contributions.push_back({row, column, value, row, position});
if (mirror_upper && column != row) {
columns[column].push_back(row);
contributions.push_back({column, row, value, row, position});
}
}
}
SparsePattern pattern{{0U}, {}};
for (auto& row : columns) {
std::sort(row.begin(), row.end());
pattern.column_indices.insert(pattern.column_indices.end(), row.begin(),
row.end());
pattern.row_offsets.push_back(pattern.column_indices.size());
}
return SparseMatrix::FromCoo(matrix.Rows(), matrix.Columns(),
std::move(contributions), pattern);
}
Result<Csr> IdentityOneBased(const std::size_t size) {
if (!FitsMkl(size) || !FitsMkl(size + 1U)) {
return Result<Csr>::Failure(
@@ -321,13 +354,11 @@ bool FillInitialBasis(std::vector<double>& vectors, const std::size_t n,
}
for (double& value : reflector) value /= reflector_norm;
for (std::size_t target = column; target < columns; ++target) {
double dot = 0.0;
for (std::size_t row = column; row < n; ++row) {
dot += reflector[row - column] * vectors[row + target * n];
}
for (std::size_t row = column; row < n; ++row) {
vectors[row + target * n] -= 2.0 * reflector[row - column] * dot;
}
const auto length = static_cast<MKL_INT>(n - column);
auto* target_column = vectors.data() + column + target * n;
const double dot = cblas_ddot(length, reflector.data(), 1,
target_column, 1);
cblas_daxpy(length, -2.0 * dot, reflector.data(), 1, target_column, 1);
}
reflectors.push_back(std::move(reflector));
}
@@ -338,13 +369,11 @@ bool FillInitialBasis(std::vector<double>& vectors, const std::size_t n,
const std::size_t column = index - 1U;
const auto& reflector = reflectors[column];
for (std::size_t target = 0U; target < columns; ++target) {
double dot = 0.0;
for (std::size_t row = column; row < n; ++row) {
dot += reflector[row - column] * vectors[row + target * n];
}
for (std::size_t row = column; row < n; ++row) {
vectors[row + target * n] -= 2.0 * reflector[row - column] * dot;
}
const auto length = static_cast<MKL_INT>(n - column);
auto* target_column = vectors.data() + column + target * n;
const double dot = cblas_ddot(length, reflector.data(), 1,
target_column, 1);
cblas_daxpy(length, -2.0 * dot, reflector.data(), 1, target_column, 1);
}
}
for (std::size_t column = 0U; column < columns; ++column)
@@ -353,10 +382,9 @@ bool FillInitialBasis(std::vector<double>& vectors, const std::size_t n,
double gram_error = 0.0;
for (std::size_t left = 0U; left < columns; ++left) {
for (std::size_t right = 0U; right < columns; ++right) {
double dot = 0.0;
for (std::size_t row = 0U; row < n; ++row) {
dot += vectors[row + left * n] * vectors[row + right * n];
}
const double dot = cblas_ddot(static_cast<MKL_INT>(n),
vectors.data() + left * n, 1,
vectors.data() + right * n, 1);
gram_error = std::hypot(gram_error, dot - (left == right ? 1.0 : 0.0));
}
}
@@ -390,7 +418,7 @@ Result<FeastEigenResult> MklFeastEigenSolver::Solve(
Result<FeastEigenResult> MklFeastEigenSolver::SolveImpl(
const SparseMatrix& whitened_operator, const SparseMatrix* mass,
const std::size_t requested_modes, const double zero_operator_scale,
const double mass_lower_bound) const {
const double mass_lower_bound) const try {
static_assert(sizeof(MKL_INT) == 8U,
"FEAST requires the installed ILP64 ABI.");
const Status csr_status = whitened_operator.Validate();
@@ -472,6 +500,9 @@ Result<FeastEigenResult> MklFeastEigenSolver::SolveImpl(
auto b =
mass != nullptr ? CopyUpperOneBased(*mass, 1.0) : IdentityOneBased(n);
if (!b.HasValue()) return Result<FeastEigenResult>::Failure(b.GetStatus());
auto action_matrix = ScaledActionMatrix(whitened_operator, operator_scale);
if (!action_matrix.HasValue())
return Result<FeastEigenResult>::Failure(action_matrix.GetStatus());
double lower = -1.0;
double upper = 1.0;
@@ -1045,13 +1076,11 @@ Result<FeastEigenResult> MklFeastEigenSolver::SolveImpl(
"eigen-endpoint-separation", std::to_string(input),
"A returned FEAST eigenvalue is too close to a solve endpoint."));
}
double norm = 0.0;
Vector raw(n);
for (std::size_t row = 0U; row < n; ++row)
norm += vectors[row + input * n] * vectors[row + input * n];
raw[row] = vectors[row + input * n];
double norm = raw.Dot(raw);
if (mass != nullptr) {
Vector raw(n);
for (std::size_t row = 0U; row < n; ++row)
raw[row] = vectors[row + input * n];
norm = raw.Dot(mass->Multiply(raw));
}
norm = std::sqrt(norm);
@@ -1060,21 +1089,10 @@ Result<FeastEigenResult> MklFeastEigenSolver::SolveImpl(
TraceFailure("eigenpair-validation-failed", std::to_string(input),
"FEAST returned a non-normalizable eigenvector."));
double residual_norm = 0.0;
const auto action = action_matrix.Value().Multiply(raw);
const auto mass_action = mass != nullptr ? mass->Multiply(raw) : raw;
for (std::size_t row = 0U; row < n; ++row) {
double ax = 0.0;
for (std::size_t p = offsets[row]; p < offsets[row + 1U]; ++p) {
ax += values[p] / operator_scale *
vectors[whitened_operator.ColumnIndices()[p] + input * n];
}
double mx = vectors[row + input * n];
if (mass != nullptr) {
mx = 0.0;
for (std::size_t p = mass->RowOffsets()[row];
p < mass->RowOffsets()[row + 1U]; ++p)
mx +=
mass->Values()[p] * vectors[mass->ColumnIndices()[p] + input * n];
}
const double difference = ax - mu * mx;
const double difference = action[row] - mu * mass_action[row];
residual_norm += difference * difference;
ordered_vectors(row, output) = vectors[row + input * n] / norm;
}
@@ -1109,20 +1127,11 @@ Result<FeastEigenResult> MklFeastEigenSolver::SolveImpl(
FormatDouble(rho_b) + ", mu=" + FormatDouble(mu) +
"; calls=" + CallHistory(calls)));
const FeastBackendCall& last_call = calls.back();
std::vector<double> mirrored_action(n, 0.0);
std::vector<double> full_action(n, 0.0);
for (std::size_t row = 0U; row < n; ++row) {
for (std::size_t p = offsets[row]; p < offsets[row + 1U]; ++p) {
const std::size_t column = whitened_operator.ColumnIndices()[p];
const double entry = values[p] / operator_scale;
full_action[row] += entry * vectors[column + input * n];
if (column < row) continue;
mirrored_action[row] += entry * vectors[column + input * n];
if (column != row) {
mirrored_action[column] += entry * vectors[row + input * n];
}
}
}
auto mirrored_matrix =
ScaledActionMatrix(whitened_operator, operator_scale, true);
if (!mirrored_matrix.HasValue())
return Result<FeastEigenResult>::Failure(mirrored_matrix.GetStatus());
const auto mirrored_action = mirrored_matrix.Value().Multiply(raw);
double mirrored_residual_squared = 0.0;
double action_difference_squared = 0.0;
for (std::size_t row = 0U; row < n; ++row) {
@@ -1130,7 +1139,7 @@ Result<FeastEigenResult> MklFeastEigenSolver::SolveImpl(
mirrored_action[row] - mu * vectors[row + input * n];
mirrored_residual_squared += mirrored_residual * mirrored_residual;
const double action_difference =
full_action[row] - mirrored_action[row];
action[row] - mirrored_action[row];
action_difference_squared += action_difference * action_difference;
}
const double rho_b_upper = std::sqrt(mirrored_residual_squared) / norm;
@@ -1174,22 +1183,20 @@ Result<FeastEigenResult> MklFeastEigenSolver::SolveImpl(
}
double gram_error = 0.0;
double gram_squared = 0.0;
std::vector<Vector> mass_modes;
mass_modes.reserve(count);
for (std::size_t right = 0; right < count; ++right) {
Vector mode(n);
for (std::size_t row = 0; row < n; ++row)
mode[row] = ordered_vectors(row, right);
mass_modes.push_back(mass != nullptr ? mass->Multiply(mode)
: std::move(mode));
}
for (std::size_t left = 0U; left < count; ++left) {
for (std::size_t right = 0U; right < count; ++right) {
double dot = 0.0;
for (std::size_t row = 0U; row < n; ++row)
dot += ordered_vectors(row, left) * ordered_vectors(row, right);
if (mass != nullptr) {
dot = 0.0;
for (std::size_t row = 0U; row < n; ++row) {
double action = 0.0;
for (std::size_t p = mass->RowOffsets()[row];
p < mass->RowOffsets()[row + 1U]; ++p)
action += mass->Values()[p] *
ordered_vectors(mass->ColumnIndices()[p], right);
dot += ordered_vectors(row, left) * action;
}
}
dot += ordered_vectors(row, left) * mass_modes[right][row];
const double expected = left == right ? 1.0 : 0.0;
const double difference = std::abs(dot - expected);
gram_squared += difference * difference;
@@ -1233,6 +1240,14 @@ Result<FeastEigenResult> MklFeastEigenSolver::SolveImpl(
solve_upper,
upper_search_termination};
return Result<FeastEigenResult>::Success(std::move(result));
} catch (const std::length_error&) {
return Result<FeastEigenResult>::Failure(
Failure("feast-integer-overflow", "independent-action",
"Independent sparse action exceeds the backend range."));
} catch (const std::runtime_error&) {
return Result<FeastEigenResult>::Failure(
Failure("eigen-multiplication-failed", "independent-action",
"Independent sparse action failed."));
}
} // namespace fesa