blob: e94dcae634e2d894abdd6ff5dab561abe03ef002 [file] [edit]
// 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))));
}
}