/* 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: congruence_graphs.cc,v 1.9 2005/02/02 17:45:01 gawrilow Exp $"

#include <list>
#include <map>
#include <sstream>
#include <algorithm>
#include <Rational.h>
#include <Poly.h>
#include <Vector.h>
#include <Array.h>
#include <Map.h>
#include <Set.h>
#include <Matrix.h>
#include <pipestream.h>
#include <linalg_exceptions.h>

namespace polymake { namespace polytope {

namespace {

inline
int indexOf (int i, int j, int n)
{
   return (n-1 + (n-1)*i+j - i*(i+1)/2);
}

void write_graph ( std::ostream& out,
		   int n, int m, int full, 
		   int next_free_dist_label,
		   const Rational& factor,
		   const Matrix<Rational>& V1,
		   const Map<Rational,int>& label )
{
   out << "n=" << n+m+full << endl
       << "g" << endl;

   for (int i=0; i < n; ++i) {
      out << i << ":";
	
      for (int j=0; j < i; ++j)
	 out << " " << indexOf(j,i,n);

      for (int j=i+1; j < n; ++j)
	 out << " " << indexOf(i,j,n);
       
      out << endl;
   }

   int next_free_path_node_index = n+m;
   Array< Set<int> > nodes_of_label (next_free_dist_label);
   for (int i=0; i < n; ++i) 
      for (int j=i+1; j < n; ++j) {
	 const Rational dist  = factor * sqr(V1[i]-V1[j]);

	 const int k = indexOf(i,j,n);

	 out << k << ": " 
	     << i << " " 
	     << j << " "
	     << next_free_path_node_index 
	     << "\t!  dist = " << dist << " ----> label = " << label[dist] 
	     << endl;

	 const int l = label[dist];
	 nodes_of_label[l] += indexOf(i,j,n);

	 // write path
	 int last = k;
	 for (int k=0; k < l-1; ++k, ++next_free_path_node_index) {
	    out << next_free_path_node_index << ": " 
		<< last << " " 
		<< next_free_path_node_index+1 << endl; 
	    last = next_free_path_node_index;
	 }
	 out << next_free_path_node_index << ": " 
	     << last << endl; 

	 next_free_path_node_index++;
      }
   out << "." << endl;
       
   // hint for nauty, should work without!
   out << "f = [ 0:" << (n-1);
   for (int i=0; i < nodes_of_label.size(); ++i) 
      if (nodes_of_label[i].size() > 0) {
	 out << " | " << nodes_of_label[i].front();
	 Entire< Set<int> >::iterator v = entire(nodes_of_label[i]);
	 for (++v; !v.at_end(); ++v)
	    out << "," << *v;
      }
   out << " | " << n+m << ":" << next_free_path_node_index-1
       << " ]" << endl;	  
}

} // end unnamed namespace

void congruence_graphs(Poly& p1, Poly& p2, std::ostream& out, const char *factor_section=0)
{
   const Matrix<Rational> V1=p1.give("VERTICES"),
                          V2=p2.give("VERTICES");
   const int n = V1.rows();

   // ============= Determine distance classes ==========
      
   Set<Rational>     distances1;
   Map<Rational,int> count;
      
   for (int i=0; i < n; ++i) 
      for (int j=i+1; j < n; ++j) {
	 const Rational dist  = sqr(V1[i]-V1[j]);
	 distances1 += dist;
	  ++count[dist];
      }
 
   Set<Rational> distances2;
   Map<Rational,int> count2;

   for (int i=0; i < n; ++i) 
      for (int j=i+1; j < n; ++j) { 
	 const Rational dist  = sqr(V2[i]-V2[j]);
	 distances2 += dist;
	 ++count2[dist];
      }

   // ============= Assign labels ==========

   Map<Rational,int> label;
   int next_free_dist_label = 1;

   {
      std::multimap<int,Rational> arr_count;
      int i=0;
      for (Entire< Map<Rational,int> >::iterator c = entire(count); !c.at_end(); ++c,++i) 
	 arr_count.insert(std::make_pair(c->second, c->first));

      for (std::multimap<int,Rational>::const_iterator it = arr_count.begin();
	   it != arr_count.end(); ++it)
	 label[it->second] = next_free_dist_label++;
   }

   // ============= Compare distance classes =========
       
   Rational factor = 1;

   try {
      if (distances1 != distances2) {

	 if (distances1.size() != distances2.size())
	    throw infeasible("NOT CONGRUENT");

	 factor = distances1.front() / distances2.front();
	 if (factor == 1)
	    throw infeasible("NOT CONGRUENT");

	 count2.clear();
	 Set<Rational> distances2_prime;
	 for (int i=0; i < n; ++i) 
	    for (int j=i+1; j < n; ++j) {  
	       const Rational dist  = factor*sqr(V2[i]-V2[j]);
	       distances2_prime += dist;
	       ++count2[dist];
	 }
       
	 if (distances1 != distances2_prime) {
	    std::ostringstream err_msg;
	    err_msg << "NOT CONGRUENT (even with factor " << factor << ")";
	    throw infeasible(err_msg.str());
	 }
      }

      for (Entire<  Map<Rational,int> >::iterator c1 = entire(count), c2 = entire(count2);
	   !c1.at_end() && !c2.at_end(); ++c1, ++c2) {
	 if (*c1 != *c2) {
	    std::ostringstream err_msg;
	    err_msg << "NOT CONGRUENT (distances count differ for distance sqrt(" << c1->first << "))";
	    throw infeasible(err_msg.str());
	 }
      }

      int full = 0;
      for (Entire<  Map<Rational,int> >::iterator c = entire(count); !c.at_end(); ++c) 
	 full += label[c->first] * c->second;

      const int m = indexOf(n-1,n,n)-n;

      write_graph(out, n, m, full, next_free_dist_label, 1, V1, label);      out << "c x @" << endl;
      write_graph(out, n, m, full, next_free_dist_label, factor, V2, label); out << "x  ##" << endl;

      if (factor_section)
	 p1.take(factor_section) << factor;
   }
   catch (const infeasible&) {
      if (factor_section)
	 p1.take(factor_section) << 0;
      else
	 throw;
   }
}

} }

using namespace polymake;

/** @file congruence_graphs
 *
 * Implements the reduction of polytope congruence to the graph isomorphism problem due to 
 * 
 *    Akutsu, T.: On determining the congruence of point sets in {$d$} dimensions.
 *                Comput. Geom. Theory Appl. 9, 247--256 (1998), no. 4
 *  
 *  Used by user function @see check_congruence.
 *
 *  Roughly speaking, the client reads the @see Polytope.VERTICES of two polytopes and prints two 
 *  graphs in nauty format to stdout. Each of these graphs is a complete graph with
 *  labeled edges, such that an edge $e$ of the first graph has the same label as an
 *  an edge $f$ of the second one iff the vertices corresponding to $e$ have the same
 *  (squared) Euclidian distance as the vertices corresponding to $f$.
 *  
 *  Since 'nauty' does not handle edge labels, the client splits each edge $e$ of 
 *  the two graphs and adds a path of length $l+1$ to the 'split node', where
 *  $l$ is the label of the edge $e$.
 *
 *  @synopsis congruence_graphs <file1> <file2>
 *
 *  @reads VERTICES
 *
 *  @author Alexander Schwartz <schwartz@math.tu-berlin.de>
 *
 *  @index clients Comparing polytopes
 */

/* speed ups:
      - avoid calculating the distances twice
      - sort labels with respect to quantity  [DONE]
      - call nauty directly (and avoid the path construction)
 */

int main(int argc, const char *argv[])
{
   if (argc!=3 && argc!=5) {
      cerr << "usage: " << argv[0] << " <file1> <file2> [ <fd_out> <factor_section> ]" << endl;
      return 1;
   }
   try {
      Poly p1(argv[1], argc==5 ? ios::in|ios::out : ios::in);
      Poly p2(argv[2], ios::in);
      if (argc==3) {
	 polytope::congruence_graphs(p1, p2, std::cout);
      } else {
	 std::pipestream out(atoi(argv[3]));
	 polytope::congruence_graphs(p1, p2, out, argv[4]);
      }
   }
   catch (const std::exception& e) {
      cerr << e.what() << endl;
      return 1;
   }
   return 0;
}
