/* Copyright (c) 1997-2006
   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.6.2 2006/03/14 18:25:52 gawrilow Exp $"

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

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

namespace {
   enum { in_rounds, in_abs, in_obj, in_max_relax, in_heat,
	  in_constant, allow_rev_move, in_verbose, in_seed, quiet, in_distribution };
}

namespace polymake { namespace topaz {
   
namespace {

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> distribution_src, std::ostringstream& comment)
{
   const int verbose= options[in_verbose] ? atoi(options[in_verbose]) : 0;

   if (verbose)
      os << "initialising ...\n";
   
   const HasseDiagram<> HD = p_in.give("HASSE_DIAGRAM");
   const int dim = HD.dim()-1;
   const int 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 (!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[in_distribution] ? dim-distribution_src.size()+1 : (dim+1)/2;
   
   // compute distribution
   if (!options[in_distribution]) {  // no distribution specified
      distribution_src = Array<int>(dim-min_heat_dim+1,10);
      distribution_src[dim-min_heat_dim] = 1;
   }
   else {
      if (distribution_src.size()>dim+1)
	 throw std::runtime_error("bistellar: distribution has too many values.");
      if (distribution_src.empty())   
	 throw std::runtime_error("bistellar: distribution is empty.");
   }

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

   Array<int> min_f_vector, min_flip_vector;
   FacetList F = BC.facets();
   min_flip_vector = BC.flip_vector();

   if (verbose) {
      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:   " << distribution_src
	       << "\n\n... done\n";
   }
   
   // check for ball or sphere
   bool bos = false;
   if (is_closed && BC.n_facets() == dim+2) {
      if (!options[quiet])
	 wrap(os) << "\n\nThe complex is a " << dim << "-sphere.\n\n";
      bos = true;
   }
   if (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) {
	    wrap(os) << "\n" << rounds << ": current best " << "flip_vector: " << min_flip_vector
	       << "     n_facets: " << F.size() << endl;
	    if (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 (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);
	    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
	 const int move_dim= BC.min_rev_move();
	 
	 if ( move_dim >= (dim+1)/2 )   // up or eaven move
	    ++relax;
	 
	 else  // down move
	    if ( improved(min_flip_vector,BC.flip_vector(),obj) ) {  // new triangulation found
	       stable_rounds = 0;
	       relax = 0;
	       
	       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 ( 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 ( 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)
	    wrap(os) << " without improvement" << ".\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:   " << distribution_src;
      }
   }  // end !bos
   
   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.
 *
 *  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> [ -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  HASSE_DIAGRAM, PSEUDO_MANIFOLD, CLOSED_PSEUDO_MANIFOLD
 *  @writing  DESCRIPTION, FACETS
 *
 *  @index clients Producing a new simplicial complex from others
 */


int main(int argc, const char* argv[]) {
   argv_option options[]={ {"-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> [ -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(argv[1], ios::out | ios::trunc);
      Poly p_in(argv[2], ios::in);
      
      std::ostringstream comment;
      comment << "Simplicial complex obtained from " << argv[2]
	      << " by a series of " << "bistellar flips.";
      
      Array<int> distribution_src;
      if (options[in_distribution]) {
	  const std::list<int> read = topaz::read_int_args_as_list(argc, argv, 3);
	  distribution_src = Array<int>(read.size(),read.begin());
      }

      topaz::bistellar(std::cout, p_out, p_in, options, distribution_src, comment);
   }
   catch (const std::exception& e) {
      cerr << e.what() << endl;
      return 1;
   }
   return 0;
}


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