/*
ODE: a program to get optime Runge-Kutta and multi-steps methods.

Copyright 2011-2019, Javier Burguete Tolosa.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

	1. Redistributions of source code must retain the above copyright notice,
		this list of conditions and the following disclaimer.

	2. Redistributions in binary form must reproduce the above copyright notice,
		this list of conditions and the following disclaimer in the
		documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY Javier Burguete Tolosa ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL Javier Burguete Tolosa OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/**
 * \file ode.c
 * \brief Source file with the main function.
 * \author Javier Burguete Tolosa.
 * \copyright Copyright 2011-2019.
 */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <math.h>
#include <string.h>
#include <alloca.h>
#include <time.h>
#include <unistd.h>
#include <libxml/parser.h>
#include <glib.h>
#include <gsl/gsl_rng.h>
#include <libintl.h>
#if HAVE_MPI
#include <mpi.h>
#endif
#include "config.h"
#include "utils.h"
#include "optimize.h"
#include "steps.h"
#include "rk.h"

// OPTIMES
//
// RK_2_2 (cfl=1, cfl/steps=1/2)
// t1=1 t2=1
// a10=1 a20=a21=1/2
// b10=1 b20=b21=1/2
// c10=1 c20=0 c21=1
//
// RK_3_2 (cfl=2, cfl/steps=2/3)
// t1=1/2 t2=t3=1
// a10=1 a20=0 a21=1 a30=1/3 a31=0 a32=2/3
// b10=1/2 b20=b21=1/2 b30=b31=b32=1/3
// c10=1/2 c20=0 c21=1/2 c30=0 c31=0 c32=1/2
//
// RK_4_2 (cfl=3, cfl/steps=3/4)
// t1=1/3 t2=2/3 t3=t4=1
// a10=1 a20=0 a21=1 a30=a31=0 a32=1 a40=1/4 a41=a42=0 a43=3/4
// b10=1/3 b20=b21=1/2 b30=b31=b32=1/3
// c10=1/3 c20=0 c21=1/3 c30=c31=0 c32=1/3 c40=c41=c42=0 c43=1/3
//
// RK_5_2 (cfl=4, cfl/steps=4/5)
// t1=1/4 t2=1/2 t3=3/4 t4=t5=1
// a10=1 a20=0 a21=1 a30=a31=0 a32=1 a40=a41=a42=0 a43=1
// a50=1/5 a51=a52=a53=0 a54=4/5
// b10=b20=b21=b30=b31=b32=b40=b41=b42=b43=1/4 b50=b51=b52=b53=b54=1/5
// c10=1/4 c20=0 c21=1/4 c30=c31=0 c32=1/4 c40=c41=c42=0 c43=1/4
// c50=c51=c52=c53=0 c54=1/4
//
// RK_6_2 (cfl=5, cfl/steps=5/6)
// t1=1/5 t2=2/5 t3=3/5 t4=4/5 t5=t6=1
// a10=1 a20=0 a21=1 a30=a31=0 a32=1 a40=a41=a42=0 a43=1 a50=a51=a52=a53=0 a54=1
// a60=1/6 a61=a62=a63=a64=0 a65=5/6
// b10=b20=b21=b30=b31=b32=b40=b41=b42=b43b50=b51=b52=b53=b54=1/5
// b60=b61=b62=b63=b64=b65=1/6
// c10=1/5 c20=0 c21=1/5 c30=c31=0 c32=1/5 c40=c41=c42=0 c43=1/5
// c50=c51=c52=c53=0 c54=1/5 c60=c61=c62=c63=c64=0 c65=1/5
//
// RK_7_2 (cfl=6, cfl/steps=6/7)
// t1=1/6 t2=2/6 t3=3/6 t4=4/6 t5=5/6 t6=t7=1
// a10=1 a20=0 a21=1 a30=a31=0 a32=1 a40=a41=a42=0 a43=1 a50=a51=a52=a53=0 a54=1
// a60=a61=a62=a63=a64=0 a65=1 a70=1/7 a71=a72=a73=a74=a75=0 a76=6/7
// b10=b20=b21=b30=b31=b32=b40=b41=b42=b43b50=b51=b52=b53=b54=b60=b61=b62=b63
// =b64=b65=1/6 b70=b71=b72=b73=b74=b75=b76=1/7
// c10=1/6 c20=0 c21=1/6 c30=c31=0 c32=1/6 c40=c41=c42=0 c43=1/6
// c50=c51=c52=c53=0 c54=1/6 c60=c61=c62=c63=c64=0 c65=1/6
// c70=c71=c73=c74=c75=0 c76=1/6
//
// STEPS_3_2 (cfl=1/2)
// a0=3/4 a1=0 a2=1/4
// b0=3/2 b1=b2=0
// c0=2 c1=c2=0
//
// STEPS_4_2 (cfl=2/3)
// a0=8/9 a1=a2=0 a3=1/9
// b0=4/3 b1=b2=b3=0
// c0=3/2 c1=c2=c3=0
//
// STEPS_5_2 (cfl=3/4)
// a0=15/16 a1=a2=a3=0 a4=1/16
// b0=5/4 b1=b2=b3=b4=0
// c0=4/3 c1=c2=c3=c4=0
//
// STEPS_6_2 (cfl=4/5)
// a0=24/25 a1=a2=a3=a4=0 a5=1/25
// b0=6/5 b1=b2=b3=b4=b5=0
// c0=5/4 c1=c2=c3=c4=c5=0
//
// STEPS_7_2 (cfl=5/6)
// a0=35/36 a1=a2=a3=a4=0 a5=1/36
// b0=7/6 b1=b2=b3=b4=b5=0
// c0=6/5 c1=c2=c3=c4=c5=0
//
// STEPS_8_2 (cfl=6/7)
// a0=48/49 a1=a2=a3=a4=0 a5=1/49
// b0=8/7 b1=b2=b3=b4=b5=0
// c0=7/6 c1=c2=c3=c4=c5=0
//
// RK_3_3 (cfl=1, cfl/steps=1/3)
// t1=t3=1 t2=1/2
// a10=1 a20=3/4 a21=1/4 a30=1/3 a31=0 a32=2/3
// b10=1 b20=b21=1/4 b30=b31=1/6 b32=2/3
// c10=1 c20=0 c21=1 c30=0 c31=0 c32=1
//
// RK_4_3 (cfl=2, cfl/steps=1/2)
// t1=t3=1/2 t2=1
// a10=1 a20=0 a21=1 a30=2/3 a31=0 a32=1/3 a40=a41=a42=0 a43=1
// b10=b20=b21=1/2 b30=b31=b32=b40=b41=b42=1/6 b43=1/2
// c10=c20=c21=1/2 c30=0 c31=c32=c40=c41=c42=c43=1/2
//
// STEPS_4_3 (cfl=1/3)
// a0=16/27 a1=a2=0 a3=11/27
// b0=16/9 b1=b2=0 b3=4/9
// c0=3 c1=c2=0 c3=12/11
//
// STEPS_5_3 (cfl=1/2)
// a0=25/32 a1=a2=a3=0 a4=7/32
// b0=25/16 b1=b2=b3=0 b4=5/16
// c0=2 c1=c2=c3=0 c4=10/7
//
// STEPS_6_3 (cfl=0.58282164314264111327)
// a0=0.85070887167257828565    // (1-a4-a5)
// a1=a2=a3=0
// a4=0.030664864534382320725 // (sqrt(10*a5-1)-10*a5+1)/8
// a5=0.11862626379303939363
//      (63*sqrt(2614)+3221)^(1/3)-5*(63*sqrt(2614)+3221)^(-1/3)-13)/45
// b0=1.4596384360152765194     // a0*c0
// b1=b2=b3=0
// b4=0.052614491749197671863   // a4*c4
// b5=0.20353784933825205974    // a5*c5
// c0=c4=c5=1.715790777102726251        // (1+4*a4+5*a5)
// c1=c2=c3=0
//
// RK_4_4
// b20=b30=b31=0
// t1=t2=b10=b21=1/2
// t3=t4=b32=1
// b40=b43=1/6
// b41=b42=1/3
//
// RK_5_4 (cfl=5.7383119999072648693e-01)
// a10=1
// a20=7.6291847536448934231e-01
// a21=2.3708152463551065768e-01
// a30=2.8277566215397998339e-03
// a31=9.4483076489543642366e-01
// a32=5.2341478483023776481e-02
// a40=6.6309349322657261432e-01
// a41=4.4608078655530437600e-04
// a42=3.3644655897571642926e-01
// a43=1.3867011155652078012e-05
// a50=6.1508837844244168818e-01
// a51=1.6215445203250317099e-01
// a52=1.5031540295088349761e-03
// a53=2.9995002701206228898e-03
// a54=2.1825451522542568299e-01
// b10=4.5307898578512145396e-01
// b20=1.0742850906542086858e-01
// b21=4.1289584245869493973e-01
// b30=4.3724780003845571699e-01
// b31=1.6681424138984873981e+00
// b32=9.0588475806896936944e-02
// b40=3.6664090929447934446e-02
// b41=1.3964776663947759666e-01
// b42=5.8495388577489235762e-01
// b43=1.9355255172522962676e-05
// b50=1.8734964237542205069e-01
// b51=3.0001844590932289148e-01
// b52=1.2903718344972795025e-01
// b53=3.4166232498545846666e-03
// b54=3.8017810501567252292e-01
// c10=4.5307898578512145396e-01
// c20=1.5535519922690283242e-05
// c21=1.7415774725317857109e+00
// c30=1.2525364871787093798e+00
// c31=1.7426727581493662919e+00
// c32=1.7307206145557779180e+00
// c40=4.7046995817421687883e-04
// c41=1.5854730818354572705e+00
// c42=1.7386197420604550316e+00
// c43=1.3957769958693602267e+00
// c50=1.6974106727520458494e-01
// c51=1.6275559956612365566e+00
// c52=7.2955698585694530597e-01
// c53=1.1376557995384390896e+00
// c54=1.7419025884665109550e+00
//
// STEPS_6_4 (cfl=1.6475925238473621578e-01)
// a0=3.4246085571701207596e-01
// a1=a2=0
// a3=1.9179825943473607269e-01
// a4=9.3562124939009442453e-02
// a5=3.721787599092424089e-01
// b0=2.078553105578055306e+00
// b1=b2=b5=0
// b3=1.1641122222796929277e+00
// b4=5.6787174974870979874e-01
// c0=c3=c4=6.0694618695213438363e+00
// c1=c2=c5=0

/**
 * enum to define the method type.
 */
enum
{
  METHOD_TYPE_RUNGE_KUTTA = 1,  ///< Runge-Kutta method.
  METHOD_TYPE_STEPS = 2,        ///< multi-steps method.
} MethodType;

/**
 * enum to define the program error code.
 */
enum
{
  ERROR_CODE_NARGS = 1,         ///< bad command line arguments number.
  ERROR_CODE_BAD_DOC = 2,       ///< bad XML file.
  ERROR_CODE_NO_XML_ROOT = 3,   ///< no XML root node.
  ERROR_CODE_BAD_RK = 4,        ///< bad Runge-Kutta method.
  ERROR_CODE_BAD_STEPS = 5,     ///< bad multi-steps method.
  ERROR_CODE_UNKNOWN_METHOD = 6,        ///< unknown method.
  ERROR_CODE_UNKNOWN_OPTION = 7,        ///< unknown option.
} ErrorCode;

unsigned int nsteps;            ///< steps number.
unsigned int order;             ///< accuracy order.

/**
 * Main function
 *
 * \return 0 on succes, error code on error.
 */
int
main (int argn,                 ///< arguments number.
      char **argc)              ///< argument chains array.
{
  const struct option options[] = {
    {"help", no_argument, NULL, 'h'},
    {"seed", required_argument, NULL, 's'},
    {"threads", required_argument, NULL, 't'},
    {NULL, 0, NULL, 0}
  };
  const char *usage = _("Usage is:\n"
                        "./ode "
                        "[-t --threads threads_number] "
                        "[-s --seed random_seed] "
                        "input_file [variables_file]");
  xmlDoc *doc;
  xmlNode *node;
  gsl_rng *rng0, **rng;
  time_t d0;
  clock_t t0;
  unsigned long int seed = 7l;
  int o, option_index;
  unsigned int i, j, k, h = 0;

#if HAVE_MPI
  // Init MPI
  MPI_Init (&argn, &argc);
#endif

  // Enabling spaces in XML files
  xmlKeepBlanksDefault (0);

  // Setting the threads as the number of processors
  nthreads = sysconf (_SC_NPROCESSORS_CONF);

  // Parsing command line options
  while (1)
    {
      o = getopt_long (argn, argc, "hs:t:", options, &option_index);
      if (o == -1)
        break;
      switch (o)
        {
        case 's':
          seed = atol (optarg);
          break;
        case 't':
          nthreads = atoi (optarg);
          break;
        case 'h':
          printf ("%s\n", usage);
          h = 1;
          break;
        default:
          show_error (_("Unknown option"));
          return ERROR_CODE_UNKNOWN_OPTION;
        }
    }
  argn -= optind;
  if (argn != 1 && argn != 2)
    {
      if (h)
        return 0;
      show_error (usage);
      return ERROR_CODE_NARGS;
    }

  // Init the clock
  t0 = clock ();
  d0 = time (NULL);

#if HAVE_MPI

  // Nodes number
  MPI_Comm_size (MPI_COMM_WORLD, &nnodes);

  // Actual node
  MPI_Comm_rank (MPI_COMM_WORLD, &rank);

#else

  nnodes = 1;
  rank = 0;

#endif

  // Select the numerical model
  printf ("Rank=%d nnodes=%d nthreads=%u\n", rank, nnodes, nthreads);
  printf ("Selecting method optind=%d\n", optind);
  doc = xmlParseFile (argc[optind]);
  if (!doc)
    {
      show_error (_("Unable to parse the input file"));
      return ERROR_CODE_BAD_DOC;
    }
  node = xmlDocGetRootElement (doc);
  if (!node)
    {
      show_error (_("No XML root node"));
      return ERROR_CODE_NO_XML_ROOT;
    }

  // Init a random numbers generator per node and thread
  printf ("Initing random numbers\n");
  rng = (gsl_rng **) g_slice_alloc (nnodes * nthreads * sizeof (gsl_rng *));
  rng0 = gsl_rng_alloc (gsl_rng_ranlxs2);
  gsl_rng_set (rng0, seed);
  for (i = k = 0; (int) i < nnodes; ++i)
    for (j = 0; j < nthreads; ++j, ++k)
      {
        rng[k] = gsl_rng_alloc (gsl_rng_taus2);
        gsl_rng_set (rng[k], gsl_rng_get (rng0));
      }

  if (argn == 2)
    file_variables = fopen (argc[++optind], "w");

  j = rank * nthreads;
  if (!xmlStrcmp (node->name, XML_RUNGE_KUTTA))
    {
      if (!rk_run (node, rng))
        {
          show_error (error_message);
          return ERROR_CODE_BAD_RK;
        }
    }
  else if (!xmlStrcmp (node->name, XML_STEPS))
    {
      if (!steps_run (node, rng))
        {
          show_error (error_message);
          return ERROR_CODE_BAD_STEPS;
        }
    }
  else
    {
      show_error (_("Unknown method type"));
      return ERROR_CODE_UNKNOWN_METHOD;
    }

  printf ("cpu time=%lg real time=%lu\n",
          (clock () - t0) / ((double) CLOCKS_PER_SEC), time (NULL) - d0);

  // Free memory
  xmlFreeDoc (doc);
  if (file_variables)
    fclose (file_variables);
  j = nnodes * nthreads;
  for (i = 0; i < j; ++i)
    gsl_rng_free (rng[i]);
  g_slice_free1 (j * sizeof (gsl_rng *), rng);
  gsl_rng_free (rng0);

#if HAVE_MPI
  // Close MPI
  MPI_Finalize ();
#endif

  return 0;
}


#include <stdio.h>
#include <stdlib.h>
#include <gsl/gsl_rng.h>
#include <math.h>
#include <time.h>
#include <stdbool.h>
#include "bb_smc.h"

/* ==========================================================
Last update: 1.5.20 -- v2.0

ABC-SMC (parallel version)
Author: Marco Esposito
=========================================================== */

// =========== Define some constants =============
#define size_data 50

#define a0 0.5
#define b0 0.5
#define std_ker 0.3
#define striding_size 1
#define n 20
// =====================================

// ARGUMENT NUMBER 1: time at which the simulation starts
// ARGUMENT NUMBER 2: seed
// ARGUMENT NUMBER 3: number of particles to consider
// for the simulation (lesser for shorter runs)

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

	if (argc != 4) {

		printf("argc = %d\n", argc);
		printf("Usage: %s\n", argv[0]);
		printf("      %s [parameters] \n", argv[0]);
		exit(1);

	}

	/* ==================== VARIABLES DECLARATION ======================== */
	int time = atoi(argv[1]);
	int seed = atoi(argv[2]);
	int n_particles = atoi(argv[3]);

	double theta_star;
	double theta;
	double weight;
	double epsilon_t;
	int status;

	double* weights;
	weights = malloc((n_particles) * sizeof(double));

	int sample_accepted;
	/* ==================================================================== */

	/* ============== IMPORT DATA =========== */
	int y[size_data];
	status = import_data_BB(y, size_data);
	if (status == 1) {
		printf("Data not found\n");
		exit(1);
	}
	double mean_y = mean_int(y, size_data);
	// printf("(MAIN) -- mean_y: %f\n", mean_y);
	/* ======================================== */

	/* =========== READ THRESHOLD ============ */
	status = read_threshold(&epsilon_t, time);
	if (status == 1) {
		printf("Threshold not found\n");
		exit(1);
	}
	/* ======================================== */

	/* ================== set up GSL RNG =================== */
	gsl_rng *r = gsl_rng_alloc(gsl_rng_mt19937);
	gsl_rng_set (r, seed+101); // set seed
	/* ===================================================== */

	sample_accepted = 1;

	/* =========================================================== */
	/* ================= START THE ABC ROUND ===================== */
	/* =========================================================== */
	while (sample_accepted == 1) {

		// Select particle
		status = select_particle(weights, &theta, time, n_particles, r);
		if (status == 1) {
			printf("File with particles at time %d, %d particles not present\n", time, n_particles);
			exit(1);
		}

		// Perturb the parameter
		status = ker_perturb(&theta_star, r, theta, a0, b0, std_ker);
		if (status == 1) {
			continue;
		}

		// - If the distance function is too large, start over
		// - If all the conditions are fulfilled, the sample is accepted
		sample_accepted = verify_par_BB(theta_star, mean_y, r, size_data, n, epsilon_t, time, seed);
		if (sample_accepted == 1) {
			continue;
		}

	} //END OF ABC ROUND (WHILE LOOP) -- PARTICLE WAS ACCEPTED

	calculate_weight_BB(&weight, weights, theta, theta_star, n_particles, a0, b0, std_ker);

	write_particles_BB(theta_star, time, seed, striding_size, weight);

	free(weights);

	return 0; //return of main

} //End of the program


#include <inttypes.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <cblas.h>
#include <string.h> // memcpy
#include <math.h>
#include <clblast_c.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"

//#pragma OPENCL EXTENSION cl_khr_fp64 : enable

#if cg_float == float
#define cblas_dcopy cblas_scopy
#define cblas_dscal cblas_sscal
#define cblas_dgemm cblas_sgemm
#define cblas_ddot cblas_sdot
#define cblas_dgemv cblas_sgemv
#define cl_double cl_float
#endif

void checkErr(CCLErr* err){
    if (err != NULL) {
        fprintf(stderr, "\n%s\n", err->message);
        exit(-1);
    }
}

#define CHECK_ERROR(err) \
    checkErr(err);
    //if (err != NULL) { fprintf(stderr, "\n%s\n", err->message); exit(-1); }

CCLContext * ctx = NULL;
CCLProgram* prg = NULL;
CCLQueue* queue = NULL;
CCLDevice* dev = NULL;

#if cg_float == float
#define CLBlastDcopy CLBlastScopy
#define CLBlastDscal CLBlastSscal
#define CLBlastDgemm CLBlastSgemm
#define CLBlastDdot CLBlastSdot
#define CLBlastDomatcopy CLBlastSomatcopy
#define CLBlastDsum CLBlastSsum
#endif

void copyDataToHost(CGResultNode* res){
    CCLEvent* evt = NULL;
    CCLErr * err = NULL;

    switch(res->type){
        case CGVT_DOUBLE:
            break;

        case CGVT_VECTOR:{
            CGVector* V = (CGVector*)res->value;
            V->data = malloc(V->len* sizeof(cgcl_float));
            if (V->loc == CG_DATALOC_DEVICE_MEM) {
                evt = ccl_buffer_enqueue_read(V->buf, queue, CL_TRUE, 0,
                                              V->len * sizeof(cgcl_float), V->data, NULL, NULL);
                if (!evt) exit(-1);
                ccl_buffer_destroy(V->buf);
            }
            else
                printf("FUCK\n");
            V->loc = CG_DATALOC_HOST_MEM;
            break;
        }

        case CGVT_MATRIX:{
            CGMatrix* M = (CGMatrix*)res->value;
            M->data = malloc(M->rows*M->cols* sizeof(cgcl_float));

            if (M->loc == CG_DATALOC_DEVICE_MEM) {
                evt = ccl_buffer_enqueue_read(M->buf, queue, CL_TRUE, 0,
                                              M->rows * M->cols * sizeof(cgcl_float), M->data, NULL, NULL);
                if (!evt) exit(-1);
                ccl_buffer_destroy(M->buf);
            }
            else
            printf("FUCK2\n");
            M->loc = CG_DATALOC_HOST_MEM;
            break;
        }
    }

    cl_bool status = ccl_queue_finish(queue, NULL);
    //if (!status) exit(-1);
    ////ccl_queue_destroy(queue);
}

CGDouble* makeDeviceDouble(){
    CCLErr * err = NULL;
    CGDouble* Y = calloc(1, sizeof(CGDouble));
    cg_float* ptr = &Y->value;
    Y->buf = ccl_buffer_new(ctx, CL_MEM_READ_WRITE,
                            sizeof(cgcl_float), NULL, &err);
    CHECK_ERROR(err)

    Y->loc = CG_DATALOC_DEVICE_MEM;
    Y->value = 0.0;

    return Y;
}

CGVector* makeDeviceVector(uint64_t len){
    CCLErr * err = NULL;
    CGVector* Y = calloc(1, sizeof(CGVector));
    Y->len = len;
    Y->data = NULL; //calloc(len, sizeof(cg_float));

    Y->buf = ccl_buffer_new(ctx, CL_MEM_READ_WRITE ,
                            len * sizeof(cgcl_float), Y->data, &err);

    CHECK_ERROR(err)

    Y->loc = CG_DATALOC_DEVICE_MEM;

    return Y;
}

CGVector* makeDeviceVectorZeros(uint64_t len){
    CCLErr * err = NULL;
    CGVector* Y = calloc(1, sizeof(CGVector));
    Y->len = len;
    Y->data = calloc(len, sizeof(cg_float));

    Y->buf = ccl_buffer_new(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR,
                            len * sizeof(cgcl_float), Y->data, &err);

    CHECK_ERROR(err)

    Y->loc = CG_DATALOC_DEVICE_MEM;

    return Y;
}

CGMatrix* makeDeviceMatrix(uint64_t rows, uint64_t cols){
    CCLErr * err = NULL;
    CGMatrix* Y = calloc(1, sizeof(CGMatrix));
    Y->rows = rows;
    Y->cols = cols;
    Y->data = NULL; //calloc(rows*cols, sizeof(cg_float));

    Y->buf = ccl_buffer_new(ctx, CL_MEM_READ_WRITE ,
                            rows*cols * sizeof(cgcl_float), Y->data, &err);
    CHECK_ERROR(err)

    Y->loc = CG_DATALOC_DEVICE_MEM;

    return Y;
}

CGMatrix* makeDeviceMatrixZeros(uint64_t rows, uint64_t cols){
    CCLErr * err = NULL;
    CGMatrix* Y = calloc(1, sizeof(CGMatrix));
    Y->rows = rows;
    Y->cols = cols;
    Y->data = calloc(rows*cols, sizeof(cg_float));

    Y->buf = ccl_buffer_new(ctx, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR,
                            rows*cols * sizeof(cgcl_float), Y->data, &err);
    CHECK_ERROR(err)

    Y->loc = CG_DATALOC_DEVICE_MEM;

    return Y;
}

int selectContext() {
    /* Code. */
    ctx = ccl_context_new_from_menu(NULL);
    if (ctx == NULL)
        exit(-1);

    CCLErr * err = NULL;
    prg = ccl_program_new_from_source_file(ctx, "/Users/praisethemoon/projects/cgraph/source/libcgraph/kernels/vec.cl", NULL);
    cl_bool status = ccl_program_build(prg, NULL, &err);
    printf("%s\n", ccl_program_get_build_log(prg, NULL));
    CHECK_ERROR(err)

    dev = ccl_context_get_device(ctx, 0, &err);
    queue  = ccl_queue_new(ctx, dev, 0, &err);

    return 0;
}

/*
 * *********************
 * Multiplication
 * *********************
 */

/*
 * d.d
 */
CGResultNode* mulDD(CGDouble* M, CGDouble* V, CGraph* graph, CGNode* parentNode){
    CGDouble* Y = calloc(1, sizeof(CGDouble));
    Y->value = M->value * V->value;

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_DOUBLE;
    result->value = Y;

    return result;
}

/*
 * d.V == V.d
 */
CGResultNode* mulDV(CGDouble* A, CGVector* V, CGraph* graph, CGNode* parentNode){
    CGVector* Y = makeDeviceVector(V->len);
    CCLErr * err = NULL;

    //if(A->value != 0){
        uint64_t len = V->len;
        CCLBuffer * a = V->buf, * c = NULL;

        CHECK_ERROR(err)

        CHECK_ERROR(err)

        CCLEvent* evt = NULL;
        size_t gws = len;

        cl_mem V_mem = ccl_buffer_unwrap(V->buf);
        cl_mem Y_mem = ccl_buffer_unwrap(Y->buf);

        cl_command_queue q = ccl_queue_unwrap(queue);

        //cblas_dcopy(V->len, V->data, 1, y, 1);
        //cblas_dscal(V->len, a->value, y, 1);

        CLBlastDcopy(V->len, V_mem, 0.0, 1, Y_mem, 0.0, 1, &q, NULL);
        CLBlastDscal(V->len, A->value, Y_mem, 0.0, 1, &q, NULL);
        //ccl_queue_destroy(queue);
    //}

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_VECTOR;
    result->value = Y;

    return result;
}

/*
 * d.M == M.d
 */
CGResultNode* mulDM(CGDouble* A, CGMatrix* M, CGraph* graph, CGNode* parentNode){
    uint64_t size = M->cols*M->rows;
    cg_float value = A->value;

    CGMatrix* Y = makeDeviceMatrix(M->rows, M->cols);

    //if(A->value != 0){
        CCLErr * err = NULL;

        CCLBuffer * a = M->buf;

        CHECK_ERROR(err)

        CHECK_ERROR(err)

        cl_mem M_mem = ccl_buffer_unwrap(M->buf);
        cl_mem Y_mem = ccl_buffer_unwrap(Y->buf);

        cl_command_queue q = ccl_queue_unwrap(queue);

        //cblas_dcopy(V->len, V->data, 1, y, 1);
        //cblas_dscal(V->len, a->value, y, 1);

        CLBlastDcopy(size, M_mem, 0.0, 1, Y_mem, 0.0, 1, &q, NULL);
        CLBlastDscal(size, A->value, Y_mem, 0.0, 1, &q, NULL);

        //ccl_queue_destroy(queue);
    //}

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;
}

/*
 * M*v
 * broadcasting
 */
CGResultNode* mulMV(CGMatrix* M, CGVector* V, CGraph* graph, CGNode* parentNode){
    if(M->cols != V->len){
        char msg[MAX_ERR_FMT_LEN];
        snprintf(msg, MAX_ERR_FMT_LEN, "Cannot Add M(%"PRIu64", %"PRIu64") by V(%"PRIu64")", M->rows, M->cols, V->len);
        return returnResultError(graph, CGET_INCOMPATIBLE_DIMENTIONS_EXCEPTION, parentNode, msg);
    }
    CCLErr * err = NULL;

    CGMatrix* Y = makeDeviceMatrix(M->rows, M->cols);

    uint64_t i = 0;

    //res[i] = M->data[i] * V->data[(i)%V->len];

    uint64_t len = M->rows*M->cols;
    uint64_t vlen = V->len;

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "mul_mv", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), M->buf, V->buf, Y->buf, ccl_arg_priv(vlen, cl_ulong), NULL);
    CHECK_ERROR(err)

    //ccl_queue_destroy(queue);

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;
}

/*
 * M*N
 * broadcasting
 */
CGResultNode* mulMM(CGMatrix* M1, CGMatrix* M2, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;
    if((M1->rows != M2->rows) && (M1->cols != M2->cols)){
        char msg[MAX_ERR_FMT_LEN];
        snprintf(msg, MAX_ERR_FMT_LEN, "Cannot multiply M(%"PRIu64", %"PRIu64") by N(%"PRIu64", %"PRIu64")", M1->rows, M1->cols, M2->rows, M2->cols);
        return returnResultError(graph, CGET_INCOMPATIBLE_DIMENTIONS_EXCEPTION, parentNode, msg);
    }

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

    CGMatrix* Y = makeDeviceMatrix(M1->rows, M1->cols);
    uint64_t len = M1->rows*M1->cols;

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "mul_vv", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), M1->buf, M2->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    //ccl_queue_destroy(queue);

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;
}

/*
 * M.v
 */
CGResultNode* dotMV(CGMatrix* M, CGVector* V, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;
    if(M->cols != 1){
        char msg[MAX_ERR_FMT_LEN];
        snprintf(msg, MAX_ERR_FMT_LEN, "Cannot calculate M(%"PRIu64", %"PRIu64") DOT V(%"PRIu64")", M->rows, M->cols, V->len);
        return returnResultError(graph, CGET_INCOMPATIBLE_DIMENTIONS_EXCEPTION, parentNode, msg);
    }

    uint64_t len =  M->rows*V->len;

    CGMatrix* Y = makeDeviceMatrix(M->rows, V->len);

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    CCLEvent* evt = NULL;
    cl_command_queue q = ccl_queue_unwrap(queue);
    cl_mem M_mem = ccl_buffer_unwrap(M->buf);
    cl_mem V_mem = ccl_buffer_unwrap(V->buf);
    cl_mem Y_mem = ccl_buffer_unwrap(Y->buf);

    CLBlastDgemm(CLBlastLayoutRowMajor,
                 CLBlastTransposeNo, CLBlastTransposeNo, M->rows, V->len, M->cols,
                 1.0,
                 M_mem, 0, M->cols,
                 V_mem, 0, V->len,
                 0.0,
                 Y_mem, 0, Y->cols,
                 &q, NULL
                 );

    //evt = ccl_buffer_enqueue_read(Y->buf, queue, CL_TRUE, 0,
    //                              len * sizeof(cl_double), Y->data, NULL, NULL);

    //ccl_queue_destroy(queue);

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;
}

/*
 * v.M
 */
CGResultNode* dotVM(CGVector* V, CGMatrix* M, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;
    if(M->rows != V->len){
        char msg[MAX_ERR_FMT_LEN];
        snprintf(msg, MAX_ERR_FMT_LEN, "Cannot calculate V(%"PRIu64") DOT M(%"PRIu64", %"PRIu64")", V->len, M->rows, M->cols);
        return returnResultError(graph, CGET_INCOMPATIBLE_DIMENTIONS_EXCEPTION, parentNode, msg);
    }

    CGVector* Y = makeDeviceVector(M->cols);

    uint64_t m = 1;
    uint64_t n = M->cols;
    uint64_t k = V->len;

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    CCLEvent* evt = NULL;
    cl_command_queue q = ccl_queue_unwrap(queue);
    cl_mem M_mem = ccl_buffer_unwrap(M->buf);
    cl_mem V_mem = ccl_buffer_unwrap(V->buf);
    cl_mem Y_mem = ccl_buffer_unwrap(Y->buf);

    //cblas_dgemm(CblasRowMajor,
    //            CblasNoTrans, CblasNoTrans, m, n, k, 1, V->data, k, M->data, n, 0, Y->data, n);

    CLBlastDgemm(CLBlastLayoutRowMajor,
                 CLBlastTransposeNo, CLBlastTransposeNo, m, n, k,
                 1.0,
                 V_mem, 0, k,
                 M_mem, 0, n,
                 0.0,
                 Y_mem, 0, n,
                 &q, NULL
    );

    //ccl_queue_destroy(queue);

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_VECTOR;
    result->value = Y;

    return result;
}

/*
 * M.N
 */
CGResultNode* dotMM(CGMatrix* M, CGMatrix* N, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;
    if(M->cols != N->rows){
        char msg[MAX_ERR_FMT_LEN];
        snprintf(msg, MAX_ERR_FMT_LEN, "Cannot DOT M(%"PRIu64", %"PRIu64") by N(%"PRIu64", %"PRIu64")", M->rows, M->cols, N->rows, N->cols);
        return returnResultError(graph, CGET_INCOMPATIBLE_DIMENTIONS_EXCEPTION, parentNode, msg);
    }

    CGMatrix* Z = makeDeviceMatrix(M->rows, N->cols);

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    CCLEvent* evt = NULL;
    cl_command_queue q = ccl_queue_unwrap(queue);
    cl_mem M_mem = ccl_buffer_unwrap(M->buf);
    cl_mem N_mem = ccl_buffer_unwrap(N->buf);
    cl_mem Z_mem = ccl_buffer_unwrap(Z->buf);

    /*
    cblas_dgemm(CblasRowMajor,
                CblasNoTrans, CblasNoTrans, M->rows, N->cols, M->cols,
                1.0, M->data, M->cols, N->data, N->cols, 0, z, Z->cols);
    */

    CLBlastDgemm(CLBlastLayoutRowMajor,
                 CLBlastTransposeNo, CLBlastTransposeNo, M->rows, N->cols, M->cols,
                 1.0,
                 M_mem, 0, M->cols,
                 N_mem, 0, N->cols,
                 0.0,
                 Z_mem, 0, Z->cols,
                 &q, NULL
    );

    //ccl_queue_destroy(queue);

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Z;

    return result;
}

/*
 * V.V
 */
CGResultNode* crossVV(CGVector* V1, CGVector* V2, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;
    if(V1->len != V2->len){
        char msg[MAX_ERR_FMT_LEN];
        snprintf(msg, MAX_ERR_FMT_LEN, "Cannot compute Cross Product of V(%"PRIu64") by V(%"PRIu64")", V1->len, V2->len);
        return returnResultError(graph, CGET_INCOMPATIBLE_DIMENTIONS_EXCEPTION, parentNode, msg);
    }

    uint64_t size = V1->len;

    CGVector* Y = makeDeviceVector(V1->len);
    uint64_t len = V1->len;

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "mul_vv", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), V1->buf, V2->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    //ccl_queue_destroy(queue);

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_VECTOR;
    result->value = Y;

    return result;
}

/*
 * *********************
 * Dot Product
 * *********************
 */

/*
 * V.V
 */
CGResultNode* dotVV(CGVector* V1, CGVector* V2, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;
    if(V1->len != V2->len){
        char msg[MAX_ERR_FMT_LEN];
        snprintf(msg, MAX_ERR_FMT_LEN, "Cannot compute Dot Product of V(%"PRIu64") by V(%"PRIu64")", V1->len, V2->len);
        return returnResultError(graph, CGET_INCOMPATIBLE_DIMENTIONS_EXCEPTION, parentNode, msg);
    }

    uint64_t size = V1->len;
    cg_float res = 0;

    CGDouble* Y = makeDeviceDouble();

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    CCLEvent* evt = NULL;
    cl_command_queue q = ccl_queue_unwrap(queue);
    cl_mem M_mem = ccl_buffer_unwrap(V1->buf);
    cl_mem N_mem = ccl_buffer_unwrap(V2->buf);
    cl_mem Y_mem = ccl_buffer_unwrap(Y->buf);

    //Y->value = cblas_ddot(V1->len, V1->data, 1, V2->data, 1);

    CLBlastDdot(size, Y_mem, 0,
            M_mem, 0.0, 1,
            N_mem, 0.0, 1,
            &q, NULL);

    evt = ccl_buffer_enqueue_read(Y->buf, queue, CL_TRUE, 0,
                                  sizeof(cl_double), &Y->value, NULL, NULL);

    //ccl_queue_destroy(queue);
    ccl_buffer_destroy(Y->buf);

    Y->loc = CG_DATALOC_HOST_MEM;

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_DOUBLE;
    result->value = Y;

    return result;
}

/*
 * *********************
 * Division
 * *********************
 */

/*
 *  d/d
 */
CGResultNode* divDD(CGDouble* D1, CGDouble* D2, CGraph* graph, CGNode* parentNode){
    if(D2->value == 0.0){
        char msg[MAX_ERR_FMT_LEN];
        snprintf(msg, MAX_ERR_FMT_LEN, "Diving by zero D/(D=0)");
        return returnResultError(graph, CGET_DIVIDE_BY_ZERO, parentNode, msg);
    }

    cg_float res = D1->value / D2->value;

    CGDouble* Y = calloc(1, sizeof(CGDouble));
    Y->value = res;

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_DOUBLE;
    result->value = Y;

    return result;
}

/*
 *  V/d
 */
CGResultNode* divVD(CGVector* V, CGDouble* D, CGraph* graph, CGNode* parentNode){
    if(D->value == 0.0){
        char msg[MAX_ERR_FMT_LEN];
        snprintf(msg, MAX_ERR_FMT_LEN, "Diving by zero V/(D=0)");
        return returnResultError(graph, CGET_DIVIDE_BY_ZERO, parentNode, msg);
    }

    CGVector* Y = makeDeviceVector(V->len);
    CCLErr * err = NULL;

    //if(A->value != 0){
    uint64_t len = V->len;
    CCLBuffer * a = V->buf, * c = NULL;

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    CCLEvent* evt = NULL;
    size_t gws = len;

    cl_mem V_mem = ccl_buffer_unwrap(V->buf);
    cl_mem Y_mem = ccl_buffer_unwrap(Y->buf);

    cl_command_queue q = ccl_queue_unwrap(queue);

    //cblas_dcopy(V->len, V->data, 1, y, 1);
    //cblas_dscal(V->len, a->value, y, 1);

    CLBlastDcopy(V->len, V_mem, 0.0, 1, Y_mem, 0.0, 1, &q, NULL);
    CLBlastDscal(V->len, 1.0/D->value, Y_mem, 0.0, 1, &q, NULL);
    //ccl_queue_destroy(queue);
    //}

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_VECTOR;
    result->value = Y;

    return result;

}

/*
 *  V/V
 */
CGResultNode* divVV(CGVector* V1, CGVector* V2, CGraph* graph, CGNode* parentNode){
    if(V1->len != V2->len){
        char msg[MAX_ERR_FMT_LEN];
        snprintf(msg, MAX_ERR_FMT_LEN, "Cannot calculate  V(%"PRIu64") DIV V(%"PRIu64")", V1->len, V2->len);
        return returnResultError(graph, CGET_INCOMPATIBLE_DIMENTIONS_EXCEPTION, parentNode, msg);
    }
    CCLErr * err = NULL;

    uint64_t size = V1->len;

    CGVector* Y = makeDeviceVector(V1->len);
    uint64_t len = V1->len;

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "div_vv", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), V1->buf, V2->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    //ccl_queue_destroy(queue);

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_VECTOR;
    result->value = Y;

    return result;
}

/*
 *  d/V element-wise
 */
CGResultNode* divDV(CGDouble* D, CGVector* V, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;

    uint64_t size = V->len;

    CGVector* Y = makeDeviceVector(V->len);
    uint64_t len = V->len;

    // TODO: check if D.value == 0.0

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "div_dv", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), ccl_arg_priv(D->value, cgcl_float), V->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    //ccl_queue_destroy(queue);

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_VECTOR;
    result->value = Y;

    return result;
}

/*
 *  M/d
 */
CGResultNode* divMD(CGMatrix* M, CGDouble* D, CGraph* graph, CGNode* parentNode){
    if(D->value == 0.0){
        char msg[MAX_ERR_FMT_LEN];
        snprintf(msg, MAX_ERR_FMT_LEN, "Diving by zero M/(D=0)");
        return returnResultError(graph, CGET_DIVIDE_BY_ZERO, parentNode, msg);
    }

    uint64_t size = M->cols*M->rows;
    cg_float value = D->value;

    CGMatrix* Y = makeDeviceMatrix(M->rows, M->cols);

    //if(A->value != 0){
    CCLErr * err = NULL;

    CCLBuffer * a = M->buf;

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    cl_mem M_mem = ccl_buffer_unwrap(M->buf);
    cl_mem Y_mem = ccl_buffer_unwrap(Y->buf);

    cl_command_queue q = ccl_queue_unwrap(queue);

    CLBlastDcopy(size, M_mem, 0.0, 1, Y_mem, 0.0, 1, &q, NULL);
    CLBlastDscal(size, 1.0/D->value, Y_mem, 0.0, 1, &q, NULL);

    //ccl_queue_destroy(queue);
    //}

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;
}

/*
 * M/v
 */
CGResultNode* divMV(CGMatrix* M, CGVector* V, CGraph* graph, CGNode* parentNode){
    if(M->rows != V->len){
        char msg[MAX_ERR_FMT_LEN];
        snprintf(msg, MAX_ERR_FMT_LEN, "Cannot calculate  M(%"PRIu64", %"PRIu64") DIV V(%"PRIu64")", M->rows, M->cols, V->len);
        return returnResultError(graph, CGET_INCOMPATIBLE_DIMENTIONS_EXCEPTION, parentNode, msg);
    }

    CCLErr * err = NULL;

    CGMatrix* Y = makeDeviceMatrix(M->rows, M->cols);

    uint64_t len = M->rows*M->cols;
    uint64_t vlen = V->len;

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "div_mv", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), M->buf, V->buf, Y->buf, ccl_arg_priv(vlen, cl_ulong), NULL);
    CHECK_ERROR(err)

    //ccl_queue_destroy(queue);

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;
}

/*
 *  d/M element-wise
 */
CGResultNode* divDM(CGDouble* D, CGMatrix* M, CGraph* graph, CGNode* parentNode){
    uint64_t len = M->rows*M->cols;
    CCLErr * err = NULL;

    CGMatrix* Y = makeDeviceMatrix(M->rows, M->cols);

    // TODO: check if D.value == 0.0

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "div_dv", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), ccl_arg_priv(D->value, cgcl_float), M->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    //ccl_queue_destroy(queue);

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;
}

/*
 * *********************
 * Addition
 * *********************
 */

/*
 * d+d
 */
CGResultNode* addDD(CGDouble* D1, CGDouble* D2, CGraph* graph, CGNode* parentNode){
    cg_float res = D1->value + D2->value;

    CGDouble* Y = calloc(1, sizeof(CGDouble));
    Y->value = res;

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_DOUBLE;
    result->value = Y;

    return result;
}

/*
 * V+d
 */
CGResultNode* addVD(CGVector* V, CGDouble* D, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;

    uint64_t size = V->len;

    CGVector* Y = makeDeviceVector(V->len);
    uint64_t len = V->len;

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "add_dv", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), ccl_arg_priv(D->value, cgcl_float), V->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    //ccl_queue_destroy(queue);

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_VECTOR;
    result->value = Y;

    return result;
}

/*
 * M+d
 */
CGResultNode* addMD(CGMatrix* M, CGDouble* D, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;

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

    CGMatrix* Y = makeDeviceMatrix(M->rows, M->cols);

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    CCLEvent* evt = NULL;
    size_t gws = size;

    evt = ccl_program_enqueue_kernel(prg, "add_dv", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(size, cl_uint), ccl_arg_priv(D->value, cgcl_float), M->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    //ccl_queue_destroy(queue);

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;;
}

/*
 * V+V
 */
CGResultNode* addVV(CGVector* V1, CGVector* V2, CGraph* graph, CGNode* parentNode){
    if(V1->len != V2->len){
        char msg[MAX_ERR_FMT_LEN];
        snprintf(msg, MAX_ERR_FMT_LEN, "Cannot Add V(%"PRIu64") by V(%"PRIu64")", V1->len, V2->len);
        return returnResultError(graph, CGET_INCOMPATIBLE_DIMENTIONS_EXCEPTION, parentNode, msg);
    }

    uint64_t size = V1->len;
    CCLErr * err = NULL;

    CGVector* Y = makeDeviceVector(size);

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    CCLEvent* evt = NULL;
    size_t gws = size;

    evt = ccl_program_enqueue_kernel(prg, "add_vv", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(size, cl_uint), V1->buf, V2->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    //ccl_queue_destroy(queue);

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_VECTOR;
    result->value = Y;

    return result;
}

/*
 * M+V
 */
CGResultNode* addMV(CGMatrix* M, CGVector* V, CGraph* graph, CGNode* parentNode){
    if(M->cols != V->len){
        char msg[MAX_ERR_FMT_LEN];
        snprintf(msg, MAX_ERR_FMT_LEN, "Cannot Add M(%"PRIu64", %"PRIu64") by V(%"PRIu64")", M->rows, M->cols, V->len);
        return returnResultError(graph, CGET_INCOMPATIBLE_DIMENTIONS_EXCEPTION, parentNode, msg);
    }

    CCLErr * err = NULL;

    CGMatrix* Y = makeDeviceMatrix(M->rows, M->cols);

    uint64_t len = M->rows*M->cols;
    uint64_t vlen = V->len;

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "add_mv", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), M->buf, V->buf, Y->buf, ccl_arg_priv(vlen, cl_ulong), NULL);
    CHECK_ERROR(err)

    //ccl_queue_destroy(queue);

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;
}

/*
 * M+M
 */
CGResultNode* addMM(CGMatrix* M1, CGMatrix* M2, CGraph* graph, CGNode* parentNode){
    if((M1->rows != M2->rows) && (M1->cols != M2->cols)){
        char msg[MAX_ERR_FMT_LEN];
        snprintf(msg, MAX_ERR_FMT_LEN, "Cannot add M(%"PRIu64", %"PRIu64") by N(%"PRIu64", %"PRIu64")", M1->rows, M1->cols, M2->rows, M2->cols);
        return returnResultError(graph, CGET_INCOMPATIBLE_DIMENTIONS_EXCEPTION, parentNode, msg);
    }

    CCLErr * err = NULL;
    uint64_t size = M1->cols*M1->rows;

    CGMatrix* Y = makeDeviceMatrix(M1->rows, M1->cols);
    uint64_t len = M1->rows*M1->cols;

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "add_vv", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), M1->buf, M2->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    //ccl_queue_destroy(queue);

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;
}

/*
 * *********************
 * Substraction
 * *********************
 */

/*
 * d-d
 */
CGResultNode* subDD(CGDouble* D1, CGDouble* D2, CGraph* graph, CGNode* parentNode){
    cg_float res = D1->value - D2->value;

    CGDouble* Y = calloc(1, sizeof(CGDouble));
    Y->value = res;

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_DOUBLE;
    result->value = Y;

    return result;
}

/*
 * V-d
 */
CGResultNode* subVD(CGVector* V, CGDouble* D, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;

    uint64_t size = V->len;

    CGVector* Y = makeDeviceVector(V->len);
    uint64_t len = V->len;

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "sub_vd", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), ccl_arg_priv(D->value, cgcl_float), V->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    //ccl_queue_destroy(queue);

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_VECTOR;
    result->value = Y;

    return result;
}

/*
 * d-V
 */
CGResultNode* subDV(CGDouble* D, CGVector* V, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;

    uint64_t size = V->len;

    CGVector* Y = makeDeviceVector(V->len);
    uint64_t len = V->len;

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "sub_dv", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), ccl_arg_priv(D->value, cgcl_float), V->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    //ccl_queue_destroy(queue);

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_VECTOR;
    result->value = Y;

    return result;
}

/*
 * M-d
 */
CGResultNode* subMD(CGMatrix* M, CGDouble* D, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;

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

    CGMatrix* Y = makeDeviceMatrix(M->rows, M->cols);

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    CCLEvent* evt = NULL;
    size_t gws = size;

    evt = ccl_program_enqueue_kernel(prg, "sub_vd", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(size, cl_uint), ccl_arg_priv(D->value, cgcl_float), M->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    //ccl_queue_destroy(queue);

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;
}

/*
 * D-M
 */
CGResultNode* subDM(CGDouble* D, CGMatrix* M, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;

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

    CGMatrix* Y = makeDeviceMatrix(M->rows, M->cols);

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    CCLEvent* evt = NULL;
    size_t gws = size;

    evt = ccl_program_enqueue_kernel(prg, "sub_dv", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(size, cl_uint), ccl_arg_priv(D->value, cgcl_float), M->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    //ccl_queue_destroy(queue);

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;
}

/*
 * V-V
 */
CGResultNode* subVV(CGVector* V1, CGVector* V2, CGraph* graph, CGNode* parentNode){
    if(V1->len != V2->len){
        char msg[MAX_ERR_FMT_LEN];
        snprintf(msg, MAX_ERR_FMT_LEN, "Cannot Substract V(%"PRIu64") by V(%"PRIu64")", V1->len, V2->len);
        return returnResultError(graph, CGET_INCOMPATIBLE_DIMENTIONS_EXCEPTION, parentNode, msg);
    }

    uint64_t size = V1->len;
    CCLErr * err = NULL;

    CGVector* Y = makeDeviceVector(size);

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    CCLEvent* evt = NULL;
    size_t gws = size;

    evt = ccl_program_enqueue_kernel(prg, "sub_vv", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(size, cl_uint), V1->buf, V2->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    //ccl_queue_destroy(queue);

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_VECTOR;
    result->value = Y;

    return result;
}

/*
 * M-M
 */
CGResultNode* subMM(CGMatrix* M1, CGMatrix* M2, CGraph* graph, CGNode* parentNode){
    if((M1->rows != M2->rows) && (M1->cols != M2->cols)){
        char msg[MAX_ERR_FMT_LEN];
        snprintf(msg, MAX_ERR_FMT_LEN, "Cannot Substract M(%"PRIu64", %"PRIu64") by N(%"PRIu64", %"PRIu64")", M1->rows, M1->cols, M2->rows, M2->cols);
        return returnResultError(graph, CGET_INCOMPATIBLE_DIMENTIONS_EXCEPTION, parentNode, msg);
    }

    CCLErr * err = NULL;
    uint64_t size = M1->cols*M1->rows;

    CGMatrix* Y = makeDeviceMatrix(M1->rows, M1->cols);
    uint64_t len = M1->rows*M1->cols;

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "sub_vv", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), M1->buf, M2->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    //ccl_queue_destroy(queue);

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;
}

/*
 * M-V
 */
CGResultNode* subMV(CGMatrix* M, CGVector* V, CGraph* graph, CGNode* parentNode){
    if(M->rows != V->len){
        char msg[MAX_ERR_FMT_LEN];
        snprintf(msg, MAX_ERR_FMT_LEN, "Cannot Substract M(%"PRIu64", %"PRIu64") by V(%"PRIu64")", M->rows, M->cols, V->len);
        return returnResultError(graph, CGET_INCOMPATIBLE_DIMENTIONS_EXCEPTION, parentNode, msg);
    }

    CCLErr * err = NULL;

    CGMatrix* Y = makeDeviceMatrix(M->rows, M->cols);

    uint64_t len = M->rows*M->cols;
    uint64_t vlen = V->len;

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "sub_mv", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), M->buf, V->buf, Y->buf, ccl_arg_priv(vlen, cl_ulong), NULL);
    CHECK_ERROR(err)

    //ccl_queue_destroy(queue);

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;
}

/*
 * V-M
 */
CGResultNode* subVM(CGVector* V, CGMatrix* M, CGraph* graph, CGNode* parentNode){
    if(M->rows != V->len){
        char msg[MAX_ERR_FMT_LEN];
        snprintf(msg, MAX_ERR_FMT_LEN, "Cannot Substract M(%"PRIu64", %"PRIu64") by V(%"PRIu64")", M->rows, M->cols, V->len);
        return returnResultError(graph, CGET_INCOMPATIBLE_DIMENTIONS_EXCEPTION, parentNode, msg);
    }

    CCLErr * err = NULL;

    CGMatrix* Y = makeDeviceMatrix(M->rows, M->cols);

    uint64_t len = M->rows*M->cols;
    uint64_t vlen = V->len;

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "sub_mv", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), M->buf, V->buf, Y->buf, ccl_arg_priv(vlen, cl_ulong), NULL);
    CHECK_ERROR(err)

    //ccl_queue_destroy(queue);

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;
}

/*
 * *********************
 * Power
 * *********************
 */

/*
 * d^d
 */
CGResultNode* powDD(CGDouble* D1, CGDouble* D2, CGraph* graph, CGNode* parentNode){
    cg_float res = pow(D1->value, D2->value);

    CGDouble* Y = calloc(1, sizeof(CGDouble));
    Y->value = res;

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_DOUBLE;
    result->value = Y;

    return result;
}

/*
 * V^d
 */
CGResultNode* powVD(CGVector* V, CGDouble* D, CGraph* graph, CGNode* parentNode){
    cg_float value = D->value;

    CCLErr * err = NULL;

    CGVector* Y = makeDeviceVector(V->len);

    uint64_t len = V->len;

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "pow_vd", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), ccl_arg_priv(value, cgcl_float), V->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_VECTOR;
    result->value = Y;

    return result;
}

/*
 * M^d
 */
CGResultNode* powMD(CGMatrix* M, CGDouble* D, CGraph* graph, CGNode* parentNode){
    cg_float value = D->value;

    CCLErr * err = NULL;

    CGMatrix* Y = makeDeviceMatrix(M->rows, M->cols);

    uint64_t len = M->rows*M->cols;

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "pow_vd", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), ccl_arg_priv(value, cgcl_float), M->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;
}

/*
 * *********************
 * Transpose Multiplication
 * *********************
 */
/*
 * M^T.v
 * TODO
 */
CGResultNode* mulMtV(CGMatrix* M, CGVector* V, CGraph* graph, CGNode* parentNode){
    cg_float* y = calloc(V->len, sizeof(cg_float));
    CGVector* Y = calloc(1, sizeof(CGVector));
    Y->len = V->len;
    Y->data = y;

    cblas_dgemv(CblasRowMajor,
                CblasTrans, M->rows, M->cols, 1.0, M->data,
                V->len, V->data, 1, 0.0, Y->data, 1);

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_VECTOR;
    result->value = Y;

    return result;
}

/*
 * exp(d)
 */
CGResultNode* expD(CGDouble* D, CGraph* graph, CGNode* parentNode){
    cg_float y = exp(D->value);
    CGDouble* Y = calloc(1, sizeof(CGDouble));
    Y->value = y;

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_DOUBLE;
    result->value = Y;

    return result;
}

/*
 * exp(v)
 */
CGResultNode* expV(CGVector* V, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;

    CGVector* Y = makeDeviceVector(V->len);

    uint64_t len = V->len;

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "exp_v", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), V->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_VECTOR;
    result->value = Y;

    return result;
}

/*
 * exp(M)
 */
CGResultNode* expM(CGMatrix* M, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;

    CGMatrix* Y = makeDeviceMatrix(M->rows, M->cols);
    uint64_t len = M->rows*M->cols;

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "exp_v", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), M->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;
}

/*
 * log(d)
 */
CGResultNode* logD(CGDouble* D, CGraph* graph, CGNode* parentNode){
    cg_float y = log(D->value);
    CGDouble* Y = calloc(1, sizeof(CGDouble));
    Y->value = y;

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_DOUBLE;
    result->value = Y;

    return result;
}

/*
 * log(V)
 */
CGResultNode* logV(CGVector* V, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;

    CGVector* Y = makeDeviceVector(V->len);

    uint64_t len = V->len;

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "log_v", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), V->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_VECTOR;
    result->value = Y;

    return result;
}

/*
 * exp(M)
 */
CGResultNode* logM(CGMatrix* M, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;

    CGMatrix* Y = makeDeviceMatrix(M->rows, M->cols);
    uint64_t len = M->rows*M->cols;

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "log_v", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), M->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;
}

/*
 * *********************
 * Sin
 * *********************
 */

/*
 * sin(d)
 */
CGResultNode* sinD(CGDouble* D, CGraph* graph, CGNode* parentNode){
    cg_float y = sin(D->value);
    CGDouble* Y = calloc(1, sizeof(CGDouble));
    Y->value = y;

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_DOUBLE;
    result->value = Y;

    return result;
}

/*
 * sin(V)
 */
CGResultNode* sinV(CGVector* V, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;

    CGVector* Y = makeDeviceVector(V->len);

    uint64_t len = V->len;

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "sin_v", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), V->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_VECTOR;
    result->value = Y;

    return result;
}

/*
 * sin(M)
 */
CGResultNode* sinM(CGMatrix* M, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;

    CGMatrix* Y = makeDeviceMatrix(M->rows, M->cols);
    uint64_t len = M->rows*M->cols;

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "sin_v", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), M->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;
}

/*
 * *********************
 * cos
 * *********************
 */

/*
 * cos(d)
 */
CGResultNode* cosD(CGDouble* D, CGraph* graph, CGNode* parentNode){
    cg_float y = cos(D->value);
    CGDouble* Y = calloc(1, sizeof(CGDouble));
    Y->value = y;

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_DOUBLE;
    result->value = Y;

    return result;
}

/*
 * cos(V)
 */
CGResultNode* cosV(CGVector* V, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;

    CGVector* Y = makeDeviceVector(V->len);

    uint64_t len = V->len;

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "cos_v", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), V->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_VECTOR;
    result->value = Y;

    return result;
}

/*
 * sin(M)
 */
CGResultNode* cosM(CGMatrix* M, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;

    CGMatrix* Y = makeDeviceMatrix(M->rows, M->cols);
    uint64_t len = M->rows*M->cols;

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "cos_v", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), M->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;
}

/*
 * *********************
 * tan
 * *********************
 */

/*
 * tan(d)
 */
CGResultNode* tanD(CGDouble* D, CGraph* graph, CGNode* parentNode){
    cg_float y = tan(D->value);
    CGDouble* Y = calloc(1, sizeof(CGDouble));
    Y->value = y;

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_DOUBLE;
    result->value = Y;

    return result;
}

/*
 * tan(V)
 */
CGResultNode* tanV(CGVector* V, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;

    CGVector* Y = makeDeviceVector(V->len);

    uint64_t len = V->len;

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "tan_v", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), V->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_VECTOR;
    result->value = Y;

    return result;
}

/*
 * tan(M)
 */
CGResultNode* tanM(CGMatrix* M, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;

    CGMatrix* Y = makeDeviceMatrix(M->rows, M->cols);
    uint64_t len = M->rows*M->cols;

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "tan_v", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), M->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;
}

/*
 * *********************
 * tanh
 * *********************
 */

/*
 * tanh(d)
 */
CGResultNode* tanhD(CGDouble* D, CGraph* graph, CGNode* parentNode){
    cg_float y = tanh(D->value);
    CGDouble* Y = calloc(1, sizeof(CGDouble));
    Y->value = y;

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_DOUBLE;
    result->value = Y;

    return result;
}

/*
 * tanh(V)
 */
CGResultNode* tanhV(CGVector* V, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;

    CGVector* Y = makeDeviceVector(V->len);

    uint64_t len = V->len;

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "tanh_v", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), V->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_VECTOR;
    result->value = Y;

    return result;
}

/*
 * tanh(M)
 */
CGResultNode* tanhM(CGMatrix* M, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;

    CGMatrix* Y = makeDeviceMatrix(M->rows, M->cols);
    uint64_t len = M->rows*M->cols;

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "tanh_v", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), M->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;
}

/*
 * *********************
 * Invert
 * *********************
 */

// TODO

/*
 * *********************
 * Transpose
 * *********************
 */

/*
 * D^t
 */
CGResultNode* transposeD(CGDouble* D, CGraph* graph, CGNode* parentNode){
    CGDouble* R = calloc(1, sizeof(CGDouble));
    R->value = D->value;

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_DOUBLE;
    result->value = R;

    return result;
}

/*
 * V^t
 */
CGResultNode* transposeV(CGVector* V, CGraph* graph, CGNode* parentNode){
    uint64_t size = V->len;

    CCLErr * err = NULL;
    CCLEvent* evt = NULL;
    CGMatrix* Y = calloc(1, sizeof(CGMatrix));
    Y->rows = V->len;
    Y->cols = 1;
    Y->data = NULL;

    Y->buf = ccl_buffer_new(ctx, CL_MEM_READ_WRITE ,
                            V->len * sizeof(cgcl_float), NULL, &err);

    CHECK_ERROR(err)

    CCLEventWaitList ewl = NULL;
    evt = ccl_buffer_enqueue_copy(V->buf, Y->buf, queue, 0, 0, V->len * sizeof(cgcl_float), NULL, &err);

    CHECK_ERROR(err)
    ccl_event_wait(ccl_ewl(&ewl, evt, NULL), &err);
    CHECK_ERROR(err)

    Y->loc = CG_DATALOC_DEVICE_MEM;
    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;
}

/*
 * M^t
 */
CGResultNode* transposeM(CGMatrix* M, CGraph* graph, CGNode* parentNode){
    uint64_t size = M->cols*M->rows;
    CGMatrix* Y = makeDeviceMatrix(M->cols, M->rows);

    //if(A->value != 0){
    CCLErr * err = NULL;

    CCLBuffer * a = M->buf;

    CHECK_ERROR(err)

    CHECK_ERROR(err)

    cl_mem M_mem = ccl_buffer_unwrap(M->buf);
    cl_mem Y_mem = ccl_buffer_unwrap(Y->buf);

    cl_command_queue q = ccl_queue_unwrap(queue);

    CLBlastDomatcopy(CLBlastLayoutRowMajor, CLBlastTransposeYes, M->rows, M->cols, 1.0f, M_mem, 0, M->cols, Y_mem, 0, Y->cols, &q, NULL);

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;
}

/*
 * *********************
 * SUM
 * *********************
 */

/*
 * sum(D)
 */
CGResultNode* sumD(CGDouble* D, CGraph* graph, CGNode* parentNode){
    return makeDoubleResultNode(D->value);
}

/*
 * sum(V)
 */
CGResultNode* sumV(CGVector* V, CGraph* graph, CGNode* parentNode){
    CGDouble* Y = makeDeviceDouble();
    CCLEvent* evt = NULL;
    cl_command_queue q = ccl_queue_unwrap(queue);
    cl_mem V_mem = ccl_buffer_unwrap(V->buf);
    cl_mem Y_mem = ccl_buffer_unwrap(Y->buf);

    uint64_t  size = V->len;

    CLBlastDsum(size, Y_mem, 0, V_mem, 0, 1, &q, NULL);

    evt = ccl_buffer_enqueue_read(Y->buf, queue, CL_TRUE, 0,
                                  sizeof(cl_double), &Y->value, NULL, NULL);

    //ccl_queue_destroy(queue);
    ccl_buffer_destroy(Y->buf);

    Y->loc = CG_DATALOC_HOST_MEM;

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_DOUBLE;
    result->value = Y;

    return result;
}

/*
 * TODO:
 * sum(M, axis=axis)
 */

CGResultNode* sumM(CGMatrix* M, CGraph* graph, CGNode* parentNode, uint8_t axis){
    CCLErr * err = NULL;
    CGVector* Y = NULL;
    CCLEvent* evt = NULL;
    uint64_t  size = M->rows*M->cols;
    if(axis == 0){
        uint64_t len = M->cols;
        Y = makeDeviceVectorZeros(len);

        size_t gws = size;

        evt = ccl_program_enqueue_kernel(prg, "sum_matrix_rows", queue, 1, NULL, &gws,
                                         NULL, NULL, &err, ccl_arg_priv(size, cl_uint), ccl_arg_priv(len, cl_uint), M->buf, Y->buf, NULL);

        CHECK_ERROR(err)
    }
    else {
        uint64_t len = M->rows;
        Y = makeDeviceVectorZeros(len);

        size_t gws = size;

        evt = ccl_program_enqueue_kernel(prg, "sum_matrix_cols", queue, 1, NULL, &gws,
                                         NULL, NULL, &err, ccl_arg_priv(size, cl_uint), ccl_arg_priv(M->cols, cl_uint), M->buf, Y->buf, NULL);

        CHECK_ERROR(err)
    }

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_VECTOR;
    result->value = Y;

    return result;
}

CGResultNode* max(CGNode* X, CGraph* graph){
    CGResultNode* res = computeCGNode(graph, X->axop->uhs);

    switch(res->type){
        case CGVT_DOUBLE:
        {
            cg_float y = 0;
            CGDouble* Y = calloc(1, sizeof(CGDouble));

            Y->value = ((CGDouble*)res->value)->value;
            CGResultNode* result = calloc(1, sizeof(CGResultNode));
            result->type = CGVT_DOUBLE;
            result->value = Y;

            return result;
        }

        case CGVT_VECTOR:
        {
            CGVector* v = (CGVector*)res->value;
            CGDouble* Y = calloc(1, sizeof(CGDouble));

            uint64_t i = 1;
            cg_float m = v->data[0];

            for (;i < v->len; i++){
                if(v->data[i] > m)
                    m = v->data[i];
            }

            Y->value = m;
            CGResultNode* result = calloc(1, sizeof(CGResultNode));
            result->type = CGVT_DOUBLE;
            result->value = Y;

            return result;
        }

        case CGVT_MATRIX: {
            CGMatrix* M = (CGMatrix*)res->value;
            CGVector* V = calloc(1, sizeof(CGVector));

            if(X->axop->axis == 0){
                uint64_t len = M->cols;
                cg_float* y = calloc(len, sizeof(cg_float));

                V->data = y;
                V->len = len;

                uint64_t i = 0;

                for(;i<M->cols;i++){
                    y[i] = M->data[i];
                }

                for(i=0;i<M->rows*M->cols;i++){
                    if(y[i%len] < M->data[i])
                        y[i%len] = M->data[i];
                }
            }
            else {
                uint64_t len = M->rows;
                cg_float* y = calloc(len, sizeof(cg_float));

                V->data = y;
                V->len = len;

                uint64_t i = 0;

                for(;i<M->rows;i++){
                    y[i] = M->data[i*M->cols];
                }

                for(i=0;i<M->rows*M->cols;i++){
                    if(y[i/M->cols] < M->data[i])
                        y[i/M->cols] = M->data[i];
                }
            }

            CGResultNode* result = calloc(1, sizeof(CGResultNode));
            result->type = CGVT_VECTOR;
            result->value = V;

            return result;

        }
    }
}

CGResultNode* min(CGNode* X, CGraph* graph){
    CGResultNode* res = computeCGNode(graph, X->axop->uhs);

    switch(res->type){
        case CGVT_DOUBLE:
        {
            cg_float y = 0;
            CGDouble* Y = calloc(1, sizeof(CGDouble));

            Y->value = ((CGDouble*)res->value)->value;
            CGResultNode* result = calloc(1, sizeof(CGResultNode));
            result->type = CGVT_DOUBLE;
            result->value = Y;

            return result;
        }

        case CGVT_VECTOR:
        {
            CGVector* v = (CGVector*)res->value;
            CGDouble* Y = calloc(1, sizeof(CGDouble));

            uint64_t i = 1;
            cg_float m = v->data[0];

            for (;i < v->len; i++){
                if(v->data[i] > m)
                    m = v->data[i];
            }

            Y->value = m;
            CGResultNode* result = calloc(1, sizeof(CGResultNode));
            result->type = CGVT_DOUBLE;
            result->value = Y;

            return result;
        }

        case CGVT_MATRIX: {
            CGMatrix* M = (CGMatrix*)res->value;
            CGVector* V = calloc(1, sizeof(CGVector));

            if(X->axop->axis == 0){
                uint64_t len = M->cols;
                cg_float* y = calloc(len, sizeof(cg_float));

                V->data = y;
                V->len = len;

                uint64_t i = 0;

                for(;i<M->cols;i++){
                    y[i] = M->data[i];
                }

                for(i=0;i<M->rows*M->cols;i++){
                    if(y[i%len] > M->data[i])
                        y[i%len] = M->data[i];
                }
            }
            else {
                uint64_t len = M->rows;
                cg_float* y = calloc(len, sizeof(cg_float));

                V->data = y;
                V->len = len;

                uint64_t i = 0;

                for(;i<M->rows;i++){
                    y[i] = M->data[i*M->cols];
                }

                for(i=0;i<M->rows*M->cols;i++){
                    if(y[i/M->cols] > M->data[i])
                        y[i/M->cols] = M->data[i];
                }
            }

            CGResultNode* result = calloc(1, sizeof(CGResultNode));
            result->type = CGVT_VECTOR;
            result->value = V;

            return result;

        }
    }
}

CGResultNode* argmax(CGNode* X, CGraph* graph){
    CGResultNode* res = computeCGNode(graph, X->axop->uhs);

    switch(res->type){
        case CGVT_DOUBLE:
        {
            cg_float y = 0;
            CGDouble* Y = calloc(1, sizeof(CGDouble));

            Y->value = ((CGDouble*)res->value)->value;
            CGResultNode* result = calloc(1, sizeof(CGResultNode));
            result->type = CGVT_DOUBLE;
            result->value = Y;

            return result;
        }

        case CGVT_VECTOR:
        {
            CGVector* v = (CGVector*)res->value;
            CGDouble* Y = calloc(1, sizeof(CGDouble));

            uint64_t i = 1;
            uint64_t maxIdx = 0;

            for (;i < v->len; i++){
                if(v->data[i] > v->data[maxIdx])
                    maxIdx = i;
            }

            Y->value = maxIdx;
            CGResultNode* result = calloc(1, sizeof(CGResultNode));
            result->type = CGVT_DOUBLE;
            result->value = Y;

            return result;
        }

        case CGVT_MATRIX: {
            CGMatrix* M = (CGMatrix*)res->value;
            CGVector* V = calloc(1, sizeof(CGVector));

            if(X->axop->axis == 0){
                uint64_t len = M->cols;
                cg_float* y = calloc(len, sizeof(cg_float));

                V->data = y;
                V->len = len;

                uint64_t i = 0;

                for(;i<M->cols;i++){
                    y[i] = i;
                }

                for(i=0;i<M->rows*M->cols;i++){
                    if(M->data[(uint64_t)y[i%len]] < M->data[i])
                        y[i%len] = i;
                }

                // transform vector representation into matrix
                for(i=0;i<M->cols;i++){
                    y[i] = ((int)y[i])/M->cols;
                }
            }
            else {
                uint64_t len = M->rows;
                cg_float* y = calloc(len, sizeof(cg_float));

                V->data = y;
                V->len = len;

                uint64_t i = 0;

                for(;i<M->rows;i++){
                    y[i] = i*M->cols;
                }

                for(i=0;i<M->rows*M->cols;i++){
                    if(M->data[(uint64_t)y[i/M->cols]] < M->data[i])
                        y[i/M->cols] = i;
                }

                // transform vector representation into matrix
                for(i=0;i<M->rows;i++){
                    y[i] = ((int)y[i])%M->cols;
                }
            }

            CGResultNode* result = calloc(1, sizeof(CGResultNode));
            result->type = CGVT_VECTOR;
            result->value = V;

            return result;

        }
    }
}

CGResultNode* argmin(CGNode* X, CGraph* graph){
    CGResultNode* res = computeCGNode(graph, X->axop->uhs);

    switch(res->type){
        case CGVT_DOUBLE:
        {
            cg_float y = 0;
            CGDouble* Y = calloc(1, sizeof(CGDouble));

            Y->value = 0;
            CGResultNode* result = calloc(1, sizeof(CGResultNode));
            result->type = CGVT_DOUBLE;
            result->value = Y;

            return result;
        }

        case CGVT_VECTOR:
        {
            CGVector* v = (CGVector*)res->value;
            CGDouble* Y = calloc(1, sizeof(CGDouble));

            uint64_t i = 1;
            uint64_t minIdx = 0;

            for (;i < v->len; i++){
                if(v->data[i] > v->data[minIdx])
                    minIdx = (cg_float)i;
            }

            Y->value = minIdx;
            CGResultNode* result = calloc(1, sizeof(CGResultNode));
            result->type = CGVT_DOUBLE;
            result->value = Y;

            return result;
        }

        case CGVT_MATRIX: {
            CGMatrix* M = (CGMatrix*)res->value;
            CGVector* V = calloc(1, sizeof(CGVector));

            if(X->axop->axis == 0){
                uint64_t len = M->cols;
                cg_float* y = calloc(len, sizeof(cg_float));

                V->data = y;
                V->len = len;

                uint64_t i = 0;

                for(;i<M->cols;i++){
                    y[i] = i;
                }

                for(i=0;i<M->rows*M->cols;i++){
                    if(M->data[(uint64_t)y[i%len]] > M->data[i])
                        y[i%len] = (cg_float)i;
                }

                // transform vector representation into matrix
                for(i=0;i<M->cols;i++){
                    y[i] = ((int)y[i])/M->cols;
                }
            }
            else {
                uint64_t len = M->rows;
                cg_float* y = calloc(len, sizeof(cg_float));

                V->data = y;
                V->len = len;

                uint64_t i = 0;

                for(;i<M->rows;i++){
                    y[i] = i*M->cols;
                }

                for(i=0;i<M->rows*M->cols;i++){
                    if(M->data[(uint64_t)y[i/M->cols]] > M->data[i])
                        y[i/M->cols] = (cg_float)i;
                }

                // transform vector representation into matrix
                for(i=0;i<M->rows;i++){
                    y[i] = ((int)y[i])%M->cols;
                }
            }

            CGResultNode* result = calloc(1, sizeof(CGResultNode));
            result->type = CGVT_VECTOR;
            result->value = V;

            return result;

        }
    }
}

CGResultNode* mean(CGNode* X, CGraph* graph){
    CGResultNode* res = computeCGNode(graph, X->axop->uhs);

    switch(res->type){
        case CGVT_DOUBLE:
        {
            cg_float y = 0;
            CGDouble* Y = calloc(1, sizeof(CGDouble));

            Y->value = ((CGDouble*)res->value)->value;
            CGResultNode* result = calloc(1, sizeof(CGResultNode));
            result->type = CGVT_DOUBLE;
            result->value = Y;

            return result;
        }

        case CGVT_VECTOR:
        {
            CGVector* v = (CGVector*)res->value;
            CGDouble* Y = calloc(1, sizeof(CGDouble));

            uint64_t i = 0;
            cg_float m = v->data[0];

            for (;i < v->len; i++){
                m += v->data[i];
            }

            m /= v->len;

            Y->value = m;
            CGResultNode* result = calloc(1, sizeof(CGResultNode));
            result->type = CGVT_DOUBLE;
            result->value = Y;

            return result;
        }

        case CGVT_MATRIX: {
            CGMatrix* M = (CGMatrix*)res->value;
            CGVector* V = calloc(1, sizeof(CGVector));

            if(X->axop->axis == 0){
                uint64_t len = M->cols;
                cg_float* y = calloc(len, sizeof(cg_float));

                V->data = y;
                V->len = len;

                uint64_t i = 0;

                for(;i<M->rows*M->cols;i++){
                    y[i%len] += M->data[i];
                }

                for(i=0;i<M->cols;i++){
                    y[i] /= M->rows;
                }
            }
            else {
                uint64_t len = M->rows;
                cg_float* y = calloc(len, sizeof(cg_float));

                V->data = y;
                V->len = len;

                uint64_t i = 0;

                for(;i<M->rows*M->cols;i++){
                    y[i/M->cols] += M->data[i];
                }

                for(i=0;i<M->rows;i++){
                    y[i] /= M->cols;
                }
            }

            CGResultNode* result = calloc(1, sizeof(CGResultNode));
            result->type = CGVT_VECTOR;
            result->value = V;

            return result;

        }
    }
}

/*
 * relu(d)
 */
CGResultNode* reluD(CGDouble* D, CGraph* graph, CGNode* parentNode){
    cg_float val = D->value;
    return makeDoubleResultNode(__cg_relu(val));
}

/*
 * relu(V)
 */
CGResultNode* reluV(CGVector* V, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;

    CGVector* Y = makeDeviceVector(V->len);

    uint64_t len = V->len;

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "relu_v", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), V->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_VECTOR;
    result->value = Y;

    return result;
}

/*
 * relu(M)
 */
CGResultNode* reluM(CGMatrix* M, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;

    CGMatrix* Y = makeDeviceMatrix(M->rows, M->cols);
    uint64_t len = M->rows*M->cols;

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "relu_v", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), M->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;
}

/*
 * softplus(d)
 */
CGResultNode* softplusD(CGDouble* D, CGraph* graph, CGNode* parentNode){
    cg_float val = D->value;
    return makeDoubleResultNode(__cg_softplus(val));
}

/*
 * softplus(V)
 */
CGResultNode* softplusV(CGVector* V, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;

    CGVector* Y = makeDeviceVector(V->len);

    uint64_t len = V->len;

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "softplus_v", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), V->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_VECTOR;
    result->value = Y;

    return result;
}

/*
 * softplus(M)
 */
CGResultNode* softplusM(CGMatrix* M, CGraph* graph, CGNode* parentNode){
    CCLErr * err = NULL;

    CGMatrix* Y = makeDeviceMatrix(M->rows, M->cols);
    uint64_t len = M->rows*M->cols;

    CCLEvent* evt = NULL;
    size_t gws = len;

    evt = ccl_program_enqueue_kernel(prg, "softplus_v", queue, 1, NULL, &gws,
                                     NULL, NULL, &err, ccl_arg_priv(len, cl_uint), M->buf, Y->buf, NULL);
    CHECK_ERROR(err)

    CGResultNode* result = calloc(1, sizeof(CGResultNode));
    result->type = CGVT_MATRIX;
    result->value = Y;

    return result;
}

#if cg_float == float
#undef cblas_dcopy
#undef cblas_dscal
#undef cblas_dgemm
#undef cblas_ddot
#undef cblas_dgemv
#undef cl_double
#endif

#if cg_float == float
#undef CLBlastDcopy
#undef CLBlastDscal
#undef CLBlastDgemm
#undef CLBlastDdot
#undef CLBlastDsum
#endif


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

#include <config.h>
#include <stdlib.h>
#include <gsl/gsl_rng.h>

/* This is the old BSD rand() generator. The sequence is

   x_{n+1} = (a x_n + c) mod m

   with a = 1103515245, c = 12345 and m = 2^31 = 2147483648. The seed
   specifies the initial value, x_1.

   The theoretical value of x_{10001} is 1910041713.

   The period of this generator is 2^31.

   The rand() generator is not very good -- the low bits of successive
   numbers are correlated. */

static inline unsigned long int rand_get (void *vstate);
static double rand_get_double (void *vstate);
static void rand_set (void *state, unsigned long int s);

typedef struct
  {
    unsigned long int x;
  }
rand_state_t;

static inline unsigned long int
rand_get (void *vstate)
{
  rand_state_t *state = (rand_state_t *) vstate;

  /* The following line relies on unsigned 32-bit arithmetic */

  state->x = (1103515245 * state->x + 12345) & 0x7fffffffUL;

  return state->x;
}

static double
rand_get_double (void *vstate)
{
  return rand_get (vstate) / 2147483648.0 ;
}

static void
rand_set (void *vstate, unsigned long int s)
{
  rand_state_t *state = (rand_state_t *) vstate;

  state->x = s;

  return;
}

static const gsl_rng_type rand_type =
{"rand",			/* name */
 0x7fffffffUL,			/* RAND_MAX */
 0,				/* RAND_MIN */
 sizeof (rand_state_t),
 &rand_set,
 &rand_get,
 &rand_get_double};

const gsl_rng_type *gsl_rng_rand = &rand_type;


#include <stdio.h>
#include <stdlib.h>
//#include <gsl_matrix.h>

#define IKFAST_HAS_LIBRARY // Build IKFast with API functions
#define IKFAST_NO_MAIN // Don't include main() from IKFast

#include "ikfast.cpp"
#include "kin.h"

int KinCalcFowardKinematics(jointAngles_t* angles, eeCoord_t* ee) {
	int i;
	double eetrans[3];
	double eerot[9];

	for(i=0;i<9;i++){
		eerot[i] = -48;
	}

	ComputeFk((double*)angles,eetrans,eerot);

	printf("Found fk solution for end frame: \n\n");

	printf("  Translation:  x: %f  y: %f  z: %f  \n", eetrans[0], eetrans[1], eetrans[2] );
	printf("\n");
	printf("     Rotation     %f   %f   %f  \n", eerot[0], eerot[1], eerot[2] );
	printf("       Matrix:    %f   %f   %f  \n", eerot[3], eerot[4], eerot[5] );
	printf("                  %f   %f   %f  \n", eerot[6], eerot[7], eerot[8] );
	printf("\n");

	return 0;
}

#ifdef STANDALONE

int main(int argc, char **argv){
	int i;
	if(argc != 9) {
		printf("usage: %s x1 y1 z1 w1 x2 y2 z2 w2\n", argv[0]);
		return 1;
	}

	const int points = 100;

	waypoint_t waypoint[points];

	waypoint[0].x = atof(argv[1]);
	waypoint[0].y = atof(argv[2]);
	waypoint[0].z = atof(argv[3]);
	waypoint[0].w = atof(argv[4]);
	waypoint[points-1].x = atof(argv[5]);
	waypoint[points-1].y = atof(argv[6]);
	waypoint[points-1].z = atof(argv[7]);
	waypoint[points-1].w = atof(argv[8]);

	KinCalcFowardKinematics(&angles, &ee);

}

#endif


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

/* Author: G. Jungman */

#include <config.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_precision.h>
#include "gsl_sf_ellint.h"

#include "error.h"

/*-*-*-*-*-*-*-*-*-*-*-* Private Section *-*-*-*-*-*-*-*-*-*-*-*/

inline
static double locMAX3(double x, double y, double z)
{
  double xy = GSL_MAX(x, y);
  return GSL_MAX(xy, z);
}

inline
static double locMAX4(double x, double y, double z, double w)
{
  double xy  = GSL_MAX(x,  y);
  double xyz = GSL_MAX(xy, z);
  return GSL_MAX(xyz, w);
}

/*-*-*-*-*-*-*-*-*-*-*-* Functions with Error Codes *-*-*-*-*-*-*-*-*-*-*-*/

/* based on Carlson's algorithms:
   [B. C. Carlson Numer. Math. 33, 1 (1979)]

   see also:
   [B.C. Carlson, Special Functions of Applied Mathematics (1977)]
 */

/* According to Carlson's algorithm, the errtol parameter
   typically effects the relative error in the following way:

   relative error < 16 errtol^6 / (1 - 2 errtol)

     errtol     precision
     ------     ----------
     0.001       1.0e-17
     0.003       2.0e-14
     0.01        2.0e-11
     0.03        2.0e-8
     0.1         2.0e-5
*/

int
gsl_sf_ellint_RC_e(double x, double y, gsl_mode_t mode, gsl_sf_result * result)
{
  const double lolim = 5.0 * GSL_DBL_MIN;
  const double uplim = 0.2 * GSL_DBL_MAX;
  const gsl_prec_t goal = GSL_MODE_PREC(mode);
  const double errtol = ( goal == GSL_PREC_DOUBLE ? 0.001 : 0.03 );
  const double prec   = gsl_prec_eps[goal];

  if(x < 0.0 || y < 0.0 || x + y < lolim) {
    DOMAIN_ERROR(result);
  }
  else if(GSL_MAX(x, y) < uplim) {
    const double c1 = 1.0 / 7.0;
    const double c2 = 9.0 / 22.0;
    double xn = x;
    double yn = y;
    double mu, sn, lamda, s;
    while(1) {
      mu = (xn + yn + yn) / 3.0;
      sn = (yn + mu) / mu - 2.0;
      if (fabs(sn) < errtol) break;
      lamda = 2.0 * sqrt(xn) * sqrt(yn) + yn;
      xn = (xn + lamda) * 0.25;
      yn = (yn + lamda) * 0.25;
    }
    s = sn * sn * (0.3 + sn * (c1 + sn * (0.375 + sn * c2)));
    result->val = (1.0 + s) / sqrt(mu);
    result->err = prec * fabs(result->val);
    return GSL_SUCCESS;
  }
  else {
    DOMAIN_ERROR(result);
  }
}

int
gsl_sf_ellint_RD_e(double x, double y, double z, gsl_mode_t mode, gsl_sf_result * result)
{
  const gsl_prec_t goal = GSL_MODE_PREC(mode);
  const double errtol = ( goal == GSL_PREC_DOUBLE ? 0.001 : 0.03 );
  const double prec   = gsl_prec_eps[goal];
  const double lolim = 2.0/pow(GSL_DBL_MAX, 2.0/3.0);
  const double uplim = pow(0.1*errtol/GSL_DBL_MIN, 2.0/3.0);

  if(GSL_MIN(x,y) < 0.0 || GSL_MIN(x+y,z) < lolim) {
    DOMAIN_ERROR(result);
  }
  else if(locMAX3(x,y,z) < uplim) {
    const double c1 = 3.0 / 14.0;
    const double c2 = 1.0 /  6.0;
    const double c3 = 9.0 / 22.0;
    const double c4 = 3.0 / 26.0;
    double xn = x;
    double yn = y;
    double zn = z;
    double sigma  = 0.0;
    double power4 = 1.0;
    double ea, eb, ec, ed, ef, s1, s2;
    double mu, xndev, yndev, zndev;
    while(1) {
      double xnroot, ynroot, znroot, lamda;
      double epslon;
      mu = (xn + yn + 3.0 * zn) * 0.2;
      xndev = (mu - xn) / mu;
      yndev = (mu - yn) / mu;
      zndev = (mu - zn) / mu;
      epslon = locMAX3(fabs(xndev), fabs(yndev), fabs(zndev));
      if (epslon < errtol) break;
      xnroot = sqrt(xn);
      ynroot = sqrt(yn);
      znroot = sqrt(zn);
      lamda = xnroot * (ynroot + znroot) + ynroot * znroot;
      sigma  += power4 / (znroot * (zn + lamda));
      power4 *= 0.25;
      xn = (xn + lamda) * 0.25;
      yn = (yn + lamda) * 0.25;
      zn = (zn + lamda) * 0.25;
    }
    ea = xndev * yndev;
    eb = zndev * zndev;
    ec = ea - eb;
    ed = ea - 6.0 * eb;
    ef = ed + ec + ec;
    s1 = ed * (- c1 + 0.25 * c3 * ed - 1.5 * c4 * zndev * ef);
    s2 = zndev * (c2 * ef + zndev * (- c3 * ec + zndev * c4 * ea));
    result->val = 3.0 * sigma + power4 * (1.0 + s1 + s2) / (mu * sqrt(mu));
    result->err = prec * fabs(result->val);
    return GSL_SUCCESS;
  }
  else {
    DOMAIN_ERROR(result);
  }
}

int
gsl_sf_ellint_RF_e(double x, double y, double z, gsl_mode_t mode, gsl_sf_result * result)
{
  const double lolim = 5.0 * GSL_DBL_MIN;
  const double uplim = 0.2 * GSL_DBL_MAX;
  const gsl_prec_t goal = GSL_MODE_PREC(mode);
  const double errtol = ( goal == GSL_PREC_DOUBLE ? 0.001 : 0.03 );
  const double prec   = gsl_prec_eps[goal];

  if(x < 0.0 || y < 0.0 || z < 0.0) {
    DOMAIN_ERROR(result);
  }
  else if(x+y < lolim || x+z < lolim || y+z < lolim) {
    DOMAIN_ERROR(result);
  }
  else if(locMAX3(x,y,z) < uplim) {
    const double c1 = 1.0 / 24.0;
    const double c2 = 3.0 / 44.0;
    const double c3 = 1.0 / 14.0;
    double xn = x;
    double yn = y;
    double zn = z;
    double mu, xndev, yndev, zndev, e2, e3, s;
    while(1) {
      double epslon, lamda;
      double xnroot, ynroot, znroot;
      mu = (xn + yn + zn) / 3.0;
      xndev = 2.0 - (mu + xn) / mu;
      yndev = 2.0 - (mu + yn) / mu;
      zndev = 2.0 - (mu + zn) / mu;
      epslon = locMAX3(fabs(xndev), fabs(yndev), fabs(zndev));
      if (epslon < errtol) break;
      xnroot = sqrt(xn);
      ynroot = sqrt(yn);
      znroot = sqrt(zn);
      lamda = xnroot * (ynroot + znroot) + ynroot * znroot;
      xn = (xn + lamda) * 0.25;
      yn = (yn + lamda) * 0.25;
      zn = (zn + lamda) * 0.25;
    }
    e2 = xndev * yndev - zndev * zndev;
    e3 = xndev * yndev * zndev;
    s = 1.0 + (c1 * e2 - 0.1 - c2 * e3) * e2 + c3 * e3;
    result->val = s / sqrt(mu);
    result->err = prec * fabs(result->val);
    return GSL_SUCCESS;
  }
  else {
    DOMAIN_ERROR(result);
  }
}

int
gsl_sf_ellint_RJ_e(double x, double y, double z, double p, gsl_mode_t mode, gsl_sf_result * result)
{
  const gsl_prec_t goal = GSL_MODE_PREC(mode);
  const double errtol = ( goal == GSL_PREC_DOUBLE ? 0.001 : 0.03 );
  const double prec   = gsl_prec_eps[goal];
  const double lolim =       pow(5.0 * GSL_DBL_MIN, 1.0/3.0);
  const double uplim = 0.3 * pow(0.2 * GSL_DBL_MAX, 1.0/3.0);

  if(x < 0.0 || y < 0.0 || z < 0.0) {
    DOMAIN_ERROR(result);
  }
  else if(x + y < lolim || x + z < lolim || y + z < lolim || p < lolim) {
    DOMAIN_ERROR(result);
  }
  else if(locMAX4(x,y,z,p) < uplim) {
    const double c1 = 3.0 / 14.0;
    const double c2 = 1.0 /  3.0;
    const double c3 = 3.0 / 22.0;
    const double c4 = 3.0 / 26.0;
    double xn = x;
    double yn = y;
    double zn = z;
    double pn = p;
    double sigma = 0.0;
    double power4 = 1.0;
    double mu, xndev, yndev, zndev, pndev;
    double ea, eb, ec, e2, e3, s1, s2, s3;
    while(1) {
      double xnroot, ynroot, znroot;
      double lamda, alfa, beta;
      double epslon;
      gsl_sf_result rcresult;
      int rcstatus;
      mu = (xn + yn + zn + pn + pn) * 0.2;
      xndev = (mu - xn) / mu;
      yndev = (mu - yn) / mu;
      zndev = (mu - zn) / mu;
      pndev = (mu - pn) / mu;
      epslon = locMAX4(fabs(xndev), fabs(yndev), fabs(zndev), fabs(pndev));
      if(epslon < errtol) break;
      xnroot = sqrt(xn);
      ynroot = sqrt(yn);
      znroot = sqrt(zn);
      lamda = xnroot * (ynroot + znroot) + ynroot * znroot;
      alfa = pn * (xnroot + ynroot + znroot) + xnroot * ynroot * znroot;
      alfa = alfa * alfa;
      beta = pn * (pn + lamda) * (pn + lamda);
      rcstatus = gsl_sf_ellint_RC_e(alfa, beta, mode, &rcresult);
      if(rcstatus != GSL_SUCCESS) {
        result->val = 0.0;
	result->err = 0.0;
        return rcstatus;
      }
      sigma  += power4 * rcresult.val;
      power4 *= 0.25;
      xn = (xn + lamda) * 0.25;
      yn = (yn + lamda) * 0.25;
      zn = (zn + lamda) * 0.25;
      pn = (pn + lamda) * 0.25;
    }

    ea = xndev * (yndev + zndev) + yndev * zndev;
    eb = xndev * yndev * zndev;
    ec = pndev * pndev;
    e2 = ea - 3.0 * ec;
    e3 = eb + 2.0 * pndev * (ea - ec);
    s1 = 1.0 + e2 * (- c1 + 0.75 * c3 * e2 - 1.5 * c4 * e3);
    s2 = eb * (0.5 * c2 + pndev * (- c3 - c3 + pndev * c4));
    s3 = pndev * ea * (c2 - pndev * c3) - c2 * pndev * ec;
    result->val = 3.0 * sigma + power4 * (s1 + s2 + s3) / (mu * sqrt(mu));
    result->err = prec * fabs(result->val);
    return GSL_SUCCESS;
  }
  else {
    DOMAIN_ERROR(result);
  }
}

/* [Carlson, Numer. Math. 33 (1979) 1, (4.1)] */
int
gsl_sf_ellint_F_e(double phi, double k, gsl_mode_t mode, gsl_sf_result * result)
{
  double sin_phi  = sin(phi);
  double sin2_phi = sin_phi*sin_phi;
  double x = 1.0 - sin2_phi;
  double y = 1.0 - k*k*sin2_phi;
  gsl_sf_result rf;
  int status = gsl_sf_ellint_RF_e(x, y, 1.0, mode, &rf);
  result->val = sin_phi * rf.val;
  result->err = GSL_DBL_EPSILON * fabs(result->val) + fabs(sin_phi*rf.err);
  return status;
}

/* [Carlson, Numer. Math. 33 (1979) 1, (4.2)] */
int
gsl_sf_ellint_E_e(double phi, double k, gsl_mode_t mode, gsl_sf_result * result)
{
  const double sin_phi  = sin(phi);
  const double sin2_phi = sin_phi  * sin_phi;
  const double x = 1.0 - sin2_phi;
  const double y = 1.0 - k*k*sin2_phi;
  if(x < GSL_DBL_EPSILON) {
    return gsl_sf_ellint_Ecomp_e(k, mode, result);
  }
  else {
    gsl_sf_result rf;
    gsl_sf_result rd;
    const double sin3_phi = sin2_phi * sin_phi;
    const int rfstatus = gsl_sf_ellint_RF_e(x, y, 1.0, mode, &rf);
    const int rdstatus = gsl_sf_ellint_RD_e(x, y, 1.0, mode, &rd);
    result->val  = sin_phi * rf.val - k*k/3.0 * sin3_phi * rd.val;
    result->err  = GSL_DBL_EPSILON * fabs(sin_phi * rf.val);
    result->err += fabs(sin_phi*rf.err);
    result->err += k*k/3.0 * GSL_DBL_EPSILON * fabs(sin3_phi * rd.val);
    result->err += k*k/3.0 * fabs(sin3_phi*rd.err);
    return GSL_ERROR_SELECT_2(rfstatus, rdstatus);
  }
}

/* [Carlson, Numer. Math. 33 (1979) 1, (4.3)] */
int
gsl_sf_ellint_P_e(double phi, double k, double n, gsl_mode_t mode, gsl_sf_result * result)
{
  const double sin_phi  = sin(phi);
  const double sin2_phi = sin_phi  * sin_phi;
  const double sin3_phi = sin2_phi * sin_phi;
  const double x = 1.0 - sin2_phi;
  const double y = 1.0 - k*k*sin2_phi;
  gsl_sf_result rf;
  gsl_sf_result rj;
  const int rfstatus = gsl_sf_ellint_RF_e(x, y, 1.0, mode, &rf);
  const int rjstatus = gsl_sf_ellint_RJ_e(x, y, 1.0, 1.0 + n*sin2_phi, mode, &rj);
  result->val  = sin_phi * rf.val - n/3.0*sin3_phi * rj.val;
  result->err  = GSL_DBL_EPSILON * fabs(sin_phi * rf.val);
  result->err += n/3.0 * fabs(sin3_phi*rj.err);
  return GSL_ERROR_SELECT_2(rfstatus, rjstatus);
}

/* [Carlson, Numer. Math. 33 (1979) 1, (4.4)] */
int
gsl_sf_ellint_D_e(double phi, double k, double n, gsl_mode_t mode, gsl_sf_result * result)
{
  const double sin_phi  = sin(phi);
  const double sin2_phi = sin_phi  * sin_phi;
  const double sin3_phi = sin2_phi * sin_phi;
  const double x = 1.0 - sin2_phi;
  const double y = 1.0 - k*k*sin2_phi;
  gsl_sf_result rd;
  const int status = gsl_sf_ellint_RD_e(x, y, 1.0, mode, &rd);
  result->val = sin3_phi/3.0 * rd.val;
  result->err = GSL_DBL_EPSILON * fabs(result->val) + fabs(sin3_phi/3.0 * rd.err);
  return status;
}

/* [Carlson, Numer. Math. 33 (1979) 1, (4.5)] */
int
gsl_sf_ellint_Kcomp_e(double k, gsl_mode_t mode, gsl_sf_result * result)
{
  if(k*k >= 1.0) {
    DOMAIN_ERROR(result);
  }
  else if(k*k >= 1.0 - GSL_SQRT_DBL_EPSILON) {
    /* [Abramowitz+Stegun, 17.3.33] */
    const double y = 1.0 - k*k;
    const double a[] = { 1.38629436112, 0.09666344259, 0.03590092383 };
    const double b[] = { 0.5, 0.12498593597, 0.06880248576 };
    const double ta = a[0] + y*(a[1] + y*a[2]);
    const double tb = -log(y) * (b[0] * y*(b[1] + y*b[2]));
    result->val = ta + tb;
    result->err = 2.0 * GSL_DBL_EPSILON * result->val;
    return GSL_SUCCESS;
  }
  else {
    return gsl_sf_ellint_RF_e(0.0, 1.0 - k*k, 1.0, mode, result);
  }
}

/* [Carlson, Numer. Math. 33 (1979) 1, (4.6)] */
int
gsl_sf_ellint_Ecomp_e(double k, gsl_mode_t mode, gsl_sf_result * result)
{
  if(k*k >= 1.0) {
    DOMAIN_ERROR(result);
  }
  else if(k*k >= 1.0 - GSL_SQRT_DBL_EPSILON) {
    /* [Abramowitz+Stegun, 17.3.36] */
    const double y = 1.0 - k*k;
    const double a[] = { 0.44325141463, 0.06260601220, 0.04757383546 };
    const double b[] = { 0.24998368310, 0.09200180037, 0.04069697526 };
    const double ta = 1.0 + y*(a[0] + y*(a[1] + a[2]*y));
    const double tb = -y*log(y) * (b[0] + y*(b[1] + b[2]*y));
    result->val = ta + tb;
    result->err = 2.0 * GSL_DBL_EPSILON * result->val;
    return GSL_SUCCESS;
  }
  else {
    gsl_sf_result rf;
    gsl_sf_result rd;
    const double y = 1.0 - k*k;
    const int rfstatus = gsl_sf_ellint_RF_e(0.0, y, 1.0, mode, &rf);
    const int rdstatus = gsl_sf_ellint_RD_e(0.0, y, 1.0, mode, &rd);
    result->val = rf.val - k*k/3.0 * rd.val;
    result->err = rf.err + k*k/3.0 * rd.err;
    return GSL_ERROR_SELECT_2(rfstatus, rdstatus);
  }
}

/*-*-*-*-*-*-*-*-*-* Functions w/ Natural Prototypes *-*-*-*-*-*-*-*-*-*-*/

#include "eval.h"

double gsl_sf_ellint_Kcomp(double k, gsl_mode_t mode)
{
  EVAL_RESULT(gsl_sf_ellint_Kcomp_e(k, mode, &result));
}

double gsl_sf_ellint_Ecomp(double k, gsl_mode_t mode)
{
  EVAL_RESULT(gsl_sf_ellint_Ecomp_e(k, mode, &result));
}

double gsl_sf_ellint_F(double phi, double k, gsl_mode_t mode)
{
  EVAL_RESULT(gsl_sf_ellint_F_e(phi, k, mode, &result));
}

double gsl_sf_ellint_E(double phi, double k, gsl_mode_t mode)
{
  EVAL_RESULT(gsl_sf_ellint_E_e(phi, k, mode, &result));
}

double gsl_sf_ellint_P(double phi, double k, double n, gsl_mode_t mode)
{
  EVAL_RESULT(gsl_sf_ellint_P_e(phi, k, n, mode, &result));
}

double gsl_sf_ellint_D(double phi, double k, double n, gsl_mode_t mode)
{
  EVAL_RESULT(gsl_sf_ellint_D_e(phi, k, n, mode, &result));
}

double gsl_sf_ellint_RC(double x, double y, gsl_mode_t mode)
{
  EVAL_RESULT(gsl_sf_ellint_RC_e(x, y, mode, &result));
}

double gsl_sf_ellint_RD(double x, double y, double z, gsl_mode_t mode)
{
  EVAL_RESULT(gsl_sf_ellint_RD_e(x, y, z, mode, &result));
}

double gsl_sf_ellint_RF(double x, double y, double z, gsl_mode_t mode)
{
  EVAL_RESULT(gsl_sf_ellint_RF_e(x, y, z, mode, &result));
}

double gsl_sf_ellint_RJ(double x, double y, double z, double p, gsl_mode_t mode)
{
  EVAL_RESULT(gsl_sf_ellint_RJ_e(x, y, z, p, mode, &result));
}