
# coding: utf-8

import sys
import os
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

__all__ =  ['Ackley','Sphere','Rosenbrock','Beale','GoldsteinPrice','Booth',
            'BukinN6','Matyas','LeviN13','ThreeHumpCamel','Easom','Eggholder',
            'McCormick','SchafferN2','SchafferN4','StyblinskiTang','DeJongsF1',
            'DeJongsF2','DeJongsF3','DeJongsF4','DeJongsF5','Ellipsoid','KTablet',
            'FiveWellPotential','WeightedSphere','HyperEllipsodic',
            'SumOfDifferentPower','Griewank','Michalewicz','Perm','Rastrigin',
            'Schwefel','SixHumpCamel','Shuberts','XinSheYang','Zakharov']

__oneArgument__ = ['Beale','GoldsteinPrice','Booth','BukinN6','Matyas','LeviN13',
                   'ThreeHumpCamel','Easom','Eggholder','McCormick','SchafferN2',
                   'SchafferN4','DeJongsF3','DeJongsF4','DeJongsF5',
                   'FiveWellPotential','SixHumpCamel','Shuberts']

__twoArgument__ = ['Ackley','Sphere','Rosenbrock','StyblinskiTang','DeJongsF1',
                   'DeJongsF2','Ellipsoid','KTablet','WeightedSphere',
                   'HyperEllipsodic','SumOfDifferentPower','Griewank',
                   'Michalewicz','Rastrigin','Schwefel','XinSheYang','Zakharov']

__threeArgument__ = ['Perm']

##### Basic function #####
class OptimalBasic:
    def __init__(self, variable_num):
        self.variable_num = variable_num
        self.max_search_range = np.array([0]*self.variable_num)
        self.min_search_range = np.array([0]*self.variable_num)
        self.optimal_solution = np.array([0]*self.variable_num)
        self.global_optimum_solution = 0
        self.plot_place = 0.25
        self.func_name = ''
        self.save_dir = os.path.dirname(os.path.abspath(__file__))+'\\img\\'
        if(os.path.isdir(self.save_dir) == False):
            os.mkdir(self.save_dir)
    def get_global_optimum_solution(self):
        return self.global_optimum_solution

    def get_optimal_solution(self):
        return self.optimal_solution

    def get_search_range(self):
        return [self.max_search_range, self.min_search_range]

    def get_func_val(self, variables):
        return -1

    def plot(self):
        x = np.arange(self.min_search_range[0],self.max_search_range[0], self.plot_place, dtype=np.float32)
        y = np.arange(self.min_search_range[1],self.max_search_range[1], self.plot_place, dtype=np.float32)
        X, Y = np.meshgrid(x,y)
        Z = []
        for xy_list in zip(X,Y):
            z = []
            for xy_input in zip(xy_list[0],xy_list[1]):
                tmp = list(xy_input)
                tmp.extend(list(self.optimal_solution[0:self.variable_num-2]))
                z.append(self.get_func_val(np.array(tmp)))
            Z.append(z)
        Z = np.array(Z)
        fig = plt.figure()
        ax = Axes3D(fig)
        ax.plot_wireframe(X,Y,Z)
        plt.show()

    def save_fig(self):
        x = np.arange(self.min_search_range[0],self.max_search_range[0], self.plot_place, dtype=np.float32)
        y = np.arange(self.min_search_range[1],self.max_search_range[1], self.plot_place, dtype=np.float32)
        X, Y = np.meshgrid(x,y)
        Z = []
        for xy_list in zip(X,Y):
            z = []
            for xy_input in zip(xy_list[0],xy_list[1]):
                tmp = list(xy_input)
                tmp.extend(list(self.optimal_solution[0:self.variable_num-2]))
                z.append(self.get_func_val(np.array(tmp)))
            Z.append(z)
        Z = np.array(Z)
        fig = plt.figure()
        ax = Axes3D(fig)
        ax.plot_wireframe(X,Y,Z)
        plt.savefig(self.save_dir+self.func_name+'.png')
        plt.close()

##### Optimization benchmark function group #####
##### Class Ackley function #####
class Ackley(OptimalBasic):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([32.768]*self.variable_num)
        self.min_search_range = np.array([-32.768]*self.variable_num)
        self.optimal_solution = np.array([0]*self.variable_num)
        self.global_optimum_solution = 0
        self.func_name = 'Ackley'

    def get_func_val(self, variables):
        tmp1 = 20.-20.*np.exp(-0.2*np.sqrt(1./self.variable_num*np.sum(np.square(variables))))
        tmp2 = np.e-np.exp(1./self.variable_num*np.sum(np.cos(variables*2.*np.pi)))
        return tmp1+tmp2

##### Class Sphere function #####
class Sphere(OptimalBasic):
    def __init__(self, variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([1000]*self.variable_num) # nearly inf
        self.min_search_range = np.array([-1000]*self.variable_num) # nearly inf
        self.optimal_solution = np.array([1]*self.variable_num)
        self.global_optimum_solution = 0
        self.plot_place = 10
        self.func_name = 'Sphere'

    def get_func_val(self, variables):
        return np.sum(np.square(variables))

##### Class Rosenbrock function #####
class Rosenbrock(OptimalBasic):
    def __init__(self, variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([5]*self.variable_num)
        self.min_search_range = np.array([-5]*self.variable_num)
        self.optimal_solution = np.array([1]*self.variable_num)
        self.global_optimum_solution = 0
        self.plot_place = 0.25
        self.func_name = 'Rosenbrock'

    def get_func_val(self, variables):
        f = 0
        for i in range(self.variable_num-1):
            f += 100*np.power(variables[i+1]-np.power(variables[i],2),2)+np.power(variables[i]-1,2)
        return f

##### Class Beale function #####
class Beale(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([4.5]*self.variable_num)
        self.min_search_range = np.array([-4.5]*self.variable_num)
        self.optimal_solution = np.array([3.,0.5])
        self.global_optimum_solution = 0
        self.plot_place = 0.25
        self.func_name = 'Beale'

    def get_func_val(self, variables):
        tmp1 = np.power(1.5 - variables[0] + variables[0] * variables[1],2)
        tmp2 = np.power(2.25 - variables[0] + variables[0] * np.power(variables[1],2),2)
        tmp3 = np.power(2.625 - variables[0] + variables[0] * np.power(variables[1],3),2)
        return tmp1+tmp2+tmp3

##### Class Goldstein-Price function #####
class GoldsteinPrice(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([2.]*self.variable_num)
        self.min_search_range = np.array([-2.]*self.variable_num)
        self.optimal_solution = np.array([0.,-1.])
        self.global_optimum_solution = 3
        self.plot_place = 0.25
        self.func_name = 'GoldsteinPrice'

    def get_func_val(self, variables):
        tmp1 = (1+np.power(variables[0]+variables[1]+1,2)*(19-14*variables[0]+3*np.power(variables[0],2)-14*variables[1]+6*variables[0]*variables[1]+3*np.power(variables[1],2)))
        tmp2 = (30+(np.power(2*variables[0]-3*variables[1],2)*(18-32*variables[0]+12*np.power(variables[0],2)+48*variables[1]-36*variables[0]*variables[1]+27*np.power(variables[1],2))))
        return tmp1*tmp2

##### Class Booth function #####
class Booth(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([10.]*self.variable_num)
        self.min_search_range = np.array([-10.]*self.variable_num)
        self.optimal_solution = np.array([1.,-3.])
        self.global_optimum_solution = 0
        self.func_name = 'Booth'

    def get_func_val(self, variables):
        tmp1 = np.power(variables[0]+2*variables[1]-7,2)
        tmp2 = np.power(2*variables[0]+variables[1]-5,2)
        return tmp1+tmp2

##### Class Bukin function N.6 #####
class BukinN6(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([-5.,3.])
        self.min_search_range = np.array([-15.,-3.])
        self.optimal_solution = np.array([-10.,1.])
        self.global_optimum_solution = 0
        self.func_name = 'BukinN6'

    def get_func_val(self, variables):
        tmp1 = 100*np.sqrt(np.absolute(variables[1]-0.01*np.power(variables[1],2)))
        tmp2 = 0.01*np.absolute(variables[0]+10)
        return tmp1+tmp2

##### Class Matyas function #####
class Matyas(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([10.]*self.variable_num)
        self.min_search_range = np.array([-10.]*self.variable_num)
        self.optimal_solution = np.array([0.,0.])
        self.global_optimum_solution = 0
        self.func_name = 'Matyas'

    def get_func_val(self, variables):
        tmp1 = 0.26*(np.power(variables[0],2)+np.power(variables[1],2))
        tmp2 = 0.48*variables[0]*variables[1]
        return tmp1-tmp2

##### Class Levi function N.13 #####
class LeviN13(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([10.]*self.variable_num)
        self.min_search_range = np.array([-10.]*self.variable_num)
        self.optimal_solution = np.array([1.,1.])
        self.global_optimum_solution = 0
        self.func_name = 'LeviN13'

    def get_func_val(self, variables):
        tmp1 = np.power(np.sin(3*np.pi*variables[0]),2)
        tmp2 = np.power(variables[0]-1,2)*(1+np.power(np.sin(3*np.pi*variables[1]),2))
        tmp3 = np.power(variables[1]-1,2)*(1+np.power(np.sin(2*np.pi*variables[1]),2))
        return tmp1+tmp2+tmp3

##### Class Three-hump camel function #####
class ThreeHumpCamel(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([5.]*self.variable_num)
        self.min_search_range = np.array([-5.]*self.variable_num)
        self.optimal_solution = np.array([0.,0.])
        self.global_optimum_solution = 0
        self.func_name = 'ThreeHumpCamel'

    def get_func_val(self, variables):
        return 2*np.power(variables[0],2)-1.05*np.power(variables[0],4)+np.power(variables[0],6)/6+variables[0]*variables[1]+np.power(variables[1],2)

##### Class Easom function #####
class Easom(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([100.]*self.variable_num)
        self.min_search_range = np.array([-100.]*self.variable_num)
        self.optimal_solution = np.array([np.pi,np.pi])
        self.global_optimum_solution = -1
        self.plot_place = 10
        self.func_name = 'Easom'

    def get_func_val(self, variables):
        return -1.0*np.cos(variables[0])*np.cos(variables[1])*np.exp(-(np.power(variables[0]-np.pi,2)+np.power(variables[1]-np.pi,2)))

##### Class Eggholder function #####
class Eggholder(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([512.]*self.variable_num)
        self.min_search_range = np.array([-512.]*self.variable_num)
        self.optimal_solution = np.array([512.,404.2319])
        self.global_optimum_solution = -959.6407
        self.plot_place = 5
        self.func_name = 'Eggholder'

    def get_func_val(self, variables):
        tmp1 = -(variables[1]+47)*np.sin(np.sqrt(np.absolute(variables[1]+variables[0]/2+47)))
        tmp2 = -variables[0]*np.sin(np.sqrt(np.absolute(variables[0]-(variables[1]+47))))
        return tmp1+tmp2

##### Class McCormick function #####
class McCormick(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([4.]*self.variable_num)
        self.min_search_range = np.array([-1.5,-3.])
        self.optimal_solution = np.array([-0.54719,-1.54719])
        self.global_optimum_solution = -1.9133
        self.func_name = 'McCormick'

    def get_func_val(self, variables):
        tmp1 = np.sin(variables[0]+variables[1])+np.power(variables[0]-variables[1],2)
        tmp2 = -1.5*variables[0]+2.5*variables[1]+1
        return tmp1+tmp2

##### Class Schaffer function N.2 #####
class SchafferN2(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([100.]*self.variable_num)
        self.min_search_range = np.array([-100]*self.variable_num)
        self.optimal_solution = np.array([0.,0.])
        self.global_optimum_solution = 0
        self.plot_place = 10
        self.func_name = 'SchafferN2'

    def get_func_val(self, variables):
        tmp1 = np.power(np.sin(np.power(variables[0],2)-np.power(variables[1],2)),2)-0.5
        tmp2 = np.power(1+0.001*(np.power(variables[0],2)+np.power(variables[1],2)),2)
        return 0.5+tmp1/tmp2

##### Class Schaffer function N.4 #####
class SchafferN4(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([100.]*self.variable_num)
        self.min_search_range = np.array([-100]*self.variable_num)
        self.optimal_solution = np.array([0.,1.25313])
        self.global_optimum_solution = 0
        self.plot_place = 10
        self.func_name = 'SchafferN4'

    def get_func_val(self, variables):
        tmp1 = np.power(np.cos(np.sin(np.absolute(np.power(variables[0],2)-np.power(variables[1],2)))),2)-0.5
        tmp2 = np.power(1+0.001*(np.power(variables[0],2)+np.power(variables[1],2)),2)
        return 0.5+tmp1/tmp2

##### Class Styblinski-Tang function #####
class StyblinskiTang(OptimalBasic):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([5.]*self.variable_num)
        self.min_search_range = np.array([-5.]*self.variable_num)
        self.optimal_solution = np.array([-2.903534]*self.variable_num)
        self.global_optimum_solution = -39.166165*self.variable_num
        self.func_name = 'StyblinskiTang'

    def get_func_val(self, variables):
        tmp1 = 0
        for i in range(self.variable_num):
        	tmp1 += np.power(variables[i],4)-16*np.power(variables[i],2)+5*variables[i]
        return tmp1/2

##### Class De Jong's function F1 #####
class DeJongsF1(Sphere):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.func_name = 'DeJongsF1'

##### Class De Jong's function F2 #####
class DeJongsF2(Rosenbrock):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.func_name = 'DeJongsF2'

##### Class De Jong's function F3 #####
class DeJongsF3(OptimalBasic):
    def __init__(self):
        super().__init__(5)
        self.max_search_range = np.array([5.12]*self.variable_num)
        self.min_search_range = np.array([-5.12]*self.variable_num)
        self.optimal_solution = np.array([-5.12]*self.variable_num)
        self.global_optimum_solution = 0
        self.func_name = 'DeJongsF3'

    def get_func_val(self, variables):
        tmp1 = 0
        for i in range(self.variable_num):
        	tmp1 += np.floor(variables[i])
        return tmp1

##### Class De Jong's function F4 #####
class DeJongsF4(OptimalBasic):
    def __init__(self):
        super().__init__(30)
        self.max_search_range = np.array([1.28]*self.variable_num)
        self.min_search_range = np.array([-1.28]*self.variable_num)
        self.optimal_solution = np.array([0.]*self.variable_num)
        self.global_optimum_solution = np.random.normal(0,1)
        self.func_name = 'DeJongsF4'

    def get_func_val(self, variables):
        tmp1 = 0
        for i in range(self.variable_num):
        	tmp1 += (i+1)*np.power(variables[i],4)
        return tmp1 + np.random.normal(0, 1)

##### Class De Jong's function F5 #####
class DeJongsF5(OptimalBasic):
    def __init__(self):
        super().__init__(25)
        self.max_search_range = np.array([65.536]*self.variable_num)
        self.min_search_range = np.array([-65.536]*self.variable_num)
        self.optimal_solution = np.array([-32.32]*self.variable_num)
        self.global_optimum_solution = 1.
        self.plot_place = 1.5
        self.func_name = 'DeJongsF5'

    def get_func_val(self, variables):
        A = np.zeros([2,25])
        a = [-32,16,0,16,32]
        A[0,:] = np.tile(a,(1,5))
        tmp = []
        for x in a:
            tmp_list = [x]*5
            tmp.extend(tmp_list)
        A[1,:] = tmp

        sum = 0
        for i in range(self.variable_num):
            a1i = A[0,i]
            a2i = A[1,i]
            term1 = i
            term2 = np.power(variables[0]-a1i,6)
            term3 = np.power(variables[1]-a2i,6)
            new = 1/(term1+term2+term3)
            sum += new
        return 1/(0.002+sum)

##### Class Ellipsoid function #####
class Ellipsoid(OptimalBasic):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([5.12]*self.variable_num)
        self.min_search_range = np.array([-5.12]*self.variable_num)
        self.optimal_solution = np.array([0.]*self.variable_num)
        self.global_optimum_solution = 0.
        self.func_name = 'Ellipsoid'

    def get_func_val(self, variables):
        tmp = 0
        for i in range(self.variable_num):
            tmp += np.power(np.power(1000,i/(self.variable_num-1))*variables[i],2)
        return tmp

##### Class k-tablet function #####
class KTablet(OptimalBasic):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([5.12]*self.variable_num)
        self.min_search_range = np.array([-5.12]*self.variable_num)
        self.optimal_solution = np.array([0.]*self.variable_num)
        self.global_optimum_solution = 0.
        self.func_name = 'KTablet'

    def get_func_val(self, variables):
        tmp = 0
        k = int(self.variable_num/4)
        for i in range(k):
            tmp += variables[i]

        for i in range(k,self.variable_num):
            tmp += np.power(100*variables[i],2)
        return tmp

##### Class Five-well potential function #####
# Not yet checked to do working properly
class FiveWellPotential(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([20.]*self.variable_num)
        self.min_search_range = np.array([-20.]*self.variable_num)
        self.optimal_solution = np.array([4.92,-9.89])
        self.global_optimum_solution = -1.4616
        self.plot_place = 1
        self.func_name = 'FiveWellPotential'

    def get_func_val(self, variables):
        tmp1 = []
        tmp1.append(1-1/(1+0.05*np.power(np.power(variables[0],2)+(variables[1]-10),2)))
        tmp1.append(-1/(1+0.05*(np.power(variables[0]-10,2)+np.power(variables[1],2))))
        tmp1.append(-1/(1+0.03*(np.power(variables[0]+10,2)+np.power(variables[1],2))))
        tmp1.append(-1/(1+0.05*(np.power(variables[0]-5,2)+np.power(variables[1]+10,2))))
        tmp1.append(-1/(1+0.1*(np.power(variables[0]+5,2)+np.power(variables[1]+10,2))))
        tmp1_sum = 0
        for x in tmp1:
            tmp1_sum += x
        tmp2 = 1+0.0001*np.power((np.power(variables[0],2)+np.power(variables[1],2)),1.2)
        return tmp1_sum*tmp2

##### Class Weighted Sphere function or hyper ellipsodic function #####
class WeightedSphere(OptimalBasic):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([5.12]*self.variable_num)
        self.min_search_range = np.array([-5.12]*self.variable_num)
        self.optimal_solution = np.array([0.]*self.variable_num)
        self.global_optimum_solution = 0.
        self.func_name = 'WeightedSphere'

    def get_func_val(self, variables):
        tmp = 0
        for i in range(self.variable_num):
            tmp += (i+1)*np.power(variables[i],2)
        return tmp

class HyperEllipsodic(WeightedSphere):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.func_name = 'HyperEllipsodic'

##### Class Sum of different power function #####
class SumOfDifferentPower(OptimalBasic):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([1.]*self.variable_num)
        self.min_search_range = np.array([-1.]*self.variable_num)
        self.optimal_solution = np.array([0.]*self.variable_num)
        self.global_optimum_solution = 0.
        self.func_name = 'SumOfDifferentPower'

    def get_func_val(self, variables):
        tmp = 0
        for i in range(self.variable_num):
            tmp += np.power(np.absolute(variables[i]),i+2)
        return tmp

##### Class Griewank function #####
class Griewank(OptimalBasic):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([600.]*self.variable_num)
        self.min_search_range = np.array([-600.]*self.variable_num)
        self.optimal_solution = np.array([0.]*self.variable_num)
        self.global_optimum_solution = 0.
        self.plot_place = 10.
        self.func_name = 'Griewank'

    def get_func_val(self, variables):
        tmp1 = 0
        tmp2 = 1
        for i in range(self.variable_num):
            tmp1 += np.power(variables[i],2)
            tmp2 = tmp2*np.cos(variables[i]/np.sqrt(i+1))
        return tmp1/4000-tmp2

##### Class Michalewicz function #####
class Michalewicz(OptimalBasic):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([np.pi]*self.variable_num)
        self.min_search_range = np.array([0.]*self.variable_num)
        self.optimal_solution = np.array([0.]*self.variable_num)
        self.global_optimum_solution = -1.8013 # In case of variable_num == 2
        self.plot_place = 0.1
        self.func_name = 'Michalewicz'

    def get_func_val(self, variables):
        m = 10
        tmp1 = 0
        for i in range(self.variable_num):
            tmp1 += np.sin(variables[i])*np.power(np.sin((i+1)*np.power(variables[i],2)/np.pi),2*m)
        return -tmp1

##### Class Perm function #####
class Perm(OptimalBasic):
    def __init__(self,variable_num,beta):
        super().__init__(variable_num)
        self.beta = beta
        self.max_search_range = np.array([1.]*self.variable_num)
        self.min_search_range = np.array([-1.]*self.variable_num)
        tmp = []
        for i in range(self.variable_num):
            tmp.append(1/(i+1))
        self.optimal_solution = np.array(tmp)
        self.global_optimum_solution = 0.
        self.plot_place = 0.1
        self.func_name = 'Perm'

    def get_func_val(self, variables):
        tmp1 = 0
        tmp2 = 0
        for j in range(self.variable_num):
            for i in range(self.variable_num):
                tmp1 += (i+1+self.beta)*(np.power(variables[i],j+1)-np.power(1/(i+1),j+1))
            tmp2 += np.power(tmp1,2)
            tmp1 = 0
        return tmp2

##### Class Rastrigin function #####
class Rastrigin(OptimalBasic):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([5.12]*self.variable_num)
        self.min_search_range = np.array([-5.12]*self.variable_num)
        self.optimal_solution = np.array([0.]*self.variable_num)
        self.global_optimum_solution = 0.
        self.func_name = 'Rastrigin'

    def get_func_val(self, variables):
        tmp1 = 10 * self.variable_num
        tmp2 = 0
        for i in range(self.variable_num):
            tmp2 += np.power(variables[i],2)-10*np.cos(2*np.pi*variables[i])
        return tmp1+tmp2

##### Class Schwefel function #####
class Schwefel(OptimalBasic):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([500.]*self.variable_num)
        self.min_search_range = np.array([-500.]*self.variable_num)
        self.optimal_solution = np.array([420.9687]*self.variable_num)
        self.global_optimum_solution = -418.9829
        self.plot_place = 10.
        self.func_name = 'Schwefel'

    def get_func_val(self, variables):
        tmp = 0
        for i in range(self.variable_num):
            tmp += variables[i]*np.sin(np.sqrt(np.absolute(variables[i])))
        return -tmp

##### Class Six-hump camel function #####
class SixHumpCamel(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([3.,2.])
        self.min_search_range = np.array([-3.,-2.])
        self.optimal_solution = np.array([-0.0898,0.7126])
        self.global_optimum_solution = -1.0316
        self.func_name = 'SixHumpCamel'

    def get_func_val(self, variables):
        return 4-2.1*np.power(variables[0],2)+1/3*np.power(variables[0],4)*np.power(variables[0],2)+variables[0]*variables[1]+4*(np.power(variables[1],2)-1)*np.power(variables[1],2)

##### Class Shuberts function #####
class Shuberts(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([1000.,10.]) # Set infinite as 1000 for x1
        self.min_search_range = np.array([-10.,-1000]) # Set infinite as -1000 for x2
        self.optimal_solution = np.array([0.,0.])
        self.global_optimum_solution = -186.7309
        self.plot_place = 10.
        self.func_name = 'Shuberts'

    def get_func_val(self, variables):
        n = 5
        tmp1 = 0
        tmp2 = 0
        for i in range(n):
            tmp1 += (i+1)*np.cos((i+1)+(i+2)*variables[0])
            tmp2 += (i+1)*np.cos((i+1)+(i+2)*variables[1])
        return tmp1*tmp2

##### Class Xin-She Yang function #####
class XinSheYang(OptimalBasic):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([2.*np.pi]*self.variable_num)
        self.min_search_range = np.array([-2.*np.pi]*self.variable_num)
        self.optimal_solution = np.array([0.]*self.variable_num)
        self.global_optimum_solution = 0.
        self.func_name = 'XinSheYang'

    def get_func_val(self, variables):
        tmp1 = 0
        tmp2 = 0
        for i in range(self.variable_num):
            tmp1 += np.absolute(variables[i])
            tmp2 += np.sin(np.power(variables[i],2))
        return tmp1*np.exp(-tmp2)

##### Class Zakharov function #####
class Zakharov(OptimalBasic):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([1000.]*self.variable_num) # temporarily set as 1000
        self.min_search_range = np.array([-1000]*self.variable_num) # temporarily set as -1000
        self.optimal_solution = np.array([0.]*self.variable_num)
        self.global_optimum_solution = 0.
        self.plot_place = 10.
        self.func_name = 'Zakharov'

    def get_func_val(self, variables):
        tmp1 = 0
        tmp2 = 0
        for i in range(self.variable_num):
            tmp1 += variables[i]
            tmp2 += (i+1)*variables[i]
        return tmp1+np.power(1/2*tmp2,2)+np.power(1/2*tmp2,4)


# coding: utf-8

import sys
import os
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

__all__ =  ['Ackley','Sphere','Rosenbrock','Beale','GoldsteinPrice','Booth',
            'BukinN6','Matyas','LeviN13','ThreeHumpCamel','Easom','Eggholder',
            'McCormick','SchafferN2','SchafferN4','StyblinskiTang','DeJongsF1',
            'DeJongsF2','DeJongsF3','DeJongsF4','DeJongsF5','Ellipsoid','KTablet',
            'FiveWellPotential','WeightedSphere','HyperEllipsodic',
            'SumOfDifferentPower','Griewank','Michalewicz','Perm','Rastrigin',
            'Schwefel','SixHumpCamel','Shuberts','XinSheYang','Zakharov']

__oneArgument__ = ['Beale','GoldsteinPrice','Booth','BukinN6','Matyas','LeviN13',
                   'ThreeHumpCamel','Easom','Eggholder','McCormick','SchafferN2',
                   'SchafferN4','DeJongsF3','DeJongsF4','DeJongsF5',
                   'FiveWellPotential','SixHumpCamel','Shuberts']

__twoArgument__ = ['Ackley','Sphere','Rosenbrock','StyblinskiTang','DeJongsF1',
                   'DeJongsF2','Ellipsoid','KTablet','WeightedSphere',
                   'HyperEllipsodic','SumOfDifferentPower','Griewank',
                   'Michalewicz','Rastrigin','Schwefel','XinSheYang','Zakharov']

__threeArgument__ = ['Perm']

##### Basic function #####
class OptimalBasic:
    def __init__(self, variable_num):
        self.variable_num = variable_num
        self.max_search_range = np.array([0]*self.variable_num)
        self.min_search_range = np.array([0]*self.variable_num)
        self.optimal_solution = np.array([0]*self.variable_num)
        self.global_optimum_solution = 0
        self.plot_place = 0.25
        self.func_name = ''
        self.save_dir = os.path.dirname(os.path.abspath(__file__))+'\\img\\'
        if(os.path.isdir(self.save_dir) == False):
            os.mkdir(self.save_dir)
    def get_global_optimum_solution(self):
        return self.global_optimum_solution

    def get_optimal_solution(self):
        return self.optimal_solution

    def get_search_range(self):
        return [self.max_search_range, self.min_search_range]

    def get_func_val(self, variables):
        return -1

    def plot(self):
        x = np.arange(self.min_search_range[0],self.max_search_range[0], self.plot_place, dtype=np.float32)
        y = np.arange(self.min_search_range[1],self.max_search_range[1], self.plot_place, dtype=np.float32)
        X, Y = np.meshgrid(x,y)
        Z = []
        for xy_list in zip(X,Y):
            z = []
            for xy_input in zip(xy_list[0],xy_list[1]):
                tmp = list(xy_input)
                tmp.extend(list(self.optimal_solution[0:self.variable_num-2]))
                z.append(self.get_func_val(np.array(tmp)))
            Z.append(z)
        Z = np.array(Z)
        fig = plt.figure()
        ax = Axes3D(fig)
        ax.plot_wireframe(X,Y,Z)
        plt.show()

    def save_fig(self):
        x = np.arange(self.min_search_range[0],self.max_search_range[0], self.plot_place, dtype=np.float32)
        y = np.arange(self.min_search_range[1],self.max_search_range[1], self.plot_place, dtype=np.float32)
        X, Y = np.meshgrid(x,y)
        Z = []
        for xy_list in zip(X,Y):
            z = []
            for xy_input in zip(xy_list[0],xy_list[1]):
                tmp = list(xy_input)
                tmp.extend(list(self.optimal_solution[0:self.variable_num-2]))
                z.append(self.get_func_val(np.array(tmp)))
            Z.append(z)
        Z = np.array(Z)
        fig = plt.figure()
        ax = Axes3D(fig)
        ax.plot_wireframe(X,Y,Z)
        plt.savefig(self.save_dir+self.func_name+'.png')
        plt.close()

##### Optimization benchmark function group #####
##### Class Ackley function #####
class Ackley(OptimalBasic):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([32.768]*self.variable_num)
        self.min_search_range = np.array([-32.768]*self.variable_num)
        self.optimal_solution = np.array([0]*self.variable_num)
        self.global_optimum_solution = 0
        self.func_name = 'Ackley'

    def get_func_val(self, variables):
        tmp1 = 20.-20.*np.exp(-0.2*np.sqrt(1./self.variable_num*np.sum(np.square(variables))))
        tmp2 = np.e-np.exp(1./self.variable_num*np.sum(np.cos(variables*2.*np.pi)))
        return tmp1+tmp2

##### Class Sphere function #####
class Sphere(OptimalBasic):
    def __init__(self, variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([1000]*self.variable_num) # nearly inf
        self.min_search_range = np.array([-1000]*self.variable_num) # nearly inf
        self.optimal_solution = np.array([1]*self.variable_num)
        self.global_optimum_solution = 0
        self.plot_place = 10
        self.func_name = 'Sphere'

    def get_func_val(self, variables):
        return np.sum(np.square(variables))

##### Class Rosenbrock function #####
class Rosenbrock(OptimalBasic):
    def __init__(self, variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([5]*self.variable_num)
        self.min_search_range = np.array([-5]*self.variable_num)
        self.optimal_solution = np.array([1]*self.variable_num)
        self.global_optimum_solution = 0
        self.plot_place = 0.25
        self.func_name = 'Rosenbrock'

    def get_func_val(self, variables):
        f = 0
        for i in range(self.variable_num-1):
            f += 100*np.power(variables[i+1]-np.power(variables[i],2),2)+np.power(variables[i]-1,2)
        return f

##### Class Beale function #####
class Beale(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([4.5]*self.variable_num)
        self.min_search_range = np.array([-4.5]*self.variable_num)
        self.optimal_solution = np.array([3.,0.5])
        self.global_optimum_solution = 0
        self.plot_place = 0.25
        self.func_name = 'Beale'

    def get_func_val(self, variables):
        tmp1 = np.power(1.5 - variables[0] + variables[0] * variables[1],2)
        tmp2 = np.power(2.25 - variables[0] + variables[0] * np.power(variables[1],2),2)
        tmp3 = np.power(2.625 - variables[0] + variables[0] * np.power(variables[1],3),2)
        return tmp1+tmp2+tmp3

##### Class Goldstein-Price function #####
class GoldsteinPrice(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([2.]*self.variable_num)
        self.min_search_range = np.array([-2.]*self.variable_num)
        self.optimal_solution = np.array([0.,-1.])
        self.global_optimum_solution = 3
        self.plot_place = 0.25
        self.func_name = 'GoldsteinPrice'

    def get_func_val(self, variables):
        tmp1 = (1+np.power(variables[0]+variables[1]+1,2)*(19-14*variables[0]+3*np.power(variables[0],2)-14*variables[1]+6*variables[0]*variables[1]+3*np.power(variables[1],2)))
        tmp2 = (30+(np.power(2*variables[0]-3*variables[1],2)*(18-32*variables[0]+12*np.power(variables[0],2)+48*variables[1]-36*variables[0]*variables[1]+27*np.power(variables[1],2))))
        return tmp1*tmp2

##### Class Booth function #####
class Booth(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([10.]*self.variable_num)
        self.min_search_range = np.array([-10.]*self.variable_num)
        self.optimal_solution = np.array([1.,-3.])
        self.global_optimum_solution = 0
        self.func_name = 'Booth'

    def get_func_val(self, variables):
        tmp1 = np.power(variables[0]+2*variables[1]-7,2)
        tmp2 = np.power(2*variables[0]+variables[1]-5,2)
        return tmp1+tmp2

##### Class Bukin function N.6 #####
class BukinN6(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([-5.,3.])
        self.min_search_range = np.array([-15.,-3.])
        self.optimal_solution = np.array([-10.,1.])
        self.global_optimum_solution = 0
        self.func_name = 'BukinN6'

    def get_func_val(self, variables):
        tmp1 = 100*np.sqrt(np.absolute(variables[1]-0.01*np.power(variables[1],2)))
        tmp2 = 0.01*np.absolute(variables[0]+10)
        return tmp1+tmp2

##### Class Matyas function #####
class Matyas(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([10.]*self.variable_num)
        self.min_search_range = np.array([-10.]*self.variable_num)
        self.optimal_solution = np.array([0.,0.])
        self.global_optimum_solution = 0
        self.func_name = 'Matyas'

    def get_func_val(self, variables):
        tmp1 = 0.26*(np.power(variables[0],2)+np.power(variables[1],2))
        tmp2 = 0.48*variables[0]*variables[1]
        return tmp1-tmp2

##### Class Levi function N.13 #####
class LeviN13(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([10.]*self.variable_num)
        self.min_search_range = np.array([-10.]*self.variable_num)
        self.optimal_solution = np.array([1.,1.])
        self.global_optimum_solution = 0
        self.func_name = 'LeviN13'

    def get_func_val(self, variables):
        tmp1 = np.power(np.sin(3*np.pi*variables[0]),2)
        tmp2 = np.power(variables[0]-1,2)*(1+np.power(np.sin(3*np.pi*variables[1]),2))
        tmp3 = np.power(variables[1]-1,2)*(1+np.power(np.sin(2*np.pi*variables[1]),2))
        return tmp1+tmp2+tmp3

##### Class Three-hump camel function #####
class ThreeHumpCamel(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([5.]*self.variable_num)
        self.min_search_range = np.array([-5.]*self.variable_num)
        self.optimal_solution = np.array([0.,0.])
        self.global_optimum_solution = 0
        self.func_name = 'ThreeHumpCamel'

    def get_func_val(self, variables):
        return 2*np.power(variables[0],2)-1.05*np.power(variables[0],4)+np.power(variables[0],6)/6+variables[0]*variables[1]+np.power(variables[1],2)

##### Class Easom function #####
class Easom(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([100.]*self.variable_num)
        self.min_search_range = np.array([-100.]*self.variable_num)
        self.optimal_solution = np.array([np.pi,np.pi])
        self.global_optimum_solution = -1
        self.plot_place = 10
        self.func_name = 'Easom'

    def get_func_val(self, variables):
        return -1.0*np.cos(variables[0])*np.cos(variables[1])*np.exp(-(np.power(variables[0]-np.pi,2)+np.power(variables[1]-np.pi,2)))

##### Class Eggholder function #####
class Eggholder(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([512.]*self.variable_num)
        self.min_search_range = np.array([-512.]*self.variable_num)
        self.optimal_solution = np.array([512.,404.2319])
        self.global_optimum_solution = -959.6407
        self.plot_place = 5
        self.func_name = 'Eggholder'

    def get_func_val(self, variables):
        tmp1 = -(variables[1]+47)*np.sin(np.sqrt(np.absolute(variables[1]+variables[0]/2+47)))
        tmp2 = -variables[0]*np.sin(np.sqrt(np.absolute(variables[0]-(variables[1]+47))))
        return tmp1+tmp2

##### Class McCormick function #####
class McCormick(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([4.]*self.variable_num)
        self.min_search_range = np.array([-1.5,-3.])
        self.optimal_solution = np.array([-0.54719,-1.54719])
        self.global_optimum_solution = -1.9133
        self.func_name = 'McCormick'

    def get_func_val(self, variables):
        tmp1 = np.sin(variables[0]+variables[1])+np.power(variables[0]-variables[1],2)
        tmp2 = -1.5*variables[0]+2.5*variables[1]+1
        return tmp1+tmp2

##### Class Schaffer function N.2 #####
class SchafferN2(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([100.]*self.variable_num)
        self.min_search_range = np.array([-100]*self.variable_num)
        self.optimal_solution = np.array([0.,0.])
        self.global_optimum_solution = 0
        self.plot_place = 10
        self.func_name = 'SchafferN2'

    def get_func_val(self, variables):
        tmp1 = np.power(np.sin(np.power(variables[0],2)-np.power(variables[1],2)),2)-0.5
        tmp2 = np.power(1+0.001*(np.power(variables[0],2)+np.power(variables[1],2)),2)
        return 0.5+tmp1/tmp2

##### Class Schaffer function N.4 #####
class SchafferN4(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([100.]*self.variable_num)
        self.min_search_range = np.array([-100]*self.variable_num)
        self.optimal_solution = np.array([0.,1.25313])
        self.global_optimum_solution = 0
        self.plot_place = 10
        self.func_name = 'SchafferN4'

    def get_func_val(self, variables):
        tmp1 = np.power(np.cos(np.sin(np.absolute(np.power(variables[0],2)-np.power(variables[1],2)))),2)-0.5
        tmp2 = np.power(1+0.001*(np.power(variables[0],2)+np.power(variables[1],2)),2)
        return 0.5+tmp1/tmp2

##### Class Styblinski-Tang function #####
class StyblinskiTang(OptimalBasic):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([5.]*self.variable_num)
        self.min_search_range = np.array([-5.]*self.variable_num)
        self.optimal_solution = np.array([-2.903534]*self.variable_num)
        self.global_optimum_solution = -39.166165*self.variable_num
        self.func_name = 'StyblinskiTang'

    def get_func_val(self, variables):
        tmp1 = 0
        for i in range(self.variable_num):
        	tmp1 += np.power(variables[i],4)-16*np.power(variables[i],2)+5*variables[i]
        return tmp1/2

##### Class De Jong's function F1 #####
class DeJongsF1(Sphere):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.func_name = 'DeJongsF1'

##### Class De Jong's function F2 #####
class DeJongsF2(Rosenbrock):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.func_name = 'DeJongsF2'

##### Class De Jong's function F3 #####
class DeJongsF3(OptimalBasic):
    def __init__(self):
        super().__init__(5)
        self.max_search_range = np.array([5.12]*self.variable_num)
        self.min_search_range = np.array([-5.12]*self.variable_num)
        self.optimal_solution = np.array([-5.12]*self.variable_num)
        self.global_optimum_solution = 0
        self.func_name = 'DeJongsF3'

    def get_func_val(self, variables):
        tmp1 = 0
        for i in range(self.variable_num):
        	tmp1 += np.floor(variables[i])
        return tmp1

##### Class De Jong's function F4 #####
class DeJongsF4(OptimalBasic):
    def __init__(self):
        super().__init__(30)
        self.max_search_range = np.array([1.28]*self.variable_num)
        self.min_search_range = np.array([-1.28]*self.variable_num)
        self.optimal_solution = np.array([0.]*self.variable_num)
        self.global_optimum_solution = np.random.normal(0,1)
        self.func_name = 'DeJongsF4'

    def get_func_val(self, variables):
        tmp1 = 0
        for i in range(self.variable_num):
        	tmp1 += (i+1)*np.power(variables[i],4)
        return tmp1 + np.random.normal(0, 1)

##### Class De Jong's function F5 #####
class DeJongsF5(OptimalBasic):
    def __init__(self):
        super().__init__(25)
        self.max_search_range = np.array([65.536]*self.variable_num)
        self.min_search_range = np.array([-65.536]*self.variable_num)
        self.optimal_solution = np.array([-32.32]*self.variable_num)
        self.global_optimum_solution = 1.
        self.plot_place = 1.5
        self.func_name = 'DeJongsF5'

    def get_func_val(self, variables):
        A = np.zeros([2,25])
        a = [-32,16,0,16,32]
        A[0,:] = np.tile(a,(1,5))
        tmp = []
        for x in a:
            tmp_list = [x]*5
            tmp.extend(tmp_list)
        A[1,:] = tmp

        sum = 0
        for i in range(self.variable_num):
            a1i = A[0,i]
            a2i = A[1,i]
            term1 = i
            term2 = np.power(variables[0]-a1i,6)
            term3 = np.power(variables[1]-a2i,6)
            new = 1/(term1+term2+term3)
            sum += new
        return 1/(0.002+sum)

##### Class Ellipsoid function #####
class Ellipsoid(OptimalBasic):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([5.12]*self.variable_num)
        self.min_search_range = np.array([-5.12]*self.variable_num)
        self.optimal_solution = np.array([0.]*self.variable_num)
        self.global_optimum_solution = 0.
        self.func_name = 'Ellipsoid'

    def get_func_val(self, variables):
        tmp = 0
        for i in range(self.variable_num):
            tmp += np.power(np.power(1000,i/(self.variable_num-1))*variables[i],2)
        return tmp

##### Class k-tablet function #####
class KTablet(OptimalBasic):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([5.12]*self.variable_num)
        self.min_search_range = np.array([-5.12]*self.variable_num)
        self.optimal_solution = np.array([0.]*self.variable_num)
        self.global_optimum_solution = 0.
        self.func_name = 'KTablet'

    def get_func_val(self, variables):
        tmp = 0
        k = int(self.variable_num/4)
        for i in range(k):
            tmp += variables[i]

        for i in range(k,self.variable_num):
            tmp += np.power(100*variables[i],2)
        return tmp

##### Class Five-well potential function #####
# Not yet checked to do working properly
class FiveWellPotential(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([20.]*self.variable_num)
        self.min_search_range = np.array([-20.]*self.variable_num)
        self.optimal_solution = np.array([4.92,-9.89])
        self.global_optimum_solution = -1.4616
        self.plot_place = 1
        self.func_name = 'FiveWellPotential'

    def get_func_val(self, variables):
        tmp1 = []
        tmp1.append(1-1/(1+0.05*np.power(np.power(variables[0],2)+(variables[1]-10),2)))
        tmp1.append(-1/(1+0.05*(np.power(variables[0]-10,2)+np.power(variables[1],2))))
        tmp1.append(-1/(1+0.03*(np.power(variables[0]+10,2)+np.power(variables[1],2))))
        tmp1.append(-1/(1+0.05*(np.power(variables[0]-5,2)+np.power(variables[1]+10,2))))
        tmp1.append(-1/(1+0.1*(np.power(variables[0]+5,2)+np.power(variables[1]+10,2))))
        tmp1_sum = 0
        for x in tmp1:
            tmp1_sum += x
        tmp2 = 1+0.0001*np.power((np.power(variables[0],2)+np.power(variables[1],2)),1.2)
        return tmp1_sum*tmp2

##### Class Weighted Sphere function or hyper ellipsodic function #####
class WeightedSphere(OptimalBasic):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([5.12]*self.variable_num)
        self.min_search_range = np.array([-5.12]*self.variable_num)
        self.optimal_solution = np.array([0.]*self.variable_num)
        self.global_optimum_solution = 0.
        self.func_name = 'WeightedSphere'

    def get_func_val(self, variables):
        tmp = 0
        for i in range(self.variable_num):
            tmp += (i+1)*np.power(variables[i],2)
        return tmp

class HyperEllipsodic(WeightedSphere):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.func_name = 'HyperEllipsodic'

##### Class Sum of different power function #####
class SumOfDifferentPower(OptimalBasic):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([1.]*self.variable_num)
        self.min_search_range = np.array([-1.]*self.variable_num)
        self.optimal_solution = np.array([0.]*self.variable_num)
        self.global_optimum_solution = 0.
        self.func_name = 'SumOfDifferentPower'

    def get_func_val(self, variables):
        tmp = 0
        for i in range(self.variable_num):
            tmp += np.power(np.absolute(variables[i]),i+2)
        return tmp

##### Class Griewank function #####
class Griewank(OptimalBasic):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([600.]*self.variable_num)
        self.min_search_range = np.array([-600.]*self.variable_num)
        self.optimal_solution = np.array([0.]*self.variable_num)
        self.global_optimum_solution = 0.
        self.plot_place = 10.
        self.func_name = 'Griewank'

    def get_func_val(self, variables):
        tmp1 = 0
        tmp2 = 1
        for i in range(self.variable_num):
            tmp1 += np.power(variables[i],2)
            tmp2 = tmp2*np.cos(variables[i]/np.sqrt(i+1))
        return tmp1/4000-tmp2

##### Class Michalewicz function #####
class Michalewicz(OptimalBasic):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([np.pi]*self.variable_num)
        self.min_search_range = np.array([0.]*self.variable_num)
        self.optimal_solution = np.array([0.]*self.variable_num)
        self.global_optimum_solution = -1.8013 # In case of variable_num == 2
        self.plot_place = 0.1
        self.func_name = 'Michalewicz'

    def get_func_val(self, variables):
        m = 10
        tmp1 = 0
        for i in range(self.variable_num):
            tmp1 += np.sin(variables[i])*np.power(np.sin((i+1)*np.power(variables[i],2)/np.pi),2*m)
        return -tmp1

##### Class Perm function #####
class Perm(OptimalBasic):
    def __init__(self,variable_num,beta):
        super().__init__(variable_num)
        self.beta = beta
        self.max_search_range = np.array([1.]*self.variable_num)
        self.min_search_range = np.array([-1.]*self.variable_num)
        tmp = []
        for i in range(self.variable_num):
            tmp.append(1/(i+1))
        self.optimal_solution = np.array(tmp)
        self.global_optimum_solution = 0.
        self.plot_place = 0.1
        self.func_name = 'Perm'

    def get_func_val(self, variables):
        tmp1 = 0
        tmp2 = 0
        for j in range(self.variable_num):
            for i in range(self.variable_num):
                tmp1 += (i+1+self.beta)*(np.power(variables[i],j+1)-np.power(1/(i+1),j+1))
            tmp2 += np.power(tmp1,2)
            tmp1 = 0
        return tmp2

##### Class Rastrigin function #####
class Rastrigin(OptimalBasic):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([5.12]*self.variable_num)
        self.min_search_range = np.array([-5.12]*self.variable_num)
        self.optimal_solution = np.array([0.]*self.variable_num)
        self.global_optimum_solution = 0.
        self.func_name = 'Rastrigin'

    def get_func_val(self, variables):
        tmp1 = 10 * self.variable_num
        tmp2 = 0
        for i in range(self.variable_num):
            tmp2 += np.power(variables[i],2)-10*np.cos(2*np.pi*variables[i])
        return tmp1+tmp2

##### Class Schwefel function #####
class Schwefel(OptimalBasic):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([500.]*self.variable_num)
        self.min_search_range = np.array([-500.]*self.variable_num)
        self.optimal_solution = np.array([420.9687]*self.variable_num)
        self.global_optimum_solution = -418.9829
        self.plot_place = 10.
        self.func_name = 'Schwefel'

    def get_func_val(self, variables):
        tmp = 0
        for i in range(self.variable_num):
            tmp += variables[i]*np.sin(np.sqrt(np.absolute(variables[i])))
        return -tmp

##### Class Six-hump camel function #####
class SixHumpCamel(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([3.,2.])
        self.min_search_range = np.array([-3.,-2.])
        self.optimal_solution = np.array([-0.0898,0.7126])
        self.global_optimum_solution = -1.0316
        self.func_name = 'SixHumpCamel'

    def get_func_val(self, variables):
        return 4-2.1*np.power(variables[0],2)+1/3*np.power(variables[0],4)*np.power(variables[0],2)+variables[0]*variables[1]+4*(np.power(variables[1],2)-1)*np.power(variables[1],2)

##### Class Shuberts function #####
class Shuberts(OptimalBasic):
    def __init__(self):
        super().__init__(2)
        self.max_search_range = np.array([1000.,10.]) # Set infinite as 1000 for x1
        self.min_search_range = np.array([-10.,-1000]) # Set infinite as -1000 for x2
        self.optimal_solution = np.array([0.,0.])
        self.global_optimum_solution = -186.7309
        self.plot_place = 10.
        self.func_name = 'Shuberts'

    def get_func_val(self, variables):
        n = 5
        tmp1 = 0
        tmp2 = 0
        for i in range(n):
            tmp1 += (i+1)*np.cos((i+1)+(i+2)*variables[0])
            tmp2 += (i+1)*np.cos((i+1)+(i+2)*variables[1])
        return tmp1*tmp2

##### Class Xin-She Yang function #####
class XinSheYang(OptimalBasic):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([2.*np.pi]*self.variable_num)
        self.min_search_range = np.array([-2.*np.pi]*self.variable_num)
        self.optimal_solution = np.array([0.]*self.variable_num)
        self.global_optimum_solution = 0.
        self.func_name = 'XinSheYang'

    def get_func_val(self, variables):
        tmp1 = 0
        tmp2 = 0
        for i in range(self.variable_num):
            tmp1 += np.absolute(variables[i])
            tmp2 += np.sin(np.power(variables[i],2))
        return tmp1*np.exp(-tmp2)

##### Class Zakharov function #####
class Zakharov(OptimalBasic):
    def __init__(self,variable_num):
        super().__init__(variable_num)
        self.max_search_range = np.array([1000.]*self.variable_num) # temporarily set as 1000
        self.min_search_range = np.array([-1000]*self.variable_num) # temporarily set as -1000
        self.optimal_solution = np.array([0.]*self.variable_num)
        self.global_optimum_solution = 0.
        self.plot_place = 10.
        self.func_name = 'Zakharov'

    def get_func_val(self, variables):
        tmp1 = 0
        tmp2 = 0
        for i in range(self.variable_num):
            tmp1 += variables[i]
            tmp2 += (i+1)*variables[i]
        return tmp1+np.power(1/2*tmp2,2)+np.power(1/2*tmp2,4)

# coding: utf-8 ##### Basic function ##### ##### Optimization benchmark function group ##### ##### Class Ackley function ##### ##### Class Sphere function ##### # nearly inf # nearly inf ##### Class Rosenbrock function ##### ##### Class Beale function ##### ##### Class Goldstein-Price function ##### ##### Class Booth function ##### ##### Class Bukin function N.6 ##### ##### Class Matyas function ##### ##### Class Levi function N.13 ##### ##### Class Three-hump camel function ##### ##### Class Easom function ##### ##### Class Eggholder function ##### ##### Class McCormick function ##### ##### Class Schaffer function N.2 ##### ##### Class Schaffer function N.4 ##### ##### Class Styblinski-Tang function ##### ##### Class De Jong's function F1 ##### ##### Class De Jong's function F2 ##### ##### Class De Jong's function F3 ##### ##### Class De Jong's function F4 ##### ##### Class De Jong's function F5 ##### ##### Class Ellipsoid function ##### ##### Class k-tablet function ##### ##### Class Five-well potential function ##### # Not yet checked to do working properly ##### Class Weighted Sphere function or hyper ellipsodic function ##### ##### Class Sum of different power function ##### ##### Class Griewank function ##### ##### Class Michalewicz function ##### # In case of variable_num == 2 ##### Class Perm function ##### ##### Class Rastrigin function ##### ##### Class Schwefel function ##### ##### Class Six-hump camel function ##### ##### Class Shuberts function ##### # Set infinite as 1000 for x1 # Set infinite as -1000 for x2 ##### Class Xin-She Yang function ##### ##### Class Zakharov function ##### # temporarily set as 1000 # temporarily set as -1000