/* Copyright (c) 1997-2011
   Ewgenij Gawrilow, Michael Joswig (Technische Universitaet Darmstadt, Germany)
   http://www.polymake.de

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2, or (at your option) any
   later version: http://www.gnu.org/licenses/gpl.txt.

   This program 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 General Public License for more details.
--------------------------------------------------------------------------------
   $Project: polymake $$Id: linalg.h 10238 2011-05-29 21:51:01Z gawrilow $
*/

#ifndef POLYMAKE_LINALG_H
#define POLYMAKE_LINALG_H

#include "polymake/Rational.h"
#include "polymake/Matrix.h"
#include "polymake/SparseMatrix.h"
#include "polymake/SparseVector.h"
#include "polymake/ListMatrix.h"
#include "polymake/Set.h"

#include "polymake/internal/linalg_exceptions.h"
#include "polymake/internal/dense_linalg.h"
#include "polymake/internal/sparse_linalg.h"

namespace pm { namespace operations {

template <typename OpRef,
          typename Discr=typename object_traits<typename deref<OpRef>::type>::generic_tag>
struct normalize_impl;

template <typename OpRef>
struct normalize_impl<OpRef, is_vector>
   : div_impl<OpRef, typename deref<OpRef>::type::element_type, cons<is_vector, is_scalar> > {

   typedef OpRef argument_type;
   typedef div_impl<OpRef, typename deref<OpRef>::type::element_type, cons<is_vector, is_scalar> > _super;

   typename _super::result_type operator() (typename function_argument<OpRef>::const_type v) const
   {
      return _super::operator()(v, sqrt(sqr(v)));
   }

   void assign(typename lvalue_arg<OpRef>::type v) const
   {
      v /= sqrt(sqr(v));
   }
};

template <typename OpRef>
struct normalize_vectors : normalize_impl<OpRef> {};

template <typename OpRef,
          typename Discr=typename object_traits<typename deref<OpRef>::type>::generic_tag>
struct dehomogenize_impl;

template <typename OpRef,
          typename Discr=typename object_traits<typename deref<OpRef>::type>::generic_tag>
struct dehomogenize_trop_impl;

template <typename OpRef>
class dehomogenize_impl<OpRef, is_vector> {
protected:
   typedef IndexedSlice<typename attrib<OpRef>::plus_const_ref, sequence> slice;
   typedef typename container_traits<OpRef>::const_reference element_ref;
   static const bool _is_sparse=check_container_ref_feature<OpRef,sparse>::value;
   typedef LazyVector2<slice, constant_value_container<element_ref>, polymake::operations::div>
      lazy_vector;
public:
   typedef OpRef argument_type;
   typedef ContainerUnion< cons<slice, lazy_vector> > result_type;
protected:
   static
   result_type _do(typename function_argument<OpRef>::const_type v, False)
   {
      typename container_traits<OpRef>::const_reference first=v.front();
      if (!first || first==1) return v.slice(1);
      return lazy_vector(v.slice(1), first);
   }
   static
   result_type _do(typename function_argument<OpRef>::const_type v, True)
   {
      typename container_traits<OpRef>::const_iterator first=v.begin();
      if (first.at_end() || first.index() || *first==1) return v.slice(1);
      return lazy_vector(v.slice(1), *first);
   }
public:
   result_type operator() (typename function_argument<OpRef>::const_type v) const
   {
      return _do(v, bool2type<_is_sparse>());
   }
};

template <typename OpRef>
class dehomogenize_trop_impl<OpRef, is_vector> {
protected:
   typedef IndexedSlice<typename attrib<OpRef>::plus_const_ref, sequence> slice;
   typedef typename container_traits<OpRef>::const_reference element_ref;
   static const bool _is_sparse=check_container_ref_feature<OpRef,sparse>::value;
   typedef LazyVector2<slice, typename if_else<_is_sparse, SameElementVector<element_ref>, constant_value_container<element_ref> >::type, polymake::operations::sub>
      lazy_vector;
public:
   typedef OpRef argument_type;
   typedef ContainerUnion< cons<slice, lazy_vector> > result_type;
protected:
   static
   result_type _do(typename function_argument<OpRef>::const_type v, False)
   {
      typename container_traits<OpRef>::const_reference first=v.front();
      if (!first) return v.slice(1);
      return lazy_vector(v.slice(1), first);
   }
   static
   result_type _do(typename function_argument<OpRef>::const_type v, True)
   {
      typename container_traits<OpRef>::const_iterator first=v.begin();
      if (first.at_end() || first.index()) return v.slice(1);
      return lazy_vector(v.slice(1), SameElementVector<element_ref>(*first,v.dim()-1));
   }
public:
   result_type operator() (typename function_argument<OpRef>::const_type v) const
   {
      return _do(v, bool2type<_is_sparse>());
   }
};

template <typename OpRef>
struct dehomogenize_vectors : dehomogenize_impl<OpRef> {};

template <typename OpRef>
struct dehomogenize_trop_vectors : dehomogenize_trop_impl<OpRef> {};

} } // end namespace pm::operations

namespace polymake { namespace operations {
   typedef BuildUnary<pm::operations::normalize_vectors> normalize_vectors;
   typedef BuildUnary<pm::operations::dehomogenize_vectors> dehomogenize_vectors;
   typedef BuildUnary<pm::operations::dehomogenize_trop_vectors> dehomogenize_trop_vectors;
} }

namespace pm {

/// Divide each vector in a sequence thru its length (L2-norm)
template <typename Iterator> inline
void normalize(Iterator dst)
{
   perform_assign(dst, polymake::operations::normalize_vectors());
}

template <typename Matrix> inline
typename Matrix::persistent_type
normalized(const GenericMatrix<Matrix>& m)
{
   return typename Matrix::persistent_type(m.rows(), m.cols(),
                                           entire(attach_operation(rows(m), polymake::operations::normalize_vectors())));
}

/// Compute the determinant of a matrix using the Gauss elimination method
template <typename _Matrix> inline
typename disable_if<typename _Matrix::element_type, std::numeric_limits<typename _Matrix::element_type>::is_integer>::type
det(const GenericMatrix<_Matrix>& m)
{
   if (POLYMAKE_DEBUG || !Unwary<_Matrix>::value) {
      if (m.rows() != m.cols())
         throw std::runtime_error("det - non-square matrix");
   }
   return det(typename _Matrix::persistent_nonsymmetric_type(m));
}


template <typename _Matrix> inline
typename enable_if<Integer, std::numeric_limits<typename _Matrix::element_type>::is_integer>::type
det(const GenericMatrix<_Matrix>& m)
{
   if (POLYMAKE_DEBUG || !Unwary<_Matrix>::value) {
      if (m.rows() != m.cols())
         throw std::runtime_error("det - non-square matrix");
   }
   return Integer(det(typename GenericMatrix<_Matrix,Rational>::persistent_nonsymmetric_type(m)));
}

/// Reduce a vector with a given matrix using the Gauss elimination method
template <typename _Matrix, typename _Vector, typename E> inline
typename disable_if<typename _Vector::persistent_type, std::numeric_limits<E>::is_integer>::type
reduce(const GenericMatrix<_Matrix, E>& A, const GenericVector<_Vector, E>& V)
{
   if (POLYMAKE_DEBUG || !Unwary<_Matrix>::value || !Unwary<_Vector>::value) {
      if (V.dim() != A.cols())
         throw std::runtime_error("reduce - dimension mismatch");
   }
   typedef typename if_else<_Matrix::is_sparse, SparseVector<E>, Vector<E> >::type vector_type;
   return reduce(typename _Matrix::persistent_nonsymmetric_type(A), vector_type(V));
}

template <typename _Matrix, typename _Vector, typename E> inline
typename enable_if<Vector<Rational>, std::numeric_limits<E>::is_integer>::type
reduce(const GenericMatrix<_Matrix, E>& A, const GenericVector<_Vector, E>& V)
{
   if (POLYMAKE_DEBUG || !Unwary<_Matrix>::value || !Unwary<_Vector>::value) {
      if (V.dim() != A.cols())
         throw std::runtime_error("reduce - dimension mismatch");
   }
   typedef typename if_else<_Matrix::is_sparse, SparseVector<Rational>, Vector<Rational> >::type vector_type;
   return reduce(typename GenericMatrix<_Matrix,Rational>::persistent_nonsymmetric_type(A), vector_type(V));
}

/** Compute the inverse matrix $A^-1$ using the Gauss elimination method.
    @exception degenerate_matrix if det(A)==0
*/
template <typename _Matrix> inline
typename disable_if<typename _Matrix::persistent_type, std::numeric_limits<typename _Matrix::element_type>::is_integer>::type
inv(const GenericMatrix<_Matrix>& m)
{
   if (POLYMAKE_DEBUG || !Unwary<_Matrix>::value) {
      if (m.rows() != m.cols())
         throw std::runtime_error("inv - non-square matrix");
   }
   return inv(typename _Matrix::persistent_type(m));
}

template <typename _Matrix> inline
typename enable_if<typename GenericMatrix<_Matrix,Rational>::persistent_type,
                   std::numeric_limits<typename _Matrix::element_type>::is_integer>::type
inv(const GenericMatrix<_Matrix>& m)
{
   if (POLYMAKE_DEBUG || !Unwary<_Matrix>::value) {
      if (m.rows() != m.cols())
         throw std::runtime_error("inv - non-square matrix");
   }
   return inv(typename GenericMatrix<_Matrix,Rational>::persistent_type(m));
}

/** Solve the linear system A*x==B
    @return x
    @exception degenerate_matrix if det(A) == 0
    @exception infeasible if rank(A) != rank(A|B)
*/
template <typename _Matrix, typename _Vector, typename E> inline
typename disable_if<Vector<E>, std::numeric_limits<E>::is_integer>::type
lin_solve(const GenericMatrix<_Matrix, E>& A, const GenericVector<_Vector, E>& B)
{
   if (POLYMAKE_DEBUG || !Unwary<_Matrix>::value || !Unwary<_Vector>::value) {
      if (B.dim() != A.rows())
         throw std::runtime_error("lin_solve - dimension mismatch");
   }
   return lin_solve(typename _Matrix::persistent_nonsymmetric_type(A), Vector<E>(B));
}

template <typename _Matrix, typename _Vector, typename E> inline
typename enable_if<Vector<Rational>, std::numeric_limits<E>::is_integer>::type
lin_solve(const GenericMatrix<_Matrix, E>& A, const GenericVector<_Vector, E>& B)
{
   if (POLYMAKE_DEBUG || !Unwary<_Matrix>::value || !Unwary<_Vector>::value) {
      if (B.dim() != A.rows())
         throw std::runtime_error("lin_solve - dimension mismatch");
   }
   return lin_solve(typename GenericMatrix<_Matrix,Rational>::persistent_nonsymmetric_type(A), Vector<Rational>(B));
}

/** Compute the basis of the subspaces spanned by a sequence of vectors and orthogonal one.
    @param v input iterator over the vectors
    @param row_basis_consumer output iterator consuming the indices of basis rows (=vectors)
    @param col_basis_consumer output iterator consuming the indices of basis columns (=coordinates)
*/
template <typename VectorIterator, typename RowBasisOutputIterator, typename ColBasisOutputIterator, typename E>
void
null_space(VectorIterator v, RowBasisOutputIterator row_basis_consumer, ColBasisOutputIterator col_basis_consumer,
           ListMatrix< SparseVector<E> >& H, bool simplify=false)
{
   typedef ListMatrix< SparseVector<E> > AH_matrix;
   conv<E,bool> non_zero;

   for (int i=0; H.rows()>0 && !v.at_end(); ++v, ++i) {
      for (typename Entire< Rows<AH_matrix> >::iterator h=entire(rows(H)); !h.at_end(); ++h) {
         const E pivot=(*h)*(*v);
         if (non_zero(pivot)) {
            if (!derived_from_instance<RowBasisOutputIterator,black_hole>::value)
               *row_basis_consumer++ = i;
            if (!derived_from_instance<ColBasisOutputIterator,black_hole>::value)
               *col_basis_consumer++ = h->rbegin().index();

            typename Entire< Rows<AH_matrix> >::iterator h2=h;
            while (!(++h2).at_end()) {
               const E x=(*h2)*(*v);
               if (non_zero(x)) {
                  if (std::numeric_limits<E>::is_integer) {
                     *h2 *= pivot; *h2 -= x*(*h);
                  } else
                     *h2 -= (x/pivot)*(*h);
               }
            }
            H.delete_row(h);
            break;
         }
      }
   }

   if (std::numeric_limits<E>::is_integer && simplify) {
      for (typename Entire< Rows<AH_matrix> >::iterator h=entire(rows(H)); !h.at_end(); ++h) {
         const E g=gcd(*h);
         if (g>1) h->div_exact(g);
      }
   }
}

template <typename _Matrix, typename E> inline
typename _Matrix::persistent_nonsymmetric_type
null_space(const GenericMatrix<_Matrix, E>& M)
{
   ListMatrix< SparseVector<E> > H=unit_matrix<E>(M.cols());
   null_space(entire(rows(M)), black_hole<int>(), black_hole<int>(), H, true);
   return H;
}

template <typename _Vector, typename E> inline
ListMatrix< SparseVector<E> >
null_space(const GenericVector<_Vector, E>& V)
{
   ListMatrix< SparseVector<E> > H=unit_matrix<E>(V.dim());
   null_space(entire(item2container(V.top())), black_hole<int>(), black_hole<int>(), H, true);
   return H;
}

/// @param req_sign expected sign of det( null_space(V) / V )
template <typename _Vector, typename E>
ListMatrix< SparseVector<E> >
null_space_oriented(const GenericVector<_Vector, E>& V, int req_sign)
{
   ListMatrix< SparseVector<E> > H=unit_matrix<E>(V.dim());
   null_space(entire(item2container(V.top())), black_hole<int>(), black_hole<int>(), H, true);
   typename ensure_features<_Vector, pure_sparse>::const_iterator v_pivot=ensure(V.top(), (pure_sparse*)0).begin();
   if (v_pivot.at_end() && req_sign)
      throw infeasible("null_space_oriented: zero vector has no orientation");
   if ((sign(*v_pivot)==req_sign) == ((v_pivot.index()+V.dim()+1)%2))
      rows(H).back().negate();
   return H;
}

template <typename _Matrix, typename E> inline
typename _Matrix::persistent_nonsymmetric_type
lineality_space(const GenericMatrix<_Matrix, E>& M)
{
   ListMatrix< SparseVector<E> > H=unit_matrix<E>(M.cols()-1);
   null_space(entire(rows(M.minor(All, range(1,M.cols()-1)))), black_hole<int>(), black_hole<int>(), H, true);
   if (H.rows()) return zero_vector<E>(H.rows()) | H;
   return typename _Matrix::persistent_nonsymmetric_type();
}

template <typename VectorIterator> inline
Set<int>
basis_vectors(VectorIterator v)
{
   typedef typename iterator_traits<VectorIterator>::value_type::element_type E;
   ListMatrix< SparseVector<E> > H=unit_matrix<E>(v.at_end() ? 0 : v->dim());
   Set<int> b;
   null_space(v, std::back_inserter(b), black_hole<int>(), H);
   return b;
}

template <typename _Matrix, typename E> inline
Set<int>
basis_rows(const GenericMatrix<_Matrix,E>& M)
{
   ListMatrix< SparseVector<E> > H=unit_matrix<E>(M.cols());
   Set<int> b;
   null_space(entire(rows(M)), std::back_inserter(b), black_hole<int>(), H);
   return b;
}

template <typename _Matrix> inline
Set<int>
basis_cols(const GenericMatrix<_Matrix>& M)
{
   return basis_rows(T(M));
}

template <typename _Matrix, typename E> inline
std::pair< Set<int>, Set<int> >
basis(const GenericMatrix<_Matrix, E>& M)
{
   ListMatrix< SparseVector<E> > H=unit_matrix<E>(M.cols());
   Set<int> br, bc;
   null_space(entire(rows(M)), std::back_inserter(br), inserter(bc), H);
   return std::make_pair(br,bc);
}

template <typename _Matrix, typename E> inline
int rank(const GenericMatrix<_Matrix, E>& M)
{
   if (M.rows() <= M.cols()) {
      ListMatrix< SparseVector<E> > H=unit_matrix<E>(M.rows());
      null_space(entire(cols(M)), black_hole<int>(), black_hole<int>(), H);
      return M.rows()-H.rows();
   }
   ListMatrix< SparseVector<E> > H=unit_matrix<E>(M.cols());
   null_space(entire(rows(M)), black_hole<int>(), black_hole<int>(), H);
   return M.cols()-H.rows();
}

template <typename _Matrix> inline
Set<int>
basis_rows(const GenericMatrix<_Matrix, double>& M)
{
   ListMatrix< SparseVector<double> > H=unit_matrix<double>(M.cols());
   Set<int> b;
   null_space(entire(attach_operation(rows(M), polymake::operations::normalize_vectors())),
              std::back_inserter(b), black_hole<int>(), H);
   return b;
}

template <typename _Matrix> inline
std::pair< Set<int>, Set<int> >
basis(const GenericMatrix<_Matrix, double>& M)
{
   ListMatrix< SparseVector<double> > H=unit_matrix<double>(M.cols());
   Set<int> br, bc;
   null_space(entire(attach_operation(rows(M), polymake::operations::normalize_vectors())),
              std::back_inserter(br), inserter(bc), H);
   return std::make_pair(br,bc);
}

template <typename _Matrix> inline
int rank(const GenericMatrix<_Matrix, double>& M)
{
   if (M.rows() <= M.cols()) {
      ListMatrix< SparseVector<double> > H=unit_matrix<double>(M.rows());
      null_space(entire(attach_operation(cols(M), polymake::operations::normalize_vectors())),
                 black_hole<int>(), black_hole<int>(), H);
      return M.rows()-H.rows();
   }
   ListMatrix< SparseVector<double> > H=unit_matrix<double>(M.cols());
   null_space(entire(attach_operation(rows(M), polymake::operations::normalize_vectors())),
              black_hole<int>(), black_hole<int>(), H);
   return M.cols()-H.rows();
}

/// The same as basis(), but ignoring the first column of the matrix.
template <typename _Matrix, typename E> inline
std::pair< Set<int>, Set<int> >
basis_affine(const GenericMatrix<_Matrix, E>& M)
{
   int ad=M.cols()-1;
   ListMatrix< SparseVector<E> > H=unit_matrix<E>(ad);
   Set<int> br, bc;
   null_space(entire(rows(M.minor(All,range(1,ad)))), std::back_inserter(br),
              make_output_transform_iterator(inserter(bc), operations::fix2<int, operations::add<int,int> >(1)),
              H);
   return std::make_pair(br,bc);
}

/// Project u to v.
template <typename E, typename _Vector1, typename _Vector2> inline
typename _Vector2::persistent_type
proj(const GenericVector<_Vector1,E>& u, const GenericVector<_Vector2,E>& v)
{
   return (u*v)/sqr(v)*v;
}

/// Divide by the first element and strip it off
template <typename Vector>
typename GenericVector<Vector>::persistent_type
dehomogenize(const GenericVector<Vector>& V)
{
   if (POLYMAKE_DEBUG || !Unwary<Vector>::value) {
      if (V.dim()==0)
         throw std::runtime_error("dehomogenize - vector of zero length");
   }
   return operations::dehomogenize_vectors<const Vector&>()(V.top());
}

template <typename Matrix>
typename GenericMatrix<Matrix>::persistent_nonsymmetric_type
dehomogenize(const GenericMatrix<Matrix>& M)
{
   if (POLYMAKE_DEBUG || !Unwary<Matrix>::value) {
      if (M.cols()==0)
         throw std::runtime_error("dehomogenize - matrix of zero width");
   }
   return typename GenericMatrix<Matrix>::persistent_nonsymmetric_type(M.rows(), M.cols()-1,
                                                                       entire(attach_operation(rows(M), polymake::operations::dehomogenize_vectors())));
}

/// Subtract the first element and strip it off
template <typename Vector>
typename GenericVector<Vector>::persistent_type
dehomogenize_trop(const GenericVector<Vector>& V)
{
   if (POLYMAKE_DEBUG || !Unwary<Vector>::value) {
      if (V.dim()==0)
         throw std::runtime_error("dehomogenize_trop - vector of zero length");
   }
   return operations::dehomogenize_trop_vectors<const Vector&>()(V.top());
}

template <typename Matrix>
typename GenericMatrix<Matrix>::persistent_nonsymmetric_type
dehomogenize_trop(const GenericMatrix<Matrix>& M)
{
   if (POLYMAKE_DEBUG || !Unwary<Matrix>::value) {
      if (M.cols()==0)
         throw std::runtime_error("dehomogenize_trop - matrix of zero width");
   }
   return typename GenericMatrix<Matrix>::persistent_nonsymmetric_type(M.rows(), M.cols()-1,
                                                                       entire(attach_operation(rows(M), polymake::operations::dehomogenize_trop_vectors())));
}

template <typename VectorIterator, typename OutputIterator>
void orthogonalize(VectorIterator v, OutputIterator sqr_consumer)
{
   typedef typename iterator_traits<VectorIterator>::value_type vector_type;
   typedef typename vector_type::element_type E;
   // the first vector will not be modified
   while (!v.at_end()) {
      const E s=sqr(*v);
      if (s) {
         VectorIterator v2=v;
         for (++v2; !v2.at_end(); ++v2) {
            const E x=(*v2) * (*v);
            if (x) *v2 -= (x/s) * (*v);
         }
      }
      *sqr_consumer++ = s;
      ++v;
   }
}

template <typename VectorIterator, typename OutputIterator>
void orthogonalize_affine(VectorIterator v, OutputIterator sqr_consumer)
{
   typedef typename iterator_traits<VectorIterator>::value_type vector_type;
   typedef typename vector_type::element_type E;
   // the first vector will not be modified
   while (!v.at_end()) {
      const E s=sqr(v->slice(1));
      if (s) {
         VectorIterator v2=v;
         for (++v2; !v2.at_end(); ++v2) {
            const E x=v2->slice(1) * v->slice(1);
            if (x) *v2 -= (x/s) * (*v);
         }
      }
      *sqr_consumer++ = s;
      ++v;
   }
}

/// Apply the Gram-Schmidt orthogonalization to the vector sequence.
template <typename VectorIterator> inline
void orthogonalize(VectorIterator v)
{
   orthogonalize(v, black_hole<typename iterator_traits<VectorIterator>::value_type::element_type>());
}

/** The same as orthogonalize(.), but making the affine parts of the resulting vectors
    (without 0-th coordinate) orthogonal
*/
template <typename VectorIterator> inline
void orthogonalize_affine(VectorIterator v)
{
   orthogonalize_affine(v, black_hole<typename iterator_traits<VectorIterator>::value_type::element_type>());
}

/// Find row indices of all far points (that is, having zero in the first column).
template <typename _Matrix> inline
Set<int>
far_points(const GenericMatrix<_Matrix>& M)
{
   return indices(attach_selector(M.col(0), polymake::operations::logical_not()));
}

/// Find indices of rows orthogonal to the given vector
template <typename E, typename _Matrix, typename _Vector> inline
Set<int>
orthogonal_rows(const GenericMatrix<_Matrix,E>& M, const GenericVector<_Vector,E>& v)
{
   return indices(attach_selector(attach_operation(rows(M), constant(v), polymake::operations::mul()),
                                  polymake::operations::logical_not()));
}

template <typename Vector> inline
Integer common_denominator(const GenericVector<Vector>& v)
{
   typename Entire<Vector>::const_iterator s=entire(v.top());
   Integer LCM=denominator(*s);
   while (!(++s).at_end())
      if (denominator(*s)!=1) LCM=lcm(LCM,denominator(*s));
   return LCM;
}

} // end namespace pm

namespace polymake {
   using pm::null_space;
   using pm::lineality_space;
   using pm::basis_vectors;
   using pm::orthogonalize;
   using pm::orthogonalize_affine;
   using pm::normalize;
}

#endif // POLYMAKE_LINALG_H

// Local Variables:
// mode:C++
// c-basis-offset:3
// indent-tabs-mode:nil
// End:
