/* 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: morse_matching.cc,v 1.4 2006/01/20 14:26:06 gawrilow Exp $"


#include <Poly.h>
#include <HasseDiagram.h>
#include <stopwatch.h>
#include <argv_options.h>
#include <Array.h>

#include <cassert>

#include <morse_matching_tools.h>


using namespace polymake;




/** @file morse_matching
 *
 *  Input: simplicial complex given by its Hasse diagram
 *
 *  Output: a Morse matching
 *
 *  This client computes a Morse matching. Two heuristics are implemented:
 *
 *  - A simple greedy algorithm:
 *    The arcs are visited in lexicographical order, i.e.:
 *
 *    we proceed by levels from top to bottom,
 *    visit the faces in each dimension in lexicographical order,
 *    and visited the faces covered by these faces in lexicographical order.
 *
 *    This heuristic is used with no options and with "-greedy".
 *
 *  - A Morse matching can be improved by canceling critical cells
 *    along unique alternating paths, see function
 *    processAlternatingPaths() in file @c morse_matching_tools.h.
 *    This idea is due to Robin Forman:
 *
 *      Morse Theory for Cell-Complexes,
 *      Advances in Math., 134 (1998), pp. 90-145.
 *
 *    This heuristic is used with no options and with "-cancel".
 *
 *  The default setting is to run the greedy algorithm and then improve
 *  the result by the canceling algorithm, i.e., option "-both" is
 *  the default.
 *
 *  Morse matchings for the bottom level can be found optimally by
 *  spanning tree techniques. This can be enabled by the option
 *  "-bottom".  If the complex is a pseudo-manifold the same can be
 *  done for the top level (option "-top"). By specifying option
 *  "-bottomtop", both levels can be computed by spanning trees. For 2-dim
 *  pseudo-manifolds this computes an optimal Morse matching.
 *
 *  @client   morse_matching
 *  @synopsis morse_matching <file> [ {-greedy, -cancel, -both} {-top, -bottom, -bottomtop} ]
 *
 *  @reading  HASSE_DIAGRAM
 *  @writing  MORSE_MATCHING, MORSE_MATCHING_CRITICAL_FACES, MORSE_MATCHING_SIZE,
 *            MORSE_MATCHING_N_CRITICAL_FACES, MORSE_MATCHING_CRITICAL_FACE_VECTOR
 *
 *  @author   Marc Pfetsch
 *  @date     Fall 2005
 */

int main(int argc, const char* argv[])
{
   typedef Entire<HasseDiagram<Set<int>,bool>::out_edge_list>::const_iterator HasseDiagramOutConstIterator;

   argv_option options[] = { { "-greedy" }, { "-cancel" }, { "-top" }, { "-bottom" }, { "-bottomtop" } };
   enum { greedy, cancel, top, bottom, bottomtop };

   extract_options(argc, argv, 1, options);

   if ( argc != 2 )
   {
      cerr << "usage: " << argv[0] << " <file> [ { -greedy, -cancel, -both }, { -top, -bottom, -bottomtop } ]" << endl;
      return 1;
   }

   try
   {
      // read Hasse diagram
      Poly p(argv[1], ios::in | ios::out);
      HasseDiagram<Set<int>,bool> M;
      p.give("HASSE_DIAGRAM") >> ignore_edge_attrs(M);
      int d = M.dim() - 1;
      int size = 0;

      // determine which heuristic to run
      bool runGreedy = true;  // default is to turn both heuristics on
      bool runCancel = true;
      if ( ! options[greedy] || ! options[cancel] )
      {
	 if ( options[greedy] )
	    runCancel = false;
	 if ( options[cancel] )
	    runGreedy = false;
      }

      // find lowest and highest levels
      int bottomLevel = 0;
      int topLevel = d;
      if ( options[bottom] )
	 bottomLevel = 1;
      if ( options[top] )
	 topLevel = d-1;
      if ( options[bottomtop] )
      {
	 bottomLevel = 1;
	 topLevel = d-1;
      }

      // check whether complex is a pseudo-manifold
      if ( topLevel < d )
      {
	 const bool is_pmf = p.give("PSEUDO_MANIFOLD");
	 if ( ! is_pmf )
	 {
	    cerr << "Error. Complex is not a pseudo-manifold, which is necessary for option \"-top\"." << endl;
	    return 1;
	 }
      }

      // count arcs and fill up maps
      std::vector<int> varLevel;
      int m = 0;          // number of arcs
      int numFaces = 0;   // number of faces
      for (int k = 0; k <= d; ++k)
      {
	 for (Entire<sequence>::const_iterator f = entire(M.nodes_of_dim(k)); !f.at_end(); ++f)
	 {
	    ++numFaces;
	    if ( k < d )
	    {
	       for (HasseDiagramOutConstIterator e = entire(M.out_edges(*f)); !e.at_end(); ++e)
	       {
		  varLevel.push_back(k);
		  ++m;
	       }
	    }
	 }
      }
#if POLY_DEBUG
      cout << "Dimension of complex:  " << d << endl;
      cout << "Faces in complex:      " << numFaces << endl;
      cout << "Arcs in Hasse Diagram: " << m << endl;
      cout << "Bottom level:          " << bottomLevel << endl;
      cout << "Top level:             " << topLevel << endl;
#endif


      // run greedy algorithm if requested
      if ( runGreedy )
      {
#if POLY_DEBUG
	 cout << "\nComputing Morse matching by greedy heuristic ..." << endl;
#endif

	 pm::Stopwatch<true> timer;
	 timer.start();

	 // compute lexicographic order
	 std::vector<int> varOrder;
	 orderEdgesLex(M, varOrder, bottomLevel, topLevel);

	 // compute greedy solution
	 size = greedyHeuristic(M, varLevel, varOrder.begin(), varOrder.end());
	 timer.stop();

	 assert( checkMatching(M) );
	 assert( checkAcyclic(M) );

#if POLY_DEBUG
	 cout << "time: " << double(timer) << " seconds" << endl;

	 cout << "Found solution of size:   " << size << endl;
	 cout << "Number of critical faces: " << numFaces - 2*size << endl;
#endif
      }

      // run canceling algorithm if requested
      if ( runCancel )
      {
#if POLY_DEBUG
	 cout << "\nTrying to improve Morse matching by canceling algorithm ..." << endl;
#endif

	 pm::Stopwatch<true> timer;
	 timer.start();
	 processAlternatingPaths(M, size, bottomLevel, topLevel);
	 timer.stop();

	 assert( checkMatching(M) );
	 assert( checkAcyclic(M) );

#if POLY_DEBUG
	 cout << "time: " << double(timer) << " seconds" << endl;

	 cout << "\nFound solution of size:      " << size << endl;
	 cout << "Number of critical faces:    " << numFaces - 2*size << endl;
#endif

      }

      // complete solution to top level
      if ( topLevel < d )
      {
#if POLY_DEBUG
	 cout << "\nCompleting to top level ..." << endl;
#endif

	 pm::Stopwatch<true> timer;
	 timer.start();
	 completeToTopLevel(M);
	 timer.stop();

	 assert( checkMatching(M) );
	 assert( checkAcyclic(M) );

#if POLY_DEBUG
	 cout << "time: " << double(timer) << " seconds" << endl;

	 size = subgraphSize(M);
	 cout << "Found solution of size:   " << size << endl;
	 cout << "Number of critical faces: " << numFaces - 2*size << endl;
#endif
      }

      // complete solution to bottom level
      if ( bottomLevel > 0 )
      {
#if POLY_DEBUG
	 cout << "\nCompleting to bottom level ..." << endl;
#endif

	 pm::Stopwatch<true> timer;
	 timer.start();
	 completeToBottomLevel(M);
	 timer.stop();

	 assert( checkMatching(M) );
	 assert( checkAcyclic(M) );

#if POLY_DEBUG
	 cout << "time: " << double(timer) << " seconds" << endl;

	 size = subgraphSize(M);
	 cout << "Found solution of size:   " << size << endl;
	 cout << "Number of critical faces: " << numFaces - 2*size << endl;
#endif
      }

      // compute number of critical faces
      Bitset critical = collectCriticalFaces(M);
      Array<int> numCritical(d+1);
      for (int k = 0; k <= d; ++k)
      {
	 for (Entire<sequence>::const_iterator f = entire(M.nodes_of_dim(k)); !f.at_end(); ++f)
	 {
	    if ( critical.contains(*f) )
	    {
	       int dim = M.dim_of_node(*f);
	       assert( 0 <= dim && dim <= d );
	       ++numCritical[dim];
	    }
	 }
      }

#if POLY_DEBUG
      cout << endl;
      for (int k = 0; k <= d; ++k)
	 cout << "d: " << k << "\tcritical: " << numCritical[k] << endl;
#endif

      /* get critical faces
       * Note: this is not merged with the above code, since this code should
       * also work for arbitrary complexes in which we cannot deduce the dimension
       * from the vertex sets of the faces (as they are used in the following):
       */
      PowerSet<int> criticalFaces = findCriticalFaces(M);

      // write Morse Matching and other properties
      p.take("MORSE_MATCHING") << M;
      p.take("MORSE_MATCHING_SIZE") << size;
      p.take("MORSE_MATCHING_CRITICAL_FACES") << as_array(criticalFaces);
      p.take("MORSE_MATCHING_N_CRITICAL_FACES") << numFaces - 2*size;
      p.take("MORSE_MATCHING_CRITICAL_FACE_VECTOR") << numCritical;
   }
   catch (const std::exception& e)
   {
      std::cerr << e.what() << endl;
      abort();
   }

   return 0;
}
