| // This file is part of Eigen, a lightweight C++ template library | |
| // for linear algebra. | |
| // | |
| // 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-FileCopyrightText: The Eigen Authors | |
| // SPDX-License-Identifier: MPL-2.0 | |
| #define TEST_ENABLE_TEMPORARY_TRACKING | |
| #include "main.h" | |
| #include <Eigen/Core> | |
| #include "NonCommutativeScalar.h" | |
| // The scaled permutation P * D of a permutation and a diagonal matrix. Every closed-form operation below moves | |
| // or multiplies single coefficients, so the comparisons with the dense references are exact. | |
| template <typename Scalar, int Size> | |
| void scaled_permutation(Index size) { | |
| using PermutationType = PermutationMatrix<Size>; | |
| using DiagonalType = DiagonalMatrix<Scalar, Size>; | |
| using ScaledType = ScaledPermutationMatrix<Scalar, Size>; | |
| using MatrixType = Matrix<Scalar, Size, Size>; | |
| using VectorType = Matrix<Scalar, Size, 1>; | |
| using RealScalar = typename NumTraits<Scalar>::Real; | |
| Matrix<int, Size, 1> indices, indices2; | |
| randomPermutationVector(indices, size); | |
| randomPermutationVector(indices2, size); | |
| const PermutationType perm(indices), perm2(indices2); | |
| const DiagonalType diag(VectorType::Random(size)), diag2(VectorType::Random(size)); | |
| const MatrixType permDense = perm.toDenseMatrix().template cast<Scalar>(); | |
| const MatrixType perm2Dense = perm2.toDenseMatrix().template cast<Scalar>(); | |
| const MatrixType inverseDense = perm.inverse().toDenseMatrix().template cast<Scalar>(); | |
| const MatrixType diagDense = diag.toDenseMatrix(); | |
| const MatrixType diag2Dense = diag2.toDenseMatrix(); | |
| const Scalar alpha = internal::random<Scalar>(); | |
| // P * D and the three sibling products are scaled permutations, not dense expressions. | |
| STATIC_CHECK((internal::is_same<decltype(perm * diag), ScaledType>::value)); | |
| STATIC_CHECK((internal::is_same<decltype(diag * perm.inverse()), ScaledType>::value)); | |
| const ScaledType scaled = perm * diag; | |
| const MatrixType scaledDense = permDense * diagDense; | |
| VERIFY_IS_EQUAL(scaled.toDenseMatrix(), scaledDense); | |
| VERIFY_IS_EQUAL(MatrixType(diag * perm), MatrixType(diagDense * permDense)); | |
| VERIFY_IS_EQUAL(MatrixType(perm.inverse() * diag), MatrixType(inverseDense * diagDense)); | |
| VERIFY_IS_EQUAL(MatrixType(perm.transpose() * diag), MatrixType(inverseDense * diagDense)); | |
| VERIFY_IS_EQUAL(MatrixType(diag * perm.inverse()), MatrixType(diagDense * inverseDense)); | |
| VERIFY_IS_EQUAL(MatrixType(indices.asPermutation() * diag.diagonal().asDiagonal()), scaledDense); | |
| VERIFY_IS_EQUAL(MatrixType(diag * Map<PermutationType>(indices.data(), size)), MatrixType(diagDense * permDense)); | |
| VERIFY_IS_EQUAL(MatrixType(ScaledType(perm)), permDense); | |
| VERIFY_IS_EQUAL(MatrixType(ScaledType(diag)), diagDense); | |
| for (Index i = 0; i < size; ++i) | |
| for (Index j = 0; j < size; ++j) VERIFY_IS_EQUAL(scaled.coeff(i, j), scaledDense(i, j)); | |
| // Assignment to dense. | |
| MatrixType result = MatrixType::Random(size, size), expected = result; | |
| result = scaled; | |
| VERIFY_IS_EQUAL(result, scaledDense); | |
| result = expected; | |
| result += scaled; | |
| expected += scaledDense; | |
| VERIFY_IS_EQUAL(result, expected); | |
| result -= scaled; | |
| expected -= scaledDense; | |
| VERIFY_IS_EQUAL(result, expected); | |
| // Products of scales are complex multiplications, whose rounding depends on the kernel: compare approximately. | |
| // Closed-form algebra: every result is a scaled permutation and costs O(n). | |
| VERIFY_IS_APPROX(MatrixType(scaled * diag2), MatrixType(scaledDense * diag2Dense)); | |
| VERIFY_IS_APPROX(MatrixType(diag2 * scaled), MatrixType(diag2Dense * scaledDense)); | |
| VERIFY_IS_EQUAL(MatrixType(scaled * perm2), MatrixType(scaledDense * perm2Dense)); | |
| VERIFY_IS_EQUAL(MatrixType(perm2 * scaled), MatrixType(perm2Dense * scaledDense)); | |
| VERIFY_IS_EQUAL(MatrixType(scaled * perm2.inverse()), MatrixType(scaledDense * perm2Dense.transpose())); | |
| VERIFY_IS_EQUAL(MatrixType(perm2.inverse() * scaled), MatrixType(perm2Dense.transpose() * scaledDense)); | |
| const ScaledType scaled2 = diag2 * perm2; | |
| VERIFY_IS_APPROX(MatrixType(scaled * scaled2), MatrixType(scaledDense * scaled2.toDenseMatrix())); | |
| VERIFY_IS_APPROX(MatrixType(alpha * scaled), MatrixType(alpha * scaledDense)); | |
| VERIFY_IS_APPROX(MatrixType(scaled * alpha), MatrixType(scaledDense * alpha)); | |
| VERIFY_IS_EQUAL(MatrixType(-scaled), MatrixType(-scaledDense)); | |
| VERIFY_IS_EQUAL(MatrixType(scaled.transpose()), MatrixType(scaledDense.transpose())); | |
| VERIFY_IS_EQUAL(MatrixType(scaled.adjoint()), MatrixType(scaledDense.adjoint())); | |
| STATIC_CHECK((internal::is_same<decltype(perm * diag * perm.inverse()), ScaledType>::value)); | |
| VERIFY_IS_EQUAL(MatrixType(perm * diag * perm.inverse()), MatrixType(permDense * diagDense * inverseDense)); | |
| VERIFY_IS_APPROX(MatrixType(scaled.inverse() * scaled), MatrixType::Identity(size, size)); | |
| VERIFY_IS_APPROX(MatrixType(scaled.inverse()), scaledDense.inverse()); | |
| // The dense determinant underflows for large sizes; the sign of a unit-scale permutation is exact at any size. | |
| if (size <= 8) VERIFY_IS_APPROX(scaled.determinant(), scaledDense.determinant()); | |
| VERIFY_IS_EQUAL(ScaledType(perm).determinant(), Scalar(RealScalar(perm.determinant()))); | |
| // Products with dense operands scale and permute rows or columns; no n x n intermediate. Complex products round | |
| // differently in the dense kernels, so those comparisons are approximate. | |
| const MatrixType a = MatrixType::Random(size, size); | |
| const VectorType x = VectorType::Random(size); | |
| VectorType y(size); | |
| VERIFY_EVALUATION_COUNT(y.noalias() = scaled * x, 0); | |
| VERIFY_IS_APPROX(y, VectorType(scaledDense * x)); | |
| VERIFY_EVALUATION_COUNT(result.noalias() = scaled * a, 0); | |
| VERIFY_IS_APPROX(result, MatrixType(scaledDense * a)); | |
| VERIFY_EVALUATION_COUNT(result.noalias() = a * scaled, 0); | |
| VERIFY_IS_APPROX(result, MatrixType(a * scaledDense)); | |
| VERIFY_IS_APPROX(VectorType(perm * diag * x), VectorType(scaledDense * x)); | |
| VERIFY_IS_APPROX(VectorType(x.transpose() * (diag * perm)).eval(), | |
| VectorType((x.transpose() * diagDense * permDense).eval())); | |
| result = a; | |
| expected = a; | |
| result.noalias() += scaled * a; | |
| expected += scaledDense * a; | |
| VERIFY_IS_APPROX(result, expected); | |
| result.noalias() -= a * scaled.inverse(); | |
| expected -= a * scaledDense.inverse(); | |
| VERIFY_IS_APPROX(result, expected); | |
| result = alpha * (scaled * a); | |
| VERIFY_IS_APPROX(result, alpha * scaledDense * a); | |
| result = a * (scaled * a); | |
| VERIFY_IS_APPROX(result, a * scaledDense * a); | |
| result = scaled * (a * a); | |
| VERIFY_IS_APPROX(result, scaledDense * a * a); | |
| // In-place: the dense permutation kernel detects the aliasing. | |
| result = a; | |
| result.noalias() = scaled * result; | |
| VERIFY_IS_APPROX(result, MatrixType(scaledDense * a)); | |
| result = a; | |
| result = result * scaled; | |
| VERIFY_IS_APPROX(result, MatrixType(a * scaledDense)); | |
| // Triangular and self-adjoint operands are densified, as toDenseMatrix() does. | |
| const MatrixType lower = a.template triangularView<Lower>(); | |
| const MatrixType upperSelfadjoint = a.template selfadjointView<Upper>(); | |
| VERIFY_IS_APPROX(MatrixType(scaled * a.template triangularView<Lower>()), MatrixType(scaledDense * lower)); | |
| VERIFY_IS_APPROX(MatrixType(a.template triangularView<Lower>() * scaled), MatrixType(lower * scaledDense)); | |
| VERIFY_IS_APPROX(MatrixType(scaled * a.template selfadjointView<Upper>()), | |
| MatrixType(scaledDense * upperSelfadjoint)); | |
| VERIFY_IS_APPROX(MatrixType(a.template selfadjointView<Upper>() * scaled), | |
| MatrixType(upperSelfadjoint * scaledDense)); | |
| // Lazy sums with a dense matrix. | |
| STATIC_CHECK( | |
| (internal::is_same<decltype(a + scaled), const CwiseBinaryOp<internal::scalar_sum_op<Scalar, Scalar>, | |
| const MatrixType, const ScaledType>>::value)); | |
| VERIFY_IS_EQUAL(MatrixType(a + scaled), MatrixType(a + scaledDense)); | |
| VERIFY_IS_EQUAL(MatrixType(scaled + a), MatrixType(scaledDense + a)); | |
| VERIFY_IS_EQUAL(MatrixType(a - scaled), MatrixType(a - scaledDense)); | |
| VERIFY_IS_EQUAL(MatrixType(scaled - a), MatrixType(scaledDense - a)); | |
| VERIFY_IS_APPROX((a + scaled) * x, (a + scaledDense) * x); | |
| // The lazy sum reduces in scalar order and the dense one in packets, so the results differ by rounding of order | |
| // eps * sum|t|, not of order eps * |result|, which cancellation can make arbitrarily small. Measured below 2 eps. | |
| const RealScalar sumMagnitude = (a + scaledDense).cwiseAbs().sum(); | |
| VERIFY(numext::abs((a + scaled).sum() - (a + scaledDense).sum()) <= | |
| 8 * NumTraits<RealScalar>::epsilon() * sumMagnitude); | |
| // Storage and setters. | |
| ScaledType identity(size); | |
| identity.setIdentity(); | |
| VERIFY_IS_EQUAL(MatrixType(identity), MatrixType::Identity(size, size)); | |
| VERIFY_IS_EQUAL(MatrixType(identity * scaled), scaledDense); | |
| ScaledType copy = scaled; | |
| copy.scales() *= alpha; | |
| VERIFY_IS_APPROX(MatrixType(copy), MatrixType(alpha * scaledDense)); | |
| copy = scaled2; | |
| VERIFY_IS_EQUAL(MatrixType(copy), scaled2.toDenseMatrix()); | |
| } | |
| template <typename Real> | |
| void scaled_permutation_mixed_scalars() { | |
| using Complex = std::complex<Real>; | |
| using Dense = Matrix<Complex, 3, 3>; | |
| ScaledPermutationMatrix<Real, 3> real; | |
| ScaledPermutationMatrix<Complex, Dynamic> complex(3); | |
| real.indices() << 1, 2, 0; | |
| real.scales() << 2, 3, 5; | |
| complex.indices() << 2, 0, 1; | |
| complex.scales() << Complex(1, 2), Complex(3, -1), Complex(-2, 4); | |
| const DiagonalMatrix<Real, 3> realDiagonal(real.scales()); | |
| const DiagonalMatrix<Complex, 3> complexDiagonal(complex.scales()); | |
| const Dense realDense = real.toDenseMatrix().template cast<Complex>(); | |
| const Dense complexDense = complex.toDenseMatrix(); | |
| const Dense realDiagonalDense = realDiagonal.toDenseMatrix().template cast<Complex>(); | |
| const Dense complexDiagonalDense = complexDiagonal.toDenseMatrix(); | |
| const auto product = real * complex; | |
| STATIC_CHECK((internal::is_same<typename decltype(product)::Scalar, Complex>::value)); | |
| STATIC_CHECK(decltype(product)::RowsAtCompileTime == 3); | |
| VERIFY_IS_EQUAL(Dense(product), Dense(realDense * complexDense)); | |
| VERIFY_IS_EQUAL(Dense(complex * real), Dense(complexDense * realDense)); | |
| VERIFY_IS_EQUAL(Dense(real * complexDiagonal), Dense(realDense * complexDiagonalDense)); | |
| VERIFY_IS_EQUAL(Dense(complexDiagonal * real), Dense(complexDiagonalDense * realDense)); | |
| VERIFY_IS_EQUAL(Dense(complex * realDiagonal), Dense(complexDense * realDiagonalDense)); | |
| VERIFY_IS_EQUAL(Dense(realDiagonal * complex), Dense(realDiagonalDense * complexDense)); | |
| VERIFY_IS_EQUAL(Dense(real * complexDense), Dense(realDense * complexDense)); | |
| VERIFY_IS_EQUAL(Dense(complexDense * real), Dense(complexDense * realDense)); | |
| Dense accumulated = Dense::Zero(); | |
| accumulated.noalias() += real * complexDense; | |
| VERIFY_IS_EQUAL(accumulated, Dense(realDense * complexDense)); | |
| accumulated.setZero(); | |
| accumulated.noalias() += complexDense * real; | |
| VERIFY_IS_EQUAL(accumulated, Dense(complexDense * realDense)); | |
| } | |
| template <int Order> | |
| void scaled_permutation_noncommutative() { | |
| using Scalar = noncommutative_scalar::Quaternion; | |
| using Dense = Matrix<Scalar, 3, 3, Order>; | |
| ScaledPermutationMatrix<Scalar, 3> scaled; | |
| scaled.indices() << 1, 2, 0; | |
| scaled.scales() << Scalar(1, 2, 0, -1), Scalar(2, -1, 3, 0), Scalar(-1, 0, 2, 1); | |
| Dense matrix, left, right; | |
| for (Index i = 0; i < 3; ++i) | |
| for (Index j = 0; j < 3; ++j) matrix(i, j) = Scalar(i + 1, j + 1, i - j, i + j); | |
| for (Index k = 0; k < 3; ++k) { | |
| for (Index j = 0; j < 3; ++j) left(scaled.indices()(k), j) = scaled.scales()(k) * matrix(k, j); | |
| for (Index i = 0; i < 3; ++i) right(i, k) = matrix(i, scaled.indices()(k)) * scaled.scales()(k); | |
| } | |
| Dense result = scaled * matrix; | |
| VERIFY((result.array() == left.array()).all()); | |
| result = matrix * scaled; | |
| VERIFY((result.array() == right.array()).all()); | |
| result = matrix; | |
| result = scaled * result; | |
| VERIFY((result.array() == left.array()).all()); | |
| result = matrix; | |
| result = result * scaled; | |
| VERIFY((result.array() == right.array()).all()); | |
| result.setZero(); | |
| result.noalias() += scaled * matrix; | |
| VERIFY((result.array() == left.array()).all()); | |
| result.setZero(); | |
| result.noalias() += matrix * scaled; | |
| VERIFY((result.array() == right.array()).all()); | |
| const Scalar alpha(1, -2, 3, 1); | |
| result.setZero(); | |
| result.noalias() += alpha * (scaled * matrix); | |
| for (Index i = 0; i < 3; ++i) | |
| for (Index j = 0; j < 3; ++j) VERIFY(result(i, j) == alpha * left(i, j)); | |
| result.setZero(); | |
| result.noalias() -= alpha * (matrix * scaled); | |
| for (Index i = 0; i < 3; ++i) | |
| for (Index j = 0; j < 3; ++j) VERIFY(result(i, j) == -(alpha * right(i, j))); | |
| } | |
| EIGEN_DECLARE_TEST(scaled_permutation) { | |
| CALL_SUBTEST_1((scaled_permutation<float, 1>(1))); | |
| CALL_SUBTEST_1((scaled_permutation<double, 3>(3))); | |
| CALL_SUBTEST_1((scaled_permutation<std::complex<double>, 4>(4))); | |
| CALL_SUBTEST_4(scaled_permutation_mixed_scalars<float>()); | |
| CALL_SUBTEST_4(scaled_permutation_mixed_scalars<double>()); | |
| CALL_SUBTEST_5(scaled_permutation_noncommutative<ColMajor>()); | |
| CALL_SUBTEST_5(scaled_permutation_noncommutative<RowMajor>()); | |
| for (int i = 0; i < g_repeat; i++) { | |
| CALL_SUBTEST_2((scaled_permutation<float, Dynamic>(internal::random<int>(1, EIGEN_TEST_MAX_SIZE)))); | |
| CALL_SUBTEST_2((scaled_permutation<double, Dynamic>(internal::random<int>(1, EIGEN_TEST_MAX_SIZE)))); | |
| CALL_SUBTEST_3((scaled_permutation<std::complex<double>, Dynamic>(internal::random<int>(1, EIGEN_TEST_MAX_SIZE)))); | |
| } | |
| } |