orthonormal.h

Back to Common stuff

pastel/math/matrix/

// Description: Perpendicular vectors
// Detail: orthonormalize, perpendicular, etc.
// Documentation: common.txt

#ifndef PASTELMATH_ORTHONORMAL_H
#define PASTELMATH_ORTHONORMAL_H

#include "pastel/sys/vector.h"
#include "pastel/math/matrix/matrix.h"
#include "pastel/sys/vector/vector_tools.h"

#include <vector>

namespace Pastel
{

    //! Orthonormalizes the columns of a matrix.
    /*!
   This is stabilized Gram-Schmidt orthonormalization.
   It is numerically ok; if you need more stability, 
   use the QR-decomposition instead. 

   matrix:
   An mxn-matrix Q.

   returns:
   A matrix Q such that Q^T Q = I.

   throws:
   SingularMatrix_Exception if the matrix has 
   effectively linearly dependent columns.
   */
    template <typename Real, int M, int N>
    Matrix<Real, M, N> orthonormalize(Matrix<Real, M, N> matrix)
    {
        if (matrix.size() == 0)
        {
            return matrix;
        }

        integer n = matrix.cols();
        for (integer i = 0;i < n;++i)
        {
            // We will retain the loop-invariant that the
            // first i columns form an orthonormal set.

            for (integer j = 0;j < i;++j)
            {
                // From the i:th column, remove the contributions of the
                // vectors in matrix[0]..matrix[i - 1].

                // Note: While equivalent mathematically, 
                // it is important for numerical stability
                // that we use the modified vectors in the dot product
                // computation, rather than the original ones.
                matrix.col(i) -= 
                    matrix.col(j) * matrix.col(i).dot(matrix.col(j));
            }

            Real vNorm = norm(asVector(matrix.col(i)));

            // EPSILON
            if (vNorm == 0)
            {
                throw SingularMatrix_Exception();
            }

            // Normalize to unit length.
            matrix.col(i) /= vNorm;
        }

        return matrix;
    }

    //! Calculates a perpendicular to a set of vectors
    /*!
   Preconditions:
   Vectors in 'orthonormalSet' are orthonormal.

   If the set is bigger than the dimensionality, 
   a zero vector is returned. 
   This is because every set bigger than dimensionality 
   is linearly dependent. If the set is empty, a unit vector 
   of the form (1, 0, 0, ..., 0) is returned.

   See also:
   orthonormalize()
   */
    template <typename Real, int N>
    Vector<Real, N> perpendicular(
        integer dimension,
        const std::vector<Vector<Real, N> >& orthonormalSet)
    {
        ENSURE_OP(dimension, >, 0);

        // 'orthonormalSet' is assumed to be an orthonormal set of vectors

        if (orthonormalSet.empty())
        {
            return unitAxis<Real, N>(dimension, 0);
        }

        ENSURE_OP(orthonormalSet.front().n(), ==, dimension);

        integer vectors = orthonormalSet.size();

        if (vectors >= dimension)
        {
            return Vector<Real, N>(ofDimension(dimension), 0);
        }

        // Find the positive axis-aligned vector e_i
        // that has the minimum maximum absolute dot product
        // with the set flat.

        integer minMaxDotIndex = 0;
        Real minMaxDot = Infinity();

        for (integer i = 0;i < dimension;++i)
        {
            Real maxDot = 0;
            for (integer j = 0;j < vectors;++j)
            {
                Real absDot = abs(orthonormalSet[j][i]);
                if (absDot > maxDot)
                {
                    maxDot = absDot;
                }
            }

            if (maxDot < minMaxDot)
            {
                minMaxDot = maxDot;
                minMaxDotIndex = i;
            }
        }

        // Make e_i orthogonal to flat

        Vector<Real, N> result = unitAxis<Real, N>(dimension, minMaxDotIndex);

        for (integer i = 0;i < vectors;++i)
        {
            // Remove flat[i] directed contribution
            result -= orthonormalSet[i] * dot(result, orthonormalSet[i]);
        }

        // Normalize to unit length

        result /= norm(result);

        return result;
    }

}

#endif