def climb_ineff(seq, N, phase_unity=2, num_elem=None, min_fn=second_max): """ Function to find the optimal neighbor in the one-neighborhood of a polyphase code with values drawn from the roots of unity of order phase_unity. :param seq: the code we are considering :type seq: array :param N: the doppler width :type N: int :param phase_unity: the order of the roots of unity :type phase_unity: int :param num_elem: number of elements that min_fn is being evaluated on :type num_elem: int :param min_fn: function of the ambiguity function/autocorrelation we are minimizing :type min_fn: function :return: an updated sequence, the optimal neighbor of seq """ val = fast_autocorrelations(seq, N, num_elem, min_fn) # initial peak sidelobe value current_seq = np.copy(seq) min_val = val for i in range(len(seq)): # loop over all possible coordinates to change for j in range(1, phase_unity): # loop over all possible phase additions seq_new = np.copy(seq) phase_add = random.randint(1, phase_unity - 1) seq_new[i] = seq_new[i] * cmath.exp( phase_add * 2 * cmath.pi * imag / phase_unity) new_val = fast_autocorrelations(seq_new, N, num_elem, min_fn) if new_val < min_val: min_val = new_val current_seq = seq_new return current_seq
def conv_ineff(left, right, N, length): """ Inefficient convolution of the finished code from our peak sidelobe exhaustive algorithm, which outputs the peak sidelobe of the finished code. If the length of the code in question is odd, it tries both possible values for the middle entry. :param left: left-hand side of the code built up :type left: array :param right: right-hand side of the code :type right: array :param N: doppler width :type N: int :param length: length of the codes we are considering :type length: int :return: peak sidelobe of possible codes from combining left and right """ if length % 2 == 0: # if it is even, the only possible code is exactly their concatenation l_copy = left[:] r_copy = right[:] r_copy.reverse() signal = l_copy + r_copy convert(signal) # convert to -1, 1 form return fast_autocorrelations(signal, N) else: # otherwise, the middle entry can be 1 or -1; we check both l_copy = left[:] l_copy.append(1) r_copy = right[:] r_copy.reverse() signal = l_copy + r_copy convert(signal) corr_1 = fast_autocorrelations(signal, N) l_copy[-1] = 0 signal = l_copy + r_copy convert(signal) corr_2 = fast_autocorrelations(signal, N) return min(corr_1, corr_2)
def select_parent(population, N=1, phase_unity=2, num_elem=None, min_fn=second_max): """ A tournament selection algorithm to select a parent, with k = 2. :param population: array of the codes at our current generation :type population: array :param N: doppler width of our ambiguity function :type N: int :param phase_unity: order of the roots of unity filling our codes :type phase_unity: int :param num_elem: number of elements that min_fn is being evaluated on :type num_elem: int :param min_fn: function of the ambiguity function/autocorrelation we are minimizing :type min_fn: function :return: a parent for the next generation with the maximal fitness among 2 randomly selected """ poss_parents = random.sample(range(0, len(population)), 2) if fast_autocorrelations(population[poss_parents[0]], N, num_elem, min_fn) < fast_autocorrelations( population[poss_parents[1]], N, num_elem, min_fn): return poss_parents[0] else: return poss_parents[1]
def ts_local_search(child, N=1, phase_unity=2, num_elem=None, min_fn=second_max): """ Tabu local search algorithm; works by climbing to better neighbors, excluding those with coordinates in a tabu, for a number of iterations. :param child: initial sequence :type child: array :param N: doppler width of our ambiguity function :type N: int :param phase_unity: order of the roots of unity filling our codes :type phase_unity: int :param num_elem: number of elements that min_fn is being evaluated on :type num_elem: int :param min_fn: function of the ambiguity function/autocorrelation we are minimizing :type min_fn: function :return: locally near-optimal value near child, found by tabu local search, which allows for more potential movement away from a very close local minima """ length = len(child) tabu_table = [0 for x in range(length) ] # table to store most recently changed coordinates max_iters = 20 min_iters = 2 extra_iters = 1 best_code = np.copy(child) best_val = fast_autocorrelations(child, N, num_elem, min_fn) for k in range(max_iters): code_this_gen = np.array([]) val_this_gen = float('inf') which_i = None for i in range(length): # loop over all coordinates for j in range( 1, phase_unity): # loop over all possible phase additions seq_new = np.copy(child) if phase_unity == 2: seq_new[i] = -seq_new[i] else: seq_new[i] = seq_new[i] * cmath.exp( j * 2 * cmath.pi * imag / phase_unity) val = fast_autocorrelations(seq_new, N, num_elem, min_fn) if k >= tabu_table[ i] or val < best_val: # if it improves on the best value or it hasn't beeen changed in a while, move to that code. if val < val_this_gen: val_this_gen = val code_this_gen = seq_new which_i = i if code_this_gen.any(): child = np.copy(code_this_gen) if which_i != None: # increase value of tabu_table for the coordinate that changed tabu_table[which_i] = k + min_iters + random.randint( 0, extra_iters) if val_this_gen < best_val: best_code = np.copy(code_this_gen) best_val = val_this_gen return best_code
def threshold(code, N, threshold_schedule, phase_unity, num_elem = None, min_fn = second_max): """ The core threshold accepting algorithm, which starts with a random polyphase code (with generating phase described by phase_unity) and moves around with gen_neighbor, excluding movements for which the new code has a min_fn value that exceeds our threshold. :param code: the code in question :type code: array :param N: doppler width of our ambiguity function :type N: int :param threshold_schedule: function that describes how the threshold decreases from generation to generation :type threshold_schedule: function :param phase_unity: order of the roots of unity that fill the codes we will be considering :type phase_unity: int :param num_elem: number of elements that min_fn is being evaluated on :type num_elem: int :param min_fn: function of the ambiguity function/autocorrelation we are minimizing :type min_fn: function :return: a tuple of the best code and its corresponding min_fn value found by the algorithm, given the particular starting point """ psl = fast_autocorrelations(code, N, num_elem, min_fn) # initial peak sidelobe value curr_threshold = int(psl * 2) count = 0 kappa = 0.2 num_gen = 30 * len(code) # number of generations at a fixed threshold best_found = code best_psl = psl stuck_count = 0 # number of iterations we have been stuck at a given code upp_bound = 2 * len(code) ** (kappa) num_threshold_gens = int(len(code)/2) for j in range(1, num_threshold_gens): for i in range(num_gen): # loop at a given threshold of movements from neighbor to neighbor new_code = gen_neighbor(code, phase_unity, 3) # new_code found by gen_neighbor new_psl = fast_autocorrelations(new_code, N, num_elem, min_fn) if new_psl < curr_threshold: # move to the new code if it is better than the threshold code = new_code stuck_count = 0 if new_psl < best_psl: # update best_psl as well if it is improved on best_found = new_code best_psl = new_psl else: stuck_count+=1 if stuck_count > upp_bound: # if we have been stuck for a while, break out of the loop count += 1 break if stuck_count > upp_bound: break curr_threshold = semiexponential(curr_threshold, j) # update the threshold return (best_found, best_psl)
def k_select_parent(population, k, N=1, phase_unity=2, num_elem=None, min_fn=second_max): """ A tournament selection algorithm to select a parent, for arbitrary k. :param population: array of the codes at our current generation :type population: array :param k: number of members from which we are choosing :type k: int :param N: doppler width of our ambiguity function :type N: int :param phase_unity: order of the roots of unity filling our codes :type phase_unity: int :param num_elem: number of elements that min_fn is being evaluated on :type num_elem: int :param min_fn: function of the ambiguity function/autocorrelation we are minimizing :type min_fn: function :return: a parent for the next generation with the maximal fitness among k randomly selected """ poss_parents = random.sample(range(0, len(population)), k) poss_parents_psl = np.array([ fast_autocorrelations(population[x], N, num_elem, min_fn) for x in poss_parents ]) max_index = np.argmax( poss_parents_psl) # utility of using np; argmax exists return poss_parents[max_index]
def select_parents_random(population, retain, random_select, k, N=1, phase_unity=2, num_elem=None, min_fn=second_max): """ Function to select the parents from the population, via a random weighted selection algorithm. :param population: list of arrays comprising our population :type population: array :param retain: fraction of population that we retain as is, unchanged :type retain: float :param random_select: unused variable :param k: unused variable :param N: doppler width :type N: int :param phase_unity: order of the roots of unity in our codes :type phase_unity: int :param num_elem: number of elements that min_fn is being evaluated on :type num_elem: int :param min_fn: function of the ambiguity function/autocorrelation we are minimizing :type min_fn: function :return: the population after the selection process, in the array parents """ # sort the population by min_fn value psl_values = [(fast_autocorrelations(x, N, num_elem, min_fn), x) for x in population] pop_length = len(psl_values) psl_values.sort(key=lambda x: x[0]) seqs_sorted_by_psl = [x[1] for x in psl_values] num_retain = int(pop_length * retain) # number of parents we will retain min_fitness = psl_values[0][0] parents = [] count = 0 while count < num_retain: # until we reach num_retain members, select a random parent, add it with probability weighted by its min_fn value. select_point = random.randint(0, pop_length - 1) poss_parent = seqs_sorted_by_psl[select_point] prob = min_fitness / fast_autocorrelations(poss_parent, N, num_elem, min_fn) if prob > random.random(): count += 1 parents.append(poss_parent) return parents
def hill_climbing_iterative(length, climb_type, N=1, phase_unity=2, min_fn=second_max, num_elem=None): """ An iterative, partial-restart based hill climbing algorithm with flexibility for doppler width, polyphase codes, and different types of local search. This algorithm is used to find codes with the minimal min_fn value. :param length: length of the codes in question :type length: int :param climb_type: local search function :type climb_type: function :param N: doppler width of the ambiguity function :type N: int :param phase_unity: order of the roots of unity in our codes :type phase_unity: int :param num_elem: number of elements that min_fn is being evaluated on :type num_elem: int :param min_fn: function of the ambiguity function/autocorrelation we are minimizing :type min_fn: function :return: the minimal min_fn value found by our algorithm, as well as the corresponding polyphase code """ f = open( 'best_known_sidelobes.txt') # best known sidelobes from the literature lines = f.readlines() best_code = None best_val = float('inf') cutoff_val = int(lines[length - 1]) # lowest possible value we can find time_limit = 0 if length <= 30: time_limit = 300 else: time_limit = (300 + 60 * (length - 30)) start_time = time.time() while time.time() - start_time < time_limit and (best_val != cutoff_val or phase_unity != 2): x = gen_rand_seq( length, phase_unity ) # generate a random sequence of the given specifications x = climb_type( x, N, phase_unity, num_elem, min_fn) # climb using the given climb_type to a nearby minima val = fast_autocorrelations( x, N, num_elem, min_fn ) # compute the psl/min_fn value, update if it improves on the previous best if val < best_val: best_code = x best_val = val return (best_code, best_val)
def stochastic_climb(seq, N, phase_unity, num_elem=None, min_fn=second_max): """ An algorithm to climb to a neighbor of a given sequence, where each step taken is always taken if it improves the min_fn value, and sometimes taken if it does not, probabilistically according to the difference between the new and old min_fn values. :param seq: initial sequence :type seq: array :param N: doppler width of the ambiguity function over which we are minimizing :type N: int :param phase_unity: order of the roots of unity in our codes :type phase_unity: int :param num_elem: number of elements that min_fn is being evaluated on :type num_elem: int :param min_fn: function of the ambiguity function/autocorrelation we are minimizing :type min_fn: function :return: a neighbor of seq """ val = fast_autocorrelations(seq, N, num_elem, min_fn) current_seq = np.copy(seq) min_val = val for i in range(len(seq)): # loop over all possible coordinates to change for j in range(1, phase_unity): # loop over all possible phase additions seq_new = np.copy(seq) phase_add = random.randint(1, phase_unity - 1) seq_new[i] = seq_new[i] * cmath.exp( phase_add * 2 * cmath.pi * imag / phase_unity) # multiply by the given root of unity new_val = fast_autocorrelations(seq_new, N, num_elem, min_fn) if new_val < min_val: min_val = new_val current_seq = seq_new return current_seq else: if prob_of_acceptance(min_val, new_val) > random.random( ): # if the min_fn value is worse, move with some probability. current_seq = seq_new return current_seq return current_seq
def simple_climb(seq, N, phase_unity, num_elem=None, min_fn=second_max): """ An algorithm to climb to a local minima from a given sequence, where each step taken is the first that improves the current sequence. :param seq: initial sequence :type seq: array :param N: doppler width of the ambiguity function over which we are minimizing :type N: int :param phase_unity: order of the roots of unity in our codes :type phase_unity: int :param num_elem: number of elements that min_fn is being evaluated on :type num_elem: int :param min_fn: function of the ambiguity function/autocorrelation we are minimizing :type min_fn: function :return: a code neighboring seq with an improved min_fn value, as well as whether climbing there required at least one change to seq. """ val = fast_autocorrelations(seq, N, num_elem, min_fn) current_seq = np.copy(seq) min_val = val changed = False for i in range(len(seq)): # loop over all possible coordinates to change for j in range(1, phase_unity): # loop over all possible phase additions seq_new = np.copy(seq) seq_new[i] = seq_new[i] * cmath.exp( j * 2 * cmath.pi * imag / phase_unity) # multiply by the given root of unity new_val = fast_autocorrelations(seq_new, N, num_elem, min_fn) if new_val < min_val: min_val = new_val current_seq = seq_new changed = True return current_seq, changed return current_seq, changed
def stochastic_local_search( seq, N, phase_unity, num_elem=None, min_fn=second_max ): # note: for future, would be good to collapse this with sdls, simple into one algor """ Handler for our simple climb algorithm, where we keep climbing for a fixed number of steps using stochastic_climb. :param seq: initial sequence :type seq: array :param N: doppler width over which we are minimizing :type N: int :param phase_unity: order of the roots of unity in our codes :type phase_unity: int :param num_elem: number of elements that min_fn is being evaluated on :type num_elem: int :param min_fn: function of the ambiguity function/autocorrelation we are minimizing :type min_fn: function :return: probabilistically, a locally minimal code near seq """ max_iters = 40 # perhaps fewer iterations, if want to optimize to_return = seq best_found = seq best_val = fast_autocorrelations(seq, N, num_elem, min_fn) for i in range(max_iters): to_return = stochastic_climb(to_return, N, phase_unity, num_elem, min_fn) # climb stochastically to a code new_val = fast_autocorrelations( to_return, N, num_elem, min_fn ) # compute min_fn value of the ambiguity function, update best_val if it improves. if new_val < best_val: best_val = new_val best_found = np.copy(to_return) return best_found
def select_parents_part_random(population, retain, random_select, k, N=1, phase_unity=2, num_elem=None, min_fn=second_max): """ Function to select the parents from the population, via a partially random, partially fitness-based selection process. :param population: list of arrays comprising our population :type population: array :param retain: fraction of population that we retain as is, unchanged :type retain: float :param random_select: probability for selecting additional parents, in addition to the most fit :type random_select: float :param k: unused variable :param N: doppler width :type N: int :param phase_unity: order of the roots of unity in our codes :type phase_unity: int :param num_elem: number of elements that min_fn is being evaluated on :type num_elem: int :param min_fn: function of the ambiguity function/autocorrelation we are minimizing :type min_fn: function :return: the population after the selection process, in the array parents """ # sort the population by min_fn value psl_values = [(fast_autocorrelations(x, N, num_elem, min_fn), x) for x in population] psl_values.sort(key=lambda x: x[0]) seqs_sorted_by_psl = [x[1] for x in psl_values] num_retain = int(len(seqs_sorted_by_psl) * retain) parents = seqs_sorted_by_psl[: num_retain] # add the num_retain most fit parents for individual in seqs_sorted_by_psl[ num_retain:]: # add a randomly selected additional set of parents from the population if random_select > random.random(): parents.append(individual) return parents
def fast_autocorrelations_phases(code, N=1, num_elem=None, min_fn=second_max): """ Calculate the min_fn value of the corresponding polyphase code to a given phase sequence. :param code: The phase sequence in question :type code: array :param N: doppler width of our ambiguity function :type N: int :param num_elem: number of elements that min_fn is being evaluated on :type num_elem: int :param min_fn: function of the ambiguity function/autocorrelation we are minimizing :type min_fn: function :return: the min_fn value of the polyphase code corresponding to the phase sequence. """ act_code = [] for i in range( len(code) ): # construct the actual polyphase code corresponding to a sequence of phases act_code.append(cmath.exp(code[i] * imag)) return fast_autocorrelations(act_code, N, num_elem, min_fn)
def climb(seq, num_elem=None, min_fn=second_max): """ Efficient algorithm to climb, in nondoppler, binary space, to the highest neighbor. IMPORTANT NOTE: is outdated, does not work with updated fast_autocorrelation because the list there was cut in half. To fix, need to change inner if statements below in some way. :param seq: initial code in question :type seq: array :param num_elem: number of elements that min_fn is being evaluated on :type num_elem: int :param min_fn: function of the ambiguity function/autocorrelation we are minimizing :type min_fn: function :return: the best neighbor, and the corresponding min_fn value """ lst = fast_autocorrelations(seq, 1) current_seq = seq min_val = min_fn(lst, num_elem) for i in range(len(seq)): # looping over all possible coordinates lst_new = list(lst) length = len(lst_new) for j in range(length): # looping over length of autocorrelation if j < ((length - 1) / 2): if j >= i: lst_new[j] = lst_new[j] - 2 * seq[i] * seq[ len(seq) - 1 - j + i] # exploiting singled changed element on left side if i + j + 1 >= len(seq): lst_new[j] -= 2 * seq[i] * seq[i + j + 1 - len( seq)] # exploiting single changed element on right side elif j > ((length - 1) / 2) and j >= i: lst_new[j] = lst_new[length - 1 - j] min_pos_new = min_fn(lst_new, num_elem) if min_pos_new < min_val: min_val = min_pos_new seq_2 = np.copy(seq) seq_2[i] = -seq_2[i] current_seq = seq_2 return (current_seq, min_val)
def peak_sidelobes_general(length, num_elem, N, gen_codes, op_on_list=second_max, optional_limit=0): """ :param length: length of the code in question :type length: int :param num_elem: number of elements that concern our minimization function :type num_elem: int :param N: doppler window for which we are calculating the ambiguity function :type N: int :param op_on_list: the operation we are minimizing of the ambiguity function :type op_on_list: fn :param gen_codes: function to generate the codes we are considering :type gen_codes: fn :param optional_limit: optimal limit for gen_codes, if we are dealing with arbitrary amplitude codes :type optional_limit: int :return: the code with the minimum value of op_on_list on its ambiguity function, as well as that value. See also: peak_sidelobes_single, peak_sidelobes_efficient """ all_codes = gen_codes( length, optional_limit) # generate the codes over which we are minimizing min_sl = float('inf') min_code = None for code in all_codes: sidelobe_size = fast_autocorrelations( code, N, num_elem, op_on_list) # calculate the value of op_on_list on this code if sidelobe_size < min_sl: # update the value if it improves min_sl = sidelobe_size min_code = code if min_sl == 1: # break if we reach 1, since we can never do better. break return (min_code, min_sl)
def peak_sidelobes_single(length): """ A function to return the binary code of a given length with the minimum peak sidelobe :param length: length of the binary codes in question :type length: int :return: the binary code with minimal peak sidelobe, as well as that peak sidelobe value. See also: peak_sidelobes_general, peak_sidelobes_efficient """ codes = gen_binary_codes(length, None) min_sl = float("inf") min_code = None for code in codes: val = fast_autocorrelations(code, 1) # calculate the peak sidelobe if val < min_sl: # update if it improves on the previous value min_sl = val min_code = code if min_sl == 1: break return (min_code, min_sl)
def run_evolution_memet(length, local_search_type, amount_retain=0.1, N=1, phase_unity=2, num_elem=None, min_fn=second_max): """ Handler for memetic algorithm. :param length: length of the codes in question :type length: int :param local_search_type: the local search function we are using to minimize min_fn :type local_search_type: function :param amount_retain: the fraction of parents we will retain for the next generation (give or take mutations) :type amount_retain: float :param N: doppler width of our ambiguity function :type N: int :param phase_unity: order of the roots of unity filling our codes :type phase_unity: int :param num_elem: number of elements that min_fn is being evaluated on :type num_elem: int :param min_fn: function of the ambiguity function/autocorrelation we are minimizing :type min_fn: function :return: (best_code, best_val): a tuple of a complex value and numpy array representing the minimum min_fn code found """ f = open( 'best_known_sidelobes.txt') # best known sidelobes in the literature lines = f.readlines() best_code = None best_val = float('inf') cutoff_val = int(lines[length - 1]) # best min_fun value our algorithm could find time_limit = 0 if length <= 30: time_limit = 1.5 * 300 else: time_limit = 1.5 * (300 + 60 * (length - 30)) start_time = time.time() while time.time() - start_time < time_limit: partial_restart = 8 num_members = 100 # number of members of our population num_generations = 30 mut_prob = 1. / length offspring = 500 # number of offspring created at each step pop = gen_population(num_members, length, phase_unity) # initial population amount_restart = int(1.5 * length) for i in range(num_generations): if i % partial_restart == 0: pop_add = gen_population(amount_restart, length, phase_unity) pop = np.concatenate((pop, pop_add), 0) pop = evolve_mem( pop, mut_prob, offspring, local_search_type, amount_retain, N, phase_unity, num_elem, min_fn ) # main step in the code: evolving the population, giving an offspring size, initial pop, and mutation probability psl_values = [(fast_autocorrelations(x, N, num_elem, min_fn), x) for x in pop] psl_values.sort(key=lambda x: x[0]) seqs_sorted_by_psl = [x[1] for x in psl_values] new_val = fast_autocorrelations(seqs_sorted_by_psl[0], N, num_elem, min_fn) if new_val < best_val: # keep track of the best value found so far, in case we lose it. best_code = seqs_sorted_by_psl[0] best_val = new_val #print((seqs_sorted_by_psl[0], con_psl(seqs_sorted_by_psl[0]))) if best_val == cutoff_val and phase_unity == 2: break if best_val == cutoff_val and phase_unity == 2: break print(time.time() - start_time) return (best_code, best_val)
def anneal_with_local_search(code, N, cooling_schedule, phase_unity, num_elem=None, min_fn=second_max): """ Our fundamental annealing algorithm, which starts with a random polyphase code (phase described by phase_unity) and moves around, returning the best code and min_fn value found. The premise of the algorithm is simple: start with a high temperature, which allows more movement around the current code, even if it increases the min_fn value and over time lower the temperature. This is one altered version of the algorithm, where we perform a local search after generating each new code. Note: I do not provide additional comments here, outside of comments on the new sections of the code. :param code: the initial code :type code: array :param N: doppler width of our ambiguity function :type N: int :param cooling_schedule: function that describes how the temperature decreases from generation to generation :type cooling_schedule: function :param phase_unity: order of the roots of unity that fill the codes we will be considering :type phase_unity: int :param num_elem: number of elements that min_fn is being evaluated on :type num_elem: int :param min_fn: function of the ambiguity function/autocorrelation we are minimizing :type min_fn: function :return: a tuple of the best code and its corresponding min_fn value found by the algorithm See also: anneal, anneal_with_tabu """ init_temperature = 2 * len(code) temperature = init_temperature psl = fast_autocorrelations(code, N, num_elem) count = 0 kappa = 0.2 num_gen = 3 * len(code) best_found = code best_psl = psl stuck_count = 0 num_temp_gens = int(1.2 * init_temperature) upp_bound = 2 * len(code)**(kappa) for j in range(1, num_temp_gens): for i in range(num_gen): new_code = gen_neighbor(code, phase_unity, min(len(code) - 1, 3)) new_code = simple_local_search( new_code, N, phase_unity, num_elem, min_fn ) # locally search and find the best point around this new neighbor new_psl = fast_autocorrelations(new_code, N, num_elem, min_fn) if new_psl < psl: code = new_code stuck_count = 0 if new_psl < best_psl: best_found = new_code best_psl = new_psl else: if acceptance_probability(psl, new_psl, temperature) > random.random(): code = new_code stuck_count = 0 else: stuck_count += 1 if stuck_count > upp_bound: count += 1 break if stuck_count > upp_bound: break temperature = cooling_schedule(temperature, init_temperature, 0.25, num_temp_gens, j) return (best_found, best_psl)
def evolut_algo(length, N=1, phase_unity=2, num_elem=None, min_fn=second_max): """ Our most in-depth evolutionary algorithm, incorporating a neighborhood selection step, a climb step, but no crossover. :param length: length of the codes we are considering :type length: int :param N: doppler width :type N: int :param phase_unity: order of the roots of unity in our codes :type phase_unity: int :param num_elem: number of elements that min_fn is being evaluated on :type num_elem: int :param min_fn: function of the ambiguity function/autocorrelation we are minimizing :type min_fn: function :return: the best code found, along with its minimum min_fn value. """ f = open('best_known_sidelobes.txt' ) # file with the best known sidelobes in the literature lines = f.readlines() best_code = None best_val = float('inf') cutoff_val = int(lines[length - 1]) # best possible value our algorithm can find time_limit = 0 if length <= 30: time_limit = 2 * 300 else: time_limit = 2 * (300 + 60 * (length - 30)) start_time = time.time() while time.time() - start_time < time_limit and (best_val != cutoff_val or N != 1 or phase_unity != 2): time_to_restart = 8 num_generations = 25 num_members = 100 num_offspring = 500 pop = gen_population(num_members, length, phase_unity) restart_members = 15 for i in range(len(pop)): pop[i] = climb_ineff( pop[i], N, phase_unity, num_elem, min_fn) # first move the population to its best state for i in range(1, num_generations + 1): # partial restart step; for the sake of introducing variance, we add some new members to the population every partial_restart steps if i % time_to_restart == 0: pop_add = gen_population(restart_members, length, phase_unity) for i in range(len(pop_add)): pop_add[i] = climb_ineff(pop_add[i], N, phase_unity, num_elem, min_fn) pop = np.concatenate((pop, pop_add), 0) children = [] while len( children ) < num_offspring: # mutate and climb until we have sufficient offspring father = random.randint(0, len(pop) - 1) father_mut = gen_neighbor(pop[father], phase_unity, min(length - 1, 3)) father_mut_best = climb_ineff(father_mut, N, phase_unity, num_elem, min_fn) children.append(father_mut_best) pop = np.concatenate((pop, children), axis=0) psl_values = [(fast_autocorrelations(x, N, num_elem, min_fn), x) for x in pop] psl_values.sort(key=lambda x: x[ 0]) # can make more efficient, only need top num_members seqs_sorted_by_psl = [x[1] for x in psl_values] if psl_values[0][0] < best_val: best_val = psl_values[0][0] best_code = psl_values[0][1] pop = seqs_sorted_by_psl[:num_members] if best_val == cutoff_val: break return (best_code, best_val)
def evolut_algo_naive(length, N=1, phase_unity=2, num_elem=None, min_fn=second_max): """ Our basic evolutionary algorithm, incorporating only crossover and fitness-based selection. :param length: length of the codes we are considering :type length: int :param N: doppler width :type N: int :param phase_unity: order of the roots of unity in our codes :type phase_unity: int :param num_elem: number of elements that min_fn is being evaluated on :type num_elem: int :param min_fn: function of the ambiguity function/autocorrelation we are minimizing :type min_fn: function :return: the best code found, along with its minimum min_fn value. """ f = open('best_known_sidelobes.txt' ) # file with the best known sidelobes in the literature lines = f.readlines() best_code = None best_val = float('inf') cutoff_val = int(lines[length - 1]) # best possible value our algorithm can find time_limit = 0 if length <= 30: time_limit = 2 * 300 else: time_limit = 2 * (300 + 60 * (length - 30)) start_time = time.time() while time.time() - start_time < time_limit and (best_val != cutoff_val or N != 1 or phase_unity != 2): num_members = 100 num_generations = 25 num_offspring = 500 pop = gen_population(num_members, length, phase_unity) for i in range(num_generations): children = [] while len(children) < num_offspring: father = random.randint(0, len(pop) - 1) mother = random.randint(0, len(pop) - 1) if father != mother: father = pop[father] mother = pop[mother] splice_point = random.randint( 0, len(father) - 1) # analogue of crossover_randpoint. child = np.append(father[:splice_point], mother[splice_point:]) children.append(child) pop = np.concatenate((pop, children), axis=0) psl_values = [(fast_autocorrelations(x, N, num_elem, min_fn), x) for x in pop] psl_values.sort(key=lambda x: x[0]) seqs_sorted_by_psl = [x[1] for x in psl_values] if psl_values[0][0] < best_val: best_val = psl_values[0][0] best_code = psl_values[0][1] pop = seqs_sorted_by_psl[:num_members] if best_val == cutoff_val: break return (best_code, best_val)
def anneal_with_tabu(code, N, cooling_schedule, phase_unity, num_elem=None, min_fn=second_max): """ Our fundamental annealing algorithm, which starts with a random polyphase code (phase described by phase_unity) and moves around, returning the best code and min_fn value found. The premise of the algorithm is simple: start with a high temperature, which allows more movement around the current code, even if it increases the min_fn value and over time lower the temperature. This is one altered version of the algorithm, where we store recent codes in a tabu and do not proceed with any normal steps when a considered code is in this tabu. Note: I do not provide additional comments here, outside of comments on the new sections of the code. :param code: the initial code :type code: array :param N: doppler width of our ambiguity function :type N: int :param cooling_schedule: function that describes how the temperature decreases from generation to generation :type cooling_schedule: function :param phase_unity: order of the roots of unity that fill the codes we will be considering :type phase_unity: int :param num_elem: number of elements that min_fn is being evaluated on :type num_elem: int :param min_fn: function of the ambiguity function/autocorrelation we are minimizing :type min_fn: function :return: a tuple of the best code and its corresponding min_fn value found by the algorithm See also: anneal, anneal_with_local_search """ tabu = deque() # initialize an empty queue init_temperature = 2 * len(code) temperature = init_temperature psl = fast_autocorrelations(code, N, num_elem, min_fn) count = 0 kappa = 0.2 num_gen = 25 * len(code) best_found = code best_psl = psl stuck_count = 0 num_temp_gens = int(2.9 * init_temperature) upp_bound = 2 * len(code)**(kappa) for j in range(1, num_temp_gens): for i in range(num_gen): new_code = gen_neighbor(code, phase_unity, min(len(code) - 1, 3)) new_psl = fast_autocorrelations(new_code, N, num_elem, min_fn) list_v = new_code.tolist() if list_v not in tabu: # if the new_code is not in the tabu, we can proceed in the normal steps if new_psl < psl: code = new_code stuck_count = 0 if new_psl < best_psl: best_found = new_code best_psl = new_psl else: if acceptance_probability(psl, new_psl, temperature) > random.random(): code = new_code stuck_count = 0 else: stuck_count += 1 tabu.append( list_v ) # we add this code to the tabu so we do not consider it again if len( tabu ) == 101: # if the tabu is full, we pop off the front of the queue tabu.popleft() if stuck_count > upp_bound: count += 1 break if stuck_count > upp_bound: break temperature = cooling_schedule(temperature, init_temperature, 0.25, num_temp_gens, j) return (best_found, best_psl)
def run_evolution(length, num_members=100, num_gens=25, parent_func=select_parents_part_random, mut_func=mutate_all, crossover_func=crossover_random, retain=0.2, random_select=0.05, mutate=0.02, k=2, N=1, phase_unity=2, num_elem=None, min_fn=second_max): """ Handler for running evolution; for a fixed number of number of generations, generates a population, evolves it, and returns the best code found. :param length: length of the codes in question :type length: int :param num_members: number of members to put in the population :type num_members: int :param parent_func: function to select the parents of the next generation :type parent_func: function :param mut_func: function to mutate the members of a population :type mut_func: function :param crossover_func: function to crossover two parents to create a child :type crossover_func: function :param retain: fraction of parents to retain :type retain: float :param random_select: fraction of parents to randomly select, if necessary :type random_select: float :param mutate: fraction of population to mutate :type mutate: float :param k: number of members for tournament selection :type k: int :param N: doppler width :type N: int :param phase_unity: order of the roots of unity in our codes :type phase_unity: int :param num_elem: number of elements that min_fn is being evaluated on :type num_elem: int :param min_fn: function of the ambiguity function/autocorrelation we are minimizing :type min_fn: function :return: (best_code, best_val): a tuple of a complex value and numpy array representing the minimum min_fn code found """ f = open( 'best_known_sidelobes.txt') # best known sidelobes in the literature lines = f.readlines() best_code = None best_val = float('inf') cutoff_val = int(lines[length - 1]) # best possible value our algorithm can find time_limit = 0 if length <= 30: time_limit = 0.5 * 300 else: time_limit = 0.5 * (300 + 60 * (length - 30)) start_time = time.time() while time.time() - start_time < time_limit and (best_val != cutoff_val or N != 1 or phase_unity != 2): pop = gen_population(num_members, length, phase_unity) # generate a population of codes for i in range(num_gens): pop = evolve(pop, parent_func, mut_func, crossover_func, retain, random_select, mutate, k) # evolve that population # sort the population by min_fn, if best fitness improves on the previous best, update best_val. psl_values = [(fast_autocorrelations(x, N, num_elem, min_fn), x) for x in pop] psl_values.sort(key=lambda x: x[0]) seqs_sorted_by_psl = [x[1] for x in psl_values] new_val = fast_autocorrelations(seqs_sorted_by_psl[0], N, num_elem, min_fn) if new_val < best_val: # keep track of the best value found so far, in case we lose it. best_code = seqs_sorted_by_psl[0] best_val = new_val if best_val == cutoff_val: break return (best_code, best_val)
def anneal(code, N, cooling_schedule, phase_unity, num_elem=None, min_fn=second_max): """ Our fundamental annealing algorithm, which starts with a random polyphase code (phase described by phase_unity) and moves around, returning the best code and min_fn value found. The premise of the algorithm is simple: start with a high temperature, which allows more movement around the current code, even if it increases the min_fn and over time lower the temperature. This is the bare-bones algorithm, with no extra features besides that. :param code: the initial code :type code: array :param N: doppler width of our ambiguity function :type N: int :param cooling_schedule: function that describes how the temperature decreases from generation to generation :type cooling_schedule: function :param phase_unity: order of the roots of unity that fill the codes we will be considering :type phase_unity: int :param num_elem: number of elements that min_fn is being evaluated on :type num_elem: int :param min_fn: function of the ambiguity function/autocorrelation we are minimizing :type min_fn: function :return: a tuple of the best code and its corresponding min_fn value found by the algorithm See also: anneal_with_local_search, anneal_with_tabu """ init_temperature = 2 * len(code) temperature = init_temperature psl = fast_autocorrelations(code, N, num_elem, min_fn) # initial peak sidelobe value count = 0 kappa = 0.2 num_gen = 25 * len(code) # number of generations at a fixed temperature best_found = code best_psl = psl stuck_count = 0 # number of iterations we have been stuck at a given code num_temp_gens = int(2.9 * init_temperature) upp_bound = 2 * len(code)**(kappa) for j in range( 1, num_temp_gens): # loop from high temperature to low temperature for i in range( num_gen ): # loop at a given temperature of movements from neighbor to neighbor new_code = gen_neighbor(code, phase_unity, min(len(code) - 1, 3)) # new_code found by gen_neighbor new_psl = fast_autocorrelations(new_code, N, num_elem, min_fn) if new_psl < psl: # move to the new code if the peak sidelobe value improves code = new_code stuck_count = 0 if new_psl < best_psl: # update best_psl as well if it is improved on best_found = new_code best_psl = new_psl else: if acceptance_probability( psl, new_psl, temperature ) > random.random( ): # otherwise, only move to the code if a randomly generated number is greater than acceptance_probability code = new_code stuck_count = 0 else: stuck_count += 1 if stuck_count > upp_bound: # if we have been stuck for a while, break out of the loop count += 1 break if stuck_count > upp_bound: break temperature = cooling_schedule(temperature, init_temperature, 0.25, num_temp_gens, j) return (best_found, best_psl)