/*
  This Source Code Form is subject to the terms of the Mozilla Public
  License, v. 2.0. If a copy of the MPL was not distributed with this
  file, You can obtain one at http://mozilla.org/MPL/2.0/.

  Copyright (c) 2019 Panda Team
*/
#include <algorithm>

#include "modules/distance.hpp"

#include "modules/utils/graph.hpp"

#define BOOST_TEST_MODULE Main
#define BOOST_TEST_DYN_LINK

#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_CASE(Metric)
{
    using Vector = std::vector<double>;
    metric::Euclidian<double> metric;

    Vector vNull = {};
    Vector v0 = { 0, 0, 0, 0, 0 };
    Vector v7 = { 7, 7, 7.5, 7, 7 };
    Vector v = { 1, 2.3, -2.7, 0, 3 };

    double t = 0.0000001;
    BOOST_CHECK_CLOSE(metric(vNull, vNull), 0, t);
    BOOST_CHECK_CLOSE(metric(v0, v0), 0, t);
    BOOST_CHECK_CLOSE(metric(v7, v), 15.070832757349542, t);
}

BOOST_AUTO_TEST_CASE(Grid4)
{
    metric::Grid4 grid5(5);  // replaced everywhere mapping::SOM_details with graph by Max F, 2019-05-16
    BOOST_CHECK(!grid5.isValid());

    metric::Grid4 grid25(25);
    BOOST_CHECK(grid25.isValid());
    BOOST_CHECK_EQUAL(grid25.getNodesNumber(), 25);

    metric::Grid4 grid32(3, 2);
    BOOST_CHECK(grid32.isValid());
    BOOST_CHECK_EQUAL(grid32.getNodesNumber(), 6);

    auto neighboursList = grid25.getNeighbours(9, 3);
    for (auto& neighbours : neighboursList) {
        std::sort(neighbours.begin(), neighbours.end());
    }

    std::vector<size_t> neighbours0 = { 9 };
    BOOST_CHECK_EQUAL_COLLECTIONS(
        neighboursList[0].begin(), neighboursList[0].end(), neighbours0.begin(), neighbours0.end());

    std::vector<size_t> neighbours1 = { 4, 8, 14 };
    BOOST_CHECK_EQUAL_COLLECTIONS(
        neighboursList[1].begin(), neighboursList[1].end(), neighbours1.begin(), neighbours1.end());

    std::vector<size_t> neighbours2 = { 3, 7, 13, 19 };
    BOOST_CHECK_EQUAL_COLLECTIONS(
        neighboursList[2].begin(), neighboursList[2].end(), neighbours2.begin(), neighbours2.end());
}

BOOST_AUTO_TEST_CASE(Grid6)
{
    metric::Grid6 grid5(5);
    BOOST_CHECK(!grid5.isValid());

    metric::Grid6 grid25(25);
    BOOST_CHECK(grid25.isValid());
    BOOST_CHECK_EQUAL(grid25.getNodesNumber(), 25);

    metric::Grid6 grid30(6, 5);
    BOOST_CHECK(grid30.isValid());
    BOOST_CHECK_EQUAL(grid30.getNodesNumber(), 30);

    auto neighboursList = grid30.getNeighbours(12, 3);
    for (auto& neighbours : neighboursList) {
        std::sort(neighbours.begin(), neighbours.end());
    }

    std::vector<size_t> neighbours0 = { 12 };
    BOOST_CHECK_EQUAL_COLLECTIONS(
        neighboursList[0].begin(), neighboursList[0].end(), neighbours0.begin(), neighbours0.end());

    std::vector<size_t> neighbours1 = { 6, 13, 18 };
    BOOST_CHECK_EQUAL_COLLECTIONS(
        neighboursList[1].begin(), neighboursList[1].end(), neighbours1.begin(), neighbours1.end());

    std::vector<size_t> neighbours2 = { 0, 1, 7, 14, 19, 24, 25 };
    BOOST_CHECK_EQUAL_COLLECTIONS(
        neighboursList[2].begin(), neighboursList[2].end(), neighbours2.begin(), neighbours2.end());
}
BOOST_AUTO_TEST_CASE(Grid8)
{
    metric::Grid8 grid5(5);
    BOOST_CHECK(!grid5.isValid());

    metric::Grid8 grid25(25);
    BOOST_CHECK(grid25.isValid());
    BOOST_CHECK_EQUAL(grid25.getNodesNumber(), 25);

    metric::Grid8 grid6(3, 2);
    BOOST_CHECK(grid6.isValid());
    BOOST_CHECK_EQUAL(grid6.getNodesNumber(), 6);

    auto neighboursList = grid25.getNeighbours(9, 3);
    for (auto& neighbours : neighboursList) {
        std::sort(neighbours.begin(), neighbours.end());
    }

    std::vector<size_t> neighbours0 = { 9 };
    BOOST_CHECK_EQUAL_COLLECTIONS(
        neighboursList[0].begin(), neighboursList[0].end(), neighbours0.begin(), neighbours0.end());

    std::vector<size_t> neighbours1 = { 3, 4, 8, 13, 14 };
    BOOST_CHECK_EQUAL_COLLECTIONS(
        neighboursList[1].begin(), neighboursList[1].end(), neighbours1.begin(), neighbours1.end());

    std::vector<size_t> neighbours2 = { 2, 7, 12, 17, 18, 19 };
    BOOST_CHECK_EQUAL_COLLECTIONS(
        neighboursList[2].begin(), neighboursList[2].end(), neighbours2.begin(), neighbours2.end());

    std::vector<size_t> neighbours3 = { 1, 6, 11, 16, 21, 22, 23, 24 };
    BOOST_CHECK_EQUAL_COLLECTIONS(
        neighboursList[3].begin(), neighboursList[3].end(), neighbours3.begin(), neighbours3.end());
}

BOOST_AUTO_TEST_CASE(LPS)
{
    metric::LPS lps5(5);
    BOOST_CHECK_EQUAL(lps5.isValid(), true);

    metric::LPS lps(11);
    BOOST_CHECK_EQUAL(lps.isValid(), true);

    auto neighboursList = lps.getNeighbours(9, 2);
    for (auto& neighbours : neighboursList) {
        std::sort(neighbours.begin(), neighbours.end());
    }

    std::vector<size_t> neighbours0 = { 9 };
    BOOST_CHECK_EQUAL_COLLECTIONS(
        neighboursList[0].begin(), neighboursList[0].end(), neighbours0.begin(), neighbours0.end());

    std::vector<size_t> neighbours1 = { 5, 8, 10 };
    BOOST_CHECK_EQUAL_COLLECTIONS(
        neighboursList[1].begin(), neighboursList[1].end(), neighbours1.begin(), neighbours1.end());

    std::vector<size_t> neighbours2 = { 0, 4, 6, 7 };
    BOOST_CHECK_EQUAL_COLLECTIONS(
        neighboursList[2].begin(), neighboursList[2].end(), neighbours2.begin(), neighbours2.end());

    /* LPS(41) */
    metric::LPS lps41(41);
    BOOST_CHECK_EQUAL(lps41.isValid(), true);

    neighboursList = lps41.getNeighbours(9, 2);
    for (auto& neighbours : neighboursList) {
        std::sort(neighbours.begin(), neighbours.end());
    }

    neighbours0 = { 9 };
    BOOST_CHECK_EQUAL_COLLECTIONS(
        neighboursList[0].begin(), neighboursList[0].end(), neighbours0.begin(), neighbours0.end());

    neighbours1 = { 8, 10, 32 };
    BOOST_CHECK_EQUAL_COLLECTIONS(
        neighboursList[1].begin(), neighboursList[1].end(), neighbours1.begin(), neighbours1.end());

    neighbours2 = { 7, 11, 31, 33, 36, 37 };
    BOOST_CHECK_EQUAL_COLLECTIONS(
        neighboursList[2].begin(), neighboursList[2].end(), neighbours2.begin(), neighbours2.end());
}

BOOST_AUTO_TEST_CASE(Paley)
{
    metric::Paley paley12(12);
    BOOST_CHECK_EQUAL(paley12.isValid(), false);

    metric::Paley paley13(13);
    BOOST_CHECK_EQUAL(paley13.isValid(), true);

    auto neighboursList = paley13.getNeighbours(9, 1);
    for (auto& neighbours : neighboursList) {
        std::sort(neighbours.begin(), neighbours.end());
    }

    std::vector<size_t> neighbours0 = { 9 };
    BOOST_CHECK_EQUAL_COLLECTIONS(
        neighboursList[0].begin(), neighboursList[0].end(), neighbours0.begin(), neighbours0.end());

    std::vector<size_t> neighbours1 = { 0, 5, 6, 8, 10, 12 };
    BOOST_CHECK_EQUAL_COLLECTIONS(
        neighboursList[1].begin(), neighboursList[1].end(), neighbours1.begin(), neighbours1.end());
}

BOOST_AUTO_TEST_CASE(Margulis)
{
    metric::Margulis margulis5(5);
    BOOST_CHECK_EQUAL(margulis5.isValid(), false);

    metric::Margulis margulis25(25);
    BOOST_CHECK_EQUAL(margulis25.isValid(), true);

    auto neighboursList = margulis25.getNeighbours(7, 1);
    for (auto& neighbours : neighboursList) {
        std::sort(neighbours.begin(), neighbours.end());
    }

    std::vector<size_t> neighbours0 = { 7 };
    BOOST_CHECK_EQUAL_COLLECTIONS(
        neighboursList[0].begin(), neighboursList[0].end(), neighbours0.begin(), neighbours0.end());

    std::vector<size_t> neighbours1 = { 2, 5, 9, 12 };
    BOOST_CHECK_EQUAL_COLLECTIONS(
        neighboursList[1].begin(), neighboursList[1].end(), neighbours1.begin(), neighbours1.end());
}


#ifndef mesh_reader
#define mesh_reader

#include <fstream>
#include <sstream>
#include <iostream>
#include <string>
#include <math.h>
#include <Eigen/Dense>

using namespace Eigen;
typedef Matrix<int,Dynamic,Dynamic> MatrixXi;

namespace mesh
{

    MatrixXd read_vertices(std::string location)
    {
        /* index # is vertix id, contains x and y
        coordinate respectively of vertix, and
        a flag, 1 if boundary point 0 if not */
        std::ifstream infile(location);
        std::string line;
        double a, b, c, d, line_nb = 0;
        std::getline(infile, line);
        std::istringstream iss(line);
        iss >> a >> b >> c;
        int nb_nodes = a;
        MatrixXd vertices(nb_nodes, 3);
        while (std::getline(infile, line))
        {
            std::istringstream iss(line);
            if (iss >> a >> b >> c >> d)
            {
                vertices(line_nb, 0) = b/1000.;
                vertices(line_nb, 1) = c/1000.;
                vertices(line_nb, 2) = d;
            }
            else
            {
                break;
            }
            line_nb += 1;
        }
        return vertices;
    }

    MatrixXd read_triangles(MatrixXd& vertices,std::string location)
    {
        /* index # is triangle id, contains
        the 3 vertix id's of the corner nodes  and
        the area of the triangle, calculated using
        the vertices matrix */
        std::ifstream infile(location);
        std::string line;
        int a, b, c, d, line_nb = 0;
        std::getline(infile, line);
        std::istringstream iss(line);
        iss >> a >> b >> c;
        int nb_triangles = a;
        MatrixXd triangles(nb_triangles, 4);
        while (std::getline(infile, line))
        {
            std::istringstream iss(line);
            if (iss >> a >> b >> c >> d)
            {
                triangles(line_nb, 0) = b-1;
                triangles(line_nb, 1) = c-1;
                triangles(line_nb, 2) = d-1;
                triangles(line_nb, 3) = 0.5*fabs(vertices(b-1, 0)*(vertices(c-1, 1) - vertices(d-1, 1)) +
                    vertices(c-1, 0)*(vertices(d-1, 1) - vertices(b-1, 1)) +
                    vertices(d-1, 0)*(vertices(b-1, 1) - vertices(c-1, 1)));
            }
            else
            {
                break;
            }
            line_nb += 1;
        }
        return triangles;
    }

    MatrixXi read_boundaries(MatrixXd& vertices,std::string location) {
        /* index # is triangle id, contains
        the 3 vertix id's of the corner nodes  and
        the area of the triangle, calculated using
        the vertices matrix */
        std::ifstream infile(location);
        std::string line;
        int a, b, c, d, count = 0;
        std::getline(infile, line);
        std::getline(infile, line);
        std::istringstream iss(line);
        iss >> a >> b;
        int nb_boundaries = a;
        MatrixXi boundaries(nb_boundaries, 2);
        while (std::getline(infile, line))
        {
            std::istringstream iss(line);
            if (iss >> a >> b >> c >> d)
            {
                if (vertices(b-1, 0) != 0 || vertices(c-1, 0) != 0) {
                  boundaries(count, 0) = b-1;
                  boundaries(count, 1) = c-1;
                  count += 1;
                }
            }
            else
            {
                break;
            }
        }

        MatrixXi boundaries_red(count, 2);
        boundaries_red = boundaries.block(0,0,count,2);
        return boundaries_red;
    }

    void write_result(VectorXd& u,VectorXd& v)
    {
      std::ofstream output_u;
      std::ofstream output_v;
      output_u.open("../mesh/result_u.out");
      output_u << u;
      output_v.open("../mesh/result_v.out");
      output_v << v;
      output_u.close();
      output_v.close();
    }

}

#endif


#include <bits/types/FILE.h>
#include <fstream>
#include <iostream>
#include <stdio.h>
#include <vector>
#include <string>
#include <tgmath.h>
#include "image_ppm.h"
#include <filesystem>
#include <Eigen/Dense>

using namespace std;
using namespace Eigen;

typedef Matrix<u_char, Dynamic, Dynamic> MatrixImg;
typedef Matrix<double, Dynamic, Dynamic> TempMatrixImg;
typedef Vector<u_char,Dynamic> ImgLine;
typedef Vector<double,Dynamic> TempImgLine;
//typedef Matrix<ImgLine,Dynamic,Dynamic> FlattenedImages;

unsigned char max(u_char a, u_char b){
    if (a<b) return b;
    else return a;
}

unsigned char min(u_char a, u_char b){
    if (a>b) return b;
    else return a;
}

double max(double a, double b){
    if (a<b) return b;
    else return a;
}

double min(double a, double b){
    if (a>b) return b;
    else return a;
}

vector<double> projectOnEigenSpace(vector<TempImgLine> eigenfaces, TempImgLine imToProj,int K){
    vector<double> res = vector<double>();
    for (int i=0;i<K;i++){
        res.push_back(eigenfaces[i].dot(imToProj));
    }
    return res;
}

ImgLine octToVec(OCTET* im, int nH, int nW){
    ImgLine res(nH*nW);
    for (int i=0; i<nH*nW;i++){
        res(i)=im[i];
    }
    return res;
}

int main(){
    vector<TempImgLine> eigenfaces;
    int K =100; int nH;int nW;
    for (int i=0; i<K;i++){
        OCTET* im;
        char name[50];
        sprintf(name,"eigenfaces/im%d.pgm",i);
        lire_nb_lignes_colonnes_image_pgm(name,&nH,&nW);
        allocation_tableau(im,OCTET,nH*nW);
        lire_image_pgm(name,im,nH*nW);
        TempImgLine eigenFace(nH*nW) ;
        double sum=0.0;
        for (int j=0;j<nH*nW;j++){
            eigenFace(j)=im[j]-127;
            sum+=(double)(im[j]-127)*(double)(im[j]-127);
        }
        for (int j=0;j<nH*nW;j++){eigenFace(j)=eigenFace(j)/sqrt(sum);}
        eigenfaces.push_back(eigenFace);
        free(im);
    }

    OCTET* meanIm; allocation_tableau(meanIm,OCTET, nH*nW); lire_image_pgm("MEAN.pgm",meanIm,nH*nW);
    OCTET* felix; allocation_tableau(felix,OCTET,nH*nW); //lire_image_pgm("../Databases/yalefaces/test/pgms/subject01.noglasses.pgm",felix,nH*nW);
    lire_image_pgm("550.pgm",felix,nH*nW);
    //lire_image_pgm("felix320243.pgm",felix,nH*nW);

    //for (int i=0; i<nH*nW;i++){felix[i]=min(255,max(0,felix[i]-meanIm[i]));}

    //ecrire_image_pgm("meanLessFix.pgm",felix,nH,nW);

    //cout<<eigenfaces[1]<<endl;
    double sumF=0.0;
    for (int i=0; i<nH*nW;i++){sumF+=(double)(felix[i]-127)*(double)(felix[i]-127);}
    TempImgLine fixLine(nH*nW) ;for (int i=0;i<nH*nW;i++){fixLine(i)=(double)(felix[i]-127)/sumF;}
    //cout<<fixLine<<endl;
    //cout<<eigenfaces[0]<<endl;
    //cout<<fixLine.dot(eigenfaces[0])<<endl;
    vector<double> projs = projectOnEigenSpace(eigenfaces,fixLine,K);
    double sum=0.0;
    for (int i=0;i<K;i++){
        //cout<<i<<" : "<<projs[i]<<endl;
        sum+=projs[i];
    }
    //cout<<sum<<endl;

    // for (int i=0;i<K;i++){
    //     OCTET* im;allocation_tableau(im,OCTET,nH*nW);
    //     for (int pix=0;pix<nH*nW;pix++){
    //         im[pix]=eigenfaces[i](pix);

    //     }
    //     char name2[50];
    //     sprintf(name2,"testEigenFaces/im%d.pgm",i);
    //     ecrire_image_pgm(name2,im,nH,nW);
    // }

    OCTET* reco; allocation_tableau(reco,OCTET, nH*nW);

    TempImgLine recoTemp(nH*nW); for (int i=0;i<nH*nW;i++){recoTemp(i)=0.0;}

    for (int i=0; i<K;i++){
        for (int pix=0; pix<nH*nW;pix++){
            recoTemp(pix)+=(projs[i]) * (eigenfaces[i])(pix);
        }
        //cout<<recoTemp<<endl;
    }

    // for (int i=0;i<nH*nW;i++){
    //     recoTemp(i)+=meanIm[i];
    // }

    double minR=recoTemp(0); double maxR=recoTemp(0);
    for (int i=0; i<nH*nW;i++){
        minR=min(minR,recoTemp(i));
        maxR=max(maxR,recoTemp(i));
    }
    cout<<minR<<"  "<<maxR<<endl;

    for (int i=0;i<nH*nW;i++){
        reco[i]=(unsigned char)((1.0/2.0)*(((recoTemp(i)-minR)*(255.0/(maxR-minR)))+(meanIm[i])));
        //reco[i]=min(255,max(0,recoTemp(i)));
        //cout<<(int)reco[i]<<endl;
    }

    ecrire_image_pgm("RecoFelix.pgm",reco,nH,nW);

}


// This file is part of OpenMVG, an Open Multiple View Geometry C++ library.

// Copyright (c) 2013,2014 Pierre MOULON.

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include "openMVG/geometry/box.hpp"
#include "openMVG/geometry/frustum.hpp"
#include "openMVG/geometry/half_space_intersection.hpp"
#include "openMVG/multiview/projection.hpp"
#include "openMVG/multiview/test_data_sets.hpp"

#include "CppUnitLite/TestHarness.h"
#include "testing/testing.h"

#include <Eigen/Geometry>

#include <fstream>
#include <iostream>

using namespace openMVG;
using namespace openMVG::geometry;
using namespace openMVG::geometry::halfPlane;
using namespace std;

//--
// Box/Camera frustum intersection unit test
//--

TEST(box_point, intersection)
{
  const Box box(Vec3::Zero(), sqrt(1.));

  // Test with a point that is inside the defined volume
  EXPECT_TRUE( box.contains(Vec3(0,0,0)) );

  // Test with a point that is outside the defined volume
  EXPECT_FALSE( box.contains(Vec3(1,1,1)) );
}

TEST(box_box, intersection)
{
  const double r = 1.;

  // Test with a set of intersecting boxes
  std::vector<HalfPlaneObject> boxes_ok;
  for (int i=0; i < 6; ++i)
  {
    Vec3 center = Vec3::Zero();
    center[i/2] += std::pow(-1, i%2) * r / 5.;
    boxes_ok.emplace_back(Box(center, r));
  }
  EXPECT_TRUE( boxes_ok[0].intersect(boxes_ok[1]) );
  EXPECT_TRUE( intersect(boxes_ok) );

  // Test with a set of non-intersecting boxes
  std::vector<HalfPlaneObject> boxes_ko;
  for (int i=0; i < 6; ++i)
  {
    Vec3 center = Vec3::Zero();
    center[i/2] += std::pow(-1, i%2) * 1.5 * r;
    boxes_ko.emplace_back(Box(center, 1));
  }
  EXPECT_FALSE( boxes_ko[0].intersect(boxes_ko[1]) );
  EXPECT_FALSE( intersect(boxes_ko) );
}

TEST(box_frustum, intersection)
{
  const int focal = 1000;
  const int principal_Point = 500;
  //-- Setup a circular camera rig or "cardioid".
  const int iNviews = 4;
  const int iNbPoints = 6;
  const NViewDataSet d =
    NRealisticCamerasRing(
    iNviews, iNbPoints,
    nViewDatasetConfigurator(focal, focal, principal_Point, principal_Point, 5, 0));

  const Box box(Vec3::Zero(), sqrt(1.));
  {
    std::ostringstream os;
    os << "box.ply";
    Box::export_Ply(box, os.str());
  }

  // Test with infinite Frustum for each camera
  {
    for (int i=0; i < iNviews; ++i)
    {
      const Frustum f (principal_Point*2, principal_Point*2, d._K[i], d._R[i], d._C[i]);
      EXPECT_TRUE(f.intersect(box));
      EXPECT_TRUE(box.intersect(f));
      EXPECT_TRUE(intersect({f, box}));

      std::ostringstream os;
      os << i << "frust.ply";
      Frustum::export_Ply(f, os.str());
    }
  }

  // Test with truncated frustum
  {
    // Build frustum with near and far plane defined by min/max depth per camera
    for (int i=0; i < iNviews; ++i)
    {
      double minDepth = std::numeric_limits<double>::max();
      double maxDepth = std::numeric_limits<double>::min();
      for (int j=0; j < iNbPoints; ++j)
      {
        const double depth = Depth(d._R[i], d._t[i], d._X.col(j));
        if (depth < minDepth)
          minDepth = depth;
        if (depth > maxDepth)
          maxDepth = depth;
      }
      const Frustum f(principal_Point*2, principal_Point*2,
          d._K[i], d._R[i], d._C[i], minDepth, maxDepth);

      EXPECT_TRUE(f.intersect(box));
      EXPECT_TRUE(box.intersect(f));
      EXPECT_TRUE(intersect({f, box}));
    }
  }
}

TEST(box_frustum, no_intersection)
{
  const int focal = 1000;
  const int principal_Point = 500;
  //-- Setup a circular camera rig or "cardioid".
  const int iNviews = 4;
  const int iNbPoints = 6;
  const NViewDataSet d =
    NRealisticCamerasRing(
      iNviews, iNbPoints,
      nViewDatasetConfigurator(focal, focal, principal_Point, principal_Point, 5, 0));

  // Put the box out of field of the camera
  // (since camera are Y up, we move the box along Y axis)
  const Vec3 position(0, -4, 0);
  const Box box(position, sqrt(1.));
  {
    std::ostringstream os;
    os << "box.ply";
    Box::export_Ply(box, os.str());
  }

  // Test with infinite Frustum for each camera
  {
    std::vector<Frustum> vec_frustum;
    for (int i = 0; i < iNviews; ++i)
    {
      const Frustum f(principal_Point * 2, principal_Point * 2, d._K[i], d._R[i], d._C[i]);
      EXPECT_FALSE(f.intersect(box));
      EXPECT_FALSE(box.intersect(f));
      EXPECT_FALSE(intersect({f, box}));

      std::ostringstream os;
      os << i << "frust.ply";
      Frustum::export_Ply(f, os.str());
    }
  }

  // Test with truncated frustum
  {
    // Build frustum with near and far plane defined by min/max depth per camera
    for (int i = 0; i < iNviews; ++i)
    {
      double minDepth = std::numeric_limits<double>::max();
      double maxDepth = std::numeric_limits<double>::min();
      for (int j = 0; j < iNbPoints; ++j)
      {
        const double depth = Depth(d._R[i], d._t[i], d._X.col(j));
        if (depth < minDepth)
          minDepth = depth;
        if (depth > maxDepth)
          maxDepth = depth;
      }
      const Frustum f(principal_Point * 2, principal_Point * 2,
        d._K[i], d._R[i], d._C[i], minDepth, maxDepth);

      EXPECT_FALSE(f.intersect(box));
      EXPECT_FALSE(box.intersect(f));
      EXPECT_FALSE(intersect({f, box}));
    }
  }
}

/* ************************************************************************* */
int main() { TestResult tr; return TestRegistry::runAllTests(tr);}
/* ************************************************************************* */


/* MonteCarlo.hpp
-Description:
	*Declare and define MonteCarlo template class that calculates the price of a financial option using the Euler-Maruyama scheme.
-State Objects:
	*variate_generator rng: Boost implemented normal random number generator for Monte Carlo path generation.
-Class Methods:
	// Constructors/Destructors:
	*MonteCarlo(double t, double s, double k, double b, double r, double sigma, unsigned long NSIM, unsigned long NT, bool isCall): Overloaded Constructor.
	*MonteCarlo(const MonteCarlo&): Copy constructor.
	*~MonteCarlo(): Destructor.
	// Accessors:
	*unsigned long NSIM() const: Return the number of trials to be run in Monte Carlo.
	*unsigned long NT() const: Return number of sub-intervals in time to be used in generating each path.
	// Mutators:
	*void NSIM(unsigned long): Set number of trials.
	*void NT(unsigned long): Set number of time sub-intervals.
	// Misc. Methods:
	*double Price() const: Calculate price of associated option using Monte Carlo method with Euler-Maruyama scheme.
*/
#ifndef MONTECARLO_HPP
#define MONTECARLO_HPP

#include <boost/random/variate_generator.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/normal_distribution.hpp>
#include <cmath>
#include <string>
#include <vector>
#include "MonteCarlo.hpp"
#include "MonteCarloExcept.hpp"
#include "Option.hpp"
#include "OptionExcept.hpp"

namespace Options
{
	class MonteCarlo : public Option
	{
	private:
		////////////////////////////
		// State Variables/Objects:
		////////////////////////////
		boost::variate_generator<boost::mt19937, boost::normal_distribution<double> > *mainRNG;	/* Random number generator for Monte Carlo path generation. */
		unsigned long nSIM, nT;									/* Number of trials and time intervals for price generator. */
	public:
		////////////////////////////
		// Constructors/Destructor:
		////////////////////////////
		MonteCarlo(double t, double s, double k, double b, double r, double sigma, bool isCall, unsigned long NSIM, unsigned long NT); /* Overloaded Constructor. Set all parameters AND set number of time intervals and simulations to run. */
		MonteCarlo(const MonteCarlo &in);															/* Copy Constructor 1. */
		MonteCarlo(const Option &in, unsigned long NSIM, unsigned long NT);							/* Copy Constructor 2, with NT and NSIM setting*/
		virtual ~MonteCarlo();									/* Destructor. */
		////////////////////////////
		// Accessors:
		////////////////////////////
		unsigned long NSIM() const;								/* Return number of simulations run in the Monte Carlo. */
		unsigned long NT() const;								/* Return number of time intervals run in the Monte Carlo path generation. */
		////////////////////////////
		// Mutators:
		////////////////////////////
		void NSIM(unsigned long NSIM_in);						/* Set the number of simulations to run in the Monte Carlo. */
		void NT(unsigned long NT_in);							/* Set the number of time intervals to run in the Monte Carlo path generation. */
		////////////////////////////
		// Misc. Methods:
		////////////////////////////
		double Price() const;									/* Calculate price using Monte Carlo method. */
		////////////////////////////
		// Overloaded Operators:
		////////////////////////////
		MonteCarlo& operator=(const MonteCarlo &in);		    /* Assignment Operator. */
	};
}
#endif


#ifndef STAN_MATH_PRIM_FUN_BESSEL_FIRST_KIND_HPP
#define STAN_MATH_PRIM_FUN_BESSEL_FIRST_KIND_HPP

#include <stan/math/prim/meta.hpp>
#include <stan/math/prim/err.hpp>
#include <boost/math/special_functions/bessel.hpp>
#include <stan/math/prim/functor/apply_scalar_binary.hpp>

namespace stan {
namespace math {

/**
 *
   \f[
   \mbox{bessel\_first\_kind}(v, x) =
   \begin{cases}
     J_v(x) & \mbox{if } -\infty\leq x \leq \infty \\[6pt]
     \textrm{error} & \mbox{if } x = \textrm{NaN}
   \end{cases}
   \f]

   \f[
   \frac{\partial\, \mbox{bessel\_first\_kind}(v, x)}{\partial x} =
   \begin{cases}
     \frac{\partial\, J_v(x)}{\partial x} & \mbox{if } -\infty\leq x\leq \infty
 \\[6pt] \textrm{error} & \mbox{if } x = \textrm{NaN} \end{cases} \f]

   \f[
   J_v(x)=\left(\frac{1}{2}x\right)^v
   \sum_{k=0}^\infty \frac{\left(-\frac{1}{4}x^2\right)^k}{k!\, \Gamma(v+k+1)}
   \f]

   \f[
   \frac{\partial \, J_v(x)}{\partial x} = \frac{v}{x}J_v(x)-J_{v+1}(x)
   \f]
 *
 */
template <typename T2, require_arithmetic_t<T2>* = nullptr>
inline T2 bessel_first_kind(int v, const T2 z) {
  check_not_nan("bessel_first_kind", "z", z);
  return boost::math::cyl_bessel_j(v, z);
}

/**
 * Enables the vectorised application of the bessel first kind function, when
 * the first and/or second arguments are containers.
 *
 * @tparam T1 type of first input
 * @tparam T2 type of second input
 * @param a First input
 * @param b Second input
 * @return Bessel first kind function applied to the two inputs.
 */
template <typename T1, typename T2, require_any_container_t<T1, T2>* = nullptr,
          require_not_var_matrix_t<T2>* = nullptr>
inline auto bessel_first_kind(const T1& a, const T2& b) {
  return apply_scalar_binary(a, b, [&](const auto& c, const auto& d) {
    return bessel_first_kind(c, d);
  });
}

}  // namespace math
}  // namespace stan
#endif


#include "Test.h"
#include <Eigen/Dense>
#include <iostream>

typedef Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> MatrixXdR;
JNIEXPORT void JNICALL Java_Test_inverse (JNIEnv *env, jclass cls,jdoubleArray jm1,  jdoubleArray jm2, jdoubleArray jr, jint nrow, jint ncol)
{
	jboolean isCopy=JNI_TRUE;
	double* pm1=(double*)env->GetPrimitiveArrayCritical(jm1, &isCopy);
	double* pm2=(double*)env->GetPrimitiveArrayCritical(jm2, &isCopy);
	double* pr=(double*)env->GetPrimitiveArrayCritical(jr, &isCopy);
	Eigen::Map<MatrixXdR> em1(pm1, nrow, ncol);
	Eigen::Map<MatrixXdR> em2(pm2, nrow, ncol);
	Eigen::Map<MatrixXdR> er(pr, nrow, ncol);
	er=em1*em2; //.inverse();

	/*
	std::cout << "----" << std::endl;
	std::cout << em1 << std::endl;
	std::cout << em2 << std::endl;
	std::cout << er << std::endl;
	*/
	env->ReleasePrimitiveArrayCritical(jm1, pm1, 0);
	env->ReleasePrimitiveArrayCritical(jm2, pm2, 0);
	env->ReleasePrimitiveArrayCritical(jr, pr, 0);
}

JNIEXPORT void inverse(double* m1, double* m2, double* r, int nrow, int ncol)
{
	Eigen::Map<MatrixXdR> em1(m1, nrow, ncol);
	Eigen::Map<MatrixXdR> em2(m2, nrow, ncol);
	Eigen::Map<MatrixXdR> er(r, nrow, ncol);
	//er=em1*em2; //.inverse();
}


// SPDX-License-Identifier: MIT
// Copyright (c) 2019-2021 Thomas Vanderbruggen <th.vanderbruggen@gmail.com>

#ifndef SCICPP_POLYNOMIALS_POLYNOMIAL
#define SCICPP_POLYNOMIALS_POLYNOMIAL

#include "scicpp/core/equal.hpp"
#include "scicpp/core/functional.hpp"
#include "scicpp/core/macros.hpp"
#include "scicpp/core/meta.hpp"
#include "scicpp/core/numeric.hpp"
#include "scicpp/linalg/solve.hpp"
#include "scicpp/linalg/utils.hpp"
#include "scicpp/signal/convolve.hpp"

#include <Eigen/Dense>
#include <Eigen/Eigenvalues>
#include <algorithm>
#include <array>
#include <cmath>
#include <complex>
#include <functional>
#include <initializer_list>
#include <iterator>
#include <limits>
#include <tuple>
#include <utility>
#include <vector>

namespace scicpp::polynomial {

//---------------------------------------------------------------------------------
// Polyval
//---------------------------------------------------------------------------------

template <class T, class Array>
auto polyval(T &&x, const Array &coeffs) {
    if constexpr (meta::is_iterable_v<T>) {
        return map([&](auto v) { return polyval(v, coeffs); },
                   std::forward<T>(x));
    } else {
        using scalar_t = std::remove_reference_t<T>;
        static_assert(std::is_same_v<scalar_t, typename Array::value_type>);

        // https://en.wikipedia.org/wiki/Horner%27s_method
        auto res = scalar_t{0};

        std::for_each(coeffs.crbegin(), coeffs.crend(), [&](auto c) {
            res = std::fma(res, x, c);
        });

        return res;
    }
}

//---------------------------------------------------------------------------------
// polyadd
//---------------------------------------------------------------------------------

namespace detail {

template <class U, class V, class W>
void add_arrays(const U &P1, const V &P2, W &P) {
    using T = typename U::value_type;
    static_assert(std::is_same_v<typename U::value_type, T>);
    static_assert(std::is_same_v<typename W::value_type, T>);

    if (P1.size() <= P2.size()) {
        scicpp_require(P.size() >= P2.size());
        std::transform(
            P1.cbegin(), P1.cend(), P2.cbegin(), P.begin(), std::plus<T>());
        std::copy(P2.cbegin() + int(P1.size()),
                  P2.cend(),
                  P.begin() + int(P1.size()));
    } else {
        scicpp_require(P.size() >= P1.size());
        std::transform(
            P2.cbegin(), P2.cend(), P1.cbegin(), P.begin(), std::plus<T>());
        std::copy(P1.cbegin() + int(P2.size()),
                  P1.cend(),
                  P.begin() + int(P2.size()));
    }
}

} // namespace detail

template <typename T, std::size_t M, std::size_t N>
auto polyadd(const std::array<T, M> &P1, const std::array<T, N> &P2) {
    std::array<T, (M > N ? M : N)> P{};
    detail::add_arrays(P1, P2, P);
    return P;
}

template <typename T>
auto polyadd(const std::vector<T> &P1, const std::vector<T> &P2) {
    std::vector<T> P(P1.size() > P2.size() ? P1.size() : P2.size());
    detail::add_arrays(P1, P2, P);
    return P;
}

//---------------------------------------------------------------------------------
// polysub
//---------------------------------------------------------------------------------

namespace detail {

template <class U, class V, class W>
void sub_arrays(const U &P1, const V &P2, W &P) {
    using T = typename U::value_type;
    static_assert(std::is_same_v<typename U::value_type, T>);
    static_assert(std::is_same_v<typename W::value_type, T>);

    if (P1.size() <= P2.size()) {
        scicpp_require(P.size() >= P2.size());
        std::transform(
            P1.cbegin(), P1.cend(), P2.cbegin(), P.begin(), std::minus<T>());
        std::transform(P2.cbegin() + int(P1.size()),
                       P2.cend(),
                       P.begin() + int(P1.size()),
                       [](auto v) { return -v; });
    } else {
        scicpp_require(P.size() >= P1.size());
        std::transform(P2.cbegin(),
                       P2.cend(),
                       P1.cbegin(),
                       P.begin(),
                       [](auto v2, auto v1) { return v1 - v2; });
        std::copy(P1.cbegin() + int(P2.size()),
                  P1.cend(),
                  P.begin() + int(P2.size()));
    }
}

} // namespace detail

template <typename T, std::size_t M, std::size_t N>
auto polysub(const std::array<T, M> &P1, const std::array<T, N> &P2) {
    std::array<T, (M > N ? M : N)> P{};
    detail::sub_arrays(P1, P2, P);
    return P;
}

template <typename T>
auto polysub(const std::vector<T> &P1, const std::vector<T> &P2) {
    std::vector<T> P(P1.size() > P2.size() ? P1.size() : P2.size());
    detail::sub_arrays(P1, P2, P);
    return P;
}

//---------------------------------------------------------------------------------
// polymul
//---------------------------------------------------------------------------------

template <signal::ConvMethod method, class U, class V>
auto polymul(const U &P1, const V &P2) {
    return signal::convolve<method>(P1, P2);
}

// Specialization for the default convolution method
template <class U, class V>
auto polymul(const U &P1, const V &P2) {
    return polymul<signal::DIRECT>(P1, P2);
}

//---------------------------------------------------------------------------------
// polydiv
//---------------------------------------------------------------------------------

namespace detail {

// https://stackoverflow.com/questions/44770632/fft-division-for-fast-polynomial-division
// http://web.cs.iastate.edu/~cs577/handouts/polydivide.pdf

template <class U, class V>
constexpr auto polydiv_impl(const U &u, const V &v) {
    using T = typename U::value_type;
    static_assert(std::is_same_v<T, typename V::value_type>);

    auto tmp(u);
    const auto m = signed_size_t(u.size()) - 1;
    const auto n = signed_size_t(v.size()) - 1;
    const auto scale = T{1} / v[std::size_t(n)];

    for (signed_size_t k = m - n; k >= 0; --k) {
        const auto d = scale * tmp[std::size_t(n + k)];

        for (signed_size_t j = n + k - 1; j >= k; --j) {
            tmp[std::size_t(j)] -= d * v[std::size_t(j - k)];
        }
    }

    return tmp;
}

} // namespace detail

template <typename T, std::size_t M, std::size_t N>
constexpr auto polydiv(const std::array<T, M> &u, const std::array<T, N> &v) {
    static_assert(M > 0);
    static_assert(N > 0);

    if constexpr (N == 1) {
        using namespace scicpp::operators;
        return std::make_tuple(u / v[0], std::array<T, 1>{});
    } else if constexpr (M < N) {
        return std::make_tuple(std::array<T, 1>{}, u);
    } else {
        const auto tmp = detail::polydiv_impl(u, v);

        std::array<T, M - N + 1> q{};
        std::array<T, N - 1> r{};
        std::move(tmp.begin(), tmp.begin() + r.size(), r.begin());
        std::move(tmp.begin() + r.size(), tmp.end(), q.begin());
        return std::make_tuple(q, r);
    }
}

template <typename T>
auto polydiv(const std::vector<T> &u, const std::vector<T> &v) {
    scicpp_require(!u.empty());
    scicpp_require(!v.empty());

    if (v.size() == 1) {
        using namespace scicpp::operators;
        return std::make_tuple(u / v[0], std::vector<T>(1, T{0}));
    }

    if (u.size() < v.size()) {
        return std::make_tuple(std::vector<T>(1, T{0}), u);
    }

    const auto tmp = detail::polydiv_impl(u, v);
    const auto len = signed_size_t(v.size()) - 1;

    return std::make_tuple(
        std::vector<T>(std::make_move_iterator(tmp.begin() + len),
                       std::make_move_iterator(tmp.end())),
        std::vector<T>(std::make_move_iterator(tmp.begin()),
                       std::make_move_iterator(tmp.begin() + len)));
}

//---------------------------------------------------------------------------------
// polymulx
//---------------------------------------------------------------------------------

template <typename T, std::size_t N>
auto polymulx(const std::array<T, N> &P) {
    std::array<T, N + 1> res{};
    std::copy(P.cbegin(), P.cend(), res.begin() + 1);
    return res;
}

template <typename T>
auto polymulx(const std::vector<T> &P) {
    std::vector<T> res(P.size() + 1);
    std::copy(P.cbegin(), P.cend(), res.begin() + 1);
    return res;
}

template <typename T>
auto polymulx(std::vector<T> &&P) {
    P.push_back(T{0});
    std::rotate(P.rbegin(), P.rbegin() + 1, P.rend());
    return std::move(P);
}

//---------------------------------------------------------------------------------
// polypow
//---------------------------------------------------------------------------------

// https://en.wikipedia.org/wiki/Exponentiation_by_squaring
// - Recursive implementation is used for fixed size arrays (std::array).
// - Iterative implementation is used for dynamic size arrays (std::vector).

template <std::size_t pow, typename T, std::size_t N>
auto polypow(const std::array<T, N> &P) {
    // Return a std::array<T, pow * (N - 1) + 1>
    if constexpr (pow == 0) {
        return std::array{T{1}};
    } else if constexpr (pow == 1) {
        return std::array{P};
    } else {
        if constexpr (pow % 2 == 0) {
            return polypow<pow / 2>(polymul(P, P));
        } else {
            return polymul(P, polypow<(pow - 1) / 2>(polymul(P, P)));
        }
    }
}

template <signal::ConvMethod method, typename T>
auto polypow(const std::vector<T> &P, std::size_t pow) {
    if (pow == 0) {
        return std::vector(1, T{1});
    } else if (pow == 1) {
        return std::vector<T>(P.cbegin(), P.cend());
    }

    std::vector<T> x(P.cbegin(), P.cend());
    std::vector<T> y(1, T(1));
    x.reserve(pow * (P.size() - 1) + 1);
    y.reserve(pow * (P.size() - 1) + 1);

    while (pow > 1) {
        if (pow % 2 == 1) {
            y = std::move(polymul<method>(x, y));
        }

        pow = pow / 2;
        x = std::move(polymul<method>(x, x));
    }

    return polymul(x, y);
}

// Specialization for the default convolution method (DIRECT)
template <typename T>
auto polypow(const std::vector<T> &P, std::size_t pow) {
    return polypow<signal::DIRECT>(P, pow);
}

//---------------------------------------------------------------------------------
// polyder
//---------------------------------------------------------------------------------

namespace detail {

template <typename T, std::size_t N>
constexpr auto polyder_once(const std::array<T, N> &P) {
    static_assert(N >= 1);
    std::array<T, N - 1> res{};

    for (std::size_t i = 0; i < res.size(); ++i) {
        res[i] = T(N - 1 - i) * P[i];
    }

    return res;
}

template <typename T>
void polyder_once(std::vector<T> &P) {
    scicpp_require(P.size() >= 1);
    const auto N = P.size();

    for (std::size_t i = 0; i < (N - 1); ++i) {
        P[i] *= T(N - 1 - i);
    }

    P.resize(N - 1);
}

} // namespace detail

template <signed_size_t m, typename T, std::size_t N>
constexpr auto polyder(const std::array<T, N> &P) {
    static_assert(m >= 0);

    if constexpr (m == 0) {
        return std::array{P};
    } else if constexpr (m >= N) {
        return std::array<T, 0>{};
    } else {
        return polyder<m - 1>(detail::polyder_once(P));
    }
}

template <typename T, std::size_t N>
constexpr auto polyder(const std::array<T, N> &P) {
    return polyder<1>(P);
}

template <typename T>
auto polyder(const std::vector<T> &P, signed_size_t m = 1) {
    scicpp_require(m >= 0);

    if (m == 0) {
        return std::vector<T>(P);
    }

    if (m >= int(P.size())) {
        return std::vector<T>(0);
    }

    std::vector<T> res(P);

    while (m--) {
        detail::polyder_once(res);
    }

    return res;
}

//---------------------------------------------------------------------------------
// polyint
//---------------------------------------------------------------------------------

namespace detail {

template <typename T, std::size_t N>
constexpr auto polyint_once(const std::array<T, N> &P) {
    std::array<T, N + 1> res{};

    for (std::size_t i = 0; i < N; ++i) {
        res[i + 1] = P[i] / T(i + 1);
    }

    return res;
}

template <typename T>
void polyint_once(std::vector<T> &P) {
    const auto N = P.size();
    P.resize(N + 1);

    for (signed_size_t i = signed_size_t(N) - 1; i >= 0; --i) {
        P[std::size_t(i) + 1] = P[std::size_t(i)] / T(i + 1);
    }

    P[0] = T{0};
}

} // namespace detail

template <signed_size_t m, typename T, std::size_t N>
constexpr auto polyint(const std::array<T, N> &P) {
    static_assert(N > 0);
    static_assert(m >= 0);

    if constexpr (m == 0) {
        return std::array{P};
    } else {
        return polyint<m - 1>(detail::polyint_once(P));
    }
}

template <typename T, std::size_t N>
constexpr auto polyint(const std::array<T, N> &P) {
    return polyint<1>(P);
}

template <typename T>
auto polyint(const std::vector<T> &P, signed_size_t m = 1) {
    scicpp_require(P.size() > 0);
    scicpp_require(m >= 0);

    if (m == 0) {
        return std::vector<T>(P);
    }

    std::vector<T> res(P);
    res.reserve(P.size() + std::size_t(m));

    while (m--) {
        detail::polyint_once(res);
    }

    return res;
}

//---------------------------------------------------------------------------------
// polycompanion
//---------------------------------------------------------------------------------

template <typename T, std::size_t N>
auto polycompanion(const std::array<T, N> &P) {
    constexpr int deg = N - 1;
    Eigen::Matrix<T, deg, deg> res{};
    res.setZero();
    res.diagonal(-1).setOnes();
    res.col(deg - 1) = -linalg::to_eigen_array<deg>(P) / P[deg];
    return res;
}

template <typename T>
auto polycompanion(const std::vector<T> &P) {
    const int deg = int(P.size()) - 1;
    Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic> res(deg, deg);
    res.setZero();
    res.diagonal(-1).setOnes();
    res.col(deg - 1) = -linalg::to_eigen_matrix(P, deg) / P[std::size_t(deg)];
    return res;
}

//---------------------------------------------------------------------------------
// polyroots
//---------------------------------------------------------------------------------

template <class Array>
auto polyroots(const Array &P) {
    return linalg::to_std_container(polycompanion(P).eigenvalues());
}

//---------------------------------------------------------------------------------
// polyvander
//---------------------------------------------------------------------------------

namespace detail {

template <class Matrix, class Vector>
void polyvander_filler(Matrix &res, const Vector &x) {
    static_assert(meta::is_eigen_matrix_v<Matrix>);

    res.col(0).setOnes();
    res.col(1) = linalg::to_eigen_array(x);

    for (int i = 2; i < res.cols(); ++i) {
        res.col(i) = res.col(i - 1).array() * res.col(1).array();
    }
}

} // namespace detail

template <int deg, typename T, std::size_t N>
auto polyvander(const std::array<T, N> &x) {
    Eigen::Matrix<T, int(N), deg + 1> res{};
    detail::polyvander_filler(res, x);
    return res;
}

template <typename T>
auto polyvander(const std::vector<T> &x, int deg) {
    Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic> res(int(x.size()),
                                                         deg + 1);
    detail::polyvander_filler(res, x);
    return res;
}

//---------------------------------------------------------------------------------
// polyfit
//---------------------------------------------------------------------------------

template <int deg, typename T, std::size_t N>
auto polyfit(const std::array<T, N> &x, const std::array<T, N> &y) {
    return linalg::lstsq(polyvander<deg>(x), y);
}

template <typename T>
auto polyfit(const std::vector<T> &x, const std::vector<T> &y, int deg) {
    return linalg::lstsq(polyvander(x, deg), y);
}

//---------------------------------------------------------------------------------
// polytrim
//---------------------------------------------------------------------------------

template <typename T>
void polytrim(std::vector<T> &P, T tol = T{0}) {
    P.erase(
        std::find_if(P.crbegin(), P.crend(), [=](auto c) { return c > tol; })
            .base(),
        P.end());

    if (P.empty()) {
        P.push_back(T{0});
    }
}

//---------------------------------------------------------------------------------
// Polynomial class
//---------------------------------------------------------------------------------

template <typename T>
class Polynomial {
  public:
    using value_type = typename std::vector<T>::value_type;
    using size_type = typename std::vector<T>::size_type;

    explicit Polynomial(size_type deg) : m_coef(deg + 1, T{0}) {}

    explicit Polynomial(const std::vector<T> &coef) : m_coef(coef) {}
    explicit Polynomial(std::vector<T> &&coef) : m_coef(std::move(coef)) {}

    template <size_type N>
    explicit Polynomial(const std::array<T, N> &coef)
        : m_coef(coef.cbegin(), coef.cend()) {}

    template <class Iterator>
    Polynomial(Iterator first, Iterator last) : m_coef(first, last) {}

    Polynomial(std::initializer_list<T> l)
        : Polynomial(std::begin(l), std::end(l)) {}

    size_type degree() const { return m_coef.size() - 1; }

    const auto &data() const { return m_coef; }

    void mulx() { m_coef = polymulx(std::move(m_coef)); }

    void trim(T tol = T(0)) { polytrim(m_coef, tol); }

    T operator()(T x) const { return polyval(x, m_coef); }

    void reserve(size_type n_coefs) { m_coef.reserve(n_coefs); }

    Polynomial &operator+=(const Polynomial &rhs) {
        if (rhs.degree() > this->degree()) {
            const long old_size = long(m_coef.size());
            m_coef.resize(rhs.degree() + 1);
            std::transform(rhs.m_coef.begin(),
                           rhs.m_coef.begin() + old_size,
                           m_coef.begin(),
                           m_coef.begin(),
                           std::plus<T>());
            std::copy(rhs.m_coef.begin() + old_size,
                      rhs.m_coef.end(),
                      m_coef.begin() + old_size);
        } else {
            std::transform(rhs.m_coef.begin(),
                           rhs.m_coef.end(),
                           m_coef.begin(),
                           m_coef.begin(),
                           std::plus<T>());
        }

        return *this;
    }

    Polynomial &operator-=(const Polynomial &rhs) {
        if (rhs.degree() > this->degree()) {
            const auto old_size = m_coef.size();
            m_coef.resize(rhs.degree() + 1);

            for (size_type i = 0; i < old_size; ++i) {
                m_coef[i] -= rhs.m_coef[i];
            }

            for (size_type i = old_size; i < m_coef.size(); ++i) {
                m_coef[i] = -rhs.m_coef[i];
            }
        } else {
            for (size_type i = 0; i < rhs.degree() + 1; ++i) {
                m_coef[i] -= rhs.m_coef[i];
            }
        }

        return *this;
    }

    Polynomial &operator*=(T scalar) {
        for (auto &c : m_coef) {
            c *= scalar;
        }

        return *this;
    }

    Polynomial &operator*=(const Polynomial &rhs) {
        m_coef = std::move(polymul(m_coef, rhs.m_coef));
        return *this;
    }

    Polynomial operator+(const Polynomial &rhs) const {
        auto tmp(*this);
        tmp += rhs;
        return tmp;
    }

    Polynomial operator-(const Polynomial &rhs) const {
        auto tmp(*this);
        tmp -= rhs;
        return tmp;
    }

    Polynomial operator*(const Polynomial &rhs) const {
        auto tmp(*this);
        tmp *= rhs;
        return tmp;
    }

    bool scicpp_pure operator==(const Polynomial &rhs) const {
        return m_coef == rhs.m_coef;
    }

    bool scicpp_pure operator!=(const Polynomial &rhs) const {
        return m_coef != rhs.m_coef;
    }

    template <int rel_tol = 1>
    bool is_approx(const Polynomial &rhs) const {
        return almost_equal<rel_tol>(m_coef, rhs.m_coef);
    }

  private:
    std::vector<T> m_coef;

}; // class Polynomial

template <typename T>
Polynomial<T> operator*(const Polynomial<T> &P, T scalar) {
    auto tmp(P);
    tmp *= scalar;
    return tmp;
}

template <typename T>
Polynomial<T> operator*(T scalar, const Polynomial<T> &P) {
    return P * scalar;
}

//---------------------------------------------------------------------------------
// polyfromroots
//---------------------------------------------------------------------------------

// https://stackoverflow.com/questions/33594384/find-the-coefficients-of-the-polynomial-given-its-roots
template <class Array>
auto polyfromroots(const Array &roots) {
    using T = typename Array::value_type;

    auto P = Polynomial<T>({T{1}});
    auto P_ = Polynomial<T>({T{1}});
    P.reserve(roots.size() + 1);
    P_.reserve(roots.size());

    for (const auto &r : roots) {
        P_ = P * r;
        P.mulx();
        P -= P_;
    }

    return P;
}

} // namespace scicpp::polynomial

#endif // SCICPP_POLYNOMIALS_POLYNOMIAL


/*******************************************************************************
 *
 * Standard domain of numerical congruences extended with bitwise
 * operations.
 *
 * Author: Alexandre C. D. Wimmers (alexandre.c.wimmers@nasa.gov)
 *
 * Contributors: Jorge A. Navas (jorge.a.navaslaserna@nasa.gov)
 *
 * Notices:
 *
 * Copyright (c) 2011 United States Government as represented by the
 * Administrator of the National Aeronautics and Space Administration.
 * All Rights Reserved.
 *
 * Disclaimers:
 *
 * No Warranty: THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF
 * ANY KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED
 * TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO SPECIFICATIONS,
 * ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
 * OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL BE
 * ERROR FREE, OR ANY WARRANTY THAT DOCUMENTATION, IF PROVIDED, WILL CONFORM TO
 * THE SUBJECT SOFTWARE. THIS AGREEMENT DOES NOT, IN ANY MANNER, CONSTITUTE AN
 * ENDORSEMENT BY GOVERNMENT AGENCY OR ANY PRIOR RECIPIENT OF ANY RESULTS,
 * RESULTING DESIGNS, HARDWARE, SOFTWARE PRODUCTS OR ANY OTHER APPLICATIONS
 * RESULTING FROM USE OF THE SUBJECT SOFTWARE.  FURTHER, GOVERNMENT AGENCY
 * DISCLAIMS ALL WARRANTIES AND LIABILITIES REGARDING THIRD-PARTY SOFTWARE,
 * IF PRESENT IN THE ORIGINAL SOFTWARE, AND DISTRIBUTES IT "AS IS."
 *
 * Waiver and Indemnity:  RECIPIENT AGREES TO WAIVE ANY AND ALL CLAIMS AGAINST
 * THE UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL
 * AS ANY PRIOR RECIPIENT.  IF RECIPIENT'S USE OF THE SUBJECT SOFTWARE RESULTS
 * IN ANY LIABILITIES, DEMANDS, DAMAGES, EXPENSES OR LOSSES ARISING FROM SUCH
 * USE, INCLUDING ANY DAMAGES FROM PRODUCTS BASED ON, OR RESULTING FROM,
 * RECIPIENT'S USE OF THE SUBJECT SOFTWARE, RECIPIENT SHALL INDEMNIFY AND HOLD
 * HARMLESS THE UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS,
 * AS WELL AS ANY PRIOR RECIPIENT, TO THE EXTENT PERMITTED BY LAW.
 * RECIPIENT'S SOLE REMEDY FOR ANY SUCH MATTER SHALL BE THE IMMEDIATE,
 * UNILATERAL TERMINATION OF THIS AGREEMENT.
 *
 ******************************************************************************/

#pragma once

#include <crab/domains/abstract_domain.hpp>
#include <crab/domains/backward_assign_operations.hpp>
#include <crab/domains/interval.hpp>
#include <crab/domains/separate_domains.hpp>
#include <crab/support/debug.hpp>
#include <crab/support/stats.hpp>
#include <crab/types/variable.hpp>

#include <boost/optional.hpp>

namespace ikos {

template <typename Number> class congruence {
  using interval_t = interval<Number>;

public:
  using congruence_t = congruence<Number>;

private:
  bool _is_bottom;

  /// A congruence is denoted by aZ + b, where b \in Z and a \in N.
  /// The abstract state aZ + b represents all numbers that are
  /// congruent to b modulo a.
  Number _a; // modulo
  Number _b; // remainder

  // Notes about the % operator
  //
  // The semantics of r = n % d is to set r to "n mod d". The sign of
  // the d is ignored and r is always non-negative.
  //
  // We assume that n % d (also n /d) raises a runtime error if d==0.

  void normalize(void) {
    // Set to standard form: 0 <= b < a for a != 0
    if (_a != 0) {
      _b = _b % _a;
    }
  }

  // if true then top (1Z + 0) else bottom
  congruence(bool b) : _is_bottom(!b), _a(1), _b(0) {}

  congruence(int n) : _is_bottom(false), _a(0), _b(n) {}

  congruence(Number a, Number b) : _is_bottom(false), _a(a), _b(b) {
    normalize();
  }

  Number abs(Number x) const { return x < 0 ? -x : x; }

  Number max(Number x, Number y) const { return x.operator<=(y) ? y : x; }

  Number min(Number x, Number y) const { return x.operator<(y) ? x : y; }

  Number gcd(Number x, Number y, Number z) const { return gcd(x, gcd(y, z)); }
  // Not to be called explicitly outside of gcd
  Number gcd_helper(Number x, Number y) const {
    return (y == 0) ? x : gcd_helper(y, x % y);
  }
  Number gcd(Number x, Number y) const { return gcd_helper(abs(x), abs(y)); }

  Number lcm(Number x, Number y) const {
    Number tmp = gcd(x, y);
    return abs(x * y) / tmp;
  }

  bool is_zero() const { return !is_bottom() && _a == 0 && _b == 0; }

  bool all_ones() const { return !is_bottom() && _a == 0 && _b == -1; }

  interval_t to_interval() const {
    assert(singleton());
    return interval_t(*(singleton()));
  }

public:
  static congruence_t top() { return congruence(true); }

  static congruence_t bottom() { return congruence(false); }

  congruence() : _is_bottom(false), _a(1), _b(0) {}

  congruence(Number n) : _is_bottom(false), _a(0), _b(n) {}

  congruence(const congruence_t &o)
      : _is_bottom(o._is_bottom), _a(o._a), _b(o._b) {}

  congruence_t operator=(congruence_t o) {
    _is_bottom = o._is_bottom;
    _a = o._a;
    _b = o._b;
    return *this;
  }

  bool is_bottom() const { return _is_bottom; }

  bool is_top() const { return _a == 1; }

  boost::optional<Number> singleton() const {
    if (!this->is_bottom() && _a == 0) {
      return boost::optional<Number>(_b);
    } else {
      return boost::optional<Number>();
    }
  }

  Number get_modulo() const { return _a; }

  Number get_remainder() const { return _b; }

  bool operator==(congruence_t o) const {
    return (is_bottom() == o.is_bottom() && _a == o._a && _b == o._b);
  }

  bool operator!=(congruence_t x) const { return !this->operator==(x); }

  /** Lattice Operations **/

  bool operator<=(const congruence_t &o) const {
    if (is_bottom()) {
      return true;
    } else if (o.is_bottom()) {
      return false;
    } else if (_a == 0 && o._a == 0) {
      return (_b == o._b);
    } else if (_a == 0) {
      if ((_b % o._a) == (o._b % o._a)) {
        return true;
      }
    } else if (o._a == 0) {
      if (_b % _a == (o._b % _a)) {
        return false;
      }
    }
    return (_a % o._a == 0) && (_b % o._a == o._b % o._a);
  }

  congruence_t operator|(congruence_t o) {
    if (is_bottom()) {
      return o;
    } else if (o.is_bottom()) {
      return *this;
    } else if (is_top() || o.is_top()) {
      return top();
    } else {
      return congruence_t(gcd(_a, o._a, abs(_b - o._b)), min(_b, o._b));
    }
  }

  congruence_t operator&(congruence_t o) {
    if (is_bottom() || o.is_bottom()) {
      return bottom();
    }

    // lcm has meaning only if both a and o.a are not 0
    if (_a == 0 && o._a == 0) {
      if (_b == o._b) {
        return *this;
      } else {
        return bottom();
      }
    } else if (_a == 0) {
      // b & a'Z + b' iff \exists k such that a'*k + b' = b iff ((b - b') %a' ==
      // 0)
      if ((_b - o._b) % o._a == 0) {
        return *this;
      } else {
        return bottom();
      }
    } else if (o._a == 0) {
      // aZ+b & b' iff \exists k such that a*k+b  = b' iff ((b'-b %a) == 0)
      if ((o._b - _b) % _a == 0) {
        return o;
      } else {
        return bottom();
      }
    } else {
      // pre: a and o.a != 0
      Number x = gcd(_a, o._a);
      if (_b % x == (o._b % x)) {
        // the part max(b,o.b) needs to be verified. What we really
        // want is to find b'' such that
        // 1) b'' % lcm(a,a') == b  % lcm(a,a'), and
        // 2) b'' % lcm(a,a') == b' % lcm(a,a').
        // An algorithm for that is provided in Granger'89.
        return congruence_t(lcm(_a, o._a), max(_b, o._b));
      } else {
        return congruence_t::bottom();
      }
    }
  }

  congruence_t operator||(congruence_t o) {
    // Equivalent to join, domain is flat
    return *this | o;
  }

  congruence_t operator&&(congruence_t o) {
    // Simply refines top element
    return (is_top()) ? o : *this;
  }

  /** Arithmetic Operators **/

  congruence_t operator+(congruence_t o) {
    if (this->is_bottom() || o.is_bottom())
      return congruence_t::bottom();
    else if (this->is_top() || o.is_top())
      return congruence_t::top();
    else
      return congruence_t(gcd(_a, o._a), _b + o._b);
  }

  congruence_t operator-(congruence_t o) {
    if (this->is_bottom() || o.is_bottom())
      return congruence_t::bottom();
    else if (this->is_top() || o.is_top())
      return congruence_t::top();
    else
      return congruence_t(gcd(_a, o._a), _b - o._b);
  }

  congruence_t operator-() {
    if (this->is_bottom() || this->is_top())
      return *this;
    else
      return congruence_t(_a, -_b + _a);
  }

  congruence_t operator*(congruence_t o) {
    if (this->is_bottom() || o.is_bottom())
      return congruence_t::bottom();
    else if ((this->is_top() || o.is_top()) && _a != 0 && o._a != 0)
      return congruence_t::top();
    else
      return congruence_t(gcd(_a * o._a, _a * o._b, o._a * _b), _b * o._b);
  }

  // signed division
  congruence_t operator/(congruence_t o) {
    if (this->is_bottom() || o.is_bottom())
      return congruence_t::bottom();
    else if (o == congruence(0))
      return congruence_t::bottom();
    else if (this->is_top() || o.is_top())
      return congruence_t::top();
    else {

      /*
         aZ+b / 0Z+b':
            if b'|a then  (a/b')Z + b/b'
            else          top
      */
      if (o._a == 0) {
        if (_a % o._b == 0)
          return congruence_t(_a / o._b, _b / o._b);
        else
          return congruence_t::top();
      }

      /*
         0Z+b / a'Z+b':
            if N>0   (b div N)Z + 0
            else     0Z + 0

           where N = a'((b-b') div a') + b'
      */
      if (_a == 0) {
        Number n(o._a * (((_b - o._b) / o._a) + o._b));
        if (n > 0) {
          return congruence_t(_b / n, 0);
        } else {
          return congruence_t(0, 0);
        }
      }

      /*
        General case: no singleton
      */
      return congruence_t::top();
    }
  }

  // signed remainder operator
  congruence_t operator%(congruence_t o) {
    if (this->is_bottom() || o.is_bottom())
      return congruence_t::bottom();
    else if (o == congruence(0))
      return congruence_t::bottom();
    else if (this->is_top() || o.is_top())
      return congruence_t::top();
    else {
      /*
         aZ+b mod 0Z+b':
             if b'|a then  (a/b')Z + b/b'
             else          top
      */
      if (o._a == 0) {
        if (_a % o._b == 0) {
          return congruence_t(0, _b % o._b);
        } else {
          return congruence_t(gcd(_a, o._b), _b);
        }
      }

      /*
          0Z+b mod a'Z+b':
           if N<=0           then 0Z+b
           if (b div N) == 1 then gcd(b',a')Z + b
           if (b div N) >= 2 then N(b div N)Z  + b

         where N = a'((b-b') div a') + b'
      */
      if (_a == 0) {
        Number n(o._a * (((_b - o._b) / o._a) + o._b));
        if (n <= 0) {
          return congruence_t(_a, _b);
        } else if (_b == n) {
          return congruence_t(gcd(o._b, o._a), _b);
        } else if ((_b / n) >= 2) {
          return congruence_t(_b, _b);
        } else {
          CRAB_ERROR("unreachable");
        }
      }

      /*
          general case: no singleton
      */
      return congruence_t(gcd(_a, o._a, o._b), _b);
    }
  }

  /**
      Bitwise operators.
      They are very imprecise because we ignore bitwidth.

      Bitwise operation can be implemented more precisely based on
      Stefan Bygde's paper: Static WCET analysis based on abstract
      interpretation and counting of elements, Vasteras : School of
      Innovation, Design and Engineering, Malardalen University (2010).
   **/

  congruence_t And(congruence_t o) {
    if (this->is_bottom() || o.is_bottom())
      return congruence_t::bottom();
    else if (this->is_top() || o.is_top())
      return congruence_t::top();
    else {
      if (is_zero() || o.is_zero()) {
        return congruence_t(0);
      } else if (all_ones()) {
        return o;
      } else if (o.all_ones()) {
        return *this;
      } else if (_a == 0 && o._a == 0) {
        return congruence_t(_b & o._b);
      } else {
        return top();
      }
    }
  }

  congruence_t Or(congruence_t o) {
    if (this->is_bottom() || o.is_bottom())
      return congruence_t::bottom();
    else if (this->is_top() || o.is_top())
      return congruence_t::top();
    else {
      if (all_ones() || o.all_ones()) {
        return congruence_t(-1);
      } else if (is_zero()) {
        return o;
      } else if (o.is_zero()) {
        return *this;
      } else if (_a == 0 && o._a == 0) {
        return congruence_t(_b | o._b);
      } else {
        return top();
      }
    }
  }

  congruence_t Xor(congruence_t o) {
    if (this->is_bottom() || o.is_bottom())
      return congruence_t::bottom();
    else if (this->is_top() || o.is_top())
      return congruence_t::top();
    else {
      if (is_zero()) {
        return o;
      } else if (o.is_zero()) {
        return *this;
      } else if (_a == 0 && o._a == 0) {
        return congruence_t(_b ^ o._b);
      } else {
        return top();
      }
    }
  }

  congruence_t Shl(congruence_t o) {
    if (this->is_bottom() || o.is_bottom())
      return congruence_t::bottom();
    else if (this->is_top() || o.is_top())
      return congruence_t::top();
    else {

      if (o._a == 0) { // singleton

        if (o._b < 0) {
          return bottom();
        }

        // aZ + b << 0Z + b'  = (a*2^b')Z + b*2^b'
        Number x = Number(1) << o._b;
        return congruence_t(_a * x, _b * x);
      } else {

        Number x = Number(1) << o._b;
        Number y = Number(1) << o._a;
        // aZ + b << a'Z + b' = (gcd(a, b * (2^a' - 1)))*(2^b')Z + b*(2^b')
        return congruence_t(gcd(_a, _b * (y - 1)) * x, _b * x);
      }
    }
  }

  congruence_t AShr(congruence_t o) {
    if (this->is_bottom() || o.is_bottom())
      return congruence_t::bottom();
    else if (this->is_top() || o.is_top())
      return congruence_t::top();
    else {

      if (o._a == 0) { // singleton
        // aZ + b >> 0Z + b'
        if (o._b < 0) {
          return congruence_t::bottom();
        }
      }

      if (singleton() && o.singleton()) {
        interval_t res = to_interval().AShr(o.to_interval());
        if (boost::optional<Number> n = res.singleton()) {
          return congruence(*n);
        }
      }

      return congruence_t::top();
    }
  }

  congruence_t LShr(congruence_t o) {
    if (this->is_bottom() || o.is_bottom())
      return congruence_t::bottom();
    else if (this->is_top() || o.is_top())
      return congruence_t::top();
    else {

      if (o._a == 0) {
        // aZ + b >> 0Z + b'
        if (o._b < 0) {
          return congruence_t::bottom();
        }
      }

      if (singleton() && o.singleton()) {
        interval_t res = to_interval().LShr(o.to_interval());
        if (boost::optional<Number> n = res.singleton()) {
          return congruence(*n);
        }
      }

      return congruence_t::top();
    }
  }

  // division and remainder operations

  congruence_t SDiv(congruence_t x) { return this->operator/(x); }

  congruence_t UDiv(congruence_t x) { return congruence_t::top(); }

  congruence_t SRem(congruence_t x) { return this->operator%(x); }

  congruence_t URem(congruence_t x) { return congruence_t::top(); }

  void write(crab::crab_os &o) const {
    if (is_bottom()) {
      o << "_|_";
      return;
    }

    if (_a == 0) {
      o << _b;
      return;
    }

    o << _a << "Z+" << _b;
  }
}; // end class congruence

template <typename Number>
inline crab::crab_os &operator<<(crab::crab_os &o,
                                 const congruence<Number> &c) {
  c.write(o);
  return o;
}

template <typename Number>
inline congruence<Number> operator+(Number c, congruence<Number> x) {
  return congruence<Number>(c) + x;
}

template <typename Number>
inline congruence<Number> operator+(congruence<Number> x, Number c) {
  return x + congruence<Number>(c);
}

template <typename Number>
inline congruence<Number> operator*(Number c, congruence<Number> x) {
  return congruence<Number>(c) * x;
}

template <typename Number>
inline congruence<Number> operator*(congruence<Number> x, Number c) {
  return x * congruence<Number>(c);
}

template <typename Number>
inline congruence<Number> operator/(Number c, congruence<Number> x) {
  return congruence<Number>(c) / x;
}

template <typename Number>
inline congruence<Number> operator/(congruence<Number> x, Number c) {
  return x / congruence<Number>(c);
}

template <typename Number>
inline congruence<Number> operator-(Number c, congruence<Number> x) {
  return congruence<Number>(c) - x;
}

template <typename Number>
inline congruence<Number> operator-(congruence<Number> x, Number c) {
  return x - congruence<Number>(c);
}

template <typename Number, typename VariableName, typename CongruenceCollection>
class equality_congruence_solver {
  // TODO: check correctness of the solver. Granger provides a sound
  // and more precise solver for equality linear congruences (see
  // Theorem 4.4).
private:
  using congruence_t = congruence<Number>;
  using variable_t = crab::variable<Number, VariableName>;
  using linear_expression_t = linear_expression<Number, VariableName>;
  using linear_constraint_t = linear_constraint<Number, VariableName>;
  using linear_constraint_system_t =
      linear_constraint_system<Number, VariableName>;

  using cst_table_t = std::vector<linear_constraint_t>;
  using variable_set_t = std::set<variable_t>;

  std::size_t m_max_cycles;
  bool m_is_contradiction;
  cst_table_t m_cst_table;
  variable_set_t m_refined_variables;
  std::size_t m_op_count;

private:
  bool refine(const variable_t &v, congruence_t i, CongruenceCollection &env) {
    congruence_t old_i = env[v];
    congruence_t new_i = old_i & i;
    if (new_i.is_bottom()) {
      return true;
    }
    if (old_i != new_i) {
      env.set(v, new_i);
      m_refined_variables.insert(v);
      ++(m_op_count);
    }
    return false;
  }

  congruence_t compute_residual(const linear_constraint_t &cst,
                                const variable_t &pivot,
                                CongruenceCollection &env) {
    congruence_t residual(cst.constant());
    for (auto kv : cst) {
      const variable_t &v = kv.second;
      if (!(v == pivot)) {
        residual = residual - (kv.first * env[v]);
        ++(m_op_count);
      }
    }
    return residual;
  }

  bool propagate(const linear_constraint_t &cst, CongruenceCollection &env) {
    for (auto kv : cst) {
      Number c = kv.first;
      const variable_t &pivot = kv.second;
      congruence_t rhs = compute_residual(cst, pivot, env) / congruence_t(c);

      if (cst.is_equality()) {
        if (refine(pivot, rhs, env)) {
          return true;
        }
      } else if (cst.is_inequality() || cst.is_strict_inequality()) {
        // Inequations (>=, <=, >, and <) do not work well with
        // congruences because for any number n there is always x and y
        // \in gamma(aZ+b) such that n < x and n > y.
        //
        // The only cases we can catch is when all the expressions
        // are constants. We do not bother because any product
        // with intervals or constants should get those cases.
        continue;
      } else {
        // TODO: cst is a disequation
      }
    }
    return false;
  }

  bool solve_system(CongruenceCollection &env) {
    std::size_t cycle = 0;
    do {
      ++cycle;
      m_refined_variables.clear();
      for (const linear_constraint_t &cst : m_cst_table) {
        if (propagate(cst, env)) {
          return true;
        }
      }
    } while (m_refined_variables.size() > 0 && cycle <= m_max_cycles);
    return false;
  }

public:
  equality_congruence_solver(const linear_constraint_system_t &csts,
                             std::size_t max_cycles)
      : m_max_cycles(max_cycles), m_is_contradiction(false) {
    for (auto const &cst : csts) {
      if (cst.is_contradiction()) {
        m_is_contradiction = true;
        return;
      } else if (cst.is_tautology()) {
        continue;
      } else {
        m_cst_table.push_back(cst);
      }
    }
  }

  void run(CongruenceCollection &env) {
    if (m_is_contradiction) {
      env.set_to_bottom();
    } else {
      if (solve_system(env)) {
        env.set_to_bottom();
      }
    }
  }

}; // class equality_congruence_solver

template <typename Number, typename VariableName>
class congruence_domain final : public crab::domains::abstract_domain_api<
                                    congruence_domain<Number, VariableName>> {
public:
  using congruence_t = congruence<Number>;

private:
  // note that this is assuming that all variables have the same bit
  // width which is unrealistic.
  using congruence_domain_t = congruence_domain<Number, VariableName>;
  using abstract_domain_t =
      crab::domains::abstract_domain_api<congruence_domain_t>;

public:
  using typename abstract_domain_t::disjunctive_linear_constraint_system_t;
  using typename abstract_domain_t::interval_t;
  using typename abstract_domain_t::linear_constraint_system_t;
  using typename abstract_domain_t::linear_constraint_t;
  using typename abstract_domain_t::linear_expression_t;
  using typename abstract_domain_t::variable_or_constant_t;
  using typename abstract_domain_t::variable_t;
  using typename abstract_domain_t::variable_vector_t;
  using typename abstract_domain_t::variable_or_constant_vector_t;
  using number_t = Number;
  using varname_t = VariableName;
  using typename abstract_domain_t::reference_constraint_t;

private:
  using separate_domain_t = separate_domain<variable_t, congruence_t>;
  using solver_t =
      equality_congruence_solver<number_t, varname_t, separate_domain_t>;

public:
  using iterator = typename separate_domain_t::iterator;

private:
  separate_domain_t _env;

private:
  congruence_domain(separate_domain_t env) : _env(env) {}

public:
  congruence_domain_t make_top() const override {
    return congruence_domain_t(separate_domain_t::top());
  }

  congruence_domain_t make_bottom() const override {
    return congruence_domain_t(separate_domain_t::bottom());
  }

  void set_to_top() override {
    congruence_domain abs(separate_domain_t::top());
    std::swap(*this, abs);
  }

  void set_to_bottom() override {
    congruence_domain abs(separate_domain_t::bottom());
    std::swap(*this, abs);
  }

  congruence_domain() : _env(separate_domain_t::top()) {}

  congruence_domain(const congruence_domain_t &e) : _env(e._env) {
    crab::CrabStats::count(domain_name() + ".count.copy");
    crab::ScopedCrabStats __st__(domain_name() + ".copy");
  }

  congruence_domain_t &operator=(const congruence_domain_t &o) {
    crab::CrabStats::count(domain_name() + ".count.copy");
    crab::ScopedCrabStats __st__(domain_name() + ".copy");
    if (this != &o)
      this->_env = o._env;
    return *this;
  }

  iterator begin() { return this->_env.begin(); }

  iterator end() { return this->_env.end(); }

  bool is_bottom() const override { return this->_env.is_bottom(); }

  bool is_top() const override { return this->_env.is_top(); }

  bool operator<=(const congruence_domain_t &e) const override {
    crab::CrabStats::count(domain_name() + ".count.leq");
    crab::ScopedCrabStats __st__(domain_name() + ".leq");
    return this->_env <= e._env;
  }

  void operator|=(const congruence_domain_t &e) override {
    crab::CrabStats::count(domain_name() + ".count.join");
    crab::ScopedCrabStats __st__(domain_name() + ".join");
    this->_env = this->_env | e._env;
  }

  congruence_domain_t operator|(const congruence_domain_t &e) const override {
    crab::CrabStats::count(domain_name() + ".count.join");
    crab::ScopedCrabStats __st__(domain_name() + ".join");
    return this->_env | e._env;
  }

  congruence_domain_t operator&(const congruence_domain_t &e) const override {
    crab::CrabStats::count(domain_name() + ".count.meet");
    crab::ScopedCrabStats __st__(domain_name() + ".meet");
    return this->_env & e._env;
  }

  congruence_domain_t operator||(const congruence_domain_t &e) const override {
    crab::CrabStats::count(domain_name() + ".count.widening");
    crab::ScopedCrabStats __st__(domain_name() + ".widening");
    return this->_env || e._env;
  }

  congruence_domain_t widening_thresholds(
      const congruence_domain_t &other,
      const crab::iterators::thresholds<number_t> &) const override {
    return (*this || other);
  }

  congruence_domain_t operator&&(const congruence_domain_t &e) const override {
    crab::CrabStats::count(domain_name() + ".count.narrowing");
    crab::ScopedCrabStats __st__(domain_name() + ".narrowing");
    return this->_env && e._env;
  }

  void set(const variable_t &v, congruence_t i) {
    crab::CrabStats::count(domain_name() + ".count.assign");
    crab::ScopedCrabStats __st__(domain_name() + ".assign");
    this->_env.set(v, i);
  }

  void set(const variable_t &v, number_t n) {
    crab::CrabStats::count(domain_name() + ".count.assign");
    crab::ScopedCrabStats __st__(domain_name() + ".assign");
    this->_env.set(v, congruence_t(n));
  }

  void operator-=(const variable_t &v) override {
    crab::CrabStats::count(domain_name() + ".count.forget");
    crab::ScopedCrabStats __st__(domain_name() + ".forget");
    this->_env -= v;
  }

  virtual interval_t operator[](const variable_t &v) override {
    CRAB_WARN(domain_name(), "::operator[] not implemented");
    return interval_t::top();
  }

  congruence_t to_congruence(const variable_t &v) { return this->_env[v]; }

  congruence_t to_congruence(const linear_expression_t &expr) {
    congruence_t r(expr.constant());
    for (auto kv : expr) {
      congruence_t c(kv.first);
      r = r + (c * to_congruence(kv.second));
    }
    return r;
  }

  void operator+=(const linear_constraint_system_t &csts) override {
    crab::CrabStats::count(domain_name() + ".count.add_constraints");
    crab::ScopedCrabStats __st__(domain_name() + ".add_constraints");
    const std::size_t threshold = 10;
    if (!this->is_bottom()) {
      solver_t solver(csts, threshold);
      solver.run(this->_env);
    }
  }

  void assign(const variable_t &x, const linear_expression_t &e) override {
    crab::CrabStats::count(domain_name() + ".count.assign");
    crab::ScopedCrabStats __st__(domain_name() + ".assign");

    congruence_t r = e.constant();
    for (auto kv : e) {
      r = r + (kv.first * this->_env[kv.second]);
    }
    this->_env.set(x, r);
  }

  void apply(crab::domains::arith_operation_t op, const variable_t &x,
             const variable_t &y, const variable_t &z) override {
    crab::CrabStats::count(domain_name() + ".count.apply");
    crab::ScopedCrabStats __st__(domain_name() + ".apply");

    congruence_t yi = this->_env[y];
    congruence_t zi = this->_env[z];
    congruence_t xi = congruence_t::bottom();

    switch (op) {
    case crab::domains::OP_ADDITION:
      xi = yi + zi;
      break;
    case crab::domains::OP_SUBTRACTION:
      xi = yi - zi;
      break;
    case crab::domains::OP_MULTIPLICATION:
      xi = yi * zi;
      break;
    case crab::domains::OP_SDIV:
      xi = yi / zi;
      break;
    case crab::domains::OP_UDIV:
      xi = yi.UDiv(zi);
      break;
    case crab::domains::OP_SREM:
      xi = yi.SRem(zi);
      break;
    case crab::domains::OP_UREM:
      xi = yi.URem(zi);
      break;
    default:
      CRAB_ERROR("Operation ", op, " not supported");
    }
    this->_env.set(x, xi);
  }

  void apply(crab::domains::arith_operation_t op, const variable_t &x,
             const variable_t &y, number_t k) override {
    crab::CrabStats::count(domain_name() + ".count.apply");
    crab::ScopedCrabStats __st__(domain_name() + ".apply");

    congruence_t yi = this->_env[y];
    congruence_t zi(k);
    congruence_t xi = congruence_t::bottom();

    switch (op) {
    case crab::domains::OP_ADDITION:
      xi = yi + zi;
      break;
    case crab::domains::OP_SUBTRACTION:
      xi = yi - zi;
      break;
    case crab::domains::OP_MULTIPLICATION:
      xi = yi * zi;
      break;
    case crab::domains::OP_SDIV:
      xi = yi / zi;
      break;
    case crab::domains::OP_UDIV:
      xi = yi.UDiv(zi);
      break;
    case crab::domains::OP_SREM:
      xi = yi.SRem(zi);
      break;
    case crab::domains::OP_UREM:
      xi = yi.URem(zi);
      break;
    default:
      CRAB_ERROR("Operation ", op, " not supported");
    }
    this->_env.set(x, xi);
  }

  // backward operations
  void backward_assign(const variable_t &x, const linear_expression_t &e,
                       const congruence_domain_t &inv) override {
    crab::CrabStats::count(domain_name() + ".count.backward_assign");
    crab::ScopedCrabStats __st__(domain_name() + ".backward_assign");

    crab::domains::BackwardAssignOps<congruence_domain_t>::assign(*this, x, e,
                                                                  inv);
  }

  void backward_apply(crab::domains::arith_operation_t op, const variable_t &x,
                      const variable_t &y, number_t z,
                      const congruence_domain_t &inv) override {
    crab::CrabStats::count(domain_name() + ".count.backward_apply");
    crab::ScopedCrabStats __st__(domain_name() + ".backward_apply");

    crab::domains::BackwardAssignOps<congruence_domain_t>::apply(*this, op, x,
                                                                 y, z, inv);
  }

  void backward_apply(crab::domains::arith_operation_t op, const variable_t &x,
                      const variable_t &y, const variable_t &z,
                      const congruence_domain_t &inv) override {
    crab::CrabStats::count(domain_name() + ".count.backward_apply");
    crab::ScopedCrabStats __st__(domain_name() + ".backward_apply");

    crab::domains::BackwardAssignOps<congruence_domain_t>::apply(*this, op, x,
                                                                 y, z, inv);
  }

  // cast operations

  void apply(crab::domains::int_conv_operation_t /*op*/, const variable_t &dst,
             const variable_t &src) override {
    // ignore widths
    assign(dst, src);
  }

  // bitwise operations

  void apply(crab::domains::bitwise_operation_t op, const variable_t &x,
             const variable_t &y, const variable_t &z) override {
    crab::CrabStats::count(domain_name() + ".count.apply");
    crab::ScopedCrabStats __st__(domain_name() + ".apply");

    congruence_t yi = this->_env[y];
    congruence_t zi = this->_env[z];
    congruence_t xi = congruence_t::bottom();

    switch (op) {
    case crab::domains::OP_AND: {
      xi = yi.And(zi);
      break;
    }
    case crab::domains::OP_OR: {
      xi = yi.Or(zi);
      break;
    }
    case crab::domains::OP_XOR: {
      xi = yi.Xor(zi);
      break;
    }
    case crab::domains::OP_SHL: {
      xi = yi.Shl(zi);
      break;
    }
    case crab::domains::OP_LSHR: {
      xi = yi.LShr(zi);
      break;
    }
    case crab::domains::OP_ASHR: {
      xi = yi.AShr(zi);
      break;
    }
    default: { CRAB_ERROR("unreachable"); }
    }
    this->_env.set(x, xi);
  }

  void apply(crab::domains::bitwise_operation_t op, const variable_t &x,
             const variable_t &y, number_t k) override {
    crab::CrabStats::count(domain_name() + ".count.apply");
    crab::ScopedCrabStats __st__(domain_name() + ".apply");

    congruence_t yi = this->_env[y];
    congruence_t zi(k);
    congruence_t xi = congruence_t::bottom();

    switch (op) {
    case crab::domains::OP_AND: {
      xi = yi.And(zi);
      break;
    }
    case crab::domains::OP_OR: {
      xi = yi.Or(zi);
      break;
    }
    case crab::domains::OP_XOR: {
      xi = yi.Xor(zi);
      break;
    }
    case crab::domains::OP_SHL: {
      xi = yi.Shl(zi);
      break;
    }
    case crab::domains::OP_LSHR: {
      xi = yi.LShr(zi);
      break;
    }
    case crab::domains::OP_ASHR: {
      xi = yi.AShr(zi);
      break;
    }
    default: { CRAB_ERROR("unreachable"); }
    }
    this->_env.set(x, xi);
  }

  DEFAULT_SELECT(congruence_domain_t)

  /// congruence_domain implements only standard abstract operations
  /// of a numerical domain so it is intended to be used as a leaf
  /// domain in the hierarchy of domains.
  BOOL_OPERATIONS_NOT_IMPLEMENTED(congruence_domain_t)
  ARRAY_OPERATIONS_NOT_IMPLEMENTED(congruence_domain_t)
  REGION_AND_REFERENCE_OPERATIONS_NOT_IMPLEMENTED(congruence_domain_t)

  void forget(const variable_vector_t &variables) override {
    if (is_bottom() || is_top()) {
      return;
    }
    for (variable_t var : variables) {
      this->operator-=(var);
    }
  }

  void project(const variable_vector_t &variables) override {
    crab::CrabStats::count(domain_name() + ".count.project");
    crab::ScopedCrabStats __st__(domain_name() + ".project");

    _env.project(variables);
  }

  void expand(const variable_t &x, const variable_t &new_x) override {
    crab::CrabStats::count(domain_name() + ".count.expand");
    crab::ScopedCrabStats __st__(domain_name() + ".expand");

    if (is_bottom() || is_top()) {
      return;
    }

    set(new_x, this->_env[x]);
  }

  void normalize() override {}

  void minimize() override {}

  void rename(const variable_vector_t &from,
              const variable_vector_t &to) override {
    crab::CrabStats::count(domain_name() + ".count.rename");
    crab::ScopedCrabStats __st__(domain_name() + ".rename");

    _env.rename(from, to);
  }

  /* begin intrinsics operations */
  void intrinsic(std::string name,
		 const variable_or_constant_vector_t &inputs,
                 const variable_vector_t &outputs) override {
    CRAB_WARN("Intrinsics ", name, " not implemented by ", domain_name());
  }

  void backward_intrinsic(std::string name,
			  const variable_or_constant_vector_t &inputs,
                          const variable_vector_t &outputs,
                          const congruence_domain_t &invariant) override {
    CRAB_WARN("Intrinsics ", name, " not implemented by ", domain_name());
  }
  /* end intrinsics operations */

  void write(crab::crab_os &o) const override {
    crab::CrabStats::count(domain_name() + ".count.write");
    crab::ScopedCrabStats __st__(domain_name() + ".write");

    this->_env.write(o);
  }

  linear_constraint_system_t to_linear_constraint_system() const override {
    crab::CrabStats::count(domain_name() +
                           ".count.to_linear_constraint_system");
    crab::ScopedCrabStats __st__(domain_name() +
                                 ".to_linear_constraint_system");

    linear_constraint_system_t csts;
    if (is_bottom()) {
      csts += linear_constraint_t::get_false();
      return csts;
    }

    for (iterator it = this->_env.begin(); it != this->_env.end(); ++it) {
      const variable_t &v = it->first;
      congruence_t c = it->second;
      boost::optional<number_t> n = c.singleton();
      if (n) {
        csts += (v == *n);
      }
    }
    return csts;
  }

  disjunctive_linear_constraint_system_t
  to_disjunctive_linear_constraint_system() const override {
    auto lin_csts = to_linear_constraint_system();
    if (lin_csts.is_false()) {
      return disjunctive_linear_constraint_system_t(true /*is_false*/);
    } else if (lin_csts.is_true()) {
      return disjunctive_linear_constraint_system_t(false /*is_false*/);
    } else {
      return disjunctive_linear_constraint_system_t(lin_csts);
    }
  }

  std::string domain_name() const override { return "Congruences"; }

}; // class congruence_domain

} // namespace ikos

namespace crab {
namespace domains {

template <typename Number, typename VariableName>
struct abstract_domain_traits<ikos::congruence_domain<Number, VariableName>> {
  using number_t = Number;
  using varname_t = VariableName;
};

} // namespace domains
} // namespace crab


#include <stan/math/mix/scal.hpp>
#include <gtest/gtest.h>
#include <boost/math/special_functions/digamma.hpp>
#include <math/rev/scal/util.hpp>
#include <math/mix/scal/fun/nan_util.hpp>

TEST(AgradFwdBinomialCoefficientLog, FvarVar_Double_1stDeriv) {
  using boost::math::digamma;
  using stan::math::binomial_coefficient_log;
  using stan::math::fvar;
  using stan::math::var;

  fvar<var> x(2004.0, 1.0);
  double z(1002);
  fvar<var> a = binomial_coefficient_log(x, z);

  EXPECT_FLOAT_EQ(binomial_coefficient_log(2004.0, 1002.0), a.val_.val());
  EXPECT_FLOAT_EQ(0.69289774, a.d_.val());

  AVEC y = createAVEC(x.val_);
  VEC g;
  a.val_.grad(y, g);
  EXPECT_FLOAT_EQ(0.69289774, g[0]);
}
TEST(AgradFwdBinomialCoefficientLog, Double_FvarVar_1stDeriv) {
  using boost::math::digamma;
  using stan::math::binomial_coefficient_log;
  using stan::math::fvar;
  using stan::math::var;

  double x(2004.0);
  fvar<var> z(1002.0, 2.0);
  fvar<var> a = binomial_coefficient_log(x, z);

  EXPECT_FLOAT_EQ(binomial_coefficient_log(2004.0, 1002.0), a.val_.val());
  EXPECT_NEAR(0, a.d_.val(), 1e-8);

  AVEC y = createAVEC(z.val_);
  VEC g;
  a.val_.grad(y, g);
  EXPECT_NEAR(0, g[0], 1e-8);
}
TEST(AgradFwdBinomialCoefficientLog, FvarVar_Double_2ndDeriv) {
  using boost::math::digamma;
  using stan::math::binomial_coefficient_log;
  using stan::math::fvar;
  using stan::math::var;

  fvar<var> x(2004.0, 1.0);
  double z(1002);
  fvar<var> a = binomial_coefficient_log(x, z);

  EXPECT_FLOAT_EQ(binomial_coefficient_log(2004.0, 1002.0), a.val_.val());
  EXPECT_FLOAT_EQ(0.69289774, a.d_.val());

  AVEC y = createAVEC(x.val_);
  VEC g;
  a.d_.grad(y, g);
  EXPECT_FLOAT_EQ(-0.00049862865, g[0]);
}
TEST(AgradFwdBinomialCoefficientLog, Double_FvarVar_2ndDeriv) {
  using boost::math::digamma;
  using stan::math::binomial_coefficient_log;
  using stan::math::fvar;
  using stan::math::var;

  double x(2004.0);
  fvar<var> z(1002.0, 2.0);
  fvar<var> a = binomial_coefficient_log(x, z);

  EXPECT_FLOAT_EQ(binomial_coefficient_log(2004.0, 1002.0), a.val_.val());
  EXPECT_NEAR(0, a.d_.val(), 1e-8);

  AVEC y = createAVEC(z.val_);
  VEC g;
  a.d_.grad(y, g);
  EXPECT_FLOAT_EQ(-0.00399002460681026, g[0]);
}

TEST(AgradFwdBinomialCoefficientLog, FvarVar_FvarVar_1stDeriv) {
  using boost::math::digamma;
  using stan::math::binomial_coefficient_log;
  using stan::math::fvar;
  using stan::math::var;

  fvar<var> x(2004.0, 1.0);
  fvar<var> z(1002.0, 2.0);
  fvar<var> a = binomial_coefficient_log(x, z);

  EXPECT_FLOAT_EQ(binomial_coefficient_log(2004.0, 1002.0), a.val_.val());
  EXPECT_FLOAT_EQ(0.69289774, a.d_.val());

  AVEC y = createAVEC(x.val_, z.val_);
  VEC g;
  a.val_.grad(y, g);
  EXPECT_FLOAT_EQ(0.69289774, g[0]);
  EXPECT_NEAR(0, g[1], 1e-8);
}
TEST(AgradFwdBinomialCoefficientLog, FvarVar_FvarVar_2ndDeriv) {
  using boost::math::digamma;
  using stan::math::binomial_coefficient_log;
  using stan::math::fvar;
  using stan::math::var;

  fvar<var> x(2004.0, 1.0);
  fvar<var> z(1002.0, 2.0);
  fvar<var> a = binomial_coefficient_log(x, z);

  EXPECT_FLOAT_EQ(binomial_coefficient_log(2004.0, 1002.0), a.val_.val());
  EXPECT_FLOAT_EQ(0.69289774, a.d_.val());

  AVEC y = createAVEC(x.val_, z.val_);
  VEC g;
  a.d_.grad(y, g);
  EXPECT_FLOAT_EQ(0.0014963837, g[0]);
  EXPECT_FLOAT_EQ(-0.0029925184551076781, g[1]);
}

TEST(AgradFwdBinomialCoefficientLog, FvarFvarVar_FvarFvarVar_1stDeriv) {
  using stan::math::binomial_coefficient_log;
  using stan::math::binomial_coefficient_log;
  using stan::math::fvar;
  using stan::math::var;

  fvar<fvar<var> > x;
  x.val_.val_ = 2004.0;
  x.val_.d_ = 1.0;

  fvar<fvar<var> > y;
  y.val_.val_ = 1002.0;
  y.d_.val_ = 1.0;

  fvar<fvar<var> > a = binomial_coefficient_log(x, y);

  EXPECT_FLOAT_EQ(binomial_coefficient_log(2004.0, 1002.0), a.val_.val_.val());
  EXPECT_FLOAT_EQ(0.69289774, a.val_.d_.val());
  EXPECT_NEAR(0, a.d_.val_.val(), 1e-8);
  EXPECT_FLOAT_EQ(0.0009975062, a.d_.d_.val());

  AVEC p = createAVEC(x.val_.val_, y.val_.val_);
  VEC g;
  a.val_.val_.grad(p, g);
  EXPECT_FLOAT_EQ(0.69289774, g[0]);
  EXPECT_NEAR(0, g[1], 1e-8);
}
TEST(AgradFwdBinomialCoefficientLog, FvarFvarVar_Double_1stDeriv) {
  using stan::math::binomial_coefficient_log;
  using stan::math::binomial_coefficient_log;
  using stan::math::fvar;
  using stan::math::var;

  fvar<fvar<var> > x;
  x.val_.val_ = 2004.0;
  x.val_.d_ = 1.0;

  double y(1002.0);

  fvar<fvar<var> > a = binomial_coefficient_log(x, y);

  EXPECT_FLOAT_EQ(binomial_coefficient_log(2004.0, 1002.0), a.val_.val_.val());
  EXPECT_FLOAT_EQ(0.69289774, a.val_.d_.val());
  EXPECT_NEAR(0, a.d_.val_.val(), 1e-8);
  EXPECT_FLOAT_EQ(0, a.d_.d_.val());

  AVEC p = createAVEC(x.val_.val_);
  VEC g;
  a.val_.val_.grad(p, g);
  EXPECT_FLOAT_EQ(0.69289774, g[0]);
}
TEST(AgradFwdBinomialCoefficientLog, Double_FvarFvarVar_1stDeriv) {
  using stan::math::binomial_coefficient_log;
  using stan::math::binomial_coefficient_log;
  using stan::math::fvar;
  using stan::math::var;

  double x(2004.0);

  fvar<fvar<var> > y;
  y.val_.val_ = 1002.0;
  y.d_.val_ = 1.0;

  fvar<fvar<var> > a = binomial_coefficient_log(x, y);

  EXPECT_FLOAT_EQ(binomial_coefficient_log(2004.0, 1002.0), a.val_.val_.val());
  EXPECT_FLOAT_EQ(0, a.val_.d_.val());
  EXPECT_NEAR(0, a.d_.val_.val(), 1e-8);
  EXPECT_FLOAT_EQ(0, a.d_.d_.val());

  AVEC p = createAVEC(y.val_.val_);
  VEC g;
  a.val_.val_.grad(p, g);
  EXPECT_NEAR(0, g[0], 1e-8);
}

TEST(AgradFwdBinomialCoefficientLog, FvarFvarVar_FvarFvarVar_2ndDeriv_x) {
  using stan::math::binomial_coefficient_log;
  using stan::math::binomial_coefficient_log;
  using stan::math::fvar;
  using stan::math::var;

  fvar<fvar<var> > x;
  x.val_.val_ = 2004.0;
  x.val_.d_ = 1.0;

  fvar<fvar<var> > y;
  y.val_.val_ = 1002.0;
  y.d_.val_ = 1.0;

  fvar<fvar<var> > a = binomial_coefficient_log(x, y);

  EXPECT_FLOAT_EQ(binomial_coefficient_log(2004.0, 1002.0), a.val_.val_.val());
  EXPECT_FLOAT_EQ(0.69289774, a.val_.d_.val());
  EXPECT_NEAR(0, a.d_.val_.val(), 1e-8);
  EXPECT_FLOAT_EQ(0.0009975062, a.d_.d_.val());

  AVEC p = createAVEC(x.val_.val_, y.val_.val_);
  VEC g;
  a.val_.d_.grad(p, g);
  EXPECT_FLOAT_EQ(-0.00049862865, g[0]);
  EXPECT_FLOAT_EQ(0.00099750615170258105, g[1]);
}
TEST(AgradFwdBinomialCoefficientLog, FvarFvarVar_FvarFvarVar_2ndDeriv_y) {
  using stan::math::binomial_coefficient_log;
  using stan::math::binomial_coefficient_log;
  using stan::math::fvar;
  using stan::math::var;

  fvar<fvar<var> > x;
  x.val_.val_ = 2004.0;
  x.val_.d_ = 1.0;

  fvar<fvar<var> > y;
  y.val_.val_ = 1002.0;
  y.d_.val_ = 1.0;

  fvar<fvar<var> > a = binomial_coefficient_log(x, y);

  EXPECT_FLOAT_EQ(binomial_coefficient_log(2004.0, 1002.0), a.val_.val_.val());
  EXPECT_FLOAT_EQ(0.69289774, a.val_.d_.val());
  EXPECT_NEAR(0, a.d_.val_.val(), 1e-8);
  EXPECT_FLOAT_EQ(0.0009975062, a.d_.d_.val());

  AVEC p = createAVEC(x.val_.val_, y.val_.val_);
  VEC g;
  a.d_.val_.grad(p, g);
  EXPECT_FLOAT_EQ(0.0009975062, g[0]);
  EXPECT_FLOAT_EQ(-0.0019950123034051291, g[1]);
}
TEST(AgradFwdBinomialCoefficientLog, Double_FvarFvarVar_2ndDeriv) {
  using stan::math::binomial_coefficient_log;
  using stan::math::binomial_coefficient_log;
  using stan::math::fvar;
  using stan::math::var;

  double x(2004.0);

  fvar<fvar<var> > y;
  y.val_.val_ = 1002.0;
  y.d_.val_ = 1.0;

  fvar<fvar<var> > a = binomial_coefficient_log(x, y);

  EXPECT_FLOAT_EQ(binomial_coefficient_log(2004.0, 1002.0), a.val_.val_.val());
  EXPECT_FLOAT_EQ(0, a.val_.d_.val());
  EXPECT_NEAR(0, a.d_.val_.val(), 1e-8);
  EXPECT_FLOAT_EQ(0, a.d_.d_.val());

  AVEC p = createAVEC(y.val_.val_);
  VEC g;
  a.d_.val_.grad(p, g);
  EXPECT_NEAR(-0.00199501230340513, g[0], 1e-8);
}
TEST(AgradFwdBinomialCoefficientLog, FvarFvarVar_Double_2ndDeriv) {
  using stan::math::binomial_coefficient_log;
  using stan::math::binomial_coefficient_log;
  using stan::math::fvar;
  using stan::math::var;

  fvar<fvar<var> > x;
  x.val_.val_ = 2004.0;
  x.val_.d_ = 1.0;

  double y(1002.0);

  fvar<fvar<var> > a = binomial_coefficient_log(x, y);

  EXPECT_FLOAT_EQ(binomial_coefficient_log(2004.0, 1002.0), a.val_.val_.val());
  EXPECT_FLOAT_EQ(0.69289774, a.val_.d_.val());
  EXPECT_NEAR(0, a.d_.val_.val(), 1e-8);
  EXPECT_FLOAT_EQ(0, a.d_.d_.val());

  AVEC p = createAVEC(x.val_.val_);
  VEC g;
  a.val_.d_.grad(p, g);
  EXPECT_FLOAT_EQ(-0.00049862863648177515, g[0]);
}
TEST(AgradFwdBinomialCoefficientLog, FvarFvarVar_FvarFvarVar_3rdDeriv) {
  using stan::math::binomial_coefficient_log;
  using stan::math::binomial_coefficient_log;
  using stan::math::fvar;
  using stan::math::var;

  fvar<fvar<var> > x;
  x.val_.val_ = 2004.0;
  x.val_.d_ = 1.0;

  fvar<fvar<var> > y;
  y.val_.val_ = 1002.0;
  y.d_.val_ = 1.0;

  fvar<fvar<var> > a = binomial_coefficient_log(x, y);

  EXPECT_FLOAT_EQ(binomial_coefficient_log(2004.0, 1002.0), a.val_.val_.val());
  EXPECT_FLOAT_EQ(0.69289774, a.val_.d_.val());
  EXPECT_NEAR(0, a.d_.val_.val(), 1e-8);
  EXPECT_FLOAT_EQ(0.0009975062, a.d_.d_.val());

  AVEC p = createAVEC(x.val_.val_, y.val_.val_);
  VEC g;
  a.d_.d_.grad(p, g);
  EXPECT_FLOAT_EQ(-9.9501847e-07, g[0]);
  EXPECT_FLOAT_EQ(9.9501847e-07, g[1]);
}
TEST(AgradFwdBinomialCoefficientLog, Double_FvarFvarVar_3rdDeriv) {
  using stan::math::binomial_coefficient_log;
  using stan::math::binomial_coefficient_log;
  using stan::math::fvar;
  using stan::math::var;

  double x(2004.0);

  fvar<fvar<var> > y;
  y.val_.val_ = 1002.0;
  y.d_.val_ = 1.0;
  y.val_.d_ = 1.0;

  fvar<fvar<var> > a = binomial_coefficient_log(x, y);

  EXPECT_FLOAT_EQ(binomial_coefficient_log(2004.0, 1002.0), a.val_.val_.val());
  EXPECT_NEAR(0, a.val_.d_.val(), 1e-8);
  EXPECT_NEAR(0, a.d_.val_.val(), 1e-8);
  EXPECT_FLOAT_EQ(-0.0019950124, a.d_.d_.val());

  AVEC p = createAVEC(y.val_.val_);
  VEC g;
  a.d_.d_.grad(p, g);
  EXPECT_NEAR(0, g[0], 1e-8);
}
TEST(AgradFwdBinomialCoefficientLog, FvarFvarVar_Double_3rdDeriv) {
  using stan::math::binomial_coefficient_log;
  using stan::math::binomial_coefficient_log;
  using stan::math::fvar;
  using stan::math::var;

  fvar<fvar<var> > x;
  x.val_.val_ = 2004.0;
  x.val_.d_ = 1.0;
  x.d_.val_ = 1.0;

  double y(1002.0);

  fvar<fvar<var> > a = binomial_coefficient_log(x, y);

  EXPECT_FLOAT_EQ(binomial_coefficient_log(2004.0, 1002.0), a.val_.val_.val());
  EXPECT_FLOAT_EQ(0.69289774, a.val_.d_.val());
  EXPECT_FLOAT_EQ(0.69289774181268948, a.d_.val_.val());
  EXPECT_FLOAT_EQ(-0.00049862865, a.d_.d_.val());

  AVEC p = createAVEC(x.val_.val_);
  VEC g;
  a.d_.d_.grad(p, g);
  EXPECT_FLOAT_EQ(7.4613968e-07, g[0]);
}

struct binomial_coefficient_log_fun {
  template <typename T0, typename T1>
  inline typename boost::math::tools::promote_args<T0, T1>::type operator()(
      const T0 arg1, const T1 arg2) const {
    return binomial_coefficient_log(arg1, arg2);
  }
};

TEST(AgradFwdBinomialCoefficientLog, nan_1) {
  binomial_coefficient_log_fun binomial_coefficient_log_;
  test_nan_mix(binomial_coefficient_log_, 3.0, 5.0, false);
}


// Copyright 2015 National ICT Australia Limited (NICTA)
//
// Licensed under the Apache License, 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 <SgtCore/Random.h>

#include <boost/random/mersenne_twister.hpp>
#include <boost/random/discrete_distribution.hpp>
#include <boost/random/normal_distribution.hpp>
#include <boost/random/uniform_real_distribution.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <boost/random/weibull_distribution.hpp>

#include <ctime>

namespace Sgt
{
    namespace
    {
        boost::random::mt19937 sRng; // Random number generator.
    }

    void randSeedWithTime()
    {
        sRng.seed(static_cast<unsigned int>(std::time(nullptr)));
    }

    void randSeed(const unsigned int n)
    {
        sRng.seed(n);
    }

    double randDiscrete(const double probs[], int nProbs)
    {
        boost::random::discrete_distribution<> dist(&probs[0], &probs[nProbs]);
        return dist(sRng);
    }

    double randNormal(double mu, double sig)
    {
        typedef boost::random::normal_distribution<> DistType;
        static DistType dist(0.0, 1.0);
        return dist(sRng, DistType::param_type(mu, sig));
    }

    double randUniform(double lower, double upper)
    {
        typedef boost::random::uniform_real_distribution<> DistType;
        static DistType dist(0.0, 1.0);
        return dist(sRng,  DistType::param_type(lower, upper));
    }

    int randUniformInt(int lower, int upper)
    {
        typedef boost::random::uniform_int_distribution<> DistType;
        static DistType dist(0, 1);
        return dist(sRng,  DistType::param_type(lower, upper));
    }

    double randWeibull(double a, double b)
    {
        typedef boost::random::weibull_distribution<> DistType;
        static DistType dist(0.0, 1.0);
        return dist(sRng,  DistType::param_type(a, b));
    }
}