/* ************************************************************************
 * Copyright 2016 Advanced Micro Devices, Inc.
 * ************************************************************************ */

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <boost/program_options.hpp>

#include "rocblas.h"
#include "utility.h"
#include "rocblas.hpp"
#include "testing_iamax.hpp"
#include "testing_iamin.hpp"
#include "testing_asum.hpp"
#include "testing_axpy.hpp"
#include "testing_copy.hpp"
#include "testing_dot.hpp"
#include "testing_swap.hpp"
#include "testing_gemv.hpp"
#include "testing_ger.hpp"
#include "testing_syr.hpp"
#include "testing_nrm2.hpp"
#include "testing_scal.hpp"
#include "testing_trtri.hpp"
#include "testing_trtri_batched.hpp"
#include "testing_geam.hpp"
#include "testing_set_get_vector.hpp"
#include "testing_set_get_matrix.hpp"
#if BUILD_WITH_TENSILE
#include "testing_gemm.hpp"
#include "testing_gemm_strided_batched.hpp"
#include "testing_gemm_kernel_name.hpp"
#include "testing_gemm_strided_batched_kernel_name.hpp"
#include "testing_trsm.hpp"
#include "testing_gemm_ex.hpp"
#include "testing_gemm_strided_batched_ex.hpp"
#endif

static int run_bench_test(const char* function, char precision, Arguments argus)
{
    static const char prefix[] = "testing_";
    if(!strncmp(function, prefix, sizeof(prefix) - 1))
    {
        function += sizeof(prefix) - 1;
    }

    if(!strcmp(function, "asum"))
    {
        if(precision == 's')
            testing_asum<float, float>(argus);
        else if(precision == 'd')
            testing_asum<double, double>(argus);
    }
    else if(!strcmp(function, "axpy"))
    {
        if(precision == 'h')
            testing_axpy<rocblas_half>(argus);
        else if(precision == 's')
            testing_axpy<float>(argus);
        else if(precision == 'd')
            testing_axpy<double>(argus);
    }
    else if(!strcmp(function, "copy"))
    {
        if(precision == 's')
            testing_copy<float>(argus);
        else if(precision == 'd')
            testing_copy<double>(argus);
    }
    else if(!strcmp(function, "dot"))
    {
        if(precision == 's')
            testing_dot<float>(argus);
        else if(precision == 'd')
            testing_dot<double>(argus);
    }
    else if(!strcmp(function, "swap"))
    {
        if(precision == 's')
            testing_swap<float>(argus);
        else if(precision == 'd')
            testing_swap<double>(argus);
    }
    else if(!strcmp(function, "iamax"))
    {
        if(precision == 's')
            testing_iamax<float>(argus);
        else if(precision == 'd')
            testing_iamax<double>(argus);
    }
    else if(!strcmp(function, "iamin"))
    {
        if(precision == 's')
            testing_iamin<float>(argus);
        else if(precision == 'd')
            testing_iamin<double>(argus);
    }
    else if(!strcmp(function, "nrm2"))
    {
        if(precision == 's')
            testing_nrm2<float, float>(argus);
        else if(precision == 'd')
            testing_nrm2<double, double>(argus);
    }
    else if(!strcmp(function, "scal"))
    {
        if(precision == 's')
            testing_scal<float>(argus);
        else if(precision == 'd')
            testing_scal<double>(argus);
    }
    else if(!strcmp(function, "gemv"))
    {
        if(precision == 's')
            testing_gemv<float>(argus);
        else if(precision == 'd')
            testing_gemv<double>(argus);
    }
    else if(!strcmp(function, "ger"))
    {
        if(precision == 's')
            testing_ger<float>(argus);
        else if(precision == 'd')
            testing_ger<double>(argus);
    }
    else if(!strcmp(function, "syr"))
    {
        if(precision == 's')
            testing_syr<float>(argus);
        else if(precision == 'd')
            testing_syr<double>(argus);
    }
    else if(!strcmp(function, "trtri"))
    {
        if(precision == 's')
            testing_trtri<float>(argus);
        else if(precision == 'd')
            testing_trtri<double>(argus);
    }
    else if(!strcmp(function, "trtri_batched"))
    {
        if(precision == 's')
            testing_trtri_batched<float>(argus);
        else if(precision == 'd')
            testing_trtri_batched<double>(argus);
    }
    else if(!strcmp(function, "geam"))
    {
        if(precision == 's')
            testing_geam<float>(argus);
        else if(precision == 'd')
            testing_geam<double>(argus);
    }
    else if(!strcmp(function, "set_get_vector"))
    {
        if(precision == 's')
            testing_set_get_vector<float>(argus);
        else if(precision == 'd')
            testing_set_get_vector<double>(argus);
    }
    else if(!strcmp(function, "set_get_matrix"))
    {
        if(precision == 's')
            testing_set_get_matrix<float>(argus);
        else if(precision == 'd')
            testing_set_get_matrix<double>(argus);
    }
#if BUILD_WITH_TENSILE
    else if(!strcmp(function, "gemm"))
    {
        // adjust dimension for GEMM routines
        rocblas_int min_lda = argus.transA_option == 'N' ? argus.M : argus.K;
        rocblas_int min_ldb = argus.transB_option == 'N' ? argus.K : argus.N;
        rocblas_int min_ldc = argus.M;

        if(argus.lda < min_lda)
        {
            std::cout << "rocblas-bench INFO: lda < min_lda, set lda = " << min_lda << std::endl;
            argus.lda = min_lda;
        }
        if(argus.ldb < min_ldb)
        {
            std::cout << "rocblas-bench INFO: ldb < min_ldb, set ldb = " << min_ldb << std::endl;
            argus.ldb = min_ldb;
        }
        if(argus.ldc < min_ldc)
        {
            std::cout << "rocblas-bench INFO: ldc < min_ldc, set ldc = " << min_ldc << std::endl;
            argus.ldc = min_ldc;
        }

        if(precision == 'h')
            testing_gemm<rocblas_half>(argus);
        else if(precision == 's')
            testing_gemm<float>(argus);
        else if(precision == 'd')
            testing_gemm<double>(argus);
    }
    else if(!strcmp(function, "gemm_ex"))
    {
        // adjust dimension for GEMM routines
        rocblas_int min_lda = argus.transA_option == 'N' ? argus.M : argus.K;
        rocblas_int min_ldb = argus.transB_option == 'N' ? argus.K : argus.N;
        rocblas_int min_ldc = argus.M;
        rocblas_int min_ldd = argus.M;

        if(argus.lda < min_lda)
        {
            std::cout << "rocblas-bench INFO: lda < min_lda, set lda = " << min_lda << std::endl;
            argus.lda = min_lda;
        }
        if(argus.ldb < min_ldb)
        {
            std::cout << "rocblas-bench INFO: ldb < min_ldb, set ldb = " << min_ldb << std::endl;
            argus.ldb = min_ldb;
        }
        if(argus.ldc < min_ldc)
        {
            std::cout << "rocblas-bench INFO: ldc < min_ldc, set ldc = " << min_ldc << std::endl;
            argus.ldc = min_ldc;
        }
        if(argus.ldd < min_ldd)
        {
            std::cout << "rocblas-bench INFO: ldd < min_ldd, set ldd = " << min_ldc << std::endl;
            argus.ldd = min_ldd;
        }
        testing_gemm_ex(argus);
    }
    else if(!strcmp(function, "gemm_strided_batched"))
    {
        // adjust dimension for GEMM routines
        rocblas_int min_lda = argus.transA_option == 'N' ? argus.M : argus.K;
        rocblas_int min_ldb = argus.transB_option == 'N' ? argus.K : argus.N;
        rocblas_int min_ldc = argus.M;
        if(argus.lda < min_lda)
        {
            std::cout << "rocblas-bench INFO: lda < min_lda, set lda = " << min_lda << std::endl;
            argus.lda = min_lda;
        }
        if(argus.ldb < min_ldb)
        {
            std::cout << "rocblas-bench INFO: ldb < min_ldb, set ldb = " << min_ldb << std::endl;
            argus.ldb = min_ldb;
        }
        if(argus.ldc < min_ldc)
        {
            std::cout << "rocblas-bench INFO: ldc < min_ldc, set ldc = " << min_ldc << std::endl;
            argus.ldc = min_ldc;
        }

        //      rocblas_int min_stride_a =
        //          argus.transA_option == 'N' ? argus.K * argus.lda : argus.M * argus.lda;
        //      rocblas_int min_stride_b =
        //          argus.transB_option == 'N' ? argus.N * argus.ldb : argus.K * argus.ldb;
        //      rocblas_int min_stride_a =
        //          argus.transA_option == 'N' ? argus.K * argus.lda : argus.M * argus.lda;
        //      rocblas_int min_stride_b =
        //          argus.transB_option == 'N' ? argus.N * argus.ldb : argus.K * argus.ldb;
        rocblas_int min_stride_c = argus.ldc * argus.N;
        //      if (argus.stride_a < min_stride_a)
        //      {
        //          std::cout << "rocblas-bench INFO: stride_a < min_stride_a, set stride_a = " <<
        //          min_stride_a << std::endl;
        //          argus.stride_a = min_stride_a;
        //      }
        //      if (argus.stride_b < min_stride_b)
        //      {
        //          std::cout << "rocblas-bench INFO: stride_b < min_stride_b, set stride_b = " <<
        //          min_stride_b << std::endl;
        //          argus.stride_b = min_stride_b;
        //      }
        if(argus.stride_c < min_stride_c)
        {
            std::cout << "rocblas-bench INFO: stride_c < min_stride_c, set stride_c = "
                      << min_stride_c << std::endl;
            argus.stride_c = min_stride_c;
        }

        if(precision == 'h')
            testing_gemm_strided_batched<rocblas_half>(argus);
        else if(precision == 's')
            testing_gemm_strided_batched<float>(argus);
        else if(precision == 'd')
            testing_gemm_strided_batched<double>(argus);
    }
    else if(!strcmp(function, "gemm_strided_batched_ex"))
    {
        // adjust dimension for GEMM routines
        rocblas_int min_lda = argus.transA_option == 'N' ? argus.M : argus.K;
        rocblas_int min_ldb = argus.transB_option == 'N' ? argus.K : argus.N;
        rocblas_int min_ldc = argus.M;
        rocblas_int min_ldd = argus.M;
        if(argus.lda < min_lda)
        {
            std::cout << "rocblas-bench INFO: lda < min_lda, set lda = " << min_lda << std::endl;
            argus.lda = min_lda;
        }
        if(argus.ldb < min_ldb)
        {
            std::cout << "rocblas-bench INFO: ldb < min_ldb, set ldb = " << min_ldb << std::endl;
            argus.ldb = min_ldb;
        }
        if(argus.ldc < min_ldc)
        {
            std::cout << "rocblas-bench INFO: ldc < min_ldc, set ldc = " << min_ldc << std::endl;
            argus.ldc = min_ldc;
        }
        if(argus.ldd < min_ldd)
        {
            std::cout << "rocblas-bench INFO: ldd < min_ldd, set ldd = " << min_ldc << std::endl;
            argus.ldd = min_ldd;
        }
        rocblas_int min_stride_c = argus.ldc * argus.N;
        if(argus.stride_c < min_stride_c)
        {
            std::cout << "rocblas-bench INFO: stride_c < min_stride_c, set stride_c = "
                      << min_stride_c << std::endl;
            argus.stride_c = min_stride_c;
        }

        testing_gemm_strided_batched_ex(argus);
    }
    else if(!strcmp(function, "gemm_kernel_name"))
    {
        // adjust dimension for GEMM routines
        rocblas_int min_lda = argus.transA_option == 'N' ? argus.M : argus.K;
        rocblas_int min_ldb = argus.transB_option == 'N' ? argus.K : argus.N;
        rocblas_int min_ldc = argus.M;
        if(argus.lda < min_lda)
        {
            std::cout << "rocblas-bench INFO: lda < min_lda, set lda = " << min_lda << std::endl;
            argus.lda = min_lda;
        }
        if(argus.ldb < min_ldb)
        {
            std::cout << "rocblas-bench INFO: ldb < min_ldb, set ldb = " << min_ldb << std::endl;
            argus.ldb = min_ldb;
        }
        if(argus.ldc < min_ldc)
        {
            std::cout << "rocblas-bench INFO: ldc < min_ldc, set ldc = " << min_ldc << std::endl;
            argus.ldc = min_ldc;
        }

        if(precision == 'h')
            testing_gemm_strided_batched_kernel_name<rocblas_half>(argus);
        else if(precision == 's')
            testing_gemm_strided_batched_kernel_name<float>(argus);
        else if(precision == 'd')
            testing_gemm_strided_batched_kernel_name<double>(argus);
    }
    else if(!strcmp(function, "gemm_strided_batched_kernel_name"))
    {
        // adjust dimension for GEMM routines
        rocblas_int min_lda = argus.transA_option == 'N' ? argus.M : argus.K;
        rocblas_int min_ldb = argus.transB_option == 'N' ? argus.K : argus.N;
        rocblas_int min_ldc = argus.M;
        if(argus.lda < min_lda)
        {
            std::cout << "rocblas-bench INFO: lda < min_lda, set lda = " << min_lda << std::endl;
            argus.lda = min_lda;
        }
        if(argus.ldb < min_ldb)
        {
            std::cout << "rocblas-bench INFO: ldb < min_ldb, set ldb = " << min_ldb << std::endl;
            argus.ldb = min_ldb;
        }
        if(argus.ldc < min_ldc)
        {
            std::cout << "rocblas-bench INFO: ldc < min_ldc, set ldc = " << min_ldc << std::endl;
            argus.ldc = min_ldc;
        }

        //      rocblas_int min_stride_a =
        //          argus.transA_option == 'N' ? argus.K * argus.lda : argus.M * argus.lda;
        //      rocblas_int min_stride_b =
        //          argus.transB_option == 'N' ? argus.N * argus.ldb : argus.K * argus.ldb;
        rocblas_int min_stride_c = argus.ldc * argus.N;
        //      if (argus.stride_a < min_stride_a)
        //      {
        //          std::cout << "rocblas-bench INFO: stride_a < min_stride_a, set stride_a = " <<
        //          min_stride_a << std::endl;
        //          argus.stride_a = min_stride_a;
        //      }
        //      if (argus.stride_b < min_stride_b)
        //      {
        //          std::cout << "rocblas-bench INFO: stride_b < min_stride_b, set stride_b = " <<
        //          min_stride_b << std::endl;
        //          argus.stride_b = min_stride_b;
        //      }
        if(argus.stride_c < min_stride_c)
        {
            std::cout << "rocblas-bench INFO: stride_c < min_stride_c, set stride_c = "
                      << min_stride_c << std::endl;
            argus.stride_c = min_stride_c;
        }

        if(precision == 'h')
            testing_gemm_strided_batched_kernel_name<rocblas_half>(argus);
        else if(precision == 's')
            testing_gemm_strided_batched_kernel_name<float>(argus);
        else if(precision == 'd')
            testing_gemm_strided_batched_kernel_name<double>(argus);
    }
    else if(!strcmp(function, "trsm"))
    {
        if(precision == 's')
            testing_trsm<float>(argus);
        else if(precision == 'd')
            testing_trsm<double>(argus);
    }
#endif
    else
    {
        printf("Invalid value for --function \n");
        return -1;
    }

    return 0;
}

static int rocblas_bench_datafile(const string& datafile)
{
    RocBLAS_PerfData::init(datafile);

    for(auto i = RocBLAS_PerfData::begin(); i != RocBLAS_PerfData::end(); ++i)
    {
        Arguments argus = *i;
        char precision;

        // disable unit_check in client benchmark, it is only used in gtest unit test
        argus.unit_check = 0;

        // enable timing check,otherwise no performance data collected
        argus.timing = 1;

        switch(argus.a_type)
        {
        case rocblas_datatype_f64_r: precision = 'd'; break;
        case rocblas_datatype_f32_r: precision = 's'; break;
        case rocblas_datatype_f16_r: precision = 'h'; break;
        case rocblas_datatype_f64_c: precision = 'z'; break;
        case rocblas_datatype_f32_c: precision = 'c'; break;
        case rocblas_datatype_f16_c: precision = 'k'; break;
        default: precision                     = 's'; break;
        }
        run_bench_test(argus.function, precision, argus);
    }

    return 0;
}

using namespace boost::program_options;

int main(int argc, char* argv[])
{
    Arguments argus;
    argus.unit_check =
        0;            // disable unit_check in client benchmark, it is only used in gtest unit test
    argus.timing = 1; // enable timing check,otherwise no performance data collected

    std::string function;
    char precision;
    char a_type;
    char b_type;
    char c_type;
    char d_type;
    char compute_type;

    rocblas_int device_id;
    std::string datafile;

    options_description desc("rocblas client command line options");
    desc.add_options()
        // clang-format off
        ("sizem,m",
         value<rocblas_int>(&argus.M)->default_value(128),
         "Specific matrix size: sizem is only applicable to BLAS-2 & BLAS-3: the number of "
         "rows or columns in matrix.")

        ("sizen,n",
         value<rocblas_int>(&argus.N)->default_value(128),
         "Specific matrix/vector size: BLAS-1: the length of the vector. BLAS-2 & "
         "BLAS-3: the number of rows or columns in matrix")

        ("sizek,k",
         value<rocblas_int>(&argus.K)->default_value(128),
         "Specific matrix size:sizek is only applicable to BLAS-3: the number of columns in "
         "A and rows in B.")

        ("lda",
         value<rocblas_int>(&argus.lda)->default_value(128),
         "Leading dimension of matrix A, is only applicable to BLAS-2 & BLAS-3.")

        ("ldb",
         value<rocblas_int>(&argus.ldb)->default_value(128),
         "Leading dimension of matrix B, is only applicable to BLAS-2 & BLAS-3.")

        ("ldc",
         value<rocblas_int>(&argus.ldc)->default_value(128),
         "Leading dimension of matrix C, is only applicable to BLAS-2 & BLAS-3.")

        ("ldd",
         value<rocblas_int>(&argus.ldd)->default_value(128),
         "Leading dimension of matrix D, is only applicable to BLAS-EX ")

        ("stride_a",
         value<rocblas_int>(&argus.stride_a)->default_value(128*128),
         "Specific stride of strided_batched matrix A, is only applicable to strided batched"
         "BLAS-2 and BLAS-3: second dimension * leading dimension.")

        ("stride_b",
         value<rocblas_int>(&argus.stride_b)->default_value(128*128),
         "Specific stride of strided_batched matrix B, is only applicable to strided batched"
         "BLAS-2 and BLAS-3: second dimension * leading dimension.")

        ("stride_c",
         value<rocblas_int>(&argus.stride_c)->default_value(128*128),
         "Specific stride of strided_batched matrix C, is only applicable to strided batched"
         "BLAS-2 and BLAS-3: second dimension * leading dimension.")

        ("stride_d",
         value<rocblas_int>(&argus.stride_d)->default_value(128*128),
         "Specific stride of strided_batched matrix D, is only applicable to strided batched"
         "BLAS_EX: second dimension * leading dimension.")

        ("incx",
         value<rocblas_int>(&argus.incx)->default_value(1),
         "increment between values in x vector")

        ("incy",
         value<rocblas_int>(&argus.incy)->default_value(1),
         "increment between values in y vector")

        ("alpha",
          value<double>(&argus.alpha)->default_value(1.0), "specifies the scalar alpha")

        ("beta",
         value<double>(&argus.beta)->default_value(0.0), "specifies the scalar beta")

        ("function,f",
         value<std::string>(&function)->default_value("gemv"),
         "BLAS function to test. Options: gemv, ger, syr, trsm, trmm, symv, syrk, syr2k")

        ("precision,r",
         value<char>(&precision)->default_value('s'), "Options: h,s,d,c,z")

        ("a_type",
         value<char>(&a_type)->default_value('s'), "Options: h,s,d,c,z"
         "Precision of matrix A, only applicable to BLAS_EX")

        ("b_type",
         value<char>(&b_type)->default_value('s'), "Options: h,s,d,c,z"
         "Precision of matrix B, only applicable to BLAS_EX")

        ("c_type",
         value<char>(&c_type)->default_value('s'), "Options: h,s,d,c,z"
         "Precision of matrix C, only applicable to BLAS_EX")

        ("d_type",
         value<char>(&d_type)->default_value('s'), "Options: h,s,d,c,z"
         "Precision of matrix D, only applicable to BLAS_EX")

        ("compute_type",
         value<char>(&compute_type)->default_value('s'), "Options: h,s,d,c,z"
         "Precision of computation, only applicable to BLAS_EX")

        ("transposeA",
         value<char>(&argus.transA_option)->default_value('N'),
         "N = no transpose, T = transpose, C = conjugate transpose")

        ("transposeB",
         value<char>(&argus.transB_option)->default_value('N'),
         "N = no transpose, T = transpose, C = conjugate transpose")

        ("side",
         value<char>(&argus.side_option)->default_value('L'),
         "L = left, R = right. Only applicable to certain routines")

        ("uplo",
         value<char>(&argus.uplo_option)->default_value('U'),
         "U = upper, L = lower. Only applicable to certain routines") // xsymv xsyrk xsyr2k xtrsm
                                                                     // xtrmm
        ("diag",
         value<char>(&argus.diag_option)->default_value('N'),
         "U = unit diagonal, N = non unit diagonal. Only applicable to certain routines") // xtrsm
                                                                                // xtrmm
        ("batch",
         value<rocblas_int>(&argus.batch_count)->default_value(1),
         "Number of matrices. Only applicable to batched routines") // xtrsm xtrmm xgemm

        ("verify,v",
         value<rocblas_int>(&argus.norm_check)->default_value(0),
         "Validate GPU results with CPU? 0 = No, 1 = Yes (default: No)")

        ("iters,i",
         value<rocblas_int>(&argus.iters)->default_value(10),
         "Iterations to run inside timing loop")

        ("algo",
         value<uint32_t>(&argus.algo)->default_value(0),
         "extended precision gemm algorithm")

        ("solution_index",
         value<int32_t>(&argus.solution_index)->default_value(0),
         "extended precision gemm solution index")

        ("flags",
         value<uint32_t>(&argus.flags)->default_value(10),
         "extended precision gemm flags")

        ("workspace_size",
         value<size_t>(&argus.workspace_size)->default_value(10),
         "extended precision gemm workspace size")

        ("data",
         value<string>(&datafile),
         "Data file to use for test arguments (overrides all of the above)")

        ("device",
         value<rocblas_int>(&device_id)->default_value(0),
         "Set default device to be used for subsequent program runs")

        ("help,h", "produces this help message");
    // clang-format on

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

    if(vm.count("help"))
    {
        std::cout << desc << std::endl;
        return 0;
    }

    // Device Query
    rocblas_int device_count = query_device_property();

    std::cout << std::endl;
    if(device_count <= device_id)
    {
        printf("Error: Invalid device ID. There may not be such device ID. Will exit \n");
        return -1;
    }
    else
    {
        set_device(device_id);
    }

    if(datafile != "")
    {
        return rocblas_bench_datafile(datafile);
    }

    if(!strchr("hsdcz", tolower(precision)))
    {
        std::cerr << "Invalid value for --precision" << std::endl;
        return -1;
    }

    argus.a_type = char2rocblas_datatype(a_type);
    if(argus.a_type == static_cast<rocblas_datatype>(-1))
    {
        std::cerr << "Invalid value for --a_type" << std::endl;
        return -1;
    }

    argus.b_type = char2rocblas_datatype(b_type);
    if(argus.b_type == static_cast<rocblas_datatype>(-1))
    {
        std::cerr << "Invalid value for --b_type" << std::endl;
        return -1;
    }

    argus.c_type = char2rocblas_datatype(c_type);
    if(argus.c_type == static_cast<rocblas_datatype>(-1))
    {
        std::cerr << "Invalid value for --c_type" << std::endl;
        return -1;
    }

    argus.d_type = char2rocblas_datatype(d_type);
    if(argus.d_type == static_cast<rocblas_datatype>(-1))
    {
        std::cerr << "Invalid value for --d_type" << std::endl;
        return -1;
    }

    argus.compute_type = char2rocblas_datatype(compute_type);
    if(argus.compute_type == static_cast<rocblas_datatype>(-1))
    {
        std::cerr << "Invalid value for --compute_type" << std::endl;
        return -1;
    }

    if(argus.M < 0 || argus.N < 0 || argus.K < 0)
    {
        printf("Invalid matrix dimension\n");
    }

    return run_bench_test(function.c_str(), precision, argus);
}


#include <iostream> // cerr
#include <random> // mt19937_64, uniform_x_distribution
#include <vector>
#include <boost/config.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/johnson_all_pairs_shortest.hpp>

#include "johnson.hpp"

graph_t *johnson_init(const int n, const double p, const unsigned long seed) {
  static std::uniform_real_distribution<double> flip(0, 1);
  static std::uniform_int_distribution<int> choose_weight(1, 100);

  std::mt19937_64 rand_engine(seed);

  int *adj_matrix = new int[n * n];
  size_t E = 0;
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < n; j++) {
      if (i == j) {
        adj_matrix[i*n + j] = 0;
      } else if (flip(rand_engine) < p) {
        adj_matrix[i*n + j] = choose_weight(rand_engine);
        E ++;
      } else {
        adj_matrix[i*n + j] = INT_MAX;
      }
    }
  }
  Edge *edge_array = new Edge[E];
  int *weights = new int[E];
  int ei = 0;
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < n; j++) {
      if (adj_matrix[i*n + j] != 0
          && adj_matrix[i*n + j] != INT_MAX) {
        edge_array[ei] = Edge(i,j);
        weights[ei] = adj_matrix[i*n + j];
        ei++;
      }
    }
  }

  delete[] adj_matrix;

  graph_t *gr = new graph_t;
  gr->V = n;
  gr->E = E;
  gr->edge_array = edge_array;
  gr->weights = weights;

  return gr;
}

#ifdef CUDA
void free_graph_cuda(graph_cuda_t *g) {
  delete[] g->edge_array;
  delete[] g->weights;
  delete g;
}

void set_edge(edge_t *edge, int u, int v) {
  edge->u = u;
  edge->v = v;
}

graph_cuda_t *johnson_cuda_init(const int n, const double p, const unsigned long seed) {
  static std::uniform_real_distribution<double> flip(0, 1);
  static std::uniform_int_distribution<int> choose_weight(1, 100);

  std::mt19937_64 rand_engine(seed);

  int *adj_matrix = new int[n * n];
  size_t E = 0;
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < n; j++) {
      if (i == j) {
        adj_matrix[i*n + j] = 0;
      } else if (flip(rand_engine) < p) {
        adj_matrix[i*n + j] = choose_weight(rand_engine);
        E ++;
      } else {
        adj_matrix[i*n + j] = INT_MAX;
      }
    }
  }
  edge_t *edge_array = new edge_t[E];
  int* starts = new int[n + 1];  // Starting point for each edge
  int* weights = new int[E];
  int ei = 0;
  for (int i = 0; i < n; i++) {
    starts[i] = ei;
    for (int j = 0; j < n; j++) {
      if (adj_matrix[i*n + j] != 0
          && adj_matrix[i*n + j] != INT_MAX) {
        set_edge(&edge_array[ei], i, j);
        weights[ei] = adj_matrix[i*n + j];
        ei++;
      }
    }
  }
  starts[n] = ei; // One extra

  delete[] adj_matrix;

  graph_cuda_t *gr = new graph_cuda_t;
  gr->V = n;
  gr->E = E;
  gr->edge_array = edge_array;
  gr->weights = weights;
  gr->starts = starts;

  return gr;
}

void free_cuda_graph(graph_cuda_t* g) {
  delete[] g->edge_array;
  delete[] g->weights;
  delete[] g->starts;
  delete g;
}

#endif

void free_graph(graph_t* g) {
  delete[] g->edge_array;
  delete[] g->weights;
  delete g;
}

inline bool bellman_ford(graph_t* gr, int* dist, int src) {
  int V = gr->V;
  int E = gr->E;
  Edge* edges = gr->edge_array;
  int* weights = gr->weights;

#ifdef _OPENMP
#pragma omp parallel for
#endif
  for (int i = 0; i < V; i++) {
    dist[i] = INT_MAX;
  }
  dist[src] = 0;

  for (int i = 1; i <= V-1; i++) {
#ifdef _OPENMP
#pragma omp parallel for
#endif
    for (int j = 0; j < E; j++) {
      int u = std::get<0>(edges[j]);
      int v = std::get<1>(edges[j]);
      int new_dist = weights[j] + dist[u];
      if (dist[u] != INT_MAX && new_dist < dist[v])
        dist[v] = new_dist;
    }
  }

  bool no_neg_cycle = true;
#ifdef _OPENMP
#pragma omp parallel for
#endif
  for (int i = 0; i < E; i++) {
    int u = std::get<0>(edges[i]);
    int v = std::get<1>(edges[i]);
    int weight = weights[i];
    if (dist[u] != INT_MAX && dist[u] + weight < dist[v])
      no_neg_cycle = false;
  }
  return no_neg_cycle;
}

void johnson_parallel(graph_t* gr, int* output) {

  int V = gr->V;

  // Make new graph for Bellman-Ford
  // First, a new node q is added to the graph, connected by zero-weight edges
  // to each of the other nodes.
  graph_t* bf_graph = new graph_t;
  bf_graph->V = V + 1;
  bf_graph->E = gr->E + V;
  bf_graph->edge_array = new Edge[bf_graph->E];
  bf_graph->weights = new int[bf_graph->E];

  std::memcpy(bf_graph->edge_array, gr->edge_array, gr->E  * sizeof(Edge));
  std::memcpy(bf_graph->weights, gr->weights, gr->E * sizeof(int));
  std::memset(&bf_graph->weights[gr->E], 0, V * sizeof(int));

#ifdef _OPENMP
#pragma omp parallel for
#endif
  for (int e = 0; e < V; e++) {
    bf_graph->edge_array[e + gr->E] = Edge(V, e);
  }

  // Second, the Bellman–Ford algorithm is used, starting from the new vertex q,
  // to find for each vertex v the minimum weight h(v) of a path from q to v. If
  // this step detects a negative cycle, the algorithm is terminated.
  // TODO Can run parallel version?
  int* h = new int[bf_graph->V];
  bool r = bellman_ford(bf_graph, h, V);
  if (!r) {
    std::cerr << "\nNegative Cycles Detected! Terminating Early\n";
    exit(1);
  }
  // Next the edges of the original graph are reweighted using the values computed
  // by the Bellman–Ford algorithm: an edge from u to v, having length
  // w(u,v), is given the new length w(u,v) + h(u) − h(v).
#ifdef _OPENMP
#pragma omp parallel for
#endif
  for (int e = 0; e < gr->E; e++) {
    int u = std::get<0>(gr->edge_array[e]);
    int v = std::get<1>(gr->edge_array[e]);
    gr->weights[e] = gr->weights[e] + h[u] - h[v];
  }

  Graph G(gr->edge_array, gr->edge_array + gr->E, gr->weights, V);

#ifdef _OPENMP
#pragma omp parallel for schedule(dynamic)
#endif
  for (int s = 0; s < V; s++) {
    std::vector<int> d(num_vertices(G));
    dijkstra_shortest_paths(G, s, distance_map(&d[0]));
    for (int v = 0; v < V; v++) {
      output[s*V + v] = d[v] + h[v] - h[s];
    }
  }

  delete[] h;
  free_graph(bf_graph);
}


// Do not include this line twice in your project!
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION

#include <memory>
#include <random>

#include <Eigen/Core>

#include "camera.hpp"
#include "color_factory.hpp"
#include "image.hpp"
#include "log.hpp"
#include "orthographic_projection.hpp"
#include "ray_tracing_renderer.hpp"
#include "renderer.hpp"
#include "scene.hpp"
#include "sphere.hpp"

Scene createScene1(); // With accretion disk
Scene createScene2(); // Without accretion disk
Scene createScene3(); // With accretion disk but without black hole distortion
Scene createScene4(); // Without accretion disk nor black hole distortion
Scene createScene5(); // Without accretion disk but with black hole distortion
Scene createScene6(); // With background texture and no black hole
Scene createScene7(); // With background texture and black hole but no accretion disk
Scene createScene8(); // With background texture, black hole and accretion disk

std::shared_ptr<Renderer> createRenderer1(int width, int height);

void task1();
void task2();
void task3();
void task4();
void task5();
void task6();
void task7();
void task8();
void task9();
void task10();

int main() {
    // LOG_SET_DEBUG();
    task1();
    task2();
    task3();
    task4();
    task5();
    task6();
    task7();
    task8();
    task9();
    task10();
    return 0;
}

Scene createScene1() {
    std::random_device r;
    std::default_random_engine generator {0};

    Scene scene("Scene 1");

    // Creating stars distributed in a sphere
    float radius;
    float x, y, z;
    float rho, theta, psi;
    std::uniform_real_distribution<float> radius_distribution(0.03, 0.1);
    std::uniform_real_distribution<float> rho_distribution(4.0, 9.0);
    std::uniform_real_distribution<float> theta_distribution(-M_PI, M_PI);
    std::uniform_real_distribution<float> psi_distribution(-M_PI/4, M_PI/4);
    for (int i = 0; i < 100; i++) {
        radius = radius_distribution(generator);
        rho = rho_distribution(generator);
        theta = theta_distribution(generator);
        psi = psi_distribution(generator);

        x = rho*sin(psi)*cos(theta);
        y = rho*sin(psi)*sin(theta);
        z = rho*cos(psi);

        scene.addStar(radius, Eigen::Vector4d(x, y, z, 1));
    }

    scene.addBlackHole(0.25, Eigen::Vector4d(0, 0, 0, 1));
    scene.addAccretionDisk(1.75, 0.75, Eigen::Vector4d(0, 0, 0, 1), Eigen::Vector4d(4, -30, 1, 0).normalized());
    return scene;
}

Scene createScene2() {
    std::random_device r;
    std::default_random_engine generator {0};

    Scene scene("Scene 2");

    // Creating stars distributed in a sphere
    float radius;
    float x, y, z;
    float rho, theta, psi;
    std::uniform_real_distribution<float> radius_distribution(0.005, 0.12);
    std::uniform_real_distribution<float> rho_distribution(4.0, 9.0);
    std::uniform_real_distribution<float> theta_distribution(-M_PI, M_PI);
    std::uniform_real_distribution<float> psi_distribution(-M_PI/4, M_PI/4);
    for (int i = 0; i < 100; i++) {
        radius = radius_distribution(generator);
        rho = rho_distribution(generator);
        theta = theta_distribution(generator);
        psi = psi_distribution(generator);

        x = rho*sin(psi)*cos(theta);
        y = rho*sin(psi)*sin(theta);
        z = rho*cos(psi);

        scene.addStar(radius, Eigen::Vector4d(x, y, z, 1));
    }

    scene.addBlackHole(0.25, Eigen::Vector4d(0, 0, 0, 1));

    return scene;
}

Scene createScene3() {
    std::random_device r;
    std::default_random_engine generator {0};

    Scene scene("Scene 3");

    // Creating stars distributed in a sphere
    float radius;
    float x, y, z;
    float rho, theta, psi;
    std::uniform_real_distribution<float> radius_distribution(0.005, 0.12);
    std::uniform_real_distribution<float> rho_distribution(4.0, 9.0);
    std::uniform_real_distribution<float> theta_distribution(-M_PI, M_PI);
    std::uniform_real_distribution<float> psi_distribution(-M_PI/4, M_PI/4);
    for (int i = 0; i < 100; i++) {
        radius = radius_distribution(generator);
        rho = rho_distribution(generator);
        theta = theta_distribution(generator);
        psi = psi_distribution(generator);

        x = rho*sin(psi)*cos(theta);
        y = rho*sin(psi)*sin(theta);
        z = rho*cos(psi);

        scene.addStar(radius, Eigen::Vector4d(x, y, z, 1));
    }

    scene.addAccretionDisk(1.75, 0.75, Eigen::Vector4d(0, 0, 0, 1), Eigen::Vector4d(4, -30, 1, 0).normalized());
    return scene;
}

Scene createScene4() {
    std::random_device r;
    std::default_random_engine generator {0};

    Scene scene("Scene 4");

    // Creating stars distributed in a sphere
    float radius;
    float x, y, z;
    float rho, theta, psi;
    std::uniform_real_distribution<float> radius_distribution(0.005, 0.12);
    std::uniform_real_distribution<float> rho_distribution(4.0, 9.0);
    std::uniform_real_distribution<float> theta_distribution(-M_PI, M_PI);
    std::uniform_real_distribution<float> psi_distribution(-M_PI/4, M_PI/4);
    for (int i = 0; i < 100; i++) {
        radius = radius_distribution(generator);
        rho = rho_distribution(generator);
        theta = theta_distribution(generator);
        psi = psi_distribution(generator);

        x = rho*sin(psi)*cos(theta);
        y = rho*sin(psi)*sin(theta);
        z = rho*cos(psi);

        scene.addStar(radius, Eigen::Vector4d(x, y, z, 1));
    }

    return scene;
}

Scene createScene5() {
    std::random_device r;
    std::default_random_engine generator {0};

    Scene scene("Scene 5");

    // Creating stars distributed in a sphere
    float radius;
    float x, y, z;
    float rho, theta, psi;
    std::uniform_real_distribution<float> radius_distribution(0.005, 0.12);
    std::uniform_real_distribution<float> rho_distribution(4.0, 9.0);
    std::uniform_real_distribution<float> theta_distribution(-M_PI, M_PI);
    std::uniform_real_distribution<float> psi_distribution(-M_PI/4, M_PI/4);
    for (int i = 0; i < 100; i++) {
        radius = radius_distribution(generator);
        rho = rho_distribution(generator);
        theta = theta_distribution(generator);
        psi = psi_distribution(generator);

        x = rho*sin(psi)*cos(theta);
        y = rho*sin(psi)*sin(theta);
        z = rho*cos(psi);

        scene.addStar(radius, Eigen::Vector4d(x, y, z, 1));
    }

    scene.addBlackHole(0.25, Eigen::Vector4d(0, 0, 0, 1));

    return scene;
}

Scene createScene6() {
    std::random_device r;
    std::default_random_engine generator {0};

    Scene scene("Scene 6");

    scene.addUniverse(5, Eigen::Vector4d(0, 0, 0, 1));
    return scene;
}

Scene createScene7() {
    std::random_device r;
    std::default_random_engine generator {0};

    Scene scene("Scene 7");

    scene.addUniverse(5, Eigen::Vector4d(0, 0, 0, 1));
    scene.addBlackHole(0.25, Eigen::Vector4d(0, 0, 0, 1));
    return scene;
}

Scene createScene8() {
    std::random_device r;
    std::default_random_engine generator {0};

    Scene scene("Scene 8");

    scene.addUniverse(5, Eigen::Vector4d(0, 0, 0, 1));
    scene.addBlackHole(0.25, Eigen::Vector4d(0, 0, 0, 1));
    scene.addAccretionDisk(1.75, 0.75, Eigen::Vector4d(0, 0, 0, 1), Eigen::Vector4d(4, -30, 1, 0).normalized());
    return scene;
}

std::shared_ptr<Renderer> createRenderer1(int width, int height) {
    std::shared_ptr<Viewport> viewport = std::make_shared<Viewport>(width, height);

    std::shared_ptr<Projection> projection = std::make_shared<OrthographicProjection>(-1, 1, -1, 1, -1, 1);

    Eigen::Vector4d cameraPositionPoint(0, 0, -2, 1);
    Eigen::Vector4d cameraGazeDirection(0, 0, 1, 0);
    Eigen::Vector4d cameraViewUpDirection(0, -1, 0, 0);
    std::shared_ptr<Camera> camera = std::make_shared<Camera>(cameraPositionPoint, cameraGazeDirection, cameraViewUpDirection);

    camera->setVelocityVector(Eigen::Vector4d(-0.1, 0, 0, 0));

    std::shared_ptr<Renderer> renderer = std::make_shared<RayTracingRenderer>(viewport, projection, camera);

    return renderer;
}

void task1() {
    Scene scene = createScene1();
    LOG_I("Running task 1");
    std::shared_ptr<Renderer> renderer = createRenderer1(300, 300);
    Image image = renderer->render(scene, Renderer::PerspectiveProjection);
    image.save("task1.png");
}

void task2() {
    Scene scene = createScene1();
    LOG_I("Running task 2");
    std::shared_ptr<Renderer> renderer = createRenderer1(300, 300);
    renderer->animate(scene, 10, 0.25, "task2", Renderer::PerspectiveProjection);
}

void task3() {
    Scene scene = createScene2();
    LOG_I("Running task 3");
    std::shared_ptr<Renderer> renderer = createRenderer1(300, 300);
    renderer->animate(scene, 10, 0.25, "task3", Renderer::PerspectiveProjection);
}

void task4() {
    Scene scene = createScene3();
    LOG_I("Running task 4");
    std::shared_ptr<Renderer> renderer = createRenderer1(300, 300);
    Image image = renderer->render(scene, Renderer::PerspectiveProjection);
    image.save("task4.png");
}

void task5() {
    Scene scene = createScene4();
    LOG_I("Running task 5");
    std::shared_ptr<Renderer> renderer = createRenderer1(300, 300);
    Image image = renderer->render(scene, Renderer::PerspectiveProjection);
    image.save("task5.png");
}

void task6() {
    Scene scene = createScene5();
    LOG_I("Running task 6");
    std::shared_ptr<Renderer> renderer = createRenderer1(300, 300);
    Image image = renderer->render(scene, Renderer::PerspectiveProjection);
    image.save("task6.png");
}

void task7() {
    Scene scene = createScene6();
    LOG_I("Running task 7");
    std::shared_ptr<Renderer> renderer = createRenderer1(600, 600);
    renderer->animate(scene, 10, 0.25, "task7", Renderer::PerspectiveProjection);
}

void task8() {
    Scene scene = createScene7();
    LOG_I("Running task 8");
    std::shared_ptr<Renderer> renderer = createRenderer1(600, 600);
    renderer->animate(scene, 10, 0.25, "task8", Renderer::PerspectiveProjection);
}

void task9() {
    Scene scene = createScene8();
    LOG_I("Running task 9");
    std::shared_ptr<Renderer> renderer = createRenderer1(600, 600);
    renderer->animate(scene, 10, 0.25, "task9", Renderer::PerspectiveProjection);
}

void task10() {
    Scene scene = createScene8();
    LOG_I("Running task 10");
    std::shared_ptr<Renderer> renderer = createRenderer1(600, 600);
    Image image = renderer->render(scene, Renderer::PerspectiveProjection);
    image.save("task10.png");
}


#ifndef NUMERICS_HPP
#define NUMERICS_HPP

#include <functional>
#include <cmath>
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <queue>
#include <set>
#include <memory>

#include <omp.h>

#define ARMA_USE_SUPERLU 1 // optional, but really should be used when handling sparse matrices
#include <armadillo>

/* Copyright 2019 Amit Rotem

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. */

namespace numerics {
    #include <numerics/utility.hpp>
    #include <numerics/interpolation.hpp>
    #include <numerics/neural_network.hpp>
    #include <numerics/data_science.hpp>
    #include <numerics/derivatives.hpp>
    #include <numerics/integrals.hpp>
    #include <numerics/optimization.hpp>
    #include <numerics/ode.hpp>
}

#endif


// Copyright (c) 2017 Graphcore Ltd. All rights reserved.
#include "Constraint.hpp"

#include "Scheduler.hpp"

#include <popsolver/Model.hpp>

#include <poplibs_support/Visitor.hpp>

#include <boost/multiprecision/cpp_int.hpp>
#include <boost/range/iterator_range.hpp>

#include <limits>

using namespace popsolver;

using BigInteger = boost::multiprecision::uint128_t;
static_assert(
    sizeof(BigInteger) >= sizeof(DataType::UnderlyingType) * 2,
    "BigInteger isn't large enough to perform operations on DataType");

Constraint::~Constraint() = default;

bool Product::propagate(Scheduler &scheduler) {
  const Domains &domains = scheduler.getDomains();
  const auto result = vars[0];
  const auto left = vars[1];
  const auto right = vars[2];

  bool madeChange;
  do {
    madeChange = false;

    // Check if the result doesn't match the inputs
    // left[max] * right[max] = result[max]
    // left[min] * right[min] = result[min]
    const BigInteger maxProduct =
        BigInteger{*domains[left].max()} * BigInteger{*domains[right].max()};
    const BigInteger minProduct =
        BigInteger{*domains[left].min()} * BigInteger{*domains[right].min()};
    if (minProduct > *domains[result].max() ||
        maxProduct < *domains[result].min()) {
      return false;
    }
    if (minProduct > *domains[result].min()) {
      // This is fine to do unchecked because minProduct is less than the
      // value in *domains[result].max() which is limited to DataType. Otherwise
      // We would have already bailed out.
      scheduler.setMin(result,
                       popsolver::DataType{
                           minProduct.convert_to<DataType::UnderlyingType>()});
      madeChange = true;
    }
    if (maxProduct < *domains[result].max()) {
      // This is fine to do unchecked because we are less than the value of
      // *domains[result].max(), which is of type DataType.
      scheduler.setMax(result,
                       popsolver::DataType{
                           maxProduct.convert_to<DataType::UnderlyingType>()});
      madeChange = true;
    }

    // Check if the inputs do not match the result
    // We shortcut some calculations avoiding a costly divide by noting:
    // result[min] <= left[max] * right[min] <= result[max]
    // result[min] <= left[min] * right[max] <= result[max]

    // If we want to reduce the value of left[max] we can check if it
    // is valid by comparing against result[max].
    //
    // If (left[max] * right[min] <= result[max]) is not true,
    // we can reduce the value of left[max] to be result[max] / right[min]. And
    // likewise for all other permutations.

    // left[max] * right[min] <= result[max]
    if (domains[right].min() != popsolver::DataType{0} &&
        BigInteger{*domains[right].min()} * BigInteger{*domains[left].max()} >
            BigInteger{*domains[result].max()}) {
      auto newLeftMax = domains[result].max() / domains[right].min();
      assert(newLeftMax < domains[left].max());
      if (newLeftMax < domains[left].min())
        return false;
      scheduler.setMax(left, newLeftMax);
      madeChange = true;
    }
    // left[min] * right[max] <= result[max]
    if (domains[left].min() != popsolver::DataType{0} &&
        BigInteger{*domains[left].min()} * BigInteger{*domains[right].max()} >
            BigInteger{*domains[result].max()}) {
      auto newRightMax = domains[result].max() / domains[left].min();
      assert(newRightMax < domains[right].max());
      if (newRightMax < domains[right].min())
        return false;
      scheduler.setMax(right, newRightMax);
      madeChange = true;
    }

    // left[min] * right[max] >= result[min]
    if (domains[right].max() != popsolver::DataType{0} &&
        BigInteger{*domains[right].max()} * BigInteger{*domains[left].min()} <
            BigInteger{*domains[result].min()}) {
      auto newLeftMin =
          domains[result].min() / domains[right].max() +
          popsolver::DataType{(domains[result].min() % domains[right].max() !=
                               popsolver::DataType{0})};
      if (newLeftMin > domains[left].min()) {
        if (newLeftMin > domains[left].max())
          return false;
        scheduler.setMin(left, newLeftMin);
        madeChange = true;
      }
    }
    // left[max] * right[min] >= result[min]
    if (domains[left].max() != popsolver::DataType{0} &&
        BigInteger{*domains[left].max()} * BigInteger{*domains[right].min()} <
            BigInteger{*domains[result].min()}) {
      auto newRightMin =
          domains[result].min() / domains[left].max() +
          popsolver::DataType{(domains[result].min() % domains[left].max() !=
                               popsolver::DataType{0})};
      if (newRightMin > domains[right].min()) {
        if (newRightMin > domains[right].max())
          return false;
        scheduler.setMin(right, newRightMin);
        madeChange = true;
      }
    }
  } while (madeChange);
  return true;
}

bool Sum::propagate(Scheduler &scheduler) {
  const Domains &domains = scheduler.getDomains();
  const auto result = vars[0];
  const auto args =
      boost::make_iterator_range(std::begin(vars) + 1, std::end(vars));

  // The data type used to store the max sum must be large enough to store
  // the maximum value of the result plus the maximum value of any operand so
  // that we can compute the max sum of a subset containing all but one variable
  // by subtracting the the maximum value of the variable from the max sum.
  BigInteger minSum = 0;
  BigInteger maxSum = 0;
  for (const auto &v : args) {
    minSum = minSum + *domains[v].min();
    maxSum = maxSum + *domains[v].max();
  }
  if (minSum > *domains[result].max() || maxSum < *domains[result].min()) {
    return false;
  }
  if (minSum > *domains[result].min()) {
    scheduler.setMin(
        result,
        popsolver::DataType{minSum.convert_to<DataType::UnderlyingType>()});
  }
  if (maxSum < *domains[result].max()) {
    scheduler.setMax(
        result,
        popsolver::DataType{maxSum.convert_to<DataType::UnderlyingType>()});
  }
  for (const auto &v : args) {
    auto &domain = domains[v];
    auto minOtherVarsSum = minSum - *domain.min();
    if (minOtherVarsSum > *domains[result].max())
      return false;
    auto newMax = *domains[result].max() - minOtherVarsSum;
    if (newMax < *domain.min())
      return false;
    if (newMax < *domain.max())
      scheduler.setMax(v, popsolver::DataType{
                              newMax.convert_to<DataType::UnderlyingType>()});
    auto maxOtherVarsSum = maxSum - *domain.max();
    if (maxOtherVarsSum < *domains[result].min()) {
      auto newMin = *domains[result].min() - maxOtherVarsSum;
      if (newMin > *domain.max())
        return false;
      if (newMin > *domain.min())
        scheduler.setMin(v, popsolver::DataType{
                                newMin.convert_to<DataType::UnderlyingType>()});
    }
  }
  return true;
}

bool Max::propagate(Scheduler &scheduler) {
  const Domains &domains = scheduler.getDomains();
  const auto result = vars[0];
  const auto args =
      boost::make_iterator_range(std::begin(vars) + 1, std::end(vars));

  // give A = max(B, C), we can deduce:
  //  - upperbound(A) = min(upperbound(A), max(upperbound(B), upperbound(C))),
  //  - lowerbound(A) = max(lowerbound(A), max(lowerbound(B), lowerbound(C))),
  //  - upperbound(B) = min(upperbound(A), upperbound(B)),
  //  - upperbound(C) = min(upperbound(A), upperbound(C))
  // propagation will fail if:
  //  - upperbound(A) < max(lowerbound(B), lowerbound(C)),
  //  - lowerbound(A) > max(upperbound(B), upperbound(C))

  const auto resultLower = domains[result].min();
  const auto resultUpper = domains[result].max();

  auto maxLowerBound = DataType::min();
  auto minLowerBound = DataType::max();
  auto maxUpperBound = DataType::min();
  auto minUpperBound = DataType::max();

  for (const auto &var : args) {
    if (domains[var].min() > resultUpper) {
      return false;
    } else if (domains[var].max() > resultUpper) {
      scheduler.setMax(var, resultUpper);
    }

    maxLowerBound = std::max(maxLowerBound, domains[var].min());
    minLowerBound = std::min(minLowerBound, domains[var].min());
    maxUpperBound = std::max(maxUpperBound, domains[var].max());
    minUpperBound = std::min(minUpperBound, domains[var].max());
  }

  assert(resultUpper >= maxLowerBound);
  if (resultLower > maxUpperBound) {
    return false;
  }

  if (resultUpper > maxUpperBound) {
    scheduler.setMax(result, maxUpperBound);
  }

  if (resultLower < maxLowerBound) {
    scheduler.setMin(result, maxLowerBound);
  }

  return true;
}

bool Min::propagate(Scheduler &scheduler) {
  const Domains &domains = scheduler.getDomains();
  const auto result = vars[0];
  const auto args =
      boost::make_iterator_range(std::begin(vars) + 1, std::end(vars));

  // give A = min(B, C), we can deduce:
  //  - upperbound(A) = min(upperbound(A), min(upperbound(B), upperbound(C))),
  //  - lowerbound(A) = max(lowerbound(A), min(lowerbound(B), lowerbound(C))),
  //  - lowerbound(B) = max(lowerbound(A), lowerbound(B)),
  //  - lowerbound(C) = max(lowerbound(A), lowerbound(C))
  // propagation will fail if:
  //  - upperbound(A) < max(lowerbound(B), lowerbound(C)),
  //  - lowerbound(A) > max(upperbound(B), upperbound(C))

  const auto resultLower = domains[result].min();
  const auto resultUpper = domains[result].max();

  auto maxLowerBound = DataType::min();
  auto minLowerBound = DataType::max();
  auto maxUpperBound = DataType::min();
  auto minUpperBound = DataType::max();

  for (const auto &var : args) {
    if (domains[var].max() < resultLower) {
      return false;
    } else if (domains[var].min() < resultLower) {
      scheduler.setMin(var, resultLower);
    }

    maxLowerBound = std::max(maxLowerBound, domains[var].min());
    minLowerBound = std::min(minLowerBound, domains[var].min());
    maxUpperBound = std::max(maxUpperBound, domains[var].max());
    minUpperBound = std::min(minUpperBound, domains[var].max());
  }

  assert(resultLower <= minUpperBound);
  if (resultUpper < minLowerBound) {
    return false;
  }

  if (resultUpper > minUpperBound) {
    scheduler.setMax(result, minUpperBound);
  }

  if (resultLower < minLowerBound) {
    scheduler.setMin(result, minLowerBound);
  }

  return true;
}

bool Less::propagate(Scheduler &scheduler) {
  const Domains &domains = scheduler.getDomains();
  const auto left = vars[0];
  const auto right = vars[1];

  if (domains[left].min() >= domains[right].max()) {
    return false;
  }
  if (domains[left].min() >= domains[right].min()) {
    scheduler.setMin(right, domains[left].min() + popsolver::DataType{1});
  }
  if (domains[right].max() <= domains[left].max()) {
    scheduler.setMax(left, domains[right].max() - popsolver::DataType{1});
  }
  return true;
}

bool LessOrEqual::propagate(Scheduler &scheduler) {
  const Domains &domains = scheduler.getDomains();
  const auto left = vars[0];
  const auto right = vars[1];

  if (domains[left].min() > domains[right].max()) {
    return false;
  }
  if (domains[left].min() > domains[right].min()) {
    scheduler.setMin(right, domains[left].min());
  }
  if (domains[right].max() < domains[left].max()) {
    scheduler.setMax(left, domains[right].max());
  }
  return true;
}

template <> bool GenericAssignment<DataType>::propagate(Scheduler &scheduler) {
  const Domains &domains = scheduler.getDomains();
  for (std::size_t i = 1; i != vars.size(); ++i) {
    const auto domain = domains[vars[i]];
    if (domain.size() > popsolver::DataType{1}) {
      return true;
    }
    values[i - 1] = domain.val();
  }

  const auto x = f(values);

  if (!x) {
    return false;
  }
  const auto result = vars[0];
  if (x.get() < domains[result].min() || x.get() > domains[result].max()) {
    return false;
  }
  if (domains[result].min() != x.get() || domains[result].max() != x.get()) {
    scheduler.set(result, x.get());
  }
  return true;
}

template <typename T>
bool GenericAssignment<T>::propagate(Scheduler &scheduler) {
  const Domains &domains = scheduler.getDomains();
  for (std::size_t i = 1; i != vars.size(); ++i) {
    // Input operands must be with the range allowed for the data type
    const auto &domain = domains[vars[i]];
    if (domain.min() > popsolver::DataType{std::numeric_limits<T>::max()} ||
        domain.max() < popsolver::DataType{std::numeric_limits<T>::min()}) {
      return false;
    }
    if (domain.min() < popsolver::DataType{std::numeric_limits<T>::min()}) {
      scheduler.setMin(vars[i],
                       popsolver::DataType{std::numeric_limits<T>::min()});
    }
    if (domain.max() > popsolver::DataType{std::numeric_limits<T>::max()}) {
      scheduler.setMax(vars[i],
                       popsolver::DataType{std::numeric_limits<T>::max()});
    }
  }

  for (std::size_t i = 1; i != vars.size(); ++i) {
    const auto domain = domains[vars[i]];
    if (domain.size() > popsolver::DataType{1}) {
      return true;
    }
    values[i - 1] = domain.val();
  }

  std::vector<T> castedValues{};
  castedValues.reserve(values.size());
  for (auto v : values) {
    castedValues.push_back(v.template getAs<T>());
  }

  const auto x = f(castedValues);

  if (!x) {
    return false;
  }
  const auto result = vars[0];
  if (x.get() < domains[result].min() || x.get() > domains[result].max()) {
    return false;
  }
  if (domains[result].min() != x.get() || domains[result].max() != x.get()) {
    scheduler.set(result, x.get());
  }
  return true;
}

template class popsolver::GenericAssignment<DataType>;
template class popsolver::GenericAssignment<unsigned>;
template class popsolver::GenericAssignment<uint64_t>;


#include <catch2/catch.hpp>

#include "../src/lib/cpplib.h"
#include <Eigen/Core>
#include <boost/math/quadrature/trapezoidal.hpp>
#include <tbb/parallel_for.h>

using namespace Catch::literals;

#if (__clang__)
  #pragma clang diagnostic push
  #pragma clang diagnostic ignored "-Wsign-conversion"
  #pragma clang diagnostic ignored "-Wconversion"
  #pragma clang diagnostic ignored "-Wold-style-cast"
  #pragma clang diagnostic ignored "-Wshadow"
  #include <EigenRand/EigenRand>
  #pragma clang diagnostic pop
#elif (__GNUC__)|| defined(__GNUG__)
  #pragma GCC diagnostic push
  #pragma GCC diagnostic ignored "-Wsign-conversion"
  #pragma GCC diagnostic ignored "-Wconversion"
  #pragma GCC diagnostic ignored "-Wold-style-cast"
  #pragma GCC diagnostic ignored "-Wuseless-cast"
  #include <EigenRand/EigenRand>
  #pragma GCC diagnostic pop
#elif (_MSC_VER)
  # pragma warning(disable: 4245)
  #include <EigenRand/EigenRand>
#endif

unsigned int Factorial(unsigned int number)// NOLINT(misc-no-recursion)
{
  return number <= 1 ? number : Factorial(number - 1) * number;
}

TEST_CASE("Factorials are computed", "[factorial]")
{
  REQUIRE(Factorial(1) == 1);
  REQUIRE(Factorial(2) == 2);
  REQUIRE(Factorial(3) == 6);
  REQUIRE(Factorial(10) == 3628800);
}

TEST_CASE("FindMax", "[cpplib]") {
  CPPLib cpplib;

  SECTION("Should find max of consecutive numbers") {
    std::vector<int> inputs = {1, 2, 3, 4};
    REQUIRE(cpplib.FindMax(inputs) == 4);
  }

  SECTION("Should find max of non consecutive numbers") {
    std::vector<int> inputs = {1, 7, 3, 4};
    REQUIRE(cpplib.FindMax(inputs) == 7);
  }

  SECTION("Should find max with negative numbers") {
    std::vector<int> inputs = {-1, -7, -3, -4};
    REQUIRE(cpplib.FindMax(inputs) == -1);
  }

  SECTION("Should find max of all equal values") {
    std::vector<int> inputs = {-1, -1, -1, -1};
    REQUIRE(cpplib.FindMax(inputs) == -1);
  }

  SECTION("Should find return min for empty vector") {
    std::vector<int> inputs = {};
    REQUIRE(cpplib.FindMax(inputs) == std::numeric_limits<int>::min());
  }

  SECTION("Should find return min for empty vector") {
    std::vector<int> inputs = {1000};
    REQUIRE(cpplib.FindMax(inputs) == 1000);
  }
}

TEST_CASE("TBB parallel for", "[TBB]")
{
  auto values = std::vector<double>(10000);
  tbb::parallel_for( tbb::blocked_range<size_t>(0,values.size()),
                       [&](tbb::blocked_range<size_t> r){
    for (size_t i=r.begin(); i<r.end(); ++i){
      values[i] = std::sin(static_cast<double>(i) * 0.001);
    }
  });

  double total = 0;
  for (double value : values){
    total += value;
  }
  //REQUIRE(total == Approx(1839.3433863759381));
  REQUIRE(1 == 1);
}


// Created by Dinies on 23/11/2018.

#pragma once
#include <unistd.h>
#include <Eigen/Core>

namespace MultiRobot {

  class Gaussian{
    private:

    double m_expFactor;
    Eigen::Vector2d m_center;

    public:
    Gaussian( const double t_expFactor, const Eigen::Vector2d &t_center );

    double eval( const double t_x, const double t_y);

    double computeDistrib( const double t_x, const double t_center);
  };
}


#include <catch.hpp>
#include <tmock.hpp>

#include <boost/filesystem.hpp>

unsigned int Factorial1( unsigned int number ) {
    return number <= 1 ? number : Factorial1(number-1)*number;
}

TEST_CASE( "Factorials are computed once more", "[factorial]" )
{
    REQUIRE( Factorial1(1) == 1 );
    REQUIRE( Factorial1(2) == 2 );
    REQUIRE( Factorial1(3) == 6 );
    REQUIRE( Factorial1(10) == 3628800 );
}

TEST_CASE("Cmake has placed data correctly", "")
{
    CHECK(boost::filesystem::exists( "./test_data/example_test_catch/test1/data.txt" ));
}


//****************************************************************************
// (c) 2008, 2009 by the openOR Team
//****************************************************************************
// The contents of this file are available under the GPL v2.0 license
// or under the openOR comercial license. see
//   /Doc/openOR_free_license.txt or
//   /Doc/openOR_comercial_license.txt
// for Details.
//****************************************************************************
//!
//! @file
//! @ingroup openOR_core_math

#ifndef openOR_core_Math_matrix_types_hpp
#define openOR_core_Math_matrix_types_hpp

#include <boost/numeric/ublas/fwd.hpp>

namespace openOR {
   namespace Math {

      //----------------------------------------------------------------------------
      // convenience typedefs
      //----------------------------------------------------------------------------

      typedef boost::numeric::ublas::bounded_matrix<int, 2, 2, boost::numeric::ublas::column_major>          Matrix22i;    //!< @ingroup openOR_core_math
      typedef boost::numeric::ublas::bounded_matrix<unsigned int, 2, 2, boost::numeric::ublas::column_major> Matrix22ui;   //!< @ingroup openOR_core_math
      typedef boost::numeric::ublas::bounded_matrix<float, 2, 2, boost::numeric::ublas::column_major>        Matrix22f;    //!< @ingroup openOR_core_math
      typedef boost::numeric::ublas::bounded_matrix<double, 2, 2, boost::numeric::ublas::column_major>       Matrix22d;    //!< @ingroup openOR_core_math

      typedef boost::numeric::ublas::bounded_matrix<int, 3, 3, boost::numeric::ublas::column_major>          Matrix33i;    //!< @ingroup openOR_core_math
      typedef boost::numeric::ublas::bounded_matrix<unsigned int, 3, 3, boost::numeric::ublas::column_major> Matrix33ui;   //!< @ingroup openOR_core_math
      typedef boost::numeric::ublas::bounded_matrix<float, 3, 3, boost::numeric::ublas::column_major>        Matrix33f;    //!< @ingroup openOR_core_math
      typedef boost::numeric::ublas::bounded_matrix<double, 3, 3, boost::numeric::ublas::column_major>       Matrix33d;    //!< @ingroup openOR_core_math

      typedef boost::numeric::ublas::bounded_matrix<int, 4, 4, boost::numeric::ublas::column_major>          Matrix44i;    //!< @ingroup openOR_core_math
      typedef boost::numeric::ublas::bounded_matrix<unsigned int, 4, 4, boost::numeric::ublas::column_major> Matrix44ui;   //!< @ingroup openOR_core_math
      typedef boost::numeric::ublas::bounded_matrix<float, 4, 4, boost::numeric::ublas::column_major>        Matrix44f;    //!< @ingroup openOR_core_math
      typedef boost::numeric::ublas::bounded_matrix<double, 4, 4, boost::numeric::ublas::column_major>       Matrix44d;    //!< @ingroup openOR_core_math

   }
}

#endif


// C_error_policy_example.cpp

// Copyright Paul A. Bristow 2007, 2010.
// Copyright John Maddock 2007.

// Use, modification and distribution are subject to the
// Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)

// Suppose we want a call to tgamma  to behave in a C-compatible way
// and set global ::errno rather than throw an exception.

#include <cerrno> // for ::errno

#include <boost/math/special_functions/gamma.hpp>
using boost::math::tgamma;

using boost::math::policies::policy;
// Possible errors
using boost::math::policies::overflow_error;
using boost::math::policies::underflow_error;
using boost::math::policies::domain_error;
using boost::math::policies::pole_error;
using boost::math::policies::denorm_error;
using boost::math::policies::evaluation_error;

using boost::math::policies::errno_on_error;
using boost::math::policies::ignore_error;

//using namespace boost::math::policies;
//using namespace boost::math; // avoid potential ambiguity with std:: <random>

// Define a policy:
typedef policy<
      domain_error<errno_on_error>, // 'bad' arguments.
      pole_error<errno_on_error>, // argument is pole value.
      overflow_error<errno_on_error>, // argument value causes overflow.
      evaluation_error<errno_on_error>  // evaluation does not converge and may be inaccurate, or worse,
      // or there is no way  known (yet) to implement this evaluation,
      // for example, kurtosis of non-central beta distribution.
      > C_error_policy;

// std
#include <iostream>
   using std::cout;
   using std::endl;

int main()
{
  // We can achieve this at the function call site
  // with the previously defined policy C_error_policy.
  double t = tgamma(4., C_error_policy());
  cout << "tgamma(4., C_error_policy() = " << t << endl; // 6

  // Alternatively we could use the function make_policy,
  // provided for convenience,
  // and define everything at the call site:
  t = tgamma(4., make_policy(
         domain_error<errno_on_error>(),
         pole_error<errno_on_error>(),
         overflow_error<errno_on_error>(),
         evaluation_error<errno_on_error>()
      ));
  cout << "tgamma(4., make_policy(...) = " << t << endl; // 6

  return 0;
} // int main()

/*

Output

  c_error_policy_example.cpp
  Generating code
  Finished generating code
  c_error_policy_example.vcxproj -> J:\Cpp\MathToolkit\test\Math_test\Release\c_error_policy_example.exe
  tgamma(4., C_error_policy() = 6
  tgamma(4., make_policy(...) = 6
  tgamma(4., C_error_policy() = 6
  tgamma(4., make_policy(...) = 6

*/


// Boost.Geometry (aka GGL, Generic Geometry Library)
// Unit Test

// Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2008-2012 Bruno Lalande, Paris, France.
// Copyright (c) 2009-2012 Mateusz Loskot, London, UK.

// This file was modified by Oracle on 2017, 2018.
// Modifications copyright (c) 2017-2018, Oracle and/or its affiliates.
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Parts of Boost.Geometry are redesigned from Geodan's Geographic Library
// (geolib/GGL), copyright (c) 1995-2010 Geodan, Amsterdam, the Netherlands.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#if defined(_MSC_VER)
#pragma warning( disable : 4305 ) // truncation double -> float
#endif // defined(_MSC_VER)

#define BOOST_GEOMETRY_SRS_ENABLE_STATIC_PROJECTION_HYBRID_INTERFACE

#include <geometry_test_common.hpp>

#include <boost/geometry/srs/projection.hpp>

#include <boost/geometry/algorithms/transform.hpp>
#include <boost/geometry/core/coordinate_type.hpp>

#include <boost/geometry/geometries/geometries.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/adapted/c_array.hpp>
#include <test_common/test_point.hpp>

namespace srs = bg::srs;

template <typename P1, typename P2, typename Params>
void test_one(double lon, double lat,
              typename bg::coordinate_type<P2>::type x,
              typename bg::coordinate_type<P2>::type y,
              Params const& params)
{
    // hybrid interface disabled by default
    // static_proj4 default ctor, dynamic parameters passed
    srs::projection<Params> prj(params);

    P1 ll;
    bg::set<0>(ll, lon);
    bg::set<1>(ll, lat);

    P2 xy;
    prj.forward(ll, xy);

    BOOST_CHECK_CLOSE(bg::get<0>(xy), x, 0.001);
    BOOST_CHECK_CLOSE(bg::get<1>(xy), y, 0.001);
}

template <typename P>
void test_all()
{
    typedef typename bg::coordinate_type<P>::type coord_type;
    typedef bg::model::point<coord_type, 2, bg::cs::geographic<bg::degree> > point_type;

    using namespace srs::spar;

    // aea
    test_one<point_type, P>
        (4.897000, 52.371000, 334609.583974, 5218502.503686,
         parameters<proj_aea, ellps_wgs84, units_m, lat_1<>, lat_2<> >(
             proj_aea(), ellps_wgs84(), units_m(), lat_1<>(55), lat_2<>(65)));
}

BOOST_GEOMETRY_REGISTER_C_ARRAY_CS(bg::cs::cartesian)

int test_main(int, char* [])
{
    //test_all<int[2]>();
    test_all<float[2]>();
    test_all<double[2]>();

    // 2D -> 3D
    //test_all<test::test_point>();

    //test_all<bg::model::d2::point_xy<int> >();
    test_all<bg::model::d2::point_xy<float> >();
    test_all<bg::model::d2::point_xy<double> >();
    test_all<bg::model::d2::point_xy<long double> >();

    return 0;
}


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

using namespace std;
using namespace Eigen;

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

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

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

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

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

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

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

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

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

double eigenProjsDistance(vector<double> proj1, vector<double> proj2){

    double res =0.0;
    for (int i=0; i<min(proj1.size(),proj2.size());i++){
        res+=(proj1[i]-proj2[i])*(proj1[i]-proj2[i]);
    }
    return res;
}

int main(int argc, char* argv[]){

    //recup eigenfaces

    vector<TempImgLine> eigenfaces;
    int K =200; int nH;int nW;

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

    string dirIn = string(argv[1]);

    //vector<vector<int*>> histos = vector<vector<int*>>();

    vector<vector<vector<double>>> registre = vector<vector<vector<double>>>();

    int countDirs =0;
    for (auto & dir : std::filesystem::directory_iterator(dirIn)){
        registre.push_back(vector<vector<double>>());
        int countFile=0;
        for (auto & file : std::filesystem::directory_iterator(dir.path())){
            OCTET* img;
            lire_nb_lignes_colonnes_image_pgm(file.path().c_str(),&nH,&nW);
            TempImgLine imgLine(nH*nW);
            allocation_tableau(img,OCTET, nH*nW);
            lire_image_pgm(file.path().c_str(),img,nH*nW);
            double sumIm=0.0;
            for (int i=0;i<nH*nW;i++){sumIm+=(double)(img[i]-127)*(double)(img[i]-127);}
            for (int i=0; i<nH*nW;i++){imgLine(i)=(double)(img[i]-127)/sumIm;}
            registre[countDirs].push_back(projectOnEigenSpace(eigenfaces,imgLine,K));

            countFile++;
        }
        countDirs++;
    }

    for (double threshold=0.0;threshold<0.0000005;threshold+=0.00000001){
        int VP=0,VN=0,FP=0,FN=0;

        for (int i=0;i<registre.size();i++){
            for (int j=0; j<registre[i].size();j++){
                for (int ip=0;ip<registre.size();ip++){
                    for (int jp=0;jp<registre[ip].size();jp++){
                        if (!(i==ip && j==jp)){
                            //cout<<eigenProjsDistance(registre[i][j],registre[ip][jp])<<endl;
                            if (eigenProjsDistance(registre[i][j],registre[ip][jp])<threshold){
                                if (i==ip){
                                    VP++;
                                }
                                else{
                                    FP++;
                                }
                            }

                        else{
                                if (i==ip){
                                    FN++;
                                }
                                else{
                                    VN++;
                                }
                            }

                        }
                    }
                }
            }
        }

        double sensitivite = (double)((double)VP/(double)(VP+FN));
        double antispecificite = 1.0-(double)((double)VN/(double)(VN+FP));
        double distance = antispecificite*antispecificite + (1-sensitivite)*(1-sensitivite);
        cout<<antispecificite<<" "<<sensitivite<<" "<<distance<<" "<<threshold<<endl;
    }

}