#include "objparse.h"

#include <cstdio>
#include <iterator>
#include <vector>
#include <map>
#include <iostream>

#include <Eigen/Dense>

using namespace std;
using namespace Eigen;

struct objface {
  unsigned int id;
  vector<unsigned int> vids;
  vector<unsigned int> nids;
};

vector<vertex*> verteces;
vector<edge*> edges;
vector<face*> faces;
vector<struct objface> objfaces;
subd_method method = UNKNOWN;

int parseVertexSpec(const string vspec) {
  unsigned int vid;
  sscanf(vspec.c_str(), "%d", &vid);
  return vid;
}

void parseLine(string line) {
  if (line[0] == '#' && line.find("loop") != string::npos) {
    method = LOOP_SUBD;
  } else if (line[0] == '#' && line.find("catmullclark") != string::npos) {
    method = CATMULL_CLARK_SUBD;
  } else if (line[0] == 'v' && line[1] == ' ') {
    vertex *v = new vertex();

    float x, y, z;
    sscanf(line.c_str(), "v %f %f %f", &x, &y, &z);
    v->loc = Vector3f(x, y, z);
    v->id = verteces.size() + 1;

    verteces.push_back(v);
  } else if (line[0] == 'f' && line[1] == ' ') {
    istringstream tokenizer(line);
    vector<string> tokens;
    copy(istream_iterator<string>(tokenizer),
        istream_iterator<string>(),
        back_inserter<vector<string> >(tokens));

    struct objface f;
    f.id = objfaces.size() + 1;
    for (unsigned int i=1; i<tokens.size(); i++) {
      string token = tokens[i];
      f.vids.push_back(parseVertexSpec(token));
    }

    objfaces.push_back(f);
  }
}

void mergeHalfEdges() {
  map<pair<unsigned int, unsigned int>, edge*> halfedges;
  for (vector<edge*>::const_iterator edge_iter = edges.begin();
       edge_iter != edges.end(); edge_iter++) {
    edge *e = *edge_iter;

    int vid1 = e->vert->id;
    edge *previous = e;
    while (previous->next != e) previous = previous->next;
    int vid2 = previous->vert->id;

    pair<unsigned int, unsigned int> vids =
      pair<unsigned int, unsigned int>(
        vid1 < vid2 ? vid1 : vid2, vid1 < vid2 ? vid2 : vid1);

    map<pair<unsigned int, unsigned int>, edge*>::iterator
      mapval = halfedges.find(vids);

    if (mapval == halfedges.end()) {
      halfedges.insert(
          pair<pair<unsigned int, unsigned int>, edge*>(vids, e));
    } else {
      edge *opposite = mapval->second;
      e->pair = opposite;
      opposite->pair = e;
    }
  }
}

void addTriangle(vector<unsigned int> vids) {
  face *f = new face();
  f->id = faces.size() + 1;

  edge *last_edge;
  for (unsigned int i = 0; i < vids.size(); i++) {
    edge *e = new edge();
    e->vert = verteces[vids[i] - 1];
    verteces[vids[i] - 1]->e = e;
    e->f = f;
    e->id = edges.size() + 1;
    e->pair = NULL;
    if (i != 0) {
      last_edge->next = e;
    } else {
      f->e = e;
    }

    last_edge = e;
    edges.push_back(e);
  }
  last_edge->next = f->e;
  f->calculateNormal();
  faces.push_back(f);
}

void loadObjFile(istream& file, mesh &mesh) {
  string line;
  while (file.good()) {
    getline(file, line);
    parseLine(line);
  }

  for (auto objf = objfaces.begin(); objf != objfaces.end(); objf++) {
    addTriangle(objf->vids);
  }
  mergeHalfEdges();

  mesh.verteces = verteces;
  mesh.edges = edges;
  mesh.faces = faces;
  if (method != UNKNOWN) {
    mesh.subdivision = method;
  } else {
    mesh.guessSubdMethod();
  }
  mesh.scaleToUnitCube();

  cout << "Loaded mesh: " << endl
    << "  " << verteces.size() << " verteces." << endl
    << "  " << edges.size() << " edges." << endl
    << "  " << objfaces.size() << " faces in OBJ file." << endl;
}


#include <boost/timer.hpp>
#include <kv/allsol.hpp>
#include "allsolexample.hpp"

using namespace std;

namespace ub = boost::numeric::ublas;

int main()
{
	int i;
	boost::timer t;
	ub::vector< kv::interval<double> > I;

	Matsu1().range(I);
	std::cout << "Matsu1\n";
	t.restart();
	allsol(Matsu1(), I);
	cout << t.elapsed() << " sec\n";

	Matsu2().range(I);
	std::cout << "Matsu2\n";
	t.restart();
	allsol(Matsu2(), I);
	cout << t.elapsed() << " sec\n";

	NoSol().range(I);
	std::cout << "NoSol\n";
	t.restart();
	allsol(NoSol(), I);
	cout << t.elapsed() << " sec\n";

	BadCond().range(I);
	std::cout << "BadCond\n";
	t.restart();
	allsol(BadCond(), I);
	cout << t.elapsed() << " sec\n";

	Hansen1().range(I);
	std::cout << "Hansen1\n";
	t.restart();
	allsol(Hansen1(), I);
	cout << t.elapsed() << " sec\n";

	GE1().range(I);
	std::cout << "GE1\n";
	t.restart();
	allsol(GE1(), I);
	cout << t.elapsed() << " sec\n";

	Shinohara1().range(I);
	std::cout << "Shinohara1\n";
	t.restart();
	allsol(Shinohara1(), I);
	cout << t.elapsed() << " sec\n";

	// has multiple root (2,0)
	Shinohara2().range(I);
	std::cout << "Shinohara2\n";
	t.restart();
	allsol(Shinohara2(), I, 1, 1e-8);
	cout << t.elapsed() << " sec\n";

	Shinohara3().range(I);
	std::cout << "Shinohara3\n";
	t.restart();
	allsol(Shinohara3(), I);
	cout << t.elapsed() << " sec\n";

	ModifiedHimmelblau().range(I);
	std::cout << "ModifiedHimmelblau\n";
	t.restart();
	allsol(ModifiedHimmelblau(), I);
	cout << t.elapsed() << " sec\n";

	Heihachiro().range(I);
	std::cout << "Heihachiro\n";
	t.restart();
	allsol(Heihachiro(), I);
	cout << t.elapsed() << " sec\n";

	Yamamura2().range(7, I);
	std::cout << "Yamamura2(7)\n";
	t.restart();
	allsol(Yamamura2(), I);
	cout << t.elapsed() << " sec\n";

	HydroCarbon().range(I);
	std::cout << "HydroCarbon\n";
	t.restart();
	allsol(HydroCarbon(), I);
	cout << t.elapsed() << " sec\n";
}


/*
 / * *
 * Copyright (c) 2015 Claus Christmann <hcc |ä| gatech.edu>.
 *
 * Licensed under the Apache Lice*nse, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#include "rigidbody.h"

#include "units.hpp"
#include "commoncoordinatesystems.h"
#include "gis.h"
#include <Eigen/Geometry>

namespace AMG {

RigidBody::RigidBody(const AMG::FrameOfReference* parentFrame)
  : FrameOfReference(parentFrame)
  , mass(1.0) // initialize to 1kg
  , inertiaMatrix(Eigen::Matrix3d::Zero()) // initialize to a point mass
  , m_x(0.0)
  , m_y(0.0)
  , m_z(0.0)
  , m_u(0.0)
  , m_v(0.0)
  , m_w(0.0)
  , m_phi(0.0)
  , m_theta(0.0)
  , m_psi(0.0)
  , m_p(0.0)
  , m_q(0.0)
  , m_r(0.0)
  , x(m_x)
  , y(m_y)
  , z(m_z)
  , u(m_u)
  , v(m_v)
  , w(m_w)
  , phi(m_phi)
  , theta(m_theta)
  , psi(m_psi)
  , p(m_p)
  , q(m_q)
  , r(m_r)
{
  inertial=false; // this reference frame is not an inertial one as it could accelerate
}

RigidBody::RigidBody(const double& bodyMass                    ,
                     const Eigen::Matrix3d & bodyInertiaMatrix ,
                     const FrameOfReference* parentFrame       )
  : FrameOfReference(parentFrame)
  , mass(bodyMass)
  , inertiaMatrix(bodyInertiaMatrix)
  , m_x(0.0), x(m_x)
  , m_y(0.0), y(m_y)
  , m_z(0.0), z(m_z)
  , m_u(0.0), u(m_u)
  , m_v(0.0), v(m_v)
  , m_w(0.0), w(m_w)
  , m_phi(0.0), phi(m_phi)
  , m_theta(0.0), theta(m_theta)
  , m_psi(0.0), psi(m_psi)
  , m_p(0.0), p(m_p)
  , m_q(0.0), q(m_q)
  , m_r(0.0), r(m_r)
{
  inertial=false; // this reference frame is not an inertial one as it could accelerate
}

// NOTE: not implemented to dissallow copying.
// RigidBody::RigidBody(const RigidBody& other)
// {
// }

RigidBody::~RigidBody()
{

};

Vector RigidBody::position() const
{
  return FrameOfReference::positionOfOrigin();
}

Vector RigidBody::velocity() const
{
  return Vector(u,v,w,this); //FIXME: well, this is just wrong ;)
}

Vector RigidBody::angularRates() const
{
  return Vector(p,q,r,this); //FIXME: well, this is just wrong ;)
}

void RigidBody::movePositionBy(const Vector& relativeMotion)
{
  translate(relativeMotion.coordsIn(this));
}

void RigidBody::setPositionTo(const Vector& absoluteTargetLocation)
{
  translate(absoluteTargetLocation.absoluteCoordsIn(this));
}

void RigidBody::setVelocityTo(const Vector& velocity)
{
  CoordinateTupel bodySpeeds(velocity.coordsIn(this));

  m_u = bodySpeeds[0];
  m_v = bodySpeeds[1];
  m_w = bodySpeeds[2];
}

void RigidBody::setRatesTo(const Vector& rotationalRates)
{
  CoordinateTupel bodyRates(rotationalRates.coordsIn(this));

  m_phi = bodyRates[0];
  m_theta = bodyRates[1];
  m_psi = bodyRates[2];

}

//NOTE: order matters here! this specialization to <radian> has to appear ABOVE
// the specialization to <degree> as that calls this.
template<>
void RigidBody::setPositionTo<AMG::Units::radian>(const double& longitude, const double& latitude, const double& elevation)
{
  assert( NULL != CoSy::getECEF() );

  double x_ecef,y_ecef,z_ecef;
  GIS::geodetic2ecef(longitude,latitude,elevation,x_ecef,y_ecef,z_ecef);

  setPositionTo(Vector(x_ecef,y_ecef,z_ecef,CoSy::getECEF()));
}

template<>
void RigidBody::setPositionTo<AMG::Units::degree>(const double& longitude, const double& latitude, const double& elevation)
{
 using Units::degree2radian;
 setPositionTo<Units::radian>(degree2radian(longitude),degree2radian(latitude),elevation);
}

} // end namespace AMG


//          Copyright Oliver Kowalke 2014.
// 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 <cstdlib>
#include <iostream>

#include <boost/context/all.hpp>

#define yield(x) p=x; mctx.resume();

int main() {
    int n=35;
    int p=0;
    boost::context::execution_context mctx( boost::context::execution_context::current() );
    boost::context::execution_context ctx(
        boost::context::fixedsize_stack(),
        [n,&p,mctx]()mutable{
            int a=0;
            int b=1;
            while(n-->0){
                yield(a);
                auto next=a+b;
                a=b;
                b=next;
            }
        });
    for(int i=0;i<10;++i){
        ctx.resume();
        std::cout<<p<<std::endl;
    }

    std::cout << "main: done" << std::endl;
}


#include <iostream>
#include <chrono>
#include <boost/progress.hpp>
#include <boost/program_options.hpp>

#include "iabn.h"

namespace po = boost::program_options;
using namespace std;

int main(int argc, char** argv)
{
    auto start = std::chrono::high_resolution_clock::now();

    std::string svg_name = "network.svg";
    bool b_draw = false;
    double area_coeff = 1.0;
    bool b_tree = true;
    double wired_fractoin = def_Wired_fraction;
    //Fixed wired Base stations
    bool fixed = false;
    int fixed_count = -1;
    double node_density = def_lambda_SBS;

    // Declare the supported options.
    po::options_description desc("Allowed options");
    desc.add_options()
        ("help", "Construct your network based on the following inputs.")
        ("area", po::value<double>() ,"Area coefficient, Area=coeff*1.e6")
        ("tree", po::value<bool>() ,"Use R-tree for spatial indexing (1) or array (0)")
        ("if", po::value<string>(), "Input file name")
        ("fixed", po::value<int>(), "Set wired BS implementation type (fixed(1) or variable(0))")
        ("count", po::value<int>(), "Number of fixed wired base stations")
        ("svg", po::value<string>(), "Topology SVG file name")
        ;

    po::variables_map vm;
    po::store(po::parse_command_line(argc, argv, desc), vm);
    po::notify(vm);

    if (vm.count("help"))
    {
        std::cout << desc << "\n";
        return 1;
    }

    if(vm.count("area"))
        area_coeff = vm["area"].as<double>();

    if(vm.count("tree"))
        b_tree = vm["tree"].as<bool>();

    std::string input;
    bool b_input = false;
    if(vm.count("if"))
    {
        input = vm["if"].as<string>();
        b_input = true;
    }

    if (vm.count("fixed"))
    {
        cout << "Fixed wired Base Stations is set to "  << vm["fixed"].as<int>() << ".\n";
        int dd = vm["fixed"].as<int>();
        if(dd==1)
            fixed = true;
        else
            fixed = false;

    } else {
        cerr << "Input Arg Error: set the type of wired base station implementation.\n";
        return 1;
    }

    if(vm.count("count"))
        fixed_count = vm["count"].as<int>();
    else
        fixed_count = 4;

    if(vm.count("svg"))
    {
        svg_name = "SVG_"+vm["svg"].as<string>()+".svg";
        b_draw = true;
    }

    IABN iabn;
    m_nextId = 0; //TODO fix the id generator
    iabn.create_svg(svg_name);

// Generate data on a disk with radius r with poisson point process
    double area = def_Area*node_density;
    double r = 100*sqrt(1/M_PI)*sqrt(area); // radius of the disk
    double xx0=r; double yy0=r;    // center of the disk
    iabn.set_center(xx0, yy0, r);

    if(b_input)
        iabn.load_nodes(input, fixed, fixed_count, wired_fractoin);
    else
        iabn.generate_nodes(node_density, fixed, fixed_count, wired_fractoin);

    std::cout << "IAB node count = " << iabn.get_IAB_count() << std::endl;
    std::cout << "Wired node count = " << iabn.get_wired_count() << std::endl;
    matrix<double> matrix_SINR = iabn.Generate_Matrix_SINR();

//     std::cout << matrix_SINR << std::endl;

    iabn.draw_svg(b_draw);
//     if(b_tree)
//         iabn.check_SINR_tree();
//     else
//         iabn.check_SINR_array();

//     finish = std::chrono::high_resolution_clock::now();
//     std::chrono::duration<double> elapsed = finish - start;
//     std::cout << "search = "<< elapsed.count() - loading_time.count() << "," << std::endl;
//
//     std::cout << "computation = "<< elapsed.count() << "," << std::endl;

    std::cout << "Get out of my Audio, Adios!!" << std::endl;

//     std::cout << "ratio = " << loading_time.count()/elapsed.count() << "," << std::endl;

    return 0;
}


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef NT2_TOOLBOX_EXPONENTIAL_FUNCTIONS_SIMD_COMMON_REALPOW_HPP_INCLUDED
#define NT2_TOOLBOX_EXPONENTIAL_FUNCTIONS_SIMD_COMMON_REALPOW_HPP_INCLUDED
#include <nt2/toolbox/exponential/functions/realpow.hpp>
#include <nt2/include/functions/simd/logical_or.hpp>
#include <nt2/include/functions/simd/powi.hpp>
#include <nt2/include/functions/simd/pow.hpp>
#include <nt2/include/functions/simd/is_nltz.hpp>
#include <nt2/include/functions/simd/is_flint.hpp>
#include <nt2/include/functions/simd/all.hpp>
#include <boost/assert.hpp>

/////////////////////////////////////////////////////////////////////////////
// Implementation when type A0 is floating_
/////////////////////////////////////////////////////////////////////////////
namespace nt2 { namespace ext
{
  NT2_FUNCTOR_IMPLEMENTATION( nt2::tag::realpow_, tag::cpu_
                            , (A0)(X)
                            , ((simd_<floating_<A0>,X>))((simd_<floating_<A0>,X>))
                            )
  {
    typedef A0 result_type;
    NT2_FUNCTOR_CALL_REPEAT(2)
    {
      BOOST_ASSERT_MSG( nt2::all(logical_or(is_nltz(a0), is_flint(a1))),"realpow cannot produce complex result." );
      return pow(a0, a1);
    }
  };
} }

/////////////////////////////////////////////////////////////////////////////
// Implementation when type A1 is integer_
/////////////////////////////////////////////////////////////////////////////
namespace nt2 { namespace ext
{
  NT2_FUNCTOR_IMPLEMENTATION( nt2::tag::realpow_, tag::cpu_
                            , (A0)(A1)(X)
                            , ((simd_<arithmetic_<A0>,X>))((simd_<integer_<A1>,X>))
                            )
  {
    typedef typename meta::as_floating<A0>::type result_type;
    NT2_FUNCTOR_CALL(2)
    {
      return pow(a0, a1);
    }
  };
} }

/////////////////////////////////////////////////////////////////////////////
// Implementation when type A1 is scalar integer_
/////////////////////////////////////////////////////////////////////////////
namespace nt2 { namespace ext
{
  NT2_FUNCTOR_IMPLEMENTATION( nt2::tag::realpow_, tag::cpu_
                            , (A0)(A1)(X)
                            , ((simd_<arithmetic_<A0>,X>))(scalar_< integer_<A1> >)
                            )
  {

    typedef typename meta::as_floating<A0>::type result_type;

    NT2_FUNCTOR_CALL(2)
    {
      return powi(a0, a1);
    }
  };
} }

#endif


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef NT2_TRIGONOMETRIC_FUNCTIONS_GENERIC_REM_PIO2_STRAIGHT_HPP_INCLUDED
#define NT2_TRIGONOMETRIC_FUNCTIONS_GENERIC_REM_PIO2_STRAIGHT_HPP_INCLUDED

#include <nt2/trigonometric/functions/rem_pio2_straight.hpp>
#include <nt2/include/constants/one.hpp>
#include <nt2/include/constants/pio2_1.hpp>
#include <nt2/include/constants/pio2_2.hpp>
#include <nt2/include/constants/pio2_3.hpp>
#include <nt2/sdk/meta/as_integer.hpp>
#include <boost/fusion/include/std_pair.hpp>
#include <nt2/include/functions/simd/multiplies.hpp>
#include <nt2/include/functions/simd/minus.hpp>

namespace nt2 { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT  (rem_pio2_straight_, tag::cpu_,
                             (A0)(A1),
                             (generic_ < floating_<A0> > )
                             (generic_ < integer_<A1>  > )
                             (generic_ < floating_<A0> > )
                            )
  {
    typedef void result_type;
    inline result_type operator()(A0 const& x, A1 & n, A0 & xr)
    {
      n = nt2::rem_pio2_straight(x, xr);
    }
  };

  BOOST_DISPATCH_IMPLEMENT  (rem_pio2_straight_, tag::cpu_,
                             (A0),
                             (generic_ < floating_<A0> > )
                            )
  {
    typedef typename nt2::meta::as_integer<A0>::type int_t;
    typedef std::pair<int_t, A0>           result_type;
    NT2_FUNCTOR_CALL(1)
    {
      A0 second;
      int_t const first = nt2::rem_pio2_straight(a0,second);
      return result_type(first, second);
    }
  };

  BOOST_DISPATCH_IMPLEMENT  (rem_pio2_straight_, tag::cpu_,
           (A0),
           (generic_< floating_<A0> >)
           (generic_< floating_<A0> >)
           )
  {
    typedef typename meta::as_integer<A0>::type result_type;
    inline result_type operator()(A0 const& x, A0 & xr) const
    {
      xr = x-Pio2_1<A0>();
      xr -= Pio2_2<A0>();
      xr -= Pio2_3<A0>();
      return  nt2::One<result_type>();
    }
  };
} }
#endif


#include <gtest/gtest.h>
#include <list>
#include "Cady/Cady.h"
#include "Cady/Transform.h"

using namespace Cady;

TEST(Expr,Constant){
        auto value = 1.234;
        auto x = Constant::Make(value);
        auto dx = x->Diff("x");

        EXPECT_EQ(OPKind_Constant, x->Kind()  );
        EXPECT_EQ(value, std::reinterpret_pointer_cast<Constant>(x)->Value() );
        EXPECT_EQ(OPKind_Constant, dx->Kind()  );
        EXPECT_EQ(0.0, std::reinterpret_pointer_cast<Constant>(dx)->Value() );
}

TEST(Expr,ExogenousSymbol){
        auto sym = "Foo";
        auto s = ExogenousSymbol::Make(sym);
        EXPECT_EQ(OPKind_ExogenousSymbol, s->Kind()  );
        EXPECT_EQ(sym, std::reinterpret_pointer_cast<ExogenousSymbol>(s)->Name() );

        auto ds_ds = s->Diff(sym);
        auto ds_dt = s->Diff("Other");

        EXPECT_EQ(OPKind_Constant, ds_ds->Kind());
        EXPECT_EQ(1.0, std::reinterpret_pointer_cast<Constant>(ds_ds)->Value() );
        EXPECT_EQ(OPKind_Constant, ds_dt->Kind());
        EXPECT_EQ(0.0, std::reinterpret_pointer_cast<Constant>(ds_dt)->Value() );
}

TEST(Expr,EndgenousSymbol){

        auto sub_expr = Constant::Make(2.0);

        auto sym = "Foo";
        auto s = EndgenousSymbol::Make(sym, sub_expr);
        EXPECT_EQ(OPKind_EndgenousSymbol, s->Kind()  );
        EXPECT_EQ(sym, std::reinterpret_pointer_cast<EndgenousSymbol>(s)->Name() );

        auto ds_ds = s->Diff(sym);
        auto ds_dt = s->Diff("Other");

        EXPECT_EQ(OPKind_Constant, ds_ds->Kind());
        EXPECT_EQ(1.0, std::reinterpret_pointer_cast<Constant>(ds_ds)->Value() );
        EXPECT_EQ(OPKind_Constant, ds_dt->Kind());
        EXPECT_EQ(0.0, std::reinterpret_pointer_cast<Constant>(ds_dt)->Value() );
}

enum InstructionKind{
        Instr_VarDecl,
        Instr_PointerAssignment,
        Instr_Return,
        Instr_Comment,
};
struct Instruction{
        explicit Instruction(InstructionKind kind)
                :kind_(kind)
        {}
        virtual ~Instruction()=default;
        InstructionKind Kind()const{ return kind_; }
        virtual void EmitCode(std::ostream& out)const=0;
private:
        InstructionKind kind_;
};
struct InstructionComment : Instruction{
        InstructionComment(std::vector<std::string> const& text)
                :Instruction{Instr_Comment}
                ,text_{text}
        {}
        virtual void EmitCode(std::ostream& out)const{
                for(auto const& comment : text_){
                        out << "    // " << comment << "\n";
                }
        }
private:
        std::vector<std::string> text_;
};
struct InstructionDeclareVariable : Instruction{
        InstructionDeclareVariable(std::string const& name, std::shared_ptr<Operator> op)
                :Instruction{Instr_VarDecl}
                ,name_(name)
                ,op_(op)
        {}
        virtual void EmitCode(std::ostream& out)const{
                out << "    double const " << name_ << " = ";
                op_->EmitCode(out);
                out << ";\n";
        }
        auto const& as_operator_()const{ return op_; }
private:
        std::string name_;
        std::shared_ptr<Operator> op_;
};
struct InstructionPointerAssignment : Instruction{
        InstructionPointerAssignment(std::string const& name, std::string const& r_value)
                :Instruction{Instr_PointerAssignment}
                ,name_(name)
                ,r_value_{r_value}
        {}
        virtual void EmitCode(std::ostream& out)const{
                out << "    *" << name_ << " = " << r_value_ << ";\n";
        }
private:
        std::string name_;
        std::string r_value_;
};
struct InstructionReturn : Instruction{
        InstructionReturn(std::string const& name)
                :Instruction{Instr_Return}
                ,name_(name)
        {}
        virtual void EmitCode(std::ostream& out)const{
                out << "    return " << name_ << ";\n";
        }
private:
        std::string name_;
};

struct InstructionBlock : std::vector<std::shared_ptr<Instruction> >{
        virtual ~InstructionBlock()=default;
        void Add(std::shared_ptr<Instruction> instr){
                // quick hack for now
                if( size() > 0 && back()->Kind() == Instr_Return ){
                        auto tmp = back();
                        pop_back();
                        push_back(instr);
                        push_back(tmp);
                } else {
                        push_back(instr);
                }
        }
        virtual void EmitCode(std::ostream& out)const{
                for(auto const& ptr : *this){
                        ptr->EmitCode(out);
                }
        }
};

TEST(Instruction,InstructionDeclareVariable){
        auto expr = BinaryOperator::Add(
                Constant::Make(2.0),
                Constant::Make(3.0)
        );
        auto instr = std::make_shared<InstructionDeclareVariable>("tmp", expr);
        instr->EmitCode(std::cout);

}

/*
        From Savine
 */
TEST(Black,Numeric){
        auto S0  = ExogenousSymbol::Make("S0");
        auto r   = ExogenousSymbol::Make("r");
        auto y   = ExogenousSymbol::Make("y");
        auto sig = ExogenousSymbol::Make("sig");
        auto K   = ExogenousSymbol::Make("K");
        auto T   = ExogenousSymbol::Make("T");

        auto df = Exp::Make(
                UnaryOperator::UnaryMinus(
                        BinaryOperator::Mul(
                                r,
                                T
                        )
                )
        );
        auto sym_df = EndgenousSymbol::Make("df", df);

        auto F = BinaryOperator::Mul(
                S0,
                Exp::Make(
                        BinaryOperator::Mul(
                                BinaryOperator::Sub(r,y),
                                T
                        )
                )
        );
        auto sym_F = EndgenousSymbol::Make("F", F);

        auto std = BinaryOperator::Mul(
                sig,
                BinaryOperator::Pow(
                        T,
                        Constant::Make(0.5)
                )
        );
        auto sym_std = EndgenousSymbol::Make("std", std);

        auto d = BinaryOperator::Div(
                Log::Make(
                        BinaryOperator::Div(
                                sym_F,
                                K
                        )
                ),
                sym_std
        );
        auto sym_d = EndgenousSymbol::Make("d", d);

        auto d1 = BinaryOperator::Add(
                sym_d,
                BinaryOperator::Mul(
                        Constant::Make(0.5),
                        sym_std
                )
        );
        auto sym_d1 = EndgenousSymbol::Make("d1", d1);

        auto d2 = BinaryOperator::Sub(
                sym_d,
                BinaryOperator::Mul(
                        Constant::Make(0.5),
                        sym_std
                )
        );
        auto sym_d2 = EndgenousSymbol::Make("d2", d2);

        auto nd1 = Phi::Make(sym_d1);
        auto sym_nd1 = EndgenousSymbol::Make("nd1", nd1);

        auto nd2 = Phi::Make(sym_d2);
        auto sym_nd2 = EndgenousSymbol::Make("nd2", nd2);

        auto c = BinaryOperator::Mul(
                sym_df,
                BinaryOperator::Sub(
                        BinaryOperator::Mul(
                                sym_F,
                                sym_nd1
                        ),
                        BinaryOperator::Mul(
                                K,
                                sym_nd2
                        )
                )
        );
        auto sym_c = EndgenousSymbol::Make("c", c);

        double S0_value = 10;
        double r_value = 0.05;
        double y_value = 0.00;
        double sig_value = 0.2;
        double K_value = 13;
        double T_value = 1;

        SymbolTable ST;
        ST("S0" , S0_value);
        ST("r"  , r_value);
        ST("y"  , y_value);
        ST("sig", sig_value);
        ST("K"  , K_value);
        ST("T"  , T_value);

        std::cout << "c->Eval(ST) => " << c->Eval(ST) << "\n"; // __CandyPrint__(cxx-print-scalar,c->Eval(ST))

        using std::exp;
        using std::sqrt;
        auto phi = [](auto x){
                return std::erfc(-x/std::sqrt(2))/2;
        };

        double df_value = exp(-r_value*T_value);
        EXPECT_FLOAT_EQ(df_value, df->Eval(ST));

        double F_value = S0_value * exp((r_value-y_value)*T_value);
        EXPECT_FLOAT_EQ(F_value, F->Eval(ST));

        double std_value = sig_value * sqrt(T_value);
        EXPECT_FLOAT_EQ(std_value, std->Eval(ST));

        double d_value = log(F_value/K_value)/std_value;
        EXPECT_FLOAT_EQ(d_value, d->Eval(ST));

        double d1_value = d_value + 0.5 * std_value;
        EXPECT_FLOAT_EQ(d1_value, d1->Eval(ST));

        double d2_value = d_value - 0.5 * std_value;
        EXPECT_FLOAT_EQ(d2_value, d2->Eval(ST));

        double nd1_value = phi(d1_value);
        EXPECT_FLOAT_EQ(nd1_value, nd1->Eval(ST));

        double nd2_value = phi(d2_value);
        EXPECT_FLOAT_EQ(nd2_value, nd2->Eval(ST));

        double c_value = df_value * ( F_value * nd1_value - K_value * nd2_value );
        EXPECT_FLOAT_EQ(c_value, c->Eval(ST));

        struct RemoveEndo : OperatorTransform{
                virtual std::shared_ptr<Operator> Apply(std::shared_ptr<Operator> const& ptr){
                        auto candidate = ptr->Clone(shared_from_this());
                        if( candidate->Kind() == OPKind_EndgenousSymbol ){
                                if( auto typed = std::dynamic_pointer_cast<EndgenousSymbol>(candidate)){
                                        return typed->Expr();
                                }
                        }
                        return candidate;
                }
        };
        auto c_raw = c->Clone(std::make_shared<RemoveEndo>());

        EXPECT_FLOAT_EQ(c->Eval(ST), c_raw->Eval(ST));

        double epsilon = 0.00001;
        auto baseline = c->Eval(ST);
        auto bump_ST = ST;
        bump_ST("sig", sig_value + epsilon);
        auto sig_bump = c->Eval(bump_ST);
        auto fd_d_sig = ( sig_bump - baseline ) / epsilon;
        std::cout << "sig_bump => " << sig_bump << "\n"; // __CandyPrint__(cxx-print-scalar,sig_bump)
        std::cout << "fd_d_sig => " << fd_d_sig << "\n"; // __CandyPrint__(cxx-print-scalar,fd_d_sig)
        std::cout << "c_raw->Diff(\"sig\")->Eval(ST) => " << c_raw->Diff("sig")->Eval(ST) << "\n"; // __CandyPrint__(cxx-print-scalar,c_raw->Diff("sig")->Eval(ST))

        double adj_S0  = 0.0;
        double adj_r   = 0.0;
        double adj_y   = 0.0;
        double adj_sig = 0.0;
        double adj_K   = 0.0;
        double adj_T   = 0.0;
        double adj_df  = 0.0;
        double adj_F   = 0.0;
        double adj_std = 0.0;
        double adj_d   = 0.0;
        double adj_d1  = 0.0;
        double adj_d2  = 0.0;
        double adj_nd1 = 0.0;
        double adj_nd2 = 0.0;
        double adj_c   = 1.0;

        EXPECT_FLOAT_EQ(F_value * nd1_value - K_value * nd2_value, c->Diff("df")->Eval(ST));
        EXPECT_FLOAT_EQ(df_value * nd1_value, c->Diff("F")->Eval(ST));
        EXPECT_FLOAT_EQ(df_value * F_value, c->Diff("nd1")->Eval(ST));
        EXPECT_FLOAT_EQ(-df_value * nd2_value, c->Diff("K")->Eval(ST));
        EXPECT_FLOAT_EQ(-df_value * K_value, c->Diff("nd2")->Eval(ST));

        adj_df   +=  adj_c * c->Diff("df")->Eval(ST);
        adj_F    +=  adj_c * c->Diff("F") ->Eval(ST);
        adj_nd1  +=  adj_c * c->Diff("nd1") ->Eval(ST);
        adj_K    +=  adj_c * c->Diff("K") ->Eval(ST);
        adj_nd2  +=  adj_c * c->Diff("nd2") ->Eval(ST);

        adj_d1   += adj_nd1 * nd1->Diff("d1")->Eval(ST);
        adj_d2   += adj_nd2 * nd2->Diff("d2")->Eval(ST);

        adj_d    += adj_d1 * d1->Diff("d1")->Eval(ST);
        adj_std  += adj_d1 * d1->Diff("std")->Eval(ST);
        adj_d    += adj_d2 * d2->Diff("d2")->Eval(ST);
        adj_std  += adj_d2 * d2->Diff("std")->Eval(ST);

        adj_F    += adj_d * d->Diff("F")->Eval(ST);
        adj_K    += adj_d * d->Diff("K")->Eval(ST);
        adj_std  += adj_d * d->Diff("std")->Eval(ST);

        adj_sig  += adj_std * std->Diff("sig")->Eval(ST);

        std::cout << "adj_sig => " << adj_sig << "\n"; // __CandyPrint__(cxx-print-scalar,adj_sig)

        #if 0

        struct StackFrame{
                std::string name;
                std::shared_ptr<Operator> Op;
        };
        std::list<StackFrame> stack{StackFrame{"c", c}};

        std::unordered_set<std::string> seen;
        for(;stack.size();){
                auto head = stack.front();
                stack.pop_front();
                auto deps = head.Op->DepthFirstAnySymbolicDependencyNoRecurse();
                for(auto ptr : deps.Set){
                        if(ptr->IsEndo() ){
                                if( seen.count(ptr->Name()) == 0){
                                        std::cout << head.name << " -> " << ptr->Name() << "\n"; // __CandyPrint__(cxx-print-scalar,ptr->Name())
                                        stack.push_back(StackFrame{ptr->Name(), std::reinterpret_pointer_cast<EndgenousSymbol>(ptr)->Expr()});
                                        seen.insert(ptr->Name());
                                }
                        } else {
                                //std::cout << head.name << " -> " << ptr->Name() << " [terminal]\n"; // __CandyPrint__(cxx-print-scalar,ptr->Name())
                        }
                }
        }

        std::cout << "\n\n";
        #endif
        auto deps = sym_c->DepthFirstAnySymbolicDependencyAndThis();

        for(auto ptr : deps.DepthFirst ){
                if( ptr->IsExo() )
                        continue;
                std::cout << "ptr->Name() => " << ptr->Name() << "\n"; // __CandyPrint__(cxx-print-scalar,ptr->Name())
        }

        std::unordered_map<std::string, double> adj;
        adj[deps.DepthFirst.back()->Name()] = 1.0; // seed

        std::unordered_map<std::string, std::shared_ptr<Operator> > adj_expr;
        for(auto head : deps.DepthFirst){
                adj_expr[head->Name()] = Constant::Make(0.0);
        }
        adj_expr[deps.DepthFirst.back()->Name()] = Constant::Make(1.0);

        for(size_t idx=deps.DepthFirst.size();idx!=0;){
                --idx;
                auto head = deps.DepthFirst[idx];
                if( head->IsExo() )
                        continue;
                auto expr = std::reinterpret_pointer_cast<EndgenousSymbol>(head)->Expr();
                auto subs = head->DepthFirstAnySymbolicDependencyNoRecurse();
                for(auto ptr : subs.DepthFirst){
                        std::cout << "adj_" << ptr->Name() << " => " << "adj_" << head->Name() << " * " << expr->Diff(ptr->Name())->Eval(ST) << "\n";
                        adj[ptr->Name()] += adj[head->Name()]*expr->Diff(ptr->Name())->Eval(ST);

                        adj_expr[ptr->Name()] = BinaryOperator::Add(
                                adj_expr[ptr->Name()],
                                BinaryOperator::Mul(
                                        adj_expr[head->Name()],
                                        expr->Diff(ptr->Name())
                                )
                        );
                }
        }
        for(auto const& p : adj){
                std::cout << std::left << std::setw(10) << p.first << " => " << p.second << "\n";
        }

        auto adj_diff = [&](auto sym){
                return adj[sym];
        };
        auto fd_diff = [&](auto sym){
                auto bump_ST = ST;
                bump_ST(sym, ST[sym] + epsilon);
                auto sig_bump = c->Eval(bump_ST);
                return ( sig_bump - baseline ) / epsilon;
        };
        auto ana_diff = [&](auto sym){
                return c_raw->Diff(sym)->Eval(ST);
        };

        std::vector<std::string> exo{ "S0", "r", "y", "sig", "K", "T"};
        for(auto sym : exo ){
                std::cout << "adj_diff(sym) => " << adj_diff(sym) << "\n"; // __CandyPrint__(cxx-print-scalar,adj_diff(sym))
                std::cout << "fd_diff(sym) => " << fd_diff(sym) << "\n"; // __CandyPrint__(cxx-print-scalar,fd_diff(sym))
                std::cout << "ana_diff(sym) => " << ana_diff(sym) << "\n"; // __CandyPrint__(cxx-print-scalar,ana_diff(sym))

                EXPECT_FLOAT_EQ( ana_diff(sym), adj_diff(sym));
        }

        InstructionBlock IB;
        for(auto head : deps.DepthFirst){
                if( head->IsExo() )
                        continue;
                auto expr = std::reinterpret_pointer_cast<EndgenousSymbol>(head)->Expr();
                auto subs = head->DepthFirstAnySymbolicDependencyNoRecurse();
                IB.Add(std::make_shared<InstructionDeclareVariable>(head->Name(), expr));
        }

        Transform::FoldZero fold_zero;
        for(auto p : adj_expr ){
                auto folded = fold_zero.Fold(p.second);
                IB.Add(std::make_shared<InstructionDeclareVariable>("adj_" + p.first, folded));
        }
        IB.Add(std::make_shared<InstructionReturn>("c"));
        IB.EmitCode(std::cout);

}

#include "Cady/Frontend.h"
struct BlackScholesCallOption{
        template<class Double>
        struct Build{
                Double Evaluate(
                        Double t,
                        Double T,
                        Double r,
                        Double S,
                        Double K,
                        Double vol )const
                {
                        using MathFunctions::Phi;
                        using MathFunctions::Exp;
                        using MathFunctions::Pow;
                        using MathFunctions::Log;

                        Double d1 = ((1.0 / ( vol * Pow((T - t),0.5) )) * ( Log(S / K) +   (r + ( Pow(vol,2.0) ) / 2 ) * (T - t) ));
                        Double d2 = d1 - vol * (T - t);
                        Double pv = K * Exp( -r * ( T - t ) );
                        Double black = Phi(d1) * S - Phi(d2) * pv;
                        return black;
                }
        };
};

TEST(Kernel,Black){
        auto ad_kernel = BlackScholesCallOption::Build<DoubleKernel>{};

        auto as_black = ad_kernel.Evaluate(
                DoubleKernel::BuildFromExo("t"),
                DoubleKernel::BuildFromExo("T"),
                DoubleKernel::BuildFromExo("r"),
                DoubleKernel::BuildFromExo("S"),
                DoubleKernel::BuildFromExo("K"),
                DoubleKernel::BuildFromExo("vol")
        );

        auto expr = as_black.as_operator_();

        InstructionBlock IB;
        auto deps = expr->DepthFirstAnySymbolicDependencyAndThis();
        for(auto head : deps.DepthFirst){
                if( head->IsExo() )
                        continue;
                auto expr = std::reinterpret_pointer_cast<EndgenousSymbol>(head)->Expr();
                auto subs = head->DepthFirstAnySymbolicDependencyNoRecurse();
                IB.Add(std::make_shared<InstructionDeclareVariable>(head->Name(), expr));
        }
        IB.Add(std::make_shared<InstructionReturn>(expr->Name()));
        IB.EmitCode(std::cout);

        std::cout << "expr->Kind() => " << expr->Kind() << "\n"; // __CandyPrint__(cxx-print-scalar,expr->Kind())
}

/*
        struct BaseName{
                struct ResultType{
                        double Fields[0];
                        double Fields[1];
                        ...
                        double Fields[n-1];
                };
                virtual ~BaseBame()=default;
                virtual ResultType FunctionType(double Args[0],
                                                double Args[1],
                                                ...
                                                double Args[m-1])const=0;
                using map_ty = std::unordered_map<std::string, std::shared_ptr<BaseName> >;
                static map_ty& AllImplementations(){
                        static map_ty;
                        return map_ty;
                }
        };

 */
struct ProfileFunctionDefinition{
        std::string BaseName;
        std::string FunctionName;
        std::vector<std::string> Args;
        std::vector<std::string> Fields;
};

struct ProfileFunction{
        std::string ImplName;
        std::shared_ptr<InstructionBlock> IB;
        std::unordered_map<std::string, std::string> ResultMapping;
};

struct ProfileCodeGen{
        explicit ProfileCodeGen(std::shared_ptr<ProfileFunctionDefinition> const& def):def_{def}{}
        void AddImplementation(std::shared_ptr<ProfileFunction> func){
                vec_.push_back(func);
        }
        void EmitCode(std::ostream& out = std::cout){
                out << "#include <vector>\n";
                out << "#include <unordered_map>\n";
                out << "#include <cstdio>\n";
                out << "#include <cmath>\n";
                out << "#include <iostream>\n";
                out << "#include <memory>\n";
                out << "#include <boost/timer/timer.hpp>\n";
                out << "#include <boost/lexical_cast.hpp>\n";
                out << "\n";
                out << "\n";
                out << "struct " << def_->BaseName << "{\n";
                out << "    struct ResultType{\n";
                for(auto const& field : def_->Fields ){
                out << "        double " << field << " = 0.0;\n";
                };
                out << "    };\n";
                out << "    virtual ~" << def_->BaseName << "()=default;\n";
                out << "    virtual ResultType " << def_->FunctionName << "(\n";
                for(size_t idx=0;idx!=def_->Args.size();++idx){
                out << "        " << (idx==0?"  ":", ") << "double " << def_->Args[idx] << (idx+1==def_->Args.size()?")const=0;\n":"\n");
                }
                out << "    using map_ty = std::unordered_map<std::string, std::shared_ptr<" << def_->BaseName << "> >;\n";
                out << "    static map_ty& AllImplementations(){\n";
                out << "            static map_ty mem;\n";
                out << "            return mem;\n";
                out << "    }\n";
                out << "};\n";
                out << "\n";
                auto RegisterBaseBame = "Register" + def_->BaseName;
                out << "template<class T>\n";
                out << "struct " << RegisterBaseBame << "{\n";
                out << "    template<class... Args>\n";
                out << "    " << RegisterBaseBame << "(std::string const& name, Args&&... args){\n";
                out << "        " << def_->BaseName << "::AllImplementations()[name] = std::make_shared<T>(std::forward<Args>(args)...);\n";
                out << "    }\n";
                out << "};\n";
                out << "\n";

                for(auto func : vec_){
                out << "struct " << func->ImplName << " : " << def_->BaseName << "{\n";
                out << "    virtual ResultType " << def_->FunctionName << "(\n";
                for(size_t idx=0;idx!=def_->Args.size();++idx){
                out << "        " << (idx==0?"  ":", ") << "double " << def_->Args[idx] << (idx+1==def_->Args.size()?")const override{\n":"\n");
                }
                func->IB->EmitCode(out);
                out << "        ResultType result;\n";
                for(auto const& p : func->ResultMapping){
                out << "        result." << p.first << " = " << p.second << ";\n";
                }
                out << "        return result;\n";
                out << "    }\n";
                out << "};\n";
                out << "static " << RegisterBaseBame << "<" << func->ImplName << "> Reg" << func->ImplName << "(\"" << func->ImplName << "\");\n";
                }

                out << "int main(){\n";
                out << "    enum{ InitialCount = 10000 };\n";
                out << "    double t       = 0.0;\n";
                out << "    double T       = 10.0;\n";
                out << "    double r       = 0.04;\n";
                out << "    double S       = 50;\n";
                out << "    double K       = 60;\n";
                out << "    double vol     = 0.2;\n";
                out << "    double epsilon = 1e-10;\n";
                out << "    auto impls = " << def_->BaseName << "::AllImplementations();\n";
                out << "    std::vector<std::string> header{\"\"};\n";
                out << "    for(auto impl : impls ){\n";
                out << "        header.push_back(impl.first);\n";
                out << "    }\n";
                out << "    for(auto item : header){ std::cout << item << ','; } std::cout << '\\n';\n";
                out << "    std::vector<std::string> check;\n";
                out << "    auto proto_name = \"NaiveBlack\";\n";
                out << "    auto proto = impls[proto_name]\n;";
                out << "    auto baseline = proto->" << def_->FunctionName << "(t,T,r,S,K,vol);\n";
                for(auto const& field : def_->Fields ){
                out << "    check.clear();\n";
                out << "    check.push_back(\"" << field << "\");\n";
                out << "    for(auto impl : impls ){\n";
                out << "        check.push_back(std::to_string(impl.second->" << def_->FunctionName << "(t,T,r,S,K,vol)." << field << "));\n";
                out << "    }\n";
                out << "    check.push_back(std::to_string((proto->" << def_->FunctionName << "(";
                for(size_t idx=0;idx!=def_->Args.size();++idx){
                out << (idx==0?"": ",") << def_->Args[idx];
                if("d_" + def_->Args[idx] == field ){
                        out << "+epsilon";
                }
                }
                out << ").c-baseline.c)/epsilon));\n";

                out << "    for(auto item : check){ std::cout << item << ','; } std::cout << '\\n';\n";
                }
                out << "    for(volatile size_t N = 100;;N*=2){\n";
                out << "        std::vector<std::string> line;\n";
                out << "        line.push_back(boost::lexical_cast<std::string>(N));\n";
                out << "        for(auto impl : impls ){\n";
                out << "            boost::timer::cpu_timer timer;\n";
                out << "            for(volatile size_t idx=0;idx!=N;++idx){\n";
                out << "                auto result = impl.second->" << def_->FunctionName << "(t,T,r,S,K,vol);\n";
                out << "            }\n";
                out << "            line.push_back(timer.format(4, \"%w\"));\n";
                out << "        }\n";
                out << "        for(auto item : line){ std::cout << item << ','; } std::cout << '\\n';\n";
                out << "    }\n";
                out << "}\n";

        }
private:
        std::shared_ptr<ProfileFunctionDefinition> def_;
        std::vector<std::shared_ptr<ProfileFunction> > vec_;
};

struct NaiveBlackProfile : ProfileFunction{
        NaiveBlackProfile(){
                ImplName = "NaiveBlack";
                auto ad_kernel = BlackScholesCallOption::Build<DoubleKernel>{};

                auto as_black = ad_kernel.Evaluate(
                        DoubleKernel::BuildFromExo("t"),
                        DoubleKernel::BuildFromExo("T"),
                        DoubleKernel::BuildFromExo("r"),
                        DoubleKernel::BuildFromExo("S"),
                        DoubleKernel::BuildFromExo("K"),
                        DoubleKernel::BuildFromExo("vol")
                );

                auto expr = as_black.as_operator_();

                IB = std::make_shared<InstructionBlock>();
                auto deps = expr->DepthFirstAnySymbolicDependencyAndThis();
                for(auto head : deps.DepthFirst){
                        if( head->IsExo() )
                                continue;
                        auto expr = std::reinterpret_pointer_cast<EndgenousSymbol>(head)->Expr();
                        IB->Add(std::make_shared<InstructionDeclareVariable>(head->Name(), expr));
                }
                ResultMapping["c"] = deps.DepthFirst.back()->Name();
        }
};

struct NaiveBlackSingleProfile : ProfileFunction{
        NaiveBlackSingleProfile(){
                ImplName = "NaiveBlackSingle";
                auto ad_kernel = BlackScholesCallOption::Build<DoubleKernel>{};

                auto as_black = ad_kernel.Evaluate(
                        DoubleKernel::BuildFromExo("t"),
                        DoubleKernel::BuildFromExo("T"),
                        DoubleKernel::BuildFromExo("r"),
                        DoubleKernel::BuildFromExo("S"),
                        DoubleKernel::BuildFromExo("K"),
                        DoubleKernel::BuildFromExo("vol")
                );

                auto expr = as_black.as_operator_();

                struct RemoveEndo : OperatorTransform{
                        virtual std::shared_ptr<Operator> Apply(std::shared_ptr<Operator> const& ptr){
                                auto candidate = ptr->Clone(shared_from_this());
                                if( candidate->Kind() == OPKind_EndgenousSymbol ){
                                        if( auto typed = std::dynamic_pointer_cast<EndgenousSymbol>(candidate)){
                                                return typed->Expr();
                                        }
                                }
                                return candidate;
                        }
                };
                auto single_expr = std::reinterpret_pointer_cast<EndgenousSymbol>(expr)->Expr()->Clone(std::make_shared<RemoveEndo>());

                IB = std::make_shared<InstructionBlock>();
                IB->Add(std::make_shared<InstructionDeclareVariable>("c", single_expr));
                ResultMapping["c"] = "c";

        }
};
struct RemapUnique : OperatorTransform{
        explicit RemapUnique(std::string const& prefix = "__symbol_")
                : prefix_{prefix}
        {
                std::cerr << " RemapUnique()\n";
        }
        ~RemapUnique(){
                std::cerr << "~RemapUnique()\n";
        }
        void mutate_prefix(std::string const& prefix){
                prefix_ = prefix;
        }
        virtual std::shared_ptr<Operator> Apply(std::shared_ptr<Operator> const& ptr){

                auto candidate = ptr->Clone(shared_from_this());

                auto key = std::make_tuple(
                        candidate->NameInvariantOfChildren(),
                        candidate->Children()
                        );

                auto iter = ops_.find(key);
                if( iter != ops_.end() )
                        return iter->second;

                if(
                    candidate->Kind() != OPKind_EndgenousSymbol &&
                    #if 0
                    candidate->Kind() != OPKind_ExogenousSymbol &&
                    #endif
                    candidate->Kind() != OPKind_Constant )
                {

                        std::stringstream ss;
                        ss << prefix_ << (ops_.size()+1);
                        auto endogous_sym = EndgenousSymbol::Make(ss.str(), candidate);

                        ops_[key] = endogous_sym;
                        return endogous_sym;
                } else {
                        ops_[key] = candidate;
                        return candidate;
                }
        }
private:
        std::string prefix_;
        std::map<
                std::tuple<
                        std::string,
                        std::vector<std::shared_ptr<Operator> >
                >,
                std::shared_ptr<Operator>
        > ops_;
};
struct NaiveBlackThreeAddressProfile : ProfileFunction{
        NaiveBlackThreeAddressProfile(){
                ImplName = "NaiveBlackThreeAddress";
                auto ad_kernel = BlackScholesCallOption::Build<DoubleKernel>{};

                auto as_black = ad_kernel.Evaluate(
                        DoubleKernel::BuildFromExo("t"),
                        DoubleKernel::BuildFromExo("T"),
                        DoubleKernel::BuildFromExo("r"),
                        DoubleKernel::BuildFromExo("S"),
                        DoubleKernel::BuildFromExo("K"),
                        DoubleKernel::BuildFromExo("vol")
                );

                auto expr = as_black.as_operator_();
                //auto unique = std::reinterpret_pointer_cast<EndgenousSymbol>(expr)->Expr()->Clone(std::make_shared<RemapUnique>());
                auto unique = expr->Clone(std::make_shared<RemapUnique>());

                IB = std::make_shared<InstructionBlock>();
                auto deps = unique->DepthFirstAnySymbolicDependencyAndThis();
                for(auto head : deps.DepthFirst){
                        if( head->IsExo() )
                                continue;
                        auto expr = std::reinterpret_pointer_cast<EndgenousSymbol>(head)->Expr();
                        IB->Add(std::make_shared<InstructionDeclareVariable>(head->Name(), expr));
                }
                ResultMapping["c"] = deps.DepthFirst.back()->Name();

        }
};

struct NaiveBlackForwardThreeAddressProfile : ProfileFunction{
        NaiveBlackForwardThreeAddressProfile(){
                ImplName = "NaiveBlackForwardThreeAddress";
                auto ad_kernel = BlackScholesCallOption::Build<DoubleKernel>{};

                auto as_black = ad_kernel.Evaluate(
                        DoubleKernel::BuildFromExo("t"),
                        DoubleKernel::BuildFromExo("T"),
                        DoubleKernel::BuildFromExo("r"),
                        DoubleKernel::BuildFromExo("S"),
                        DoubleKernel::BuildFromExo("K"),
                        DoubleKernel::BuildFromExo("vol")
                );

                auto expr = as_black.as_operator_();
                //auto unique = std::reinterpret_pointer_cast<EndgenousSymbol>(expr)->Expr()->Clone(std::make_shared<RemapUnique>());
                auto RU = std::make_shared<RemapUnique>();
                auto unique = expr->Clone(RU);

                std::unordered_set<std::string> three_addr_seen;

                IB = std::make_shared<InstructionBlock>();
                auto deps = unique->DepthFirstAnySymbolicDependencyAndThis();
                for(auto head : deps.DepthFirst){
                        if( head->IsExo() )
                                continue;
                        if( three_addr_seen.count(head->Name()) == 0 ){
                                auto expr = std::reinterpret_pointer_cast<EndgenousSymbol>(head)->Expr();
                                IB->Add(std::make_shared<InstructionDeclareVariable>(head->Name(), expr));
                                three_addr_seen.insert(head->Name());
                        }
                }
                ResultMapping["c"] = deps.DepthFirst.back()->Name();

                struct RemoveEndo : OperatorTransform{
                        virtual std::shared_ptr<Operator> Apply(std::shared_ptr<Operator> const& ptr){
                                auto candidate = ptr->Clone(shared_from_this());
                                if( candidate->Kind() == OPKind_EndgenousSymbol ){
                                        if( auto typed = std::dynamic_pointer_cast<EndgenousSymbol>(candidate)){
                                                return typed->Expr();
                                        }
                                }
                                return candidate;
                        }
                };
                auto single_expr = std::reinterpret_pointer_cast<EndgenousSymbol>(expr)->Expr()->Clone(std::make_shared<RemoveEndo>());

                std::vector<std::string> exo{ "t", "T", "r", "S", "K", "vol" };
                #if 1
                for(auto sym : exo ){
                        auto sym_diff = single_expr->Diff(sym);
                        #if 0
                        auto sym_diff_unique = EndgenousSymbol::Make("d_"+sym,sym_diff->Clone(std::make_shared<RemapUnique>("__d_" + sym)));
                        #endif
                        auto sym_diff_unique = EndgenousSymbol::Make("d_"+sym,sym_diff->Clone(RU));
                        auto deps = sym_diff_unique->DepthFirstAnySymbolicDependencyAndThis();
                        for(auto head : deps.DepthFirst){
                                if( head->IsExo() )
                                        continue;
                                if( three_addr_seen.count(head->Name()) == 0 ){
                                        auto expr = std::reinterpret_pointer_cast<EndgenousSymbol>(head)->Expr();
                                        IB->Add(std::make_shared<InstructionDeclareVariable>(head->Name(), expr));
                                        three_addr_seen.insert(head->Name());
                                }
                        }
                        ResultMapping["d_"+sym] = deps.DepthFirst.back()->Name();
                }
                #else
                for(auto sym : exo ){
                        IB->Add(std::make_shared<InstructionDeclareVariable>("d_"+sym, single_expr->Diff(sym)));
                        ResultMapping["d_"+sym] = "d_" + sym;
                }
                #endif
        }
};

struct NaiveBlackADThreeAddressProfile : ProfileFunction{
        NaiveBlackADThreeAddressProfile(){
                ImplName = "NaiveBlackADThreeAddress";
                auto ad_kernel = BlackScholesCallOption::Build<DoubleKernel>{};

                auto as_black = ad_kernel.Evaluate(
                        DoubleKernel::BuildFromExo("t"),
                        DoubleKernel::BuildFromExo("T"),
                        DoubleKernel::BuildFromExo("r"),
                        DoubleKernel::BuildFromExo("S"),
                        DoubleKernel::BuildFromExo("K"),
                        DoubleKernel::BuildFromExo("vol")
                );

                auto expr = as_black.as_operator_();
                //auto unique = std::reinterpret_pointer_cast<EndgenousSymbol>(expr)->Expr()->Clone(std::make_shared<RemapUnique>());
                auto RU = std::make_shared<RemapUnique>();
                auto unique = expr->Clone(RU);

                std::unordered_set<std::string> three_addr_seen;

                IB = std::make_shared<InstructionBlock>();
                auto deps = unique->DepthFirstAnySymbolicDependencyAndThis();
                for(auto head : deps.DepthFirst){
                        if( head->IsExo() )
                                continue;
                        if( three_addr_seen.count(head->Name()) == 0 ){
                                auto expr = std::reinterpret_pointer_cast<EndgenousSymbol>(head)->Expr();
                                IB->Add(std::make_shared<InstructionDeclareVariable>(head->Name(), expr));
                                three_addr_seen.insert(head->Name());
                        }
                }
                ResultMapping["c"] = deps.DepthFirst.back()->Name();

                struct RemoveEndo : OperatorTransform{
                        virtual std::shared_ptr<Operator> Apply(std::shared_ptr<Operator> const& ptr){
                                auto candidate = ptr->Clone(shared_from_this());
                                if( candidate->Kind() == OPKind_EndgenousSymbol ){
                                        if( auto typed = std::dynamic_pointer_cast<EndgenousSymbol>(candidate)){
                                                return typed->Expr();
                                        }
                                }
                                return candidate;
                        }
                };
                auto single_expr = std::reinterpret_pointer_cast<EndgenousSymbol>(expr)->Expr()->Clone(std::make_shared<RemoveEndo>());
                auto expr_deps = unique->DepthFirstAnySymbolicDependencyAndThis();

                std::unordered_map<std::string, std::shared_ptr<Operator> > adj_expr;
                for(auto head : expr_deps.DepthFirst){
                        adj_expr[head->Name()] = Constant::Make(0.0);
                }
                adj_expr[expr_deps.DepthFirst.back()->Name()] = Constant::Make(1.0);

                for(size_t idx=expr_deps.DepthFirst.size();idx!=0;){
                        --idx;
                        auto head = expr_deps.DepthFirst[idx];
                        if( head->IsExo() )
                                continue;
                        auto expr = std::reinterpret_pointer_cast<EndgenousSymbol>(head)->Expr();
                        auto subs = head->DepthFirstAnySymbolicDependencyNoRecurse();
                        for(auto ptr : subs.DepthFirst){
                                adj_expr[ptr->Name()] = BinaryOperator::Add(
                                        adj_expr[ptr->Name()],
                                        BinaryOperator::Mul(
                                                adj_expr[head->Name()],
                                                expr->Diff(ptr->Name())
                                        )
                                );
                        }
                }
                for(auto p : adj_expr ){
                        std::cout << p.first << " => ";
                        p.second->EmitCode(std::cout);
                        std::cout << "\n";
                }
                std::vector<std::string> exo{ "t", "T", "r", "S", "K", "vol" };
                for(auto sym : exo ){
                        auto sym_diff_unique = EndgenousSymbol::Make("d_"+sym,adj_expr[sym]->Clone(RU));
                        auto deps = sym_diff_unique->DepthFirstAnySymbolicDependencyAndThis();
                        for(auto head : deps.DepthFirst){
                                if( head->IsExo() )
                                        continue;
                                if( three_addr_seen.count(head->Name()) == 0 ){
                                        auto expr = std::reinterpret_pointer_cast<EndgenousSymbol>(head)->Expr();
                                        IB->Add(std::make_shared<InstructionDeclareVariable>(head->Name(), expr));
                                        three_addr_seen.insert(head->Name());
                                }
                        }
                        ResultMapping["d_"+sym] = deps.DepthFirst.back()->Name();
                }
        }
};

TEST(ProfileGen,A){
        auto def = std::make_shared<ProfileFunctionDefinition>();
        def->BaseName = "BlackPricer";
        def->FunctionName = "Black";
        def->Args.push_back("t");
        def->Args.push_back("T");
        def->Args.push_back("r");
        def->Args.push_back("S");
        def->Args.push_back("K");
        def->Args.push_back("vol");
        def->Fields.push_back("c");
        def->Fields.push_back("d_t");
        def->Fields.push_back("d_T");
        def->Fields.push_back("d_r");
        def->Fields.push_back("d_S");
        def->Fields.push_back("d_K");
        def->Fields.push_back("d_vol");
        ProfileCodeGen cg(def);

        cg.AddImplementation(std::make_shared<NaiveBlackProfile>());
        cg.AddImplementation(std::make_shared<NaiveBlackSingleProfile>());
        cg.AddImplementation(std::make_shared<NaiveBlackThreeAddressProfile>());
        cg.AddImplementation(std::make_shared<NaiveBlackForwardThreeAddressProfile>());
        cg.AddImplementation(std::make_shared<NaiveBlackADThreeAddressProfile>());

        cg.EmitCode();

        std::ofstream out("pc.cxx");
        cg.EmitCode(out);
}


//==================================================================================================
/*!

  Copyright 2015 NumScale SAS

  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/simd/constant/maxlog2.hpp>
#include <boost/simd/as.hpp>
#include <simd_test.hpp>

STF_CASE_TPL( "Check maxlog2 behavior for integral types"
            , (std::uint32_t)(std::uint64_t)
              (std::int32_t)(std::int64_t)
            )
{
  using boost::simd::as;
  using boost::simd::detail::maxlog2;
  using boost::simd::Maxlog2;
  T ref = T(0);
  STF_TYPE_IS(decltype(Maxlog2<T>()), T);
  STF_EQUAL(Maxlog2<T>(), ref);
  STF_EQUAL(maxlog2( as(T{}) ),ref);
}

STF_CASE_TPL( "Check maxlog2 behavior for double"
            , (double)
            )
{
  using boost::simd::as;
  using boost::simd::detail::maxlog2;
  using boost::simd::Maxlog2;
  T ref = T(1023);

  STF_TYPE_IS(decltype(Maxlog2<T>()), T);
  STF_IEEE_EQUAL(Maxlog2<T>(), ref);
  STF_IEEE_EQUAL(maxlog2( as(T{}) ), ref);
}

STF_CASE_TPL( "Check maxlog2 behavior for float"
            , (float)
            )
{
  using boost::simd::as;
  using boost::simd::detail::maxlog2;
  using boost::simd::Maxlog2;
  T ref = T(127);
  STF_TYPE_IS(decltype(Maxlog2<T>()), T);
  STF_IEEE_EQUAL(Maxlog2<T>(), ref);
  STF_IEEE_EQUAL(maxlog2( as(T{}) ), ref);
}


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_CONSTANT_FACT_12_HPP_INCLUDED
#define BOOST_SIMD_CONSTANT_FACT_12_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{
  /*!
    @ingroup group-constant

    Generate 12! that is 479001600

    @par Semantic:

    @code
    T r = Fact_12<T>();
    @endcode

    is similar to:

    @code
    T r = T(479001600);
    @endcode

    @return The Fact_12 constant for the proper type
  **/
  template<typename T> T Fact_12();

  namespace functional
  {
    /*!
      @ingroup group-callable-constant
      Generate the  constant fact_12.

      @return The Fact_12 constant for the proper type
    **/
    Value Fact_12<Value>();
  }
} }
#endif

#include <boost/simd/constant/definition/fact_12.hpp>
#include <boost/simd/arch/common/scalar/constant/constant_value.hpp>
#include <boost/simd/arch/common/simd/constant/constant_value.hpp>

#endif


#include <iostream>
#include <cstdint>

#include <boost/safe_numerics/safe_integer.hpp>

using namespace std;
using namespace boost::safe_numerics;

void f(const unsigned int & x, const int8_t & y){
    cout << x * y << endl;
}
void safe_f(
    const safe<unsigned int> & x,
    const safe<int8_t> & y
){
    cout << x * y << endl;
}

int main(){
    cout << "example 4: ";
    cout << "mixing types produces surprising results" << endl;
    try {
        std::cout << "Not using safe numerics" << std::endl;
        // problem: mixing types produces surprising results.
        f(100, 100);  // works as expected
        f(100, -100); // wrong result - unnoticed
        cout << "error NOT detected!" << endl;;
    }
    catch(const std::exception & e){
        // never arrive here
        cout << "error detected:" << e.what() << endl;;
    }
    try {
        // solution: use safe types
        std::cout << "Using safe numerics" << std::endl;
        safe_f(100, 100);  // works as expected
        safe_f(100, -100); // throw error
        cout << "error NOT detected!" << endl;;
    }
    catch(const std::exception & e){
        cout << "error detected:" << e.what() << endl;;
    }
    return 0;
}


/*
@copyright Louis Dionne 2015
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/ext/std/integral_constant.hpp>
#include <boost/hana/tuple.hpp>

#include <laws/comparable.hpp>
#include <laws/orderable.hpp>

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

int main() {
    //////////////////////////////////////////////////////////////////////////
    // Setup for the laws below
    //////////////////////////////////////////////////////////////////////////
    auto ints = make<Tuple>(
        std::integral_constant<int, -10>{},
        std::integral_constant<int, -2>{},
        std::integral_constant<int, 0>{},
        std::integral_constant<int, 1>{},
        std::integral_constant<int, 3>{}
    );

    //////////////////////////////////////////////////////////////////////////
    // Comparable and Orderable
    //////////////////////////////////////////////////////////////////////////
    {
        // laws
        test::TestComparable<ext::std::IntegralConstant<int>>{ints};
        test::TestOrderable<ext::std::IntegralConstant<int>>{ints};
    }
}


/*
 Copyright (C) 2018 Quaternion Risk Management Ltd
 All rights reserved.

 This file is part of ORE, a free-software/open-source library
 for transparent pricing and risk analysis - http://opensourcerisk.org

 ORE is free software: you can redistribute it and/or modify it
 under the terms of the Modified BSD License.  You should have received a
 copy of the license along with this program.
 The license is also available online at <http://opensourcerisk.org>

 This program is distributed on the basis that it will form a useful
 contribution to risk analytics and model standardisation, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
*/

#include "toplevelfixture.hpp"
#include <boost/assign/list_of.hpp>
#include <boost/make_shared.hpp>
#include <boost/test/unit_test.hpp>
#include <ql/math/interpolations/all.hpp>
#include <ql/quotes/simplequote.hpp>
#include <ql/termstructures/yield/flatforward.hpp>
#include <ql/termstructures/yield/zerocurve.hpp>
#include <ql/time/daycounters/actual365fixed.hpp>

#include <qle/termstructures/pricecurve.hpp>
#include <qle/termstructures/pricetermstructureadapter.hpp>

using namespace std;

using namespace boost::unit_test_framework;
using namespace boost::assign;

using namespace QuantLib;
using namespace QuantExt;

namespace {

class CommonData {
public:
    SavedSettings backup;

    Real tolerance;
    boost::shared_ptr<SimpleQuote> flatZero;
    DayCounter dayCounter;
    boost::shared_ptr<SimpleQuote> spotPrice;
    vector<Period> priceTenors;
    vector<boost::shared_ptr<SimpleQuote> > priceQuotes;

    CommonData() : tolerance(1e-10), priceTenors(4), priceQuotes(4) {
        flatZero = boost::make_shared<SimpleQuote>(0.015);
        dayCounter = Actual365Fixed();

        spotPrice = boost::make_shared<SimpleQuote>(14.5);
        priceTenors[0] = 6 * Months;
        priceQuotes[0] = boost::make_shared<SimpleQuote>(16.7);
        priceTenors[1] = 1 * Years;
        priceQuotes[1] = boost::make_shared<SimpleQuote>(19.9);
        priceTenors[2] = 2 * Years;
        priceQuotes[2] = boost::make_shared<SimpleQuote>(28.5);
        priceTenors[3] = 5 * Years;
        priceQuotes[3] = boost::make_shared<SimpleQuote>(38.8);
    }
};

} // namespace

BOOST_FIXTURE_TEST_SUITE(QuantExtTestSuite, qle::test::TopLevelFixture)

BOOST_AUTO_TEST_SUITE(PriceTermStructureAdapterTest)

BOOST_AUTO_TEST_CASE(testImpliedZeroRates) {

    BOOST_TEST_MESSAGE("Testing implied zero rates from PriceTermStructureAdapter");

    CommonData td;

    // Set arbitrary evaluation date
    Date asof(27, Feb, 2018);
    Settings::instance().evaluationDate() = asof;

    // Discount curve
    boost::shared_ptr<YieldTermStructure> discount =
        boost::make_shared<FlatForward>(0, NullCalendar(), Handle<Quote>(td.flatZero), td.dayCounter);

    // Price curve
    vector<Time> times(td.priceTenors.size());
    vector<Handle<Quote> > prices(td.priceTenors.size());
    for (Size i = 0; i < td.priceTenors.size(); i++) {
        times[i] = td.dayCounter.yearFraction(asof, asof + td.priceTenors[i]);
        prices[i] = Handle<Quote>(td.priceQuotes[i]);
    }
    boost::shared_ptr<PriceTermStructure> priceCurve =
        boost::make_shared<InterpolatedPriceCurve<Linear> >(times, prices, td.dayCounter);

    // Adapted price curve i.e. implied yield termstructure
    PriceTermStructureAdapter priceAdapter(td.spotPrice, priceCurve, discount);

    // Check the implied zero rates
    for (Size i = 0; i < times.size(); i++) {
        Real impliedZero = priceAdapter.zeroRate(times[i], Continuous);
        Real expectedZero =
            td.flatZero->value() - std::log(td.priceQuotes[i]->value() / td.spotPrice->value()) / times[i];
        BOOST_CHECK_CLOSE(impliedZero, expectedZero, td.tolerance);
    }

    // Bump price curve and check again
    for (Size i = 0; i < td.priceTenors.size(); i++) {
        td.priceQuotes[i]->setValue(td.priceQuotes[i]->value() * 1.10);
    }

    // Check the implied zero rates
    for (Size i = 0; i < times.size(); i++) {
        Real impliedZero = priceAdapter.zeroRate(times[i], Continuous);
        Real expectedZero =
            td.flatZero->value() - std::log(td.priceQuotes[i]->value() / td.spotPrice->value()) / times[i];
        BOOST_CHECK_CLOSE(impliedZero, expectedZero, td.tolerance);
    }

    // Bump spot price and check again
    td.spotPrice->setValue(td.spotPrice->value() * 1.05);
    for (Size i = 0; i < times.size(); i++) {
        Real impliedZero = priceAdapter.zeroRate(times[i], Continuous);
        Real expectedZero =
            td.flatZero->value() - std::log(td.priceQuotes[i]->value() / td.spotPrice->value()) / times[i];
        BOOST_CHECK_CLOSE(impliedZero, expectedZero, td.tolerance);
    }

    // Bump discount curve and check again
    td.flatZero->setValue(td.flatZero->value() * 0.9);
    for (Size i = 0; i < times.size(); i++) {
        Real impliedZero = priceAdapter.zeroRate(times[i], Continuous);
        Real expectedZero =
            td.flatZero->value() - std::log(td.priceQuotes[i]->value() / td.spotPrice->value()) / times[i];
        BOOST_CHECK_CLOSE(impliedZero, expectedZero, td.tolerance);
    }
}

BOOST_AUTO_TEST_CASE(testFloatingDiscountFixedPrice) {

    BOOST_TEST_MESSAGE("Testing behaviour of PriceTermStructureAdapter with floating reference discount curve and "
                       "fixed reference price curve");

    CommonData td;

    // Set arbitrary evaluation date
    Date asof(27, Feb, 2018);
    Settings::instance().evaluationDate() = asof;

    // Discount curve (floating reference)
    boost::shared_ptr<YieldTermStructure> floatReferenceDiscountCurve =
        boost::make_shared<FlatForward>(0, NullCalendar(), Handle<Quote>(td.flatZero), td.dayCounter);

    // Price curve (fixed reference)
    vector<Date> dates(td.priceTenors.size() + 1);
    vector<Handle<Quote> > prices(td.priceTenors.size() + 1);
    dates[0] = asof;
    prices[0] = Handle<Quote>(td.spotPrice);
    for (Size i = 0; i < td.priceTenors.size(); i++) {
        dates[i + 1] = asof + td.priceTenors[i];
        prices[i + 1] = Handle<Quote>(td.priceQuotes[i]);
    }
    boost::shared_ptr<PriceTermStructure> fixedReferencePriceCurve =
        boost::make_shared<InterpolatedPriceCurve<Linear> >(dates, prices, td.dayCounter);

    // Check construction of adapted price curve passes => reference dates same on construction
    BOOST_REQUIRE_NO_THROW(
        PriceTermStructureAdapter(td.spotPrice, fixedReferencePriceCurve, floatReferenceDiscountCurve));

    // Construct adapted price curve
    PriceTermStructureAdapter adaptedPriceCurve(td.spotPrice, fixedReferencePriceCurve, floatReferenceDiscountCurve);
    BOOST_CHECK_NO_THROW(adaptedPriceCurve.zeroRate(0.5, Continuous));

    // Change Settings::instance().evaluationDate() - discount curve reference date changes and price curve's does not
    Settings::instance().evaluationDate() = asof + 1 * Days;
    BOOST_CHECK_THROW(adaptedPriceCurve.zeroRate(0.5, Continuous), Error);
}

BOOST_AUTO_TEST_CASE(testFixedDiscountFloatingPrice) {

    BOOST_TEST_MESSAGE("Testing behaviour of PriceTermStructureAdapter with fixed reference discount curve and "
                       "floating reference price curve");

    CommonData td;

    // Set arbitrary evaluation date
    Date asof(27, Feb, 2018);
    Settings::instance().evaluationDate() = asof;

    // Discount curve (fixed reference)
    boost::shared_ptr<YieldTermStructure> floatReferenceDiscountCurve =
        boost::make_shared<FlatForward>(asof, Handle<Quote>(td.flatZero), td.dayCounter);

    // Price curve (floating reference)
    vector<Time> times(td.priceTenors.size());
    vector<Handle<Quote> > prices(td.priceTenors.size());
    for (Size i = 0; i < td.priceTenors.size(); i++) {
        times[i] = td.dayCounter.yearFraction(asof, asof + td.priceTenors[i]);
        prices[i] = Handle<Quote>(td.priceQuotes[i]);
    }
    boost::shared_ptr<PriceTermStructure> fixedReferencePriceCurve =
        boost::make_shared<InterpolatedPriceCurve<Linear> >(times, prices, td.dayCounter);

    // Check construction of adapted price curve passes => reference dates same on construction
    BOOST_REQUIRE_NO_THROW(
        PriceTermStructureAdapter(td.spotPrice, fixedReferencePriceCurve, floatReferenceDiscountCurve));

    // Construct adapted price curve
    PriceTermStructureAdapter adaptedPriceCurve(td.spotPrice, fixedReferencePriceCurve, floatReferenceDiscountCurve);
    BOOST_CHECK_NO_THROW(adaptedPriceCurve.zeroRate(0.5, Continuous));

    // Change Settings::instance().evaluationDate() - price curve reference date changes and discount curve's does not
    Settings::instance().evaluationDate() = asof + 1 * Days;
    BOOST_CHECK_THROW(adaptedPriceCurve.zeroRate(0.5, Continuous), Error);
}

BOOST_AUTO_TEST_CASE(testExtrapolation) {

    BOOST_TEST_MESSAGE("Testing extrapolation behaviour of PriceTermStructureAdapter");

    CommonData td;

    // Set arbitrary evaluation date
    Date asof(27, Feb, 2018);
    Settings::instance().evaluationDate() = asof;

    // Zero curve: times in ~ [0, 3], turn off extrapolation
    vector<Date> zeroDates(2);
    vector<Real> zeroRates(2);
    zeroDates[0] = asof;
    zeroRates[0] = td.flatZero->value();
    zeroDates[1] = asof + 3 * Years;
    zeroRates[1] = td.flatZero->value();
    boost::shared_ptr<YieldTermStructure> zeroCurve =
        boost::make_shared<ZeroCurve>(zeroDates, zeroRates, td.dayCounter);

    // Price curve: times in ~ [0.5, 5], turn off extrapolation
    vector<Time> times(td.priceTenors.size());
    vector<Handle<Quote> > prices(td.priceTenors.size());
    for (Size i = 0; i < td.priceTenors.size(); i++) {
        times[i] = td.dayCounter.yearFraction(asof, asof + td.priceTenors[i]);
        prices[i] = Handle<Quote>(td.priceQuotes[i]);
    }
    boost::shared_ptr<PriceTermStructure> priceCurve =
        boost::make_shared<InterpolatedPriceCurve<Linear> >(times, prices, td.dayCounter);

    // Check construction of adapted price curve passes
    BOOST_REQUIRE_NO_THROW(PriceTermStructureAdapter(td.spotPrice, priceCurve, zeroCurve));

    // Construct adapted price curve
    PriceTermStructureAdapter adaptedPriceCurve(td.spotPrice, priceCurve, zeroCurve);

    // Asking for zero at time ~ 1.0 should not throw
    BOOST_CHECK_NO_THROW(adaptedPriceCurve.zeroRate(1.0, Continuous));
    BOOST_CHECK_NO_THROW(adaptedPriceCurve.zeroRate(asof + 1 * Years, td.dayCounter, Continuous));

    // Asking for zero at time ~ 0.25 should throw because < 0.5, min time for price curve
    BOOST_CHECK_THROW(adaptedPriceCurve.zeroRate(0.25, Continuous), Error);
    BOOST_CHECK_THROW(adaptedPriceCurve.zeroRate(asof + 3 * Months, td.dayCounter, Continuous), Error);

    // Asking for zero at time ~ 4.0 should throw because > 3.0, max time for discount curve
    BOOST_CHECK_THROW(adaptedPriceCurve.zeroRate(4.0, Continuous), Error);
    BOOST_CHECK_THROW(adaptedPriceCurve.zeroRate(asof + 4 * Years, td.dayCounter, Continuous), Error);

    // Allow extrapolation on discount curve - should hit the max date restriction on the price curve at ~ t = 5
    zeroCurve->enableExtrapolation();
    BOOST_CHECK_THROW(adaptedPriceCurve.zeroRate(6.0, Continuous), Error);
    BOOST_CHECK_THROW(adaptedPriceCurve.zeroRate(asof + 6 * Years, td.dayCounter, Continuous), Error);

    // Allow extrapolation on price curve, expect no errors below min time or above max time
    priceCurve->enableExtrapolation();
    // above max
    BOOST_CHECK_NO_THROW(adaptedPriceCurve.zeroRate(6.0, Continuous));
    BOOST_CHECK_NO_THROW(adaptedPriceCurve.zeroRate(asof + 6 * Years, td.dayCounter, Continuous));
    // below min
    BOOST_CHECK_NO_THROW(adaptedPriceCurve.zeroRate(0.25, Continuous));
    BOOST_CHECK_NO_THROW(adaptedPriceCurve.zeroRate(asof + 3 * Months, td.dayCounter, Continuous));
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()


/*
 * Copyright (c) 2015 Aldebaran Robotics. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the COPYING file.
 */

#include <iostream>

#include <gtest/gtest.h>

#include <qi/clock.hpp>
#include <boost/chrono/chrono_io.hpp>

namespace chrono = boost::chrono;

template<class Clock>
void clock_output_()
{
  typename Clock::duration d(1);
  typename Clock::time_point t = Clock::now();
  std::cout << "name: " << boost::chrono::clock_string<Clock, char>::name() << ",\t"
            << "tick: " << d << ",\t"
            << "now: " << t << "\n";
  // same, using wide chars
  std::wcout << "name: " << boost::chrono::clock_string<Clock, wchar_t>::name() << ",\t"
             << "tick: " << d << ",\t"
             << "now: " << t << "\n";
}

TEST(QiClock, clock_output)
{
  clock_output_<qi::SteadyClock>();
  clock_output_<qi::Clock>();
  clock_output_<qi::SystemClock>();
}


//Link to Boost
#define BOOST_TEST_DYN_LINK

//Define our Module name (prints at testing)
#define BOOST_TEST_MODULE BetheSolver UnitTests

//VERY IMPORTANT - include this last
#include <boost/test/included/unit_test.hpp>
//#include <boost/test/unit_test.hpp>

#include "test.h"
#include "../PowerSeries.h"

// test suite
BOOST_FIXTURE_TEST_SUITE(ExpBetaSeries_suite, SimpleTestFixture, * utf::label("ExpBetaSeries"))

BOOST_DATA_TEST_CASE(constructor_test, bdata::random(-10, 10) ^ bdata::xrange(20), val, index)
{
    ExpBetaSeries ebs(val);

    BOOST_TEST(ebs == val);
    if (val != 0) {
        BOOST_TEST(ebs.TermNumber() == 1);
    } else {
        BOOST_TEST(ebs.IsZero());
        BOOST_TEST(ebs.TermNumber() == 0);
    }

    BOOST_TEST(ebs.FirstTermPositive() == (val >0));
    BOOST_TEST(ebs.ToNumerical(0.2) == complex<f_type>((f_type)val, 0.0L));
}

BOOST_DATA_TEST_CASE(constructor_test2, bdata::random(-5, 5) ^ bdata::random(-10, 10) ^ bdata::xrange(20), order, val, index)
{
    ExpBetaSeries ebs(order, val);

    BOOST_TEST((ebs == val) == (val == 0 || order == 0));
    if (val != 0) {
        BOOST_TEST(ebs.TermNumber() == 1);
    } else {
        BOOST_TEST(ebs.IsZero());
        BOOST_TEST(ebs.TermNumber() == 0);
    }

    BOOST_TEST(ebs.FirstTermPositive() == (val >0));
    f_type beta = 0.4;
    BOOST_TEST(ebs.ToNumerical(beta) == ExpI(beta * order) * (f_type)val);
}

BOOST_DATA_TEST_CASE(shiftOrder_test, bdata::random(-5, 5) ^ bdata::random(-10, 10) ^ bdata::random(-5, 5) ^ bdata::xrange(20), order, val, n, index)
{
    f_type beta = 0.6L;
    ExpBetaSeries ebs(order, val);

    complex<f_type> c = ebs.ToNumerical(beta);
    ebs.ShiftOrder(n);
    BOOST_TEST(std::abs(ebs.ToNumerical(beta) - c * ExpI(beta * n)) < eps);
}

BOOST_DATA_TEST_CASE(plus_test, bdata::random(-5, 5) ^ bdata::random(-10, 10) ^ bdata::random(-5, 5) ^ bdata::random(-5, 5) ^ bdata::xrange(20), order1, val1, order2, val2, index)
{
    f_type beta = 0.9L;
    ExpBetaSeries ebs1(order1, val1);
    ExpBetaSeries ebs2(order2, val2);
    ExpBetaSeries ebs3 = ebs1 + ebs2;

    BOOST_TEST(std::abs(ebs3.ToNumerical(beta) - (ebs1.ToNumerical(beta) + ebs2.ToNumerical(beta))) < eps);
}

BOOST_DATA_TEST_CASE(plus_test2, bdata::random(-5, 5) ^ bdata::random(-10, 10) ^ bdata::random(-5, 5) ^ bdata::random(-10, 10) ^ bdata::xrange(20), order1, val1, order2, val2, index)
{
    f_type beta = 0.9L;
    ExpBetaSeries ebs1(order1, val1);
    ExpBetaSeries ebs2(order2, val2);
    std::complex<f_type> c1 = ebs1.ToNumerical(beta);
    std::complex<f_type> c2 = ebs2.ToNumerical(beta);
    ebs2 += ebs1;

    BOOST_TEST(std::abs(ebs2.ToNumerical(beta) - (c1 + c2)) < eps);
}

BOOST_DATA_TEST_CASE(multiply_integer_test, bdata::random(-5, 5) ^ bdata::random(-10, 10) ^ bdata::random(-10, 10) ^ bdata::xrange(20), order1, val1, n, index)
{
    f_type beta = 0.9L;
    ExpBetaSeries ebs1(order1, val1);
    std::complex<f_type> c1 = ebs1.ToNumerical(beta);
    ExpBetaSeries ebs2 = ebs1 * n;

    BOOST_TEST(std::abs(ebs2.ToNumerical(beta) - (c1 * (f_type)n)) < eps);
}

BOOST_DATA_TEST_CASE(multiply_integer_test2, bdata::random(-5, 5) ^ bdata::random(-10, 10) ^ bdata::random(-10, 10) ^ bdata::xrange(20), order1, val1, n, index)
{
    f_type beta = 0.9L;
    ExpBetaSeries ebs1(order1, val1);
    std::complex<f_type> c1 = ebs1.ToNumerical(beta);
    ebs1 *= n;

    BOOST_TEST(std::abs(ebs1.ToNumerical(beta) - (c1 * (f_type)n)) < eps);
}

BOOST_DATA_TEST_CASE(multiply_test, bdata::random(-5, 5) ^ bdata::random(-10, 10) ^ bdata::random(-5, 5) ^ bdata::random(-10, 10) ^ bdata::xrange(20), order1, val1, order2, val2, index)
{
    f_type beta = 0.9L;
    ExpBetaSeries ebs1(order1, val1);
    ExpBetaSeries ebs2(order2, val2);
    ExpBetaSeries ebs3 = ebs1 * ebs2;

    BOOST_TEST(std::abs(ebs3.ToNumerical(beta) - (ebs2.ToNumerical(beta) * ebs1.ToNumerical(beta))) < eps);
}

BOOST_DATA_TEST_CASE(multiply_test2, bdata::random(-5, 5) ^ bdata::random(-10, 10) ^ bdata::random(-5, 5) ^ bdata::random(-10, 10) ^ bdata::xrange(20), order1, val1, order2, val2, index)
{
    f_type beta = 0.9L;
    ExpBetaSeries ebs1(order1, val1);
    ExpBetaSeries ebs2(order2, val2);
    complex<f_type> c1 = ebs1.ToNumerical(beta);
    complex<f_type> c2 = ebs2.ToNumerical(beta);
    ebs2 *= ebs1;

    BOOST_TEST(std::abs(ebs2.ToNumerical(beta) - (c1 * c2)) < eps);
}

BOOST_AUTO_TEST_SUITE_END()

// test suite
BOOST_FIXTURE_TEST_SUITE(NExpansionSeries_suite, SimpleTestFixture, * utf::label("NExpansionSeries"))

BOOST_DATA_TEST_CASE(constructor_test, bdata::random(-5, 5) ^ bdata::random(-5, 5) ^ bdata::random(-10, 10) ^ bdata::xrange(20), nOrder, eOrder, val, index)
{
    int N = 11;
    f_type beta = 1.2;
    NExpansionSeries ne(nOrder, ExpBetaSeries(eOrder, val));
    complex<f_type> expected = ExpI(beta * eOrder) * (f_type)val * pow((f_type)N, nOrder);

    BOOST_TEST(std::abs(ne.ToNumerical(N, beta) - expected) < eps);
}

BOOST_DATA_TEST_CASE(plus_test, bdata::random(-5, 5) ^ bdata::random(-5, 5) ^ bdata::random(-10, 10) ^ bdata::random(-5, 5) ^ bdata::random(-5, 5) ^ bdata::random(-10, 10) ^ bdata::xrange(20), nOrder1, eOrder1, val1, nOrder2, eOrder2, val2, index)
{
    int N = 11;
    f_type beta = 1.2;
    NExpansionSeries ne1(nOrder1, ExpBetaSeries(eOrder1, val1));
    NExpansionSeries ne2(nOrder2, ExpBetaSeries(eOrder2, val2));
    NExpansionSeries ne3 = ne1 + ne2;
    complex<f_type> c1 = ne1.ToNumerical(N, beta);
    complex<f_type> c2 = ne2.ToNumerical(N, beta);

    complex<f_type> expected = c1 + c2;

    BOOST_TEST(std::abs(ne3.ToNumerical(N, beta) - expected) < eps);
}

BOOST_DATA_TEST_CASE(plus_test2, bdata::random(-5, 5) ^ bdata::random(-5, 5) ^ bdata::random(-10, 10) ^ bdata::random(-5, 5) ^ bdata::random(-5, 5) ^ bdata::random(-10, 10) ^ bdata::xrange(20), nOrder1, eOrder1, val1, nOrder2, eOrder2, val2, index)
{
    int N = 11;
    f_type beta = 1.2;
    NExpansionSeries ne1(nOrder1, ExpBetaSeries(eOrder1, val1));
    NExpansionSeries ne2(nOrder2, ExpBetaSeries(eOrder2, val2));
    complex<f_type> c1 = ne1.ToNumerical(N, beta);
    complex<f_type> c2 = ne2.ToNumerical(N, beta);
    ne2 += ne1;

    complex<f_type> expected = c1 + c2;

    BOOST_TEST(std::abs(ne2.ToNumerical(N, beta) - expected) < eps);
}

BOOST_DATA_TEST_CASE(multiply_ExpBetaSeries_test, bdata::random(-5, 5) ^ bdata::random(-5, 5) ^ bdata::random(-10, 10) ^  bdata::random(-5, 5) ^ bdata::random(-10, 10) ^ bdata::xrange(20), nOrder1, eOrder1, val1, eOrder2, val2, index)
{
    int N = 11;
    f_type beta = 1.2;
    NExpansionSeries ne1(nOrder1, ExpBetaSeries(eOrder1, val1));
    ExpBetaSeries ebs(eOrder2, val2);
    NExpansionSeries ne3 = ne1 * ebs;
    complex<f_type> c1 = ne1.ToNumerical(N, beta);
    complex<f_type> c2 = ebs.ToNumerical(beta);

    complex<f_type> expected = c1 * c2;

    BOOST_TEST(std::abs(ne3.ToNumerical(N, beta) - expected) < eps);
}

BOOST_DATA_TEST_CASE(multiply_ExpBetaSeries_test2, bdata::random(-5, 5) ^ bdata::random(-5, 5) ^ bdata::random(-10, 10) ^  bdata::random(-5, 5) ^ bdata::random(-10, 10) ^ bdata::xrange(20), nOrder1, eOrder1, val1, eOrder2, val2, index)
{
    int N = 11;
    f_type beta = 1.2;
    NExpansionSeries ne1(nOrder1, ExpBetaSeries(eOrder1, val1));
    ExpBetaSeries ebs(eOrder2, val2);
    complex<f_type> c1 = ne1.ToNumerical(N, beta);
    complex<f_type> c2 = ebs.ToNumerical(beta);

    ne1 *= ebs;

    complex<f_type> expected = c1 * c2;

    BOOST_TEST(std::abs(ne1.ToNumerical(N, beta) - expected) < eps);
}

BOOST_DATA_TEST_CASE(multiply_test, bdata::random(-5, 5) ^ bdata::random(-5, 5) ^ bdata::random(-10, 10) ^ bdata::random(-5, 5) ^ bdata::random(-5, 5) ^ bdata::random(-10, 10) ^ bdata::xrange(20), nOrder1, eOrder1, val1, nOrder2, eOrder2, val2, index)
{
    int N = 7;
    f_type beta = 1.2;
    NExpansionSeries ne1(nOrder1, ExpBetaSeries(eOrder1, val1));
    NExpansionSeries ne2(nOrder2, ExpBetaSeries(eOrder2, val2));
    NExpansionSeries ne3 = ne1 * ne2;
    complex<f_type> c1 = ne1.ToNumerical(N, beta);
    complex<f_type> c2 = ne2.ToNumerical(N, beta);

    complex<f_type> expected = c1 * c2;

    BOOST_TEST(std::abs(ne3.ToNumerical(N, beta) - expected) < eps);
}

BOOST_DATA_TEST_CASE(multiply_test2, bdata::random(-5, 5) ^ bdata::random(-5, 5) ^ bdata::random(-10, 10) ^ bdata::random(-5, 5) ^ bdata::random(-5, 5) ^ bdata::random(-10, 10) ^ bdata::xrange(20), nOrder1, eOrder1, val1, nOrder2, eOrder2, val2, index)
{
    int N = 7;
    f_type beta = 1.2;
    NExpansionSeries ne1(nOrder1, ExpBetaSeries(eOrder1, val1));
    NExpansionSeries ne2(nOrder2, ExpBetaSeries(eOrder2, val2));
    complex<f_type> c1 = ne1.ToNumerical(N, beta);
    complex<f_type> c2 = ne2.ToNumerical(N, beta);
    ne2 *= ne1;

    complex<f_type> expected = c1 * c2;

    BOOST_TEST(std::abs(ne2.ToNumerical(N, beta) - expected) < eps);
}

BOOST_AUTO_TEST_SUITE_END()


#include <map>
#include <string>
#include <iostream>
#include <iomanip>
#include <cmath>
#include <numeric>
#include <chrono>
#include <random>

#include <EigenRand/EigenRand>

class BenchmarkHelper
{
	std::map<std::string, double>& timing;
	std::map<std::string, std::pair<Eigen::MatrixXd, Eigen::MatrixXd> >& mean_var;
public:

	template<bool _matrix, typename _Ty>
	class ScopeMeasure
	{
		BenchmarkHelper& bh;
		std::string name;
		Eigen::MatrixBase<_Ty>& results;
		std::chrono::high_resolution_clock::time_point start;
	public:
		ScopeMeasure(BenchmarkHelper& _bh,
			const std::string& _name,
			Eigen::MatrixBase<_Ty>& _results) :
			bh{ _bh }, name{ _name }, results{ _results },
			start{ std::chrono::high_resolution_clock::now() }
		{
		}

		~ScopeMeasure()
		{
			if (!name.empty())
			{
				bh.timing[name] = std::chrono::duration<double>(std::chrono::high_resolution_clock::now() - start).count();

				if (_matrix)
				{
					Eigen::Index dim = results.rows(), cnt = results.cols() / dim;
					Eigen::MatrixXd sum(dim, dim), sqsum(dim, dim);
					sum.setZero();
					sqsum.setZero();
					for (Eigen::Index i = 0; i < cnt; ++i)
					{
						auto t = results.middleCols(dim * i, dim).template cast<double>().array();
						sum += t.matrix();
						sqsum += (t * t).matrix();
					}
					sum /= cnt;
					sqsum /= cnt;
					sqsum = sqsum.array() - (sum.array() * sum.array());
					bh.mean_var[name] = std::make_pair(std::move(sum), std::move(sqsum));
				}
				else
				{
					Eigen::MatrixXd zc = results.template cast<double>();
					Eigen::VectorXd mean = zc.rowwise().mean();
					zc.colwise() -= mean;
					Eigen::MatrixXd cov = zc * zc.transpose();
					cov /= results.cols();
					bh.mean_var[name] = std::make_pair(std::move(mean), std::move(cov));
				}
			}
		}
	};

	BenchmarkHelper(std::map<std::string, double>& _timing,
		std::map<std::string, std::pair<Eigen::MatrixXd, Eigen::MatrixXd>>& _mean_var)
		: timing{ _timing }, mean_var{ _mean_var }
	{
	}

	template<bool _matrix, typename _Ty>
	ScopeMeasure<_matrix, _Ty> measure(const std::string& name, Eigen::MatrixBase<_Ty>& results)
	{
		return ScopeMeasure<_matrix, _Ty>(*this, name, results);
	}
};

template<typename Scalar, typename GenTy, typename Rng>
void test_single_and_batch(size_t size, BenchmarkHelper& bh, const std::string& suffix, GenTy&& gen, Rng& urng)
{
	Eigen::Matrix<Scalar, -1, -1> xf;
	xf.resize(gen.dims(), size);
	{
		auto scope = bh.template measure<false>(suffix, xf);
		for (size_t i = 0; i < size; ++i)
		{
			xf.col(i) = gen.generate(urng);
		}
	}

	{
		auto scope = bh.template measure<false>("batch/" + suffix, xf);
		xf = gen.generate(urng, size);
	}
}

template<typename Scalar, typename GenTy, typename Rng>
void test_single_and_batch_matrix(size_t size, BenchmarkHelper& bh, const std::string& suffix, GenTy&& gen, Rng& urng)
{
	Eigen::Matrix<Scalar, -1, -1> xf;
	xf.resize(gen.dims(), size * gen.dims());
	{
		auto scope = bh.template measure<true>(suffix, xf);
		for (size_t i = 0; i < size; ++i)
		{
			xf.middleCols(i * gen.dims(), gen.dims()) = gen.generate(urng);
		}
	}

	{
		auto scope = bh.template measure<true>("batch/" + suffix, xf);
		xf = gen.generate(urng, size);
	}
}

template<typename Rng>
std::map<std::string, double> test_eigenrand(size_t size, const std::string& suffix, std::map<std::string, std::pair<Eigen::MatrixXd, Eigen::MatrixXd> >& results)
{
	std::map<std::string, double> ret;
	BenchmarkHelper bh{ ret, results };
	Rng urng;

	{
		Eigen::Vector4f mean{ 0, 1, 2, 3 };
		Eigen::Matrix4f cov;
		cov << 1, 1, 0, 0,
			1, 2, 0, 0,
			0, 0, 3, 1,
			0, 0, 1, 2;
		test_single_and_batch<float>(size, bh, "MvNormal/4/float" + suffix, Eigen::Rand::makeMvNormalGen(mean, cov), urng);
	}

	{
		Eigen::VectorXf mean = Eigen::VectorXf::LinSpaced(100, 0, 99);
		Eigen::MatrixXf cov(100, 100);
		cov = Eigen::Rand::normalLike(cov, urng) * 0.1f;
		cov = cov * cov.transpose();
		cov += Eigen::MatrixXf::Identity(100, 100) * 4;
		test_single_and_batch<float>(size, bh, "MvNormal/100/float" + suffix, Eigen::Rand::makeMvNormalGen(mean, cov), urng);
	}

	{
		Eigen::Vector4f w{ 1, 2, 3, 4 };
		test_single_and_batch<int32_t>(size, bh, "Multinomial/4/float t=20" + suffix, Eigen::Rand::makeMultinomialGen<int32_t>(20, w), urng);
		test_single_and_batch<int32_t>(size, bh, "Multinomial/4/float t=1000" + suffix, Eigen::Rand::makeMultinomialGen<int32_t>(1000, w), urng);
	}

	{
		Eigen::VectorXf w = Eigen::VectorXf::LinSpaced(100, 0.1, 10.0);
		test_single_and_batch<int32_t>(size, bh, "Multinomial/100/float t=20" + suffix, Eigen::Rand::makeMultinomialGen<int32_t>(20, w), urng);
		test_single_and_batch<int32_t>(size, bh, "Multinomial/100/float t=1000" + suffix, Eigen::Rand::makeMultinomialGen<int32_t>(1000, w), urng);
	}

	{
		Eigen::Vector4f alpha{ 0.1, 0.2, 0.3, 0.4 };
		test_single_and_batch<float>(size, bh, "Dirichlet/4/float" + suffix, Eigen::Rand::makeDirichletGen(alpha), urng);
	}

	{
		Eigen::VectorXf alpha = Eigen::VectorXf::LinSpaced(100, 0.1, 10.0);
		test_single_and_batch<float>(size, bh, "Dirichlet/100/float" + suffix, Eigen::Rand::makeDirichletGen(alpha), urng);
	}

	{
		Eigen::Matrix4f scale;
		scale << 2, 1, 0, 0,
				1, 2, 1, 0,
				0, 1, 2, 1,
				0, 0, 1, 2;
		test_single_and_batch_matrix<float>(size, bh, "Wishart/4/float" + suffix, Eigen::Rand::makeWishartGen(4, scale), urng);
	}

	{
		Eigen::MatrixXf scale(50, 50);
		scale = Eigen::Rand::uniformRealLike(scale, urng) * 0.1f;
		scale = scale * scale.transpose();
		scale += Eigen::MatrixXf::Identity(50, 50);
		test_single_and_batch_matrix<float>(size, bh, "Wishart/50/float" + suffix, Eigen::Rand::makeWishartGen(50, scale), urng);
	}

	{
		Eigen::Matrix4f scale;
		scale << 2, 1, 0, 0,
			1, 2, 1, 0,
			0, 1, 2, 1,
			0, 0, 1, 2;
		test_single_and_batch_matrix<float>(size, bh, "InvWishart/4/float" + suffix, Eigen::Rand::makeInvWishartGen(8, scale), urng);
	}

	{
		Eigen::MatrixXf scale(50, 50);
		scale = Eigen::Rand::uniformRealLike(scale, urng) * 0.1f;
		scale = scale * scale.transpose();
		scale += Eigen::MatrixXf::Identity(50, 50);
		test_single_and_batch_matrix<float>(size, bh, "InvWishart/50/float" + suffix, Eigen::Rand::makeInvWishartGen(54, scale), urng);
	}
	return ret;
}

int main(int argc, char** argv)
{
	size_t size = 10000, repeat = 20;

	if (argc > 1) size = std::stoi(argv[1]);
	if (argc > 2) repeat = std::stoi(argv[2]);

	std::cout << "[Benchmark] Generating Random Matrix " << size << " samples "
		<< repeat << " times" << std::endl;

	std::map<std::string, double> time, timeSq;
	std::map<std::string, std::pair<Eigen::MatrixXd, Eigen::MatrixXd> > results;

	for (size_t i = 0; i < repeat; ++i)
	{
		for (auto& p : test_eigenrand<std::mt19937_64>(size, "\t:ERand", results))
		{
			time[p.first] += p.second;
			timeSq[p.first] += p.second * p.second;
		}

#if defined(EIGEN_VECTORIZE_SSE2) || defined(EIGEN_VECTORIZE_AVX)
		for (auto& p : test_eigenrand<Eigen::Rand::Vmt19937_64>(size, "\t:ERand+vRNG", results))
		{
			time[p.first] += p.second;
			timeSq[p.first] += p.second * p.second;
		}
#endif

	}

	std::cout << "[Average Time] Mean (Stdev)" << std::endl;
	for (auto& p : time)
	{
		double mean = p.second / repeat;
		double var = (timeSq[p.first] / repeat) - mean * mean;
		size_t sp = p.first.find('\t');
		std::cout << std::left << std::setw(28) << p.first.substr(0, sp);
		std::cout << std::setw(14) << p.first.substr(sp + 1);
		std::cout << ": " << mean * 1000 << " (" << std::sqrt(var) * 1000 << ")" << std::endl;
	}

	std::cout << std::endl << "[Statistics]" << std::endl;
	Eigen::IOFormat sformat{ 4, 0, ", ", "\n     ", "[", "]" };
	for (auto& p : results)
	{
		if (p.second.first.rows() >= 10) continue;
		size_t sp = p.first.find('\t');
		std::cout << std::left << std::setw(28) << p.first.substr(0, sp);
		std::cout << std::setw(14) << p.first.substr(sp + 1) << std::endl;
		std::cout << "Mean " << p.second.first.transpose().format(sformat) << std::endl;
		std::cout << "Var  " << p.second.second.format(sformat) << std::endl << std::endl;
	}
	std::cout << std::endl;
	return 0;
}