/*
    This file is part of svmcore.
    
    This code is written by Davide Albanese, <albanese@fbk.it>. 
    (C) 2008 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 <math.h>
#include <stdio.h>
#include <stdlib.h>
#include "numpysupport.h"
#include "svm.h"

/*  Compute SVM */
static PyObject *svmcore_computesvm(PyObject *self, PyObject *args, PyObject *keywds)
{
  PyObject *x  = NULL; PyObject *y  = NULL;
  PyObject *xc = NULL; PyObject *yc = NULL;

  int kernel, maxloops;
  double kp, C, tol, eps, cost, alpha_tversky, beta_tversky;
  
  int i;

  /* Parse Tuple*/
  static char *kwlist[] = {"x", "y", "kernel", "kp", "C", "tol", "eps", "maxloops", "cost", "alpha_tversky", "beta_tversky", NULL};
  if (!PyArg_ParseTupleAndKeywords(args, keywds, "OOiddddiddd", kwlist, &x, &y,
				   &kernel, &kp, &C, &tol, &eps, &maxloops, &cost, &alpha_tversky, &beta_tversky))
    return NULL;
   
  xc = PyArray_FROM_OTF(x, NPY_DOUBLE, NPY_IN_ARRAY);
  if (xc == NULL) return NULL;

  yc = PyArray_FROM_OTF(y, NPY_LONG, NPY_IN_ARRAY);
  if (yc == NULL) return NULL;

  /* Check size */
  if (PyArray_DIM(yc, 0) != PyArray_DIM(xc, 0)){
    PyErr_SetString(PyExc_ValueError, "y array has wrong 0-dimension");
    return NULL;
  }
   
  int n       = (int) PyArray_DIM(xc, 0);
  int d       = (int) PyArray_DIM(xc, 1);
  double **_x = dmatrix_from_numpy(xc);
  long *_ytmp = (long *) PyArray_DATA(yc);
  int verbose = 0;

  int *_y = (int *) malloc(n * sizeof(int));
  for(i=0; i<n; i++)
    _y[i] = (int) _ytmp[i];
  
  /* Compute _W */
  double *_W = (double*) malloc(n * sizeof(double));
  for (i=0; i<n; i++){
    _W[i] = 1.0;
    if ((cost * _y[i]) < 0.0)
      _W[i] = 1.0 - fabs(cost);
  }

  SupportVectorMachine svm;  
  if(compute_svm(&svm, n, d, _x, _y, kernel, kp, C, 
		 tol, eps, maxloops, verbose, _W, alpha_tversky, beta_tversky)){
    PyErr_SetString(PyExc_StandardError, "Problem with compute_svm()");
    return NULL;
  }
   
  free(_x);
  free(_y);
  free(_W);
  free(svm.error_cache);
  free(svm.precomputed_self_dot_product);
  free(svm.Cw);
  
  npy_intp wdims[1];
  npy_intp adims[1];
  wdims[0] = (npy_intp) d;
  adims[0] = (npy_intp) n;

  /* Create Array Objects */
  PyObject *w = PyArray_SimpleNew(1, wdims, NPY_DOUBLE);
  if(w == NULL) return NULL;
  PyObject *a = PyArray_SimpleNew(1, adims, NPY_DOUBLE);
  if(a == NULL) return NULL;

  double *_w = (double *) PyArray_DATA(w);
  double *_a = (double *) PyArray_DATA(a);
 
  /* Copy and free svm.w */
  if(svm.kernel_type == 1){ // SVM_KERNEL_LINEAR
    for(i=0; i<d; i++)
      _w[i] = svm.w[i];
    free(svm.w);
  }
  else{
    for(i=0; i<d; i++)
      _w[i] = 0.0;
  }
  
  /* Copy and free svm.a */
  for(i=0; i<n; i++)
    _a[i] = svm.alph[i];
  free(svm.alph);

  Py_DECREF(xc);
  Py_DECREF(yc);
    
  return Py_BuildValue("(N, N, d, i)", w, a, svm.b, svm.convergence);
}


/* Predict SVM */
static PyObject *svmcore_predictsvm(PyObject *self, PyObject *args, PyObject *keywds)
{
  PyObject *x = NULL;      PyObject *xc = NULL;
  PyObject *y = NULL;      PyObject *yc = NULL;
  PyObject *sample = NULL; PyObject *samplec = NULL;
  PyObject *w = NULL;      PyObject *wc = NULL;
  PyObject *a = NULL;      PyObject *ac = NULL;
  
  int kernel;
  double kp, b, alpha_tversky, beta_tversky;
  int i;
  
  static char *kwlist[] = {"x", "y", "sample", "w", "a", "b", "kp", "kernel", "alpha_tversky", "beta_tversky", NULL};

  if (!PyArg_ParseTupleAndKeywords(args, keywds, "OOOOOddidd", kwlist, 
				   &x, &y, &sample, &w, &a, &b, &kp, &kernel, &alpha_tversky, &beta_tversky))
    return NULL;
  
  xc = PyArray_FROM_OTF(x, NPY_DOUBLE, NPY_IN_ARRAY);
  if (xc == NULL) return NULL;

  yc = PyArray_FROM_OTF(y, NPY_LONG, NPY_IN_ARRAY);
  if (yc == NULL) return NULL;
  
  samplec = PyArray_FROM_OTF(sample, NPY_DOUBLE, NPY_IN_ARRAY);
  if (samplec == NULL) return NULL;
  
  wc = PyArray_FROM_OTF(w, NPY_DOUBLE, NPY_IN_ARRAY);
  if (wc == NULL) return NULL;
  
  ac = PyArray_FROM_OTF(a, NPY_DOUBLE, NPY_IN_ARRAY);
  if (ac == NULL) return NULL;
  
  
  /* Check size */
  if (PyArray_DIM(yc, 0) != PyArray_DIM(xc, 0)){
    PyErr_SetString(PyExc_ValueError, "y array has wrong 0-dimension");
    return NULL;
  }
  
  if (PyArray_DIM(samplec, 0) != PyArray_DIM(xc, 1)){
    PyErr_SetString(PyExc_ValueError, "sample array has wrong 0-dimension");
    return NULL;
  }
  
  if (PyArray_DIM(wc, 0) != PyArray_DIM(xc, 1)){
    PyErr_SetString(PyExc_ValueError, "w array has wrong 0-dimension");
    return NULL;
  }
  
  if (PyArray_DIM(ac, 0) != PyArray_DIM(xc, 0)){
    PyErr_SetString(PyExc_ValueError, "a array has wrong 0-dimension");
    return NULL;
  }
  
  int n           = (int) PyArray_DIM(xc, 0);
  int d           = (int) PyArray_DIM(xc, 1);
  double **_x     = dmatrix_from_numpy(xc);
  long *_ytmp     = (long *) PyArray_DATA(yc);
  double *_sample = (double *) PyArray_DATA(samplec);
  double *_w      = (double *) PyArray_DATA(wc);
  double *_a      = (double *) PyArray_DATA(ac);
  double *margin;

  int *_y = (int *) malloc(n * sizeof(int));
  for(i=0; i<n; i++)
    _y[i] = (int) _ytmp[i];

  SupportVectorMachine svm;
  svm.n = n;
  svm.d = d;
  svm.x = _x;
  svm.y = _y;
  svm.w = _w;
  svm.alph = _a;
  svm.b = b;
  svm.kernel_type = kernel;
  svm.two_sigma_squared = kp;
  svm.alpha_tversky = alpha_tversky;
  svm.beta_tversky = beta_tversky;
  
  double pred = predict_svm(&svm, _sample, &margin);
  
  free(_x);
  free(_y);
  free(margin);
    
  Py_DECREF(xc);
  Py_DECREF(yc);
  Py_DECREF(samplec);
  Py_DECREF(wc);
  Py_DECREF(ac);
  
  return Py_BuildValue("d", pred);
}


/*  Compute SVM TR*/
static PyObject *svmcore_computesvmtr(PyObject *self, PyObject *args, PyObject *keywds)
{
  PyObject *x  = NULL; PyObject *y  = NULL;
  PyObject *xc = NULL; PyObject *yc = NULL;

  int maxloops;
  double C, tol, eps, cost;
  int i, j;

  /* Parse Tuple*/
  static char *kwlist[] = {"x", "y", "C", "tol", "eps", "maxloops", "cost", NULL};
  if (!PyArg_ParseTupleAndKeywords(args, keywds, "OOdddid", kwlist,
				   &x, &y, &C, &tol, &eps, &maxloops, &cost))
    return NULL;
  
  xc = PyArray_FROM_OTF(x, NPY_DOUBLE, NPY_IN_ARRAY);
  if (xc == NULL) return NULL;
  
  yc = PyArray_FROM_OTF(y, NPY_LONG, NPY_IN_ARRAY);
  if (yc == NULL) return NULL;
  
  /* Check size */
  if (PyArray_DIM(yc, 0) != PyArray_DIM(xc, 0)){
    PyErr_SetString(PyExc_ValueError, "y array has wrong 0-dimension");
    return NULL;
  }
 
  int n       = (int) PyArray_DIM(xc, 0);
  int d       = (int) PyArray_DIM(xc, 1);
  double **_x = dmatrix_from_numpy(xc);
  long *_ytmp = (long *) PyArray_DATA(yc);
  int verbose = 0;
  double threshold = 0.0;
  int knn = 0;

  int *_y = (int *) malloc(n * sizeof(int));
  for(i=0; i<n; i++)
    _y[i] = (int) _ytmp[i];

  /* Compute _W */
  double *_W = (double*) malloc(n * sizeof(double));
  for (i=0; i<n; i++){
    _W[i] = 1.0;
    if ((cost * _y[i]) < 0.0)
      _W[i] = 1.0 - fabs(cost);
  } 

  RegularizedSlopeFunctionNetworks rsfn;  
  if(compute_rsfn(&rsfn, n, d, _x , _y, C, tol, eps, maxloops, verbose, _W, threshold, knn)){
    PyErr_SetString(PyExc_StandardError, "Problem with compute_rsfn()");
    return NULL;
  }
  
  free(_x);
  free(_y);
  free(_W);
  free(rsfn.svm.y);
  free(rsfn.svm.error_cache);
  free(rsfn.svm.Cw);
  free_dmatrix(rsfn.svm.K, rsfn.svm.n, rsfn.svm.n);
  free_dmatrix(rsfn.x, n, d);
  
  npy_intp wdims[1];
  npy_intp adims[1];
  npy_intp sf_wdims[1];
  npy_intp sf_idims[1];
  npy_intp sf_jdims[1];
  npy_intp sf_bdims[1];
  npy_intp svm_xdims[2];
  wdims[0]     = (npy_intp) rsfn.sf.nsf;
  adims[0]     = (npy_intp) n;
  sf_wdims[0]  = (npy_intp) rsfn.sf.nsf;
  sf_bdims[0]  = (npy_intp) rsfn.sf.nsf;
  sf_idims[0]  = (npy_intp) rsfn.sf.nsf;
  sf_jdims[0]  = (npy_intp) rsfn.sf.nsf;
  svm_xdims[0] = (npy_intp) n;
  svm_xdims[1] = (npy_intp) rsfn.sf.nsf;
  
  /* Create Array Objects */
  PyObject *w = PyArray_SimpleNew(1, wdims, NPY_DOUBLE);
  if(w == NULL) return NULL;
  PyObject *a = PyArray_SimpleNew(1, adims, NPY_DOUBLE);
  if(a == NULL) return NULL;
  PyObject *sf_w = PyArray_SimpleNew(1, sf_wdims, NPY_DOUBLE);
  if(sf_w == NULL) return NULL;
  PyObject *sf_b = PyArray_SimpleNew(1, sf_bdims, NPY_DOUBLE);
  if(sf_b == NULL) return NULL;
  PyObject *sf_i = PyArray_SimpleNew(1, sf_idims, NPY_INT);
  if(sf_i == NULL) return NULL;
  PyObject *sf_j = PyArray_SimpleNew(1, sf_jdims, NPY_INT);
  if(sf_j == NULL) return NULL;
  PyObject *svm_x = PyArray_SimpleNew(2, svm_xdims, NPY_DOUBLE);
  if(svm_x == NULL) return NULL;

  double *_w      = (double *) PyArray_DATA(w);
  double *_a      = (double *) PyArray_DATA(a);
  double *_sf_w   = (double *) PyArray_DATA(sf_w);
  double *_sf_b   = (double *) PyArray_DATA(sf_b);
  int    *_sf_i   = (int *) PyArray_DATA(sf_i);
  int    *_sf_j   = (int *) PyArray_DATA(sf_j);
  double **_svm_x = dmatrix_from_numpy(svm_x);

  /* Copy and free svm.w */
  for(i=0; i<rsfn.sf.nsf; i++)
    _w[i] = rsfn.svm.w[i];
  free(rsfn.svm.w);

  /* Copy and free svm.a */
  for(i=0; i<n; i++)
    _a[i] = rsfn.svm.alph[i];
  free(rsfn.svm.alph);
      
  /* Copy and free rsfn.sf->w */
  for(i=0; i<rsfn.sf.nsf; i++)
    _sf_w[i] = rsfn.sf.w[i];
  free(rsfn.sf.w);

  /* Copy and free rsfn.sf->b */
  for(i=0; i<rsfn.sf.nsf; i++)
    _sf_b[i] = rsfn.sf.b[i];
  free(rsfn.sf.b);

  /* Copy and free rsfn.sf->i */
  for(i=0; i<rsfn.sf.nsf; i++)
    _sf_i[i] = rsfn.sf.i[i];
  free(rsfn.sf.i);

  /* Copy and free rsfn.sf->i */
  for(i=0; i<rsfn.sf.nsf; i++)
    _sf_j[i] = rsfn.sf.j[i];
  free(rsfn.sf.j);

  /* Copy and free svm.x */
  for(i=0; i<n; i++)
    for(j=0; j<rsfn.sf.nsf; j++)
      _svm_x[i][j] = rsfn.svm.x[i][j];
  free_dmatrix(rsfn.svm.x, n, rsfn.sf.nsf);
  free(_svm_x);

  Py_DECREF(xc);
  Py_DECREF(yc);
  
  return Py_BuildValue("(N, N, d, i, N, N, N, N, N)", w, a, rsfn.svm.b, 
		       rsfn.svm.convergence, sf_w, sf_b, sf_i, sf_j, svm_x);
}

/* Predict SVM */
static PyObject *svmcore_predictsvmtr(PyObject *self, PyObject *args, PyObject *keywds)
{
  PyObject *x      = NULL; PyObject *xc      = NULL;
  PyObject *y      = NULL; PyObject *yc      = NULL;
  PyObject *sample = NULL; PyObject *samplec = NULL;
  PyObject *w      = NULL; PyObject *wc      = NULL;
  PyObject *sf_w   = NULL; PyObject *sf_wc   = NULL;
  PyObject *sf_b   = NULL; PyObject *sf_bc   = NULL; 
  PyObject *sf_i   = NULL; PyObject *sf_ic   = NULL; 
  PyObject *sf_j   = NULL; PyObject *sf_jc   = NULL;
  
  double b;
  int i;
   
  static char *kwlist[] = {"x", "y", "sample", "w", "b", "sf_w", "sf_b", "sf_i", "sf_j", NULL};
  
  if (!PyArg_ParseTupleAndKeywords(args, keywds, "OOOOdOOOO", kwlist, 
				   &x, &y, &sample, &w, &b,  &sf_w, &sf_b, &sf_i, &sf_j))
    return NULL; 
  
  xc = PyArray_FROM_OTF(x, NPY_DOUBLE, NPY_IN_ARRAY);
  if (xc == NULL) return NULL;
  
  yc = PyArray_FROM_OTF(y, NPY_LONG, NPY_IN_ARRAY);
  if (yc == NULL) return NULL;
  
  samplec = PyArray_FROM_OTF(sample, NPY_DOUBLE, NPY_IN_ARRAY);
  if (samplec == NULL) return NULL;
  
  wc = PyArray_FROM_OTF(w, NPY_DOUBLE, NPY_IN_ARRAY);
  if (wc == NULL) return NULL;

  sf_wc = PyArray_FROM_OTF(sf_w, NPY_DOUBLE, NPY_IN_ARRAY);
  if (sf_wc == NULL) return NULL;

  sf_bc = PyArray_FROM_OTF(sf_b, NPY_DOUBLE, NPY_IN_ARRAY);
  if (sf_bc == NULL) return NULL;

  sf_ic = PyArray_FROM_OTF(sf_i, NPY_INT, NPY_IN_ARRAY);
  if (sf_ic == NULL) return NULL;

  sf_jc = PyArray_FROM_OTF(sf_j, NPY_INT, NPY_IN_ARRAY);
  if (sf_jc == NULL) return NULL;

  /* Check size */
  if (PyArray_DIM(yc, 0) != PyArray_DIM(xc, 0)){
    PyErr_SetString(PyExc_ValueError, "y array has wrong 0-dimension");
    return NULL;
  }
  
  if (PyArray_DIM(samplec, 0) != PyArray_DIM(xc, 1)){
    PyErr_SetString(PyExc_ValueError, "sample array has wrong 0-dimension");
    return NULL;
  }

  int n           = (int) PyArray_DIM(xc, 0);
  int d           = (int) PyArray_DIM(xc, 1);
  double **_x     = dmatrix_from_numpy(xc);
  long *_ytmp     = (long *) PyArray_DATA(yc);
  double *_sample = (double *) PyArray_DATA(samplec);
  double *_w      = (double *) PyArray_DATA(wc);
  int nsf         = (int) PyArray_DIM(sf_wc, 0);
  double *_sf_w   = (double *) PyArray_DATA(sf_wc);
  double *_sf_b   = (double *) PyArray_DATA(sf_bc);
  int   *_sf_i    = (int *) PyArray_DATA(sf_ic);
  int   *_sf_j    = (int *) PyArray_DATA(sf_jc);
  double *margin;

  int *_y = (int *) malloc(n * sizeof(int));
  for(i=0; i<n; i++)
    _y[i] = (int) _ytmp[i];
    
  RegularizedSlopeFunctionNetworks rsfn;
  rsfn.x = _x;
  rsfn.d = d;
  rsfn.sf.nsf = nsf;
  rsfn.sf.w = _sf_w;
  rsfn.sf.b = _sf_b;
  rsfn.sf.i = _sf_i;
  rsfn.sf.j = _sf_j;
  rsfn.svm.d = nsf;
  rsfn.svm.w = _w;
  rsfn.svm.b = b;
  rsfn.svm.y = _y;
  rsfn.svm.kernel_type = 1; /* kernel linear*/
  
  double pred = predict_rsfn(&rsfn, _sample, &margin);

  free(_x);
  free(_y);
  free(margin);
    
  Py_DECREF(xc);
  Py_DECREF(yc);
  Py_DECREF(samplec);
  Py_DECREF(wc);
  Py_DECREF(sf_wc);
  Py_DECREF(sf_bc);
  Py_DECREF(sf_ic);
  Py_DECREF(sf_jc);
  
  return Py_BuildValue("d", pred);
}


/* Doc strings: */
static char svmcore_computesvm_doc[] = "Compute SVM. Return a tuple (w, a, b, conv)";
static char svmcore_predictsvm_doc[] = "Predict SVM. Return the prediction (an integer)";
static char svmcore_computesvmtr_doc[] = "Compute Terminated Ramp SVM";
static char svmcore_predictsvmtr_doc[] = "Predict Terminated Ramp SVM";
static char module_doc[] = "SVM module based on SVM C-libraries developed by Stefano Merler";

/* Method table */
static PyMethodDef svmcore_methods[] = {
  {"computesvm",
   (PyCFunction)svmcore_computesvm,
   METH_VARARGS | METH_KEYWORDS,
   svmcore_computesvm_doc},
  {"predictsvm",
   (PyCFunction)svmcore_predictsvm,
   METH_VARARGS | METH_KEYWORDS,
   svmcore_predictsvm_doc},
  {"computesvmtr",
   (PyCFunction)svmcore_computesvmtr,
   METH_VARARGS | METH_KEYWORDS,
   svmcore_computesvmtr_doc},
  {"predictsvmtr",
   (PyCFunction)svmcore_predictsvmtr,
   METH_VARARGS | METH_KEYWORDS,
   svmcore_predictsvmtr_doc},
  {NULL, NULL, 0, NULL}
};

/* Init */
void initsvmcore()
{
  Py_InitModule3("svmcore", svmcore_methods, module_doc);
  import_array();
}
