/* Copyright (c) 1997-2010
   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: projection.cc 10061 2011-03-16 12:46:53Z herr $
*/

#include "polymake/client.h"
#include "polymake/Rational.h"
#include "polymake/Matrix.h"
#include "polymake/ListMatrix.h"
#include "polymake/PowerSet.h"
#include "polymake/linalg.h"
#include "polymake/Array.h"

namespace polymake { namespace polytope {

perl::Object projection(perl::Object p_in, const Array<int>& indices, perl::OptionSet options)
{
  const int ambient_dim=p_in.CallPolymakeMethod("AMBIENT_DIM");
  const int dim=p_in.CallPolymakeMethod("DIM");
  const int codim=ambient_dim-dim;
  const Matrix<Rational> affine_hull=p_in.give("AFFINE_HULL");
  if (codim!=affine_hull.rows())
    throw std::runtime_error("projection: AFFINE_HULL has wrong number of rows");
  
  Set<int> coords;   // set of columns to project to
  
  if (indices.empty()) {
    if (ambient_dim==dim)
      throw std::runtime_error("projection: polyhedron full-dimensional, don't know where to project on");
    bool found=false;
    for (Entire< Subsets_of_k<const sequence&> >::const_iterator i=entire(all_subsets_of_k(sequence(1,ambient_dim),codim));!found&&!i.at_end(); ++i)
      if (det(affine_hull.minor(All,*i))!=0) {
        coords=*i;
        found=true;
      }
    if (!found) throw std::runtime_error("projection: no non-singular minor in AFFINE_HULL");
  } else {
    for (Entire< Array<int> >::const_iterator i=entire(indices); !i.at_end(); ++i) {
      if (*i<1 || *i>ambient_dim)
        throw std::runtime_error("projection: index out of range");
      coords+=*i;
    }
    if (!options["revert"])
      coords=range(1,ambient_dim)-coords;
  }
  
  Matrix<Rational> Points, Inequalities;
  bool points_read=false, inequalities_read=false;
  
  perl::Object p_out("Polytope<Rational>");

  if (p_in.lookup("VERTICES | POINTS") >> Points) {
    points_read=true;
    p_out.take("POINTS") << Points.minor(All,~coords);
  }
  
  if (!points_read && options["nofm"])
    throw std::runtime_error("projection: no points found and Fourier-Motzkin elimination excluded");
  
  if (!options["nofm"]) {
    if (p_in.lookup("FACETS | INEQUALITIES") >> Inequalities) {
      inequalities_read=true;
      Inequalities /= affine_hull / (-affine_hull);
    }
    
    if (inequalities_read) {
       // perform Fourier-Motzkin elimination on coords
      for (Entire< Set<int> >::const_reverse_iterator c=rentire(coords); !c.at_end(); ++c) {
        Set<int> negative, zero, positive;
        // normalize Inequalities such that there is a -1,0, or 1 in the next column to be eliminated
        for (int i=0; i<Inequalities.rows(); ++i) {
          const Rational x=Inequalities(i,*c);
          const int s=sign(x);
          if (s) {
            if (s<0)
                     negative += i;
            else
              positive += i;
            Inequalities[i] /= abs(x);
          } else
            zero += i;
        }
        const Set<int> remaining_coords=range(0,Inequalities.cols()-1)-(*c);
        Inequalities = Inequalities.minor(All,remaining_coords);
        ListMatrix< Vector<Rational> > Combined_Ineqs=Inequalities.minor(zero,All);
        for (Entire< Set<int> >::const_iterator i=entire(negative); !i.at_end(); ++i)
          for (Entire< Set<int> >::const_iterator k=entire(positive); !k.at_end(); ++k)
            Combined_Ineqs /= Inequalities[*i]+Inequalities[*k];
        // delete redundant rows
        for (Entire< Rows< ListMatrix< Vector<Rational> > > >::iterator i=entire(rows(Combined_Ineqs)); !i.at_end(); ) {
          const Rows< ListMatrix< Vector<Rational> > >::iterator j(i++);
          if (!*j) Combined_Ineqs.delete_row(j);
        }
        Inequalities = Combined_Ineqs;
      }
      p_out.take("INEQUALITIES") << Inequalities;
    }
  }
  return p_out;
}

UserFunction4perl("# @category Producing a new polyhedron from others"
                  "# Orthogonally project a polyhedron to a coordinate subspace."
                  "# "
                  "# The subspace the polyhedron //P// is projected on is given by the coordinate indices in the set //indices//."
                  "# The option //revert// inverts the coordinate list."
                  "# The client scans for all coordinate sections and produces proper output from each."
                  "# If a description in terms of inequalities is found, the client performs Fourier-Motzkin elimination"
                  "# unless the //nofm// option is set.  Setting the //nofm// option is useful if the corank of the projection"
                  "# is large; in this case the number of inequalities produced grows quickly."
                  "# @param Polytope P"
                  "# @param Array<Int> indices"
                  "# @option Bool revert inverts the coordinate list"
                  "# @option Bool nofm suppresses Fourier-Motzkin elimination"
                  "# @return Polytope", 
                  &projection,"projection(Polytope; $=[ ], {revert => 0, nofm => 0})");

InsertEmbeddedRule("# @category Producing a new polyhedron from others"
                   "# Orthogonally project a polyhedron to a coordinate subspace such that \"redundant\" columns are omitted,"
                   "# i.e., the projection becomes full-dimensional without changing the combinatorial type."
                   "# The client scans for all coordinate sections and produces proper output from each."
                   "# If a description in terms of inequalities is found, the client performs Fourier-Motzkin elimination"
                   "# unless the //nofm// option is set.  Setting the //nofm// option is useful if the corank of the projection"
                   "# is large; in this case the number of inequalities produced grows quickly."
                   "# @param Polytope P"
                   "# @option Bool nofm suppresses Fourier-Motzkin elimination"
                   "# @return Polytope\n"
                   "user_function projection_full(Polytope; {nofm => 0}) { projection(@_); }\n");
} }

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