| // SPDX-FileCopyrightText: The Eigen Authors | |
| // SPDX-License-Identifier: MPL-2.0 | |
| #define EIGEN_RUNTIME_NO_MALLOC | |
| #define TEST_ENABLE_TEMPORARY_TRACKING | |
| #include "main.h" | |
| #include <Eigen/Core> | |
| #include "CustomComplex.h" | |
| template <typename Derived> | |
| void check_vectorwise_norms(const DenseBase<Derived>& input) { | |
| using Real = typename Derived::RealScalar; | |
| const Derived& matrix = input.derived(); | |
| const Index rows = matrix.rows(); | |
| const Index cols = matrix.cols(); | |
| Matrix<Real, 1, Dynamic> columnSquares(cols), columnNorms(cols); | |
| Matrix<Real, Dynamic, 1> rowSquares(rows), rowNorms(rows); | |
| using ColumnNorm = decltype(matrix.colwise().squaredNorm()); | |
| using RowNorm = decltype(matrix.rowwise().squaredNorm()); | |
| STATIC_CHECK(ColumnNorm::RowsAtCompileTime == 1); | |
| STATIC_CHECK(int(ColumnNorm::ColsAtCompileTime) == int(Derived::ColsAtCompileTime)); | |
| STATIC_CHECK(int(RowNorm::RowsAtCompileTime) == int(Derived::RowsAtCompileTime)); | |
| STATIC_CHECK(RowNorm::ColsAtCompileTime == 1); | |
| STATIC_CHECK((internal::is_same<typename internal::traits<ColumnNorm>::XprKind, | |
| typename internal::traits<Derived>::XprKind>::value)); | |
| internal::set_is_malloc_allowed(false); | |
| columnSquares = matrix.colwise().squaredNorm(); | |
| rowSquares = matrix.rowwise().squaredNorm(); | |
| columnNorms = matrix.colwise().norm(); | |
| rowNorms = matrix.rowwise().norm(); | |
| internal::set_is_malloc_allowed(true); | |
| for (int direction = 0; direction < 2; ++direction) { | |
| const Index count = direction == 0 ? cols : rows; | |
| const Index length = direction == 0 ? rows : cols; | |
| for (Index k = 0; k < count; ++k) { | |
| long double reference = 0; | |
| for (Index i = 0; i < length; ++i) { | |
| const auto value = direction == 0 ? matrix.coeff(i, k) : matrix.coeff(k, i); | |
| const long double real = static_cast<long double>(numext::real(value)); | |
| const long double imag = static_cast<long double>(numext::imag(value)); | |
| reference += real * real + imag * imag; | |
| } | |
| const Real square = direction == 0 ? columnSquares(k) : rowSquares(k); | |
| const Real norm = direction == 0 ? columnNorms(k) : rowNorms(k); | |
| // FMA contraction can round coefficient access and vector reductions differently. | |
| const Real coefficientSquare = | |
| direction == 0 ? matrix.colwise().squaredNorm()(k) : matrix.rowwise().squaredNorm()(k); | |
| const Real vectorSquare = | |
| direction == 0 ? matrix.col(k).matrix().squaredNorm() : matrix.row(k).matrix().squaredNorm(); | |
| // Two squares and at most two additions per complex coefficient, plus sqrt rounding. | |
| const long double relativeBound = (4 * length + 4) * static_cast<long double>(NumTraits<Real>::epsilon()); | |
| VERIFY(numext::abs(static_cast<long double>(square) - reference) <= relativeBound * reference); | |
| VERIFY(numext::abs(static_cast<long double>(coefficientSquare) - reference) <= relativeBound * reference); | |
| VERIFY(numext::abs(static_cast<long double>(vectorSquare) - reference) <= relativeBound * reference); | |
| VERIFY(numext::abs(static_cast<long double>(norm) - numext::sqrt(reference)) <= | |
| relativeBound * numext::sqrt(reference)); | |
| } | |
| } | |
| } | |
| template <typename Derived> | |
| void check_vectorwise_norm_evaluations(const DenseBase<Derived>& input) { | |
| using Scalar = typename Derived::Scalar; | |
| using Real = typename Derived::RealScalar; | |
| Index calls = 0; | |
| const auto expression = input.derived().unaryExpr([&calls](const Scalar& value) { | |
| ++calls; | |
| return value; | |
| }); | |
| using Expression = internal::remove_all_t<decltype(expression)>; | |
| STATIC_CHECK((int(internal::evaluator<Expression>::Flags) & (DirectAccessBit | PacketAccessBit)) == 0); | |
| Matrix<Real, 1, Dynamic> columns(input.cols()); | |
| Matrix<Real, Dynamic, 1> rows(input.rows()); | |
| const Matrix<Real, 1, Dynamic> columnSquares = input.colwise().squaredNorm(); | |
| const Matrix<Real, 1, Dynamic> columnNorms = input.colwise().norm(); | |
| const Matrix<Real, Dynamic, 1> rowSquares = input.rowwise().squaredNorm(); | |
| const Matrix<Real, Dynamic, 1> rowNorms = input.rowwise().norm(); | |
| const auto check = [&](const auto& actual, const auto& expected) { | |
| VERIFY_IS_EQUAL(calls, input.size()); | |
| VERIFY_IS_APPROX(actual, expected); | |
| calls = 0; | |
| }; | |
| internal::set_is_malloc_allowed(false); | |
| columns = expression.colwise().squaredNorm(); | |
| check(columns, columnSquares); | |
| columns = expression.colwise().norm(); | |
| check(columns, columnNorms); | |
| rows = expression.rowwise().squaredNorm(); | |
| check(rows, rowSquares); | |
| rows = expression.rowwise().norm(); | |
| check(rows, rowNorms); | |
| internal::set_is_malloc_allowed(true); | |
| } | |
| template <typename Scalar, int Order> | |
| void vectorwise_norm_layout() { | |
| using MatrixType = Matrix<Scalar, Dynamic, Dynamic, Order>; | |
| using Real = typename NumTraits<Scalar>::Real; | |
| // Only complex scalars with array-oriented access use the shared squaredNorm kernel. | |
| constexpr bool useVectorNorm = NumTraits<Scalar>::IsComplex && internal::complex_array_access<Scalar>::value; | |
| using InnerReduction = | |
| typename VectorwiseOp<const MatrixType, Order == RowMajor ? Horizontal : Vertical>::SquaredNormReturnType; | |
| using InnerInput = internal::remove_all_t<decltype(std::declval<InnerReduction>().nestedExpression())>; | |
| using InnerScalar = std::conditional_t<useVectorNorm, Scalar, Real>; | |
| STATIC_CHECK((internal::is_same<typename InnerInput::Scalar, InnerScalar>::value)); | |
| using OuterReduction = | |
| typename VectorwiseOp<const MatrixType, Order == RowMajor ? Vertical : Horizontal>::SquaredNormReturnType; | |
| if (!NumTraits<Scalar>::IsComplex) { | |
| VERIFY(bool(internal::evaluator<OuterReduction>::Flags & PacketAccessBit) == | |
| bool(internal::evaluator<MatrixType>::Flags & PacketAccessBit)); | |
| } | |
| for (Index rows : {0, 1, 2, 7, 8, 9, 17}) { | |
| for (Index cols : {0, 1, 3, 7, 8, 9, 19}) { | |
| MatrixType matrix = MatrixType::Random(rows, cols); | |
| check_vectorwise_norms(matrix); | |
| check_vectorwise_norms(matrix.array()); | |
| check_vectorwise_norms(matrix.transpose()); | |
| check_vectorwise_norms(matrix + matrix); | |
| } | |
| } | |
| Matrix<Scalar, 3, 5, Order> fixed = Matrix<Scalar, 3, 5, Order>::Random(); | |
| check_vectorwise_norms(fixed); | |
| Matrix<Scalar, Dynamic, Dynamic, Order, 9, 13> bounded(7, 11); | |
| bounded.setRandom(); | |
| check_vectorwise_norms(bounded); | |
| MatrixType storage = MatrixType::Random(23, 29); | |
| check_vectorwise_norms(storage.block(1, 2, 17, 19)); | |
| check_vectorwise_norm_evaluations(storage); | |
| check_vectorwise_norm_evaluations(storage.array()); | |
| for (Index innerStride : {1, 2, 3}) { | |
| const Index innerSize = Order == RowMajor ? 19 : 17; | |
| const Index outerSize = Order == RowMajor ? 17 : 19; | |
| const Index outerStride = innerSize * innerStride + 5; | |
| Matrix<Scalar, Dynamic, 1> buffer = Matrix<Scalar, Dynamic, 1>::Random(outerSize * outerStride + 1); | |
| Map<const MatrixType, Unaligned, Stride<Dynamic, Dynamic>> strided( | |
| buffer.data() + 1, 17, 19, Stride<Dynamic, Dynamic>(outerStride, innerStride)); | |
| check_vectorwise_norms(strided); | |
| } | |
| MatrixType matrix = MatrixType::Random(17, 19); | |
| const auto delayed = (matrix + matrix).colwise().squaredNorm(); | |
| Matrix<Real, 1, Dynamic> actual = delayed; | |
| VERIFY_IS_APPROX(actual, (matrix + matrix).eval().colwise().squaredNorm()); | |
| MatrixType product = matrix.adjoint() * matrix; | |
| Matrix<Real, 1, Dynamic> productNorms(19); | |
| // The abs2 path materializes the product and its squared coefficients. | |
| const int expectedTemporaries = useVectorNorm ? 1 : 2; | |
| VERIFY_EVALUATION_COUNT(productNorms = (matrix.adjoint() * matrix).colwise().squaredNorm(), expectedTemporaries); | |
| VERIFY_IS_APPROX(productNorms, product.colwise().squaredNorm()); | |
| matrix.setZero(); | |
| for (Index i = 0; i < matrix.size(); ++i) { | |
| matrix.data()[i] = Scalar(-Real(0)); | |
| } | |
| const Matrix<Real, 1, Dynamic> zeros = matrix.colwise().squaredNorm(); | |
| for (Index i = 0; i < zeros.size(); ++i) { | |
| VERIFY_IS_EQUAL(zeros(i), Real(0)); | |
| VERIFY(!numext::signbit(zeros(i))); | |
| } | |
| matrix(3, 5) = Scalar(NumTraits<Real>::infinity()); | |
| VERIFY((numext::isinf)(matrix.colwise().norm()(5))); | |
| VERIFY((numext::isinf)(matrix.rowwise().squaredNorm()(3))); | |
| matrix(3, 5) = Scalar(NumTraits<Real>::quiet_NaN()); | |
| VERIFY((numext::isnan)(matrix.colwise().squaredNorm()(5))); | |
| VERIFY((numext::isnan)(matrix.rowwise().norm()(3))); | |
| } | |
| template <typename Real, int Order> | |
| void vectorwise_norm_mixed_special_values() { | |
| using Scalar = std::complex<Real>; | |
| Matrix<Scalar, Dynamic, Dynamic, Order> matrix = Matrix<Scalar, Dynamic, Dynamic, Order>::Zero(17, 19); | |
| matrix(0, 0) = Scalar(NumTraits<Real>::infinity(), Real(0)); | |
| matrix(1, 0) = matrix(0, 1) = Scalar(Real(0), NumTraits<Real>::quiet_NaN()); | |
| VERIFY((numext::isnan)(matrix.colwise().squaredNorm()(0))); | |
| VERIFY((numext::isnan)(matrix.colwise().norm()(0))); | |
| VERIFY((numext::isnan)(matrix.rowwise().squaredNorm()(0))); | |
| VERIFY((numext::isnan)(matrix.rowwise().norm()(0))); | |
| } | |
| EIGEN_DECLARE_TEST(vectorwise_norm) { | |
| for (int i = 0; i < g_repeat; ++i) { | |
| CALL_SUBTEST_1((vectorwise_norm_layout<float, ColMajor>())); | |
| CALL_SUBTEST_2((vectorwise_norm_layout<float, RowMajor>())); | |
| CALL_SUBTEST_3((vectorwise_norm_layout<double, ColMajor>())); | |
| CALL_SUBTEST_4((vectorwise_norm_layout<double, RowMajor>())); | |
| CALL_SUBTEST_5((vectorwise_norm_layout<std::complex<float>, ColMajor>())); | |
| CALL_SUBTEST_6((vectorwise_norm_layout<std::complex<float>, RowMajor>())); | |
| CALL_SUBTEST_7((vectorwise_norm_layout<std::complex<double>, ColMajor>())); | |
| CALL_SUBTEST_8((vectorwise_norm_layout<std::complex<double>, RowMajor>())); | |
| CALL_SUBTEST_5((vectorwise_norm_mixed_special_values<float, ColMajor>())); | |
| CALL_SUBTEST_6((vectorwise_norm_mixed_special_values<float, RowMajor>())); | |
| CALL_SUBTEST_7((vectorwise_norm_mixed_special_values<double, ColMajor>())); | |
| CALL_SUBTEST_8((vectorwise_norm_mixed_special_values<double, RowMajor>())); | |
| CALL_SUBTEST_10((vectorwise_norm_layout<CustomComplex<float>, ColMajor>())); | |
| CALL_SUBTEST_11((vectorwise_norm_layout<CustomComplex<float>, RowMajor>())); | |
| CALL_SUBTEST_12((vectorwise_norm_layout<CustomComplex<double>, ColMajor>())); | |
| CALL_SUBTEST_13((vectorwise_norm_layout<CustomComplex<double>, RowMajor>())); | |
| CALL_SUBTEST_9(([] { | |
| Matrix<int, 2, 2> integers; | |
| integers << 3, 0, 4, 5; | |
| VERIFY_IS_EQUAL(integers.colwise().squaredNorm().eval(), RowVector2i(25, 25)); | |
| VERIFY_IS_EQUAL(integers.transpose().rowwise().squaredNorm().eval(), Vector2i(25, 25)); | |
| Array<bool, 2, 2> flags; | |
| flags << false, true, true, false; | |
| VERIFY(flags.colwise().squaredNorm().all()); | |
| VERIFY(flags.rowwise().squaredNorm().all()); | |
| flags.setZero(); | |
| VERIFY(!flags.colwise().squaredNorm().any()); | |
| VERIFY(!flags.rowwise().squaredNorm().any()); | |
| }())); | |
| } | |
| } |