/* 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: unirand.cc,v 1.18 2004/09/20 16:06:25 gawrilow Exp $"

#include <cstdlib>
#include <ctime>
#include <cmath>
#include <argv_options.h>
#include <Rational.h>
#include <Poly.h>
#include <Map.h>
#include <PowerSet.h>
#include <Array.h>
#include <Matrix.h>
#include <Vector.h>
#include <linalg.h>
#include <random_generators.h>

namespace {
  enum { seed, boundary };
}

namespace polymake { namespace polytope {

void unirand(Poly& p_out, Poly& p_in, int n_points_out, const std::string& comment, const argv_option* options)
{
   const Matrix<Rational> V=p_in.give("VERTICES");
   const int d=V.cols(), dim=p_in.give("DIM");
   if (dim!=d-1) {
      throw std::runtime_error("unirand: polytope must be full-dimensional");
   }

   Rational total_volume(0);
   // partial_volume[i] = sum(volume(simplex[j])), j=[0..i]
   Map<Rational, const Set<int> > partial_volume;

   if (options[boundary]) {
      typedef Array< PowerSet<int> > triangulation;
      const triangulation Triangulation=p_in.give("TRIANGULATION_BOUNDARY");
      Matrix<Rational> F=p_in.give("FACETS");
      F.col(0).fill(0);

      Rows< Matrix<Rational> >::iterator f=rows(F).begin();
      for (Entire<triangulation>::const_iterator fs=entire(Triangulation);  
	   !fs.at_end();  ++fs, ++f) {
	 const Rational approx_f_norm=sqrt(double(sqr(*f)));
	 for (Entire< PowerSet<int> >::const_iterator s=entire(*fs); !s.at_end(); ++s) {
	    partial_volume.push_back(total_volume, *s);
	    total_volume += std::abs(det( V.minor(*s,All) / *f )) / approx_f_norm;
	 }
      }
   } else {
      typedef Array< Set<int> > triangulation;
      const triangulation Triangulation=p_in.give("TRIANGULATION");

      for (Entire<triangulation>::const_iterator s=entire(Triangulation); !s.at_end(); ++s) {
	 partial_volume.push_back(total_volume, *s);
	 total_volume += std::abs(det( V.minor(*s,All) ));
      }
   }

   time_t t;
   const long s= options[seed] ? atol(options[seed]) : (time(&t), t);
  
   // initialize random rumber generator
   UniformRNG<Rational> rg(s);
   UniformRNG<Rational>::iterator random=rg.begin();

   // create n_points-many random points
   Matrix<Rational> points_out(n_points_out, d);
   Vector<Rational> part_1(d-bool(options[boundary]));

   for (Entire< Rows< Matrix<Rational> > >::iterator p_i=entire(rows(points_out)); 
	!p_i.at_end(); ++p_i) {
      // choose the simplex randomly
      const Set<int>& simplex=partial_volume.find_nearest((*random)*total_volume, operations::le())->second;

      // produce a random partition of 1 for the simplex vertices
      std::copy(random, entire(part_1));
      part_1 /= accumulate(part_1, operations::add());

      *p_i= part_1 * V.minor(simplex, All);
   }

   p_out.take("POINTS") << comment << "#seed s = " << s << '\n' 
			<< points_out;
}
} }

using namespace polymake;

/** @file unirand
 *
 *  Produce a polytope with $n$ random points.
 *  Points are uniformly distributed within the given polytope, which must be full-dimensional.
 *
 *  If @c -boundary option is used, generated points will lie on the boundary of the given polytope.
 *
 *  @synopsis unirand <output_file> <input_file> <n> [-seed <seed> ] [-boundary]
 *
 *  @reading  DIM, VERTICES, TRIANGULATION
 *  @writing  POINTS
 *
 *  @index clients Producing a new polyhedron from others
 */
int main(int argc, const char *argv[]) {
   argv_option options[]={ { "-seed", true }, { "-boundary" } };
   if (!extract_options(argc,argv,3,options) || argc > 5) {
      cerr << "usage: " << argv[0] << " <out_file> <in_file> <n> [ -seed <seed> ] [-boundary]" << endl;
      return 1;
   }
   try {
      Poly p_in(argv[2], ios::in);
      Poly p_out(argv[1], ios::out | ios::trunc);
      const int n_points_out = atoi(argv[3]);

      polytope::unirand(p_out,p_in, n_points_out, std::string("# unirand from ")+argv[2]+'\n', options);

   }
   catch (const std::exception& e) {
      cerr << e.what() << endl;
      return 1;
   }
   return 0;
}
