/* 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: bistellar.cc,v 1.31 2004/12/08 09:34:56 gawrilow Exp $"

#include <Poly.h>
#include <psBistellarComplex.h>
#include <BistellarComplex.h>
#include <argv_options.h>
#include <read_int_args.h>

#include <sstream>
#include <cmath>
#include <ctime>
#include <list>

namespace polymake { namespace topaz {
   
namespace {

enum { in_ps, in_rounds, in_abs, in_obj, in_max_relax, in_heat,
       in_constant, allow_rev_move, in_verbose, in_seed, quiet, distribution };

bool improved(const Array<int>& min, const Array<int>& cand, const int obj)
{
   if (obj == 0) {  // lex order
      for (int i=0; i<min.size(); ++i) {
	 if (cand[i] < min[i]) return true;
	 if (cand[i] > min[i]) return false;
      }
      return false;
   }

   if (obj == 0) {  // rev lex order
      for (int i=min.size()-1; i>=0; ++i) {
	 if (cand[i] < min[i]) return true;
	 if (cand[i] > min[i]) return false;
      }
      return false;
   }

   // obj 2
   return accumulate(cand, operations::add()) < accumulate(min, operations::add());
}
   
   // random up or eaven move according to distribution
   int rnd_dim(const int min_heat_dim, const Array<int>& distribution) {
      const int rnd = int(floor(drand48()*distribution[distribution.size()-1]));
      int move = 0;
      for ( ; rnd>=distribution[move]; ++move);
      
      return min_heat_dim + move;
   }
   
}

void bistellar (std::ostream& os, Poly& p_out, Poly& p_in, const argv_option *options,
		Array<int> in_distribution, std::ostringstream& comment)
{
   const bool ps = options[in_ps];
   const int verbose= options[in_verbose] ? atoi(options[in_verbose]) : 0;

   if (verbose)
      os << "initialising ...\n";
   
   HasseDiagram<> HD;
   PseudoComplex C;
   int dim, size;
   if (ps) {
      if (p_in.isa("PseudoComplex"))
	 C = p_in.give("LATTICE");
      else {
	 HD = p_in.give("HASSE_DIAGRAM");
	 C = PseudoComplex(HD);
      }
      
      dim = C.dim();
      size = C.n_facets();
   } else {
      HD = p_in.give("HASSE_DIAGRAM");
      dim = HD.dim()-1;
      size = HD.nodes_of_dim(dim).size();
   }
   
   const bool is_closed = p_in.give("CLOSED_PSEUDO_MANIFOLD");
   const bool is_pmf = p_in.give("PSEUDO_MANIFOLD");
   if (ps && !is_closed)
      throw std::runtime_error("bistellar -ps: Complex is not a CLOSED_PSEUDO_MANIFOLD.");
   if (!is_pmf)
      throw std::runtime_error("bistellar: Complex is not a PSEUDO_MANIFOLD.");

   // get the time as initial seed
   time_t t;
   time(&t);
   const int seed = options[in_seed] ? atoi(options[in_seed]) : t;
   srand48(seed);
   
   const int n_rounds= options[in_rounds] ? atoi(options[in_rounds]) : 100*size;
   const bool abs= options[in_abs];
   const int obj= options[in_obj] ? atoi(options[in_obj]) : 0;
   if (obj<0 || obj>2)
      throw std::runtime_error("bistellar: no objective function {0|1|2} specified.");

   int max_relax= options[in_max_relax] ? atoi(options[in_max_relax]) : std::max(dim,size/10);
   const int init_max_relax = max_relax;
   const bool my_constant = options[in_constant];
   int heat= options[in_heat] ? atoi(options[in_heat]) : std::max(dim,size/10);
   const int init_heat = heat;
   const int min_heat_dim= options[distribution] ? dim-in_distribution.size()+1 : (dim+1)/2;
   
   // compute distribution
   if (!options[distribution]) {  // no distribution specified
      in_distribution = Array<int>(dim-min_heat_dim+1,10);
      in_distribution[dim-min_heat_dim] = 1;
   }
   else {
      if (in_distribution.size()>dim+1)
	 throw std::runtime_error("bistellar: distribution has too many values.");
      if (in_distribution.empty())   
	 throw std::runtime_error("bistellar: distribution is empty.");
   }

   Array<int> distribution(in_distribution.size());
   distribution[0] = in_distribution[0];
   for (int i=1; i<dim-min_heat_dim+1; ++i)
      distribution[i] = distribution[i-1] + in_distribution[i];
   
   // initialise psBC or BC
   psBistellarComplex psBC;
   BistellarComplex BC;
   if (ps)  psBC = psBistellarComplex(C,seed,verbose==1,options[allow_rev_move]);
   else     BC = BistellarComplex(HD,seed,verbose==1,is_closed,options[allow_rev_move]);

   Array<int> min_f_vector, min_flip_vector;
   FacetList F;
   if (ps)
      min_f_vector = psBC.f_vector();
   else {
      F = BC.facets();
      min_flip_vector = BC.flip_vector();
   }

   if (verbose) {
      if (ps)  os << "\nPSEUDO SIMPLICIAL bistellar flips\n";
      wrap(os) << "\nseed:           " << seed
	       << "\ndim:            " << dim
	       << "\nrounds:         " << n_rounds
	       << "\nabs:            " << (abs?"true":"false")
	       << "\nobjective:      " << obj
	       << "\nrelax:          " << max_relax
	       << "\nheat:           " << heat
	       << "\nconstant:       " << (my_constant?"true":"false")
	       << "\nallow_rev_move: " << (options[allow_rev_move]?"true":"false")
	       << "\nverbose:        " << verbose
	       << "\ndistribution:   " << in_distribution
	       << "\n\n... done\n";
   }
   
   // check for ball or sphere
   bool bos = false;
   if (ps && psBC.n_facets() == 2) {
      if (!options[quiet])
	 wrap(os) << "\n\nThe complex is a " << dim << "-sphere.\n\n";
   }
   if (!ps && is_closed && BC.n_facets() == dim+2) {
      if (!options[quiet])
	 wrap(os) << "\n\nThe complex is a " << dim << "-sphere.\n\n";
      bos = true;
   }
   if (!ps && BC.n_facets() == 1) {
      if (!options[quiet])
	 os  << "\n\nThe complex is a " << dim << "-ball.\n\n";
      bos = true;
   }
   
   if (!bos) {
      int stable_rounds=0, rounds=0, relax=0, heating=0;
      for ( ; (!abs && stable_rounds<n_rounds) || (abs && rounds<n_rounds); ++stable_rounds, ++rounds) {
	 if (verbose && rounds%verbose==0) {
	    os << "\n" << rounds << ": current best ";
	    if (ps)  wrap(os) << "f-vector: " << min_f_vector << endl;
	    else wrap(os) << "flip_vector: " << min_flip_vector
			  << "     n_facets: " << F.size() << endl;
	    
	    if (ps && verbose==1)
	       wrap(os) << "f-vector: " << psBC.f_vector() << endl;
	    if (!ps && verbose==1)
	       wrap(os) << "flip_vector: " << BC.flip_vector() << "     n_facets: " << BC.n_facets() << endl;
	 }

	 if (relax >= max_relax) {  // heating up
	    relax = 0;
	    heating = heat;
	 }
	 
	 if (verbose==1 && heating>0)
	       os << "HEATING UP for another " << heating << " moves\n";
	 if (ps && verbose>1 && heating>0 && verbose<=5*init_heat && heating==heat)
	    wrap(os) << rounds << ": current f-vector: " << psBC.f_vector()
		     << "\nHEATING UP for " << heating << " moves\n";
	 if (!ps && verbose>1 && heating>0 && verbose<=5*init_heat && heating==heat)
	    wrap(os) << rounds << ": current flip_vector: " << BC.flip_vector()
		     << "\nHEATING UP for " << heating << " moves\n";

	 if (heating>0) {
	    --heating;
	    const int rnd_d = rnd_dim(min_heat_dim, distribution);
	    
	    if (ps) {
	       const int move_dim = psBC.find_move(rnd_d);
	       if ( rnd_d!=move_dim )
		  psBC.min_rev_move(min_heat_dim);
	       else
	    	  psBC.execute_move();
	       
	    } else {
	       const int move_dim = BC.find_move(rnd_d);
	       if ( rnd_d!=move_dim)
		  BC.min_rev_move(min_heat_dim);
	       else
	       BC.execute_move();
	    }
	    
	    continue;
	 }
	 
	 // make smallest reversed move
	 int move_dim= ps ? psBC.min_rev_move() : BC.min_rev_move();
	 
	 if ( move_dim >= (dim+1)/2 )   // up or eaven move
	    ++relax;
	 
	 else  // down move
	    if ( ps && improved(min_f_vector,psBC.f_vector(),obj) ||
		 !ps && improved(min_flip_vector,BC.flip_vector(),obj) ) {  // new triangulation found
	       stable_rounds = 0;
	       relax = 0;
	       
	       if (ps) {
		  min_f_vector = psBC.f_vector();
		  C = psBC.pseudo_complex();
		  
		  if (!my_constant) {
		     max_relax = std::min( init_max_relax, std::max(dim,init_max_relax*psBC.n_facets()/size) );
		     heat = std::min( init_heat, std::max(dim,init_heat*C.n_facets()/size) );
		  }
		  
	       } else {
		  min_flip_vector = BC.flip_vector();
		  F=BC.facets();
		  if (!my_constant) {
		     max_relax = std::min( init_max_relax, std::max(dim,init_max_relax*BC.n_facets()/size) );
		     heat = std::min( init_heat, std::max(dim,init_heat*BC.n_facets()/size) );
		  }
	       }
	       
	       if (verbose == 1)
		  wrap(os) << "new smallest triangulation found\n";
	       
	       // check for sphere
	       if ( ps && C.n_facets()==2 ) {
		  if (!options[quiet])
		     wrap(os) << "\n" << rounds
			      << ": f-vector: " << min_f_vector
			      << "\n\nThe complex is a " << dim << "-sphere.\n\n";
		  break;
	       }
	       if ( !ps && is_closed && BC.n_facets()==dim+2) {
		  if (!options[quiet])
		     wrap(os) << "\n" << rounds
			      << ": flip_vector: " << min_flip_vector
			      << "     n_facets: " << F.size()
			      << "\n\nThe complex is a " << dim << "-sphere.\n\n";
		  break;
	       }
	       
	       // check for ball
	       if ( !ps && BC.n_facets()==1 ) {
		  if (!options[quiet])
		     wrap(os) << "\n" << rounds
			      << ": flip_vector: " << min_flip_vector
			      << "     n_facets: " << F.size()
			      << "\n\nThe complex is a " << dim << "-ball.\n\n";
		  break;
	       }
	    }
      }
      
      if (!options[quiet] && (stable_rounds==n_rounds || rounds==n_rounds) ) {
	 os << "\nAll " << n_rounds
	    << " moves were executed";
	 if (!abs)
	    os << " without improvement";
	 if (ps)  wrap(os) << ".\nf-vector: " << min_f_vector
			   << "\nFurther simplification might be possible.\n\n";
	 else     wrap(os) << ".\nflip_vector: " << min_flip_vector
			   << "     n_facets: " << F.size()
			   << "\nFurther simplification might be possible.\n\n";
      }

      if (verbose) {
	 wrap(comment) << "\nparameters for the bistellar client:"
		       << "\nseed:           " << seed
		       << "\nrounds:         " << n_rounds
		       << "\nabs:            " << (abs?"true":"false")
		       << "\ntotal rounds:   " << rounds
		       << "\nobjective:      " << obj
		       << "\nrelax:          " << init_max_relax
		       << "\nheat:           " << init_heat
		       << "\nconstant:       " << (my_constant?"true":"false")
		       << "\nallow_rev_move: " << (options[allow_rev_move]?"true":"false")
		       << "\ndistribution:   " << in_distribution;
      }
   }  // end !bos
   
   if (ps) {
      C.squeeze();
      C.restore_vertex_sets();
      
      p_out.take("DESCRIPTION") << comment.str();
      p_out.take("LATTICE") << C;
      
   } else {
      Array< Set<int> >C_out(F.size(),F.begin());
      const Set<int> V = accumulate(C_out, operations::add());
      adj_numbering(C_out,V);
      
      p_out.take("DESCRIPTION") << comment.str();
      p_out.take("FACETS") << C_out;
   }
}

} }

using namespace polymake;

/** @file bistellar
 *
 *  Heuristic for simplifying the triangulation of the given manifold
 *  without changing the represented topological space. The client uses
 *  bistellar flips and a simulated annealing strategy.
 *
 *  If the @c -ps option is set, the client computes a pseudo simplicial complex.
 *
 *  You may specify the maximal number of @c -rounds, how often the system
 *  may @c -relax before heating up and how much @c -heat should be applied.
 *  The client stops computing, once the size of the triangulation has not decreased
 *  for @c -rounds iterations. If the @c -abs flag is set, the client stops
 *  after @c -rounds iterations regardless of when the last improvement took place.
 *
 *  If you want to influence the distribution of the dimension of the moves when warming up
 *  you may do so by specifying a @c -distribution. The number of values in @c -distribution
 *  determine the dimensions used for heating up. The heating and relaxing parameters decrease dynamically
 *  unless the @c -constant flag is set. The client prohibits to execute the reversed move of a move
 *  directly after the move itself unless the @c -allow_rev_move flag is set.
 *
 *  If you are interested in how the process is coming on, try the @c -verbose option.
 *  It specifies after how many rounds the current best result is displayed.
 *
 *  The @c -obj determines the objective function used for the optimization. If @c -obj is set to @c 0,
 *  the client searches for the triangulation with the lexicographically smallest f-vector,
 *  if @c -obj is set to @c 1, the client searches for the triangulation with the reversed-lexicographically
 *  smallest f-vector and if @c -obj is set to @c 2 the sum of the f-vector entries is used.
 *  The default is @c 0.
 *
 *  @synopsis bistellar <out_file> <in_file> [ -ps -rounds <r> -abs -obj {0|1|2} -relax <r> -heat <h> -constant -allow_rev_move -verbose <r> -seed <s> -quiet -distribution s_(dim+1)/2 .. s_dim ]
 *
 *  @reading  may read HASSE_DIAGRAM, may read LATTICE, PSEUDO_MANIFOLD, CLOSED_PSEUDO_MANIFOLD
 *  @writing  DESCRIPTION, may write FACETS, may write LATTICE
 *
 *  @index clients Producing a new simplicial complex from others
 */


int main(int argc, const char* argv[]) {
   argv_option options[]={ {"-ps", false}, {"-rounds", true}, {"-abs", false},
			   {"-obj", true}, {"-relax", true}, {"-heat", true},
			   {"-constant", false}, {"-allow_rev_move", false}, {"-verbose", true}, {"-seed", true},
			   {"-quiet", false}, {"-distribution", false} };

   if (!extract_options(argc,argv,2,options) || argc<3) {
      cerr << "usage: " << argv[0] << " <out_file> <in_file> [ -ps -rounds <r> -abs -obj {0|1|2} -relax <r> -heat <h> -constant -allow_rev_move -verbose <r> -seed <s> -quiet -distribution s_(dim+1)/2 .. s_dim ]" << endl;
      return 1;
   }
   try {
      Poly::set_options("-A topaz");

      Poly p_out_ps(argv[1], ios::out | ios::trunc, "PseudoComplex");
      Poly p_out(argv[1], ios::out | ios::trunc);
      Poly p_in(argv[2], ios::in);
      
      std::ostringstream comment;
      if (options[0])  comment << "pseudo-simplicial ";
      comment << "complex obtained from " << argv[2]
	      << " by a series of ";
      if (options[0])  comment << "ps-";
      comment << "bistellar flips.";
      
      Array<int> in_distribution;
      if (options[12]) {
	  const std::list<int> read = topaz::read_int_args_as_list(argc, argv, 3);
	  in_distribution = Array<int>(read.size(),read.begin());
      }

      if (options[0])  topaz::bistellar(std::cout, p_out_ps, p_in, options, in_distribution, comment);
      else             topaz::bistellar(std::cout, p_out, p_in, options, in_distribution, comment);
   }
   catch (const std::exception& e) {
      cerr << e.what() << endl;
      return 1;
   }
   return 0;
}
   
   
// Local Variables:
// mode:C++
// c-basic-offset:3
// End:
