/**
 * @file   libhades.c
 * @author Michael Hartmann <michael.hartmann@physik.uni-augsburg.de>
 * @date   February, 2016
 * @brief  library to access low-level LAPACK functions
 */

#include <cblas.h>
#include <errno.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <strings.h>

#include <libhades.h>
#include <libhades/parse_npy_dict.h>

/** \defgroup misc miscellaneous functions
 *  @{
 */

/** @brief malloc wrapper
 *
 * This function uses malloc to allocate size bytes of memory and returns a
 * pointer to the memory. If an error occures, an error message is printed to
 * stderr and the program is aborted.
 *
 * @param [in] size amount of memory to allocate
 * @retval ptr pointer to the allocated memory
 */
void *libhades_malloc(size_t size)
{
    void *ptr = malloc(size);
    if(ptr == NULL)
    {
        const int err = errno;
        fprintf(stderr, "malloc can't allocate %zu bytes of memory: %s (%d)\n", size, strerror(err), err);
        abort();
    }
    return ptr;
}

/** @brief free wrapper
 *
 * This function frees the memory allocated by \ref libhades_malloc or \ref
 * libhades_realloc (or malloc and realloc). If the pointer given is NULL, an error is
 * printed to stderr and the program is aborted.
 *
 * @param [in] ptr pointer to the memory that should be freed
 */
void libhades_free(void *ptr)
{
    if(ptr == NULL)
    {
        fprintf(stderr, "Trying to free NULL pointer\n");
        abort();
    }

    free(ptr);
}

/** @brief realloc wrapper
 *
 * This function changes the size of the memory block pointed to by ptr to size
 * bytes. If ptr is NULL, this function behaves like \ref libhades_malloc. If
 * an error occures, an error is printed to stderr and the program is aboprted.
 *
 * @param [in] ptr pointer to the memory block
 * @param [in] size size of the memory block
 * @retval ptr_new pointer to the new memory block
 */
void *libhades_realloc(void *ptr, size_t size)
{
    void *ptr_new = realloc(ptr, size);

    if(ptr_new == NULL)
    {
        int err = errno;
        fprintf(stderr, "realloc can't allocate %zu bytes of memory: %s (%d)\n", size, strerror(err), err);
        abort();
    }

    return ptr_new;
}

static void *(*malloc_cb)(size_t)          = &libhades_malloc;
static void *(*realloc_cb)(void *, size_t) = &libhades_realloc;
static void  (*free_cb)(void *)            = &libhades_free;

/** macro to create functions argmin, argmax, argabsmin, argabsmax */
#define ARGXXX(FUNCTION_NAME, FUNCTION, RELATION) \
size_t FUNCTION_NAME(double list[], size_t size) \
{ \
    size_t index = 0; \
    for(size_t i = 0; i < size; i++) \
        if(FUNCTION(list[i]) RELATION FUNCTION(list[index])) \
            index = i; \
    return index; \
}

/** @brief Return index of smallest element in list
 *
 * @param [in] list
 * @param [in] size elements in list
 *
 * @retval index
 */
ARGXXX(argmin, +, <)

/** @brief Return index of largest element in list
 *
 * @param [in] list
 * @param [in] size elements in list
 *
 * @retval index
 */
ARGXXX(argmax, +, >)

/** @brief Return index of element with smallest absolute value in list
 *
 * @param [in] list
 * @param [in] size elements in list
 *
 * @retval index
 */
ARGXXX(argabsmin, fabs, <)
/** @brief Return index of element with largest absolute value in list
 *
 * @param [in] list
 * @param [in] size elements in list
 *
 * @retval index
 */
ARGXXX(argabsmax, fabs, >)

/** @}*/

/** \defgroup create Creating, printing and freeing matrices
 *  @{
 */

static inline void _swap_int(int *a, int *b);
static inline void _swap_size_t(size_t *a, size_t *b);

static inline void _swap_int(int *a, int *b)
{
    int c = *a;
    *a = *b;
    *b = c;
}

static inline void _swap_size_t(size_t *a, size_t *b)
{
    size_t c = *a;
    *a = *b;
    *b = c;
}

#define MATRIX_SWAP(FUNCTION_NAME, MATRIX_TYPE, TYPE) \
void FUNCTION_NAME(MATRIX_TYPE *A, MATRIX_TYPE *B) \
{ \
    /* swap pointers */ \
    TYPE *ptr = A->M; \
    A->M = B->M; \
    B->M = ptr; \
\
    /* swap rows */ \
    _swap_int(&A->rows, &B->rows); \
\
    /* swap columns */ \
    _swap_int(&A->columns, &B->columns); \
\
    /* swap min */ \
    _swap_int(&A->min, &B->min); \
\
    /* swap size */ \
    _swap_size_t(&A->size, &B->size); \
\
    /* swap type */ \
    _swap_int(&A->type, &B->type); \
\
    /* swap min */ \
    _swap_int(&A->min, &B->min); \
}

/** @brief Swap matrices A and B
 *
 * This function swaps the matrices A and B. The former content of A will be
 * the content of B and vice versa. No data is copied or moved, but the
 * pointers are swapped.
 *
 * @param [in,out] A matrix A
 * @param [in,out] B matrix B
 */
MATRIX_SWAP(matrix_swap, matrix_t, double);

/** @brief Swap matrices A and B
 *
 * See \ref matrix_swap.
 *
 * @param [in,out] A matrix A
 * @param [in,out] B matrix B
 */
MATRIX_SWAP(matrix_complex_swap, matrix_complex_t, complex_t);

/** macro to create a diagnal matrix out of a vector */
#define MATRIX_DIAG(FUNCTION_NAME, MATRIX_TYPE, ZEROS) \
MATRIX_TYPE *FUNCTION_NAME(MATRIX_TYPE *v) \
{ \
    int dim = v->size; \
    MATRIX_TYPE *A = ZEROS(dim, dim, NULL); \
    if(A == NULL) \
        return NULL; \
\
    for(int i = 0; i < dim; i++) \
        matrix_set(A, i,i, v->M[i]); \
\
    return A; \
}

/** @brief Construct a real diagonal matrix from a row or columns vector
 *
 * @param [in] v row or column vector
 *
 * @retval A A = diag(v) if successfull, NULL otherwise
 */
MATRIX_DIAG(matrix_diag, matrix_t, matrix_zeros)

/** @brief Construct a complex diagonal matrix from a row or columns vector
 *
 * @param [in] v row or column vector
 *
 * @retval A A = diag(v) if successfull, NULL otherwise
 */
MATRIX_DIAG(matrix_complex_diag, matrix_complex_t, matrix_complex_zeros)

/** @brief Set functions to allocate and free memory
 *
 * By default wrappers to malloc and free from <stdlib.h> are used to allocate
 * and free memory. If allocation of memory fails or a NULL pointer is freed,
 * the program will terminate.
 *
 * @param [in] _malloc_cb callback to a malloc-alike function
 * @param [in] _free_cb callback to a free-alike function
 */
void matrix_set_alloc(void *(*_malloc_cb)(size_t), void  (*_free_cb)(void *))
{
    malloc_cb = _malloc_cb;
    free_cb   = _free_cb;
}

/** macro for copying matrices. */
#define MATRIX_COPY(FUNCTION_NAME, MTYPE, TYPE, ALLOC) \
MTYPE *FUNCTION_NAME(MTYPE *A, MTYPE *C) \
{ \
    if(C == NULL) { \
        C = ALLOC(A->rows, A->columns); \
        if(C == NULL) \
            return NULL; \
    } \
\
    C->rows    = A->rows; \
    C->columns = A->columns; \
    C->min     = A->min; \
    C->size    = A->size; \
    C->type    = A->type; \
    C->view    = 0; \
    memcpy(C->M, A->M, C->size*sizeof(TYPE)); \
    return C; \
}

/** @brief Copy real matrix A
 *
 * Copy matrix A into C. If C is NULL, space for the matrix C will be
 * allocated.
 *
 * @param [in]     A real matrix
 * @param [in,out] C real matrix
 *
 * @retval C copy of A
 */
MATRIX_COPY(matrix_copy, matrix_t, double, matrix_alloc)

/** @brief Copy complex matrix A
 *
 * Copy matrix A into C. If C is NULL, space for the matrix C will be
 * allocated.
 *
 * @param [in]     A complex matrix
 * @param [in,out] C complex matrix
 *
 * @retval C copy of A
 */
MATRIX_COPY(matrix_complex_copy, matrix_complex_t, complex_t, matrix_complex_alloc)

/** @brief Copy a real matrix A to a complex matrix C
 *
 * Copy the matrix A to a complex matrix C. The matrix elements of A and C will
 * be identical. If C is NULL, memory for the matrix will be allocated.
 *
 * @param [in] A real matrix
 *
 * @retval C copy of A if successfull, NULL otherwise
 */
matrix_complex_t *matrix_tocomplex(matrix_t *A, matrix_complex_t *C)
{
    if(C == NULL)
    {
        C = matrix_complex_alloc(A->rows, A->columns);
        if(C == NULL)
            return NULL;
    }

    for(size_t i = 0; i < C->size; i++)
        C->M[i] = A->M[i];

    return C;
}

/** @brief Print real matrix M to stream
 *
 * Print the matrix A to the stream given by stream, e.g. stdout or stderr.
 * The format is given by the format string format, the separator of two
 * columns is sep, the separator between lines is given by sep_line. Both sep
 * and sep_line may be NULL.
 *
 * @param [in] stream output stream
 * @param [in] A real matrix
 * @param [in] format output format, e.g. "%lf" or "%g"
 * @param [in] sep separator between columns, e.g. "\t"
 * @param [in] sep_line separator between lines, e.g. "\n"
 */
void matrix_fprintf(FILE *stream, matrix_t *A, const char *format, const char *sep, const char *sep_line)
{
    const int rows = A->rows, columns = A->columns;

    for(int i = 0; i < rows; i++)
    {
        for(int j = 0; j < columns; j++)
        {
            fprintf(stream, format, matrix_get(A, i,j));
            if(sep != NULL)
                fputs(sep, stream);
        }
        if(sep_line != NULL)
            fputs(sep_line, stream);
    }
}

/** @brief Print complex matrix A to stream
 *
 * See matrix_fprintf.
 *
 * @param [in] stream output stream
 * @param [in] A complex matrix
 * @param [in] format output format, e.g. "%+lf%+lfi"
 * @param [in] sep separator between columns, e.g. "\t"
 * @param [in] sep_line separator between lines, e.g. "\n"
 */
void matrix_complex_fprintf(FILE *stream, matrix_complex_t *A, const char *format, const char *sep, const char *sep_line)
{
    const int rows = A->rows, columns = A->columns;

    for(int i = 0; i < rows; i++)
    {
        for(int j = 0; j < columns; j++)
        {
            const complex_t c = matrix_get(A, i,j);
            fprintf(stream, format, CREAL(c), CIMAG(c));
            if(sep != NULL)
                fputs(sep, stream);
        }
        if(sep_line != NULL)
            fputs(sep_line, stream);
    }
}

/** macro for matrix allocations. */
#define MATRIX_ALLOC(FUNCTION_NAME, MTYPE, TYPE) \
MTYPE *FUNCTION_NAME(int rows, int columns) \
{ \
    if(rows <= 0 || columns <= 0) \
        return NULL; \
\
    MTYPE *A = malloc_cb(sizeof(MTYPE)); \
    if(A == NULL) \
        return NULL; \
\
    const size_t size = (size_t)rows*(size_t)columns; \
\
    A->rows    = rows; \
    A->columns = columns; \
    A->min     = MIN(rows, columns); \
    A->size    = size; \
    A->type    = 0; \
    A->view    = 0; \
    A->M       = malloc_cb(size*sizeof(TYPE)); \
    if(A->M == NULL) \
    { \
        free_cb(A); \
        return NULL; \
    } \
\
    return A; \
}

/** @brief Allocate memory for real matrix of m rows and n columns
 *
 * This function will allocate and return a matrix of m lines and n columns.
 * The function given by matrix_set_alloc will be used to allocate memory; by
 * default this is malloc from <stdlib.h>.
 *
 * The matrix elements will be undefined. To allocate a matrix initialized with
 * zeros, see matrix_zeros. To create a unity matrix, see matrix_eye.
 *
 * @param [in] rows rows of matrix M (rows > 0)
 * @param [in] columns columns of matrix M (columns > 0)
 *
 * @retval A real matrix if successful, NULL otherwise
 */
MATRIX_ALLOC(matrix_alloc, matrix_t, double)

/** @brief Allocate memory for complex matrix of m rows and n columns
 *
 * See matrix_alloc.
 *
 * @param [in] rows rows of matrix M (rows > 0)
 * @param [in] columns columns of matrix M (columns > 0)
 *
 * @retval A complex matrix if successful, otherwise NULL
 */
MATRIX_ALLOC(matrix_complex_alloc, matrix_complex_t, complex_t)

/** macro to create zero matrix */
#define MATRIX_ZEROS(FUNCTION_NAME, MTYPE, TYPE, ALLOC, SETALL) \
MTYPE *FUNCTION_NAME(int rows, int columns, MTYPE *A) \
{ \
    if(A == NULL) \
    { \
        A = ALLOC(rows,columns); \
        if(A == NULL) \
            return NULL; \
    } \
\
    SETALL(A, 0); \
\
    return A; \
}

/** @brief Generate a real zero matrix of m lines and n columns
 *
 * Set every element of matrix A to 0. If A is NULL, the matrix will be
 * created. In this case, you have to free the matrix yourself.
 *
 * @param [in] rows rows of matrix M
 * @param [in] columns columns of matrix M
 * @param [in,out] A: matrix
 *
 * @retval A real matrix 0 if successful, otherwise NULL
 */
MATRIX_ZEROS(matrix_zeros, matrix_t, double, matrix_alloc, matrix_setall)

/** @brief Generate a complex zero matrix of m lines and n columns
 *
 * See matrix_zeros.
 *
 * @param [in] rows rows of matrix M
 * @param [in] columns columns of matrix M
 * @param [in,out] A: matrix
 *
 * @retval A complex matrix 0 if successful, otherwise NULL
 */
MATRIX_ZEROS(matrix_complex_zeros, matrix_complex_t, complex_t, matrix_complex_alloc, matrix_complex_setall)

/** macro to create matrix x*Id */
#define MATRIX_SETALL(FUNCTION_NAME, MATRIX_TYPE, TYPE) \
void FUNCTION_NAME(MATRIX_TYPE *A, TYPE x) \
{ \
    const size_t size = A->size; \
    TYPE *M = A->M; \
    for(size_t i = 0; i < size; i++) \
        *M++ = x; \
}

/** @brief Set matrix elements of A to x
 *
 * @param [in,out] A real matrix
 * @param [in] x real number
 */
MATRIX_SETALL(matrix_setall, matrix_t, double)

/** @brief Set matrix elements of A to x
 *
 * @param [in,out] A complex matrix
 * @param [in] x complex number
 */
MATRIX_SETALL(matrix_complex_setall, matrix_complex_t, complex_t)

/** macro to create unity matrix */
#define MATRIX_EYE(FUNCTION_NAME, MTYPE, TYPE, ALLOC, SETALL) \
MTYPE *FUNCTION_NAME(int dim, MTYPE *A) \
{ \
    if(A == NULL) \
    { \
        A = ALLOC(dim,dim); \
        if(A == NULL) \
            return NULL; \
    } \
 \
    const int min = A->min; \
    TYPE *M = A->M; \
    SETALL(A,0); \
    for(int i = 0; i < min; i++) \
        *(M+i*(min+1)) = 1; \
 \
    return A; \
}

/** @brief Create real identity matrix
 *
 * If A == NULL, a identity matrix of dimension dim times dim is created and
 * returned.
 * If A != NULL, the matrix A is set to the identity matrix and the parameter
 * dim is ignored. More specific, for a general (e.g. not square) matrix A, the
 * matrix elements are set to A_ij = Delta_ij.
 *
 * @param [in]     dim dimension of identity matrix (ignored for A == NULL)
 * @param [in,out] A real matrix
 *
 * @retval A identity matrix if successful, NULL otherwise
 */
MATRIX_EYE(matrix_eye, matrix_t, double, matrix_alloc, matrix_setall)

/** @brief Create complex identity matrix
 *
 * See matrix_eye.
 *
 * @param [in]     dim dimension of identity matrix (ignored for A == NULL)
 * @param [in,out] A complex matrix
 *
 * @retval A identity matrix if successful, NULL otherwise
 */
MATRIX_EYE(matrix_complex_eye, matrix_complex_t, complex_t, matrix_complex_alloc, matrix_complex_setall)

/** macro to free matrices */
#define MATRIX_FREE(FUNCTION_NAME, MTYPE) \
void FUNCTION_NAME(MTYPE *A) \
{ \
    if(A != NULL) \
    { \
        if(!A->view && A->M != NULL) \
        { \
            free_cb(A->M); \
            A->M = NULL; \
        } \
        free_cb(A); \
    } \
}

/** @brief Free real matrix
 *
 * This function will free the memory allocated for matrix M. If M is NULL this
 * function will do nothing.
 *
 * @param [in,out] A matrix to free
 */
MATRIX_FREE(matrix_free, matrix_t)

/** @brief Free complex matrix
 *
 * See matrix_free.
 *
 * @param [in,out] A matrix to free
 */
MATRIX_FREE(matrix_complex_free, matrix_complex_t)

/** @}*/

/** \defgroup trdet trace and determinant
 *  @{
 */

/** macro to calculate trace of matrix */
#define MATRIX_TRACE(FUNCTION_NAME, MATRIX_TYPE, TYPE) \
TYPE FUNCTION_NAME(MATRIX_TYPE *A) \
{ \
    const int min = A->min; \
    const TYPE *M = A->M; \
    TYPE trace = 0; \
    for(int i = 0; i < min; i++) \
        trace += *(M+i*(1+min)); \
\
    return trace; \
}

/** @brief Calculate Tr(A) of real matrix A
 *
 * @param [in] A complex matrix
 *
 * @retval x with x=Tr(A)
 */
MATRIX_TRACE(matrix_trace, matrix_t, double)

/** @brief Calculate Tr(A) of complex matrix A
 *
 * @param [in] A complex matrix
 *
 * @retval z with z=Tr(A)
 */
MATRIX_TRACE(matrix_complex_trace, matrix_complex_t, complex_t)

/** @brief Calculate Tr(A*B)
 *
 * This will calculate the trace of A*B: Tr(A*B). Matrix A and B must be square
 * matrices of dimension dim, dim.
 *
 * A and B must not point to the same matrix or the behaviour will be
 * undefined!
 *
 * @param [in] A real matrix
 * @param [in] B real matrix
 *
 * @retval x with x=Tr(A*B)
 */
double matrix_trace_AB(matrix_t *A, matrix_t *B)
{
    const int dim = A->min;
    double sum = 0;
    double *M1 = A->M;
    double *M2 = B->M;

    for(int i = 0; i < dim; i++)
        sum += cblas_ddot(dim, M1+i, dim, M2+i*dim, 1);

    return sum;
}

/** @brief Calculate Tr(A*B) for A,B complex
 *
 * See matrix_trace_AB.
 *
 * @param [in] A complex matrix
 * @param [in] B complex matrix
 *
 * @retval z with z=Tr(A*B)
 */
complex_t matrix_trace_complex_AB(matrix_complex_t *A, matrix_complex_t *B)
{
    const int dim = A->min;
    complex_t sum = 0;

    for(int i = 0; i < dim; i++)
        for(int k = 0; k < dim; k++)
            sum += matrix_get(A, i,k)*matrix_get(B, k,i);

    return sum;
}

/** @brief Calculate Re(Tr(A*B)) for A,B complex
 *
 * See matrix_trace_AB.
 *
 * @param [in] A complex matrix
 * @param [in] B complex matrix
 *
 * @retval x with x=Re(Tr(A*B))
 */
double matrix_trace_complex_AB_real(matrix_complex_t *A, matrix_complex_t *B)
{
    const int dim = A->rows;
    double sum = 0;
    const complex_t *M1 = A->M;
    const complex_t *M2 = B->M;

    for(int i = 0; i < dim; i++)
        for(int k = 0; k < dim; k++)
            sum += CREAL(M1[i*dim+k]*M2[k*dim+i]);

    return sum;
}

/** @}*/

/** \defgroup kron Kronecker product
 *  @{
 */

/** macro to create Kronecker product */
#define MATRIX_KRON(FUNCTION_NAME, MTYPE, TYPE, ALLOC, SETALL) \
MTYPE *FUNCTION_NAME(MTYPE *A, MTYPE *B, MTYPE *C) \
{ \
    const int Am = A->rows, An = A->columns; \
    const int Bm = B->rows, Bn = B->columns; \
    if(C == NULL) \
    { \
        C = ALLOC(Am*Bm, An*Bn); \
        if(C == NULL) \
            return NULL; \
    } \
    SETALL(C, 0); \
\
    for(int m = 0; m < Am; m++) \
        for(int n = 0; n < An; n++) \
        { \
            const TYPE c = matrix_get(A,m,n); \
            if(c != 0) \
            { \
                for(int im = 0; im < Bm; im++) \
                    for(int in = 0; in < Bn; in++) \
                        matrix_set(C, m*Bm+im, n*Bn+in, c*matrix_get(B,im,in)); \
            } \
        } \
\
    return C; \
}

/** @brief Calculate Kronecker product of real matrices A and B
 *
 * Matrix A has dimension Am,An, matrix B has dimension Bm,Bn.
 *
 * If C is not NULL, the Kronecker product will be stored in C. C must have
 * dimension Am+Bm,An+Bn. If C is NULL, memory for the matrix will be allocated
 * and the matrix will be returned. You have to free the memory for the
 * returned matrix yourself.
 *
 * @param [in] A real matrix
 * @param [in] B real matrix
 * @param [in,out] C Kronecker product
 *
 * @retval C Kronecker product of A and B, NULL if no memory could be allocated
 */
MATRIX_KRON(matrix_kron, matrix_t, double, matrix_alloc, matrix_setall)

/** @brief Calculate Kronecker product of complex matrices A and B
 *
 * See matrix_kron.
 *
 * @param [in] A complex matrix
 * @param [in] B complex matrix
 * @param [in,out] C Kronecker product
 *
 * @retval C Kronecker product of A and B
 */
MATRIX_KRON(matrix_complex_kron, matrix_complex_t, complex_t, matrix_complex_alloc, matrix_complex_setall)

/** @}*/

/** \defgroup addsubmult Add, subtract and multiply matrices
 *  @{
 */

/** macro to multiply matrix with scalar factor */
#define MATRIX_MULT_SCALAR(FUNCTION_NAME, MTYPE, TYPE) \
void FUNCTION_NAME(MTYPE *A, TYPE alpha) \
{ \
    const size_t size = A->size; \
    TYPE *M = A->M; \
    for(size_t i = 0; i < size; i++) \
        M[i] *= alpha; \
}

/** @brief Multiply real matrix with a real scalar
 *
 * alpha*A -> A
 *
 * @param [in,out] A real matrix
 * @param [in] alpha real scalar
 * @retval A, A=alpha*A
 */
MATRIX_MULT_SCALAR(matrix_mult_scalar, matrix_t, double)

/** @brief Multiply complex matrix with a complex scalar
 *
 * alpha*A -> A
 *
 * @param [in,out] A complex matrix
 * @param [in] alpha complex scalar
 * @retval A, A=alpha*A
 */
MATRIX_MULT_SCALAR(matrix_complex_mult_scalar, matrix_complex_t, complex_t)

/** @brief Multiply real matrix with a complex scalar
 *
 * alpha*A -> C
 *
 * If C is NULL, memory for the matrix C will be allocated. If C is not NULL, C
 * must have the correct dimension.
 *
 * @param [in] A real matrix
 * @param [in] alpha complex scalar
 * @param [in,out] C complex matrix
 *
 * @retval C, C = alpha*A
 */
matrix_complex_t *matrix_mult_complex_scalar(matrix_t *A, complex_t alpha, matrix_complex_t *C)
{
    const int rows    = A->rows;
    const int columns = A->columns;
    const double *AM  = A->M;
    complex_t *CM;
    if(C == NULL)
    {
        C = matrix_complex_alloc(rows, columns);
        if(C == NULL)
            return NULL;
    }
    CM = C->M;

    for(size_t i = 0; i < C->size; i++)
        CM[i] = alpha*AM[i];

    return C;
}

/* compute alpha*A*B */
#define MATRIX_MULT(FUNCTION_NAME, MATRIX_TYPE, TYPE, ALLOC, BLAS_xGEMM, PTR) \
MATRIX_TYPE *FUNCTION_NAME(MATRIX_TYPE *A, MATRIX_TYPE *B, TYPE alpha, MATRIX_TYPE *C) \
{ \
    TYPE beta = 0; \
\
    if(A->columns != B->rows) \
        return NULL; \
\
    if(C == NULL) \
    { \
        C = ALLOC(A->rows, B->columns); \
        if(C == NULL) \
            return NULL; \
    } \
\
    /* xGEMM ( TRANSA, TRANSB, M, N, K, ALPHA, A, LDA, B, LDB, BETA, C, LDC) */ \
    BLAS_xGEMM(CblasColMajor, /* column order */ \
               CblasNoTrans,  /* don't transpose/conjugate A */ \
               CblasNoTrans,  /* don't transpose/conjugate B */ \
               A->rows,       /* M: rows of A and C */ \
               B->columns,    /* N: columns of B and C */ \
               A->columns,    /* K: columns of A and rows of B */ \
               PTR alpha,     /* alpha: scalar */ \
               A->M,          /* A: matrix A */ \
               A->rows,       /* LDA: leading dimension of A (columns) */ \
               B->M,          /* B: matrix B */ \
               A->columns,    /* LDB: leading dimension of B (columns) */ \
               PTR beta,      /* beta: scalar */ \
               C->M,          /* C: matrix C */ \
               C->rows        /* LDC: leading dimension of C (columns) */ \
    ); \
\
    return C; \
}

/** @brief Multiply real matrices
 *
 * alpha*A*B -> C
 *
 * If C is NULL, memory for the matrix C will be allocated.
 *
 * @param [in] A real matrix
 * @param [in] B real matrix
 * @param [in] alpha real scalar
 * @param [in,out] C real matrix
 *
 * @retval C, C = alpha*A*B
 */
MATRIX_MULT(matrix_mult, matrix_t, double, matrix_alloc, cblas_dgemm, +)

/** @brief Multiply complex matrices
 *
 * alpha*A*B -> C
 *
 * If C is NULL, memory for the matrix C will be allocated.
 *
 * @param [in] A complex matrix
 * @param [in] B complex matrix
 * @param [in] alpha complex scalar
 * @param [in,out] C complex matrix
 *
 * @retval C, C = alpha*A*B
 */
MATRIX_MULT(matrix_complex_mult, matrix_complex_t, complex_t, matrix_complex_alloc, cblas_zgemm, &)

/** macro to add two matrices */
#define MATRIX_ADD(FUNCTION_NAME, TYPE1, MTYPE1, TYPE2, MTYPE2) \
int FUNCTION_NAME(MTYPE1 *A, MTYPE2 *B, TYPE1 alpha, MTYPE1 *C) \
{ \
    const size_t size = A->size; \
    TYPE1 *M3; \
    TYPE1 *M1 = A->M; \
    TYPE2 *M2 = B->M; \
\
    if(C == NULL) \
        M3 = A->M; \
    else \
        M3 = C->M; \
\
    if(A->rows != B->rows || A->columns != B->columns) \
        return LIBHADES_ERROR_SHAPE; \
\
    for(size_t i = 0; i < size; i++) \
        M3[i] = M1[i] + (alpha*M2[i]); \
\
    return 0; \
}

/** @brief Add real matrices A and B
 *
 * Calculate A+alpha*B -> C.
 *
 * The result will be stored in C. If C is NULL, the result is stored in A.
 *
 * @param [in,out] A real matrix
 * @param [in] B real matrix
 * @param [in] alpha real scalar
 * @param [in,out] C real matrix or NULL
 *
 * @retval 0 if successfull
 * @retval LIBHADES_ERROR_SHAPE if matrices have wrong shape
 */
MATRIX_ADD(matrix_add, double, matrix_t, double, matrix_t)

/** @brief Add complex matrices A and B
 *
 * Calculate A+alpha*B -> C.
 *
 * The result will be stored in C. If C is NULL, the result is stored in A.
 *
 * @param [in,out] A complex matrix
 * @param [in] B complex matrix
 * @param [in] alpha complex number
 * @param [in,out] C complex matrix or NULL
 *
 * @retval 0 if successfull
 * @retval LIBHADES_ERROR_SHAPE if matrices have wrong shape
 */
MATRIX_ADD(matrix_complex_add, complex_t, matrix_complex_t, complex_t, matrix_complex_t)

/** @brief Add complex matrix A and real matrix B
 *
 * Calculate A+alpha*B -> C.
 *
 * The result will be stored in C. If C is NULL, the result is stored in A.
 *
 * @param [in,out] A complex matrix
 * @param [in] B real matrix
 * @param [in] alpha complex scalar
 * @param [in,out] C complex matrix or NULL
 *
 * @retval 0 if successfull
 * @retval LIBHADES_ERROR_SHAPE if matrices have wrong shape
 */
MATRIX_ADD(matrix_complex_add_real, complex_t, matrix_complex_t, double, matrix_t)

/** @}*/

/** \defgroup transconj Transpose, conjugate
 *  @{
 */

#define MATRIX_TRANSPOSE(FUNCTION_NAME, MTYPE, TYPE) \
void FUNCTION_NAME(MTYPE *A) \
{ \
    const int rows    = A->rows; \
    const int columns = A->columns; \
    for(int im = 0; im < rows; im++) \
        for(int in = im+1; in < columns; in++) \
        { \
            TYPE temp = matrix_get(A, im, in); \
            matrix_set(A, im, in, matrix_get(A, in, im)); \
            matrix_set(A, in, im, temp); \
        } \
\
    A->rows    = columns; \
    A->columns = rows; \
}

/** @brief Transpose real matrix A
 *
 * The matrix will be transposed.
 *
 * @param [in,out] A real matrix
 *
 * @retval C with C=A^T
 */
MATRIX_TRANSPOSE(matrix_transpose, matrix_t, double)

/** @brief Transpose complex matrix A
 *
 * The matrix will be transposed.
 *
 * @param [in,out] A complex matrix
 *
 * @retval C with C=A^T
 */
MATRIX_TRANSPOSE(matrix_complex_transpose, matrix_complex_t, complex_t)

/** @}*/

/** \defgroup ev Eigenvalue problems
 *  @{
 */

/** @brief Compute eigenvalues and optionally eigenvectors of symmetric matrix A
 *
 * This function computes all eigenvalues and, optionally, eigenvectors of a
 * real symmetric matrix A.
 *
 * See dsyev.
 *
 * @param [in] A real matrix
 * @param [in] JOBZ 'N': only eigenvalues, 'V' eigenvalues and eigenvectors
 * @param [in] UPLO 'U': upper triangle part of A is stored; 'L': lower triangle part of A is stored
 * @param [in] w real matrix of dimension (dim,1) (i.e. a vector); the eigenvalues will be stored in w
 *
 * @retval 0 on success
 */
int eig_sym(matrix_t *A, char *JOBZ, char *UPLO, matrix_t *w)
{
    int info, lwork = -1, N = A->min;
    double workopt;
    double *work;

    dsyev_(JOBZ, UPLO, &N, A->M, &N, w->M, &workopt, &lwork, &info);
    if(info != 0)
        return info;

    lwork = workopt;
    work = malloc_cb(lwork*sizeof(double));
    if(work == NULL)
        return LIBHADES_ERROR_OOM;

    dsyev_(JOBZ, UPLO, &N, A->M, &N, w->M, work, &lwork, &info);

    free_cb(work);

    return info;
}

/** @brief Compute eigenvalues and optionally eigenvectors of Hermitian matrix A
 *
 * This function computes all eigenvalues and, optionally, eigenvectors of a
 * Hermitian symmetric matrix A.
 *
 * See zheev.
 *
 * @param [in] A complex matrix
 * @param [in] JOBZ 'N': only eigenvalues, 'V' eigenvalues and eigenvectors
 * @param [in] UPLO 'U': upper triangle part of A is stored; 'L': lower triangle part of A is stored
 * @param [in] w real matrix of dimension (dim,1) (i.e. a vector); the eigenvalues will be stored in w
 *
 * @retval 0 on success
 */
int eig_herm(matrix_complex_t *A, char *JOBZ, char *UPLO, matrix_t *w)
{
    int info, lwork = -1, N = A->min;
    complex_t workopt;
    complex_t *work = NULL;
    double *rwork;

    rwork = malloc_cb(MAX(1, 3*N-2)*sizeof(double));
    if(rwork == NULL)
        return LIBHADES_ERROR_OOM;

    zheev_(JOBZ, UPLO, &N, A->M, &N, w->M, &workopt, &lwork, rwork, &info);
    if(info != 0)
    {
        free_cb(rwork);
        return info;
    }

    lwork = CREAL(workopt);
    work = malloc_cb(lwork*sizeof(complex_t));
    if(work == NULL)
    {
        free_cb(rwork);
        return LIBHADES_ERROR_OOM;
    }

    zheev_(JOBZ, UPLO, &N, A->M, &N, w->M, work, &lwork, rwork, &info);

    free_cb(rwork);
    free_cb(work);

    return info;
}

/** @brief Compute eigenvalues and optionally eigenvectors of matrix A
 *
 * Compute for an N-by-N complex nonsymmetric matrix A, the eigen- values and,
 * optionally, right eigenvectors
 *
 * See zgeev.
 *
 * @param [in] A real matrix
 * @param [in] w list containing the eigenvalues of A
 * @param [in] vr if vr != NULL, right eigenvectors are computed and will be stored in vr; vr must be a complex matrix of dimension (dim,dim)
 * @param [in] vl if vl != NULL, lefft eigenvectors are computed and will be stored in vl; vl must be a complex matrix of dimension (dim,dim)
 *
 * @retval 0 on success
 */

/* Parameters */
int eig_complex_generic(matrix_complex_t *A, matrix_complex_t *w, matrix_complex_t *vl, matrix_complex_t *vr)
{
    int N = A->min;
    int lwork = -1;
    char jobvr = 'N';
    char jobvl = 'N';
    int info;
    complex_t *evr  = NULL;
    complex_t *evl  = NULL;
    complex_t *work = NULL;
    double *rwork   = NULL;
    complex_t wopt;

    /* if vr is not NULL, calculate right eigenvectors */
    if(vr != NULL)
    {
        jobvr = 'V';
        evr   = vr->M;
    }
    /* if vl is not NULL, calculate left eigenvectors */
    if(vl != NULL)
    {
        jobvl = 'V';
        evl   = vl->M;
    }

    /* get the optimal size for workspace work */
    zgeev_(&jobvl, &jobvr, &N, A->M, &N, w->M, evl, &N, evr, &N, &wopt, &lwork, rwork, &info);

    if(info != 0)
        return info;

    lwork = CREAL(wopt);

    rwork = malloc_cb(2*N*sizeof(double));
    work  = malloc_cb(lwork*sizeof(complex_t));

    if(rwork == NULL || work == NULL)
    {
        if(rwork != NULL)
            free(rwork);
        if(work != NULL)
            free(work);

        return LIBHADES_ERROR_OOM;
    }

    /* SUBROUTINE ZGEEV( JOBVL, JOBVR, N, A, LDA, W, VL, LDVL, VR, LDVR, WORK, LWORK, RWORK, INFO ) */
    zgeev_(
        &jobvl,     /* left eigenvectors of A are not computed */
        &jobvr,     /* calculate/don't calculate right eigenvectors */
        &N,         /* order of matrix A */
        A->M,       /* matrix A */
        &N,         /* LDA - leading dimension of A */
        w->M,       /* eigenvalues */
        evl,        /* left eigenvectors */
        &N,         /* leading dimension of array vl */
        evr,        /* eigenvectors */
        &N,         /* leading dimension of the array VR */
        work,       /* COMPLEX*16 array, dimension (LWORK) */
        &lwork,     /* dimension of the array WORK; LWORK >= max(1,2*N) */
        rwork,      /* (workspace) DOUBLE PRECISION array, dimension (2*N) */
        &info       /* 0 == success */
    );

    free_cb(work);
    free_cb(rwork);

    return info;
}

int eig_complex_vr(matrix_complex_t *M, complex_t lambda, matrix_complex_t *vr)
{
    matrix_complex_t *A = NULL, *b = NULL;
    const int rows    = M->rows;
    const int columns = M->columns;

    /* lapack */
    char trans = 'N';
    int info, lwork;
    complex_t work_size, *work = NULL;
    int m = rows+1, n = columns, nrhs = 1;
    int lda = m, ldb = MAX(m,n);

    if((A = matrix_complex_alloc(rows+1, columns)) == NULL)
        return LIBHADES_ERROR_OOM;

    if((b = matrix_complex_zeros(rows+1,1,NULL)) == NULL)
    {
        matrix_complex_free(A);
        return LIBHADES_ERROR_OOM;
    }
    matrix_set(b, rows,0, 1);

    for(int j = 0; j < columns; j++)
    {
        for(int i = 0; i < rows; i++)
            matrix_set(A, i, j, matrix_get(M, i,j));

        /* - lambda Id */
        matrix_set(A, j,j, matrix_get(A,j,j)-lambda);

        /* set nomalization condition */
        matrix_set(A, rows,j, 1);
    }

    /* determine work size */
    lwork = -1;
    zgels_(&trans, &m, &n, &nrhs, A->M, &lda, b->M, &ldb, &work_size, &lwork, &info);
    lwork = (int)CREAL(work_size);
    if((work = malloc_cb(lwork*sizeof(complex_t))) == NULL)
    {
        matrix_complex_free(A);
        matrix_complex_free(vr);
        return LIBHADES_ERROR_OOM;
    }

    /* calculate eigenvector */
    zgels_(
        &trans, /* find least squares solution of overdetermined system */
        &m,     /* number of rows of matrix A */
        &n,     /* number of columns of matrix A */
        &nrhs,  /* number of columns of matrix B */
        A->M,   /* matrix A (will be overwritten) */
        &lda,   /* leading dimension of A, LDA >= max(1,M) */
        b->M,   /* on entry: right hand side vectors, on exit: solution */
        &ldb,   /* leading dimension of B, LDB >= MAX(1,M,N) */
        work,   /* workspace */
        &lwork, /* dimension of work */
        &info   /* status */
    );

    free_cb(work);
    matrix_complex_free(A);

    for(int i = 0; i < rows; i++)
        matrix_set(vr, i,0, matrix_get(b, i,0));

    matrix_complex_free(b);

    return info;
}

/** @}*/

/** \defgroup exp Calculate matrix exponential
 *  @{
 */

/** macro to calculate matrix norm */
#define MATRIX_NORM(FUNCTION_NAME, MTYPE, LAPACK_FUNC) \
int FUNCTION_NAME(MTYPE *A, char norm_type, double *norm) \
{ \
    double *work = NULL; \
\
    if(norm_type == 'I') \
    { \
        work = malloc_cb(A->rows*sizeof(double)); \
        if(work == NULL) \
            return LIBHADES_ERROR_OOM; \
    } \
\
    *norm = LAPACK_FUNC(&norm_type, &A->rows, &A->columns, A->M, &A->rows, work); \
\
    if(work != NULL) \
        free_cb(work); \
\
    return 0; \
}

/** @brief Compute matrix norm for real matrix
 *
 * See dlange.
 *
 * Returns
 *      max(abs(A(i,j))), norm_type = 'M' or 'm'
 *      norm1(A),         norm_type = '1', 'O' or 'o'
 *      normI(A),         norm_type = 'I' or 'i'
 *      normF(A),         norm_type = 'F', 'f', 'E' or 'e'
 *
 * @param [in]  A real matrix
 * @param [in]  norm_type type of norm, e.g. 'F' for Frobenius norm
 * @param [out] norm value of norm
 *
 * @retval ret 0 if successfull, <0 otherwise
 */
MATRIX_NORM(matrix_norm, matrix_t, dlange_);

/** @brief Compute matrix norm for complex matrix
 *
 * See matrix_norm.
 *
 * @param [in]  A complex matrix
 * @param [in]  norm_type type of norm, e.g. 'F' for Frobenius norm
 * @param [out] norm value of norm
 *
 * @retval ret 0 if successfull, <0 otherwise
 */
MATRIX_NORM(matrix_complex_norm, matrix_complex_t, zlange_);

matrix_complex_t *matrix_complex_exp_taylor(matrix_complex_t *A, int order)
{
    const int rows = A->min;
    matrix_complex_t *C = matrix_complex_alloc(rows,rows);

    /* B = E+A/order */
    matrix_complex_t *B = matrix_complex_copy(A,NULL);
    matrix_complex_mult_scalar(B, 1./order);

    for(int i = 0; i < rows; i++)
        matrix_set(B, i,i, 1+matrix_get(B,i,i));

    for(int k = order-1; k > 0; k--)
    {
        matrix_complex_mult(A, B, 1./order, C);

        /* swap */
        {
            matrix_complex_t *temp;
            temp = C;
            C = B;
            B = temp;
        }

        for(int i = 0; i < rows; i++)
            matrix_set(B, i,i, 1+matrix_get(B,i,i));
    }

    matrix_complex_free(C);

    return B;
}

/** @}*/

/** \defgroup LA LU decomposition, inverting
 *  @{
 */

#define LU_DECOMPOSITION(FUNCTION_NAME, MATRIX_TYPE, XGETRF) \
int FUNCTION_NAME(MATRIX_TYPE *A, int ipiv[]) \
{ \
    int info; \
\
    XGETRF( \
        &A->rows,    /* M number of rows of A */ \
        &A->columns, /* N number of columns of A */ \
        A->M,        /* matrix A to be factored */ \
        &A->columns, /* LDA: leading dimension of A */ \
        ipiv,        /* pivot indices of dimension (min(M,N)) */ \
        &info \
    ); \
\
    return info; \
}

/** @brief Compute LU decomposition of real matrix A
 *
 * See dgetrf.
 *
 * The factorization has the form
 *    A = P * L * U
 * where P is a permutation matrix, L is lower triangular with unit
 * diagonal elements (lower trapezoidal if rows > columns), and U is upper
 * triangular (upper trapezoidal if m < n).
 *
 * @param [in,out] A real matrix
 * @param [out] ipiv pivot indices; array of dimension MIN(rows,columns)
 *
 * @retval INFO
 */
LU_DECOMPOSITION(matrix_lu_decomposition, matrix_t, dgetrf_)

/** @brief Compute LU decomposition of complex matrix A
 *
 * See matrix_lu_decomposition.
 *
 * @param [in,out] A complex matrix
 * @param [out] ipiv pivot indices; array of dimension MIN(rows,columns)
 *
 * @retval INFO
 */
LU_DECOMPOSITION(matrix_complex_lu_decomposition, matrix_complex_t, zgetrf_)

#define MATRIX_INVERT(FUNCTION_NAME, TYPE, MATRIX_TYPE, LU_DECOMPOSITION, XGETRI) \
int FUNCTION_NAME(MATRIX_TYPE *A) \
{ \
    int info, lwork, dim = A->min; \
    int *ipiv = NULL; \
    TYPE *work = NULL; \
    TYPE workopt; \
\
    ipiv = malloc_cb(dim*sizeof(int)); \
    if(ipiv == NULL) \
        return LIBHADES_ERROR_OOM; \
\
    info = LU_DECOMPOSITION(A, ipiv); \
    if(info != 0) \
        goto out; \
\
    lwork = -1; \
    XGETRI(&dim, A->M, &dim, ipiv, &workopt, &lwork, &info); \
    if(info != 0) \
        goto out; \
\
    lwork = (int)workopt; \
    work = malloc_cb(lwork*sizeof(TYPE)); \
    if(work == NULL) \
    { \
        info = LIBHADES_ERROR_OOM; \
        goto out; \
    } \
\
    XGETRI( \
        &dim,   /* order of matrix A */ \
        A->M,   /* factors L and U from LU decomposition */ \
        &dim,   /* LDA: leading dimension of A */ \
        ipiv,   /* pivot indices */ \
        work,   /* workspace of dimension LWORK */ \
        &lwork, /* length of work */ \
        &info \
    ); \
\
out: \
    free_cb(ipiv); \
    if(work != NULL) \
        free_cb(work); \
\
    return info; \
}

/** @brief Invert real matrix A
 *
 * The inverse of A is computed using the LU factorzation of A
 *
 * @param [in] A real matrix
 *
 * @retval INFO
 */
MATRIX_INVERT(matrix_invert, double, matrix_t, matrix_lu_decomposition, dgetri_)

/** @brief Invert complex matrix A
 *
 * See matrix_invert.
 *
 * @param [in] A complex matrix
 *
 * @retval INFO
 */
MATRIX_INVERT(matrix_complex_invert, complex_t, matrix_complex_t, matrix_complex_lu_decomposition, zgetri_)

#define MATRIX_SOLVE(FUNCTION_NAME, MATRIX_TYPE, LU_DECOMPOSITION, XGETRS) \
int FUNCTION_NAME(MATRIX_TYPE *A, MATRIX_TYPE *b) \
{ \
    int N = A->min; \
    char trans = 'N'; \
    int nrhs = b->columns; \
    int info = -1; \
    int *ipiv = malloc_cb(N*sizeof(int)); \
\
    if(ipiv == NULL) \
        return LIBHADES_ERROR_OOM; \
\
    LU_DECOMPOSITION(A, ipiv); \
    XGETRS(&trans, &N, &nrhs, A->M, &N, ipiv, b->M, &N, &info); \
\
    free_cb(ipiv); \
\
    return info; \
}

/** @brief Solve system of linear equations
 *
 * Solve the system of linear equations:
 *      A*x = b
 *
 * @param [in,out] A matrix
 * @param [in,out] b vector/matrix
 *
 * @retval INFO
 */

/** @}*/
MATRIX_SOLVE(matrix_solve, matrix_t, matrix_lu_decomposition, dgetrs_);

/** @brief Solve system of linear equations
 *
 * Solve the system of complex linear equations:
 *      A*x = b
 *
 * @param [in,out] A complex matrix
 * @param [in,out] b complex vector/matrix
 *
 * @retval INFO
 */

/** @}*/
MATRIX_SOLVE(matrix_complex_solve, matrix_complex_t, matrix_complex_lu_decomposition, zgetrs_);

/*
static void _cblas_zaxpy(const int N, const double alpha, const void *X, const int incX, void *Y, const int incY)
{
    complex_t beta = alpha;
    cblas_zaxpy(N, &beta, X, incX, Y, incY);
}
*/

/** @brief Calculate dot product of vectors x and y
 *
 * Calculate dot product of first column of x,y. If x and y have different
 * rows, the minimum is used.
 *
 * @param [in] x vector
 * @param [in] y vector
 * @retval x*y
 */
double vector_dot(matrix_t *x, matrix_t *y)
{
    int incx = 1;
    int incy = 1;
    int N = MIN(x->rows, y->rows);

    return ddot_(&N, x->M, &incx, y->M, &incy);
}

/** @brief Calculate dot product of vectors x and y
 *
 * Calculate dot product of first column of x,y. If x and y have different
 * rows, the minimum is used.
 *
 * @param [in] x vector
 * @param [in] y vector
 * @retval x*y
 */
complex_t vector_complex_dot(matrix_complex_t *x, matrix_complex_t *y)
{
    /*
    int incx = 1;
    int incy = 1;
    int N = MIN(x->rows, y->rows);
    complex_t z = 0;

    zdotc_(&z, &N, x->M, &incx, y->M, &incy);
    return z;
    */

    int N = MIN(x->rows, y->rows);
    complex_t z = 0;
    for(int i = 0; i < N; i++)
        z += matrix_get(x,i,0)*matrix_get(y,i,0);
    return z;
}

#define MATRIX_GET_COLUMN(FUNCTION_NAME, TYPE, MATRIX_TYPE) \
MATRIX_TYPE *FUNCTION_NAME(MATRIX_TYPE *A, int i) \
{ \
    const int rows = A->rows; \
    MATRIX_TYPE *v = malloc_cb(sizeof(MATRIX_TYPE)); \
    if(v == NULL) \
        return NULL; \
\
    v->rows    = rows; \
    v->columns = 1; \
    v->min     = 1; \
    v->size    = rows; \
    v->type    = 0; \
    v->view    = 1; \
    v->M       = &A->M[i*rows]; \
\
    return v; \
}

/** @brief Get i-th column of matrix A
 *
 * @param [in] A matrix
 * @param [in] i column number
 * @retval x*y
 */
MATRIX_GET_COLUMN(matrix_get_column,         double,    matrix_t);

/** @brief Get i-th column of matrix A
 *
 * @param [in] A matrix
 * @param [in] i column number
 * @retval x*y
 */
MATRIX_GET_COLUMN(matrix_complex_get_column, complex_t, matrix_complex_t);

/** \defgroup sparse Functions for sparse matrices
 *  @{
 */

#ifdef SUPPORT_SPARSE

/** @brief Calculate eigenvalues of a sparse complex matrix
 *
 * @param [in] N     number of columns/rows of matrix
 * @param [in] nev   number of eigenvalues to compute
 * @param [in] which LM (largest magnitude), SM (smallest magnitude), LR (largest real part), SR (smallest real part), LI (largest imaginary part), SI (smallest imaginary part)
 * @param [in] Av callback function that implements the matrix-vector operation Av; the input vector is given as in, the vector Av must be written in out
 * @param [in,out] d on exit d contains the Rith approximations (must be of length nev+1)
 * @param [in] mxiter maximum number of Arnoldi update iterations allowed
 * @param [in] tol relative accuracy of the Ritz value
 * @param [in] data pointer that is given to callback function Av
 *
 * @retval 0 if successful
 */
int sparse_complex_eig(int N, int nev, char *which, void (*Av)(int N, complex_t *in, complex_t *out, void *data), complex_t *d, int mxiter, double tol, void *data)
{
    int ret = LIBHADES_ERROR_OOM;
    int info = 0;
    int ido = 0;
    char *bmat = "I";          /* standard eigenproblem */
    int ncv = MIN(2*nev+2, N);

    int ishift = 1;
    int mode = 1;
    int iparam[11] = { ishift, 0, mxiter, 1, 0, 0, mode, 0, 0, 0, 0 };

    int ipntr[14];
    int lworkl = ncv*(3*ncv + 5);
    int rvec = 0;
    char *howmny = "P";

    complex_t *workd = NULL, *workl = NULL, *resid = NULL, *v = NULL, *workev = NULL;
    int *select = NULL;
    double *rwork = NULL;

    /* allocate memory */
    ret = LIBHADES_ERROR_OOM;

    workd = malloc_cb(3*N*sizeof(complex_t));
    if(workd == NULL)
        goto out;
    workl = malloc_cb(lworkl*sizeof(complex_t));
    if(workl == NULL)
        goto out;
    rwork = malloc_cb(ncv*sizeof(double));
    if(rwork == NULL)
        goto out;
    resid  = malloc_cb(N*sizeof(complex_t));
    if(resid == NULL)
        goto out;
    v = malloc_cb(N*ncv*sizeof(complex_t));
    if(v == NULL)
        goto out;
    select = malloc_cb(ncv*sizeof(int));
    if(select == NULL)
        goto out;
    workev = malloc_cb((2*ncv)*sizeof(complex_t));
    if(workev == NULL)
        goto out;

    /* loop */
    while(1)
    {
        /* http://www.caam.rice.edu/software/ARPACK/UG/node138.html */
        znaupd_(
            &ido, bmat, &N, which, &nev, &tol, resid, &ncv, v, &N, iparam,
            ipntr, workd, workl, &lworkl, rwork, &info, strlen(bmat),
            strlen(which)
        );

        if(ido == 1 || ido == -1)
            Av(N, &workd[ipntr[0]-1], &workd[ipntr[1]-1], data);
        else if(ido == 99)
            break;
        else
        {
            ret = ido;
            goto out;
        }
    }

    if(info != 0)
    {
        ret = info;
        goto out;
    }

    /* http://www.mathkeisan.com/usersguide/man/zneupd.html */
    zneupd_(
        &rvec, howmny, select, d, NULL, &N, NULL, workev, bmat, &N, which,
        &nev, &tol, resid, &ncv, v, &N, iparam, ipntr, workd, workl, &lworkl,
        rwork, &info, strlen(howmny), strlen(bmat), strlen(which)
    );

    ret = info;

out:
    if(resid != NULL)
        free_cb(resid);
    if(v != NULL)
        free_cb(v);
    if(workd != NULL)
        free_cb(workd);
    if(workl != NULL)
        free_cb(workl);
    if(rwork != NULL)
        free_cb(rwork);
    if(select != NULL)
        free_cb(select);
    if(workev != NULL)
        free_cb(workev);

    return ret;
}

#endif
/** @}*/

/** \defgroup io Save/load matrices
 *  @{
 */

#define MATRIX_LOAD_FROM_STREAM(FUNCTION_NAME, MATRIX_TYPE, TYPE, ALLOC, TRANSPOSE, IS_COMPLEX) \
MATRIX_TYPE *FUNCTION_NAME(FILE *stream, int *ret) \
{ \
    MATRIX_TYPE *M; \
    uint16_t len; \
    int rows, columns, fortran_order, is_complex; \
    char header[10] = { 0 }; \
    char dict[2048] = { 0 }; \
\
    if(ret != NULL) \
        *ret = 0; \
\
    /* read magic string, major and minor number */ \
    fread(header, 8, 1, stream); \
    if(memcmp(header, "\x93NUMPY\x01\x00", 8) != 0) \
    { \
        if(ret != NULL) \
            *ret = LIBHADES_ERROR_HEADER; \
        return NULL; \
    } \
\
    /* read length of dict */ \
    fread(&len, sizeof(uint16_t), 1, stream); \
\
    if(len >= sizeof(dict)/sizeof(dict[0])) \
    { \
        if(ret != NULL) \
            *ret = LIBHADES_ERROR_INV_LENGTH; \
        return NULL; \
    } \
\
    fread(dict, sizeof(char), len, stream); \
\
    if(npy_dict_get_fortran_order(dict, &fortran_order) != 0) \
    { \
        if(ret != NULL) \
            *ret = LIBHADES_ERROR_ORDER; \
        return NULL; \
    } \
\
    if(npy_dict_get_shape(dict, &rows, &columns) != 0) \
    { \
        if(ret != NULL) \
            *ret = LIBHADES_ERROR_SHAPE; \
        return NULL; \
    } \
\
    if(npy_dict_get_descr(dict, &is_complex) != 0) \
    { \
        if(ret != NULL) \
            *ret = LIBHADES_ERROR_DESCR; \
        return NULL; \
    } \
\
    if(is_complex != IS_COMPLEX) \
    { \
        if(ret != NULL) \
            *ret = LIBHADES_ERROR_FORMAT; \
        return NULL; \
    } \
\
    M = ALLOC(rows,columns); \
    fread(M->M, sizeof(TYPE), rows*columns, stream); \
\
    if(!fortran_order) \
        TRANSPOSE(M); \
\
    M->min  = MIN(rows,columns); \
    M->size = (size_t)rows*(size_t)columns; \
    M->view = 0; \
    M->type = 0; \
\
    return M; \
}

/** @brief Load real matrix from stream
 *
 * Load real matrix A from a stream. This function will also allocate memory
 * for the matrix.
 *
 * If error != NULL, error will be set to:
 *  0                         if successful
 *  LIBHADES_ERROR_HEADER     if magic or major/minor number is invalid
 *  LIBHADES_ERROR_INV_LENGTH if length of dictionary is invalid (too long)
 *  LIBHADES_ERROR_ORDER      if order is invalid (Fortran/C order)
 *  LIBHADES_ERROR_SHAPE      if shape is invalid (rows/columns)
 *  LIBHADES_ERROR_DESCR      if dtype is wrong/not supported
 *  LIBHADES_ERROR_FORMAT     if wrong format (real instead of complex)
 *  0 rows/columns wrong
 *
 * @param [in] stream file handle of a opened file
 * @param [out] error error code
 * @retval A matrix
 * @retval NULL if an error occured
 */
MATRIX_LOAD_FROM_STREAM(matrix_load_from_stream, matrix_t, double, matrix_alloc, matrix_transpose, 0);

/** @brief Load complex matrix from stream
 *
 * Load complex matrix A from a stream. This function will also allocate memory
 * for the matrix.
 *
 * If error != NULL, error will be set to:
 *  0                         if successful
 *  LIBHADES_ERROR_HEADER     if magic or major/minor number is invalid
 *  LIBHADES_ERROR_INV_LENGTH if length of dictionary is invalid (too long)
 *  LIBHADES_ERROR_ORDER      if order is invalid (Fortran/C order)
 *  LIBHADES_ERROR_SHAPE      if shape is invalid (rows/columns)
 *  LIBHADES_ERROR_DESCR      if dtype is wrong/not supported
 *  LIBHADES_ERROR_FORMAT     if wrong format (complex instead of real)
 *  0 rows/columns wrong
 *
 * @param [in] stream file handle of a opened file
 * @param [out] error error code
 * @retval A matrix
 * @retval NULL if an error occured
 */
MATRIX_LOAD_FROM_STREAM(matrix_complex_load_from_stream, matrix_complex_t, complex_t, matrix_complex_alloc, matrix_complex_transpose, 1);

#define MATRIX_LOAD(FUNCTION_NAME, MATRIX_TYPE, LOAD_FUNCTION) \
MATRIX_TYPE *FUNCTION_NAME(const char *filename, int *ret) \
{ \
    FILE *stream; \
    MATRIX_TYPE *M; \
\
    if((stream = fopen(filename, "r")) == NULL) \
    { \
        if(ret != NULL) \
            *ret = LIBHADES_ERROR_IO; \
        return NULL; \
    } \
\
    M = LOAD_FUNCTION(stream, ret); \
\
    fclose(stream); \
\
    return M; \
}

/** @brief Load real matrix from file
 *
 * Load real matrix A from file given by filename. This function will also
 * allocate memory for the matrix. See \ref matrix_load_from_stream for errors.
 *
 * @param [in] filename path to the file
 * @param [out] error error code
 * @retval A matrix
 * @retval NULL if an error occured
 */
MATRIX_LOAD(matrix_load, matrix_t, matrix_load_from_stream);

/** @brief Load complex matrix from file
 *
 * Load complex matrix A from file given by filename. This function will also
 * allocate memory for the matrix. See \ref matrix_complex_load_from_stream for
 * errors.
 *
 * @param [in] filename path to the file
 * @param [out] error error code
 * @retval A matrix
 * @retval NULL if an error occured
 */
MATRIX_LOAD(matrix_complex_load, matrix_complex_t, matrix_complex_load_from_stream);

#define MATRIX_SAVE_TO_STREAM(FUNCTION_NAME, TYPE, MATRIX_TYPE, DTYPE) \
void FUNCTION_NAME(MATRIX_TYPE *M, FILE *stream) \
{ \
    char d_str[512] = { 0 }; \
    uint16_t len = 0; \
    const int rows = M->rows, columns = M->columns; \
\
    /* write magic string, major number and minor number */ \
    fwrite("\x93NUMPY\x01\x00", sizeof(char), 8, stream); \
\
    /* write length of header and header */ \
    snprintf(d_str, sizeof(d_str)/sizeof(d_str[0]), "{'descr': '%s', 'fortran_order': True, 'shape': (%d, %d), }", DTYPE, rows, columns); \
\
    len = strlen(d_str); \
\
    fwrite(&len,  sizeof(len),  1,   stream); \
    fwrite(d_str, sizeof(char), len, stream); \
\
    /* write matrix */ \
    fwrite(M->M, sizeof(TYPE), M->size, stream); \
}

/** @brief Save real matrix A to stream
 *
 * Save real matrix A to a file handle given by stream. The datatype
 * corresponds to Numpy's npy file format.
 *
 * @param [in] A      matrix to be dumped to file
 * @param [in] stream file handle of opened file
 */
MATRIX_SAVE_TO_STREAM(matrix_save_to_stream, double, matrix_t, "<f8");

/** @brief Save complex matrix A to stream
 *
 * Save complex matrix A to a file handle given by stream. The datatype
 * corresponds to Numpy's npy file format.
 *
 * @param [in] A      matrix to be dumped to file
 * @param [in] stream file handle of opened file
 */
MATRIX_SAVE_TO_STREAM(matrix_complex_save_to_stream, complex_t, matrix_complex_t, "<c16");

#define MATRIX_SAVE(FUNCTION_NAME, MATRIX_TYPE, SAVE_FUNCTION) \
int FUNCTION_NAME(MATRIX_TYPE *M, const char *filename) \
{ \
    FILE *stream = fopen(filename, "w"); \
    if(stream == NULL) \
        return LIBHADES_ERROR_IO; \
\
    SAVE_FUNCTION(M, stream); \
\
    fclose(stream); \
\
    return 0; \
};

/** @brief Save real matrix A to file
 *
 * Save real matrix A to file given by filename. The datatype corresponds to
 * Numpy's .npy file format.
 *
 * @param [in] A        matrix to be dumped to file
 * @param [in] filename path to the file
 * @retval 0 if successful
 * @retval LIBHADES_ERROR_IO if file could not be opened
 */
MATRIX_SAVE(matrix_save, matrix_t, matrix_save_to_stream);

/** @brief Save complex matrix A to file
 *
 * Save complex matrix A to file given by filename. The datatype corresponds to
 * Numpy's .npy file format.
 *
 * @param [in] A        matrix to be dumped to file
 * @param [in] filename path to the file
 * @retval 0 if successful
 * @retval LIBHADES_ERROR_IO if file could not be opened
 */
MATRIX_SAVE(matrix_complex_save, matrix_complex_t, matrix_complex_save_to_stream);

/** @}*/


#ifndef TTT_BOARD_H
#define TTT_BOARD_H

#include <SDL_rect.h>

#include <gsl/util>

#include <array>
#include <optional>

namespace ttt
{
	class Game;

	enum class CellState
	{
		empty, x, o
	};

	class Board final
	{
	public:
		SDL_FPoint pos;
		SDL_FPoint cellSize;
		static constexpr int size{3};

		CellState preview{CellState::empty};
		SDL_Point previewCell{};

		Board(const SDL_FPoint &pos, const SDL_FPoint &cellSize, const Game &game) noexcept;

		void update() noexcept;
		void render() const noexcept;

		[[nodiscard]] CellState getCellState(const SDL_Point &pos) const noexcept;
		void setCellState(const SDL_Point &pos, CellState state) noexcept;
		void clear() noexcept;

		[[nodiscard]] std::optional<SDL_Point> getCell(const SDL_Point &point) const noexcept;
		[[nodiscard]] bool isEmpty() const noexcept;

	private:
		const Game &game;
		std::array<CellState, 9> grid{};

		static constexpr float lineWidth{16.0f};
		float previewOffset{0.0f};
		bool shrinkPreview{false};

		const SDL_Rect lineSrc{0, 0, 8, 576};
		const SDL_Rect xSrc{8, 0, 48, 48};
		const SDL_Rect oSrc{8, 48, 48, 48};
	};

	inline CellState Board::getCellState(const SDL_Point &pos) const noexcept
	{
		return gsl::at(grid, pos.x + pos.y * size);
	}

	inline void Board::setCellState(const SDL_Point &pos, CellState state) noexcept
	{
		gsl::at(grid, pos.x + pos.y * size) = state;
	}
}

#endif


/* multilarge_nlinear/dummy.c
 *
 * Copyright (C) 2016 Patrick Alken
 *
 * 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 3 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/* dummy linear solver */

#include <config.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_linalg.h>
#include <gsl/gsl_multilarge_nlinear.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_permutation.h>

static void *dummy_alloc (const size_t n, const size_t p);
static int dummy_init(const void * vtrust_state, void * vstate);
static int dummy_presolve(const double mu, const void * vtrust_state, void * vstate);
static int dummy_solve(const gsl_vector * g, gsl_vector *x,
                       const  void * vtrust_state, void *vstate);
static int dummy_rcond(double * rcond, const gsl_matrix * JTJ, void * vstate);
static int dummy_covar(const gsl_matrix * JTJ, gsl_matrix * covar, void * vstate);

static void *
dummy_alloc (const size_t n, const size_t p)
{
  (void) n;
  (void) p;
  return NULL;
}

static void
dummy_free(void *vstate)
{
  (void) vstate;
}

static int
dummy_init(const void * vtrust_state, void * vstate)
{
  (void) vtrust_state;
  (void) vstate;
  return GSL_SUCCESS;
}

static int
dummy_presolve(const double mu, const void * vtrust_state, void * vstate)
{
  (void) mu;
  (void) vtrust_state;
  (void) vstate;
  return GSL_SUCCESS;
}

static int
dummy_solve(const gsl_vector * g, gsl_vector *x,
            const void * vtrust_state, void *vstate)
{
  (void) g;
  (void) x;
  (void) vtrust_state;
  (void) vstate;
  return GSL_SUCCESS;
}

static int
dummy_rcond(double * rcond, const gsl_matrix * JTJ, void * vstate)
{
  (void) vstate;
  (void) rcond;
  (void) JTJ;
  *rcond = 0.0;
  return GSL_SUCCESS;
}

static int
dummy_covar(const gsl_matrix * JTJ, gsl_matrix * covar, void * vstate)
{
  (void) vstate;
  (void) JTJ;
  gsl_matrix_set_zero(covar);
  return GSL_SUCCESS;
}

static const gsl_multilarge_nlinear_solver dummy_type =
{
  "dummy",
  dummy_alloc,
  dummy_init,
  dummy_presolve,
  dummy_solve,
  dummy_rcond,
  dummy_covar,
  dummy_free
};

const gsl_multilarge_nlinear_solver *gsl_multilarge_nlinear_solver_none = &dummy_type;


#ifndef CWANNIER_PARSESCF_H
#define CWANNIER_PARSESCF_H

#include <stdbool.h>
#include <stdio.h>
#include <gsl/gsl_matrix.h>
#include "bstrlib/bstrlib.h"

#define CWANNIER_PARSESCF_OK 0
#define CWANNIER_PARSESCF_ERR 1

int ParseSCF(char *filePath, double *num_electrons, double *alat, gsl_matrix *R);

void get_b_row(gsl_matrix *R, int row, bstring line);

#endif // CWANNIER_PARSESCF_H


/* ****************************************************************** **
**    OpenSees - Open System for Earthquake Engineering Simulation    **
**          Pacific Earthquake Engineering Research Center            **
**                                                                    **
**                                                                    **
** (C) Copyright 1999, The Regents of the University of California    **
** All Rights Reserved.                                               **
**                                                                    **
** Commercial use of this program without express permission of the   **
** University of California, Berkeley, is strictly prohibited.  See   **
** file 'COPYRIGHT'  in main directory for information on usage and   **
** redistribution,  and for a DISCLAIMER OF ALL WARRANTIES.           **
**                                                                    **
** Developed by:                                                      **
**   Frank McKenna (fmckenna@ce.berkeley.edu)                         **
**   Gregory L. Fenves (fenves@ce.berkeley.edu)                       **
**   Filip C. Filippou (filippou@ce.berkeley.edu)                     **
**                                                                    **
** ****************************************************************** */

// $Revision: 1.1.1.1 $
// $Date: 2000-09-15 08:23:29 $
// $Source: /usr/local/cvs/OpenSees/SRC/system_of_eqn/linearSOE/petsc/ActorPetscSOE.h,v $

// File: ~/system_of_eqn/linearSOE/petsc/ActorPetscSOE.h
//
// Written: fmk & om
// Created: 7/98
// Revision: A
//
// Description: This file contains the class definition for ActorPetscSOE
// ActorPetscSOE is a subclass of LinearSOE. It uses the LAPACK storage
// scheme to store the components of the A matrix, which is a full matrix.

// What: "@(#) ActorPetscSOE.h, revA"

#ifndef ActorPetscSOE_h
#define ActorPetscSOE_h

#include <LinearSOE.h>
#include <Vector.h>

// extern "C" {
#include <petsc.h>
// }

class PetscSolver;
class PetscSOE;

class ActorPetscSOE
{
  public:
    ActorPetscSOE(PetscSolver &theSolver, int blockSize);

    ~ActorPetscSOE();
    int run(void);

  protected:

  private:
    MPI_Comm theComm;
    PetscSOE *theSOE;  // the local portion of the SOE
    PetscSolver *theSolver; // created locally via data from process 0
    int myRank;
    int recvData[3];
    void *recvBuffer;
    int numProcessors;
};

#endif


/*
 *
 *
 *
 */

#include <inttypes.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <cblas.h>
#include <string.h> // memcpy
#include <math.h>
#include <stdarg.h>

#include "cgraph.h"
#include "cg_operation.h"
#include "cg_types.h"
#include "cg_variables.h"
#include "cg_errors.h"
#include "cg_constants.h"
#include "cg_enums.h"
#include "cg_factory.h"
#include "cg_math.h"
#include "cg_ops.h"

void cg_assert(int cond, const char * rawcond, const char * fmt, ...)
{
	if(cond)
		return;

	char temp[1024];
	va_list vl;
	va_start(vl, fmt);
	vsprintf(temp, fmt, vl);
	va_end(vl);
	fprintf(stdout, "Fatal error, assertion failed: %s\n", rawcond);
	fprintf(stdout, temp);
	fprintf(stdout, "\n");

	exit(-1);
}

#define CHECK_RESULT(node) \
if(node->error != NULL){\
	return node;\
}

/*
 * Works only with constant types
 */
CGNode* copyNode(CGNode* node){
	CGNode* n = calloc(1, sizeof(CGNode));
	if(node->type != CGNT_CONSTANT){
		fprintf(stderr, "Call to copyNode with a non-constant node.\n... This should not happen, but who knows these days.\n");
		fprintf(stderr, "copy node with nodetype = %d\n", node->type);
		exit(-1);
	}

	n->type = CGNT_CONSTANT;
	n->constant = calloc(1, sizeof(CGPConstant));
	n->constant->type = node->constant->type;
	n->result = NULL;
	n->diff = NULL;

	switch(node->constant->type){
		case CGVT_DOUBLE: {
			CGDouble* d = calloc(1, sizeof(CGDouble));
			d->value = ((CGDouble*)node->constant->value)->value;

			n->constant->value = d;
			break;
		}

		case CGVT_VECTOR: {
			CGVector* V = calloc(1, sizeof(CGVector));
			CGVector* src = (CGVector*)node->constant->value;

			V->len = src->len;
			if(src->data != NULL) {
				V->data = calloc(V->len, sizeof(cg_float));
				memcpy(V->data, src->data, V->len * sizeof(cg_float));
			}

#ifdef CG_USE_OPENCL
	        V->buf = NULL;
	        V->loc = CG_DATALOC_HOST_MEM;
#endif
			n->constant->value = V;
			break;
		}

		case CGVT_MATRIX: {
			CGMatrix* M = calloc(1, sizeof(CGMatrix));
			CGMatrix* src = (CGMatrix*)node->constant->value;

			uint64_t size = src->cols * src->rows;

			M->rows = src->rows;
			M->cols = src->cols;

			if(src->data != NULL) {
				M->data = calloc(size, sizeof(cg_float));
				memcpy(M->data, src->data, size*sizeof(cg_float));
			}

#ifdef CG_USE_OPENCL
			M->buf = NULL;
			M->loc = CG_DATALOC_HOST_MEM;
#endif
			n->constant->value = M;
			break;
		}
	}

	return n;
}

// @Deprecated @NotUsed
void* copyNodeValue(CGNode* node){
	if(node->type != CGNT_CONSTANT){
		fprintf(stderr, "Call to copyNodeValue with a non-constant node.\n... This should not happen, but who knows these days.");
		exit(-1);
	}

	switch(node->constant->type){
		case CGVT_DOUBLE: {
			CGDouble* d = calloc(1, sizeof(CGDouble));
			d->value = ((CGDouble*)node->constant->value)->value;

			return d;
		}

		case CGVT_VECTOR: {
			CGVector* V = calloc(1, sizeof(CGVector));
			CGVector* src = (CGVector*)node->constant->value;

			V->len = src->len;
			V->data = calloc(V->len, sizeof(cg_float));

			memcpy(V->data, src->data, V->len*sizeof(cg_float));
			return V;
		}

		case CGVT_MATRIX: {
			CGMatrix* M = calloc(1, sizeof(CGMatrix));
			CGMatrix* src = (CGMatrix*)node->constant->value;

			uint64_t size = src->cols * src->rows;

			M->rows = src->rows;
			M->cols = src->cols;
			M->data = calloc(size, sizeof(cg_float));

			memcpy(M->data, src->data, size*sizeof(cg_float));
			return M;
		}
	}
}

/*
 * @Deprecated
 */
void* copyRNodeValue(CGResultNode* node){
	switch(node->type){
		case CGVT_DOUBLE: {
			CGDouble* d = calloc(1, sizeof(CGDouble));
			d->value = ((CGDouble*)node->value)->value;

			return d;
		}

		case CGVT_VECTOR: {
			CGVector* V = calloc(1, sizeof(CGVector));
			CGVector* src = (CGVector*)node->value;

			V->len = src->len;
			V->data = calloc(V->len, sizeof(cg_float));

			memcpy(V->data, src->data, V->len*sizeof(cg_float));
			return V;
		}

		case CGVT_MATRIX: {
			CGMatrix* M = calloc(1, sizeof(CGMatrix));
			CGMatrix* src = (CGMatrix*)node->value;

			uint64_t size = src->cols * src->rows;

			M->rows = src->rows;
			M->cols = src->cols;
			M->data = calloc(size, sizeof(cg_float));

			memcpy(M->data, src->data, size*sizeof(cg_float));
			return M;
		}
	}
}

CGResultNode* copyResultNode(CGResultNode* node){
	CGResultNode* res = calloc(1, sizeof(CGResultNode));

	switch(node->type){
		case CGVT_DOUBLE: {
			CGDouble* d = calloc(1, sizeof(CGDouble));
			CGDouble* d2 = (CGDouble*)node->value;
			d->value = d2->value;

			res->value = d;
			break;
		}

		case CGVT_VECTOR: {
			CGVector* V = calloc(1, sizeof(CGVector));
			CGVector* src = (CGVector*)node->value;

			V->len = src->len;
			V->data = calloc(V->len, sizeof(cg_float));

#ifdef CG_USE_OPENCL
			V->buf = src->buf;
#endif
			memcpy(V->data, src->data, V->len*sizeof(cg_float));

			res->value = V;
			break;
		}

		case CGVT_MATRIX: {
			CGMatrix* M = calloc(1, sizeof(CGMatrix));
			CGMatrix* src = (CGMatrix*)node->value;

			uint64_t size = src->cols * src->rows;

			M->rows = src->rows;
			M->cols = src->cols;
			M->data = calloc(size, sizeof(cg_float));
#ifdef CG_USE_OPENCL
            M->buf = src->buf;
#endif

			memcpy(M->data, src->data, size*sizeof(cg_float));

			res->value = M;
			break;
		}
	}
	res->type = node->type;

	return res;
}

CGMatrix* vectorToMatrix(CGVector* v){
	CGMatrix* m = calloc(1, sizeof(CGMatrix));
	m->rows = v->len;
	m->cols = 1;
	m->data = v->data;

	return m;
}

/*
 * Computational Graph traversing
 */

CGResultNode* processUnaryOperation(CGraph* graph, CGUnaryOperationType type, CGNode* uhs, CGNode* parentNode){
	CGVarType uhsType = CGVT_DOUBLE;
	void* uhsValue = NULL;
	CGResultNode* newres = NULL;

	CGResultNode* lhsResult = computeCGNode(graph, uhs);
	CHECK_RESULT(lhsResult)
	uhsType = lhsResult->type;
	uhsValue = lhsResult->value;

	switch(type){
		case CGUOT_EXP:{
			if(uhsType == CGVT_DOUBLE){
				newres = expD((CGDouble*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if(uhsType == CGVT_VECTOR){
				newres = expV((CGVector*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if(uhsType == CGVT_MATRIX){
				newres = expM((CGMatrix*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}
			break;
		}

		case CGUOT_LOG:{
			if(uhsType == CGVT_DOUBLE){
				newres = logD((CGDouble*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if(uhsType == CGVT_VECTOR){
				newres = logV((CGVector*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if(uhsType == CGVT_MATRIX){
				newres = logM((CGMatrix*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}
			break;
		}

		case CGUOT_MINUS:{
			if(uhsType == CGVT_DOUBLE){
				CGDouble* rhs = calloc(1, sizeof(CGDouble));
				rhs->value = -1;

				CGResultNode* res = mulDD((CGDouble*)uhsValue, rhs, graph, parentNode);

				freeDoubleValue(rhs);

				parentNode->result = res;
				return res;
			}

			if(uhsType == CGVT_VECTOR){
				CGDouble* lhs = calloc(1, sizeof(CGDouble));
				lhs->value = -1;

				CGResultNode* res = mulDV(lhs, (CGVector*)uhsValue, graph, parentNode);

				free(lhs);
				parentNode->result = res;
				return res;
			}

			if(uhsType == CGVT_MATRIX){
				CGDouble* lhs = calloc(1, sizeof(CGDouble));
				lhs->value = -1;
				CGResultNode* res = mulDM(lhs, (CGMatrix*)uhsValue, graph, parentNode);

				free(lhs);

				parentNode->result = res;
				return res;
			}
			break;
		}

		case CGUOT_SIN:{
			if(uhsType == CGVT_DOUBLE){
				newres = sinD((CGDouble*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if(uhsType == CGVT_VECTOR){
				newres = sinV((CGVector*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if(uhsType == CGVT_MATRIX){
				newres = sinM((CGMatrix*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}
			break;
		}

		case CGUOT_COS:{
			if(uhsType == CGVT_DOUBLE){
				newres = cosD((CGDouble*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if(uhsType == CGVT_VECTOR){
				newres = cosV((CGVector*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if(uhsType == CGVT_MATRIX){
				newres = cosM((CGMatrix*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}
			break;
		}

		case CGUOT_TAN:{
			if(uhsType == CGVT_DOUBLE){
				newres = tanD((CGDouble*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if(uhsType == CGVT_VECTOR){
				newres = tanV((CGVector*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if(uhsType == CGVT_MATRIX){
				newres = tanM((CGMatrix*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}
			break;
		}

		case CGUOT_TANH:{
			if(uhsType == CGVT_DOUBLE){
				newres = tanhD((CGDouble*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if(uhsType == CGVT_VECTOR){
				newres = tanhV((CGVector*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if(uhsType == CGVT_MATRIX){
				newres = tanhM((CGMatrix*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}
			break;
		}

		case CGUOT_INV:{
			char msg[MAX_ERR_FMT_LEN];
			snprintf(msg, MAX_ERR_FMT_LEN, "Operation `%s` is not implemented/supported", getUnaryOperationTypeString(type));
			newres = returnResultError(graph, CGET_OPERATION_NOT_IMPLEMENTED, parentNode, msg);
		}

		case CGUOT_TRANSPOSE:{

			if(uhsType == CGVT_DOUBLE){
				newres = transposeD((CGDouble*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if(uhsType == CGVT_VECTOR){
				newres = transposeV((CGVector*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if(uhsType == CGVT_MATRIX){
				newres = transposeM((CGMatrix*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			break;
		}

		case CGUOT_RELU:{
			if(uhsType == CGVT_DOUBLE){
				newres = reluD((CGDouble*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if(uhsType == CGVT_VECTOR){
				newres = reluV((CGVector*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if(uhsType == CGVT_MATRIX){
				newres = reluM((CGMatrix*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			break;
		}

        case CGUOT_SOFTPLUS:{
			if(uhsType == CGVT_DOUBLE){
				newres = softplusD((CGDouble*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if(uhsType == CGVT_VECTOR){
				newres = softplusV((CGVector*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if(uhsType == CGVT_MATRIX){
				newres = softplusM((CGMatrix*)uhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			break;
        }
	}
	char msg[MAX_ERR_FMT_LEN];
	snprintf(msg, MAX_ERR_FMT_LEN, "Operation [%s %s] cannot be applied", getVariableTypeString(uhsType), getUnaryOperationTypeString(type));
	newres = returnResultError(graph, CGET_INCOMPATIBLE_ARGS_EXCEPTION, parentNode, msg);
	return newres;
}

CGResultNode* processBinaryOperation(CGraph* graph, CGBinaryOperationType type, CGNode* lhs, CGNode* rhs, CGNode* parentNode){
	CGVarType lhsType = CGVT_DOUBLE;
	CGVarType rhsType = CGVT_DOUBLE;
	CGResultNode* newres = NULL;
	void* lhsValue = NULL;
	void* rhsValue = NULL;

	CGResultNode* lhsResult = computeCGNode(graph, lhs);
	CHECK_RESULT(lhsResult)
	lhsType = lhsResult->type;
	lhsValue = lhsResult->value;

	CGResultNode* rhsResult = computeCGNode(graph, rhs);
	CHECK_RESULT(rhsResult)
	rhsType = rhsResult->type;
	rhsValue = rhsResult->value;

	switch(type){
		case CGBOT_ADD:{
			if((lhsType == CGVT_DOUBLE) && (rhsType == CGVT_DOUBLE)){
				newres = addDD((CGDouble*)lhsValue, (CGDouble*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_VECTOR) && (rhsType == CGVT_DOUBLE)){
				newres = addVD((CGVector*)lhsValue, (CGDouble*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_DOUBLE) && (rhsType == CGVT_VECTOR)){
				newres = addVD((CGVector*)rhsValue, (CGDouble*)lhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_MATRIX) && (rhsType == CGVT_DOUBLE)){
				newres = addMD((CGMatrix*)lhsValue, (CGDouble*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_DOUBLE) && (rhsType == CGVT_MATRIX)){
				newres = addMD((CGMatrix*)rhsValue, (CGDouble*)lhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_VECTOR) && (rhsType == CGVT_VECTOR)){
				newres = addVV((CGVector*)lhsValue, (CGVector*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_MATRIX) && (rhsType == CGVT_VECTOR)){
				newres = addMV((CGMatrix*)lhsValue, (CGVector*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_VECTOR) && (rhsType == CGVT_MATRIX)){
				newres = addMV((CGMatrix*)rhsValue, (CGVector*)lhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_MATRIX) && (rhsType == CGVT_MATRIX)){
				newres = addMM((CGMatrix*)lhsValue, (CGMatrix*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}
			break;
		}

		case CGBOT_SUB:{
			if((lhsType == CGVT_DOUBLE) && (rhsType == CGVT_DOUBLE)){
				newres = subDD((CGDouble*)lhsValue, (CGDouble*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_VECTOR) && (rhsType == CGVT_DOUBLE)){
				newres = subVD((CGVector*)lhsValue, (CGDouble*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_DOUBLE) && (rhsType == CGVT_VECTOR)){
				newres = subDV((CGDouble*)lhsValue, (CGVector*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_MATRIX) && (rhsType == CGVT_DOUBLE)){
				newres = subMD((CGMatrix*)lhsValue, (CGDouble*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_DOUBLE) && (rhsType == CGVT_MATRIX)){
				newres = subDM((CGDouble*)lhsValue, (CGMatrix*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_VECTOR) && (rhsType == CGVT_VECTOR)){
				newres = subVV((CGVector*)lhsValue, (CGVector*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_MATRIX) && (rhsType == CGVT_MATRIX)){
				newres = subMM((CGMatrix*)lhsValue, (CGMatrix*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_MATRIX) && (rhsType == CGVT_VECTOR)){
				newres = subMV((CGMatrix*)lhsValue, (CGVector*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_VECTOR) && (rhsType == CGVT_MATRIX)){
				newres = subVM((CGVector*)lhsValue, (CGMatrix*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}
			break;
		}

		case CGBOT_DIV:{
			if((lhsType == CGVT_DOUBLE) && (rhsType == CGVT_DOUBLE)){
				newres = divDD((CGDouble*)lhsValue, (CGDouble*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_VECTOR) && (rhsType == CGVT_DOUBLE)){
				newres = divVD((CGVector*)lhsValue, (CGDouble*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_DOUBLE) && (rhsType == CGVT_VECTOR)){
				newres = divDV((CGDouble*)lhsValue, (CGVector*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_VECTOR) && (rhsType == CGVT_VECTOR)){
				newres = divVV((CGVector*)lhsValue, (CGVector*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_MATRIX) && (rhsType == CGVT_DOUBLE)){
				newres = divMD((CGMatrix*)lhsValue, (CGDouble*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_MATRIX) && (rhsType == CGVT_VECTOR)){
				newres = divMV((CGMatrix*)lhsValue, (CGVector*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_DOUBLE) && (rhsType == CGVT_MATRIX)){
				newres = divDM((CGDouble*)lhsValue, (CGMatrix*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			break;
		}

		case CGBOT_MULT:{
			// TODO: update
			if((lhsType == CGVT_MATRIX) && (rhsType == CGVT_MATRIX)){
				newres = mulMM((CGMatrix*)lhsValue, (CGMatrix*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			// TODO: update
			if((lhsType == CGVT_MATRIX) && (rhsType == CGVT_VECTOR)){
				newres = mulMV((CGMatrix*)lhsValue, (CGVector*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			// TODO: update
			if((lhsType == CGVT_VECTOR) && (rhsType == CGVT_MATRIX)){
				newres = mulMV((CGMatrix*)rhsValue, (CGVector*)lhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_DOUBLE) && (rhsType == CGVT_DOUBLE)){
				newres = mulDD((CGDouble*)lhsValue, (CGDouble*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_DOUBLE) && (rhsType == CGVT_VECTOR)){
				newres = mulDV((CGDouble*)lhsValue, (CGVector*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_VECTOR) && (rhsType == CGVT_DOUBLE)){
				newres = mulDV((CGDouble*)rhsValue, (CGVector*)lhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_DOUBLE) && (rhsType == CGVT_MATRIX)){
				newres = mulDM((CGDouble*)lhsValue, (CGMatrix*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_MATRIX) && (rhsType == CGVT_DOUBLE)){
				newres = mulDM((CGDouble*)rhsValue, (CGMatrix*)lhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_VECTOR) && (rhsType == CGVT_VECTOR)){
				newres = crossVV((CGVector*)rhsValue, (CGVector*)lhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}
			break;
		}

		case CGBOT_POW:{
			if((lhsType == CGVT_DOUBLE) && (rhsType == CGVT_DOUBLE)){
				newres = powDD((CGDouble*)lhsValue, (CGDouble*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_VECTOR) && (rhsType == CGVT_DOUBLE)){
				newres = powVD((CGVector*)lhsValue, (CGDouble*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_MATRIX) && (rhsType == CGVT_DOUBLE)){
				newres = powMD((CGMatrix*)lhsValue, (CGDouble*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}
			break;
		}

		case CGBOT_DOT: {
			/*
			 * The following are the same as MUL
			 */
			if((lhsType == CGVT_DOUBLE) && (rhsType == CGVT_DOUBLE)){
				newres = mulDD((CGDouble*)lhsValue, (CGDouble*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_DOUBLE) && (rhsType == CGVT_VECTOR)){
				newres = mulDV((CGDouble*)lhsValue, (CGVector*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_VECTOR) && (rhsType == CGVT_DOUBLE)){
				newres = mulDV((CGDouble*)rhsValue, (CGVector*)lhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_DOUBLE) && (rhsType == CGVT_MATRIX)){
				newres = mulDM((CGDouble*)lhsValue, (CGMatrix*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_MATRIX) && (rhsType == CGVT_DOUBLE)){
				newres = mulDM((CGDouble*)rhsValue, (CGMatrix*)lhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			/* here starts dot specific impl */

			if((lhsType == CGVT_MATRIX) && (rhsType == CGVT_MATRIX)){
				newres = dotMM((CGMatrix*)lhsValue, (CGMatrix*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_MATRIX) && (rhsType == CGVT_VECTOR)){
				newres = dotMV((CGMatrix*)lhsValue, (CGVector*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			if((lhsType == CGVT_VECTOR) && (rhsType == CGVT_MATRIX)){
				newres = dotVM((CGVector*)lhsValue, (CGMatrix*)rhsValue, graph, parentNode);
				//newres = dotMM(vectorToMatrix((CGVector*)lhsValue), (CGMatrix*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}

			/*
			if((lhsType == CGVT_VECTOR) && (rhsType == CGVT_MATRIX)){
				newres = mulMV((CGMatrix*)rhsValue, (CGVector*)lhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}
			*/

			if((lhsType == CGVT_VECTOR) && (rhsType == CGVT_VECTOR)){
				newres = dotVV((CGVector*)lhsValue, (CGVector*)rhsValue, graph, parentNode);
				parentNode->result = newres;
				return newres;
			}
			break;
		}

		case CGBOT_TMULT:{
			char msg[MAX_ERR_FMT_LEN];
			snprintf(msg, MAX_ERR_FMT_LEN, "Operation `%s` is not implemented/supported", getBinaryOperationTypeString(type));
			return returnResultError(graph, CGET_OPERATION_NOT_IMPLEMENTED, parentNode, msg);
		}
	}

	char msg[MAX_ERR_FMT_LEN];
	snprintf(msg, MAX_ERR_FMT_LEN, "Operation [%s %s %s] cannot be applied", getVariableTypeString(lhsType), getBinaryOperationTypeString(type), getVariableTypeString(rhsType));
	return returnResultError(graph, CGET_INCOMPATIBLE_ARGS_EXCEPTION, parentNode, msg);
}

CGResultNode* computeRawNode(CGNode* node){
	CGraph* tmp_G = makeGraph("temp_g");

	tmp_G->root = node;
	storeNodesInGraph(tmp_G, node);

	CGResultNode* res = computeGraph(tmp_G);
	res = copyResultNode(res);
	freeGraph(tmp_G);
	free(tmp_G);

	return res;
}

CGResultNode* computeCGNode(CGraph* graph, CGNode* node){
	CGResultNode* result = NULL;

	if(node->result != NULL){
		return node->result;
	}

	switch(node->type){
		case CGNT_CONSTANT:{
			result = constantNodeToResultNodeCopy(node);
			break;
		}

		case CGNT_VARIABLE:{
			if(graph == NULL)
			{
				char msg[MAX_ERR_FMT_LEN];
				snprintf(msg, MAX_ERR_FMT_LEN, "Cannot compute variable`%s` without the graph instance", node->var->name);
				return returnResultError(graph, CGET_NO_GRAPH_INSTANCE, node, msg);
			}

			CGNode* constantNode = *map_get(&graph->vars, node->var->name);
			if(constantNode == NULL)
			{
				char msg[MAX_ERR_FMT_LEN];
				snprintf(msg, MAX_ERR_FMT_LEN, "No variable `%s` was found in graph `%s`", node->var->name, graph!=NULL?graph->name:"[anonymous]");
				return returnResultError(graph, CGET_VARIABLE_DOES_NOT_EXIST, node, msg);
			}

			CGResultNode* rnode = computeCGNode(graph, constantNode);
			CHECK_RESULT(rnode)
			constantNode->result = rnode;
			node->result = copyResultNode(rnode);

			result = node->result;
			break;
		}
		case CGNT_BINARY_OPERATION:
			result = processBinaryOperation(graph, node->bop->type, node->bop->lhs, node->bop->rhs, node);
			break;
		case CGNT_UNARY_OPERATION:
			result = processUnaryOperation(graph, node->uop->type, node->uop->uhs, node);
			break;

		/*
		 * TODO: add this test to unittest
		 */
		case CGNT_AXIS_BOUND_OPERATION:
		{
            //CGResultNode* res = computeCGNode(graph, node->axop->uhs);

			switch(node->axop->type){
				case CGABOT_SUM:{
					CGResultNode* newres = computeCGNode(graph, node->axop->uhs);
					CHECK_RESULT(newres)

					if(newres->type == CGVT_DOUBLE){
						result = sumD((CGDouble*)newres->value, graph, node);
					}

					if(newres->type == CGVT_VECTOR){
						result = sumV((CGVector*)newres->value, graph, node);
					}

					if(newres->type == CGVT_MATRIX){
						result = sumM((CGMatrix*)newres->value, graph, node, node->axop->axis);
					}

					break;
				}

				case CGABOT_MAX:{
					result = max(node, graph);
					break;
				}

				case CGABOT_MIN:{
					result = min(node, graph);
					break;
				}

				case CGABOT_MEAN:{
					result = mean(node, graph);
					break;
				}

				case CGABOT_SOFTMAX:{
					char msg[MAX_ERR_FMT_LEN];
					snprintf(msg, MAX_ERR_FMT_LEN, "Operation [CGABOT_SOFTMAX] is not implemented/supported");
					return returnResultError(graph, CGET_OPERATION_NOT_IMPLEMENTED, node, msg);
					//CGResultNode* res = computeCGNode(graph, node->axop->uhs);
					//break;
				}

				case CGABOT_ARGMAX:{
				    result = argmax(node, graph);
                    break;
				}

                case CGABOT_ARGMIN:{
                    result = argmin(node, graph);
                    break;
                }

				default:
				    break;
			}

			break;
		}
		case CGNT_GRAPH:{
			result = computeGraph(node->graph);
			break;
			/*
			char msg[MAX_ERR_FMT_LEN];
			snprintf(msg, MAX_ERR_FMT_LEN, "Operation [GRAPH] is not implemented/supported");
			return returnResultError(graph, CGET_OPERATION_NOT_IMPLEMENTED, node, msg);
			*/
		}

		case CGNT_CROSS_ENTROPY_LOSS_FUNC:
		{
			CGResultNode* X_res = computeCGNode(graph, node->crossEntropyLoss->x);
			CGNode* X = resultNodeToConstantNodeCopy(X_res);

			CGNode* X_val = softmax_node(X);

			CGResultNode* x_res = computeRawNode(X_val);

			CGResultNode* y_res = computeCGNode(graph, node->crossEntropyLoss->y);

			result = crossEntropy(x_res, y_res, node->crossEntropyLoss->num_classes);

			freeResultNode(x_res);
			free(x_res);
			break;
		}
	}

	node->result = reduceDim(result);

	if(node->diff == NULL)
		switch(node->result->type){
			case CGVT_DOUBLE:
				node->diff = makeZeroDoubleConstantNode();
				break;
			case CGVT_VECTOR:{
				CGVector* v = (CGVector*)node->result ->value;
				node->diff = makeZeroVectorConstantNode(v->len);
				break;
			}

			case CGVT_MATRIX:{
				CGMatrix* v = (CGMatrix*)node->result ->value;
				node->diff = makeZeroMatrixConstantNode(v->rows, v->cols);
				break;
			}
		}

	return node->result;
}

CGResultNode* reduceDim(CGResultNode* result){
    //return result;

	switch(result->type){
		case CGVT_DOUBLE:{
			return result;
		}

		case CGVT_VECTOR:{
			CGVector* vec = (CGVector*)result->value;
			if (vec->len > 1)
				return result;

#ifdef CG_USE_OPENCL
			copyDataToHost(result);
#endif

			CGDouble* d = calloc(1, sizeof(CGDouble));
			d->value = vec->data[0];

			freeVectorValue(result->value);
			free(result->value);

			result->type = CGVT_DOUBLE;
			result->value = d;

			return result;
		}

		case CGVT_MATRIX:{
			CGMatrix* mat = (CGMatrix*)result->value;

			if((mat->rows>1) &&(mat->cols>1))
				return result;

			if((mat->rows == 1) && (mat->cols == 1)){
                copyDataToHost(result);

				CGDouble* d = calloc(1, sizeof(CGDouble));
				d->value = mat->data[0];

				freeMatrixValue(result->value);
				free(result->value);

				result->type = CGVT_DOUBLE;
				result->value = d;

				return result;
			}

			if(mat->rows == 1){
				CGVector* vec = calloc(1, sizeof(CGVector));
				vec->len = mat->cols;
				vec->data = mat->data;
				//TODO: Memory leak here
#ifdef CG_USE_OPENCL
				vec->buf = mat->buf;
#endif

				//freeMatrixValue(result->value);
				free(result->value);

				result->type = CGVT_VECTOR;
				result->value = vec;

				return result;
			}

			return result;
		}
	}
}

CGResultNode* computeGraph(CGraph* graph){
	resetGraphResultNodes(graph, graph->root);
	CGResultNode* res = computeCGNode(graph, graph->root);
	copyDataToHost(res);

	return res;
}

CGResultNode* computeGraphNode(CGraph* graph, CGNode* node){
	storeNodesInGraph(graph, node);
	resetGraphResultNodes(graph, node);
	return computeCGNode(graph, node);
}

void storeNodesInGraph(CGraph* graph, CGNode* node){
	int idx = -1;

	vec_find(&graph->nodes, node, idx);

	if(idx != -1)
		return;

	vec_push(&graph->nodes, node);

	switch(node->type){
		case CGNT_CONSTANT:
			break;
		case CGNT_VARIABLE:
			break;
		case CGNT_BINARY_OPERATION:
			storeNodesInGraph(graph, node->bop->lhs);
			storeNodesInGraph(graph, node->bop->rhs);
			break;
		case CGNT_UNARY_OPERATION:
			storeNodesInGraph(graph, node->uop->uhs);
			break;
		case CGNT_AXIS_BOUND_OPERATION:
			storeNodesInGraph(graph, node->axop->uhs);
			break;
		case CGNT_GRAPH:
			storeNodesInGraph(graph, node->graph->root);
			break;
		case CGNT_CROSS_ENTROPY_LOSS_FUNC:
			storeNodesInGraph(graph, node->crossEntropyLoss->x);
			storeNodesInGraph(graph, node->crossEntropyLoss->y);
			break;
	}
}

void resetGraphResultNodes(CGraph* graph, CGNode* node){
	if(node->result != NULL){
		freeResultNode(node->result);
		free(node->result);
		node->result = NULL;
	}

	if(node->diff != NULL){
		freeNode(graph, node->diff);
		free(node->diff);
		node->diff = NULL;
	}

	switch(node->type){
		case CGNT_CONSTANT:
			break;
		case CGNT_VARIABLE:{
			CGNode* var = graphGetVar(graph, node->var->name);
			if(var != NULL)
				resetGraphResultNodes(graph, var);
			break;
		}
		case CGNT_BINARY_OPERATION:
			resetGraphResultNodes(graph, node->bop->lhs);
			resetGraphResultNodes(graph, node->bop->rhs);
			break;
		case CGNT_UNARY_OPERATION:
			resetGraphResultNodes(graph, node->uop->uhs);
			break;
		case CGNT_AXIS_BOUND_OPERATION:
			resetGraphResultNodes(graph, node->axop->uhs);
			break;
		case CGNT_GRAPH:
			resetGraphResultNodes(graph, node->graph->root);
			break;
		case CGNT_CROSS_ENTROPY_LOSS_FUNC:
			resetGraphResultNodes(graph, node->crossEntropyLoss->x);
			resetGraphResultNodes(graph, node->crossEntropyLoss->y);
			break;
	}
}

void graphSetVar_lua(CGraph* graph, const char* name, CGNode* value){
	CGNode** old = map_get(&graph->vars, name);
	if(old != NULL){
		//freeNode(graph, *old);
		map_remove(&graph->vars, name);
	}

	int res = map_set(&graph->vars, name, value);
}


// Copyright (c) 2022 SmartPolarBear.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

//
// Created by cleve on 2/1/2022.
//
#pragma once

#include "types.h"

#include <utility>
#include <cstdint>
#include <string_view>
#include <bit>

#include <gsl/gsl>

namespace jpeg_lite::decoder::transform
{
[[nodiscard]] FORCE_INLINE gsl::index coordinate_to_zigzag(gsl::index x, gsl::index y);

[[nodiscard]] FORCE_INLINE std::pair<gsl::index, gsl::index> zigzag_to_coordinate(gsl::index zigzag);

[[nodiscard, maybe_unused]] int16_t binary_string_to_int16(std::string_view sv);

[[nodiscard]] static inline FORCE_INLINE constexpr int16_t value_category(int16_t val)
{
	if (val == 0x0000)
		return 0;
	return std::bit_width(static_cast<uint16_t>(val > 0 ? val : -val));
}
}


/*
** Read design file
**
** G.Lohmann, MPI-KYB 2018
*/
#include <viaio/Vlib.h>
#include <viaio/VImage.h>
#include <viaio/mu.h>
#include <viaio/option.h>

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

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

typedef struct TrialStruct {
  int   id;
  float onset;
  float duration;
  float height;
} Trial;

extern void printmat(gsl_matrix *R,char *str);
extern void printvec(gsl_vector *x,char *str);

int test_ascii(int val)
{
  if (val >= 'a' && val <= 'z') return 1;
  if (val >= 'A' && val <= 'Z') return 1;
  if (val >= '0' && val <= '9') return 1;
  if (val ==  ' ') return 1;
  if (val ==  '-') return 1;
  if (val ==  '+') return 1;
  if (val ==  '.') return 1;
  if (val == '\0') return 1;
  if (val == '\n') return 1;
  if (val == '\r') return 1;
  if (val == '\t') return 1;
  if (val == '\v') return 1;
  return 0;
}

int line_empty(char *buf,int len)
{
  int i;
  for (i=0; i<len; i++) {
    if (buf[i] != ' ' && buf[i] != '\0' && buf[i] != '\n') return 0;
  }
  return 1;
}

int CheckBuffer(char *buf,int len)
{
  int j;
  if(strlen(buf) < 1) return 0;
  if (buf[0] == '%' || buf[0] == '#' || buf[0] == '/') return 0;  /* comment */
  for (j=0; j<len; j++) {
    if (buf[j] == '\v') buf[j] = ' '; /* remove tabs */
    if (buf[j] == '\t') buf[j] = ' ';
  }
  if (line_empty(buf,len) > 0) return 0;
  return 1;
}

int VistaFormat(char *buf,int len)
{
  if (strncmp(buf,"V-data",6) == 0) return 1;
  return 0;
}

/* parse design file */
Trial *ReadDesign(VString designfile,int *numtrials,int *nevents)
{
  FILE *fp=NULL;
  int  i,j,id,len=4096;
  float onset=0,duration=0,height=0;
  char *buf = (char *)VCalloc(len,sizeof(char));

  fp = fopen(designfile,"r");
  if (!fp) VError(" error opening design file %s",designfile);
  int ntrials=0;
  while (!feof(fp)) {
    for (j=0; j<len; j++) buf[j] = '\0';
    if (fgets(buf,len,fp) == NULL) break;
    if (VistaFormat(buf,len) > 1) VError(" Design file must be a text file");
    if (CheckBuffer(buf,len) < 1) continue;
    if (! test_ascii((int)buf[0])) VError(" File %s: line %d begins with an illegal character (%c)",designfile,ntrials,buf[0]);
    ntrials++;
  }
  *numtrials = ntrials;

  Trial *trial = (Trial *) VCalloc(ntrials+1,sizeof(Trial));

  i = (*nevents) = 0;
  rewind(fp);
  while (!feof(fp)) {
    for (j=0; j<len; j++) buf[j] = '\0';
    if (fgets(buf,len,fp) == NULL) break;
    if (CheckBuffer(buf,len) < 1) continue;

    int nch = sscanf(buf,"%d %f %f %f",&id,&onset,&duration,&height);
    if (nch > 0 && nch != 4) VError(" line %d: illegal input format",i+1);
    if (duration < 0.5 && duration >= -0.0001) duration = 0.5;
    trial[i].id       = id;
    trial[i].onset    = onset;
    trial[i].duration = duration;
    trial[i].height   = height;
    i++;
    if (id > (*nevents)) (*nevents) = id;
  }
  fclose(fp);
  return trial;
}

Trial *CopyTrials(Trial *trial,int numtrials)
{
  int i;
  Trial *newtrial = (Trial *) VCalloc(numtrials,sizeof(Trial));
  for (i=0; i<numtrials; i++) {
    newtrial[i].id       = trial[i].id;
    newtrial[i].onset    = trial[i].onset;
    newtrial[i].duration = trial[i].duration;
    newtrial[i].height   = trial[i].height;
  }
  return newtrial;
}

/* concatenate trials from all runs */
Trial *ConcatenateTrials(Trial **trial,int *numtrials,float *run_duration,int dlists,int sumtrials)
{
  int i,j;
  Trial *alltrials = (Trial *) VCalloc(sumtrials,sizeof(Trial));

  int ii=0;
  float add=0;
  for (i=0; i<dlists; i++) {
    for (j=0; j<numtrials[i]; j++) {
      alltrials[ii].id       = trial[i][j].id;
      alltrials[ii].onset    = trial[i][j].onset + add;
      alltrials[ii].duration = trial[i][j].duration;
      alltrials[ii].height   = trial[i][j].height;
      ii++;
    }
    add += run_duration[i];
  }
  return alltrials;
}

/* check if trial labels are complete and positive */
void CheckTrialLabels(Trial *trial,int numtrials)
{
  int i,j;
  int maxlabel = -1;
  for (i=0; i<numtrials; i++) {
    j = trial[i].id;
    if (j > maxlabel) maxlabel = j;
    if (j < 1) VError(" trial[%d] has illegal label %d",i,j);
  }

  int *tmp = (int *) VCalloc(maxlabel+1,sizeof(int));
  for (i=0; i<numtrials; i++) {
    j = trial[i].id;
    tmp[j]++;
  }
  for (i=1; i<=maxlabel; i++) {
    if (tmp[i] < 1) VError(" Label %d missing in design file",i);
    if (tmp[i] < 4) VWarning(" Label %d has too few trials (%d) for random permutations",i,tmp[i]);
  }
  VFree(tmp);
}