#ifndef _fim_h_included_
#define _fim_h_included_

#include <assert.h>
#include <stdlib.h>
#include <algorithm>
#include <cctype>
#include <fstream>
#include <functional>
#include <iostream>
#include <map>
#include <numeric>
#include <set>
#include <sstream>
#include <vector>
#include <sstream>
#include <string>
#include <boost/config.hpp>
#include <boost/foreach.hpp>
#include <boost/random.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/matrix.hpp>

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

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

#include <jsc/bioinfo/gene_anno.hpp>
#include <jsc/util/log.hpp>

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

#include "splicing_graph.h"
#include "read.h"
#include "linalg.h"
#include "accessible_read_starts.h"

using namespace std;
using namespace boost;
using namespace boost::numeric;
using namespace jsc::bioinfo;
using namespace jsc::util;

class FIM {
	public:
		FIM () {
			ofim_call_count = 0;
		}

		double get_ofim_call_count() const {
			return ofim_call_count;
		};

		static double estimate_mle_variance(
				ublas::matrix<double> const & I) {
			return estimate_mle_variance_by_diag(I);
		}

		static double estimate_mle_variance_by_diag(
				ublas::matrix<double> const & I) {
			unsigned long K = I.size1() + 1;
			double sum = 0;
			for (unsigned long p = 0; p < K - 1; ++p) {
				sum += 1.0 / I(p,p);
			}
			return sum;
		}

		static double estimate_mle_variance_by_inv(
				ublas::matrix<double> const & I) {
			assert(I.size1() == I.size2());
			L_(debug) << I;
			ublas::matrix<double> inv =
				ublas::zero_matrix<double>(I.size1(), I.size1());
			linalg::invert_matrix(I, inv);

			L_(debug) << inv;

			unsigned long K = I.size1() + 1;
			double sum = 0;
			for (unsigned long p = 0; p < K - 1; ++p) {
				for (unsigned long q = 0; q < K - 1; ++q) {
					sum += inv(p,q);
				}
				sum += inv(p,p);
			}

			return sum;
		}

		static void print_fim_for_R(ostream & os, ublas::matrix<double> I) {
			unsigned long K = I.size1() + 1;
			for (unsigned long p = 0; p < K - 1; ++p) {
				for (unsigned long q = 0; q < K - 1; ++q) {
					os << I(p,q) << "\t";
				}
				os << endl;
			}
		}

		static void print_fim_diag_for_R(ostream & os, ublas::matrix<double> I) {
			unsigned long K = I.size1() + 1;
			os << "(";
			for (unsigned long p = 0; p < K - 1; ++p) {
				os << I(p,p) << ",";
			}
			os << ")";
			os << endl;
		}

		ublas::matrix<double> bruteforce_fim(shared_ptr<Read> readp,
				vector<double> const & iso_probs) {
			L_(debug) << "FIM::bruteforce_fim";
			reset_ofim_call_count();
			shared_ptr<AccessibleReadStarts> ars = readp->get_ARS();
			unsigned long K = ars->get_num_isoforms();
			ublas::matrix<double> I = ublas::zero_matrix<double>(K-1, K-1);

			for (unsigned long k = 0; k < K; ++k) {
				if (iso_probs[k] == 0) {
					continue;
				}
				ublas::matrix<int> sign;
				L_(debug) << "Possible Isoform #" << k
					<< ": Generating reads with starts from 0 to "
					<< ars->get_iso_ARS_total_length(k)
					<< " in ARS";
				for (unsigned long a = 0;
						a < ars->get_iso_ARS_total_length(k);
						++a) {
					// generate a read starting from a in ARS
					readp->generate_read(k, ars->ARStart2IsoStart(k, a));
					double G = readp->prob_generated_by_iso(k);
					double log_scaler = log(G) + log(iso_probs[k]);
					ublas::matrix<double> m = ofim(readp, iso_probs, sign);
					for (unsigned long p = 0; p < K - 1; ++p) {
						for (unsigned long q = 0; q < K - 1; ++q) {
							if (sign(p,q) > 0) {
								m(p,q) = exp(m(p,q) + log_scaler);
							} else if (sign(p,q) < 0) {
								m(p,q) = - exp(m(p,q) + log_scaler);
							} else {
								// m(p,q) must be zero, no need to perform scaling
							}
							assert(!isinf(m(p,q)) && !isnan(m(p,q)));
						}
					}
					I += m;
				}
			}
			return I;
		}

		ublas::matrix<double> fast_fim(shared_ptr<Read> readp,
				vector<double> const & iso_probs) {
			L_(debug) << "FIM::fast_fim";
			reset_ofim_call_count();
			shared_ptr<AccessibleReadStarts> ars = readp->get_ARS();
			unsigned long K = ars->get_num_isoforms();
			ublas::matrix<double> I = ublas::zero_matrix<double>(K-1, K-1);
			for (unsigned long k = 0; k < K; ++k) {
				if (iso_probs[k] == 0) {
					continue;
				}
				ublas::matrix<int> sign;
				L_(debug) << "Possible Isoform #" << k
					<< ": Generating reads with starts from 0 to "
					<< ars->get_iso_ARS_total_length(k)
					<< " in ARS";

				unsigned long a = 0;
				while (a < ars->get_iso_ARS_total_length(k)) {
					// generate a read starting from a
					readp->generate_read(k, ars->ARStart2IsoStart(k, a));

					unsigned long N_overlapping_exons = readp->exon_indices.size();
					unsigned long N_eq_samples = min(
							ars->exon_lengths[readp->exon_indices[0]]
							- readp->start_at_first_exon,
							ars->exon_lengths[readp->exon_indices[N_overlapping_exons - 1]]
							- readp->end_at_last_exon + 1);

					while ( (a + N_eq_samples - 1 >= ars->get_iso_ARS_total_length(k)) || (ars->ARStart2IsoStart(k, a + N_eq_samples - 1) != (ars->ARStart2IsoStart(k, a) + N_eq_samples - 1)) ){
						N_eq_samples--;
					}

					double G = readp->prob_generated_by_iso(k);
					ublas::matrix<double> m = ofim(readp, iso_probs, sign);
					bool zero_scaler = ((G == 0) || (iso_probs[k] == 0));
					double log_scaler = log(N_eq_samples)
						+ log(G)
						+ log(iso_probs[k]);
					for (unsigned long p = 0; p < K - 1; ++p) {
						for (unsigned long q = 0; q < K - 1; ++q) {
							if (zero_scaler) {
								m(p,q) = 0;
							} else if (sign(p,q) > 0) {
								m(p,q) = exp(m(p,q) + log_scaler);
							} else if (sign(p,q) < 0) {
								m(p,q) = - exp(m(p,q) + log_scaler);
							} else {
								// m(p,q) must be zero, no need to perform scaling
							}
							assert(!isinf(m(p,q)) && !isnan(m(p,q)));
						}
					}
					I += m;
					a += N_eq_samples;
				}
			}
			return I;
		}

		ublas::matrix<double> faster_fim(shared_ptr<Read> readp,
				vector<double> const & iso_probs) {
			L_(debug) << "FIM::faster_fim";
			reset_ofim_call_count();
			shared_ptr<AccessibleReadStarts> ars = readp->get_ARS();
			unsigned long K = ars->get_num_isoforms();
			ublas::matrix<double> I = ublas::zero_matrix<double>(K-1, K-1);
			vector<interval_list<long > > covered_sample_starts;
			for (unsigned long k = 0; k < K; ++k) {
				interval_list<long> il;
				covered_sample_starts.push_back(il);
			}
			for (unsigned long k = 0; k < K; ++k) {
				if (iso_probs[k] == 0) {
					continue;
				}

				unsigned long a = covered_sample_starts[k].find_min_uncovered(0);
				L_(debug) << "Possible Isoform #" << k
					<< ": Generating reads with starts from " << a << " to "
					<< ars->get_iso_ARS_total_length(k)
					<< " in ARS";
				while (a < ars->get_iso_ARS_total_length(k)) {
					// generate a read starting from a
					readp->generate_read(k, ars->ARStart2IsoStart(k, a));

					unsigned long N_overlapping_exons = readp->exon_indices.size();
					unsigned long N_eq_samples = min(
							ars->exon_lengths[readp->exon_indices[0]]
							- readp->start_at_first_exon,
							ars->exon_lengths[readp->exon_indices[N_overlapping_exons - 1]]
							- readp->end_at_last_exon + 1);
					while ( (a + N_eq_samples - 1 >= ars->get_iso_ARS_total_length(k)) || (ars->ARStart2IsoStart(k, a + N_eq_samples - 1) != (ars->ARStart2IsoStart(k, a) + N_eq_samples - 1)) ){
						N_eq_samples--;
					}

					ublas::matrix<int> sign;
					ublas::matrix<double> m = ofim(readp, iso_probs, sign);

					double G = readp->prob_generated_by_iso(k);
					double scaler = G * N_eq_samples * iso_probs[k];

					if (covered_sample_starts[k].compute_overlap(a, a + N_eq_samples)
							> 0) {
						L_(debug2) << "Cov for iso # " << k
							<< ": "	<< covered_sample_starts[k];
						L_(error) << "Overlap > 0!";
					}
					covered_sample_starts[k].add_interval(a, a + N_eq_samples);
					L_(debug2) << "Adding [" << a
						<< "," << (a + N_eq_samples)
						<< ") to possible isoform #" << k;
					for (unsigned long j = 0; j < K; ++j) {
						if (j == k) continue;
						long first_matching_idx =
							is_connected_exons_compatible_with_isoform(
									readp->exon_indices,
									ars->iso_exon_indices[j]);
						if (first_matching_idx >= 0) {
							double G_j = readp->prob_generated_by_iso(j);
							scaler += G_j * N_eq_samples * iso_probs[j];
							unsigned long a2 = readp->start_at_first_exon
								+ ars->iso_exon_total_lengths[j][first_matching_idx]
								- ars->exon_lengths[readp->exon_indices[0]];
							a2 = ars->IsoStart2ARStart(j, a2);

							if (covered_sample_starts[j].compute_overlap(a2, a2 + N_eq_samples)
									> 0) {
								L_(debug2) << "Cov for iso # " << j
									<< ": "	<< covered_sample_starts[j];
								L_(error) << "Overlap > 0!";
							}
							covered_sample_starts[j].add_interval(a2, a2 + N_eq_samples);
							L_(debug2) << "Adding [" << a2
								<< "," << (a2 + N_eq_samples)
								<< ") to possible isoform #" << j;
						}
					}

					double log_scaler = log(scaler);
					for (unsigned long p = 0; p < K - 1; ++p) {
						for (unsigned long q = 0; q < K - 1; ++q) {
							if (sign(p,q) > 0) {
								m(p,q) = exp(m(p,q) + log_scaler);
							} else if (sign(p,q) < 0) {
								m(p,q) = - exp(m(p,q) + log_scaler);
							} else {
								// m(p,q) must be zero, no need to perform scaling
							}
							assert(!isinf(m(p,q)) && !isnan(m(p,q)));
						}
					}
					I += m;
					a = covered_sample_starts[k].find_min_uncovered(0);
				}
			}
			return I;
		}
	protected:
		void reset_ofim_call_count() {
			ofim_call_count = 0;
		};

		// \mathfrak{I}_{s}^{(m)}(\Theta)
		ublas::matrix<double> ofim(shared_ptr<const Read> readp,
				vector<double> const & iso_probs,
				ublas::matrix<int> & sign) {
			++ofim_call_count;
			shared_ptr<AccessibleReadStarts> ars = readp->get_ARS();
			unsigned long K = ars->get_num_isoforms();
			ublas::matrix<double> m = ublas::zero_matrix<double>(K-1,K-1);
			sign = ublas::zero_matrix<int>(K-1, K-1);
			vector<double> v_delta_G(K, 0);
			for (unsigned long k = 0; k < K; ++k) {
				if (readp->is_compatible_with_iso(k)) {
					v_delta_G[k] = readp->prob_generated_by_iso(k);
				}
			}

			double sum = 0;
			for (unsigned long k = 0; k < K; ++k) {
				if (v_delta_G[k] > 0 && iso_probs[k] > 0) {
					sum += iso_probs[k] * v_delta_G[k];
				}
			}

			assert(sum > 0);

			double log_prod = log(sum) + log(sum);
			for (unsigned long p = 0; p < K - 1; ++p) {
				for (unsigned long q = 0; q < K - 1; ++q) {
					m(p,q) = (v_delta_G[p] - v_delta_G[K - 1])*
						(v_delta_G[q] - v_delta_G[K - 1]);
					if (m(p,q) != 0) {
						sign(p,q) = m(p,q) > 0 ? 1 : -1;
						if (m(p,q) < 0) {
							m(p,q) = - m(p,q);
						}
						m(p,q) = log(m(p,q)) - log_prod;
					}
				}
			}

			if (readp->exon_indices[0] == 0
					&& readp->start_at_first_exon == 2730) {
				L_(info) << *readp << " " << m(0,0) << " " << sign(0,0);
			}

			return m;
		}

	private:
		double ofim_call_count;
};

#endif


#ifndef ConditionalTest_SERIAL_H
#define ConditionalTest_SERIAL_H
#include <ace/core/core.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_statistics_double.h>
#include <gsl/gsl_randist.h>
#include "conditionaltest.h"

class ConditionalTest::Serial : public EAbstractAnalyticSerial
{
    Q_OBJECT
public:
    explicit Serial(ConditionalTest* parent);
    virtual std::unique_ptr<EAbstractAnalyticBlock> execute(const EAbstractAnalyticBlock* block) override final;

    // helper functions
    bool isEmpty(QVector<QVector<double>>& matrix);

    // Statistical Tests
    void hypergeom(
        const QVector<QString>& amx_column,
        const CCMatrix::Pair& ccmPair,
        int clusterIndex,
        int featureIndex,
        int labelIndex,
        double& results);

    void test_proportions(
        const QVector<QString>& amx_column,
        const CCMatrix::Pair& ccmPair,
        int clusterIndex,
        int featureIndex,
        int labelIndex,
        double& results);

    void regression(
        const QVector<QString>& amx_column,
        const CCMatrix::Pair& ccmPair,
        int clusterIndex,
        QVector<double>& results);

private:
    /*!
     * Pointer to the base analytic for this object.
     */
    ConditionalTest* _base;

    // Performs power analysis for multiple-linear regression.
    double pwr_f2_test(int u, int v, double f2, double sig_level);
};

#endif


#include "std_includes.h"

#ifndef MATRIX_H
#define MATRIX_H

#ifdef CRANIUM_USE_MKL
#define CRANIUM_USE_BLAS
#include "mkl.h"
#else
#ifdef CRANIUM_USE_CBLAS
#define CRANIUM_USE_BLAS
#include <cblas.h>
#endif
#endif

// represents user-supplied training data
typedef struct DataSet_ {
    size_t rows;
    size_t cols;
    float** data;
} DataSet;

// represents a matrix of data in row-major order
typedef struct Matrix_ {
    size_t rows;
    size_t cols;
    float* data;
} Matrix;

#ifndef RAND_MOD
#define RAND_MOD 2147483647
#endif

// create dataset given user data
static DataSet* createDataSet(size_t rows, size_t cols, float** data);

// uses memory of the original data to split dataset into batches
static DataSet** createBatches(DataSet* allData, int numBatches);

// split a dataset into row matrices
static Matrix** splitRows(DataSet* dataset);

// destroy dataset
static void destroyDataSet(DataSet* dataset);

// convert dataset to matrix
static Matrix* dataSetToMatrix(DataSet* dataset);

// creates a matrix given data
static Matrix* createMatrix(size_t rows, size_t cols, float* data);

// creates a matrix zeroed out
static Matrix* createMatrixZeroes(size_t rows, size_t cols);

// get an element of a matrix
static float getMatrix(Matrix* mat, size_t row, size_t col);

// set an element of a matrix
static void setMatrix(Matrix* mat, size_t row, size_t col, float val);

// sets the values in $to equal to values in $from
static void copyValuesInto(Matrix* from, Matrix* to);

// prints the entries of a matrix
static void printMatrix(Matrix* input);

// sets each entry in matrix to 0
static void zeroMatrix(Matrix* orig);

// returns transpose of matrix
static Matrix* transpose(Matrix* orig);

// transposes matrix and places data into $origT
static void transposeInto(Matrix* orig, Matrix* origT);

// adds two matrices and returns result
static Matrix* add(Matrix* A, Matrix* b);

// adds $from to $to and places result in $to
static void addTo(Matrix* from, Matrix* to);

// adds $B, a row vector, to each row of $A
static Matrix* addToEachRow(Matrix* A, Matrix* B);

// multiplies every element of $orig by $C
static void scalarMultiply(Matrix* orig, float c);

// multiplies $A and $B (ordering: AB) and returns product matrix
static Matrix* multiply(Matrix* A, Matrix* B);

// multiplies $A and $B (ordering: AB) and places values into $into
static void multiplyInto(Matrix* A, Matrix* B, Matrix* into);

// element-wise multiplcation
static Matrix* hadamard(Matrix* A, Matrix* B);

// places values of hadamard product of $A and $B into $into
static void hadamardInto(Matrix* A, Matrix* B, Matrix* into);

// returns a shallow copy of input matrix
static Matrix* copy(Matrix* orig);

// returns 1 if matrices are equal, 0 otherwise
static int equals(Matrix* A, Matrix* B);

// frees a matrix and its data
static void destroyMatrix(Matrix* matrix);

/*
    Begin functions.
*/

static DataSet* createDataSet(size_t rows, size_t cols, float** data){
    DataSet* dataset = (DataSet*)malloc(sizeof(DataSet));
    dataset->rows = rows;
    dataset->cols = cols;
    dataset->data = data;
    return dataset;
}

DataSet** createBatches(DataSet* allData, int numBatches){
    DataSet** batches = (DataSet**)malloc(sizeof(DataSet*) * numBatches);
    int remainder = allData->rows % numBatches;
    int i;
    int curRow = 0;
    for (i = 0; i < numBatches; i++){
        size_t batchSize = allData->rows / numBatches;
        if (remainder-- > 0){
            batchSize++;
        }
        batches[i] = createDataSet(batchSize, allData->cols, allData->data + curRow);
        curRow += batchSize;
    }
    return batches;
}

static Matrix** splitRows(DataSet* dataset){
    Matrix** rows = (Matrix**)malloc(sizeof(Matrix*) * dataset->rows);
    int i;
    for (i = 0; i < dataset->rows; i++){
        rows[i] = createMatrix(1, dataset->cols, dataset->data[i]);
    }
    return rows;
}

void shuffleTogether(DataSet* A, DataSet* B, std::minstd_rand &gen){
    assert(A->rows == B->rows);
    int i;
    for (i = 0; i < A->rows - 1; i++){
        size_t j = i + gen() / (RAND_MOD / (A->rows - i) + 1);
        float* tmpA = A->data[j];
        A->data[j] = A->data[i];
        A->data[i] = tmpA;
        float* tmpB = B->data[j];
        B->data[j] = B->data[i];
        B->data[i] = tmpB;
    }
}

static void destroyDataSet(DataSet* dataset){
    int i;
    for (i = 0; i < dataset->rows; i++){
        free(dataset->data[i]);
    }
    free(dataset->data);
    free(dataset);
}

static Matrix* dataSetToMatrix(DataSet* dataset){
    Matrix* convert = (Matrix*)malloc(sizeof(Matrix));
    convert->rows = dataset->rows;
    convert->cols = dataset->cols;
    convert->data = (float*)malloc(sizeof(float) * dataset->rows * dataset->cols);
    int i, j;
    for (i = 0; i < dataset->rows; i++){
        for (j = 0; j < dataset->cols; j++){
            setMatrix(convert, i, j, dataset->data[i][j]);
        }
    }
    return convert;
}

Matrix* createMatrix(size_t rows, size_t cols, float* data){
    assert(rows > 0 && cols > 0);
    Matrix* matrix = (Matrix*)malloc(sizeof(Matrix));
    matrix->rows = rows;
    matrix->cols = cols;
    matrix->data = data;
    return matrix;
}

Matrix* createMatrixZeroes(size_t rows, size_t cols){
    assert(rows > 0 && cols > 0);
    Matrix* matrix = (Matrix*)malloc(sizeof(Matrix));
    matrix->rows = rows;
    matrix->cols = cols;
    float* data = (float*)calloc(rows * cols, sizeof(float));
    matrix->data = data;
    return matrix;
}

inline float getMatrix(Matrix* mat, size_t row, size_t col){
    return mat->data[row * mat->cols + col];
}

inline void setMatrix(Matrix* mat, size_t row, size_t col, float val){
    mat->data[row * mat->cols + col] = val;
}

void copyValuesInto(Matrix* from, Matrix* to){
    assert(from->rows == to->rows && from->cols == to->cols);
    memcpy(to->data, from->data, sizeof(float) * to->rows * to->cols);
}

void printMatrix(Matrix* input){
    int i, j;
    for (i = 0; i < input->rows; i++){
        printf("\n");
        for (j = 0; j < input->cols; j++){
            printf("%.2f ", getMatrix(input, i, j));
        }
    }
    printf("\n");
}

void zeroMatrix(Matrix* orig){
    memset(orig->data, 0, orig->rows * orig->cols * sizeof(float));
}

Matrix* transpose(Matrix* orig){
    float* data = (float*)malloc(sizeof(float) * orig->rows * orig->cols);
    Matrix* transpose = createMatrix(orig->cols, orig->rows, data);
    int i, j;
    for (i = 0; i < orig->rows; i++){
        for (j = 0; j < orig->cols; j++){
            setMatrix(transpose, i, j, getMatrix(orig, i, j));
        }
    }
    return transpose;
}

void transposeInto(Matrix* orig, Matrix* origT){
    assert(orig->rows == origT->cols && orig->cols == origT->rows);
    int i, j;
    for (i = 0; i < orig->rows; i++){
        for (j = 0; j < orig->cols; j++){
            setMatrix(origT, j, i, getMatrix(orig, i, j));
        }
    }
}

Matrix* add(Matrix* A, Matrix* B){
    assert(A->rows == B->rows && A->cols == B->cols);
    float* data = (float*)malloc(sizeof(float) * A->rows * B->rows);
    Matrix* result = createMatrix(A->rows, A->cols, data);
    int i, j;
    for (i = 0; i < A->rows; i++){
        for (j = 0; j < A->cols; j++){
            setMatrix(result, i, j, getMatrix(B, i, j) + getMatrix(A, i, j));
        }
    }
    return result;
}

void matrix_mul_add(Matrix * to, float a, Matrix * x, float b, bool square = false) {
	assert(to->rows == x->rows && to->cols == x->cols);
	int i, j;
	for (i = 0; i < to->rows; i++) {
		if (!square) {
			for (j = 0; j < to->cols; j++)
				setMatrix(to, i, j, getMatrix(to, i, j) * a + getMatrix(x, i, j) * b);
		}
		else for (j = 0; j < to->cols; j++) {
			float val = getMatrix(x, i, j);
			setMatrix(to, i, j, getMatrix(to, i, j) * a + val * val * b);
		}
	}
}

void matrix_replace(Matrix * to, Matrix * x, float b) {
	assert(to->rows == x->rows && to->cols == x->cols);
	int i, j;
	for (i = 0; i < to->rows; i++) {
		for (j = 0; j < to->cols; j++)
			setMatrix(to, i, j, getMatrix(x, i, j) * b);
	}
}

void adam_update(Matrix * to, Matrix * m, Matrix * v, float a) {
	assert(to->rows == m->rows && to->cols == m->cols);
	assert(to->rows == v->rows && to->cols == v->cols);
	int i, j;
	for (i = 0; i < to->rows; i++) {
		for (j = 0; j < to->cols; j++)
			setMatrix(to, i, j, (getMatrix(m, i, j) * a) / (sqrt(getMatrix(v, i, j)) + 0.000000001));
	}
}

void addTo(Matrix* from, Matrix* to){
    assert(from->rows == to->rows && from->cols == to->cols);
    int i, j;
    for (i = 0; i < from->rows; i++){
        for (j = 0; j < from->cols; j++){
            setMatrix(to, i, j, getMatrix(from, i, j) + getMatrix(to, i, j));
        }
    }
}

// add B to each row of A
Matrix* addToEachRow(Matrix* A, Matrix* B){
    assert(A->cols == B->cols && B->rows == 1);
    float* data = (float*)malloc(sizeof(float) * A->rows * A->cols);
    Matrix* result = createMatrix(A->rows, A->cols, data);
    int i, j;
    for (i = 0; i < A->rows; i++){
        for (j = 0; j < A->cols; j++){
            setMatrix(result, i, j, getMatrix(A, i, j) + getMatrix(B, 0, j));
        }
    }
    return result;
}

void scalarMultiply(Matrix* orig, float c){
    int i, j;
    for (i = 0; i < orig->rows; i++){
        for (j = 0; j < orig->cols; j++){
            setMatrix(orig, i, j, getMatrix(orig, i, j) * c);
        }
    }
}

Matrix* multiply(Matrix* A, Matrix* B){
    assert(A->cols == B->rows);
    float* data = (float*)malloc(sizeof(float) * A->rows * B->cols);
    Matrix* result = createMatrix(A->rows, B->cols, data);
#ifdef CRANIUM_USE_BLAS
    zeroMatrix(result);
    cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, A->rows, B->cols
    , A->cols, 1, A->data, A->cols, B->data, B->cols, 1, result->data, result->cols);
    return result;
#endif
    int i, j;
    for (i = 0; i < A->rows; i++){
        for (j = 0; j < B->cols; j++){
            float sum = 0;
            int k;
            for (k = 0; k < B->rows; k++){
                sum += getMatrix(A, i, k) * getMatrix(B, k, j);
            }
            setMatrix(result, i, j, sum);
        }
    }
    return result;
}

void multiplyInto(Matrix* A, Matrix* B, Matrix* into){
#ifdef CRANIUM_USE_BLAS
    zeroMatrix(into);
    cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, A->rows, B->cols
    , A->cols, 1, A->data, A->cols, B->data, B->cols, 1, into->data, into->cols);
    return;
#endif
    assert(A->cols == B->rows);
    assert(A->rows == into->rows && B->cols == into->cols);
    int i, j;
    for (i = 0; i < A->rows; i++){
        for (j = 0; j < B->cols; j++){
            float sum = 0;
            int k;
            for (k = 0; k < B->rows; k++){
                sum += getMatrix(A, i, k) * getMatrix(B, k, j);
            }
            setMatrix(into, i, j, sum);
        }
    }
}

Matrix* hadamard(Matrix* A, Matrix* B){
    assert(A->rows == B->rows && A->cols == B->cols);
    float* data = (float*)malloc(sizeof(float) * A->rows * A->cols);
    Matrix* result = createMatrix(A->rows, A->cols, data);
    int i, j;
    for (i = 0; i < A->rows; i++){
        for (j = 0; j < A->cols; j++){
            setMatrix(result, i, j, getMatrix(A, i, j) * getMatrix(B, i, j));
        }
    }
    return result;
}

void hadamardInto(Matrix* A, Matrix* B, Matrix* into){
    assert(A->rows == B->rows && A->cols == B->cols);
    assert(A->rows == into->rows && A->cols == into->cols);
    int i, j;
    for (i = 0; i < A->rows; i++){
        for (j = 0; j < A->cols; j++){
            setMatrix(into, i, j, getMatrix(A, i, j) * getMatrix(B, i, j));
        }
    }
}

Matrix* copy(Matrix* orig){
    float* data = (float*)malloc(sizeof(float) * orig->rows * orig->cols);
    memcpy(data, orig->data, sizeof(float) * orig->cols * orig->rows);
    return createMatrix(orig->rows, orig->cols, data);
}

int equals(Matrix* A, Matrix* B){
    if (A->rows != B->rows){
        return 0;
    }
    if (A->cols != B->cols){
        return 0;
    }
    int i, j;
    for (i = 0; i < A->rows; i++){
        for (j = 0; j < A->cols; j++){
            if (getMatrix(A, i, j) != getMatrix(B, i, j)){
                return 0;
            }
        }
    }
    return 1;
}

void destroyMatrix(Matrix* matrix){
    free(matrix->data);
    free(matrix);
}

#endif


/* multifit/covar.c
 *
 * Copyright (C) 1996, 1997, 1998, 1999, 2000 Brian Gough
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <config.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_multifit_nlin.h>
#include <gsl/gsl_blas.h>

int
gsl_multifit_gradient (const gsl_matrix * J, const gsl_vector * f,
                       gsl_vector * g)
{
  int status = gsl_blas_dgemv (CblasTrans, 1.0, J, f, 0.0, g);
  return status;
}


/*! /file global.h
 *  /brief Declarations of global variables and functions. */

#ifndef GLOBAL_H
#define GLOBAL_H

#ifdef COOLING_CPU
#include <gsl/gsl_spline.h>
#include <gsl/gsl_spline2d.h>
#endif

#if PRECISION == 1
#ifndef FLOAT_TYPEDEF_DEFINED
typedef float Real;
#endif //FLOAT_TYPEDEF_DEFINED
#endif //PRECISION == 1
#if PRECISION == 2
#ifndef FLOAT_TYPEDEF_DEFINED
typedef double Real;
#endif //FLOAT_TYPEDEF_DEFINED
#endif //PRECISION == 2

#define MAXLEN 100
#define TINY_NUMBER 1.0e-20
#define PI 3.141592653589793
#define MP 1.672622e-24 // mass of proton, grams
#define KB 1.380658e-16 // boltzmann constant, cgs
//#define GN 6.67259e-8 // gravitational constant, cgs
#define GN 4.49451e-18 // gravitational constant, kpc^3 / M_sun / kyr^2

//#define TIME_UNIT 3.15569e10 // 1 kyr in s
//#define LENGTH_UNIT 3.08567758e21 // 1 kpc in cm
//#define MASS_UNIT 1.98855e33 // 1 solar mass in grams
#define TIME_UNIT 60300.114856343294
#define LENGTH_UNIT 310994529228.64191
#define MASS_UNIT 2.6020994527238984e+18
#define DENSITY_UNIT (MASS_UNIT/(LENGTH_UNIT*LENGTH_UNIT*LENGTH_UNIT))
#define VELOCITY_UNIT (LENGTH_UNIT/TIME_UNIT)
#define ENERGY_UNIT (DENSITY_UNIT*VELOCITY_UNIT*VELOCITY_UNIT)
#define PRESSURE_UNIT (DENSITY_UNIT*VELOCITY_UNIT*VELOCITY_UNIT)
#define SP_ENERGY_UNIT (VELOCITY_UNIT*VELOCITY_UNIT)

#ifdef SCALAR
#define NSCALARS 1
#endif

#define SIGN(a) ( ((a) < 0.) ? -1. : 1. )

/* Global variables */
extern Real gama; // Ratio of specific heats
extern Real C_cfl; // CFL number (0 - 0.5)
extern Real t_comm;
extern Real t_other;

#ifdef COOLING_CPU
extern gsl_interp_accel *acc;
extern gsl_interp_accel *xacc;
extern gsl_interp_accel *yacc;
extern gsl_spline *highT_C_spline;
extern gsl_spline2d *lowT_C_spline;
extern gsl_spline2d *lowT_H_spline;
#endif
#ifdef COOLING_GPU
extern float *cooling_table;
extern float *heating_table;
#endif

/*! \fn void Set_Gammas(Real gamma_in)
 *  \brief Set gamma values for Riemann solver. */
extern void Set_Gammas(Real gamma_in);

/*! \fn double get_time(void)
 *  \brief Returns the current clock time. */
extern double get_time(void);

/*! \fn int sgn
 *  \brief Mathematical sign function. Returns sign of x. */
extern int sgn(Real x);

#ifndef CUDA
/*! \fn Real calc_eta(Real cW[], Real gamma)
 *  \brief Calculate the eta value for the H correction. */
extern Real calc_eta(Real cW[], Real gamma);
#endif

struct parameters
{
  int nx;
  int ny;
  int nz;
  double tout;
  double outstep;
  Real gamma;
  char init[MAXLEN];
  int nfile;
  int nfull;
  Real xmin;
  Real ymin;
  Real zmin;
  Real xlen;
  Real ylen;
  Real zlen;
  int xl_bcnd;
  int xu_bcnd;
  int yl_bcnd;
  int yu_bcnd;
  int zl_bcnd;
  int zu_bcnd;
#ifdef   MPI_CHOLLA
  int xlg_bcnd;
  int xug_bcnd;
  int ylg_bcnd;
  int yug_bcnd;
  int zlg_bcnd;
  int zug_bcnd;
#endif /*MPI_CHOLLA*/
  char custom_bcnd[MAXLEN];
  char outdir[MAXLEN];
  Real rho;
  Real vx;
  Real vy;
  Real vz;
  Real P;
  Real A;
  Real rho_l;
  Real v_l;
  Real P_l;
  Real rho_r;
  Real v_r;
  Real P_r;
  Real diaph;
#ifdef ROTATED_PROJECTION
  int nxr;
  int nzr;
  Real delta;
  Real theta;
  Real phi;
  Real Lx;
  Real Lz;
  int n_delta;
  Real ddelta_dt;
  int flag_delta;
#endif /*ROTATED_PROJECTION*/
  // added by TRW
  int ncycle_out; // controls freq of output to screen
  Real my_reals[99]; // an array to hold an additional 99 parameters

};

/*! \fn void parse_params(char *param_file, struct parameters * parms);
 *  \brief Reads the parameters in the given file into a structure. */
extern void parse_params (char *param_file, struct parameters * parms);

#endif //GLOBAL_H


/* ============================================================ *
 * halomodel.h							*
 * Martin Kilbinger 2006-2009					*
 * ============================================================ */

#ifndef __HALOMODEL_H
#define __HALOMODEL_H

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <assert.h>
#include <string.h>
#include <fftw3.h>

#include <gsl/gsl_sf_erf.h>

#include "io.h"
#include "errorlist.h"
#include "config.h"
#include "maths.h"

#include "cosmo.h"
#include "nofz.h"

#define hm_base     -1900
#define hm_hodtype   hm_base + 1
#define hm_Mmin      hm_base + 2
#define hm_pofk      hm_base + 3
#define hm_nfw       hm_base + 4
#define hm_par       hm_base + 5
#define hm_overflow  hm_base + 6
#define hm_io        hm_base + 7
#define hm_zbin      hm_base + 8
#define hm_alpha     hm_base + 9
#define hm_negative  hm_base + 10
#define hm_zmean_2h  hm_base + 11
#define hm_halo_bias hm_base + 12
#define hm_undef     hm_base + 13
#define hm_gsl_int   hm_base + 14

/* Ranges of interpolation tables */
#define k_max_HOD     3336.0

/* Present critical density [M_sol h^2 / Mpc^3] */
#define rho_c0  2.7754e11

/* Mass limits for integration over mass functions */
#define logMmin (3.0*log(10.0))
#define logMmax (16.0*log(10.0))

/* Number of steps for scale-factor-integration (redshift) */
#define Na_hm 20

/* Bit-coded power spectrum types */
typedef enum {pofk_undef=-1, pl=1, pnl=2, p1hdm=4, p2hdm=8, pthdm=16, p1hg=32, p2hg=64, pthg=128,
	      p1hgcs=256, p1hgss=512, pstellar=1024} pofk_t;

/* Halo mass function type */
typedef enum {ps, st, st2, j01} massfct_t;
#define smassfct_t(i) ( \
  i==ps  ? "ps" : \
  i==st  ? "st" : \
  i==st2 ? "st2" : \
  i==j01 ? "j01" : \
  "")
#define Nmassfct_t 4

/* Halo bias type */
typedef enum {halo_bias_sc, halo_bias_tinker05, halo_bias_tinker10} halo_bias_t;
#define shalo_bias_t(i) ( \
   i==halo_bias_sc       ? "halo_bias_sc" : \
   i==halo_bias_tinker05 ? "halo_bias_tinker05" : \
   i==halo_bias_tinker10 ? "halo_bias_tinker10" : \
   "")
#define Nhalo_bias_t 3

/* HOD (Halo occupation distribution) type */
#define Nhod_t 5
typedef enum {hod_none, hamana04, berwein02, berwein02_hexcl, leauthaud11} hod_t;
#define shod_t(i) (		\
 i==hod_none  ? "hod_none" :	\
 i==hamana04  ? "hamana04" :	\
 i==berwein02 ? "berwein02" :	\
 i==berwein02_hexcl ? "berwein02_hexcl" :\
 i==leauthaud11 ? "leauthaud11" :	\
 "")

/* ---------------------------------------------------------------- *
 * Global variables and functions                                   *
 * ---------------------------------------------------------------- */

double FFTLog_TMP;

typedef struct FFTLog_complex
{
  double re;
  double im;
  double amp;
  double arg;
}  FFTLog_complex;

typedef struct {
  int N;
  fftw_plan p_forward;
  fftw_plan p_backward;
  fftw_complex *an;
  fftw_complex *ak;
  fftw_complex *cm;
  fftw_complex *um;
  fftw_complex *cmum;
  double min;
  double max;
  double q;
  double mu;
  double kr;
} FFTLog_config;

typedef struct {

  cosmo *cosmo;
  redshift_t *redshift;
  double zmin, zmax;

  /* Dark matter halo profile                                                      */
  double c0;			/* concentration parameter                         */
  double alpha_NFW;		/* density slope                                   */
  double beta_NFW;		/* concentration slope as fct of mass              */
  massfct_t massfct;            /* halo mass function				   */
  halo_bias_t halo_bias;        /* Halo bias                                       */

  /* Mass function parameters (Sheth&Torman). Do not set manually, they are set    *
   * in set_massfct() according to enum massfct.				   */
  double nmz_a;                 /* Called q in CS02                                */
  double nmz_p;                 /* a=1, p=1/2 is Press-Schechter mass fct.         */

  /* HOD (halo occupation distribution) parameters				   */
  hod_t hod;                    /* HOD type                                        */
  double M1, M0, sigma_log_M;
  double M_min;
  double alpha;
  double pi_max;
  double eta;                   /* central galaxy proportion                       */

  /* galaxy-galaxy lensing and wp(rp) */
  double log10Mhalo;
  double coord_phys;

  /* For Leauthaud11 model                                                         */
  double beta,delta,gamma,Mstar0;
  double beta_sat,B_sat,beta_cut,B_cut;
  double x;                     /* any parameter to propagate if needed            */
  double Mstellar_min, Mstellar_max;
  double fcen1, fcen2;

  /* Precomputed stuff */
  double A;			/* Mass function normalisation                     */
  double Mstar;                 /* M_*(a=1.0)					   */
  interTable2D *Pthdm;
  interTable *xir;
  interTable *xi_dm;
  interTable2D *rhohat;
  splineTable* sigRsqr;
  double a_xir;

  /* FFTLOG flag - OBSOLETE */
  int FFTLog;

} cosmo_hm;

typedef struct {

  cosmo    *cosmo;
  cosmo_hm *model;
  double   a, r, k, ng, ngp, eps, c;
  double  logMlim, bias_fac, Mh, Mstellar, Mstellar_min, Mstellar_max;
  double  M, r_vir, *kk;
  error    **err;

  double   logrmin, logrmax, rp, xi;
  gsl_interp_accel *acc;
  gsl_spline *spline;

  int i, j, type, asymptotic, logintegrate;

  double   (*bias_func)(double, void *);
} cosmo_hm_params;

typedef struct gsl_int_params
{
  void *params;
  funcwithpars func;
  error **err;

} gsl_int_params;

typedef struct {
  double *z;
  double *fac;
  double *ypn; /* for spline interpolation */
  double zm;   /* average weighted redshift*/
  int nbins;
} nz_t;

cosmo_hm* init_parameters_hm(double OMEGAM, double OMEGADE, double W0_DE, double W1_DE,
			     double *W_POLY_DE, int N_POLY_DE,
			     double H100, double OMEGAB, double OMEGANUMASS,
			     double NEFFNUMASS, double NORM, double NSPEC,
			     int Nzbin, const int *Nnz, const nofz_t *nofz, double *par_nz,
			     double zmin, double zmax,
			     nonlinear_t NONLINEAR, transfer_t TRANSFER, growth_t GROWTH,
			     de_param_t DEPARAM, norm_t normmode,
			     double C0, double ALPHANFW, double BETANFW, massfct_t MASSFCT, halo_bias_t HALO_BIAS,
			     double M_min, double M1, double M0, double sigma_log_M, double alpha,
			     double Mstar0, double beta, double delta, double gamma, double B_cut, double B_sat,
			     double beta_cut, double beta_sat, double Mstellar_min, double Mstellar_max, double eta,
			     double fcen1, double fcen2,
			     hod_t HOD, double pi_max, error **err);

cosmo_hm* copy_parameters_hm_only(cosmo_hm* source, error **err);
cosmo_hm *copy_parameters_hm(cosmo_hm *source, error **err);
void read_cosmological_parameters_hm(cosmo_hm **model, FILE *F, error **err);
cosmo_hm *set_cosmological_parameters_to_default_hm(error **err);
void free_parameters_hm(cosmo_hm** model);

void set_massfct(massfct_t massfct, double *nmz_a, double *nmz_p, error **err);
void dump_param_only_hm(cosmo_hm* model, FILE *F);
void dump_param_hm(cosmo_hm* model, FILE *F, error **err);

double sm2_rtbis(double (*func)(double, void *, error **), double x1, double x2,
		 double xacc, void *param, error **err);

/* From nrcomplex.h,c */
#ifndef _DCOMPLEX_DECLARE_T_
typedef struct DCOMPLEX {double r,i;} dcomplex;
#define _DCOMPLEX_DECLARE_T_
#endif /* _DCOMPLEX_DECLARE_T_ */
dcomplex Complex(double re, double im);
dcomplex Cadd(dcomplex a, dcomplex b);
dcomplex Cmul(dcomplex a, dcomplex b);
dcomplex Cdiv(dcomplex a, dcomplex b);
dcomplex RCmul(double x, dcomplex a);
void sm2_cisi(double x, double *ci, double *si, error **err);

double delta_c(cosmo *model, double a, error **err);
double bis_Mstar(double logM, void *param, error **err);
double bis_Mstar_a(double logM, void *param, error **err);
double Mstar(cosmo_hm *model, error **err);
double Mstar_a(cosmo_hm *model, double a, error **err);
double concentration(cosmo_hm *model, double Mh, double a, error **err);
double Delta_vir(cosmo_hm *model, double a);
double dsigma_R_sqr_dR(cosmo_hm *model, double R, error **err);
double nufnu(cosmo_hm *model, double nu, int asymptotic, error **err);
double nufnu_j01(double x);
double sigma_R_sqr(cosmo_hm *model, double R, error **err);
double sigmasqr_M(cosmo_hm *model, double M, error **err);

double dsigma_m1_dlnM(cosmo_hm *model, double M, error **err);
double dnu_dlnM(cosmo_hm *model, double M, double a, error **err);
double dn_dlnM_lnM(double logM, void *intpar, error **err);
double dn_dlnM_uf(double M, cosmo_hm *model, double a, error **err);
double dn_dlnM(double M, void *intpar, error **err);

double r_vir(cosmo_hm *model, double M, double a, error **err);
double M_vir(cosmo_hm *model, double r_vir, double a, error **err);
double Delta_h(cosmo_hm *model, double a, error **err);
double rho_crit(cosmo_hm *model, double a, error **err);
double rho_crit_halo(cosmo_hm *model, double a, error **err);
double Omega_m_halo(cosmo_hm *model, double a, error **err);
double rho_halo(cosmo_hm *model, double r, double a, double Mh, double c, error **err);

double DeltaSigma_WB2000(cosmo_hm *model, double r, const double a, const double M,  double c,  double Delta, error **err);
double g_inf(double x, error **err);
double g_sup(double x, error **err);

double int_for_rhohat(double, void *, error **err);
double rhohat_halo(cosmo_hm *model, double k, double M, double a, double c, error **err);

double halo_bias(cosmo_hm *model, double M, double a, int k, error **err);
double bias(cosmo_hm *model, double M, double a, int k, error **err);
double bias_tinker(cosmo_hm *model, double M, double a, error **err);
double bias_tinker10(cosmo_hm *model, double M, double a, error **err);
double int_for_bias_norm(double logM, void *intpar, error **err);
double bias_norm(cosmo_hm *model, double a, error **err);

double int_for_M_ij(double, void *, error **);
double M_ij(cosmo_hm *model, int i, int j, double a, const double *k, error **err);
double P1h_dm(cosmo_hm *model, double a, double k, error **err);
double P2h_dm(cosmo_hm *model, double a, double k, error **err);

double xi_dm_NL_OBSOLETE(cosmo_hm *model, double a, double r, error **err);  // non-linear DM xi
double int_for_xi_dm_NL_OBSOLETE(double k, void *intpar,  error **err);  // non-linear DM xi

#define CHANGE(fct) int change_##fct(cosmo_hm*, cosmo_hm*)

/* ---------------------------------------------------------------- *
 * Utils                                                            *
 * ---------------------------------------------------------------- */

double int_gsl(funcwithpars func,void *params, double a, double b, double eps, error **err);
double integrand_gsl(double x,void *p);

CHANGE(massfct);
CHANGE(massfct_params);
CHANGE(halo_bias);
CHANGE(sigma_R_sqr);
CHANGE(Mstar);
CHANGE(rhohat_halo);
CHANGE(Pth);

#undef CHANGE

#endif


/**
 * Simple and fast kmeans-implementation making use of multithreading.
 *
 * Author: Anders Bennehag
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Anders Bennehag
 *
 */
#ifndef FASTKMEANS_H_
#define FASTKMEANS_H_

#include <gsl/gsl_matrix.h>

int fkm_kmeans(const gsl_matrix* points, gsl_matrix* clusters,
               size_t max_iter, int num_threads);

gsl_matrix* fkm_matrix_load(FILE* fout);
int fkm_matrix_save(FILE* fout, gsl_matrix* mat);

#define WHERESTR  "[%s:%d]: "
#define WHEREARG  __FILE__, __LINE__
#define DEBUGPRINT2(...)       fprintf(stderr, __VA_ARGS__)

#define FKM_LOGFMT(_fmt, ...)  DEBUGPRINT2(WHERESTR _fmt "\n", WHEREARG, __VA_ARGS__)
#define FKM_LOG(_s) DEBUGPRINT2(WHERESTR "%s\n", WHEREARG, _s)
#define FKM_LOGTYPE(_tp, _s)  DEBUGPRINT2(WHERESTR "%s: %s" "\n", WHEREARG, _tp, _s)
#define FKM_LOGTYPEFMT(_tp, _fmt, ...)  DEBUGPRINT2(WHERESTR "%s: " _fmt "\n", WHEREARG, _tp, __VA_ARGS__)

#if (ISDEBUG + 0)
    #define FKM_DEBUGFMT(_fmt, ...)  FKM_LOGTYPEFMT("DEBUG", _fmt, __VA_ARGS__)
    #define FKM_DEBUG(_s)  FKM_LOGTYPE("DEBUG", _s)
#else
    #define FKM_DEBUGFMT(...)
    #define FKM_DEBUG(...)
#endif
#define FKM_INFOFMT(_fmt, ...)   FKM_LOGTYPEFMT("INFO", _fmt, __VA_ARGS__)
#define FKM_WARNFMT(_fmt, ...)   FKM_LOGTYPEFMT("WARN", _fmt, __VA_ARGS__)
#define FKM_ERRORFMT(_fmt, ...)  FKM_LOGTYPEFMT("ERROR", _fmt, __VA_ARGS__)

#define FKM_INFO(_s)   FKM_LOGTYPE("INFO", _s)
#define FKM_WARN(_s)   FKM_LOGTYPE("WARN", _s)
#define FKM_ERROR(_s)  FKM_LOGTYPE("ERROR", _s)

#endif


#include "stdlib.h"
#include "stdio.h"
#include "/home/lillian/work/install_fpdebug/valgrind-3.7.0/fpdebug/fpdebug.h"
#include <gsl/gsl_sf.h>
int main(int argc, const char * argv[]) {
unsigned long int hexdouble;
int a;
a = atoi(argv[1]);
int b;
b = atoi(argv[2]);
int c;
c = atoi(argv[3]);
int d;
d = atoi(argv[4]);
int e;
e = atoi(argv[5]);
int f;
f = atoi(argv[6]);
double result = gsl_sf_coupling_6j(a, b, c, d, e, f);
//printf("%.15f\n", result);
VALGRIND_PRINT_ERROR("result", &result);
return 0;
}


/**
 *
 * @file core_claset.c
 *
 *  PLASMA core_blas kernel
 *  PLASMA is a software package provided by Univ. of Tennessee,
 *  Univ. of California Berkeley and Univ. of Colorado Denver
 *
 * @version 2.6.0
 * @author Hatem Ltaief
 * @date 2010-11-15
 * @generated c Tue Jan  7 11:44:48 2014
 *
 **/
#include <lapacke.h>
#include "common.h"

/***************************************************************************//**
 *
 * @ingroup CORE_PLASMA_Complex32_t
 *
 *  CORE_claset - Sets the elements of the matrix A on the diagonal
 *  to beta and on the off-diagonals to alpha
 *
 *******************************************************************************
 *
 * @param[in] uplo
 *          Specifies which elements of the matrix are to be set
 *          = PlasmaUpper: Upper part of A is set;
 *          = PlasmaLower: Lower part of A is set;
 *          = PlasmaUpperLower: ALL elements of A are set.
 *
 * @param[in] M
 *          The number of rows of the matrix A.  M >= 0.
 *
 * @param[in] N
 *         The number of columns of the matrix A.  N >= 0.
 *
 * @param[in] alpha
 *         The constant to which the off-diagonal elements are to be set.
 *
 * @param[in] beta
 *         The constant to which the diagonal elements are to be set.
 *
 * @param[in,out] A
 *         On entry, the M-by-N tile A.
 *         On exit, A has been set accordingly.
 *
 * @param[in] LDA
 *         The leading dimension of the array A.  LDA >= max(1,M).
 *
 **/
#if defined(PLASMA_HAVE_WEAK)
#pragma weak CORE_claset = PCORE_claset
#define CORE_claset PCORE_claset
#endif
void CORE_claset(PLASMA_enum uplo, int M, int N,
                 PLASMA_Complex32_t alpha, PLASMA_Complex32_t beta,
                 PLASMA_Complex32_t *A, int LDA)
{
    LAPACKE_claset_work(
        LAPACK_COL_MAJOR,
        lapack_const(uplo),
        M, N, alpha, beta, A, LDA);
}


/**
 *
 * @file dsungesv.c
 *
 *  PLASMA computational routines
 *  PLASMA is a software package provided by Univ. of Tennessee,
 *  Univ. of California Berkeley and Univ. of Colorado Denver
 *
 * @version 2.6.0
 * @author Emmanuel Agullo
 * @date 2010-11-15
 * @generated ds Tue Jan  7 11:45:09 2014
 *
 **/
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <lapacke.h>
#include "common.h"

#define PLASMA_dlag2s(_descA, _descSB)                \
  plasma_parallel_call_4(plasma_pdlag2s,              \
                         PLASMA_desc,      (_descA),  \
                         PLASMA_desc,      (_descSB), \
                         PLASMA_sequence*, sequence,  \
                         PLASMA_request*,  request)

#define PLASMA_slag2d(_descSA, _descB)                \
  plasma_parallel_call_4(plasma_pslag2d,              \
                         PLASMA_desc,      (_descSA), \
                         PLASMA_desc,      (_descB),  \
                         PLASMA_sequence*, sequence,  \
                         PLASMA_request*,  request)

#define PLASMA_dlange(_norm, _descA, _result, _work)   \
  _result = 0;                                         \
  plasma_parallel_call_6(plasma_pdlange,               \
                         PLASMA_enum,      (_norm),    \
                         PLASMA_desc,      (_descA),   \
                         double*,          (_work),    \
                         double*,          &(_result), \
                         PLASMA_sequence*, sequence,   \
                         PLASMA_request*,  request);

#define PLASMA_dlacpy(_descA, _descB)                        \
  plasma_parallel_call_5(plasma_pdlacpy,                     \
                         PLASMA_enum,      PlasmaUpperLower, \
                         PLASMA_desc,      (_descA),         \
                         PLASMA_desc,      (_descB),         \
                         PLASMA_sequence*, sequence,         \
                         PLASMA_request*,  request)

#define PLASMA_dgeadd(_alpha, _descA, _descB)                 \
  plasma_parallel_call_5(plasma_pdgeadd,                      \
                         double, (_alpha),        \
                         PLASMA_desc,        (_descA),        \
                         PLASMA_desc,        (_descB),        \
                         PLASMA_sequence*,   sequence,        \
                         PLASMA_request*,    request)

/***************************************************************************//**
 *
 * @ingroup double
 *
 *  PLASMA_dsungesv - Solves overdetermined or underdetermined linear systems involving an M-by-N
 *  matrix A using the QR or the LQ factorization of A.  It is assumed that A has full rank.
 *  The following options are provided:
 *
 *  # trans = PlasmaNoTrans and M >= N: find the least squares solution of an overdetermined
 *    system, i.e., solve the least squares problem: minimize || B - A*X ||.
 *
 *  # trans = PlasmaNoTrans and M < N:  find the minimum norm solution of an underdetermined
 *    system A * X = B.
 *
 *  Several right hand side vectors B and solution vectors X can be handled in a single call;
 *  they are stored as the columns of the M-by-NRHS right hand side matrix B and the N-by-NRHS
 *  solution matrix X.
 *
 *  PLASMA_dsungesv first attempts to factorize the matrix in COMPLEX and use this
 *  factorization within an iterative refinement procedure to produce a
 *  solution with COMPLEX*16 normwise backward error quality (see below).
 *  If the approach fails the method switches to a COMPLEX*16
 *  factorization and solve.
 *
 *  The iterative refinement is not going to be a winning strategy if
 *  the ratio COMPLEX performance over COMPLEX*16 performance is too
 *  small. A reasonable strategy should take the number of right-hand
 *  sides and the size of the matrix into account. This might be done
 *  with a call to ILAENV in the future. Up to now, we always try
 *  iterative refinement.
 *
 *  The iterative refinement process is stopped if ITER > ITERMAX or
 *  for all the RHS we have: RNRM < N*XNRM*ANRM*EPS*BWDMAX
 *  where:
 *
 *  - ITER is the number of the current iteration in the iterative refinement process
 *  - RNRM is the infinity-norm of the residual
 *  - XNRM is the infinity-norm of the solution
 *  - ANRM is the infinity-operator-norm of the matrix A
 *  - EPS is the machine epsilon returned by DLAMCH('Epsilon').
 *
 *  Actually, in its current state (PLASMA 2.1.0), the test is slightly relaxed.
 *
 *  The values ITERMAX and BWDMAX are fixed to 30 and 1.0D+00 respectively.
 *
 *  We follow Bjorck's algorithm proposed in "Iterative Refinement of Linear
 *  Least Squares solutions I", BIT, 7:257-278, 1967.4
 *
 *******************************************************************************
 *
 * @param[in] trans
 *          Intended usage:
 *          = PlasmaNoTrans:   the linear system involves A;
 *          = PlasmaTrans: the linear system involves A**H.
 *          Currently only PlasmaNoTrans is supported.
 *
 * @param[in] N
 *          The number of columns of the matrix A. N >= 0.
 *
 * @param[in] NRHS
 *          The number of right hand sides, i.e., the number of columns of the matrices B and X.
 *          NRHS >= 0.
 *
 * @param[in] A
  *          The M-by-N matrix A. This matrix is not modified.
 *
 * @param[in] LDA
 *          The leading dimension of the array A. LDA >= max(1,M).
 *
 * @param[in] B
 *          The M-by-NRHS matrix B of right hand side vectors, stored columnwise. Not modified.
 *
 * @param[in] LDB
 *          The leading dimension of the array B. LDB >= MAX(1,M,N).
 *
 * @param[out] X
 *          If return value = 0, the solution vectors, stored columnwise.
 *          if M >= N, rows 1 to N of B contain the least squares solution vectors; the residual
 *          sum of squares for the solution in each column is given by the sum of squares of the
 *          modulus of elements N+1 to M in that column;
 *          if M < N, rows 1 to N of B contain the minimum norm solution vectors;
 *
 * @param[in] LDX
 *          The leading dimension of the array B. LDB >= MAX(1,M,N).
 *
 * @param[out] ITER
 *          The number of the current iteration in the iterative refinement process
 *
 *******************************************************************************
 *
 * @return
 *          \retval PLASMA_SUCCESS successful exit
 *          \retval <0 if -i, the i-th argument had an illegal value
 *
 *******************************************************************************
 *
 * @sa PLASMA_dsungesv_Tile
 * @sa PLASMA_dsungesv_Tile_Async
 * @sa PLASMA_dsungesv
 * @sa PLASMA_dgels
 *
 ******************************************************************************/
int PLASMA_dsungesv(PLASMA_enum trans, int N, int NRHS,
                    double *A, int LDA,
                    double *B, int LDB,
                    double *X, int LDX, int *ITER)
{
    int NB;
    int status;
    PLASMA_desc  descA;
    PLASMA_desc  descB;
    PLASMA_desc *descT;
    PLASMA_desc  descX;
    plasma_context_t *plasma;
    PLASMA_sequence *sequence = NULL;
    PLASMA_request request = PLASMA_REQUEST_INITIALIZER;

    plasma = plasma_context_self();
    if (plasma == NULL) {
        plasma_fatal_error("PLASMA_dsungesv", "PLASMA not initialized");
        return PLASMA_ERR_NOT_INITIALIZED;
    }

    *ITER = 0;

    /* Check input arguments */
    if (trans != PlasmaNoTrans   &&
        trans != PlasmaTrans &&
        trans != PlasmaTrans )
    {
        plasma_error("PLASMA_dsungesv", "illegal value of trans");
        return -1;
    }
    if (trans != PlasmaNoTrans) {
        plasma_error("PLASMA_dsungesv", "only PlasmaNoTrans supported");
        return PLASMA_ERR_NOT_SUPPORTED;
    }
    if (N < 0) {
        plasma_error("PLASMA_dsungesv", "illegal value of N");
        return -2;
    }
    if (NRHS < 0) {
        plasma_error("PLASMA_dsungesv", "illegal value of NRHS");
        return -3;
    }
    if (LDA < max(1, N)) {
        plasma_error("PLASMA_dsungesv", "illegal value of LDA");
        return -5;
    }
    if (LDB < max(1, N)) {
        plasma_error("PLASMA_dsungesv", "illegal value of LDB");
        return -7;
    }
    if (LDX < max(1, N)) {
        plasma_error("PLASMA_dsungesv", "illegal value of LDX");
        return -9;
    }

    /* Quick return */
    if ( N == 0 )
        return PLASMA_SUCCESS;

    /* Tune NB & IB depending on M, N & NRHS; Set NBNB */
    status = plasma_tune(PLASMA_FUNC_DSGELS, N, N, NRHS);
    if (status != PLASMA_SUCCESS) {
        plasma_error("PLASMA_dsungesv", "plasma_tune() failed");
        return status;
    }

    NB = PLASMA_NB;

    plasma_sequence_create(plasma, &sequence);

    /* DOUBLE PRECISION INITIALIZATION */
    if ( PLASMA_TRANSLATION == PLASMA_OUTOFPLACE ) {
        plasma_dooplap2tile( descA, A, NB, NB, LDA, N,    0, 0, N, N,    sequence, &request,
                             plasma_desc_mat_free(&(descA)) );
        plasma_dooplap2tile( descB, B, NB, NB, LDB, NRHS, 0, 0, N, NRHS, sequence, &request,
                             plasma_desc_mat_free(&(descA)); plasma_desc_mat_free(&(descB)) );
        plasma_ddesc_alloc(  descX, NB, NB, N, NRHS, 0, 0, N, NRHS, plasma_desc_mat_free(&(descA)); plasma_desc_mat_free(&(descB)); plasma_desc_mat_free(&(descX)) );
    } else {
        plasma_diplap2tile( descA, A, NB, NB, LDA, N,    0, 0, N, N,
                            sequence, &request);
        plasma_diplap2tile( descB, B, NB, NB, LDB, NRHS, 0, 0, N, NRHS,
                            sequence, &request);

        descX = plasma_desc_init(
            PlasmaRealDouble, NB, NB, (NB*NB),
            LDX, NRHS, 0, 0, N, NRHS);
        descX.mat = X;
    }

    /* Allocate workspace */
    PLASMA_Alloc_Workspace_dgels_Tile(N, N, &descT);

    /* Call the native interface */
    status = PLASMA_dsungesv_Tile_Async(PlasmaNoTrans, &descA, descT, &descB, &descX, ITER,
                                        sequence, &request);

    if (status == PLASMA_SUCCESS) {
        if ( PLASMA_TRANSLATION == PLASMA_OUTOFPLACE ) {
            plasma_dooptile2lap( descX, X, NB, NB, LDX, NRHS,  sequence, &request);
            plasma_dynamic_sync();
            plasma_desc_mat_free(&descA);
            plasma_desc_mat_free(&descB);
            plasma_desc_mat_free(&descX);
        } else {
            plasma_diptile2lap( descA, A, NB, NB, LDA, N,     sequence, &request);
            plasma_diptile2lap( descB, B, NB, NB, LDB, NRHS,  sequence, &request);
            plasma_diptile2lap( descX, X, NB, NB, LDX, NRHS,  sequence, &request);
            plasma_dynamic_sync();
        }
    }

    PLASMA_Dealloc_Handle_Tile(&descT);
    plasma_sequence_destroy(plasma, sequence);
    return status;
}

/***************************************************************************//**
 *
 * @ingroup double_Tile
 *
 *  PLASMA_dsungesv_Tile - Solves symmetric linear system of equations using the tile QR
 *  or the tile LQ factorization and mixed-precision iterative refinement.
 *  Tile equivalent of PLASMA_dsungesv().
 *  Operates on matrices stored by tiles.
 *  All matrices are passed through descriptors.
 *  All dimensions are taken from the descriptors.
 *
 *******************************************************************************
 *
 * @param[in] trans
 *          Intended usage:
 *          = PlasmaNoTrans:   the linear system involves A;
 *          = PlasmaTrans: the linear system involves A**H.
 *          Currently only PlasmaNoTrans is supported.
 *
 * @param[in,out] A
 *          - If the iterative refinement converged, A is not modified;
 *          - otherwise, it fell back to double precision solution, and
 *          on exit the M-by-N matrix A contains:
 *          if M >= N, A is overwritten by details of its QR factorization as returned by
 *                     PLASMA_dgeqrf;
 *          if M < N, A is overwritten by details of its LQ factorization as returned by
 *                      PLASMA_dgelqf.
 *
 * @param[out] T
 *          On exit:
 *          - if the iterative refinement converged, T is not modified;
 *          - otherwise, it fell back to double precision solution,
 *          and then T is an auxiliary factorization data.
 *
 * @param[in,out] B
 *          On entry, the M-by-NRHS matrix B of right hand side vectors, stored columnwise;
 * @param[in] B
 *          The N-by-NRHS matrix of right hand side matrix B.
 *
 * @param[out] X
 *          If return value = 0, X is the solution vectors, stored columnwise:
 *          if M >= N, rows 1 to N of X contain the least squares solution vectors; the residual
 *          sum of squares for the solution in each column is given by the sum of squares of the
 *          modulus of elements N+1 to M in that column;
 *          if M < N, rows 1 to N of X contain the minimum norm solution vectors;
 *
 * @param[out] ITER
 *          The number of the current iteration in the iterative refinement process
 *
 *******************************************************************************
 *
 * @return
 *          \retval PLASMA_SUCCESS successful exit
 *
 *******************************************************************************
 *
 * @sa PLASMA_dsungesv
 * @sa PLASMA_dsungesv_Tile_Async
 * @sa PLASMA_dsungesv_Tile
 * @sa PLASMA_dgels_Tile
 *
 ******************************************************************************/
int PLASMA_dsungesv_Tile(PLASMA_enum trans, PLASMA_desc *A, PLASMA_desc *T,
                         PLASMA_desc *B, PLASMA_desc *X, int *ITER)
{
    plasma_context_t *plasma;
    PLASMA_sequence *sequence = NULL;
    PLASMA_request request = PLASMA_REQUEST_INITIALIZER;
    int status;

    plasma = plasma_context_self();
    if (plasma == NULL) {
        plasma_fatal_error("PLASMA_dsungesv_Tile", "PLASMA not initialized");
        return PLASMA_ERR_NOT_INITIALIZED;
    }
    plasma_sequence_create(plasma, &sequence);
    status = PLASMA_dsungesv_Tile_Async(trans, A, T, B, X, ITER, sequence, &request);
    if (status != PLASMA_SUCCESS)
        return status;
    plasma_dynamic_sync();
    status = sequence->status;
    plasma_sequence_destroy(plasma, sequence);
    return status;
}

/***************************************************************************//**
 *
 * @ingroup double_Tile_Async
 *
 *  PLASMA_dsungesv_Tile_Async - Solves symmetric linear system of equations using
 *  the tile QR or the tile LQ factorization and mixed-precision iterative refinement.
 *  Non-blocking equivalent of PLASMA_dsungesv_Tile().
 *  May return before the computation is finished.
 *  Allows for pipelining of operations at runtime.
 *
 *******************************************************************************
 *
 * @param[in] sequence
 *          Identifies the sequence of function calls that this call belongs to
 *          (for completion checks and exception handling purposes).
 *
 * @param[out] request
 *          Identifies this function call (for exception handling purposes).
 *
 *******************************************************************************
 *
 * @sa PLASMA_dsungesv
 * @sa PLASMA_dsungesv_Tile
 * @sa PLASMA_dsungesv_Tile_Async
 * @sa PLASMA_dgels_Tile_Async
 *
 ******************************************************************************/
int PLASMA_dsungesv_Tile_Async(PLASMA_enum trans, PLASMA_desc *A, PLASMA_desc *T,
                               PLASMA_desc *B, PLASMA_desc *X, int *ITER,
                               PLASMA_sequence *sequence, PLASMA_request *request)
{
    int N, NB, IB;
    PLASMA_desc descA;
    PLASMA_desc descT;
    PLASMA_desc descB;
    PLASMA_desc descX;
    PLASMA_desc descR, descSA, descST, descSX;
    plasma_context_t *plasma;
    double *work;

    const int    itermax = 30;
    const double bwdmax  = 1.0;
    const double negone = -1.0;
    const double one = 1.0;
    int iiter;
    double Anorm, cte, eps, Rnorm, Xnorm;
    *ITER=0;

    plasma = plasma_context_self();
    if (plasma == NULL) {
        plasma_fatal_error("PLASMA_dsungesv_Tile", "PLASMA not initialized");
        return PLASMA_ERR_NOT_INITIALIZED;
    }
    if (sequence == NULL) {
        plasma_fatal_error("PLASMA_dsungesv_Tile", "NULL sequence");
        return PLASMA_ERR_UNALLOCATED;
    }
    if (request == NULL) {
        plasma_fatal_error("PLASMA_dsungesv_Tile", "NULL request");
        return PLASMA_ERR_UNALLOCATED;
    }
    /* Check sequence status */
    if (sequence->status == PLASMA_SUCCESS)
        request->status = PLASMA_SUCCESS;
    else
        return plasma_request_fail(sequence, request, PLASMA_ERR_SEQUENCE_FLUSHED);
    /* Check descriptors for correctness */
    if (plasma_desc_check(A) != PLASMA_SUCCESS) {
        plasma_error("PLASMA_dsungesv_Tile", "invalid first descriptor");
        return PLASMA_ERR_ILLEGAL_VALUE;
    } else {
        descA = *A;
    }
    if (plasma_desc_check(T) != PLASMA_SUCCESS) {
        plasma_error("PLASMA_dsungesv_Tile", "invalid second descriptor");
        return PLASMA_ERR_ILLEGAL_VALUE;
    } else {
        descT = *T;
    }
    if (plasma_desc_check(B) != PLASMA_SUCCESS) {
        plasma_error("PLASMA_dsungesv_Tile", "invalid third descriptor");
        return PLASMA_ERR_ILLEGAL_VALUE;
    } else {
        descB = *B;
    }
    if (plasma_desc_check(X) != PLASMA_SUCCESS) {
        plasma_error("PLASMA_dsungesv_Tile", "invalid fourth descriptor");
        return PLASMA_ERR_ILLEGAL_VALUE;
    } else {
        descX = *X;
    }
    /* Check input arguments */
    if ( (descA.nb != descA.mb) || (descB.nb != descB.mb) || (descX.nb != descX.mb) ||
         (descA.mb != descB.mb) || (descB.mb != descX.mb) ) {
        plasma_error("PLASMA_dsungesv_Tile", "only square tiles of same size are supported");
        return PLASMA_ERR_ILLEGAL_VALUE;
    }
    if (trans != PlasmaNoTrans) {
        plasma_error("PLASMA_dsungesv_Tile", "only PlasmaNoTrans supported");
        return PLASMA_ERR_NOT_SUPPORTED;
    }

    /* Set N, NRHS, NB */
    N  = descA.m;
    NB = descA.nb;
    IB = descT.mb;

    work = (double *)plasma_shared_alloc(plasma, PLASMA_SIZE, PlasmaRealDouble);
    if (work == NULL) {
        plasma_error("PLASMA_dsungesv", "plasma_shared_alloc() failed");
        plasma_shared_free(plasma, work);
        return PLASMA_ERR_OUT_OF_RESOURCES;
    }

    plasma_ddesc_alloc( descR,  NB, NB, descB.m, descB.n, 0, 0, descB.m, descB.n, plasma_shared_free( plasma, work ); plasma_desc_mat_free(&descR) );
    plasma_sdesc_alloc( descSA, NB, NB, descA.m, descA.n, 0, 0, descA.m, descA.n, plasma_shared_free( plasma, work ); plasma_desc_mat_free(&descR); plasma_desc_mat_free(&descSA) );
    plasma_sdesc_alloc( descST, IB, NB, descT.m, descT.n, 0, 0, descT.m, descT.n, plasma_shared_free( plasma, work ); plasma_desc_mat_free(&descR); plasma_desc_mat_free(&descSA); plasma_desc_mat_free(&descST) );
    plasma_sdesc_alloc( descSX, NB, NB, descX.m, descX.n, 0, 0, descX.m, descX.n, plasma_shared_free( plasma, work ); plasma_desc_mat_free(&descR); plasma_desc_mat_free(&descSA); plasma_desc_mat_free(&descST); plasma_desc_mat_free(&descSX) );

    /* Compute some constants */
    PLASMA_dlange(PlasmaInfNorm, descA, Anorm, work);
    eps = LAPACKE_dlamch_work('e');

    /* Convert B from double precision to single precision and store
       the result in SX. */
    PLASMA_dlag2s(descB, descSX);
    if (sequence->status != PLASMA_SUCCESS)
        return plasma_request_fail(sequence, request, PLASMA_ERR_SEQUENCE_FLUSHED);

    /* Convert A from double precision to single precision and store
       the result in SA. */
    PLASMA_dlag2s(descA, descSA);
    if (sequence->status != PLASMA_SUCCESS)
        return plasma_request_fail(sequence, request, PLASMA_ERR_SEQUENCE_FLUSHED);

    /* Compute the QR factorization of SA */
    plasma_parallel_call_4(plasma_psgeqrf,
        PLASMA_desc, descSA,
        PLASMA_desc, descST,
        PLASMA_sequence*, sequence,
        PLASMA_request*, request);

    /* Compute the solve in simple */
    plasma_parallel_call_7(plasma_psormqr,
        PLASMA_enum, PlasmaLeft,
        PLASMA_enum, PlasmaTrans,
        PLASMA_desc, descSA,
        PLASMA_desc, descSX,
        PLASMA_desc, descST,
        PLASMA_sequence*, sequence,
        PLASMA_request*, request);

    plasma_parallel_call_9(plasma_pstrsm,
        PLASMA_enum, PlasmaLeft,
        PLASMA_enum, PlasmaUpper,
        PLASMA_enum, PlasmaNoTrans,
        PLASMA_enum, PlasmaNonUnit,
        float, 1.0,
        PLASMA_desc, descSA,
        PLASMA_desc, descSX,
        PLASMA_sequence*, sequence,
        PLASMA_request*, request);

    /* Convert SX back to double precision */
    PLASMA_slag2d(descSX, descX);

    /* Compute R = B - AX. */
    PLASMA_dlacpy(descB, descR);

    plasma_parallel_call_9(plasma_pdgemm,
        PLASMA_enum, PlasmaNoTrans,
        PLASMA_enum, PlasmaNoTrans,
        double, negone,
        PLASMA_desc, descA,
        PLASMA_desc, descX,
        double, one,
        PLASMA_desc, descR,
        PLASMA_sequence*, sequence,
        PLASMA_request*, request);

    /* Check whether the NRHS normwise backward error satisfies the
       stopping criterion. If yes return. Note that ITER=0 (already set). */
    PLASMA_dlange(PlasmaInfNorm, descX, Xnorm, work);
    PLASMA_dlange(PlasmaInfNorm, descR, Rnorm, work);

    /* Wait for the end of Anorm, Xnorm and Bnorm computations */
    plasma_dynamic_sync();

    cte = Anorm*eps*((double) N)*bwdmax;
    if (Rnorm < Xnorm * cte){
        /* The NRHS normwise backward errors satisfy the
           stopping criterion. We are good to exit. */
        plasma_desc_mat_free(&descSA);
        plasma_desc_mat_free(&descST);
        plasma_desc_mat_free(&descSX);
        plasma_desc_mat_free(&descR);
        plasma_shared_free(plasma, work);
        return PLASMA_SUCCESS;
    }

    /* Iterative refinement */
    for (iiter = 0; iiter < itermax; iiter++){

        /* Convert R from double precision to single precision
           and store the result in SX. */
        PLASMA_dlag2s(descR, descSX);

        plasma_parallel_call_7(plasma_psormqr,
            PLASMA_enum, PlasmaLeft,
            PLASMA_enum, PlasmaTrans,
            PLASMA_desc, descSA,
            PLASMA_desc, descSX,
            PLASMA_desc, descST,
            PLASMA_sequence*, sequence,
            PLASMA_request*, request);

        plasma_parallel_call_9(plasma_pstrsm,
            PLASMA_enum, PlasmaLeft,
            PLASMA_enum, PlasmaUpper,
            PLASMA_enum, PlasmaNoTrans,
            PLASMA_enum, PlasmaNonUnit,
            float, (float)1.0,
            PLASMA_desc, descSA,
            PLASMA_desc, descSX,
            PLASMA_sequence*, sequence,
            PLASMA_request*, request);

        /* Convert SX back to double precision and update the current
           iterate. */
        PLASMA_slag2d(descSX, descR);
        PLASMA_dgeadd(one, descR, descX);

        /* Compute R = B - AX. */
        PLASMA_dlacpy(descB,descR);
        plasma_parallel_call_9(plasma_pdgemm,
            PLASMA_enum, PlasmaNoTrans,
            PLASMA_enum, PlasmaNoTrans,
            double, negone,
            PLASMA_desc, descA,
            PLASMA_desc, descX,
            double, one,
            PLASMA_desc, descR,
            PLASMA_sequence*, sequence,
            PLASMA_request*, request);

        /* Check whether the NRHS normwise backward errors satisfy the
           stopping criterion. If yes, set ITER=IITER>0 and return. */
        PLASMA_dlange(PlasmaInfNorm, descX, Xnorm, work);
        PLASMA_dlange(PlasmaInfNorm, descR, Rnorm, work);

        /* Wait for the end of Xnorm and Bnorm computations */
        plasma_dynamic_sync();

        if (Rnorm < Xnorm * cte){
            /* The NRHS normwise backward errors satisfy the
               stopping criterion. We are good to exit. */
            *ITER = iiter;

            plasma_desc_mat_free(&descSA);
            plasma_desc_mat_free(&descST);
            plasma_desc_mat_free(&descSX);
            plasma_desc_mat_free(&descR);
            plasma_shared_free(plasma, work);
            return PLASMA_SUCCESS;
        }
    }

    /* We have performed ITER=itermax iterations and never satisified
       the stopping criterion, set up the ITER flag accordingly and
       follow up on double precision routine. */
    *ITER = -itermax - 1;

    plasma_desc_mat_free(&descSA);
    plasma_desc_mat_free(&descST);
    plasma_desc_mat_free(&descSX);
    plasma_desc_mat_free(&descR);
    plasma_shared_free(plasma, work);

    /* Single-precision iterative refinement failed to converge to a
       satisfactory solution, so we restart to double precision. */
    PLASMA_dlacpy(descB, descX);

    plasma_parallel_call_4(plasma_pdgeqrf,
        PLASMA_desc, descA,
        PLASMA_desc, descT,
        PLASMA_sequence*, sequence,
        PLASMA_request*, request);

    plasma_parallel_call_7(plasma_pdormqr,
        PLASMA_enum, PlasmaLeft,
        PLASMA_enum, PlasmaTrans,
        PLASMA_desc, descA,
        PLASMA_desc, descX,
        PLASMA_desc, descT,
        PLASMA_sequence*, sequence,
        PLASMA_request*, request);

    plasma_parallel_call_9(plasma_pdtrsm,
        PLASMA_enum, PlasmaLeft,
        PLASMA_enum, PlasmaUpper,
        PLASMA_enum, PlasmaNoTrans,
        PLASMA_enum, PlasmaNonUnit,
        double, (double)1.0,
        PLASMA_desc, descA,
        PLASMA_desc, descX,
        PLASMA_sequence*, sequence,
        PLASMA_request*, request);

    return PLASMA_SUCCESS;
}


#include <stdio.h>
#include <gsl/gsl_matrix.h>
#include "bstrlib/bstrlib.h"
#include "paths.h"
#include "ParseSCF.h"
#include "HTightBinding.h"
#include "BandEnergy.h"
#include "SpinOrbit.h"

int main(int argc, char *argv[]) {
    if (argc < 8) {
        printf("SOC-induced anisotropy calculation -- invoke with:\n");
        printf("anisotropy.out (system_name) (na) (nb) (nc) (soc_strength) (theta1) (phi1) (theta2) (phi2)\n");
        return 1;
    }
    char *system_name = argv[1];
    int na = atoi(argv[2]);
    int nb = atoi(argv[3]);
    int nc = atoi(argv[4]);
    double soc_strength = atof(argv[5]);
    double theta1 = atof(argv[6]);
    double phi1 = atof(argv[7]);
    double theta2 = atof(argv[8]);
    double phi2 = atof(argv[9]);

    char *scf_path = cwannier_data_path(system_name, "wannier", "\0", "scf.out");
    double num_electrons, alat;
    gsl_matrix *R = gsl_matrix_alloc(3, 3);

    int err = ParseSCF(scf_path, &num_electrons, &alat, R);
    if (err != CWANNIER_PARSESCF_OK) {
        printf("Error code = %d returned from ParseSCF\n", err);
        return err;
    }
    bcstrfree(scf_path);

    char *hr_up_path = cwannier_data_path(system_name, "wannier", system_name, "_up_hr.dat");
    HTightBinding *Hrs_up = ExtractHTightBinding(hr_up_path);
    bcstrfree(hr_up_path);

    char *hr_dn_path = cwannier_data_path(system_name, "wannier", system_name, "_dn_hr.dat");
    HTightBinding *Hrs_dn = ExtractHTightBinding(hr_dn_path);
    bcstrfree(hr_dn_path);

    printf("Hamiltonian loaded.\n");

    HTightBinding *Hrs_soc_1 = HamiltonianWithSOC(soc_strength, theta1, phi1, Hrs_up, Hrs_dn);
    HTightBinding *Hrs_soc_2 = HamiltonianWithSOC(soc_strength, theta2, phi2, Hrs_up, Hrs_dn);

    bool use_cache = true;
    double E_Fermi_1 = 0.0;
    double E_Fermi_2 = 0.0;

    double energy1 = BandEnergy(&E_Fermi_1, Hrs_soc_1, R, num_electrons, na, nb, nc, use_cache);
    printf("Got energy1 = %f\n", energy1);

    double energy2 = BandEnergy(&E_Fermi_2, Hrs_soc_2, R, num_electrons, na, nb, nc, use_cache);
    printf("Got energy2 = %f\n", energy2);

    printf("energy1 - energy2 = %e\n", energy1 - energy2);

    FreeHTightBinding(Hrs_up);
    FreeHTightBinding(Hrs_dn);
    FreeHTightBinding(Hrs_soc_1);
    FreeHTightBinding(Hrs_soc_2);
    return 0;
}


/**
 * @file convolution.h
 * @brief Block convolution realization of an LTI system with the FFT.Time delay estimation
 * @author John McDonough
 */

#ifndef CONVOLUTION_H
#define CONVOLUTION_H

#include <stdio.h>
#include <assert.h>

#include <gsl/gsl_block.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_complex.h>
#include <gsl/gsl_complex_math.h>
#include "common/jexception.h"

#include "stream/stream.h"
#include "feature/feature.h"

// ----- definition for class `OverlapAdd' -----
//
class OverlapAdd : public VectorFloatFeatureStream {
 public:
  OverlapAdd(VectorFloatFeatureStreamPtr& samp,
             const gsl_vector* impulseResponse = NULL, unsigned fftLen = 0,
             const String& nm = "OverlapAdd");

  ~OverlapAdd();

  virtual const gsl_vector_float* next(int frame_no = -5);

  virtual void reset();

 private:
  void set_impulse_response_(const gsl_vector* impulseResponse);
  unsigned check_fftLen_(unsigned sectionLen, unsigned irLen, unsigned fftLen);

  const VectorFloatFeatureStreamPtr			samp_;
  const unsigned					L_;
  const unsigned					P_;
  const unsigned					N_;
  const unsigned					N2_;

  double*						section_;
  gsl_vector_complex*					frequencyResponse_;
  gsl_vector_float*					buffer_;
};

typedef Inherit<OverlapAdd, VectorFloatFeatureStreamPtr> OverlapAddPtr;

// ----- definition for class `OverlapSave' -----
//
class OverlapSave : public VectorFloatFeatureStream {
 public:
  OverlapSave(VectorFloatFeatureStreamPtr& samp,
              const gsl_vector* impulseResponse = NULL, const String& nm = "OverlapSave");

  ~OverlapSave();

  virtual const gsl_vector_float* next(int frame_no = -5);

  virtual void reset();

  void update(const gsl_vector_complex* delta);

 private:
  void set_impulse_response_(const gsl_vector* impulseResponse);
  unsigned check_output_size_(unsigned irLen, unsigned sampLen);
  unsigned check_L_(unsigned irLen, unsigned sampLen);

  const VectorFloatFeatureStreamPtr			samp_;
  const unsigned					L_;
  const unsigned					L2_;
  const unsigned					P_;

  double*						section_;
  gsl_vector_complex*					frequencyResponse_;
};

typedef Inherit<OverlapSave, VectorFloatFeatureStreamPtr> OverlapSavePtr;

#endif // CONVOLUTION_H


#ifndef KEYPOINTS_H_CFNIGHAG
#define KEYPOINTS_H_CFNIGHAG

#include <boost/histogram.hpp>
#include <gsl/gsl>
#include <opencv2/core/persistence.hpp>
#include <opencv2/core/types.hpp>
#include <sens_loc/analysis/distance.h>

namespace sens_loc::analysis {

/// Analyze the distribution and characteristics of detected keypoints.
class keypoints {
  public:
    /// Use a regular grid with no transformation, no overflow and an axis
    /// title.
    using axis_t = distance::axis_t;
    /// Histogram type for the 2D distribution of keypoints in the dataset.
    using distribution_histo_t =
        decltype(boost::histogram::make_histogram(axis_t{}, axis_t{}));
    /// Histogram type for 'size' and 'response' of the keypoints.
    using histo_t = decltype(boost::histogram::make_histogram(axis_t{}));

    keypoints() = default;
    keypoints(unsigned int img_width, unsigned int img_height)
        : _img_width{img_width}
        , _img_height{img_height} {}

    void configure_image_dimension(unsigned int img_width,
                                   unsigned int img_height) noexcept {
        _img_width  = img_width;
        _img_height = img_height;
    }

    void configure_size(unsigned int bins) noexcept { _size_bins = bins; }
    void configure_size(std::string axis_title) noexcept {
        _size_title = std::move(axis_title);
    }
    void configure_size(unsigned int bins, std::string axis_title) noexcept {
        _size_bins  = bins;
        _size_title = std::move(axis_title);
    }
    void enable_size_histo(bool enabled) noexcept {
        _size_histo_enabled = enabled;
    }

    void configure_response(unsigned int bins) noexcept {
        _response_bins = bins;
    }
    void configure_response(std::string axis_title) noexcept {
        _response_title = std::move(axis_title);
    }
    void configure_response(unsigned int bins,
                            std::string  axis_title) noexcept {
        _response_bins  = bins;
        _response_title = std::move(axis_title);
    }
    void enable_response_histo(bool enabled) noexcept {
        _response_histo_enabled = enabled;
    }

    void configure_distribution(unsigned int both_bins) noexcept {
        _dist_width_bins  = both_bins;
        _dist_height_bins = both_bins;
    }
    void configure_distribution(unsigned int w_bins,
                                unsigned int h_bins) noexcept {
        _dist_width_bins  = w_bins;
        _dist_height_bins = h_bins;
    }
    void configure_distribution(std::string w_axis_title,
                                std::string h_axis_title) noexcept {
        _dist_w_title = std::move(w_axis_title);
        _dist_h_title = std::move(h_axis_title);
    }

    /// Analyze the properties of a set of keypoints. The booleans are toggles
    /// to deactivate analysis for the specified quantity to save some
    /// computations.
    void analyze(gsl::span<const cv::KeyPoint> points,
                 bool                          distribution = true,
                 bool                          size         = true,
                 bool                          response     = true) noexcept;

    [[nodiscard]] const distribution_histo_t& distribution() const noexcept {
        return _distribution;
    }
    [[nodiscard]] const histo_t& size_histo() const noexcept {
        return _size_histo;
    }
    [[nodiscard]] const histo_t& response_histo() const noexcept {
        return _response_histo;
    }

    [[nodiscard]] const statistic& size() const noexcept { return _size; }
    [[nodiscard]] const statistic& response() const noexcept {
        return _response;
    }

  private:
    unsigned int _img_width  = 0U;
    unsigned int _img_height = 0U;

    bool         _size_histo_enabled = true;
    statistic    _size;
    histo_t      _size_histo;
    unsigned int _size_bins  = 50U;
    std::string  _size_title = "size of keypoints";

    bool         _response_histo_enabled = true;
    statistic    _response;
    histo_t      _response_histo;
    unsigned int _response_bins  = 50U;
    std::string  _response_title = "response of keypoints";

    distribution_histo_t _distribution;
    unsigned int         _dist_width_bins  = 50U;
    unsigned int         _dist_height_bins = 50U;
    std::string          _dist_w_title = "width - distribution of keypoints";
    std::string          _dist_h_title = "height - distribution of keypoints";
};

/// Write the statistics for the keypoint distribution in a file with OpenCVs
/// FileStorage API.
void write(cv::FileStorage& fs, const std::string& name, const keypoints& kp);

}  // namespace sens_loc::analysis

#endif /* end of include guard: KEYPOINTS_H_CFNIGHAG */