blob: db3e2c815f553b277a57e4790a486416e45146da [file]
// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2025 The Eigen Authors
//
// 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/.
// SPDX-License-Identifier: MPL-2.0
#include "main.h"
// wrapper that disables array-oriented access to real and imaginary components
struct TestComplex : public std::complex<float> {
TestComplex() = default;
TestComplex(std::complex<float> x) : std::complex<float>(x){};
TestComplex(float x) : std::complex<float>(x){};
};
template <>
struct Eigen::NumTraits<TestComplex> : Eigen::NumTraits<std::complex<float>> {};
template <>
struct Eigen::internal::random_impl<TestComplex> : Eigen::internal::random_impl<std::complex<float>> {};
template <typename T>
void test_realview_readonly(const T&) {
using Scalar = typename T::Scalar;
using RealScalar = typename NumTraits<Scalar>::Real;
constexpr Index minRows = T::RowsAtCompileTime == Dynamic ? 1 : T::RowsAtCompileTime;
constexpr Index maxRows = T::MaxRowsAtCompileTime == Dynamic ? (EIGEN_TEST_MAX_SIZE / 2) : T::MaxRowsAtCompileTime;
constexpr Index minCols = T::ColsAtCompileTime == Dynamic ? 1 : T::ColsAtCompileTime;
constexpr Index maxCols = T::MaxColsAtCompileTime == Dynamic ? (EIGEN_TEST_MAX_SIZE / 2) : T::MaxColsAtCompileTime;
constexpr Index rowFactor = (NumTraits<Scalar>::IsComplex && !T::IsRowMajor) ? 2 : 1;
constexpr Index colFactor = (NumTraits<Scalar>::IsComplex && T::IsRowMajor) ? 2 : 1;
constexpr Index sizeFactor = NumTraits<Scalar>::IsComplex ? 2 : 1;
Index rows = internal::random<Index>(minRows, maxRows);
Index cols = internal::random<Index>(minCols, maxCols);
T A(rows, cols), B(rows, cols);
VERIFY(A.realView().rows() == rowFactor * A.rows());
VERIFY(A.realView().cols() == colFactor * A.cols());
VERIFY(A.realView().size() == sizeFactor * A.size());
A.setRandom();
VERIFY_IS_APPROX(A.matrix().cwiseAbs2().sum(), A.realView().matrix().cwiseAbs2().sum());
for (Index col = 0; col < cols; ++col) {
VERIFY_IS_APPROX(A.colwise().squaredNorm()(col), A.col(col).matrix().squaredNorm());
}
for (Index row = 0; row < rows; ++row) {
VERIFY_IS_APPROX(A.rowwise().squaredNorm()(row), A.row(row).matrix().squaredNorm());
}
RealScalar alpha = internal::random(RealScalar(1), RealScalar(2));
// B = A * alpha
for (Index r = 0; r < rows; r++) {
for (Index c = 0; c < cols; c++) {
B.coeffRef(r, c) = A.coeff(r, c) * Scalar(alpha);
}
}
VERIFY_IS_CWISE_APPROX(B.realView(), A.realView() * alpha);
// B = A / alpha
for (Index r = 0; r < rows; r++) {
for (Index c = 0; c < cols; c++) {
B.coeffRef(r, c) = A.coeff(r, c) / Scalar(alpha);
}
}
VERIFY_IS_CWISE_APPROX(B.realView(), A.realView() / alpha);
}
template <typename T>
void test_realview(const T&) {
using Scalar = typename T::Scalar;
using RealScalar = typename NumTraits<Scalar>::Real;
constexpr Index minRows = T::RowsAtCompileTime == Dynamic ? 1 : T::RowsAtCompileTime;
constexpr Index maxRows = T::MaxRowsAtCompileTime == Dynamic ? (EIGEN_TEST_MAX_SIZE / 2) : T::MaxRowsAtCompileTime;
constexpr Index minCols = T::ColsAtCompileTime == Dynamic ? 1 : T::ColsAtCompileTime;
constexpr Index maxCols = T::MaxColsAtCompileTime == Dynamic ? (EIGEN_TEST_MAX_SIZE / 2) : T::MaxColsAtCompileTime;
constexpr Index rowFactor = (NumTraits<Scalar>::IsComplex && !T::IsRowMajor) ? 2 : 1;
constexpr Index colFactor = (NumTraits<Scalar>::IsComplex && T::IsRowMajor) ? 2 : 1;
constexpr Index sizeFactor = NumTraits<Scalar>::IsComplex ? 2 : 1;
const Index rows = internal::random<Index>(minRows, maxRows);
const Index cols = internal::random<Index>(minCols, maxCols);
const Index realViewRows = rowFactor * rows;
const Index realViewCols = colFactor * cols;
const T A = T::Random(rows, cols);
T B;
VERIFY_IS_EQUAL(A.realView().rows(), rowFactor * A.rows());
VERIFY_IS_EQUAL(A.realView().cols(), colFactor * A.cols());
VERIFY_IS_EQUAL(A.realView().size(), sizeFactor * A.size());
VERIFY_IS_APPROX(A.matrix().cwiseAbs2().sum(), A.realView().matrix().cwiseAbs2().sum());
// test re-sizing realView during assignment
B.realView() = A.realView();
VERIFY_IS_APPROX(A, B);
VERIFY_IS_APPROX(A.realView(), B.realView());
const RealScalar alpha = internal::random(RealScalar(1), RealScalar(2));
// B = A * alpha
for (Index r = 0; r < rows; r++) {
for (Index c = 0; c < cols; c++) {
B.coeffRef(r, c) = A.coeff(r, c) * Scalar(alpha);
}
}
VERIFY_IS_APPROX(B.realView(), A.realView() * alpha);
B = A;
B.realView() *= alpha;
VERIFY_IS_APPROX(B.realView(), A.realView() * alpha);
// B = A / alpha
for (Index r = 0; r < rows; r++) {
for (Index c = 0; c < cols; c++) {
B.coeffRef(r, c) = A.coeff(r, c) / Scalar(alpha);
}
}
VERIFY_IS_APPROX(B.realView(), A.realView() / alpha);
B = A;
B.realView() /= alpha;
VERIFY_IS_APPROX(B.realView(), A.realView() / alpha);
// force some usual access patterns
Index malloc_size = (rows * cols * sizeof(Scalar)) + sizeof(RealScalar);
void* data1 = internal::aligned_malloc(malloc_size);
void* data2 = internal::aligned_malloc(malloc_size);
Scalar* ptr1 = static_cast<Scalar*>(static_cast<void*>(static_cast<uint8_t*>(data1) + sizeof(RealScalar)));
Scalar* ptr2 = static_cast<Scalar*>(static_cast<void*>(static_cast<uint8_t*>(data2) + sizeof(RealScalar)));
Map<T> C(ptr1, rows, cols), D(ptr2, rows, cols);
C.setRandom();
D.setRandom();
for (Index r = 0; r < realViewRows; r++) {
for (Index c = 0; c < realViewCols; c++) {
C.realView().coeffRef(r, c) = D.realView().coeff(r, c);
}
}
VERIFY_IS_CWISE_EQUAL(C, D);
// swap() through the views: these maps start at an odd real offset, and a reversed view has no direct access.
C.setRandom();
T C0 = C, D0 = D;
C.realView().swap(D.realView());
VERIFY_IS_CWISE_EQUAL(C, D0);
VERIFY_IS_CWISE_EQUAL(D, C0);
C.reverse().realView().swap(D.realView());
VERIFY_IS_CWISE_EQUAL(C, T(C0.reverse()));
VERIFY_IS_CWISE_EQUAL(D, T(D0.reverse()));
C = A;
for (Index c = 0; c < realViewCols - 1; c++) {
B.realView().row(0).coeffRef(realViewCols - 1 - c) = C.realView().row(0).coeff(c + 1);
}
D.realView().row(0).tail(realViewCols - 1) = C.realView().row(0).tail(realViewCols - 1).reverse();
VERIFY_IS_CWISE_EQUAL(B.realView().row(0).tail(realViewCols - 1), D.realView().row(0).tail(realViewCols - 1));
for (Index r = 0; r < realViewRows - 1; r++) {
B.realView().col(0).coeffRef(realViewRows - 1 - r) = C.realView().col(0).coeff(r + 1);
}
D.realView().col(0).tail(realViewRows - 1) = C.realView().col(0).tail(realViewRows - 1).reverse();
VERIFY_IS_CWISE_EQUAL(B.realView().col(0).tail(realViewRows - 1), D.realView().col(0).tail(realViewRows - 1));
}
template <typename ComplexScalar, bool Enable = internal::packet_traits<ComplexScalar>::Vectorizable>
struct test_edge_cases_impl {
static void run() {
using namespace internal;
using RealScalar = typename NumTraits<ComplexScalar>::Real;
using ComplexPacket = typename packet_traits<ComplexScalar>::type;
using RealPacket = typename unpacket_traits<ComplexPacket>::as_real;
constexpr int ComplexSize = unpacket_traits<ComplexPacket>::size;
constexpr int RealSize = 2 * ComplexSize;
VectorX<ComplexScalar> a_data(2 * ComplexSize);
Map<const VectorX<RealScalar>> a_data_asreal(reinterpret_cast<const RealScalar*>(a_data.data()), 2 * a_data.size());
VectorX<RealScalar> b_data(RealSize);
a_data.setRandom();
evaluator<RealView<VectorX<ComplexScalar>>> eval(a_data.realView());
for (Index offset = 0; offset < RealSize; offset++) {
for (Index begin = 0; offset + begin < RealSize; begin++) {
for (Index count = 0; begin + count < RealSize; count++) {
b_data.setRandom();
RealPacket res = eval.template packetSegment<Unaligned, RealPacket>(offset, begin, count);
pstoreSegment(b_data.data(), res, begin, count);
VERIFY_IS_CWISE_EQUAL(a_data_asreal.segment(offset + begin, count), b_data.segment(begin, count));
}
}
}
}
};
template <typename ComplexScalar>
struct test_edge_cases_impl<ComplexScalar, false> {
static void run() {}
};
// Complex sequence c(i) = (i, -i) whose scalar path records the largest index it is asked for.
template <typename ComplexScalar>
struct index_recording_complex_op {
Index* maxIndex;
ComplexScalar operator()(Index i) const {
*maxIndex = numext::maxi(*maxIndex, i);
return value(i);
}
template <typename Packet>
Packet packetOp(Index i) const {
ComplexScalar values[internal::unpacket_traits<Packet>::size];
for (int k = 0; k < internal::unpacket_traits<Packet>::size; ++k) values[k] = value(i + k);
return internal::ploadu<Packet>(values);
}
static ComplexScalar value(Index i) {
using RealScalar = typename NumTraits<ComplexScalar>::Real;
return ComplexScalar(RealScalar(i), RealScalar(-i));
}
};
template <typename ComplexScalar>
struct Eigen::internal::functor_traits<index_recording_complex_op<ComplexScalar>> {
static constexpr int Cost = 1;
static constexpr bool PacketAccess = true;
static constexpr bool IsRepeatable = true;
};
// A segment starting at an odd (imaginary) position must not read the coefficient past its end.
template <typename ComplexScalar>
void test_odd_segment_bounds() {
using namespace internal;
using RealScalar = typename NumTraits<ComplexScalar>::Real;
using ComplexPacket = typename packet_traits<ComplexScalar>::type;
using RealPacket = typename unpacket_traits<ComplexPacket>::as_real;
constexpr Index RealSize = 2 * unpacket_traits<ComplexPacket>::size;
const Index n = RealSize;
Index maxIndex = -1;
const auto sequence = VectorX<ComplexScalar>::NullaryExpr(n, index_recording_complex_op<ComplexScalar>{&maxIndex});
evaluator<RealView<const std::decay_t<decltype(sequence)>>> eval(sequence.realView());
VectorX<RealScalar> expected(2 * n), actual(RealSize);
for (Index i = 0; i < n; ++i) expected.template segment<2>(2 * i) << RealScalar(i), RealScalar(-i);
for (Index count = 1; count <= RealSize; ++count) {
const Index offset = 2 * n - count;
RealPacket res = eval.template packetSegment<Unaligned, RealPacket>(offset, 0, count);
pstoreSegment(actual.data(), res, 0, count);
VERIFY_IS_CWISE_EQUAL(actual.head(count), expected.segment(offset, count));
VERIFY(maxIndex < n);
}
}
template <typename ComplexScalar, bool Enable = internal::packet_traits<ComplexScalar>::Vectorizable>
struct test_odd_segment_bounds_impl {
static void run() { test_odd_segment_bounds<ComplexScalar>(); }
};
template <typename ComplexScalar>
struct test_odd_segment_bounds_impl<ComplexScalar, false> {
static void run() {}
};
template <typename ComplexScalar>
void test_edge_cases(const ComplexScalar&) {
test_edge_cases_impl<ComplexScalar>::run();
test_odd_segment_bounds_impl<ComplexScalar>::run();
}
// The parts of non-adjacent complex coefficients have no uniform stride, so their RealView has no direct access.
template <typename ComplexScalar>
void test_strided_realview(const ComplexScalar&) {
using RealScalar = typename NumTraits<ComplexScalar>::Real;
using ComplexMatrix = Matrix<ComplexScalar, Dynamic, Dynamic>;
using RealRowVector = Matrix<RealScalar, 1, Dynamic>;
const ComplexMatrix a = ComplexMatrix::Random(6, 8);
VERIFY((int(internal::traits<std::decay_t<decltype(a.row(2).realView())>>::Flags) & DirectAccessBit) == 0);
VERIFY((int(internal::traits<std::decay_t<decltype(a.col(2).realView())>>::Flags) & DirectAccessBit) != 0);
RealRowVector expected(2 * a.cols());
for (Index j = 0; j < a.cols(); ++j) {
expected(2 * j) = numext::real(a(2, j));
expected(2 * j + 1) = numext::imag(a(2, j));
}
const Ref<const RealRowVector, 0, InnerStride<>> row = a.row(2).realView();
VERIFY_IS_CWISE_EQUAL(row, expected);
// A writable view keeps LvalueBit, so its coeff() and wrapping evaluators return references into the complex
// storage.
ComplexMatrix b = a;
auto bRow = b.row(2);
auto bView = bRow.realView();
const auto& constView = bView;
for (Index j = 0; j < constView.size(); ++j) VERIFY_IS_EQUAL(constView.coeff(j), expected(j));
VERIFY_IS_CWISE_EQUAL(RealRowVector(b.row(2).realView().array().matrix()), expected);
VERIFY_IS_EQUAL(b.row(2).realView().segment(1, 5).sum(), expected.segment(1, 5).sum());
// Products read a direct-access vector operand through data() and innerStride().
using RealMatrix = Matrix<RealScalar, Dynamic, Dynamic>;
const RealMatrix e = RealMatrix::Random(3, 2 * a.cols());
VERIFY_IS_APPROX(e * a.row(2).realView().transpose(), e * expected.transpose());
// data() keeps the constness of the viewed storage, including through an IndexedView of a const matrix.
const RealScalar* aCol1 = reinterpret_cast<const RealScalar*>(a.data() + a.rows());
VERIFY_IS_EQUAL(a(placeholders::all, seq(1, 7, 3)).realView().data(), aCol1);
auto aCols = a(placeholders::all, seq(1, 7, 3));
const auto aColsView = aCols.realView();
VERIFY_IS_EQUAL(aColsView.data(), aCol1);
}
template <typename Scalar, int Rows, int Cols, int MaxRows = Rows, int MaxCols = Cols>
void test_realview_readonly() {
// if Rows == 1, don't test ColMajor as it is not a valid array
using ColMajorMatrixType = Matrix<Scalar, Rows, Cols, Rows == 1 ? RowMajor : ColMajor, MaxRows, MaxCols>;
using ColMajorArrayType = Array<Scalar, Rows, Cols, Rows == 1 ? RowMajor : ColMajor, MaxRows, MaxCols>;
// if Cols == 1, don't test RowMajor as it is not a valid array
using RowMajorMatrixType = Matrix<Scalar, Rows, Cols, Cols == 1 ? ColMajor : RowMajor, MaxRows, MaxCols>;
using RowMajorArrayType = Array<Scalar, Rows, Cols, Cols == 1 ? ColMajor : RowMajor, MaxRows, MaxCols>;
test_realview_readonly(ColMajorMatrixType());
test_realview_readonly(ColMajorArrayType());
test_realview_readonly(RowMajorMatrixType());
test_realview_readonly(RowMajorArrayType());
}
template <typename Scalar, int Rows, int Cols, int MaxRows = Rows, int MaxCols = Cols>
void test_realview_readwrite() {
// if Rows == 1, don't test ColMajor as it is not a valid array
using ColMajorMatrixType = Matrix<Scalar, Rows, Cols, Rows == 1 ? RowMajor : ColMajor, MaxRows, MaxCols>;
using ColMajorArrayType = Array<Scalar, Rows, Cols, Rows == 1 ? RowMajor : ColMajor, MaxRows, MaxCols>;
// if Cols == 1, don't test RowMajor as it is not a valid array
using RowMajorMatrixType = Matrix<Scalar, Rows, Cols, Cols == 1 ? ColMajor : RowMajor, MaxRows, MaxCols>;
using RowMajorArrayType = Array<Scalar, Rows, Cols, Cols == 1 ? ColMajor : RowMajor, MaxRows, MaxCols>;
test_realview(ColMajorMatrixType());
test_realview(ColMajorArrayType());
test_realview(RowMajorMatrixType());
test_realview(RowMajorArrayType());
}
template <int Rows, int Cols, int MaxRows = Rows, int MaxCols = Cols>
void test_realview() {
test_realview_readwrite<float, Rows, Cols, MaxRows, MaxCols>();
test_realview_readwrite<std::complex<float>, Rows, Cols, MaxRows, MaxCols>();
test_realview_readwrite<double, Rows, Cols, MaxRows, MaxCols>();
test_realview_readwrite<std::complex<double>, Rows, Cols, MaxRows, MaxCols>();
test_realview_readonly<TestComplex, Rows, Cols, MaxRows, MaxCols>();
}
// packet_traits<long double>::size is 1 on every supported target, so RealView's
// vectorized paths are unreachable for it and repeating it over the shape matrix
// only re-instantiates the same scalar fallback. Cover it on the fully dynamic
// and fully fixed shapes, which are the two storage layouts it can distinguish.
template <int Rows, int Cols, int MaxRows = Rows, int MaxCols = Cols>
void test_realview_long_double() {
test_realview_readwrite<long double, Rows, Cols, MaxRows, MaxCols>();
test_realview_readwrite<std::complex<long double>, Rows, Cols, MaxRows, MaxCols>();
}
EIGEN_DECLARE_TEST(realview) {
for (int i = 0; i < g_repeat; i++) {
CALL_SUBTEST_1((test_realview<Dynamic, Dynamic, Dynamic, Dynamic>()));
CALL_SUBTEST_2((test_realview<Dynamic, Dynamic, 17, Dynamic>()));
CALL_SUBTEST_3((test_realview<Dynamic, Dynamic, Dynamic, 19>()));
CALL_SUBTEST_4((test_realview<Dynamic, Dynamic, 17, 19>()));
CALL_SUBTEST_5((test_realview<17, Dynamic, 17, Dynamic>()));
CALL_SUBTEST_6((test_realview<Dynamic, 19, Dynamic, 19>()));
CALL_SUBTEST_7((test_realview<17, 19, 17, 19>()));
CALL_SUBTEST_8((test_realview<Dynamic, 1>()));
CALL_SUBTEST_9((test_realview<1, Dynamic>()));
CALL_SUBTEST_10((test_realview<1, 1>()));
CALL_SUBTEST_11(test_edge_cases(std::complex<float>()));
CALL_SUBTEST_11(test_strided_realview(std::complex<float>()));
CALL_SUBTEST_12(test_edge_cases(std::complex<double>()));
CALL_SUBTEST_12(test_strided_realview(std::complex<double>()));
CALL_SUBTEST_13((test_realview_long_double<Dynamic, Dynamic, Dynamic, Dynamic>()));
CALL_SUBTEST_13((test_realview_long_double<17, 19, 17, 19>()));
}
}