/*
 * SVD_pvfmm.hpp
 *
 *  Created on: Feb 20, 2017
 *      Author: wyan
 */

#ifndef SVD_PVFMM_HPP_
#define SVD_PVFMM_HPP_

#include <cassert>
#include <cmath>
#include <iostream>
#include <vector>

#include <Eigen/Dense>

template <class T>
inline void gemm(char TransA, char TransB, int M, int N, int K, T alpha, T *A,
                 int lda, T *B, int ldb, T beta, T *C, int ldc) {
    if ((TransA == 'N' || TransA == 'n') && (TransB == 'N' || TransB == 'n')) {
#pragma omp parallel for
        for (auto n = 0; n < N; n++) {     // Columns of C
            for (auto m = 0; m < M; m++) { // Rows of C
                T AxB = 0;
                for (auto k = 0; k < K; k++) {
                    AxB += A[m + lda * k] * B[k + ldb * n];
                }
                C[m + ldc * n] =
                    alpha * AxB + (beta == 0 ? 0 : beta * C[m + ldc * n]);
            }
        }
    } else if (TransA == 'N' || TransA == 'n') {
#pragma omp parallel for
        for (auto n = 0; n < N; n++) {     // Columns of C
            for (auto m = 0; m < M; m++) { // Rows of C
                T AxB = 0;
                for (auto k = 0; k < K; k++) {
                    AxB += A[m + lda * k] * B[n + ldb * k];
                }
                C[m + ldc * n] =
                    alpha * AxB + (beta == 0 ? 0 : beta * C[m + ldc * n]);
            }
        }
    } else if (TransB == 'N' || TransB == 'n') {
#pragma omp parallel for
        for (auto n = 0; n < N; n++) {     // Columns of C
            for (auto m = 0; m < M; m++) { // Rows of C
                T AxB = 0;
                for (auto k = 0; k < K; k++) {
                    AxB += A[k + lda * m] * B[k + ldb * n];
                }
                C[m + ldc * n] =
                    alpha * AxB + (beta == 0 ? 0 : beta * C[m + ldc * n]);
            }
        }
    } else {
#pragma omp parallel for
        for (auto n = 0; n < N; n++) {     // Columns of C
            for (auto m = 0; m < M; m++) { // Rows of C
                T AxB = 0;
                for (auto k = 0; k < K; k++) {
                    AxB += A[k + lda * m] * B[n + ldb * k];
                }
                C[m + ldc * n] =
                    alpha * AxB + (beta == 0 ? 0 : beta * C[m + ldc * n]);
            }
        }
    }
}

#define U(i, j) U_[(i)*dim[0] + (j)]
#define S(i, j) S_[(i)*dim[1] + (j)]
#define V(i, j) V_[(i)*dim[1] + (j)]

template <class T>
void GivensL(T *S_, const size_t dim[2], size_t m, T a, T b) {
    T r = sqrt(a * a + b * b);
    T c = a / r;
    T s = -b / r;

#pragma omp parallel for
    for (size_t i = 0; i < dim[1]; i++) {
        T S0 = S(m + 0, i);
        T S1 = S(m + 1, i);
        S(m, i) += S0 * (c - 1);
        S(m, i) += S1 * (-s);

        S(m + 1, i) += S0 * (s);
        S(m + 1, i) += S1 * (c - 1);
    }
}

template <class T>
void GivensR(T *S_, const size_t dim[2], size_t m, T a, T b) {
    T r = sqrt(a * a + b * b);
    T c = a / r;
    T s = -b / r;

#pragma omp parallel for
    for (size_t i = 0; i < dim[0]; i++) {
        T S0 = S(i, m + 0);
        T S1 = S(i, m + 1);
        S(i, m) += S0 * (c - 1);
        S(i, m) += S1 * (-s);

        S(i, m + 1) += S0 * (s);
        S(i, m + 1) += S1 * (c - 1);
    }
}

template <class T>
void SVD(const size_t dim[2], T *U_, T *S_, T *V_, T eps = -1) {
    assert(dim[0] >= dim[1]);
#ifdef SVD_DEBUG
    Matrix<T> M0(dim[0], dim[1], S_);
#endif

    { // Bi-diagonalization
        size_t n = std::min(dim[0], dim[1]);
        std::vector<T> house_vec(std::max(dim[0], dim[1]));
        for (size_t i = 0; i < n; i++) {
            // Column Householder
            {
                T x1 = S(i, i);
                if (x1 < 0)
                    x1 = -x1;

                T x_inv_norm = 0;
                for (size_t j = i; j < dim[0]; j++) {
                    x_inv_norm += S(j, i) * S(j, i);
                }
                if (x_inv_norm > 0)
                    x_inv_norm = 1 / sqrt(x_inv_norm);

                T alpha = sqrt(1 + x1 * x_inv_norm);
                T beta = x_inv_norm / alpha;

                house_vec[i] = -alpha;
                for (size_t j = i + 1; j < dim[0]; j++) {
                    house_vec[j] = -beta * S(j, i);
                }
                if (S(i, i) < 0)
                    for (size_t j = i + 1; j < dim[0]; j++) {
                        house_vec[j] = -house_vec[j];
                    }
            }
#pragma omp parallel for
            for (size_t k = i; k < dim[1]; k++) {
                T dot_prod = 0;
                for (size_t j = i; j < dim[0]; j++) {
                    dot_prod += S(j, k) * house_vec[j];
                }
                for (size_t j = i; j < dim[0]; j++) {
                    S(j, k) -= dot_prod * house_vec[j];
                }
            }
#pragma omp parallel for
            for (size_t k = 0; k < dim[0]; k++) {
                T dot_prod = 0;
                for (size_t j = i; j < dim[0]; j++) {
                    dot_prod += U(k, j) * house_vec[j];
                }
                for (size_t j = i; j < dim[0]; j++) {
                    U(k, j) -= dot_prod * house_vec[j];
                }
            }

            // Row Householder
            if (i >= n - 1)
                continue;
            {
                T x1 = S(i, i + 1);
                if (x1 < 0)
                    x1 = -x1;

                T x_inv_norm = 0;
                for (size_t j = i + 1; j < dim[1]; j++) {
                    x_inv_norm += S(i, j) * S(i, j);
                }
                if (x_inv_norm > 0)
                    x_inv_norm = 1 / sqrt(x_inv_norm);

                T alpha = sqrt(1 + x1 * x_inv_norm);
                T beta = x_inv_norm / alpha;

                house_vec[i + 1] = -alpha;
                for (size_t j = i + 2; j < dim[1]; j++) {
                    house_vec[j] = -beta * S(i, j);
                }
                if (S(i, i + 1) < 0)
                    for (size_t j = i + 2; j < dim[1]; j++) {
                        house_vec[j] = -house_vec[j];
                    }
            }
#pragma omp parallel for
            for (size_t k = i; k < dim[0]; k++) {
                T dot_prod = 0;
                for (size_t j = i + 1; j < dim[1]; j++) {
                    dot_prod += S(k, j) * house_vec[j];
                }
                for (size_t j = i + 1; j < dim[1]; j++) {
                    S(k, j) -= dot_prod * house_vec[j];
                }
            }
#pragma omp parallel for
            for (size_t k = 0; k < dim[1]; k++) {
                T dot_prod = 0;
                for (size_t j = i + 1; j < dim[1]; j++) {
                    dot_prod += V(j, k) * house_vec[j];
                }
                for (size_t j = i + 1; j < dim[1]; j++) {
                    V(j, k) -= dot_prod * house_vec[j];
                }
            }
        }
    }

    size_t k0 = 0;
    size_t iter = 0;
    if (eps < 0) {
        eps = 1.0;
        while (eps + (T)1.0 > 1.0)
            eps *= 0.5;
        eps *= 64.0;
    }
    while (k0 < dim[1] - 1) { // Diagonalization
        iter++;

        T S_max = 0.0;
        for (size_t i = 0; i < dim[1]; i++)
            S_max = (S_max > S(i, i) ? S_max : S(i, i));

        while (k0 < dim[1] - 1 && std::abs<T>(S(k0, k0 + 1)) <= eps * S_max)
            k0++;
        if (k0 == dim[1] - 1)
            continue;

        size_t n = k0 + 2;
        while (n < dim[1] && std::abs<T>(S(n - 1, n)) > eps * S_max)
            n++;

        T alpha = 0;
        T beta = 0;
        { // Compute mu
            T C[2][2];
            C[0][0] = S(n - 2, n - 2) * S(n - 2, n - 2);
            if (n - k0 > 2)
                C[0][0] += S(n - 3, n - 2) * S(n - 3, n - 2);
            C[0][1] = S(n - 2, n - 2) * S(n - 2, n - 1);
            C[1][0] = S(n - 2, n - 2) * S(n - 2, n - 1);
            C[1][1] = S(n - 1, n - 1) * S(n - 1, n - 1) +
                      S(n - 2, n - 1) * S(n - 2, n - 1);

            T b = -(C[0][0] + C[1][1]) / 2;
            T c = C[0][0] * C[1][1] - C[0][1] * C[1][0];
            T d = 0;
            if (b * b - c > 0)
                d = sqrt(b * b - c);
            else {
                T b = (C[0][0] - C[1][1]) / 2;
                T c = -C[0][1] * C[1][0];
                if (b * b - c > 0)
                    d = sqrt(b * b - c);
            }

            T lambda1 = -b + d;
            T lambda2 = -b - d;

            T d1 = lambda1 - C[1][1];
            d1 = (d1 < 0 ? -d1 : d1);
            T d2 = lambda2 - C[1][1];
            d2 = (d2 < 0 ? -d2 : d2);
            T mu = (d1 < d2 ? lambda1 : lambda2);

            alpha = S(k0, k0) * S(k0, k0) - mu;
            beta = S(k0, k0) * S(k0, k0 + 1);
        }

        for (size_t k = k0; k < n - 1; k++) {
            size_t dimU[2] = {dim[0], dim[0]};
            size_t dimV[2] = {dim[1], dim[1]};
            GivensR(S_, dim, k, alpha, beta);
            GivensL(V_, dimV, k, alpha, beta);

            alpha = S(k, k);
            beta = S(k + 1, k);
            GivensL(S_, dim, k, alpha, beta);
            GivensR(U_, dimU, k, alpha, beta);

            alpha = S(k, k + 1);
            beta = S(k, k + 2);
        }

        { // Make S bi-diagonal again
            for (size_t i0 = k0; i0 < n - 1; i0++) {
                for (size_t i1 = 0; i1 < dim[1]; i1++) {
                    if (i0 > i1 || i0 + 1 < i1)
                        S(i0, i1) = 0;
                }
            }
            for (size_t i0 = 0; i0 < dim[0]; i0++) {
                for (size_t i1 = k0; i1 < n - 1; i1++) {
                    if (i0 > i1 || i0 + 1 < i1)
                        S(i0, i1) = 0;
                }
            }
            for (size_t i = 0; i < dim[1] - 1; i++) {
                if (std::abs<T>(S(i, i + 1)) <= eps * S_max) {
                    S(i, i + 1) = 0;
                }
            }
        }
        // std::cout<<iter<<' '<<k0<<' '<<n<<'\n';
    }

    { // Check Error
#ifdef SVD_DEBUG
        Matrix<T> U0(dim[0], dim[0], U_);
        Matrix<T> S0(dim[0], dim[1], S_);
        Matrix<T> V0(dim[1], dim[1], V_);
        Matrix<T> E = M0 - U0 * S0 * V0;
        T max_err = 0;
        T max_nondiag0 = 0;
        T max_nondiag1 = 0;
        for (size_t i = 0; i < E.Dim(0); i++)
            for (size_t j = 0; j < E.Dim(1); j++) {
                if (max_err < pvfmm::fabs<T>(E[i][j]))
                    max_err = pvfmm::fabs<T>(E[i][j]);
                if ((i > j + 0 || i + 0 < j) &&
                    max_nondiag0 < pvfmm::fabs<T>(S0[i][j]))
                    max_nondiag0 = pvfmm::fabs<T>(S0[i][j]);
                if ((i > j + 1 || i + 1 < j) &&
                    max_nondiag1 < pvfmm::fabs<T>(S0[i][j]))
                    max_nondiag1 = pvfmm::fabs<T>(S0[i][j]);
            }
        std::cout << max_err << '\n';
        std::cout << max_nondiag0 << '\n';
        std::cout << max_nondiag1 << '\n';
#endif
    }
}

#undef U
#undef S
#undef V
#undef SVD_DEBUG

template <class T>
inline void svd(char *JOBU, char *JOBVT, int *M, int *N, T *A, int *LDA, T *S,
                T *U, int *LDU, T *VT, int *LDVT, T *WORK, int *LWORK,
                int *INFO) {

    const size_t dim[2] = {static_cast<size_t>(std::max(*N, *M)),
                           static_cast<size_t>(std::min(*N, *M))};

    std::vector<T> Udata(dim[0] * dim[0], 0);
    std::vector<T> Vdata(dim[1] * dim[1], 0);
    std::vector<T> Sdata(dim[0] * dim[1], 0);
    T *U_ = Udata.data();
    T *V_ = Vdata.data();
    T *S_ = Sdata.data();
    //	T* U_ = mem::aligned_new < T > (dim[0] * dim[0]);
    //	memset(U_, 0, dim[0] * dim[0] * sizeof(T));
    //	T* V_ = mem::aligned_new < T > (dim[1] * dim[1]);
    //	memset(V_, 0, dim[1] * dim[1] * sizeof(T));
    //	T* S_ = mem::aligned_new < T > (dim[0] * dim[1]);

    const size_t lda = *LDA;
    const size_t ldu = *LDU;
    const size_t ldv = *LDVT;

    if (dim[1] == static_cast<size_t>(*M)) {
        for (size_t i = 0; i < dim[0]; i++) {
            for (size_t j = 0; j < dim[1]; j++) {
                S_[i * dim[1] + j] = A[i * lda + j];
            }
        }
    } else {
        for (size_t i = 0; i < dim[0]; i++) {
            for (size_t j = 0; j < dim[1]; j++) {
                S_[i * dim[1] + j] = A[j * lda + i];
            }
        }
    }
    for (size_t i = 0; i < dim[0]; i++) {
        U_[i * dim[0] + i] = 1;
    }
    for (size_t i = 0; i < dim[1]; i++) {
        V_[i * dim[1] + i] = 1;
    }

    SVD<T>(dim, U_, S_, V_, (T)-1);

    for (size_t i = 0; i < dim[1]; i++) { // Set S
        S[i] = S_[i * dim[1] + i];
    }
    if (dim[1] == static_cast<size_t>(*M)) { // Set U
        for (size_t i = 0; i < dim[1]; i++)
            for (int j = 0; j < *M; j++) {
                U[j + ldu * i] = V_[j + i * dim[1]] * (S[i] < 0.0 ? -1.0 : 1.0);
            }
    } else {
        for (size_t i = 0; i < dim[1]; i++)
            for (int j = 0; j < *M; j++) {
                U[j + ldu * i] = U_[i + j * dim[0]] * (S[i] < 0.0 ? -1.0 : 1.0);
            }
    }
    if (dim[0] == static_cast<size_t>(*N)) { // Set V
        for (int i = 0; i < *N; i++)
            for (size_t j = 0; j < dim[1]; j++) {
                VT[j + ldv * i] = U_[j + i * dim[0]];
            }
    } else {
        for (int i = 0; i < *N; i++)
            for (size_t j = 0; j < dim[1]; j++) {
                VT[j + ldv * i] = V_[i + j * dim[1]];
            }
    }
    for (size_t i = 0; i < dim[1]; i++) {
        S[i] = S[i] * (S[i] < 0.0 ? -1.0 : 1.0);
    }

    //	mem::aligned_delete < T > (U_);
    //	mem::aligned_delete < T > (S_);
    //	mem::aligned_delete < T > (V_);

    if (0) { // Verify
        const size_t dim[2] = {static_cast<size_t>(std::max(*N, *M)),
                               static_cast<size_t>(std::min(*N, *M))};
        const size_t lda = *LDA;
        const size_t ldu = *LDU;
        const size_t ldv = *LDVT;

        Eigen::MatrixXd A1(*M, *N);
        Eigen::MatrixXd S1(dim[1], dim[1]);
        Eigen::MatrixXd U1(*M, dim[1]);
        Eigen::MatrixXd V1(dim[1], *N);
        for (size_t i = 0; i < *N; i++)
            for (size_t j = 0; j < *M; j++) {
                //				A1[j][i] = A[j + i * lda];
                A1(j, i) = A[j + i * lda];
            }
        S1.setZero();
        for (size_t i = 0; i < dim[1]; i++) { // Set S
                                              //			S1[i][i] = S[i];
            S1(i, i) = S[i];
        }
        for (size_t i = 0; i < dim[1]; i++)
            for (size_t j = 0; j < *M; j++) {
                //				U1[j][i] = U[j + ldu * i];
                U1(j, i) = U[j + ldu * i];
            }
        for (size_t i = 0; i < *N; i++)
            for (size_t j = 0; j < dim[1]; j++) {
                //				V1[j][i] = VT[j + ldv * i];
                V1(j, i) = VT[j + ldv * i];
            }
        std::cout << U1 * S1 * V1 - A1 << '\n';
    }
}

template <class T>
void pinv_pvfmm(T *M, int n1, int n2, T eps, T *M_) {
    if (n1 * n2 == 0)
        return;
    int m = n2;
    int n = n1;
    int k = (m < n ? m : n);

    std::vector<T> Udata(m * k);
    std::vector<T> Sdata(k);
    std::vector<T> VTdata(k * n);

    //	T* tU = mem::aligned_new < T > (m * k);
    //	T* tS = mem::aligned_new < T > (k);
    //	T* tVT = mem::aligned_new < T > (k * n);

    T *tU = Udata.data();
    T *tS = Sdata.data();
    T *tVT = VTdata.data();

    // SVD
    int INFO = 0;
    char JOBU = 'S';
    char JOBVT = 'S';

    // int wssize = max(3*min(m,n)+max(m,n), 5*min(m,n));
    int wssize = 3 * (m < n ? m : n) + (m > n ? m : n);
    int wssize1 = 5 * (m < n ? m : n);
    wssize = (wssize > wssize1 ? wssize : wssize1);

    //	T* wsbuf = mem::aligned_new < T > (wssize);
    std::vector<T> wsbufdata(wssize);
    T *wsbuf = wsbufdata.data();

    svd(&JOBU, &JOBVT, &m, &n, &M[0], &m, &tS[0], &tU[0], &m, &tVT[0], &k,
        wsbuf, &wssize, &INFO);
    if (INFO != 0)
        std::cout << INFO << '\n';
    assert(INFO == 0);
    //	mem::aligned_delete < T > (wsbuf);

    T eps_ = tS[0] * eps;
    for (int i = 0; i < k; i++)
        if (tS[i] < eps_) {
            tS[i] = 0;
        } else {
            tS[i] = 1.0 / tS[i];
            //			std::cout << tS[i] << std::endl;
        }
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < k; j++) {
            tU[i + j * m] *= tS[j];
        }
    }

    gemm<T>('T', 'T', n, m, k, 1.0, &tVT[0], k, &tU[0], m, 0.0, M_, n);
    //	mem::aligned_delete < T > (tU);
    //	mem::aligned_delete < T > (tS);
    //	mem::aligned_delete < T > (tVT);
}

inline void pinv(const Eigen::MatrixXd &Mat, Eigen::MatrixXd &MatPinv) {
    double eps = 1;
    while (eps + 1.0 > 1.0) {
        eps *= 0.5;
    }
    eps = sqrt(eps);

    std::vector<double> M(Mat.cols() * Mat.rows());
    std::vector<double> Mpinv(M.size());

    // row major
    for (int i = 0; i < Mat.rows(); i++) {
        for (int j = 0; j < Mat.cols(); j++) {
            M[i * Mat.cols() + j] = Mat(i, j);
        }
    }

    pinv_pvfmm<double>(M.data(), Mat.rows(), Mat.cols(), eps, Mpinv.data());

    //#define U(i,j) U_[(i)*dim[0]+(j)]
    // row major
    for (int i = 0; i < Mat.cols(); i++) {
        for (int j = 0; j < Mat.rows(); j++) {
            MatPinv(i, j) = Mpinv[i * Mat.rows() + j];
        }
    }
}

inline void pinv(const Eigen::MatrixXd &Mat, Eigen::MatrixXd &MatPinvU,
                 Eigen::MatrixXd &MatPinvVT) {
    // this is the really backward stable SVD used by pvfmm

    double eps = 1;
    while (eps * (double)0.5 + (double)1.0 > 1.0) {
        eps *= 0.5;
    }

    std::vector<double> M(Mat.cols() * Mat.rows());
    std::vector<double> Mpinv(M.size());

    // row major
    for (int i = 0; i < Mat.rows(); i++) {
        for (int j = 0; j < Mat.cols(); j++) {
            M[i * Mat.cols() + j] = Mat(i, j);
        }
    }

    const int n1 = Mat.rows();
    const int n2 = Mat.cols();

    if (n1 * n2 == 0)
        return;
    int m = n2;
    int n = n1;
    int k = (m < n ? m : n);

    std::vector<double> Udata(m * k);
    std::vector<double> Sdata(k);
    std::vector<double> VTdata(k * n);

    //	T* tU = mem::aligned_new < T > (m * k);
    //	T* tS = mem::aligned_new < T > (k);
    //	T* tVT = mem::aligned_new < T > (k * n);

    double *tU = Udata.data();
    double *tS = Sdata.data();
    double *tVT = VTdata.data();

    // SVD
    int INFO = 0;
    char JOBU = 'S';
    char JOBVT = 'S';

    // int wssize = max(3*min(m,n)+max(m,n), 5*min(m,n));
    int wssize = 3 * (m < n ? m : n) + (m > n ? m : n);
    int wssize1 = 5 * (m < n ? m : n);
    wssize = (wssize > wssize1 ? wssize : wssize1);

    //	T* wsbuf = mem::aligned_new < T > (wssize);
    std::vector<double> wsbufdata(wssize);
    double *wsbuf = wsbufdata.data();

    svd(&JOBU, &JOBVT, &m, &n, &M[0], &m, &tS[0], &tU[0], &m, &tVT[0], &k,
        wsbuf, &wssize, &INFO);
    if (INFO != 0) {
        std::cout << INFO << '\n';
    }
    assert(INFO == 0);
    //	mem::aligned_delete < T > (wsbuf);

    double eps_ = tS[0] * eps;

    for (int i = 0; i < k; i++) {
        tS[i] = (tS[i] > eps_ * 4 ? 1.0 / tS[i] : 0.0);
    }

    //	for (int i = 0; i < k; i++)
    //		if (tS[i] < eps_) {
    //			tS[i] = 0;
    //		} else {
    //			tS[i] = 1.0 / tS[i];
    ////			std::cout << tS[i] << std::endl;
    //		}
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < k; j++) {
            tU[i + j * m] *= tS[j];
        }
    }

    MatPinvU.resize(m, k);
    MatPinvVT.resize(n, k);
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < k; j++) {
            MatPinvU(i, j) = tU[i * k + j];
        }
    }

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < k; j++) {
            MatPinvVT(i, j) = tVT[i * k + j];
        }
    }

    //#define U(i,j) U_[(i)*dim[0]+(j)]
    // row major
}

#endif /* M2LLAPLACE_1D3D_SVD_PVFMM_HPP_ */


//  Copyright John Maddock 2007.
//  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)

#include <iostream>

//[policy_eg_2

#include <boost/math/special_functions/gamma.hpp>

int main()
{
   using namespace boost::math::policies;
   errno = 0;
   std::cout << "Result of tgamma(30000) is: "
      << boost::math::tgamma(
         30000,
         make_policy(
            domain_error<errno_on_error>(),
            pole_error<errno_on_error>(),
            overflow_error<errno_on_error>(),
            evaluation_error<errno_on_error>()
         )
      ) << std::endl;
   // Check errno was set:
   std::cout << "errno = " << errno << std::endl;
   // and again with evaluation at a pole:
   std::cout << "Result of tgamma(-10) is: "
      << boost::math::tgamma(
         -10,
         make_policy(
            domain_error<errno_on_error>(),
            pole_error<errno_on_error>(),
            overflow_error<errno_on_error>(),
            evaluation_error<errno_on_error>()
         )
      ) << std::endl;
   // Check errno was set:
   std::cout << "errno = " << errno << std::endl;
}

//]


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

#include <nt2/exponential/functions/realsqrt.hpp>
#include <boost/assert.hpp>
#include <boost/simd/operator/functions/details/assert_utils.hpp>
#include <nt2/include/functions/simd/is_nltz.hpp>
#include <nt2/include/functions/simd/sqrt.hpp>

namespace nt2 { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT  ( realsqrt_, tag::cpu_
                            , (A0)
                            , (generic_< floating_<A0> >)
                            )
  {
    typedef A0 result_type;
    NT2_FUNCTOR_CALL(1)
    {
      BOOST_ASSERT_MSG(boost::simd::assert_all(nt2::is_nltz(a0)),
                       "realsqrt cannot produce complex result.");
      return nt2::sqrt(a0);
    }
  };
} }

#endif


#include <tiny.h>
#include <geometry.h>  // Needed for geometry::Sphere
#include <convex.h>

#define BOOST_AUTO_TEST_MAIN
#include <boost/test/auto_unit_test.hpp>
#include <boost/test/unit_test_suite.hpp>
#include <boost/test/floating_point_comparison.hpp>
#include <boost/test/test_tools.hpp>

#include <cmath>   // for std::sqrt

BOOST_AUTO_TEST_SUITE(conservative_advancement);

BOOST_AUTO_TEST_CASE(case_by_case_test)
{
  typedef tiny::MathTypes<double>                 M;
  typedef M::value_traits                         VT;
  typedef M::vector3_type                         V;
  typedef M::real_type                            T;
  typedef M::quaternion_type                      Q;
  typedef M::coordsys_type                        X;

  // Setup a central impact, two sphere hitting in perfect symmetry, totally independent of their rotationnal motion!
  {
    geometry::Sphere<V> const A;
    geometry::Sphere<V> const B;

    X A_from, A_to;
    X B_from, B_to;

    A_from.T() = V::make(-2.0, 0.0, 0.0);
    A_from.Q() = Q::identity();
    A_to.T()   = V::make(0.0, 0.0, 0.0);
    A_to.Q()   = Q::Ry( 5.0 );
    T r_max_a = A.radius();

    B_from.T() = V::make( 2.0, 0.0, 0.0);
    B_from.Q() = Q::identity();
    B_to.T()   = V::make( 0.0, 0.0, 0.0);
    B_to.Q()   = Q::Rz( 5.0 );
    T r_max_b = B.radius();

    size_t const max_iterations = 100u;
    T      const epsilon        = 0.01;
    T            toi            = 0.0;
    size_t iterations;
    V p_a;
    V p_b;

    bool impact = convex::motion_interpolation<M>(
                                                  A_from
                                                  , A_to
                                                  , &A
                                                  , r_max_a
                                                  , B_from
                                                  , B_to
                                                  , &B
                                                  , r_max_b
                                                  , p_a
                                                  , p_b
                                                  , toi
                                                  , iterations
                                                  , epsilon
                                                  , max_iterations
                                                  );

    BOOST_CHECK( impact );
    BOOST_CHECK_CLOSE( toi, 0.5, 1.0 );

    BOOST_CHECK( fabs( p_a(0) ) < epsilon );
    BOOST_CHECK( fabs( p_a(1) ) < epsilon );
    BOOST_CHECK( fabs( p_a(2) ) < epsilon );
    BOOST_CHECK( fabs( p_b(0) ) < epsilon );
    BOOST_CHECK( fabs( p_b(1) ) < epsilon );
    BOOST_CHECK( fabs( p_b(2) ) < epsilon );
  }

  // Setup separating motion, two spheres moving away from each other
  {
    geometry::Sphere<V> const A;
    geometry::Sphere<V> const B;

    X A_from, A_to;
    X B_from, B_to;

    A_from.T() = V::make(-2.0, 0.0, 0.0);
    A_from.Q() = Q::identity();
    A_to.T()   = V::make(-4.0, 0.0, 0.0);
    A_to.Q()   = Q::Ry( 5.0 );
    T r_max_a = A.radius();

    B_from.T() = V::make( 2.0, 0.0, 0.0);
    B_from.Q() = Q::identity();
    B_to.T()   = V::make( 4.0, 0.0, 0.0);
    B_to.Q()   = Q::Rz( 5.0 );
    T r_max_b = B.radius();

    size_t const max_iterations = 100u;
    T      const epsilon        = 0.01;
    T            toi            = 0.0;
    size_t iterations;
    V p_a;
    V p_b;

    bool impact = convex::motion_interpolation<M>(
                                                  A_from
                                                  , A_to
                                                  , &A
                                                  , r_max_a
                                                  , B_from
                                                  , B_to
                                                  , &B
                                                  , r_max_b
                                                  , p_a
                                                  , p_b
                                                  , toi
                                                  , iterations
                                                  , epsilon
                                                  , max_iterations
                                                  );

    BOOST_CHECK( !impact );
  }

  // Setup two sphere moving close by each other but never impacting
  {
    geometry::Sphere<V> const A;
    geometry::Sphere<V> const B;

    X A_from, A_to;
    X B_from, B_to;

    A_from.T() = V::make(-2.0, 1.01, 0.0);
    A_from.Q() = Q::identity();
    A_to.T()   = V::make(0.0, 1.01, 0.0);
    A_to.Q()   = Q::Ry( 5.0 );
    T r_max_a  = A.radius();

    B_from.T() = V::make( 2.0, -1.01, 0.0);
    B_from.Q() = Q::identity();
    B_to.T()   = V::make( 0.0, -1.01, 0.0);
    B_to.Q()   = Q::Rz( 5.0 );
    T r_max_b  = B.radius();

    size_t const max_iterations = 100u;
    T      const epsilon        = 0.01;
    T            toi            = 0.0;
    size_t iterations;
    V p_a;
    V p_b;

    bool impact = convex::motion_interpolation<M>(
                                                  A_from
                                                  , A_to
                                                  , &A
                                                  , r_max_a
                                                  , B_from
                                                  , B_to
                                                  , &B
                                                  , r_max_b
                                                  , p_a
                                                  , p_b
                                                  , toi
                                                  , iterations
                                                  , epsilon
                                                  , max_iterations
                                                  );

    BOOST_CHECK( !impact );
  }

  // Setup test case where only rotational motion is of importance!
  {
    using std::sqrt;

    convex::Cylinder<M> A;
    geometry::Sphere<V> B;

    A.half_height() = 10.0;
    A.radius()      =  1.0;
    B.radius()      =  1.0;

    X A_from, A_to;
    X B_from, B_to;

    A_from.T() = V::make(0.0, 0.0, 2.0);
    A_from.Q() = Q::identity();
    A_to.T()   = V::make(0.0, 0.0, 2.0);
    A_to.Q()   = Q::Rx( -VT::pi() );
    T r_max_a = sqrt( A.half_height()*A.half_height() + A.radius()*A.radius() );

    B_from.T() = V::make( 0.0, 10.0, 0.0);
    B_from.Q() = Q::identity();
    B_to.T()   = V::make( 0.0, 10.0, 0.0);
    B_to.Q()   = Q::identity();
    T r_max_b  = B.radius();

    size_t const max_iterations = 100u;
    T      const epsilon        = 0.01;
    T            toi            = 0.0;
    size_t iterations;
    V p_a;
    V p_b;

    bool impact = convex::motion_interpolation<M>(
                                                  A_from
                                                  , A_to
                                                  , &A
                                                  , r_max_a
                                                  , B_from
                                                  , B_to
                                                  , &B
                                                  , r_max_b
                                                  , p_a
                                                  , p_b
                                                  , toi
                                                  , iterations
                                                  , epsilon
                                                  , max_iterations
                                                  );

    BOOST_CHECK( impact );
    BOOST_CHECK_CLOSE( toi, 0.5, 1.0 );
    BOOST_CHECK( norm(p_a-p_b) < epsilon );
  }

}

BOOST_AUTO_TEST_SUITE_END();


//---------------------------------------------------------------------------//
//!
//! \file   tstWattDistribution.cpp
//! \author Aaron Tumulak
//! \brief  Watt distribution unit tests.
//!
//---------------------------------------------------------------------------//

// Std Lib Includes
#include <iostream>

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

// FRENSIE Includes
#include "Utility_WattDistribution.hpp"
#include "Utility_PhysicalConstants.hpp"
#include "Utility_RandomNumberGenerator.hpp"
#include "Utility_UnitTraits.hpp"
#include "Utility_QuantityTraits.hpp"
#include "Utility_ElectronVoltUnit.hpp"
#include "Utility_UnitTestHarnessWithMain.hpp"
#include "ArchiveTestHelpers.hpp"

//---------------------------------------------------------------------------//
// Testing Types
//---------------------------------------------------------------------------//

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

typedef TestArchiveHelper::TestArchives TestArchives;

typedef std::tuple<
  std::tuple<si::energy,si::amount,cgs::energy,si::amount>,
  std::tuple<cgs::energy,si::amount,si::energy,si::amount>,
  std::tuple<si::energy,si::length,cgs::energy,cgs::length>,
  std::tuple<cgs::energy,cgs::length,si::energy,si::length>,
  std::tuple<si::energy,si::mass,cgs::energy,cgs::mass>,
  std::tuple<cgs::energy,cgs::mass,si::energy,si::mass>,
  std::tuple<si::energy,si::dimensionless,cgs::energy,cgs::dimensionless>,
  std::tuple<cgs::energy,cgs::dimensionless,si::energy,si::dimensionless>,
  std::tuple<si::energy,void*,cgs::energy,void*>,
  std::tuple<cgs::energy,void*,si::energy,void*>,
  std::tuple<ElectronVolt,si::amount,si::energy,si::amount>,
  std::tuple<ElectronVolt,si::amount,cgs::energy,si::amount>,
  std::tuple<ElectronVolt,si::amount,KiloElectronVolt,si::amount>,
  std::tuple<ElectronVolt,si::amount,MegaElectronVolt,si::amount>,
  std::tuple<KiloElectronVolt,si::amount,si::energy,si::amount>,
  std::tuple<KiloElectronVolt,si::amount,cgs::energy,si::amount>,
  std::tuple<KiloElectronVolt,si::amount,ElectronVolt,si::amount>,
  std::tuple<KiloElectronVolt,si::amount,MegaElectronVolt,si::amount>,
  std::tuple<MegaElectronVolt,si::amount,si::energy,si::amount>,
  std::tuple<MegaElectronVolt,si::amount,cgs::energy,si::amount>,
  std::tuple<MegaElectronVolt,si::amount,ElectronVolt,si::amount>,
  std::tuple<MegaElectronVolt,si::amount,KiloElectronVolt,si::amount>,
  std::tuple<void*,MegaElectronVolt,void*,KiloElectronVolt>
 > TestUnitTypeQuads;

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

std::shared_ptr<Utility::UnivariateDistribution> distribution(
			 new Utility::WattDistribution( 1.0, 1.0, 1.0, 0.1 ) );

std::shared_ptr<Utility::UnitAwareUnivariateDistribution<MegaElectronVolt,si::amount> >
unit_aware_distribution( new Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount>( 1e6*eV, 1e3*keV, 1e-6/eV, 0.1*MeV ) );

//---------------------------------------------------------------------------//
// Tests.
//---------------------------------------------------------------------------//
// Check that the distribution can be evaluated
FRENSIE_UNIT_TEST( WattDistribution, evaluate )
{
  double test_value_1 = 0.0 ;
  double test_value_2 = exp(-0.9)*sinh(sqrt(0.9));

  FRENSIE_CHECK_EQUAL( distribution->evaluate( 0.0 ), test_value_1 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 0.9 ), test_value_2 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 1.0 ), test_value_1 );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be evaluated
FRENSIE_UNIT_TEST( UnitAwareWattDistribution, evaluate )
{
  double scale_factor = exp(-0.9)*sinh(sqrt(0.9));

  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 0.0*MeV ),
		       0.0*si::mole );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 0.9*MeV ),
		       scale_factor*si::mole );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 1.0*MeV ),
                       0.0*si::mole );
}

//---------------------------------------------------------------------------//
// Check that the PDF can be evaluated
FRENSIE_UNIT_TEST( WattDistribution, evaluatePDF )
{
  double test_value_1 = 0.0 ;
  double test_value_2 = exp(-0.9)*sinh(sqrt(0.9))/
    (0.25*sqrt(Utility::PhysicalConstants::pi)*exp(0.25)*
     (erf(sqrt(0.9)-sqrt(0.25))+erf(sqrt(0.9)+sqrt(0.25))) -
     exp(-0.9)*sinh(sqrt(0.9)));

  FRENSIE_CHECK_EQUAL( distribution->evaluatePDF( 0.0 ), test_value_1 );
  FRENSIE_CHECK_EQUAL( distribution->evaluatePDF( 0.9 ), test_value_2 );
  FRENSIE_CHECK_EQUAL( distribution->evaluatePDF( 1.0 ), test_value_1 );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware PDF can be evaluated
FRENSIE_UNIT_TEST( UnitAwareWattDistribution, evaluatePDF )
{
  double scale_factor = exp(-0.9)*sinh(sqrt(0.9))/
    (0.25*sqrt(Utility::PhysicalConstants::pi)*exp(0.25)*
     (erf(sqrt(0.9)-sqrt(0.25))+erf(sqrt(0.9)+sqrt(0.25))) -
     exp(-0.9)*sinh(sqrt(0.9)));

  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluatePDF( 0.0*MeV ),
		       0.0/MeV );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluatePDF( 0.9*MeV ),
                       scale_factor/MeV );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluatePDF( 1.0*MeV ),
		       0.0/MeV );
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled using OpenMC method
FRENSIE_UNIT_TEST( WattDistribution, sample )
{
  std::vector<double> fake_stream( 15 );
  fake_stream[0] = 0.8110855833521807; // Maxwellian Distribution
  fake_stream[1] = 0.9603231091455754; // Sample Accepted. Sample is:
  fake_stream[2] = 0.3888878000049402; // 0.23654793157394
  fake_stream[3] = 0.1790067705163392; // Watt Distribution Sample Accepted. Sample is: 0.17431015530718
  fake_stream[4] = 0.7971990719536760; // Maxwellian Distribution
  fake_stream[5] = 0.0448247918373053; // Sample Rejected. Sample is:
  fake_stream[6] = 0.0375590391821071; // 3.32084968701263
  fake_stream[7] = 0.9522705508082248; // Maxwellian Distribution
  fake_stream[8] = 0.5691586660769004; // Sample Accepted. Sample is:
  fake_stream[9] = 0.0083308067369354; // 0.61240561891848
  fake_stream[10] = 0.6415677450291360; // Watt Distribution Sample Rejected. Sample is: 1.08397711671084
  fake_stream[11] = 0.8589987123720327; // Maxwellian Distribution
  fake_stream[12] = 0.4981431293148418; // Sample Accepted. Sample is:
  fake_stream[13] = 0.5932835342621010; // 0.39976547875177
  fake_stream[14] = 0.6731968005067009; // Watt Distribution Sample Accepted. Sample is: 0.86877979533891

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  double sample = distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.17431015530718, 1e-13 );

  sample = distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.86877979533891, 1e-13 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled using OpenMC method
FRENSIE_UNIT_TEST( UnitAwareWattDistribution, sample )
{
  std::vector<double> fake_stream( 15 );
  fake_stream[0] = 0.8110855833521807; // Maxwellian Distribution
  fake_stream[1] = 0.9603231091455754; // Sample Accepted. Sample is:
  fake_stream[2] = 0.3888878000049402; // 0.23654793157394
  fake_stream[3] = 0.1790067705163392; // Watt Distribution Sample Accepted. Sample is: 0.17431015530718
  fake_stream[4] = 0.7971990719536760; // Maxwellian Distribution
  fake_stream[5] = 0.0448247918373053; // Sample Rejected. Sample is:
  fake_stream[6] = 0.0375590391821071; // 3.32084968701263
  fake_stream[7] = 0.9522705508082248; // Maxwellian Distribution
  fake_stream[8] = 0.5691586660769004; // Sample Accepted. Sample is:
  fake_stream[9] = 0.0083308067369354; // 0.61240561891848
  fake_stream[10] = 0.6415677450291360; // Watt Distribution Sample Rejected. Sample is: 1.08397711671084
  fake_stream[11] = 0.8589987123720327; // Maxwellian Distribution
  fake_stream[12] = 0.4981431293148418; // Sample Accepted. Sample is:
  fake_stream[13] = 0.5932835342621010; // 0.39976547875177
  fake_stream[14] = 0.6731968005067009; // Watt Distribution Sample Accepted. Sample is: 0.86877979533891

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  quantity<MegaElectronVolt> sample = unit_aware_distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.17431015530718*MeV, 1e-13 );

  sample = unit_aware_distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.86877979533891*MeV, 1e-13 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled using OpenMC method, passing in
// parameters
FRENSIE_UNIT_TEST( WattDistribution, sample_pass_parameters )
{
  std::vector<double> fake_stream( 15 );
  fake_stream[0] = 0.6617443503056450; // Maxwellian Distribution
  fake_stream[1] = 0.8510592242616175; // Sample Accepted. Sample is:
  fake_stream[2] = 0.5401745197210969; // 0.09667248521245
  fake_stream[3] = 0.2005969462099806; // Watt Distribution Sample Accepted. Sample is: 0.07927727029875
  fake_stream[4] = 0.5189418543931951; // Maxwellian Distribution
  fake_stream[5] = 0.7484231272861934; // Sample Rejected. Sample is:
  fake_stream[6] = 0.8345865134048199; // 0.13501824430785
  fake_stream[7] = 0.8704061668810904; // Maxwellian Distribution
  fake_stream[8] = 0.5575773053122431; // Sample Accepted. Sample is:
  fake_stream[9] = 0.7452925729418971; // 0.04548390223293
  fake_stream[10] = 0.6556974287666129; // Watt Distribution Sample Rejected. Sample is: 0.05068410532744
  fake_stream[11] = 0.8140855348977614; // Maxwellian Distribution
  fake_stream[12] = 0.5750169688125915; // Sample Accepted. Sample is:
  fake_stream[13] = 0.8372435273691630; // 0.04821527540845
  fake_stream[14] = 0.4721727279140688; // Watt Distribution Sample Accepted. Sample is: 0.04844237604136

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  double incident_energy, a_parameter, b_parameter, restriction_energy, sample;

  incident_energy = 0.5;
  a_parameter = 0.2;
  b_parameter = 0.3;
  restriction_energy = 0.4;

  sample = Utility::WattDistribution::sample( incident_energy, a_parameter, b_parameter, restriction_energy );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.07927727029875, 1e-13 );

  incident_energy = 0.3;
  a_parameter = 0.2;
  b_parameter = 0.1;
  restriction_energy = 0.25;

  sample = Utility::WattDistribution::sample( incident_energy, a_parameter, b_parameter, restriction_energy );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.04844237604136, 1e-13 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled using OpenMC method,
// passing in parameters
FRENSIE_UNIT_TEST( UnitAwareWattDistribution,
		   sample_pass_parameters )
{
  std::vector<double> fake_stream( 15 );
  fake_stream[0] = 0.6617443503056450; // Maxwellian Distribution
  fake_stream[1] = 0.8510592242616175; // Sample Accepted. Sample is:
  fake_stream[2] = 0.5401745197210969; // 0.09667248521245
  fake_stream[3] = 0.2005969462099806; // Watt Distribution Sample Accepted. Sample is: 0.07927727029875
  fake_stream[4] = 0.5189418543931951; // Maxwellian Distribution
  fake_stream[5] = 0.7484231272861934; // Sample Rejected. Sample is:
  fake_stream[6] = 0.8345865134048199; // 0.13501824430785
  fake_stream[7] = 0.8704061668810904; // Maxwellian Distribution
  fake_stream[8] = 0.5575773053122431; // Sample Accepted. Sample is:
  fake_stream[9] = 0.7452925729418971; // 0.04548390223293
  fake_stream[10] = 0.6556974287666129; // Watt Distribution Sample Rejected. Sample is: 0.05068410532744
  fake_stream[11] = 0.8140855348977614; // Maxwellian Distribution
  fake_stream[12] = 0.5750169688125915; // Sample Accepted. Sample is:
  fake_stream[13] = 0.8372435273691630; // 0.04821527540845
  fake_stream[14] = 0.4721727279140688; // Watt Distribution Sample Accepted. Sample is: 0.04844237604136

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  Utility::UnitAwareWattDistribution<MegaElectronVolt>::IndepQuantity incident_energy, a_parameter, restriction_energy, sample;

  Utility::UnitAwareWattDistribution<MegaElectronVolt>::InverseIndepQuantity b_parameter;

  incident_energy = 0.5*MeV;
  a_parameter = 0.2*MeV;
  b_parameter = 0.3/MeV;
  restriction_energy = 0.4*MeV;

  sample = Utility::UnitAwareWattDistribution<MegaElectronVolt>::sample( incident_energy, a_parameter, b_parameter, restriction_energy );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.07927727029875*MeV, 1e-13 );

  incident_energy = 0.3*MeV;
  a_parameter = 0.2*MeV;
  b_parameter = 0.1/MeV;
  restriction_energy = 0.25*MeV;

  sample = Utility::UnitAwareWattDistribution<MegaElectronVolt>::sample( incident_energy, a_parameter, b_parameter, restriction_energy );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.04844237604136*MeV, 1e-13 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled using OpenMC method, passing in
// parameters
FRENSIE_UNIT_TEST( WattDistribution, sampleAndRecordTrials_pass_parameters )
{
  std::vector<double> fake_stream( 15 );
  fake_stream[0] = 0.6617443503056450; // Maxwellian Distribution
  fake_stream[1] = 0.8510592242616175; // Sample Accepted. Sample is:
  fake_stream[2] = 0.5401745197210969; // 0.09667248521245
  fake_stream[3] = 0.2005969462099806; // Watt Distribution Sample Accepted. Sample is: 0.07927727029875
  fake_stream[4] = 0.5189418543931951; // Maxwellian Distribution
  fake_stream[5] = 0.7484231272861934; // Sample Rejected. Sample is:
  fake_stream[6] = 0.8345865134048199; // 0.13501824430785
  fake_stream[7] = 0.8704061668810904; // Maxwellian Distribution
  fake_stream[8] = 0.5575773053122431; // Sample Accepted. Sample is:
  fake_stream[9] = 0.7452925729418971; // 0.04548390223293
  fake_stream[10] = 0.6556974287666129; // Watt Distribution Sample Rejected. Sample is: 0.05068410532744
  fake_stream[11] = 0.8140855348977614; // Maxwellian Distribution
  fake_stream[12] = 0.5750169688125915; // Sample Accepted. Sample is:
  fake_stream[13] = 0.8372435273691630; // 0.04821527540845
  fake_stream[14] = 0.4721727279140688; // Watt Distribution Sample Accepted. Sample is: 0.04844237604136

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  double incident_energy, a_parameter, b_parameter, restriction_energy, sample;
  Utility::DistributionTraits::Counter trials = 0;

  incident_energy = 0.5;
  a_parameter = 0.2;
  b_parameter = 0.3;
  restriction_energy = 0.4;

  sample = Utility::WattDistribution::sampleAndRecordTrials(incident_energy, a_parameter, b_parameter, restriction_energy, trials);
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.07927727029875, 1e-13 );
  FRENSIE_CHECK_EQUAL( trials, 1.0 );

  incident_energy = 0.3;
  a_parameter = 0.2;
  b_parameter = 0.1;
  restriction_energy = 0.25;

  sample = Utility::WattDistribution::sampleAndRecordTrials(incident_energy, a_parameter, b_parameter, restriction_energy, trials);
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.04844237604136, 1e-13 );
  FRENSIE_CHECK_EQUAL( trials, 4.0 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled using OpenMC method,
// passing in parameters
FRENSIE_UNIT_TEST( UnitAwareWattDistribution,
		   sampleAndRecordTrials_pass_parameters )
{
  std::vector<double> fake_stream( 15 );
  fake_stream[0] = 0.6617443503056450; // Maxwellian Distribution
  fake_stream[1] = 0.8510592242616175; // Sample Accepted. Sample is:
  fake_stream[2] = 0.5401745197210969; // 0.09667248521245
  fake_stream[3] = 0.2005969462099806; // Watt Distribution Sample Accepted. Sample is: 0.07927727029875
  fake_stream[4] = 0.5189418543931951; // Maxwellian Distribution
  fake_stream[5] = 0.7484231272861934; // Sample Rejected. Sample is:
  fake_stream[6] = 0.8345865134048199; // 0.13501824430785
  fake_stream[7] = 0.8704061668810904; // Maxwellian Distribution
  fake_stream[8] = 0.5575773053122431; // Sample Accepted. Sample is:
  fake_stream[9] = 0.7452925729418971; // 0.04548390223293
  fake_stream[10] = 0.6556974287666129; // Watt Distribution Sample Rejected. Sample is: 0.05068410532744
  fake_stream[11] = 0.8140855348977614; // Maxwellian Distribution
  fake_stream[12] = 0.5750169688125915; // Sample Accepted. Sample is:
  fake_stream[13] = 0.8372435273691630; // 0.04821527540845
  fake_stream[14] = 0.4721727279140688; // Watt Distribution Sample Accepted. Sample is: 0.04844237604136

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  Utility::UnitAwareUnivariateDistribution<MegaElectronVolt,si::amount>::IndepQuantity incident_energy, a_parameter, restriction_energy, sample;

  Utility::UnitAwareUnivariateDistribution<MegaElectronVolt,si::amount>::InverseIndepQuantity b_parameter;

  Utility::DistributionTraits::Counter trials = 0;

  incident_energy = 0.5*MeV;
  a_parameter = 0.2*MeV;
  b_parameter = 0.3/MeV;
  restriction_energy = 0.4*MeV;

  sample = Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount>::sampleAndRecordTrials(incident_energy, a_parameter, b_parameter, restriction_energy, trials);
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.07927727029875*MeV, 1e-13 );
  FRENSIE_CHECK_EQUAL( trials, 1.0 );

  incident_energy = 0.3*MeV;
  a_parameter = 0.2*MeV;
  b_parameter = 0.1/MeV;
  restriction_energy = 0.25*MeV;

  sample = Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount>::sampleAndRecordTrials(incident_energy, a_parameter, b_parameter, restriction_energy, trials);
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.04844237604136*MeV, 1e-13 );
  FRENSIE_CHECK_EQUAL( trials, 4.0 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the upper bound of the distribution independent variable can be
// returned
FRENSIE_UNIT_TEST( WattDistribution, getUpperBoundOfIndepVar )
{
  FRENSIE_CHECK_EQUAL( distribution->getUpperBoundOfIndepVar(), 0.9 );
}

//---------------------------------------------------------------------------//
// Check that the upper bound of the unit-aware distribution independent
// variable can be returned
FRENSIE_UNIT_TEST( UnitAwareWattDistribution, getUpperBoundOfIndepVar )
{
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->getUpperBoundOfIndepVar(),
		       0.9*MeV );
}

//---------------------------------------------------------------------------//
// Check that the lower bound of the distribution independent variable can be
// returned
FRENSIE_UNIT_TEST( WattDistribution, getLowerBoundOfIndepVar )
{
  FRENSIE_CHECK_EQUAL( distribution->getLowerBoundOfIndepVar(), 0.0 );
}

//---------------------------------------------------------------------------//
// Check that the lower bound of the unit-aware distribution independent
// variable can be returned
FRENSIE_UNIT_TEST( UnitAwareWattDistribution, getLowerBoundOfIndepVar )
{
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->getLowerBoundOfIndepVar(),
		       0.0*MeV );
}

//---------------------------------------------------------------------------//
// Check that the distribution type can be returned
FRENSIE_UNIT_TEST( WattDistribution, getDistributionType )
{
  FRENSIE_CHECK_EQUAL( distribution->getDistributionType(),
		       Utility::WATT_DISTRIBUTION );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution type can be returned
FRENSIE_UNIT_TEST( UnitAwareWattDistribution, getDistributionType )
{
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->getDistributionType(),
		       Utility::WATT_DISTRIBUTION );
}

//---------------------------------------------------------------------------//
// Check if the distribution is tabular
FRENSIE_UNIT_TEST( WattDistribution, isTabular )
{
  FRENSIE_CHECK( !distribution->isTabular() );
}

//---------------------------------------------------------------------------//
// Check if the unit-aware distribution is tabular
FRENSIE_UNIT_TEST( UnitAwareWattDistribution, isTabular )
{
  FRENSIE_CHECK( !unit_aware_distribution->isTabular() );
}

//---------------------------------------------------------------------------//
// Check if the distribution is continuous
FRENSIE_UNIT_TEST( WattDistribution, isContinuous )
{
  FRENSIE_CHECK( distribution->isContinuous() );
}

//---------------------------------------------------------------------------//
// Check if the unit-aware distribution is continuous
FRENSIE_UNIT_TEST( UnitAwareWattDistribution, isContinuous )
{
  FRENSIE_CHECK( unit_aware_distribution->isContinuous() );
}

//---------------------------------------------------------------------------//
// Check if the distribution is compatible with the interpolation type
FRENSIE_UNIT_TEST( WattDistribution, isCompatibleWithInterpType )
{
  FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LinLin>() );
  FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LinLog>() );
  FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LogLin>() );
  FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LogLog>() );
}

//---------------------------------------------------------------------------//
// Check if the unit-aware distribution is compatible with the interp type
FRENSIE_UNIT_TEST( UnitAwareWattDistribution, isCompatibleWithInterpType )
{
  FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLin>() );
  FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLog>() );
  FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLin>() );
  FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLog>() );
}

//---------------------------------------------------------------------------//
// Check that a distribution can be placed in a stream
FRENSIE_UNIT_TEST( WattDistribution, ostream_operator )
{
  std::ostringstream oss;

  oss << Utility::WattDistribution();

  Utility::VariantMap dist_data =
    Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Watt Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toDouble(), 1.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["a parameter"].toDouble(), 1.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["b parameter"].toDouble(), 1.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toDouble(), 0.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 1.0, SHOW_LHS );

  oss.str( "" );
  oss.clear();

  oss << Utility::WattDistribution( 3.0 );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Watt Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toDouble(), 3.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["a parameter"].toDouble(), 1.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["b parameter"].toDouble(), 1.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toDouble(), 0.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 1.0, SHOW_LHS );

  oss.str( "" );
  oss.clear();

  oss << Utility::WattDistribution( 5.0, 4.0 );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Watt Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toDouble(), 5.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["a parameter"].toDouble(), 4.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["b parameter"].toDouble(), 1.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toDouble(), 0.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 1.0, SHOW_LHS );

  oss.str( "" );
  oss.clear();

  oss << Utility::WattDistribution( 5.0, 4.0, 3.0 );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Watt Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toDouble(), 5.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["a parameter"].toDouble(), 4.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["b parameter"].toDouble(), 3.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toDouble(), 0.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 1.0, SHOW_LHS );

  oss.str( "" );
  oss.clear();

  oss << Utility::WattDistribution( 5.0, 4.0, 3.0, 2.0 );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Watt Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toDouble(), 5.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["a parameter"].toDouble(), 4.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["b parameter"].toDouble(), 3.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toDouble(), 2.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 1.0, SHOW_LHS );

  oss.str( "" );
  oss.clear();

  oss << Utility::WattDistribution( 5.0, 4.0, 3.0, 2.0, 0.5 );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Watt Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toDouble(), 5.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["a parameter"].toDouble(), 4.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["b parameter"].toDouble(), 3.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toDouble(), 2.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 0.5, SHOW_LHS );

  oss.str( "" );
  oss.clear();

  std::shared_ptr<Utility::WattDistribution> shared_dist( new Utility::WattDistribution( 5.0, 4.0, 3.0, 2.0, 0.5 ) );

  oss << *shared_dist;

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Watt Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toDouble(), 5.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["a parameter"].toDouble(), 4.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["b parameter"].toDouble(), 3.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toDouble(), 2.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 0.5, SHOW_LHS );
}

//---------------------------------------------------------------------------//
// Check that a unit-aware distribution can be placed in a stream
FRENSIE_UNIT_TEST( UnitAwareWattDistribution, ostream_operator )
{
  std::ostringstream oss;

  oss << Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount>();

  Utility::VariantMap dist_data =
    Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Watt Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<MegaElectronVolt>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toType<quantity<MegaElectronVolt> >(),
                       1.0*MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["a parameter"].toType<quantity<MegaElectronVolt> >(),
                       1.0*MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["b parameter"].toType<quantity<Utility::UnitTraits<MegaElectronVolt>::InverseUnit> >(),
                       1.0/MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toType<quantity<MegaElectronVolt> >(),
                       0.0*MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 1.0, SHOW_LHS );

  oss.str( "" );
  oss.clear();

  oss << Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount>( 3.0*MeV );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Watt Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<MegaElectronVolt>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toType<quantity<MegaElectronVolt> >(),
                       3.0*MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["a parameter"].toType<quantity<MegaElectronVolt> >(),
                       1.0*MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["b parameter"].toType<quantity<Utility::UnitTraits<MegaElectronVolt>::InverseUnit> >(),
                       1.0/MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toType<quantity<MegaElectronVolt> >(),
                       0.0*MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 1.0, SHOW_LHS );

  oss.str( "" );
  oss.clear();

  oss << Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount>( 5.0*MeV, 4.0*MeV );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Watt Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<MegaElectronVolt>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toType<quantity<MegaElectronVolt> >(),
                       5.0*MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["a parameter"].toType<quantity<MegaElectronVolt> >(),
                       4.0*MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["b parameter"].toType<quantity<Utility::UnitTraits<MegaElectronVolt>::InverseUnit> >(),
                       1.0/MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toType<quantity<MegaElectronVolt> >(),
                       0.0*MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 1.0, SHOW_LHS );

  oss.str( "" );
  oss.clear();

  oss << Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount>( 5.0*MeV, 4.0*MeV, 3.0/MeV );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Watt Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<MegaElectronVolt>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toType<quantity<MegaElectronVolt> >(),
                       5.0*MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["a parameter"].toType<quantity<MegaElectronVolt> >(),
                       4.0*MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["b parameter"].toType<quantity<Utility::UnitTraits<MegaElectronVolt>::InverseUnit> >(),
                       3.0/MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toType<quantity<MegaElectronVolt> >(),
                       0.0*MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 1.0, SHOW_LHS );

  oss.str( "" );
  oss.clear();

  oss << Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount>( 5.0*MeV, 4.0*MeV, 3.0/MeV, 2.0*MeV );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Watt Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<MegaElectronVolt>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toType<quantity<MegaElectronVolt> >(),
                       5.0*MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["a parameter"].toType<quantity<MegaElectronVolt> >(),
                       4.0*MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["b parameter"].toType<quantity<Utility::UnitTraits<MegaElectronVolt>::InverseUnit> >(),
                       3.0/MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toType<quantity<MegaElectronVolt> >(),
                       2.0*MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 1.0, SHOW_LHS );

  oss.str( "" );
  oss.clear();

  oss << Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount>( 5.0*MeV, 4.0*MeV, 3.0/MeV, 2.0*MeV, 0.5 );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Watt Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<MegaElectronVolt>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toType<quantity<MegaElectronVolt> >(),
                       5.0*MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["a parameter"].toType<quantity<MegaElectronVolt> >(),
                       4.0*MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["b parameter"].toType<quantity<Utility::UnitTraits<MegaElectronVolt>::InverseUnit> >(),
                       3.0/MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toType<quantity<MegaElectronVolt> >(),
                       2.0*MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 0.5, SHOW_LHS );

  oss.str( "" );
  oss.clear();

  std::shared_ptr<Utility::UnitAwareUnivariateDistribution<MegaElectronVolt,si::amount>> shared_dist( new Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount>( 5.0*MeV, 4.0*MeV, 3.0/MeV, 2.0*MeV, 0.5 ) );

  oss << *shared_dist;

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Watt Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<MegaElectronVolt>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["incident energy"].toType<quantity<MegaElectronVolt> >(),
                       5.0*MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["a parameter"].toType<quantity<MegaElectronVolt> >(),
                       4.0*MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["b parameter"].toType<quantity<Utility::UnitTraits<MegaElectronVolt>::InverseUnit> >(),
                       3.0/MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["restriction energy"].toType<quantity<MegaElectronVolt> >(),
                       2.0*MeV,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 0.5, SHOW_LHS );
}

//---------------------------------------------------------------------------//
// Check that a distribution can be archived
FRENSIE_UNIT_TEST_TEMPLATE_EXPAND( WattDistribution, archive, TestArchives )
{
  FETCH_TEMPLATE_PARAM( 0, RawOArchive );
  FETCH_TEMPLATE_PARAM( 1, RawIArchive );

  typedef typename std::remove_pointer<RawOArchive>::type OArchive;
  typedef typename std::remove_pointer<RawIArchive>::type IArchive;

  std::string archive_base_name( "test_watt_dist" );
  std::ostringstream archive_ostream;

  // Create and archive some watt distributions
  {
    std::unique_ptr<OArchive> oarchive;

    createOArchive( archive_base_name, archive_ostream, oarchive );

    Utility::WattDistribution dist_a;
    Utility::WattDistribution dist_b( 5.0 );
    Utility::WattDistribution dist_c( 5.0, 4.0 );
    Utility::WattDistribution dist_d( 5.0, 4.0, 3.0 );
    Utility::WattDistribution dist_e( 5.0, 4.0, 3.0, 2.0 );
    Utility::WattDistribution dist_f( 5.0, 4.0, 3.0, 2.0, 0.5 );

    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_a )
                             );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_b )
                             );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_c )
                             );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_d )
                             );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_e )
                             );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_f )
                             );
    FRENSIE_REQUIRE_NO_THROW(
                         (*oarchive) << BOOST_SERIALIZATION_NVP( distribution )
                             );
  }

  // Copy the archive ostream to an istream
  std::istringstream archive_istream( archive_ostream.str() );

  // Load the archived distributions
  std::unique_ptr<IArchive> iarchive;

  createIArchive( archive_istream, iarchive );

  Utility::WattDistribution dist_a;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_a ) );
  FRENSIE_CHECK_EQUAL( dist_a, Utility::WattDistribution() );

  Utility::WattDistribution dist_b;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_b ) );
  FRENSIE_CHECK_EQUAL( dist_b, Utility::WattDistribution( 5.0 ) );

  Utility::WattDistribution dist_c;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_c ) );
  FRENSIE_CHECK_EQUAL( dist_c, Utility::WattDistribution( 5.0, 4.0 ) );

  Utility::WattDistribution dist_d;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_d ) );
  FRENSIE_CHECK_EQUAL( dist_d, Utility::WattDistribution( 5.0, 4.0, 3.0 ) );

  Utility::WattDistribution dist_e;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_e ) );
  FRENSIE_CHECK_EQUAL( dist_e, Utility::WattDistribution( 5.0, 4.0, 3.0, 2.0 ) );

  Utility::WattDistribution dist_f;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_f ) );
  FRENSIE_CHECK_EQUAL( dist_f, Utility::WattDistribution( 5.0, 4.0, 3.0, 2.0, 0.5 ) );

  std::shared_ptr<Utility::UnivariateDistribution> shared_dist;

  FRENSIE_REQUIRE_NO_THROW( (*iarchive) >> boost::serialization::make_nvp( "distribution", shared_dist ) );
  FRENSIE_CHECK_EQUAL( *dynamic_cast<Utility::WattDistribution*>( shared_dist.get() ),
                       *dynamic_cast<Utility::WattDistribution*>( distribution.get() ) );
}

//---------------------------------------------------------------------------//
// Check that a unit-aware distribution can be archived
FRENSIE_UNIT_TEST_TEMPLATE_EXPAND( UnitAwareWattDistribution,
                                   archive,
                                   TestArchives )
{
  FETCH_TEMPLATE_PARAM( 0, RawOArchive );
  FETCH_TEMPLATE_PARAM( 1, RawIArchive );

  typedef typename std::remove_pointer<RawOArchive>::type OArchive;
  typedef typename std::remove_pointer<RawIArchive>::type IArchive;

  std::string archive_base_name( "test_watt_dist" );
  std::ostringstream archive_ostream;

  // Create and archive some watt distributions
  {
    std::unique_ptr<OArchive> oarchive;

    createOArchive( archive_base_name, archive_ostream, oarchive );

    Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount> dist_a;
    Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount> dist_b( 5.0*MeV );
    Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount> dist_c( 5.0*MeV, 4.0*MeV );
    Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount> dist_d( 5.0*MeV, 4.0*MeV, 3.0/MeV );
    Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount> dist_e( 5.0*MeV, 4.0*MeV, 3.0/MeV, 2.0*MeV );
    Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount> dist_f( 5.0*MeV, 4.0*MeV, 3.0/MeV, 2.0*MeV, 0.5 );

    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_a )
                             );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_b )
                             );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_c )
                             );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_d )
                             );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_e )
                             );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_f )
                             );
    FRENSIE_REQUIRE_NO_THROW(
              (*oarchive) << BOOST_SERIALIZATION_NVP( unit_aware_distribution )
                             );
  }

  // Copy the archive ostream to an istream
  std::istringstream archive_istream( archive_ostream.str() );

  // Load the archived distributions
  std::unique_ptr<IArchive> iarchive;

  createIArchive( archive_istream, iarchive );

  Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount> dist_a;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_a ) );
  FRENSIE_CHECK_EQUAL( dist_a, (Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount>()) );

  Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount> dist_b;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_b ) );
  FRENSIE_CHECK_EQUAL( dist_b, (Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount>( 5.0*MeV )) );

  Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount> dist_c;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_c ) );
  FRENSIE_CHECK_EQUAL( dist_c, (Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount>( 5.0*MeV, 4.0*MeV )) );

  Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount> dist_d;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_d ) );
  FRENSIE_CHECK_EQUAL( dist_d, (Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount>( 5.0*MeV, 4.0*MeV, 3.0/MeV )) );

  Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount> dist_e;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_e ) );
  FRENSIE_CHECK_EQUAL( dist_e, (Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount>( 5.0*MeV, 4.0*MeV, 3.0/MeV, 2.0*MeV )) );

  Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount> dist_f;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_f ) );
  FRENSIE_CHECK_EQUAL( dist_f, (Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount>( 5.0*MeV, 4.0*MeV, 3.0/MeV, 2.0*MeV, 0.5 )) );

  std::shared_ptr<Utility::UnitAwareUnivariateDistribution<MegaElectronVolt,si::amount> > shared_dist;

  FRENSIE_REQUIRE_NO_THROW( (*iarchive) >> boost::serialization::make_nvp( "unit_aware_distribution", shared_dist ) );
  FRENSIE_CHECK_EQUAL( (*dynamic_cast<Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount>*>( shared_dist.get() )),
                       (*dynamic_cast<Utility::UnitAwareWattDistribution<MegaElectronVolt,si::amount>*>( unit_aware_distribution.get() )) );
}

//---------------------------------------------------------------------------//
// Check that distributions can be scaled
FRENSIE_UNIT_TEST_TEMPLATE_EXPAND( UnitAwareWattDistribution,
				   explicit_conversion,
                                   TestUnitTypeQuads )
{
  FETCH_TEMPLATE_PARAM( 0, RawIndepUnitA );
  FETCH_TEMPLATE_PARAM( 1, RawDepUnitA );
  FETCH_TEMPLATE_PARAM( 2, RawIndepUnitB );
  FETCH_TEMPLATE_PARAM( 3, RawDepUnitB );

  typedef typename std::remove_pointer<RawIndepUnitA>::type IndepUnitA;
  typedef typename std::remove_pointer<RawDepUnitA>::type DepUnitA;
  typedef typename std::remove_pointer<RawIndepUnitB>::type IndepUnitB;
  typedef typename std::remove_pointer<RawDepUnitB>::type DepUnitB;

  typedef typename Utility::UnitTraits<IndepUnitA>::template GetQuantityType<double>::type IndepQuantityA;
  typedef typename Utility::UnitTraits<typename Utility::UnitTraits<IndepUnitA>::InverseUnit>::template GetQuantityType<double>::type InverseIndepQuantityA;

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

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

  // Copy from unitless distribution to distribution type A (static method)
  Utility::UnitAwareWattDistribution<IndepUnitA,DepUnitA>
    unit_aware_dist_a_copy = Utility::UnitAwareWattDistribution<IndepUnitA,DepUnitA>::fromUnitlessDistribution( *dynamic_cast<Utility::WattDistribution*>( distribution.get() ) );

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

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

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

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

  Utility::setQuantity( indep_quantity_a, 0.89999999999999 );
  Utility::setQuantity( inv_indep_quantity_a,
                        exp(-0.9)*sinh(sqrt(0.9))/
                        (0.25*sqrt(Utility::PhysicalConstants::pi)*exp(0.25)*
                         (erf(sqrt(0.9)-sqrt(0.25))+erf(sqrt(0.9)+sqrt(0.25)))-
                         exp(-0.9)*sinh(sqrt(0.9))) );
  Utility::setQuantity( dep_quantity_a,
			exp(-0.9)*sinh(sqrt(0.9)) );

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

  FRENSIE_CHECK_FLOATING_EQUALITY(
			   unit_aware_dist_a_copy.evaluate( indep_quantity_a ),
			   dep_quantity_a,
			   1e-14 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
			unit_aware_dist_a_copy.evaluatePDF( indep_quantity_a ),
			inv_indep_quantity_a,
			1e-14 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
			   unit_aware_dist_b_copy.evaluate( indep_quantity_b ),
			   dep_quantity_b,
			   1e-14 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
			unit_aware_dist_b_copy.evaluatePDF( indep_quantity_b ),
			inv_indep_quantity_b,
			1e-14 );
}

//---------------------------------------------------------------------------//
// Custom setup
//---------------------------------------------------------------------------//
FRENSIE_CUSTOM_UNIT_TEST_SETUP_BEGIN();

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

FRENSIE_CUSTOM_UNIT_TEST_SETUP_END();

//---------------------------------------------------------------------------//
// end tstWattDistribution.cpp
//---------------------------------------------------------------------------//


#include <iostream>
#include <fstream>
#include <iostream>
#include <sstream>
#include <istream>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <vector>
#include <map>
#include <cmath>
#include <algorithm>
#include <numeric>
#include <boost/range/adaptor/indexed.hpp>
#include <pthread.h>

using namespace std;

vector<vector<int>> examples;
vector<vector<int>> centroids;
pthread_barrier_t barrier;
pthread_mutex_t lock;
int *iteration, num_threads;
bool finished = false;

int closest_centroid(vector<int> example)
{
    map<string, int> closest = {{"id", -1}, {"distance", INT_MAX}};
    for (auto centroid : centroids | boost::adaptors::indexed(1))
    {
        long long int distance = 0;
        for (auto axis : example | boost::adaptors::indexed(1))
        {
            if (axis.index() - 1 == centroid.value().size())
                break;
            int centroid_point = centroid.value().at(axis.index() - 1);
            int axis_value = axis.value();
            int sub = centroid_point - axis_value;
            distance += pow(sub, 2);
        }
        distance = sqrt(distance);
        if (distance < closest.at("distance"))
        {
            closest.at("id") = centroid.index() - 1;
            closest.at("distance") = distance;
        }
    }
    return closest.at("id");
}

vector<int> recalculate(int centroid_id, vector<int> centroid)
{
    int total = 0;
    vector<int> new_centroid(examples.at(0).size() - 1, 0);
    for (auto example : examples)
        if (example.at(example.size() - 1) == centroid_id)
        {
            for (auto example_axis : example | boost::adaptors::indexed(1))
            {
                if (example_axis.index() - 1 == new_centroid.size())
                    break;
                new_centroid.at(example_axis.index() - 1) += example_axis.value();
            }
            total++;
        }

    if (total)
        for (auto centroid_axis : new_centroid | boost::adaptors::indexed(1))
        {
            centroid_axis.value() /= total;
            if (centroid_axis.value() != centroid.at(centroid_axis.index() - 1))
            {
                finished = false;
            }
        }
    return total ? new_centroid : centroid;
}

void *k_means(void *t_id)
{
    int *id = (int *)t_id;
    int exampleStripe = (int)ceil(examples.size() / ((float)num_threads));
    int exampleBegin = min(*id * exampleStripe, (int)examples.size());
    int exampleEnd = min(exampleBegin + exampleStripe, (int)examples.size());

    int centroidStripe = (int)ceil(centroids.size() / ((float)num_threads));
    int centroidBegin = min(*id * centroidStripe, (int)centroids.size());
    int centroidEnd = min(centroidBegin + centroidStripe, (int)centroids.size());

    // pthread_mutex_lock(&lock);
    // cout << "id: " << (*id) << " begin: " << exampleBegin << " end: " << exampleEnd << " stripe: " << exampleStripe << endl;
    // cout << "id: " << (*id) << " begin: " << centroidBegin << " end: " << centroidEnd << " stripe: " << centroidStripe << endl << endl;
    // pthread_mutex_unlock(&lock);
    // while (true);

    while (!finished)
    {
        for (int example = exampleBegin; example < exampleEnd; example++)
            examples.at(example).at((examples.at(example).size() - 1)) = closest_centroid(examples.at(example));
        pthread_barrier_wait(&barrier);
        finished = true;
        for (int centroid = centroidBegin; centroid < centroidEnd; centroid++)
            centroids.at(centroid) = recalculate(centroid, centroids.at(centroid));
        pthread_barrier_wait(&barrier);
        iteration[*id] += 1;
    }
    // pthread_mutex_lock(&lock);
    // cout << "id " << *id << " iteration " << iteration[*id] << endl;
    // pthread_mutex_unlock(&lock);
    // pthread_barrier_wait(&barrier);
}

int main(int argc, char *argv[])
{
    string extracted, comma, basename(argv[1]);
    ifstream base, centroid;
    num_threads = atoi(argv[2]);
    iteration = (int *)malloc(sizeof(int) * num_threads);
    int ids[num_threads];
    pthread_t threads[num_threads];
    pthread_mutex_init(&lock, NULL);
    pthread_barrier_init(&barrier, NULL, num_threads);
    vector<int> temp;

    base.open("bases/int_base_" + basename + ".data");
    while (!base.eof())
    {
        getline(base, extracted);
        stringstream is(extracted);
        if (extracted.size() > 0)
        {
            while (getline(is, comma, ','))
                temp.push_back(stoi(comma));
            temp.push_back(-1);
            examples.push_back(temp);
            temp.clear();
        }
    }
    centroid.open("bases/int_centroid_" + basename + "_20.data");
    while (!centroid.eof())
    {
        getline(centroid, extracted);
        istringstream is(extracted);
        if (extracted.size() > 0)
        {
            while (getline(is, comma, ','))
                temp.push_back(stoi(comma));
            centroids.push_back(temp);
            temp.clear();
        }
    }
    for (int i = 0; i < num_threads; i++)
    {
        ids[i] = i;
        iteration[i] = 0;
        pthread_create(&threads[i], NULL, k_means, (void *)(&ids[i]));
    }
    for (int i = 0; i < num_threads; i++)
        pthread_join(threads[i], NULL);

    ofstream myfile("results/my_saida_" + basename + "_par_" + argv[2]);
    myfile << "numero de iteracoes:" << iteration[0];
    for (auto value : examples | boost::adaptors::indexed(1))
        myfile << "id=" << value.index() - 1 << ", classe=" << value.value().at(value.value().size() - 1) << endl;
    myfile.close();
    pthread_barrier_destroy(&barrier);
    return 0;
}


#include <unistd.h>

#include <laser-manager/Tokenizer.h>
#include <vector>
#include <queue>
#include <boost/unordered_map.hpp>
#include <util/functional.h>
#include <laser-manager/clusteringmanager/clustering/PCARunner.h>

using namespace sf1r;
using namespace sf1r::laser;

const std::size_t N_SIM = 100;

typedef izenelib::util::second_greater<std::pair<int, float> > greater_than;
typedef std::priority_queue<std::pair<int, float>, std::vector<std::pair<int, float> >, greater_than> pri_queue;

typedef std::vector<float> TokenVector;

float similarity(const TokenVector& lv, const TokenVector& rv)
{
    if (lv.size() != rv.size())
    {
        return -1;
    }
    float sim = 0.0;
    float lsum = 0.0;
    float rsum = 0.0;
    for (std::size_t i = 0; i < lv.size(); ++i)
    {
        sim += lv[i] * rv[i];
        lsum += lv[i];
        rsum += rv[i];
    }
    return sim / lsum/ rsum;
}

int main(int argc, char * argv[])
{
    if (argc != 4)
    {
        return -1;
    }

    const std::string clustering = argv[1];
    const std::string termDict = argv[2];
    const std::string similarFilename = argv[3];

    std::vector<boost::unordered_map<std::string, float> > temp;
    laser::clustering::loadClusteringResult(temp, clustering);

    Tokenizer* tokenizer = new Tokenizer(termDict);

    std::vector<TokenVector>* clusteringContainer = new std::vector<TokenVector>(temp.size());
    for (std::size_t i = 0; i < temp.size(); ++i)
    {
        TokenVector vec;
        tokenizer->numeric(temp[i], vec);
        (*clusteringContainer)[i] = vec;
    }

    std::vector<std::vector<int> >* similarClustering  = new std::vector<std::vector<int> >(clusteringContainer->size());
    for (std::size_t i = 0; i < clusteringContainer->size(); ++i)
    {
        pri_queue queue;
        for (std::size_t j = 0; j < clusteringContainer->size(); j++)
        {
            if (i == j)
            {
                continue;
            }
            float sim = similarity((*clusteringContainer)[i], (*clusteringContainer)[j]);
            if(queue.size() >= N_SIM)
            {
                if(sim > queue.top().second)
                {
                //std::cout<<"pop "<<queue.top().second<<"\n";
                    queue.pop();
                    queue.push(std::make_pair(j, sim));
                //std::cout<<"push "<<sim<<"\n";
                }
            }
            else
            {
                //std::cout<<"push "<<sim<<"\n";
                queue.push(std::make_pair(j, sim));
            }
        }

        std::vector<int> similarVector;
        while (!queue.empty())
        {
            similarVector.push_back(queue.top().first);
            queue.pop();
        }
        if (i % 11 == 0)
        {
            std::cout<<"finished "<< (float) i / clusteringContainer->size() * 100<<"\n";;
        }
        (*similarClustering)[i] = similarVector;
    }

    // flush
    std::ofstream ofs(similarFilename.c_str(), std::ofstream::binary | std::ofstream::trunc);
    boost::archive::text_oarchive oa(ofs);
    try
    {
        oa << *similarClustering;
    }
    catch(std::exception& e)
    {
        LOG(INFO)<<e.what();
    }
    ofs.close();
}


#pragma once
#include <vector>
#include <string>
#include <random>
#include <unordered_set>

#include <Eigen/Core>
#include <Eigen/Dense>
#include <unsupported/Eigen/SparseExtra>

#include "buffalo/algo.hpp"
#include "buffalo/concurrent_queue.hpp"

using namespace std;
using namespace Eigen;

static const int EXP_TABLE_SIZE = 1000;

namespace bpr {

struct job_t;
struct progress_t;

class CBPRMF : public Algorithm {
public:
    CBPRMF();
    ~CBPRMF();

    void release();
    bool init(string opt_path);
    bool parse_option(string out_path);

    void build_exp_table();

    void launch_workers();

    void initialize_model(
            float* P, int32_t P_rows,
            float* Q, int32_t Q_rows,
            float* Qb,
            int64_t num_total_samples);

    void set_cumulative_table(int64_t* cum_table, int size);

    void initialize_adam_optimizer();

    void initialize_sgd_optimizer();

    void add_jobs(
            int start_x,
            int next_x,
            int64_t* indptr,
            int32_t* positives);

    void worker(int worker_id);

    void wait_until_done();

    double compute_loss(
            int32_t num_loss_samples,
            int32_t* users,
            int32_t* positives,
            int32_t* negatives);

    void update_adam(
            FactorTypeRowMajor& grad,
            FactorTypeRowMajor& momentum,
            FactorTypeRowMajor& velocity,
            int i,
            double beta1,
            double beta2);

    void progress_manager();

    double join();

    void update_parameters();

    double distance(size_t p, size_t q);

private:
    Json opt_;
    Map<FactorTypeRowMajor> P_, Q_, Qb_;
    int iters_;
    double lr_;
    string optimizer_;
    double total_processed_;

    int64_t* cum_table_;
    int cum_table_size_;
    vector<int> P_samples_per_coordinates_;
    vector<int> Q_samples_per_coordinates_;
    FactorTypeRowMajor gradP_, gradQ_, gradQb_;
    FactorTypeRowMajor momentumP_, momentumQ_, momentumQb_;
    FactorTypeRowMajor velocityP_, velocityQ_, velocityQb_;

    float exp_table_[EXP_TABLE_SIZE];

    vector<thread> workers_;
    thread* progress_manager_;
    Queue<job_t> job_queue_;
    Queue<progress_t> progress_queue_;
};

}


#ifndef THYME_ENVIRONMENTS_ROVERS_LIDAR
#define THYME_ENVIRONMENTS_ROVERS_LIDAR

#include <Eigen/Dense>
#include <numeric>
#include <rovers/core/detail/pack.hpp>
#include <rovers/core/poi/poi.hpp>
#include <rovers/core/rover/rover.hpp>
#include <rovers/utilities/math/norms.hpp>
// #include <ranges> // changed for python branch
#include <vector>

namespace rovers {

/*
 *
 * Lidar composition strategies
 *
 */
class Density {
   public:
    // template <std::ranges::range Range, typename Tp, typename Up>
    template <typename Range, typename Tp, typename Up>
    inline Tp compose(const Range& range, Tp init, Up scale) const {
        return std::accumulate(std::begin(range), std::end(range), init) / scale;
    }
};

class Closest {
   public:
    // template <std::ranges::range Range, typename Tp, typename Up>
    template <typename Range, typename Tp, typename Up>
    inline Tp compose(const Range& range, Tp, Up) const {
        return *std::max_element(std::begin(range), std::end(range));
    }
};

/*
 *
 *  composition strategy interface for bindings
 *
 */
class ISensorComposition {
   public:
    virtual inline double compose(const std::vector<double>&, double, double) const = 0;
    virtual ~ISensorComposition() = default;
};

/*
 *
 * Lidar
 *
 */
template <typename CompositionPolicy = Density>
class Lidar {
    using CPolicy = thyme::utilities::SharedWrap<CompositionPolicy>;

   public:
    Lidar(double resolution = 90, CPolicy composition_policy = CompositionPolicy())
        : m_resolution(resolution), m_composition(composition_policy) {}

    [[nodiscard]] Eigen::MatrixXd scan(const AgentPack& pack) const {
        const std::size_t num_sectors = 360 / m_resolution;
        std::vector<std::vector<double>> poi_values(num_sectors), rover_values(num_sectors);
        auto& rover = pack.agent;  // convenient handle

        // observe pois
        for (const auto& sensed_poi : pack.entities) {
            if (sensed_poi->observed()) continue;
            auto [angle, distance] = thyme::math::l2a(rover->position(), sensed_poi->position());
            if (distance > rover->obs_radius()) continue;

            int sector = angle / m_resolution;
            poi_values[sector].push_back(sensed_poi->value() /
                                         std::max(0.001, distance * distance));
        }

        // observe rovers
        for (const auto& sensed_rover : pack.agents) {
            if (&sensed_rover == &rover) continue;

            auto [angle, distance] = thyme::math::l2a(rover->position(), sensed_rover->position());
            if (distance > rover->obs_radius()) continue;

            int sector = angle / m_resolution;
            rover_values[sector].push_back(1.0 / std::max(0.001, distance * distance));
        }

        // encode state
        Eigen::MatrixXd state(num_sectors * 2, 1);

        for (std::size_t i = 0; i < num_sectors; ++i) {
            const std::size_t& num_rovers = rover_values[i].size();
            const std::size_t& num_poi = poi_values[i].size();
            state(i) = state(num_sectors + i) = -1.0;

            if (num_rovers > 0) state(i) = m_composition->compose(rover_values[i], 0.0, num_rovers);
            if (num_poi > 0)
                state(num_sectors + i) = m_composition->compose(poi_values[i], 0.0, num_poi);
        }
        return state;
    }

   private:
    double m_resolution;
    CPolicy m_composition;
};
}  // namespace rovers

#endif