//---------------------------------------------------------------------------//
// Copyright (c) 2013-2014 Kyle Lutz <kyle.r.lutz@gmail.com>
//
// Distributed under the Boost Software License, Version 1.0
// See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt
//
// See http://boostorg.github.com/compute for more information.
//---------------------------------------------------------------------------//

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

#include <boost/compute/system.hpp>
#include <boost/compute/container/vector.hpp>
#include <boost/compute/image/image2d.hpp>
#include <boost/compute/interop/opencv/core.hpp>
#include <boost/compute/interop/opencv/highgui.hpp>
#include <boost/compute/random/default_random_engine.hpp>
#include <boost/compute/random/uniform_real_distribution.hpp>
#include <boost/compute/utility/dim.hpp>
#include <boost/compute/utility/source.hpp>

namespace compute = boost::compute;

using compute::dim;
using compute::int_;
using compute::float_;
using compute::float2_;

// the k-means example implements the k-means clustering algorithm
int main()
{
    // number of clusters
    size_t k = 6;

    // number of points
    size_t n_points = 4500;

    // height and width of image
    size_t height = 800;
    size_t width = 800;

    // get default device and setup context
    compute::device gpu = compute::system::default_device();
    compute::context context(gpu);
    compute::command_queue queue(context, gpu);

    // generate random, uniformily-distributed points
    compute::default_random_engine random_engine(queue);
    compute::uniform_real_distribution<float_> uniform_distribution(0, 800);

    compute::vector<float2_> points(n_points, context);
    uniform_distribution.generate(
        compute::make_buffer_iterator<float_>(points.get_buffer(), 0),
        compute::make_buffer_iterator<float_>(points.get_buffer(), n_points * 2),
        random_engine,
        queue
    );

    // initialize all points to cluster 0
    compute::vector<int_> clusters(n_points, context);
    compute::fill(clusters.begin(), clusters.end(), 0, queue);

    // create initial means with the first k points
    compute::vector<float2_> means(k, context);
    compute::copy_n(points.begin(), k, means.begin(), queue);

    // k-means clustering program source
    const char k_means_source[] = BOOST_COMPUTE_STRINGIZE_SOURCE(
        __kernel void assign_clusters(__global const float2 *points,
                                      __global const float2 *means,
                                      const int k,
                                      __global int *clusters)
        {
            const uint gid = get_global_id(0);

            const float2 point = points[gid];

            // find the closest cluster
            float current_distance = 0;
            int closest_cluster = -1;

            // find closest cluster mean to the point
            for(int i = 0; i < k; i++){
                const float2 mean = means[i];

                int distance_to_mean = distance(point, mean);
                if(closest_cluster == -1 || distance_to_mean < current_distance){
                    current_distance = distance_to_mean;
                    closest_cluster = i;
                }
            }

            // write new cluster
            clusters[gid] = closest_cluster;
        }

        __kernel void update_means(__global const float2 *points,
                                   const uint n_points,
                                   __global float2 *means,
                                   __global const int *clusters)
        {
            const uint k = get_global_id(0);

            float2 sum = { 0, 0 };
            float count = 0;
            for(uint i = 0; i < n_points; i++){
                if(clusters[i] == k){
                    sum += points[i];
                    count += 1;
                }
            }

            means[k] = sum / count;
        }
    );

    // build the k-means program
    compute::program k_means_program =
        compute::program::build_with_source(k_means_source, context);

    // setup the k-means kernels
    compute::kernel assign_clusters_kernel(k_means_program, "assign_clusters");
    assign_clusters_kernel.set_arg(0, points);
    assign_clusters_kernel.set_arg(1, means);
    assign_clusters_kernel.set_arg(2, int_(k));
    assign_clusters_kernel.set_arg(3, clusters);

    compute::kernel update_means_kernel(k_means_program, "update_means");
    update_means_kernel.set_arg(0, points);
    update_means_kernel.set_arg(1, int_(n_points));
    update_means_kernel.set_arg(2, means);
    update_means_kernel.set_arg(3, clusters);

    // run the k-means algorithm
    for(int iteration = 0; iteration < 25; iteration++){
        queue.enqueue_1d_range_kernel(assign_clusters_kernel, 0, n_points, 0);
        queue.enqueue_1d_range_kernel(update_means_kernel, 0, k, 0);
    }

    // create output image
    compute::image2d image(
        context, width, height, compute::image_format(CL_RGBA, CL_UNSIGNED_INT8)
    );

    // program with two kernels, one to fill the image with white, and then
    // one the draw to points calculated in coordinates on the image
    const char draw_walk_source[] = BOOST_COMPUTE_STRINGIZE_SOURCE(
        __kernel void draw_points(__global const float2 *points,
                                  __global const int *clusters,
                                  __write_only image2d_t image)
        {
            const uint i = get_global_id(0);
            const float2 coord = points[i];

            // map cluster number to color
            uint4 color = { 0, 0, 0, 0 };
            switch(clusters[i]){
              case 0:
                  color = (uint4)(255, 0, 0, 255);
                  break;
              case 1:
                  color = (uint4)(0, 255, 0, 255);
                  break;
              case 2:
                  color = (uint4)(0, 0, 255, 255);
                  break;
              case 3:
                  color = (uint4)(255, 255, 0, 255);
                  break;
              case 4:
                  color = (uint4)(255, 0, 255, 255);
                  break;
              case 5:
                  color = (uint4)(0, 255, 255, 255);
                  break;
            }

            // draw a 3x3 pixel point
            for(int x = -1; x <= 1; x++){
                for(int y = -1; y <= 1; y++){
                    if(coord.x + x > 0 && coord.x + x < get_image_width(image) &&
                       coord.y + y > 0 && coord.y + y < get_image_height(image)){
                        write_imageui(image, (int2)(coord.x, coord.y) + (int2)(x, y), color);
                    }
                }
            }
        }

        __kernel void fill_gray(__write_only image2d_t image)
        {
            const int2 coord = { get_global_id(0), get_global_id(1) };

            if(coord.x < get_image_width(image) && coord.y < get_image_height(image)){
                uint4 gray = { 15, 15, 15, 15 };
                write_imageui(image, coord, gray);
            }
        }
    );

    // build the program
    compute::program draw_program =
        compute::program::build_with_source(draw_walk_source, context);

    // fill image with dark gray
    compute::kernel fill_kernel(draw_program, "fill_gray");
    fill_kernel.set_arg(0, image);

    queue.enqueue_nd_range_kernel(
        fill_kernel, dim(0, 0), dim(width, height), dim(1, 1)
    );

    // draw points colored according to cluster
    compute::kernel draw_kernel(draw_program, "draw_points");
    draw_kernel.set_arg(0, points);
    draw_kernel.set_arg(1, clusters);
    draw_kernel.set_arg(2, image);
    queue.enqueue_1d_range_kernel(draw_kernel, 0, n_points, 0);

    // show image
    compute::opencv_imshow("k-means", image, queue);

    // wait and return
    cv::waitKey(0);

    return 0;
}


/* The MIT License (MIT)
 * Copyright (c) 2014 Nicholas Wright
 * http://opensource.org/licenses/MIT
 */

#include "../../include/hash/ImagePHash.hpp"
#include <string>
#include <cmath>
#include <boost/multi_array.hpp>
#include <log4cplus/logger.h>
#include <log4cplus/loggingmacros.h>
#include <log4cplus/configurator.h>
#include <iomanip>
#include <ostream>
#include "../../include/util/Bit.hpp"

using namespace log4cplus;
using namespace std;
using namespace Magick;

	ImagePHash::ImagePHash() {
		size = 32;
		smallerSize = 8;
		init();
	}

	ImagePHash::ImagePHash(int size, int smallerSize) {
		this->size = size;
		this->smallerSize = smallerSize;

		init();
	}

	ImagePHash::~ImagePHash(){
		delete [] c;
	}

	void ImagePHash::init() {
		logger = Logger::getInstance(LOG4CPLUS_TEXT("ImagePHash"));
		InitializeMagick(NULL);
		initCoefficients();
	}

	long ImagePHash::getLongHash(string filename) {
		LOG4CPLUS_DEBUG(logger, "Opening image " << filename);
		Image img(filename);
		Blob blob;
		img.write(&blob);

		return getLongHash(blob);
	}

	long ImagePHash::getLongHash(Magick::Blob image_data) {
		double avg;
		long pHash;

		Image img(image_data);
		Geometry geo(size, size);
		geo.aspect(true);
		img.scale(geo);
		img.type(GrayscaleType);
		img.modifyImage();

		Pixels view(img);
		const PixelPacket *pixels = view.getConst(0,0,size,size);

		if(pixels == NULL) {
			return 0L;
		}

		dctMatrix values = createMatrix();

		int x, y;

		for (x = 0; x < size; x ++) {
			for (y = 0; y < size; y++) {
				values[x][y] = pixels[x + y*size].blue;
			}
		}

		values = applyDCT(values);
		avg = calcDctAverage(values);
		pHash = convertToLong(values, avg);

		return pHash;
	}

	/**
	 *
	 * @param is
	 *            file to hash
	 * @return hash in as long
	 * @throws IOException
	 */
/*	long getLongHash(ofstream is) {
		double[][] dct = calculateDctMap(is);
		double dctAvg = calcDctAverage(dct);
		long hash = convertToLong(dct, dctAvg);
		return hash;
	}

	long getLongHash(BufferedImage img) throws Exception {
		double[][] dct = calculateDctMap(img);
		double dctAvg = calcDctAverage(dct);
		long hash = convertToLong(dct, dctAvg);
		return hash;
	}*/

	/**
	 *
	 * @param is
	 *            file to hash
	 * @return a 'binary string' (like. 001010111011100010) which is easy to do
	 *         a hamming distance on.
	 * @throws IOException
	 */
/*	public String getHash(InputStream is) throws IOException {
		String hash;
		double[][] dct = calculateDctMap(is);
		double dctAvg = calcDctAverage(dct);
		hash = convertToBitString(dct, dctAvg);

		return hash;
	}*/

/*	private double[][] reduceColor(BufferedImage img) {
		double values[][] = new double[size][size];

		for (int x = 0; x < img.getWidth(); x++) {
			for (int y = 0; y < img.getHeight(); y++) {
				values[x][y] = getBlue(img, x, y);
			}
		}

		return values;
	}*/

/*	public double[][] calculateDctMap(InputStream is) throws IOException {
		BufferedImage img = readImage(is);
		return calculateDctMap(img);
	}

	public double[][] calculateDctMap(BufferedImage img) throws IOException {

		 * 1. Reduce size. Like Average Hash, pHash starts with a small image.
		 * However, the image is larger than 8x8; 32x32 is a good size. This is
		 * really done to simplify the DCT computation and not because it is
		 * needed to reduce the high frequencies.

		img = resize(img, size, size);

		 * 2. Reduce color. The image is reduced to a grayscale just to further
		 * simplify the number of computations.

		img = grayscale(img);

		double[][] vals = reduceColor(img);

		 * 3. Compute the DCT. The DCT separates the image into a collection of
		 * frequencies and scalars. While JPEG uses an 8x8 DCT, this algorithm
		 * uses a 32x32 DCT.

		// long start = System.currentTimeMillis();
		double[][] dctVals = applyDCT(vals);
		// System.out.println("DCT: " + (System.currentTimeMillis() - start));
		// // Removed to prevent system.out spam

		return dctVals;
	}*/

/*	private String convertToBitString(double[][] dctVals, double avg) {

		 * 6. Further reduce the DCT. This is the magic step. Set the 64 hash
		 * bits to 0 or 1 depending on whether each of the 64 DCT values is
		 * above or below the average value. The result doesn't tell us the
		 * actual low frequencies; it just tells us the very-rough relative
		 * scale of the frequencies to the mean. The result will not vary as
		 * long as the overall structure of the image remains the same; this can
		 * survive gamma and color histogram adjustments without a problem.

		String hash = "";

		for (int x = 0; x < smallerSize; x++) {
			for (int y = 0; y < smallerSize; y++) {
				if (x != 0 && y != 0) {
					hash += (dctVals[x][y] > avg ? "1" : "0");
				}
			}
		}
		return hash;
	}*/

	int64_t ImagePHash::convertToLong(dctMatrix dctVals, double avg) {
		double currentValue;

		if (smallerSize > 9) {
			throw "The selected smallerSize value is to big for the long datatype";
		}

		int64_t hash = 0;

		for (int x = 0; x < smallerSize; x++) {
			for (int y = 0; y < smallerSize; y++) {
				if (x != 0 && y != 0) {
					currentValue = dctVals[x][y];
					hash += (currentValue > avg ? 1 : 0);
					hash = Bit::rotateLeft(hash);
				}
			}
		}
		return hash;
	}

	double ImagePHash::calcDctAverage(dctMatrix dctVals) {
		/*
		 * 4. Reduce the DCT. This is the magic step. While the DCT is 32x32,
		 * just keep the top-left 8x8. Those represent the lowest frequencies in
		 * the picture.
		 */
		/*
		 * 5. Compute the average value. Like the Average Hash, compute the mean
		 * DCT value (using only the 8x8 DCT low-frequency values and excluding
		 * the first term since the DC coefficient can be significantly
		 * different from the other values and will throw off the average).
		 */
		double total = 0;

		for (int x = 0; x < smallerSize; x++) {
			for (int y = 0; y < smallerSize; y++) {
				total += dctVals[x][y];
			}
		}
		total -= dctVals[0][0];

		double avg = total / (double) ((smallerSize * smallerSize) - 1);
		return avg;
	}

/*	private BufferedImage resize(BufferedImage image, int width, int height) {
		BufferedImage resizedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g = resizedImage.createGraphics();
		g.drawImage(image, 0, 0, width, height, null);
		g.dispose();
		return resizedImage;
	}

	private ColorConvertOp colorConvert = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);

	private BufferedImage grayscale(BufferedImage img) {
		colorConvert.filter(img, img);
		return img;
	}

	private static int getBlue(BufferedImage img, int x, int y) {
		return (img.getRGB(x, y)) & 0xff;
	}*/

	// DCT function stolen from
	// http://stackoverflow.com/questions/4240490/problems-with-dct-and-idct-algorithm-in-java

	void ImagePHash::initCoefficients() {
		c = new double[size];

		for (int i = 1; i < size; i++) {
			c[i] = 1;
		}

		c[0] = 1 / sqrt((double)(2.0)); //sqrt(2.0);
	}

	ImagePHash::dctMatrix ImagePHash::applyDCT(dctMatrix f) {
		int N = size;

		dctMatrix F = createMatrix();
		for (int u = 0; u < N; u++) {
			for (int v = 0; v < N; v++) {
				double sum = 0.0;
				for (int i = 0; i < N; i++) {
					for (int j = 0; j < N; j++) {
						sum += std::cos(((2 * i + 1) / (2.0 * N)) * u * M_PI) * std::cos(((2 * j + 1) / (2.0 * N)) * v * M_PI)
								* (f[i][j]);
					}
				}
				sum *= ((c[u] * c[v]) / 4.0);
				F[u][v] = sum;
			}
		}
		return F;
	}

	ImagePHash::dctMatrix ImagePHash::createMatrix() {
		  boost::array<dctMatrix::index, 2> shape = {{size, size}};
		  dctMatrix matrix(shape);
		return matrix;
}


//  (C) Copyright Nick Thompson 2021.
//  Use, modification and distribution are subject to the
//  Boost Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_MATH_TOOLS_CUBIC_ROOTS_HPP
#define BOOST_MATH_TOOLS_CUBIC_ROOTS_HPP
#include <algorithm>
#include <array>
#include <boost/math/special_functions/sign.hpp>
#include <boost/math/tools/roots.hpp>

namespace boost::math::tools {

// Solves ax^3 + bx^2 + cx + d = 0.
// Only returns the real roots, as types get weird for real coefficients and
// complex roots. Follows Numerical Recipes, Chapter 5, section 6. NB: A better
// algorithm apparently exists: Algorithm 954: An Accurate and Efficient Cubic
// and Quartic Equation Solver for Physical Applications However, I don't have
// access to that paper!
template <typename Real>
std::array<Real, 3> cubic_roots(Real a, Real b, Real c, Real d) {
    using std::abs;
    using std::acos;
    using std::cbrt;
    using std::cos;
    using std::fma;
    using std::sqrt;
    std::array<Real, 3> roots = {std::numeric_limits<Real>::quiet_NaN(),
                                 std::numeric_limits<Real>::quiet_NaN(),
                                 std::numeric_limits<Real>::quiet_NaN()};
    if (a == 0) {
        // bx^2 + cx + d = 0:
        if (b == 0) {
            // cx + d = 0:
            if (c == 0) {
                if (d != 0) {
                    // No solutions:
                    return roots;
                }
                roots[0] = 0;
                roots[1] = 0;
                roots[2] = 0;
                return roots;
            }
            roots[0] = -d / c;
            return roots;
        }
        auto [x0, x1] = quadratic_roots(b, c, d);
        roots[0] = x0;
        roots[1] = x1;
        return roots;
    }
    if (d == 0) {
        auto [x0, x1] = quadratic_roots(a, b, c);
        roots[0] = x0;
        roots[1] = x1;
        roots[2] = 0;
        std::sort(roots.begin(), roots.end());
        return roots;
    }
    Real p = b / a;
    Real q = c / a;
    Real r = d / a;
    Real Q = (p * p - 3 * q) / 9;
    Real R = (2 * p * p * p - 9 * p * q + 27 * r) / 54;
    if (R * R < Q * Q * Q) {
        Real rtQ = sqrt(Q);
        Real theta = acos(R / (Q * rtQ)) / 3;
        Real st = sin(theta);
        Real ct = cos(theta);
        roots[0] = -2 * rtQ * ct - p / 3;
        roots[1] = -rtQ * (-ct + sqrt(Real(3)) * st) - p / 3;
        roots[2] = rtQ * (ct + sqrt(Real(3)) * st) - p / 3;
    } else {
        // In Numerical Recipes, Chapter 5, Section 6, it is claimed that we
        // only have one real root if R^2 >= Q^3. But this isn't true; we can
        // even see this from equation 5.6.18. The condition for having three
        // real roots is that A = B. It *is* the case that if we're in this
        // branch, and we have 3 real roots, two are a double root. Take
        // (x+1)^2(x-2) = x^3 - 3x -2 as an example. This clearly has a double
        // root at x = -1, and it gets sent into this branch.
        Real arg = R * R - Q * Q * Q;
        Real A = (R >= 0 ? -1 : 1) * cbrt(abs(R) + sqrt(arg));
        Real B = 0;
        if (A != 0) {
            B = Q / A;
        }
        roots[0] = A + B - p / 3;
        // Yes, we're comparing floats for equality:
        // Any perturbation pushes the roots into the complex plane; out of the
        // bailiwick of this routine.
        if (A == B || arg == 0) {
            roots[1] = -A - p / 3;
            roots[2] = -A - p / 3;
        }
    }
    // Root polishing:
    for (auto &r : roots) {
        // Horner's method.
        // Here I'll take John Gustaffson's opinion that the fma is a *distinct*
        // operation from a*x +b: Make sure to compile these fmas into a single
        // instruction and not a function call! (I'm looking at you Windows.)
        Real f = fma(a, r, b);
        f = fma(f, r, c);
        f = fma(f, r, d);
        Real df = fma(3 * a, r, 2 * b);
        df = fma(df, r, c);
        if (df != 0) {
            Real d2f = fma(6 * a, r, 2 * b);
            Real denom = 2 * df * df - f * d2f;
            if (denom != 0) {
                r -= 2 * f * df / denom;
            } else {
                r -= f / df;
            }
        }
    }
    std::sort(roots.begin(), roots.end());
    return roots;
}

// Computes the empirical residual p(r) (first element) and expected residual
// eps*|rp'(r)| (second element) for a root. Recall that for a numerically
// computed root r satisfying r = r_0(1+eps) of a function p, |p(r)| <=
// eps|rp'(r)|.
template <typename Real>
std::array<Real, 2> cubic_root_residual(Real a, Real b, Real c, Real d,
                                        Real root) {
    using std::abs;
    using std::fma;
    std::array<Real, 2> out;
    Real residual = fma(a, root, b);
    residual = fma(residual, root, c);
    residual = fma(residual, root, d);

    out[0] = residual;

    // The expected residual is:
    // eps*[4|ar^3| + 3|br^2| + 2|cr| + |d|]
    // This can be demonstrated by assuming the coefficients and the root are
    // perturbed according to the rounding model of floating point arithmetic,
    // and then working through the inequalities.
    root = abs(root);
    Real expected_residual = fma(4 * abs(a), root, 3 * abs(b));
    expected_residual = fma(expected_residual, root, 2 * abs(c));
    expected_residual = fma(expected_residual, root, abs(d));
    out[1] = expected_residual * std::numeric_limits<Real>::epsilon();
    return out;
}

// Computes the condition number of rootfinding. This is defined in Corless, A
// Graduate Introduction to Numerical Methods, Section 3.2.1.
template <typename Real>
Real cubic_root_condition_number(Real a, Real b, Real c, Real d, Real root) {
    using std::abs;
    using std::fma;
    // There are *absolute* condition numbers that can be defined when r = 0;
    // but they basically reduce to the residual computed above.
    if (root == static_cast<Real>(0)) {
        return std::numeric_limits<Real>::infinity();
    }

    Real numerator = fma(abs(a), abs(root), abs(b));
    numerator = fma(numerator, abs(root), abs(c));
    numerator = fma(numerator, abs(root), abs(d));
    Real denominator = fma(3 * a, root, 2 * b);
    denominator = fma(denominator, root, c);
    if (denominator == static_cast<Real>(0)) {
        return std::numeric_limits<Real>::infinity();
    }
    denominator *= root;
    return numerator / abs(denominator);
}

} // namespace boost::math::tools
#endif


//==================================================================================================
/**
  EVE - Expressive Vector Engine
  Copyright : EVE Contributors & Maintainers
  SPDX-License-Identifier: MIT
**/
//==================================================================================================
#include <boost/math/special_functions/gegenbauer.hpp>
#include <eve/function/gegenbauer.hpp>
#include <eve/platform.hpp>
#include <cmath>

TTS_CASE_TPL("Check eve::gegenbauer return type", EVE_TYPE)
{
  TTS_EXPR_IS(eve::gegenbauer(0, T(0), T(0)), T);
}

TTS_CASE_TPL("Check eve::gegenbauer behavior", EVE_TYPE)
{
  using elt_t = eve::element_type_t<T>;
  elt_t l(-3.0/8.0);
  auto eve__gegenbauer =  [&l](unsigned n, auto x) { return eve::gegenbauer(n, T(l), x); };
  auto boost_gegenbauer =  [&l](unsigned n, auto x) { return boost::math::gegenbauer(n, double(l), x); };

  for(unsigned int i=0; i < 10; ++i)
  {
    TTS_ULP_EQUAL(eve__gegenbauer(i, T(10)), T(boost_gegenbauer(i, 10.0)), 2);
    TTS_ULP_EQUAL(eve__gegenbauer(i, T(5)), T(boost_gegenbauer(i, 5.0)), 1);
    TTS_ULP_EQUAL(eve__gegenbauer(i, T(2)), T(boost_gegenbauer(i, 2.0)), 1);
    TTS_ULP_EQUAL(eve__gegenbauer(i, T(1)), T(boost_gegenbauer(i, 1.0)), 20);
    TTS_ULP_EQUAL(eve__gegenbauer(i, T(0)), T(boost_gegenbauer(i, 0.0)), 1);
  }
}


// test performance.cpp : Defines the entry point for the console application.
//

#include <cstdio>
#include <cstdint>
#include <iostream>
#include <chrono>
#include <boost/multiprecision/cpp_int.hpp>
#include <boost/multiprecision/integer.hpp>

#include <boost/safe_numerics/safe_integer.hpp>

typedef boost::safe_numerics::safe<unsigned> safe_type;

namespace boost {
namespace multiprecision {

    template <class Integer, class I2>
    typename enable_if_c<boost::safe_numerics::is_safe<Integer>::value, Integer&>::type
    multiply(Integer& result, const I2& a, const I2& b){
        return result = static_cast<Integer>(a) * static_cast<Integer>(b);
    }

    template <class Integer>
    typename enable_if_c<boost::safe_numerics::is_safe<Integer>::value, bool>::type
    bit_test(const Integer& val, unsigned index){
        Integer mask = 1;
        if (index >= sizeof(Integer) * CHAR_BIT)
            return 0;
        if (index)
            mask <<= index;
        return val & mask ? true : false;
    }

    template <class I1, class I2>
    typename enable_if_c<boost::safe_numerics::is_safe<I1>::value, I2>::type
    integer_modulus(const I1& x, I2 val){
        return x % val;
    }

    namespace detail {
        template <class T> struct double_integer;

        template <>
        struct double_integer<safe_type>{
            using type = boost::safe_numerics::safe<std::uint64_t>;
        };
    }

    template <class I1, class I2, class I3>
    typename enable_if_c<boost::safe_numerics::is_safe<I1>::value, I1>::type
    powm(const I1& a, I2 b, I3 c){
        typedef typename detail::double_integer<I1>::type double_type;

        I1 x(1), y(a);
        double_type result;

        while (b > 0){
            if (b & 1){
                multiply(result, x, y);
                x = integer_modulus(result, c);
            }
            multiply(result, y, y);
            y = integer_modulus(result, c);
            b >>= 1;
        }
        return x % c;
    }

    template <class T, class PP, class EP>
    inline unsigned
    lsb(const boost::safe_numerics::safe<T, PP, EP>& x){
        return lsb(static_cast<T>(x));
    }

} }

#include <boost/multiprecision/miller_rabin.hpp>

template <class Clock>
class stopwatch
{
    const typename Clock::time_point m_start;
public:
    stopwatch() :
        m_start(Clock::now())
    {}
    typename Clock::duration elapsed() const {
        return Clock::now() - m_start;
    }
};

template<typename T>
void test(const char * msg){
    const stopwatch<std::chrono::high_resolution_clock> c;

    unsigned count = 0;
    for (T i = 3; i < 30000000; ++i)
        if (boost::multiprecision::miller_rabin_test(i, 25)) ++count;

    std::chrono::duration<double> time = c.elapsed();
    std::cout<< msg << ":\ntime = " << time.count();
    std::cout << "\ncount = " << count << std::endl;
}

int main()
{
    test<unsigned>("Testing type unsigned");
    test<boost::safe_numerics::safe<unsigned>>("Testing type safe<unsigned>");
    return 0;
}


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

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

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

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

/*! \file qle/termstructures/equityvolconstantspread.hpp
    \brief equity surface that combines an ATM curve and vol spreads from a surface
    \ingroup termstructures
*/

#ifndef quantext_equityvolatilityconstantspread_hpp
#define quantext_equityvolatilityconstantspread_hpp

#include <boost/shared_ptr.hpp>
#include <ql/termstructures/volatility/equityfx/blackvariancecurve.hpp>
#include <ql/termstructures/volatility/equityfx/blackvariancesurface.hpp>
#include <ql/termstructures/volatility/equityfx/blackvoltermstructure.hpp>

namespace QuantExt {
using namespace QuantLib;

//! Equity cube that combines an ATM matrix and vol spreads from a cube
/*! Notice that the TS has a floating reference date and accesses the source TS only via
 their time-based volatility methods.

 \warning the given atm vol structure should be strike independent, this is not checked
*/
class EquityVolatilityConstantSpread : public BlackVolTermStructure {
public:
    EquityVolatilityConstantSpread(const Handle<BlackVolTermStructure>& atm,
                                   const Handle<BlackVolTermStructure>& surface)
        : BlackVolTermStructure(0, atm->calendar(), atm->businessDayConvention(), atm->dayCounter()), atm_(atm),
          surface_(surface) {
        enableExtrapolation(atm->allowsExtrapolation());
        registerWith(atm);
        registerWith(surface);
    }

    //! \name TermStructure interface
    //@{
    DayCounter dayCounter() const { return atm_->dayCounter(); }
    Date maxDate() const { return atm_->maxDate(); }
    Time maxTime() const { return atm_->maxTime(); }
    const Date& referenceDate() const { return atm_->referenceDate(); }
    Calendar calendar() const { return atm_->calendar(); }
    Natural settlementDays() const { return atm_->settlementDays(); }
    //@}
    //! \name VolatilityTermStructure interface
    //@{
    Rate minStrike() const { return surface_->minStrike(); }
    Rate maxStrike() const { return surface_->maxStrike(); }
    //@}

protected:
    Volatility blackVolImpl(Time t, Rate strike) const {
        Real s = surface_->blackVol(t, strike, true) - surface_->blackVol(t, Null<Real>(), true);
        Real v = atm_->blackVol(t, Null<Real>(), true);
        return v + s;
    }

    Real blackVarianceImpl(Time t, Real strike) const {
        Real vol = blackVolImpl(t, strike);
        return vol * vol * t;
    }

private:
    Handle<BlackVolTermStructure> atm_, surface_;
};

} // namespace QuantExt

#endif


/*
 * ConvecMatrixEquationSolverQP.hpp
 *
 *  Created on: August 20, 2020
 *      Author: Quincy Jones
 *
 * Copyright (c) <2020> <Quincy Jones - quincy@implementedrobotics.com/>
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the Software
 * is furnished to do so, subject to the following conditions:
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#ifndef NOMAD_CORE_OPTIMALCONTROL_CONVEXMATRIXEQUATIONSOLVERQP_H_
#define NOMAD_CORE_OPTIMALCONTROL_CONVEXMATRIXEQUATIONSOLVERQP_H_

// C System Files

// C++ System Files
#include <iostream>

// Third Party Includes
#include <Eigen/Dense>
#include <qpOASES.hpp>

// Project Includes
#include <Common/Math/MathUtils.hpp>

// Helper class to solve a system of linear equations of the form: A * x = b
// In general you would use this is there is reduncancy in the system (Number of equations/Number of unknowns)
// Therefore this is only solved in a somewhat "least squares sense" by exploiting variable weighting and inequality constraints to handle
// the reduncancy in the sysytem.  This is good because the redundancy in the system allows you to meet secondary objectives.

// x* = min(Ax-b)^T * S * (A*x-b) + alpha*x^T*W1*x + beta*(x-x_prev)^T * W2 * (x-x_prev))
// s.t. lb < C*x < ub

// Maps to a standard QP Formulation using qpOases:
//
// min x      1/2*x^T*H*x + x^t*g
// s.t. lb < A_qp*x > ub // Inequality Contraints

// x* = optimal solution (Num Equations)
// S  = Relative priority of decision variables (Num Equations x Num Equations)
// alpha = decision variable minimization weight (scalar)
// W1   = Relative priority of weight minimization on decision variables (Num Variables x Num Variables)
// beta = solution filtering from a previous solution.  Helps with smoothing from a previous solution (scalar)
// W2   = Relative priority of weight smoothing on decision variables (Num Variables x Num Variables)
// C    = Constraint matrix for solution (Num inequality constraints x Num Variables)

namespace Core::OptimalControl
{
    class ConvexLinearSystemSolverQP
    {

    public:
        // Base Class ConvexLinearSystemSolverQP
        // num_eq = Number of equations of OCP
        // num_vars = Number of veriables of OCP
        ConvexLinearSystemSolverQP(const unsigned int num_eq, const unsigned int num_vars, const unsigned int num_constraints);

        // Solve
        virtual void Solve();

        // Return Current Solution X
        Eigen::VectorXd X() const { return x_star_; }

        double GetSolverTime() const { return solver_time_; }

        void EnableQPDebug(bool enable);
        void PrintDebug();

    protected:

        qpOASES::SQProblem qp_;        // qpOases Solver Object

        qpOASES::int_t max_iterations_;    // Max Iterations for Solver
        qpOASES::int_t solver_iterations_; // Total number of Solver iterations for solution

        Eigen::MatrixXd A_;           // Coefficient Matrix
        Eigen::VectorXd x_star_;      // Solution Vector
        Eigen::VectorXd x_star_prev_; // Previous Solutions Vector
        Eigen::VectorXd b_;           // Constant Vector

        Eigen::MatrixXd S_;           // Relative Priority Weighting of Values
        Eigen::MatrixXd W_1_;         // Relative Priority Weighting of Solution Minimization
        Eigen::MatrixXd W_2_;         // Relative Priority Weighting of Solution Filtering Minimization
        //Eigen::MatrixXd C_;         // Inequality Constraint Matrix

        Eigen::VectorXd lbA_; // Lower Bound of Inequality Constraint Matrix
        Eigen::VectorXd ubA_; // Upper Bound of Inequality Constraint Matrix

        Eigen::MatrixXd H_qp_; // QP Hessian
        Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> A_qp_; // Inequality Constraint Matrix QP
        Eigen::VectorXd g_qp_; // Linear

        double alpha_; // Influence of Solution Minimization
        double beta_;  // Influece of Solution Filtering

        int num_equations_;   // Number of System Equations
        int num_variables_;   // Number of System Variables
        int num_constraints_; // Number of Inequality Constraints

        double solver_time_; // Total time for Solver to compute a solution

        bool solved_; // Valid Solution?

        bool is_hot_; // We have a warm QP and can use hotstarting

        // TODO:
        // Infeasible, BlahBlah
    };
} // namespace Core::OptimalControl

#endif // NOMAD_CORE_OPTIMALCONTROL_CONVEXMATRIXEQUATIONSOLVERQP_H_


#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <iostream>
#include <fstream>
#include <cassert>
#include <memory>

#include <Eigen/Dense>
#include "Components.h"
#include "Network.h"
// #include "spdlog/spdlog.h"

using Eigen::MatrixXd;

int main(int argc, char const *argv[])
{
    if (argc != 2){
        std::cout << "Incorrect usage" << std::endl;
        return -1;
    }
    // spdlog::info("Welcome to spdlog!");
    std::ifstream net_file (argv[1]);
    if (!net_file.is_open()){
        std::cout << "Failed to open in file" << std::endl;
        return -1;
    }
    Network net = Network();
    net.preprocess_netlist(net_file);
    net.parse_netlist(net_file);
    net.calculate();

	std::cout << "Conductance\n" << net.get_conductance_matrix() << '\n';
	std::cout << "Incidence\n" << net.get_incidence_matrix() << '\n';
	std::cout << "z\n" << net.get_z_matrix() << '\n';
	std::cout << "result\n" << net.get_result_matrix() << '\n';
	net_file.close();
    return 0;
}

// <comp type><name> <+> <-> <value>
// https://lpsa.swarthmore.edu/Systems/Electrical/mna/MNA3.html#Notational_Convention
/*todo:
	- look at initializing node 0 to id -1, gets rid of us having to use offset
*/


// This file is part of libigl, a simple c++ geometry processing library.
//
// Copyright (C) 2013 Alec Jacobson <alecjacobson@gmail.com>
//
// 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 "bbw.h"

#include <igl/cotmatrix.h>
#include <igl/massmatrix.h>
#include <igl/invert_diag.h>
#include <igl/speye.h>
#include <igl/slice_into.h>
#include <igl/min_quad_with_fixed.h>

#include <Eigen/Sparse>

#include <iostream>
#include <cstdio>

igl::bbw::BBWData::BBWData():
  partition_unity(false),
  W0(),
#ifndef IGL_NO_MOSEK
  mosek_data(),
#endif
  active_set_params(),
  qp_solver(QP_SOLVER_IGL_ACTIVE_SET),
  verbosity(0)
{
  // We know that the Bilaplacian is positive semi-definite
  active_set_params.Auu_pd = true;
}

void igl::bbw::BBWData::print()
{
  using namespace std;
  cout<<"partition_unity: "<<partition_unity<<endl;
  cout<<"W0=["<<endl<<W0<<endl<<"];"<<endl;
  cout<<"qp_solver: "<<QPSolverNames[qp_solver]<<endl;
}

template <
  typename DerivedV,
  typename DerivedEle,
  typename Derivedb,
  typename Derivedbc,
  typename DerivedW>
IGL_INLINE bool igl::bbw::bbw(
  const Eigen::PlainObjectBase<DerivedV> & V,
  const Eigen::PlainObjectBase<DerivedEle> & Ele,
  const Eigen::PlainObjectBase<Derivedb> & b,
  const Eigen::PlainObjectBase<Derivedbc> & bc,
  igl::bbw::BBWData & data,
  Eigen::PlainObjectBase<DerivedW> & W
  )
{
  using namespace std;
  using namespace Eigen;

  // number of domain vertices
  int n = V.rows();
  // number of handles
  int m = bc.cols();

  SparseMatrix<typename DerivedW::Scalar> L;
  cotmatrix(V,Ele,L);
  MassMatrixType mmtype = MASSMATRIX_TYPE_VORONOI;
  if(Ele.cols() == 4)
  {
    mmtype = MASSMATRIX_TYPE_BARYCENTRIC;
  }
  SparseMatrix<typename DerivedW::Scalar> M;
  SparseMatrix<typename DerivedW::Scalar> Mi;
  massmatrix(V,Ele,mmtype,M);

  invert_diag(M,Mi);

  // Biharmonic operator
  SparseMatrix<typename DerivedW::Scalar> Q = L.transpose() * Mi * L;

  W.derived().resize(n,m);
  if(data.partition_unity)
  {
    // Not yet implemented
    assert(false);
  }else
  {
    // No linear terms
    VectorXd c = VectorXd::Zero(n);
    // No linear constraints
    SparseMatrix<typename DerivedW::Scalar> A(0,n),Aeq(0,n),Aieq(0,n);
    VectorXd uc(0,1),Beq(0,1),Bieq(0,1),lc(0,1);
    // Upper and lower box constraints (Constant bounds)
    VectorXd ux = VectorXd::Ones(n);
    VectorXd lx = VectorXd::Zero(n);
    active_set_params eff_params = data.active_set_params;
    switch(data.qp_solver)
    {
      case QP_SOLVER_IGL_ACTIVE_SET:
      {
        if(data.verbosity >= 1)
        {
          cout<<"BBW: max_iter: "<<data.active_set_params.max_iter<<endl;
          cout<<"BBW: eff_max_iter: "<<eff_params.max_iter<<endl;
        }
        if(data.verbosity >= 1)
        {
          cout<<"BBW: Computing initial weights for "<<m<<" handle"<<
            (m!=1?"s":"")<<"."<<endl;
        }
        min_quad_with_fixed_data<typename DerivedW::Scalar > mqwf;
        min_quad_with_fixed_precompute(Q,b,Aeq,true,mqwf);
        min_quad_with_fixed_solve(mqwf,c,bc,Beq,W);
        // decrement
        eff_params.max_iter--;
        bool error = false;
        // Loop over handles
#pragma omp parallel for
        for(int i = 0;i<m;i++)
        {
          // Quicker exit for openmp
          if(error)
          {
            continue;
          }
          if(data.verbosity >= 1)
          {
#pragma omp critical
            cout<<"BBW: Computing weight for handle "<<i+1<<" out of "<<m<<
              "."<<endl;
          }
          VectorXd bci = bc.col(i);
          VectorXd Wi;
          // use initial guess
          Wi = W.col(i);
          SolverStatus ret = active_set(
              Q,c,b,bci,Aeq,Beq,Aieq,Bieq,lx,ux,eff_params,Wi);
          switch(ret)
          {
            case SOLVER_STATUS_CONVERGED:
              break;
            case SOLVER_STATUS_MAX_ITER:
              cerr<<"active_set: max iter without convergence."<<endl;
              break;
            case SOLVER_STATUS_ERROR:
            default:
              cerr<<"active_set error."<<endl;
              error = true;
          }
          W.col(i) = Wi;
        }
        if(error)
        {
          return false;
        }
        break;
      }
      case QP_SOLVER_MOSEK:
      {
#ifdef IGL_NO_MOSEK
        assert(false && "Use another QPSolver. Recompile without IGL_NO_MOSEK defined.");
        cerr<<"Use another QPSolver. Recompile without IGL_NO_MOSEK defined."<<endl;
        return false;
#else
        // Loop over handles
        for(int i = 0;i<m;i++)
        {
          if(data.verbosity >= 1)
          {
            cout<<"BBW: Computing weight for handle "<<i+1<<" out of "<<m<<
              "."<<endl;
          }
          VectorXd bci = bc.col(i);
          VectorXd Wi;
          // impose boundary conditions via bounds
          slice_into(bci,b,ux);
          slice_into(bci,b,lx);
          bool r = mosek_quadprog(Q,c,0,A,lc,uc,lx,ux,data.mosek_data,Wi);
          if(!r)
          {
            return false;
          }
          W.col(i) = Wi;
        }
#endif
        break;
      }
      default:
      {
        assert(false && "Unknown qp_solver");
        return false;
      }
    }
#ifndef NDEBUG
    const double min_rowsum = W.rowwise().sum().array().abs().minCoeff();
    if(min_rowsum < 0.1)
    {
      cerr<<"bbw.cpp: Warning, minimum row sum is very low. Consider more "
        "active set iterations or enforcing partition of unity."<<endl;
    }
#endif
  }

  return true;
}

#ifdef IGL_STATIC_LIBRARY
// Explicit template specialization
template bool igl::bbw::bbw<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, 1, 0, -1, 1>, Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<double, -1, -1, 0, -1, -1> >(Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, 1, 0, -1, 1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> > const&, igl::bbw::BBWData&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&);
#endif


/*
 * Software License Agreement (BSD License)
 *
 *  Copyright (c) 2018, Locus Robotics
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the copyright holder nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */
#include <fuse_constraints/normal_delta.h>

#include <Eigen/Core>
#include <glog/logging.h>

namespace fuse_constraints
{

NormalDelta::NormalDelta(const fuse_core::MatrixXd& A, const fuse_core::VectorXd& b) :
  A_(A),
  b_(b)
{
  CHECK_GT(b_.rows(), 0);
  CHECK_GT(A_.rows(), 0);
  CHECK_EQ(b_.rows(), A.cols());
  set_num_residuals(A_.rows());
  mutable_parameter_block_sizes()->push_back(b_.rows());
  mutable_parameter_block_sizes()->push_back(b_.rows());
}

bool NormalDelta::Evaluate(
  double const* const* parameters,
  double* residuals,
  double** jacobians) const
{
  Eigen::Map<const fuse_core::VectorXd> x0(parameters[0], parameter_block_sizes()[0]);
  Eigen::Map<const fuse_core::VectorXd> x1(parameters[1], parameter_block_sizes()[1]);
  Eigen::Map<fuse_core::VectorXd> r(residuals, num_residuals());
  r = A_ * (x1 - x0 - b_);
  if (jacobians != NULL)
  {
    if (jacobians[0] != NULL)
    {
      Eigen::Map<fuse_core::MatrixXd>(jacobians[0], num_residuals(), parameter_block_sizes()[0]) = -A_;
    }
    if (jacobians[1] != NULL)
    {
      Eigen::Map<fuse_core::MatrixXd>(jacobians[1], num_residuals(), parameter_block_sizes()[1]) = A_;
    }
  }
  return true;
}

}  // namespace fuse_constraints


/* ---------------------------------------------------------------------
**
** Copyright (C) 2017 Xiaoyu Wei
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to deal
** in the Software without restriction, including without limitation the rights
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
** copies of the Software, and to permit persons to whom the Software is
** furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in
** all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
** THE SOFTWARE.
**
** -------------------------------------------------------------------*/

#include <iostream>
#include <fstream>
#include <limits>
#include <string>
#include <array>
#include <cmath>

#include <boost/python/def.hpp>
#include <boost/python/docstring_options.hpp>
#include <boost/python/module.hpp>
#include <boost/python/numpy.hpp>

#include <deal.II/base/quadrature_lib.h>
#include <deal.II/fe/fe_q.h>
#include <deal.II/fe/fe_values.h>
#include <deal.II/grid/grid_generator.h>
#include <deal.II/grid/grid_out.h>
#include <deal.II/grid/grid_refinement.h>
#include <deal.II/grid/tria.h>
#include <deal.II/lac/vector.h>

namespace p  = boost::python;
namespace np = boost::python::numpy;
namespace d  = dealii;

constexpr int        dim = 2;
constexpr double     a   = -1;
constexpr double     b   = 1;
const d::UpdateFlags update_flags =
    d::update_quadrature_points | d::update_JxW_values;

void greet() { std::cout << "Yey!" << std::endl; }

double l_infty_distance(d::Point<dim> & p1, d::Point<dim> & p2){
  double dist = std::numeric_limits<double>::max();
  for (unsigned int d=0; d < dim; ++d) {
    double tmp = std::abs(p1[d] - p2[d]);
    dist = tmp > dist ? dist : tmp;
  }
  return dist;
}

double compute_q_point_radii(d::Point<dim> q_point, std::array<d::Point<dim>,
    d::GeometryInfo<dim>::vertices_per_cell> & vertices){
  double rad = std::numeric_limits<double>::max();
  for(auto && v : vertices){
    double tmp = l_infty_distance(q_point, v);
    rad = tmp > rad ? rad : tmp;
  }
  return rad;
}

template<int dimension, int n_vertices>
d::Point<dimension> compute_barycenter(
    std::array<d::Point<dimension>, n_vertices> & vertices){

  d::Point<dimension> bc;
  for (unsigned int d=0; d < dimension; ++d) {
    bc[d] = 0;
  }

  for(auto && v : vertices){
    bc += v;
  }
  bc /= n_vertices;
  return bc;
}

template <int dimension>
class MeshGenerator {
public:
  d::Triangulation<dimension> triangulation;
  d::FE_Q<dimension>          fe;
  d::DoFHandler<dimension>    dof_handler;
  d::QGauss<dimension>              quadrature_formula;

  double box_a, box_b;

  // q: quad order
  // level: initial (uniform) level
  MeshGenerator(int q, int level):
      triangulation(d::Triangulation<dimension>::maximum_smoothing),
      fe(q),
      dof_handler(triangulation),
      quadrature_formula(q),
      box_a(a),
      box_b(b)
  {
    d::GridGenerator::hyper_cube(triangulation, box_a, box_b);
    if (level > 1) {
      triangulation.refine_global(level - 1);
    }
    this->dof_handler.distribute_dofs(fe);
  }

  // customized bounding box
  MeshGenerator(int q, int level, double aa, double bb):
      triangulation(d::Triangulation<dimension>::maximum_smoothing),
      fe(q),
      dof_handler(triangulation),
      quadrature_formula(q),
      box_a(aa),
      box_b(bb)
  {
    d::GridGenerator::hyper_cube(triangulation, box_a, box_b);
    if (level > 1) {
      triangulation.refine_global(level - 1);
    }
    this->dof_handler.distribute_dofs(fe);
  }

  // NOTE: Copy constructor is not supported since dealii::DoFHandler is not
  // copy constructable, but a dummy copy constructor is required by
  // boost.python to build the code.
  MeshGenerator(const MeshGenerator<dimension>&):
    MeshGenerator(1, 1){};

  std::string greet() { return "Hello from MeshGen."; }

  void generate_gmsh(p::object &fn) {
    std::string filename = p::extract<std::string>(fn);
    std::ofstream output_file(filename);
    d::GridOut().write_msh(this->triangulation, output_file);
  }

  np::ndarray get_q_points() {
    np::dtype dtype = np::dtype::get_builtin<double>();

    const unsigned int n_q_points = this->quadrature_formula.size();
    const int total_n_q_points = triangulation.n_active_cells() * n_q_points;
    Py_intptr_t pt_shape[2] = {total_n_q_points, dimension};

    d::FEValues<dimension>   fe_values(this->fe, this->quadrature_formula,
        d::update_quadrature_points);
    np::ndarray points = np::zeros(2, pt_shape, dtype);
    auto ptp           = reinterpret_cast<double*>(points.get_data());

    auto cell = dof_handler.begin_active();
    auto endc = dof_handler.end();
    for (; cell != endc; ++cell) {
      fe_values.reinit(cell);
      std::vector<d::Point<dimension>> q_points = fe_values.get_quadrature_points();

      for (auto&& point : q_points) {
        for (unsigned int d = 0; d < dimension; ++d) {
          // For a preferred ordering of quad points
          // This does not change anything but the ordering due to the symmetry
          // in each direction (as long as the subdivided boxes are cubes), not
          // even weights are changed.
          *ptp = point[dim-1-d];
          ++ptp;
        }
      }
    }

    return points;
  }

  np::ndarray get_q_weights(){
    np::dtype dtype = np::dtype::get_builtin<double>();

    const unsigned int n_q_points = this->quadrature_formula.size();
    const int total_n_q_points = this->triangulation.n_active_cells() * n_q_points;
    Py_intptr_t w_shape[1]       = {total_n_q_points};

    d::FEValues<dimension>   fe_values(this->fe, this->quadrature_formula,
        d::update_JxW_values);
    np::ndarray weights = np::zeros(1, w_shape, dtype);
    auto ptw = reinterpret_cast<double*>(weights.get_data());

    auto cell = this->dof_handler.begin_active();
    auto endc = this->dof_handler.end();
    for (; cell != endc; ++cell) {
      fe_values.reinit(cell);
      std::vector<double> q_weights;

      for (unsigned int q_index = 0; q_index < n_q_points; ++q_index) {
        q_weights.push_back(fe_values.JxW(q_index));
      }

      for (auto&& weight : q_weights) {
          *ptw = weight;
          ++ptw;
      }
    }

    return weights;
  }

  np::ndarray get_cell_measures(){
    np::dtype dtype = np::dtype::get_builtin<double>();
    Py_intptr_t m_shape[1] = {this->triangulation.n_active_cells()};
    np::ndarray measures = np::zeros(1, m_shape, dtype);
    auto ptm = reinterpret_cast<double*>(measures.get_data());

    auto cell = this->dof_handler.begin_active();
    auto endc = this->dof_handler.end();
    for (; cell != endc; ++cell) {
      *ptm = cell->measure();
      ++ptm;
    }

    return measures;
  }

  np::ndarray get_cell_centers(){
    np::dtype dtype = np::dtype::get_builtin<double>();
    Py_intptr_t c_shape[2] = {this->triangulation.n_active_cells(), dimension};
    np::ndarray centers = np::zeros(2, c_shape, dtype);
    auto ptc = reinterpret_cast<double*>(centers.get_data());

    auto cell = this->dof_handler.begin_active();
    auto endc = this->dof_handler.end();
    for (; cell != endc; ++cell) {
      std::array<d::Point<dimension>,d::GeometryInfo<dimension>::vertices_per_cell> vertices;
      for (unsigned int v=0; v<d::GeometryInfo<dimension>::vertices_per_cell; ++v) {
        vertices[v] = cell->vertex(v);
      }
      auto barycenter = compute_barycenter<dimension,
                          d::GeometryInfo<dimension>::vertices_per_cell>(vertices);
      for (unsigned int d = 0; d < dimension; ++d) {
        *ptc = barycenter[dimension-1-d];
        ++ptc;
      }
    }

    return centers;
  }

  // Interface for dealii::GridRefinement::refine_and_coarsen_fixed_number
  void refine_and_coarsen_fixed_number(double* criteria,
                                       const double top_fraction_of_cells,
                                       const double bottom_fraction_of_cells) {
    d::Vector<double> ctr;
    ctr.reinit(this->triangulation.n_active_cells());
    double* ptc = criteria;
    for (unsigned int c = 0; c < this->triangulation.n_active_cells(); ++c){
      ctr[c] = *ptc;
      ++ptc;
    }

    d::GridRefinement::refine_and_coarsen_fixed_number (this->triangulation,
                                                        ctr,
                                                        top_fraction_of_cells,
                                                        bottom_fraction_of_cells);
  }

  int n_active_cells() {
    return this->triangulation.n_active_cells();
  }

  // Do both preparation for refinement and coarsening as well as mesh smoothing.
  // The function returns whether some cells' flagging has been changed in the process.
  bool prepare_coarsening_and_refinement() {
    return this->triangulation.prepare_coarsening_and_refinement();
  }

  void execute_coarsening_and_refinement() {
    this->triangulation.execute_coarsening_and_refinement();
    this->dof_handler.distribute_dofs(fe);
  }

  // Driver function for mesh adaptivity
  int update_mesh(np::ndarray const & criteria,
                   const double top_fraction_of_cells,
                   const double bottom_fraction_of_cells) {
    if (criteria.get_dtype() != np::dtype::get_builtin<double>()) {
        PyErr_SetString(PyExc_TypeError, "Incorrect array data type in the criteria");
        p::throw_error_already_set();
    }
    if (criteria.get_nd() != 1) {
        PyErr_SetString(PyExc_TypeError, "Incorrect number of dimensions of the criteria");
        p::throw_error_already_set();
    }

    // Refining top 1/3 in 2D doubles the number of cells
    double * iter = reinterpret_cast<double*>(criteria.get_data());
    this->refine_and_coarsen_fixed_number(iter, top_fraction_of_cells,
                                          bottom_fraction_of_cells);

    this->prepare_coarsening_and_refinement();
    this->execute_coarsening_and_refinement();

    return this->n_active_cells();
  }

  // Show some info about the mesh
  void print_info() {
    std::cout << "Number of active cells: " << this->triangulation.n_active_cells()
              << std::endl;

    d::FEValues<dimension>   fe_values(fe, quadrature_formula, d::update_quadrature_points);
    const unsigned int n_q_points = quadrature_formula.size();
    std::cout << "Number of quad points per cell: " << n_q_points << std::endl;
  }

};
typedef MeshGenerator<2> MeshGen2D;
typedef MeshGenerator<3> MeshGen3D;

// A legacy one-stop mesh generation function, returns quad points, weights
// and some spacing info within a tuple.
template <int dim>
p::tuple make_uniform_cubic_grid_details(int q, int level) {
  np::dtype dtype = np::dtype::get_builtin<double>();

  d::Triangulation<dim> triangulation;
  d::FE_Q<dim>          fe(q);
  d::DoFHandler<dim>    dof_handler(triangulation);
  d::QGauss<dim>        quadrature_formula(q);

  d::GridGenerator::hyper_cube(triangulation, a, b);
  if (level > 1) {
    triangulation.refine_global(level - 1);
  }
  dof_handler.distribute_dofs(fe);
  //std::cout << "Number of active cells: " << triangulation.n_active_cells()
            //<< std::endl;

  d::FEValues<dim>   fe_values(fe, quadrature_formula, update_flags);
  const unsigned int n_q_points = quadrature_formula.size();
  //std::cout << "Number of quad points per cell: " << n_q_points << std::endl;

  const int   total_n_q_points = triangulation.n_active_cells() * n_q_points;
  Py_intptr_t pt_shape[2]      = {total_n_q_points, dim};
  Py_intptr_t w_shape[1]       = {total_n_q_points};
  // Quad points
  np::ndarray points           = np::zeros(2, pt_shape, dtype);
  // Quad weights
  np::ndarray weights          = np::zeros(1, w_shape, dtype);
  // Distance (l_infty) to the closest cell vertex
  // (used for reconstructing the mesh in boxtree)
  np::ndarray radii            = np::zeros(1, w_shape, dtype);
  auto        ptp              = reinterpret_cast<double*>(points.get_data());
  auto        ptw              = reinterpret_cast<double*>(weights.get_data());
  auto        ptr              = reinterpret_cast<double*>(radii.get_data());

  // For margins
  std::array<double,dim*2> margins;
  std::array<double*, dim*2> mpts;
  margins.fill(std::numeric_limits<double>::max());

  auto cell = dof_handler.begin_active();
  auto endc = dof_handler.end();
  for (; cell != endc; ++cell) {
    fe_values.reinit(cell);
    std::vector<d::Point<dim>> q_points = fe_values.get_quadrature_points();
    std::vector<double>        q_weights;
    std::array<d::Point<dim>,d::GeometryInfo<dim>::vertices_per_cell> vertices;

    for (unsigned int q_index = 0; q_index < n_q_points; ++q_index) {
      q_weights.push_back(fe_values.JxW(q_index));
    }

    for (unsigned int v=0; v<d::GeometryInfo<dim>::vertices_per_cell; ++v) {
      vertices[v] = cell->vertex(v);
    }

    for (auto&& point : q_points) {
      for (unsigned int d = 0; d < dim; ++d) {
        // For a preferred ordering of quad points
        // This does not change anything but the ordering due to the symmetry
        // in each direction (as long as the subdivided boxes are cubes), not
        // even weights are changed.
        *ptp = point[dim-1-d];
        ++ptp;
      }
      if( std::abs(point[0] - a) < margins[0] ){
        margins[0] = std::abs(point[0] - a);
        mpts[0] = ptr;
      }
      if( std::abs(point[0] - b) < margins[1] ){
        margins[1] = std::abs(point[0] - b);
        mpts[1] = ptr;
      }
      if( std::abs(point[1] - a) < margins[2] ){
        margins[2] = std::abs(point[1] - a);
        mpts[2] = ptr;
      }
      if( std::abs(point[1] - b) < margins[3] ){
        margins[3] = std::abs(point[1] - b);
        mpts[3] = ptr;
      }
      ++ptr;
    }

    for (auto&& weight : q_weights) {
      *ptw = weight;
      ++ptw;
    }
  }

  for (unsigned int i=0; i < dim*2; ++i) {
    *(mpts[i]) = margins[i];
  }

  p::tuple result = p::make_tuple(points, weights, radii);
  return result;
}

p::tuple make_uniform_cubic_grid(int q, int level, int dim) {
  if (dim == 1) {
    return make_uniform_cubic_grid_details<1>(q, level);
  }
  else if (dim == 2) {
    return make_uniform_cubic_grid_details<2>(q, level);
  }
  else if (dim == 3) {
    return make_uniform_cubic_grid_details<3>(q, level);
  }
  else {
    std::cout << "Dimension must be 1,2 or 3." << std::endl;
    return make_uniform_cubic_grid_details<3>(q, level);
  }
}

BOOST_PYTHON_MODULE(meshgen_dealii) {
  using namespace boost::python;
  Py_Initialize();
  np::initialize();

  docstring_options doc_options(/*show_all=*/true);

  def("greet", greet,
      "Greentings! This module handles generation of quadrature points & "
      "weights.\n\nThe points and weights follow Legendre-Gauss quadrature "
      "rules.");

  def("make_uniform_cubic_grid", make_uniform_cubic_grid,
      (p::arg("degree") = 3, p::arg("level") = 2, p::arg("dim") = 2), "Make a simple cubic grid.");

  class_<MeshGen2D>("MeshGen2D", init<int, int>())
        .def(init<int, int, double, double>())
        .def("greet", &MeshGen2D::greet)
        .def("get_q_points", &MeshGen2D::get_q_points)
        .def("get_q_weights", &MeshGen2D::get_q_weights)
        .def("get_cell_centers", &MeshGen2D::get_cell_centers)
        .def("get_cell_measures", &MeshGen2D::get_cell_measures)
        .def("n_active_cells", &MeshGen2D::n_active_cells)
        .def("prepare_coarsening_and_refinement", &MeshGen2D::prepare_coarsening_and_refinement)
        .def("execute_coarsening_and_refinement", &MeshGen2D::execute_coarsening_and_refinement)
        .def("update_mesh", &MeshGen2D::update_mesh)
        .def("print_info", &MeshGen2D::print_info)
        .def("generate_gmsh", &MeshGen2D::generate_gmsh)
    ;

  class_<MeshGen3D>("MeshGen3D", init<int, int>())
        .def(init<int, int, double, double>())
        .def("greet", &MeshGen3D::greet)
        .def("get_q_points", &MeshGen3D::get_q_points)
        .def("get_q_weights", &MeshGen3D::get_q_weights)
        .def("get_cell_centers", &MeshGen3D::get_cell_centers)
        .def("get_cell_measures", &MeshGen3D::get_cell_measures)
        .def("n_active_cells", &MeshGen3D::n_active_cells)
        .def("prepare_coarsening_and_refinement", &MeshGen3D::prepare_coarsening_and_refinement)
        .def("execute_coarsening_and_refinement", &MeshGen3D::execute_coarsening_and_refinement)
        .def("update_mesh", &MeshGen3D::update_mesh)
        .def("print_info", &MeshGen3D::print_info)
        .def("generate_gmsh", &MeshGen3D::generate_gmsh)
    ;
}


#include <stan/math/prim.hpp>
#include <test/unit/util.hpp>
#include <gtest/gtest.h>

#ifdef STAN_OPENCL
#include <stan/math/opencl/opencl.hpp>
#include <boost/random/mersenne_twister.hpp>
#endif

TEST(MathMatrixPrim, mdivide_left_tri_val) {
  using stan::math::mdivide_left_tri;
  stan::math::matrix_d I = Eigen::MatrixXd::Identity(2, 2);

  stan::math::matrix_d Ad(2, 2);
  Ad << 2.0, 0.0, 5.0, 7.0;
  EXPECT_MATRIX_FLOAT_EQ(I, mdivide_left_tri<Eigen::Lower>(Ad, Ad));

  stan::math::matrix_d A_Ainv = Ad * mdivide_left_tri<Eigen::Lower>(Ad);
  EXPECT_MATRIX_NEAR(I, A_Ainv, 1e-15);

  Ad << 2.0, 3.0, 0.0, 7.0;
  EXPECT_MATRIX_FLOAT_EQ(I, mdivide_left_tri<Eigen::Upper>(Ad, Ad));
}

TEST(MathMatrixPrim, mdivide_left_tri_size_zero) {
  using stan::math::mdivide_left_tri;
  stan::math::matrix_d Ad(0, 0);
  stan::math::matrix_d b0(0, 2);
  stan::math::matrix_d I;

  I = mdivide_left_tri<Eigen::Lower>(Ad, Ad);
  EXPECT_EQ(0, I.rows());
  EXPECT_EQ(0, I.cols());

  I = mdivide_left_tri<Eigen::Upper>(Ad, Ad);
  EXPECT_EQ(0, I.rows());
  EXPECT_EQ(0, I.cols());

  I = mdivide_left_tri<Eigen::Lower>(Ad);
  EXPECT_EQ(0, I.rows());
  EXPECT_EQ(0, I.cols());

  I = mdivide_left_tri<Eigen::Upper>(Ad);
  EXPECT_EQ(0, I.rows());
  EXPECT_EQ(0, I.cols());

  I = mdivide_left_tri<Eigen::Lower>(Ad, b0);
  EXPECT_EQ(0, I.rows());
  EXPECT_EQ(b0.cols(), I.cols());

  I = mdivide_left_tri<Eigen::Upper>(Ad, b0);
  EXPECT_EQ(0, I.rows());
  EXPECT_EQ(b0.cols(), I.cols());
}

#ifdef STAN_OPENCL
void mdivide_left_tri_lower_cl_test(int size) {
  boost::random::mt19937 rng;
  stan::math::matrix_d m1(size, size);
  for (int i = 0; i < size; i++) {
    for (int j = 0; j < i; j++) {
      m1(i, j) = stan::math::uniform_rng(-5, 5, rng);
    }
    m1(i, i) = 20.0;
    for (int j = i + 1; j < size; j++) {
      m1(i, j) = 0.0;
    }
  }

  stan::math::opencl_context.tuning_opts().tri_inverse_size_worth_transfer
      = size * 2;

  stan::math::matrix_d m1_cpu = stan::math::mdivide_left_tri<Eigen::Lower>(m1);

  stan::math::opencl_context.tuning_opts().tri_inverse_size_worth_transfer = 0;

  stan::math::matrix_d m1_cl = stan::math::mdivide_left_tri<Eigen::Lower>(m1);

  EXPECT_MATRIX_NEAR(m1_cpu, m1_cl, 1E-8);
}
TEST(MathMatrixCL, mdivide_left_tri_lower_cl_small) {
  mdivide_left_tri_lower_cl_test(3);
}
TEST(MathMatrixCL, mdivide_left_tri_lower_cl_mid) {
  mdivide_left_tri_lower_cl_test(100);
}
TEST(MathMatrixCL, mdivide_left_tri_lower_cl_big) {
  mdivide_left_tri_lower_cl_test(500);
}

void mdivide_left_tri_upper_cl_test(int size) {
  boost::random::mt19937 rng;
  stan::math::matrix_d m1(size, size);
  for (int i = 0; i < size; i++) {
    for (int j = 0; j < i; j++) {
      m1(i, j) = 0.0;
    }
    m1(i, i) = 20.0;
    for (int j = i + 1; j < size; j++) {
      m1(i, j) = stan::math::uniform_rng(-5, 5, rng);
    }
  }

  stan::math::opencl_context.tuning_opts().tri_inverse_size_worth_transfer
      = size * 2;

  stan::math::matrix_d m1_cpu = stan::math::mdivide_left_tri<Eigen::Upper>(m1);

  stan::math::opencl_context.tuning_opts().tri_inverse_size_worth_transfer = 0;

  stan::math::matrix_d m1_cl = stan::math::mdivide_left_tri<Eigen::Upper>(m1);

  EXPECT_MATRIX_NEAR(m1_cpu, m1_cl, 1E-8);
}
TEST(MathMatrixCL, mdivide_left_tri_upper_cl_small) {
  mdivide_left_tri_upper_cl_test(3);
}
TEST(MathMatrixCL, mdivide_left_tri_upper_cl_mid) {
  mdivide_left_tri_upper_cl_test(100);
}
TEST(MathMatrixCL, mdivide_left_tri_upper_cl_big) {
  mdivide_left_tri_upper_cl_test(500);
}

void mdivide_left_tri_cl_test(int size) {
  boost::random::mt19937 rng;
  stan::math::matrix_d m1(size, size);
  for (int i = 0; i < size; i++) {
    for (int j = 0; j < i; j++) {
      m1(i, j) = stan::math::uniform_rng(-5, 5, rng);
    }
    m1(i, i) = 20.0;
    for (int j = i + 1; j < size; j++) {
      m1(i, j) = 0.0;
    }
  }

  stan::math::opencl_context.tuning_opts().tri_inverse_size_worth_transfer
      = size * 2;

  stan::math::matrix_d m1_cpu
      = stan::math::mdivide_left_tri<Eigen::Lower>(m1, m1);

  stan::math::opencl_context.tuning_opts().tri_inverse_size_worth_transfer = 0;

  stan::math::matrix_d m1_cl
      = stan::math::mdivide_left_tri<Eigen::Lower>(m1, m1);

  EXPECT_MATRIX_NEAR(m1_cpu, m1_cl, 1E-8);
}
TEST(MathMatrixCL, mdivide_left_tri_cl_small) { mdivide_left_tri_cl_test(3); }
TEST(MathMatrixCL, mdivide_left_tri_cl_mid) { mdivide_left_tri_cl_test(100); }
TEST(MathMatrixCL, mdivide_left_tri_cl_big) { mdivide_left_tri_cl_test(500); }
#endif


#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics.hpp>
#include <iostream>

using namespace boost::accumulators;

int main()
{
  accumulator_set<double, features<tag::mean, tag::variance>, int> acc;
  acc(8, weight = 1);
  acc(9, weight = 1);
  acc(10, weight = 4);
  acc(11, weight = 1);
  acc(12, weight = 1);
  std::cout << mean(acc) << '\n';
  std::cout << variance(acc) << '\n';
}


#include <stan/math/prim/scal.hpp>
#include <gtest/gtest.h>
#include <boost/random/mersenne_twister.hpp>
#include <boost/math/distributions.hpp>

TEST(ProbDistributionBetaBinomial, error_check) {
  boost::random::mt19937 rng;
  EXPECT_NO_THROW(stan::math::beta_binomial_rng(4, 0.6, 2.0, rng));

  EXPECT_THROW(stan::math::beta_binomial_rng(-4, 0.6, 2, rng),
               std::domain_error);
  EXPECT_THROW(stan::math::beta_binomial_rng(4, -0.6, 2, rng),
               std::domain_error);
  EXPECT_THROW(stan::math::beta_binomial_rng(4, 0.6, -2, rng),
               std::domain_error);
  EXPECT_THROW(
      stan::math::beta_binomial_rng(4, stan::math::positive_infinity(), 2, rng),
      std::domain_error);
  EXPECT_THROW(stan::math::beta_binomial_rng(
                   4, 0.6, stan::math::positive_infinity(), rng),
               std::domain_error);
}


// test external pac
#include <Eigen/Dense>
#include <fmt/core.h>
#include <fmt/ostream.h>

// test std libraries
#include <iostream>
#include <string>
#include <string_view>

// test c libraries
#include <cassert>
#include <cctype>
#include <cstddef>
#include <cstdint>
#include <cstring>

int some_fun2() {
    fmt::print("Hello from fmt{}", "!");

    // populate an Eigen vector with the values
    auto eigen_vec = Eigen::VectorXd::LinSpaced(10, 0, 1);

    // print the vector
    fmt::print("{}", eigen_vec);

    return 0;
}