// Copyright 2004 The Trustees of Indiana University.

// Use, modification and distribution is subject to the Boost Software
// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

//  Authors: Nick Edmonds
//           Andrew Lumsdaine

// #define PBGL_ACCOUNTING

#include <boost/graph/use_mpi.hpp>

#include <boost/graph/distributed/compressed_sparse_row_graph.hpp>
#include <boost/graph/distributed/adjacency_list.hpp>

#include <boost/graph/distributed/mpi_process_group.hpp>

#include <boost/test/minimal.hpp>
#include <boost/random.hpp>
#include <boost/property_map/parallel/distributed_property_map.hpp>
#include <boost/graph/distributed/graphviz.hpp>
#include <boost/graph/iteration_macros.hpp>
#include <boost/graph/properties.hpp>

#include <boost/graph/rmat_graph_generator.hpp>
#include <boost/graph/small_world_generator.hpp>
#include <boost/graph/erdos_renyi_generator.hpp>

#include <boost/graph/distributed/connected_components.hpp>
#include <boost/graph/distributed/connected_components_parallel_search.hpp>
#include <boost/graph/distributed/betweenness_centrality.hpp>
#include <boost/graph/distributed/delta_stepping_shortest_paths.hpp>

#include <time.h>
#include <sys/time.h>

#include <iostream>
#include <iomanip>
#include <vector>
#include <stdint.h>

// Edge distribution tags select a generator
struct rmat_edge_distribution_tag { };
struct rmat_unique_edge_distribution_tag { };

using namespace boost;
using boost::graph::distributed::mpi_process_group;

/****************************************************************************
 * Timing
 ****************************************************************************/
#ifndef PBGL_ACCOUNTING 

typedef double time_type;

inline time_type get_time()
{
  timeval tp;
  gettimeofday(&tp, 0);
  return tp.tv_sec + tp.tv_usec / 1000000.0;
}

std::string print_time(time_type t)
{
  std::ostringstream out;
  out << std::setiosflags(std::ios::fixed) << std::setprecision(2) << t;
  return out.str();
}

#endif // PBGL_ACCOUNTING

/****************************************************************************
 * Edge weight generator iterator                                           *
 ****************************************************************************/
template<typename F, typename RandomGenerator>
class generator_iterator
{
public:
  typedef std::input_iterator_tag iterator_category;
  typedef typename F::result_type value_type;
  typedef const value_type&       reference;
  typedef const value_type*       pointer;
  typedef void                    difference_type;

  explicit 
  generator_iterator(RandomGenerator& gen, const F& f = F()) 
    : f(f), gen(&gen) 
  { 
    value = this->f(gen); 
  }

  reference operator*() const  { return value; }
  pointer   operator->() const { return &value; }

  generator_iterator& operator++()
  {
    value = f(*gen);
    return *this;
  }

  generator_iterator operator++(int)
  {
    generator_iterator temp(*this);
    ++(*this);
    return temp;
  }

  bool operator==(const generator_iterator& other) const
  { return f == other.f; }

  bool operator!=(const generator_iterator& other) const
  { return !(*this == other); }

private:
  F f;
  RandomGenerator* gen;
  value_type value;
};

template<typename F, typename RandomGenerator>
inline generator_iterator<F, RandomGenerator> 
make_generator_iterator( RandomGenerator& gen, const F& f)
{ return generator_iterator<F, RandomGenerator>(gen, f); }

/****************************************************************************
 * Edge Property                                                            *
 ****************************************************************************/
typedef int weight_type;

struct WeightedEdge {
  WeightedEdge(weight_type weight = 0) : weight(weight) { }
  
  weight_type weight;

  template<typename Archiver>
  void serialize(Archiver& ar, const unsigned int /*version*/)
  {
    ar & weight;
  }
};

/****************************************************************************
 * Algorithm Tests                                                          *
 ****************************************************************************/
template <typename Graph>
void test_directed_sequential_algorithms(const Graph& g)
{ }

template <typename Graph>
void test_undirected_sequential_algorithms(const Graph& g)
{ 
  std::vector<unsigned int> componentS(num_vertices(g));
  typedef iterator_property_map<
      std::vector<unsigned int>::iterator, 
      typename property_map<Graph, vertex_index_t>::type> 
    ComponentMap;
  ComponentMap component(componentS.begin(), get(vertex_index, g));

  time_type start = get_time();
  unsigned int num_components = connected_components(g, component);
  time_type end = get_time();
  
  std::cerr << "    Sequential connected Components time = " 
            << print_time(end - start) << " seconds.\n" 
            << "    " << num_components << " components identified\n";
}

template <typename Graph, typename EdgeWeightMap>
void test_directed_csr_only_algorithms(const Graph& g, EdgeWeightMap weight,
                                       typename graph_traits<Graph>::vertices_size_type num_sources,
                                       typename property_traits<EdgeWeightMap>::value_type C)
{
  typedef typename graph_traits<Graph>::vertex_descriptor vertex_descriptor;
  typedef typename graph_traits<Graph>::vertex_iterator vertex_iterator;
  typedef typename graph_traits<Graph>::vertices_size_type vertices_size_type;
  typedef typename graph_traits<Graph>::edges_size_type edges_size_type;

  typedef typename boost::graph::parallel::process_group_type<Graph>::type 
    process_group_type;

  process_group_type pg = process_group(g);
  typename process_group_type::process_id_type id = process_id(pg);
  typename process_group_type::process_size_type p = num_processes(pg);    

  vertices_size_type n = num_vertices(g);
  n = boost::parallel::all_reduce(pg, n, std::plus<vertices_size_type>());

  edges_size_type m = num_edges(g);
  m = boost::parallel::all_reduce(pg, m, std::plus<edges_size_type>());

  //
  // Betweenness Centrality (Approximate)
  //
  queue<vertex_descriptor> delta_stepping_vertices;

  {
    // Distributed Centrality Map
    typedef typename property_map<Graph, vertex_index_t>::const_type IndexMap;
    typedef iterator_property_map<std::vector<int>::iterator, IndexMap> CentralityMap;
    
    std::vector<int> centralityS(num_vertices(g), 0);
    CentralityMap centrality(centralityS.begin(), get(vertex_index, g));

    // Calculate number of vertices of degree 0
    vertices_size_type n0 = 0;
    BGL_FORALL_VERTICES_T(v, g, Graph) {
      if (out_degree(v, g) == 0) n0++;
    }
    n0 = boost::parallel::all_reduce(pg, n0, std::plus<vertices_size_type>());

    queue<vertex_descriptor> sources;

    {
      assert(num_sources >= p);  // Don't feel like adding a special case for num_sources < p

      minstd_rand gen;
      uniform_int<vertices_size_type> rand_vertex(0, num_vertices(g) - 1);
      std::vector<vertex_descriptor> all_sources, local_sources;
      vertices_size_type local_vertices = vertices_size_type(floor((double)num_sources / p));
      local_vertices += (id < (num_sources - (p * local_vertices)) ? 1 : 0); 

      while (local_vertices > 0) {
        vertex_iterator iter = vertices(g).first;
        std::advance(iter, rand_vertex(gen));
        
        if (out_degree(*iter, g) != 0
            && std::find(local_sources.begin(), local_sources.end(), *iter) == local_sources.end()) {
          local_sources.push_back(*iter);
          --local_vertices;
        }
      }
      all_gather(pg, local_sources.begin(), local_sources.end(), all_sources);
      std::sort(all_sources.begin(), all_sources.end());
      for (typename std::vector<vertex_descriptor>::iterator iter = all_sources.begin(); 
           iter != all_sources.end(); ++iter) {
        sources.push(*iter);
        delta_stepping_vertices.push(*iter);
      }
    }

    // NOTE: The delta below assumes uniform edge weight distribution
    time_type start = get_time();
    brandes_betweenness_centrality(g, buffer(sources).weight_map(weight).
                                   centrality_map(centrality).lookahead((m / n) * (C / 2)));
    time_type end = get_time();
    
    edges_size_type exactTEPs = edges_size_type(floor(7 * n* (n - n0) / (end - start)));
    
    if (id == 0)
      std::cerr << "    Betweenness Centrality Approximate (" << num_sources << " sources) = " 
                << print_time(end - start) << " (" << exactTEPs << " TEPs)\n";
  }

  //
  // Delta stepping performance (to compare to SSSP inside BC
  //
  if (false) {
    typedef typename property_map<Graph, vertex_index_t>::const_type IndexMap;
    typedef iterator_property_map<std::vector<int>::iterator, IndexMap> DistanceMap;
    
    std::vector<int> distanceS(num_vertices(g), 0);
    DistanceMap distance(distanceS.begin(), get(vertex_index, g));

    while(!delta_stepping_vertices.empty()) {

      time_type start = get_time();
      delta_stepping_shortest_paths(g, delta_stepping_vertices.top(), dummy_property_map(),
                                    distance, weight);
      time_type end = get_time();
  
      delta_stepping_vertices.pop();
      distance.reset();
    
      if (id == 0)
        std::cerr << "    Delta-stepping shortest paths = " << print_time(end - start) 
                  << std::endl;
    }
  }

}

template <typename Graph>
void test_directed_algorithms(const Graph& g)
{
}

template <typename Graph>
void test_undirected_algorithms(const Graph& g)
{
  typedef typename boost::graph::parallel::process_group_type<Graph>::type 
    process_group_type;

  process_group_type pg = process_group(g);
  typename process_group_type::process_id_type id = process_id(pg);
  typename process_group_type::process_size_type p = num_processes(pg);    


  // Connected Components
  std::vector<unsigned int> local_components_vec(num_vertices(g));
  typedef iterator_property_map<
      std::vector<unsigned int>::iterator, 
      typename property_map<Graph, vertex_index_t>::type> 
    ComponentMap;
  ComponentMap component(local_components_vec.begin(), get(vertex_index, g));

  int num_components;

  time_type start = get_time();
  num_components = connected_components(g, component);
  time_type end = get_time();
  if (id == 0) 
    std::cerr << "    Connected Components time = " << print_time(end - start) 
              << " seconds.\n" 
              << "    " << num_components << " components identified\n";

  start = get_time();
  num_components = boost::graph::distributed::connected_components_ps(g, component);
  end = get_time();
  if (id == 0) 
    std::cerr << "    Connected Components (parallel search) time = " 
              << print_time(end - start) << " seconds.\n"
              << "    " << num_components << " components identified\n";
}

/****************************************************************************
 * Graph Type Tests                                                         *
 ****************************************************************************/

// TODO: Re-seed PRNG before sequential tests to get the same graph as the 
// distributed case?

//
// Compressed Sparse Row
//

// R-MAT 
template <typename ProcessGroup, typename RandomGenerator, typename Distribution>
void test_csr(const ProcessGroup& pg, RandomGenerator& gen, Distribution& distrib,
              bool sequential_tests, size_t N, size_t M, size_t C, 
              double a, double b, double c, double d, size_t num_sources,
              rmat_edge_distribution_tag)
{
  if (process_id(pg) == 0)
    std::cerr << "  R-MAT\n";

  typedef compressed_sparse_row_graph<directedS, no_property, WeightedEdge, no_property,
                                      distributedS<mpi_process_group> > Graph;

  Graph g(sorted_rmat_iterator<RandomGenerator, Graph>(gen, N, M, a, b, c, d),
          sorted_rmat_iterator<RandomGenerator, Graph>(),
          make_generator_iterator(gen, uniform_int<int>(1, C)),
          N, pg, distrib);
  
  test_directed_algorithms(g);
  test_directed_csr_only_algorithms(g, get(&WeightedEdge::weight, g), num_sources, C);

  if (sequential_tests && process_id(pg) == 0) {
    typedef compressed_sparse_row_graph<directedS, no_property, WeightedEdge>
      seqGraph;
    
    seqGraph sg(edges_are_sorted,
                sorted_rmat_iterator<RandomGenerator, seqGraph>(gen, N, M, a, b, c, d),
                sorted_rmat_iterator<RandomGenerator, seqGraph>(),
                make_generator_iterator(gen, uniform_int<int>(1, C)),
                N);
    
    test_directed_sequential_algorithms(sg);
  }
}

// R-MAT with unique edges
template <typename ProcessGroup, typename RandomGenerator, typename Distribution>
void test_csr(const ProcessGroup& pg, RandomGenerator& gen, Distribution& distrib,
              bool sequential_tests, size_t N, size_t M, size_t C,  
              double a, double b, double c, double d, size_t num_sources,
              rmat_unique_edge_distribution_tag)
{
  if (process_id(pg) == 0)
    std::cerr << "  R-MAT - unique\n";

  typedef compressed_sparse_row_graph<directedS, no_property, WeightedEdge, no_property,
                                      distributedS<mpi_process_group> > Graph;
  
  // Last boolean parameter makes R-MAT bidirectional
  Graph g(sorted_unique_rmat_iterator<RandomGenerator, Graph>(gen, N, M, a, b, c, d),
          sorted_unique_rmat_iterator<RandomGenerator, Graph>(),
          make_generator_iterator(gen, uniform_int<int>(1, C)),
          N, pg, distrib);
  
  test_directed_algorithms(g);
  test_directed_csr_only_algorithms(g, get(&WeightedEdge::weight, g), num_sources, C);

  if (sequential_tests && process_id(pg) == 0) { 
    typedef compressed_sparse_row_graph<directedS, no_property, WeightedEdge>
      seqGraph;
    
    seqGraph sg(edges_are_sorted,
                sorted_unique_rmat_iterator<RandomGenerator, seqGraph>(gen, N, M, a, b, c, d),
                sorted_unique_rmat_iterator<RandomGenerator, seqGraph>(),
                make_generator_iterator(gen, uniform_int<int>(1, C)),
                N);
    
    test_directed_sequential_algorithms(sg);
  }
}

// Erdos-Renyi
template <typename ProcessGroup, typename RandomGenerator, typename Distribution>
void test_csr(const ProcessGroup& pg, RandomGenerator& gen, Distribution& distrib,
              bool sequential_tests, size_t N, size_t M, size_t C, size_t num_sources)
{
  if (process_id(pg) == 0)
    std::cerr << "  Erdos-Renyi\n";

  double _p = static_cast<double>(M) / (N*N);

  typedef compressed_sparse_row_graph<directedS, no_property, WeightedEdge, no_property,
                                      distributedS<mpi_process_group> > Graph;
    
  Graph g(sorted_erdos_renyi_iterator<RandomGenerator, Graph>(gen, N, _p/2),
          sorted_erdos_renyi_iterator<RandomGenerator, Graph>(),
          make_generator_iterator(gen, uniform_int<int>(1, C)),
          N, pg, distrib);
  
  test_directed_algorithms(g);
  test_directed_csr_only_algorithms(g, get(&WeightedEdge::weight, g), num_sources, C);

  if (sequential_tests && process_id(pg) == 0) {
    typedef compressed_sparse_row_graph<directedS, no_property, WeightedEdge>
      seqGraph;
    
    seqGraph sg(edges_are_sorted,
                sorted_erdos_renyi_iterator<RandomGenerator, seqGraph>(gen, N, _p/2),
                sorted_erdos_renyi_iterator<RandomGenerator, seqGraph>(),
                make_generator_iterator(gen, uniform_int<int>(1, C)),
                N);
    
    test_directed_sequential_algorithms(sg);
  }
}

// Small World
template <typename ProcessGroup, typename RandomGenerator, typename Distribution>
void test_csr(const ProcessGroup& pg, RandomGenerator& gen, Distribution& distrib,
              bool sequential_tests, size_t N, size_t M, size_t C, double p, 
              size_t num_sources)
{
  if (process_id(pg) == 0)
    std::cerr << "  Small-World\n";

  int k = M / N;

  typedef compressed_sparse_row_graph<directedS, no_property, WeightedEdge, no_property,
                                      distributedS<mpi_process_group> > Graph;

  Graph g(small_world_iterator<RandomGenerator, Graph>(gen, N, k, p),
          small_world_iterator<RandomGenerator, Graph>(),
          make_generator_iterator(gen, uniform_int<int>(1, C)),
          N, pg, distrib);
  
  test_directed_algorithms(g);
  test_directed_csr_only_algorithms(g, get(&WeightedEdge::weight, g), num_sources, C);

  if (sequential_tests && process_id(pg) == 0) {
    typedef compressed_sparse_row_graph<directedS, no_property, WeightedEdge>
      seqGraph;
    
    seqGraph sg(edges_are_sorted,
                small_world_iterator<RandomGenerator, seqGraph>(gen, N, k, p),
                small_world_iterator<RandomGenerator, seqGraph>(),
                make_generator_iterator(gen, uniform_int<int>(1, C)),
                N);
    
    test_directed_sequential_algorithms(sg);
  }
}

//
// Adjacency List
//

// R-MAT 
template <typename ProcessGroup, typename RandomGenerator, typename Distribution>
void test_adjacency_list(const ProcessGroup& pg, RandomGenerator& gen, Distribution& distrib,
                         bool sequential_tests, size_t N, size_t M, size_t C, 
                         double a, double b, double c, double d,
                         rmat_edge_distribution_tag)
{
  if (process_id(pg) == 0)
    std::cerr << "R-MAT\n";

  {
    typedef adjacency_list<vecS, distributedS<mpi_process_group, vecS>,
                           directedS, no_property, WeightedEdge> Graph;
    
    Graph g(rmat_iterator<RandomGenerator, Graph>(gen, N, M, a, b, c, d),
            rmat_iterator<RandomGenerator, Graph>(),
            make_generator_iterator(gen, uniform_int<int>(1, C)),
            N, pg, distrib);

    test_directed_algorithms(g);
  }

  {
    typedef adjacency_list<vecS, distributedS<mpi_process_group, vecS>,
                           undirectedS, no_property, WeightedEdge> Graph;
    
    Graph g(rmat_iterator<RandomGenerator, Graph>(gen, N, M, a, b, c, d),
            rmat_iterator<RandomGenerator, Graph>(),
            make_generator_iterator(gen, uniform_int<int>(1, C)),
            N, pg, distrib);

    test_undirected_algorithms(g);
  }

  if (sequential_tests && process_id(pg) == 0) {
    {
      typedef adjacency_list<vecS, vecS, directedS, no_property, property<edge_weight_t, int> > 
        seqGraph;
      
      seqGraph sg(rmat_iterator<RandomGenerator, seqGraph>(gen, N, M, a, b, c, d),
                  rmat_iterator<RandomGenerator, seqGraph>(),
                  make_generator_iterator(gen, uniform_int<int>(1, C)),
                  N);

      test_directed_sequential_algorithms(sg);
    }

    {
      typedef adjacency_list<vecS, vecS, undirectedS, no_property, property<edge_weight_t, int> > 
        seqGraph;
      
      seqGraph sg(rmat_iterator<RandomGenerator, seqGraph>(gen, N, M, a, b, c, d),
                  rmat_iterator<RandomGenerator, seqGraph>(),
                  make_generator_iterator(gen, uniform_int<int>(1, C)),
                  N);

      test_undirected_sequential_algorithms(sg);
    }
  }
}

// R-MAT with unique edges
template <typename ProcessGroup, typename RandomGenerator, typename Distribution>
void test_adjacency_list(const ProcessGroup& pg, RandomGenerator& gen, Distribution& distrib,
                         bool sequential_tests, size_t N, size_t M, size_t C,  
                         double a, double b, double c, double d,
                         rmat_unique_edge_distribution_tag)
{
  if (process_id(pg) == 0)
    std::cerr << "  R-MAT - unique\n";

  {
    typedef adjacency_list<vecS, distributedS<mpi_process_group, vecS>,
                           directedS, no_property, WeightedEdge> Graph;

    Graph g(sorted_unique_rmat_iterator<RandomGenerator, Graph>(gen, N, M, a, b, c, d),
            sorted_unique_rmat_iterator<RandomGenerator, Graph>(),
            make_generator_iterator(gen, uniform_int<int>(1, C)),
            N, pg, distrib);
    
    test_directed_algorithms(g);
  }

  {
    typedef adjacency_list<vecS, distributedS<mpi_process_group, vecS>,
                           undirectedS, no_property, WeightedEdge> Graph;

    Graph g(sorted_unique_rmat_iterator<RandomGenerator, Graph>(gen, N, M, a, b, c, d),
            sorted_unique_rmat_iterator<RandomGenerator, Graph>(),
            make_generator_iterator(gen, uniform_int<int>(1, C)),
            N, pg, distrib);
    
    test_undirected_algorithms(g);
  }

  if (sequential_tests && process_id(pg) == 0) {
    {
      typedef adjacency_list<vecS, vecS, directedS, no_property, property<edge_weight_t, int> > 
        seqGraph;
      
      seqGraph sg(sorted_unique_rmat_iterator<RandomGenerator, seqGraph>(gen, N, M, a, b, c, d),
                  sorted_unique_rmat_iterator<RandomGenerator, seqGraph>(),
                  make_generator_iterator(gen, uniform_int<int>(1, C)),
                  N);

      test_directed_sequential_algorithms(sg);
    }

    {
      typedef adjacency_list<vecS, vecS, undirectedS, no_property, property<edge_weight_t, int> > 
        seqGraph;
      
      seqGraph sg(sorted_unique_rmat_iterator<RandomGenerator, seqGraph>(gen, N, M, a, b, c, d),
                  sorted_unique_rmat_iterator<RandomGenerator, seqGraph>(),
                  make_generator_iterator(gen, uniform_int<int>(1, C)),
                  N);

      test_undirected_sequential_algorithms(sg);
    }
  }
}

// Erdos-Renyi
template <typename ProcessGroup, typename RandomGenerator, typename Distribution>
void test_adjacency_list(const ProcessGroup& pg, RandomGenerator& gen, Distribution& distrib,
                         bool sequential_tests, size_t N, size_t M, size_t C)
{
  if (process_id(pg) == 0)
    std::cerr << "  Erdos-Renyi\n";

  double _p = static_cast<double>(M) / N*N;

  {
    typedef adjacency_list<vecS, distributedS<mpi_process_group, vecS>,
                           directedS, no_property, WeightedEdge> Graph;

    Graph g(erdos_renyi_iterator<RandomGenerator, Graph>(gen, N, _p/2),
            erdos_renyi_iterator<RandomGenerator, Graph>(),
            make_generator_iterator(gen, uniform_int<int>(1, C)),
            N, pg, distrib);
    
    test_directed_algorithms(g);
  }

  {
    typedef adjacency_list<vecS, distributedS<mpi_process_group, vecS>,
                           undirectedS, no_property, WeightedEdge> Graph;

    Graph g(erdos_renyi_iterator<RandomGenerator, Graph>(gen, N, _p/2),
            erdos_renyi_iterator<RandomGenerator, Graph>(),
            make_generator_iterator(gen, uniform_int<int>(1, C)),
            N, pg, distrib);
    
    test_undirected_algorithms(g);
  }

  if (sequential_tests && process_id(pg) == 0) {
    {
      typedef adjacency_list<vecS, vecS, directedS, no_property, property<edge_weight_t, int> > 
        seqGraph;

      seqGraph sg(erdos_renyi_iterator<RandomGenerator, seqGraph>(gen, N, _p/2),
                  erdos_renyi_iterator<RandomGenerator, seqGraph>(),
                  make_generator_iterator(gen, uniform_int<int>(1, C)),
                  N);
      
      test_directed_sequential_algorithms(sg);
    }

    {
      typedef adjacency_list<vecS, vecS, undirectedS, no_property, property<edge_weight_t, int> > 
        seqGraph;

      seqGraph sg(erdos_renyi_iterator<RandomGenerator, seqGraph>(gen, N, _p/2),
                  erdos_renyi_iterator<RandomGenerator, seqGraph>(),
                  make_generator_iterator(gen, uniform_int<int>(1, C)),
                  N);
      
      test_undirected_sequential_algorithms(sg);
    }
  }
}

// Small World
template <typename ProcessGroup, typename RandomGenerator, typename Distribution>
void test_adjacency_list(const ProcessGroup& pg, RandomGenerator& gen, Distribution& distrib,
                         bool sequential_tests, size_t N, size_t M, size_t C, double p)
{
  if (process_id(pg) == 0)
    std::cerr << "  Small-World\n";

  int k = M / N;

  {
    typedef adjacency_list<vecS, distributedS<mpi_process_group, vecS>,
                           directedS, no_property, WeightedEdge> Graph;

    Graph g(small_world_iterator<RandomGenerator, Graph>(gen, N, k, p),
            small_world_iterator<RandomGenerator, Graph>(),
            make_generator_iterator(gen, uniform_int<int>(1, C)),
            N, pg, distrib);

    test_directed_algorithms(g);
  }

  {
    typedef adjacency_list<vecS, distributedS<mpi_process_group, vecS>,
                           undirectedS, no_property, WeightedEdge> Graph;

    Graph g(small_world_iterator<RandomGenerator, Graph>(gen, N, k, p),
            small_world_iterator<RandomGenerator, Graph>(),
            make_generator_iterator(gen, uniform_int<int>(1, C)),
            N, pg, distrib);

    test_undirected_algorithms(g);
  }

  if (sequential_tests && process_id(pg) == 0) {
    {
      typedef adjacency_list<vecS, vecS, directedS, no_property, property<edge_weight_t, int> > 
        seqGraph;
      
      seqGraph sg(small_world_iterator<RandomGenerator, seqGraph>(gen, N, k, p),
                  small_world_iterator<RandomGenerator, seqGraph>(),
                  make_generator_iterator(gen, uniform_int<int>(1, C)),
                  N);
      
      test_directed_sequential_algorithms(sg);
    }

    {
      typedef adjacency_list<vecS, vecS, undirectedS, no_property, property<edge_weight_t, int> > 
        seqGraph;
      
      seqGraph sg(small_world_iterator<RandomGenerator, seqGraph>(gen, N, k, p),
                  small_world_iterator<RandomGenerator, seqGraph>(),
                  make_generator_iterator(gen, uniform_int<int>(1, C)),
                  N);
      
      test_undirected_sequential_algorithms(sg);
    }
  }
}

void usage()
{
  std::cerr << "Algorithm Performance Test\n"
            << "Usage : algorithm_performance [options]\n\n"
            << "Options are:\n"
            << "\t--vertices v\t\t\tNumber of vertices in the graph\n"
            << "\t--edges v\t\t\tNumber of edges in the graph\n"
            << "\t--seed s\t\t\tSeed for synchronized random number generator\n"
            << "\t--max-weight miw\t\tMaximum integer edge weight\n"
            << "\t--rewire-probability\t\tRewire-probabitility (p) for small-world graphs\n"
            << "\t--dot\t\t\t\tEmit a dot file containing the graph\n"
            << "\t--verify\t\t\tVerify result\n"
            << "\t--degree-dist\t\t\tOutput degree distribution of graph\n"
            << "\t--sequential-graph\t\tRun sequential graph tests\n"
            << "\t--erdos-renyi\t\t\tRun tests on Erdos-Renyi graphs\n"   
            << "\t--small-world\t\t\tRun tests on Small World graphs\n"   
            << "\t--rmat\t\t\t\tRun tests on R-MAT graphs\n"   
            << "\t--rmat-unique\t\t\tRun tests on R-MAT graphs with no duplicate edges\n"
            << "\t--csr <bool>\t\t\tRun tests using CSR graphs\n"   
            << "\t--adjacency-list <bool>\t\tRun tests using Adjacency List graphs\n";   
}


int test_main(int argc, char* argv[])
{
  mpi::environment env(argc, argv);

  rand48 gen;
  
  // Default args
  size_t n = 100, 
    m = 8*n, 
    c = 100,
    num_sources = 32,
    num_headers = 16 * 1024,
    batch_buffer_size = 1024 * 1024;
  uint64_t seed = 1;
  bool emit_dot_file = false, 
    verify = false,
    sequential_graph = false,
    show_degree_dist = false,
    erdos_renyi = false,
    small_world = false,
    rmat = false,
    rmat_unique = false,
    csr = false,
    adj_list = false;
  double p = 0.1,
    rmat_a = 0.5, 
    rmat_b = 0.25,
    rmat_c = 0.1,
    rmat_d = 0.15; 

  // Parse args
  for (int i = 1; i < argc; ++i) {
    std::string arg = argv[i];

    if (arg == "--vertices")
      n = boost::lexical_cast<size_t>( argv[i+1] );

    if (arg == "--seed")
      seed = boost::lexical_cast<uint64_t>( argv[i+1] );

    if (arg == "--edges")
      m = boost::lexical_cast<size_t>( argv[i+1] );

    if (arg == "--max-weight")
      c = boost::lexical_cast<int>( argv[i+1] );

    if (arg == "--batch-buffer-size") {
      batch_buffer_size = boost::lexical_cast<size_t>( argv[i+1] );
      num_headers = batch_buffer_size / 16;
      num_headers = num_headers > 0 ? num_headers : 1;
    }

    if (arg == "--rewire-probability")
      p = boost::lexical_cast<double>( argv[i+1] );

    if (arg == "--num-sources")
      num_sources = boost::lexical_cast<size_t>( argv[i + 1] );

    if (arg == "--erdos-renyi")
      erdos_renyi = true;

    if (arg == "--small-world")
      small_world = true;

    if (arg == "--rmat")
      rmat = true;

    if (arg == "--rmat-unique")
      rmat_unique = true;

    if (arg == "--dot")
      emit_dot_file = true;

    if (arg == "--verify")
      verify = true;

    if (arg == "--degree-dist")
      show_degree_dist = true;

    if (arg == "--sequential-graph")
      sequential_graph = true;

    if (arg == "--csr")
      csr = std::string(argv[i+1]) == "true";

    if (arg == "--adjacency-list")
      adj_list = std::string(argv[i+1]) == "true";
  }

  mpi_process_group pg(num_headers, batch_buffer_size);

  if (argc == 1) {
    if (process_id(pg) == 0)
      usage();
    exit(-1);
  }

  gen.seed(seed);

  parallel::variant_distribution<mpi_process_group> distrib 
    = parallel::block(pg, n);

  if (csr) {
    if (process_id(pg) == 0)
      std::cerr << "CSR Graph Tests\n";

    if (erdos_renyi)
      test_csr(pg, gen, distrib, sequential_graph, n, m, c, num_sources);
    if (small_world)
      test_csr(pg, gen, distrib, sequential_graph, n, m, c, p, num_sources);
    if (rmat)
      test_csr(pg, gen, distrib, sequential_graph, n, m, c, 
               rmat_a, rmat_b, rmat_c, rmat_d, num_sources, rmat_edge_distribution_tag());
    if (rmat_unique)
      test_csr(pg, gen, distrib, sequential_graph, n, m, c, 
               rmat_a, rmat_b, rmat_c, rmat_d, num_sources, rmat_unique_edge_distribution_tag());
  }

  gen.seed(seed); // DEBUG 

  if (adj_list) {
    if (process_id(pg) == 0)
      std::cerr << "Adjacency List Graph Tests\n";

    if (erdos_renyi)
      test_adjacency_list(pg, gen, distrib, sequential_graph, n, m, c);
    if (small_world)
      test_adjacency_list(pg, gen, distrib, sequential_graph, n, m, c, p);
    if (rmat)
      test_adjacency_list(pg, gen, distrib, sequential_graph, n, m, c, 
                          rmat_a, rmat_b, rmat_c, rmat_d, rmat_edge_distribution_tag());
    if (rmat_unique)
      test_adjacency_list(pg, gen, distrib, sequential_graph, n, m, c, 
                          rmat_a, rmat_b, rmat_c, rmat_d, rmat_unique_edge_distribution_tag());
  }

  return 0;
}
