def iteratedLocalSearch(kLimit = 4, MAX_ITERATION = 3):
	optimalCost = MAX_INT
	for j in range(MAX_ITERATION):
		found = False
		localOptimal = rep_solution.generateRandomSolution()
		size = len(localOptimal)
		localOptimalCost = f(localOptimal)
		initialCost = localOptimalCost
		generation = 0
		while found is False:
			newLimit = (ceil(kLimit - generation / size))
			if newLimit <= 2:
				newLimit = 2
			generation += 4

			neighbourhood = generateNeighbors(localOptimal, newLimit)
			neighbourhoodWidhts = getFitnessesOfNeighbours(neighbourhood)
			minNeighbour = min(neighbourhoodWidhts)
			if localOptimalCost <= minNeighbour:
				found = True
			else:
				localOptimal = neighbourhood[neighbourhoodWidhts.index(minNeighbour)]
				localOptimalCost = f(localOptimal)
		if localOptimalCost < optimalCost:
			optimalCost = localOptimalCost
	return format3Dec(optimalCost), format3Dec(initialCost), format3Dec(initialCost / optimalCost)
def tabuSearch(kLimit=4):
	MAX_ITER = 300
	TABU_TENURE = 7
	curIterNum = 0
	tanures = []
	tabuList = {}
	optimal = rep_solution.generateRandomSolution()
	size = len(optimal)
	initialCost = f(optimal)
	optimalCost = initialCost
	optimalCandidate = optimal
	tabuList[tuple(optimal)] = TABU_TENURE
	generation = 0    
	while curIterNum != MAX_ITER:
		newLimit = (ceil(kLimit - generation / size))
		if newLimit <= 2:
			newLimit = 2
		neighbourhood = generateNeighbors(optimalCandidate, newLimit)
		neighbourhood = random.sample(neighbourhood, len(neighbourhood))
		optimalCandidate = neighbourhood[0]

		for candidate in neighbourhood:
			if (tuple(candidate) not in tabuList) and (f(candidate) < f(optimalCandidate)):
				optimalCandidate = candidate
		
		candidateCost = f(optimalCandidate)
		if candidateCost < optimalCost:
			optimalCost = candidateCost
			optimal = optimalCandidate

		for key in tabuList.keys():
			tabuList[key] -= 1

		if tuple(optimalCandidate) not in tabuList:
			if len(tabuList) > 50:
				for i in range(10):
					del tabuList[min(tabuList, key=tabuList.get)]
			tabuList[tuple(optimalCandidate)] = TABU_TENURE
		generation += 4
		curIterNum += 1
	return format3Dec(optimalCost), format3Dec(initialCost), format3Dec(initialCost / optimalCost)
def getWorstIndexes(population, elitismLevel):
	if elitismLevel <= 0:
		return []
	worstList = [MIN_INT] * elitismLevel
	worstIndexes = [MIN_INT] * elitismLevel
	for i in range(len(population)):
		temp = f(population[i])        
		maximum = min(worstList)
		if temp > maximum:
			curInd = worstList.index(min(worstList))
			worstList[curInd] = temp
			worstIndexes[curInd] = i
	return worstIndexes
def getFittestIndexes(population, elitismLevel):
	if elitismLevel <= 0:
		return []
	fittestList = [MAX_INT] * elitismLevel
	fittestIndexes = [-1] * elitismLevel
	for i in range(len(population)):
		temp = f(population[i])        
		minimum = max(fittestList)
		if temp < minimum:
			curInd = fittestList.index(max(fittestList))
			fittestList[curInd] = temp
			fittestIndexes[curInd] = i
	return fittestIndexes
def bruteForce():
	problem = rep_solution.generateRandomSolution()
	size = len(problem)
	permutations = permutation_generator.generator(size)
	optimalCost = MAX_INT
	start = time.time()
	for permutation in permutations:
		arrangement = []
		for j in range(0, size):
			arrangement.append(problem[permutation[j]-1])
		result = f(arrangement)
		if result < optimalCost:
			optimalCost = result
	return format3Dec(optimalCost), "........", "...."
def getFittestIndividuals(population, populationPerm, elitismLevel):
	if elitismLevel <= 0:
		return []
	fittestList = [MAX_INT] * elitismLevel
	fittestSol = [None] * elitismLevel
	fittestSolPerm = [None] * elitismLevel
	for i in range(len(population)):
		temp = f(population[i])        
		minimum = max(fittestList)
		if temp < minimum:
			curInd = fittestList.index(max(fittestList))
			fittestList[curInd] = temp
			fittestSol[curInd] = deepcopy(population[i])
			fittestSolPerm[curInd] = deepcopy(populationPerm[i])
	return fittestSol, fittestSolPerm
def tournamentSelection(population, populationPerm, minimize=True, k = 2):
	selected = 0
	newPopulation = []
	newPopulationPerm = []
	
	while selected != len(population):
		indexes = random.sample(range(0, len(population)), k)
		minimum = MAX_INT
		minimumInd = -1
		for ind in indexes:
			temp = f(population[ind])
			if temp < minimum:
				minimum = temp
				minimumInd = ind
		newPopulation.append(population[minimumInd])
		newPopulationPerm.append(populationPerm[minimumInd])
		selected += 1
	population = newPopulation
	populationPerm = newPopulationPerm
def tournamentSelectionStoc(population, populationPerm, minimize=True, k = 2):
	selected = 0
	newPopulation = []
	newPopulationPerm = []
	magnitude = 0.95
	while selected != len(population):
		probability = random.uniform(0, 1)
		if probability <= magnitude:
			indexes = random.sample(range(0, len(population)), k)
			minimum = MAX_INT
			minimumInd = -1
			for ind in indexes:
				temp = f(population[ind])
				if temp < minimum:
					minimum = temp
					minimumInd = ind
			newPopulation.append(population[minimumInd])
			newPopulationPerm.append(populationPerm[minimumInd])
			magnitude = magnitude * (pow((1 - magnitude), selected))
			selected += 1
	population = newPopulation
	populationPerm = newPopulationPerm
def geneticAlgorithm(MAX_ITERATION, POPULATION_SIZE=100):
	'''Genetic Algorithm'''
	optimal = rep_solution.generateRandomSolution()
	optimalCost = f(optimal)
	initialCost = optimalCost
	size = len(optimal)
	initialPermutation = [i for i in range(1, size+1)]

	'''
	if size < 20:
		MAX_ITERATION = size * 3
	else:
		MAX_ITERATION = (size * 3) // 2
	'''

	if size > 100:
		POPULATION_SIZE = size
	else:
		# Set population size
		numUnique = countUniques(optimal)
		if numUnique < 5:
			POPULATION_SIZE = factorial(numUnique)
	elitismLevel = POPULATION_SIZE // 2
	
	# Create initial population
	population, populationPerm = initialPopulation(optimal, initialPermutation, POPULATION_SIZE)
	for j in range(MAX_ITERATION):
		tournamentSelection(population, populationPerm, POPULATION_SIZE, k=2)
		shuffleMatingPool(population, populationPerm)
		fittestParents, fittestParentsPerm = getFittestIndividuals(population, populationPerm, elitismLevel)
		crossover(population, populationPerm)
		mutation(population, populationPerm, j, MAX_ITERATION)
		survivorSelection(population, populationPerm, fittestParents, fittestParentsPerm, elitismLevel)
		minimum = min(getFitnessesOfNeighbours(population))
		if minimum < optimalCost:
			optimalCost = minimum
	return format3Dec(optimalCost), format3Dec(initialCost), format3Dec(initialCost / optimalCost)
def getFitnessesOfNeighbours(neighbourhood):
	result = list()
	for neighbour in neighbourhood:
		result.append(f(neighbour))
	return result
def stochasticVNS():
	solution = rep_solution.initialSolution()
	solutionCost = f(solution)
	optimal, optimalCost = solution[:], solutionCost
	initialCost = solutionCost
	stop = False
	i = 0 # number of succesive returns
	threshold = 2000
	tabuDelta = 7 
	tabuCosts = [] 
	tabuCosts.append(solutionCost)
	uniformDelta = 1
	c = 0
	flagC = True
	tour = 2.25
	tourCoef = 3
	while stop is False:
		i += 1
		c += 1
		if i % (threshold // 5) == 0:
			c = 1
			if flagC is False:
				solution = optimal
				solutionCost = optimalCost
				tabuCosts = []
				tour = 2.25
			else:
				tour = 0.125
			flagC = not flagC
		k = 1
		allNeighbors = []
		allNeighborsCosts = []
		costAllNeighbors = 0
		while k <= len(solution) // 2:
			neighbors = generateNeighbors(solution, k)
			bestNeighborCost = MAX_INT
			bestNeighborIndex = -1          
			for j in range(len(neighbors)):
				allNeighbors.append(neighbors[j])
				temp = f(neighbors[j])
				allNeighborsCosts.append(1 / temp)
				costAllNeighbors += 1 / temp
				if temp < bestNeighborCost:
					bestNeighborCost = temp
					bestNeighborIndex = j
			if bestNeighborCost < solutionCost:
				solution = neighbors[bestNeighborIndex]
				solutionCost = bestNeighborCost
				if solutionCost < optimalCost:
					optimal = solution
					optimalCost = solutionCost
					uniformDelta = 1
				break
			else: 
				k += 1
		if k > len(solution) // 2: 
			probSol = (1 / solutionCost) / ((1 / solutionCost) + costAllNeighbors)
			precision = pow(10, len(re.search('\d+\.(0*)', str(probSol)).group(1)))
			probNeighbors = neighborsProbs(allNeighborsCosts, (1 / solutionCost) + costAllNeighbors)            
			if i > threshold:
				stop = True
			else:
				newFlag = False
				probNeighbors.insert(0, probSol)
				while newFlag is False:
					if len(tabuCosts) >= len(allNeighborsCosts) * tabuDelta:
						tabuCosts = []
						break
					else:
						temp = tabuCosts[:]
						for ind in range(len(temp)):
							temp[ind] = 1 / temp[ind]
						if (list(set(allNeighborsCosts) - set(temp))) == []:
							tabuCosts = []
							ind = random.randint(len(allNeighbors)*1//3, len(allNeighbors)*2//3)
							solution = allNeighbors[ind]
							solutionCost = f(allNeighbors[ind])
							break
					probMagnitude = random.uniform(0, uniformDelta)
					sumMagnitude = 0
					for j in range(len(probNeighbors)):
						sumMagnitude += probNeighbors[j] * (precision+1 - (precision / threshold) * c)
						if probMagnitude < sumMagnitude:
							if j != 0:
								tempcost = f(allNeighbors[j-1])
								if tempcost not in tabuCosts:
									solution = allNeighbors[j-1]
									solutionCost = tempcost
									tabuCosts.append(tempcost)
									newFlag = True
									uniformDelta =  1+(precision / (threshold*tour)) * c
									break
								elif tabuDelta != 2:
									tabuDelta -= 1
	return format3Dec(optimalCost), format3Dec(initialCost), format3Dec(initialCost / optimalCost)
def simulatedAnnealing():
	'''
		Vc = current solution
		Vn = new solution
		T = temperature
	'''
	Tmax = 100
	Tmin = pow(10, -8)
	iterMax = 5000
	T = Tmax
	coolingRatio = 0.98
	i = 1
	Vc = rep_solution.generateRandomSolution()
	Vc_cost = f(Vc)
	optimal = Vc
	optimalCost = Vc_cost
	initialCost = Vc_cost
	trialForOptimum = 0
	kLimit = len(optimal)//2+1
	while i <= iterMax or T > Tmin:
		if trialForOptimum >= iterMax // 3:
			trialForOptimum = 0
			restart = True
		if T <= Tmin and restart is True:
			Vc = rep_solution.generateRandomSolution()   
			Vc_cost = f(Vc)
			T = Tmax
		
		neighbors = generateNeighbors(Vc, kLimit)
		if T <= Tmin and restart is False:
			allNeighborsCosts = getFitnessesOfNeighbours(neighbors)
			bestCost = MAX_INT
			for j in range(len(allNeighborsCosts)):
				if bestCost > allNeighborsCosts[j]:
					bestCost = allNeighborsCosts[j]
				Vn = neighbors[allNeighborsCosts.index(bestCost)]
				Vn_cost = bestCost
		else:
			restart = False
			ind = random.randint(0, len(neighbors)-1)
			Vn = neighbors[ind]
			Vn_cost = f(Vn)
		deltaCost = Vn_cost - Vc_cost 
		# Operation on cooled state.
		if deltaCost < 0: 
			Vc = Vn
			Vc_cost = Vn_cost
			if Vc_cost < optimalCost:
				optimal = Vn
				optimalCost = Vn_cost
				trialForOptimum = 0
		elif deltaCost / T < 1 and random.random() >= pow(e, deltaCost / T):
			Vc = Vn
			Vc_cost = Vn_cost
			if Vc_cost < optimalCost:
				optimal = Vn
				optimalCost = Vn_cost
				trialForOptimum = 0
		elif T <= Tmin and restart is False:
			Vc = rep_solution.generateRandomSolution()
			Vc_cost = f(Vc)
		T *= coolingRatio
		i += 1
		trialForOptimum += 1
		if i == iterMax:
			break
	return format3Dec(optimalCost), format3Dec(initialCost), format3Dec(initialCost / optimalCost)