/** 
* @file KCGPOneClass.cpp
* @brief One-Class Gaussian Process Regression for Classification
* @author Erik Rodner + Mi.Ke.
* @date 12/03/2010

*/
#include <iostream>
#include <typeinfo>

#include "KCMinimumEnclosingBall.h"

using namespace std;
using namespace NICE;
using namespace OBJREC;


KCMinimumEnclosingBall::KCMinimumEnclosingBall( const Config *conf, Kernel *kernel, const string & section ) 
	: KernelClassifier ( conf, kernel )
{
	//specify parameters, if any
	nu = conf->gD(section,"outlier_fraction");
	fprintf(stderr,"\nUsing (upper bound) outlier fraction of nu=%f (upper bound is so far guaranteed only for stationary kernels)\n",nu);
	if(nu<0 || nu>1){
		fprintf(stderr,"ERROR: Outlier fraction 'nu' (outlier_fraction) specifies must be in [0,1] !!!\n\n");
		exit(-1);
	}
}

KCMinimumEnclosingBall::KCMinimumEnclosingBall( const KCMinimumEnclosingBall & src ) : KernelClassifier ( src )
{
	this->alpha=src.alpha;
	this->radius=src.radius;
	this->aKa=src.aKa;
	this->nu=src.nu;
	this->trainingSize=src.trainingSize;
	this->TwoK=src.TwoK;
	this->Eye=src.Eye;
	this->Ones=src.Ones;
	this->ones=src.ones;
	this->b=src.b;
	this->minusDiagK=src.minusDiagK;
	this->minusOne=src.minusOne;
	this->zeros=src.zeros;
}

KCMinimumEnclosingBall::~KCMinimumEnclosingBall()
{
}


void KCMinimumEnclosingBall::teach ( KernelData *kernelData, const NICE::Vector & y )
{
	NICE::Matrix kernelMatrix = kernelData->getKernelMatrix();
	trainingSize=kernelMatrix.rows();
	int n=trainingSize;

	if(nu==0.0){ //no outlier, i.e. hard minimum enclosing ball algorithm
		TwoK.resize(n,n);
		Eye.resize(n,n);
		minusDiagK.resize(n);
		Ones.resize(n,1);
		minusOne.resize(1);
		zeros.resize(n);
		alpha.resize(n);
		for(int i=0;i<n;i++){
			for(int j=0;j<n;j++){
				Eye[i][j]= ( i==j ? 1 : 0 );
				TwoK[i][j]=2*kernelMatrix(i,j);
			}
			minusDiagK[i]=-kernelMatrix(i,i);
			Ones[i][0]=1;
			zeros[i]=0;
		}
		minusOne[0]=-1;
	}else{	//soft MEB problem (more complicated since we must introduce further inequalities)
		TwoK.resize(n,n);
		Eye.resize(n,2*n);	// instead of Id, we also insert -Id (to the right of Id)
		minusDiagK.resize(n);
		Ones.resize(n,1);
		minusOne.resize(1);
		zeros.resize(2*n);	// instead of zeros we also insert 1/(v*n)
		alpha.resize(n);
		for(int i=0;i<n;i++){
			for(int j=0;j<n;j++){
				Eye[i][j]= ( i==j ? 1 : 0 );
				Eye[i][n+j]= ( i==j ? -1 : 0 );
				TwoK[i][j]=2*kernelMatrix(i,j);
			}
			minusDiagK[i]=-kernelMatrix(i,i);
			Ones[i][0]=1;
			zeros[i]=0;
			zeros[n+i]=1.0/(nu*double(n));
		}
		minusOne[0]=-1;

	}

	QuadProgPP::solve_quadprog(TwoK, minusDiagK, Ones, minusOne, Eye, zeros, alpha);
	radius=0.0;
	aKa=0.0;
	double rad2=-1e+100,tmp;
	for(int i=0;i<n;i++){
		tmp=0.0;
		for(int j=0;j<n;j++){
			aKa+=0.5*alpha[i]*TwoK[i][j]*alpha[j];
			tmp+=-TwoK[i][j]*alpha[j];
		}
		if( (alpha[i]>1e-10) && (alpha[i]<1/(n*nu)+1e-10) ){
			tmp+=0.5*TwoK[i][i];
			rad2=(rad2>tmp ? rad2 : tmp);
		}
		//fprintf(stderr,"alpha_%d=%f\t 1/vl=%f\t 0>alpha<1/vl? %d => R_%d=aKa+%f\n",i,alpha[i],1.0/(nu*n),((alpha[i]>1e-10)&&alpha[i]<1/(n*nu)+1e-10),i,tmp);
	}
	radius=aKa+rad2;
	//fprintf(stderr,"\nfinal radius R=aKa+%f\n",rad2);
}
	
ClassificationResult KCMinimumEnclosingBall::classifyKernel ( const NICE::Vector & kernelVector, double kernelSelf ) const
{
	FullVector scores ( 2 );
	scores[0] = 0.0;
	double rest = 0.0;
	for(int i=0;i<trainingSize;i++){
		rest+=-2*kernelVector[i]*alpha[i];
	}
	rest=rest+kernelSelf;
	double inlierness = radius - (aKa + rest);
	scores[1] = inlierness;
	ClassificationResult r ( inlierness<0 ? 0 : 1, scores );

	return r;
}

KCMinimumEnclosingBall *KCMinimumEnclosingBall::clone() const
{
	return new KCMinimumEnclosingBall ( *this );
}

