/* 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: spring_embedder.cc,v 1.4 2005/02/21 17:28:44 gawrilow Exp $"

#include <cstdlib>
#include <cmath>
#include <ctime>
#include <Poly.h>
#include <random_generators.h>
#include <SpringEmbedder.h>
#include <argv_options.h>

namespace polymake { namespace graph {

namespace {

  enum { opt_read_edge_weights=SpringEmbedder::opt_next, opt_skip_node_attr, opt_seed, opt_max_iter };

} // end unnamed namespace

void spring_embedder(Poly& p, const char *graph_section, const char *embedding_section, const argv_option *options)
{
   SpringEmbedder SE(options);
   if (options[opt_skip_node_attr]) {
      if (options[opt_read_edge_weights]) {
	 p.give(graph_section) >> ignore_node_attrs<int>(SE.G);
      } else {
	 p.give(graph_section) >> ignore_attrs<int,nothing>(SE.G);
      }
   } else {
      if (options[opt_read_edge_weights]) {
	 p.give(graph_section) >> ignore_node_attrs(SE.G);
      } else {
	 p.give(graph_section) >> ignore_attrs(SE.G);
      }
   }
   SE.adjust_edge_lengths(options[opt_read_edge_weights]);

   if (options[SpringEmbedder::opt_objective]) {
      const Matrix<double> V=p.give("VERTICES");
      Vector<double> Obj=p.give(options[SpringEmbedder::opt_objective]);
      Obj[0]=0;
      Obj /= std::sqrt(sqr(Obj));
      SE.apply_objective_function(V, Obj);
   }

   time_t t;
   UniformRNG< Vector<double> > random_points(3, options[opt_seed] ? atol(options[opt_seed]) : (time(&t), t));
   SE.start_points(random_points.begin());

   int max_iter=options[opt_max_iter] ? atoi(options[opt_max_iter]) : 10000;
   if (! SE.calculate(max_iter))
      cerr << "WARNING: spring_embedder not converged after " << max_iter << " iterations" << endl;
   p.take(embedding_section) << std::setprecision(6) << SE.get();
}

} }

using namespace polymake;

/** @file spring_embedder
 *
 *  Produce a 3-d embedding for the graph using the spring embedding algorithm
 *  along the lines of
 *    Thomas Fruchtermann and Edward Reingold:
 *      Graph Drawing by Force-directed Placement.
 *      Software Practice and Experience Vol. 21, 1129-1164 (1992), no. 11
 *
 *  The initial node coordinates are chosen randomly on the unit sphere.
 *  The optional parameter @c seed controls the initial setting.
 *
 *  In the standard setting, the embedding algorithm tries to stretch all edges to the
 *  same length. If you prefer different edge lengths, store them as the edge attributes
 *  of the input graph, and put the @c -read-edge-weights option on the command line.
 *
 *  If the nodes already have an embedding in $R^d$ and there is a linear objective function defined
 *  in the coordinate space, it can be used to rearrange the 3-d embedding along the z-axis corresponding
 *  to the objective function growth.  This mode is enabled with option @c objective.
 *
 *  The embedding algorithm can be fine-tuned with several "black magic" options.
 *  All of them take double values, which are multiplied with internal initial settings,
 *  so all defaults are equal to 1.
 *
 *  @c scale enlarges the ideal edge length. @c balance changes the balance between the
 *  edge contraction and node repulsion forces. @c inertion and @c viscosity affects how the nodes
 *  are moved, and can be used to restrain oscillations. @c objective-factor changes the relative influence
 *  of the linear objective function on the embedding. @c eps controls how far a point may move, to be
 *  considered standing still.
 *
 *  @synopsis spring_embedder <file> <graph_section> <embedding_section>
 *            [ -objective <section>  -read-edge-weights  -seed <s>  -max-iterations <n> ]
 *            [ {-scale,-balance,-viscosity,-inertion,-eps,-objective-factor} <x> ... ]
 *
 *  @reading  <graph_section>; VERTICES, FAR_FACE, <objective_section> if -objective specified
 *  @writing  <embedding_section>
 *
 */

int main(int argc, const char *argv[])
{
   // Caution: the order of these options is settled by enum's in SpringEmbedderBase and SpringEmbedder!
   argv_option options[]={ { "-viscosity", true }, { "-inertion", true }, {"-eps", true},
			   { "-scale", true }, { "-balance", true },
			   { "-objective", true }, { "-objective-factor", true },
			   { "-read-edge-weights", false }, { "-skip-node-attr", false },
			   { "-seed", true }, { "-max-iterations", true }
			 };
   if (!extract_options(argc,argv,3,options) || argc != 4) {
      cerr << "usage: " << argv[0] << " <file> <graph_section> <embedding_section> [ -objective <section>  -read-edge-weights -skip-node-attr   -seed <s>  -max-iterations <n>  {-scale,-balance,-viscosity,-inertion,-eps,-objective-factor} <x> ... ]" << endl;
      return 1;
   }
   try {
      Poly p(argv[1], ios::in | ios::out);
      graph::spring_embedder(p, argv[2], argv[3], options);
   }
   catch (const std::exception& e) {
      cerr << e.what() << endl;
      return 1;
   }
   return 0;
}

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