Skip to content
Snippets Groups Projects
VMD.cpp 4.48 KiB
Newer Older
#include "MantidKernel/VMD.h"
#include "MantidKernel/System.h"
#include "MantidKernel/DllConfig.h"

using namespace Mantid::Kernel;

namespace Mantid
{
namespace Kernel
{

/**
  Prints a text representation of itself
  @param os :: the Stream to output to
  @param v :: the vector to output
  @return the output stream
  */
  std::ostream& operator<<(std::ostream& os, const VMDBase<double>& v)
  {
    os << v.toString();
    return os;
  }

  std::ostream& operator<<(std::ostream& os, const VMDBase<float>& v)
  {
    os << v.toString();
    return os;
  }
//-------------------------------------------------------------------------------------------------
/** Make an orthogonal system with 2 input 3D vectors.
 * Currently only works in 3D!
 *
 * @param vectors :: list of 2 vectors with 3D
 * @return list of 3 vectors
 */
template<typename TYPE>
std::vector<VMDBase<TYPE> > VMDBase<TYPE>::makeVectorsOrthogonal(std::vector<VMDBase> & vectors)
    throw std::runtime_error("VMDBase::makeVectorsOrthogonal(): Need 2 input vectors.");
  if (vectors[0].getNumDims() != 3 || vectors[1].getNumDims() != 3)
    throw std::runtime_error("VMDBase::makeVectorsOrthogonal(): Need 3D input vectors.");
  std::vector<V3D> in, out;
  for (size_t i=0; i<vectors.size(); i++)
    in.push_back( V3D( vectors[i][0], vectors[i][1], vectors[i][2]) );
  out = V3D::makeVectorsOrthogonal(in);

  std::vector<VMDBase> retVal;
    retVal.push_back( VMDBase(out[i]) );

//-------------------------------------------------------------------------------------------------
/** Given N-1 vectors defining a N-1 dimensional hyperplane in N dimensions,
 * returns a vector that is normal (perpendicular) to all the input vectors
 *
 * Given planar vectors a, b, c, ...
 * Build a NxN matrix of this style:
 *  x1  x2  x3  x4
 *  a1  a2  a4  a4
 *  b1  b2  b4  b4
 *  c1  c2  c4  c4
 *
 * Where xn = the basis unit vector of the space, e.g. x1 = x, x2 = y, etc.
 *
 * The determinant of the matrix gives the normal vector. This is analogous
 * to the determinant method of finding the cross product of 2 3D vectors.
 *
 * It can be shown that the resulting vector n is such that:
 *  n . a = 0; n . b = 0 etc.
 * ... meaning that all the in-plane vectors are perpendicular to the normal, which is what we wanted!
 *
 * (I've worked it out in 4D and its a long proof (not shown here)
 * I'm assuming it holds for higher dimensions,
 * I'll let a mathematician prove this :) )
 *
 * @param vectors :: vector of N-1 vectors with N dimensions.
 * @throw if the vectors are collinear
 * @return the normal vector
 */
template<typename TYPE>
VMDBase<TYPE> VMDBase<TYPE>::getNormalVector(const std::vector<VMDBase<TYPE> > & vectors)
    throw std::invalid_argument("VMDBase::getNormalVector: Must give at least 1 vector");
  size_t nd = vectors[0].getNumDims();
  if (nd < 2)
    throw std::invalid_argument("VMDBase::getNormalVector: Must have at least 2 dimensions!");
    throw std::invalid_argument("VMDBase::getNormalVector: Must have as many N-1 vectors if there are N dimensions.");
  for (size_t i=0; i < vectors.size(); i++)
    if (vectors[i].getNumDims() != nd)
      throw std::invalid_argument("VMDBase::getNormalVector: Inconsistent number of dimensions in the vectors given!");
  VMDBase normal = VMDBase(nd);
  TYPE sign = +1.0;
  for (size_t d=0; d<nd; d++)
  {
    // Make the sub-determinant with the columns of every other dimension.
    Matrix<TYPE> mat(nd-1, nd-1);
    for (size_t row=0; row<vectors.size(); row++)
    {
      VMDBase vec = vectors[row];
      size_t col = 0;
      for (size_t i=0; i < nd; i++)
      {
        if (i != d) // Skip the column of this dimension
        {
          mat[row][col] = vec[i];
          col++;
        }
      }
    } // Building the matrix rows

    TYPE det = mat.determinant();

    // The determinant of the sub-matrix = the normal at that dimension
    normal[d] = sign * det;

    // Sign flips each time
  } // each dimension of the normal vector

  // Unity normal is better.
  double length = normal.normalize();
  if (length == 0)
    throw std::runtime_error("VMDBase::getNormalVector: 0-length normal found. Are your vectors collinear?");
/// Instantiate VMDBase classes
template MANTID_KERNEL_DLL class VMDBase<double>;
template MANTID_KERNEL_DLL class VMDBase<float>;
} // namespace Mantid
} // namespace Kernel