#include "dnnl.hpp"
#include "dnnl_debug.h"
#include "bfloat16.hpp"

#include "dnnl.h"

#include <iostream>
#include <iomanip>
#include <chrono>

#include <mkl.h>

#define EIGEN_USE_MKL_ALL
#include <Eigen/Dense>

//#define XBYAK_NO_OP_NAMES
//#include <xbyak.h>

#include "dnnl_common.h"
#include "dnnl_inner_product.h"
#include "dnnl_matmul.h"

void init_param(int m, int n, int k, float *A, float *B, float *C, bfloat16 *A_bf16, bfloat16 *B_bf16, bfloat16 *C_bf16, Eigen::MatrixXf& A_mat, Eigen::MatrixXf& B_mat, Eigen::MatrixXf& C_mat);

double test_eigen_sgemm(Eigen::MatrixXf& A_mat, Eigen::MatrixXf& B_mat, Eigen::MatrixXf& C_mat, int m, int n, int k);
double test_mkl_sgemm(float *A, float *B, float *C, int m, int n, int k);
double test_mkl_sgemm_transB(float *A, float *B, float *C, int m, int n, int k);
double test_dnnl_sgemm(float *A, float *B, float *C, int m, int n, int k);
double test_dnnl_gemm_bf16bf16f32(float *A, float *B, float *C, int m, int n, int k, bfloat16 *A_bf16, bfloat16 *B_bf16, bfloat16 *C_bf16);
double test_dnnl_gemm_bf16bf16f32_transB(float *A, float *B, float *C, int m, int n, int k, bfloat16 *A_bf16, bfloat16 *B_bf16, bfloat16 *C_bf16);
double test_dnnl_gemm_bf16bf16f32_cvt(float *A, float *B, float *C, int m, int n, int k, bfloat16 *A_bf16, bfloat16 *B_bf16, bfloat16 *C_bf16);
double test_dnnl_gemm_bf16bf16f32_transB_cvt(float *A, float *B, float *C, int m, int n, int k, bfloat16 *A_bf16, bfloat16 *B_bf16, bfloat16 *C_bf16);
double test_dnnl_gemm_bf16bf16f32_omp_cvt(float *A, float *B, float *C, int m, int n, int k, bfloat16 *A_bf16, bfloat16 *B_bf16, bfloat16 *C_bf16);
double test_dnnl_gemm_bf16bf16f32_transB_omp_cvt(float *A, float *B, float *C, int m, int n, int k, bfloat16 *A_bf16, bfloat16 *B_bf16, bfloat16 *C_bf16);
double test_dnnl_cvt_float_to_bfloat16(float *A, float *B, float *C, int m, int n, int k, bfloat16 *A_bf16, bfloat16 *B_bf16, bfloat16 *C_bf16);
double test_dnnl_omp_cvt_float_to_bfloat16(float *A, float *B, float *C, int m, int n, int k, bfloat16 *A_bf16, bfloat16 *B_bf16, bfloat16 *C_bf16);
//double test_jit_cvt_float_to_bfloat16(float *A, float *B, float *C, int m, int n, int k, bfloat16 *A_bf16, bfloat16 *B_bf16, bfloat16 *C_bf16);
double test_dnnl_cvt_bfloat16_to_float(float *A, float *B, float *C, int m, int n, int k, bfloat16 *A_bf16, bfloat16 *B_bf16, bfloat16 *C_bf16);
double test_dnnl_omp_cvt_bfloat16_to_float(float *A, float *B, float *C, int m, int n, int k, bfloat16 *A_bf16, bfloat16 *B_bf16, bfloat16 *C_bf16);

template <typename T_A, typename T_B, typename T_bias, typename T_C>
double test_dnnl_inner_product(engine eng, stream stm, T_A* A_buf, T_B* B_buf, T_bias* bias_buf, T_C* C_buf, int m, int n, int k);
template <typename T_A, typename T_B, typename T_bias, typename T_C>
double test_dnnl_inner_product_v2(engine eng, stream stm, T_A* A_buf, T_B* B_buf, T_bias* bias_buf, T_C* C_buf, int m, int n, int k);
template <typename T_A, typename T_B, typename T_bias, typename T_C>
double test_dnnl_inner_product_eltwise(engine eng, stream stm, T_A* A_buf, T_B* B_buf, T_bias* bias_buf, T_C* C_buf, int m, int n, int k);

template <typename T_A, typename T_B, typename T_bias, typename T_C>
double test_dnnl_matmul(engine eng, stream stm, T_A* A_buf, T_B* B_buf, T_bias* bias_buf, T_C* C_buf, int m, int n, int k);
template <typename T_A, typename T_B, typename T_C>
double test_dnnl_matmul2(engine eng, stream stm, T_A* A_buf, T_B* B_buf, T_C* C_buf, int m, int n, int k);
template <typename T_A, typename T_B, typename T_C>
double test_dnnl_matmul2_eltwise(engine eng, stream stm, T_A* A_buf, T_B* B_buf, T_C* C_buf, int m, int n, int k);

template <typename T_A, typename T_B, typename T_C>
double test_dnnl_batchmatmul(engine eng, stream stm, T_A* A_buf, T_B* B_buf, T_C* C_buf, int mb, int m, int n, int k);

int main(int argc, char *argv[])
{
    printf("./gemmbench_dnnl m n k\nargc = %d\n", argc);
    for(int ndx = 0; ndx != argc; ++ndx)
        printf("argv[%d] --> %s\n", ndx, argv[ndx]);

    int m = atoi(argv[1]);
    int n = atoi(argv[2]);
    int k = atoi(argv[3]);

    bfloat16 *A_bf16 = new bfloat16[m*k];
    bfloat16 *B_bf16 = new bfloat16[k*n];
    bfloat16 *C_bf16 = new bfloat16[m*n];

    float *A = new float[m*k];
    float *B = new float[k*n];
    float *C = new float[m*n];

    Eigen::MatrixXf A_mat(m, k);
    Eigen::MatrixXf B_mat(k, n);
    Eigen::MatrixXf C_mat(m, n);

    init_param(m, n, k, A, B, C, A_bf16, B_bf16, C_bf16, A_mat, B_mat, C_mat);
    std::cout << "\nstarting..." << std::endl;

    double t_eigen_sgemm  = test_eigen_sgemm(A_mat, B_mat, C_mat, m, n, k);

    double t_mkl_sgemm    = test_mkl_sgemm(A, B, C, m, n, k);
    double t_mkl_sgemm_tB = test_mkl_sgemm_transB(A, B, C, m, n, k);

    double t_dnnl_sgemm = test_dnnl_sgemm(A, B, C, m, n, k);
    double t_dnnl_gemm_bf16 = test_dnnl_gemm_bf16bf16f32(A, B, C, m, n, k, A_bf16, B_bf16, C_bf16);
    double t_dnnl_gemm_bf16_tB = test_dnnl_gemm_bf16bf16f32_transB(A, B, C, m, n, k, A_bf16, B_bf16, C_bf16);
    double t_dnnl_gemm_bf16_cvt = test_dnnl_gemm_bf16bf16f32_cvt(A, B, C, m, n, k, A_bf16, B_bf16, C_bf16);
    double t_dnnl_gemm_bf16_tB_cvt = test_dnnl_gemm_bf16bf16f32_transB_cvt(A, B, C, m, n, k, A_bf16, B_bf16, C_bf16);
    double t_dnnl_gemm_bf16_omp_cvt = test_dnnl_gemm_bf16bf16f32_omp_cvt(A, B, C, m, n, k, A_bf16, B_bf16, C_bf16);
    double t_dnnl_gemm_bf16_tB_omp_cvt = test_dnnl_gemm_bf16bf16f32_transB_omp_cvt(A, B, C, m, n, k, A_bf16, B_bf16, C_bf16);
    double t_dnnl_cvt_f2b     = test_dnnl_cvt_float_to_bfloat16(A, B, C, m, n, k, A_bf16, B_bf16, C_bf16);
    double t_dnnl_omp_cvt_f2b = test_dnnl_omp_cvt_float_to_bfloat16(A, B, C, m, n, k, A_bf16, B_bf16, C_bf16);
    //double t_dnnl_jit_cvt = test_jit_cvt_float_to_bfloat16(A, B, C, m, n, k, A_bf16, B_bf16, C_bf16);
    double t_dnnl_cvt_b2f     = test_dnnl_cvt_bfloat16_to_float(A, B, C, m, n, k, A_bf16, B_bf16, C_bf16);
    double t_dnnl_omp_cvt_b2f = test_dnnl_omp_cvt_bfloat16_to_float(A, B, C, m, n, k, A_bf16, B_bf16, C_bf16);

    engine cpu_engine;
    stream cpu_stream;

    stream stream(eng);
    cpu_engine = eng;
    cpu_stream = stream;

    float *bias = new float[n];
    bfloat16 *bias_bf16 = new bfloat16[n];
    for (int i = 0; i < n; ++i) {
        bias[i] = 1.1;
        bias_bf16[i] = (bfloat16)1.1;
    }

    double t_dnnl_ip_ffff  = test_dnnl_inner_product(cpu_engine, cpu_stream, A, B, bias, C, m, n, k);
    double t_dnnl_ip2_ffff = test_dnnl_inner_product_v2(cpu_engine, cpu_stream, A, B, bias, C, m, n, k);
    double t_dnnl_ip2_fffb = test_dnnl_inner_product_v2(cpu_engine, cpu_stream, A, B, bias, C_bf16, m, n, k);
    double t_dnnl_ip2_fbbb = test_dnnl_inner_product_v2(cpu_engine, cpu_stream, A, B_bf16, bias_bf16, C_bf16, m, n, k);
    double t_dnnl_ip_bbbb  = test_dnnl_inner_product(cpu_engine, cpu_stream, A_bf16, B_bf16, bias_bf16, C_bf16, m, n, k);
    double t_dnnl_ip_bbbb_e= test_dnnl_inner_product_eltwise(cpu_engine, cpu_stream, A_bf16, B_bf16, bias_bf16, C_bf16, m, n, k);
    double t_dnnl_ip_bbbf  = test_dnnl_inner_product(cpu_engine, cpu_stream, A_bf16, B_bf16, bias_bf16, C, m, n, k);
    double t_dnnl_ip_bbff  = test_dnnl_inner_product(cpu_engine, cpu_stream, A_bf16, B_bf16, bias, C, m, n, k);

    double t_dnnl_mm_ffff  = test_dnnl_matmul(cpu_engine, cpu_stream, A, B, bias, C, m, n, k);
    double t_dnnl_mm_bbbb  = test_dnnl_matmul(cpu_engine, cpu_stream, A_bf16, B_bf16, bias_bf16, C_bf16, m, n, k);
    double t_dnnl_mm_bbbf  = test_dnnl_matmul(cpu_engine, cpu_stream, A_bf16, B_bf16, bias_bf16, C, m, n, k);
    double t_dnnl_mm_fff  = test_dnnl_matmul2(cpu_engine, cpu_stream, A, B, C, m, n, k);
    double t_dnnl_mm_bbb  = test_dnnl_matmul2(cpu_engine, cpu_stream, A_bf16, B_bf16, C_bf16, m, n, k);
    double t_dnnl_mm_bbb_e= test_dnnl_matmul2_eltwise(cpu_engine, cpu_stream, A_bf16, B_bf16, C_bf16, m, n, k);
    double t_dnnl_mm_bbf  = test_dnnl_matmul2(cpu_engine, cpu_stream, A_bf16, B_bf16, C, m, n, k);

    int mb = 10;
    bfloat16 *BA_bf16 = new bfloat16[mb*m*k];
    bfloat16 *BB_bf16 = new bfloat16[mb*k*n];
    bfloat16 *BC_bf16 = new bfloat16[mb*m*n];

    for (int i = 0; i < mb*m*k; ++i)
        BA_bf16[i] = (bfloat16)1.1;
    for (int i = 0; i < mb*k*n; ++i)
        BB_bf16[i] = (bfloat16)1.1;
    for (int i = 0; i < mb*m*n; ++i)
        BC_bf16[i] = (bfloat16)1.1;

    double t_dnnl_bmm_bbb = test_dnnl_batchmatmul(cpu_engine, cpu_stream, BA_bf16, BB_bf16, BC_bf16, mb, m, n, k);

    del_dnnl();

    printf("\n>> omp num_procs: %d\n", omp_get_num_procs());
    printf("eigen sgemm:               \t%.6f\n", t_eigen_sgemm);
    printf("mkl sgemm:                 \t%.6f ms --> baseline\n", t_mkl_sgemm);
    printf("mkl sgemm+transB:          \t%.6f \t+%.3fX\n", t_mkl_sgemm_tB,              t_mkl_sgemm/t_mkl_sgemm_tB);
    printf("dnnl sgemm:                \t%.6f \t+%.3fX\n", t_dnnl_sgemm,                t_mkl_sgemm/t_dnnl_sgemm);
    printf("dnnl bgemm:                \t%.6f \t+%.3fX\n", t_dnnl_gemm_bf16,            t_mkl_sgemm/t_dnnl_gemm_bf16);
    printf("dnnl bgemm+transB:         \t%.6f \t+%.3fX\n", t_dnnl_gemm_bf16_tB,         t_mkl_sgemm/t_dnnl_gemm_bf16_tB);
    printf("dnnl bgemm+cvt:            \t%.6f \t+%.3fX\n", t_dnnl_gemm_bf16_cvt,        t_mkl_sgemm/t_dnnl_gemm_bf16_cvt);
    printf("dnnl bgemm+transB+cvt:     \t%.6f \t+%.3fX\n", t_dnnl_gemm_bf16_tB_cvt,     t_mkl_sgemm/t_dnnl_gemm_bf16_tB_cvt);
    printf("dnnl bgemm+omp_cvt:        \t%.6f \t+%.3fX\n", t_dnnl_gemm_bf16_omp_cvt,    t_mkl_sgemm/t_dnnl_gemm_bf16_omp_cvt);
    printf("dnnl bgemm+transB+omp_cvt: \t%.6f \t+%.3fX\n", t_dnnl_gemm_bf16_tB_omp_cvt, t_mkl_sgemm/t_dnnl_gemm_bf16_tB_omp_cvt);
    printf("dnnl cvt f2b:              \t%.6f \tt/bgemm:   %.3f%\n", t_dnnl_cvt_f2b,     t_dnnl_cvt_f2b/t_dnnl_gemm_bf16*100);
    printf("dnnl omp_cvt f2b:          \t%.6f \tt/bgemm:   %.3f%\n", t_dnnl_omp_cvt_f2b, t_dnnl_omp_cvt_f2b/t_dnnl_gemm_bf16*100);
    //printf("dnnl jit_cvt: \t%.6f \tt/bgemm:   %.3f%\n", t_dnnl_jit_cvt, t_dnnl_jit_cvt/t_dnnl_gemm_bf16*100);
    printf("dnnl cvt b2f:              \t%.6f \tt/bgemm:   %.3f%\n", t_dnnl_cvt_b2f,     t_dnnl_cvt_b2f/t_dnnl_gemm_bf16*100);
    printf("dnnl omp_cvt b2f:          \t%.6f \tt/bgemm:   %.3f%\n", t_dnnl_omp_cvt_b2f, t_dnnl_omp_cvt_b2f/t_dnnl_gemm_bf16*100);

    printf(">> inner_product, f: fp32, b: bf16, elw: eltwise\n");
    printf("dnnl inner_product  ffff:     \t%.6f \t+%.3fX\n", t_dnnl_ip_ffff,   t_mkl_sgemm/t_dnnl_ip_ffff);
    printf("dnnl inner_product2 ffff:     \t%.6f \t+%.3fX\n", t_dnnl_ip2_ffff,  t_mkl_sgemm/t_dnnl_ip2_ffff);
    printf("dnnl inner_product2 fffb:     \t%.6f \t+%.3fX\n", t_dnnl_ip2_fffb,  t_mkl_sgemm/t_dnnl_ip2_fffb);
    printf("dnnl inner_product2 fbbb:     \t%.6f \t+%.3fX\n", t_dnnl_ip2_fbbb,  t_mkl_sgemm/t_dnnl_ip2_fbbb);
    printf("dnnl inner_product  bbbb:     \t%.6f \t+%.3fX\n", t_dnnl_ip_bbbb,   t_mkl_sgemm/t_dnnl_ip_bbbb);
    printf("dnnl inner_product  bbbb+elw: \t%.6f \t+%.3fX\n", t_dnnl_ip_bbbb_e, t_mkl_sgemm/t_dnnl_ip_bbbb_e);
    printf("dnnl inner_product  bbbf:     \t%.6f \t+%.3fX\n", t_dnnl_ip_bbbf,   t_mkl_sgemm/t_dnnl_ip_bbbf);
    printf("dnnl inner_product  bbff:     \t%.6f \t+%.3fX\n", t_dnnl_ip_bbff,   t_mkl_sgemm/t_dnnl_ip_bbff);

    printf(">> matmul, f: fp32, b: bf16, elw: eltwise\n");
    printf("dnnl matmul ffff:          \t%.6f \t+%.3fX\n", t_dnnl_mm_ffff,   t_mkl_sgemm/t_dnnl_mm_ffff);
    printf("dnnl matmul bbbb:          \t%.6f \t+%.3fX\n", t_dnnl_mm_bbbb,   t_mkl_sgemm/t_dnnl_mm_bbbb);
    printf("dnnl matmul bbbf:          \t%.6f \t+%.3fX\n", t_dnnl_mm_bbbf,   t_mkl_sgemm/t_dnnl_mm_bbbf);
    printf("dnnl matmul2 fff:          \t%.6f \t+%.3fX\n", t_dnnl_mm_fff,    t_mkl_sgemm/t_dnnl_mm_fff);
    printf("dnnl matmul2 bbb:          \t%.6f \t+%.3fX\n", t_dnnl_mm_bbb,    t_mkl_sgemm/t_dnnl_mm_bbb);
    printf("dnnl matmul2 bbb+elw:      \t%.6f \t+%.3fX\n", t_dnnl_mm_bbb_e,  t_mkl_sgemm/t_dnnl_mm_bbb_e);
    printf("dnnl matmul2 bbf:          \t%.6f \t+%.3fX\n", t_dnnl_mm_bbf,    t_mkl_sgemm/t_dnnl_mm_bbf);
    printf("dnnl 10 batch matmul bbb:  \t%.6f \t+%.3fX\n", t_dnnl_bmm_bbb/10,t_mkl_sgemm/t_dnnl_bmm_bbb*10);

    delete[] A_bf16;
    delete[] B_bf16;
    delete[] C_bf16;
    delete[] bias_bf16;

    delete[] A;
    delete[] B;
    delete[] C;

    return 0;
}

void init_param(int m, int n, int k, float *A, float *B, float *C, bfloat16 *A_bf16, bfloat16 *B_bf16, bfloat16 *C_bf16, Eigen::MatrixXf& A_mat, Eigen::MatrixXf& B_mat, Eigen::MatrixXf& C_mat)
{
    for (int i = 0; i < m; ++i) {
        for (int j = 0; j < k; ++j) {
            A_bf16[i*k+j] = (bfloat16)1.1;
            A[i*k+j] = 1.1;
            A_mat.row(i).col(j) << 1.1;
        }
    }

    for (int i = 0; i < k; ++i) {
        for (int j = 0; j < n; ++j) {
            B_bf16[i*n+j] = (bfloat16)1.1;
            B[i*n+j] = 1.1;
            B_mat.row(i).col(j) << 1.1;
        }
    }

    for (int i = 0; i < m; ++i) {
        for (int j = 0; j < n; ++j) {
            C_bf16[i*n+j] = (bfloat16)1.1;
            C[i*n+j] = 1.1;
            C_mat.row(i).col(j) << 1.1;
        }
    }
}

double test_eigen_sgemm(Eigen::MatrixXf& A_mat, Eigen::MatrixXf& B_mat, Eigen::MatrixXf& C_mat, int m, int n, int k)
{
    C_mat = A_mat * B_mat;

    auto tag_1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000; ++i) {
        C_mat = A_mat * B_mat;
    }

    auto tag_2 = std::chrono::high_resolution_clock::now();
    auto tag_diff = std::chrono::duration<double>(tag_2 - tag_1).count();
    std::cout << "result: " << C_mat(0, 0) << "," << C_mat(m-1, n-1) << std::endl;

    return tag_diff;
}

double test_mkl_sgemm(float *A, float *B, float *C, int m, int n, int k)
{
    cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, m, n, k, 1.0, A, k, B, n, 0.0, C, n);

    auto tag_1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000; ++i) {
        cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, m, n, k, 1.0, A, k, B, n, 0.0, C, n);
    }

    auto tag_2 = std::chrono::high_resolution_clock::now();
    auto tag_diff = std::chrono::duration<double>(tag_2 - tag_1).count();
    std::cout << "result: " << C[0] << "," << C[m*n-1] << std::endl;

    return tag_diff;
}

double test_mkl_sgemm_transB(float *A, float *B, float *C, int m, int n, int k)
{
    cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasTrans, m, n, k, 1.0, A, k, B, k, 0.0, C, n);

    auto tag_1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000; ++i) {
        cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasTrans, m, n, k, 1.0, A, k, B, k, 0.0, C, n);
    }

    auto tag_2 = std::chrono::high_resolution_clock::now();
    auto tag_diff = std::chrono::duration<double>(tag_2 - tag_1).count();
    std::cout << "result: " << C[0] << "," << C[m*n-1] << std::endl;

    return tag_diff;
}

double test_dnnl_sgemm(float *A, float *B, float *C, int m, int n, int k)
{
    dnnl_sgemm('N', 'N', m, n, k, 1.0, A, k, B, n, 0.0, C, n);

    auto tag_1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000; ++i) {
        dnnl_sgemm('N', 'N', m, n, k, 1.0, A, k, B, n, 0.0, C, n);
    }

    auto tag_2 = std::chrono::high_resolution_clock::now();
    auto tag_diff = std::chrono::duration<double>(tag_2 - tag_1).count();
    std::cout << "result: " << C[0] << "," << C[m*n-1] << std::endl;

    return tag_diff;
}

double test_dnnl_gemm_bf16bf16f32(float *A, float *B, float *C, int m, int n, int k, bfloat16 *A_bf16, bfloat16 *B_bf16, bfloat16 *C_bf16)
{
    dnnl_gemm_bf16bf16f32('N', 'N', m, n, k, 1.0, A_bf16, k, B_bf16, n, 0.0, C, n);

    auto tag_1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000; ++i) {
        dnnl_gemm_bf16bf16f32('N', 'N', m, n, k, 1.0, A_bf16, k, B_bf16, n, 0.0, C, n);
    }

    auto tag_2 = std::chrono::high_resolution_clock::now();
    auto tag_diff = std::chrono::duration<double>(tag_2 - tag_1).count();
    std::cout << "result: " << C[0] << "," << C[m*n-1] << std::endl;

    return tag_diff;
}

double test_dnnl_gemm_bf16bf16f32_transB(float *A, float *B, float *C, int m, int n, int k, bfloat16 *A_bf16, bfloat16 *B_bf16, bfloat16 *C_bf16)
{
    dnnl_gemm_bf16bf16f32('N', 'T', m, n, k, 1.0, A_bf16, k, B_bf16, k, 0.0, C, n);
    auto tag_1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000; ++i) {
        dnnl_gemm_bf16bf16f32('N', 'T', m, n, k, 1.0, A_bf16, k, B_bf16, k, 0.0, C, n);
    }
    auto tag_2 = std::chrono::high_resolution_clock::now();
    auto tag_diff = std::chrono::duration<double>(tag_2 - tag_1).count();
    std::cout << "result: " << C[0] << "," << C[m*n-1] << std::endl;

    return tag_diff;
}

double test_dnnl_gemm_bf16bf16f32_cvt(float *A, float *B, float *C, int m, int n, int k, bfloat16 *A_bf16, bfloat16 *B_bf16, bfloat16 *C_bf16)
{
    dnnl::impl::cvt_float_to_bfloat16(A_bf16, A, m*k);
    dnnl_gemm_bf16bf16f32('N', 'N', m, n, k, 1.0, A_bf16, k, B_bf16, n, 0.0, C, n);

    auto tag_1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000; ++i) {
        dnnl::impl::cvt_float_to_bfloat16(A_bf16, A, m*k);
        dnnl_gemm_bf16bf16f32('N', 'N', m, n, k, 1.0, A_bf16, k, B_bf16, n, 0.0, C, n);
    }

    auto tag_2 = std::chrono::high_resolution_clock::now();
    auto tag_diff = std::chrono::duration<double>(tag_2 - tag_1).count();
    std::cout << "result: " << C[0] << "," << C[m*n-1] << std::endl;

    return tag_diff;
}

double test_dnnl_gemm_bf16bf16f32_transB_cvt(float *A, float *B, float *C, int m, int n, int k, bfloat16 *A_bf16, bfloat16 *B_bf16, bfloat16 *C_bf16)
{

    dnnl::impl::cvt_float_to_bfloat16(A_bf16, A, m*k);
    dnnl_gemm_bf16bf16f32('N', 'T', m, n, k, 1.0, A_bf16, k, B_bf16, k, 0.0, C, n);

    auto tag_1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000; ++i) {
        dnnl::impl::cvt_float_to_bfloat16(A_bf16, A, m*k);
        dnnl_gemm_bf16bf16f32('N', 'T', m, n, k, 1.0, A_bf16, k, B_bf16, k, 0.0, C, n);
    }

    auto tag_2 = std::chrono::high_resolution_clock::now();
    auto tag_diff = std::chrono::duration<double>(tag_2 - tag_1).count();
    std::cout << "result: " << C[0] << "," << C[m*n-1] << std::endl;

    return tag_diff;
}

double test_dnnl_gemm_bf16bf16f32_omp_cvt(float *A, float *B, float *C, int m, int n, int k, bfloat16 *A_bf16, bfloat16 *B_bf16, bfloat16 *C_bf16)
{
    #pragma omp parallel for num_threads(omp_get_num_procs())
    for (int i = 0; i < m; ++i)
        dnnl::impl::cvt_float_to_bfloat16(A_bf16+i*k, A+i*k, k);
    dnnl_gemm_bf16bf16f32('N', 'N', m, n, k, 1.0, A_bf16, k, B_bf16, n, 0.0, C, n);

    auto tag_1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000; ++i) {
        #pragma omp parallel for num_threads(omp_get_num_procs())
        for (int i = 0; i < m; ++i)
            dnnl::impl::cvt_float_to_bfloat16(A_bf16+i*k, A+i*k, k);
	dnnl_gemm_bf16bf16f32('N', 'N', m, n, k, 1.0, A_bf16, k, B_bf16, n, 0.0, C, n);
    }

    auto tag_2 = std::chrono::high_resolution_clock::now();
    auto tag_diff = std::chrono::duration<double>(tag_2 - tag_1).count();
    std::cout << "result: " << C[0] << "," << C[m*n-1] << std::endl;

    return tag_diff;
}

double test_dnnl_gemm_bf16bf16f32_transB_omp_cvt(float *A, float *B, float *C, int m, int n, int k, bfloat16 *A_bf16, bfloat16 *B_bf16, bfloat16 *C_bf16)
{
    #pragma omp parallel for num_threads(omp_get_num_procs())
    for (int i = 0; i < m; ++i)
        dnnl::impl::cvt_float_to_bfloat16(A_bf16+i*k, A+i*k, k);
    dnnl_gemm_bf16bf16f32('N', 'T', m, n, k, 1.0, A_bf16, k, B_bf16, k, 0.0, C, n);

    auto tag_1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000; ++i) {
        #pragma omp parallel for num_threads(omp_get_num_procs())
        for (int i = 0; i < m; ++i)
            dnnl::impl::cvt_float_to_bfloat16(A_bf16+i*k, A+i*k, k);
	dnnl_gemm_bf16bf16f32('N', 'T', m, n, k, 1.0, A_bf16, k, B_bf16, k, 0.0, C, n);
    }

    auto tag_2 = std::chrono::high_resolution_clock::now();
    auto tag_diff = std::chrono::duration<double>(tag_2 - tag_1).count();
    std::cout << "result: " << C[0] << "," << C[m*n-1] << std::endl;

    return tag_diff;
}

double test_dnnl_cvt_float_to_bfloat16(float *A, float *B, float *C, int m, int n, int k, bfloat16 *A_bf16, bfloat16 *B_bf16, bfloat16 *C_bf16)
{
    dnnl::impl::cvt_float_to_bfloat16(A_bf16, A, m*k);

    auto tag_1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000; ++i) {
        dnnl::impl::cvt_float_to_bfloat16(A_bf16, A, m*k);
    }

    auto tag_2 = std::chrono::high_resolution_clock::now();
    auto tag_diff = std::chrono::duration<double>(tag_2 - tag_1).count();
    std::cout << "result: " << C[0] << "," << C[m*n-1] << std::endl;

    return tag_diff;
}

double test_dnnl_omp_cvt_float_to_bfloat16(float *A, float *B, float *C, int m, int n, int k, bfloat16 *A_bf16, bfloat16 *B_bf16, bfloat16 *C_bf16)
{
    #pragma omp parallel for num_threads(omp_get_num_procs())
    for (int i = 0; i < m; ++i)
        dnnl::impl::cvt_float_to_bfloat16(A_bf16+i*k, A+i*k, k);

    auto tag_1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000; ++i) {
        #pragma omp parallel for num_threads(omp_get_num_procs())
        for (int i = 0; i < m; ++i)
            dnnl::impl::cvt_float_to_bfloat16(A_bf16+i*k, A+i*k, k);
    }

    auto tag_2 = std::chrono::high_resolution_clock::now();
    auto tag_diff = std::chrono::duration<double>(tag_2 - tag_1).count();
    std::cout << "result: " << C[0] << "," << C[m*n-1] << std::endl;

    return tag_diff;
}

double test_dnnl_cvt_bfloat16_to_float(float *A, float *B, float *C, int m, int n, int k, bfloat16 *A_bf16, bfloat16 *B_bf16, bfloat16 *C_bf16)
{
    dnnl::impl::cvt_bfloat16_to_float(A, A_bf16, m*k);

    auto tag_1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000; ++i) {
        dnnl::impl::cvt_bfloat16_to_float(A, A_bf16, m*k);
    }

    auto tag_2 = std::chrono::high_resolution_clock::now();
    auto tag_diff = std::chrono::duration<double>(tag_2 - tag_1).count();
    std::cout << "result: " << C[0] << "," << C[m*n-1] << std::endl;

    return tag_diff;
}

double test_dnnl_omp_cvt_bfloat16_to_float(float *A, float *B, float *C, int m, int n, int k, bfloat16 *A_bf16, bfloat16 *B_bf16, bfloat16 *C_bf16)
{
    #pragma omp parallel for num_threads(omp_get_num_procs())
    for (int i = 0; i < m; ++i)
        dnnl::impl::cvt_bfloat16_to_float(A+i*k, A_bf16+i*k, k);

    auto tag_1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000; ++i) {
        #pragma omp parallel for num_threads(omp_get_num_procs())
        for (int i = 0; i < m; ++i)
            dnnl::impl::cvt_bfloat16_to_float(A+i*k, A_bf16+i*k, k);
    }

    auto tag_2 = std::chrono::high_resolution_clock::now();
    auto tag_diff = std::chrono::duration<double>(tag_2 - tag_1).count();
    std::cout << "result: " << C[0] << "," << C[m*n-1] << std::endl;

    return tag_diff;
}

template <typename T_A, typename T_B, typename T_bias, typename T_C>
double test_dnnl_inner_product(engine eng, stream stm, T_A* A_buf, T_B* B_buf, T_bias* bias_buf, T_C* C_buf, int m, int n, int k)
{
    InnerProduct(eng, stm, A_buf, B_buf, bias_buf, C_buf, m, n, k);

    auto tag_1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000; ++i) {
        InnerProduct(eng, stm, A_buf, B_buf, bias_buf, C_buf, m, n, k);
    }

    auto tag_2 = std::chrono::high_resolution_clock::now();
    auto tag_diff = std::chrono::duration<double>(tag_2 - tag_1).count();
    std::cout << "result: " << C_buf[0] << "," << C_buf[m*n-1] << std::endl;

    return tag_diff;
}

template <typename T_A, typename T_B, typename T_bias, typename T_C>
double test_dnnl_inner_product_v2(engine eng, stream stm, T_A* A_buf, T_B* B_buf, T_bias* bias_buf, T_C* C_buf, int m, int n, int k)
{
    InnerProduct_v2(eng, stm, A_buf, B_buf, bias_buf, C_buf, m, n, k);

    auto tag_1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000; ++i) {
        InnerProduct_v2(eng, stm, A_buf, B_buf, bias_buf, C_buf, m, n, k);
    }

    auto tag_2 = std::chrono::high_resolution_clock::now();
    auto tag_diff = std::chrono::duration<double>(tag_2 - tag_1).count();
    std::cout << "result: " << C_buf[0] << "," << C_buf[m*n-1] << std::endl;
    // std::cout << "result: " << (bfloat16)933.023 << std::endl;

    return tag_diff;
}

template <typename T_A, typename T_B, typename T_bias, typename T_C>
double test_dnnl_inner_product_eltwise(engine eng, stream stm, T_A* A_buf, T_B* B_buf, T_bias* bias_buf, T_C* C_buf, int m, int n, int k)
{
    InnerProduct_eltwise(eng, stm, A_buf, B_buf, bias_buf, C_buf, m, n, k);

    auto tag_1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000; ++i) {
        InnerProduct_eltwise(eng, stm, A_buf, B_buf, bias_buf, C_buf, m, n, k);
    }

    auto tag_2 = std::chrono::high_resolution_clock::now();
    auto tag_diff = std::chrono::duration<double>(tag_2 - tag_1).count();
    std::cout << "result: " << C_buf[0] << "," << C_buf[m*n-1] << std::endl;

    return tag_diff;
}

template <typename T_A, typename T_B, typename T_bias, typename T_C>
double test_dnnl_matmul(engine eng, stream stm, T_A* A_buf, T_B* B_buf, T_bias* bias_buf, T_C* C_buf, int m, int n, int k)
{
    MatMul(eng, stm, A_buf, B_buf, bias_buf, C_buf, m, n, k);

    auto tag_1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000; ++i) {
        MatMul(eng, stm, A_buf, B_buf, bias_buf, C_buf, m, n, k);
    }

    auto tag_2 = std::chrono::high_resolution_clock::now();
    auto tag_diff = std::chrono::duration<double>(tag_2 - tag_1).count();
    std::cout << "result: " << C_buf[0] << "," << C_buf[m*n-1] << std::endl;

    return tag_diff;
}

template <typename T_A, typename T_B, typename T_C>
double test_dnnl_matmul2(engine eng, stream stm, T_A* A_buf, T_B* B_buf, T_C* C_buf, int m, int n, int k)
{
    MatMul2(eng, stm, A_buf, B_buf, C_buf, m, n, k);

    auto tag_1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000; ++i) {
        MatMul2(eng, stm, A_buf, B_buf, C_buf, m, n, k);
    }

    auto tag_2 = std::chrono::high_resolution_clock::now();
    auto tag_diff = std::chrono::duration<double>(tag_2 - tag_1).count();
    std::cout << "result: " << C_buf[0] << "," << C_buf[m*n-1] << std::endl;

    return tag_diff;
}

template <typename T_A, typename T_B, typename T_C>
double test_dnnl_matmul2_eltwise(engine eng, stream stm, T_A* A_buf, T_B* B_buf, T_C* C_buf, int m, int n, int k)
{
    MatMul2_eltwise(eng, stm, A_buf, B_buf, C_buf, m, n, k);

    auto tag_1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000; ++i) {
        MatMul2_eltwise(eng, stm, A_buf, B_buf, C_buf, m, n, k);
    }

    auto tag_2 = std::chrono::high_resolution_clock::now();
    auto tag_diff = std::chrono::duration<double>(tag_2 - tag_1).count();
    std::cout << "result: " << C_buf[0] << "," << C_buf[m*n-1] << std::endl;

    return tag_diff;
}

template <typename T_A, typename T_B, typename T_C>
double test_dnnl_batchmatmul(engine eng, stream stm, T_A* A_buf, T_B* B_buf, T_C* C_buf, int mb, int m, int n, int k)
{
    BatchMatMul(eng, stm, A_buf, B_buf, C_buf, mb, m, n, k);

    auto tag_1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000; ++i) {
        BatchMatMul(eng, stm, A_buf, B_buf, C_buf, mb, m, n, k);
    }

    auto tag_2 = std::chrono::high_resolution_clock::now();
    auto tag_diff = std::chrono::duration<double>(tag_2 - tag_1).count();
    std::cout << "result: " << C_buf[0] << "," << C_buf[m*n-1] << std::endl;

    return tag_diff;
}

/*
struct Code : Xbyak::CodeGenerator {
    const Xbyak::Reg64& src;
    const Xbyak::Reg64& dst;
    const Xbyak::Reg32& loop;
    Code()
        : src(rsi)
        , dst(rdi)
        , loop(edx)
    {
        Xbyak::Label l0;
        L(l0);

        vcvtneps2bf16(ymm0, zword[src]);
        vmovups(yword[dst], ymm0);
        add(src, 64);
        add(dst, 32);

        dec(loop);
        jg(l0, T_NEAR);

        mov(eax, loop);
        ret();
    }
};

double test_jit_cvt_float_to_bfloat16(float *A, float *B, float *C, int m, int n, int k, bfloat16 *A_bf16, bfloat16 *B_bf16, bfloat16 *C_bf16)
{
    Code c;
    int (*f)(void*, void*, int) = c.getCode<int (*)(void*, void*, int)>();
    int num = m*k/16;
    int ret = f(A_bf16, A, num);

    auto tag_1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 1000; ++i) {
        f(A_bf16, A, num);
    }

    auto tag_2 = std::chrono::high_resolution_clock::now();
    auto tag_diff = std::chrono::duration<double>(tag_2 - tag_1).count();
    std::cout << "result: " << C[0] << "," << C[m*n-1] << std::endl;

    return tag_diff;
}
*/


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

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

#include <boost/hana/detail/assert.hpp>
#include <boost/hana/detail/minimal/foldable.hpp>
#include <boost/hana/integral.hpp>
using namespace boost::hana;

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

    BOOST_HANA_CONSTANT_ASSERT(product(foldable()) == int_<1>);
    BOOST_HANA_CONSTANT_ASSERT(product(foldable(int_<2>)) == int_<2>);
    BOOST_HANA_CONSTANT_ASSERT(product(foldable(int_<2>, int_<3>)) == int_<2 * 3>);
    BOOST_HANA_CONSTANT_ASSERT(product(foldable(int_<2>, int_<3>, int_<4>)) == int_<2 * 3 * 4>);
    BOOST_HANA_CONSTANT_ASSERT(product(foldable(int_<2>, int_<3>, int_<4>, int_<5>)) == int_<2 * 3 * 4 * 5>);

    BOOST_HANA_CONSTEXPR_ASSERT(product(foldable(2)) == 2);
    BOOST_HANA_CONSTEXPR_ASSERT(product(foldable(2, 3)) == 2 * 3);
    BOOST_HANA_CONSTEXPR_ASSERT(product(foldable(2, 3, 4)) == 2 * 3 * 4);
    BOOST_HANA_CONSTEXPR_ASSERT(product(foldable(2, 3, 4, 5)) == 2 * 3 * 4 * 5);
}

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


#include <boost/graph/sloan_ordering.hpp>


/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/*
 Copyright (C) 2013 Peter Caspers

 This file is part of QuantLib, a free-software/open-source library
 for financial quantitative analysts and developers - http://quantlib.org/

 QuantLib is free software: you can redistribute it and/or modify it
 under the terms of the QuantLib license.  You should have received a
 copy of the license along with this program; if not, please email
 <quantlib-dev@lists.sf.net>. The license is also available online at
 <http://quantlib.org/license.shtml>.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

/*! \file kahalesmilesection.hpp
    \brief Arbitrage free smile section using a C^1 inter- and extrapolation
   method proposed by Kahale, see
   http://www.risk.net/data/Pay_per_view/risk/technical/2004/0504_tech_option2.pdf
   Exponential extrapolation for high strikes can be used alternatively to avoid
   a too slowly decreasing call price function. Note that in the leftmost
   interval and right from the last grid point the input smile is always
   replaced by the extrapolating functional forms, so if you are sure that the
   input smile is globally arbitrage free and you do not want to change it in
   these strike regions you should not use this class at all.
   Input smile sections with a shift are handled accordingly, normal input
   smile section are not possible though.
*/

#ifndef quantlib_kahale_smile_section_hpp
#define quantlib_kahale_smile_section_hpp

#include <ql/termstructures/volatility/smilesection.hpp>
#include <ql/pricingengines/blackformula.hpp>
#include <ql/math/solvers1d/brent.hpp>
#include <ql/termstructures/volatility/smilesectionutils.hpp>
#if defined(__GNUC__) && (((__GNUC__ == 4) && (__GNUC_MINOR__ >= 8)) || (__GNUC__ > 4))
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
#endif
#include <boost/math/distributions/normal.hpp>
#if defined(__GNUC__) && (((__GNUC__ == 4) && (__GNUC_MINOR__ >= 8)) || (__GNUC__ > 4))
#pragma GCC diagnostic pop
#endif
#include <vector>
#include <utility>

// numerical constants, still experimental
#define QL_KAHALE_FMAX QL_MAX_REAL
#define QL_KAHALE_SMAX 5.0
#define QL_KAHALE_ACC 1E-12
#define QL_KAHALE_EPS QL_EPSILON

namespace QuantLib {

    class KahaleSmileSection : public SmileSection {

      public:
        struct cFunction {
            // this is just a helper class where we do not want virtual
            // functions
            cFunction(Real f, Real s, Real a, Real b)
                : f_(f), s_(s), a_(a), b_(b), exponential_(false) {}
            cFunction(Real a, Real b) : a_(a), b_(b), exponential_(true) {}
            Real operator()(Real k) {
                if (exponential_)
                    return std::exp(-a_ * k + b_);
                if (s_ < QL_EPSILON)
                    return std::max(f_ - k, 0.0) + a_ * k + b_;
                boost::math::normal normal;
                Real d1 = std::log(f_ / k) / s_ + s_ / 2.0;
                Real d2 = d1 - s_;
                return f_ * boost::math::cdf(normal, d1) -
                       k * boost::math::cdf(normal, d2) + a_ * k + b_;
            }
            Real f_, s_, a_, b_;
            const bool exponential_;
        };

        struct aHelper {
            aHelper(Real k0, Real k1, Real c0, Real c1, Real c0p, Real c1p)
                : k0_(k0), k1_(k1), c0_(c0), c1_(c1), c0p_(c0p), c1p_(c1p) {}
            Real operator()(Real a) const {
                boost::math::normal normal;
                Real d20 = boost::math::quantile(normal, -c0p_ + a);
                Real d21 = boost::math::quantile(normal, -c1p_ + a);
                Real alpha = (d20 - d21) / (std::log(k0_) - std::log(k1_));
                Real beta = d20 - alpha * std::log(k0_);
                s_ = -1.0 / alpha;
                f_ = std::exp(s_ * (beta + s_ / 2.0));
                QL_REQUIRE(f_ < QL_KAHALE_FMAX, "dummy"); // this is caught
                cFunction cTmp(f_, s_, a, 0.0);
                b_ = c0_ - cTmp(k0_);
                cFunction c(f_, s_, a, b_);
                return c(k1_) - c1_;
            }
            Real k0_, k1_, c0_, c1_, c0p_, c1p_;
            mutable Real s_, f_, b_;
        };

        struct sHelper {
            sHelper(Real k0, Real c0, Real c0p) : k0_(k0), c0_(c0), c0p_(c0p) {}
            Real operator()(Real s) const {
                s = std::max(s, 0.0);
                boost::math::normal normal;
                Real d20 = boost::math::quantile(normal, -c0p_);
                f_ = k0_ * std::exp(s * d20 + s * s / 2.0);
                QL_REQUIRE(f_ < QL_KAHALE_FMAX, "dummy"); // this is caught
                cFunction c(f_, s, 0.0, 0.0);
                return c(k0_) - c0_;
            }
            Real k0_, c0_, c0p_;
            mutable Real f_;
        };

        struct sHelper1 {
            sHelper1(Real k1, Real c0, Real c1, Real c1p)
                : k1_(k1), c0_(c0), c1_(c1), c1p_(c1p) {}
            Real operator()(Real s) const {
                s = std::max(s, 0.0);
                boost::math::normal normal;
                Real d21 = boost::math::quantile(normal, -c1p_);
                f_ = k1_ * std::exp(s * d21 + s * s / 2.0);
                QL_REQUIRE(f_ < QL_KAHALE_FMAX, "dummy"); // this is caught
                b_ = c0_ - f_;
                cFunction c(f_, s, 0.0, b_);
                return c(k1_) - c1_;
            }
            Real k1_, c0_, c1_, c1p_;
            mutable Real f_, b_;
        };

        KahaleSmileSection(const boost::shared_ptr<SmileSection> source,
                           const Real atm = Null<Real>(),
                           const bool interpolate = false,
                           const bool exponentialExtrapolation = false,
                           const bool deleteArbitragePoints = false,
                           const std::vector<Real> &moneynessGrid =
                               std::vector<Real>(),
                           const Real gap = 1.0E-5,
                           const int forcedLeftIndex = -1,
                           const int forcedRightIndex = QL_MAX_INTEGER);

        Real minStrike() const { return 0.0; }
        Real maxStrike() const { return QL_MAX_REAL; }
        Real atmLevel() const { return f_; }
        const Date& exerciseDate() const { return source_->exerciseDate(); }
        Time exerciseTime() const { return source_->exerciseTime(); }
        const DayCounter& dayCounter() const { return source_->dayCounter(); }
        const Date& referenceDate() const { return source_->referenceDate(); }
        const VolatilityType volatilityType() const {
            return source_->volatilityType();
        }
        const Real shift() const { return source_->shift(); }

        Real leftCoreStrike() const { return k_[leftIndex_]; }
        Real rightCoreStrike() const { return k_[rightIndex_]; }

        std::pair<Size, Size> coreIndices() const {
            return std::make_pair(leftIndex_, rightIndex_);
        }

        Real optionPrice(Rate strike, Option::Type type = Option::Call,
                         Real discount = 1.0) const;

      protected:
        Volatility volatilityImpl(Rate strike) const;

      private:
        Size index(Rate strike) const;
        void compute();
        boost::shared_ptr<SmileSection> source_;
        std::vector<Real> moneynessGrid_, k_, c_;
        Real f_;
        const Real gap_;
        Size leftIndex_, rightIndex_;
        std::vector<boost::shared_ptr<cFunction> > cFunctions_;
        const bool interpolate_, exponentialExtrapolation_;
        int forcedLeftIndex_, forcedRightIndex_;
        boost::shared_ptr<SmileSectionUtils> ssutils_;
    };
}

#endif


/*
   For more information, please see: http://software.sci.utah.edu

   The MIT License

   Copyright (c) 2020 Scientific Computing and Imaging Institute,
   University of Utah.

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included
   in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
   DEALINGS IN THE SOFTWARE.
*/

/*
*  BuildBEMatrix.cc:  class to build Boundary Elements matrix
*
*  Written by:
*   Saeed Babaeizadeh
*   Northeastern University
*   January 2006
*/

#include <Core/Algorithms/Legacy/Forward/BuildBEMatrixAlgo.h>

#include <algorithm>
#include <map>
#include <iostream>
#include <string>
#include <fstream>
#include <numeric>
#include <boost/range/adaptors.hpp>
#include <boost/range/algorithm/copy.hpp>

#include <Core/Datatypes/DenseMatrix.h>
#include <Core/Datatypes/BlockMatrix.h>
#include <Core/Basis/TriLinearLgn.h>
#include <Core/Datatypes/Legacy/Field/Field.h>
#include <Core/Datatypes/Legacy/Field/TriSurfMesh.h>
#include <Core/GeometryPrimitives/Vector.h>
#include <Core/GeometryPrimitives/Point.h>
#include <Core/GeometryPrimitives/PointVectorOperators.h>

using namespace SCIRun;
using namespace SCIRun::Core::Algorithms::Forward;
using namespace SCIRun::Core::Datatypes;
using namespace SCIRun::Core::Geometry;

ALGORITHM_PARAMETER_DEF(Forward, FieldNameList);
ALGORITHM_PARAMETER_DEF(Forward, FieldTypeList);
ALGORITHM_PARAMETER_DEF(Forward, BoundaryConditionList);
ALGORITHM_PARAMETER_DEF(Forward, InsideConductivityList);
ALGORITHM_PARAMETER_DEF(Forward, OutsideConductivityList);

void BuildBEMatrixBase::getOmega(
  const Vector& y1,
  const Vector& y2,
  const Vector& y3,
  DenseMatrix& coef)
{
  /*
  This function deals with the analytical solutions of the various integrals in the stiffness matrix
  The function is concerned with the integrals of the linear interpolations functions over the triangles
  and takes care of the solid spherical angle. As in most cases not all values are needed the computation
  is split up in integrals from one surface to another one

  The computational scheme follows the analytical formulas derived by the
  de Munck 1992 (IEEE Trans Biomed Engng, 39-9, pp 986-90)
  */
  const double epsilon  = 1e-12;
  Vector y21 = y2 - y1;
  Vector y32 = y3 - y2;
  Vector y13 = y1 - y3;

  Vector Ny( y1.length() , y2.length() , y3.length() );

  Vector Nyij( y21.length() , y32.length() , y13.length() );

  Vector gamma( 0 , 0 , 0 );
  double NomGamma , DenomGamma;

  NomGamma = Ny[0]*Nyij[0] + Dot(y1,y21);
  DenomGamma = Ny[1]*Nyij[0] + Dot(y2,y21);
  if (fabs(DenomGamma-NomGamma) > epsilon && (DenomGamma != 0) && NomGamma != 0 ){
    gamma[0] = -1/Nyij[0] * log(NomGamma/DenomGamma);
  }
  NomGamma = Ny[1]*Nyij[1] + Dot(y2,y32);
  DenomGamma = Ny[2]*Nyij[1] + Dot(y3,y32);
  if (fabs(DenomGamma-NomGamma) > epsilon && (DenomGamma != 0) && NomGamma != 0 ){
    gamma[1] = -1/Nyij[1] * log(NomGamma/DenomGamma);
  }
  NomGamma = Ny[2]*Nyij[2] + Dot(y3,y13);
  DenomGamma = Ny[0]*Nyij[2] + Dot(y1,y13);
  if (fabs(DenomGamma-NomGamma) > epsilon && (DenomGamma != 0) && NomGamma != 0 ){
    gamma[2] = -1/Nyij[2] * log(NomGamma/DenomGamma);
  }

  double d = Dot( y1, Cross(y2, y3) );

  Vector OmegaVec = (gamma[2]-gamma[0])*y1 + (gamma[0]-gamma[1])*y2 + (gamma[1]-gamma[2])*y3;

  /*
  In order to avoid problems with the arctan used in de Muncks paper
  the result is tested. A problem is that his formula under certain
  circumstances leads to unexpected changes of signs. Hence to avoid
  this, the denominator is checked and 2*pi is added if necessary.
  The problem without the two pi results in the following situation in
  which division of the triangle into three pieces results into
  an opposite sign compared to the spherical angle of the total
  triangle. These cases are rare but existing.
  */

  double Nn=0 , Omega=0 ;
  Nn = Ny[0]*Ny[1]*Ny[2] + Ny[0]*Dot(y2,y3) + Ny[2]*Dot(y1,y2) + Ny[1]*Dot(y3,y1);

  if (Nn > 0)  Omega = 2 * atan( d / Nn );
  if (Nn < 0)  Omega = 2 * atan( d / Nn ) + 2*M_PI ;
  if (Nn == 0)
  {
    if ( d > 0 )
      Omega = M_PI;
    else
      Omega = -M_PI;
  }

  Vector N = Cross(y21, -y13);
  double Zn1 = Dot(Cross(y2, y3) , N);
  double Zn2 = Dot(Cross(y3, y1) , N);
  double Zn3 = Dot(Cross(y1, y2) , N);

  double A2 = N.length2();
  coef(0,0) = (1/A2) * ( Zn1*Omega + d * Dot(y32, OmegaVec) );
  coef(0,1) = (1/A2) * ( Zn2*Omega + d * Dot(y13, OmegaVec) );
  coef(0,2) = (1/A2) * ( Zn3*Omega + d * Dot(y21, OmegaVec) );

}

void  BuildBEMatrixBase::get_cruse_weights(
  const Vector& p1,
  const Vector& p2,
  const Vector& p3,
  double s,
  double r,
  double area,
  DenseMatrix& cruse_weights)
{
  /*
  Inputs: p1,p2,p3= cartesian coordiantes of the triangle vertices ;
  area = triangle area
  Output: cruse_weights = The weighting factors for the 7 Radon points of the triangle
  Format of the cruse_weights matrix
  Radon point 1       Radon Point 2    ...     Radon Point 7
  Vertex 1 ->
  Vertex 2 ->
  Vertex 3 ->

  Set up the local coordinate system around the triangle. This is a 2-D system and
  for ease of use, the x-axis is chosen as the line between the first and second vertex
  of the triangle. From that the vertex of the third point is found in local coordinates.
  */

  // The angle between the F2 and F3, at vertex 1 is (pi - 'alpha').
  Vector fg2 = p1 - p3;
  double fg2_length = fg2.length();

  Vector fg3 = p2 - p1;
  double fg3_length = fg3.length();

  double cos_alpha = - Dot(fg3,fg2) / (fg2_length * fg3_length);
  double sin_alpha = sqrt(1 - cos_alpha * cos_alpha);

  // Now the vertices in local coordinates
  Vector locp1(0 , 0 , 0);
  Vector locp2(fg3_length , 0 , 0);
  Vector locp3(fg2_length * cos_alpha , fg2_length * sin_alpha , 0);

  DenseMatrix Fx(3, 1);
  Fx << locp3[0] - locp2[0],
    locp1[0] - locp3[0],
    locp2[0] - locp1[0];

  DenseMatrix Fy(3, 1);
  Fy << locp3[1] - locp2[1],
    locp1[1] - locp3[1],
    locp2[1] - locp1[1];

  Vector centroid = (locp1 + locp2 + locp3) / 3;
  DenseMatrix loc_radpt_x(1, 7);
  DenseMatrix loc_radpt_y(1, 7);
  loc_radpt_x(0,0) = centroid[0];
  loc_radpt_y(0,0) = centroid[1];
  Vector temp = (1-s) * centroid;
  loc_radpt_x(0,1) = temp[0] + locp1[0]*s;
  loc_radpt_y(0,1) = temp[1] + locp1[1]*s;
  loc_radpt_x(0,2) = temp[0] + locp2[0]*s;
  loc_radpt_y(0,2) = temp[1] + locp2[1]*s;
  loc_radpt_x(0,3) = temp[0] + locp3[0]*s;
  loc_radpt_y(0,3) = temp[1] + locp3[1]*s;
  temp = (1-r) * centroid;
  loc_radpt_x(0,4) = temp[0] + locp1[0]*r;
  loc_radpt_y(0,4) = temp[1] + locp1[1]*r;
  loc_radpt_x(0,5) = temp[0] + locp2[0]*r;
  loc_radpt_y(0,5) = temp[1] + locp2[1]*r;
  loc_radpt_x(0,6) = temp[0] + locp3[0]*r;
  loc_radpt_y(0,6) = temp[1] + locp3[1]*r;

  auto A = (0.5/area) * (Fy * loc_radpt_x - Fx * loc_radpt_y);

  DenseMatrix ones(1, 7, 1.0);
  DenseMatrix E(3, 1);
  /*
  E is a 1X3 matrix: [1st vertex  ;  2nd vertex  ;  3rd vertex]
  E = [1/3 ; 1/3 ; 1/3] + (0.5/area)*(Fy*xmid - Fx*ymid);
  but there is no need to compute the E because by our choice of the
  local coordinates, it is easy to show that the E is always [1 ; 0 ; 0]!
  */
  E << 1,0,0;
  cruse_weights = (E * ones) - A;
}

void BuildBEMatrixBase::get_g_coef(
  const Vector& p1,
  const Vector& p2,
  const Vector& p3,
  const Vector& op,
  double s,
  double r,
  const Vector& centroid,
  DenseMatrix& g_coef)
{
  // Inputs: p1,p2,p3= cartesian coordiantes of the triangle vertices ; op= Observation Point
  // Output: g_coef = G Values (Coefficients) at 7 Radon's points = 1/r
  Vector radpt = centroid - op;
  g_coef(0,0) = 1 / radpt.length();

  Vector temp = centroid * (1-s) - op;
  radpt = temp + p1 * s;
  g_coef(0,1) = 1 / radpt.length();
  radpt = temp + p2 * s;
  g_coef(0,2) = 1 / radpt.length();
  radpt = temp + p3 * s;
  g_coef(0,3) = 1 / radpt.length();

  temp = centroid * (1-r) - op;
  radpt = temp + p1 * r;
  g_coef(0,4) = 1 / radpt.length();
  radpt = temp + p2 * r;
  g_coef(0,5) = 1 / radpt.length();
  radpt = temp + p3 * r;
  g_coef(0,6) = 1 / radpt.length();
}

void BuildBEMatrixBase::bem_sing(
  const Vector& p1,
  const Vector& p2,
  const Vector& p3,
  unsigned int op_n,
  DenseMatrix& g_values,
  double s,
  double r,
  DenseMatrix& R_W)
{
  /*
  This is Jeroen's method, converted from his Matlab code, for dealing with weightings corresponding to singular triangles
  */
  Vector A,B,C,P,BC,BA,AC,AP;
  DenseMatrix WAPB(3,1);
  DenseMatrix WAPC(3,1);
  int one=0,two=1,three=2;

  switch(op_n)
  {
  case 0:
    A = p1; B=p2; C=p3;
    one=0; two=1; three=2;
    break;
  case 1:
    A = p2; B=p3; C=p1;
    one=1; two=2; three=0;
    break;
  case 2:
    A = p3; B=p1; C=p2;
    one=2; two=0; three=1;
    break;
  default:;
  }

  BC=C-B; BA=A-B; AC=C-A;
  double RL = Dot(BA,BC/BC.length())/BC.length();
  P=RL*BC+B;
  AP=A-P;
  double lAP=AP.length();
  double lBC=BC.length();
  double lBP=fabs(RL)*lBC;
  double lCP=fabs(1-RL)*lBC;
  double lAB=BA.length();
  double lAC=AC.length();
  double a,b,c,w,log_term;

  if(fabs(RL) > 0)
  {
    a=lAP; b=lBP; c=lAB;
    log_term=log( (b+c)/a );
    WAPB(0,0)=a/2 * log_term;
    w=1-RL;
    WAPB(1,0)=a* (( a-c)*(-1+w) + b*w*log_term )/(2*b);
    w=RL;
    WAPB(2,0)=a*w *( a-c  +  b*log_term )/(2*b);
  }
  else
  {
    WAPB(0,0)=0; WAPB(1,0)=0; WAPB(2,0)=0;
  }

  if(fabs(RL-1) > 0)
  {
    a = lAP; b = lCP; c = lAC;
    log_term = log( (b+c)/a );
    WAPC(0,0)=a/2 * log_term;
    w = 1-RL;
    WAPC(1,0)=a*w *( a-c  +  b*log_term )/(2*b);
    w = RL;
    WAPC(2,0)=a* (( a-c)*(-1+w) + b*w*log_term )/(2*b);
  }
  else
  {
    WAPC(0,0)=0; WAPC(1,0)=0; WAPC(2,0)=0;
  }

  if(RL<0)
  {
    WAPB(0,0)*=-1.0; WAPB(1,0)*=-1.0; WAPB(2,0)*=-1.0;
  }
  if(RL>1)
  {
    WAPC(0,0)*=-1.0; WAPC(1,0)*=-1.0; WAPC(2,0)*=-1.0;
  }

  g_values(one,0) = WAPB(0,0) + WAPC(0,0);
  g_values(two,0) = WAPB(1,0) + WAPC(1,0);
  g_values(three,0) = WAPB(2,0) + WAPC(2,0);
}

void BuildBEMatrixBase::get_auto_g(
  const Vector& p1,
  const Vector& p2,
  const Vector& p3,
  unsigned int op_n,
  DenseMatrix& g_values,
  double s,
  double r,
  DenseMatrix& R_W)
{
  /*
  A routine to solve the Auto G-parameter integral for a triangle from
  a "closed" observation point.
  The scheme is the standard one for all the BEM routines.
  Input are the observation point and triangle co-ordinates
  Input:
  op_n = observation point number (1, 2 or 3)
  p1,p2,p3 = triangle co-ordinates (REAL)

  Output:
  g_values
  = the total values for the 1/r integral for
  each of the subtriangles associated with each
  integration triangle vertex about the observation
  point defined by the calling
  program.
  */

  Vector p5 = (p1 + p2) / 2;
  Vector p6 = (p2 + p3) / 2;
  Vector p4 = (p1 + p3) / 2;
  Vector ctroid = (p1 + p2 + p3) / 3;
  Vector op;

  switch(op_n)
  {
  case 0:
    op = p1;
    g_values(0,0) = get_new_auto_g(op, p5, p4) + do_radon_g(p5, ctroid, p4, op, s, r, R_W);
    g_values(1,0) = do_radon_g(p2, p6, p5, op, s, r, R_W) + do_radon_g(p5, p6, ctroid, op, s, r, R_W);
    g_values(2,0) = do_radon_g(p3, p4, p6, op, s, r, R_W) + do_radon_g(p4, ctroid, p6, op, s, r, R_W);
    break;
  case 1:
    op = p2;
    g_values(0,0) = do_radon_g(p1, p5, p4, op, s, r, R_W) + do_radon_g(p5, ctroid, p4, op, s, r, R_W);
    g_values(1,0) = get_new_auto_g(op, p6, p5) + do_radon_g(p5, p6, ctroid, op, s, r, R_W);
    g_values(2,0) = do_radon_g(p3, p4, p6, op, s, r, R_W) + do_radon_g(p4, ctroid, p6, op, s, r, R_W);
    break;
  case 2:
    op = p3;
    g_values(0,0) = do_radon_g(p1, p5, p4, op, s, r, R_W) + do_radon_g(p5, ctroid, p4, op, s, r, R_W);
    g_values(1,0) = do_radon_g(p2, p6, p5, op, s, r, R_W) + do_radon_g(p5, p6, ctroid, op, s, r, R_W);
    g_values(2,0) = get_new_auto_g(op, p4, p6) + do_radon_g(p4, ctroid, p6, op, s, r, R_W);
    break;
  }
}

double BuildBEMatrixBase::get_new_auto_g(
  const Vector& op,
  const Vector& p2,
  const Vector& p3)
{
  //  Inputs: op,p2,p3= cartesian coordiantes of the triangle vertices ; op= Observation Point
  //  Output: g1 = G value for the triangle for "auto_g"
  //  This function is called from get_auto_g.m

  double delta_min = 0.00001;
  unsigned int max_number_of_divisions = 256;

  Vector a = p2 - op; double a_mag = a.length();
  Vector b = p3 - p2; double b_mag = b.length();
  Vector c = op - p3; double c_mag = c.length();

  Vector aV = Cross(p2 - op, p3 - p2)*0.5;
  double area = aV.length();
  double area2 = 2.0*area;
  double h = (area2) / b_mag;
  double alfa=0;  if (h<a_mag) alfa = acos(h/a_mag);
  double AC = a_mag*c_mag;
  double teta = 0; if (area2<=AC) teta = asin( area2 / AC );

  unsigned int nod = 1;
  double sai_old = sqrt(area2 * teta);
  double delta = 1;

  double gama, gama_j, rhoj_1, rhoj, sum, sai_new=0;

  while( (delta >= delta_min) && (nod <= max_number_of_divisions) )
  {
    nod = 2*nod;
    gama = teta / nod;
    sum = 0;
    gama_j = 0;
    rhoj_1 = a_mag;
    for ( unsigned int j = 1; j <= nod; j++)
    {
      gama_j = gama_j + gama;
      rhoj = h / cos(alfa - gama_j);
      sum = sum + sqrt( std::fabs(rhoj * rhoj_1) );
      rhoj_1 = rhoj;
    }
    sai_new = sum * sqrt(std::fabs(gama * sin(gama)));
    delta = 0;
    if (sai_new + sai_old)
      delta = std::fabs((sai_new - sai_old) / (sai_new + sai_old));
    sai_old = sai_new;
  }
  return sai_new;
}

double BuildBEMatrixBase::do_radon_g(
  const Vector& p1,
  const Vector& p2,
  const Vector& p3,
  const Vector& op,
  double s,
  double r,
  DenseMatrix& R_W)
{
  //  Inputs: p1,p2,p3= cartesian coordiantes of the triangle vertices ; op= Observation Point
  //  Output: g2 = G value for the triangle for "auto_g"
  //  This function is called from get_auto_g.m

  Vector centroid = (p1 + p2 + p3) / 3;

  DenseMatrix g_coef(1, 7);
  get_g_coef(p1, p2, p3, op, s, r, centroid, g_coef);

  double g2 = 0;
  for (int i=0; i<7; i++)   g2 = g2 + g_coef(0,i)*R_W(0,i);

  Vector aV = Cross(p2 - p1, p3 - p2)*0.5;

  return g2 * aV.length();
}

class BuildBEMatrixBaseCompute : public BuildBEMatrixBase
{
public:
  template <class MatrixType>
  static void make_auto_P_compute(VMesh* hsurf, MatrixType& auto_P, double in_cond, double out_cond, double op_cond);

  template <class MatrixType>
  static void make_cross_P_compute(VMesh* hsurf1, VMesh* hsurf2, MatrixType& cross_P, double in_cond, double out_cond, double op_cond);

  template <class MatrixType>
  static void make_auto_G_compute(VMesh* hsurf, MatrixType& auto_G, double in_cond, double out_cond, double op_cond, const std::vector<double>& avInn);

  template <class MatrixType>
  static void make_cross_G_compute( VMesh*,
  VMesh*,
  MatrixType&,
  double,
  double,
  double,
  const std::vector<double>& );
};

void BuildBEMatrixBase::make_auto_G_allocate(VMesh* hsurf, DenseMatrixHandle &h_GG_)
{
  auto nnodes = numNodes(hsurf);
  h_GG_.reset(new DenseMatrix(nnodes, nnodes, 0.0));
}

void BuildBEMatrixBase::make_auto_G(VMesh* hsurf, DenseMatrixHandle &h_GG_,
double in_cond, double out_cond, double op_cond, const std::vector<double>& avInn)
{
  make_auto_G_allocate(hsurf, h_GG_);
  BuildBEMatrixBaseCompute::make_auto_G_compute(hsurf, *h_GG_, in_cond, out_cond, op_cond, avInn);
}

template <class MatrixType>
void BuildBEMatrixBaseCompute::make_auto_G_compute(VMesh* hsurf, MatrixType& auto_G,
  double in_cond, double out_cond, double op_cond, const std::vector<double>& avInn)
{
  //const double mult = 1/(2*M_PI)*((out_cond - in_cond)/op_cond);  // op_cond=out_cond for all the surfaces but the outermost surface which in op_cond=in_cond
  const double mult = 1/(4*M_PI)*(out_cond - in_cond);  // op_cond=out_cond for all the surfaces but the outermost surface which in op_cond=in_cond

  VMesh::Node::array_type nodes;

  VMesh::Node::iterator ni, nie;
  VMesh::Face::iterator fi, fie;
  DenseMatrix cruse_weights(3, 7);
  DenseMatrix g_coef(1, 7);
  DenseMatrix R_W(1,7); // Radon Points Weights
  DenseMatrix temp(1,7);
  DenseMatrix g_values(3, 1);

  double area;

  double sqrt15 = sqrt(15.0);
  //R_W(0,0) = 9/40; // <- Burak! FIX ME!
  R_W(0,0) = 9.0/40.0;
  R_W(0,1) = (155 + sqrt15) / 1200;
  R_W(0,2) = R_W(0,1);
  R_W(0,3) = R_W(0,1);
  R_W(0,4) = (155 - sqrt15) / 1200;
  R_W(0,5) = R_W(0,4);
  R_W(0,6) = R_W(0,4);

  double s = (1 - sqrt15) / 7;
  double r = (1 + sqrt15) / 7;

  hsurf->begin(fi); hsurf->end(fie);
  for (; fi != fie; ++fi)
  { //! find contributions from every triangle
    hsurf->get_nodes(nodes, *fi);
    Vector p1(hsurf->get_point(nodes[0]));
    Vector p2(hsurf->get_point(nodes[1]));
    Vector p3(hsurf->get_point(nodes[2]));

    area = avInn[*fi];

    get_cruse_weights(p1, p2, p3, s, r, area, cruse_weights);
    Vector centroid = (p1 + p2 + p3) / 3.0;
    hsurf->begin(ni); hsurf->end(nie);
    for (; ni != nie; ++ni)
    { //! for every node
      VMesh::Node::index_type ppi = *ni;
      Vector op(hsurf->get_point(ppi));

      if (ppi == nodes[0])       bem_sing(p1, p2, p3, 0, g_values, s, r, R_W);
      else if (ppi == nodes[1])       bem_sing(p1, p2, p3, 1, g_values, s, r, R_W);
      else if (ppi == nodes[2])       bem_sing(p1, p2, p3, 2, g_values, s, r, R_W);
      else
      {
        get_g_coef(p1, p2, p3, op, s, r, centroid, g_coef);

        for (int i=0; i<7; i++)  temp(0,i) = g_coef(0,i)*R_W(0,i);

        g_values = area * (cruse_weights * temp.transpose());
      } // else

      for (int i=0; i<3; ++i)
        auto_G(ppi, nodes[i])+=g_values(i,0)*mult;
    }
  }
}

void BuildBEMatrixBase::make_cross_G_allocate(VMesh* hsurf1, VMesh* hsurf2, DenseMatrixHandle &h_GG_)
{
  h_GG_.reset(new DenseMatrix(numNodes(hsurf1), numNodes(hsurf2), 0.0));
}

void BuildBEMatrixBase::make_cross_G(VMesh* hsurf1, VMesh* hsurf2, DenseMatrixHandle &h_GG_,
  double in_cond, double out_cond, double op_cond, const std::vector<double>& avInn)
{
  make_cross_G_allocate(hsurf1, hsurf2, h_GG_);
  BuildBEMatrixBaseCompute::make_cross_G_compute(hsurf1, hsurf2, *h_GG_, in_cond, out_cond, op_cond, avInn);
}

template <class MatrixType>
void BuildBEMatrixBaseCompute::make_cross_G_compute(VMesh* hsurf1, VMesh* hsurf2, MatrixType& cross_G,
  double in_cond, double out_cond, double op_cond, const std::vector<double>& avInn)
{
  const double mult = 1/(4*M_PI)*(out_cond - in_cond);
  //   out_cond and in_cond belong to hsurf2 and op_cond is the out_cond of hsurf1 for all the surfaces but the outermost surface which in op_cond=in_cond

  VMesh::Node::array_type nodes;

  VMesh::Node::iterator  ni, nie;
  VMesh::Face::iterator  fi, fie;

  DenseMatrix cruse_weights(3, 7);
  DenseMatrix g_coef(1, 7);
  DenseMatrix R_W(1,7); // Radon Points Weights
  DenseMatrix temp(1,7);
  DenseMatrix g_values(3, 1);

  double area;

  double sqrt15 = sqrt(15.0);
  //R_W(0,0) = 9/40; // <- Burak! FIX ME!
  R_W(0,0) = 9.0/40.0;
  R_W(0,1) = (155 + sqrt15) / 1200;
  R_W(0,2) = R_W(0,1);
  R_W(0,3) = R_W(0,1);
  R_W(0,4) = (155 - sqrt15) / 1200;
  R_W(0,5) = R_W(0,4);
  R_W(0,6) = R_W(0,4);

  double s = (1 - sqrt15) / 7;
  double r = (1 + sqrt15) / 7;

  hsurf2->begin(fi); hsurf2->end(fie);
  for (; fi != fie; ++fi)
  { //! find contributions from every triangle
    hsurf2->get_nodes(nodes, *fi);
    Vector p1(hsurf2->get_point(nodes[0]));
    Vector p2(hsurf2->get_point(nodes[1]));
    Vector p3(hsurf2->get_point(nodes[2]));

    area = avInn[*fi];

    get_cruse_weights(p1, p2, p3, s, r, area, cruse_weights);
    Vector centroid = (p1 + p2 + p3) / 3.0;

    hsurf1->begin(ni); hsurf1->end(nie);
    for (; ni != nie; ++ni)
    { //! for every node
      VMesh::Node::index_type ppi = *ni;
      Vector op(hsurf1->get_point(ppi));
      get_g_coef(p1, p2, p3, op, s, r, centroid, g_coef);

      for (int i=0; i<7; i++)  temp(0,i) = g_coef(0,i)*R_W(0,i);

      g_values = area * (cruse_weights * temp.transpose());

      for (int i=0; i<3; ++i)
        cross_G(ppi, nodes[i])+=g_values(i,0)*mult;
    }
  }
}

void BuildBEMatrixBase::make_cross_P_allocate(VMesh* hsurf1, VMesh* hsurf2, DenseMatrixHandle &h_PP_)
{
  h_PP_.reset(new DenseMatrix(numNodes(hsurf1), numNodes(hsurf2), 0.0));
}

void BuildBEMatrixBase::make_cross_P(VMesh* hsurf1, VMesh* hsurf2, DenseMatrixHandle &h_PP_,
  double in_cond, double out_cond, double op_cond)
{
  make_cross_P_allocate(hsurf1, hsurf2, h_PP_);
  BuildBEMatrixBaseCompute::make_cross_P_compute(hsurf1, hsurf2, *h_PP_, in_cond, out_cond, op_cond);
}

template <class MatrixType>
void BuildBEMatrixBaseCompute::make_cross_P_compute(VMesh* hsurf1, VMesh* hsurf2, MatrixType& cross_P, double in_cond, double out_cond, double op_cond)
{
  const double mult = 1/(4*M_PI)*(out_cond - in_cond);
  //   out_cond and in_cond belong to hsurf2 and op_cond is the out_cond of hsurf1 for all the surfaces but the outermost surface which in op_cond=in_cond
  VMesh::Node::array_type nodes;
  DenseMatrix coef(1, 3);
  int i;

  VMesh::Node::iterator  ni, nie;
  VMesh::Face::iterator  fi, fie;

  hsurf1->begin(ni); hsurf1->end(nie);
  for (; ni != nie; ++ni){ //! for every node
    VMesh::Node::index_type ppi = *ni;
    Point pp = hsurf1->get_point(ppi);

    hsurf2->begin(fi); hsurf2->end(fie);
    for (; fi != fie; ++fi){ //! find contributions from every triangle

      hsurf2->get_nodes(nodes, *fi);
      Vector v1 = hsurf2->get_point(nodes[0]) - pp;
      Vector v2 = hsurf2->get_point(nodes[1]) - pp;
      Vector v3 = hsurf2->get_point(nodes[2]) - pp;

      getOmega(v1, v2, v3, coef);

      for (i=0; i<3; ++i)
        cross_P(ppi, nodes[i])-=coef(0,i)*mult;
    }
  }
}

void BuildBEMatrixBase::make_auto_P_allocate(VMesh* hsurf, DenseMatrixHandle &h_PP_)
{
  auto nnodes = numNodes(hsurf);
  h_PP_.reset(new DenseMatrix(nnodes, nnodes, 0.0));
}

int BuildBEMatrixBase::numNodes(FieldHandle f)
{
  return numNodes(f->vmesh());
}

int BuildBEMatrixBase::numNodes(VMesh* hsurf)
{
  VMesh::Node::size_type nsize;
  hsurf->size(nsize);
  return static_cast<int>(nsize);
}

template <class MatrixType>
void BuildBEMatrixBaseCompute::make_auto_P_compute(VMesh* hsurf, MatrixType& auto_P, double in_cond, double out_cond, double op_cond)
{
  auto nnodes = auto_P.rows();

  //const double mult = 1/(2*M_PI)*((out_cond - in_cond)/op_cond);  // op_cond=out_cond for all the surfaces but the outermost surface which in op_cond=in_cond
  const double mult = 1/(4*M_PI)*(out_cond - in_cond);

  VMesh::Node::array_type nodes;
  DenseMatrix coef(1, 3);

  VMesh::Node::iterator ni, nie;
  VMesh::Face::iterator fi, fie;

  unsigned int i;

  hsurf->begin(ni); hsurf->end(nie);

  for (; ni != nie; ++ni){ //! for every node
    VMesh::Node::index_type ppi = *ni;
    Point pp = hsurf->get_point(ppi);

    hsurf->begin(fi); hsurf->end(fie);

    for (; fi != fie; ++fi) { //! find contributions from every triangle

      hsurf->get_nodes(nodes, *fi);
      if (ppi!=nodes[0] && ppi!=nodes[1] && ppi!=nodes[2]){
        Vector v1 = hsurf->get_point(nodes[0]) - pp;
        Vector v2 = hsurf->get_point(nodes[1]) - pp;
        Vector v3 = hsurf->get_point(nodes[2]) - pp;

        getOmega(v1, v2, v3, coef);

        for (i=0; i<3; ++i)
          auto_P(ppi, nodes[i])-=coef(0,i)*mult;
      }
    }
  }

  //! accounting for autosolid angle
  auto sumOfRows = auto_P.rowwise().sum().eval();
  for (i=0; i<nnodes; ++i)
  {
    auto_P(i,i) = out_cond - sumOfRows(i);
  }
}

void BuildBEMatrixBase::make_auto_P(VMesh* hsurf, DenseMatrixHandle &h_PP_,
  double in_cond, double out_cond, double op_cond)
{
  make_auto_P_allocate(hsurf, h_PP_);
  BuildBEMatrixBaseCompute::make_auto_P_compute(hsurf, *h_PP_, in_cond, out_cond, op_cond);
}

// precalculate triangles area
void BuildBEMatrixBase::pre_calc_tri_areas(VMesh* hsurf, std::vector<double>& areaV){

  VMesh::Face::iterator  fi, fie;

  hsurf->begin(fi);
  hsurf->end(fie);
  for (; fi != fie; ++fi)
    areaV.push_back(hsurf->get_area(*fi));
}

// C++ized MollerTrumbore97 Ray Triangle intersection test.
bool BuildBEMatrixBase::ray_triangle_intersect(double &t,
  const Point &point,
  const Vector &dir,
  const Point &p0,
  const Point &p1,
  const Point &p2)
{
  // Find vectors for two edges sharing p0.
  const Vector edge1 = p1 - p0;
  const Vector edge2 = p2 - p0;

  // begin calculating determinant - also used to calculate U parameter.
  const Vector pvec = Cross(dir, edge2);

  // if determinant is near zero, ray lies in plane of triangle.
  const double det = Dot(edge1, pvec);
  const double EPSILON = 1.0e-6;
  if (det > -EPSILON && det < EPSILON)
  {
    return false;
  }
  const double inv_det = 1.0 / det;

  // Calculate distance from vert0 to ray origin.
  const Vector tvec = point - p0;

  // Calculate U parameter and test bounds.
  const double u = Dot(tvec, pvec) * inv_det;
  if (u < 0.0 || u > 1.0)
  {
    return false;
  }

  // Prepare to test V parameter.
  const Vector qvec = Cross(tvec, edge1);

  // Calculate V parameter and test bounds.
  const double v = Dot(dir, qvec) * inv_det;
  if (v < 0.0 || u + v > 1.0)
  {
    return false;
  }

  // Calculate t, ray intersects triangle.
  t = Dot(edge2, qvec) * inv_det;

  return true;
}

void BuildBEMatrixBase::compute_intersections(std::vector<std::pair<double, int> >
  &results,
  const VMesh* mesh,
  const Point &p, const Vector &v,
  int marker)
{
  VMesh::Face::iterator itr, eitr;
  mesh->begin(itr);
  mesh->end(eitr);
  double t;
  while (itr != eitr)
  {
    VMesh::Node::array_type nodes;
    mesh->get_nodes(nodes, *itr);
    Point p0, p1, p2;
    mesh->get_center(p0, nodes[0]);
    mesh->get_center(p1, nodes[1]);
    mesh->get_center(p2, nodes[2]);
    if (ray_triangle_intersect(t, p, v, p0, p1, p2))
    {
      results.push_back(std::make_pair(t, marker));
    }
    ++itr;
  }
}

static bool
  pair_less(const std::pair<double, int> &a,
  const std::pair<double, int> &b)
{
  return a.first < b.first;
}

int BuildBEMatrixBase::compute_parent(const std::vector<VMesh*> &meshes, int index)
{
  Point point;
  meshes[index]->get_center(point, VMesh::Node::index_type(0));
  Vector dir(1.0, 1.0, 1.0);
  std::vector<std::pair<double, int> > intersections;

  unsigned int i;
  for (i = 0; i < (unsigned int)meshes.size(); i++)
  {
    compute_intersections(intersections, meshes[i], point, dir, i);
  }

  std::sort(intersections.begin(), intersections.end(), pair_less);

  std::vector<int> counts(meshes.size(), 0);
  for (i = 0; i < intersections.size(); i++)
  {
    if (intersections[i].second == index)
    {
      // First odd count is parent.
      for (int j = i-1; j >= 0; j--)
      {
        // TODO: unusual odd/even number test?
        if (counts[intersections[j].second] & 1)
        {
          return intersections[j].second;
        }
      }
      // No odd parent, is outside.
      return static_cast<int>( meshes.size() );
    }
    counts[intersections[i].second]++;
  }

  // Indeterminate, we should intersect with ourselves.
  return static_cast<int>( meshes.size() );
}

bool BuildBEMatrixBase::compute_nesting(std::vector<int> &nesting, const std::vector<VMesh*> &meshes)
{
  nesting.resize(meshes.size());

  unsigned int i;
  for (i = 0; i < (unsigned int)meshes.size(); i++)
  {
    nesting[i] = compute_parent(meshes, i);
  }

  return true;
}

class SurfaceAndPoints : public BEMAlgoImpl, public BuildBEMatrixBaseCompute
{
public:
  MatrixHandle compute(const bemfield_vector& fields) const override;
};

class SurfaceToSurface : public BEMAlgoImpl, public BuildBEMatrixBaseCompute
{
public:
  MatrixHandle compute(const bemfield_vector& fields) const override;
};

BEMAlgoPtr BEMAlgoImplFactory::create(const bemfield_vector& fields)
{
  ///////////////////////////////////////////////////////////////////////////////////////////////////
  // Check for special case where the potentials need to be evaluated at the nodes of a lead
  // This case assumes the first input is the surface mesh and the second is the location of the
  // nodes

  const bemfield_vector::size_type SPECIAL_CASE_LEN = 2;

  if ( fields.size() == SPECIAL_CASE_LEN )
  {
    VMesh *surface, *nodes;
    int surfcount=0, pointcloudcount=0;

    bool meets_conditions = true;

    for (bemfield_vector::size_type i = 0; i < SPECIAL_CASE_LEN; i++)
    {
      if (fields[i].surface)
      {
        surface = fields[i].field_->vmesh();
        if (! surface->is_trisurfmesh() ) meets_conditions = false;
        surfcount++;
      }
      else
      {
        nodes = fields[i].field_->vmesh();
        if (! ( nodes->is_pointcloudmesh() ) || nodes->is_curvemesh() )
        {
          meets_conditions = false;
        }
        pointcloudcount++;
      }
    }

    if ( (surfcount == 0) || (pointcloudcount == 0) )
    {
      meets_conditions = false;
    }

    // If all of the checks above don't flag meets_conditions as false,
    // return a value that indicates the algorithm to use is the surface-to-nodes case
    if ( meets_conditions )
      return boost::make_shared<SurfaceAndPoints>();
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // Check for case where all inputs are triangle surfaces, and there is at least one source and
  // one measurement surface

  bool allsurfaces=true, hasmeasurementsurf=false, hassourcesurf=false;

  for (bemfield_vector::size_type i = 0; i < fields.size(); i++)
  {
    // if the current field is not marked to be used as a surface OR it's not of trisurfmesh type, this algorithm does not apply
    if ( (! fields[i].field_->vmesh()->is_trisurfmesh()) || (! fields[i].surface) )
    {
      allsurfaces=false;
      break;
    }

    if (fields[i].measurement) hasmeasurementsurf = true;
    if (fields[i].source) hassourcesurf = true;
  }

  // if all fields are surfaces, there exists a measurement and a source surface, then use the surface-to-surface algorithm... else fail
  if (allsurfaces && hasmeasurementsurf && hassourcesurf)
  {
    return boost::make_shared<SurfaceToSurface>();
  }
  else
  {
    return nullptr;
  }
}

static void printInfo(const DenseMatrix& m, const std::string& name)
{
#if 0
  std::cout << name << ": " << m.rows() << " x " << m.cols() << std::endl;
  std::cout << name << " min: " << m.minCoeff() << std::endl;
  std::cout << name << " max: " << m.maxCoeff() << std::endl;
#endif
}

MatrixHandle SurfaceToSurface::compute(const bemfield_vector& fields) const
{
  // Math for surface-to-surface BEM algorithm (based on Jeroen Stinstra's BEM Matlab code that's part of SCIRun)
  // -------------------------------------------------------------------------------------------------------------
  // EE = matrix relating potentials on surfaces to potentials on other surfaces
  // EJ = matrix relating current density on surfaces (normal to surface) to potentials on surfaces
  // u  = potentials on the surfaces
  // j  = current density normal to the surfaces
  //
  // General equation: EE*u + EJ*j = (dipolar sources not on the surfaces)
  // Assuming all sources are on surfaces: EE*u + EJ*j = 0
  // (below assumes that measurement=Neumann boundary conditions and source=Dirichlet boundary conditions)
  //
  // s = source indices
  // m = measurement indices
  //
  // Pmm = EE(m,m)
  // Pss = EE(s,s)
  // Pms = EE(m,s)
  // Psm = EE(s,m)
  //
  // Gms = EJ(m,s)
  // Gss = EJ(s,s)
  //
  // After some block-matrix math to eliminate j from the equation and find T s.t. u(m)=T*u(s), we get:
  // iGss = inv(Gss)
  // T = inv(Pmm - Gms*iGss*Psm)*(Gms*iGss*Pss - Pms)
  //

  const size_t Nfields = fields.size();
  double op_cond=0.0; // op_cond is not used in this formulation -- someone needs to check this math and make a better decision about how to handle this value below

  // Count the number of fields that have been specified as being "sources" or "measurements" (and keep track of indices)
  int Nsources = 0;
  std::vector<int> sourcefieldindices;
  int Nmeasurements = 0;
  std::vector<int> measurementfieldindices;

  for(int i=0; i < Nfields; i++)
  {
    if(fields[i].source)
    {
      Nsources++;
      sourcefieldindices.push_back(i);
    }
    else if(fields[i].measurement)
    {
      Nmeasurements++;
      measurementfieldindices.push_back(i);
    }
  }

  std::vector<int> fieldNodeSize(fields.size());
  std::transform(fields.begin(), fields.end(), fieldNodeSize.begin(), [](const bemfield& f) { return numNodes(f.field_); } );
  DenseBlockMatrix EE(fieldNodeSize, fieldNodeSize);

  // Calculate EE in block matrix form
  for(int i = 0; i < Nfields; i++)
  {
    for(int j = 0; j < Nfields; j++)
    {
      if (i == j)
      {
        auto block = EE.blockRef(i, j);
        make_auto_P_compute(fields[i].field_->vmesh(), block, fields[i].insideconductivity, fields[i].outsideconductivity, op_cond);
      }
      else
      {
        auto block = EE.blockRef(i, j);
        make_cross_P_compute(fields[i].field_->vmesh(), fields[j].field_->vmesh(), block, fields[j].insideconductivity, fields[j].outsideconductivity, op_cond);
      }
    }
  }

  printInfo(EE.matrix(), "EE");

  std::vector<int> sourceFieldNodeSize(sourcefieldindices.size());
  auto sourceFields = fields | boost::adaptors::filtered([](const bemfield& f) { return f.source; });
  //following doesn't compile in VS2010, but does in clang: enable it after upgrading to 2013
  //auto transformer = [this](const bemfield& f) -> int { return numNodes(f.field_); };
  //auto trans = filt | boost::adaptors::transformed(transformer);
  //boost::copy(trans, sourceFieldNodeSize.begin());
  std::transform(sourceFields.begin(), sourceFields.end(), sourceFieldNodeSize.begin(), [](const bemfield& f) { return numNodes(f.field_); } );

  DenseBlockMatrix EJ(fieldNodeSize, sourceFieldNodeSize);

  // Calculate EJ(:,s) in block matrix form
  // ***NOTE THE CHANGE IN INDEXING!!!***
  // (The indices of block columns of EJ correspond to field indices according to "sourcefieldindices", and this affects everything with EJ below this point too!)
  for(int j = 0; j < Nsources; j++)
  {
    // Precalculate triangle areas for this source field/surface
    std::vector<double> triangleareas;
    pre_calc_tri_areas(fields[sourcefieldindices[j]].field_->vmesh(), triangleareas);

    for(int i = 0; i < Nfields; i++)
    {
      if (i == sourcefieldindices[j])
      {
        auto block = EJ.blockRef(i,j);
        make_auto_G_compute(fields[i].field_->vmesh(), block, fields[i].insideconductivity, fields[i].outsideconductivity, op_cond, triangleareas);
      }
      else
      {
        auto block = EJ.blockRef(i,j);
        make_cross_G_compute(fields[i].field_->vmesh(), fields[sourcefieldindices[j]].field_->vmesh(), block, fields[j].insideconductivity, fields[j].outsideconductivity, op_cond, triangleareas);
      }
    }
  }

  printInfo(EJ.matrix(), "EJ");

  // This needs to be checked.  It was taken out because the deflation was producing errors
  // Jeroen's matlab code, which was the basis of this code, only does a defation in test cases.

  // Perform deflation on EE matrix
  //const double deflationconstant = 1.0/EE.matrix().ncols();
  //EE.matrix() = EE.matrix().array() + deflationconstant;

  std::vector<int> measurementNodeSize(measurementfieldindices.size());
  auto measFields = fields | boost::adaptors::filtered([](const bemfield& f) { return f.measurement; });
  //following doesn't compile in VS2010, but does in clang: enable it after upgrading to 2013
  //auto transformer = [this](const bemfield& f) -> int { return numNodes(f.field_); };
  //auto trans = filt | boost::adaptors::transformed(transformer);
  //boost::copy(trans, sourceFieldNodeSize.begin());
  std::transform(measFields.begin(), measFields.end(), measurementNodeSize.begin(), [](const bemfield& f) { return numNodes(f.field_); } );

  // Split EE apart into Pmm, Pss, Pms, and Psm
  // -----------------------------------------------
  // Pmm:
  DenseBlockMatrix Pmm(measurementNodeSize, measurementNodeSize);
  for(int i = 0; i < Nmeasurements; i++)
  {
    for(int j = 0; j < Nmeasurements; j++)
    {
      Pmm.blockRef(i,j) = EE.blockRef(measurementfieldindices[i], measurementfieldindices[j]);
    }
  }
  printInfo(Pmm.matrix(), "Pmm");

  // Pss:
  DenseBlockMatrix Pss(sourceFieldNodeSize, sourceFieldNodeSize);
  for(int i = 0; i < Nsources; i++)
  {
    for(int j = 0; j < Nsources; j++)
    {
      Pss.blockRef(i,j) = EE.blockRef(sourcefieldindices[i],sourcefieldindices[j]);
    }
  }
  printInfo(Pss.matrix(), "Pss");

  // Pms:
  DenseBlockMatrix Pms(measurementNodeSize, sourceFieldNodeSize);
  for(int i = 0; i < Nmeasurements; i++)
  {
    for(int j = 0; j < Nsources; j++)
    {
      Pms.blockRef(i,j) = EE.blockRef(measurementfieldindices[i],sourcefieldindices[j]);
    }
  }
  printInfo(Pms.matrix(), "Pms");

  // Psm:
  DenseBlockMatrix Psm(sourceFieldNodeSize, measurementNodeSize);
  for(int i = 0; i < Nsources; i++)
  {
    for(int j = 0; j < Nmeasurements; j++)
    {
      Psm.blockRef(i,j) = EE.blockRef(sourcefieldindices[i],measurementfieldindices[j]);
    }
  }
  printInfo(Psm.matrix(), "Psm");

  // Split EJ apart into Gms and Gss (see ALL-CAPS note above about differences in block row vs column indexing in EJ matrix)
  // -----------------------------------------------
  // Gms:
  DenseBlockMatrix Gms(measurementNodeSize, sourceFieldNodeSize);
  for(int i = 0; i < Nmeasurements; i++)
  {
    for(int j = 0; j < Nsources; j++)
    {
      Gms.blockRef(i,j) = EJ.blockRef(measurementfieldindices[i],j);
    }
  }
  printInfo(Gms.matrix(), "Gms");

  // Gss:
  DenseBlockMatrix Gss(sourceFieldNodeSize, sourceFieldNodeSize);
  for(int i = 0; i < Nsources; i++)
  {
    for(int j = 0; j < Nsources; j++)
    {
      Gss.blockRef(i,j) = EJ.blockRef(sourcefieldindices[i],j);
    }
  }
  printInfo(Gss.matrix(), "Gss");

  // TODO: add deflation step

  // Compute T here (see math in comments above)
  // TransferMatrix = T = inv(Pmm - Gms*iGss*Psm)*(Gms*iGss*Pss - Pms) = inv(C)*D

  auto Y = Gms.matrix() * Gss.matrix().inverse();
  auto C = Pmm.matrix() - Y * Psm.matrix();
  auto D = Y * Pss.matrix() - Pms.matrix();

  auto T = C.inverse() * D; // T = inv(C)*D
  return boost::make_shared<DenseMatrix>(T);

  //This could be done on one line (see below), but Y (see above) would need to be calculated twice:
  //MatrixHandle TransferMatrix1 = inv(Pmm - Gms * Gss * Psm) * (Gms * Gss * Pss - Pms);
}

MatrixHandle SurfaceAndPoints::compute(const bemfield_vector& fields) const
{
  // NOTE: This is Jeroen's code that has been adapted to fit the new module structure
  //
  // Math:
  // The boundary element formulation is based on Matlab code
  // bemMatrixPP2.m, which can be found in the matlab package

  // The BEM formulation assumes the following matrix equations
  // P_surf_surf * PHI_surf + G_surf_surf * J_surf =  sources_in_volume
  //
  // PHI_surf are the potentials on the surface
  // J_surf are the currents passing perpendicular to the surface
  // sources_in_volume is empty in this case
  //
  // P_surf_surf is the matrix that connects the potentials at the nodes to the integral over the
  // potential at the surface. Its terms consist of Green's function ( 1/ ( 4pi*||r-r'|| ) ) over
  // the surface of each element. As this integral becomes singular for a node and a triangle that
  // share a corner node, we use a trick to avoid computing this integral as we know that the
  // the system should reference potential invariant. Hence the rows of the matrix need to sum to
  // to zero
  //
  // G_surf_surf is the matrix that connects the potentials at the nodes to the integral over the
  // currents flowing through the surface.
  //
  // The second equation that we use is the expression of the potentials at an arbitrary point
  // to the potentials at the surface and the current flowing through the surface
  //
  // PHI_nodes = P_nodes_surf * PHI_surf + G_nodes_surf * J_surf
  //
  // Here matrix P_nodes_surf is the matrix that projects the contribution of the potentials of the
  // surface to the nodes within the volume
  //
  // Here G_nodes_surf is the matrix that projects the contribution of the currents flowing through
  // the surface to the nodes within the volume
  //
  // Adding both equations together will result in
  //
  // PHI_nodes = P_nodes_surf* PHI_surf - G_nodes_surf * inv( G_surf_surf) * P_surf_surf * PHI_surf
  //
  // In other words the transfer matrix is
  // P_nodes_surf - G_nodes_surf * inv( G_surf_surf) * P_surf_surf

  VMesh *nodes = nullptr;
  VMesh *surface = nullptr;

  for (int i=0; i<2; i++)
  {
    if (fields[i].surface)
      surface = fields[i].field_->vmesh();
    else
      nodes = fields[i].field_->vmesh();
  }

  DenseMatrixHandle Pss;
  DenseMatrixHandle Gss;
  DenseMatrixHandle Pns;
  DenseMatrixHandle Gns;
  make_auto_P( surface, Pss, 1.0, 0.0, 1.0 );
  make_cross_P( nodes, surface, Pns, 1.0, 0.0, 1.0 );

  std::vector<double> area;
  pre_calc_tri_areas( surface, area );

  make_auto_G( surface, Gss, 1.0, 0.0, 1.0, area );
  make_cross_G( nodes, surface, Gns, 1.0, 0.0, 1.0, area );

  return boost::make_shared<DenseMatrix>(*Pns - (*Gns * Gss->inverse() * *Pss));
}


/*
 * statistics.cc
 *
 *  Created on: Aug 30, 2016
 *      Author: egilad
 */

#include "statistics.h"

#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/mean.hpp>
#include <boost/accumulators/statistics/density.hpp>
#include <boost/accumulators/statistics/rolling_mean.hpp>

#include <iostream>

using namespace boost::accumulators;

namespace ycsbc
{

constexpr size_t WindowsNum = 100;

struct Statistics::State
{
	accumulator_set<double,
		stats<
			tag::mean,
			tag::rolling_mean
		>
	> acc;
	size_t expectingEvents;

	State(size_t expectedEvents) :
		acc(tag::rolling_window::window_size = expectedEvents / WindowsNum),
		expectingEvents(expectedEvents)
	{

	}
};

Statistics::Statistics(size_t expectedEvents) : eventCtr(0)
{
	state.reset(new State(expectedEvents));
	partMeans.reserve(expectedEvents / WindowsNum);
}

Statistics::Statistics(const Statistics& other) : eventCtr(other.eventCtr)
{
	state.reset(new State(other.state->expectingEvents));
	partMeans.reserve(other.partMeans.capacity());
}

Statistics::~Statistics()
{
}

void Statistics::addEvent(double time)
{
	state->acc(time);
	if (++eventCtr % WindowsNum == 0)
	{
		partMeans.push_back(rolling_mean(state->acc));
	}
}

void Statistics::getMeans(double& total, std::vector<double>& partials) const
{
	total = mean(state->acc);
	partials = partMeans;
}

} /* namespace ycsbc */


#include <stan/math/mix.hpp>
#include <gtest/gtest.h>
#include <boost/random/mersenne_twister.hpp>
#include <boost/math/distributions.hpp>
#include <vector>

using Eigen::Dynamic;
using Eigen::Matrix;

TEST(ProbDistributionsCategorical, fvar_var) {
  using stan::math::fvar;
  using stan::math::var;
  Matrix<fvar<var>, Dynamic, 1> theta(3, 1);
  theta << 0.3, 0.5, 0.2;
  for (int i = 0; i < 3; i++)
    theta(i).d_ = 1.0;

  EXPECT_FLOAT_EQ(std::log(0.3),
                  stan::math::categorical_log(1, theta).val_.val());
  EXPECT_FLOAT_EQ(std::log(0.5),
                  stan::math::categorical_log(2, theta).val_.val());
  EXPECT_FLOAT_EQ(std::log(0.2),
                  stan::math::categorical_log(3, theta).val_.val());
  EXPECT_FLOAT_EQ(1.0 / 0.3, stan::math::categorical_log(1, theta).d_.val());
  EXPECT_FLOAT_EQ(1.0 / 0.5, stan::math::categorical_log(2, theta).d_.val());
  EXPECT_FLOAT_EQ(1.0 / 0.2, stan::math::categorical_log(3, theta).d_.val());
}
TEST(ProbDistributionsCategorical, fvar_var_vector) {
  using stan::math::fvar;
  using stan::math::var;
  Matrix<fvar<var>, Dynamic, 1> theta(3, 1);
  theta << 0.3, 0.5, 0.2;
  for (int i = 0; i < 3; i++)
    theta(i).d_ = 1.0;

  std::vector<int> xs(3);
  xs[0] = 1;
  xs[1] = 3;
  xs[2] = 1;

  EXPECT_FLOAT_EQ(log(0.3) + log(0.2) + log(0.3),
                  stan::math::categorical_log(xs, theta).val_.val());
  EXPECT_FLOAT_EQ(1.0 / 0.3 + 1.0 / 0.2 + 1.0 / 0.3,
                  stan::math::categorical_log(xs, theta).d_.val());
}

TEST(ProbDistributionsCategorical, fvar_fvar_var) {
  using stan::math::fvar;
  using stan::math::var;
  Matrix<fvar<fvar<var> >, Dynamic, 1> theta(3, 1);
  theta << 0.3, 0.5, 0.2;
  for (int i = 0; i < 3; i++)
    theta(i).d_.val_ = 1.0;

  EXPECT_FLOAT_EQ(std::log(0.3),
                  stan::math::categorical_log(1, theta).val_.val_.val());
  EXPECT_FLOAT_EQ(std::log(0.5),
                  stan::math::categorical_log(2, theta).val_.val_.val());
  EXPECT_FLOAT_EQ(std::log(0.2),
                  stan::math::categorical_log(3, theta).val_.val_.val());
  EXPECT_FLOAT_EQ(1.0 / 0.3,
                  stan::math::categorical_log(1, theta).d_.val_.val());
  EXPECT_FLOAT_EQ(1.0 / 0.5,
                  stan::math::categorical_log(2, theta).d_.val_.val());
  EXPECT_FLOAT_EQ(1.0 / 0.2,
                  stan::math::categorical_log(3, theta).d_.val_.val());
}
TEST(ProbDistributionsCategorical, fvar_fvar_var_vector) {
  using stan::math::fvar;
  using stan::math::var;
  Matrix<fvar<fvar<var> >, Dynamic, 1> theta(3, 1);
  theta << 0.3, 0.5, 0.2;
  for (int i = 0; i < 3; i++)
    theta(i).d_.val_ = 1.0;

  std::vector<int> xs(3);
  xs[0] = 1;
  xs[1] = 3;
  xs[2] = 1;

  EXPECT_FLOAT_EQ(log(0.3) + log(0.2) + log(0.3),
                  stan::math::categorical_log(xs, theta).val_.val_.val());
  EXPECT_FLOAT_EQ(1.0 / 0.3 + 1.0 / 0.2 + 1.0 / 0.3,
                  stan::math::categorical_log(xs, theta).d_.val_.val());
}


#ifndef INCLUDE_SWIFT_VIO_EPIPOLAR_JACOBIAN_HPP_
#define INCLUDE_SWIFT_VIO_EPIPOLAR_JACOBIAN_HPP_
#include <Eigen/Geometry>

#include <swift_vio/ProjParamOptModels.hpp>
#include <okvis/cameras/CameraBase.hpp>
#include <okvis/kinematics/operators.hpp>

namespace swift_vio {
/**
 * @brief obsDirectionJacobian compute the Jacobian of the obsDirection
 *     relative to the camera parameters and its covariance.
 * @param obsDirection [x/z, y/z, 1] backprojected undistorted coordinates.
 * @param cameraGeometry
 * @param imageObservationCov covariance of image observation.
 * @param dfj_dXcam
 * @param cov_fj cov([x/z, y/z, 1])
 * @return false if backprojected direction failed to project onto image.
 */
inline bool obsDirectionJacobian(
    const Eigen::Vector3d& obsDirection,
    std::shared_ptr<const okvis::cameras::CameraBase> cameraGeometry,
    int projOptModelId, const Eigen::Matrix2d& imageObservationCov,
    Eigen::Matrix<double, 3, Eigen::Dynamic>* dfj_dXcam,
    Eigen::Matrix3d* cov_fj) {
  const Eigen::Vector3d& fj = obsDirection;
  Eigen::Vector2d imagePoint;
  Eigen::Matrix<double, 2, 3> pointJacobian;
  Eigen::Matrix2Xd intrinsicsJacobian;
  okvis::cameras::CameraBase::ProjectionStatus projectOk =
      cameraGeometry->project(fj, &imagePoint, &pointJacobian, &intrinsicsJacobian);
  if (projectOk != okvis::cameras::CameraBase::ProjectionStatus::Successful)
    return false;
  ProjectionOptMinimalIntrinsicJacobian(projOptModelId, &intrinsicsJacobian);
  Eigen::Matrix2d dz_df12 = pointJacobian.topLeftCorner<2, 2>();
  Eigen::Matrix2d df12_dz = dz_df12.inverse();
  int cols = intrinsicsJacobian.cols();
  dfj_dXcam->resize(3, cols);
  dfj_dXcam->topLeftCorner(2, cols) = -df12_dz * intrinsicsJacobian;
  dfj_dXcam->row(2).setZero();
  cov_fj->setZero();
  cov_fj->topLeftCorner<2, 2>() = df12_dz * imageObservationCov *
                                  df12_dz.transpose();
  return true;
}

class EpipolarJacobian {
 public:
  EIGEN_MAKE_ALIGNED_OPERATOR_NEW

  inline EpipolarJacobian(const Eigen::Matrix3d& R_CjCk,
                          const Eigen::Vector3d& t_CjCk,
                          const Eigen::Vector3d& fj, const Eigen::Vector3d& fk);
  inline double evaluate() const;
  inline void de_dtheta_CjCk(Eigen::Matrix<double, 1, 3>* jac) const;
  inline void de_dfj(Eigen::Matrix<double, 1, 3>* jac) const;
  inline void de_dt_CjCk(Eigen::Matrix<double, 1, 3>* jac) const;
  inline void de_dfk(Eigen::Matrix<double, 1, 3>* jac) const;

 private:
  const Eigen::Matrix3d R_CjCk_;
  const Eigen::Vector3d t_CjCk_;
  const Eigen::Vector3d fj_;  // z = 1
  const Eigen::Vector3d fk_;  // z = 1
};

inline EpipolarJacobian::EpipolarJacobian(const Eigen::Matrix3d& R_CjCk,
                                          const Eigen::Vector3d& t_CjCk,
                                          const Eigen::Vector3d& fj,
                                          const Eigen::Vector3d& fk)
    : R_CjCk_(R_CjCk), t_CjCk_(t_CjCk), fj_(fj), fk_(fk) {}

inline double EpipolarJacobian::evaluate() const {
  // variants of the below expression does not change the Jacobians
  return (R_CjCk_ * fk_).dot(t_CjCk_.cross(fj_));
}

inline void EpipolarJacobian::de_dtheta_CjCk(
    Eigen::Matrix<double, 1, 3>* jac) const {
  *jac = (R_CjCk_ * fk_).transpose() *
         okvis::kinematics::crossMx(t_CjCk_.cross(fj_));
}

inline void EpipolarJacobian::de_dfj(Eigen::Matrix<double, 1, 3>* jac) const {
  *jac = (R_CjCk_ * fk_).transpose() * okvis::kinematics::crossMx(t_CjCk_);
}

inline void EpipolarJacobian::de_dt_CjCk(
    Eigen::Matrix<double, 1, 3>* jac) const {
  *jac = -(R_CjCk_ * fk_).transpose() * okvis::kinematics::crossMx(fj_);
}

inline void EpipolarJacobian::de_dfk(Eigen::Matrix<double, 1, 3>* jac) const {
  *jac = (t_CjCk_.cross(fj_)).transpose() * R_CjCk_;
}
}  // namespace swift_vio
#endif  // INCLUDE_SWIFT_VIO_EPIPOLAR_JACOBIAN_HPP_


#include <Eigen/Dense>
#include "ukf.h"

using Eigen::MatrixXd;

int main() {

  // Create a UKF instance
  UKF ukf;

  /**
   * Programming assignment calls
   */
  MatrixXd Xsig_pred = MatrixXd(5, 15); // Swapped dimensions - error from original code
  ukf.SigmaPointPrediction(&Xsig_pred);

  return 0;
}


//    Copyright 2017 Rainer Gemulla
//
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
//
//        http://www.apache.org/licenses/LICENSE-2.0
//
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.
/** \file
 *
 * Illustrates matrix factorization with GNMF. */
#include <iostream>

#include <boost/math/distributions/normal.hpp>
#include <boost/math/distributions/binomial.hpp>
#include <boost/random/uniform_real.hpp>

#include <util/evaluation.h>
#include <util/io.h>

#include <mf/mf.h>

using namespace std;
using namespace mf;
using namespace rg;
using namespace boost::numeric::ublas;

log4cxx::LoggerPtr logger(log4cxx::Logger::getLogger("main"));

int main(int argc, char* argv[]) {
#ifndef NDEBUG
	LOG4CXX_WARN(logger, "Warning: Debug mode activated (runtimes may be slow).");
#endif

	// parameters for the factorization
	mf_size_type size1 = 100; //10000;
	mf_size_type size2 = 100; //10000;
	mf_size_type nnz = 50; //5000000;
	mf_size_type r = 2; // 10

	// parameters for GNMF
	unsigned epochs = 1600;
	mf_size_type testNnz = nnz/100;

	BalanceType type = BALANCE_L2;;
	BalanceMethod method = BALANCE_OPTIMAL;

	// generate original factors by sampling from a uniform[0,1] distribution
	Random32 random; // note: this takes a default seed (not randomized!)
	DenseMatrix wIn(size1, r);
	DenseMatrixCM hIn(r, size2);
	generateRandom(wIn, random, boost::uniform_real<>(0,1));
	generateRandom(hIn, random, boost::uniform_real<>(0,1));

	// clear one row
	for (mf_size_type k=0; k<r; k++) {
		wIn(0,k) = 0;
	}

	// generate a sparse matrix by selecting random entries from the generated factors
	// and add small Gaussian noise
	SparseMatrix v;
	generateRandom(v, nnz, wIn, hIn, random);
	//addRandom(v, random, boost::normal_distribution<>(0, 0.1));
	LOG4CXX_INFO(logger, "Data matrix: "
		<< v.size1() << " x " << v.size2() << ", " << v.nnz() << " nonzeros");
	v.sort();
	//LOG4CXX_INFO(logger, "Loss with original factors: " << loss((FactorizationData<>(v, wIn, hIn))));
	SparseMatrixCM vc;
	copyCm(v, vc);

	// create a test matrix (without noise)
	SparseMatrix vTest;
	generateRandom(vTest, testNnz, wIn, hIn, random);
	LOG4CXX_INFO(logger, "Test matrix: "
		<< v.size1() << " x " << v.size2() << ", " << vTest.nnz() << " nonzeros");

	// generate initial factors by sampling from a uniform[0,1] distribution
	DenseMatrix w(size1, r);
	DenseMatrixCM h(r, size2);
	generateRandom(w, random, boost::uniform_real<>(0,1));
	LOG4CXX_INFO(logger, "Row factors: " << w.size1() << " x " << w.size2());
	generateRandom(h, random, boost::uniform_real<>(0,1));
	LOG4CXX_INFO(logger, "Column factors: " << h.size1() << " x " << h.size2());

	// initialize
	FactorizationData<> data(v, w, h, 1, &vc);
	FactorizationData<> testJob(vTest,w,h);
	Trace trace;
	Timer t;

	LOG4CXX_INFO(logger, "Start");
	// run GNMF to try to reconstruct the original factors
	t.start();
	gnmf(data, epochs, trace, type, method, &testJob);
	t.stop();
	LOG4CXX_INFO(logger, "Total time: " << t);

	// write trace to an R file
	LOG4CXX_INFO(logger, "Writing trace to " << "/tmp/gnmf-trace.R");
	trace.toRfile("/tmp/gnmf-trace.R", "gnmf");

	return 0;
}


#define BOOST_TEST_MODULE MultivectorArithmetics
#include <boost/test/unit_test.hpp>
#include <vexcl/constants.hpp>
#include <vexcl/multivector.hpp>
#include <vexcl/reductor.hpp>
#include <vexcl/element_index.hpp>
#include <vexcl/function.hpp>
#include "context_setup.hpp"

BOOST_AUTO_TEST_CASE(arithmetics)
{
    typedef std::array<double, 4> elem_t;

    const size_t n = 1024;

    vex::multivector<double, 4> x(ctx, n);
    vex::multivector<double, 4> y(ctx, random_vector<double>(n * 4));
    vex::multivector<double, 4> z(ctx, random_vector<double>(n * 4));

    vex::Reductor<double,vex::MIN> min(ctx);
    vex::Reductor<double,vex::MAX> max(ctx);

    elem_t v = {{0, 1, 2, 3}};
    x = v;

    BOOST_CHECK(min(x) == v);
    BOOST_CHECK(max(x) == v);

    x = std::make_tuple(1, 2, 3, 4) * y + z;

    check_sample(x, y, z, [](size_t, elem_t a, elem_t b, elem_t c) {
            for(size_t i = 0; i < 4; ++i)
                BOOST_CHECK_CLOSE(a[i], (i + 1) * b[i] + c[i], 1e-8);
            });
}

BOOST_AUTO_TEST_CASE(multivector_multiexpressions)
{
    typedef std::array<double, 2> elem_t;

    const size_t n = 1024;

    vex::multivector<double, 2> x(ctx, n);
    vex::multivector<double, 2> y(ctx, random_vector<double>(n * 2));

    x = std::tie(
            sin( y(0) ) + cos( y(1) ),
            cos( y(0) ) + sin( y(1) )
            );

    check_sample(x, y, [](size_t, elem_t a, elem_t b) {
            BOOST_CHECK_CLOSE(a[0], sin(b[0]) + cos(b[1]), 1e-8);
            BOOST_CHECK_CLOSE(a[1], cos(b[0]) + sin(b[1]), 1e-8);
            });
}

BOOST_AUTO_TEST_CASE(tied_vectors)
{
    const size_t n = 1024;

    vex::vector<double> X(ctx, random_vector<double>(n));
    vex::vector<double> Y(ctx, random_vector<double>(n));

    vex::vector<double> A(ctx, n);
    vex::vector<double> B(ctx, n);

    vex::tie(A, B) = std::tie(X + Y, X - Y);

    check_sample(A, X, Y, [](size_t, double a, double x, double y) {
            BOOST_CHECK(a == x + y);
            });

    check_sample(B, X, Y, [](size_t, double b, double x, double y) {
            BOOST_CHECK(b == x - y);
            });
}

BOOST_AUTO_TEST_CASE(builtin_functions)
{
    typedef std::array<double, 2> elem_t;
    const size_t n = 1024;

    vex::multivector<double, 2> x(ctx, random_vector<double>(n * 2));
    vex::multivector<double, 2> y(ctx, n);

    y = pow(sin(x), 2.0) + pow(cos(x), 2.0);

    check_sample(y, [](size_t, elem_t a) {
            BOOST_CHECK_CLOSE(a[0], 1, 1e-8);
            BOOST_CHECK_CLOSE(a[1], 1, 1e-8);
            });
}

BOOST_AUTO_TEST_CASE(user_defined_functions)
{
    typedef std::array<double, 2> elem_t;

    const size_t n = 1024;
    const size_t m = 2;

    vex::multivector<double, m> x(ctx, n);
    vex::multivector<double, m> y(ctx, n);

    elem_t v1 = {{1, 2}};
    elem_t v2 = {{2, 1}};

    VEX_FUNCTION(size_t, greater, (double, x)(double, y), return x > y;);

    x = v1;
    y = v2;

    x = greater(x, y);

    check_sample(x, [](size_t, elem_t a) {
            BOOST_CHECK(a[0] == 0);
            BOOST_CHECK(a[1] == 1);
            });
}

BOOST_AUTO_TEST_CASE(reduction)
{
    typedef std::array<double, 2> elem_t;

    const size_t n = 1024;

    std::vector<double> x = random_vector<double>(n);
    std::vector<double> y = random_vector<double>(n);

    vex::multivector<double, 2> m(ctx, n);
    copy(x, m(0));
    copy(y, m(1));

    vex::Reductor<double, vex::SUM> sum(ctx);
    vex::Reductor<double, vex::MIN> min(ctx);
    vex::Reductor<double, vex::MAX> max(ctx);

    elem_t summ = sum(m);
    elem_t minm = min(m);
    elem_t maxm = max(m);

    BOOST_CHECK_CLOSE(summ[0], std::accumulate(x.begin(), x.end(), 0.0), 1e-6);
    BOOST_CHECK_CLOSE(summ[1], std::accumulate(y.begin(), y.end(), 0.0), 1e-6);

    BOOST_CHECK_CLOSE(minm[0], *std::min_element(x.begin(), x.end()), 1e-12);
    BOOST_CHECK_CLOSE(minm[1], *std::min_element(y.begin(), y.end()), 1e-12);

    BOOST_CHECK_CLOSE(maxm[0], *std::max_element(x.begin(), x.end()), 1e-12);
    BOOST_CHECK_CLOSE(maxm[1], *std::max_element(y.begin(), y.end()), 1e-12);
}

BOOST_AUTO_TEST_CASE(element_index)
{
    typedef std::array<double, 2> elem_t;

    const size_t N = 1024;

    vex::multivector<double, 2> x(ctx, N);

    x = 0.5 * vex::element_index();

    check_sample(x, [](size_t idx, elem_t a) {
            BOOST_CHECK_CLOSE(a[0], 0.5 * idx, 1e-12);
            BOOST_CHECK_CLOSE(a[1], 0.5 * idx, 1e-12);
            });

    x = std::tie(
            sin(0.5 * vex::element_index()),
            cos(0.5 * vex::element_index())
            );

    check_sample(x, [](size_t idx, elem_t a) {
            BOOST_CHECK_CLOSE(a[0], sin(0.5 * idx), 1e-6);
            BOOST_CHECK_CLOSE(a[1], cos(0.5 * idx), 1e-6);
            });
}

BOOST_AUTO_TEST_CASE(compound_assignment)
{
    const size_t n = 1024;
    const size_t m = 2;

    typedef std::array<double, m> elem_t;

    vex::multivector<double, m> x(ctx, n);
    vex::multivector<double, m> y(ctx, random_vector<double>(n * m));

    x = 0;

    x += sin(2 * y);

    check_sample(x, y, [&](size_t, elem_t a, elem_t b) {
            for(size_t i = 0; i < m; ++i)
                BOOST_CHECK_CLOSE(a[i], sin(2 * b[i]), 1e-8);
            });

    x = 0;
    x -= sin(2 * y);

    check_sample(x, y, [&](size_t, elem_t a, elem_t b) {
            for(size_t i = 0; i < m; ++i)
                BOOST_CHECK_CLOSE(a[i], -sin(2 * b[i]), 1e-8);
            });

    x = 1;
    x *= std::tie(y(1), sin(y(0)));

    check_sample(x, y, [](size_t, elem_t a, elem_t b) {
            BOOST_CHECK_CLOSE(a[0], b[1], 1e-8);
            BOOST_CHECK_CLOSE(a[1], sin(b[0]), 1e-8);
            });
}

BOOST_AUTO_TEST_CASE(integral_constants)
{
    typedef std::array<double, 4> elem_t;

    const size_t n = 1024;

    vex::multivector<double, 4> x(ctx, n);

    x = std::integral_constant<int, 42>();
    check_sample(x, [](size_t, elem_t a) {
            for(size_t i = 0; i < 4; ++i) BOOST_CHECK_EQUAL(a[i], 42);
            });

    x = sin( vex::constants::e() * vex::element_index() );
    check_sample(x, [](size_t idx, elem_t a) {
            for(size_t i = 0; i < 4; ++i)
                BOOST_CHECK_CLOSE(a[i], sin(boost::math::constants::e<double>() * idx), 1e-8);
            });
}

#if (VEXCL_CHECK_SIZES > 0)
BOOST_AUTO_TEST_CASE(expression_size_check)
{
    vex::multivector<int, 2> x(ctx, 16);
    vex::multivector<int, 2> y(ctx, 32);

    BOOST_CHECK_THROW(x = y, std::runtime_error);
}
#endif

BOOST_AUTO_TEST_SUITE_END()