/* Copyright (c) 1997-2005
   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: HDEmbedder.cc,v 1.4 2005/11/11 10:13:13 gawrilow Exp $"

#if defined(__GNUC__) && __GNUC__==3 && __GNUC_MINOR__<3 && !defined(__APPLE__) && !defined(__COMO__)
#pragma implementation
#endif

#include <HDEmbedder.h>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <RandomSubset.h>

namespace polymake { namespace graph {

void HDEmbedder::init(const argv_option *options)
{
   epsilon= options[opt_eps] ? atof(options[opt_eps]) : 1e-4;
   time_t t;
   unsigned long seed= options[opt_seed] ? atol(options[opt_seed]) : (time(&t), t);

   total_width=label_width[0];
   // randomly toss the nodes in each layer
   layer_vector::iterator l=layers.begin();
   std::vector<double>::iterator wd=width_in_layer.begin();
   for (int d=0; d<dim; ++d, ++seed, ++l, ++wd) {
      const sequence nodes=HD.nodes_of_dim(d);
      const int n=nodes.size();
      l->resize(n);
      std::copy(entire(random_permutation(nodes, seed)), l->begin());
      accumulate_in(entire(select(label_width, nodes)), operations::max(), *wd);
      assign_max(total_width, (*wd)*n);
   }

   // place the nodes equidistantly and symmetrically around x=0
   // weights[d]=nodes_of_dim(d).size()/nodes_of_dim(d-1).size()
   // weight for out_edges = weights[d+1]
   // weight for in_edges = 1/weights[d]

   std::vector<double>::iterator wt=weights.begin();
   double prev_width=1;
   for (int d=0; d<dim; ++d, ++wt) {
      const int layer_width=HD.nodes_of_dim(d).size();
      const double width=total_width/layer_width;
      double x=(width-total_width)/2;
      for (Entire< std::vector<int> >::iterator n=entire(layers[d]); !n.at_end(); ++n, x+=width)
	 node_x[*n]=x;
      *wt=layer_width/prev_width;
      prev_width=layer_width;
   }
   *wt=1/prev_width;

   wt=weights.begin();
   for (int d=0; d<dim; ++d, ++wt) {
      for (Entire<sequence>::const_iterator nodes=entire(HD.nodes_of_dim(d)); !nodes.at_end(); ++nodes) {
	 const int n=*nodes;
	 nb_x_sum[n]= accumulate(select(node_x, HD.out_neighbors(n)), operations::add())*wt[1] +
	              accumulate(select(node_x, HD.in_neighbors(n)), operations::add())/wt[0];
      }
   }
}

Matrix<double> HDEmbedder::compute(const argv_option *options)
{
   init(options);

   // try to decrease the total tension
   bool anyone_moved;
   do {
      anyone_moved=false;
      std::vector<double>::const_iterator wd=width_in_layer.begin(), wt=weights.begin();
      for (Entire<layer_vector>::iterator l=entire(layers); !l.at_end(); ++l, ++wd, ++wt) {
	 std::vector<int>::iterator n_first=l->begin(), n_last=l->end();
#ifdef POLY_DEBUG
	 cout << "layer " << (l-layers.begin()) << ":";
	 for (std::vector<int>::iterator n=n_first; n!=n_last; ++n) {
	    cout << ' ' << *n << '(' << node_x[*n] << ')';
	 }
	 cout << endl;
#endif
	 for (std::vector<int>::iterator n=n_first; n!=n_last; ++n) {
#ifdef POLY_DEBUG
	    int node=*n;
#endif
	    bool moved=try_move_node(n, n_first, n_last, *wd, &*wt);
	    anyone_moved |= moved;
#ifdef POLY_DEBUG
	    if (moved)
	       for (std::vector<int>::iterator n2=n_first+1; n2!=n_last; ++n2)
		  if (node_x[*n2]-node_x[n2[-1]]+epsilon < *wd)
		     cerr << "ERROR after moving " << node << ": dist(" << n2[-1] << "," << *n2 << ") < " << *wd << endl;
#endif
	 }
      }
   } while (anyone_moved);

   Matrix<double> embedding(HD.nodes(), 2);
   const bool primal=options[opt_primal];
   double y=0;
   for (int d=primal ? 0 : -1, d_step=primal ? 1 : -1, d_last=primal ? dim : -dim-1;  
	d!=d_last; d+=d_step, y+=1) {
      for (Entire<sequence>::const_iterator n=entire(HD.nodes_of_dim(d)); !n.at_end(); ++n) {
	 embedding(*n,0)=node_x[*n];
	 embedding(*n,1)=y;
      }
   }
   embedding(HD.top_node(), 1)=primal ? y : -1;
   embedding(HD.bottom_node(), 1)=primal ? -1 : y;
   return embedding;
}

int HDEmbedder::try_move_node(std::vector<int>::iterator n,
			      const std::vector<int>::iterator& n_first, const std::vector<int>::iterator& n_last,
			      const double delta, const double* wt)
{
   int node=*n;
   int moved=0;
   double x_min=nb_x_sum[node]/(HD.out_degree(node)*wt[1]+HD.in_degree(node)/wt[0]),
          diff_to_min=node_x[node]-x_min;
   if (diff_to_min>epsilon) {
      // try moving to the left
      while (true) {
	 if (n--==n_first) {
	    // the leftmost node, no neighbors to spring over
	    INDEX_CHECK((x_min < -total_width/2-epsilon), "internal error: node " << node << " gone too much left");
	 } else {
	    const int node2=*n;
	    if (node_x[node2]+delta > x_min) {
	       // gap between the neighbors is not large enough
	       if (good_swap(node2, node, delta, wt)) {
		  adjust_x(node2, node_x[node2]+delta, wt);
		  n[1]=node2;
		  moved=1;
		  continue;
	       } else {
		  if (moved || node_x[node2]+delta+epsilon < node_x[node]) {
		     adjust_x(node, node_x[node2]+delta, wt);
		     if (moved++) n[1]=node;
		  }
		  break;
	       }
	    }
	 }
	 if (moved) {
	    n[1]=node;
	    assign_min(x_min, node_x[n[2]]-delta);
	 }
	 adjust_x(node, x_min, wt);
	 return true;
      }

   } else if (diff_to_min<-epsilon) {
      // try moving to the right
      while (true) {
	 if (++n == n_last) {
	    // the rightmost mode
	    INDEX_CHECK((x_min > total_width/2+epsilon), "internal error: node " << node << " gone too much right");
	 } else {
	    const int node2=*n;
	    if (node_x[node2]-delta < x_min) {
	       // gap between the neighbors is not large enough
	       if (good_swap(node2, node, -delta, wt)) {
		  adjust_x(node2, node_x[node2]-delta, wt);
		  n[-1]=node2;
		  moved=1;
		  continue;
	       } else {
		  if (moved || node_x[node2]-delta-epsilon > node_x[node]) {
		     adjust_x(node, node_x[node2]-delta, wt);
		     if (moved++) n[-1]=node;
		  }
		  break;
	       }
	    }
	 }
	 if (moved) {
	    n[-1]=node;
	    assign_max(x_min, node_x[n[-2]]+delta);
	 }
	 adjust_x(node, x_min, wt);
	 return true;
      }
   }

   return moved;
}

inline void HDEmbedder::adjust_x(int node, const double x, const double* wt)
{
   const double dx=x-node_x[node];
   node_x[node]=x;
   for (Entire<HasseDiagram<>::out_neighbor_list>::const_iterator nb=entire(HD.out_neighbors(node));
	!nb.at_end(); ++nb)
      nb_x_sum[*nb]+=dx/wt[1];
   for (Entire<HasseDiagram<>::in_neighbor_list>::const_iterator nb=entire(HD.in_neighbors(node));
	!nb.at_end(); ++nb)
      nb_x_sum[*nb]+=dx*wt[0];
}

inline bool HDEmbedder::good_swap(int lnode, int rnode, const double delta, const double* wt) const
{
   return delta*( (delta+2*node_x[lnode])*( (HD.out_degree(lnode)-HD.out_degree(rnode))*wt[1] +
					    (HD.in_degree(lnode)-HD.in_degree(rnode))/wt[0] )
		  - 2*(nb_x_sum[lnode]-nb_x_sum[rnode])) < 0;
}

} }

// Local Variables:
// c-basic-offset:3
// End:
