blob: 9e8b91cdc7db11e3ecf5a2e8b7d0696dcb0f8f94 [file] [edit]
// SPDX-FileCopyrightText: The Eigen Authors
// SPDX-License-Identifier: MPL-2.0
#ifndef EIGEN_SPARSE_FACTORIZE_BENCHMARK_H
#define EIGEN_SPARSE_FACTORIZE_BENCHMARK_H
#include <benchmark/benchmark.h>
#include <Eigen/SparseCore>
#include <contrib/Eigen/SparseExtra>
#include <cstdlib>
#include <fstream>
#include <string>
#include <vector>
template <typename Solver>
void sparseFactorizeBenchmark(benchmark::State& state) {
using Scalar = typename Solver::Scalar;
using RealScalar = typename Eigen::NumTraits<Scalar>::Real;
using SparseMatrix = typename Solver::MatrixType;
using Vector = Eigen::Matrix<Scalar, Eigen::Dynamic, 1>;
const Eigen::Index n = state.range(0), bandwidth = state.range(1);
std::srand(1);
std::vector<Eigen::Triplet<Scalar>> entries;
for (Eigen::Index j = 0; j < n; ++j) {
entries.emplace_back(j, j, Scalar(2 * bandwidth + 1));
for (Eigen::Index i = j + 1; i < (std::min)(n, j + bandwidth + 1); ++i) {
const Scalar value = Eigen::internal::random<Scalar>();
entries.emplace_back(i, j, value);
entries.emplace_back(j, i, Eigen::numext::conj(value));
}
}
SparseMatrix matrix(n, n);
matrix.setFromTriplets(entries.begin(), entries.end());
Solver solver;
solver.compute(matrix);
if (solver.info() != Eigen::Success) {
state.SkipWithError("factorization failed");
return;
}
const Vector rhs = Vector::Ones(n);
const Vector solution = solver.solve(rhs);
// Allow O(n * epsilon) roundoff from factorization and the triangular solves.
const RealScalar bound = RealScalar(32 * n) * Eigen::NumTraits<RealScalar>::epsilon();
if (solver.info() != Eigen::Success || !((matrix * solution - rhs).norm() <= bound * rhs.norm())) {
state.SkipWithError("solve residual exceeds bound");
return;
}
for (auto _ : state) {
solver.factorize(matrix);
benchmark::DoNotOptimize(solver.info());
benchmark::ClobberMemory();
}
}
template <typename Solver, bool RequireSquare = true>
void sparseFactorizeFileBenchmark(benchmark::State& state) {
using Scalar = typename Solver::Scalar;
using RealScalar = typename Eigen::NumTraits<Scalar>::Real;
using SparseMatrix = typename Solver::MatrixType;
using Vector = Eigen::Matrix<Scalar, Eigen::Dynamic, 1>;
const char* filename = std::getenv("EIGEN_BENCHMARK_MATRIX");
if (!filename) {
state.SkipWithError("Set EIGEN_BENCHMARK_MATRIX to a MatrixMarket file");
return;
}
std::ifstream input(filename);
std::string banner, object, format, field, symmetry;
if (!(input >> banner >> object >> format >> field >> symmetry) || banner != "%%MatrixMarket" || object != "matrix" ||
format != "coordinate" || field != "real" || (symmetry != "general" && symmetry != "symmetric")) {
state.SkipWithError("Expected a real coordinate MatrixMarket matrix");
return;
}
SparseMatrix matrix;
if (!Eigen::loadMarket(matrix, filename) || matrix.rows() == 0 || matrix.cols() == 0 ||
((RequireSquare || symmetry == "symmetric") && matrix.rows() != matrix.cols())) {
state.SkipWithError("Matrix loading or shape check failed");
return;
}
// loadMarket reads the stored triangle without expanding MatrixMarket symmetry.
if (symmetry == "symmetric") {
for (Eigen::Index j = 0; j < matrix.outerSize(); ++j) {
for (typename SparseMatrix::InnerIterator it(matrix, j); it; ++it) {
if (it.row() < it.col()) {
state.SkipWithError("Expected lower-triangular storage for a symmetric MatrixMarket matrix");
return;
}
}
}
SparseMatrix full = matrix.template selfadjointView<Eigen::Lower>();
matrix.swap(full);
}
matrix.makeCompressed();
Solver solver;
solver.compute(matrix);
Vector reference(matrix.cols());
for (Eigen::Index i = 0; i < reference.size(); ++i) reference[i] = Scalar((17 * i) % 101 - 50) / Scalar(50);
const Vector rhs = matrix * reference;
const RealScalar matrixNorm = matrix.norm(), rhsNorm = rhs.norm();
// Normwise backward error: ||Ax-b|| / (||A|| ||x|| + ||b||) <= O(n * epsilon).
const RealScalar bound =
RealScalar(64 * (std::max)(matrix.rows(), matrix.cols())) * Eigen::NumTraits<RealScalar>::epsilon();
const auto validate = [&]() {
if (solver.info() != Eigen::Success) return false;
const Vector solution = solver.solve(rhs);
const RealScalar residual = (matrix * solution - rhs).norm();
const RealScalar error = residual / (matrixNorm * solution.norm() + rhsNorm);
state.counters["backward_error"] = error;
state.counters["relative_residual"] = residual / rhsNorm;
state.counters["relative_solution_error"] = (solution - reference).norm() / reference.norm();
state.counters["solution_norm"] = solution.norm();
return solver.info() == Eigen::Success && error <= bound;
};
state.SetLabel(filename);
state.counters["rows"] = double(matrix.rows());
state.counters["cols"] = double(matrix.cols());
state.counters["nnz"] = double(matrix.nonZeros());
if (!validate()) {
state.SkipWithError("Initial factorization or solve validation failed");
return;
}
for (auto _ : state) {
solver.factorize(matrix);
benchmark::DoNotOptimize(solver.info());
benchmark::ClobberMemory();
}
if (!validate()) state.SkipWithError("Repeated factorization or solve validation failed");
}
#endif