/*  
    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>

void matrix_dot_vector(double *A, double *b, double *out,
		       int nn, int pp)
{
  int n, p;
  
  for (n=0; n<nn; n++)
    {
      out[n] = 0.0;
      
      for (p=0; p<pp; p++)
	out[n] += A[p + (n * pp)] * b[p];  
    }
}


void gradient_descent_steps(double *c, double *k, double *y, int t,
			    double stepsize, int nn, double *cout)
{
  int i, n;
  double *tmp; 
  
  tmp = (double *) malloc (nn * sizeof(double));
  
  for (n=0; n<nn; n++)
    cout[n] = c[n];

  for (i=0; i<t; i++)
    {
      matrix_dot_vector(k, cout, tmp, nn, nn);
      
      for (n=0; n<nn; n++)
	{
	  tmp[n] = (y[n] - tmp[n]) * stepsize;
	  cout[n] = cout[n] + tmp[n];
	}
    }

  free(tmp);
}


static PyObject *spectralreg_gradient_descent_steps(PyObject *self, PyObject *args, PyObject *keywds)
{
  PyObject *c = NULL;
  PyObject *cC = NULL;
  double *cD;

  PyObject *k = NULL;
  PyObject *kC = NULL;
  double *kD;

  PyObject *y = NULL;
  PyObject *yC = NULL;
  double *yD;
  
  double stepsize;
  int t;

  PyObject *coutC = NULL;
  double *coutD;
  
  npy_intp nn;
  npy_intp cout_dims[1];


  /* Parse Tuple*/
  static char *kwlist[] = {"c", "k", "y", "stepsize", "t", NULL};
  if (!PyArg_ParseTupleAndKeywords(args, keywds, "OOOdi", kwlist,
				   &c, &k, &y, &stepsize, &t))
    return NULL;
  
  cC = PyArray_FROM_OTF(c, NPY_DOUBLE, NPY_IN_ARRAY);
  if (cC == NULL) return NULL;

  kC = PyArray_FROM_OTF(k, NPY_DOUBLE, NPY_IN_ARRAY);
  if (kC == NULL) return NULL;
  
  yC = PyArray_FROM_OTF(y, NPY_DOUBLE, NPY_IN_ARRAY);
  if (yC == NULL) return NULL;

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

  if (PyArray_NDIM(kC) != 2)
    {
      PyErr_SetString(PyExc_ValueError, "k must be 2d array");
      return NULL;
    }

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

  nn = PyArray_DIM(cC, 0);
  
  cD = (double *) PyArray_DATA(cC);
  kD = (double *) PyArray_DATA(kC);
  yD = (double *) PyArray_DATA(yC);
  
  cout_dims[0] = nn;

  coutC = PyArray_SimpleNew (1, cout_dims, NPY_DOUBLE);
  coutD = (double *) PyArray_DATA(coutC);

  gradient_descent_steps(cD, kD, yD, t, stepsize, nn, coutD);

  Py_DECREF(cC);
  Py_DECREF(kC);
  Py_DECREF(yC);

  return Py_BuildValue("N", coutC);
}


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

static char spectralreg_gradient_descent_steps_doc[] = "";

/* Method table */
static PyMethodDef spectralreg_methods[] = {
  {"gradient_descent_steps",
   (PyCFunction)spectralreg_gradient_descent_steps,
   METH_VARARGS | METH_KEYWORDS,
   spectralreg_gradient_descent_steps_doc},
  {NULL, NULL, 0, NULL}
};


/* Init */
void initspectralreg()
{
  Py_InitModule3("spectralreg", spectralreg_methods, module_doc);
  import_array();
}
