From ad7f706c90f77f3ff54ad9becb2829034239cf0b Mon Sep 17 00:00:00 2001 From: Ruben-FreddyLoafers Date: Tue, 28 Oct 2025 12:20:58 +0100 Subject: [PATCH] fixed selection; static population size; fixed whitespaces in bits --- 03_euler_gen_alg/main.py | 70 +++++++++++++++++++-------------------- 03_euler_gen_alg/utils.py | 35 +++++++++----------- 2 files changed, 50 insertions(+), 55 deletions(-) diff --git a/03_euler_gen_alg/main.py b/03_euler_gen_alg/main.py index ac7470e..5fa9d73 100644 --- a/03_euler_gen_alg/main.py +++ b/03_euler_gen_alg/main.py @@ -17,16 +17,16 @@ import utils POPULATION_SIZE = 10 SELECTION_SIZE = (POPULATION_SIZE * 7) // 10 # 70% of population, rounded down for selection -XOVER_PAIR_SIZE = (POPULATION_SIZE - SELECTION_SIZE) // 2 # pairs needed for crossover +XOVER_PAIR_SIZE = (POPULATION_SIZE - SELECTION_SIZE) XOVER_POINT = 3 # 4th position MUTATION_BITS = POPULATION_SIZE // 2 fitness = 2 -fitness_arr = [2,2,2,2,2,2,2,2,2,2] -grey_pop = [] -bin_pop = [] # 32 Bit Binary -bin_pop_params = [] -new_pop = [] # 32 Bit Grey-Code as String +fitness_arr = [0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1] +gray_pop = [] # 32 Bit-Binary as String +bin_pop = [] # 32 Bit-Binary as String +bin_pop_params = [] # Arrays with 4 Binary values of 8s +new_pop = [] # 32 Bit Gray-Code as String e_func = lambda x: np.e**x @@ -35,10 +35,10 @@ def generate_random_population(num=POPULATION_SIZE): # Generate new population for _ in range(num): - grey = format(random.getrandbits(32), '32b') - grey_pop.append(grey) + gray = format(random.getrandbits(32), '032b') + gray_pop.append(gray) - bin_str = utils.grey_to_bin(grey) + bin_str = utils.gray_to_bin(gray) bin_pop.append(bin_str) params = [bin_str[i:i+7] for i in range(0, 31, 8)] @@ -63,19 +63,20 @@ def eval_fitness(bin_pop_values): # Create polynomial function with current parameters approx = lambda x: a*x**3 + b*x**2 + c*x + d - fitness = quadratic_error(e_func, approx, 6) + quad_error = quadratic_error(e_func, approx, 6) - inverse_fitness = 1 / fitness + inverse_fitness = 1 / quad_error # the bigger the error, the worse the fitness print("Fitness: " + str(inverse_fitness)) # debugging fitness_arr.append(inverse_fitness) # save fitness - # save params # already saved in grey_pop + # save params # already saved in gray_pop return fitness_arr -def select(population, fitness_arr): +def select(fitness_arr): fitness_arr_copy = fitness_arr.copy() sum_of_fitness = sum(fitness_arr_copy) - while len(population) < SELECTION_SIZE: + selected_pop = [] + while len(selected_pop) < SELECTION_SIZE: # Roulette logic roulette_num = random.random() is_chosen = False @@ -84,8 +85,8 @@ def select(population, fitness_arr): for i, fitness in enumerate(fitness_arr_copy): cumulative_p += fitness / sum_of_fitness if roulette_num < cumulative_p: - # Add the 32 Bit individual in grey code to population - population.append(grey_pop[i]) + # Add the 32 Bit individual in gray code to population + selected_pop.append(gray_pop[i]) # Calc new sum of fitness fitness_arr_copy.pop(i) @@ -93,16 +94,16 @@ def select(population, fitness_arr): is_chosen = True # break while loop break # break for loop - return population - -def xover(population, xover_rate=XOVER_PAIR_SIZE): + return selected_pop + +# TODO: xover the old population not the new one +def xover(population): """Performs crossover on pairs of individuals from population.""" offspring = [] # Process pairs while we have enough individuals and haven't reached xover_rate - pair_count = 0 i = 0 - while i < len(population) - 1 and pair_count < xover_rate: + while i < len(population) - 1 and len(population) + len(offspring) < 10: parent_a = population[i] parent_b = population[i + 1] @@ -111,9 +112,11 @@ def xover(population, xover_rate=XOVER_PAIR_SIZE): offspring_b = parent_b[:XOVER_POINT] + parent_a[XOVER_POINT:] offspring.extend([offspring_a, offspring_b]) - pair_count += 1 i += 2 # Move to next pair + if len(offspring) > 3: + offspring.pop() + return offspring def mutate(population, mutation_rate): @@ -131,42 +134,37 @@ def mutate(population, mutation_rate): population[random_num] = ''.join(bits) # will work because lists are passed by reference def main(): - global grey_pop, bin_pop, bin_pop_params, new_pop, fitness, fitness_arr + global gray_pop, bin_pop, bin_pop_params, new_pop, fitness, fitness_arr bin_pop_values = generate_random_population(POPULATION_SIZE) - new_pop = grey_pop.copy() # Make a copy of the populated grey_pop iteration = 0 - # TODO: Have to decide with probability somehow - while not np.all(np.array(fitness_arr) <= 1): # Continue while any fitness value is > 1 - print("Iteration: " + str(iteration)) + while not np.all((1 / np.array(fitness_arr)) <= 1): # Continue while any fitness value is > 1 + print("Iteration: " + str(iteration)) # debugging # Evaluate fitness fitness_arr = eval_fitness(bin_pop_values) # Selection - new_pop = select(new_pop, fitness_arr) # Alters new_pop + new_pop = select(fitness_arr) # assigns # Crossover - offspring = xover(new_pop, XOVER_PAIR_SIZE) + offspring = xover(new_pop) new_pop.extend(offspring) # Add offspring to population # Mutation mutate(new_pop, MUTATION_BITS) - # Ensure population size stays constant - new_pop = new_pop[(len(new_pop) - POPULATION_SIZE):len(new_pop)] - # Update populations for next generation - grey_pop = new_pop.copy() + gray_pop = new_pop.copy() bin_pop_values = [] - for grey_bin_string in grey_pop: - bin_str = utils.grey_to_bin(grey_bin_string) + for gray_bin_string in gray_pop: + bin_str = utils.gray_to_bin(gray_bin_string) params = [bin_str[i:i+7] for i in range(0, 31, 8)] bin_pop_values.append(params) # print(new_pop) - time.sleep(1.0) + # time.sleep(0.5) iteration += 1 return 0 diff --git a/03_euler_gen_alg/utils.py b/03_euler_gen_alg/utils.py index 5767a41..f813140 100644 --- a/03_euler_gen_alg/utils.py +++ b/03_euler_gen_alg/utils.py @@ -1,13 +1,8 @@ -def clean_binary_string(binary_str, length=32): - """Clean and format a binary string to ensure proper format""" - # Remove any whitespace and ensure proper length - cleaned = ''.join(binary_str.split()) - return cleaned.zfill(length) - -def grey_to_bin(gray): - """Convert Gray code to binary, operating on the integer value directly""" - # Clean and format input string - gray = clean_binary_string(gray, 32) +def gray_to_bin(gray): + """ + Convert Gray code to binary, operating on the integer value directly. + :returns: 32-bit String + """ try: num = int(gray, 2) # Convert string to integer mask = num @@ -16,25 +11,27 @@ def grey_to_bin(gray): num ^= mask return format(num, '032b') # Always return 32-bit string except ValueError as e: - print(f"Error in grey_to_bin with input: '{gray}'") + print(f"Error in gray_to_bin with input: '{gray}'") raise e -def bin_to_grey(binary): - """Convert binary to Gray code using XOR with right shift""" - # Clean and format input string - binary = clean_binary_string(binary, 32) +def bin_to_gray(binary): + """ + Convert binary to Gray code using XOR with right shift + :returns: 32-bit String + """ try: num = int(binary, 2) # Convert string to integer gray = num ^ (num >> 1) # Gray code formula: G = B ^ (B >> 1) return format(gray, '032b') # Always return 32-bit string except ValueError as e: - print(f"Error in bin_to_grey with input: '{binary}'") + print(f"Error in bin_to_gray with input: '{binary}'") raise e def bin_to_param(binary, q_min = 0.0, q_max = 10.0): - """Convert one binary string to float parameter in range [q_min, q_max]""" - # Clean and format input string - binary = clean_binary_string(binary, 7) # 7 bits for parameters + """ + Convert one binary string to float parameter in range [q_min, q_max] + :returns: float + """ try: val = int(binary, 2) / 25.5 * 10 # conversion to 0.0 - 10.0 float # Scale to range [q_min, q_max]