//  Copyright John Maddock 2008.
//  Copyright Paul A. Bristow 2016
//  Use, modification and distribution are 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)

#ifdef _MSC_VER
#  pragma warning (disable : 4127) // conditional expression is constant
#  pragma warning (disable : 4180) // qualifier applied to function type has no meaning; ignored
#  pragma warning (disable : 4503) // decorated name length exceeded, name was truncated
#  pragma warning (disable : 4512) // assignment operator could not be generated
#  pragma warning (disable : 4224) // nonstandard extension used : formal parameter 'function_ptr' was previously defined as a type
#endif

// #define BOOST_SVG_DIAGNOSTICS // define to provide diagnostic output from plotting.

#include <boost/math/special_functions.hpp>
#include <boost/math/tools/roots.hpp>
#include <boost/function.hpp>
#include <boost/bind.hpp>

#include <list>
#include <map>
#include <string>
#include <boost/svg_plot/svg_2d_plot.hpp>
#include <boost/svg_plot/show_2d_settings.hpp>

class function_arity1_plotter
{
public:
   function_arity1_plotter() : m_min_x(0), m_max_x(0), m_min_y(0), m_max_y(0), m_has_legend(false) {}

   //! Add a function to the plotter, compute the axes using range a to b and compute & add data points to map.

   void add(boost::function<double(double)> f, double x_lo, double x_hi, const std::string& name)
   {
     std::cout << "Adding function " << name << ", x range " << x_lo << " to " << x_hi << std::endl;
      if(name.size())
         m_has_legend = true;
      //
      // Now set our x-axis limits:
      if(m_max_x == m_min_x)
      {
         m_max_x = x_hi;
         m_min_x = x_lo;
      }
      else
      {
         if(x_lo < m_min_x)
            m_min_x = x_lo;
         if(x_hi > m_max_x)
            m_max_x = x_hi;
      }
      m_points.push_back(std::pair<std::string, std::map<double,double> >(name, std::map<double,double>()));
      std::map<double,double>& points = m_points.rbegin()->second;
      double interval = (x_hi - x_lo) / 200;
      for(double x = x_lo; x <= x_hi; x += interval)
      {
         double y = f(x); // Evaluate the function.
         // Set the Y axis limits if needed.
         if((m_min_y == m_max_y) && (m_min_y == 0))
            m_min_y = m_max_y = y;
         if(m_min_y > y)
            m_min_y = y;
         if(m_max_y < y)
            m_max_y = y;
         points[x] = y; // Store the pair of points values.
      } // for x

#ifdef BOOST_SVG_DIAGNOSTICS
  std::cout << "Added function " << name
    << ", x range " << x_lo << " to " << x_hi
    << ", x min = " << m_min_x << ", x max = " << m_max_x
    << ", y min = " << m_min_y << ", y max = " << m_max_y
    << ", interval = " << interval
    << std::endl;
#endif
   } // void add(boost::function<double(double)> f, double a, double b, const std::string& name)

   //! Compute x and y min and max from a map of pre-computed data points.
   void add(const std::map<double, double>& m, const std::string& name)
   {
     if (name.size() != 0)
     {
       m_has_legend = true;
     }
      m_points.push_back(std::pair<std::string, std::map<double,double> >(name, m));

      std::map<double, double>::const_iterator i = m.begin();
      while(i != m.end())
      {
         if((m_min_x == m_min_y) && (m_min_y == 0))
         {
            m_min_x = m_max_x = i->first;
         }
         if(i->first < m_min_x)
         {
            m_min_x = i->first;
         }
         if(i->first > m_max_x)
         {
            m_max_x = i->first;
         }

         if((m_min_y == m_max_y) && (m_min_y == 0))
         {
            m_min_y = m_max_y = i->second;
         }
         if(i->second < m_min_y)
         {
            m_min_y = i->second;
         }
         if(i->second > m_max_y)
         {
            m_max_y = i->second;
         }

         ++i;
      }
   } // void add(const std::map<double, double>& m, const std::string& name)

   //! Plot pre-computed m_points data for function.
   void plot(const std::string& title, const std::string& file,
      const std::string& x_lable = std::string(), const std::string& y_lable = std::string())
   {
      using namespace boost::svg;

      static const svg_color colors[5] =
      { // Colors for plot curves, used in turn.
         darkblue,
         darkred,
         darkgreen,
         darkorange,
         chartreuse
      };

      std::cout << "Plotting Special Function " << title << " to file " << file << std::endl;

      svg_2d_plot plot;
      plot.image_x_size(600);
      plot.image_y_size(400);
      plot.copyright_holder("John Maddock").copyright_date("2008").boost_license_on(true);
      plot.coord_precision(4); // Could be 3 for smaller plots?
      plot.title(title).title_font_size(20).title_on(true);
      plot.legend_on(m_has_legend);

      double x_delta = (m_max_x - m_min_x) / 50;
      double y_delta = (m_max_y - m_min_y) / 50;
      plot.x_range(m_min_x, m_max_x + x_delta)
         .y_range(m_min_y, m_max_y + y_delta);
      plot.x_label_on(true).x_label(x_lable);
      plot.y_label_on(true).y_label(y_lable);
      plot.y_major_grid_on(false).x_major_grid_on(false);
      plot.x_num_minor_ticks(3);
      plot.y_num_minor_ticks(3);
      //
      // Work out axis tick intervals:
      double l = std::floor(std::log10((m_max_x - m_min_x) / 10) + 0.5);
      double interval = std::pow(10.0, (int)l);
      if(((m_max_x - m_min_x) / interval) > 10)
         interval *= 5;
      plot.x_major_interval(interval);
      l = std::floor(std::log10((m_max_y - m_min_y) / 10) + 0.5);
      interval = std::pow(10.0, (int)l);
      if(((m_max_y - m_min_y) / interval) > 10)
         interval *= 5;
      plot.y_major_interval(interval);
      plot.plot_window_on(true);
      plot.plot_border_color(lightslategray)
          .background_border_color(lightslategray)
          .legend_border_color(lightslategray)
          .legend_background_color(white);

      int color_index = 0; // Cycle through the colors for each curve.

      for(std::list<std::pair<std::string, std::map<double,double> > >::const_iterator i = m_points.begin();
         i != m_points.end(); ++i)
      {
         plot.plot(i->second, i->first)
            .line_on(true)
            .line_color(colors[color_index])
            .line_width(1.)
            .shape(none);
         if(i->first.size())
            ++color_index;
         color_index = color_index % (sizeof(colors)/sizeof(colors[0]));
      }
      plot.write(file);
   } //    void plot(const std::string& title, const std::string& file,

   void clear()
   {
      m_points.clear();
      m_min_x = m_min_y = m_max_x = m_max_y = 0;
      m_has_legend = false;
   } // clear

private:
   std::list<std::pair<std::string, std::map<double, double> > > m_points;
   double m_min_x, m_max_x, m_min_y, m_max_y;
   bool m_has_legend;
};

template <class F>
struct location_finder
{
   location_finder(F _f, double t, double x0) : f(_f), target(t), x_off(x0){}

   double operator()(double x)
   {
      try
      {
         return f(x + x_off) - target;
      }
      catch(const std::overflow_error&)
      {
         return boost::math::tools::max_value<double>();
      }
      catch(const std::domain_error&)
      {
         if(x + x_off == x_off)
            return f(x_off + boost::math::tools::epsilon<double>() * x_off);
         throw;
      }
   }

private:
   F f;
   double target;
   double x_off;
};

template <class F>
double find_end_point(F f, double x0, double target, bool rising, double x_off = 0)
{
   boost::math::tools::eps_tolerance<double> tol(50);
   boost::uintmax_t max_iter = 1000;
   return x_off + boost::math::tools::bracket_and_solve_root(
      location_finder<F>(f, target, x_off),
      x0,
      1.5,
      rising,
      tol,
      max_iter).first;
}

double sqrt1pm1(double x)
{
   return boost::math::sqrt1pm1(x);
}

double lbeta(double a, double b)
{
   return std::log(boost::math::beta(a, b));
}

int main()
{
  try
  {
    function_arity1_plotter plot;

    // Functions may have varying numbers and types of parameters.
    // plot.add calls must use the appropriate function type.
    // Not all function types may be used, so can ignore any warning like
    // "C4101: 'f4': unreferenced local variable"
    double(*f)(double); // Simplest function type, suits most functions.
    double(*f2)(double, double);
    double(*f2u)(unsigned, double);
    double(*f2i)(int, double);
    double(*f3)(double, double, double);
    double(*f4)(double, double, double, double);
    double max_val; // Hold evaluated value of function for use in find_end_point.

       f = boost::math::zeta;
       plot.add(f, find_end_point(f, 0.1, 40.0, false, 1.0), 10, "");
       plot.add(f, -20, find_end_point(f, -0.1, -40.0, false, 1.0), "");
       plot.plot("Zeta Function Over [-20,10]", "zeta1.svg", "z", "zeta(z)");

       plot.clear();
       plot.add(f, -14, 0, "");
       plot.plot("Zeta Function Over [-14,0]", "zeta2.svg", "z", "zeta(z)");

       f = boost::math::tgamma;
       max_val = f(6);
       plot.clear();
       plot.add(f, find_end_point(f, 0.1, max_val, false), 6, "");
       plot.add(f, find_end_point(f, 0.1, -max_val, true, -1), find_end_point(f, -0.1, -max_val, false), "");
       plot.add(f, find_end_point(f, 0.1, max_val, false, -2), find_end_point(f, -0.1, max_val, true, -1), "");
       plot.add(f, find_end_point(f, 0.1, -max_val, true, -3), find_end_point(f, -0.1, -max_val, false, -2), "");
       plot.add(f, find_end_point(f, 0.1, max_val, false, -4), find_end_point(f, -0.1, max_val, true, -3), "");
       plot.plot("tgamma", "tgamma.svg", "z", "tgamma(z)");

       f = boost::math::lgamma;
       max_val = f(10);
       plot.clear();
       plot.add(f, find_end_point(f, 0.1, max_val, false), 10, "");
       plot.add(f, find_end_point(f, 0.1, max_val, false, -1), find_end_point(f, -0.1, max_val, true), "");
       plot.add(f, find_end_point(f, 0.1, max_val, false, -2), find_end_point(f, -0.1, max_val, true, -1), "");
       plot.add(f, find_end_point(f, 0.1, max_val, false, -3), find_end_point(f, -0.1, max_val, true, -2), "");
       plot.add(f, find_end_point(f, 0.1, max_val, false, -4), find_end_point(f, -0.1, max_val, true, -3), "");
       plot.add(f, find_end_point(f, 0.1, max_val, false, -5), find_end_point(f, -0.1, max_val, true, -4), "");
       plot.plot("lgamma", "lgamma.svg", "z", "lgamma(z)");

       f = boost::math::digamma;
       max_val = 10;
       plot.clear();
       plot.add(f, find_end_point(f, 0.1, -max_val, true), 10, "");
       plot.add(f, find_end_point(f, 0.1, -max_val, true, -1), find_end_point(f, -0.1, max_val, true), "");
       plot.add(f, find_end_point(f, 0.1, -max_val, true, -2), find_end_point(f, -0.1, max_val, true, -1), "");
       plot.add(f, find_end_point(f, 0.1, -max_val, true, -3), find_end_point(f, -0.1, max_val, true, -2), "");
       plot.add(f, find_end_point(f, 0.1, -max_val, true, -4), find_end_point(f, -0.1, max_val, true, -3), "");
      plot.plot("digamma", "digamma.svg", "z", "digamma(z)");

      f = boost::math::erf;
      plot.clear();
      plot.add(f, -3, 3, "erf");
      plot.plot("erf", "erf.svg", "z", "erf(z)");

      f = boost::math::erfc;
      plot.clear();
      plot.add(f, -3, 3, "erfc");
      plot.plot("erfc", "erfc.svg", "z", "erfc(z)");

    f = boost::math::erf_inv;
    plot.clear();
    plot.add(f, find_end_point(f, 0.1, -3, true, -1), find_end_point(f, -0.1, 3, true, 1), "");
    plot.plot("erf_inv", "erf_inv.svg", "z", "erf_inv(z)");
    f = boost::math::erfc_inv;
    plot.clear();
    plot.add(f, find_end_point(f, 0.1, 3, false), find_end_point(f, -0.1, -3, false, 2), "");
    plot.plot("erfc_inv", "erfc_inv.svg", "z", "erfc_inv(z)");

    f = boost::math::log1p;
    plot.clear();
    plot.add(f, find_end_point(f, 0.1, -10, true, -1), 10, "");
    plot.plot("log1p", "log1p.svg", "z", "log1p(z)");

    f = boost::math::expm1;
    plot.clear();
    plot.add(f, -4, 2, "");
    plot.plot("expm1", "expm1.svg", "z", "expm1(z)");

    f = boost::math::cbrt;
    plot.clear();
    plot.add(f, -10, 10, "");
    plot.plot("cbrt", "cbrt.svg", "z", "cbrt(z)");

    f = sqrt1pm1;
    plot.clear();
    plot.add(f, find_end_point(f, 0.1, -10, true, -1), 5, "");
    plot.plot("sqrt1pm1", "sqrt1pm1.svg", "z", "sqrt1pm1(z)");

    f2 = boost::math::powm1;
    plot.clear();
    plot.add(boost::bind(f2, 0.0001, _1), find_end_point(boost::bind(f2, 0.0001, _1), -1, 10, false), 5, "a=0.0001");
    plot.add(boost::bind(f2, 0.001, _1), find_end_point(boost::bind(f2, 0.001, _1), -1, 10, false), 5, "a=0.001");
    plot.add(boost::bind(f2, 0.01, _1), find_end_point(boost::bind(f2, 0.01, _1), -1, 10, false), 5, "a=0.01");
    plot.add(boost::bind(f2, 0.1, _1), find_end_point(boost::bind(f2, 0.1, _1), -1, 10, false), 5, "a=0.1");
    plot.add(boost::bind(f2, 0.75, _1), -5, 5, "a=0.75");
    plot.add(boost::bind(f2, 1.25, _1), -5, 5, "a=1.25");
    plot.plot("powm1", "powm1.svg", "z", "powm1(a, z)");

    f = boost::math::sinc_pi;
    plot.clear();
    plot.add(f, -10, 10, "");
    plot.plot("sinc_pi", "sinc_pi.svg", "z", "sinc_pi(z)");

    f = boost::math::sinhc_pi;
    plot.clear();
    plot.add(f, -5, 5, "");
    plot.plot("sinhc_pi", "sinhc_pi.svg", "z", "sinhc_pi(z)");

    f = boost::math::acosh;
    plot.clear();
    plot.add(f, 1, 10, "acosh");
    plot.plot("acosh", "acosh.svg", "z", "acosh(z)");

    f = boost::math::asinh;
    plot.clear();
    plot.add(f, -10, 10, "");
    plot.plot("asinh", "asinh.svg", "z", "asinh(z)");

    f = boost::math::atanh;
    plot.clear();
    plot.add(f, find_end_point(f, 0.1, -5, true, -1), find_end_point(f, -0.1, 5, true, 1), "");
    plot.plot("atanh", "atanh.svg", "z", "atanh(z)");

    f2 = boost::math::tgamma_delta_ratio;
    plot.clear();
    plot.add(boost::bind(f2, _1, -0.5), 1, 40, "delta = -0.5");
    plot.add(boost::bind(f2, _1, -0.2), 1, 40, "delta = -0.2");
    plot.add(boost::bind(f2, _1, -0.1), 1, 40, "delta = -0.1");
    plot.add(boost::bind(f2, _1, 0.1), 1, 40, "delta = 0.1");
    plot.add(boost::bind(f2, _1, 0.2), 1, 40, "delta = 0.2");
    plot.add(boost::bind(f2, _1, 0.5), 1, 40, "delta = 0.5");
    plot.add(boost::bind(f2, _1, 1.0), 1, 40, "delta = 1.0");
    plot.plot("tgamma_delta_ratio", "tgamma_delta_ratio.svg", "z", "tgamma_delta_ratio(delta, z)");

    f2 = boost::math::gamma_p;
    plot.clear();
    plot.add(boost::bind(f2, 0.5, _1), 0, 20, "a = 0.5");
    plot.add(boost::bind(f2, 1.0, _1), 0, 20, "a = 1.0");
    plot.add(boost::bind(f2, 5.0, _1), 0, 20, "a = 5.0");
    plot.add(boost::bind(f2, 10.0, _1), 0, 20, "a = 10.0");
    plot.plot("gamma_p", "gamma_p.svg", "z", "gamma_p(a, z)");

    f2 = boost::math::gamma_q;
    plot.clear();
    plot.add(boost::bind(f2, 0.5, _1), 0, 20, "a = 0.5");
    plot.add(boost::bind(f2, 1.0, _1), 0, 20, "a = 1.0");
    plot.add(boost::bind(f2, 5.0, _1), 0, 20, "a = 5.0");
    plot.add(boost::bind(f2, 10.0, _1), 0, 20, "a = 10.0");
    plot.plot("gamma_q", "gamma_q.svg", "z", "gamma_q(a, z)");

    f2 = lbeta;
    plot.clear();
    plot.add(boost::bind(f2, 0.5, _1), 0.00001, 5, "a = 0.5");
    plot.add(boost::bind(f2, 1.0, _1), 0.00001, 5, "a = 1.0");
    plot.add(boost::bind(f2, 5.0, _1), 0.00001, 5, "a = 5.0");
    plot.add(boost::bind(f2, 10.0, _1), 0.00001, 5, "a = 10.0");
    plot.plot("beta", "beta.svg", "z", "log(beta(a, z))");

    f = boost::math::expint;
    max_val = f(4);
    plot.clear();
    plot.add(f, find_end_point(f, 0.1, -max_val, true), 4, "");
    plot.add(f, -3, find_end_point(f, -0.1, -max_val, false), "");
    plot.plot("Exponential Integral Ei", "expint_i.svg", "z", "expint(z)");

    f2u = boost::math::expint;
    max_val = 1;
    plot.clear();
    plot.add(boost::bind(f2u, 1, _1), find_end_point(boost::bind(f2u, 1, _1), 0.1, max_val, false), 2, "n = 1 ");
    plot.add(boost::bind(f2u, 2, _1), find_end_point(boost::bind(f2u, 2, _1), 0.1, max_val, false), 2, "n = 2 ");
    plot.add(boost::bind(f2u, 3, _1), 0, 2, "n = 3 ");
    plot.add(boost::bind(f2u, 4, _1), 0, 2, "n = 4 ");
    plot.plot("Exponential Integral En", "expint2.svg", "z", "expint(n, z)");

    f3 = boost::math::ibeta;
    plot.clear();
    plot.add(boost::bind(f3, 9, 1, _1), 0, 1, "a = 9, b = 1");
    plot.add(boost::bind(f3, 7, 2, _1), 0, 1, "a = 7, b = 2");
    plot.add(boost::bind(f3, 5, 5, _1), 0, 1, "a = 5, b = 5");
    plot.add(boost::bind(f3, 2, 7, _1), 0, 1, "a = 2, b = 7");
    plot.add(boost::bind(f3, 1, 9, _1), 0, 1, "a = 1, b = 9");
    plot.plot("ibeta", "ibeta.svg", "z", "ibeta(a, b, z)");

    f2i = boost::math::legendre_p;
    plot.clear();
    plot.add(boost::bind(f2i, 1, _1), -1, 1, "l = 1");
    plot.add(boost::bind(f2i, 2, _1), -1, 1, "l = 2");
    plot.add(boost::bind(f2i, 3, _1), -1, 1, "l = 3");
    plot.add(boost::bind(f2i, 4, _1), -1, 1, "l = 4");
    plot.add(boost::bind(f2i, 5, _1), -1, 1, "l = 5");
    plot.plot("Legendre Polynomials", "legendre_p.svg", "x", "legendre_p(l, x)");

    f2u = boost::math::legendre_q;
    plot.clear();
    plot.add(boost::bind(f2u, 1, _1), -0.95, 0.95, "l = 1");
    plot.add(boost::bind(f2u, 2, _1), -0.95, 0.95, "l = 2");
    plot.add(boost::bind(f2u, 3, _1), -0.95, 0.95, "l = 3");
    plot.add(boost::bind(f2u, 4, _1), -0.95, 0.95, "l = 4");
    plot.add(boost::bind(f2u, 5, _1), -0.95, 0.95, "l = 5");
    plot.plot("Legendre Polynomials of the Second Kind", "legendre_q.svg", "x", "legendre_q(l, x)");

    f2u = boost::math::laguerre;
    plot.clear();
    plot.add(boost::bind(f2u, 0, _1), -5, 10, "n = 0");
    plot.add(boost::bind(f2u, 1, _1), -5, 10, "n = 1");
    plot.add(boost::bind(f2u, 2, _1),
       find_end_point(boost::bind(f2u, 2, _1), -2, 20, false),
       find_end_point(boost::bind(f2u, 2, _1), 4, 20, true),
       "n = 2");
    plot.add(boost::bind(f2u, 3, _1),
       find_end_point(boost::bind(f2u, 3, _1), -2, 20, false),
       find_end_point(boost::bind(f2u, 3, _1), 1, 20, false, 8),
       "n = 3");
    plot.add(boost::bind(f2u, 4, _1),
       find_end_point(boost::bind(f2u, 4, _1), -2, 20, false),
       find_end_point(boost::bind(f2u, 4, _1), 1, 20, true, 8),
       "n = 4");
    plot.add(boost::bind(f2u, 5, _1),
       find_end_point(boost::bind(f2u, 5, _1), -2, 20, false),
       find_end_point(boost::bind(f2u, 5, _1), 1, 20, true, 8),
       "n = 5");
    plot.plot("Laguerre Polynomials", "laguerre.svg", "x", "laguerre(n, x)");

    f2u = boost::math::hermite;
    plot.clear();
    plot.add(boost::bind(f2u, 0, _1), -1.8, 1.8, "n = 0");
    plot.add(boost::bind(f2u, 1, _1), -1.8, 1.8, "n = 1");
    plot.add(boost::bind(f2u, 2, _1), -1.8, 1.8, "n = 2");
    plot.add(boost::bind(f2u, 3, _1), -1.8, 1.8, "n = 3");
    plot.add(boost::bind(f2u, 4, _1), -1.8, 1.8, "n = 4");
    plot.plot("Hermite Polynomials", "hermite.svg", "x", "hermite(n, x)");

    f2 = boost::math::cyl_bessel_j;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), -20, 20, "v = 0");
    plot.add(boost::bind(f2, 1, _1), -20, 20, "v = 1");
    plot.add(boost::bind(f2, 2, _1), -20, 20, "v = 2");
    plot.add(boost::bind(f2, 3, _1), -20, 20, "v = 3");
    plot.add(boost::bind(f2, 4, _1), -20, 20, "v = 4");
    plot.plot("Bessel J", "cyl_bessel_j.svg", "x", "cyl_bessel_j(v, x)");

    f2 = boost::math::cyl_neumann;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), find_end_point(boost::bind(f2, 0, _1), 0.1, -5, true), 20, "v = 0");
    plot.add(boost::bind(f2, 1, _1), find_end_point(boost::bind(f2, 1, _1), 0.1, -5, true), 20, "v = 1");
    plot.add(boost::bind(f2, 2, _1), find_end_point(boost::bind(f2, 2, _1), 0.1, -5, true), 20, "v = 2");
    plot.add(boost::bind(f2, 3, _1), find_end_point(boost::bind(f2, 3, _1), 0.1, -5, true), 20, "v = 3");
    plot.add(boost::bind(f2, 4, _1), find_end_point(boost::bind(f2, 4, _1), 0.1, -5, true), 20, "v = 4");
    plot.plot("Bessel Y", "cyl_neumann.svg", "x", "cyl_neumann(v, x)");

    f2 = boost::math::cyl_bessel_i;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), find_end_point(boost::bind(f2, 0, _1), -0.1, 20, false), find_end_point(boost::bind(f2, 0, _1), 0.1, 20, true), "v = 0");
    plot.add(boost::bind(f2, 2, _1), find_end_point(boost::bind(f2, 2, _1), -0.1, 20, false), find_end_point(boost::bind(f2, 2, _1), 0.1, 20, true), "v = 2");
    plot.add(boost::bind(f2, 5, _1), find_end_point(boost::bind(f2, 5, _1), -0.1, -20, true), find_end_point(boost::bind(f2, 5, _1), 0.1, 20, true), "v = 5");
    plot.add(boost::bind(f2, 7, _1), find_end_point(boost::bind(f2, 7, _1), -0.1, -20, true), find_end_point(boost::bind(f2, 7, _1), 0.1, 20, true), "v = 7");
    plot.add(boost::bind(f2, 10, _1), find_end_point(boost::bind(f2, 10, _1), -0.1, 20, false), find_end_point(boost::bind(f2, 10, _1), 0.1, 20, true), "v = 10");
    plot.plot("Bessel I", "cyl_bessel_i.svg", "x", "cyl_bessel_i(v, x)");

    f2 = boost::math::cyl_bessel_k;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), find_end_point(boost::bind(f2, 0, _1), 0.1, 10, false), 10, "v = 0");
    plot.add(boost::bind(f2, 2, _1), find_end_point(boost::bind(f2, 2, _1), 0.1, 10, false), 10, "v = 2");
    plot.add(boost::bind(f2, 5, _1), find_end_point(boost::bind(f2, 5, _1), 0.1, 10, false), 10, "v = 5");
    plot.add(boost::bind(f2, 7, _1), find_end_point(boost::bind(f2, 7, _1), 0.1, 10, false), 10, "v = 7");
    plot.add(boost::bind(f2, 10, _1), find_end_point(boost::bind(f2, 10, _1), 0.1, 10, false), 10, "v = 10");
    plot.plot("Bessel K", "cyl_bessel_k.svg", "x", "cyl_bessel_k(v, x)");

    f2u = boost::math::sph_bessel;
    plot.clear();
    plot.add(boost::bind(f2u, 0, _1), 0, 20, "v = 0");
    plot.add(boost::bind(f2u, 2, _1), 0, 20, "v = 2");
    plot.add(boost::bind(f2u, 5, _1), 0, 20, "v = 5");
    plot.add(boost::bind(f2u, 7, _1), 0, 20, "v = 7");
    plot.add(boost::bind(f2u, 10, _1), 0, 20, "v = 10");
    plot.plot("Bessel j", "sph_bessel.svg", "x", "sph_bessel(v, x)");

    f2u = boost::math::sph_neumann;
    plot.clear();
    plot.add(boost::bind(f2u, 0, _1), find_end_point(boost::bind(f2u, 0, _1), 0.1, -5, true), 20, "v = 0");
    plot.add(boost::bind(f2u, 2, _1), find_end_point(boost::bind(f2u, 2, _1), 0.1, -5, true), 20, "v = 2");
    plot.add(boost::bind(f2u, 5, _1), find_end_point(boost::bind(f2u, 5, _1), 0.1, -5, true), 20, "v = 5");
    plot.add(boost::bind(f2u, 7, _1), find_end_point(boost::bind(f2u, 7, _1), 0.1, -5, true), 20, "v = 7");
    plot.add(boost::bind(f2u, 10, _1), find_end_point(boost::bind(f2u, 10, _1), 0.1, -5, true), 20, "v = 10");
    plot.plot("Bessel y", "sph_neumann.svg", "x", "sph_neumann(v, x)");

    f4 = boost::math::ellint_rj;
    plot.clear();
    plot.add(boost::bind(f4, _1, _1, _1, _1), find_end_point(boost::bind(f4, _1, _1, _1, _1), 0.1, 10, false), 4, "RJ");
    f3 = boost::math::ellint_rf;
    plot.add(boost::bind(f3, _1, _1, _1), find_end_point(boost::bind(f3, _1, _1, _1), 0.1, 10, false), 4, "RF");
    plot.plot("Elliptic Integrals", "ellint_carlson.svg", "x", "");

    f2 = boost::math::ellint_1;
    plot.clear();
    plot.add(boost::bind(f2, _1, 0.5), -0.9, 0.9, "φ=0.5");
    plot.add(boost::bind(f2, _1, 0.75), -0.9, 0.9, "φ=0.75");
    plot.add(boost::bind(f2, _1, 1.25), -0.9, 0.9, "φ=1.25");
    plot.add(boost::bind(f2, _1, boost::math::constants::pi<double>() / 2), -0.9, 0.9, "φ=π/2");
    plot.plot("Elliptic Of the First Kind", "ellint_1.svg", "k", "ellint_1(k, phi)");

    f2 = boost::math::ellint_2;
    plot.clear();
    plot.add(boost::bind(f2, _1, 0.5), -1, 1, "φ=0.5");
    plot.add(boost::bind(f2, _1, 0.75), -1, 1, "φ=0.75");
    plot.add(boost::bind(f2, _1, 1.25), -1, 1, "φ=1.25");
    plot.add(boost::bind(f2, _1, boost::math::constants::pi<double>() / 2), -1, 1, "φ=π/2");
    plot.plot("Elliptic Of the Second Kind", "ellint_2.svg", "k", "ellint_2(k, phi)");

    f3 = boost::math::ellint_3;
    plot.clear();
    plot.add(boost::bind(f3, _1, 0, 1.25), -1, 1, "n=0 φ=1.25");
    plot.add(boost::bind(f3, _1, 0.5, 1.25), -1, 1, "n=0.5 φ=1.25");
    plot.add(boost::bind(f3, _1, 0.25, boost::math::constants::pi<double>() / 2),
       find_end_point(
          boost::bind(f3, _1, 0.25, boost::math::constants::pi<double>() / 2),
          0.5, 4, false, -1),
       find_end_point(
          boost::bind(f3, _1, 0.25, boost::math::constants::pi<double>() / 2),
          -0.5, 4, true, 1), "n=0.25 φ=π/2");
    plot.add(boost::bind(f3, _1, 0.75, boost::math::constants::pi<double>() / 2),
       find_end_point(
          boost::bind(f3, _1, 0.75, boost::math::constants::pi<double>() / 2),
          0.5, 4, false, -1),
       find_end_point(
          boost::bind(f3, _1, 0.75, boost::math::constants::pi<double>() / 2),
          -0.5, 4, true, 1), "n=0.75 φ=π/2");
    plot.plot("Elliptic Of the Third Kind", "ellint_3.svg", "k", "ellint_3(k, n, phi)");

    f2 = boost::math::jacobi_sn;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), -10, 10, "k=0");
    plot.add(boost::bind(f2, 0.5, _1), -10, 10, "k=0.5");
    plot.add(boost::bind(f2, 0.75, _1), -10, 10, "k=0.75");
    plot.add(boost::bind(f2, 0.95, _1), -10, 10, "k=0.95");
    plot.add(boost::bind(f2, 1, _1), -10, 10, "k=1");
    plot.plot("Jacobi Elliptic sn", "jacobi_sn.svg", "k", "jacobi_sn(k, u)");

    f2 = boost::math::jacobi_cn;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), -10, 10, "k=0");
    plot.add(boost::bind(f2, 0.5, _1), -10, 10, "k=0.5");
    plot.add(boost::bind(f2, 0.75, _1), -10, 10, "k=0.75");
    plot.add(boost::bind(f2, 0.95, _1), -10, 10, "k=0.95");
    plot.add(boost::bind(f2, 1, _1), -10, 10, "k=1");
    plot.plot("Jacobi Elliptic cn", "jacobi_cn.svg", "k", "jacobi_cn(k, u)");

    f2 = boost::math::jacobi_dn;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), -10, 10, "k=0");
    plot.add(boost::bind(f2, 0.5, _1), -10, 10, "k=0.5");
    plot.add(boost::bind(f2, 0.75, _1), -10, 10, "k=0.75");
    plot.add(boost::bind(f2, 0.95, _1), -10, 10, "k=0.95");
    plot.add(boost::bind(f2, 1, _1), -10, 10, "k=1");
    plot.plot("Jacobi Elliptic dn", "jacobi_dn.svg", "k", "jacobi_dn(k, u)");

    f2 = boost::math::jacobi_cd;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), -10, 10, "k=0");
    plot.add(boost::bind(f2, 0.5, _1), -10, 10, "k=0.5");
    plot.add(boost::bind(f2, 0.75, _1), -10, 10, "k=0.75");
    plot.add(boost::bind(f2, 0.95, _1), -10, 10, "k=0.95");
    plot.add(boost::bind(f2, 1, _1), -10, 10, "k=1");
    plot.plot("Jacobi Elliptic cd", "jacobi_cd.svg", "k", "jacobi_cd(k, u)");

    f2 = boost::math::jacobi_cs;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), 0.1, 3, "k=0");
    plot.add(boost::bind(f2, 0.5, _1), 0.1, 3, "k=0.5");
    plot.add(boost::bind(f2, 0.75, _1), 0.1, 3, "k=0.75");
    plot.add(boost::bind(f2, 0.95, _1), 0.1, 3, "k=0.95");
    plot.add(boost::bind(f2, 1, _1), 0.1, 3, "k=1");
    plot.plot("Jacobi Elliptic cs", "jacobi_cs.svg", "k", "jacobi_cs(k, u)");

    f2 = boost::math::jacobi_dc;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), -10, 10, "k=0");
    plot.add(boost::bind(f2, 0.5, _1), -10, 10, "k=0.5");
    plot.add(boost::bind(f2, 0.75, _1), -10, 10, "k=0.75");
    plot.add(boost::bind(f2, 0.95, _1), -10, 10, "k=0.95");
    plot.add(boost::bind(f2, 1, _1), -10, 10, "k=1");
    plot.plot("Jacobi Elliptic dc", "jacobi_dc.svg", "k", "jacobi_dc(k, u)");

    f2 = boost::math::jacobi_ds;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), 0.1, 3, "k=0");
    plot.add(boost::bind(f2, 0.5, _1), 0.1, 3, "k=0.5");
    plot.add(boost::bind(f2, 0.75, _1), 0.1, 3, "k=0.75");
    plot.add(boost::bind(f2, 0.95, _1), 0.1, 3, "k=0.95");
    plot.add(boost::bind(f2, 1, _1), 0.1, 3, "k=1");
    plot.plot("Jacobi Elliptic ds", "jacobi_ds.svg", "k", "jacobi_ds(k, u)");

    f2 = boost::math::jacobi_nc;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), -5, 5, "k=0");
    plot.add(boost::bind(f2, 0.5, _1), -5, 5, "k=0.5");
    plot.add(boost::bind(f2, 0.75, _1), -5, 5, "k=0.75");
    plot.add(boost::bind(f2, 0.95, _1), -5, 5, "k=0.95");
    plot.add(boost::bind(f2, 1, _1), -5, 5, "k=1");
    plot.plot("Jacobi Elliptic nc", "jacobi_nc.svg", "k", "jacobi_nc(k, u)");

    f2 = boost::math::jacobi_ns;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), 0.1, 4, "k=0");
    plot.add(boost::bind(f2, 0.5, _1), 0.1, 4, "k=0.5");
    plot.add(boost::bind(f2, 0.75, _1), 0.1, 4, "k=0.75");
    plot.add(boost::bind(f2, 0.95, _1), 0.1, 4, "k=0.95");
    plot.add(boost::bind(f2, 1, _1), 0.1, 4, "k=1");
    plot.plot("Jacobi Elliptic ns", "jacobi_ns.svg", "k", "jacobi_ns(k, u)");

    f2 = boost::math::jacobi_nd;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), -2, 2, "k=0");
    plot.add(boost::bind(f2, 0.5, _1), -2, 2, "k=0.5");
    plot.add(boost::bind(f2, 0.75, _1), -2, 2, "k=0.75");
    plot.add(boost::bind(f2, 0.95, _1), -2, 2, "k=0.95");
    plot.add(boost::bind(f2, 1, _1), -2, 2, "k=1");
    plot.plot("Jacobi Elliptic nd", "jacobi_nd.svg", "k", "jacobi_nd(k, u)");

    f2 = boost::math::jacobi_sc;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), -5, 5, "k=0");
    plot.add(boost::bind(f2, 0.5, _1), -5, 5, "k=0.5");
    plot.add(boost::bind(f2, 0.75, _1), -5, 5, "k=0.75");
    plot.add(boost::bind(f2, 0.95, _1), -5, 5, "k=0.95");
    plot.add(boost::bind(f2, 1, _1), -5, 5, "k=1");
    plot.plot("Jacobi Elliptic sc", "jacobi_sc.svg", "k", "jacobi_sc(k, u)");

    f2 = boost::math::jacobi_sd;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), -2.5, 2.5, "k=0");
    plot.add(boost::bind(f2, 0.5, _1), -2.5, 2.5, "k=0.5");
    plot.add(boost::bind(f2, 0.75, _1), -2.5, 2.5, "k=0.75");
    plot.add(boost::bind(f2, 0.95, _1), -2.5, 2.5, "k=0.95");
    plot.add(boost::bind(f2, 1, _1), -2.5, 2.5, "k=1");
    plot.plot("Jacobi Elliptic sd", "jacobi_sd.svg", "k", "jacobi_sd(k, u)");

    plot.clear();
    f2 = boost::math::jacobi_theta1;
    plot.add(boost::bind(f2, _1, 0.15), 0.0, boost::math::constants::two_pi<double>(), "\u03B8\u2081");
    f2 = boost::math::jacobi_theta2;
    plot.add(boost::bind(f2, _1, 0.15), 0.0, boost::math::constants::two_pi<double>(), "\u03B8\u2082");
    f2 = boost::math::jacobi_theta3;
    plot.add(boost::bind(f2, _1, 0.15), 0.0, boost::math::constants::two_pi<double>(), "\u03B8\u2083");
    f2 = boost::math::jacobi_theta4;
    plot.add(boost::bind(f2, _1, 0.15), 0.0, boost::math::constants::two_pi<double>(), "\u03B8\u2084");
    plot.plot("Jacobi Theta Functions", "jacobi_theta.svg", "x", "jacobi_theta(x, q=0.15)");

    f2 = boost::math::jacobi_theta1;
    plot.clear();
    plot.add(boost::bind(f2, _1, 0.05), 0.0, boost::math::constants::two_pi<double>(), "q=0.05");
    plot.add(boost::bind(f2, _1, 0.5), 0.0, boost::math::constants::two_pi<double>(), "q=0.5");
    plot.add(boost::bind(f2, _1, 0.7), 0.0, boost::math::constants::two_pi<double>(), "q=0.7");
    plot.add(boost::bind(f2, _1, 0.9), 0.0, boost::math::constants::two_pi<double>(), "q=0.9");
    plot.plot("Jacobi Theta Function \u03B8\u2081", "jacobi_theta1.svg", "x", "jacobi_theta1(x, q)");

    f2 = boost::math::jacobi_theta2;
    plot.clear();
    plot.add(boost::bind(f2, _1, 0.05), 0.0, boost::math::constants::two_pi<double>(), "q=0.05");
    plot.add(boost::bind(f2, _1, 0.5), 0.0, boost::math::constants::two_pi<double>(), "q=0.5");
    plot.add(boost::bind(f2, _1, 0.7), 0.0, boost::math::constants::two_pi<double>(), "q=0.7");
    plot.add(boost::bind(f2, _1, 0.9), 0.0, boost::math::constants::two_pi<double>(), "q=0.9");
    plot.plot("Jacobi Theta Function \u03B8\u2082", "jacobi_theta2.svg", "x", "jacobi_theta2(x, q)");

    f2 = boost::math::jacobi_theta3;
    plot.clear();
    plot.add(boost::bind(f2, _1, 0.05), 0.0, boost::math::constants::two_pi<double>(), "q=0.05");
    plot.add(boost::bind(f2, _1, 0.5), 0.0, boost::math::constants::two_pi<double>(), "q=0.5");
    plot.add(boost::bind(f2, _1, 0.7), 0.0, boost::math::constants::two_pi<double>(), "q=0.7");
    plot.add(boost::bind(f2, _1, 0.9), 0.0, boost::math::constants::two_pi<double>(), "q=0.9");
    plot.plot("Jacobi Theta Function \u03B8\u2083", "jacobi_theta3.svg", "x", "jacobi_theta3(x, q)");

    f2 = boost::math::jacobi_theta4;
    plot.clear();
    plot.add(boost::bind(f2, _1, 0.05), 0.0, boost::math::constants::two_pi<double>(), "q=0.05");
    plot.add(boost::bind(f2, _1, 0.5), 0.0, boost::math::constants::two_pi<double>(), "q=0.5");
    plot.add(boost::bind(f2, _1, 0.7), 0.0, boost::math::constants::two_pi<double>(), "q=0.7");
    plot.add(boost::bind(f2, _1, 0.9), 0.0, boost::math::constants::two_pi<double>(), "q=0.9");
    plot.plot("Jacobi Theta Function \u03B8\u2084", "jacobi_theta4.svg", "x", "jacobi_theta4(x, q)");

    f = boost::math::airy_ai;
    plot.clear();
    plot.add(f, -20, 20, "");
    plot.plot("Ai", "airy_ai.svg", "z", "airy_ai(z)");

    f = boost::math::airy_bi;
    plot.clear();
    plot.add(f, -20, 3, "");
    plot.plot("Bi", "airy_bi.svg", "z", "airy_bi(z)");

    f = boost::math::airy_ai_prime;
    plot.clear();
    plot.add(f, -20, 20, "");
    plot.plot("Ai'", "airy_aip.svg", "z", "airy_ai_prime(z)");

    f = boost::math::airy_bi_prime;
    plot.clear();
    plot.add(f, -20, 3, "");
    plot.plot("Bi'", "airy_bip.svg", "z", "airy_bi_prime(z)");

    f = boost::math::trigamma;
    max_val = 30;
    plot.clear();
    plot.add(f, find_end_point(f, 0.1, max_val, false), 5, "");
    plot.add(f, find_end_point(f, 0.1, max_val, false, -1), find_end_point(f, -0.1, max_val, true), "");
    plot.add(f, find_end_point(f, 0.1, max_val, false, -2), find_end_point(f, -0.1, max_val, true, -1), "");
    plot.add(f, find_end_point(f, 0.1, max_val, false, -3), find_end_point(f, -0.1, max_val, true, -2), "");
    plot.add(f, find_end_point(f, 0.1, max_val, false, -4), find_end_point(f, -0.1, max_val, true, -3), "");
    plot.add(f, find_end_point(f, 0.1, max_val, false, -5), find_end_point(f, -0.1, max_val, true, -4), "");
    plot.plot("Trigamma", "trigamma.svg", "x", "trigamma(x)");

    f2i = boost::math::polygamma;
    max_val = -50;
    plot.clear();
    plot.add(boost::bind(f2i, 2, _1), find_end_point(boost::bind(f2i, 2, _1), 0.1, max_val, true), 5, "");
    plot.add(boost::bind(f2i, 2, _1), find_end_point(boost::bind(f2i, 2, _1), 0.1, max_val, true, -1), find_end_point(boost::bind(f2i, 2, _1), -0.1, -max_val, true), "");
    plot.add(boost::bind(f2i, 2, _1), find_end_point(boost::bind(f2i, 2, _1), 0.1, max_val, true, -2), find_end_point(boost::bind(f2i, 2, _1), -0.1, -max_val, true, -1), "");
    plot.add(boost::bind(f2i, 2, _1), find_end_point(boost::bind(f2i, 2, _1), 0.1, max_val, true, -3), find_end_point(boost::bind(f2i, 2, _1), -0.1, -max_val, true, -2), "");
    plot.add(boost::bind(f2i, 2, _1), find_end_point(boost::bind(f2i, 2, _1), 0.1, max_val, true, -4), find_end_point(boost::bind(f2i, 2, _1), -0.1, -max_val, true, -3), "");
    plot.add(boost::bind(f2i, 2, _1), find_end_point(boost::bind(f2i, 2, _1), 0.1, max_val, true, -5), find_end_point(boost::bind(f2i, 2, _1), -0.1, -max_val, true, -4), "");
    plot.add(boost::bind(f2i, 2, _1), find_end_point(boost::bind(f2i, 2, _1), 0.1, max_val, true, -6), find_end_point(boost::bind(f2i, 2, _1), -0.1, -max_val, true, -5), "");
    plot.plot("Polygamma", "polygamma2.svg", "x", "polygamma(2, x)");

    max_val = 800;
    plot.clear();
    plot.add(boost::bind(f2i, 3, _1), find_end_point(boost::bind(f2i, 3, _1), 0.1, max_val, false), 5, "");
    plot.add(boost::bind(f2i, 3, _1), find_end_point(boost::bind(f2i, 3, _1), 0.1, max_val, false, -1), find_end_point(boost::bind(f2i, 3, _1), -0.1, max_val, true), "");
    plot.add(boost::bind(f2i, 3, _1), find_end_point(boost::bind(f2i, 3, _1), 0.1, max_val, false, -2), find_end_point(boost::bind(f2i, 3, _1), -0.1, max_val, true, -1), "");
    plot.add(boost::bind(f2i, 3, _1), find_end_point(boost::bind(f2i, 3, _1), 0.1, max_val, false, -3), find_end_point(boost::bind(f2i, 3, _1), -0.1, max_val, true, -2), "");
    plot.add(boost::bind(f2i, 3, _1), find_end_point(boost::bind(f2i, 3, _1), 0.1, max_val, false, -4), find_end_point(boost::bind(f2i, 3, _1), -0.1, max_val, true, -3), "");
    plot.add(boost::bind(f2i, 3, _1), find_end_point(boost::bind(f2i, 3, _1), 0.1, max_val, false, -5), find_end_point(boost::bind(f2i, 3, _1), -0.1, max_val, true, -4), "");
    plot.add(boost::bind(f2i, 3, _1), find_end_point(boost::bind(f2i, 3, _1), 0.1, max_val, false, -6), find_end_point(boost::bind(f2i, 3, _1), -0.1, max_val, true, -5), "");
    plot.plot("Polygamma", "polygamma3.svg", "x", "polygamma(3, x)");

  }
  catch (const std::exception& ex)
  {
    std::cout << ex.what() << std::endl;
  }
   return 0;
}


//=======================================================================
// Copyright 1997, 1998, 1999, 2000 University of Notre Dame.
// Authors: Andrew Lumsdaine, Lie-Quan Lee, Jeremy G. Siek
//
// Distributed under 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)
//=======================================================================
#include <boost/config.hpp>
#include <assert.h>
#include <iostream>

#include <vector>
#include <algorithm>
#include <utility>

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/depth_first_search.hpp>
#include <boost/graph/visitors.hpp>

/*
  This calculates the discover finishing time.

  Sample Output

  Tree edge: 0 --> 2
  Tree edge: 2 --> 1
  Back edge: 1 --> 1
  Finish edge: 1 --> 1
  Tree edge: 1 --> 3
  Back edge: 3 --> 1
  Finish edge: 3 --> 1
  Tree edge: 3 --> 4
  Back edge: 4 --> 0
  Finish edge: 4 --> 0
  Back edge: 4 --> 1
  Finish edge: 4 --> 1
  Forward or cross edge: 2 --> 3
  Finish edge: 2 --> 3
  Finish edge: 0 --> 2
  1 10
  3 8
  2 9
  4 7
  5 6

 */

using namespace boost;
using namespace std;

template <class VisitorList>
struct edge_categorizer : public dfs_visitor<VisitorList> {
  typedef dfs_visitor<VisitorList> Base;

  edge_categorizer(const VisitorList& v = null_visitor()) : Base(v) { }

  template <class Edge, class Graph>
  void tree_edge(Edge e, Graph& G) {
    cout << "Tree edge: " << source(e, G) <<
      " --> " <<  target(e, G) << endl;
    Base::tree_edge(e, G);
  }
  template <class Edge, class Graph>
  void back_edge(Edge e, Graph& G) {
    cout << "Back edge: " << source(e, G)
         << " --> " <<  target(e, G) << endl;
    Base::back_edge(e, G);
  }
  template <class Edge, class Graph>
  void forward_or_cross_edge(Edge e, Graph& G) {
    cout << "Forward or cross edge: " << source(e, G)
         << " --> " <<  target(e, G) << endl;
    Base::forward_or_cross_edge(e, G);
  }
  template <class Edge, class Graph>
  void finish_edge(Edge e, Graph& G) {
    cout << "Finish edge: " << source(e, G) <<
      " --> " <<  target(e, G) << endl;
    Base::finish_edge(e, G);
  }
};
template <class VisitorList>
edge_categorizer<VisitorList>
categorize_edges(const VisitorList& v) {
  return edge_categorizer<VisitorList>(v);
}

int
main(int , char* [])
{

  using namespace boost;

  typedef adjacency_list<> Graph;

  Graph G(5);
  add_edge(0, 2, G);
  add_edge(1, 1, G);
  add_edge(1, 3, G);
  add_edge(2, 1, G);
  add_edge(2, 3, G);
  add_edge(3, 1, G);
  add_edge(3, 4, G);
  add_edge(4, 0, G);
  add_edge(4, 1, G);

  typedef graph_traits<Graph>::vertices_size_type size_type;

  std::vector<size_type> d(num_vertices(G));
  std::vector<size_type> f(num_vertices(G));
  int t = 0;
  depth_first_search(G, visitor(categorize_edges(
                     make_pair(stamp_times(&d[0], t, on_discover_vertex()),
                               stamp_times(&f[0], t, on_finish_vertex())))));

  std::vector<size_type>::iterator i, j;
  for (i = d.begin(), j = f.begin(); i != d.end(); ++i, ++j)
    cout << *i << " " << *j << endl;

  return 0;
}


/*
@copyright Louis Dionne 2014
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
 */

#include <boost/hana/foldable/mcd.hpp>

#include <boost/hana/detail/assert.hpp>
#include <boost/hana/detail/constexpr.hpp>
#include <boost/hana/detail/minimal/foldable.hpp>
#include <boost/hana/integral.hpp>

#include <tuple>
using namespace boost::hana;

BOOST_HANA_CONSTEXPR_LAMBDA auto tup = [](auto ...xs) {
    return std::make_tuple(xs...);
};

BOOST_HANA_CONSTEXPR_LAMBDA auto f = [](auto x, auto xs) {
    return std::tuple_cat(tup(x), xs);
};

template <typename mcd>
void test() {
    constexpr auto foldable = detail::minimal::foldable<mcd>;

    BOOST_HANA_CONSTEXPR_ASSERT(foldr1(foldable(tup(1)), f) == tup(1));
    BOOST_HANA_CONSTEXPR_ASSERT(foldr1(foldable(1, tup('2')), f) == f(1, tup('2')));
    BOOST_HANA_CONSTEXPR_ASSERT(foldr1(foldable(1, '2', tup(3.3)), f) == f(1, f('2', tup(3.3))));
    BOOST_HANA_CONSTEXPR_ASSERT(foldr1(foldable(1, '2', 3.3, tup(4.4f)), f) == f(1, f('2', f(3.3, tup(4.4f)))));

    BOOST_HANA_CONSTEXPR_ASSERT(foldr1(foldable(tup(int_<1>)), f) == tup(int_<1>));
    BOOST_HANA_CONSTEXPR_ASSERT(foldr1(foldable(int_<1>, tup(int_<2>)), f) == f(int_<1>, tup(int_<2>)));
    BOOST_HANA_CONSTEXPR_ASSERT(foldr1(foldable(int_<1>, int_<2>, tup(int_<3>)), f) == f(int_<1>, f(int_<2>, tup(int_<3>))));
    BOOST_HANA_CONSTEXPR_ASSERT(foldr1(foldable(int_<1>, int_<2>, int_<3>, tup(int_<4>)), f) == f(int_<1>, f(int_<2>, f(int_<3>, tup(int_<4>)))));
}

int main() {
    test<Foldable::mcd>();
    test<Foldable::unpack_mcd>();
}


/** Copyright (c) 2011, Edgar Solomonik, all rights reserved.
  * \addtogroup benchmarks
  * @{
  * \addtogroup bench_redistributions
  * @{
  * \brief Benchmarks arbitrary NS redistribution
  */

//#include <boost/math/distributions/normal.hpp>
//
//boost::math::normal dist(0.0, 1.0);
//
//// 95% of distribution is below q:
//double q = quantile(dist, 0.95);

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <math.h>
#include <assert.h>
#include <algorithm>
#include <ctf.hpp>
#include <iostream>
#include <fstream>
#include "../src/shared/util.h"

using namespace CTF;

void bench_redistribution(int          niter,
                          World &      dw,
                          int          order,
                          int const *  lens,
                          char const * idx,
                          int          prl1_ord,
                          int const *  prl1_lens,
                          char const * prl1_idx,
                          int          prl2_ord,
                          int const *  prl2_lens,
                          char const * prl2_idx,
                          int          blk1_ord,
                          int const *  blk1_lens,
                          char const * blk1_idx,
                          int          blk2_ord,
                          int const *  blk2_lens,
                          char const * blk2_idx){

  int rank, num_pes;

  MPI_Comm_rank(MPI_COMM_WORLD, &rank);
  MPI_Comm_size(MPI_COMM_WORLD, &num_pes);

  int sym[order];
  int64_t N = 1;
  for (int i=0; i<order; i++){
    N*=lens[i];
    sym[i] = NS;
  }

  Partition prl1(prl1_ord, prl1_lens);
  Partition prl2(prl2_ord, prl2_lens);
  Partition blk1(blk1_ord, blk1_lens);
  Partition blk2(blk2_ord, blk2_lens);

  Tensor<> A(order, lens, sym, dw, idx, prl1[prl1_idx], blk1[blk1_idx], "A", 1);

  A.fill_random(-.5, .5);

  double t = 0.0;
  double t_min;
  double t_max;

  double btime;
  MPI_Barrier(MPI_COMM_WORLD);
  btime = MPI_Wtime();
  MPI_Barrier(MPI_COMM_WORLD);
  btime -= MPI_Wtime();

  double * data_ref = A.read(idx, prl2[prl2_idx], blk2[blk2_idx]);

#ifdef USE_FOMPI
  int N_DGTOG = 6;
#else
  int N_DGTOG = 5;
#endif

  std::ofstream f;
  if (rank == 0){
    char fname[1000];
    sprintf(fname, "bench_redist.p%d.o%d.N%d.pst-%s.vst-%s.ped-%s.ved-%s.dat", num_pes, order, lens[0], prl1_idx, blk1_idx, prl2_idx, blk2_idx);
    f.open(fname);
  }

  std::vector<double> times[N_DGTOG];
  for (int D=0; D<N_DGTOG; D++){
    DGTOG_SWITCH = D;
    char const * str_name;
    switch (D){
      case 0:
        str_name = "NAIVE";
        break;
      case 1:
        str_name = "ROR";
        break;
      case 2:
        str_name = "ROR_ISR";
        break;
      case 3:
        str_name = "ROR_PUT";
        break;
      case 4:
        str_name = "ROR_ISR_ANY";
        break;
      case 5:
        str_name = "ROR_PUT_ANY";
        break;
    }
    if (rank == 0) printf("Testing redistribution via kernel %s\n", str_name);

    double * data = A.read(idx, prl2[prl2_idx], blk2[blk2_idx]);
    int pass = 1;
    for (int64_t j=0; j<N/num_pes; j++){
      if (data[j] != data_ref[j]){
        pass = 0;
        printf("[%d] Incorrect! data[%ld] = %lf instead of %lf\n",rank,j, data[j],data_ref[j]);
      }
    }
    free(data);
    MPI_Allreduce(MPI_IN_PLACE, &pass, 1, MPI_INT, MPI_MIN, MPI_COMM_WORLD);
    if (pass){
      if (rank == 0) printf("Correctness test passed.\n");
      MPI_Barrier(MPI_COMM_WORLD);
      Timer_epoch te(str_name);
      te.begin();
      for (int i=0; i<niter; i++){
        double t_st = MPI_Wtime();
        double * data = A.read(idx, prl2[prl2_idx], blk2[blk2_idx]);
        MPI_Barrier(MPI_COMM_WORLD);
        times[D].push_back(MPI_Wtime() - t_st - btime);
        free(data);
      }
      te.end();
      std::sort(&times[D][0], &times[D][0]+niter);
      if (rank == 0){
        printf("Performed %d redistributions via kernel %s sec/iter: median = %lf (median effective end-to-end bandwidth, N/(t*p) = %lf GB/s), range = [%lf, %lf]\n",
                niter, str_name, times[D][niter/2], 1.E-9*N*sizeof(double)/(num_pes*times[D][niter/2]), times[D][0], times[D][niter-1]);
        f << str_name << " ";
        for (int i=0; i<niter; i++){
          f << times[D][i] << " ";
        }
        f << "\n";
      }
    }
  }
  if (rank == 0){
    printf("Data line kernel * [min, median max]:\n");
    for (int D=0; D<N_DGTOG; D++){
      printf("%lf %lf %lf ", times[D][0], times[D][niter/2], times[D][niter-1]);
    }
    printf("\n");
  }
  if (rank == 0){
    f.close();
  }

  free(data_ref);
/*  if (rank == 0)
    printf("Performed %d redistributions in %lf time/iter %lf mem GB/sec\n",
            niter, (end_time-st_time)/niter, (2*N*1.E-9/((end_time-st_time)/niter))/num_pes);*/
}

char* getCmdOption(char ** begin,
                   char ** end,
                   const   std::string & option){
  char ** itr = std::find(begin, end, option);
  if (itr != end && ++itr != end){
    return *itr;
  }
  return 0;
}

int main(int argc, char ** argv){
  int rank, np, niter, n, phase;
  int const in_num = argc;
  char ** input_str = argv;
  char const * idx;
  char const * prl1_idx;
  char const * prl2_idx;
  char const * blk1_idx;
  char const * blk2_idx;
  int64_t prl1, prl2, blk1, blk2;
  int order;
  int prl1_ord;
  int prl2_ord;
  int blk1_ord;
  int blk2_ord;
  int * lens;
  int * prl1_lens;
  int * prl2_lens;
  int * blk1_lens;
  int * blk2_lens;

  MPI_Init(&argc, &argv);
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);
  MPI_Comm_size(MPI_COMM_WORLD, &np);

  if (getCmdOption(input_str, input_str+in_num, "-n")){
    n = atoi(getCmdOption(input_str, input_str+in_num, "-n"));
    if (n < 0) n = 4;
  } else n = 4;

  if (getCmdOption(input_str, input_str+in_num, "-phase")){
    phase = atoi(getCmdOption(input_str, input_str+in_num, "-phase"));
    if (phase < 0) phase = 10;
  } else phase = 10;

  if (getCmdOption(input_str, input_str+in_num, "-prl1")){
    prl1 = atoi(getCmdOption(input_str, input_str+in_num, "-prl1"));
    if (prl1 < 0) prl1 = np;
  } else prl1 = np;

  if (getCmdOption(input_str, input_str+in_num, "-prl2")){
    prl2 = atoi(getCmdOption(input_str, input_str+in_num, "-prl2"));
    if (prl2 < 0) prl2 = np;
  } else prl2 = np;

  if (getCmdOption(input_str, input_str+in_num, "-blk1")){
    blk1 = atoi(getCmdOption(input_str, input_str+in_num, "-blk1"));
    if (blk1 < 0) blk1 = np;
  } else blk1 = np;

  if (getCmdOption(input_str, input_str+in_num, "-blk2")){
    blk2 = atoi(getCmdOption(input_str, input_str+in_num, "-blk2"));
    if (blk2 < 0) blk2 = np;
  } else blk2 = np;

  if (getCmdOption(input_str, input_str+in_num, "-niter")){
    niter = atoi(getCmdOption(input_str, input_str+in_num, "-niter"));
    if (niter < 0) niter = 3;
  } else niter = 3;

  if (getCmdOption(input_str, input_str+in_num, "-idx")){
    idx = getCmdOption(input_str, input_str+in_num, "-idx");
  } else idx = "ij";
  if (getCmdOption(input_str, input_str+in_num, "-prl1_idx")){
    prl1_idx = getCmdOption(input_str, input_str+in_num, "-prl1_idx");
  } else prl1_idx = "i";
  if (getCmdOption(input_str, input_str+in_num, "-prl2_idx")){
    prl2_idx = getCmdOption(input_str, input_str+in_num, "-prl2_idx");
  } else prl2_idx = "j";
  if (getCmdOption(input_str, input_str+in_num, "-blk1_idx")){
    blk1_idx = getCmdOption(input_str, input_str+in_num, "-blk1_idx");
  } else blk1_idx = "";
  if (getCmdOption(input_str, input_str+in_num, "-blk2_idx")){
    blk2_idx = getCmdOption(input_str, input_str+in_num, "-blk2_idx");
  } else blk2_idx = "";

  order = strlen(idx);
  prl1_ord = strlen(prl1_idx);
  prl2_ord = strlen(prl2_idx);
  blk1_ord = strlen(blk1_idx);
  blk2_ord = strlen(blk2_idx);

  if (rank==0){
    printf("Redistributing order %d tensor with all dims %d and idx %s from order %d proc grid with dims %ld and idx %s, to order %d proc grid with dims %ld and idx %s\n", order, n, idx, prl1_ord, prl1, prl1_idx, prl2_ord, prl2, prl2_idx);
    printf("Initial blocking order %d dims %ld and idx %s, to final blocking order %d dims %ld and idx %s\n", blk1_ord, blk1, blk1_idx, blk2_ord, blk2, blk2_idx);
  }

  lens = (int*)malloc(order*sizeof(int));
  for (int i=0; i<order; i++){
    lens[i] = n;
  }
  prl1_lens = (int*)malloc(prl1_ord*sizeof(int));
  for (int i=0; i<prl1_ord; i++){
    prl1_lens[prl1_ord-i-1] = prl1%phase;
    prl1 = prl1/phase;
  }
  if (rank == 0){
    printf("start topology:");
    for (int i=0; i<prl1_ord; i++){
      printf(" %d", prl1_lens[i]);
    }
    printf("\n");
  }
  prl2_lens = (int*)malloc(prl2_ord*sizeof(int));
  for (int i=0; i<prl2_ord; i++){
    prl2_lens[prl2_ord-i-1] = prl2%phase;
    prl2 = prl2/phase;
  }
  if (rank == 0){
    printf("end topology:");
    for (int i=0; i<prl2_ord; i++){
      printf(" %d", prl2_lens[i]);
    }
    printf("\n");
  }

  blk1_lens = (int*)malloc(blk1_ord*sizeof(int));
  for (int i=0; i<blk1_ord; i++){
    blk1_lens[blk1_ord-i-1] = blk1%phase;
    blk1 = blk1/phase;
  }
  if (rank == 0){
    printf("start blocking:");
    for (int i=0; i<blk1_ord; i++){
      printf(" %d", blk1_lens[i]);
    }
    printf("\n");
  }

  blk2_lens = (int*)malloc(blk2_ord*sizeof(int));
  for (int i=0; i<blk2_ord; i++){
    blk2_lens[blk2_ord-i-1] = blk2%phase;
    blk2 = blk2/phase;
  }
  if (rank == 0){
    printf("end blocking:");
    for (int i=0; i<blk2_ord; i++){
      printf(" %d", blk2_lens[i]);
    }
    printf("\n");
  }

  {
    CTF_World dw(argc, argv);
    bench_redistribution(niter, dw, order, lens, idx,
                         prl1_ord, prl1_lens, prl1_idx,
                         prl2_ord, prl2_lens, prl2_idx,
                         blk1_ord, blk1_lens, blk1_idx,
                         blk2_ord, blk2_lens, blk2_idx);
  }

  MPI_Finalize();
  return 0;
}
/**
 * @}
 * @}
 */


//---------------------------------------------------------------------------//
//!
//! \file   tstTabularDistribution.cpp
//! \author Alex Robinson
//! \brief  Tabular distribution unit tests.
//!
//---------------------------------------------------------------------------//

// Std Lib Includes
#include <iostream>

// Boost Includes
#include <boost/units/systems/si.hpp>
#include <boost/units/systems/cgs.hpp>
#include <boost/units/io.hpp>

// Trilinos Includes
#include <Teuchos_UnitTestHarness.hpp>
#include <Teuchos_RCP.hpp>
#include <Teuchos_Array.hpp>
#include <Teuchos_ParameterList.hpp>
#include <Teuchos_XMLParameterListCoreHelpers.hpp>
#include <Teuchos_VerboseObject.hpp>

// FRENSIE Includes
#include "Utility_UnitTestHarnessExtensions.hpp"
#include "Utility_TabularOneDDistribution.hpp"
#include "Utility_TabularDistribution.hpp"
#include "Utility_RandomNumberGenerator.hpp"
#include "Utility_PhysicalConstants.hpp"
#include "Utility_UnitTraits.hpp"
#include "Utility_QuantityTraits.hpp"
#include "Utility_ElectronVoltUnit.hpp"

using boost::units::quantity;
using namespace Utility::Units;
namespace si = boost::units::si;
namespace cgs = boost::units::cgs;

//---------------------------------------------------------------------------//
// Testing Variables
//---------------------------------------------------------------------------//

Teuchos::RCP<Teuchos::ParameterList> test_dists_list;

Teuchos::RCP<Utility::OneDDistribution> distribution;
Teuchos::RCP<Utility::TabularOneDDistribution> tab_distribution;

Teuchos::RCP<Utility::UnitAwareOneDDistribution<MegaElectronVolt,si::amount> >
  unit_aware_distribution;
Teuchos::RCP<Utility::UnitAwareTabularOneDDistribution<MegaElectronVolt,si::amount> >
unit_aware_tab_distribution;

//---------------------------------------------------------------------------//
// Instantiation Macros.
//---------------------------------------------------------------------------//
#define UNIT_TEST_INSTANTIATION( type, name )				\
  typedef Utility::LinLin LinLin;					\
  typedef Utility::LogLin LogLin;					\
  typedef Utility::LinLog LinLog;					\
  typedef Utility::LogLog LogLog;					\
  TEUCHOS_UNIT_TEST_TEMPLATE_1_INSTANT( type, name, LinLin )		\
  TEUCHOS_UNIT_TEST_TEMPLATE_1_INSTANT( type, name, LogLin )		\
  TEUCHOS_UNIT_TEST_TEMPLATE_1_INSTANT( type, name, LinLog )		\
  TEUCHOS_UNIT_TEST_TEMPLATE_1_INSTANT( type, name, LogLog )

//---------------------------------------------------------------------------//
// Testing Functions.
//---------------------------------------------------------------------------//
// Initialize the distribution
template<typename InterpolationPolicy, typename BaseDistribution>
void initialize( Teuchos::RCP<BaseDistribution>& dist )
{
  // Use the basic constructor
  Teuchos::Array<typename BaseDistribution::IndepQuantity>
    independent_values( 4 );
  Utility::setQuantity( independent_values[0], 1e-3 );
  Utility::setQuantity( independent_values[1], 1e-2 );
  Utility::setQuantity( independent_values[2], 1e-1 );
  Utility::setQuantity( independent_values[3], 1.0 );

  Teuchos::Array<typename BaseDistribution::DepQuantity> dependent_values( 4 );
  Utility::setQuantity( dependent_values[0], 1e2 );
  Utility::setQuantity( dependent_values[1], 1e1 );
  Utility::setQuantity( dependent_values[2], 1.0 );
  Utility::setQuantity( dependent_values[3], 1e-1 );

  dist.reset(new Utility::UnitAwareTabularDistribution<InterpolationPolicy,typename BaseDistribution::IndepUnit, typename BaseDistribution::DepUnit>(
							  independent_values,
							  dependent_values ) );
}

//---------------------------------------------------------------------------//
// Tests.
//---------------------------------------------------------------------------//
// Check that the distribution can be evaluated
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( TabularDistribution,
				   evaluate,
				   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( distribution );

  TEST_EQUALITY_CONST( distribution->evaluate( 0.0 ), 0.0 );
  TEST_EQUALITY_CONST( distribution->evaluate( 1e-3 ), 1e2 );
  TEST_EQUALITY_CONST( distribution->evaluate( 1e-2 ), 1e1 );
  TEST_EQUALITY_CONST( distribution->evaluate( 1e-1 ), 1.0 );
  TEST_EQUALITY_CONST( distribution->evaluate( 1.0 ), 1e-1 );
  TEST_EQUALITY_CONST( distribution->evaluate( 2.0 ), 0.0 );
}

UNIT_TEST_INSTANTIATION( TabularDistribution, evaluate );

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be evaluated
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( UnitAwareTabularDistribution,
				   evaluate,
				   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( unit_aware_distribution );

  TEST_EQUALITY_CONST( unit_aware_distribution->evaluate( 0.0*MeV ),
		       0.0*si::mole );
  TEST_EQUALITY_CONST( unit_aware_distribution->evaluate( 1e-3*MeV ),
		       1e2*si::mole );
  TEST_EQUALITY_CONST( unit_aware_distribution->evaluate( 1e-2*MeV ),
		       1e1*si::mole );
  TEST_EQUALITY_CONST( unit_aware_distribution->evaluate( 1e-1*MeV ),
		       1.0*si::mole );
  TEST_EQUALITY_CONST( unit_aware_distribution->evaluate( 1.0*MeV ),
		       1e-1*si::mole );
  TEST_EQUALITY_CONST( unit_aware_distribution->evaluate( 2.0*MeV ),
		       0.0*si::mole );
}

UNIT_TEST_INSTANTIATION( UnitAwareTabularDistribution, evaluate );

//---------------------------------------------------------------------------//
// Check that the PDF can be evaluated
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( TabularDistribution,
				   evaluatePDF,
				   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( distribution );

  TEST_EQUALITY_CONST( distribution->evaluatePDF( 0.0 ), 0.0 );
  TEST_FLOATING_EQUALITY( distribution->evaluatePDF( 1e-3 ),
			  67.340006734,
			  1e-6 );
  TEST_FLOATING_EQUALITY( distribution->evaluatePDF( 1e-2 ),
			  6.7340006734,
			  1e-6 );
  TEST_FLOATING_EQUALITY( distribution->evaluatePDF( 1e-1 ),
			  0.67340006734,
			  1e-6 );
  TEST_FLOATING_EQUALITY( distribution->evaluatePDF( 1.0 ),
			  0.067340006734,
			  1e-6 );
  TEST_EQUALITY_CONST( distribution->evaluatePDF( 2.0 ), 0.0 );
}

UNIT_TEST_INSTANTIATION( TabularDistribution, evaluatePDF );

//---------------------------------------------------------------------------//
// Check that the unit-aware PDF can be evaluated
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( UnitAwareTabularDistribution,
				   evaluatePDF,
				   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( unit_aware_distribution );

  TEST_EQUALITY_CONST( unit_aware_distribution->evaluatePDF( 0.0*MeV ),
		       0.0/MeV );
  UTILITY_TEST_FLOATING_EQUALITY(
			     unit_aware_distribution->evaluatePDF( 1e-3*MeV ),
			     67.340006734/MeV,
			     1e-6 );
  UTILITY_TEST_FLOATING_EQUALITY(
			     unit_aware_distribution->evaluatePDF( 1e-2*MeV ),
			     6.7340006734/MeV,
			     1e-6 );
  UTILITY_TEST_FLOATING_EQUALITY(
			     unit_aware_distribution->evaluatePDF( 1e-1*MeV ),
			     0.67340006734/MeV,
			     1e-6 );
  UTILITY_TEST_FLOATING_EQUALITY(
			      unit_aware_distribution->evaluatePDF( 1.0*MeV ),
			      0.067340006734/MeV,
			      1e-6 );
  TEST_EQUALITY_CONST( unit_aware_distribution->evaluatePDF( 2.0*MeV ),
		       0.0/MeV );
}

UNIT_TEST_INSTANTIATION( UnitAwareTabularDistribution, evaluatePDF );

//---------------------------------------------------------------------------//
// Check that the CDF can be evaluated
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( TabularDistribution,
				   evaluateCDF,
				   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( tab_distribution );

  TEST_EQUALITY_CONST( tab_distribution->evaluateCDF( 0.0 ), 0.0 );
  TEST_FLOATING_EQUALITY( tab_distribution->evaluateCDF( 1e-3 ),
			  0.0000000000,
			  1e-10 );
  TEST_FLOATING_EQUALITY( tab_distribution->evaluateCDF( 1e-2 ),
			  0.33333333333,
			  1e-10 );
  TEST_FLOATING_EQUALITY( tab_distribution->evaluateCDF( 1e-1 ),
			  0.66666666667,
			  1e-10 );
  TEST_FLOATING_EQUALITY( tab_distribution->evaluateCDF( 1.0 ),
			  1.0000000000,
			  1e-10 );
  TEST_EQUALITY_CONST( tab_distribution->evaluateCDF( 2.0 ), 1.0 );
}

UNIT_TEST_INSTANTIATION( TabularDistribution, evaluateCDF );

//---------------------------------------------------------------------------//
// Check that the unit-aware CDF can be evaluated
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( UnitAwareTabularDistribution,
				   evaluateCDF,
				   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( unit_aware_tab_distribution );

  TEST_EQUALITY_CONST( unit_aware_tab_distribution->evaluateCDF( 0.0*MeV ),
		       0.0 );
  TEST_FLOATING_EQUALITY( unit_aware_tab_distribution->evaluateCDF( 1e-3*MeV ),
			  0.0000000000,
			  1e-10 );
  TEST_FLOATING_EQUALITY( unit_aware_tab_distribution->evaluateCDF( 1e-2*MeV ),
			  0.33333333333,
			  1e-10 );
  TEST_FLOATING_EQUALITY( unit_aware_tab_distribution->evaluateCDF( 1e-1*MeV ),
			  0.66666666667,
			  1e-10 );
  TEST_FLOATING_EQUALITY( unit_aware_tab_distribution->evaluateCDF( 1.0*MeV ),
			  1.0000000000,
			  1e-10 );
  TEST_EQUALITY_CONST( unit_aware_tab_distribution->evaluateCDF( 2.0*MeV ),
		       1.0 );
}

UNIT_TEST_INSTANTIATION( UnitAwareTabularDistribution, evaluateCDF );

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( TabularDistribution,
                                   sample,
                                   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( distribution );

  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  double sample = distribution->sample();
  TEST_EQUALITY_CONST( sample, 1e-3 );

  sample = distribution->sample();
  TEST_FLOATING_EQUALITY( sample, 1.0, 1e-12 );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = distribution->sample();
  TEST_COMPARE( sample, >=, 1e-3 );
  TEST_COMPARE( sample, <=, 1.0 );
}

UNIT_TEST_INSTANTIATION( TabularDistribution, sample );

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( UnitAwareTabularDistribution,
                                   sample,
                                   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( unit_aware_distribution );

  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  quantity<MegaElectronVolt> sample = unit_aware_distribution->sample();
  TEST_EQUALITY_CONST( sample, 1e-3*MeV );

  sample = unit_aware_distribution->sample();
  UTILITY_TEST_FLOATING_EQUALITY( sample, 1.0*MeV, 1e-12 );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = unit_aware_distribution->sample();
  TEST_COMPARE( sample, >=, 1e-3*MeV );
  TEST_COMPARE( sample, <=, 1.0*MeV );
}

UNIT_TEST_INSTANTIATION( UnitAwareTabularDistribution, sample );

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( TabularDistribution,
                                   sampleAndRecordTrials,
                                   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( distribution );

  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  unsigned trials = 0;

  double sample = distribution->sampleAndRecordTrials( trials );
  TEST_EQUALITY_CONST( sample, 1e-3 );
  TEST_EQUALITY_CONST( 1.0/trials, 1.0 );

  sample = distribution->sampleAndRecordTrials( trials );
  TEST_FLOATING_EQUALITY( sample, 1.0, 1e-12 );
  TEST_EQUALITY_CONST( 2.0/trials, 1.0 );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = distribution->sampleAndRecordTrials( trials );
  TEST_COMPARE( sample, >=, 1e-3 );
  TEST_COMPARE( sample, <=, 1.0 );
  TEST_EQUALITY_CONST( 3.0/trials, 1.0 );
}

UNIT_TEST_INSTANTIATION( TabularDistribution, sampleAndRecordTrials );

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( UnitAwareTabularDistribution,
                                   sampleAndRecordTrials,
                                   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( unit_aware_distribution );

  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  unsigned trials = 0;

  quantity<MegaElectronVolt> sample =
    unit_aware_distribution->sampleAndRecordTrials( trials );
  TEST_EQUALITY_CONST( sample, 1e-3*MeV );
  TEST_EQUALITY_CONST( 1.0/trials, 1.0 );

  sample = unit_aware_distribution->sampleAndRecordTrials( trials );
  UTILITY_TEST_FLOATING_EQUALITY( sample, 1.0*MeV, 1e-12 );
  TEST_EQUALITY_CONST( 2.0/trials, 1.0 );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = unit_aware_distribution->sampleAndRecordTrials( trials );
  TEST_COMPARE( sample, >=, 1e-3*MeV );
  TEST_COMPARE( sample, <=, 1.0*MeV );
  TEST_EQUALITY_CONST( 3.0/trials, 1.0 );
}

UNIT_TEST_INSTANTIATION( UnitAwareTabularDistribution, sampleAndRecordTrials );

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( TabularDistribution,
                                   sampleAndRecordBinIndex,
                                   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( tab_distribution );

  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  unsigned bin_index;

  double sample = tab_distribution->sampleAndRecordBinIndex( bin_index );
  TEST_EQUALITY_CONST( sample, 1e-3 );
  TEST_EQUALITY_CONST( bin_index, 0u );

  sample = tab_distribution->sampleAndRecordBinIndex( bin_index );
  TEST_FLOATING_EQUALITY( sample, 1.0, 1e-12 );
  TEST_EQUALITY_CONST( bin_index, 2u );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = tab_distribution->sampleAndRecordBinIndex( bin_index );
  TEST_COMPARE( sample, >=, 1e-3 );
  TEST_COMPARE( sample, <=, 1.0 );
}

UNIT_TEST_INSTANTIATION( TabularDistribution, sampleAndRecordBinIndex );

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( UnitAwareTabularDistribution,
                                   sampleAndRecordBinIndex,
                                   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( unit_aware_tab_distribution );

  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  unsigned bin_index;

  quantity<MegaElectronVolt> sample =
    unit_aware_tab_distribution->sampleAndRecordBinIndex( bin_index );
  TEST_EQUALITY_CONST( sample, 1e-3*MeV );
  TEST_EQUALITY_CONST( bin_index, 0u );

  sample = unit_aware_tab_distribution->sampleAndRecordBinIndex( bin_index );
  UTILITY_TEST_FLOATING_EQUALITY( sample, 1.0*MeV, 1e-12 );
  TEST_EQUALITY_CONST( bin_index, 2u );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = unit_aware_tab_distribution->sampleAndRecordBinIndex( bin_index );
  TEST_COMPARE( sample, >=, 1e-3*MeV );
  TEST_COMPARE( sample, <=, 1.0*MeV );
}

UNIT_TEST_INSTANTIATION( UnitAwareTabularDistribution,
			 sampleAndRecordBinIndex );

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( TabularDistribution,
                                   sampleWithRandomNumber,
                                   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( tab_distribution );

  double sample = tab_distribution->sampleWithRandomNumber( 0.0 );
  TEST_EQUALITY_CONST( sample, 1e-3 );

  sample = tab_distribution->sampleWithRandomNumber( 1.0 - 1e-15 );
  TEST_FLOATING_EQUALITY( sample, 1.0, 1e-12 );
}

UNIT_TEST_INSTANTIATION( TabularDistribution, sampleWithRandomNumber );

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( UnitAwareTabularDistribution,
                                   sampleWithRandomNumber,
                                   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( unit_aware_tab_distribution );

  quantity<MegaElectronVolt> sample =
    unit_aware_tab_distribution->sampleWithRandomNumber( 0.0 );
  TEST_EQUALITY_CONST( sample, 1e-3*MeV );

  sample = unit_aware_tab_distribution->sampleWithRandomNumber( 1.0 - 1e-15 );
  UTILITY_TEST_FLOATING_EQUALITY( sample, 1.0*MeV, 1e-12 );
}

UNIT_TEST_INSTANTIATION( UnitAwareTabularDistribution,
			 sampleWithRandomNumber );

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled from a subrange
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( TabularDistribution,
                                   sampleInSubrange,
                                   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( tab_distribution );

  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  double sample = tab_distribution->sampleInSubrange( 1e-1  );
  TEST_EQUALITY_CONST( sample, 1e-3 );

  sample = tab_distribution->sampleInSubrange( 1e-1 );
  TEST_FLOATING_EQUALITY( sample, 1e-1, 1e-12 );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = tab_distribution->sampleInSubrange( 1e-1 );
  TEST_COMPARE( sample, >=, 1e-3 );
  TEST_COMPARE( sample, <=, 1e-1 );
}

UNIT_TEST_INSTANTIATION( TabularDistribution, sampleInSubrange );

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled from a subrange
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( UnitAwareTabularDistribution,
                                   sampleInSubrange,
                                   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( unit_aware_tab_distribution );

  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  quantity<MegaElectronVolt> sample =
    unit_aware_tab_distribution->sampleInSubrange( 1e-1*MeV  );
  TEST_EQUALITY_CONST( sample, 1e-3*MeV );

  sample = unit_aware_tab_distribution->sampleInSubrange( 1e-1*MeV );
  UTILITY_TEST_FLOATING_EQUALITY( sample, 1e-1*MeV, 1e-12 );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = unit_aware_tab_distribution->sampleInSubrange( 1e-1*MeV );
  TEST_COMPARE( sample, >=, 1e-3*MeV );
  TEST_COMPARE( sample, <=, 1e-1*MeV );
}

UNIT_TEST_INSTANTIATION( UnitAwareTabularDistribution, sampleInSubrange );

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled from a subrange
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( TabularDistribution,
                                   sampleWithRandomNumberInSubrange,
                                   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( tab_distribution );

  double sample =
    tab_distribution->sampleWithRandomNumberInSubrange( 0.0, 1e-1  );
  TEST_EQUALITY_CONST( sample, 1e-3 );

  sample = tab_distribution->sampleWithRandomNumberInSubrange( 1.0, 1e-1 );
  TEST_FLOATING_EQUALITY( sample, 1e-1, 1e-12 );
}

UNIT_TEST_INSTANTIATION( TabularDistribution,
			 sampleWithRandomNumberInSubrange );

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled from a subrange
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( UnitAwareTabularDistribution,
                                   sampleWithRandomNumberInSubrange,
                                   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( unit_aware_tab_distribution );

  quantity<MegaElectronVolt> sample =
    unit_aware_tab_distribution->sampleWithRandomNumberInSubrange(
							       0.0, 1e-1*MeV );
  TEST_EQUALITY_CONST( sample, 1e-3*MeV );

  sample = unit_aware_tab_distribution->sampleWithRandomNumberInSubrange(
							       1.0, 1e-1*MeV );
  UTILITY_TEST_FLOATING_EQUALITY( sample, 1e-1*MeV, 1e-12 );
}

UNIT_TEST_INSTANTIATION( UnitAwareTabularDistribution,
			 sampleWithRandomNumberInSubrange );

//---------------------------------------------------------------------------//
// Check that a distribution can be constructed from a cdf
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( TabularDistribution,
				   cdf_constructor,
				   InterpolationPolicy )
{
  Teuchos::Array<double> indep_values( 4 ), cdf_values( 4 );
  indep_values[0] = 1e-3;
  indep_values[1] = 1e-2;
  indep_values[2] = 1e-1;
  indep_values[3] = 1.0;

  cdf_values[0] = 0.0;
  cdf_values[1] = 0.495;
  cdf_values[2] = 0.99;
  cdf_values[3] = 1.485;

  Teuchos::RCP<Utility::TabularOneDDistribution>
    dist_from_cdf( new Utility::TabularDistribution<InterpolationPolicy>(
								indep_values,
								cdf_values,
								true ) );

  TEST_EQUALITY_CONST( dist_from_cdf->evaluate( 0.0 ), 0.0 );
  TEST_FLOATING_EQUALITY( dist_from_cdf->evaluate( 1e-3 ), 55.0, 1e-15 );
  TEST_FLOATING_EQUALITY( dist_from_cdf->evaluate( 1e-2 ), 55.0, 1e-15 );
  TEST_FLOATING_EQUALITY( dist_from_cdf->evaluate( 1e-1 ), 5.5, 1e-15 );
  TEST_FLOATING_EQUALITY( dist_from_cdf->evaluate( 1.0 ), 0.55, 1e-15 );
  TEST_EQUALITY_CONST( dist_from_cdf->evaluate( 2.0 ), 0.0 );

  TEST_EQUALITY_CONST( dist_from_cdf->evaluatePDF( 0.0 ), 0.0 );
  TEST_FLOATING_EQUALITY( dist_from_cdf->evaluatePDF( 1e-3 ),
			  37.03703703703703,
			  1e-15 );
  TEST_FLOATING_EQUALITY( dist_from_cdf->evaluatePDF( 1e-2 ),
			  37.03703703703703,
			  1e-15 );
  TEST_FLOATING_EQUALITY( dist_from_cdf->evaluatePDF( 1e-1 ),
			  3.703703703703703,
			  1e-15 );
  TEST_FLOATING_EQUALITY( dist_from_cdf->evaluatePDF( 1.0 ),
			  0.37037037037037046,
			  1e-15 );
  TEST_EQUALITY_CONST( dist_from_cdf->evaluatePDF( 2.0 ), 0.0 );

  TEST_EQUALITY_CONST( dist_from_cdf->evaluateCDF( 0.0 ), 0.0 );
  TEST_FLOATING_EQUALITY( dist_from_cdf->evaluateCDF( 1e-3 ),
			  0.0000000000,
			  1e-10 );
  TEST_FLOATING_EQUALITY( dist_from_cdf->evaluateCDF( 1e-2 ),
			  0.33333333333,
			  1e-10 );
  TEST_FLOATING_EQUALITY( dist_from_cdf->evaluateCDF( 1e-1 ),
			  0.66666666667,
			  1e-10 );
  TEST_FLOATING_EQUALITY( dist_from_cdf->evaluateCDF( 1.0 ),
			  1.0000000000,
			  1e-10 );
  TEST_EQUALITY_CONST( dist_from_cdf->evaluateCDF( 2.0 ), 1.0 );
}

UNIT_TEST_INSTANTIATION( TabularDistribution, cdf_constructor );

//---------------------------------------------------------------------------//
// Check that a unit-aware distribution can be constructed from a cdf
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( UnitAwareTabularDistribution,
				   cdf_constructor,
				   InterpolationPolicy )
{
  Teuchos::Array<double> indep_values( 4 ), cdf_values( 4 );
  indep_values[0] = 1e-3;
  indep_values[1] = 1e-2;
  indep_values[2] = 1e-1;
  indep_values[3] = 1.0;

  cdf_values[0] = 0.0;
  cdf_values[1] = 0.495;
  cdf_values[2] = 0.99;
  cdf_values[3] = 1.485;

  Teuchos::RCP<Utility::UnitAwareTabularOneDDistribution<MegaElectronVolt,si::amount> >
    dist_from_cdf( new Utility::UnitAwareTabularDistribution<InterpolationPolicy,MegaElectronVolt,si::amount>(
								indep_values,
								cdf_values,
								true ) );

  TEST_EQUALITY_CONST( dist_from_cdf->evaluate( 0.0*MeV ), 0.0*si::mole );
  UTILITY_TEST_FLOATING_EQUALITY( dist_from_cdf->evaluate( 1e-3*MeV ),
				  55.0*si::mole,
				  1e-15 );
  UTILITY_TEST_FLOATING_EQUALITY( dist_from_cdf->evaluate( 1e-2*MeV ),
				  55.0*si::mole,
				  1e-15 );
  UTILITY_TEST_FLOATING_EQUALITY( dist_from_cdf->evaluate( 1e-1*MeV ),
				  5.5*si::mole,
				  1e-15 );
  UTILITY_TEST_FLOATING_EQUALITY( dist_from_cdf->evaluate( 1.0*MeV ),
				  0.55*si::mole,
				  1e-15 );
  TEST_EQUALITY_CONST( dist_from_cdf->evaluate( 2.0*MeV ), 0.0*si::mole );

  TEST_EQUALITY_CONST( dist_from_cdf->evaluatePDF( 0.0*MeV ), 0.0/MeV );
  UTILITY_TEST_FLOATING_EQUALITY( dist_from_cdf->evaluatePDF( 1e-3*MeV ),
				  37.03703703703703/MeV,
				  1e-15 );
  UTILITY_TEST_FLOATING_EQUALITY( dist_from_cdf->evaluatePDF( 1e-2*MeV ),
				  37.03703703703703/MeV,
				  1e-15 );
  UTILITY_TEST_FLOATING_EQUALITY( dist_from_cdf->evaluatePDF( 1e-1*MeV ),
				  3.703703703703703/MeV,
				  1e-15 );
  UTILITY_TEST_FLOATING_EQUALITY( dist_from_cdf->evaluatePDF( 1.0*MeV ),
				  0.37037037037037046/MeV,
				  1e-15 );
  TEST_EQUALITY_CONST( dist_from_cdf->evaluatePDF( 2.0*MeV ), 0.0/MeV );

  TEST_EQUALITY_CONST( dist_from_cdf->evaluateCDF( 0.0*MeV ), 0.0 );
  UTILITY_TEST_FLOATING_EQUALITY( dist_from_cdf->evaluateCDF( 1e-3*MeV ),
				  0.0,
				  1e-10 );
  UTILITY_TEST_FLOATING_EQUALITY( dist_from_cdf->evaluateCDF( 1e-2*MeV ),
				  0.33333333333,
				  1e-10 );
  UTILITY_TEST_FLOATING_EQUALITY( dist_from_cdf->evaluateCDF( 1e-1*MeV ),
				  0.66666666667,
				  1e-10 );
  UTILITY_TEST_FLOATING_EQUALITY( dist_from_cdf->evaluateCDF( 1.0*MeV ),
				  1.0,
				  1e-10 );
  TEST_EQUALITY_CONST( dist_from_cdf->evaluateCDF( 2.0*MeV ), 1.0 );
}

UNIT_TEST_INSTANTIATION( UnitAwareTabularDistribution, cdf_constructor );

//---------------------------------------------------------------------------//
// Check that the upper bound of the distribution independent variable can be
// returned
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( TabularDistribution,
				   getUpperBoundOfIndepVar,
				   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( distribution );

  TEST_EQUALITY_CONST( distribution->getUpperBoundOfIndepVar(), 1.0 );
}

UNIT_TEST_INSTANTIATION( TabularDistribution, getUpperBoundOfIndepVar );

//---------------------------------------------------------------------------//
// Check that the upper bound of the unit-aware distribution independent
// variable can be returned
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( UnitAwareTabularDistribution,
				   getUpperBoundOfIndepVar,
				   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( unit_aware_distribution );

  TEST_EQUALITY_CONST( unit_aware_distribution->getUpperBoundOfIndepVar(),
		       1.0*MeV );
}

UNIT_TEST_INSTANTIATION( UnitAwareTabularDistribution,
			 getUpperBoundOfIndepVar );

//---------------------------------------------------------------------------//
// Check that the lower bound of the distribution independent variable can be
// returned
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( TabularDistribution,
				   getLowerBoundOfIndepVar,
				   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( distribution );

  TEST_EQUALITY_CONST( distribution->getLowerBoundOfIndepVar(), 1e-3 );
}

UNIT_TEST_INSTANTIATION( TabularDistribution, getLowerBoundOfIndepVar );

//---------------------------------------------------------------------------//
// Check that the lower bound of the unit-aware distribution independent
// variable can be returned
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( UnitAwareTabularDistribution,
				   getLowerBoundOfIndepVar,
				   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( unit_aware_distribution );

  TEST_EQUALITY_CONST( unit_aware_distribution->getLowerBoundOfIndepVar(),
		       1e-3*MeV );
}

UNIT_TEST_INSTANTIATION( UnitAwareTabularDistribution,
			 getLowerBoundOfIndepVar );

//---------------------------------------------------------------------------//
// Check that the distribution type can be returned
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( TabularDistribution,
				   getDistributionType,
				   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( distribution );

  TEST_EQUALITY_CONST( distribution->getDistributionType(),
		       Utility::TABULAR_DISTRIBUTION );
}

UNIT_TEST_INSTANTIATION( TabularDistribution, getDistributionType );

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution type can be returned
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( UnitAwareTabularDistribution,
				   getDistributionType,
				   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( unit_aware_distribution );

  TEST_EQUALITY_CONST( unit_aware_distribution->getDistributionType(),
		       Utility::TABULAR_DISTRIBUTION );
}

UNIT_TEST_INSTANTIATION( UnitAwareTabularDistribution, getDistributionType );

//---------------------------------------------------------------------------//
// Check if the distribution is tabular
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( TabularDistribution,
				   isTabular,
				   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( distribution );

  TEST_ASSERT( distribution->isTabular() );
}

UNIT_TEST_INSTANTIATION( TabularDistribution, isTabular );

//---------------------------------------------------------------------------//
// Check if the unit-aware distribution is tabular
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( UnitAwareTabularDistribution,
				   isTabular,
				   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( unit_aware_distribution );

  TEST_ASSERT( unit_aware_distribution->isTabular() );
}

UNIT_TEST_INSTANTIATION( UnitAwareTabularDistribution, isTabular );

//---------------------------------------------------------------------------//
// Check that the distribution is continuous
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( TabularDistribution,
				   isContinuous,
				   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( distribution );

  TEST_ASSERT( distribution->isContinuous() );
}

UNIT_TEST_INSTANTIATION( TabularDistribution, isContinuous );

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution is continuous
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( UnitAwareTabularDistribution,
				   isContinuous,
				   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( unit_aware_distribution );

  TEST_ASSERT( unit_aware_distribution->isContinuous() );
}

UNIT_TEST_INSTANTIATION( UnitAwareTabularDistribution, isContinuous );

//---------------------------------------------------------------------------//
// Check that the distribution can be written to an xml file
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( TabularDistribution,
				   toParameterList,
				   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( distribution );

  typedef Utility::TabularDistribution<InterpolationPolicy> Distribution;

  Teuchos::RCP<Distribution> true_distribution =
    Teuchos::rcp_dynamic_cast<Distribution>( distribution );

  Teuchos::ParameterList parameter_list;

  parameter_list.set<Distribution>( "test distribution",
				      *true_distribution );

  std::ostringstream xml_file_name;
  xml_file_name << "tabular_" << InterpolationPolicy::name()
		<< "_dist_test_list.xml";

  Teuchos::writeParameterListToXmlFile( parameter_list,
					xml_file_name.str() );

  Teuchos::RCP<Teuchos::ParameterList> read_parameter_list =
    Teuchos::getParametersFromXmlFile( xml_file_name.str() );

  TEST_EQUALITY( parameter_list, *read_parameter_list );

  Teuchos::RCP<Distribution>
    copy_distribution( new Distribution );

  *copy_distribution = read_parameter_list->get<Distribution>(
							  "test distribution");

  TEST_EQUALITY( *copy_distribution, *true_distribution );
}

UNIT_TEST_INSTANTIATION( TabularDistribution, toParameterList );

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be written to an xml file
TEUCHOS_UNIT_TEST_TEMPLATE_1_DECL( UnitAwareTabularDistribution,
				   toParameterList,
				   InterpolationPolicy )
{
  initialize<InterpolationPolicy>( unit_aware_distribution );

  typedef Utility::UnitAwareTabularDistribution<InterpolationPolicy,MegaElectronVolt,si::amount> Distribution;

  Teuchos::RCP<Distribution> true_distribution =
    Teuchos::rcp_dynamic_cast<Distribution>( unit_aware_distribution );

  Teuchos::ParameterList parameter_list;

  parameter_list.set<Distribution>( "test distribution",
				    *true_distribution );

  std::ostringstream xml_file_name;
  xml_file_name << "unit_aware_tabular_" << InterpolationPolicy::name()
		<< "_dist_test_list.xml";

  Teuchos::writeParameterListToXmlFile( parameter_list,
					xml_file_name.str() );

  Teuchos::RCP<Teuchos::ParameterList> read_parameter_list =
    Teuchos::getParametersFromXmlFile( xml_file_name.str() );

  TEST_EQUALITY( parameter_list, *read_parameter_list );

  Teuchos::RCP<Distribution>
    copy_distribution( new Distribution );

  *copy_distribution = read_parameter_list->get<Distribution>(
							  "test distribution");

  TEST_EQUALITY( *copy_distribution, *true_distribution );
}

UNIT_TEST_INSTANTIATION( UnitAwareTabularDistribution, toParameterList );

//---------------------------------------------------------------------------//
// Check that the distribution can be read from an xml file
TEUCHOS_UNIT_TEST( TabularDistribution, fromParameterList )
{
  Utility::TabularDistribution<Utility::LinLin> distribution_1 =
    test_dists_list->get<Utility::TabularDistribution<Utility::LinLin> >( "Tabular Distribution A" );

  TEST_EQUALITY_CONST( distribution_1.getLowerBoundOfIndepVar(), 0.001 );
  TEST_EQUALITY_CONST( distribution_1.getUpperBoundOfIndepVar(),
		       Utility::PhysicalConstants::pi );

  distribution_1 =
    test_dists_list->get<Utility::TabularDistribution<Utility::LinLin> >( "Tabular Distribution B" );

  TEST_EQUALITY_CONST( distribution_1.getLowerBoundOfIndepVar(), 0.001 );
  TEST_EQUALITY_CONST( distribution_1.getUpperBoundOfIndepVar(), 1.0 );

  Utility::TabularDistribution<Utility::LogLog> distribution_2 =
    test_dists_list->get<Utility::TabularDistribution<Utility::LogLog> >( "Tabular Distribution C" );

  TEST_EQUALITY_CONST( distribution_2.getLowerBoundOfIndepVar(), 0.001 );
  TEST_EQUALITY_CONST( distribution_2.getUpperBoundOfIndepVar(), 10.0 );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be read from an xml file
TEUCHOS_UNIT_TEST( UnitAwareTabularDistribution, fromParameterList )
{
  Utility::UnitAwareTabularDistribution<Utility::LinLin,MegaElectronVolt,si::amount>
    distribution_1 =
    test_dists_list->get<Utility::UnitAwareTabularDistribution<Utility::LinLin,MegaElectronVolt,si::amount> >( "Unit-Aware Tabular Distribution A" );

  TEST_EQUALITY_CONST( distribution_1.getLowerBoundOfIndepVar(), 0.001*MeV );
  TEST_EQUALITY_CONST( distribution_1.getUpperBoundOfIndepVar(),
		       Utility::PhysicalConstants::pi*MeV );

  distribution_1 =
    test_dists_list->get<Utility::UnitAwareTabularDistribution<Utility::LinLin,MegaElectronVolt,si::amount> >( "Unit-Aware Tabular Distribution B" );

  TEST_EQUALITY_CONST( distribution_1.getLowerBoundOfIndepVar(), 0.001*MeV );
  TEST_EQUALITY_CONST( distribution_1.getUpperBoundOfIndepVar(), 1.0*MeV );

  Utility::UnitAwareTabularDistribution<Utility::LogLog,MegaElectronVolt,si::amount>
    distribution_2 =
    test_dists_list->get<Utility::UnitAwareTabularDistribution<Utility::LogLog,MegaElectronVolt,si::amount> >( "Unit-Aware Tabular Distribution C" );

  TEST_EQUALITY_CONST( distribution_2.getLowerBoundOfIndepVar(), 0.001*MeV );
  TEST_EQUALITY_CONST( distribution_2.getUpperBoundOfIndepVar(), 10.0*MeV );
}

//---------------------------------------------------------------------------//
// Check that distributions can be scaled
TEUCHOS_UNIT_TEST_TEMPLATE_4_DECL( UnitAwareTabularDistribution,
				   explicit_conversion,
				   IndepUnitA,
				   DepUnitA,
				   IndepUnitB,
				   DepUnitB )
{
  typedef typename Utility::UnitTraits<IndepUnitA>::template GetQuantityType<double>::type IndepQuantityA;
  typedef typename Utility::UnitTraits<typename Utility::UnitTraits<IndepUnitA>::InverseUnit>::template GetQuantityType<double>::type InverseIndepQuantityA;

  typedef typename Utility::UnitTraits<IndepUnitB>::template GetQuantityType<double>::type IndepQuantityB;
  typedef typename Utility::UnitTraits<typename Utility::UnitTraits<IndepUnitB>::InverseUnit>::template GetQuantityType<double>::type InverseIndepQuantityB;

  typedef typename Utility::UnitTraits<DepUnitA>::template GetQuantityType<double>::type DepQuantityA;
  typedef typename Utility::UnitTraits<DepUnitB>::template GetQuantityType<double>::type DepQuantityB;

  initialize<Utility::LinLin>( distribution );

  // Copy from unitless distribution to distribution type A
  Utility::UnitAwareTabularDistribution<Utility::LinLin,IndepUnitA,DepUnitA>
    unit_aware_dist_a_copy = Utility::UnitAwareTabularDistribution<Utility::LinLin,IndepUnitA,DepUnitA>::fromUnitlessDistribution( *Teuchos::rcp_dynamic_cast<Utility::TabularDistribution<Utility::LinLin> >( distribution ) );

  // Copy from distribution type A to distribution type B
  Utility::UnitAwareTabularDistribution<Utility::LinLin,IndepUnitB,DepUnitB>
    unit_aware_dist_b_copy( unit_aware_dist_a_copy );

  IndepQuantityA indep_quantity_a =
    Utility::QuantityTraits<IndepQuantityA>::initializeQuantity( 0.0 );
  InverseIndepQuantityA inv_indep_quantity_a =
    Utility::QuantityTraits<InverseIndepQuantityA>::initializeQuantity( 0.0 );
  DepQuantityA dep_quantity_a =
    Utility::QuantityTraits<DepQuantityA>::initializeQuantity( 0.0 );

  IndepQuantityB indep_quantity_b( indep_quantity_a );
  InverseIndepQuantityB inv_indep_quantity_b( inv_indep_quantity_a );
  DepQuantityB dep_quantity_b( dep_quantity_a );

  UTILITY_TEST_FLOATING_EQUALITY(
			   unit_aware_dist_a_copy.evaluate( indep_quantity_a ),
			   dep_quantity_a,
			   1e-15 );
  UTILITY_TEST_FLOATING_EQUALITY(
			unit_aware_dist_a_copy.evaluatePDF( indep_quantity_a ),
			inv_indep_quantity_a,
			1e-15 );
  UTILITY_TEST_FLOATING_EQUALITY(
			   unit_aware_dist_b_copy.evaluate( indep_quantity_b ),
			   dep_quantity_b,
			   1e-15 );
  UTILITY_TEST_FLOATING_EQUALITY(
			unit_aware_dist_b_copy.evaluatePDF( indep_quantity_b ),
			inv_indep_quantity_b,
			1e-15 );

  Utility::setQuantity( indep_quantity_a, 0.1 );
  Utility::setQuantity( inv_indep_quantity_a, 0.67340006734 );
  Utility::setQuantity( dep_quantity_a, 1.0 );

  indep_quantity_b = IndepQuantityB( indep_quantity_a );
  inv_indep_quantity_b = InverseIndepQuantityB( inv_indep_quantity_a );
  dep_quantity_b = DepQuantityB( dep_quantity_a );

  UTILITY_TEST_FLOATING_EQUALITY(
			   unit_aware_dist_a_copy.evaluate( indep_quantity_a ),
			   dep_quantity_a,
			   1e-15 );
  UTILITY_TEST_FLOATING_EQUALITY(
			unit_aware_dist_a_copy.evaluatePDF( indep_quantity_a ),
			inv_indep_quantity_a,
			1e-6 );
  UTILITY_TEST_FLOATING_EQUALITY(
			   unit_aware_dist_b_copy.evaluate( indep_quantity_b ),
			   dep_quantity_b,
			   1e-15 );
  UTILITY_TEST_FLOATING_EQUALITY(
			unit_aware_dist_b_copy.evaluatePDF( indep_quantity_b ),
			inv_indep_quantity_b,
			1e-6 );
}

typedef si::energy si_energy;
typedef cgs::energy cgs_energy;
typedef si::amount si_amount;
typedef si::length si_length;
typedef cgs::length cgs_length;
typedef si::mass si_mass;
typedef cgs::mass cgs_mass;
typedef si::dimensionless si_dimensionless;
typedef cgs::dimensionless cgs_dimensionless;

TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT( UnitAwareTabularDistribution,
				      explicit_conversion,
				      si_energy,
				      si_amount,
				      cgs_energy,
				      si_amount );
TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT( UnitAwareTabularDistribution,
				      explicit_conversion,
				      cgs_energy,
				      si_amount,
				      si_energy,
				      si_amount );
TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT( UnitAwareTabularDistribution,
				      explicit_conversion,
				      si_energy,
				      si_length,
				      cgs_energy,
				      cgs_length );
TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT( UnitAwareTabularDistribution,
				      explicit_conversion,
				      cgs_energy,
				      cgs_length,
				      si_energy,
				      si_length );
TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT( UnitAwareTabularDistribution,
				      explicit_conversion,
				      si_energy,
				      si_mass,
				      cgs_energy,
				      cgs_mass );
TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT( UnitAwareTabularDistribution,
				      explicit_conversion,
				      cgs_energy,
				      cgs_mass,
				      si_energy,
				      si_mass );
TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT( UnitAwareTabularDistribution,
				      explicit_conversion,
				      si_energy,
				      si_dimensionless,
				      cgs_energy,
				      cgs_dimensionless );
TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT( UnitAwareTabularDistribution,
				      explicit_conversion,
				      cgs_energy,
				      cgs_dimensionless,
				      si_energy,
				      si_dimensionless );
TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT( UnitAwareTabularDistribution,
				      explicit_conversion,
				      si_energy,
				      void,
				      cgs_energy,
				      void );
TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT( UnitAwareTabularDistribution,
				      explicit_conversion,
				      cgs_energy,
				      void,
				      si_energy,
				      void );
TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT( UnitAwareTabularDistribution,
				      explicit_conversion,
				      ElectronVolt,
				      si_amount,
				      si_energy,
				      si_amount );
TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT( UnitAwareTabularDistribution,
				      explicit_conversion,
				      ElectronVolt,
				      si_amount,
				      cgs_energy,
				      si_amount );
TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT( UnitAwareTabularDistribution,
				      explicit_conversion,
				      ElectronVolt,
				      si_amount,
				      KiloElectronVolt,
				      si_amount );
TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT( UnitAwareTabularDistribution,
				      explicit_conversion,
				      ElectronVolt,
				      si_amount,
				      MegaElectronVolt,
				      si_amount );
TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT( UnitAwareTabularDistribution,
				      explicit_conversion,
				      KiloElectronVolt,
				      si_amount,
				      si_energy,
				      si_amount );
TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT( UnitAwareTabularDistribution,
				      explicit_conversion,
				      KiloElectronVolt,
				      si_amount,
				      cgs_energy,
				      si_amount );
TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT( UnitAwareTabularDistribution,
				      explicit_conversion,
				      KiloElectronVolt,
				      si_amount,
				      ElectronVolt,
				      si_amount );
TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT( UnitAwareTabularDistribution,
				      explicit_conversion,
				      KiloElectronVolt,
				      si_amount,
				      MegaElectronVolt,
				      si_amount );
TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT( UnitAwareTabularDistribution,
				      explicit_conversion,
				      MegaElectronVolt,
				      si_amount,
				      si_energy,
				      si_amount );
TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT( UnitAwareTabularDistribution,
				      explicit_conversion,
				      MegaElectronVolt,
				      si_amount,
				      cgs_energy,
				      si_amount );
TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT( UnitAwareTabularDistribution,
				      explicit_conversion,
				      MegaElectronVolt,
				      si_amount,
				      ElectronVolt,
				      si_amount );
TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT( UnitAwareTabularDistribution,
				      explicit_conversion,
				      MegaElectronVolt,
				      si_amount,
				      KiloElectronVolt,
				      si_amount );
TEUCHOS_UNIT_TEST_TEMPLATE_4_INSTANT( UnitAwareTabularDistribution,
				      explicit_conversion,
				      void,
				      MegaElectronVolt,
				      void,
				      KiloElectronVolt );

//---------------------------------------------------------------------------//
// Custom main function
//---------------------------------------------------------------------------//
int main( int argc, char** argv )
{
  std::string test_dists_xml_file;

  Teuchos::CommandLineProcessor& clp = Teuchos::UnitTestRepository::getCLP();

  clp.setOption( "test_dists_xml_file",
		 &test_dists_xml_file,
		 "Test distributions xml file name" );

  const Teuchos::RCP<Teuchos::FancyOStream> out =
    Teuchos::VerboseObjectBase::getDefaultOStream();

  Teuchos::CommandLineProcessor::EParseCommandLineReturn parse_return =
    clp.parse(argc,argv);

  if ( parse_return != Teuchos::CommandLineProcessor::PARSE_SUCCESSFUL ) {
    *out << "\nEnd Result: TEST FAILED" << std::endl;
    return parse_return;
  }

  TEUCHOS_ADD_TYPE_CONVERTER( Utility::TabularDistribution<Utility::LinLin> );
  TEUCHOS_ADD_TYPE_CONVERTER( Utility::TabularDistribution<Utility::LogLin> );
  TEUCHOS_ADD_TYPE_CONVERTER( Utility::TabularDistribution<Utility::LinLog> );
  TEUCHOS_ADD_TYPE_CONVERTER( Utility::TabularDistribution<Utility::LogLog> );
  typedef Utility::UnitAwareTabularDistribution<Utility::LinLin,MegaElectronVolt,si::amount> UnitAwareTabularLinLinDist;
  TEUCHOS_ADD_TYPE_CONVERTER( UnitAwareTabularLinLinDist );
  typedef Utility::UnitAwareTabularDistribution<Utility::LogLin,MegaElectronVolt,si::amount> UnitAwareTabularLogLinDist;
  TEUCHOS_ADD_TYPE_CONVERTER( UnitAwareTabularLogLinDist );
  typedef Utility::UnitAwareTabularDistribution<Utility::LinLog,MegaElectronVolt,si::amount> UnitAwareTabularLinLogDist;
  TEUCHOS_ADD_TYPE_CONVERTER( UnitAwareTabularLinLogDist );
  typedef Utility::UnitAwareTabularDistribution<Utility::LogLog,MegaElectronVolt,si::amount> UnitAwareTabularLogLogDist;
  TEUCHOS_ADD_TYPE_CONVERTER( UnitAwareTabularLogLogDist );

  test_dists_list = Teuchos::getParametersFromXmlFile( test_dists_xml_file );

  // Initialize the random number generator
  Utility::RandomNumberGenerator::createStreams();

  // Run the unit tests
  Teuchos::GlobalMPISession mpiSession( &argc, &argv );

  const bool success = Teuchos::UnitTestRepository::runUnitTests(*out);

  if (success)
    *out << "\nEnd Result: TEST PASSED" << std::endl;
  else
    *out << "\nEnd Result: TEST FAILED" << std::endl;

  clp.printFinalTimerSummary(out.ptr());

  return (success ? 0 : 1);
}

//---------------------------------------------------------------------------//
// end tstTabularDistribution.cpp
//---------------------------------------------------------------------------//