/*  
    This code is written by <albanese@fbk.it>.
    (C) 2010 Fondazione Bruno Kessler - Via Santa Croce 77, 38100 Trento, ITALY.

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

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


#include <Python.h>
#include <numpy/arrayobject.h>
#include <stdlib.h>
#include <math.h>


double
euclidean_norm_squared(double *x, int nn)
{
  int n;
  double en = 0.0;
  
  for(n=0; n<nn; n++)
    en += pow(x[n], 2);
  
  return en;
}


/***** Linear Kernel *****/

/* Computes the kernel matrix */
void
linear_matrix(double *x, int nn, int pp, double *k)
{
  int n1, n2, p;
  double tmp;
  
  for (n1=0; n1<nn; n1++)
    for (n2=n1; n2<nn; n2++)
      {
	tmp = 0.0;

	for (p=0; p<pp; p++)
	  tmp += x[p + (n1 * pp)] * x[p + (n2 * pp)];
	
	k[n2 + (n1 * nn)] = tmp;
	k[n1 + (n2 * nn)] = tmp;	
      }
}

/* Computes the kernel vector k = {K(a, x_0), K(a, x_1), ..., K(a, x_n-1)} */
void
linear_vector(double *a, double *x, int nn, int pp, double *k)
{
  int n, p;
  
  for (n=0; n<nn; n++)
    {
      k[n] = 0.0;
      
      for (p=0; p<pp; p++)
	k[n] += a[p] * x[p + (n * pp)]; 
    }
}

/***** Gaussian Kernel *****/

/* Computes the kernel matrix */
void gaussian_matrix(double *x, int nn, int pp, double *k, double sigma)
{
  int n1, n2, p;
  double tmp;
  double *tmp2;

  tmp2 = (double *) malloc (pp * sizeof(double));

  for (n1=0; n1<nn; n1++)
    for (n2=n1; n2<nn; n2++)
      {
	for (p=0; p<pp; p++)
	  tmp2[p] = x[p + (n1 * pp)] - x[p + (n2 * pp)];

	tmp = exp(- euclidean_norm_squared(tmp2, pp) / (2.0 * pow(sigma, 2)));
	
	k[n2 + (n1 * nn)] = tmp;
	k[n1 + (n2 * nn)] = tmp;	
      }
  
  free(tmp2); 
}

/* Computes the kernel vector k = {K(a, x_0), K(a, x_1), ..., K(a, x_n-1)} */
void gaussian_vector(double *a, double *x, int nn, int pp, double *k, double sigma)
{
  int n, p;
  double *tmp;

  tmp = (double *) malloc (pp * sizeof(double));

  for (n=0; n<nn; n++)
    {
      for (p=0; p<pp; p++)
	tmp[p] = a[p] - x[p + (n * pp)];

      k[n] = exp(- euclidean_norm_squared(tmp, pp) / (2.0 * pow(sigma, 2)));
    }

  free(tmp);
}


/***** Polynomial Kernel *****/

/* Computes the kernel matrix */
void
polynomial_matrix(double *x, int nn, int pp, double *k, int d)
{
  int n1, n2, p;
  double tmp;
  
  for (n1=0; n1<nn; n1++)
    for (n2=n1; n2<nn; n2++)
      {
	tmp = 0.0;

	for (p=0; p<pp; p++)
	  tmp += x[p + (n1 * pp)] * x[p + (n2 * pp)];
	
	tmp = pow(tmp + 1.0, d);
	
	k[n2 + (n1 * nn)] = tmp;
	k[n1 + (n2 * nn)] = tmp;	
      }
}

/* Computes the kernel vector k = {K(a, x_0), K(a, x_1), ..., K(a, x_n-1)} */
void
polynomial_vector(double *a, double *x, int nn, int pp, double *k, int d)
{
  int n, p;
  
  for (n=0; n<nn; n++)
    {
      k[n] = 0.0;
      
      for (p=0; p<pp; p++)
	k[n] += a[p] * x[p + (n * pp)];
      
      k[n] = pow(k[n] + 1, d);
    }
}


static PyObject *kernel_linear_matrix(PyObject *self, PyObject *args, PyObject *keywds)
{
  PyObject *x = NULL;
  PyObject *xC = NULL;
  double *xD;

  PyObject *kC = NULL;
  double *kD;
  
  npy_intp nn, pp;
  npy_intp k_dims[2];

  /* Parse Tuple*/
  static char *kwlist[] = {"x", NULL};
  if (!PyArg_ParseTupleAndKeywords(args, keywds, "O", kwlist, &x))
    return NULL;
  
  xC = PyArray_FROM_OTF(x, NPY_DOUBLE, NPY_IN_ARRAY);
  if (xC == NULL) return NULL;
  
  if (PyArray_NDIM(xC) != 2)
    {
      PyErr_SetString(PyExc_ValueError, "x must be 2d array");
      return NULL;
    }

  nn = PyArray_DIM(xC, 0);
  pp = PyArray_DIM(xC, 1);
  xD = (double *) PyArray_DATA(xC);

  k_dims[0] = nn;
  k_dims[1] = nn;
  kC = PyArray_SimpleNew (2, k_dims, NPY_DOUBLE);
  kD = (double *) PyArray_DATA(kC);
  
  linear_matrix(xD, nn, pp, kD);
  
  Py_DECREF(xC);
 
  return Py_BuildValue("N", kC);
}

static PyObject *kernel_linear_vector(PyObject *self, PyObject *args, PyObject *keywds)
{
  PyObject *x = NULL;
  PyObject *xC = NULL;
  double *xD;

  PyObject *a = NULL;
  PyObject *aC = NULL;
  double *aD;

  PyObject *kC = NULL;
  double *kD;
  
  npy_intp nn, pp, app;
  npy_intp k_dims[1];


  /* Parse Tuple*/
  static char *kwlist[] = {"a", "x", NULL};
  if (!PyArg_ParseTupleAndKeywords(args, keywds, "OO", kwlist, &a, &x))
    return NULL;
  
  xC = PyArray_FROM_OTF(x, NPY_DOUBLE, NPY_IN_ARRAY);
  if (xC == NULL) return NULL;

  aC = PyArray_FROM_OTF(a, NPY_DOUBLE, NPY_IN_ARRAY);
  if (aC == NULL) return NULL;
  
  if (PyArray_NDIM(xC) != 2)
    {
      PyErr_SetString(PyExc_ValueError, "x must be 2d array");
      return NULL;
    }

  if (PyArray_NDIM(aC) != 1)
    {
      PyErr_SetString(PyExc_ValueError, "a must be 1d array");
      return NULL;
    }

  nn = PyArray_DIM(xC, 0);
  pp = PyArray_DIM(xC, 1);
  app = PyArray_DIM(aC, 0);

  if (pp != app)
    {
      PyErr_SetString(PyExc_ValueError, "a and x are not aligned");
      return NULL;
    }
  
  xD = (double *) PyArray_DATA(xC);
  aD = (double *) PyArray_DATA(aC);

  k_dims[0] = nn;

  kC = PyArray_SimpleNew (1, k_dims, NPY_DOUBLE);
  kD = (double *) PyArray_DATA(kC);
  
  linear_vector(aD, xD, nn, pp, kD);

  Py_DECREF(xC);
  Py_DECREF(aC);

  return Py_BuildValue("N", kC);
}


static PyObject *kernel_gaussian_matrix(PyObject *self, PyObject *args, PyObject *keywds)
{
  PyObject *x = NULL;
  PyObject *xC = NULL;
  double *xD;
  
  double sigma;

  PyObject *kC = NULL;
  double *kD;
  
  npy_intp nn, pp;
  npy_intp k_dims[2];

  /* Parse Tuple*/
  static char *kwlist[] = {"x", "sigma", NULL};
  if (!PyArg_ParseTupleAndKeywords(args, keywds, "Od", kwlist, &x, &sigma))
    return NULL;
  
  xC = PyArray_FROM_OTF(x, NPY_DOUBLE, NPY_IN_ARRAY);
  if (xC == NULL) return NULL;
  
  if (PyArray_NDIM(xC) != 2)
    {
      PyErr_SetString(PyExc_ValueError, "x must be 2d array");
      return NULL;
    }

  nn = PyArray_DIM(xC, 0);
  pp = PyArray_DIM(xC, 1);
  xD = (double *) PyArray_DATA(xC);

  k_dims[0] = nn;
  k_dims[1] = nn;
  kC = PyArray_SimpleNew (2, k_dims, NPY_DOUBLE);
  kD = (double *) PyArray_DATA(kC);
  
  gaussian_matrix(xD, nn, pp, kD, sigma);
  
  Py_DECREF(xC);
 
  return Py_BuildValue("N", kC);
}

static PyObject *kernel_gaussian_vector(PyObject *self, PyObject *args, PyObject *keywds)
{
  PyObject *x = NULL;
  PyObject *xC = NULL;
  double *xD;

  PyObject *a = NULL;
  PyObject *aC = NULL;
  double *aD;

  double sigma;
  
  PyObject *kC = NULL;
  double *kD;
  
  npy_intp nn, pp, app;
  npy_intp k_dims[1];


  /* Parse Tuple*/
  static char *kwlist[] = {"a", "x", "sigma", NULL};
  if (!PyArg_ParseTupleAndKeywords(args, keywds, "OOd", kwlist, &a, &x, &sigma))
    return NULL;
  
  xC = PyArray_FROM_OTF(x, NPY_DOUBLE, NPY_IN_ARRAY);
  if (xC == NULL) return NULL;

  aC = PyArray_FROM_OTF(a, NPY_DOUBLE, NPY_IN_ARRAY);
  if (aC == NULL) return NULL;
  
  if (PyArray_NDIM(xC) != 2)
    {
      PyErr_SetString(PyExc_ValueError, "x must be 2d array");
      return NULL;
    }

  if (PyArray_NDIM(aC) != 1)
    {
      PyErr_SetString(PyExc_ValueError, "a must be 1d array");
      return NULL;
    }

  nn = PyArray_DIM(xC, 0);
  pp = PyArray_DIM(xC, 1);
  app = PyArray_DIM(aC, 0);

  if (pp != app)
    {
      PyErr_SetString(PyExc_ValueError, "a and x are not aligned");
      return NULL;
    }
  
  xD = (double *) PyArray_DATA(xC);
  aD = (double *) PyArray_DATA(aC);

  k_dims[0] = nn;

  kC = PyArray_SimpleNew (1, k_dims, NPY_DOUBLE);
  kD = (double *) PyArray_DATA(kC);
  
  gaussian_vector(aD, xD, nn, pp, kD, sigma);

  Py_DECREF(xC);
  Py_DECREF(aC);

  return Py_BuildValue("N", kC);
}


static PyObject *kernel_polynomial_matrix(PyObject *self, PyObject *args, PyObject *keywds)
{
  PyObject *x = NULL;
  PyObject *xC = NULL;
  double *xD;
  int d;
  
  PyObject *kC = NULL;
  double *kD;
  
  npy_intp nn, pp;
  npy_intp k_dims[2];

  /* Parse Tuple*/
  static char *kwlist[] = {"x", "d", NULL};
  if (!PyArg_ParseTupleAndKeywords(args, keywds, "Oi", kwlist, &x, &d))
    return NULL;
  
  xC = PyArray_FROM_OTF(x, NPY_DOUBLE, NPY_IN_ARRAY);
  if (xC == NULL) return NULL;
  
  if (PyArray_NDIM(xC) != 2)
    {
      PyErr_SetString(PyExc_ValueError, "x must be 2d array");
      return NULL;
    }

  nn = PyArray_DIM(xC, 0);
  pp = PyArray_DIM(xC, 1);
  xD = (double *) PyArray_DATA(xC);

  k_dims[0] = nn;
  k_dims[1] = nn;
  kC = PyArray_SimpleNew (2, k_dims, NPY_DOUBLE);
  kD = (double *) PyArray_DATA(kC);
  
  polynomial_matrix(xD, nn, pp, kD, d);
  
  Py_DECREF(xC);
 
  return Py_BuildValue("N", kC);
}

static PyObject *kernel_polynomial_vector(PyObject *self, PyObject *args, PyObject *keywds)
{
  PyObject *x = NULL;
  PyObject *xC = NULL;
  double *xD;
  int d;

  PyObject *a = NULL;
  PyObject *aC = NULL;
  double *aD;

  PyObject *kC = NULL;
  double *kD;
  
  npy_intp nn, pp, app;
  npy_intp k_dims[1];


  /* Parse Tuple*/
  static char *kwlist[] = {"a", "x", "d", NULL};
  if (!PyArg_ParseTupleAndKeywords(args, keywds, "OOi", kwlist, &a, &x, &d))
    return NULL;
  
  xC = PyArray_FROM_OTF(x, NPY_DOUBLE, NPY_IN_ARRAY);
  if (xC == NULL) return NULL;

  aC = PyArray_FROM_OTF(a, NPY_DOUBLE, NPY_IN_ARRAY);
  if (aC == NULL) return NULL;
  
  if (PyArray_NDIM(xC) != 2)
    {
      PyErr_SetString(PyExc_ValueError, "x must be 2d array");
      return NULL;
    }

  if (PyArray_NDIM(aC) != 1)
    {
      PyErr_SetString(PyExc_ValueError, "a must be 1d array");
      return NULL;
    }

  nn = PyArray_DIM(xC, 0);
  pp = PyArray_DIM(xC, 1);
  app = PyArray_DIM(aC, 0);

  if (pp != app)
    {
      PyErr_SetString(PyExc_ValueError, "a and x are not aligned");
      return NULL;
    }
  
  xD = (double *) PyArray_DATA(xC);
  aD = (double *) PyArray_DATA(aC);

  k_dims[0] = nn;

  kC = PyArray_SimpleNew (1, k_dims, NPY_DOUBLE);
  kD = (double *) PyArray_DATA(kC);
  
  polynomial_vector(aD, xD, nn, pp, kD, d);

  Py_DECREF(xC);
  Py_DECREF(aC);

  return Py_BuildValue("N", kC);
}


/* Doc strings: */
static char module_doc[] = "";

static char kernel_linear_matrix_doc[] = "";
static char kernel_linear_vector_doc[] = "";
static char kernel_gaussian_matrix_doc[] = "";
static char kernel_gaussian_vector_doc[] = "";
static char kernel_polynomial_matrix_doc[] = "";
static char kernel_polynomial_vector_doc[] = "";

/* Method table */
static PyMethodDef kernel_methods[] = {
  {"linear_matrix",
   (PyCFunction)kernel_linear_matrix,
   METH_VARARGS | METH_KEYWORDS,
   kernel_linear_matrix_doc},
  {"linear_vector",
   (PyCFunction)kernel_linear_vector,
   METH_VARARGS | METH_KEYWORDS,
   kernel_linear_vector_doc},
  {"gaussian_matrix",
   (PyCFunction)kernel_gaussian_matrix,
   METH_VARARGS | METH_KEYWORDS,
   kernel_gaussian_matrix_doc},
  {"gaussian_vector",
   (PyCFunction)kernel_gaussian_vector,
   METH_VARARGS | METH_KEYWORDS,
   kernel_gaussian_vector_doc},
  {"polynomial_matrix",
   (PyCFunction)kernel_polynomial_matrix,
   METH_VARARGS | METH_KEYWORDS,
   kernel_polynomial_matrix_doc},
  {"polynomial_vector",
   (PyCFunction)kernel_polynomial_vector,
   METH_VARARGS | METH_KEYWORDS,
   kernel_polynomial_vector_doc},
  {NULL, NULL, 0, NULL}
};

/* Init */
void initkernel()
{
  Py_InitModule3("kernel", kernel_methods, module_doc);
  import_array();
}
