Path: blob/main/Homework/Lesson 07 HW - Global Optimization 2/GAUtilities.py
870 views
import importlib1import numpy as np23def computeFitness(f, pop, **kwargs):4'''5Computes fitness based on passed in function.67Parameters:8f (function, required): the function used to evaluate fitness9pop (numpy array, required): the population on which to evaluate fitness - individuals in rows.10**kwargs (named arguments, optional): additional arguments that will be passed through to the fitness function1112Returns a numpy array of computed fitnesses.13'''14#get sizes from pop15pop_size, ind_size = pop.shape[0], pop.shape[1]1617#create the fitness array of zeros18fitness = np.zeros(pop_size)19#fill the fitness array20for j in range(pop_size):21fitness[j] = f(pop[j], **kwargs)2223return fitness2425# Sort population26def sortPop(pop, fitness):27'''28Sorts a population with minimal fitness in first place.2930Parameters:31pop (numpy array, required): The population to sort, individuals in rows32fitness (numpy array, required): The values used to sort the population3334Returns:35Sorted numpy array of population36'''37#sort by increasing fitness38sorted_pos = fitness.argsort()39fitness = fitness[sorted_pos]40pop = pop[sorted_pos]41return pop.copy()4243####################################44# Selection operators45####################################4647# tournament selection48def tournamentSelection(pop, tourn_size, debug=False):49'''50Implements tournameent selection on a population.5152Parameters:53pop (numpy array, required): The sorted population from which selections will be drawn.54tourn_size (between 2 and population size, required): The number of individuals that will compete in each tournament55debug (boolean, optional, default=False): Flag to indicate whether to output debugging print statements.5657Returns:58Numpy Array of the selected population.5960'''61#get the population size62pop_size, ind_size = pop.shape[0], pop.shape[1]6364# initialize selected population65select_pop = np.zeros((pop_size,ind_size))66for j in range(pop_size):67subset_pos = np.random.choice(pop_size,tourn_size,replace=False) # select without replacement68smallest_pos = np.min(subset_pos) # choose index corresponding to lowest fitness69if debug:70print('Individuals in tournament:', subset_pos)71print('Individual selected:', smallest_pos)72select_pop[j] = pop[smallest_pos]73return select_pop7475####################################76# Crossover operators77####################################7879def orderedCrossover(pop, cx_prob, debug=False):80'''81Peforms ordered crossover on permutation populations.8283Parameters:84pop (numpy array of permutations, required): The population of permutations, individuals as rows85cx_prob (real between 0 and 1, required): The probability that any two individuals will mate86debug (boolean, optional, default=False): Flag to indicate whether to output debugging print statements.8788Returns:89Population of integers90'''91#get the sizes from population92pop_size, ind_size = pop.shape[0], pop.shape[1]93cx_pop = np.zeros((pop_size,ind_size)) # initialize crossover population94for j in range(int(pop_size/2)): # pop_size must be even95parent1, parent2 = pop[2*j], pop[2*j+1]96child1, child2 = parent2.copy(), parent1.copy()97if np.random.uniform() < cx_prob: # crossover occurs98swap_idx = np.sort(np.random.randint(0,ind_size,2))99hole = np.full( ind_size, False, dtype = bool)100hole[swap_idx[0]:swap_idx[1]+1] = True101child1[~hole] = np.array([x for x in parent1 if x not in parent2[hole]])102child2[~hole] = np.array([x for x in parent2 if x not in parent1[hole]])103if debug:104print("Crossover happened for individual", j)105print('Parent 1', parent1)106print('Parent 2', parent2)107print('Swap Index:', swap_idx)108print('Hole', hole)109print('Child1', child1)110print('Child2', child2)111cx_pop[2*j] = child1112cx_pop[2*j+1] = child2113return cx_pop.astype(int)114115116def onePointCrossover(pop, cx_prob, debug=False):117'''118Peforms one-point crossover on integer, boolean or real populations.119120Parameters:121pop (numpy array, required): The population, individuals as rows122cx_prob (real between 0 and 1, required): The probability that any two individuals will mate123debug (boolean, optional, default=False): Flag to indicate whether to output debugging print statements.124125Returns:126Population (will return as reals, should be cast if integer or boolean is desired)127'''128# one-point crossover (mating)129#get the sizes from pop130pop_size, ind_size = pop.shape[0], pop.shape[1]131cx_pop = np.zeros((pop_size,ind_size)) # initialize crossover population132for j in range(int(pop_size/2)): # pop_size must be even133parent1, parent2 = pop[2*j], pop[2*j+1]134child1, child2 = parent1.copy(), parent2.copy()135if np.random.uniform() < cx_prob: # crossover occurs136cx_point = np.random.randint(1,ind_size) # crossover point between 1 and ind_size137if debug:138print('Crossover happened between Individuals', 2*j, 'and', 2*j+1, 'at point', cx_point)139child1[0:cx_point], child2[0:cx_point] = parent2[0:cx_point], parent1[0:cx_point]140cx_pop[2*j] = child1141cx_pop[2*j+1] = child2142return cx_pop143144def blendedCrossover(pop, cx_prob, alpha, bounds, debug=False):145'''146Peforms blended crossover on real populations.147148Parameters:149pop (numpy array, required): The population, individuals as rows150cx_prob (real between 0 and 1, required): The probability that any two individuals will mate151alpha (real, required): the amount of expansion152bounds (list, required): the [lower, upper] bounds of possible values for each gene153debug (boolean, optional, default=False): Flag to indicate whether to output debugging print statements.154155Returns:156Population of real variables157'''158#get individual size and population size159pop_size, ind_size = pop.shape[0], pop.shape[1]160cx_pop = np.zeros((pop_size,ind_size)) # initialize crossover population161for j in range(int(pop_size/2)): # pop_size must be even162parent1, parent2 = pop[2*j], pop[2*j+1]163child1, child2 = parent1.copy(), parent2.copy()164if np.random.uniform() < cx_prob: # crossover occurs165if debug:166print('Crossover occurred between', 2*j, 'and', 2*j+1)167for i, (x1, x2) in enumerate(zip(child1, child2)):168l = min(x1,x2)169r = max(x1,x2)170bb = np.clip(np.random.uniform(l-alpha*(x2-x1),r+alpha*(x2-x1),size=2),bounds[0],bounds[1])171child1[i] = bb[0]172child2[i] = bb[1]173cx_pop[2*j] = child1174cx_pop[2*j+1] = child2175return cx_pop176177178179####################################180# Mutation operators181####################################182def gaussianMutation(pop, mut_prob, ind_prob, bounds, sigma, debug=False):183'''184Peforms gaussian mutation on real populations.185186Parameters:187pop (numpy array, required): The population, individuals as rows188mut_prob (real between 0 and 1, required): The probability that any individual will mutate189ind_prob (real between 0 and 1, required): The probability that a gene will mutate190bounds (list, required): the [lower, upper] bounds of possible values for each gene191sigma (real, required): standard deviation used to generate new mutations192debug (boolean, optional, default=False): Flag to indicate whether to output debugging print statements.193194Returns:195Population of real variables196'''197#get individual size and population size198pop_size, ind_size = pop.shape[0], pop.shape[1]199mut_pop = np.zeros((pop_size,ind_size)) # initialize mutation population200for j in range(pop_size):201individual = pop[j].copy() # copy is necessary to avoid conflicts in memory202if np.random.uniform()<mut_prob:203if debug:204print("Mutation probability met for individual", j)205individual = individual + np.random.normal(0,sigma,ind_size)*(np.random.uniform(size=ind_size)<ind_prob)206individual = np.maximum(individual,bounds[0]) # clip to lower bound207individual = np.minimum(individual,bounds[1]) # clip to upper bound208mut_pop[j] = individual.copy() # copy is necessary to avoid conflicts in memory209return mut_pop210211212def uniformIntMutation(pop, mut_prob, ind_prob, bounds, debug=False):213'''214Peforms uniform integer mutation on integer populations.215216Parameters:217pop (numpy array, required): The population, individuals as rows218mut_prob (real between 0 and 1, required): The probability that any individual will mutate219ind_prob (real between 0 and 1, required): The probability that a gene will mutate220bounds (list, required): the [lower, upper] bounds of possible values for each gene221debug (boolean, optional, default=False): Flag to indicate whether to output debugging print statements.222223Returns:224Population of integer variables225'''226mut_pop = pop.copy()227pop_size, ind_size = pop.shape[0], pop.shape[1]228for j in range(pop_size):229if np.random.uniform()<mut_prob:230new_assign = mut_pop[j].copy()231for i in range(ind_size):232if np.random.uniform() < ind_prob:233if debug:234print('Gene', i, 'in Individual', j, 'mutated.')235while new_assign[i] == mut_pop[j][i]: # loops until new and old are different236new_assign[i] = np.random.randint(bounds[0], bounds[1])237mut_pop[j] = new_assign238return mut_pop.astype(int)239240241def bitFlipMutation(pop, mut_prob, ind_prob, debug=False):242'''243Peforms bit-flipping mutation on boolean populations.244245Parameters:246pop (numpy array, required): The population, individuals as rows247mut_prob (real between 0 and 1, required): The probability that any individual will mutate248ind_prob (real between 0 and 1, required): The probability that a gene will mutate249debug (boolean, optional, default=False): Flag to indicate whether to output debugging print statements.250251Returns:252Population of boolean variables253'''254255#get sizes256pop_size, ind_size = pop.shape[0], pop.shape[1]257mut_pop = np.zeros((pop_size,ind_size),dtype=bool) # initialize mutation population258for j in range(pop_size):259individual = pop[j].copy() # copy is necessary to avoid conflicts in memory260if np.random.uniform()<mut_prob:261for i in range(ind_size):262if np.random.uniform() < ind_prob:263if debug:264print('Gene', i, 'in Individual', j, 'flipped.')265individual[i] = ~individual[i]266mut_pop[j] = individual.copy() # copy is necessary to avoid conflicts in memory267return mut_pop.astype(bool)268269def shuffleMutation(pop, mut_prob, ind_prob, debug=False):270'''271Peforms index shuffling mutation on permutation populations.272273Parameters:274pop (numpy array, required): The population, individuals as rows275mut_prob (real between 0 and 1, required): The probability that any individual will mutate276ind_prob (real between 0 and 1, required): The probability that a gene will mutate277debug (boolean, optional, default=False): Flag to indicate whether to output debugging print statements.278279Returns:280Population of permutation integer variables281'''282#get sizes283pop_size, ind_size = pop.shape[0], pop.shape[1]284if debug:285print('Pop size:', pop_size)286print('Individual size:', ind_size)287mut_pop = np.zeros((pop_size,ind_size),dtype=int) # initialize mutation population288for j in range(pop_size):289individual = pop[j].copy() # copy is necessary to avoid conflicts in memory290if np.random.uniform()<mut_prob:291for k in range(ind_size):292if np.random.uniform() < ind_prob:293swap = np.random.randint(ind_size)294if debug:295print('Gene', k, 'in Individual', j, 'swapped with', swap)296print('Individual before\n', individual)297individual[k],individual[swap] = individual[swap],individual[k]298if debug:299print('Individual after\n', individual)300mut_pop[j] = individual.copy() # copy is necessary to avoid conflicts in memory301return mut_pop.astype(int)302303304def flipSegmentsMutation(pop, mut_prob, ind_prob, debug=False):305'''306Performs multiple random segment reversal on permutation populations307308Parameters:309pop (numpy array, required): The population, individuals310mut_prob (real between 0 and 1, required): The probability an individual will mutate311ind_prob (real between 0 and 1, required): The probability a gene will be part of a segment reversal312'''313pop_size, ind_size = pop.shape[0], pop.shape[1]314mut_pop = np.zeros((pop_size,ind_size),dtype=int)315for k in range(pop_size):316individual = pop[k].copy() # make a copy to avoid conflicts317if np.random.uniform() < mut_prob:318num_swaps = np.random.binomial(ind_size,ind_prob)319for m in range(num_swaps): # choose now many swaps320i, j = np.sort(np.random.choice(ind_size, 2, replace=False))321individual = np.concatenate((individual[0:i], individual[j:-ind_size + i - 1:-1],322individual[j + 1:ind_size]))323mut_pop[k] = individual.copy()324return mut_pop.astype(int)325326# Elitism327def addElitism(initPop, mutPop, num_elite):328'''329Peforms elitism by pulling in num_elite best individuals from initPop to mutPop.330331Parameters:332initPop (numpy array, required): The population from the beginning of the loop, individuals as rows333mutPop (numpy array, required): The population post-mutation.334335Returns:336Population numpy array population337'''338pop_size = initPop.shape[0]339initPop[(num_elite):pop_size] = mutPop[(num_elite):pop_size].copy()340return initPop341342###############################343# Stats Tracking344###############################345346def initStats(fitness, pop, num_iter):347'''348Sets up initial stats tracking349350Parameters:351fitness (numpy array, required): The current fitness at the start of the algorithm352pop (numpy array, required): The population for which we are tracking fitness353num_iter (integer, required): The number of iterations we'll track.354355Returns:356stats (numpy array)357best_fitness (real)358best_x (individual)359'''360stats = np.zeros((num_iter+1,3)) # for collecting statistics361#get the initial best fitness362best_fitness = min(fitness)363min_fitness = min(fitness) # best for this iteration364index = np.argmin(fitness)365#set the initial best_x366best_x = pop[index]367# initialize stats and output368stats[0,:] = np.array([0,best_fitness, best_fitness])369print('Iteration | Best this iter | Best ever')370return stats, best_fitness, best_x371372373def updateStats(stats, fitness, best_x, pop, iter, update_iter):374'''375Updates stats tracking376377Parameters:378stats (numpy array, required): The stats that have been collected so far379fitness (numpy array, required): The current fitness at the start of the algorithm380best_x (numpy array individual, required): The current best individual381pop (numpy array, required): The population for which we are tracking fitness382iter (integer, required): The current iteration we are on.383update_iter (integer, required): How often to display stats384385Returns:386stats (numpy array)387best_fitness (real)388best_x (individual)389'''390# collect stats and output to screen391min_fitness = min(fitness) # best for this iteration392393#get stats less than this iteration394snipped_stats = stats[0:iter]395if len(snipped_stats) > 0:396index = np.argmin(snipped_stats[:,2])397best_fitness = min(snipped_stats[:,1])398else:399best_fitness = min_fitness400best_x = []401if min_fitness < best_fitness: # best for all iterations402best_fitness = min_fitness403index = np.argmin(fitness)404best_x = pop[index]405406stats[iter+1,:] = np.array([iter+1,min_fitness, best_fitness])407if (iter+1) % update_iter == 0 or (iter+1) ==1 :408print(f"{stats[iter+1,0]:9.0f} | {stats[iter+1,1]:14.3e} | {stats[iter+1,2]:12.3e}")409410return stats, best_fitness, best_x411412413414415