/*
 * library_def.h
 *
 *  Created on: 2019. 9. 3.
 *      Author: Misun Yu, Dongsik Choi
 */

#ifndef LIBRARY_H_
#define LIBRARY_H_

//#define _DEBUG
//#define THREAD_MATMUL
//#define _USE_OPENBLAS_MATMUL
//#define _USE_OPENBLAS_CONV

//#define _CONV_ORIGINAL
//#define _CONV_GROUP_ORIGINAL
//#define _USE_LLVM 1

#ifdef __MACH__
#include <stdlib.h>
#else
#include <malloc.h>
#endif

#include <vector>
#include <string.h>
#include <math.h>
#include <iostream>
#include <fstream>

#include <algorithm>
#include <vector>
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
//#include <omp.h>
#include <time.h>
#include <assert.h>

#ifdef _USE_OPENBLAS_MATMUL
#include <cblas.h>
#else

#ifdef _USE_OPENBLAS_CONV
#include <cblas.h>
#else

#endif

#endif

#ifdef __MACH__
#include <dispatch/dispatch.h>
#else
#include <semaphore.h>
#endif

using namespace std;

//#define FLOAT    0
//#define DOUBLE   1
//#define INT      2
//#define CHAR     3

typedef int8_t i8;
typedef uint8_t ui8;
typedef int16_t i16;
typedef int32_t i32;

#if defined(__MACH__) || defined(_USE_LLVM)
#define MAX_PAD_SIZE 3 //msyu
#endif

using namespace std;

void read_vector(vector<size_t>* vec, ifstream* readFileBin);

/*
 *
 *
 * Batch Normalization
 *
 *
 */

template <typename T1, size_t N1, size_t N2, size_t N3, size_t N4,
        typename T2>
void splat(T1 (&in1)[N1][N2][N3][N4], int axis, T2 (&result)[N1][N2][N3][N4])
{
#ifdef _DEBUG
    cout << "splat()-4D" << endl;
#endif

//	end = clock();
//    cout << "==> [time] : " << (((double)(end - start)) / CLOCKS_PER_SEC) << " seconds" << endl;

}

template <typename T1, size_t K1, size_t L1, size_t M1, size_t N1,
        typename T2, size_t K2, size_t L2, size_t M2, size_t N2,
        typename T3, size_t K3, size_t L3, size_t M3, size_t N3>
void concat(int axis, T1 (&in1)[N1][M1][K1][L1], T2 (&in2)[N2][M2][K2][L2], T3 (&result)[N3][M3][K3][L3])
{
#ifdef _DEBUG
    cout << "concat()-4D" << endl;
    cout << "axis: " << axis << endl;
#endif
    if(axis == 3) {
        for(size_t i = 0; i < N3; i++) {
            for(size_t j = 0; j < M3; j++) {
                for(size_t k = 0; k < K3; k++) {
                    for(size_t l = 0; l < L3; l++) {
                        if(l < L1) {
                            result[i][j][k][l] = in1[i][j][k][l];
                        } else {
                            result[i][j][k][l] = in2[i][j][k][l-L1];
                        }
                    }
                }
            }
        }
    } else {
        cout << "Not supported. " << axis << endl;
    }
    //	end = clock();
    //    cout << "==> [time] : " << (((double)(end - start)) / CLOCKS_PER_SEC) << " seconds" << endl;

}

template <typename T1, size_t N, size_t M, size_t K, size_t L>
void batchNorm(T1 (&in)[N][M][K][L], T1 (&scale)[M], T1 (&bias)[M], T1 (&mean)[M], T1 (&var)[M], unsigned int channelIdx, T1 epsilon, T1 momentum, T1 (&result)[N][M][K][L])
{
    //NCHW
//
#ifdef _DEBUG
    cout << "batchNorm() - 1" << endl;
  cout << "channel: " << channelIdx << endl;
  cout << "N: " << N << endl;
  cout << "M: " << M << endl;
  cout << "K: " << K << endl;
  cout << "L: " << L << endl;
#endif

    for(size_t i = 0; i < N; i++) {
        for(size_t j = 0; j < M; j++) {
            for(size_t k = 0; k < K; k++) {
                for(size_t l = 0; l < L; l++) {
                    float norm = (in[i][j][k][l] - mean[j])/sqrtf(epsilon + var[j]);
                    result[i][j][k][l] = scale[j]* norm + bias[j];
                }
            }
        }
    }
}

template <typename T1, size_t N, size_t M, size_t K, size_t L>
void batchNorm(T1 (&in)[N][L][K][M], T1 (&scale)[M], T1 (&bias)[M], T1 (&mean)[M], T1 (&var)[M], unsigned int channelIdx, T1 epsilon, T1 momentum, T1 (&result)[N][L][K][M])
{
    //NCHW
//
#ifdef _DEBUG
    cout << "batchNorm() - 2" << endl;
    cout << "channel: " << channelIdx << endl;
    cout << "N: " << N << endl;
    cout << "L: " << L << endl;
    cout << "K: " << K << endl;
    cout << "M: " << M << endl;

#endif

    for(size_t i = 0; i < N; i++) {
        for(size_t j = 0; j < L; j++) {
            for(size_t k = 0; k < K; k++) {
                for(size_t l = 0; l < M; l++) {
                    float norm = (in[i][j][k][l] - mean[l])/sqrtf(epsilon + var[l]);
                    result[i][j][k][l] = scale[l]* norm + bias[l];
                }
            }
        }
    }
}

/*
 *
 *
 * Convolution
 *
 *
 */

extern int conv_idx;

#ifdef _CONV_ORIGINAL

template <typename T1, size_t BATCH, size_t HEIGHT, size_t WIDTH, size_t CHANNEL,
        typename T2, size_t FILTER_SIZE, size_t FILTER_HEIGHT, size_t FILTER_WIDTH, size_t FILTER_CHANNEL,
        typename T3, size_t N2,
        typename T4, size_t N3, size_t M3, size_t K3, size_t L3>
void conv(T1 (&in1)[BATCH][HEIGHT][WIDTH][CHANNEL], T2 (&filter)[FILTER_SIZE][FILTER_HEIGHT][FILTER_WIDTH][FILTER_CHANNEL], vector<size_t> filter_dim, T3 (&bias)[N2], \
	vector<size_t> stride, vector<size_t> pad, int group, T4 (&result)[N3][M3][K3][L3])
{
#ifdef _DEBUG
    cout << conv_idx++ << ": conv()-original" << endl;

    cout << "BATCH: " << BATCH << endl;
    cout << "HEIGHT: " << HEIGHT << endl;
    cout << "WIDTH: " << WIDTH << endl;
    cout << "CHANNEL: " << CHANNEL << endl;
    cout << "pad: " << pad.at(0) << endl;
    cout << "stride: " << stride.at(0) << endl;
    cout << "FILTER_HEIGHT: " << FILTER_HEIGHT << endl;
    cout << "FILTER_WIDTH: " << FILTER_WIDTH << endl;
    cout << "FILTER_CHANNEL: " << FILTER_CHANNEL << endl;
    //N, H, W, C
    //N == 1
    //group == 1
#endif

    int pad_xleft = pad.at(0);
    int pad_xright = pad.at(1);
    int pad_ytop = pad.at(2);
    int pad_ybottom = pad.at(3);
    int xadd = pad_xleft + pad_xright;
    int yadd = pad_ytop + pad_ybottom;

    int stride_size = stride.at(0);

    int filter_h = filter_dim.at(1);
    int filter_w = filter_dim.at(2);
    int filter_size = filter_dim.at(0);

    int out_h = (HEIGHT + yadd - filter_h)/stride_size + 1;
    int out_w = (WIDTH + xadd - filter_w)/stride_size + 1;

#ifdef _DEBUG
    cout << "FILTER_SIZE: " << FILTER_SIZE << endl;
    cout << "out_h: " << out_h << endl;
    cout << "out_w: " << out_w << endl;

    struct timespec tp, ep;
    double timeCheck;
    clock_gettime(CLOCK_MONOTONIC, &tp);
#endif

#if defined(__MACH__) || defined(_USE_LLVM)
    float (* newin)[BATCH][HEIGHT+MAX_PAD_SIZE*2][WIDTH+MAX_PAD_SIZE*2][CHANNEL];
    newin = (float (*)[BATCH][HEIGHT+MAX_PAD_SIZE*2][WIDTH+MAX_PAD_SIZE*2][CHANNEL])malloc(sizeof(float[BATCH][HEIGHT+MAX_PAD_SIZE*2][WIDTH+MAX_PAD_SIZE*2][CHANNEL]));
    memset(newin, 0x00, sizeof(float[BATCH][HEIGHT+MAX_PAD_SIZE*2][WIDTH+MAX_PAD_SIZE*2][CHANNEL]));
#else
    float (* newin)[BATCH][HEIGHT+yadd][WIDTH+xadd][CHANNEL];
    newin = (float (*)[BATCH][HEIGHT+yadd][WIDTH+xadd][CHANNEL])malloc(sizeof(float[BATCH][HEIGHT+yadd][WIDTH+xadd][CHANNEL]));
    memset(newin, 0x00, sizeof(float[BATCH][HEIGHT+yadd][WIDTH+xadd][CHANNEL]));
#endif

    for (int b = 0; b < BATCH; b++){
        for (size_t c = 0; c < CHANNEL; c++){
            for (int h = 0; h < HEIGHT; h++){
                for (int w = 0; w < WIDTH; w++){
                    (*newin)[b][pad_ytop+h][pad_xleft+w][c] = in1[b][h][w][c];
                }
            }
        }
    }

    int new_h_idx = 0, new_w_idx = 0;
    for (int b = 0; b < BATCH; b++){
        for(int fc = 0; fc < filter_size; fc++) {
            for(int i = 0; i <out_h; i++) {
                for(int j = 0; j <out_w; j++) {

                    float total = 0.0;
                    for(int fh = 0; fh <filter_h; fh++) {
                        for(int fw = 0; fw <filter_w; fw++) {
                            for(size_t f = 0; f < CHANNEL; f++) {
                                total = total + (*newin)[b][new_h_idx+fh][new_w_idx+fw][f]*filter[fc][fh][fw][f];
                            }
                        }
                    }
                    result[b][i][j][fc] = total + bias[fc];
                    new_w_idx += stride_size;
                }
                new_h_idx += stride_size;
                new_w_idx = 0;
            }
            new_h_idx = 0;
        }
    }

#ifdef _DEBUG
    free(newin);
    clock_gettime(CLOCK_MONOTONIC, &ep);
    timeCheck = ((ep.tv_sec - tp.tv_sec) + (ep.tv_nsec - tp.tv_nsec) / 1000000000.0);
    cout << "==================== " + to_string(conv_idx++) + " convolution time : " << timeCheck <<" seconds  ====================" << endl << endl;
#endif
}
#else

#ifdef __MACH__
extern dispatch_semaphore_t    semaphore;
#else
extern    sem_t  semaphore;
#endif

// 스레드 수 정의
#define MAX_THREAD 2

//스레드를 사용하기 위한 구조체.
template <typename T1, typename T2, typename T3, int tN, int tM, int tM1>
struct tTHREAD{
    T1(*arrr)[tM];
    T2(*brrr1)[tM][tM1];
    T3(*crrr1)[tN][tM1];
    int b;
    int cnt;
};
// 스레드 함수
template <typename T1, typename T2, typename T3, int tN, int tM, int tM1>
void *multi(void *num)
{
    struct tTHREAD<float, float, float, tN, tM, tM1> *numMul = (struct tTHREAD<float, float, float, tN, tM, tM1> *)num;
    // cnt는 현재 사용되려는 스레드 번호.
    int core = numMul->cnt;
    int B = numMul->b;

    int mN = tN / MAX_THREAD * (core + 1);
    if (core == MAX_THREAD - 1) mN = tN;

    for (int i = core * (tN / MAX_THREAD); i < mN; i++) {// 각 스레드는 행렬 행 기준으로 스레드 개수만큼 나눠서 연산
        for (int k = 0; k < tM; k++) {
            for (int j = 0; j < tM1; j++) {
                numMul->crrr1[B][i][j] += numMul->arrr[i][k] * numMul->brrr1[B][k][j];
            }
        }
    }

#ifdef __MACH__
    dispatch_semaphore_signal(semaphore);
#else
    sem_post(&semaphore);
#endif

    return nullptr;
}

#ifdef _CONV_GROUP_ORIGINAL

template <typename T1, size_t BATCH, size_t HEIGHT, size_t WIDTH, size_t CHANNEL,
        typename T2, size_t FILTER_SIZE, size_t FILTER_HEIGHT, size_t FILTER_WIDTH, size_t FILTER_CHANNEL,
        typename T3, size_t N2,
        typename T4, size_t N3, size_t M3, size_t K3, size_t L3>
void conv_group(T1 (&in1)[BATCH][HEIGHT][WIDTH][CHANNEL], T2 (&filter)[FILTER_SIZE][FILTER_HEIGHT][FILTER_WIDTH][FILTER_CHANNEL], vector<size_t> kernel_dim, T3 (&bias)[N2], \
	vector<size_t> stride, vector<size_t> pad, int group, T4 (&result)[N3][M3][K3][L3])
{
#ifdef _DEBUG
    cout << "conv_group()-original" << endl;

    cout << "BATCH: " << BATCH << endl;
    cout << "HEIGHT: " << HEIGHT << endl;
    cout << "WIDTH: " << WIDTH << endl;
    cout << "CHANNEL: " << CHANNEL << endl;
    cout << "pad: " << pad.at(0) << endl;
    cout << "stride: " << stride.at(0) << endl;
    cout << "FILTER_SIZE: " << FILTER_SIZE << endl;
    cout << "FILTER_HEIGHT: " << FILTER_HEIGHT << endl;
    cout << "FILTER_WIDTH: " << FILTER_WIDTH << endl;
    cout << "FILTER_CHANNEL: " << FILTER_CHANNEL << endl;

    cout << "OUT BATCH: " << N3 << endl;
    cout << "OUT HEIGHT: " << M3 << endl;
    cout << "OUT WIDTH: " << K3 << endl;
    cout << "OUT CHANNEL: " << L3 << endl;

    cout << "group: " << group << endl;
    //N, H, W, C
    //N == 1
    //group == 1
#endif

    int pad_xleft = pad.at(0);
    int pad_xright = pad.at(1);
    int pad_ytop = pad.at(2);
    int pad_ybottom = pad.at(3);
    int xadd = pad_xleft + pad_xright;
    int yadd = pad_ytop + pad_ybottom;

    int stride_size = stride.at(0);

    int kernel_h = kernel_dim.at(1);
    int kernel_w = kernel_dim.at(2);
    int kernel_size = kernel_dim.at(0);

    int out_h = (HEIGHT + yadd - kernel_h)/stride_size + 1;
    int out_w = (WIDTH + xadd - kernel_w)/stride_size + 1;

#ifdef _DEBUG
    cout << "FILTER_SIZE: " << FILTER_SIZE << endl;
    cout << "out_h: " << out_h << endl;
    cout << "out_w: " << out_w << endl;
    cout << "kernel_h: " << kernel_h << endl;
    cout << "kernel_w: " << kernel_w << endl;
    cout << "kernel_size: " << kernel_size << endl;

    struct timespec tp, ep;
    double timeCheck;
    clock_gettime(CLOCK_MONOTONIC, &tp);
#endif

#if defined(__MACH__) || defined(_USE_LLVM)
    float (* newin)[BATCH][HEIGHT+MAX_PAD_SIZE*2][WIDTH+MAX_PAD_SIZE*2][CHANNEL];
    newin = (float (*)[BATCH][HEIGHT+MAX_PAD_SIZE*2][WIDTH+MAX_PAD_SIZE*2][CHANNEL])malloc(sizeof(float[BATCH][HEIGHT+MAX_PAD_SIZE*2][WIDTH+MAX_PAD_SIZE*2][CHANNEL]));
    memset(newin, 0x00, sizeof(float[BATCH][HEIGHT+MAX_PAD_SIZE*2][WIDTH+MAX_PAD_SIZE*2][CHANNEL]));
#else
    float (* newin)[BATCH][HEIGHT+yadd][WIDTH+xadd][CHANNEL];
    newin = (float (*)[BATCH][HEIGHT+yadd][WIDTH+xadd][CHANNEL])malloc(sizeof(float[BATCH][HEIGHT+yadd][WIDTH+xadd][CHANNEL]));
    memset(newin, 0x00, sizeof(float[BATCH][HEIGHT+yadd][WIDTH+xadd][CHANNEL]));
#endif

    for (int b = 0; b < BATCH; b++){
        for (size_t c = 0; c < CHANNEL; c++){
            for (int h = 0; h < HEIGHT; h++){
                for (int w = 0; w < WIDTH; w++){
                    (*newin)[b][pad_ytop+h][pad_xleft+w][c] = in1[b][h][w][c];
                }
            }
        }
    }

    int splitKernelSize = kernel_size/group;
    int new_h_idx = 0, new_w_idx = 0;

    for (int b = 0; b < BATCH; b++) {
        for(int g = 0; g < group; g++) {
            for (int fc = 0; fc < splitKernelSize; fc++) {
                for (int i = 0; i < out_h; i++) {
                    for (int j = 0; j < out_w; j++) {

                        float total = 0.0;
                        for (int fh = 0; fh < kernel_h; fh++) {
                            for (int fw = 0; fw < kernel_w; fw++) {
                                for (size_t f = 0; f < FILTER_CHANNEL; f++) {
                                    total = total + (*newin)[b][new_h_idx + fh][new_w_idx + fw][g*FILTER_CHANNEL + f] *
                                                    filter[g*splitKernelSize + fc][fh][fw][f];
                                }
                            }
                        }
                        result[b][i][j][g*splitKernelSize + fc] = total + bias[g*splitKernelSize + fc];
                        new_w_idx += stride_size;
                    }
                    new_h_idx += stride_size;
                    new_w_idx = 0;
                }
                new_h_idx = 0;
            }
        }
    }

#ifdef _DEBUG
    free(newin);
    clock_gettime(CLOCK_MONOTONIC, &ep);
    timeCheck = ((ep.tv_sec - tp.tv_sec) + (ep.tv_nsec - tp.tv_nsec) / 1000000000.0);
    cout << "==================== " + to_string(conv_idx++) + " convolution time : " << timeCheck <<" seconds  ====================" << endl << endl;
#endif
}
#else
//
//convolution image to columns
template <typename T1, size_t BATCH, size_t HEIGHT, size_t WIDTH, size_t CHANNEL,
        typename T2, size_t FILTER_SIZE, size_t FILTER_HEIGHT, size_t FILTER_WIDTH, size_t FILTER_CHANNEL,
        typename T3, size_t N2,
        typename T4, size_t N3, size_t M3, size_t K3, size_t L3>
void conv_group(T1 (&in1)[BATCH][HEIGHT][WIDTH][CHANNEL], T2 (&filter)[FILTER_SIZE][FILTER_HEIGHT][FILTER_WIDTH][FILTER_CHANNEL], vector<size_t> filter_dim, T3 (&bias)[N2], \
	vector<size_t> stride, vector<size_t> pad, size_t group, T4 (&result)[N3][M3][K3][L3])
{
 //   cout <<  "-- conv() group: " << group << endl;

#ifdef _DEBUG
    cout <<  "-- conv() -- " << endl;

  cout << "BATCH: " << BATCH << endl;
  cout << "HEIGHT: " << HEIGHT << endl;
  cout << "WIDTH: " << WIDTH << endl;
  cout << "CHANNEL: " << CHANNEL << endl;
  cout << "pad: " << pad.at(0) << endl;
  cout << "stride: " << stride.at(0) << endl;
  cout << "FILTER_HEIGHT: " << FILTER_HEIGHT << endl;
  cout << "FILTER_WIDTH: " << FILTER_WIDTH << endl;
  cout << "FILTER_CHANNEL: " << FILTER_CHANNEL << endl;
  cout << "BIAS LEN: " << N2 << endl;
#endif

    int pad_xleft = pad.at(0);
    int pad_xright = pad.at(1);
    int pad_ytop = pad.at(2);
    int pad_ybottom = pad.at(3);
    int xadd = pad_xleft + pad_xright;
    int yadd = pad_ytop + pad_ybottom;

    int stride_size = stride.at(0);

    int out_h = (HEIGHT + yadd - FILTER_HEIGHT)/stride_size + 1;
    int out_w = (WIDTH + xadd - FILTER_WIDTH)/stride_size + 1;

#ifdef _DEBUG
    cout << "FILTER_SIZE: " << FILTER_SIZE << endl;
  cout << "out_h: " << out_h << endl;
  cout << "out_w: " << out_w << endl;

  struct timespec tp, ep;
  double timeCheck;
  clock_gettime(CLOCK_MONOTONIC, &tp);
#endif

#if defined(__MACH__) || defined(_USE_LLVM)
    float (* newin)[BATCH][HEIGHT+MAX_PAD_SIZE*2][WIDTH+MAX_PAD_SIZE*2][CHANNEL];
    newin = (float (*)[BATCH][HEIGHT+MAX_PAD_SIZE*2][WIDTH+MAX_PAD_SIZE*2][CHANNEL])malloc(sizeof(float[BATCH][HEIGHT+MAX_PAD_SIZE*2][WIDTH+MAX_PAD_SIZE*2][CHANNEL]));
    memset(newin, 0x00, sizeof(float[BATCH][HEIGHT+MAX_PAD_SIZE*2][WIDTH+MAX_PAD_SIZE*2][CHANNEL]));
#else
    float (* newin)[BATCH][HEIGHT+yadd][WIDTH+xadd][CHANNEL];
  newin = (float (*)[BATCH][HEIGHT+yadd][WIDTH+xadd][CHANNEL])malloc(sizeof(float[BATCH][HEIGHT+yadd][WIDTH+xadd][CHANNEL]));
  memset(newin, 0x00, sizeof(float[BATCH][HEIGHT+yadd][WIDTH+xadd][CHANNEL]));
#endif

    //step 1. 패딩

    //int jp, kp;
    //printf("패딩 변환 중\n");
    for (int b = 0; b < BATCH; b++){
        for (int h = 0; h < HEIGHT; h++){
            for (int w = 0; w < WIDTH; w++){
                for (size_t c = 0; c < CHANNEL; c++){
                    (*newin)[b][pad_ytop+h][pad_xleft+w][c] = in1[b][h][w][c];
                }
            }
        }
    }

    //step 2. 입력값 차원 변환
    //printf("입력값 차원 변환 중 \n");
    float (*newin2)[BATCH][CHANNEL*FILTER_HEIGHT*FILTER_WIDTH][M3*K3];
    newin2 = (float(*)[BATCH][CHANNEL*FILTER_HEIGHT*FILTER_WIDTH][M3*K3])malloc(sizeof(float[BATCH][CHANNEL*FILTER_HEIGHT*FILTER_WIDTH][M3*K3]));
    //하나의 스트라이트에 각 채널들을 하나의 행으로 구성
    for (int b = 0, p = 0; b < BATCH; b++, p = 0)
        for (int i = 0; i < out_h; i++)
            for (int j = 0, q = 0; j < out_w; j++, q = 0, p++)
                for (int c = 0; c < CHANNEL; c++)
                    for (int ii = i * stride_size; ii < i * stride_size + FILTER_HEIGHT; ii++)
                        for (int jj = j * stride_size; jj < j * stride_size + FILTER_WIDTH; jj++)
                            (*newin2)[b][q++][p] = (*newin)[b][ii][jj][c];

    free(newin);

    // step 3. 필터 평활화
    //printf("필터 평활화 중\n");

    float (*transFilter)[FILTER_SIZE][FILTER_HEIGHT*FILTER_WIDTH*FILTER_CHANNEL]; //패딩 배열
    transFilter = (float(*)[FILTER_SIZE][FILTER_HEIGHT*FILTER_WIDTH*FILTER_CHANNEL])malloc(sizeof(float[FILTER_SIZE][FILTER_WIDTH*FILTER_HEIGHT*FILTER_CHANNEL]));
    memset(transFilter, 0x00, sizeof(float[FILTER_SIZE][FILTER_HEIGHT*FILTER_WIDTH*FILTER_CHANNEL]));

    for (int i1 = 0, q = 0; i1 < FILTER_SIZE; i1++, q = 0)
        for (int i2 = 0; i2 < FILTER_CHANNEL; i2++)
            for (int i3 = 0; i3 < FILTER_HEIGHT; i3++)
                for (int i4 = 0; i4 < FILTER_WIDTH; i4++)
                    (*transFilter)[i1][q++] = filter[i1][i3][i4][i2];

    // step 4. 행렬 곱.
    float (*finalResult)[BATCH][FILTER_SIZE][M3*K3];
    finalResult = (float (*)[BATCH][FILTER_SIZE][M3*K3])malloc(sizeof(float[BATCH][FILTER_SIZE][M3*K3]));
    memset(finalResult, 0x00, sizeof(float[BATCH][FILTER_SIZE][M3*K3]));

    int div = out_h * out_w; // B * out_h * out_w
    //int total2 = BATCH * CHANNEL * FILTER_HEIGHT * FILTER_WIDTH * out_h * out_w;
    int total2 = BATCH * FILTER_CHANNEL * FILTER_HEIGHT * FILTER_WIDTH * out_h * out_w;
    int total3 = total2 / div;
    //printf("행렬 곱 중\n");
#ifdef _USE_OPENBLAS_CONV
  size_t m1len = FILTER_SIZE*total3, m2len = total3*div, m3len = FILTER_SIZE*div;
    float *m1, *m2, *m3;

    m1 = (float *)malloc(sizeof(float)*m1len);
    m2 = (float *)malloc(sizeof(float)*m2len);
    m3 = (float *)malloc(sizeof(float)*m3len);

    for (int b = 0; b < BATCH; b++) {
        for (int i = 0; i < m1len; i++) {
            m1[i] = (*transFilter)[i/total3][i % total3];
        }

        for (int i = 0; i < m2len; i++) {
            m2[i] = (*newin2)[b][i/div][i % div];
        }

        cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, FILTER_SIZE, div, total3, 1.0, m1, total3, m2, div, 0.0, m3, div);

        for (int i = 0; i < m3len; i++) {
            (*finalResult)[b][i/div][i % div] = m3[i];
        }
    }
    free(m1);
    free(m2);
    free(m3);

#else

#ifdef THREAD_MATMUL

    //printf("행렬 곱 중\n");
    struct tTHREAD<float,float,float, FILTER_SIZE, BATCH*FILTER_CHANNEL*FILTER_HEIGHT*FILTER_WIDTH, M3*K3> p;

    // 스레드에 연산할 메모리 복사
    pthread_t threads[MAX_THREAD];
    p.arrr = (*transFilter);
    p.brrr1 =(*newin2);
    p.crrr1 =(*finalResult);

#ifdef __MACH__
    semaphore = dispatch_semaphore_create(1);
#else
    sem_init(&semaphore, 0, 1);
#endif

    for(int b = 0; b < BATCH; b++){
        for (int i = 0; i < MAX_THREAD; i++) {
#ifdef __MACH__
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#else
            //sem_wait(&semaphore);
            int r;

            do {
                r = sem_wait(&semaphore);
            } while (r == -1 && errno == EINTR);
#endif

            p.cnt = i;
            p.b = b; // 배치 값

            pthread_create(&threads[i], NULL, multi<float, float, float, FILTER_SIZE, BATCH*FILTER_CHANNEL*FILTER_HEIGHT*FILTER_WIDTH, M3*K3>, (void *)&p); // filter_size, total3, div
        }

        for (int t = 0; t < MAX_THREAD; t++)
            pthread_join(threads[t], NULL); // 모든 스레드가 끝날 때까지 결합 및 대기
    }

#ifdef __MACH__
    dispatch_release(semaphore);
#else
    // sem_distroy(&semaphore);
#endif

#else

//total3:  BATCH * FILTER_CHANNEL * FILTER_HEIGHT * FILTER_WIDTH
//div: out_w * out_h
    int splitedKernelSize = FILTER_SIZE/group;
    // k for문과 j for 문을 변경함으로써, 지역성 향상.
    for (int b = 0; b < BATCH; b++) {
        for (int i = 0; i < splitedKernelSize; i++) {
            for(int g = 0; g < group; g++) {
                for (int k = 0; k < total3; k++) {
                    for (int j = 0; j < div; j++) {
                        (*finalResult)[b][g*splitedKernelSize + i][j] += (*transFilter)[g*splitedKernelSize + i][k] * (*newin2)[b][g*FILTER_CHANNEL*FILTER_WIDTH*FILTER_HEIGHT + k][j];
                    }
                }
            }
        }
    }
#endif

#endif
    // step 5. 결과 차원 변환및 편향 합
    //printf("마지막 차원 변환 중\n");
    //int putout_c = (FILTER_SIZE) / (BATCH);

    for (int i1 = 0; i1 < BATCH; i1++) {
        for(int g = 0; g < group; g++) {
            for (int i4 = 0; i4 < splitedKernelSize; i4++) {
                size_t q = 0;
                for (int i2 = 0; i2 < out_h; i2++) {
                    for (int i3 = 0; i3 < out_w; i3++) {
                        result[i1][i2][i3][g*splitedKernelSize + i4] = (*finalResult)[i1][g*splitedKernelSize + i4][q++] + bias[g*splitedKernelSize + i4];
                    }
                }
                //cout << (float)bias[i4] << "\t" << endl;
            }
        }
    }

    free(newin2);
    free(transFilter);
    free(finalResult);

#ifdef _DEBUG
    clock_gettime(CLOCK_MONOTONIC, &ep);
  timeCheck = ((ep.tv_sec - tp.tv_sec) + (ep.tv_nsec - tp.tv_nsec) / 1000000000.0);
  cout << "==================== " + to_string(conv_idx++) + " convolution time : " << timeCheck <<" seconds  ====================" << endl << endl;
#endif
}

#endif

//
//convolution image to columns
template <typename T1, size_t BATCH, size_t HEIGHT, size_t WIDTH, size_t CHANNEL,
        typename T2, size_t FILTER_SIZE, size_t FILTER_HEIGHT, size_t FILTER_WIDTH, size_t FILTER_CHANNEL,
        typename T3, size_t N2,
        typename T4, size_t N3, size_t M3, size_t K3, size_t L3>
void conv(T1 (&in1)[BATCH][HEIGHT][WIDTH][CHANNEL], T2 (&filter)[FILTER_SIZE][FILTER_HEIGHT][FILTER_WIDTH][FILTER_CHANNEL], vector<size_t> filter_dim, T3 (&bias)[N2], \
	vector<size_t> stride, vector<size_t> pad, size_t group, T4 (&result)[N3][M3][K3][L3])
{
    cout <<  "-- conv() group: " << group << endl;

#ifdef _DEBUG
    cout <<  "-- conv() -- " << endl;

  cout << "BATCH: " << BATCH << endl;
  cout << "HEIGHT: " << HEIGHT << endl;
  cout << "WIDTH: " << WIDTH << endl;
  cout << "CHANNEL: " << CHANNEL << endl;
  cout << "pad: " << pad.at(0) << endl;
  cout << "stride: " << stride.at(0) << endl;
  cout << "FILTER_HEIGHT: " << FILTER_HEIGHT << endl;
  cout << "FILTER_WIDTH: " << FILTER_WIDTH << endl;
  cout << "FILTER_CHANNEL: " << FILTER_CHANNEL << endl;
  cout << "BIAS LEN: " << N2 << endl;
#endif

    int pad_xleft = pad.at(0);
    int pad_xright = pad.at(1);
    int pad_ytop = pad.at(2);
    int pad_ybottom = pad.at(3);
    int xadd = pad_xleft + pad_xright;
    int yadd = pad_ytop + pad_ybottom;

    int stride_size = stride.at(0);

    int out_h = (HEIGHT + yadd - FILTER_HEIGHT)/stride_size + 1;
    int out_w = (WIDTH + xadd - FILTER_WIDTH)/stride_size + 1;

#ifdef _DEBUG
    cout << "FILTER_SIZE: " << FILTER_SIZE << endl;
  cout << "out_h: " << out_h << endl;
  cout << "out_w: " << out_w << endl;

  struct timespec tp, ep;
  double timeCheck;
  clock_gettime(CLOCK_MONOTONIC, &tp);
#endif

#if defined(__MACH__) || defined(_USE_LLVM)
    float (* newin)[BATCH][HEIGHT+MAX_PAD_SIZE*2][WIDTH+MAX_PAD_SIZE*2][CHANNEL];
    newin = (float (*)[BATCH][HEIGHT+MAX_PAD_SIZE*2][WIDTH+MAX_PAD_SIZE*2][CHANNEL])malloc(sizeof(float[BATCH][HEIGHT+MAX_PAD_SIZE*2][WIDTH+MAX_PAD_SIZE*2][CHANNEL]));
    memset(newin, 0x00, sizeof(float[BATCH][HEIGHT+MAX_PAD_SIZE*2][WIDTH+MAX_PAD_SIZE*2][CHANNEL]));
#else
    float (* newin)[BATCH][HEIGHT+yadd][WIDTH+xadd][CHANNEL];
    newin = (float (*)[BATCH][HEIGHT+yadd][WIDTH+xadd][CHANNEL])malloc(sizeof(float[BATCH][HEIGHT+yadd][WIDTH+xadd][CHANNEL]));
    memset(newin, 0x00, sizeof(float[BATCH][HEIGHT+yadd][WIDTH+xadd][CHANNEL]));
#endif

    //step 1. 패딩

    //int jp, kp;
    //printf("패딩 변환 중\n");
    for (int b = 0; b < BATCH; b++){
        for (int h = 0; h < HEIGHT; h++){
            for (int w = 0; w < WIDTH; w++){
                for (size_t c = 0; c < CHANNEL; c++){
                    (*newin)[b][pad_ytop+h][pad_xleft+w][c] = in1[b][h][w][c];
                }
            }
        }
    }

    //step 2. 입력값 차원 변환
    //printf("입력값 차원 변환 중 \n");
    float (*newin2)[BATCH][CHANNEL*FILTER_HEIGHT*FILTER_WIDTH][M3*K3];
    newin2 = (float(*)[BATCH][CHANNEL*FILTER_HEIGHT*FILTER_WIDTH][M3*K3])malloc(sizeof(float[BATCH][CHANNEL*FILTER_HEIGHT*FILTER_WIDTH][M3*K3]));
    //하나의 스트라이트에 각 채널들을 하나의 행으로 구성
    for (int b = 0, p = 0; b < BATCH; b++, p = 0)
        for (int i = 0; i < out_h; i++)
            for (int j = 0, q = 0; j < out_w; j++, q = 0, p++)
                for (int c = 0; c < CHANNEL; c++)
                    for (int ii = i * stride_size; ii < i * stride_size + FILTER_HEIGHT; ii++)
                        for (int jj = j * stride_size; jj < j * stride_size + FILTER_WIDTH; jj++)
                            (*newin2)[b][q++][p] = (*newin)[b][ii][jj][c];

    free(newin);

    // step 3. 필터 평활화
    //printf("필터 평활화 중\n");

    float (*transFilter)[FILTER_SIZE][FILTER_HEIGHT*FILTER_WIDTH*FILTER_CHANNEL]; //패딩 배열
    transFilter = (float(*)[FILTER_SIZE][FILTER_HEIGHT*FILTER_WIDTH*FILTER_CHANNEL])malloc(sizeof(float[FILTER_SIZE][FILTER_WIDTH*FILTER_HEIGHT*FILTER_CHANNEL]));
    memset(transFilter, 0x00, sizeof(float[FILTER_SIZE][FILTER_HEIGHT*FILTER_WIDTH*FILTER_CHANNEL]));

    for (int i1 = 0, q = 0; i1 < FILTER_SIZE; i1++, q = 0)
        for (int i2 = 0; i2 < FILTER_CHANNEL; i2++)
            for (int i3 = 0; i3 < FILTER_HEIGHT; i3++)
                for (int i4 = 0; i4 < FILTER_WIDTH; i4++)
                    (*transFilter)[i1][q++] = filter[i1][i3][i4][i2];

    // step 4. 행렬 곱.
    float (*finalResult)[BATCH][FILTER_SIZE][M3*K3];
    finalResult = (float (*)[BATCH][FILTER_SIZE][M3*K3])malloc(sizeof(float[BATCH][FILTER_SIZE][M3*K3]));
    memset(finalResult, 0x00, sizeof(float[BATCH][FILTER_SIZE][M3*K3]));

    int div = out_h * out_w; // B * out_h * out_w
    int total2 = BATCH * CHANNEL * FILTER_HEIGHT * FILTER_WIDTH * out_h * out_w;
    int total3 = total2 / div;
    //printf("행렬 곱 중\n");
#ifdef _USE_OPENBLAS_CONV
  size_t m1len = FILTER_SIZE*total3, m2len = total3*div, m3len = FILTER_SIZE*div;
    float *m1, *m2, *m3;

    m1 = (float *)malloc(sizeof(float)*m1len);
    m2 = (float *)malloc(sizeof(float)*m2len);
    m3 = (float *)malloc(sizeof(float)*m3len);

    for (int b = 0; b < BATCH; b++) {
        for (int i = 0; i < m1len; i++) {
            m1[i] = (*transFilter)[i/total3][i % total3];
        }

        for (int i = 0; i < m2len; i++) {
            m2[i] = (*newin2)[b][i/div][i % div];
        }

        cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, FILTER_SIZE, div, total3, 1.0, m1, total3, m2, div, 0.0, m3, div);

        for (int i = 0; i < m3len; i++) {
            (*finalResult)[b][i/div][i % div] = m3[i];
        }
    }
    free(m1);
    free(m2);
    free(m3);

#else

#ifdef THREAD_MATMUL

    //printf("행렬 곱 중\n");
    struct tTHREAD<float,float,float, FILTER_SIZE, BATCH*FILTER_CHANNEL*FILTER_HEIGHT*FILTER_WIDTH, M3*K3> p;

    // 스레드에 연산할 메모리 복사
    pthread_t threads[MAX_THREAD];
    p.arrr = (*transFilter);
    p.brrr1 =(*newin2);
    p.crrr1 =(*finalResult);

#ifdef __MACH__
    semaphore = dispatch_semaphore_create(1);
#else
    sem_init(&semaphore, 0, 1);
#endif

    for(int b = 0; b < BATCH; b++){
        for (int i = 0; i < MAX_THREAD; i++) {
#ifdef __MACH__
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#else
            //sem_wait(&semaphore);
            int r;

            do {
                r = sem_wait(&semaphore);
            } while (r == -1 && errno == EINTR);
#endif

            p.cnt = i;
            p.b = b; // 배치 값

            pthread_create(&threads[i], NULL, multi<float, float, float, FILTER_SIZE, BATCH*FILTER_CHANNEL*FILTER_HEIGHT*FILTER_WIDTH, M3*K3>, (void *)&p); // filter_size, total3, div
        }

        for (int t = 0; t < MAX_THREAD; t++)
            pthread_join(threads[t], NULL); // 모든 스레드가 끝날 때까지 결합 및 대기
    }

#ifdef __MACH__
    dispatch_release(semaphore);
#else
    // sem_distroy(&semaphore);
#endif

#else
    // k for문과 j for 문을 변경함으로써, 지역성 향상.
    for (int b = 0; b < BATCH; b++) {
        for (int i = 0; i < FILTER_SIZE; i++) {
            for (int k = 0; k < total3; k++) {
                for (int j = 0; j < div; j++) {
                    (*finalResult)[b][i][j] += (*transFilter)[i][k] * (*newin2)[b][k][j];
                }
            }
        }
    }
#endif

#endif
    // step 5. 결과 차원 변환및 편향 합
    //printf("마지막 차원 변환 중\n");
    //int putout_c = (FILTER_SIZE) / (BATCH);

    for (int i1 = 0; i1 < BATCH; i1++) {
        for (int i4 = 0; i4 < FILTER_SIZE; i4++) {
            size_t q = 0;
            for (int i2 = 0; i2 < out_h; i2++) {
                for (int i3 = 0; i3 < out_w; i3++) {
                    result[i1][i2][i3][i4] = (*finalResult)[i1][i4][q++] + bias[i4];
                }
            }
            //cout << (float)bias[i4] << "\t" << endl;
        }
    }

    free(newin2);
    free(transFilter);
    free(finalResult);

#ifdef _DEBUG
    clock_gettime(CLOCK_MONOTONIC, &ep);
  timeCheck = ((ep.tv_sec - tp.tv_sec) + (ep.tv_nsec - tp.tv_nsec) / 1000000000.0);
  cout << "==================== " + to_string(conv_idx++) + " convolution time : " << timeCheck <<" seconds  ====================" << endl << endl;
#endif
}

#endif

/*
 *
 *
 * Average Pooling
 *
 *
 */

template <typename T1, size_t BATCH, size_t HEIGHT, size_t WIDTH, size_t CHANNEL,
        typename T2, size_t N2, size_t M2, size_t K2, size_t L2>
void avgpool(T1 (&in)[BATCH][HEIGHT][WIDTH][CHANNEL], vector<size_t> windows_dim, vector<size_t> stride, vector<size_t> pad, T2 (&result)[N2][M2][K2][L2])
{
    //	time_t istart,iend;
//	time (&istart);

    //N, H, W, C
    //N == 1
    //group == 1

    int pad_xleft = pad.at(0);
    int pad_xright = pad.at(1);
    int pad_ytop = pad.at(2);
    int pad_ybottom = pad.at(3);
    int xadd = pad_xleft + pad_xright;
    int yadd = pad_ytop + pad_ybottom;

    int stride_size = stride.at(0);

    int window_h = windows_dim.at(0);
    int window_w = windows_dim.at(1);

    int out_h = (HEIGHT + yadd - window_h)/stride_size + 1;
    int out_w = (WIDTH + xadd - window_w)/stride_size + 1;

    const int new_height = HEIGHT + pad.at(0) + pad.at(1);
    const int new_width = WIDTH +  pad.at(2) + pad.at(3);

#ifdef _DEBUG
    cout << "avgpool()" <<endl;

//  cout << "BATCH: " << BATCH << endl;
//  cout << "HEIGHT: " << HEIGHT << endl;
//  cout << "WIDTH: " << WIDTH << endl;
//  cout << "CHANNEL: " << CHANNEL << endl;
//  cout << "pad: " << pad.at(0) << endl;
//  cout << "stride: " << stride.at(0) << endl;
//  cout << "WINDOW_HEIGHT: " << window_h << endl;
//  cout << "WINDOW_WIDTH: " << window_w << endl;
#endif

#if defined(__MACH__) || defined(_USE_LLVM)
    float (* newin)[BATCH][HEIGHT+MAX_PAD_SIZE*2][WIDTH+MAX_PAD_SIZE*2][CHANNEL];
    newin = (float (*)[BATCH][HEIGHT+MAX_PAD_SIZE*2][WIDTH+MAX_PAD_SIZE*2][CHANNEL])malloc(sizeof(float[BATCH][HEIGHT+MAX_PAD_SIZE*2][WIDTH+MAX_PAD_SIZE*2][CHANNEL]));
    memset(newin, 0x00, sizeof(float[BATCH][HEIGHT+MAX_PAD_SIZE*2][WIDTH+MAX_PAD_SIZE*2][CHANNEL]));
#else
    float (*newin)[BATCH][HEIGHT+yadd][WIDTH+xadd][CHANNEL];
    newin = (float (*)[BATCH][HEIGHT+yadd][WIDTH+xadd][CHANNEL])malloc(sizeof(float[BATCH][HEIGHT+yadd][WIDTH+xadd][CHANNEL]));
    memset(newin, 0x00, sizeof(float[BATCH][HEIGHT+yadd][WIDTH+xadd][CHANNEL]));
#endif

//    float (* newin)[BATCH][HEIGHT+MAX_PAD_SIZE][WIDTH+MAX_PAD_SIZE][CHANNEL];
//    newin = (float (*)[BATCH][HEIGHT+MAX_PAD_SIZE][WIDTH+MAX_PAD_SIZE][CHANNEL])malloc(sizeof(float[BATCH][HEIGHT+MAX_PAD_SIZE][WIDTH+MAX_PAD_SIZE][CHANNEL]));

    for (int b = 0; b < BATCH; b++){
        for (size_t h = 0; h < HEIGHT; h++){
            for (size_t w = 0; w < WIDTH; w++){
                for (int c = 0; c < CHANNEL; c++){
                    (*newin)[b][pad_ytop+h][pad_xleft+w][c] = in[b][h][w][c];
                }
            }
        }
    }

    int wnum = window_h*window_w;
    int new_h_idx = 0, new_w_idx = 0;
    for (int b = 0; b < BATCH; b++){
        for(int c = 0; c < CHANNEL; c++) {

            for(int i = 0; i <out_h; i++) {
                for(int j = 0; j <out_w; j++) {

                    int pad_cnt = 0;
                    float total = 0.0;
                    for(int fh = 0; fh <window_h; fh++) {
                        for(int fw = 0; fw <window_w; fw++) {

                            int h_idx = new_h_idx+fh, w_idx = new_w_idx+fw;

                            if(h_idx < pad_ytop || w_idx < pad_xleft || h_idx >= (new_height - pad_ybottom)|| w_idx >= (new_width - pad_xright)) {
                                pad_cnt++;
                            }

                            total = total + (*newin)[b][h_idx][w_idx][c];
                        }
                    }

                    //cout << "pad_cnt = " << pad_cnt << endl;

                    result[b][i][j][c] = total/(wnum-pad_cnt);
                    new_w_idx += stride_size;
                }
                new_h_idx += stride_size;
                new_w_idx = 0;
            }
            new_h_idx = 0;
        }
    }

    free(newin);

//    time (&iend);
//    double dif = difftime (iend,istart);
//    printf ("==> [Avg. pooling time] %.4lf seconds.\n", dif );

}

//N H W C
/*
 *
 *
 * Max Pooling
 *
 *
 */

template <typename T1, size_t BATCH, size_t HEIGHT, size_t WIDTH, size_t CHANNEL,
        typename T3, size_t N2, size_t M2, size_t K2, size_t L2>
void maxpool(T1 (&in1)[BATCH][HEIGHT][WIDTH][CHANNEL], vector<size_t> windows_dim, vector<size_t> stride, vector<size_t> pad, T3 (&result)[N2][M2][K2][L2])
{

#ifdef _DEBUG
    cout << "maxpool()" << endl;
#endif
//	time_t istart,iend;
//	time (&istart);

    //N, H, W, C
    //N == 1
    //group == 1

    int pad_xleft = pad.at(0);
    int pad_xright = pad.at(1);
    int pad_ytop = pad.at(2);
    int pad_ybottom = pad.at(3);
    int xadd = pad_xleft + pad_xright;
    int yadd = pad_ytop + pad_ybottom;

    int stride_size = stride.at(0);

    int window_h = windows_dim.at(0);
    int window_w = windows_dim.at(1);

    int out_h = (HEIGHT + yadd - window_h)/stride_size + 1;
    int out_w = (WIDTH + xadd - window_w)/stride_size + 1;

#if defined(__MACH__) || defined(_USE_LLVM)
    float (* newin)[BATCH][HEIGHT+MAX_PAD_SIZE*2][WIDTH+MAX_PAD_SIZE*2][CHANNEL];
    newin = (float (*)[BATCH][HEIGHT+MAX_PAD_SIZE*2][WIDTH+MAX_PAD_SIZE*2][CHANNEL])malloc(sizeof(float[BATCH][HEIGHT+MAX_PAD_SIZE*2][WIDTH+MAX_PAD_SIZE*2][CHANNEL]));
    memset(newin, 0x00, sizeof(float[BATCH][HEIGHT+MAX_PAD_SIZE*2][WIDTH+MAX_PAD_SIZE*2][CHANNEL]));
#else
    float (* newin)[BATCH][HEIGHT+yadd][WIDTH+xadd][CHANNEL];
  newin = (float (*)[BATCH][HEIGHT+yadd][WIDTH+xadd][CHANNEL])malloc(sizeof(float[BATCH][HEIGHT+yadd][WIDTH+xadd][CHANNEL]));
  memset(newin, 0x00, sizeof(float[BATCH][HEIGHT+yadd][WIDTH+xadd][CHANNEL]));
#endif

    // memset(newin, 0x00, sizeof(float[BATCH][HEIGHT+yadd][WIDTH+xadd][CHANNEL]));

    for (int b = 0; b < BATCH; b++){
        for (size_t h = 0; h < HEIGHT; h++){
            for (size_t w = 0; w < WIDTH; w++){
                for (int c = 0; c < CHANNEL; c++){
                    (*newin)[b][pad_ytop+h][pad_xleft+w][c] = in1[b][h][w][c];
                }
            }
        }
    }

    int new_h_idx = 0, new_w_idx = 0;
    for (int b = 0; b < BATCH; b++){
        for(int c = 0; c < CHANNEL; c++) {

            for(int i = 0; i <out_h; i++) {
                for(int j = 0; j <out_w; j++) {

                    float max = -INFINITY;;
                    for(int fh = 0; fh <window_h; fh++) {
                        for(int fw = 0; fw <window_w; fw++) {

                            float value = (*newin)[b][new_h_idx+fh][new_w_idx+fw][c];

                            if(max < value)
                                max = value;
                        }
                    }

                    result[b][i][j][c] = max;
                    new_w_idx += stride_size;
                }
                new_h_idx += stride_size;
                new_w_idx = 0;
            }
            new_h_idx = 0;

        }
    }

//    if(pad_size > 0)
    free(newin);

//    time (&iend);
//    double dif = difftime (iend,istart);
//    printf ("==> [Max. pooling time] %.4lf seconds.\n", dif );
} // end maxpool

/*
 *
 *
 * Softmax 2D
 *
 *
 */
template <typename T1, size_t N, size_t M, typename T2>
void softmax(T1 (&in1)[N][M], int axis, T2 (&result)[N][M])
{

#ifdef _DEBUG
    cout << "softmax()-2D" << endl;
#endif
    //axis

    // clock_t start, end;
    // start = clock();

    if(axis == 1) {

        float max[N];
        for(size_t i = 0; i < N; i++) {
            max[i] = -INFINITY;
        }

        for (size_t i = 0; i < N; i++) {
            for (size_t j = 0; j < M; j++) {
                if (in1[i][j] > max[i]) {
                    max[i] = in1[i][j];
                }
            }
        }

        float sum[N];
        memset(sum, 0x00, sizeof(float)*N);
        for (size_t i = 0; i < N; i++) {
            for (size_t j = 0; j < M; j++) {
                sum[i] += expf(in1[i][j]- max[i]);
            }
        }

        for (size_t i = 0; i < N; i++) {
            for (size_t j = 0; j < M; j++) {
                result[i][j] = expf(in1[i][j]-max[i])/sum[i];
            }
        }
    } else {

        float max = -INFINITY;
        float sum = 0.0;

        for (size_t i = 0; i < N; i++) {
            for (size_t j = 0; j < M; j++) {
                if (in1[i][j] > max) {
                    max = in1[i][j];
                }
            }
        }

        for (size_t i = 0; i < N; i++) {
            for (size_t j = 0; j < M; j++) {
                sum += expf(in1[i][j]- max);
            }
        }

        for (size_t i = 0; i < N; i++) {
            for (size_t j = 0; j < M; j++) {
                result[i][j] = expf(in1[i][j]-max)/sum;
            }
        }
    }

//	end = clock();
//    cout << "==> [time] : " << (((double)(end - start)) / CLOCKS_PER_SEC) << " seconds" << endl;

}

/*
 *
 *
 * Softmax 3D
 *
 *
 *
 */

template <typename T1, size_t N1, size_t N2, size_t N3,
        typename T2>
void softmax(T1 (&in1)[N1][N2][N3], int axis, T2 (&result)[N1][N2][N3])
{
#ifdef _DEBUG
    cout << "softmax()-3D" << endl;
#endif
    //cout << "axis = " << axis << endl;

    //clock_t start, end;
    //start = clock();

    if(axis == 1) {

        float max[N1];
        for(int i = 0; i < N1; i++) {
            max[i] = -INFINITY;
        }

        for (size_t i = 0; i < N1; i++) {
            for (size_t k = 0; k < N3; k++) {
                for (size_t j = 0; j < N2; j++) {
                    if (in1[i][j][k] > max[i]) {
                        max[i] = in1[i][j][k];
                    }
                }
            }
        }

        float sum[N1];
        memset(sum, 0x00, sizeof(float)*N1);
        for (size_t i = 0; i < N1; i++) {
            for (size_t j = 0; j < N2; j++) {
                for (size_t k = 0; k < N3; k++) {
                    sum[i] += expf(in1[i][j][k]- max[i]);
                }
            }
        }

        for (size_t i = 0; i < N1; i++) {
            for (size_t j = 0; j < N2; j++) {
                for (size_t k = 0; k < N3; k++) {
                    result[i][j][k] = expf(in1[i][j][k]-max[i])/sum[i];
                }
            }
        }
    } else {

        float max = -INFINITY;
        float sum = 0.0;

        for (size_t i = 0; i < N1; i++) {
            for (size_t k = 0; k < N3; k++) {
                for (size_t j = 0; j < N2; j++) {
                    if (in1[i][j][k] > max) {
                        max = in1[i][j][k];
                    }
                }
            }
        }

        for (size_t i = 0; i < N1; i++) {
            for (size_t j = 0; j < N2; j++) {
                for (size_t k = 0; k < N3; k++) {
                    sum += expf(in1[i][j][k]- max);
                }
            }
        }

        for (size_t i = 0; i < N1; i++) {
            for (size_t j = 0; j < N2; j++) {
                for (size_t k = 0; k < N3; k++) {
                    result[i][j][k] = expf(in1[i][j][k]-max)/sum;
                }
            }
        }
    }

//	end = clock();
//    cout << "==> [time] : " << (((double)(end - start)) / CLOCKS_PER_SEC) << " seconds" << endl;

}

/*
 *
 *
 * Relu 4D
 *
 *
 *
 */

template <typename T1, size_t N, size_t M, size_t K, size_t L>
void relu(T1 (&in1)[N][M][K][L])
{
#ifdef _DEBUG
    cout << "relu()-4D" << endl;
#endif
    //  clock_t start, end;
//	start = clock();

    for (size_t i = 0; i < N; i++){
        for (size_t j=0; j<M; j++){
            for (size_t k=0; k<K; k++){
                for (size_t l=0; l<L; l++)
                {
                    if (in1[i][j][k][l] < 0.0)
                        in1[i][j][k][l] = 0.0;

                }
            }
        }
    }

//	end = clock();
//    cout << "==> [time] : " << (((double)(end - start)) / CLOCKS_PER_SEC) << " seconds" << endl;

} //end relu

/*
 *
 *
 * Relu 2D
 *
 *
 */
template <typename T1, size_t N, size_t M>
void relu(T1 (&in1)[N][M])
{
#ifdef _DEBUG
    cout << "relu()-2D" << endl;
#endif
    //    clock_t start, end;
//	start = clock();

    for (size_t i=0; i < N; i++) {
        for (size_t j=0; j<M; j++){
            if (in1[i][j] <= 0.0)
                in1[i][j] = 0.0;
        }
    }

//	end = clock();
//	cout << "==> [time] : " << (((double)(end - start)) / CLOCKS_PER_SEC) << " seconds" << endl;

} //end relu

/*
 *
 *
 * Add 2D + 2D
 *
 *
 */

template <typename T1, size_t N, size_t M>
void add(T1 (&in1)[N][M], T1 (&in2)[N][M], T1 (&result)[N][M])
{

#ifdef _DEBUG
    cout << "add()-2D + 2D" << endl;
#endif
    //    clock_t start, end;
//	start = clock();

    for (size_t i = 0; i < N; i++){
        for (size_t j = 0; j < M; j++){
            result[i][j] = in1[i][j] + in2[i][j];
        }
    }

//	end = clock();
//    cout << "==> [time] : " << (((double)(end - start)) / CLOCKS_PER_SEC) << " seconds" << endl;

    //cout << "[LIB] add end" << endl;

}

/*
 *
 *
 * Add 2D + 1D
 *
 *
 */
template <typename T1, size_t N, size_t M>
void add(T1 (&in1)[N][M], T1 (&in2)[M], T1 (&result)[N][M])
{
#ifdef _DEBUG
    cout << "add()-2D + 1D" << endl;
#endif
    //    clock_t start, end;
//	start = clock();

    for (size_t i = 0; i < N; i++){
        for (size_t j = 0; j < M; j++){
            result[i][j] = in1[i][j] + in2[j];
        }
    }

//	end = clock();
//    cout << "==> [time] : " << (((double)(end - start)) / CLOCKS_PER_SEC) << " seconds" << endl;

    //cout << "[LIB] add end" << endl;

}

/*
 *
 *
 * Add 2D + 2D
 *
 *
 */
template <typename T1, size_t N, size_t M>
void add(T1 (&in1)[N][M], T1 (&in2)[N][M])
{
#ifdef _DEBUG
    cout << "add()-2D + 2D" << endl;
#endif
    //    clock_t start, end;
//	start = clock();

    for (size_t i = 0; i < N; i++){
        for (size_t j = 0; j < M; j++){
            in1[i][j] = in1[i][j] + in2[i][j];
        }
    }
}

/*
 *
 *
 * Add 2D + 1D
 *
 *
 */
template <typename T1, size_t N, size_t M>
void add(T1 (&in1)[N][M], T1 (&in2)[M])
{
#ifdef _DEBUG
    cout << "add()-2D + 1D" << endl;
#endif
    //    clock_t start, end;
//	start = clock();

    for (size_t i = 0; i < N; i++){
        for (size_t j = 0; j < M; j++){
            in1[i][j] = in1[i][j] + in2[j];
        }
    }

}

/*
 *
 *
 * Add 4D + 1D
 *
 *
 */
template <typename T1, size_t N1, size_t N2, size_t N3, size_t N4,
        typename T2, size_t M>
void add(T1 (&in1)[N1][N2][N3][N4], T2 (&in2)[M], T1 (&result)[N1][N2][N3][N4])
{
#ifdef _DEBUG
    cout << "add()-2D + 1D" << endl;
#endif
    //clock_t start, end;
    //start = clock();

    for (int i1 = 0; i1 < N1; i1++){
        for (int i2 = 0; i2 < N2; i2++){
            for (int i3 = 0; i3 < N3; i3++){
                for (int i4 = 0; i4 < N4; i4++){

                    result[i1][i2][i3][i4] = in1[i1][i2][i3][i4] + in2[i2];
                }
            }
        }
    }

}

/*
 *
 *
 * Add 4D + 1D
 *
 *
 */
template <typename T1, size_t N1, size_t N2, size_t N3, size_t N4,
        typename T2, size_t M>
void add(T1 (&in1)[N1][N2][N3][N4], T2 (&in2)[M])
{
#ifdef _DEBUG
    cout << "add()-2D + 1D" << endl;
#endif
    //clock_t start, end;
    //start = clock();

    for (int i1 = 0; i1 < N1; i1++){
        for (int i2 = 0; i2 < N2; i2++){
            for (int i3 = 0; i3 < N3; i3++){
                for (int i4 = 0; i4 < N4; i4++){

                    in1[i1][i2][i3][i4] = in1[i1][i2][i3][i4] + in2[i2];
                }
            }
        }
    }

//	end = clock();
//    cout << "==> [time] : " << (((double)(end - start)) / CLOCKS_PER_SEC) << " seconds" << endl;

    //cout << "[LIB] add end" << endl;

}

/*
 *
 *
 * Add 4D + 4D
 *
 *
 */
template <typename T1, size_t N, size_t M, size_t K, size_t L>
void add(T1 (&in1)[N][M][K][L], T1 (&in2)[N][M][K][L], T1 (&result)[N][M][K][L])
{
#ifdef _DEBUG
    cout << "add()" << endl;
#endif
    //    clock_t start, end;
//	start = clock();

    for (int i = 0; i < N; i++){
        for (int j = 0; j < M; j++){
            for (int k = 0; k < K; k++){
                for (int l = 0; l < L; l++){
                    result[i][j][k][l] = in1[i][j][k][l] + in2[i][j][k][l];
                }
            }
        }
    }

//	end = clock();
//	cout << "==> [time] : " << (((double)(end - start)) / CLOCKS_PER_SEC) << " seconds" << endl;

} //end add

/*
 *
 *
 * Add 4D + 4D
 *
 *
 */
template <typename T1, size_t N, size_t M, size_t K, size_t L>
void add(T1 (&in1)[N][M][K][L], T1 (&in2)[N][M][K][L])
{
#ifdef _DEBUG
    cout << "add()- 4D +4D" << endl;
#endif
    //    clock_t start, end;
//	start = clock();

    for (size_t i = 0; i < N; i++){
        for (size_t j = 0; j < M; j++){
            for (size_t k = 0; k < K; k++){
                for (size_t l = 0; l < L; l++){
                    in1[i][j][k][l] = in1[i][j][k][l] + in2[i][j][k][l];
                }
            }
        }
    }

//	end = clock();
//	cout << "==> [time] : " << (((double)(end - start)) / CLOCKS_PER_SEC) << " seconds" << endl;
//
//

}

template <typename T1, size_t N1, size_t M1, typename T2, size_t N2, size_t M2, typename T3, size_t N3, typename T4, size_t N4, size_t M4>
void gemm(T1 (&a)[N1][M1], T2 (&b)[N2][M2], T3 (&c)[N3], float alpha, float beta, int transA, int transB, T4 (&result)[N4][M4]) {

#ifdef _DEBUG
    cout << "gemm()" << endl;
//  cout << "N: " << N << endl;
//  cout << "K: " << K << endl;
//  cout << "M: " << M << endl;

    struct timespec tp, ep;
    double timeCheck;
    clock_gettime(CLOCK_MONOTONIC, &tp);
#endif

//#ifdef _USE_OPENBLAS_MATMUL
//    size_t m1len = N*K, m2len = K*M, m3len = N*M;
//    float *m1, *m2, *m3;
//
//    m1 = (float *)malloc(sizeof(float)*m1len);
//    m2 = (float *)malloc(sizeof(float)*m2len);
//    m3 = (float *)malloc(sizeof(float)*m3len);
//
//    for(int i = 0; i < m1len; i++) {
//        m1[i] = in1[i/K][i%K];
//    }
//
//    for(int i = 0; i < m2len; i++) {
//        m2[i] = weight[i/M][i%M];
//    }
//
//    cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, N, M, K, 1, m1, K, m2, M, 0, m3, M);
//
//    for(int i = 0; i < m3len; i++) {
//        result[i/M][i%M] = m3[i] + bias[i];
//    }
//
//    free(m1);
//    free(m2);
//    free(m3);
//
//#else

//    cout << "transA = " << transA << " transB = " << transB << endl;
    if(transA == 0 && transB == 0) {
        for (size_t i = 0; i < N1; i++){
            for (size_t j = 0;  j <M1; j++){
                for (size_t k = 0; k < M2; k++){
                    result[i][j] += a[i][k]*b[k][j];
                }
            }
        }
    } else if (transA == 0 && transB == 1) {

        float transposedB[M2][N2];
        for (size_t i = 0; i < N2; i++) {
            for (size_t j = 0; j < M2; j++) {
                transposedB[j][i] = b[i][j];
            }
        }

        for (size_t i = 0; i < N1; i++){
            for (size_t j = 0;  j <M2; j++){
                for (size_t k = 0; k < M1; k++){
                    result[i][j] += a[i][k]*transposedB[k][j];
                }
            }
        }
    }  else if (transA == 1 && transB == 0) {
        float transposedA[M1][N1];
        for (size_t i = 0; i < N1; i++) {
            for (size_t j = 0; j < M1; j++) {
                transposedA[j][i] = a[i][j];
            }
        }

        for (size_t i = 0; i < M1; i++){
            for (size_t j = 0;  j <M2; j++){
                for (size_t k = 0; k < N1; k++){
                    result[i][j] += transposedA[i][k]*b[k][j];
                }
            }
        }
    }

    for (size_t i = 0; i < N4; i++){
        for (size_t j = 0; j < M4; j++){
            result[i][j] = result[i][j] + c[j];
        }
    }

//#endif

#ifdef _DEBUG
    clock_gettime(CLOCK_MONOTONIC, &ep);
    timeCheck = ((ep.tv_sec - tp.tv_sec) + (ep.tv_nsec - tp.tv_nsec) / 1000000000.0);
    cout << "====================  gemm time : " << timeCheck <<" seconds  ====================" << endl << endl;
#endif
}

//void cblas_sgemm(const enum CBLAS_ORDER __Order, const enum CBLAS_TRANSPOSE __TransA, const enum CBLAS_TRANSPOSE __TransB,
// const int __M, const int __N, const int __K, const float __alpha, const float *__A, const int __lda, const float *__B, const int __ldb, const float __beta, float *__C, const int __ldc);

template <typename T1, size_t N, size_t K, typename T2, size_t M, typename T3, typename T4>
void fullyconnected(T1 (&in1)[N][K], T2 (&weight)[K][M], T3 (&bias)[M], T4 (&result)[N][M]) {

#ifdef _DEBUG
    cout << "fullyconnected()" << endl;
//  cout << "N: " << N << endl;
//  cout << "K: " << K << endl;
//  cout << "M: " << M << endl;

  struct timespec tp, ep;
  double timeCheck;
  clock_gettime(CLOCK_MONOTONIC, &tp);
#endif

#ifdef _USE_OPENBLAS_MATMUL
    size_t m1len = N*K, m2len = K*M, m3len = N*M;
    float *m1, *m2, *m3;

    m1 = (float *)malloc(sizeof(float)*m1len);
    m2 = (float *)malloc(sizeof(float)*m2len);
    m3 = (float *)malloc(sizeof(float)*m3len);

    for(int i = 0; i < m1len; i++) {
        m1[i] = in1[i/K][i%K];
    }

    for(int i = 0; i < m2len; i++) {
        m2[i] = weight[i/M][i%M];
    }

    cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, N, M, K, 1, m1, K, m2, M, 0, m3, M);

    for(int i = 0; i < m3len; i++) {
        result[i/M][i%M] = m3[i] + bias[i];
    }

    free(m1);
    free(m2);
    free(m3);

#else
    for (size_t i = 0; i < N; i++){
        for (size_t j = 0;  j <M; j++){
            for (size_t k = 0; k < K; k++){
                result[i][j] += in1[i][k]*weight[k][j];
            }
        }
    }

    for (size_t i = 0; i < N; i++){
        for (size_t j = 0; j < M; j++){
            result[i][j] = result[i][j] + bias[j];
        }
    }

#endif

#ifdef _DEBUG
    clock_gettime(CLOCK_MONOTONIC, &ep);
  timeCheck = ((ep.tv_sec - tp.tv_sec) + (ep.tv_nsec - tp.tv_nsec) / 1000000000.0);
  cout << "====================  fullyconnected time : " << timeCheck <<" seconds  ====================" << endl << endl;
#endif
}

template <typename T1, size_t N, size_t K, typename T2, size_t M, typename T3, typename T4>
void fullyconnected(T1 (&in1)[N][K], T2 (&weight)[K][M], T3 (&bias)[N][M], T4 (&result)[N][M]) {

#ifdef _DEBUG
    cout << "fullyconnected()" << endl;
//  cout << "N: " << N << endl;
//  cout << "K: " << K << endl;
//  cout << "M: " << M << endl;

  struct timespec tp, ep;
  double timeCheck;
  clock_gettime(CLOCK_MONOTONIC, &tp);
#endif

#ifdef _USE_OPENBLAS_MATMUL
    size_t m1len = N*K, m2len = K*M, m3len = N*M;
    float *m1, *m2, *m3;

    m1 = (float *)malloc(sizeof(float)*m1len);
    m2 = (float *)malloc(sizeof(float)*m2len);
    m3 = (float *)malloc(sizeof(float)*m3len);

    for(int i = 0; i < m1len; i++) {
        m1[i] = in1[i/K][i%K];
    }

    for(int i = 0; i < m2len; i++) {
        m2[i] = weight[i/M][i%M];
    }

    cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, N, M, K, 1, m1, K, m2, M, 0, m3, M);

    for(int i = 0; i < m3len; i++) {
        result[i/M][i%M] = m3[i] + bias[i];
    }

    free(m1);
    free(m2);
    free(m3);

#else
    for (size_t i = 0; i < N; i++){
        for (size_t j = 0;  j <M; j++){
            for (size_t k = 0; k < K; k++){
                result[i][j] += in1[i][k]*weight[k][j];
            }
        }
    }

    for (size_t i = 0; i < N; i++){
        for (size_t j = 0; j < M; j++){
            result[i][j] = result[i][j] + bias[i][j];
        }
    }

#endif

#ifdef _DEBUG
    clock_gettime(CLOCK_MONOTONIC, &ep);
  timeCheck = ((ep.tv_sec - tp.tv_sec) + (ep.tv_nsec - tp.tv_nsec) / 1000000000.0);
  cout << "====================  fullyconnected time : " << timeCheck <<" seconds  ====================" << endl << endl;
#endif
}

template <typename T1, size_t N, size_t K,
        typename T2, size_t N1, size_t M1,
        typename T3, size_t N2, size_t M2>
void matmul(T1 (&in1)[N][K], T2 (&in2)[N1][M1], T3 (&result)[N2][M2]) {

#ifdef _DEBUG
    cout << "N: " << N << endl;
  cout << "K: " << K << endl;
  cout << "M: " << M1 << endl;

  struct timespec tp, ep;
  double timeCheck;
  clock_gettime(CLOCK_MONOTONIC, &tp);
#endif

#ifdef _USE_OPENBLAS_MATMUL
    size_t m1len = N*K, m2len = N1*M1, m3len = N2*M2;
    float *m1, *m2, *m3;

    m1 = (float *)malloc(sizeof(float)*m1len);
    m2 = (float *)malloc(sizeof(float)*m2len);
    m3 = (float *)malloc(sizeof(float)*m3len);

    for(int i = 0; i < m1len; i++) {
        m1[i] = in1[i/K][i%K];
    }

    for(int i = 0; i < m2len; i++) {
        m2[i] = in2[i/M1][i%M1];
    }

    cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, N, M1, K, 1, m1, K, m2, M1, 0, m3, M1);

    for(int i = 0; i < m3len; i++) {
        result[i/M2][i%M2] = m3[i];
    }

    free(m1);
    free(m2);
    free(m3);

#else
    for (size_t i = 0; i < N; i++){
//        for (size_t k = 0; k < M; k++){
        for (size_t j = 0;  j <M1; j++){
            for (size_t k = 0; k < K; k++){
                result[i][j] += in1[i][k]*in2[k][j];
            }
        }
    }

#endif

#ifdef _DEBUG
    clock_gettime(CLOCK_MONOTONIC, &ep);
  timeCheck = ((ep.tv_sec - tp.tv_sec) + (ep.tv_nsec - tp.tv_nsec) / 1000000000.0);
  cout << "====================  matmul time : " << timeCheck <<" seconds  ====================" << endl << endl;
#endif
}

/*
 *
 *
 * Transpose 2D
 *
 *
 */
template <typename T1, size_t N, size_t M, typename T2, size_t N1, size_t M1>
void transpose(T1 (&in1)[N][M], vector<size_t> shuffle, T2 (&result)[N1][M1])
{
#ifdef _DEBUG
    cout << "transpose()-2D-2D" << endl;
#endif
    //  clock_t start, end;
//	start = clock();

    for (size_t i=0; i<N; i++){
        for (size_t j=0; j<M; j++){
            result[j][i] = in1[i][j];
        }
    }

//
//	end = clock();
//    cout << "==> [time] : " << (((double)(end - start)) / CLOCKS_PER_SEC) << " seconds" << endl;

}

/*
 *
 *
 * Transpose 3D
 *
 *
 */
template <typename T1, size_t M, size_t K, size_t L,
        typename T2, size_t M1, size_t K1, size_t L1>
void transpose(T1 (&in1)[M][K][L], vector<size_t> shuffle, T2 (&result)[M1][K1][L1])
{
#ifdef _DEBUG
    cout << "transpose()-3D" << endl;
#endif
    //
//    clock_t start, end;
//    start = clock();

    int shuffleType = 0;
 //   int shuffle0 = shuffle.at(0);
    int shuffle1 = shuffle.at(1);
    int shuffle2 = shuffle.at(2);
    int shuffle3 = shuffle.at(3);

    if(shuffle1 == 1 && shuffle2 == 2 && shuffle3  == 0) {
        shuffleType = 120;
    } else if(shuffle1 == 2 && shuffle2 == 0 && shuffle3  == 1) {
        shuffleType = 201;
    } else if(shuffle1 == 0 && shuffle2 == 2 && shuffle3  == 1) {
        shuffleType = 021;
    } else if(shuffle1 == 1 && shuffle2 == 0 && shuffle3  == 2) {
        shuffleType = 102;
    }

    cout << shuffleType <<endl;
    for (int j=0; j<M; j++){
        for (int k=0; k<K; k++){
            for (int l=0; l<L; l++){
                if(shuffleType == 120) {
                    result[k][l][j] = in1[j][k][l]; // 1, 2, 0
                } else if (shuffleType == 201) {
                    result[l][j][k] = in1[j][k][l]; // 2, 0, 1
                } else if (shuffleType == 021) {
                    result[j][l][k] = in1[j][k][l]; // 0, 2, 1
                } else if (shuffleType == 102) {
                    result[k][j][l] = in1[j][k][l]; // 1, 0, 2
                } else {
                    cout << "No!!!!!!!!!!!!";
                }
            }
            // cout <<endl;
        }
        //cout <<endl;
    }
    //cout <<endl;

//	end = clock();
//    cout << "==> [time] : " << (((double)(end - start)) / CLOCKS_PER_SEC) << " seconds" << endl;

} // end transpose

/*
 *
 *
 * Transpose 6D
 *
 *
 */
template <typename T1, size_t N1, size_t N2, size_t N3, size_t N4, size_t N5, size_t N6,
        typename T2, size_t M1, size_t M2, size_t M3, size_t M4, size_t M5, size_t M6>
void transpose( T1 (&in1)[N1][N2][N3][N4][N5][N6], vector<size_t> shuffle, T2 (&result)[M1][M2][M3][M4][M5][M6])
{
#ifdef _DEBUG
    cout << "transpose-6D" << endl;
#endif
    //
//    clock_t start, end;
//    start = clock();

    int shuffleType = 0;
    int shuffle0 = shuffle.at(0);
    int shuffle1 = shuffle.at(1);
    int shuffle2 = shuffle.at(2);
    int shuffle3 = shuffle.at(3);
    int shuffle4 = shuffle.at(4);
    int shuffle5 = shuffle.at(5);

    if(shuffle0 == 0 &&shuffle1 == 1 && shuffle2 == 4 && shuffle3  == 2 && shuffle4  == 5 && shuffle5  == 3) {
        shuffleType = 14253;
    }

    //  cout << shuffleType <<endl;
//#pragma omp parallel for
    for (int i1 = 0; i1 < N1; i1++){
        for (int i2 = 0; i2 < N2; i2++){
            for (int i3 = 0; i3 < N3; i3++){
                for (int i4 = 0; i4 < N4; i4++){
                    for (int i5 = 0; i5 < N5; i5++){
                        for (int i6 = 0; i6 < N6; i6++){

                            if(shuffleType == 14253) {
                                result[i1][i2][i5][i3][i6][i4] = in1[i1][i2][i3][i4][i5][i6]; // 0, 1, 4, 2, 5, 3
                            }

                        }

                    }
                }
                // cout <<endl;
            }
            //cout <<endl;
        }
        //cout <<endl;
    }

//	end = clock();
//    cout << "==> [time] : " << (((double)(end - start)) / CLOCKS_PER_SEC) << " seconds" << endl;

}

/*
 *
 *
 * Transpose 4D
 *
 *
 */
template <typename T1, size_t N1, size_t N2, size_t N3, size_t N4,
        typename T2, size_t M1, size_t M2, size_t M3, size_t M4>
void transpose( T1 (&in1)[N1][N2][N3][N4], vector<size_t> shuffle, T2 (&result)[M1][M2][M3][M4])
{
#ifdef _DEBUG
    cout << "transpose()-4D" << endl;
#endif
//    clock_t start, end;
//    start = clock();

    int shuffleType = 0;
//    int shuffle0 = shuffle.at(0);
    int shuffle1 = shuffle.at(1);
    int shuffle2 = shuffle.at(2);
    int shuffle3 = shuffle.at(3);

    if(shuffle1 == 2 && shuffle2 == 3 && shuffle3  == 1) {
        shuffleType = 231;
    } else if(shuffle1 == 3 && shuffle2 == 1 && shuffle3  == 2) {
        shuffleType = 312;
    } else if(shuffle1 == 1 && shuffle2 == 3 && shuffle3  == 2) {
        shuffleType = 132;
    } else if(shuffle1 == 2 && shuffle2 == 1 && shuffle3  == 3) {
        shuffleType = 213;
    } else if(shuffle1 == 3 && shuffle2 == 2 && shuffle3  == 1) {
        shuffleType = 321;
    }else if(shuffle1 == 1 && shuffle2 == 3 && shuffle3  == 2) {
        shuffleType = 132;
    }

//   cout << "shuffle type = " << shuffleType <<endl;
//#pragma omp parallel for
    for (size_t i1 = 0; i1 < N1; i1++){
        for (size_t i2 = 0; i2< N2; i2++){
            for (size_t i3 = 0; i3 < N3; i3++){
                for (size_t i4 = 0; i4 < N4; i4++){
                    if(shuffleType == 231) {
                        result[i1][i3][i4][i2] = in1[i1][i2][i3][i4]; // 0, 2, 3, 1
                    } else if (shuffleType == 312) {
                        result[i1][i4][i2][i3] = in1[i1][i2][i3][i4]; // 0, 3, 1, 2
                    } else if (shuffleType == 132) {
                        result[i1][i2][i4][i3] = in1[i1][i2][i3][i4]; // 0, 1, 3, 2
                    } else if (shuffleType == 213) {
                        result[i1][i3][i2][i4] = in1[i1][i2][i3][i4]; // 0, 2, 1, 3
                    }else if (shuffleType == 321) {
                        result[i1][i4][i3][i2] = in1[i1][i2][i3][i4]; // 0, 2, 1, 3
                    }else if (shuffleType == 132) {
                        result[i1][i2][i4][i3] = in1[i1][i2][i3][i4]; // 0, 2, 1, 3
                    }
                    else {
                        cout << "No!!!!!!!!!!!!";
                    }
                }
                // cout <<endl;
            }
            //cout <<endl;
        }
        //cout <<endl;
    }

//	end = clock();
//    cout << "==> [time] : " << (((double)(end - start)) / CLOCKS_PER_SEC) << " seconds" << endl;

}

/*
 *
 *
 * Reshape 4D => 2D
 *
 *
 */
template <typename T1, size_t N, size_t M, size_t L, size_t K,
        typename T2, size_t N1, size_t M1>
void reshape(T1 (&in1)[N][M][L][K], T2 (&result)[N1][M1])
{
#ifdef _DEBUG
    cout << "reshape()-4D -> 2D)" << endl;
#endif
    //
//    clock_t start, end;
//    start = clock();

//    cout << "^^^^^^^^^^" << endl;
//
//	for(int j = 0; j < M; j++){
//		if(j%10 == 0) cout <<endl;
//		cout << in1[0][j][0][0] << "\t";
//	}

    int total = 0;
    for(size_t i1 = 0; i1 < N; i1++) {
        for(size_t i2 = 0; i2 < M; i2++) {
            for(size_t i3 = 0; i3 < L; i3++) {
                for(size_t i4 = 0; i4 < K; i4++) {

                    total = i1*M*L*K + i2*L*K + i3*K + i4;
                    int idx1 = total/M1, idx2 = total%M1;

                    result[idx1][idx2] = in1[i1][i2][i3][i4];

                }
            }
        }
    }
//
//	end = clock();
//    cout << "==> [time] : " << (((double)(end - start)) / CLOCKS_PER_SEC) << " seconds" << endl;

}

/*
 *
 *
 * Reshape 6D => 4D
 *
 *
 */
template <typename T1, size_t N1, size_t N2, size_t N3, size_t N4, size_t N5, size_t N6,
        typename T2, size_t M1, size_t M2, size_t M3, size_t M4>
void reshape(T1 (&in1)[N1][N2][N3][N4][N5][N6], T2 (&result)[M1][M2][M3][M4])
{
#ifdef _DEBUG
    cout << "reshape()-6D -> 4D" << endl;
#endif
//    clock_t start, end;
//    start = clock();

    int total = 0;
    for(int i1 = 0; i1 < N1; i1++) {
        for(int i2 = 0; i2 < N2; i2++) {
            for(int i3 = 0; i3 <N3; i3++) {
                for(int i4 = 0; i4 < N4; i4++) {
                    for(int i5 = 0; i5 < N5; i5++) {
                        for(int i6 = 0; i6 < N6; i6++) {
                            total = i1*N2*N3*N4*N5 + i2*N3*N4*N5 + i3*N4*N5 + i4*N5 + i6;

                            int m1 = M2*M3*M4;
                            int m2 = M3*M4;

                            int idx1 = total/m1;
                            int idx2 = (total%m1)/m2;
                            int idx3 = (total%m1)%m2/M4;
                            int idx4 = (total%m1)%m2%M4;
                            result[idx1][idx2][idx3][idx4] = in1[i1][i2][i3][i4][i5][i6];
                        }
                    }
                }
            }
        }
    }

//	end = clock();
//    cout << "==> [time] : " << (((double)(end - start)) / CLOCKS_PER_SEC) << " seconds" << endl;

}

/*
 *
 *
 * Reshape 4D => 6D
 *
 *
 */
template <typename T1, size_t N1, size_t N2, size_t N3, size_t N4,
        typename T2, size_t M1, size_t M2, size_t M3, size_t M4, size_t M5, size_t M6>
void reshape(T1 (&in1)[N1][N2][N3][N4], T2 (&result)[M1][M2][M3][M4][M5][M6])
{
#ifdef _DEBUG
    cout << "reshape()4D -> 6D" << endl;
#endif
    //
//    clock_t start, end;
//    start = clock();

    int total = 0;
    for(int i1 = 0; i1 < N1; i1++) {
        for(int i2 = 0; i2 < N2; i2++) {
            for(int i3 = 0; i3 <N3; i3++) {
                for(int i4 = 0; i4 < N4; i4++) {
                    total = i1*N2*N3*N4 + i2*N3*N4 + i3*N4 + i4;

                    int m1 = M2*M3*M4*M5*M6;
                    int m2 = M3*M4*M5*M6;
                    int m3 = M4*M5*M6;
                    int m4 = M5*M6;

                    int idx1 = total/m1;
                    int idx2 = (total%m1)/m2;
                    int idx3 = ((total%m1)%m2)/m3;
                    int idx4 = ((total%m1)%m2%m3)/m4;
                    int idx5 = (((total%m1)%m2%m3)%m4)/M6;
                    int idx6 = (((total%m1)%m2%m3)%m4)%M6;
                    result[idx1][idx2][idx3][idx4][idx5][idx6] = in1[i1][i2][i3][i4];
                }
            }
        }
    }
//
//	end = clock();
//    cout << "==> [time] : " << (((double)(end - start)) / CLOCKS_PER_SEC) << " seconds" << endl;

}

template<typename T1>
void leakyRelu(T1 in, vector<size_t> idim, T1 result, int type)
{
    cout << "leakyRelu():TBD" << endl;
}

template<typename T1, typename T2>
void LRN(T1 in, vector<size_t> dim, float size, float beta, float bias, float windowSize, T2 result, int type)
{
    cout << "LRN(): TBD" << endl;
}

template<typename T1, size_t N, size_t M, typename T2>
void dropout(T1 (&in)[N][M], vector<size_t> dim, T1 (&result)[N][M], T2 (&mask)[N][M], int type)
{
    cout << "dropout(): TBD" << endl;
}

template<typename T1, typename T2>
void mul(T1 in1, vector<size_t> dim, T2 in2, T1 result, int type)
{
    cout << "mul(): TBD" << endl;
}

template<typename T1, typename T2, size_t N, size_t M>
void splat(T1 value, T2 (&result)[N][M])
{
    for(int i = 0; i < N; i++) {
        for(int j = 0; j < M; j++) {
            result[i][j] = (T2)value;
        }
    }
}

template <typename T1, size_t N, size_t M, size_t K, size_t L>
void channel_shuffle(T1 (&in1)[N][M][K][L], const int group, int kernel, T1 (&result)[N][M][K][L])
{
#ifdef _DEBUG
    cout << "shuffle()-4D" << endl;
    cout << "group: " << group << endl;
    cout << "kernel: " << kernel << endl;
#endif

    const int gdim = L/group;
    T1 rt[N][M][K][group][gdim];

    //reshape 4d -> 5d
    int total = 0;
    for(int i1 = 0; i1 < N; i1++) {
        for(int i2 = 0; i2 < M; i2++) {
            for(int i3 = 0; i3 <K; i3++) {
                for(int i4 = 0; i4 < L; i4++) {
                    total = i1*M*K*L + i2*K*L + i3*L + i4;

                    int m1 = M*K*group*gdim;
                    int m2 = K*group*gdim;
                    int m3 = group*gdim;
                    int m4 = gdim;

                    int idx1 = total/m1;
                    int idx2 = (total%m1)/m2;
                    int idx3 = ((total%m1)%m2)/m3;
                    int idx4 = ((total%m1)%m2%m3)/m4;
                    int idx5 = ((total%m1)%m2%m3)%m4;
                    rt[idx1][idx2][idx3][idx4][idx5] = in1[i1][i2][i3][i4];
                }
            }
        }
    }

    T1 transrt[N][M][K][gdim][group];
//    //transpose
    for (size_t i1 = 0; i1 < N; i1++){
        for (size_t i2 = 0; i2< M; i2++){
            for (size_t i3 = 0; i3 < K; i3++){
                for (size_t i4 = 0; i4 < group; i4++){
                    for (size_t i5 = 0; i5 < gdim; i5++) {
                        transrt[i1][i2][i3][i5][i4] = rt[i1][i2][i3][i4][i5];
                    }
                }
            }
        }
    }

    //reshape 5d -> 4d
    total = 0;
    for(int i1 = 0; i1 < N; i1++) {
        for(int i2 = 0; i2 < M; i2++) {
            for(int i3 = 0; i3 <K; i3++) {
                for(int i4 = 0; i4 < gdim; i4++) {
                    for(int i5 = 0; i5 < group; i5++) {
                            total = i1*M*K*gdim*group + i2*K*gdim*group + i3*gdim*group + i4*group + i5;

                            int m1 = M*K*L;
                            int m2 = K*L;

                            int idx1 = total/m1;
                            int idx2 = (total%m1)/m2;
                            int idx3 = (total%m1)%m2/L;
                            int idx4 = (total%m1)%m2%L;
                            result[idx1][idx2][idx3][idx4] = transrt[i1][i2][i3][i4][i5];
                        }
                }
            }
        }
    }

//	end = clock();
//    cout << "==> [time] : " << (((double)(end - start)) / CLOCKS_PER_SEC) << " seconds" << endl;

}

#endif /* LIBRARY_H_ */


#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION

#include <Python.h>
#include <numpy/arrayobject.h>
#include <lapacke.h>

/* DGESVD prototype */
extern void LAPACK_dgesvd( char* jobu, char* jobvt, int* m, int* n, double* a,
                    int* lda, double* s, double* u, int* ldu, double* vt, int* ldvt,
                    double* work, int* lwork, int* info );

double** An(double *pi, double *x, int labs, int dims);
double** A(double **PI, double **X, int labs, int dims, int n_pool);
double** Fisher(double *pi, double *x, double sigma, int labs, int dims);

static char estVar_docstring[] =
    "Calculate the A and Fisher matrix.";

static PyObject *varRedu_estVar(PyObject *self, PyObject *args);

static PyMethodDef module_methods[] = {
    {"estVar", varRedu_estVar, METH_VARARGS, estVar_docstring},
    {NULL, NULL, 0, NULL}
};

static struct PyModuleDef moduledef = {
    PyModuleDef_HEAD_INIT,
    "_variance_reduction",  /* m_name */
    "This module provides calculate A and Fisher matrix using C.",  /* m_doc */
    -1,                  /* m_size */
    module_methods,      /* m_methods */
    NULL,                /* m_reload */
    NULL,                /* m_traverse */
    NULL,                /* m_clear */
    NULL,                /* m_free */
};

PyMODINIT_FUNC PyInit__variance_reduction(void){
    PyObject *m = PyModule_Create(&moduledef);
    if(m==NULL){
        return NULL;
    }

    /* Load 'numpy' */
    import_array();

    return m;
}

double* matrix_mul(double* a, double* b, int m1, int n1, int m2, int n2){
    double *ret = (double*) malloc(m1 * n2 * sizeof(double));
    if(n1 != m2){
        return NULL;
    }
    for(int i=0; i<m1; i++)
        for(int j=0; j<n2; j++){
            double temp = 0.0;
            for(int p=0; p<n1; p++)
                temp += a[i*n1 + p] * b[p*n2 + j];
            ret[i*n2 + j] = temp;
        }
    return ret;
}

void pinv(double** X, int labs, int dims){
    int m = labs*dims, n = labs*dims, lda = labs*dims, ldu = labs*dims,
        ldvt = labs*dims, lwork, info;
    double wkopt;
    double *work;
    double *s  = (double*) malloc(labs*dims * sizeof(double));
    double *u  = (double*) malloc(labs*dims * labs*dims * sizeof(double));
    double *vt = (double*) malloc(labs*dims * labs*dims * sizeof(double));
    double *a  = (double*) malloc(labs*dims * labs*dims * sizeof(double));

    for(int i=0; i<m; i++)
        for(int j=0; j<n; j++)
            a[i*labs*dims + j] = X[i][j];

     /* Query and allocate the optimal workspace */
    lwork = -1;
    LAPACK_dgesvd("All", "All", &m, &n, a, &lda, s, u, &ldu, vt, &ldvt, &wkopt, &lwork,
        &info);
    lwork = (int)wkopt;
    work = (double*)malloc( lwork*sizeof(double) );
    /* Compute SVD */
    LAPACK_dgesvd("All", "All", &m, &n, a, &lda, s, u, &ldu, vt, &ldvt, work, &lwork,
        &info);
    /* Check for convergence  */
    if(info > 0) {
        printf("The algorithm computing SVD failed to converge. %d\n", info);
    }
    if(info < 0) {
        printf("Has illegal value. %d\n", info);
    }

    int numSigular = 0;
    double *si  = (double*) malloc(labs*dims * labs*dims * sizeof(double));
    memset(si, 0, labs*dims * labs*dims * sizeof(double));

    for(int i=0; i<ldu; i++){
        if(s[i] > 1e-30){
            si[i*ldu + i] = 1.0 / s[i];
            numSigular += 1;
        }else{
            si[i+ldu + i] = 0.0;
        }
    }

    /* calculating transpose */
    double *ret = matrix_mul(vt, si, labs*dims, numSigular, numSigular, numSigular);
    double *ret_pinv = matrix_mul(ret, u, labs*dims, numSigular, numSigular, labs*dims);

    for(int i=0; i<m; i++)
        for(int j=0; j<n; j++)
            X[i][j] = ret_pinv[i*n + j];

    free(ret);
    free(ret_pinv);
    free(work);
    free(a);
    free(s);
    free(vt);
    free(u);
    free(si);

    return;
}

static PyObject *varRedu_estVar(PyObject *self, PyObject *args)
{
    int dims, n_pool, labs, sigma;
    PyObject *PI_obj, *X_obj, *ePI_obj, *eX_obj;

    if (!PyArg_ParseTuple(args, "dOOOO", &sigma, &PI_obj, &X_obj, &ePI_obj, &eX_obj))
        return NULL;

    PyArrayObject *PI_array  =  (PyArrayObject*)PyArray_FROM_OTF(PI_obj,  NPY_DOUBLE, NPY_ARRAY_IN_ARRAY);
    PyArrayObject *X_array   =  (PyArrayObject*)PyArray_FROM_OTF(X_obj,   NPY_DOUBLE, NPY_ARRAY_IN_ARRAY);
    PyArrayObject *ePI_array =  (PyArrayObject*)PyArray_FROM_OTF(ePI_obj, NPY_DOUBLE, NPY_ARRAY_IN_ARRAY);
    PyArrayObject *eX_array  =  (PyArrayObject*)PyArray_FROM_OTF(eX_obj,  NPY_DOUBLE, NPY_ARRAY_IN_ARRAY);

    if (PI_array == NULL || X_array == NULL || ePI_array == NULL || eX_array == NULL) {
        Py_XDECREF(PI_array);
        Py_XDECREF(X_array);
        Py_XDECREF(ePI_array);
        Py_XDECREF(eX_array);
        return NULL;
    }

    labs   = (int)PyArray_DIM(PI_array, 1);
    n_pool = (int)PyArray_DIM(X_array, 0);
    dims   = (int)PyArray_DIM(X_array, 1);

    double **PI  =  (double**) malloc(n_pool * sizeof(double*));
    double **X   =  (double**) malloc(n_pool * sizeof(double*));
    for(int i=0; i<n_pool; i++){
        PI[i] = (double*) malloc(labs * sizeof(double));
        X[i]  = (double*) malloc(dims * sizeof(double));
    }
    for(int i=0; i<n_pool; i++){
        for(int j=0; j<labs; j++){
            PI[i][j] = *(double*)PyArray_GETPTR2(PI_array, i, j);
        }
        for(int j=0; j<dims; j++){
            X[i][j] = *(double*)PyArray_GETPTR2(X_array, i, j);
        }
    }
    double *ePI =  (double*) PyArray_DATA(ePI_array);
    double *eX  =  (double*) PyArray_DATA(eX_array);

    double **retF = Fisher(ePI, eX, sigma, labs, dims);
    double **retA = A(PI, X, labs, dims, n_pool);

    pinv(retF, labs, dims);

    /* calculates the trace of the multiply of pinv(F) and A  */
    double score = 0.0;
    for(int i=0; i<dims*labs; i++){
        for(int k=0; k<dims*labs; k++){
            score += retA[i][k] * retF[k][i];
        }
    }

    Py_DECREF(PI_array);
    Py_DECREF(X_array);
    Py_DECREF(ePI_array);
    Py_DECREF(eX_array);

    PyObject* ret = Py_BuildValue("d", score);

    for(int i=0; i<n_pool; i++){
        free(PI[i]);
        free(X[i]);
    }
    free(PI);
    free(X);

    for(int i=0; i<labs*dims; i++){
        free(retF[i]);
        free(retA[i]);
    }
    free(retF);
    free(retA);

    return ret;
}

double** An(double *pi, double *x, int labs, int dims){
    double **g = (double**) malloc(labs*dims * sizeof(double*));
    for(int i=0; i<labs*dims; i++){
        g[i] = (double*) malloc(labs * sizeof(double));
        memset(g[i], 0, labs * sizeof(double));
    }

    for(int p=0; p<labs; p++)
        for(int i=0; i<dims; i++){
            for(int c=0; c<labs; c++)
                if(p == c) g[p*dims + i][c] = pi[p] * (1.0-pi[p]) * x[i];
                else g[p*dims + i][c] = -1.0 * pi[p] * pi[c] * x[i];
        }

    double **an = (double**) malloc(labs*dims * sizeof(double*));
    for(int i=0; i<labs*dims; i++){
        an[i] = (double*) malloc(labs*dims * sizeof(double));
        memset(an[i], 0, labs*dims * sizeof(double));
    }

    for(int p=0; p<labs; p++)
        for(int i=0; i<dims; i++)
            for(int q=0; q<labs; q++)
                for(int j=0; j<dims; j++){
                    /* inner product */
                    double tmp = 0.0;
                    for(int k=0; k<labs; k++){
                        tmp += g[p*dims + i][k] * g[q*dims + j][k];
                    }
                    an[p*dims + i][q*dims + j] = tmp;
                }

    for(int i=0; i<labs*dims; i++)
        free(g[i]);
    free(g);

    return an;
}

double** A(double **PI, double **X, int labs, int dims, int n_pool){
    double **ret = (double**) malloc(labs*dims * sizeof(double*));
    for(int i=0; i<labs*dims; i++){
        ret[i] = (double*) malloc(labs*dims * sizeof(double));
        memset(ret[i], 0, labs*dims * sizeof(double));
    }

    for(int n=0; n<n_pool; n++){
        double **an = An(PI[n], X[n], labs, dims);

        for(int p=0; p<labs; p++)
            for(int i=0; i<dims; i++)
                for(int q=0; q<labs; q++)
                    for(int j=0; j<dims; j++)
                        ret[p*dims + i][q*dims + j] += an[p*dims + i][q*dims + j];
        for(int i=0; i<labs*dims; i++)
            free(an[i]);
        free(an);
    }
    return ret;
}

double** Fisher(double *pi, double *x, double sigma, int labs, int dims){
    double **ret = (double**) malloc(labs*dims * sizeof(double*));
    for(int i=0; i<labs*dims; i++){
        ret[i] = (double*) malloc(labs*dims * sizeof(double*));
        memset(ret[i], 0, labs*dims * sizeof(double));
    }

    for(int p=0; p<labs; p++)
        for(int i=0; i<dims; i++)
            for(int q=0; q<labs; q++)
                for(int j=0; j<dims; j++){
                    if(p == q && i == j)
                        ret[p*dims + i][q*dims + j] = x[i]*x[i]*pi[p]*(1.0-pi[p]) + 1.0/sigma*sigma;
                    else if(p == q && i != j)
                        ret[p*dims + i][q*dims + j] = x[i]*x[j]*pi[p]*(1.0-pi[p]);
                    else
                        ret[p*dims + i][q*dims + j] = x[i]*x[j]*pi[p]*pi[q];
                }
    return ret;
}


#ifndef H_EASY_GSL
#define H_EASY_GSL

#include <gsl/gsl_vector.h>
#include <gsl/gsl_matrix.h>

#ifdef __cplusplus
extern "C" {
#endif

struct egsl_val {
	gsl_matrix * gslm;
	int cid : 16;
	int index : 16;
};

typedef struct egsl_val val;

/* Core functions */

/* Push a new context. */
void egsl_push();
void egsl_push_named(const char*name);
/* Pops a context */
void egsl_pop();
void egsl_pop_named(const char*name);
void egsl_free(void);

double* egsl_atmp(val v, size_t i, size_t j);
val egsl_alloc(size_t rows, size_t columns);
val egsl_alloc_in_context(int cid, size_t rows, size_t cols);
gsl_matrix * egsl_gslm(val v);
/** Creates a copy of v in the previous context.*/
val egsl_promote(val v);

/** Operations among values */
val egsl_scale(double, val);
val egsl_sum(val, val);
val egsl_sum3(val, val, val);
val egsl_mult(val, val);
val egsl_transpose(val);
val egsl_inverse(val);
val egsl_sub(val,val);
val egsl_sum(val v1,val v2);
val egsl_compose_col(val v1, val v2);
val egsl_compose_row(val v1, val v2);
void egsl_add_to(val v1, val v2);
void egsl_add_to_col(val v1, size_t j, val v2);

double egsl_norm(val);

void egsl_symm_eig(val v, double* eigenvalues, val* eigenvectors);

double egsl_atv(val, size_t i);
double egsl_atm(val, size_t i, size_t j);

/* File: egsl_conversions.c
  Conversions */

val egsl_vFa(size_t rows, const double*);
val egsl_vFda(size_t rows, size_t columns, const double*);

/** Copies a VECTOR value into array */
void egsl_v2a(val, double*);
/** Copies a MATRIX value into array (row1 .. rown) */
void egsl_v2da(val, double*);
/** Copies a vector value into a gsl_vector */
void egsl_v2vec(val, gsl_vector*);

val egsl_vFgslv(const gsl_vector*);
val egsl_vFgslm(const gsl_matrix*);

gsl_matrix* egsl_v2gslm(val);

/*/ File: egsl_misc.c
    Miscellaneous useful matrixes. */
val egsl_zeros(size_t rows, size_t columns);
val egsl_ones(size_t rows, size_t columns);
val egsl_vers(double theta);
val egsl_rot(double theta);

/* Misc */
void egsl_print(const char*str, val);
/** Prints eigenvalues and eigenvectors of a symmetric matrix */
void egsl_print_spectrum(const char*s, val v);
void egsl_print_stats(void);

/** Private implementations things */
void egsl_expect_size(val v, size_t rows, size_t cols);
void egsl_error(void);

#ifdef __cplusplus
}
#endif

#endif


#include <stdio.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#include <gsl/gsl_permutation.h>

int
main (void)
{
  const size_t N = 10;
  const gsl_rng_type * T;
  gsl_rng * r;

  gsl_permutation * p = gsl_permutation_alloc (N);
  gsl_permutation * q = gsl_permutation_alloc (N);

  gsl_rng_env_setup();
  T = gsl_rng_default;
  r = gsl_rng_alloc (T);

  printf ("initial permutation:");
  gsl_permutation_init (p);
  gsl_permutation_fprintf (stdout, p, " %u");
  printf ("\n");

  printf (" random permutation:");
  gsl_ran_shuffle (r, p->data, N, sizeof(size_t));
  gsl_permutation_fprintf (stdout, p, " %u");
  printf ("\n");

  printf ("inverse permutation:");
  gsl_permutation_inverse (q, p);
  gsl_permutation_fprintf (stdout, q, " %u");
  printf ("\n");

  gsl_permutation_free (p);
  gsl_permutation_free (q);
  gsl_rng_free (r);

  return 0;
}


/* bclsqr.c
   $Revision: 273 $ $Date: 2006-09-04 15:59:04 -0700 (Mon, 04 Sep 2006) $

   ----------------------------------------------------------------------
   This file is part of BCLS (Bound-Constrained Least Squares).

   Copyright (C) 2006 Michael P. Friedlander, Department of Computer
   Science, University of British Columbia, Canada. All rights
   reserved. E-mail: <mpf@cs.ubc.ca>.

   BCLS is free software; you can redistribute it and/or modify it
   under the terms of the GNU Lesser General Public License as
   published by the Free Software Foundation; either version 2.1 of the
   License, or (at your option) any later version.

   BCLS 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 GNU Lesser General
   Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with BCLS; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
   USA
   ----------------------------------------------------------------------
*/
/*!
   \file
   Interface to LSQR routine.  Used to compute a Newton step.
*/

#include <cblas.h>
#include <string.h>
#include <stdio.h>

#include "bcls.h"
#include "bclib.h"
#include "bclsqr.h"
#include "lsqr.h"

/*!

  \brief Mat-vec routine called by LSQR.

  LSQR calls this routine, which in turn calls bcls_aprod: This
  routine is declared "static" so that it won't be confused with the
  user's own Aprod routine.

  - If mode = 1,
    - y(1:m)  <-  y(1:m)  +  A(:,ix) * dxFree.
    - y(m+1:) <-  y(m+1:) +     damp * dxFree

  - If mode = 2,
    - dxFree <- dxFree + A'* y(1:m) + damp y(m+1:).

  Note that mSubProb is the number of rows in the subproblem.  This
  may or may not be equal to m, which is the number of rows in the
  original problem.  If the subproblem is damped (because the user has
  either provided a linear term or an explicit damping parameter),
  then mSubProb = m + nFree.  Otherwise, mSubProb = m.

  \param[in]      mode      Determines which producte with A is required.

  \param[in]      mSubProb  Number of rows in the matrix seen by LSQR.  Also:
                            - length of y.
  \param[in]      nFree     Number of columns in A(:,ix).  Also:
                            - length of ix
                            - length of dxFree.
  \param[in,out]  dxFree    Primal variables
  \param[in,out]  y         Dual variables.
  \param[in,out]  UsrWrk    Transit pointer to the BCLS problem context.

*/
static void
aprod_free_lsqr( const int mode, const int mSubProb, const int nFree,
                 double dxFree[], double y[], void *UsrWrk)
{
    int j;
    BCLS *ls = (BCLS *)UsrWrk;  // Reclaim access to the BCLS workspace.
    const int    m      = ls->m;
    const int    preconditioned = ls->Usolve != NULL;
    const int    damped = ls->damp_actual > 0.0;
    const double damp   = ls->damp_actual;
    int          *ix    = ls->ix;
    double       *dx    = ls->dx;    // Used as workspace.
    double       *dy    = ls->wrk_u; // ...

    if (mode == 1) {

        // Solve U dy = dxFree.  U is nFree-by-nFree, and so the
        // relevant part of dy has length nFree.
        if (preconditioned)
            bcls_usolve( ls, BCLS_PRECON_U, nFree, ix, dy, dxFree );
        else
            cblas_dcopy( nFree, dxFree, 1, dy, 1 );

        // y2 <- y2 + damp * dy(1:nFree).
        if (damped)
            cblas_daxpy( nFree, damp, dy, 1, &y[m], 1 );

        // Scatter dy into dx: dx(ix) <- dy(1:nFree).
        for (j = 0; j < nFree; j++)  dx[ ix[j] ] = dy[j];

        // dy <- A(:,ix) * dx(ix).
        bcls_aprod( ls, BCLS_PROD_A, nFree, ix, dx, dy );

        // y1 <- y1 + dy.
        cblas_daxpy( m, 1.0, dy, 1, y, 1 );

    }
    else { // mode == 2

        // dx <- A' * y1.
        bcls_aprod( ls, BCLS_PROD_At, nFree, ix, dx, y );

        // Gather dx(ix) into dy: dy(1:nFree) <- dx(ix).
        for (j = 0; j < nFree; j++) dy[j] = dx[ ix[j] ];

        // dy <- dy + damp * y2.
        if (damped)
            cblas_daxpy( nFree, damp, &y[m], 1, dy, 1 );

        // Solve U' dx = dy.
        if (preconditioned)
            bcls_usolve( ls, BCLS_PRECON_Ut, nFree, ix, dy, dx );
        else
            cblas_dcopy( nFree, dy, 1, dx, 1 );

        // dxFree <- dxFree + dx.
        cblas_daxpy( nFree, 1.0, dx, 1, dxFree, 1 );

    }
    return;
}

/*!

  \brief Compute a Newton step using LSQR.

  \see  bcls_newton_step_cgls.

  \param[in,out] ls      BCLS problem context.
  \param[in]     m       Number of rows in A.
  \param[in]     nFree   Number of columns in A(:,ix).  Also:
                         - length of ix and dxFree
  \param[in]     ix      Index of free variables.
  \param[in]     damp    Regularization parameter.
  \param[in]     itnLim  Iteration limit on current LSQR call.
  \param[in]     tol     LSQR's atol and btol.
  \param[in,out] dxFree  Search direction on free variables.
  \param[in,out] x       Current point.
  \param[in]     c       Linear term.
  \param[in,out] r       Residual. Used as RHS for LSQR.
  \param[out]    itns    Number of LSQR iterations on current subproblem.
  \param[out]    opt     Optimality achieved by LSQR on current subproblem.

  \return
  - 0: Required accurace was achieved.
  - 1: The iteration limit (itnLim) was reached.
  - 2: A(:,ix) is excessively ill-conditioned.

*/
int
bcls_newton_step_lsqr( BCLS *ls, int m, int nFree, int ix[], double damp,
		       int itnLim, double tol, double dxFree[], double x[],
		       double c[], double r[], int *itns, double *opt )
{
    int j, k;                             // Misc. counters.
    int mpn;                              // No. of rows in [ N; damp I ].
    int          unscale_dxFree   = 0;
    const int    rescaling_method = 1;
    const int    linear   = c != NULL;
    const int    damped   = damp  > 0.0;
    const double damp_min = ls->damp_min;
    const double damp2    = damp * damp;
    const double zero     = 0.0;
    double damp_actual;
    double beta1, beta2;

    // LSQR outputs
    int    istop;      // Termination flag.
    double anorm;      // Estimate of Frobenious norm of Abar.
    double acond;      // Estimate of condition no. of Abar.
    double rnorm;      // Estimate of the final value of norm(rbar).
    double xnorm;      // Estimate of the norm of the final solution dx.

    // Set r(m+1:) <- - 1/beta c + damp^2/beta x, where
    //     beta = max(min_damp, damp).
    // Note that r is declared length (m+n), so there is always enough
    // space.  The chosen damping parameter is stored in
    // ls->damp_actual so that it can be used in bcls_aprod_free.
    if (!damped  &&  !linear) {
	mpn = m;
	ls->damp_actual = 0.0;
    }
    else {

	mpn = m + nFree;

	//--------------------------------------------------------------
	// Rescale the RHS.  Will need to unscale LSQR's solution later.
	//--------------------------------------------------------------
	if (rescaling_method) {

	    if (linear) {

		unscale_dxFree  = 1;
		damp_actual     = fmax( damp, damp_min );
		ls->damp_actual = damp_actual;

		// r(1:m) = damp_actual * r(1:m)
		cblas_dscal( m, damp_actual, r, 1 );

		// r(m+1:) = - c
		for (j = 0; j < nFree; j++ )
		    r[m+j] = - c[ ix[j] ];

		// r(m+1:) = - c - damp^2 * x
		if (damped)
		    for (j = 0; j < nFree; j++)
			r[m+j] -= damp2 * x[ ix[j] ];
	    }
	    else {

	        ls->damp_actual = damp;
		for (j = 0; j < nFree; j++)
		    r[m+j] = - damp * x[ ix[j] ];
	    }
	}
	//--------------------------------------------------------------
	// No rescaling.
	//--------------------------------------------------------------
	else {
	    if (damped  &&  linear) {

		damp_actual     =  fmax( damp, damp_min );
		beta1           =  -1.0 / damp_actual;
		beta2           =  -damp * damp / damp_actual;
		ls->damp_actual =  damp_actual;

		for (j = 0; j < nFree; j++) {
		    k = ix[j];
		    r[m+j] = beta1 * c[k] + beta2 * x[k];
		}
	    }
	    else if ( damped  &&  !linear ) {

		beta2 = - damp;
		ls->damp_actual = damp;
		for (j = 0; j < nFree; j++)
		    r[m+j] = beta2 * x[ ix[j] ];
	    }
	    else if (!damped  &&   linear ) {

		beta1 = - 1.0 / damp_min;
		ls->damp_actual = damp_min;
		for (j = 0; j < nFree; j++)
		    r[m+j] = beta1 * c[ ix[j] ];
	    }
	}
    }

    // -----------------------------------------------------------------
    // Solve the subproblem with LSQR.
    // -----------------------------------------------------------------
    bcls_timer( &(ls->stopwatch[BCLS_TIMER_LSQR]), BCLS_TIMER_START );

    lsqr( mpn, nFree, aprod_free_lsqr, zero, (void *)ls,
          r, ls->wrk_v, ls->wrk_w, dxFree, NULL,
          tol, tol, ls->conlim, itnLim, ls->minor_file,
          &istop, itns, &anorm, &acond,
          &rnorm, opt, &xnorm );

    bcls_timer( &(ls->stopwatch[BCLS_TIMER_LSQR]), BCLS_TIMER_STOP );

    // -----------------------------------------------------------------
    // Cleanup and exit.  First unscale dxFree if needed.
    // -----------------------------------------------------------------
    if (rescaling_method  &&  unscale_dxFree)
	cblas_dscal( nFree, 1.0/damp_actual, dxFree, 1 );

    if (istop <= 3)
	return 0;
    else if (istop == 5)
	return 1;
    else
	return 2;
}


// --------------------------------------------------------------------------
//                   OpenMS -- Open-Source Mass Spectrometry
// --------------------------------------------------------------------------
// Copyright The OpenMS Team -- Eberhard Karls University Tuebingen,
// ETH Zurich, and Freie Universitaet Berlin 2002-2013.
//
// This software is released under a three-clause BSD license:
//  * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//  * Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//  * Neither the name of any author or any participating institution
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission.
// For a full list of authors, refer to the file AUTHORS.
// --------------------------------------------------------------------------
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL ANY OF THE AUTHORS OR THE CONTRIBUTING
// INSTITUTIONS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// --------------------------------------------------------------------------
// $Maintainer: Erhan Kenar $
// $Authors: Vipul Patel $
// --------------------------------------------------------------------------
#ifndef OPENMS_COMPARISON_SPECTRA_COMPAREFOURIERTRANSFORM_H
#define OPENMS_COMPARISON_SPECTRA_COMPAREFOURIERTRANSFORM_H

#include <OpenMS/COMPARISON/SPECTRA/PeakSpectrumCompareFunctor.h>
#include <OpenMS/KERNEL/StandardTypes.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_fft_real.h>

namespace OpenMS
{
  /**
      @brief Compare Discrete Cosines value from a Fourier transformation, also known as Discrete Cosines Transformation

      The Direct Cosines Transformation based on the theory of the Fourier transformation.
      In this class the Fast Fourier Transformation(FFT) algorithm of the gsl library is used. FFT has a run-time complexity of
      n (log n). To get the Direct Cosines Transformation from a FFT there is preparation necessary. First the
      input data has to be mirrored. This is necessary, because FFT needs data which has a periodic nature.
      After the computation of FFT only the cosine values are important and stored in the Meta Data Array. So an inverse transformation of these values
      to get the original spectrum is not available.
      The comparison is done between two Meta Data Arrays, which contain the stored cosine values of their individual spectrum.
      The advantage of this method is how the comparison works. There is only one sum which has to be count, no multiplication is needed.

      Attention: only use the compare function, if the Spectrum was transformed earlier, else an error is going to appear.
      Only use this method of transformation, if you are sure there exists enough free memory. This is a fast estimation, but it only gives one or
      zero back.

      @htmlinclude OpenMS_CompareFouriertransform.parameters

      @ingroup SpectraComparison

  */

  class OPENMS_DLLAPI CompareFouriertransform :
    public PeakSpectrumCompareFunctor
  {
public:

    // @name Constructors and Destructors
    // @{
    /// default constructor
    CompareFouriertransform();

    /// copy constructor
    CompareFouriertransform(const CompareFouriertransform & source);

    /// destructor
    virtual ~CompareFouriertransform();
    // @}

    // @name Operators
    // @{
    /// assignment operator
    CompareFouriertransform & operator=(const CompareFouriertransform & source);

    /**
      @brief Dummy function

      This function only returns 0 for any given PeakSpectrum, please use the other compare operator function
    */
    double operator()(const PeakSpectrum &) const;
    /**
        @brief compare two PeakSpectrum by their Discrete Cosines Transformation.

        This function compares two given PeakSpectrum about their  Discrete Cosines Transformation.
        First, a transformation has to be calculated. Please use the function transform() in this class, before calling this
            function. The comparison works by summing the subtractions of each coefficient for all elements of both transformations. sum(_i=1)
            ^n x_i-y_i. If the sum is zero, both Spectra are identical in the real part and one is emitted, otherwise a zero.
        */
    double operator()(const PeakSpectrum & spec1, const PeakSpectrum & spec2) const;

    ///
    static PeakSpectrumCompareFunctor * create() { return new CompareFouriertransform(); }

    ///Returns the name used in the factory
    static const String getProductName()
    {
      return "CompareFouriertransform";
    }

    /**
    @brief calculate the Discrete Cosines Fourier Transformation.

    This function transforms a given PeakSpectrum to a Discrete Cosines Fourier
    Transformation. It stores only the part of the cosines of the FFT in the
    FloatDataArray which is a container from the PeakSpectrum.  Only call this
    function, if you are sure there is no other transformation done earlier
    over the same PeakSpectrum, because it isn't
    checked if there already exists a transformation.
    */
    void transform(PeakSpectrum & spec);
protected:
    /**
        @brief Search in the PeakSpectrum, if a Discrete Fourier transformation
        occurs, if not an error is going to be thrown, else the index
        of the occurrence is returned.

        This function gives back the position, where the transformation was
        saved in a FloatDataArray. If there is no entry, an error is thrown to
        indicate that a transformation has to be calculated before calling this
        comparison operator.
    */
    UInt searchTransformation_(const PeakSpectrum & spec) const;

  };

}
#endif /*OPENMS_COMPARISON_SPECTRA_COMPAREFOURIERTRANSFORM_H*/