#define _BSD_SOURCE
#include <bcm2835.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <math.h>
#include "csvparser.h"
#include <time.h>
#include "mailbox.h"
#include "gpu_fft.h"
#include "spi.h"
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_complex_math.h>

gsl_matrix_complex *compute_fft(int *points, int length) { //length should be specified as length = log2(FFT_length)
    unsigned t[2];
    struct GPU_FFT_COMPLEX *dataIn,*dataOut;
    struct GPU_FFT *fftinfo;

    int mb = mbox_open();
    gpu_fft_prepare(mb,length,GPU_FFT_FWD,1,&fftinfo);

    dataIn = fftinfo->in;
    dataOut = fftinfo->out;

    // Fill the fft data input array with signal
    int i;
    int FFT_length = 1 << length;
    for (i = 0; i < FFT_length; i++) {
	dataIn[i].re = points[i];
	dataIn[i].im = 0;
    }

    usleep(1); // Yield to OS
    clock_t t1 = clock();
    gpu_fft_execute(fftinfo);
    clock_t t2 = clock();
    float diff = (1000000*(float)(t2 - t1) / CLOCKS_PER_SEC );
    printf("Computed FFT of length %d in %f microseconds\n",FFT_length,diff);

    gsl_matrix_complex *matrix = gsl_matrix_complex_alloc(FFT_length,1); //Initialize complex fft vector
    // Fill it with complex values
    for (i = 0; i < FFT_length; i++) {
       gsl_complex complex = gsl_complex_rect(dataOut[i].re,dataOut[i].im);
       gsl_matrix_complex_set(matrix, i, 0,complex); //Fill matrix rows
    }
    gpu_fft_release(fftinfo);
    return matrix;
}

/*
scale_matrix function

matrix - comlex fft matrix to scale

This function scales matrix by it's size.

I use it to normalize fft vector.
*/
void scale_matrix(gsl_matrix_complex *matrix) {
     size_t size = matrix->size1;
     double scale = 1.0/size;
     gsl_complex complex_scale = gsl_complex_rect(scale,0); // This is just a scale represented by a complex number
     gsl_matrix_complex_scale(matrix,complex_scale); //Scale matrix by the length of the signal
}

/*
abs_matrix function

matrix - comlex fft matrix

This function takes an absolute valus of the complex matrix and returns a real matrix as a result.

*/
gsl_matrix *abs_matrix(gsl_matrix_complex *matrix) {
     size_t size = matrix->size1;
     gsl_matrix *absolute = gsl_matrix_alloc(size,1);
     for (int i = 0; i < size; i++) {
         gsl_complex complex = gsl_matrix_complex_get(matrix,i,0);
	 double abs = gsl_complex_abs(complex);
	 gsl_matrix_set(absolute,i,0,abs);
     }
     return absolute;
}

/*
half_matrix function

matrix - real values matrix

This function "chops" matrix in half to get the positive frequency spectrum.
*/
gsl_matrix *half_matrix(gsl_matrix *matrix) {
     size_t size = matrix->size1;
     size_t half_size = (size / 2)+1;
     gsl_matrix *half = gsl_matrix_alloc(half_size,1); //Take only half of the absolute of ff
     for (int i = 0; i < half_size; i++) {
         double value = gsl_matrix_get(matrix,i,0);
	 gsl_matrix_set(half,i,0,value);
     }
     return half;
}

/*
save_waveform_to_file function

data - signal
length - signal length
filename - name of the file to be saved to

Saves the signal to the file.
*/
void save_waveform_to_file(int *data, int length, char *filename) {
    FILE *f = fopen(filename,"w");
    printf("Length: %d\n",length);
    for (int i = 0; i < length; i++) {
	fprintf(f,"%d,\n",data[i]);
    }
    fclose(f);
}

/*
print_result_to_file function

result - vector
filename - name of the file to be saved to

Saves the vector to the file.
*/
void print_result_to_file(gsl_matrix *result, char *filename) {
        FILE *f = fopen(filename,"w");
	int size = result->size1;
	for (int i = 0; i < size; i++) {
            double value = gsl_matrix_get(result,i,0);
	    fprintf(f,"%d,\n",value);
	}
	fclose(f);
}

/*
print_fft_to_file function

result - complex fft vector

Saves the vector to the file.
*/
void print_fft_to_file(gsl_matrix_complex *result) {
        FILE *real = fopen("real.csv","w");
        FILE *imag = fopen("imag.csv","w");

	int size = result->size1;
	for (int i = 0; i < size; i++) {
            gsl_complex value = gsl_matrix_complex_get(result,i,0);
	    fprintf(real,"%f,\n",value.dat[0]);
	    fprintf(imag,"%f,\n",value.dat[1]);
	}
	fclose(real);
	fclose(imag);
}

void display_harmonics(gsl_matrix *fft_vector) {
	double value_60 = gsl_matrix_get(fft_vector,60,0);
	double amplitude = value_60 * (3.72/4096);
	printf("Amplitude of 60Hz is: %f\n",amplitude);
}

/*
extract_50_harmonics function

fft_vector - complex fft vector

Exracts 50 fundamental hormonics
60,120,180, etc

Returns the harmonics array of size 50.
*/
int *extract_50_harmonics(gsl_matrix *fft_vector) {
	int *harmonics = (int *)malloc(sizeof(int)*50);;
	for (int i = 1; i < 51; i++) {
		double value = gsl_matrix_get(fft_vector,i*60,0);
		int amplitude = (int)(value * (1000.0/4096) / 1.14);
		harmonics[i-1] = amplitude;
	}
	return harmonics;
}

/*
perform_fft_analysis function

points - signal

performs fft analysis
*/
void perform_fft_analysis(int *points) {
    clock_t t1 = clock();
    int length = 1 << WINDOW_SIZE; // Sampling frequency
    gsl_matrix_complex *fft_matrix = compute_fft(points,WINDOW_SIZE); // performs fft transformation of the signal
    scale_matrix(fft_matrix); // normalizes the resulting fft vector
    gsl_matrix *absolute = abs_matrix(fft_matrix); // takes an absolute value of the fft vector
    gsl_matrix *half = half_matrix(absolute); // and chops of the negative part.
    gsl_matrix_scale(half,2); // Scales by two
    clock_t t2 = clock();
    float diff = ((float)(t2 - t1) / CLOCKS_PER_SEC );
    printf("FFT analysis took: %f seconds\n",diff);

    int *harmonics = extract_50_harmonics(half); // extracts harmonics
    for (int i = 1; i < 51; i++) { // diplays harmonics aplitudes.
	   printf("Amplitude at %d Hz is %d mV\n",i*60,harmonics[i-1]);
    }

    // frees the resources.
    gsl_matrix_free(half);
    gsl_matrix_free(absolute);
    gsl_matrix_complex_free(fft_matrix);
    free(harmonics);
}

/*
rms function

signal - signal
frequency - signal frequency

Computes rms value of the given signal.
*/
int rms(int *signal, int frequency) {
    clock_t t1 = clock();
    int sum = 0;
    for (int i = 0; i < frequency; i++) {
       int data = signal[i];
	sum += data*data;
    }
    sum /= frequency;
    double root = sqrt((double)sum);
    clock_t t2 = clock();
    float diff = ((float)(t2 - t1) / CLOCKS_PER_SEC );
    printf("RMS analysis took: %f seconds\n",diff);
    printf("RMS value: %f\n",root);
    return (int)root;
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        printf("Usage %s channel\n",argv[0]);
	    return 1;
    }

    int channels[8] = {0};

     // Extract which channels to sample from the command arguments.
    for (int arg_index = 0; arg_index < argc - 1; arg_index++) {
         int channel = atoi(argv[arg_index + 1]);
	     printf("Sampling channel %d\n",channel);
         channels[channel] = 1;
    }

    if (init()) {
        printf("Init failed.\n");
	    return 1;
    } else {
	    printf("Succesfull init of bcm2835.\n");
    }
    printf("Starting bcm2835 setup...\n");
    setup();
    printf("bcm2835 setup finished.\n");

    for (int i = 0; i < 1; i++) { // Specify sampling duration here! If we want to sample indefinetely, just write while(1) {}
	    int frequency = 1 << WINDOW_SIZE;
        sample *s = sample_data(channels,frequency);
	    perform_fft_analysis(s->channel_data[1]);
	    sample_deallocate(s);
    }

    printf("Ending spi connection ...\n");
    end();
    printf("bcm2835 has ended.\n");
}


/* mcmc.h
 * David W. Pearson
 * 17 July 2018
 *
 * This header file will be responsible for running Markov Chain Monte Carlo fitting.
 */

#ifndef _MCMC_H_
#define _MCMC_H_

#include "bispectrum_model.h"
#include <iostream>
#include <fstream>
#include <random>
#include <vector>
#include <string>
#include <cmath>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_linalg.h>

std::random_device seeder;
std::mt19937_64 gen(seeder());
std::uniform_real_distribution<double> dist(-1.0, 1.0);

class bkmcmc{
    int num_data, num_pars;
    std::vector<double> data; // These should have size of num_data
    std::vector<std::vector<double>> Psi; // num_data vectors of size num_data
    std::vector<double> theta_0, theta_i, param_vars, min, max; // These should all have size of num_pars
    std::vector<float4> k; // This should have size of num_data
    std::vector<bool> limit_pars; // This should have size of num_pars
    double chisq_0, chisq_i;

    // Sets the values of theta_i.
    void get_param_real(); // done

    // Calculates the chi^2 for the current proposal, theta_i
    double calc_chi_squared(); // done

    // Performs one MCMC trial. Returns true if proposal accepted, false otherwise
    bool trial(float4 *ks, double *Bk, double &L, double &R); // done

    // Writes the current accepted parameters to the screen
    void write_theta_screen(); // done

    // Burns the requested number of parameter realizations to move to a higher likelihood region
    void burn_in(int num_burn, float4 *ks, double *Bk); // done

    // Changes the initial guesses for the search range around parameters until acceptance = 0.234
    void tune_vars(float4 *ks, double *Bk); // done

    public:
        std::vector<double> model; // These should have size num_data

        // Initializes most of the data members and gets an initial chisq_0
        bkmcmc(std::string data_file, std::string cov_file, std::vector<double> &pars,
               std::vector<double> &vars, float4 *ks, double *Bk); // done

        // Displays information to the screen to check that the vectors are all the correct size
        void check_init(); // done

        // Sets which parameters should be limited and what the limits are
        void set_param_limits(std::vector<bool> &lim_pars, std::vector<double> &min_in,
                              std::vector<double> &max_in); // done

        // Runs the MCMC chain for num_draws realizations, writing to reals_file
        void run_chain(int num_draws, int num_burn, std::string reals_file, float4 *ks, double *Bk, bool new_chain);

};

void bkmcmc::get_param_real() {
    for (int i = 0; i < bkmcmc::num_pars; ++i) {
        if (bkmcmc::limit_pars[i]) {
            if (bkmcmc::theta_0[i] + bkmcmc::param_vars[i] > bkmcmc::max[i]) {
                double center = bkmcmc::max[i] - bkmcmc::param_vars[i];
                bkmcmc::theta_i[i] = center + dist(gen)*bkmcmc::param_vars[i];
            } else if (bkmcmc::theta_0[i] - bkmcmc::param_vars[i] < bkmcmc::min[i]) {
                double center = bkmcmc::min[i] + bkmcmc::param_vars[i];
                bkmcmc::theta_i[i] = center + dist(gen)*bkmcmc::param_vars[i];
            } else {
                bkmcmc::theta_i[i] = bkmcmc::theta_0[i] + dist(gen)*bkmcmc::param_vars[i];
            }
        } else {
            bkmcmc::theta_i[i] = bkmcmc::theta_0[i] + dist(gen)*bkmcmc::param_vars[i];
        }
    }
}

double bkmcmc::calc_chi_squared() {
    double chisq = 0.0;
    for (int i = 0; i < bkmcmc::num_data; ++i) {
        for (int j = i; j < bkmcmc::num_data; ++j) {
            if (bkmcmc::data[i] > 0 && bkmcmc::data[j] > 0) {
                chisq += (bkmcmc::data[i] - bkmcmc::model[i])*Psi[i][j]*(bkmcmc::data[j] - bkmcmc::model[j]);
            }
        }
    }
    return chisq;
}

bool bkmcmc::trial(float4 *ks, double *d_Bk, double &L, double &R) {
    bkmcmc::get_param_real();
    model_calc(bkmcmc::theta_i, ks, d_Bk, bkmcmc::model);
    bkmcmc::chisq_i = bkmcmc::calc_chi_squared();

    L = exp(0.5*(bkmcmc::chisq_0 - bkmcmc::chisq_i));
    R = (dist(gen) + 1.0)/2.0;

    if (L > R) {
        for (int i = 0; i < bkmcmc::num_pars; ++i)
            bkmcmc::theta_0[i] = bkmcmc::theta_i[i];
        bkmcmc::chisq_0 = bkmcmc::chisq_i;
        return true;
    } else {
        return false;
    }
}

void bkmcmc::write_theta_screen() {
    std::cout.precision(6);
    for (int i = 0; i < bkmcmc::num_pars; ++i) {
        std::cout.width(15);
        std::cout << bkmcmc::theta_0[i];
    }
    std::cout.width(15);
    std::cout << pow(bkmcmc::theta_0[3]*bkmcmc::theta_0[4]*bkmcmc::theta_0[4],1.0/3.0);
    std::cout.width(15);
    std::cout << bkmcmc::chisq_0;
    std::cout.flush();
}

void bkmcmc::burn_in(int num_burn, float4 *ks, double *d_Bk) {
    std::cout << "Burning the first " << num_burn << " trials to move to higher likelihood..." << std::endl;
    double L, R;
    for (int i = 0; i < num_burn; ++i) {
        bool move = bkmcmc::trial(ks, d_Bk, L, R);
        if (true) {
            std::cout << "\r";
            std::cout.width(5);
            std::cout << i;
            bkmcmc::write_theta_screen();
            std::cout.width(15);
            std::cout << L;
            std::cout.width(15);
            std::cout << R;
            std::cout.flush();
        }
    }
    std::cout << std::endl;
}

void bkmcmc::tune_vars(float4 *ks, double *d_Bk) {
    std::cout << "Tuning acceptance ratio..." << std::endl;
    double acceptance = 0.0;
    while (acceptance <= 0.233 || acceptance >= 0.235) {
        int accept = 0;
        double L, R;
        for (int i = 0; i < 10000; ++i) {
            bool move = bkmcmc::trial(ks, d_Bk, L, R);
            if (move) {
                std::cout << "\r";
                bkmcmc::write_theta_screen();
                accept++;
            }
        }
        std::cout << std::endl;
        acceptance = double(accept)/10000.0;

        if (acceptance <= 0.233) {
            for (int i = 0; i < bkmcmc::num_pars; ++i)
                bkmcmc::param_vars[i] *= 0.99;
        }
        if (acceptance >= 0.235) {
            for (int i = 0; i < bkmcmc::num_pars; ++i)
                bkmcmc::param_vars[i] *= 1.01;
        }
        std::cout << "acceptance = " << acceptance << std::endl;
    }
    std::ofstream fout;
    fout.open("variances.dat", std::ios::out);
    for (int i = 0; i < bkmcmc::num_pars; ++i)
        fout << bkmcmc::param_vars[i] << " ";
    fout << "\n";
    fout.close();
}

bkmcmc::bkmcmc(std::string data_file, std::string cov_file, std::vector<double> &pars,
               std::vector<double> &vars, float4 *ks, double *d_Bk) {
    std::ifstream fin;
    std::ofstream fout;

    std::cout << "Reading in and storing data file..." << std::endl;
    if (std::ifstream(data_file)) {
        fin.open(data_file.c_str(), std::ios::in);
        while (!fin.eof()) {
            float4 kt;
            double B;
            fin >> kt.w >> kt.x >> kt.y >> kt.z >> B;
            if (!fin.eof()) {
                bkmcmc::k.push_back(kt);
                bkmcmc::data.push_back(B);
                bkmcmc::model.push_back(0.0);
            }
        }
        fin.close();
    } else {
        std::stringstream message;
        message << "Could not open " << data_file << std::endl;
        throw std::runtime_error(message.str());
    }

    bkmcmc::num_data = bkmcmc::data.size();
    std::cout << "num_data = " << bkmcmc::num_data << std::endl;

    gsl_matrix *cov = gsl_matrix_alloc(bkmcmc::num_data, bkmcmc::num_data);
    gsl_matrix *psi = gsl_matrix_alloc(bkmcmc::num_data, bkmcmc::num_data);
    gsl_permutation *perm = gsl_permutation_alloc(bkmcmc::num_data);

    std::cout << "Reading in covariance and computing its inverse..." << std::endl;
    if (std::ifstream(cov_file)) {
        fin.open(cov_file.c_str(), std::ios::in);
        for (int i = 0; i < bkmcmc::num_data; ++i) {
            for (int j = 0; j < bkmcmc::num_data; ++j) {
                double element;
                fin >> element;
                gsl_matrix_set(cov, i, j, element);
            }
        }
        fin.close();
    } else {
        std::stringstream message;
        message << "Could not open " << cov_file << std::endl;
        throw std::runtime_error(message.str());
    }

    int s;
    gsl_linalg_LU_decomp(cov, perm, &s);
    gsl_linalg_LU_invert(cov, perm, psi);

    for (int i = 0; i < bkmcmc::num_data; ++i) {
        std::vector<double> row;
        row.reserve(bkmcmc::num_data);
        for (int j = 0; j < bkmcmc::num_data; ++j) {
            row.push_back((1.0 - double(bkmcmc::num_data + 1.0)/2048.0)*gsl_matrix_get(psi, i, j));
        }
        bkmcmc::Psi.push_back(row);
    }

    gsl_matrix_free(cov);
    gsl_matrix_free(psi);
    gsl_permutation_free(perm);

    gpuErrchk(cudaMemcpy(ks, bkmcmc::k.data(), bkmcmc::num_data*sizeof(float4), cudaMemcpyHostToDevice));

    gpuErrchk(cudaMemcpy(d_Bk, bkmcmc::model.data(), bkmcmc::num_data*sizeof(double),
                         cudaMemcpyHostToDevice));

    bkmcmc::num_pars = pars.size();
    std::cout << "num_pars = " << bkmcmc::num_pars << std::endl;

    for (int i = 0; i < bkmcmc::num_pars; ++i) {
        bkmcmc::theta_0.push_back(pars[i]);
        bkmcmc::theta_i.push_back(0.0);
        bkmcmc::limit_pars.push_back(false);
        bkmcmc::max.push_back(0.0);
        bkmcmc::min.push_back(0.0);
        bkmcmc::param_vars.push_back(vars[i]);
    }

    std::cout << "Calculating initial model and chi^2..." << std::endl;
    model_calc(bkmcmc::theta_0, ks, d_Bk, bkmcmc::model);
    bkmcmc::chisq_0 = bkmcmc::calc_chi_squared();

    fout.open("Bk_mod_check.dat", std::ios::out);
    for (int i =0; i < bkmcmc::num_data; ++i) {
        fout.precision(3);
        fout << bkmcmc::k[i].x << " " << bkmcmc::k[i].y << " " << bkmcmc::k[i].z << " ";
        fout.precision(15);
        fout << bkmcmc::data[i] << " " << bkmcmc::model[i] << "\n";
    }
    fout.close();
}

void bkmcmc::check_init() {
    std::cout << "Number of data points: " << bkmcmc::num_data << std::endl;
    std::cout << "    data.size()      = " << bkmcmc::data.size() << std::endl;
    std::cout << "    model.size()     = " << bkmcmc::model.size() << std::endl;
    std::cout << "    Psi.size()       = " << bkmcmc::Psi.size() << std::endl;
    std::cout << "Number of parameters:  " << bkmcmc::num_pars << std::endl;
    std::cout << "    theta_0.size()   = " << bkmcmc::theta_0.size() << std::endl;
    std::cout << "    theta_i.size()   = " << bkmcmc::theta_i.size() << std::endl;
    std::cout << "    limit_pars.size()= " << bkmcmc::limit_pars.size() << std::endl;
    std::cout << "    min.size()       = " << bkmcmc::min.size() << std::endl;
    std::cout << "    max.size()       = " << bkmcmc::max.size() << std::endl;
    std::cout << "    param_vars.size()= " << bkmcmc::param_vars.size() << std::endl;
}

void bkmcmc::set_param_limits(std::vector<bool> &lim_pars, std::vector<double> &min_in,
                              std::vector<double> &max_in) {
    for (int i = 0; i < bkmcmc::num_pars; ++i) {
        bkmcmc::limit_pars[i] = lim_pars[i];
        bkmcmc::max[i] = max_in[i];
        bkmcmc::min[i] = min_in[i];
    }
}

void bkmcmc::run_chain(int num_draws, int num_burn, std::string reals_file, float4 *ks, double *d_Bk, bool new_chain) {
    int num_old_rels = 0;
    if (new_chain) {
        std::cout << "Starting new chain..." << std::endl;
        bkmcmc::burn_in(num_burn, ks, d_Bk);
        bkmcmc::tune_vars(ks, d_Bk);
    } else {
        std::cout << "Resuming previous chain..." << std::endl;
        std::ifstream fin;
        fin.open("variances.dat", std::ios::in);
        for (int i = 0; i < bkmcmc::num_pars; ++i) {
            double var;
            fin >> var;
            bkmcmc::param_vars[i] = var;
        }
        fin.close();
        fin.open(reals_file.c_str(), std::ios::in);
        while (!fin.eof()) {
            double alpha;
            num_old_rels++;
            std::cout << "\r";
            for (int i = 0; i < bkmcmc::num_pars; ++i) {
                fin >> bkmcmc::theta_0[i];
                std::cout.width(10);
                std::cout << bkmcmc::theta_0[i];
            }
            fin >> alpha;
            fin >> bkmcmc::chisq_0;
            std::cout.width(10);
            std::cout << alpha;
            std::cout.width(10);
            std::cout << bkmcmc::chisq_0;
        }
        fin.close();
        num_old_rels--;
    }

    std::ofstream fout;
    double L, R;
    fout.open(reals_file.c_str(), std::ios::app);
    fout.precision(15);
    for (int i = 0; i < num_draws; ++i) {
        bool move = bkmcmc::trial(ks, d_Bk, L, R);
        for (int par = 0; par < bkmcmc::num_pars; ++par) {
            fout << bkmcmc::theta_0[par] << " ";
        }
        double alpha = pow(bkmcmc::theta_0[3]*bkmcmc::theta_0[4]*bkmcmc::theta_0[4], 1.0/3.0);
        fout << alpha << " " << bkmcmc::chisq_0 << "\n";
        if (move) {
            std::cout << "\r";
            std::cout.width(15);
            std::cout << i + num_old_rels;
            bkmcmc::write_theta_screen();
        }
    }
    std::cout << std::endl;
    fout.close();
}

#endif


#ifndef DBJ_FLOAT_VARIOUS_MATMULS_INC
#define DBJ_FLOAT_VARIOUS_MATMULS_INC

#include "dbj_matrix.h"

#include <stdlib.h>
#include <intrin.h> // #define __SSE__ 1
#ifdef HAVE_CBLAS
#include <cblas.h>
#endif

#undef DBJ_API
#define DBJ_API

//////////////////////////////////////////////////////////////////////
// all of the bellow uses:
typedef DBJ_MATRIX_STRUCT(float) float_matrix_struct;

//////////////////////////////////////////////////////////////////////

typedef void* (*mat_mul_function) (
	const unsigned, const unsigned, const unsigned,
	float a[][*], float b[][*], float m[][*]
	);

typedef struct {
	const char* description;
	mat_mul_function function;
} description_function_pair;

#define DF_PAIR(D_,F_) (description_function_pair){ .description = D_ , .function = F_ }

//////////////////////////////////////////////////////////////////////

DBJ_API float_matrix_struct* new_float_matrix(const unsigned /*n_rows*/, const unsigned  /*n_cols*/);
DBJ_API float_matrix_struct* make_random_float_matrix(
	const unsigned /*n_rows*/, const unsigned  /*n_cols*/, float (*)(void)
);
DBJ_API void* mat_transpose(
	const unsigned /*n_rows*/, const unsigned /*n_cols*/,
	float /*a*/[/*static n_rows*/][*/*n_cols*/], float /*m*/[/*static n_cols*/][*/*n_rows*/]
);

DBJ_API void* mat_mul_null(
	const unsigned n_a_rows, const unsigned n_a_cols, const unsigned n_b_cols,
	float a[static n_a_rows][n_a_cols], float b[static n_a_rows][n_b_cols], float m[static n_a_rows][n_b_cols]
) {
	// used for when algorithms are not implemented yet
	(void)a;	(void)b;	(void)m;	return NULL;
}

DBJ_API void* mat_mul_0(const unsigned /*n_a_rows*/, const unsigned /*n_a_cols*/, const unsigned /*n_b_cols*/,
	float a[ /*n_a_rows*/][* /*n_a_cols*/], float b[ /*n_a_rows*/][* /*n_b_cols*/], float m[ /*n_a_rows*/][* /*n_b_cols*/]
);

DBJ_API void* mat_mul_1(const unsigned /*n_a_rows*/, const unsigned /*n_a_cols*/, const unsigned /*n_b_cols*/,
	float a[ /*n_a_rows*/][* /*n_a_cols*/], float b[ /*n_a_rows*/][* /*n_b_cols*/], float m[ /*n_a_rows*/][* /*n_b_cols*/]
);

DBJ_API void* mat_mul_3(const unsigned /*n_a_rows*/, const unsigned /*n_a_cols*/, const unsigned /*n_b_cols*/,
	float a[ /*n_a_rows*/][* /*n_a_cols*/], float b[ /*n_a_rows*/][* /*n_b_cols*/], float m[ /*n_a_rows*/][* /*n_b_cols*/]
);

DBJ_API void* mat_mul_4(const unsigned /*n_a_rows*/, const unsigned /*n_a_cols*/, const unsigned /*n_b_cols*/,
	float a[ /*n_a_rows*/][* /*n_a_cols*/], float b[ /*n_a_rows*/][* /*n_b_cols*/], float m[ /*n_a_rows*/][* /*n_b_cols*/]
);

#if __SSE__

DBJ_API void* mat_mul_2(const unsigned /*n_a_rows*/, const unsigned /*n_a_cols*/, const unsigned /*n_b_cols*/,
	float a[ /*n_a_rows*/][* /*n_a_cols*/], float b[ /*n_a_rows*/][* /*n_b_cols*/], float m[ /*n_a_rows*/][* /*n_b_cols*/]
);

DBJ_API void* mat_mul_7(const unsigned /*n_a_rows*/, const unsigned /*n_a_cols*/, const unsigned /*n_b_cols*/,
	float a[ /*n_a_rows*/][* /*n_a_cols*/], float b[ /*n_a_rows*/][* /*n_b_cols*/], float m[ /*n_a_rows*/][* /*n_b_cols*/]
);

#endif // __SSE__

#ifdef HAVE_CBLAS
DBJ_API void* mat_mul_5(const unsigned /*n_a_rows*/, const unsigned /*n_a_cols*/, const unsigned /*n_b_cols*/,
	float a[ /*n_a_rows*/][* /*n_a_cols*/], float b[ /*n_a_rows*/][* /*n_b_cols*/], float m[ /*n_a_rows*/][* /*n_b_cols*/]
);

DBJ_API void* mat_mul_6(const unsigned /*n_a_rows*/, const unsigned /*n_a_cols*/, const unsigned /*n_b_cols*/,
	float a[ /*n_a_rows*/][* /*n_a_cols*/], float b[ /*n_a_rows*/][* /*n_b_cols*/], float m[ /*n_a_rows*/][* /*n_b_cols*/]
);
#endif // HAVE_CBLAS

const description_function_pair dbj_float_matmuls_algo_table[] =
{
DF_PAIR("0: naive - no optimization" , mat_mul_0) ,
DF_PAIR("1: transposing the second matrix" , mat_mul_1) ,
DF_PAIR("2: explicitly vectorized sdot() with SSE" , mat_mul_2) , /* requires __SSE__ */
DF_PAIR("3: implicitly vectorized sdot()" , mat_mul_3) ,
DF_PAIR("4: no vectorization hints", mat_mul_4) ,
#ifdef HAVE_CBLAS
DF_PAIR("5: with sdot() from an external CBLAS library" , mat_mul_5) ,
DF_PAIR("6: with sgemm() from an external CBLAS library", mat_mul_6) ,
#else
DF_PAIR("5: with sdot() from CBLAS library not implemented " , mat_mul_null) ,
DF_PAIR("6: with sgemm() from CBLAS library not implemented ", mat_mul_null) ,
#endif // ! HAVE_CBLAS
DF_PAIR("7: explicitly SSE sdot() plus loop tiling", mat_mul_7) /* requires __SSE__ */
};

static const unsigned dbj_float_matmuls_algo_table_size = (sizeof(dbj_float_matmuls_algo_table) / sizeof(dbj_float_matmuls_algo_table[0]));

//////////////////////////////////////////////////////////////////////
#ifdef DBJ_FLOAT_VARIOUS_MATMULS_IMPLEMENTATION
//////////////////////////////////////////////////////////////////////

DBJ_API float_matrix_struct* new_float_matrix(const unsigned n_rows, const unsigned  n_cols)
{
	// float_matrix_struct* retval = NULL; // must be init to NULL

	float_matrix_struct* retval = DBJ_MATRIX_ALLOC(n_rows, n_cols, DBJ_MATRIX_STRUCT_SIZE(float, n_rows, n_cols));

	if (retval) {
		retval->rows = n_rows;
		retval->cols = n_cols;
	}
	else {
		fprintf(stderr, "\n%s(%d) DBJ_MATRIX_NEW failed?\n", __FILE__, __LINE__);
		perror(" ");
		exit(1);
	}
	return retval;
}

/*
allocate new matrix and populate it with random float's
*/
DBJ_API float_matrix_struct* make_random_float_matrix(
	const unsigned n_rows, const unsigned  n_cols, float (*float_rand)(void)
)
{
	float_matrix_struct* fmt_pointer = new_float_matrix(n_rows, n_cols);

	DBJ_MATRIX_ALIAS(matrix, float, n_cols);
	DBJ_MATRIX_CAST(mx, matrix, fmt_pointer);

	for (unsigned i = 0; i < n_rows; ++i)
		for (unsigned j = 0; j < n_cols; ++j)
			mx[i][j] = float_rand(); //
	return fmt_pointer;
}

DBJ_API void* mat_transpose(
	const unsigned n_rows, const unsigned n_cols,
	float a[static n_rows][n_cols], float m[static n_cols][n_rows]
)
{
	for (unsigned i = 0; i < n_rows; ++i)
		for (unsigned j = 0; j < n_cols; ++j)
			m[j][i] = a[i][j];
	return m;
}

DBJ_API float sdot_1(int n, const float x[static n], const float y[static n])
{
	float s = 0.0f;
	for (int i = 0; i < n; ++i) s += x[i] * y[i];
	return s;
}

DBJ_API float sdot_8(int n, const float x[static n], const float y[static n])
{
	int i, n8 = n >> 3 << 3;
	float s = 0.0f, t[8] = { 0.0f };
	// t[0] = t[1] = t[2] = t[3] = t[4] = t[5] = t[6] = t[7] = 0.0f;
	for (i = 0; i < n8; i += 8) {
		t[0] += x[i + 0] * y[i + 0];
		t[1] += x[i + 1] * y[i + 1];
		t[2] += x[i + 2] * y[i + 2];
		t[3] += x[i + 3] * y[i + 3];
		t[4] += x[i + 4] * y[i + 4];
		t[5] += x[i + 5] * y[i + 5];
		t[6] += x[i + 6] * y[i + 6];
		t[7] += x[i + 7] * y[i + 7];
	}
	for (s = 0.0f; i < n; ++i) s += x[i] * y[i];
	s += t[0] + t[1] + t[2] + t[3] + t[4] + t[5] + t[6] + t[7];
	return s;
}

#ifdef __SSE__

DBJ_API float sdot_sse(int n, const float x[static n], const float y[static n])
{
	int i, n8 = n >> 3 << 3;
	__m128 vs1, vs2;
	float s, t[4];
	vs1 = _mm_setzero_ps();
	vs2 = _mm_setzero_ps();
	for (i = 0; i < n8; i += 8) {
		__m128 vx1, vx2, vy1, vy2;
		vx1 = _mm_loadu_ps(&x[i]);
		vx2 = _mm_loadu_ps(&x[i + 4]);
		vy1 = _mm_loadu_ps(&y[i]);
		vy2 = _mm_loadu_ps(&y[i + 4]);
		vs1 = _mm_add_ps(vs1, _mm_mul_ps(vx1, vy1));
		vs2 = _mm_add_ps(vs2, _mm_mul_ps(vx2, vy2));
	}
	for (s = 0.0f; i < n; ++i) s += x[i] * y[i];
	_mm_storeu_ps(t, vs1);
	s += t[0] + t[1] + t[2] + t[3];
	_mm_storeu_ps(t, vs2);
	s += t[0] + t[1] + t[2] + t[3];
	return s;
}
#endif // __SSE__

/**************************************************

 Various Matrix multiplication algorithms

 NOTE: check and choose for your RT Env
	   there are differences but are *very* dependant on
	   compiler, OS and hardware

 */

DBJ_API void* mat_mul_0(
	const unsigned n_a_rows, const unsigned n_a_cols, const unsigned n_b_cols,
	float a[static n_a_rows][n_a_cols], float b[static n_a_rows][n_b_cols], float m[static n_a_rows][n_b_cols]
)
{
	for (unsigned i = 0; i < n_a_rows; ++i) {
		for (unsigned j = 0; j < n_b_cols; ++j) {
			float t = 0.0;
			for (unsigned k = 0; k < n_a_cols; ++k)
				t += a[i][k] * b[k][j];
			m[i][j] = t;
		}
	}
	return m;
}

DBJ_API void* mat_mul_1(
	const unsigned n_a_rows, const unsigned n_a_cols, const unsigned n_b_cols,
	float a[static n_a_rows][n_a_cols], float b[static n_a_rows][n_b_cols], float m[static n_a_rows][n_b_cols]
)
{
	const unsigned n_b_rows = n_a_cols;
	// Temp rows and cols are inverted
	float_matrix_struct* Temp = new_float_matrix(n_b_cols, n_b_rows);
	DBJ_MATRIX_ALIAS(matrix, float, n_b_rows);
	DBJ_MATRIX_CAST(bT, matrix, Temp);
	(void)mat_transpose(n_b_rows, n_b_cols, b, bT);

	for (unsigned i = 0; i < n_a_rows; ++i) {
		const float* ai = a[i];
		float* mi = m[i];
		for (unsigned j = 0; j < n_b_cols; ++j) {
			float t = 0.0f, * bTj = bT[j];
			for (unsigned k = 0; k < n_a_cols; ++k)
				t += ai[k] * bTj[k];
			mi[j] = t;
		}
	}

	free(Temp);

	return m;
}

DBJ_API void* mat_mul_2(
	const unsigned n_a_rows, const unsigned n_a_cols, const unsigned n_b_cols,
	float a[static n_a_rows][n_a_cols], float b[static n_a_rows][n_b_cols], float m[static n_a_rows][n_b_cols]
)
{
#ifndef __SSE__
#error __SEE__ is required here
#endif

	const unsigned n_b_rows = n_a_cols;

	float_matrix_struct* Temp = new_float_matrix(n_b_cols, n_b_rows);
	DBJ_MATRIX_ALIAS(matrix, float, n_b_rows);
	DBJ_MATRIX_CAST(bT, matrix, Temp);
	(void)mat_transpose(n_b_rows, n_b_cols, b, bT);

	for (unsigned i = 0; i < n_a_rows; ++i)
		for (unsigned j = 0; j < n_b_cols; ++j)
			m[i][j] = sdot_sse(n_a_cols, a[i], bT[j]);
	free(Temp);
	return m;
}

DBJ_API void* mat_mul_7(
	const unsigned n_a_rows, const unsigned n_a_cols, const unsigned n_b_cols,
	float a[static n_a_rows][n_a_cols], float b[static n_a_rows][n_b_cols], float m[static n_a_rows][n_b_cols]
)
{
#ifndef __SSE__
#error __SEE__ is required here
#endif

	const unsigned x = 16, n_b_rows = n_a_cols;

	float_matrix_struct* Temp = new_float_matrix(n_b_cols, n_b_rows);
	DBJ_MATRIX_ALIAS(matrix, float, n_b_rows);
	DBJ_MATRIX_CAST(bT, matrix, Temp);
	(void)mat_transpose(n_b_rows, n_b_cols, b, bT);

	for (unsigned i = 0; i < n_a_rows; i += x) {
		for (unsigned j = 0; j < n_b_cols; j += x) {
			unsigned je = n_b_cols < j + x ? n_b_cols : j + x;
			unsigned ie = n_a_rows < i + x ? n_a_rows : i + x;
			for (unsigned ii = i; ii < ie; ++ii)
				for (unsigned jj = j; jj < je; ++jj)
					m[ii][jj] += sdot_sse(n_a_cols, a[ii], bT[jj]);
		}
	}
	free(Temp);
	return m;
}

/////////////////////////////////////////////////////////////////////////////////////

DBJ_API void* mat_mul_3(
	const unsigned n_a_rows, const unsigned n_a_cols, const unsigned n_b_cols,
	float a[static n_a_rows][n_a_cols], float b[static n_a_rows][n_b_cols], float m[static n_a_rows][n_b_cols]
)
{
	int  n_b_rows = n_a_cols;

	float_matrix_struct* Temp = new_float_matrix(n_b_cols, n_b_rows);
	DBJ_MATRIX_ALIAS(matrix, float, n_b_rows);
	DBJ_MATRIX_CAST(bT, matrix, Temp);
	(void)mat_transpose(n_b_rows, n_b_cols, b, bT);

	for (unsigned i = 0; i < n_a_rows; ++i)
		for (unsigned j = 0; j < n_b_cols; ++j)
			m[i][j] = sdot_8(n_a_cols, a[i], bT[j]);
	free(Temp);
	return m;
}

DBJ_API void* mat_mul_4(
	const unsigned n_a_rows, const unsigned n_a_cols, const unsigned n_b_cols,
	float a[static n_a_rows][n_a_cols], float b[static n_a_rows][n_b_cols], float m[static n_a_rows][n_b_cols]
)
{
	int n_b_rows = n_a_cols;

	float_matrix_struct* Temp = new_float_matrix(n_b_cols, n_b_rows);
	DBJ_MATRIX_ALIAS(matrix, float, n_b_rows);
	DBJ_MATRIX_CAST(bT, matrix, Temp);
	(void)mat_transpose(n_b_rows, n_b_cols, b, bT);

	for (unsigned i = 0; i < n_a_rows; ++i)
		for (unsigned j = 0; j < n_b_cols; ++j)
			m[i][j] = sdot_1(n_a_cols, a[i], bT[j]);
	free(Temp);
	return m;
}

#ifdef HAVE_CBLAS

DBJ_API void* mat_mul_5(
	const unsigned n_a_rows, const unsigned n_a_cols, const unsigned n_b_cols,
	float a[static n_a_rows][n_a_cols], float b[static n_a_rows][n_b_cols], float m[static n_a_rows][n_b_cols]
)
{
	const unsigned n_b_rows = n_a_cols;

	float_matrix_struct* Temp = new_float_matrix(n_b_cols, n_b_rows);
	DBJ_MATRIX_ALIAS(matrix, float, n_b_rows);
	DBJ_MATRIX_CAST(bT, matrix, Temp);
	(void)mat_transpose(n_b_rows, n_b_cols, b, bT);

	for (unsigned i = 0; i < n_a_rows; ++i)
		for (unsigned j = 0; j < n_b_cols; ++j)
			m[i][j] = cblas_sdot(n_a_cols, a[i], 1, bT[j], 1); // clas_sdot() ???
	free(Temp);
	return m;
}

DBJ_API void* mat_mul_6(
	const unsigned n_a_rows, const unsigned n_a_cols, const unsigned n_b_cols,
	float a[static n_a_rows][n_a_cols], float b[static n_a_rows][n_b_cols], float m[static n_a_rows][n_b_cols]
)
{
	cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, n_a_rows, n_b_cols, n_a_cols, 1.0f, a[0], n_a_rows, b[0], n_b_rows, 0.0f, m[0], n_a_rows);
	return m;
}
#endif // HAVE_CBLAS

//////////////////////////////////////////////////////////////////////
#endif // DBJ_FLOAT_VARIOUS_MATMULS_IMPLEMENTATION
//////////////////////////////////////////////////////////////////////

#undef DF_PAIR

#endif //  DBJ_FLOAT_VARIOUS_MATMULS_INC


/*
   Compile as

   gcc compGraph2graph6.c -O2 -lgsl -lgslcblas -o compGraph2graph6
*/
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include <gsl/gsl_linalg.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_eigen.h>
#include <gsl/gsl_blas.h>

/* Order of input graphs - note this code will only work for orders smaller than 62 */
#define N 19

/* Maximum order we expect for the the comparability graph */
#define MAX_ORDER 75000

/* Forbidden eigenvalue of the star complement */
#define SC_EIG 2

/* Specified precision used while doing floating point operations we treat EPS as zero */
#define EPS 0.000001

#define EXPECTED_CLIQUE 57

/* graph6 related things */

#define SIZELEN(n) ((n)<=SMALLN?1:((n)<=SMALLISHN?4:8))
#define G6LEN(n) (SIZELEN(n) \
         + ((size_t)(n)/12)*((size_t)(n)-1) + (((size_t)(n)%12)*((size_t)(n)-1)+11)/12)

#define SMALLN 62
#define BIAS6 63
#define TOPBIT6 32
#define SMALLISHN 258047
#define MAXBYTE 126
#define C6MASK 63

static gsl_matrix *mat, *mat_inv;
static gsl_permutation *perm;

static gsl_vector *vecs[1<<N]; //vecs and vecs_prod are arrays of vectors with size 2^N
static gsl_vector *vecs_prod[1<<N];

static gsl_vector *vec_j;

/* storing the current graph6 string of our graph */
static char line[G6LEN(N)+2];

static char gcode[G6LEN(MAX_ORDER)+3]; //gcode is string with enough room to store graph of order MAX_ORDER

/* number of currently processed graphs. */
static unsigned nproc = 0;
static unsigned skipped = 0;

static FILE *outFile;

static void stringtomat(char *s) {

	char *p;
	int i,j,k,x = 0;

    /* Clear the adjacency matrix */
    gsl_matrix_set_zero(mat);

    p = s + 1;

    k = 1;

    gsl_matrix_set(mat, 0, 0, SC_EIG);

    for (j = 1; j < N; ++j) {
        gsl_matrix_set(mat, j, j, SC_EIG);
        for (i = 0; i < j; ++i) {
            if (--k == 0) {
        		k = 6;
		        x = *(p++) - BIAS6;
            }

            if (x & TOPBIT6) {
                gsl_matrix_set(mat, i, j, -1);
                gsl_matrix_set(mat, j, i, -1);
            }
            x <<= 1;
        }
    }

    int signum;
    gsl_linalg_LU_decomp(mat, perm , &signum);
    gsl_linalg_LU_invert(mat, perm, mat_inv);

}

/* Some graph6 string thingie */
static void encodegraphsize(const int n, char **pp) {
    char *p;

    p = *pp;
    if (n <= SMALLN)
        *p++ = BIAS6 + n;
    else {
        *p++ = MAXBYTE;
        *p++ = BIAS6 + (n >> 12);
        *p++ = BIAS6 + ((n >> 6) & C6MASK);
        *p++ = BIAS6 + (n & C6MASK);
    }
    *pp = p;
}

/* Given that mat_inv is the M = SC_EIG*I - A we
   compute all binary vectors x such that x M x^t == SC_EIG
   and x M j^t == -1.

   Finally for any pair x,y of such vectors we add an edge to our
   graph if x M y^t is either 0 or -1.
*/

/* This is a global thingie since declaring it localy as
 * an array of size 1<<N breaks the stack limit */
gsl_vector *verts[1<<N];

static void constructGraph(void) {

    double res; //allocate memory for double-sized result
    unsigned i, j; //declare variables used for looping

    /* After the first iteration this holds the number of
       vertices of the obtained graph. The respective vertices
       are stored in vecs_prod[0]...vecs_prod[cache_size-1].
    */
    unsigned cache_size = 0; //initialize cache_size to 0

    for (i = 0; i < 1<<N; i++) { //loop through all 2^N vectors in vecs, looking to fill in the vertices
        gsl_blas_dsymv(CblasUpper, 1, mat_inv, vecs[i], 0, vecs_prod[cache_size]); //set vecs_prod[cache_size] to mat_inv*vecs[i], assuming mat_inv is symmetrical and using upper triangle ((rI-A_H)^-1*u), this is the bilinear form <u,_>

        gsl_blas_ddot(vecs_prod[cache_size], vec_j, &res); //calculate <u,j> and store it in space allocated for result

        if (fabs(res+1) < EPS) { //if <u,j> = -1 (within EPS precision)
            gsl_blas_ddot(vecs_prod[cache_size], vecs[i], &res); //calculate <u,u> and store it in space allocated for result

            if (fabs(res-SC_EIG) < EPS) { //if <u,u> = r (within EPS precision)
                verts[cache_size] = vecs[i]; //it's a vertex, store vecs[i] in verts
                cache_size+=1; //search for the next vector
            }
        }
    }
    //we now have matched arrays vert[] and vec_prod[], with valid vertices in the former and <u,_> in the latter
    if (cache_size < EXPECTED_CLIQUE) { //if there aren't enough elements to form target clique, no point in continuing, skip to next star complement candidate
        skipped++;
        return;
    }

    assert(cache_size < MAX_ORDER); //check if order is less than maximum allowed order
	//prepare to output compatibility graph
    char *p = gcode; //point *p at start of gcode
    encodegraphsize(cache_size, &p);

    int k = 6, x = 0; //initialize bit written counter and bit to be written

    for (i = 1; i < cache_size; i++) { //loop through all vertices u_i
        for (j = 0; j < i; j++) { //loop through <u_i,u_1> up to <u_i,u_i>, since <u,v> = <v,u> we don't have to check the rest as they will be checked in a later iteration
            x <<= 1; //ready next bit
            gsl_blas_ddot(vecs_prod[i], verts[j], &res); //calculate <u_i,u_j> and store it in space allocated for result

            /* We have an edge */
            if (fabs(res) <= EPS || fabs(res+1) <= EPS) { //if <u_i,u_j> = 0 or -1 (it's an edge)
                x |= 1; //set last bit of x to 1
            }
            if (--k == 0) { //decrease bit written counter
                *p++ = BIAS6 + x; //if we wrote 6 bits, add padding for ASCII character and write byte to where *p is pointing and advance pointer *p forward by 1 character
                k = 6; //reset counters
                x = 0;
            }

        }
    }
	//all full bytes written to gcode, prepare to write to file
    if (k != 6) { //if last byte is incomplete (still waiting to be written)
        *p++ = BIAS6 + (x << k); //push last bits to front, add padding, write to *p, advance *p
    }
    *p++ = '\n'; //stitch on newline, advance *p
    *p = '\0'; //stitch on null (to end string)

    fputs(gcode, outFile); //write to output file
}

static void init_vectors(void) {

    unsigned i,j;

    vec_j = gsl_vector_alloc(N); //allocate memory for vec_j with size N

    assert(vec_j); //check if vec_j was created successfully

    gsl_vector_set_all(vec_j, 1); //initialize vec_j as all 1's

    for (i = 0; i < 1<<N; i++) { //loop through all 2^N vectors in vecs[] and vecs_prod[]
        vecs[i] = gsl_vector_calloc(N); //initialize each vecs[i] to all 0
        vecs_prod[i] = gsl_vector_alloc(N); //allocate memory for vectors of size N

        assert(vecs[i] && vecs_prod[i]); //check if vectors were initialized successfully

        /* We fill the i'th vector of vecs */
        for (j = 0; j < N; j++) { //loop through length of vecs[i]
            if ( i & (1<<j) ) { //set vecs[i] to binary representation of i
                gsl_vector_set(vecs[i], j, 1);
            }
        }
    }
}

int main(int argc, char **argv) { //start here

    static FILE *infile; //declare pointer to input file

    assert (argc > 1); //check if arguments were provided (you can provide multiple, only the first will be used though)

    infile = fopen(argv[1], "r"); //open first argument in read mode and point *infile at it
    mat = gsl_matrix_alloc(N, N); //allocate memory for mat as NxN matrix (not initialized yet, contents are garbage)
    mat_inv = gsl_matrix_alloc(N, N); //allocate memory for mat_inv as NxN matrix
    perm = gsl_permutation_calloc(N); //initialize perm as identity permutation of length N

    char buf[512]; //declare string of 511+1 characters

    snprintf(buf, sizeof(buf), "%s.out", argv[1]); //fill buf with name of input file, append .out to the name
    outFile = fopen(buf, "w"); //create file with name contained in buf in write mode, point *outFile at it

    assert (outFile && infile && mat && mat_inv && perm); //check if everything exists

    init_vectors();

    while (1) {
        if (!fgets(line, sizeof(line), infile))
            break;
        stringtomat(line);
        constructGraph();
        nproc++;
	}

    printf("Successfuly processed: %u graphs. Skipped: %u\n" , nproc, skipped);

    return 0;
}


#include <gsl/gsl_rng.h>

double GrowthFactor(double astart, double aend);
void   print_spec(void);
int    FatalError(int errnum);
void   displacement_fields(void);
void   initialize_ffts(void);
void   set_units(void);
void   assemble_particles(void);
void   free_ffts(void);
double fnl(double x);

int find_files(char *fname);

void   assemble_grid(void);
void   read_power_table(void);
double periodic_wrap(double x);

double PowerSpec(double kmag);
double PowerSpec_Efstathiou(double k);
double PowerSpec_EH(double k);
double PowerSpec_Tabulated(double k);
double PowerSpec_DM_2ndSpecies(double k);
double PowerSpec_Tabulated2nd(double k);

void   initialize_powerspectrum(void);
double GrowthFactor(double astart, double aend);
double growth(double a);
double growth_int(double, void *param);
double qromb(double (*func)(double), double a, double b);
double sigma2_int(double k, void *param);
double TopHatSigma2(double R);
double F_Omega(double a);

void   combine_particle_data(void);
int    compare_logk(const void *a, const void *b);

void  write_particle_data(void);
void  read_parameterfile(char *fname);
void  read_glass(char *fname);

double tk_eh(double k);

size_t my_fread(void *ptr, size_t size, size_t nmemb, FILE * stream);
size_t my_fwrite(void *ptr, size_t size, size_t nmemb, FILE * stream);

void save_local_data(void);
void add_WDM_thermal_speeds(float *vel);

int compare_type(const void *a, const void *b);

double get_fermi_dirac_vel_nu(void);
void fermi_dirac_init_nu(void);
void add_NU_thermal_speeds(float *vel);


/* File description : Implementation of neural networks
 * Author : Bruno Faria
 * Year : 2014
 * Work place : University of Aveiro
*/
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "neural_network.h"

//#include "mkl.h"
//#include <atlas_enum.h>
//#include <lapacke.h>
//#include <lapacke.h>
const char * nn_error_strings[19] = { "API succeed",
								  	  "API could not allocate memory for network object",
								  	  "API could not evaluate network object",
								  	  "API could not rescale data",
								  	  "neural network not initialized",
								  	  "API bad input",
								  	  "API weight initialization",
 								  	  "API could not duplicate network",
								  	  "API could not copy data from src to dest, networks are different",
								  	  "API could not copy data :s",
								  	  "API could not solve system of equations (lapack least squares)",
								  	  "API lapack error",
								  	  "API could not free memory (what?)",
								  	  "API train",
								  	  "API train 2",
								  	  "API train 3",
								  	  "API could not duplicate network",
								  	  "API could not get inverse",
								  	  "API could not compute J, G, and H"
								  };

#define log2(x) log(x)/0.30102999566
/* Random number generator vars */
unsigned long mt_state[MT_N];
unsigned long mt_constants[2];
int mt_state_idx;
bool rand_state = false;

/* global stack error */
int32_t NN_API_ERROR = 0;

/* =======================================
 * = Allocate and destroy neural network =
 * =======================================
*/
net_t * create_nn(int32_t num_layers, int32_t * layers_conf, int32_t * layers_func )
{
   int32_t i = 0, j = 0;
   net_t * nn = NULL;

   C_CHECK_CONDITION( num_layers == 0 || num_layers > 200 || num_layers < 1, NN_API_BAD_INPUT);

   /* allocate a neural network object */
   C_SAFE_CALL( nn = (net_t *)mem_alloc(sizeof(net_t), true ) );

   /* allocate the layers field pointers */
   C_SAFE_CALL( nn->layer = (net_layer_t **)mem_alloc(num_layers * sizeof(net_layer_t *), true) );

   /* set num layers */
   nn -> num_layers = num_layers;
   nn -> init_state = false;

   /* allocate layers vector */
   C_SAFE_CALL( nn->layer[0] = (net_layer_t *)mem_alloc(num_layers * sizeof(net_layer_t), true) );

   /* generate layers pointers */
   for(i = 1; i < num_layers; i++) { nn->layer[i] = nn->layer[0] + i; }

   /* allocate space for the layers */
   for(i = 0; i < num_layers; i++)
   {

   		C_CHECK_CONDITION( layers_conf[i] <= 0 || layers_conf[i] > 400, NN_API_BAD_INPUT );

   		/* insert number of inputs and outputs of the layer */
   		if(i == 0)
   		{
   			C_CHECK_CONDITION( layers_func[i] !=  NN_NONE  || layers_func[i] > net_func_nelem, NN_API_BAD_INPUT );
   			/* input layer */
   			nn->layer[i]->num_inputs = 0;
   			nn->layer[i]->num_outputs = layers_conf[i];
            nn->layer[i]->eval_funct = NN_NONE;
            nn->layer[i]->weights = NULL;
            nn->layer[i]->bias = NULL;
			C_SAFE_CALL( nn->layer[i]->results = (double*)mem_alloc( layers_conf[i] * sizeof(double), true ));

		    /* allocate memory for input and output mapping */
   			C_SAFE_CALL( nn->xmin = (double *)mem_alloc(layers_conf[i] * sizeof(double),true ));
   			C_SAFE_CALL( nn->xmax = (double *)mem_alloc(layers_conf[i] * sizeof(double),true ));

   		}else{

   			C_CHECK_CONDITION( layers_func[i] ==  NN_NONE  || layers_func[i] > net_func_nelem, NN_API_BAD_INPUT );

   			/* allocate memory for input and output mapping */
   			C_SAFE_CALL( nn->ymin = (double *)mem_alloc(layers_conf[i] * sizeof(double),true ));
   			C_SAFE_CALL( nn->ymax = (double *)mem_alloc(layers_conf[i] * sizeof(double),true ));

   			/* remaining layers */
   			nn->layer[i]->num_inputs = layers_conf[i-1];
   			nn->layer[i]->num_outputs = layers_conf[i];
        	nn->layer[i]->eval_funct = layers_func[i];

            C_SAFE_CALL( nn->layer[i]->weights 	 	 = (double**)mem_alloc( layers_conf[i] 				  	* sizeof(double 	*), true ) );
            C_SAFE_CALL( nn->layer[i]->results 	 	 = (double *)mem_alloc( layers_conf[i] 					* sizeof(double		 ), true ) );
            C_SAFE_CALL( nn->layer[i]->bias 		 = (double *)mem_alloc( layers_conf[i] 					* sizeof(double		 ), true ) );
            C_SAFE_CALL( nn->layer[i]->weights[0] 	 = (double *)mem_alloc( layers_conf[i-1] * layers_conf[i] * sizeof(double		 ), true ) );

            for(j = 1; j < layers_conf[i]; j++)
            {
            	nn->layer[i]->weights[j] = nn->layer[i]->weights[0] + j * layers_conf[i-1];
            }
       }
   }
   nn -> init_state = true;
   nn -> use_mapping = true;
   return nn;

error:
    /* something went wrong so clean memory and exit */
    destroy_nn( nn );

    return NULL;
}

int32_t destroy_nn(net_t * nn)
{
    int32_t i = 0;
    net_layer_t * layer;

    C_CHECK_CONDITION( nn == NULL, NN_API_BAD_INPUT );
    C_CHECK_CONDITION( nn->num_layers == 0, NN_API_BAD_INPUT );

    /* first deallocate the layers */
    if(nn->layer != NULL)
    {
    	if(nn->layer[0] != NULL)
    	{
			layer = nn->layer[0];
			mem_free(layer->results);
    		for(i = 1; i < nn->num_layers; i++ )
    		{
    			layer = nn->layer[i];
    			if(layer->weights != NULL)
    			{
    				mem_free( layer->weights[0] );
        			mem_free( layer->weights 	);
        		}
        		mem_free( layer->results 	 );
       	 		mem_free( layer->bias    	 );
    		}
    		/* now free the main object */
   			mem_free( nn->layer[0] );
   		}
   		mem_free( nn->layer );
    }

    mem_free( nn->xmin );
    mem_free( nn->xmax );
    mem_free( nn->ymin );
    mem_free( nn->ymax );

    mem_free( nn );

    return 0;
error:
    /* what? cannot deallocate memory? why? */
    return -1;
}

void * mem_alloc(int32_t size, bool zero_set)
{
     void * p = malloc(size);

     if( p == NULL){ NN_API_ERROR = NN_API_NETWORK_ALLOC_ERROR; return NULL;}

     if(zero_set == true)
     {
        memset(p, 0, size);
     }
     NN_API_ERROR = NN_API_SUCCESS;
     return p;
}

void mem_free(void * ptr)
{
	C_CHECK_CONDITION(ptr == NULL, NN_API_BAD_INPUT);
    free( ptr );
    ptr = NULL;
    NN_API_ERROR = NN_API_SUCCESS;
	return;
error:
	NN_API_ERROR = NN_API_FREE;
	return;
}

/* get the neural network response for a given input */
int32_t get_nn_response(net_t * nn)
{
	int32_t i = 0, n = 0, l = 0;
	double w_sum = 0.0f;

	net_layer_t * layer = NULL, * prev_layer = NULL;

	C_CHECK_CONDITION( nn == NULL, 				  NN_API_BAD_INPUT 		);
	C_CHECK_CONDITION( nn -> init_state == false, NN_API_BAD_INPUT 		);
	C_CHECK_CONDITION( nn -> layer == NULL,		  NN_API_MALFORMED_NN	);

	memcpy(nn->layer[0]->results, nn->in_layer,nn->layer[0]->num_outputs * sizeof(double));

	/* first layer hols the data to be evaluated */
	if(nn->use_mapping){
		C_SAFE_CALL( transform_in_out_data(nn, nn->layer[0]->results, nn->layer[0]->num_outputs, false) );
	}

	for(l = 1; l < nn->num_layers; l++)
	{
		C_CHECK_CONDITION( nn -> layer[l] 	== NULL,	NN_API_MALFORMED_NN	);
		C_CHECK_CONDITION( nn -> layer[l-1] == NULL,	NN_API_MALFORMED_NN	);

		/* remap layer (to increase readibility) */
		layer = nn->layer[l];
		prev_layer = nn->layer[l-1];

		C_CHECK_CONDITION( layer -> bias 		== NULL,	NN_API_MALFORMED_NN	);
		C_CHECK_CONDITION( layer -> weights 	== NULL,	NN_API_MALFORMED_NN	);
		C_CHECK_CONDITION( layer -> weights[0] 	== NULL,	NN_API_MALFORMED_NN	);

		for(n = 0; n < layer->num_outputs; n++)
		{

			w_sum = layer->bias[n];

			for( i = 0; i < layer->num_inputs; i++)
				w_sum += prev_layer->results[i] * layer->weights[n][i];

			// apply layer function
			switch( layer->eval_funct )
			{
				case NN_LOGSIG: { w_sum =  1 / (1 + exp(-w_sum)); 			break; }
				case NN_TANSIG: { w_sum =  2 / (1 + exp(-2 * w_sum)) - 1;	break; }
				default: 		{ 											break; }
			}

			layer->results[n] = w_sum;
		}
	}

	/* transform the data and output it to the outer layer */
	layer = nn->layer[nn->num_layers-1];
	memcpy(nn->out_layer, layer->results, layer->num_outputs * sizeof(double));
	if(nn->use_mapping)
	{
		C_SAFE_CALL( transform_in_out_data(nn, nn->out_layer, layer->num_outputs, true) );
	}

	NN_API_ERROR = NN_API_SUCCESS;
	return 0;
error:
	NN_API_ERROR = NN_API_RESCALE_DATA;
	return -1;
}

inline double map_min_max( double x, double xmin, double xmax, double ymin, double ymax, bool reverse )
{
	if(reverse)
	{
		return (ymax-ymin)*(x+1.0f)/2.0f + ymin;
	}else{
		return 2.0f * (x - xmin)/(xmax-xmin) - 1.0f;
	}
}

int32_t transform_in_out_data(net_t * nn, double * in_out, int32_t size, bool Mode)
{
	int32_t i = 0;

	C_CHECK_CONDITION( size == 0, NN_API_BAD_INPUT);

	for(i = 0; i < size; i++){
		if(Mode){
			in_out[i] = map_min_max(in_out[i], 0, 0, nn->ymin[i], nn->ymax[i], Mode);
		}else{
			in_out[i] = map_min_max(in_out[i], nn->xmin[i], nn->xmax[i], 0, 0, Mode);
		}
	}
	NN_API_ERROR = NN_API_SUCCESS;
	return 0;

error:
	NN_API_ERROR = NN_API_RESCALE_DATA;
	return -1;
}

/*
 * Compute stuff
 */

double compute_JGH(net_t * nn, double **J, double *G, double **H, double ** samples, double ** targets, double * errors, double * nn_out, double * nn_temp_out,
 int32_t * perm, int32_t num_samples, int32_t num_outputs, int32_t G_length)
{
	double sse = 0;
	int32_t i = 0, j = 0, k = 0, l = 0, s = 0, ct = 0;
	net_layer_t * layer = NULL;
	double weight_s = 0;

	/* verify conditions */
	C_CHECK_CONDITION( nn 			== NULL, 				  NN_API_BAD_INPUT 	);
	C_CHECK_CONDITION( J  			== NULL, 				  NN_API_BAD_INPUT 	);
	C_CHECK_CONDITION( G  			== NULL,				  NN_API_BAD_INPUT	);
	C_CHECK_CONDITION( samples  	== NULL, 				  NN_API_BAD_INPUT 	);
	C_CHECK_CONDITION( targets  	== NULL,				  NN_API_BAD_INPUT	);
	C_CHECK_CONDITION( errors  		== NULL, 				  NN_API_BAD_INPUT 	);
	C_CHECK_CONDITION( nn_out  		== NULL,				  NN_API_BAD_INPUT	);
	C_CHECK_CONDITION( nn_temp_out  == NULL, 				  NN_API_BAD_INPUT 	);
	C_CHECK_CONDITION( num_samples  == 0, 					  NN_API_BAD_INPUT 	);
	C_CHECK_CONDITION( num_outputs  == 0,					  NN_API_BAD_INPUT	);
	C_CHECK_CONDITION( G_length  	== 0,					  NN_API_BAD_INPUT	);

	/* compute sample errors and jacobian */
	for(s = 0; s < num_samples; s++)
	{
		ct = 0;

		/* short circuit the pointers */
		nn->in_layer = samples[perm[s]]; nn->out_layer = nn_out;

		/* first evaluate the network for this sample without any perturbation*/
		C_SAFE_CALL( get_nn_response( nn ) );

		/* compute errors and sse */
		for(i = 0; i < num_outputs; i++)
		{
			errors[i*num_samples + s] = targets[perm[s]][i] - nn_out[i];
			sse += errors[i*num_samples + s]*errors[i*num_samples + s];
		}

		nn->out_layer = nn_temp_out;

		/* the lines of the jacobian for this sample are computed here */
		for(l = 1; l < nn->num_layers; l++)
		{
			layer = nn->layer[l];

			for(i = 0; i < layer->num_outputs; i++)
			{
				/* weights perturbation */
				for(j = 0; j < layer->num_inputs; j++)
				{

					weight_s = layer->weights[i][j];

					/* apply perturbation parameter */
					layer->weights[i][j] += (PERTURBATION * ( 1 + abs(weight_s)));

					/* get neural network response */
					C_SAFE_CALL( get_nn_response( nn ) );

					/* restore weight */
					layer->weights[i][j] = weight_s;

					/* compute derivative */
					for(k = 0; k < num_outputs; k++){
						J[k*num_samples+s][ct] = (nn_out[k] - nn_temp_out[k]) / (PERTURBATION * ( 1 + abs(weight_s)));
					}
					ct++;
				}

				/* bias perturbation */
				weight_s = layer->bias[i];

				/* apply perturbation parameter */
				layer->bias[i] += (PERTURBATION * ( 1 + abs(weight_s)));

				/* get neural network response */
				C_SAFE_CALL( get_nn_response( nn ) );

				/* restore weight */
				layer->bias[i] = weight_s;

				/* compute derivative */
				for(k = 0; k < num_outputs; k++)
					J[k*num_samples+s][ct] = (nn_out[k] - nn_temp_out[k]) / (PERTURBATION * ( 1 + abs(weight_s)));
				ct++;
			}

		}
	}

	/* compute gradient and aproximation to the hessian matrix */
	memset(G, 0, G_length * sizeof(double));
	memset(H[0], 0, G_length * G_length * sizeof(double));

	for(i = 0; i < num_samples * num_outputs; i++ )
	{
		for(j = 0; j < G_length; j++)
		{
			G[j] += J[i][j] * errors[i];
		}
	}

	/* symmetric matrix ??? Do we have singularities? */
	for(i = 0; i < G_length; i++)
	{
		for(j = 0; j < G_length; j++)
		{
			for(k = 0; k < num_samples * num_outputs; k++)
				H[i][j] += J[k][i]*J[k][j];
		}
	}
	NN_API_ERROR = NN_API_SUCCESS;;
	return sse;
error:
	NN_API_ERROR = NN_API_COMPUTE_JGH;
	return 0;
}
/* =====================================
 * = Neural network training functions =
 * =====================================
 */
int32_t nn_train(net_t * nn, double **samples, double ** targets, int32_t num_samples, int32_t max_epochs, double min_error )
{
	double ** J = NULL, ** A = NULL, * A_temp = NULL, * G = NULL, * errors = NULL, * nn_out = NULL, * nn_t_out = NULL, * work = NULL, * S = NULL;
	double niu = 0.01, current_error = 1e20, new_error = 0;
	double **r_samples = NULL, **r_targets = NULL;
	int32_t * iwork = NULL;
	int32_t l = 0, J_ydim = 0, nlvl = 0, epoch = 0, *perm = NULL, lwork = 0, s = 0, num_outputs = 0, num_inputs = 0;
	int32_t i = 0;
	net_layer_t *layer = NULL;
	net_t * test_nn = NULL;

	/* memory for rescaled samples */

	/* conditions check */
	C_CHECK_CONDITION( nn == NULL, 							 NN_API_NOT_INIT  );
	C_CHECK_CONDITION( nn->init_state == false, 			 NN_API_NOT_INIT  );
	C_CHECK_CONDITION( num_samples <= 0, 					 NN_API_BAD_INPUT );
	C_CHECK_CONDITION( max_epochs < 0 && max_epochs > 10000, NN_API_BAD_INPUT );
	C_CHECK_CONDITION( min_error < 0 && min_error > 1000,	 NN_API_BAD_INPUT );
	C_CHECK_CONDITION( samples == NULL, 					 NN_API_BAD_INPUT );
	C_CHECK_CONDITION( targets == NULL, 					 NN_API_BAD_INPUT );

	/* init weights */
	C_SAFE_CALL( init_nn_weights(nn) );

	/* first and last layer : required to know the number of inputs and outputs and so allocate memory */
	layer = nn->layer[nn->num_layers-1];
	num_outputs = layer->num_outputs;

	layer = nn->layer[0];
	num_inputs = layer->num_outputs;

	/* compute jacobian number of lines */
	for(l = 1; l < nn->num_layers; l++)
	{
		layer = nn->layer[l];
		J_ydim += (layer->num_inputs + 1) * layer-> num_outputs;
	}
	printf("J_ydim %d\n",J_ydim);

	/* initialize required memory */
	C_SAFE_CALL( J 		  = (double **)mem_alloc(num_outputs * num_samples	* sizeof(double  *), true) );
	C_SAFE_CALL( A 		  = (double **)mem_alloc(J_ydim     				* sizeof(double  *), true) );
	C_SAFE_CALL( G 	 	  = (double  *)mem_alloc(J_ydim		  				* sizeof(double   ), true) );
	C_SAFE_CALL( errors   = (double  *)mem_alloc(num_outputs * num_samples 	* sizeof(double   ), true) );
	C_SAFE_CALL( perm     = (int32_t *)mem_alloc(num_samples 				* sizeof(int32_t  ), true) );
	C_SAFE_CALL( nn_out   = (double  *)mem_alloc(num_outputs		   		* sizeof(double   ), true) );
	C_SAFE_CALL( nn_t_out = (double  *)mem_alloc(num_outputs		   		* sizeof(double   ), true) );

	C_SAFE_CALL( J[0]     	  = (double  *)mem_alloc(num_outputs * num_samples *  J_ydim * sizeof(double ), true) );
	C_SAFE_CALL( A[0]      	  = (double  *)mem_alloc(J_ydim * J_ydim  				 	 * sizeof(double ), true) );
	C_SAFE_CALL( A_temp   	  = (double  *)mem_alloc(J_ydim * J_ydim  					 * sizeof(double ), true) );
	C_SAFE_CALL( r_samples    = (double **)mem_alloc(num_samples 						 * sizeof(double*), true) );
	C_SAFE_CALL( r_samples[0] = (double  *)mem_alloc(num_samples * num_inputs 			 * sizeof(double*), true) );
	C_SAFE_CALL( r_targets    = (double **)mem_alloc(num_samples 						 * sizeof(double*), true) );
	C_SAFE_CALL( r_targets[0] = (double  *)mem_alloc(num_samples * num_outputs 			 * sizeof(double*), true) );

	/* calculate pointers address */
	for(l = 1; l < J_ydim; l++)
		A[l] = A[0] + l * J_ydim;

	for(l = 1; l < num_outputs * num_samples; l++)
		J[l] = J[0] + l * J_ydim;

	for(l=1; l < num_samples; l++){
		r_samples[l] = r_samples[0] + l * num_inputs;
		r_targets[l] = r_targets[0] + l * num_outputs;
	}

	/* query lapack for the best size of the work area */
	nlvl = (int32_t)(floor(log2((double)J_ydim/(25.0f+1.0f)))+1.0f);

	C_SAFE_CALL( S 		= (double  *) mem_alloc( J_ydim 							 * sizeof(double ), true) );
	C_SAFE_CALL( iwork 	= (int32_t *) mem_alloc( ( 3 * J_ydim * nlvl + 11 * J_ydim ) * sizeof(int32_t), true) );
	lwork = lpck_solve_ls_svd_pivot(J_ydim, -1, NULL, 0, S, iwork, A[0], G);
	C_SAFE_CALL( work = (double *)mem_alloc(lwork * sizeof(double), true) );

	/* grab scalling parameters */
	nn_grab_rescale_params( nn, samples, targets, num_samples );

	/* rescale samples and targets to -1, 1 range */
	for(l = 0; l < num_samples; l++){
		memcpy(r_samples[l], samples[l], num_inputs * sizeof(double));
		C_SAFE_CALL( transform_in_out_data(nn, r_samples[l], num_inputs, false) );

		memcpy(r_targets[l], targets[l], num_outputs * sizeof(double));
		for(i = 0; i < num_outputs; i++){
				r_targets[l][i] = map_min_max(r_targets[l][i], nn->ymin[i], nn->ymax[i], 0, 0, false);
		}

	}

	/* generate a duplicate network */
	C_SAFE_CALL( test_nn = duplicate_network( nn ) );

	/* disable rescaling */
	nn->use_mapping = false;

	/* generate random presentation */
	random_perm(num_samples, perm);
	C_SAFE_CALL( copy_nn_data( test_nn, nn ) );

	/* compute jacobian, hessian and stuff */
	current_error = compute_JGH(test_nn, J, G, A, r_samples, r_targets, errors, nn_out, nn_t_out, perm, num_samples, num_outputs, J_ydim);

	/* lets train the neural network */
	while(sqrt(current_error) > min_error && epoch < max_epochs && niu < 1e20)
	{

		/* copy network to a safe place */
		C_SAFE_CALL( copy_nn_data( test_nn, nn ) );

		memcpy(A_temp, A[0], J_ydim * J_ydim * sizeof(double));

		/* apply niu */
		for(i = 0; i < J_ydim; i++)
			 A_temp[i*J_ydim+i] += niu ;

		/* call lapack to solve our system of equations */
		C_SAFE_CALL( lpck_solve_ls_svd_pivot(J_ydim, 0, work, lwork, S, iwork, A_temp, G) );

		/* apply changes to neural network weights (lapack gives solution in G) */
		nn_change_weights_bias(test_nn, G);

		/* compute new error */
		new_error = 0.0f;
		for(s = 0; s < num_samples; s++)
		{
			/* short circuit the pointers */
			test_nn->in_layer = r_samples[perm[s]]; test_nn->out_layer = nn_out;

			/* compute neural network response for this input */
			C_SAFE_CALL( get_nn_response( test_nn ) );

			/* compute sample errors */
			for(i = 0; i < num_outputs; i++)
				new_error += (r_targets[perm[s]][i] - nn_out[i]) * (r_targets[perm[s]][i] - nn_out[i]);
		}

		/* check wether or not we should accept this network */
		if(new_error < current_error )
		{
			/* copy network */
			C_SAFE_CALL( copy_nn_data( nn, test_nn ) );
			current_error = new_error;
			niu *= 0.1;
			random_perm(num_samples, perm);

			/* compute new hessian, jacobian and gradient */
			current_error = compute_JGH(test_nn, J, G, A, r_samples, r_targets, errors, nn_out, nn_t_out, perm, num_samples, num_outputs, J_ydim);

		}else{
			niu *= 10;
		}

		printf("epoch: %d error : %f niu : %f\n",epoch,sqrt(current_error),niu);

		epoch++;
	}

	/* clean memory and exit */
	destroy_nn( test_nn );

	/* enable rescaling */
	nn->use_mapping = true;

	if(r_samples != NULL)
	{
		mem_free(r_samples[0]);
		mem_free(r_samples);
	}

	if(r_targets != NULL)
	{
		mem_free(r_targets[0]);
		mem_free(r_targets);
	}

	if(J != NULL)
	{
		mem_free( J[0] 	);
		mem_free( J 	);
	}
	if(A != NULL)
	{
		mem_free( A[0] 	);
		mem_free( A 	);
	}
	mem_free( A_temp	);
	mem_free( G 		);
	mem_free( errors 	);
	mem_free( perm 		);
	mem_free( nn_out	);
	mem_free( nn_t_out 	);
	mem_free( work 		);
	mem_free( iwork		);
	mem_free( S			);
	NN_API_ERROR = NN_API_SUCCESS;

	return 0;
error:
	destroy_nn( test_nn );

	/* clean memory and exit */
	if(r_samples != NULL)
	{
		mem_free(r_samples[0]);
		mem_free(r_samples);
	}

	if(r_targets != NULL)
	{
		mem_free(r_targets[0]);
		mem_free(r_targets);
	}

	if(J != NULL)
	{
		mem_free( J[0] 	);
		mem_free( J 	);
	}
	if(A != NULL)
	{
		mem_free( A[0] 	);
		mem_free( A 	);
	}
	mem_free( G 		);
	mem_free( errors 	);
	mem_free( perm 		);
	mem_free( nn_out	);
	mem_free( nn_t_out 	);
	mem_free( work 		);
	mem_free( iwork		);
	mem_free( S			);

	NN_API_ERROR = NN_API_TRAIN;
	return -1;
}

/* =====================================
 * = Neural network training functions =
 * =====================================
 */
int32_t nn_train2(net_t * nn, double **samples, double ** targets, int32_t num_samples, int32_t max_epochs, double min_error, double train_perc )
{
	double ** J = NULL, ** A = NULL, * A_temp = NULL, * G = NULL, * errors = NULL, * nn_out = NULL, * nn_t_out = NULL, * work = NULL, * S = NULL;
	double niu = 0.001, current_error = 1e20, new_error = 0, error_perc = 1- train_perc, grb = 0;
	double **r_samples = NULL, **r_targets = NULL;
	int32_t * iwork = NULL;
	int32_t l = 0, J_ydim = 0, nlvl = 0, epoch = 0, *perm = NULL, lwork = 0, s = 0, num_outputs = 0, num_inputs = 0;
	int32_t i = 0, train_s_index = 0, error_e_index = 0;
	net_layer_t *layer = NULL;
	net_t * test_nn = NULL;

	/* conditions check */
	C_CHECK_CONDITION( nn == NULL, 							 NN_API_NOT_INIT  );
	C_CHECK_CONDITION( nn->init_state == false, 			 NN_API_NOT_INIT  );
	C_CHECK_CONDITION( num_samples <= 0, 					 NN_API_BAD_INPUT );
	C_CHECK_CONDITION( max_epochs < 0 && max_epochs > 10000, NN_API_BAD_INPUT );
	C_CHECK_CONDITION( min_error < 0 && min_error > 1000,	 NN_API_BAD_INPUT );
	C_CHECK_CONDITION( samples == NULL, 					 NN_API_BAD_INPUT );
	C_CHECK_CONDITION( targets == NULL, 					 NN_API_BAD_INPUT );
	C_CHECK_CONDITION( train_perc <= 0 || train_perc >= 1, 	 NN_API_BAD_INPUT );
	C_CHECK_CONDITION( train_perc*num_samples < 10,		 	 NN_API_BAD_INPUT );
	C_CHECK_CONDITION( error_perc*num_samples < 10,		 	 NN_API_BAD_INPUT );

	/* init weights */
	C_SAFE_CALL( init_nn_weights(nn) );

	/* compute jacobian number of lines */
	for(l = 1; l < nn->num_layers; l++){
		layer = nn->layer[l];
		J_ydim += (layer->num_inputs + 1) * layer-> num_outputs;
	}

	/* first and last layer : required to know the number of inputs and outputs and so allocate memory */
	layer = nn->layer[nn->num_layers-1];
	num_outputs = layer->num_outputs;

	layer = nn->layer[0];
	num_inputs = layer->num_outputs;

	/* initialize required memory */
	C_SAFE_CALL( J 		  = (double **)mem_alloc(num_outputs * num_samples	* sizeof(double  *), true) );
	C_SAFE_CALL( A 		  = (double **)mem_alloc(J_ydim     				* sizeof(double  *), true) );
	C_SAFE_CALL( G 	 	  = (double  *)mem_alloc(J_ydim		  				* sizeof(double   ), true) );
	C_SAFE_CALL( errors   = (double  *)mem_alloc(num_outputs * num_samples 	* sizeof(double   ), true) );
	C_SAFE_CALL( perm     = (int32_t *)mem_alloc(num_samples 				* sizeof(int32_t  ), true) );
	C_SAFE_CALL( nn_out   = (double  *)mem_alloc(num_outputs		   		* sizeof(double   ), true) );
	C_SAFE_CALL( nn_t_out = (double  *)mem_alloc(num_outputs		   		* sizeof(double   ), true) );

	C_SAFE_CALL( J[0]     	  = (double  *)mem_alloc(num_outputs * num_samples *  J_ydim * sizeof(double ), true) );
	C_SAFE_CALL( A[0]      	  = (double  *)mem_alloc(J_ydim * J_ydim  				 	 * sizeof(double ), true) );
	C_SAFE_CALL( A_temp   	  = (double  *)mem_alloc(J_ydim * J_ydim  					 * sizeof(double ), true) );
	C_SAFE_CALL( r_samples    = (double **)mem_alloc(num_samples 						 * sizeof(double*), true) );
	C_SAFE_CALL( r_samples[0] = (double  *)mem_alloc(num_samples * num_inputs 			 * sizeof(double*), true) );
	C_SAFE_CALL( r_targets    = (double **)mem_alloc(num_samples 						 * sizeof(double*), true) );
	C_SAFE_CALL( r_targets[0] = (double  *)mem_alloc(num_samples * num_outputs 			 * sizeof(double*), true) );

	/* calculate pointers address */
	for(l = 1; l < J_ydim; l++)
		A[l] = A[0] + l * J_ydim;

	for(l = 1; l < num_outputs * num_samples; l++)
		J[l] = J[0] + l * J_ydim;

	for(l=1; l < num_samples; l++){
		r_samples[l] = r_samples[0] + l * num_inputs;
		r_targets[l] = r_targets[0] + l * num_outputs;
	}

	/* query lapack for the best size of the work area */
	nlvl = (int32_t)(floor(log2((double)J_ydim/(25.0f+1.0f)))+1.0f);

	C_SAFE_CALL( S 		= (double  *) mem_alloc( J_ydim 							 * sizeof(double ), true) );
	C_SAFE_CALL( iwork 	= (int32_t *) mem_alloc( ( 3 * J_ydim * nlvl + 11 * J_ydim ) * sizeof(int32_t), true) );
	lwork = lpck_solve_ls_svd_pivot(J_ydim, -1, NULL, 0, S, iwork, A[0], G);
	C_SAFE_CALL( work = (double *)mem_alloc(lwork * sizeof(double), true) );

	/* grab scalling parameters */
	nn_grab_rescale_params( nn, samples, targets, num_samples );

	/* rescale samples and targets to -1, 1 range */
	for(l = 0; l < num_samples; l++){
		memcpy(r_samples[l], samples[l], num_inputs * sizeof(double));
		C_SAFE_CALL( transform_in_out_data(nn, r_samples[l], num_inputs, false) );

		memcpy(r_targets[l], targets[l], num_outputs * sizeof(double));
		for(i = 0; i < num_outputs; i++){
			r_targets[l][i] = map_min_max(r_targets[l][i], nn->ymin[i], nn->ymax[i], 0, 0, false);
		}
	}

	/* generate a duplicate network */
	C_SAFE_CALL( test_nn = duplicate_network( nn ) );

	/* copy network to a safe place */
	C_SAFE_CALL( copy_nn_data( test_nn, nn ) );

	/* generate a random permutation */
	random_perm(num_samples, perm);

	/* training start index and error end index */
	train_s_index = num_samples * error_perc + 1;
	error_e_index = num_samples * error_perc;

	/* disable rescaling */
	nn->use_mapping = false;

	/* ASSUMPTION: FIRST SAMPLES ON PERM ARE FOR ERROR CALCULATION*/
	C_SAFE_CALL( current_error = compute_JGH(test_nn, J, G, A, r_samples, r_targets, errors, nn_out, nn_t_out, perm, error_e_index, num_outputs, J_ydim) );

	/* compute new hessian, jacobian and gradient */
	C_SAFE_CALL(	grb = compute_JGH(test_nn, J, G, A, r_samples, r_targets,
							&errors[train_s_index*num_outputs], nn_out, nn_t_out,
							&perm[train_s_index],
							num_samples - train_s_index,
							num_outputs, J_ydim);
				);

	/* lets train the neural network */
	while(sqrt(current_error) > min_error && epoch < max_epochs && niu < 1e20)
	{

		/* copy network to a safe place */
		C_SAFE_CALL( copy_nn_data( test_nn, nn ) );

		memcpy(A_temp, A[0], J_ydim * J_ydim * sizeof(double));

		/* apply niu */
		for(i = 0; i < J_ydim; i++)
			 A_temp[i*J_ydim+i] += niu ;

		/* call lapack to solve our system of equations */
		C_SAFE_CALL( lpck_solve_ls_svd_pivot(J_ydim, 0, work, lwork, S, iwork, A_temp, G) );

		/* apply changes to neural network weights (lapack gives solution in G) */
		nn_change_weights_bias(test_nn, G);

		/* compute new error */
		new_error = 0.0f;
		for(s = 0; s < error_e_index; s++)
		{
			/* short circuit the pointers */
			test_nn->in_layer = r_samples[perm[s]]; test_nn->out_layer = nn_out;

			/* compute neural network response for this input */
			C_SAFE_CALL( get_nn_response( test_nn ) );

			/* compute sample errors */
			for(i = 0; i < num_outputs; i++)
			{
				new_error += (r_targets[perm[s]][i] - nn_out[i]) * (r_targets[perm[s]][i] - nn_out[i]);
			}
		}

		/* check wether or not we should accept this network */
		if(new_error < current_error )
		{
			/* copy network */
			C_SAFE_CALL( copy_nn_data( nn, test_nn ) );
			current_error = new_error;
			/* compute new hessian, jacobian and gradient */
			C_SAFE_CALL(
				grb = compute_JGH(test_nn, J, G, A, r_samples, r_targets,
								&errors[train_s_index*num_outputs], nn_out, nn_t_out,
								&perm[train_s_index],
								num_samples - train_s_index,
								num_outputs, J_ydim);
			);
			niu *= 0.1;
		}else{

			niu *= 10;
		}

		printf("epoch: %d error : %f niu : %f\n",epoch,sqrt(current_error),niu);

		epoch++;
	}

	/* clean memory and exit */
	destroy_nn( test_nn );

	/* enable rescaling */
	nn->use_mapping = true;

	if(r_samples != NULL)
	{
		mem_free(r_samples[0]);
		mem_free(r_samples);
	}

	if(r_targets != NULL)
	{
		mem_free(r_targets[0]);
		mem_free(r_targets);
	}

	if(J != NULL)
	{
		mem_free( J[0] 	);
		mem_free( J 	);
	}
	if(A != NULL)
	{
		mem_free( A[0] 	);
		mem_free( A 	);
	}
	mem_free( A_temp 	);
	mem_free( G 		);
	mem_free( errors 	);
	mem_free( perm 		);
	mem_free( nn_out	);
	mem_free( nn_t_out 	);
	mem_free( work 		);
	mem_free( iwork		);
	mem_free( S			);
	NN_API_ERROR = NN_API_SUCCESS;

	return 0;
error:
	destroy_nn( test_nn );

	/* clean memory and exit */
	if(r_samples != NULL)
	{
		mem_free(r_samples[0]);
		mem_free(r_samples);
	}

	if(r_targets != NULL)
	{
		mem_free(r_targets[0]);
		mem_free(r_targets);
	}

	if(J != NULL)
	{
		mem_free( J[0] 	);
		mem_free( J 	);
	}
	if(A != NULL)
	{
		mem_free( A[0] 	);
		mem_free( A 	);
	}
	mem_free( A_temp 	);
	mem_free( G 		);
	mem_free( errors 	);
	mem_free( perm 		);
	mem_free( nn_out	);
	mem_free( nn_t_out 	);
	mem_free( work 		);
	mem_free( iwork		);
	mem_free( S			);

	NN_API_ERROR = NN_API_TRAIN2;
	return -1;
}

int32_t nn_train3(net_t * nn, double **samples, double ** targets, int32_t num_samples, int32_t max_epochs, double min_error )
{
	double ** J = NULL, ** A = NULL, *A_temp = NULL, * G = NULL, * errors = NULL, * nn_out = NULL, * nn_t_out = NULL, * work = NULL, * S = NULL, * inv_ipiv = NULL, * inv_work = NULL;
	double niu = 0.01, current_error = 1e20, new_error = 0,  alpha = 0, beta = 1, gamma = 0, Ew = 0, Ed = 0, trace = 0;
	double **r_samples = NULL, **r_targets = NULL;
	int32_t * iwork = NULL;
	int32_t l = 0, J_ydim = 0, nlvl = 0, epoch = 0, *perm = NULL, lwork = 0, s = 0, num_outputs = 0, num_inputs  =0;
	int32_t i = 0;
	net_layer_t *layer = NULL;
	net_t * test_nn = NULL;

	/* conditions check */
	C_CHECK_CONDITION( nn == NULL, 							 NN_API_NOT_INIT  );
	C_CHECK_CONDITION( nn->init_state == false, 			 NN_API_NOT_INIT  );
	C_CHECK_CONDITION( num_samples <= 0, 					 NN_API_BAD_INPUT );
	C_CHECK_CONDITION( max_epochs < 0 && max_epochs > 10000, NN_API_BAD_INPUT );
	C_CHECK_CONDITION( min_error < 0 && min_error > 1000,	 NN_API_BAD_INPUT );
	C_CHECK_CONDITION( samples == NULL, 					 NN_API_BAD_INPUT );
	C_CHECK_CONDITION( targets == NULL, 					 NN_API_BAD_INPUT );

	/* init weights */
	C_SAFE_CALL( init_nn_weights(nn) );

	/* compute jacobian number of lines */
	for(l = 1; l < nn->num_layers; l++){
		layer = nn->layer[l];
		J_ydim += (layer->num_inputs + 1) * layer-> num_outputs;
	}

	/* first and last layer : required to know the number of inputs and outputs and so allocate memory */
	layer = nn->layer[nn->num_layers-1];
	num_outputs = layer->num_outputs;

	layer = nn->layer[0];
	num_inputs = layer->num_outputs;

	/* initialize required memory */
	C_SAFE_CALL( J 		  = (double **)mem_alloc(num_outputs * num_samples	* sizeof(double  *), true) );
	C_SAFE_CALL( A 		  = (double **)mem_alloc(J_ydim     				* sizeof(double  *), true) );
	C_SAFE_CALL( G 	 	  = (double  *)mem_alloc(J_ydim		  				* sizeof(double   ), true) );
	C_SAFE_CALL( errors   = (double  *)mem_alloc(num_outputs * num_samples 	* sizeof(double   ), true) );
	C_SAFE_CALL( perm     = (int32_t *)mem_alloc(num_samples 				* sizeof(int32_t  ), true) );
	C_SAFE_CALL( nn_out   = (double  *)mem_alloc(num_outputs		   		* sizeof(double   ), true) );
	C_SAFE_CALL( nn_t_out = (double  *)mem_alloc(num_outputs		   		* sizeof(double   ), true) );

	C_SAFE_CALL( J[0]     	  = (double  *)mem_alloc(num_outputs * num_samples *  J_ydim * sizeof(double ), true) );
	C_SAFE_CALL( A[0]      	  = (double  *)mem_alloc(J_ydim * J_ydim  				 	 * sizeof(double ), true) );
	C_SAFE_CALL( A_temp   	  = (double  *)mem_alloc(J_ydim * J_ydim  					 * sizeof(double ), true) );
	C_SAFE_CALL( r_samples    = (double **)mem_alloc(num_samples 						 * sizeof(double*), true) );
	C_SAFE_CALL( r_samples[0] = (double  *)mem_alloc(num_samples * num_inputs 			 * sizeof(double*), true) );
	C_SAFE_CALL( r_targets    = (double **)mem_alloc(num_samples 						 * sizeof(double*), true) );
	C_SAFE_CALL( r_targets[0] = (double  *)mem_alloc(num_samples * num_outputs 			 * sizeof(double*), true) );

	/* calculate pointers address */
	for(l = 1; l < J_ydim; l++)
		A[l] = A[0] + l * J_ydim;

	for(l = 1; l < num_outputs * num_samples; l++)
		J[l] = J[0] + l * J_ydim;

	for(l=1; l < num_samples; l++){
		r_samples[l] = r_samples[0] + l * num_inputs;
		r_targets[l] = r_targets[0] + l * num_outputs;
	}

	/* query lapack for the best size of the work area */
	nlvl = (int32_t)(floor(log2((double)J_ydim/(25.0f+1.0f)))+1.0f);

	C_SAFE_CALL( S 		= (double  *) mem_alloc( J_ydim 							 * sizeof(double ), true) );
	C_SAFE_CALL( iwork 	= (int32_t *) mem_alloc( ( 3 * J_ydim * nlvl + 11 * J_ydim ) * sizeof(int32_t), true) );
	lwork = lpck_solve_ls_svd_pivot(J_ydim, -1, NULL, 0, S, iwork, A[0], G);
	C_SAFE_CALL( work = (double *)mem_alloc(lwork * sizeof(double), true) );

	/* allocate space for matrix inversion */
	C_SAFE_CALL( inv_ipiv = (double *) mem_alloc( (J_ydim + 1) * sizeof(double), true ) );
	C_SAFE_CALL( inv_work = (double *) mem_alloc( J_ydim * J_ydim * sizeof(double), true ) );

	/* grab scalling parameters */
	nn_grab_rescale_params( nn, samples, targets, num_samples );

	/* rescale samples and targets to -1, 1 range */
	for(l = 0; l < num_samples; l++){
		memcpy(r_samples[l], samples[l], num_inputs * sizeof(double));
		C_SAFE_CALL( transform_in_out_data(nn, r_samples[l], num_inputs, false) );

		memcpy(r_targets[l], targets[l], num_outputs * sizeof(double));
		for(i = 0; i < num_outputs; i++){
			r_targets[l][i] = map_min_max(r_targets[l][i], nn->ymin[i], nn->ymax[i], 0, 0, false);
		}
	}

	/* generate a duplicate network */
	C_SAFE_CALL( test_nn = duplicate_network( nn ) );

	/* copy network to a safe place */
	C_SAFE_CALL( copy_nn_data( test_nn, nn ) );

	/* generate a random permutation */
	random_perm(num_samples, perm);

	C_SAFE_CALL( Ed = compute_JGH(test_nn, J, G, A, r_samples, r_targets, errors, nn_out, nn_t_out, perm, num_samples, num_outputs, J_ydim) );

	/* compute Ew */
	Ew = 0.0;
	for( l = 1; l < test_nn -> num_layers; l++){
		layer = test_nn -> layer[l];
		for(i = 0; i < layer -> num_inputs * layer -> num_outputs; i++)
			Ew += layer -> weights[0][i] * layer -> weights[0][i];
		for(i = 0; i < layer->num_outputs; i++)
			Ew += layer->bias[i];
	}
	current_error = alpha * Ew + beta *Ed;

	/* disable rescaling */
	nn->use_mapping = false;

	/* lets train the neural network */
	while(sqrt(current_error) > min_error && epoch < max_epochs && niu < 1e10)
	{

		/* copy network to a safe place */
		C_SAFE_CALL( copy_nn_data( test_nn, nn ) );

		memcpy(A_temp, A[0], J_ydim * J_ydim * sizeof(double) );
		for(i=0; i < J_ydim * J_ydim; i++)
			A_temp[i] *= 2.0f * beta;

		/* apply niu and alpha regularization parameter */
		for(i = 0; i < J_ydim; i++)
			A_temp[i*J_ydim+i] += 2.0f*niu + 2.0f*alpha;

		/* call lapack to solve our system of equations */
		C_SAFE_CALL( lpck_solve_ls_svd_pivot(J_ydim, 0, work, lwork, S, iwork, A_temp, G) );

		/* apply changes to neural network weights (lapack gives solution in G) */
		nn_change_weights_bias(test_nn, G);

		/* compute new error */
		Ed = 0.0f;
		for(s = 0; s < num_samples; s++)
		{
			/* short circuit the pointers */
			test_nn->in_layer = r_samples[s]; test_nn->out_layer = nn_out;

			/* compute neural network response for this input */
			C_SAFE_CALL( get_nn_response( test_nn ) );

			/* compute sample errors */
			for(i = 0; i < num_outputs; i++)
				Ed += (r_targets[s][i] - nn_out[i])*(r_targets[s][i] - nn_out[i]);
		}

		/* compute Ew */
		Ew = 0.0;
		for( l = 1; l < test_nn -> num_layers; l++){
			layer = test_nn -> layer[l];
			for(i = 0; i < layer -> num_inputs * layer -> num_outputs; i++)
				Ew += layer -> weights[0][i] * layer -> weights[0][i];
			for(i = 0; i < layer->num_outputs; i++)
				Ew += layer->bias[i];
		}

		new_error = alpha * Ew + beta * Ed;

		/* check wether or not we should accept this network */
		if(new_error < current_error )
		{
			/* copy network */
			C_SAFE_CALL( copy_nn_data( nn, test_nn ) );
			current_error = new_error;
			niu *= 0.1;

			/* update parameters */
			memcpy(A_temp, A[0], J_ydim * J_ydim * sizeof(double) );
			//for(i=0; i < J_ydim * J_ydim; i++)
			//	A_temp[i] *= 2.0f * beta;

			/* apply niu and alpha regularization parameter */
			//for(i = 0; i < J_ydim; i++)
			//	A_temp[i*J_ydim+i] += 2.0f*alpha;

			C_SAFE_CALL( lpck_get_inv(J_ydim, inv_work, J_ydim * J_ydim, inv_ipiv, A_temp) );

			/* compute trace of the inverse */
			trace = 0.0;
			for(i = 0; i < J_ydim; i++)
				trace += A_temp[i*J_ydim+i];

			gamma = (double)(J_ydim) - (alpha * trace);
			beta =fabs(((double)(num_samples) - gamma)/(2.0f*Ed));
			alpha = (double)J_ydim /(2.0f*Ew+trace);

			/* compute new hessian, jacobian and gradient */
			C_SAFE_CALL( current_error = compute_JGH(test_nn, J, G, A, r_samples, r_targets,
								errors, nn_out, nn_t_out, perm, num_samples, num_outputs, J_ydim);
						);

		}else{
			niu *= 10;
		}

		printf("epoch: %d  alpha : %f beta : %f gamma : (%f ,%d, %d) trace : %f error : %f (%f, %f) niu : %f\n",epoch,alpha, beta, gamma, J_ydim,num_samples, trace,sqrt(current_error), Ed, Ew,niu);

		epoch++;
	}

	/* clean memory and exit */
	destroy_nn( test_nn );

	/* enable rescaling */
	nn->use_mapping = true;

	if(r_samples != NULL)
	{
		mem_free(r_samples[0]);
		mem_free(r_samples);
	}

	if(r_targets != NULL)
	{
		mem_free(r_targets[0]);
		mem_free(r_targets);
	}

	if(J != NULL)
	{
		mem_free( J[0] 	);
		mem_free( J 	);
	}
	if(A != NULL)
	{
		mem_free( A[0] 	);
		mem_free( A 	);
	}
	mem_free( A_temp    );
	mem_free( G 		);
	mem_free( errors 	);
	mem_free( perm 		);
	mem_free( nn_out	);
	mem_free( nn_t_out 	);
	mem_free( work 		);
	mem_free( iwork		);
	mem_free( S			);
	mem_free( inv_ipiv  );
	mem_free( inv_work  );

	NN_API_ERROR = NN_API_SUCCESS;

	return 0;
error:
	destroy_nn( test_nn );

	/* clean memory and exit */
	if(r_samples != NULL)
	{
		mem_free(r_samples[0]);
		mem_free(r_samples);
	}

	if(r_targets != NULL)
	{
		mem_free(r_targets[0]);
		mem_free(r_targets);
	}

	if(J != NULL)
	{
		mem_free( J[0] 	);
		mem_free( J 	);
	}
	if(A != NULL)
	{
		mem_free( A[0] 	);
		mem_free( A 	);
	}
	mem_free( G 		);
	mem_free( errors 	);
	mem_free( perm 		);
	mem_free( nn_out	);
	mem_free( nn_t_out 	);
	mem_free( work 		);
	mem_free( iwork		);
	mem_free( S			);

	NN_API_ERROR = NN_API_TRAIN3;
	return -1;
}
/* create a network with the same characteristics of the one that has been given */
net_t * duplicate_network(net_t * nn )
{
	int32_t * layers_func = NULL, * layers_conf = NULL;
	int32_t i = 0;
	net_t * new_nn = NULL;
	net_layer_t * layer;
	C_CHECK_CONDITION( nn == NULL, 					NN_API_BAD_INPUT );
	C_CHECK_CONDITION( nn -> layer == NULL, 		NN_API_BAD_INPUT );
	C_CHECK_CONDITION( nn -> init_state == false, 	NN_API_BAD_INPUT );
	C_CHECK_CONDITION( nn -> num_layers == 0, 		NN_API_BAD_INPUT );

	/* first allocate temporary memory */
	C_SAFE_CALL( layers_func = (int32_t *) mem_alloc( nn->num_layers * sizeof(int32_t), true ) );
	C_SAFE_CALL( layers_conf = (int32_t *) mem_alloc( nn->num_layers * sizeof(int32_t), true ) );

	/* get the parameters from this network */
	for(i = 0; i < nn->num_layers; i++)
	{
		layer = nn -> layer[i];
		C_CHECK_CONDITION( layer == NULL, NN_API_MALFORMED_NN );
		layers_conf[i] = layer->num_outputs;
		layers_func[i] = layer->eval_funct;

	}

	/* call network initializer */
	C_SAFE_CALL( new_nn = create_nn(nn->num_layers, layers_conf, layers_func ) );

	/* should we also copy the information? */

	/* input layer rescale parameters */
	for(i = 0; i < (nn->layer[0])->num_outputs; i++){
		new_nn -> xmin[i] = nn -> xmin[i];
		new_nn -> xmax[i] = nn -> xmax[i];
	}
	for(i = 0; i < (nn->layer[nn->num_layers-1])->num_outputs; i++){
		new_nn -> ymin[i] = nn -> ymin[i];
		new_nn -> ymax[i] = nn -> ymax[i];
	}

	/* free auxiliary memory areas */
	mem_free(layers_func);
	mem_free(layers_conf);

	NN_API_ERROR = NN_API_SUCCESS;
	return new_nn;
error:

	/* free auxiliary memory areas */
	mem_free(layers_func);
	mem_free(layers_conf);

	NN_API_ERROR = NN_API_DUPLICATE_NN;
	return NULL;
}

/* copy data from one network into another */
int32_t copy_nn_data(net_t *dest, net_t *src)
{
	net_layer_t * l_src = NULL, * l_dest = NULL;
	int32_t l = 0;

	C_CHECK_CONDITION( dest -> num_layers != src -> num_layers, NN_API_COPY_DIFF );
	C_CHECK_CONDITION( src -> num_layers == 0, 					NN_API_COPY_DIFF );

	for( l = 1; l < src -> num_layers; l++)
	{
		l_src = src->layer[l];
		l_dest = dest->layer[l];

		C_CHECK_CONDITION( l_src->num_inputs  != l_dest->num_inputs,  NN_API_COPY_DIFF );
		C_CHECK_CONDITION( l_src->num_outputs != l_dest->num_outputs, NN_API_COPY_DIFF );

		/* proceed with the copy */
		memcpy(l_dest->weights[0], l_src->weights[0], l_src->num_inputs * l_src->num_outputs * sizeof(double));
		memcpy(l_dest->bias, l_src->bias, l_src->num_outputs * sizeof(double));

	}
	dest->use_mapping = src->use_mapping;

	NN_API_ERROR = NN_API_SUCCESS;
	return 0;
error:
	NN_API_ERROR = NN_API_COPY;
	return -1;
}

void nn_grab_rescale_params(net_t * nn, double ** samples, double ** targets, int32_t num_samples )
{
	int32_t i = 0, j = 0;
	net_layer_t * layer = NULL;
	double min = 0, max = 0;
	layer = nn->layer[0];

	/* first find maximum and minimum of samples */
	min = samples[0][0]; max = min;

	for(i = 0; i < layer->num_outputs; i++)
	{
		for(j = 0; j < num_samples; j++ )
		{
			if(max < samples[j][i])
				max = samples[j][i];
			if(min > samples[j][i])
				min = samples[j][i];
		}
		/* store input parameters */
		nn->xmin[i] = min;
		nn->xmax[i] = max;
	}

	/* do it for targets*/
	layer = nn->layer[nn->num_layers-1];
	min = targets[0][0]; max = min;
	for(i = 0; i < layer->num_outputs; i++)
	{
		for(j = 0; j < num_samples; j++ )
		{
			if(max < targets[j][i])
				max = targets[j][i];
			if(min > targets[j][i])
				min = targets[j][i];
		}
		/* store output parameters */
		nn->ymin[i] = min;
		nn->ymax[i] = max;
	}
}

/*
 * Change weights and bias based on the values provided in G
 */
void nn_change_weights_bias(net_t * nn, double * G)
{
	net_layer_t * layer = NULL;
	int32_t l = 0, i = 0, j = 0, ct = 0;

	/* for each layer */
	for(l = 1; l < nn->num_layers; l++)
	{
		layer = nn->layer[l];

		for(i = 0; i < layer->num_outputs; i++)
		{
			/* weights */
			for(j = 0; j < layer->num_inputs; j++)
			{
				 layer->weights[i][j] -= G[ct++];
			}

			/* bias */
			layer->bias[i] -= G[ct++];
		}
	}

}

int32_t init_nn_weights(net_t * nn)
{
	int32_t i = 0, l = 0, n = 0;
	net_layer_t * layer = NULL;
	double norm_weights = 0.0f, norm_bias = 0.0f, beta = 0.0f;
	double f_range_up = 0.0f, f_range_down = 0.0f;

	C_CHECK_CONDITION( nn->num_layers == 0, NN_API_BAD_INPUT );

	for(l = 1; l < nn->num_layers; l++)
	{
		layer = nn->layer[l];

		/* scale random values according to the function used */
		switch(layer->eval_funct)
		{
			case      NN_LOGSIG: { f_range_up =  1.0f; f_range_down =  0.0f; break; }
			case      NN_TANSIG: { f_range_up =  1.0f; f_range_down = -1.0f; break; }
			default:  			 { f_range_up =  1.0f; f_range_down = -1.0f; break; }
		}

		beta = 0.7f*pow((double)layer->num_outputs, 1/(double)layer->num_inputs);

		/* for each neuron in the layer */
		norm_weights = 0.0f; norm_bias = 0.0f;
		for( n = 0; n < layer->num_outputs; n++)
		{
			for(i = 0; i < layer->num_inputs; i++ )
			{
				layer->weights[n][i] = (double)(mt_random()) * RANDMAXV * (f_range_up - f_range_down) - f_range_down;
				norm_weights += layer->weights[n][i] * layer->weights[n][i];
			}

			/* randomize bias */
			norm_weights = sqrt(norm_weights);
			norm_bias = sqrt(norm_bias);
			for(i = 0; i < layer->num_inputs; i++ )
				layer->weights[n][i] *=  (beta/norm_weights);

			// generate bias
			if(layer->weights[n][0]>0)
				layer->bias[n] = beta * (-1.0f + (double)n*2/(double)(layer->num_outputs));
			else
				layer->bias[n] = -1.0*beta * (-1.0f + (double)n*2/(double)(layer->num_outputs));
		}

	}

	NN_API_ERROR = NN_API_SUCCESS;
	return 0;

error:

	NN_API_ERROR = NN_API_WEIGHT_INIT;
	return -1;
}

int32_t lpck_solve_nxn_ls(int32_t J_ydim, int32_t query, int32_t * work, int32_t lwork, double *eq_matrix, double * eq_sol)
{
	int32_t col_size = J_ydim;
	int32_t car = 1;
	int32_t info = 0;
	if(query == -1)
	{
		return J_ydim;
	}else{
		dgesv_( &col_size, &car, eq_matrix, &col_size, work, eq_sol, &col_size, &info );

		/* Check for the exact singularity */
    	C_CHECK_CONDITION(info != 0, NN_API_LS_SOLVE);
    }
    NN_API_ERROR = NN_API_SUCCESS;
    return info;

error:
	if( info > 0 ) {
                printf( "\t\tThe diagonal element of the triangular factor of A,\n" );
                printf( "\t\tU(%i,%i) is zero, so that A is singular;\n", info, info );
                printf( "\t\tthe solution could not be computed.\n" );
                exit( 1 );
        }
    NN_API_ERROR = NN_API_LS;
	return -1;
}

int32_t lpck_solve_ls_svd_pivot(int32_t J_ydim, int32_t query, double * work, int32_t lwork, double * S, int32_t * iwork,  double *eq_matrix, double * eq_sol)
{
	int32_t col_size = J_ydim;
	int32_t nrhs = 1;
	int32_t info = 0;
	int32_t rank = 0;
	double work_size = 0;
	double rcond = -1;			/* default machine precision */

	if(query == -1)
	{
		dgelsd_( &col_size,		/* m dimension */
				&col_size,		/* n dimension */
				&nrhs, 			/* number of right hand sided */
				eq_matrix, 		/* A matrix */
				&col_size, 		/* leading dimension of A */
				eq_sol,			/* B vector */
				&col_size,		/* leading dimension of B */
				S, 				/* array with M columns */
				&rcond, 		/* which precision? */
				&rank, 			/* the matrix rank */
				&work_size,		/* query workspace size */
				&query,			/* the query */
				iwork, 			/* workspace array */
				&info );
		return (int32_t)work_size;
	}else{

		dgelsd_( &col_size,		/* m dimension */
				&col_size,		/* n dimension */
				&nrhs, 			/* number of right hand sided */
				eq_matrix, 		/* A matrix */
				&col_size, 		/* leading dimension of A */
				eq_sol,			/* B vector */
				&col_size,		/* leading dimension of B */
				S, 				/* array with M columns */
				&rcond, 		/* which precision? */
				&rank, 			/* the matrix rank */
				work,			/* query workspace size */
				&lwork,			/* the query */
				iwork, 			/* workspace array */
				&info );
		/* Check for the exact singularity */
    	C_CHECK_CONDITION(info != 0, NN_API_LS_SOLVE);
    }
    NN_API_ERROR = NN_API_SUCCESS;
    return info;

error:
	if( info > 0 ) {
        printf( "\t\tThe diagonal element of the triangular factor of A,\n" );
        printf( "\t\tU(%i,%i) is zero, so that A is singular;\n", info, info );
        printf( "\t\tthe solution could not be computed.\n" );
    }

    NN_API_ERROR = NN_API_LS;
	return -1;
}

int32_t lpck_solve_ls(int32_t J_ydim, int32_t query, double * work, int32_t lwork, double * eq_matrix,  double * eq_sol)
{
	char  N = 'N';
	int32_t col_size = J_ydim;
	int32_t car = 1;
	int32_t info = 0;
	double work_size = 7000;

	if (query==-1)
	{

		dgels_(  &N,			         /* use matrix M or its transpose */
			    &col_size,           /* number of lines of matrix M   */
				&col_size,           /* number of rows of matrix M    */
				&car,                /* number of colums of B and x   */
				eq_matrix,           /* matrix M                      */
				&col_size,           /* size -> max(1,vector_size)    */
				eq_sol,              /* sol vector / out vector       */
				&col_size,           /* LDB                           */
				&work_size,          /* work array                    */
				&query,              /* LWORK                         */
				&info);
		return (int32_t)work_size;
	}else{

		dgels_(  &N,				     /* use matrix M or its transpose */
			    &col_size,           /* number of lines of matrix M   */
				&col_size,           /* number of rows of matrix M    */
				&car,                /* number of colums of B and x   */
				eq_matrix,           /* matrix M                      */
				&col_size,           /* size -> max(1,vector_size)    */
				eq_sol,        		 /* sol vector / out vector       */
				&col_size,           /* LDB                           */
				work,				 /* work array                    */
				&lwork,              /* LWORK                         */
				&info);

		C_CHECK_CONDITION(info != 0, NN_API_LS_SOLVE);
	}

	NN_API_ERROR = NN_API_SUCCESS;
	return info;
error:
	if( info > 0 )
	{
        printf( "\t\tThe diagonal element %i of the triangular factor ", info );
        printf( "\t\tof A is zero, so that A does not have full rank;\n" );
        printf( "\t\tthe least squares solution could not be computed.\n" );
    }else{
    	printf("Lwork value %d\n", lwork);

    }
    NN_API_ERROR = NN_API_LS;
	return -1;
}

int32_t lpck_get_inv(int32_t J_ydim, double * work, int32_t lwork, double * ipiv, double * eq_matrix)
{
	int32_t col_size = J_ydim;
	int32_t info = 0;
	int32_t i = 0;

	dgetrf_(&col_size,&col_size, eq_matrix, &col_size, ipiv, &info);

	if(info!= 0){
		goto error;
	}
	dgetri_(&col_size, eq_matrix, &col_size, ipiv, work, &lwork, &info);

	if(info!= 0){
		goto error;
	}

	if(info!= 0)
		goto error;

	NN_API_ERROR = NN_API_SUCCESS;
	return info;
error:
	if(info > 0)
		printf("decomposition successful but U is singular, u_%d%d=0\n", i,i);
	else
		printf("decomposition not successful %dth parameter has an illegal value\n", i);

	NN_API_ERROR = NN_API_GET_INV;
	return -1;
}

void random_perm(int32_t num_p, int32_t * ranpat)
{
	unsigned int p, np, op;
	unsigned int r;
	for( p = 0 ; p < (unsigned int)num_p; p++ ) { ranpat[p] = (unsigned int)p; }

	for( p = 0 ; p < (unsigned int)num_p; p++)
	{
		r =  (unsigned int)( mt_random() * 1.0 * ( RANDMAXV  * (num_p - p )));
		np = p + r ;
		op = ranpat[p];
		ranpat[p] = ranpat[np];
		ranpat[np] = op;
	}
}

void init_mt(void)
{
    int32_t i = 0;
    srand((int32_t)time(NULL));
    /* init mersenne twister */
	mt_state[0]= rand() & 0xffffffffUL;
    for (i = 1; i < MT_N; i++)
	{
            mt_state[i] = (1812433253UL * (mt_state[i - 1] ^ (mt_state[i-1] >> 30)) + i);
        	/* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
       		/* In the previous versions, MSBs of the seed affect   */
        	/* only MSBs of the array mt[].                        */
        	/* 2002/01/09 modified by Makoto Matsumoto             */
        	mt_state[i] &= 0xffffffffUL;
        	/* for >32 bit machines */
    }

	mt_constants[0] = 0x0L;
	mt_constants[1] = 0x9908b0dfUL;
	mt_state_idx = MT_N+1;
}

unsigned long mt_random(void){
	unsigned long y;
	if(rand_state == false){ init_mt(); rand_state = true; }
	/* Updating state */
	if( mt_state_idx >= MT_N){
		int k;
		for(k = 0; k < MT_N - MT_M; k++)
		{
			y = ( mt_state[ k ] & MT_UPPER_MASK ) | ( mt_state[ k + 1 ] & MT_LOWER_MASK );
			mt_state[ k ] = mt_state[ k + MT_M ] ^ ( y>> 1) ^ mt_constants[ y & 0x1UL ];
		}
		for(; k < MT_N - 1; k++){
			y = ( mt_state[ k ] & MT_UPPER_MASK ) | ( mt_state[ k + 1 ] & MT_LOWER_MASK );
			mt_state[ k ] = mt_state[ k + ( MT_M - MT_N ) ] ^ ( y>> 1) ^ mt_constants[ y & 0x1UL ];
		}
		y = (mt_state[ MT_N - 1 ] & MT_UPPER_MASK ) | ( mt_state[0] & MT_LOWER_MASK );
		mt_state[ MT_N - 1 ] = mt_state[ MT_M - 1 ] ^ (y >> 1) ^ mt_constants[ y & 0x1UL ];
		mt_state_idx = 0;
	}
	/* Generating */
	y = mt_state[ mt_state_idx++ ];
	/* tempering */
	y ^= (y >> 11);
	y ^= (y << 7 )  & 0x9d2c5680UL;
	y ^= (y << 15 ) & 0xefc60000UL;
	y ^= (y >> 18);
	return y;
}


#pragma once

#include <gsl/span>

#include <utility>
#include <vector>

namespace Hash
{
   template<typename T>
   inline void combine(std::size_t& seed, const T& value)
   {
      std::hash<T> hasher;
      seed ^= hasher(value) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
   }
}

namespace std
{
   template<typename T>
   struct hash<gsl::span<T>>
   {
      std::size_t operator()(const gsl::span<T>& values) const
      {
         std::size_t seed = 0;

         for (const auto& value : values)
         {
            Hash::combine(seed, value);
         }

         return seed;
      }
   };

   template<typename T>
   struct hash<std::vector<T>>
   {
      std::size_t operator()(const std::vector<T>& values) const
      {
         return hash<gsl::span<const T>>()(values);
      }
   };
}