#!/usr/bin/env python

from ism_pkg.kernels.gaussian import gaussian
from ism_pkg.optimizer.full_ism import full_ism
from ism_pkg.optimizer.stochastic_ism import stochastic_ism
from ism_pkg.tools.HSIC_IDS_optimizer import HSIC_IDS_optimizer
from ism_pkg.tools.terminal_print import *
from ism_pkg.tools.rff_layer import *

from sklearn.preprocessing import LabelEncoder
from sklearn.metrics import accuracy_score
from sklearn.svm import SVC
from sklearn.linear_model import SGDClassifier
from sklearn.kernel_approximation import RBFSampler
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline

class ism():
	def __init__(self, stochastic=False, var_percentage=0.9, debug_mode=True, max_repeat=200, batch_size_per_class=10): 
		self.db = {}

		self.db['stochastic'] = stochastic
		self.db['batch_size_per_class'] = batch_size_per_class
		self.db['max_repeat'] = max_repeat
		self.db['var_percentage'] = var_percentage
		self.db['debug_mode'] = debug_mode
		self.db['convergence_method'] = 'use_eigen_values'	# use_eigen_values is faster but gradient might not = 0 and use_W is slower but more accurate with gradient = 0
		self.db['kernel'] = gaussian(self.db)				# try : gaussian, polynomial, squared, linear
		self.rff = None

		if stochastic: self.db['optimizer'] = stochastic_ism(self.db)
		else: self.db['optimizer'] = full_ism(self.db)
		print('stochastic:%s, class batch size:%d'%(self.db['stochastic'], self.db['batch_size_per_class']))

	def __del__(self):
		pass
		#del self.db['kernel']
		#del self.db['optimizer']
		#self.db.clear()
	
	def fit(self, X, Y, use_RFF=False):
		db = self.db
		Optimizer = db['optimizer']
		Optimizer.initialize(X,Y)

		Optimizer.update_f(X, Y)

		self.W = self.db['W']
		self.σ = self.db['kernel'].σ

		if use_RFF: self.rff = rff_layer(X@self.W, self.σ)

	def predict(self, X, RFF_out=False):
		if RFF_out:
			return self.rff.apply_layer(X)
		else:
			return [X@self.W, self.σ]

	def classify_fit(self, X, Y, σ, W=None, classifier_type='HSIC_optimizer', class_batch_size=200, n_components=600):	#classifier_type='HSIC_optimizer', or 'svm'
		print('\nClassifier: %s'%classifier_type)
		if W is not None: X = X@W
	
		self.classifier_type = classifier_type
		γ = 1/(2*σ*2)	

		if classifier_type == 'svm':
			svm = SVC(gamma=γ)
			svm.fit(X,Y)
			self.classifier = svm	

		if classifier_type == 'svm_stochastic':
			self.ℱₓ = RBFSampler(gamma=γ, random_state=1, n_components=n_components)
			Φx = self.ℱₓ.fit_transform(X)

			clf = SGDClassifier(max_iter=5000, tol=1e-3, verbose=False)
			clf.fit(Φx, Y)
			self.classifier = clf

		if classifier_type == 'HSIC_optimizer':
			Ƕ = HSIC_IDS_optimizer(σ, self.db['stochastic'], class_batch_size)
			Ƕ.fit(X, Y)
			self.classifier = Ƕ

			return self.classifier

	def classify_predict(self, X, Y, W=None):	
		if W is not None: X = X@W
		#if self.rff is not None: X = self.predict(X, RFF_out=True)

		if self.classifier_type == 'svm_stochastic':
			X = self.ℱₓ.fit_transform(X)
				
		Cf = self.classifier
		Ŷ = Cf.predict(X)

		Ŷ = LabelEncoder().fit_transform(Ŷ)
		Y = LabelEncoder().fit_transform(Y)
		ᘔ = accuracy_score(Y, Ŷ)
		return [Ŷ, ᘔ]
	



#!/usr/bin/env python

from ism_pkg.kernels.gaussian import gaussian
from ism_pkg.optimizer.full_ism import full_ism
from ism_pkg.optimizer.stochastic_ism import stochastic_ism
from ism_pkg.tools.HSIC_IDS_optimizer import HSIC_IDS_optimizer
from ism_pkg.tools.terminal_print import *
from ism_pkg.tools.rff_layer import *

from sklearn.preprocessing import LabelEncoder
from sklearn.metrics import accuracy_score
from sklearn.svm import SVC
from sklearn.linear_model import SGDClassifier
from sklearn.kernel_approximation import RBFSampler
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline

class ism():
	def __init__(self, stochastic=False, var_percentage=0.9, debug_mode=True, max_repeat=200, batch_size_per_class=10): 
		self.db = {}

		self.db['stochastic'] = stochastic
		self.db['batch_size_per_class'] = batch_size_per_class
		self.db['max_repeat'] = max_repeat
		self.db['var_percentage'] = var_percentage
		self.db['debug_mode'] = debug_mode
		self.db['convergence_method'] = 'use_eigen_values'	# use_eigen_values is faster but gradient might not = 0 and use_W is slower but more accurate with gradient = 0
		self.db['kernel'] = gaussian(self.db)				# try : gaussian, polynomial, squared, linear
		self.rff = None

		if stochastic: self.db['optimizer'] = stochastic_ism(self.db)
		else: self.db['optimizer'] = full_ism(self.db)
		print('stochastic:%s, class batch size:%d'%(self.db['stochastic'], self.db['batch_size_per_class']))

	def __del__(self):
		pass
		#del self.db['kernel']
		#del self.db['optimizer']
		#self.db.clear()
	
	def fit(self, X, Y, use_RFF=False):
		db = self.db
		Optimizer = db['optimizer']
		Optimizer.initialize(X,Y)

		Optimizer.update_f(X, Y)

		self.W = self.db['W']
		self.σ = self.db['kernel'].σ

		if use_RFF: self.rff = rff_layer(X@self.W, self.σ)

	def predict(self, X, RFF_out=False):
		if RFF_out:
			return self.rff.apply_layer(X)
		else:
			return [X@self.W, self.σ]

	def classify_fit(self, X, Y, σ, W=None, classifier_type='HSIC_optimizer', class_batch_size=200, n_components=600):	#classifier_type='HSIC_optimizer', or 'svm'
		print('\nClassifier: %s'%classifier_type)
		if W is not None: X = X@W
	
		self.classifier_type = classifier_type
		γ = 1/(2*σ*2)	

		if classifier_type == 'svm':
			svm = SVC(gamma=γ)
			svm.fit(X,Y)
			self.classifier = svm	

		if classifier_type == 'svm_stochastic':
			self.ℱₓ = RBFSampler(gamma=γ, random_state=1, n_components=n_components)
			Φx = self.ℱₓ.fit_transform(X)

			clf = SGDClassifier(max_iter=5000, tol=1e-3, verbose=False)
			clf.fit(Φx, Y)
			self.classifier = clf

		if classifier_type == 'HSIC_optimizer':
			Ƕ = HSIC_IDS_optimizer(σ, self.db['stochastic'], class_batch_size)
			Ƕ.fit(X, Y)
			self.classifier = Ƕ

			return self.classifier

	def classify_predict(self, X, Y, W=None):	
		if W is not None: X = X@W
		#if self.rff is not None: X = self.predict(X, RFF_out=True)

		if self.classifier_type == 'svm_stochastic':
			X = self.ℱₓ.fit_transform(X)
				
		Cf = self.classifier
		Ŷ = Cf.predict(X)

		Ŷ = LabelEncoder().fit_transform(Ŷ)
		Y = LabelEncoder().fit_transform(Y)
		ᘔ = accuracy_score(Y, Ŷ)
		return [Ŷ, ᘔ]
	



#!/usr/bin/env python # use_eigen_values is faster but gradient might not = 0 and use_W is slower but more accurate with gradient = 0 # try : gaussian, polynomial, squared, linear #del self.db['kernel'] #del self.db['optimizer'] #self.db.clear() #classifier_type='HSIC_optimizer', or 'svm' #if self.rff is not None: X = self.predict(X, RFF_out=True)