/* 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: SpringEmbedder.cc,v 1.5 2005/09/22 17:12:12 gawrilow Exp $"

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

#include <cstdlib>
#include <cmath>
#include <SpringEmbedder.h>

namespace polymake { namespace graph {

void SpringEmbedder::init_params(const argv_option *options)
{
   if (options[opt_scale]) {
      scale=atof(options[opt_scale]);
      epsilon_2*=scale*scale;
   } else {
      scale=1;
   }

   if (options[opt_repulsion])
      rep=atof(options[opt_repulsion]);
   else
      rep=1;

   obj_function=false;
   if (options[opt_objective_factor])
      obj_factor=atof(options[opt_objective_factor]);
   else
      obj_factor=1;
}

void SpringEmbedder::adjust_edge_lengths(bool different_edge_lengths)
{
   double min_edge_weight, avg_edge_weight;
   if (different_edge_lengths) {
      min_edge_weight=1;
      avg_edge_weight=0;
      for (Entire< Edges<graph_type> >::const_iterator e=entire(edges(G)); !e.at_end(); ++e) {
	 if (e->wanted_length <= 0)
	    throw std::runtime_error("non-positive edge length encountered");
	 if (e->wanted_length < min_edge_weight)
	    min_edge_weight=e->wanted_length;
	 avg_edge_weight += e->wanted_length;
      }
      if (min_edge_weight<1)
	 avg_edge_weight/=min_edge_weight;
      else
	 min_edge_weight=1;
   } else {
      avg_edge_weight=scale;
      min_edge_weight=1/scale;
   }

   int n_edges=0;
   for (Entire< Edges<graph_type> >::iterator e=entire(edges(G)); !e.at_end(); ++e, ++n_edges)
      e->inv_wanted_length=min_edge_weight/e->wanted_length;
   if (different_edge_lengths)
      avg_edge_weight/=n_edges;

   const double n_nodes=G.nodes();
   scale=avg_edge_weight/4*std::sqrt(n_nodes);
   const double avg_deg=2*n_edges/n_nodes;
   if (avg_deg>=3) {
      const double x=2*M_PI/avg_deg;
      scale*=std::sqrt(std::sin(x)/x);
   }
#ifdef POLY_DEBUG
   cout << "initial adjustment:"
           "\n  min_edge_weight=" << min_edge_weight
	<< "\n  avg_edge_weight=" << avg_edge_weight
	<< "\n  avg_degree=" << avg_deg
	<< "\n  n_nodes=" << n_nodes
	<< "\n  scale=" << scale
	<< endl;
#endif
}

void SpringEmbedder::apply_objective_function(const Matrix<double>& V, const Vector<double>& Obj)
{
   Vector<double> diff(Obj.size());
   double longest_edge=0;
   for (Entire< Edges<graph_type> >::iterator e=entire(edges(G)); !e.at_end(); ++e) {
      diff=V[e.to_node()]-V[e.from_node()];
      e->real_edge_length=std::sqrt(sqr(diff));
      e->angle=std::acos(Obj*diff/e->real_edge_length);
      longest_edge=std::max(longest_edge,e->real_edge_length);
   }
   obj_function=true;
   obj_factor/=longest_edge;
}

void SpringEmbedder::calculate_forces(std::vector<vector3>& forces)
{
   std::vector<vector3>::iterator f = forces.begin();
   for (Entire< Nodes<graph_type> >::const_iterator this_node=entire(nodes(G));  
	!this_node.at_end(); ++this_node, ++f) {
      f->fill(0.);
      int n=this_node.index();
      graph_type::out_edge_list::const_iterator edge=this_node.out_edges().begin();

      for (int n2=0; n2<n; ++n2) {
	 const vector3 delta=G.node(n2).x - this_node->x;
	 const double delta_sqr=std::max(sqr(delta),epsilon_2), delta_abs=std::sqrt(delta_sqr);
	    
	 if (!edge.at_end() && edge.index()==n2) {
	    // the nodes are neighbors
	    const vector3 attraction= (edge->inv_wanted_length - 1/delta_abs) * delta;
	    *f += attraction;
	    forces[n2] -= attraction;

	    if (obj_function) {
	       const double alpha=std::acos(delta[2]/delta_abs);
	       const double M=obj_factor*(alpha-edge->angle)*edge->real_edge_length;
	       (*f)[2] -= M;
	       forces[n2][2] += M;
	    }
	    ++edge;

	 } else {
	    // the nodes are not neighbors
	    const vector3 repulsion= (rep/delta_sqr/delta_abs) * delta;
	    *f -= repulsion;
	    forces[n2] += repulsion;
	 }
      }
   }
      
   for (Entire< Set<int> >::const_iterator fi = entire(fixed_vertices); !fi.at_end(); ++fi)
      forces[*fi].fill(0);
}

} }

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