/*! * * * \brief Some operations for creating rotation matrices * * * * * \author O. Krause * \date 2011 * * * \par Copyright 1995-2017 Shark Development Team * *

* This file is part of Shark. * * * Shark is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Shark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Shark. If not, see . * */ #ifndef SHARK_LINALG_ROTATIONS_H #define SHARK_LINALG_ROTATIONS_H #include #include namespace shark{ namespace blas{ /** * \ingroup shark_globals * * @{ */ //! \brief Generates a Householder reflection from a vector to use with applyHouseholderLeft/Right //! //! Given a Vector x=(x0,x1,...,xn), finds a reflection with the property //! (c, 0,0,...0) = (I-beta v v^t)x //! and v = (x0-c,x1,x2,...,xn) template typename X::value_type createHouseholderReflection( vector_expression const& x, vector_expression& reflection ){ SIZE_CHECK(x().size() != 0); SIZE_CHECK(x().size() == reflection().size()); //special case for last iteration of QR etc //by convention diagonal elements are > 0 if(x().size() == 1){ reflection()(0) = 1; return 2; } typedef typename X::value_type Value; double norm = norm_2(x); if (x()(0) >= 0.0) norm *= -1.0; noalias(reflection()) = x; reflection()(0) -= norm; reflection() /= (x()(0) - norm); //if pivot is close to 0, this is one->numericaly stable //compared to the usual formula Value beta = (norm - x()(0)) / norm; return beta; } //\brief rotates a matrix using a householder reflection // //calculates A*(1-beta*xx^T) template void applyHouseholderOnTheRight( matrix_expression & matrix, vector_expression const& reflection, T beta ){ SIZE_CHECK(matrix().size2() == reflection().size()); SIZE_CHECK(reflection().size() != 0 ); //special case for last iteration of QR etc if(reflection().size() == 1){ matrix() *= 1-beta; return; } SIZE_CHECK(matrix().size2() == reflection().size()); //Ax blas::vector temp = prod(matrix,reflection); //A -=beta*(Ax)x^T noalias(matrix()) -= beta * outer_prod(temp,reflection); } /// \brief rotates a matrix using a householder reflection /// /// calculates (1-beta*xx^T)*A template void applyHouseholderOnTheLeft( matrix_expression & matrix, vector_expression const& reflection, T const& beta ){ SIZE_CHECK(matrix().size1() == reflection().size()); SIZE_CHECK(reflection().size() != 0 ); //special case for last iteration of QR etc if(reflection().size() == 1){ matrix()*=1-beta; return; } //x^T A blas::vector temp = prod(trans(matrix),reflection); //A -=beta*x(x^T A) noalias(matrix()) -= beta * outer_prod(reflection,temp); } /// \brief rotates a matrix using a householder reflection /// /// calculates (1-beta*xx^T)*A template void applyHouseholderOnTheLeft( matrix_expression&& matrix, vector_expression const& reflection, T const& beta ){ applyHouseholderOnTheLeft(matrix(),reflection,beta); } /// \brief Initializes a matrix such that it forms a random rotation matrix. /// /// The matrix needs to be quadratic and have the proper size /// (e.g. call matrix::resize before). /// /// One common way to do this is using Gram-Schmidt-Orthogonalisation /// on a matrix which is initialized with gaussian numbers. However, this is /// highly unstable for big matrices. /// /// This algorithm is implemented from one of the algorithms presented in /// Francesco Mezzadri "How to generate random matrices from the classical compact groups" /// http://arxiv.org/abs/math-ph/0609050v2 /// /// He gives two algorithms: the first one uses QR decomposition on the random gaussian /// matrix and ensures that the signs of Q are correct by multiplying every column of Q /// with the sign of the diagonal of R. /// /// We use another algorithm implemented in the paper which works similarly, but /// reversed. We apply Householder rotations H_N H_{N-1}..H_1 where /// H_1 is generated from a random vector on the n-dimensional unit sphere. /// this requires less operations and is thus preferable. Also only half the /// random numbers need to be generated template< class MatrixT> void randomRotationMatrix(random::rng_type& rng, matrix_container& matrixC){ MatrixT& matrix = matrixC(); SIZE_CHECK(matrix.size1() == matrix.size2()); SIZE_CHECK(matrix.size1() > 0); size_t size = matrix.size1(); diag(matrix) = repeat(1.0,size); RealVector v(size); //we skip the first dimension as the rotation of a 1d vector is just the identity for(std::size_t i = 2; i != size+1;++i){ //create the random vector on the unit-sphere for the i-dimensional subproblem for(std::size_t j=0;j != i; ++j){ v(j) = random::gauss(rng); } subrange(v,0,i) /=norm_2(subrange(v,0,i));//project on sphere double v0 = v(0); v(0) += v0/std::abs(v0); //compute new norm of v //~ double normvSqr = 1.0+(1+v0)*(1+v0)-v0*v0; double normvSqr = norm_sqr(subrange(v,0,i)); //apply the householder rotation to the i-th submatrix applyHouseholderOnTheLeft(subrange(matrix,size-i,size,size-i,size),subrange(v,0,i),2.0/normvSqr); } } //! Creates a random rotation matrix with a certain size using the random number generator rng. RealMatrix randomRotationMatrix(random::rng_type& rng, size_t size){ RealMatrix mat(size,size); randomRotationMatrix(rng, mat); return mat; } /** @}*/ }} #endif