
import numpy as np
from scipy import spatial


def function_for_TSP(num_points, seed=None):
    if seed:
        np.random.seed(seed=seed)

    # generate coordinate of points randomly
    points_coordinate = np.random.rand(num_points, 2)
    distance_matrix = spatial.distance.cdist(
        points_coordinate, points_coordinate, metric='euclidean')

    # print('distance_matrix is: \n', distance_matrix)

    def cal_total_distance(routine):
        num_points, = routine.shape
        return sum([distance_matrix[routine[i % num_points], routine[(i + 1) % num_points]] for i in range(num_points)])

    return num_points, points_coordinate, distance_matrix, cal_total_distance


def sphere(p):
    out_put = 0
    for i in p:
        out_put += i ** 2
    return out_put


def schaffer(p):
    '''
    This function has plenty of local minimum, with strong shocks
    global minimum at (0,0) with value 0
    https://en.wikipedia.org/wiki/Test_functions_for_optimization
    '''
    x1, x2 = p
    part1 = np.square(x1) - np.square(x2)
    part2 = np.square(x1) + np.square(x2)
    return 0.5 + (np.square(np.sin(part1)) - 0.5) / np.square(1 + 0.001 * part2)


def shubert(p):
    '''
    2-dimension
    -10<=x1,x2<=10
    has 760 local minimas, 18 of which are global minimas with -186.7309
    '''
    x, y = p
    part1 = [i * np.cos((i + 1) * x + i) for i in range(1, 6)]
    part2 = [i * np.cos((i + 1) * y + i) for i in range(1, 6)]
    return np.sum(part1) * np.sum(part2)


def griewank(p):
    part1 = [np.square(x) / 4000 for x in p]
    part2 = [np.cos(x / np.sqrt(i + 1)) for i, x in enumerate(p)]
    return np.sum(part1) - np.prod(part2) + 1


def rastrigrin(p):
    return np.sum([np.square(x) - 10 * np.cos(2 * np.pi * x) + 10 for x in p])


def rosenbrock(p):
    n_dim = len(p)
    res = 0
    for i in range(n_dim - 1):
        res += 100 * \
               np.square(np.square(p[i]) - p[i + 1]) + np.square(p[i] - 1)
    return res


def sixhumpcamel(p):
    x, y = p
    return 4 * np.square(x) + x * y - 4 * np.square(y) - 2.1 * np.power(x, 4) + 4 * np.power(y, 4) + 1 / 3 * np.power(x,
                                                                                                                      6)


def zakharov(p):
    temp2 = [0.5 * i * x for i, x in enumerate(p)]
    part2 = np.sum(temp2)

    temp1 = [np.square(x) for x in p]
    part1 = np.sum(temp1)
    return part1 + part2 ** 2 + part2 ** 4


def ackley(p):
    x, y = p
    return -200 * np.exp(-0.02 * np.sqrt(np.square(x)) + np.square(y))


def cigar(p):
    x = p
    return np.square(float(x[0])) + np.power(10.0, 6) * sphere(x[1:])
