Files
eigen/test/visitor.cpp
Pavel Guzenfeld a0e30732a7 Remove trailing semicolon from EIGEN_UNUSED_VARIABLE macro
libeigen/eigen!2301

Closes #3007

Co-authored-by: Pavel Guzenfeld <67074795+PavelGuzenfeld@users.noreply.github.com>
2026-03-21 16:54:13 -07:00

391 lines
14 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2008 Benoit Jacob <jacob.benoit.1@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include "main.h"
template <typename MatrixType>
void matrixVisitor_impl(MatrixType& m) {
typedef typename MatrixType::Scalar Scalar;
Index rows = m.rows();
Index cols = m.cols();
Scalar minc = Scalar(1000), maxc = Scalar(-1000);
Index minrow = 0, mincol = 0, maxrow = 0, maxcol = 0;
for (Index j = 0; j < cols; j++)
for (Index i = 0; i < rows; i++) {
if (m(i, j) < minc) {
minc = m(i, j);
minrow = i;
mincol = j;
}
if (m(i, j) > maxc) {
maxc = m(i, j);
maxrow = i;
maxcol = j;
}
}
Index eigen_minrow, eigen_mincol, eigen_maxrow, eigen_maxcol;
Scalar eigen_minc, eigen_maxc;
eigen_minc = m.minCoeff(&eigen_minrow, &eigen_mincol);
eigen_maxc = m.maxCoeff(&eigen_maxrow, &eigen_maxcol);
VERIFY(minrow == eigen_minrow);
VERIFY(maxrow == eigen_maxrow);
VERIFY(mincol == eigen_mincol);
VERIFY(maxcol == eigen_maxcol);
VERIFY_IS_APPROX(minc, eigen_minc);
VERIFY_IS_APPROX(maxc, eigen_maxc);
VERIFY_IS_APPROX(minc, m.minCoeff());
VERIFY_IS_APPROX(maxc, m.maxCoeff());
eigen_maxc = (m.adjoint() * m).maxCoeff(&eigen_maxrow, &eigen_maxcol);
Index maxrow2 = 0, maxcol2 = 0;
eigen_maxc = (m.adjoint() * m).eval().maxCoeff(&maxrow2, &maxcol2);
VERIFY(maxrow2 == eigen_maxrow);
VERIFY(maxcol2 == eigen_maxcol);
if (!NumTraits<Scalar>::IsInteger && m.size() > 2) {
// Test NaN propagation by replacing an element with NaN.
bool stop = false;
for (Index j = 0; j < cols && !stop; ++j) {
for (Index i = 0; i < rows && !stop; ++i) {
if (!(j == mincol && i == minrow) && !(j == maxcol && i == maxrow)) {
m(i, j) = NumTraits<Scalar>::quiet_NaN();
stop = true;
break;
}
}
}
eigen_minc = m.template minCoeff<PropagateNumbers>(&eigen_minrow, &eigen_mincol);
eigen_maxc = m.template maxCoeff<PropagateNumbers>(&eigen_maxrow, &eigen_maxcol);
VERIFY(minrow == eigen_minrow);
VERIFY(maxrow == eigen_maxrow);
VERIFY(mincol == eigen_mincol);
VERIFY(maxcol == eigen_maxcol);
VERIFY_IS_APPROX(minc, eigen_minc);
VERIFY_IS_APPROX(maxc, eigen_maxc);
VERIFY_IS_APPROX(minc, m.template minCoeff<PropagateNumbers>());
VERIFY_IS_APPROX(maxc, m.template maxCoeff<PropagateNumbers>());
eigen_minc = m.template minCoeff<PropagateNaN>(&eigen_minrow, &eigen_mincol);
eigen_maxc = m.template maxCoeff<PropagateNaN>(&eigen_maxrow, &eigen_maxcol);
VERIFY(minrow != eigen_minrow || mincol != eigen_mincol);
VERIFY(maxrow != eigen_maxrow || maxcol != eigen_maxcol);
VERIFY((numext::isnan)(eigen_minc));
VERIFY((numext::isnan)(eigen_maxc));
// Test matrix of all NaNs.
m.fill(NumTraits<Scalar>::quiet_NaN());
eigen_minc = m.template minCoeff<PropagateNumbers>(&eigen_minrow, &eigen_mincol);
eigen_maxc = m.template maxCoeff<PropagateNumbers>(&eigen_maxrow, &eigen_maxcol);
VERIFY(eigen_minrow == 0);
VERIFY(eigen_maxrow == 0);
VERIFY(eigen_mincol == 0);
VERIFY(eigen_maxcol == 0);
VERIFY((numext::isnan)(eigen_minc));
VERIFY((numext::isnan)(eigen_maxc));
eigen_minc = m.template minCoeff<PropagateNaN>(&eigen_minrow, &eigen_mincol);
eigen_maxc = m.template maxCoeff<PropagateNaN>(&eigen_maxrow, &eigen_maxcol);
VERIFY(eigen_minrow == 0);
VERIFY(eigen_maxrow == 0);
VERIFY(eigen_mincol == 0);
VERIFY(eigen_maxcol == 0);
VERIFY((numext::isnan)(eigen_minc));
VERIFY((numext::isnan)(eigen_maxc));
eigen_minc = m.template minCoeff<PropagateFast>(&eigen_minrow, &eigen_mincol);
eigen_maxc = m.template maxCoeff<PropagateFast>(&eigen_maxrow, &eigen_maxcol);
VERIFY(eigen_minrow == 0);
VERIFY(eigen_maxrow == 0);
VERIFY(eigen_mincol == 0);
VERIFY(eigen_maxcol == 0);
VERIFY((numext::isnan)(eigen_minc));
VERIFY((numext::isnan)(eigen_maxc));
}
}
template <typename MatrixType>
void matrixVisitor(const MatrixType& p) {
MatrixType m(p.rows(), p.cols());
// construct a random matrix where all coefficients are different
m.setRandom();
for (Index i = 0; i < m.size(); i++)
for (Index i2 = 0; i2 < i; i2++)
while (numext::equal_strict(m(i), m(i2))) // yes, strict equality
m(i) = internal::random<typename DenseBase<MatrixType>::Scalar>();
MatrixType n = m;
matrixVisitor_impl(m);
// force outer-inner access pattern
using BlockType = Block<MatrixType, Dynamic, Dynamic>;
BlockType m_block = n.block(0, 0, n.rows(), n.cols());
matrixVisitor_impl(m_block);
}
template <typename VectorType>
void vectorVisitor(const VectorType& w) {
typedef typename VectorType::Scalar Scalar;
Index size = w.size();
// construct a random vector where all coefficients are different
VectorType v;
v = VectorType::Random(size);
for (Index i = 0; i < size; i++)
for (Index i2 = 0; i2 < i; i2++)
while (v(i) == v(i2)) // yes, ==
v(i) = internal::random<Scalar>();
Scalar minc = v(0), maxc = v(0);
Index minidx = 0, maxidx = 0;
for (Index i = 0; i < size; i++) {
if (v(i) < minc) {
minc = v(i);
minidx = i;
}
if (v(i) > maxc) {
maxc = v(i);
maxidx = i;
}
}
Index eigen_minidx, eigen_maxidx;
Scalar eigen_minc, eigen_maxc;
eigen_minc = v.minCoeff(&eigen_minidx);
eigen_maxc = v.maxCoeff(&eigen_maxidx);
VERIFY(minidx == eigen_minidx);
VERIFY(maxidx == eigen_maxidx);
VERIFY_IS_APPROX(minc, eigen_minc);
VERIFY_IS_APPROX(maxc, eigen_maxc);
VERIFY_IS_APPROX(minc, v.minCoeff());
VERIFY_IS_APPROX(maxc, v.maxCoeff());
Index idx0 = internal::random<Index>(0, size - 1);
Index idx1 = eigen_minidx;
Index idx2 = eigen_maxidx;
VectorType v1(v), v2(v);
v1(idx0) = v1(idx1);
v2(idx0) = v2(idx2);
v1.minCoeff(&eigen_minidx);
v2.maxCoeff(&eigen_maxidx);
VERIFY(eigen_minidx == (std::min)(idx0, idx1));
VERIFY(eigen_maxidx == (std::min)(idx0, idx2));
if (!NumTraits<Scalar>::IsInteger && size > 2) {
// Test NaN propagation by replacing an element with NaN.
for (Index i = 0; i < size; ++i) {
if (i != minidx && i != maxidx) {
v(i) = NumTraits<Scalar>::quiet_NaN();
break;
}
}
eigen_minc = v.template minCoeff<PropagateNumbers>(&eigen_minidx);
eigen_maxc = v.template maxCoeff<PropagateNumbers>(&eigen_maxidx);
VERIFY(minidx == eigen_minidx);
VERIFY(maxidx == eigen_maxidx);
VERIFY_IS_APPROX(minc, eigen_minc);
VERIFY_IS_APPROX(maxc, eigen_maxc);
VERIFY_IS_APPROX(minc, v.template minCoeff<PropagateNumbers>());
VERIFY_IS_APPROX(maxc, v.template maxCoeff<PropagateNumbers>());
eigen_minc = v.template minCoeff<PropagateNaN>(&eigen_minidx);
eigen_maxc = v.template maxCoeff<PropagateNaN>(&eigen_maxidx);
VERIFY(minidx != eigen_minidx);
VERIFY(maxidx != eigen_maxidx);
VERIFY((numext::isnan)(eigen_minc));
VERIFY((numext::isnan)(eigen_maxc));
}
}
template <typename Derived, bool Vectorizable>
struct TrackedVisitor {
using Scalar = typename DenseBase<Derived>::Scalar;
static constexpr int PacketSize = Eigen::internal::packet_traits<Scalar>::size;
static constexpr bool RowMajor = Derived::IsRowMajor;
void init(Scalar v, Index i, Index j) { return this->operator()(v, i, j); }
template <typename Packet>
void initpacket(Packet p, Index i, Index j) {
return this->packet(p, i, j);
}
void operator()(Scalar v, Index i, Index j) {
EIGEN_UNUSED_VARIABLE(v);
visited.emplace_back(i, j);
scalarOps++;
}
template <typename Packet>
void packet(Packet p, Index i, Index j) {
EIGEN_UNUSED_VARIABLE(p);
for (int k = 0; k < PacketSize; k++)
if (RowMajor)
visited.emplace_back(i, j + k);
else
visited.emplace_back(i + k, j);
vectorOps++;
}
std::vector<std::pair<Index, Index>> visited;
Index scalarOps = 0;
Index vectorOps = 0;
};
namespace Eigen {
namespace internal {
template <typename T, bool Vectorizable>
struct functor_traits<TrackedVisitor<T, Vectorizable>> {
enum { PacketAccess = Vectorizable, Cost = 1 };
};
} // namespace internal
} // namespace Eigen
template <typename Derived, bool Vectorized>
void checkOptimalTraversal_impl(const DenseBase<Derived>& mat) {
using Scalar = typename DenseBase<Derived>::Scalar;
static constexpr int PacketSize = Eigen::internal::packet_traits<Scalar>::size;
static constexpr bool RowMajor = Derived::IsRowMajor;
Derived X(mat.rows(), mat.cols());
X.setRandom();
TrackedVisitor<Derived, Vectorized> visitor;
visitor.visited.reserve(X.size());
X.visit(visitor);
Index count = 0;
for (Index j = 0; j < X.outerSize(); ++j) {
for (Index i = 0; i < X.innerSize(); ++i) {
Index r = RowMajor ? j : i;
Index c = RowMajor ? i : j;
VERIFY_IS_EQUAL(visitor.visited[count].first, r);
VERIFY_IS_EQUAL(visitor.visited[count].second, c);
++count;
}
}
Index vectorOps = Vectorized ? ((X.innerSize() / PacketSize) * X.outerSize()) : 0;
Index scalarOps = X.size() - (vectorOps * PacketSize);
VERIFY_IS_EQUAL(vectorOps, visitor.vectorOps);
VERIFY_IS_EQUAL(scalarOps, visitor.scalarOps);
}
void checkOptimalTraversal() {
using Scalar = float;
constexpr int PacketSize = Eigen::internal::packet_traits<Scalar>::size;
// use sizes that mix vector and scalar ops
constexpr int Rows = 3 * PacketSize + 1;
constexpr int Cols = 4 * PacketSize + 1;
int rows = internal::random(PacketSize + 1, EIGEN_TEST_MAX_SIZE);
int cols = internal::random(PacketSize + 1, EIGEN_TEST_MAX_SIZE);
using UnrollColMajor = Matrix<Scalar, Rows, Cols, ColMajor>;
using UnrollRowMajor = Matrix<Scalar, Rows, Cols, RowMajor>;
using DynamicColMajor = Matrix<Scalar, Dynamic, Dynamic, ColMajor>;
using DynamicRowMajor = Matrix<Scalar, Dynamic, Dynamic, RowMajor>;
// Scalar-only visitors
checkOptimalTraversal_impl<UnrollColMajor, false>(UnrollColMajor(Rows, Cols));
checkOptimalTraversal_impl<UnrollRowMajor, false>(UnrollRowMajor(Rows, Cols));
checkOptimalTraversal_impl<DynamicColMajor, false>(DynamicColMajor(rows, cols));
checkOptimalTraversal_impl<DynamicRowMajor, false>(DynamicRowMajor(rows, cols));
// Vectorized visitors
checkOptimalTraversal_impl<UnrollColMajor, true>(UnrollColMajor(Rows, Cols));
checkOptimalTraversal_impl<UnrollRowMajor, true>(UnrollRowMajor(Rows, Cols));
checkOptimalTraversal_impl<DynamicColMajor, true>(DynamicColMajor(rows, cols));
checkOptimalTraversal_impl<DynamicRowMajor, true>(DynamicRowMajor(rows, cols));
const Eigen::Array<bool, Eigen::Dynamic, 1> a = Eigen::Array<bool, 2, 1>{false, true};
Eigen::Index i = -1;
VERIFY(!a.minCoeff(&i));
VERIFY(i == 0);
VERIFY(!(!a).minCoeff(&i));
VERIFY(i == 1);
Eigen::Index j = -1;
VERIFY(a.maxCoeff(&j));
VERIFY(j == 1);
VERIFY((!a).maxCoeff(&j));
VERIFY(j == 0);
}
// Test minCoeff/maxCoeff at vectorization boundary sizes.
// Visitor uses LinearVectorizedTraversal with packet-based min/max,
// so we test at sizes around packet multiples.
template <typename Scalar>
void visitor_vec_boundary() {
const Index PS = internal::packet_traits<Scalar>::size;
const Index sizes[] = {1, 2, 3, PS - 1, PS, PS + 1, 2 * PS - 1, 2 * PS, 2 * PS + 1, 4 * PS, 4 * PS + 1};
for (int si = 0; si < 11; ++si) {
const Index n = sizes[si];
if (n <= 0) continue;
typedef Matrix<Scalar, Dynamic, 1> Vec;
Vec v = Vec::Random(n);
// Ensure all elements are distinct.
for (Index i = 0; i < n; ++i)
for (Index j = 0; j < i; ++j)
while (numext::equal_strict(v(i), v(j))) v(i) = internal::random<Scalar>();
// Reference
Scalar ref_min = v(0), ref_max = v(0);
Index ref_minidx = 0, ref_maxidx = 0;
for (Index k = 0; k < n; ++k) {
if (v(k) < ref_min) {
ref_min = v(k);
ref_minidx = k;
}
if (v(k) > ref_max) {
ref_max = v(k);
ref_maxidx = k;
}
}
Index eigen_minidx, eigen_maxidx;
VERIFY_IS_APPROX(v.minCoeff(&eigen_minidx), ref_min);
VERIFY_IS_APPROX(v.maxCoeff(&eigen_maxidx), ref_max);
VERIFY(eigen_minidx == ref_minidx);
VERIFY(eigen_maxidx == ref_maxidx);
// Also test matrix form at this size (exercises different inner/outer sizes).
if (n >= 2) {
typedef Matrix<Scalar, Dynamic, Dynamic> Mat;
// Test as n×1 and 1×n (different inner sizes for visitor traversal).
Mat mc = v;
Mat mr = v.transpose();
Index ri, ci;
VERIFY_IS_APPROX(mc.minCoeff(&ri, &ci), ref_min);
VERIFY(ri == ref_minidx && ci == 0);
VERIFY_IS_APPROX(mr.minCoeff(&ri, &ci), ref_min);
VERIFY(ri == 0 && ci == ref_minidx);
}
}
}
EIGEN_DECLARE_TEST(visitor) {
for (int i = 0; i < g_repeat; i++) {
CALL_SUBTEST_1(matrixVisitor(Matrix<float, 1, 1>()));
CALL_SUBTEST_2(matrixVisitor(Matrix2f()));
CALL_SUBTEST_3(matrixVisitor(Matrix4d()));
CALL_SUBTEST_4(matrixVisitor(MatrixXd(8, 12)));
CALL_SUBTEST_5(matrixVisitor(Matrix<double, Dynamic, Dynamic, RowMajor>(20, 20)));
CALL_SUBTEST_6(matrixVisitor(MatrixXi(8, 12)));
}
for (int i = 0; i < g_repeat; i++) {
CALL_SUBTEST_7(vectorVisitor(Vector4f()));
CALL_SUBTEST_7(vectorVisitor(Matrix<int, 12, 1>()));
CALL_SUBTEST_8(vectorVisitor(VectorXd(10)));
CALL_SUBTEST_9(vectorVisitor(RowVectorXd(10)));
CALL_SUBTEST_10(vectorVisitor(VectorXf(33)));
}
CALL_SUBTEST_11(checkOptimalTraversal());
// Vectorization boundary sizes — deterministic, run once.
CALL_SUBTEST_12(visitor_vec_boundary<float>());
CALL_SUBTEST_12(visitor_vec_boundary<double>());
CALL_SUBTEST_12(visitor_vec_boundary<int>());
}