/*
    This file is part of svmcore.
    
    This code is written by Stefano Merler, merler@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 <stdio.h>
#include <math.h>
#include <stdlib.h>
#include "svm.h"

static void svm_smo();
static int examineExample();
static int takeStep();

static double learned_func_linear();
static double learned_func_nonlinear();

static double rbf_kernel();
static double polinomial_kernel();
static double dot_product_func();
static double tversky_kernel();



int compute_svm(SupportVectorMachine *svm,int n,int d,double *x[],int y[],
		int kernel,double kp,double C,double tol,
		double eps,int maxloops,int verbose,double W[], double alpha_tversky, double beta_tversky)
     /*
       compute svm model.x,y,n,d are the input data.
       kernel is the kernel type (see ml.h), kp is the kernel parameter 
       (for gaussian and polynomial kernel), C is the regularization parameter.
       eps and tol determine convergence, maxloops is thae maximum number
       of optimization loops, W is an array (of length n) of weights for 
       cost-sensitive  classification.

       Return value: 0 on success, 1 otherwise.
     */
{
  int i;
  int nclasses;
  int *classes;

  svm_srand48(0); // albanese add

  svm->n=n;
  svm->d=d;
  svm->C=C;
  svm->tolerance=tol;
  svm->eps=eps;
  svm->two_sigma_squared=kp;
  svm->kernel_type=kernel;
  svm->maxloops=maxloops;
  svm->verbose=verbose;
  svm->alpha_tversky=alpha_tversky;
  svm->beta_tversky=beta_tversky;

  svm->b=0.0;

  if(C<=0){
    fprintf(stderr,"compute_svm: regularization parameter C must be > 0\n");
    return 1;
  }
  if(eps<=0){
    fprintf(stderr,"compute_svm: parameter eps must be > 0\n");
    return 1;
  }
  if(tol<=0){
    fprintf(stderr,"compute_svm: parameter tol must be > 0\n");
    return 1;
  }
  if(maxloops<=0){
    fprintf(stderr,"compute_svm: parameter maxloops must be > 0\n");
    return 1;
  }
  if(W){
    for(i=0;i<n;i++)
      if(W[i]<=0){
	fprintf(stderr,"compute_svm: parameter W[%d] must be > 0\n",i);
	return 1;
      }
  }

  switch(kernel){
  case SVM_KERNEL_LINEAR:
    break;
  case SVM_KERNEL_GAUSSIAN:
    if(kp <=0){
      fprintf(stderr,"compute_svm: parameter kp must be > 0\n");
      return 1;
    }
    break;
  case SVM_KERNEL_POLINOMIAL:
    if(kp <=0){
      fprintf(stderr,"compute_svm: parameter kp must be > 0\n");
      return 1;
    }
    break;
  case SVM_KERNEL_TVERSKY:
    if((alpha_tversky < 0) || (beta_tversky < 0)){
      fprintf(stderr,"compute_svm: parameter alpha & beta must be >= 0\n");
      return 1;
    }
    break;
  default:
    fprintf(stderr,"compute_svm: kernel not recognized\n");
    return 1;
  }

  nclasses=iunique(y,n, &classes);

  if(nclasses<=0){
    fprintf(stderr,"compute_svm: iunique error\n");
    return 1;
  }
  if(nclasses==1){
    fprintf(stderr,"compute_svm: only 1 class recognized\n");
    return 1;
  }
  if(nclasses==2)
    if(classes[0] != -1 || classes[1] != 1){
      fprintf(stderr,"compute_svm: for binary classification classes must be -1,1\n");
      return 1;
    }
  if(nclasses>2){
    fprintf(stderr,"compute_svm: multiclass classification not allowed\n");
    return 1;
  }

  if(kernel==SVM_KERNEL_LINEAR)
    if(!(svm->w=dvector(d))){
      fprintf(stderr,"compute_svm: out of memory\n");
      return 1;
    }
  if(!(svm->Cw=dvector(n))){
    fprintf(stderr,"compute_svm: out of memory\n");
    return 1;
  }
  if(!(svm->alph=dvector(n))){
    fprintf(stderr,"compute_svm: out of memory\n");
    return 1;
  }
  if(!(svm->error_cache=dvector(n))){
    fprintf(stderr,"compute_svm: out of memory\n");
    return 1;
  }
  if(!(svm->precomputed_self_dot_product=dvector(n))){
    fprintf(stderr,"compute_svm: out of memory\n");
    return 1;
  }
  
  for(i=0;i<n;i++)
    svm->error_cache[i]=-y[i];

  if(W){
    for(i=0;i<n;i++)
      svm->Cw[i]=svm->C * W[i];
  }else{
    for(i=0;i<n;i++)
      svm->Cw[i]=svm->C;
  }    
  

  svm->x=x;
  svm->y=y;

  svm_smo(svm);
  
  svm->non_bound_support=svm->bound_support=0;
  for(i=0;i<n;i++){
    if(svm->alph[i]>0){
      if(svm->alph[i]< svm->Cw[i])
	svm->non_bound_support++;
      else
	svm->bound_support++;
    }
  }
  
  free_ivector(classes);

  return 0;
}

     
double predict_svm(SupportVectorMachine *svm,double x[],double **margin)
     /*
       predicts svm model on a test point x. the array margin (of length
       nclasses) shall contain the margin of the classes.

       Return value: the predicted value on success (<0.0 or >0.0), 
       0 on succes with non unique classification.
     */

{
  int i,j;
  double y = 0.0;
  double K;
  double s11, s12, s22;

  if(svm->kernel_type==SVM_KERNEL_GAUSSIAN){
    for(i = 0; i < svm->n; i++){
      if(svm->alph[i] > 0){
	K=0.0;
	for(j=0;j<svm->d;j++)
	  K+=(svm->x[i][j]-x[j])*(svm->x[i][j]-x[j]);
	y += svm->alph[i] * svm->y[i] * exp(-K/svm->two_sigma_squared);
      }
    }
    y -= svm->b;
  }

  if(svm->kernel_type==SVM_KERNEL_TVERSKY){
    for(i = 0; i < svm->n; i++){
      if(svm->alph[i] > 0){
	s11 = s12 = s22 = 0.0;
	for(j=0;j<svm->d;j++){
	  s11 += svm->x[i][j] * svm->x[i][j];
	  s12 += svm->x[i][j] * x[j];
	  s22 += x[j] * x[j];
	}
	K = s12/(svm->alpha_tversky * s11 + svm->beta_tversky * s22 + (1.0 - svm->alpha_tversky - svm->beta_tversky) * s12);
	y += svm->alph[i] * svm->y[i] * K;
      }
    }
    y -= svm->b;
  }

  if(svm->kernel_type==SVM_KERNEL_LINEAR){
    K=0.0;
    for(j=0;j<svm->d;j++)
      K+=svm->w[j]*x[j];
    y=K-svm->b;
  }

  if(svm->kernel_type==SVM_KERNEL_POLINOMIAL){
    for(i = 0; i < svm->n; i++){
      if(svm->alph[i] > 0){
	K=1.0;
	for(j=0;j<svm->d;j++)
	  K+=svm->x[i][j]*x[j];
	
	y += svm->alph[i] * svm->y[i] * pow(K,svm->two_sigma_squared);
      }
    }
    y -= svm->b;
  }

  (*margin)=dvector(2);
  if(y>0)
    (*margin)[1]=y;
  if(y<0)
    (*margin)[0]=-y;
  
  return y;
    
}


static void svm_smo(SupportVectorMachine *svm)
{
  int i,k;
  int numChanged;
  int examineAll;
  int nloops=0;


  svm->end_support_i=svm->n;

  if(svm->kernel_type==SVM_KERNEL_LINEAR){
    svm->kernel_func=dot_product_func;
    svm->learned_func=learned_func_linear;
  }

  if(svm->kernel_type==SVM_KERNEL_POLINOMIAL){
    svm->kernel_func=polinomial_kernel;
    svm->learned_func=learned_func_nonlinear;
  }

  if(svm->kernel_type==SVM_KERNEL_GAUSSIAN){
    /*
    svm->precomputed_self_dot_product=(double *)calloc(svm->n,sizeof(double));
    */
    for(i=0;i<svm->n;i++)
      svm->precomputed_self_dot_product[i] = dot_product_func(i,i,svm);
    svm->kernel_func=rbf_kernel;
    svm->learned_func=learned_func_nonlinear;
  }

  if(svm->kernel_type==SVM_KERNEL_TVERSKY){
    /*
      svm->precomputed_self_dot_product=(double *)calloc(svm->n,sizeof(double));
    */
    for(i=0;i<svm->n;i++)
      svm->precomputed_self_dot_product[i] = dot_product_func(i,i,svm);
    svm->kernel_func=tversky_kernel;
    svm->learned_func=learned_func_nonlinear;
  }

  numChanged=0;
  examineAll=1;

  svm->convergence=1;
  while(svm->convergence==1 &&(numChanged>0 || examineAll)){
    numChanged=0;
    if(examineAll){
      for(k=0;k<svm->n;k++)
	numChanged += examineExample(k,svm);
    }else{
      for(k=0;k<svm->n;k++)
	if(svm->alph[k] > 0 && svm->alph[k] < svm->Cw[k])
	  numChanged += examineExample(k,svm);
    }
    if(examineAll==1)
      examineAll=0;
    else if(numChanged==0)
      examineAll=1;

    nloops+=1;
    if(nloops==svm->maxloops)
      svm->convergence=0;
    if(svm->verbose==1)
      fprintf(stdout,"%6d\b\b\b\b\b\b\b",nloops);
  }

}


static double learned_func_linear(k,svm)
     int k;
     SupportVectorMachine *svm;

{
  double s=0.0;
  int i;
  
  for(i=0;i<svm->d;i++)
    s += svm->w[i] * svm->x[k][i];

  s -= svm->b;

  return s;
}

static double learned_func_nonlinear(k,svm)
     int k;
     SupportVectorMachine *svm;
{
  double s=0.0;
  int i;

  for(i=0;i<svm->end_support_i;i++)
    if(svm->alph[i]>0)
      s += svm->alph[i]*svm->y[i]*svm->kernel_func(i,k,svm);

  s -= svm->b;

  return s;
}

static double polinomial_kernel(i1,i2,svm)
     int i1,i2;
     SupportVectorMachine *svm;
{     
  double s;

  s = pow(1+dot_product_func(i1,i2,svm),svm->two_sigma_squared);
  return s;
}


static double rbf_kernel(i1,i2,svm)
     int i1,i2;
     SupportVectorMachine *svm;
{     
  double s;

  s = dot_product_func(i1,i2,svm);

  s *= -2;

  s += svm->precomputed_self_dot_product[i1] + svm->precomputed_self_dot_product[i2];
  
  return exp(-s/svm->two_sigma_squared);
}


static double tversky_kernel(i1,i2,svm)
     int i1,i2;
     SupportVectorMachine *svm;
{     
  double s11;
  double s12;
  double s22;

  s11 = dot_product_func(i1,i1,svm);
  s12 = dot_product_func(i1,i2,svm);
  s22 = dot_product_func(i2,i2,svm);

  return s12/(svm->alpha_tversky * s11 + svm->beta_tversky * s22 + (1.0 - svm->alpha_tversky - svm->beta_tversky) * s12);
}


static double dot_product_func(i1,i2,svm)
     int i1,i2;
     SupportVectorMachine *svm;
{ 
  double dot = 0.0;
  int i;

  for(i=0;i<svm->d;i++)
    dot += svm->x[i1][i] * svm->x[i2][i];

  return dot;
}

static int examineExample(i1,svm)
     int i1;
     SupportVectorMachine *svm;
{
  double y1, alph1, E1, r1;
  
  y1=svm->y[i1];
  alph1=svm->alph[i1];
  
  if(alph1>0 && alph1<svm->Cw[i1])
    E1 = svm->error_cache[i1];
  else
    E1 = svm->learned_func(i1,svm)-y1;

  r1 = y1 *E1;

  if((r1<-svm->tolerance && alph1<svm->Cw[i1]) ||(r1>svm->tolerance && alph1>0)){
    {
      int k, i2;
      double tmax;

      for(i2=(-1),tmax=0,k=0;k<svm->end_support_i;k++)
	if(svm->alph[k]>0 && svm->alph[k]<svm->Cw[k]){
	  double E2,temp;

	  E2=svm->error_cache[k];

	  temp=fabs(E1-E2);
      
	  if(temp>tmax){
	    tmax=temp;
	    i2=k;
	  }
	}
  
      if(i2>=0){
	if(takeStep(i1,i2,svm))
	  return 1;
      }
    }
    {
      int k0,k,i2;
      for(k0=(int)(svm_drand48()*svm->end_support_i),k=k0;k<svm->end_support_i+k0;k++){
	i2 = k % svm->end_support_i;
	if(svm->alph[i2]>0 && svm->alph[i2]<svm->Cw[i2]){
	  if(takeStep(i1,i2,svm))
	    return 1;
	}
      }
    }
    {
      int k0,k,i2;

      for(k0=(int)(svm_drand48()*svm->end_support_i),k=k0;k<svm->end_support_i+k0;k++){
	i2 = k % svm->end_support_i;
	if(takeStep(i1,i2,svm))
	  return 1;
      }
    }
  }
  return 0;
}


static int takeStep(i1,i2,svm)
     int i1,i2;
     SupportVectorMachine *svm;
{
  int y1,y2,s;
  double alph1,alph2;
  double a1,a2;
  double E1,E2,L,H,k11,k12,k22,eta,Lobj,Hobj;

  if(i1==i2)
    return 0;

  alph1=svm->alph[i1];
  y1=svm->y[i1];
  if(alph1>0 && alph1<svm->Cw[i1])
    E1=svm->error_cache[i1];
  else
    E1=svm->learned_func(i1,svm)-y1;


  alph2=svm->alph[i2];
  y2=svm->y[i2];
  if(alph2>0 && alph2<svm->Cw[i2])
    E2=svm->error_cache[i2];
  else
    E2=svm->learned_func(i2,svm)-y2;

  s=y1*y2;

  if(y1==y2){
    double gamma;
    
    gamma = alph1+alph2;
    if(gamma-svm->Cw[i1]>0)
      L=gamma-svm->Cw[i1];
    else
      L=0.0;

    if(gamma<svm->Cw[i2])
      H=gamma;
    else
      H=svm->Cw[i2];


  }else{
    double gamma;
    
    gamma = alph2-alph1;

    if(gamma>0)
      L=gamma;
    else
      L=0.0;

    if(svm->Cw[i1]+gamma<svm->Cw[i2])
      H=svm->Cw[i1]+gamma;
    else
      H=svm->Cw[i2];
  }
  
  if(L==H)
    return 0;

  k11=svm->kernel_func(i1,i1,svm);
  k12=svm->kernel_func(i1,i2,svm);
  k22=svm->kernel_func(i2,i2,svm);


  eta=2*k12-k11-k22;

  if(eta<0){
    a2=alph2+y2*(E2-E1)/eta;
    if(a2<L)
      a2=L;
    else if(a2>H)
      a2=H;
  }else{
    {
      double c1,c2;

      c1=eta/2;
      c2=y2*(E1-E2)-eta*alph2;
      Lobj=c1*L*L+c2*L;
      Hobj=c1*H*H+c2*H;
    }
    if(Lobj>Hobj+svm->eps)
      a2=L;
    else if(Lobj<Hobj-svm->eps)
      a2=H;
    else
      a2=alph2;
  }
  
  if(fabs(a2-alph2)<svm->eps*(a2+alph2+svm->eps))
    return 0;

  a1=alph1-s*(a2-alph2);

  if(a1<0){
    a2 += s*a1;
    a1=0;
  }else if(a1>svm->Cw[i1]){
    double t;

    t=a1-svm->Cw[i1];
    a2 += s*t;
    a1=svm->Cw[i1];
  }

  {
    double b1,b2,bnew;

    if(a1>0 && a1 <svm->Cw[i1])
      bnew=svm->b+E1+y1*(a1-alph1)*k11+y2*(a2-alph2)*k12;
    else{
      if(a2>0 && a2 <svm->Cw[i2])
	bnew=svm->b+E2+y1*(a1-alph1)*k12+y2*(a2-alph2)*k22;
      else{
	b1=svm->b+E1+y1*(a1-alph1)*k11+y2*(a2-alph2)*k12;
	b2=svm->b+E2+y1*(a1-alph1)*k12+y2*(a2-alph2)*k22;
	bnew=(b1+b2)/2;
      }
    }

    svm->delta_b=bnew-svm->b;
    svm->b=bnew;
  }

  if(svm->kernel_type==SVM_KERNEL_LINEAR){
    double t1,t2;
    int i;

    t1=y1*(a1-alph1);
    t2=y2*(a2-alph2);

    for(i=0;i<svm->d;i++)
      svm->w[i] += svm->x[i1][i]*t1+svm->x[i2][i]*t2;
  }

  {
    double t1,t2;
    int i;

    t1=y1*(a1-alph1);
    t2=y2*(a2-alph2);
    
    for(i=0;i<svm->end_support_i;i++)
      svm->error_cache[i] += t1*svm->kernel_func(i1,i,svm)+
	t2*svm->kernel_func(i2,i,svm)-svm->delta_b;
    
  }
  
  svm->alph[i1]=a1;
  svm->alph[i2]=a2;

  return 1;


}
