/* Copyright (c) 1997-2004
   Ewgenij Gawrilow, Michael Joswig (Technische Universitaet Berlin, Germany)
   http://www.math.tu-berlin.de/polymake,  mailto:polymake@math.tu-berlin.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.
*/

#ident "$Project: polymake $$Id: truncation.cc,v 1.36 2004/07/27 18:07:48 gawrilow Exp $"

#include <cstdlib>
#include <argv_options.h>
#include <labels.h>
#include <vector>
#include <list>
#include <Poly.h>
#include <cutting_plane.h>
#include <Map.h>

namespace {
   enum { no_coordinates, relabel, cutoff };
}

namespace polymake { namespace polytope {

void truncation(Poly& p_out, Poly& p_in, Set<int> trunc_vertices, const argv_option *options)
{
   const Rational cutoff_factor= options[cutoff] ? Rational(options[cutoff]) : Rational(1,2);
   if (options[cutoff] && (cutoff_factor<=0 || cutoff_factor>1))
      throw std::runtime_error("cutoff factor must be within (0,1]");

   const IncidenceMatrix<> VIF=p_in.give("VERTICES_IN_FACETS");
   const Graph<> G=p_in.give("GRAPH");

   const int n_vertices=VIF.cols(), n_facets=VIF.rows();
   typedef Map<int,int> vertex_map_type;
   vertex_map_type vertex_map;		// truncated vertex => the first of the new vertices

#if __GNUC__==3 && __GNUC_MINOR__<=1
   (void)VIF.col(0);
   (void)keys(vertex_map).size();
#endif

   int n_vertices_out, n_trunc_vertices;
   if (trunc_vertices.empty()) {
      n_trunc_vertices=n_vertices;
      n_vertices_out=0;

      for (int i=0; i<n_vertices; ++i) {
	 vertex_map[i]=n_vertices_out;
	 n_vertices_out+=G.degree(i);
      }

   } else {
      if (trunc_vertices.front() < 0 || trunc_vertices.back() >= n_vertices)
	 throw std::runtime_error("vertex numbers out of range");

      n_trunc_vertices=trunc_vertices.size();
      n_vertices_out=n_vertices-n_trunc_vertices;

      for (Entire< Set<int> >::const_iterator v=entire(trunc_vertices); !v.at_end(); ++v) {
	 vertex_map[*v]=n_vertices_out;
	 n_vertices_out+=G.degree(*v);
      }
   }

   int n_facets_out=n_facets+n_trunc_vertices;

   std::vector<int> renumber_vertices;
   if (cutoff_factor==1) {
      renumber_vertices.resize(n_vertices);
      std::copy(sequence(0,1).begin(),
		entire(select(renumber_vertices, ~keys(vertex_map))));
   }

   IncidenceMatrix<> VIF_out(n_facets_out, n_vertices_out);

   // first inherit the original facets along with untouched vertices in them
   if (n_trunc_vertices < n_vertices)
      std::copy(entire(rows(VIF.minor(All,~keys(vertex_map)))), rows(VIF_out).begin());

   int new_facet=n_facets;
   for (Entire<vertex_map_type>::iterator tv=entire(vertex_map);  !tv.at_end();  ++tv, ++new_facet) {
      int new_vertex=tv->second;
      for (Entire< NodeNeighborhood< Graph<> >::row_type >::const_iterator
	      nb=entire(G.neighbors(tv->first));  !nb.at_end();  ++nb, ++new_vertex) {
	 // the new vertex inherits the ridge from the truncated vertex,
	 // and it belongs to the new facet
	 (VIF_out.col(new_vertex) = VIF.col(tv->first) * VIF.col(*nb)) += new_facet;
      }
   }

   std::vector<std::string> labels_out;
   if (options[relabel]) {
      std::vector<std::string> labels(n_vertices);
      read_labels(p_in, "VERTEX_LABELS", labels.begin(), n_vertices);
      labels_out.resize(n_vertices_out);
      std::copy(entire(select(labels, ~keys(vertex_map))), labels_out.begin());

      for (Entire<vertex_map_type>::iterator tv=entire(vertex_map);  !tv.at_end();  ++tv) {
	 int new_vertex=tv->second;
	 for (Entire< NodeNeighborhood< Graph<> >::row_type >::const_iterator
		 nb=entire(G.neighbors(tv->first));  !nb.at_end();  ++nb, ++new_vertex) {
	    labels_out[new_vertex]=labels[tv->first] + '-' + labels[*nb];
	 }
      }
   }

   if (options[no_coordinates]) {
      if (p_in.exists("DIM")) {
	 const int dim=p_in.give("DIM");
	 p_out.take("DIM") << dim;
      }

   } else {
      const Matrix<Rational> V=p_in.give("VERTICES"),
	                     F=p_in.give("FACETS"),
	                     AH=p_in.give("AFFINE_HULL");

      Matrix<Rational> F_out=F / zero_matrix<Rational>(n_trunc_vertices, F.cols());

      Rows< Matrix<Rational> >::iterator new_facet = rows(F_out).begin()+n_facets;
      for (Entire<vertex_map_type>::iterator tv=entire(vertex_map);  !tv.at_end();  ++tv, ++new_facet) {
	 *new_facet=cutting_plane_via_barycenter(V,G,tv->first);
	 typedef std::list< std::pair<int,int> > neighbor_list;
	 neighbor_list closest_neighbors;
	 int tries=0;
	 do {
	    ++tries;
	    const Rational value_at_v= V[tv->first] * (*new_facet);
	    Rational closest_value;
	    int new_vertex=tv->second;
	    for (Entire< NodeNeighborhood< Graph<> >::row_type >::const_iterator
		    nb=entire(G.neighbors(tv->first));  !nb.at_end();  ++nb, ++new_vertex) {
	       // the cutoff factor on the edges connecting two truncated vertices must be taken
	       // half as much as on other edges, preventing intersection of the new facets inside the polytope
	       const Rational scale= vertex_map.exists(*nb) ? cutoff_factor/2 : cutoff_factor;
	       const Rational value= scale*(V[*nb] * (*new_facet)) + (1-scale)*value_at_v;
	       if (value<=value_at_v) {
		  if (tries==1) {
		     closest_neighbors.clear();
		     *new_facet=cutting_plane_via_normals(V,F,VIF,tv->first);
		  }
		  ++tries;
		  break;
	       }
	       if (closest_neighbors.empty() || value<closest_value) {
		  closest_value=value;
		  closest_neighbors.clear();
		  closest_neighbors.push_back(std::make_pair(*nb,new_vertex));
	       } else if (value==closest_value) {
		  closest_neighbors.push_back(std::make_pair(*nb,new_vertex));
	       }
	    }
	    (*new_facet)[0]=-closest_value;
	 } while (tries==2);

	 if (cutoff_factor==1) {
	    // we must take care of coinciding vertices
	    for (Entire<neighbor_list>::iterator nb=entire(closest_neighbors); !nb.at_end();  ++nb) {
	       int other_vertex;
	       if (vertex_map.exists(nb->first)) {
		  // pairs of coinciding new vertices should not be handled twice
		  if (tv->first < nb->first) continue;
		  other_vertex=vertex_map[nb->first];
		  // number of the opposite new vertex can be found only by enumeration of neighbors...
		  for (NodeNeighborhood< Graph<> >::row_type::const_iterator other_nb=G.neighbors(nb->first).begin();
		       *other_nb != tv->first;  ++other_nb, ++other_vertex) ;
	       } else {
		  other_vertex=renumber_vertices[nb->first];
	       }
	       // the new vertex coincides with the neighbor and disappears
	       VIF_out.col(other_vertex) += VIF_out.col(nb->second).back(); // the new facet
	       VIF_out.col(nb->second).clear();
	       if (options[relabel]) labels_out[nb->second].clear();
	    }
	 }
      }

      p_out.take("FACETS")  << F_out;
      p_out.take("AFFINE_HULL") << AH;

      if (cutoff_factor==1) {
	 VIF_out.squeeze();
	 if (options[relabel]) {
	    labels_out.resize(std::remove(labels_out.begin()+n_vertices-n_trunc_vertices,
					  labels_out.end(), std::string())
			      - labels_out.begin());
	 }
      }
   }

   p_out.take("N_VERTICES") << VIF_out.cols();
   p_out.take("VERTICES_IN_FACETS") << VIF_out;

   if (options[relabel])
      p_out.take("VERTEX_LABELS") << labels_out;
}

} }

using namespace polymake;

/** @file truncation
 *
 *  Cut off one or more vertices of a polyhedron.
 *
 *  $vertex$ is the number of the vertex to be cut off.
 *  The keyword @c all means all vertices of the original polyhedron.
 *
 *  Parameter $cf$ controls the exact location of the cutting hyperplane(s).
 *  It should be a rational number from $(0,1]$.
 *  When $cf=0$, the hyperplane would go through the chosen vertex, thus cutting off nothing.
 *  When $cf=1$, the hyperplane touches the nearest neighbor vertex of a polyhedron.
 *  Default value for $cf$ is $1/2$.
 *
 *  Alternatively, the option @c -noc (no coordinates) can be specified to produce a
 *  pure combinatorial description of the resulting polytope, which would correspond to
 *  the cutoff factor $1/2$.
 *
 *  The option @c -relabel creates an additional section @see VERTEX_LABELS.
 *  New vertices get labels of the form 'LABEL1-LABEL2', where LABEL1 is the original label
 *  of the truncated vertex, and LABEL2 is the original label of its neighbor.
 *
 *  @synopsis truncation <output_file> <input_file> { <vertex> [ <vertex> ... ] | all } [ -cutoff <cf> | -noc ] [ -relabel ]
 *
 *  @reading  VERTICES, FACETS, AFFINE_HULL, VERTICES_IN_FACETS, GRAPH, DIM, VERTEX_LABELS
 *  @writing  FACETS, AFFINE_HULL, VERTICES_IN_FACETS, N_VERTICES, DIM, VERTEX_LABELS
 *
 *  @index clients Producing a new polyhedron from others
 *
 *  @author Kerstin Fritzsche (initial version)
 */

int main(int argc, const char *argv[]) {
   argv_option options[]={ { "-noc" }, { "-relabel" }, { "-cutoff", true } };
   if (!extract_options(argc,argv,2,options) ||
       argc < 4 ||
       options[cutoff] && options[no_coordinates]) {
      cerr << "usage: " << argv[0] <<  " <out_file> <in_file> { <vertex> [ <vertex> ... ] | all } [ -cutoff <cf> | -noc ] [ -relabel ]" << endl;
      return 1;
   }
   try {
      Poly p_in(argv[2], ios::in);
      Poly p_out(argv[1], ios::out | ios::trunc);

      Set<int> trunc_vertices;
      if (argc>4 || strcmp(argv[3],"all")) {
	 for (int i=3; i<argc; ++i) {
	    trunc_vertices += atoi(argv[i]);
	 }
	 if (trunc_vertices.size() != argc-3) {
	    cerr << argv[0] << ": repeating vertex numbers in the list" << endl;
	    return 1;
	 }
      }

      polytope::truncation(p_out, p_in, trunc_vertices, options);
   }
   catch (const std::exception& e) {
      cerr << e.what() << endl;
      return 1;
   }
   return 0;
}
