52 KiB
52 KiB
In [ ]:
import random
import copy
import matplotlib.pyplot as plt
import numpy as np
# Fix the random seed so results are reproducible
random.seed(666)
# --- PROBLEM DATA ---
# 100 items: (name, weight_kg, value_pln)
items = [
# --- Electronics and gadgets ---
("Gaming_laptop", 3.0, 800),
("Powerbank_XXL", 1.2, 220),
("Solar_charger", 0.8, 170),
("BT_headphones", 0.2, 100),
("Smartwatch", 0.1, 180),
("Pocket_drone", 0.9, 450),
("E-reader", 0.3, 200),
("Bluetooth_speaker", 0.5, 130),
("GoPro_camera", 0.4, 500),
("3m_cable_useless", 0.1, 10),
# --- Food and drinks ---
("Gummy_bears", 0.3, 40),
("Protein_bar", 0.2, 35),
("Instant_coffee", 0.4, 90),
("Tea_100bags", 0.3, 60),
("Dark_chocolate", 0.2, 50),
("Dried_mango", 0.3, 70),
("Water_5L", 5.0, 300),
("Water_1.5L", 1.5, 150),
("Stew_can", 0.8, 100),
("Army_MRE", 1.0, 200),
("Aged_cheese", 0.5, 80),
("Oat_cookies", 0.3, 45),
("Fudge_sweets", 0.2, 30),
("Mixed_nuts", 0.4, 95),
("Classic_jerky_sticks", 0.3, 70),
# --- Clothing ---
("Rain_jacket", 0.6, 300),
("Fleece_pullover", 0.8, 250),
("Pompon_beanie", 0.1, 60),
("Wool_socks", 0.1, 40),
("Trekking_gloves", 0.1, 55),
("Spare_tshirt", 0.2, 30),
("Cargo_pants", 0.5, 120),
("Camping_sandals", 0.6, 80),
("Multifunction_bandana",0.05, 25),
("Emergency_poncho", 0.2, 90),
# --- Tools and survival ---
("Swiss_army_knife", 0.2, 200),
("Headlamp", 0.15, 180),
("Fire_striker", 0.1, 100),
("Cord_50m", 0.8, 110),
("Duct_tape", 0.3, 120),
("Compass", 0.1, 90),
("Paper_map", 0.1, 80),
("Rescue_whistle", 0.02, 70),
("Signal_mirror", 0.05, 60),
("Folding_shovel", 0.7, 130),
# --- Camping and sleep ---
("Tent_2person", 3.5, 500),
("Down_sleeping_bag", 1.5, 400),
("Foam_sleeping_pad", 0.4, 150),
("Ultralight_hammock", 0.5, 200),
("Inflatable_pillow", 0.1, 60),
("Head_mosquito_net", 0.05, 40),
("Tarp_3x3", 0.8, 160),
# --- Hygiene and health ---
("First_aid_kit", 0.5, 250),
("Sunscreen", 0.2, 60),
("Mosquito_repellent", 0.15, 50),
("Dr_Bronner_soap", 0.3, 40),
("Travel_toothbrush", 0.05, 15),
("Microfiber_towel", 0.2, 70),
("Toilet_paper", 0.3, 20),
("Water_purification_tabs", 0.05, 100),
# --- Fun and hobbies ---
("Book_1000pages", 0.8, 150),
("Playing_cards", 0.1, 50),
("Frisbee", 0.2, 30),
("Binoculars_10x42", 0.7, 250),
("Travel_journal", 0.2, 40),
("Soprano_ukulele", 0.6, 120),
("Rubiks_cube", 0.1, 35),
("Magnetic_chess", 0.3, 60),
# --- Field kitchen ---
("Gas_stove", 0.4, 200),
("Gas_cartridge", 0.3, 80),
("Titanium_mess_tin", 0.2, 180),
("Thermal_mug", 0.3, 100),
("Folding_cutlery", 0.1, 50),
("Tea_strainer", 0.02, 10),
# --- Unusual but useful ---
("Selfie_stick", 0.2, 25),
("Sunglasses", 0.05, 150),
("Earplugs", 0.01, 30),
("Trash_bags_10x", 0.1, 15),
("Paperclips_XXL_set", 0.05, 5),
("Rubber_bands", 0.02, 5),
# --- Odd but someone packs them ---
("Lucky_mascot", 0.3, 10),
("U_travel_pillow", 0.3, 80),
("Rubber_hot_water_bottle", 0.4, 60),
("Folding_stool", 1.2, 140),
("Trekking_umbrella", 0.5, 70),
("Inflatable_flamingo", 0.4, 20),
("NRC_emergency_blanket", 0.05, 90),
("Waterproof_bag", 0.2, 110),
# --- Photography ---
("Mirrorless_camera", 1.5, 700),
("Zoom_lens", 0.8, 400),
("Carbon_tripod", 1.0, 300),
("Polarizing_filter", 0.05, 100),
("SD_card_256GB", 0.01, 120),
# --- Premium and luxury ---
("Garmin_watch", 0.08, 350),
("Filtering_bottle", 0.3, 180),
("Merino_underwear", 0.15, 130),
("Merino_socks_2x", 0.15, 100),
("Trekking_poles", 0.5, 160),
("Boot_gaiters", 0.2, 70),
("Inflatable_pouf", 0.3, 50),
]
BACKPACK_CAPACITY = 25 # kg
# Display the data
print(f"Number of items: {len(items)}")
print(f"Backpack capacity: {BACKPACK_CAPACITY} kg")
print(f"Total weight of all items: {sum(p[1] for p in items)} kg")
print(f"Total value of all items: {sum(p[2] for p in items)} PLN")In [ ]:
def generate_population(population_size, chromosome_length):
# Initialize a random starting population (binary 0/1 chromosomes).
population = []
for _ in range(population_size):
individual = [random.randint(0, 1) for _ in range(chromosome_length)]
population.append(individual)
return populationIn [ ]:
def calculate_fitness(individual, items, capacity):
# Objective function: maximize total value; overweight solutions get fitness 0.
weight = 0
value = 0
for i in range(len(individual)):
if individual[i] == 1:
weight += items[i][1]
value += items[i][2]
if weight > capacity:
return 0
return valueIn [ ]:
def calculate_weight(individual, items):
weight = 0
for i in range(len(individual)):
if individual[i] == 1:
weight += items[i][1]
return weightIn [ ]:
def roulette_selection(population, fitness_values):
# Roulette wheel selection — each individual has a selection
# probability proportional to its fitness.
total_fitness = sum(fitness_values)
if total_fitness == 0:
# If all fitness values are 0, pick randomly
return random.choice(population)
# Compute probabilities
probabilities = [f / total_fitness for f in fitness_values]
# Draw based on the probability distribution
r = random.random()
total = 0
for i, p in enumerate(probabilities):
total += p
if total >= r:
return population[i]
return population[-1]
def tournament_selection(population, fitness_values, tournament_size=3):
# Tournament selection — draw a group of individuals, pick the best of the group.
# Draw the indices of the tournament participants
indices = random.sample(range(len(population)), tournament_size)
# Find the best individual in the tournament
best_index = indices[0]
for idx in indices[1:]:
if fitness_values[idx] > fitness_values[best_index]:
best_index = idx
return population[best_index]
def rank_selection(population, fitness_values):
# Rank-based selection — sort by fitness, assign ranks, select proportionally to rank.
# Create (index, fitness) pairs and sort ascending
sorted_pairs = sorted(enumerate(fitness_values), key=lambda x: x[1])
# Assign ranks: worst = 1, best = N
ranks = [0] * len(population)
for rank, (index, _) in enumerate(sorted_pairs, start=1):
ranks[index] = rank
# Select proportionally to rank (like in roulette wheel)
total_ranks = sum(ranks)
r = random.random()
total = 0
for i, rank in enumerate(ranks):
total += rank / total_ranks
if total >= r:
return population[i]
return population[-1]In [ ]:
def crossover(parent_a, parent_b, p_crossover):
# Single-point crossover: exchange chromosome fragments between parents.
if random.random() > p_crossover:
# No crossover – offspring are copies of the parents
return copy.copy(parent_a), copy.copy(parent_b)
# Draw the crossover point (from 1 to N-1)
point = random.randint(1, len(parent_a) - 1)
child_a = parent_a[:point] + parent_b[point:]
child_b = parent_b[:point] + parent_a[point:]
return child_a, child_bIn [ ]:
def mutate(individual, p_mutation):
# Bit-flip mutation. Each gene flips (0->1 or 1->0) with probability p_mutation.
mutated = copy.copy(individual)
for i in range(len(mutated)):
if random.random() < p_mutation:
mutated[i] = 1 - mutated[i]
return mutatedIn [ ]:
def genetic_algorithm(
items,
capacity,
population_size=50,
n_iterations=100,
p_crossover=0.8,
p_mutation=0.05,
n_pairs=10,
offspring_per_pair=2,
n_remove=10,
selection_method="tournament",
tournament_size=3,
elitism=True,
seed=None
):
# seed fixes the reproducibility of the draws
# (the same sequence of results for the same parameters).
if seed is not None:
random.seed(seed)
# Chromosome length = number of available items.
length = len(items)
# --- STEP 1: Initialize the population ---
population = generate_population(population_size, length)
# Compute the population size change per iteration
new_per_iteration = n_pairs * offspring_per_pair
change = new_per_iteration - n_remove
print(f" New offspring/iter: {new_per_iteration}, "
f"removed/iter: {n_remove}, "
f"size change: {'+' if change > 0 else ''}{change}/iter")
# History for the plots
best_history = []
avg_history = []
size_history = []
# Remember the global best
global_best = None
global_best_f = 0
for iteration in range(n_iterations):
# Safety net: the population cannot drop below 2
if len(population) < 2:
print(f" Population died out at iteration {iteration}!")
break
# --- STEP 2: Evaluate fitness ---
fitness_values = [calculate_fitness(o, items, capacity) for o in population]
# Record statistics
best_f = max(fitness_values)
avg_f = sum(fitness_values) / len(fitness_values)
best_history.append(best_f)
avg_history.append(avg_f)
size_history.append(len(population))
# Update the global best
if best_f > global_best_f:
idx_best = fitness_values.index(best_f)
global_best = copy.copy(population[idx_best])
global_best_f = best_f
# --- STEP 3: Create offspring ---
# This part depends the most on the hyperparameters:
# selection_method, p_crossover, p_mutation, n_pairs, offspring_per_pair.
offspring = []
for _ in range(n_pairs):
# Parent selection
if selection_method == "roulette":
parent_a = roulette_selection(population, fitness_values)
parent_b = roulette_selection(population, fitness_values)
elif selection_method == "tournament":
parent_a = tournament_selection(population, fitness_values, tournament_size)
parent_b = tournament_selection(population, fitness_values, tournament_size)
elif selection_method == "rank":
parent_a = rank_selection(population, fitness_values)
parent_b = rank_selection(population, fitness_values)
else:
raise ValueError(f"Unknown selection method: {selection_method}")
# Crossover (always yields 2 children)
child_a, child_b = crossover(parent_a, parent_b, p_crossover)
# Mutation
child_a = mutate(child_a, p_mutation)
child_b = mutate(child_b, p_mutation)
# Take 1 or 2 children from the pair
offspring.append(child_a)
if offspring_per_pair == 2:
offspring.append(child_b)
# --- STEP 4: Remove individuals ---
# How many to actually remove (no more than population - 1)
actually_removed = min(n_remove, len(population) - 1)
if elitism:
# KEY POINT: elitism=True protects good individuals (we remove the weakest).
pairs = list(zip(fitness_values, population))
pairs.sort(key=lambda x: x[0])
pairs = pairs[actually_removed:]
population = [individual for (_, individual) in pairs]
else:
# KEY POINT: elitism=False does not protect the leader;
# random removal may lose the best solution.
indices_to_remove = set(random.sample(range(len(population)), actually_removed))
population = [
individual for idx, individual in enumerate(population)
if idx not in indices_to_remove
]
# --- STEP 5: Add the offspring ---
population.extend(offspring)
# --- STEP 6: Elitism ---
if elitism and global_best is not None:
new_fitness_values = [calculate_fitness(o, items, capacity)
for o in population]
if max(new_fitness_values) < global_best_f:
# Replace the worst individual with the global best
min_idx = new_fitness_values.index(min(new_fitness_values))
population[min_idx] = copy.copy(global_best)
# Final evaluation
fitness_values = [calculate_fitness(o, items, capacity) for o in population]
best_idx = fitness_values.index(max(fitness_values))
best_individual = population[best_idx]
best_fitness = fitness_values[best_idx]
# The global best overrides the result only in the elitist variant.
if elitism and global_best_f > best_fitness:
best_individual = global_best
best_fitness = global_best_f
return (best_individual, best_fitness,
best_history, avg_history, size_history)In [ ]:
def display_result(individual, items, capacity):
# Displays the backpack contents for a given individual.
print(f"\n{'='*55}")
print(f"{'BACKPACK CONTENTS':^55}")
print(f"{'='*55}")
print(f"{'Nr':<5}{'Name':<22}{'Weight (kg)':<12}{'Value (PLN)':<12}")
print("-" * 51)
total_weight = 0
total_value = 0
for i in range(len(individual)):
if individual[i] == 1:
name, weight, value = items[i]
print(f"{i+1:<5}{name:<22}{weight:<12}{value:<12}")
total_weight += weight
total_value += value
print("-" * 51)
print(f"{'TOTAL':<27}{total_weight:<12.1f}{total_value:<12}")
print(f"Backpack capacity: {capacity} kg")
print(f"Utilization: {total_weight:.1f}/{capacity} kg "
f"({100*total_weight/capacity:.1f}%)")
print(f"{'='*55}")
def plot_convergence(best_history, avg_history, title=""):
# Convergence plot of the genetic algorithm.
plt.figure(figsize=(10, 5))
plt.plot(best_history, label="Best fitness", linewidth=2)
plt.plot(avg_history, label="Average fitness", linewidth=1, alpha=0.7)
plt.xlabel("Generation (iteration)")
plt.ylabel("Fitness (backpack value)")
plt.title(f"Genetic algorithm convergence{' – ' + title if title else ''}")
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
def plot_population_size(size_history, title=""):
# Plot of the population size over time.
plt.figure(figsize=(10, 3))
plt.plot(size_history, label="Population size",
linewidth=2, color="green")
plt.xlabel("Generation (iteration)")
plt.ylabel("Number of individuals")
plt.title(f"Population size{' – ' + title if title else ''}")
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()In [ ]:
# --- BASELINE MODEL ---
# 10 pairs × 2 offspring = 20 new, we remove 20 → stable population
individual, fitness, hist_best, hist_avg, hist_size = genetic_algorithm(
items=items,
capacity=BACKPACK_CAPACITY,
population_size=50,
n_iterations=200,
p_crossover=0.8,
p_mutation=0.05,
n_pairs=10,
offspring_per_pair=2,
n_remove=20,
selection_method="tournament",
tournament_size=3,
elitism=True,
seed=666
)
print(f"\nBest fitness: {fitness} PLN")
print(f"Final population size: {hist_size[-1]}")
display_result(individual, items, BACKPACK_CAPACITY)
plot_convergence(hist_best, hist_avg, "Baseline model")In [ ]:
# --- ANALYSIS SETTINGS: fixed seed + randomness ---
# FIXED_SEED: fair comparison between hyperparameter variants (same draws).
FIXED_SEED = 666
# RANDOM_SEEDS: assessing result stability and sensitivity to randomness.
RANDOM_SEEDS = list(range(10))
def run_variant(parameters, seed):
# Common runner for all experiments — returns the metrics needed for analysis.
_, fitness, hist_best, _, hist_size = genetic_algorithm(
items=items,
capacity=BACKPACK_CAPACITY,
seed=seed,
**parameters
)
return fitness, hist_best, hist_size[-1]
def statistics(values):
# Basic statistics for the report: mean, std deviation and range [min, max].
arr = np.array(values, dtype=float)
return arr.mean(), arr.std(ddof=0), arr.min(), arr.max()
# --- EXPERIMENT 1: Mutation probability ---
mutation_rates = [0.001, 0.01, 0.05, 0.1, 0.3]
results_mutation_fixed = {}
results_mutation_random = {}
plt.figure(figsize=(12, 5))
for pm in mutation_rates:
parameters = dict(
population_size=50,
n_iterations=200,
p_crossover=0.8,
p_mutation=pm,
n_pairs=10,
offspring_per_pair=2,
n_remove=20,
selection_method="tournament",
elitism=True,
)
fitness_fixed, hist_best_fixed, _ = run_variant(parameters, FIXED_SEED)
results_mutation_fixed[pm] = fitness_fixed
random_fitness = [run_variant(parameters, s)[0] for s in RANDOM_SEEDS]
mean, std, minimum, maximum = statistics(random_fitness)
results_mutation_random[pm] = (mean, std, minimum, maximum)
plt.plot(hist_best_fixed, label=f"p_mut={pm} (fixed seed={fitness_fixed})")
plt.xlabel("Generation")
plt.ylabel("Best fitness")
plt.title("Experiment 1: Effect of the mutation probability (fixed seed)")
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print("\nSummary (fixed seed vs randomness):")
for pm in mutation_rates:
fs = results_mutation_fixed[pm]
mean, std, mn, mx = results_mutation_random[pm]
print(
f" p_mutation={pm:<6} -> fixed seed, fitness: {fs:<5} | "
f"random: mean={mean:.1f}, std={std:.1f}, min={mn:.0f}, max={mx:.0f}"
)In [ ]:
# --- EXPERIMENT 2: Crossover probability ---
crossover_rates = [0.0, 0.3, 0.6, 0.8, 1.0]
results_crossover_fixed = {}
results_crossover_random = {}
plt.figure(figsize=(12, 5))
for pc in crossover_rates:
parameters = dict(
population_size=50,
n_iterations=200,
p_crossover=pc,
p_mutation=0.05,
n_pairs=10,
offspring_per_pair=2,
n_remove=20,
selection_method="tournament",
elitism=True,
)
fitness_fixed, hist_best_fixed, _ = run_variant(parameters, FIXED_SEED)
results_crossover_fixed[pc] = fitness_fixed
random_fitness = [run_variant(parameters, s)[0] for s in RANDOM_SEEDS]
mean, std, minimum, maximum = statistics(random_fitness)
results_crossover_random[pc] = (mean, std, minimum, maximum)
plt.plot(hist_best_fixed, label=f"p_cross={pc} (fixed seed={fitness_fixed})")
plt.xlabel("Generation")
plt.ylabel("Best fitness")
plt.title("Experiment 2: Effect of the crossover probability (fixed seed)")
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print("\nSummary (fixed seed vs randomness):")
for pc in crossover_rates:
fs = results_crossover_fixed[pc]
mean, std, mn, mx = results_crossover_random[pc]
print(
f" p_crossover={pc:<4} -> fixed seed, fitness: {fs:<5} | "
f"random: mean={mean:.1f}, std={std:.1f}, min={mn:.0f}, max={mx:.0f}"
)In [ ]:
# --- EXPERIMENT 3: Population size ---
population_sizes = [10, 30, 50, 100, 200]
results_population_fixed = {}
results_population_random = {}
plt.figure(figsize=(12, 5))
for ps in population_sizes:
# Scale the replacement parameters proportionally to the population
pairs = max(2, ps // 5)
remove = pairs * 2 # stable population
parameters = dict(
population_size=ps,
n_iterations=200,
p_crossover=0.8,
p_mutation=0.05,
n_pairs=pairs,
offspring_per_pair=2,
n_remove=remove,
selection_method="tournament",
elitism=True,
)
fitness_fixed, hist_best_fixed, _ = run_variant(parameters, FIXED_SEED)
results_population_fixed[ps] = fitness_fixed
random_fitness = [run_variant(parameters, s)[0] for s in RANDOM_SEEDS]
mean, std, minimum, maximum = statistics(random_fitness)
results_population_random[ps] = (mean, std, minimum, maximum)
plt.plot(hist_best_fixed, label=f"pop={ps} (fixed seed={fitness_fixed})")
plt.xlabel("Generation")
plt.ylabel("Best fitness")
plt.title("Experiment 3: Effect of the population size (fixed seed)")
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print("\nSummary (fixed seed vs randomness):")
for ps in population_sizes:
fs = results_population_fixed[ps]
mean, std, mn, mx = results_population_random[ps]
print(
f" population_size={ps:<4} -> fixed seed, fitness: {fs:<5} | "
f"random: mean={mean:.1f}, std={std:.1f}, min={mn:.0f}, max={mx:.0f}"
)In [ ]:
# --- EXPERIMENT 4: Selection method ---
methods = ["roulette", "tournament", "rank"]
results_selection_fixed = {}
results_selection_random = {}
plt.figure(figsize=(12, 5))
for method in methods:
parameters = dict(
population_size=50,
n_iterations=200,
p_crossover=0.8,
p_mutation=0.05,
n_pairs=10,
offspring_per_pair=2,
n_remove=20,
selection_method=method,
elitism=True,
)
fitness_fixed, hist_best_fixed, _ = run_variant(parameters, FIXED_SEED)
results_selection_fixed[method] = fitness_fixed
random_fitness = [run_variant(parameters, s)[0] for s in RANDOM_SEEDS]
mean, std, minimum, maximum = statistics(random_fitness)
results_selection_random[method] = (mean, std, minimum, maximum)
plt.plot(hist_best_fixed, label=f"{method} (fixed seed={fitness_fixed})")
plt.xlabel("Generation")
plt.ylabel("Best fitness")
plt.title("Experiment 4: Comparing selection methods (fixed seed)")
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print("\nSummary (fixed seed vs randomness):")
for method in methods:
fs = results_selection_fixed[method]
mean, std, mn, mx = results_selection_random[method]
print(
f" selection {method:<12} -> fixed seed, fitness: {fs:<5} | "
f"random: mean={mean:.1f}, std={std:.1f}, min={mn:.0f}, max={mx:.0f}"
)In [ ]:
# --- EXPERIMENT 5: Population dynamics ---
# We test different combinations of the generational replacement parameters.
# Format: (description, n_pairs, offspring_per_pair, n_remove)
scenarios = [
("stable: 3 pairs×2, remove=6", 3, 2, 6),
("stable: 10 pairs×2, remove=20", 10, 2, 20),
("growing: 10 pairs×2, remove=5", 10, 2, 5),
("shrinking: 3 pairs×1, remove=6", 3, 1, 6),
("shrinking: 5 pairs×2, remove=15", 5, 2, 15),
]
results_dynamics_fixed = {}
results_dynamics_random = {}
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8), sharex=True)
for desc, np_, opp, nr in scenarios:
parameters = dict(
population_size=50,
n_iterations=200,
p_crossover=0.8,
p_mutation=0.05,
n_pairs=np_,
offspring_per_pair=opp,
n_remove=nr,
selection_method="tournament",
elitism=True,
)
fitness_fixed, hist_best_fixed, size_fixed = run_variant(parameters, FIXED_SEED)
results_dynamics_fixed[desc] = (fitness_fixed, size_fixed)
random_runs = [run_variant(parameters, s) for s in RANDOM_SEEDS]
random_fitness = [x[0] for x in random_runs]
random_sizes = [x[2] for x in random_runs]
mean_f, std_f, min_f, max_f = statistics(random_fitness)
mean_r, std_r, min_r, max_r = statistics(random_sizes)
results_dynamics_random[desc] = (mean_f, std_f, min_f, max_f, mean_r, std_r, min_r, max_r)
ax1.plot(hist_best_fixed, label=f"{desc} (fixed f={fitness_fixed})")
_, hist_size_fixed = genetic_algorithm(
items=items,
capacity=BACKPACK_CAPACITY,
seed=FIXED_SEED,
**parameters
)[3:5]
ax2.plot(hist_size_fixed, label=desc)
ax1.set_ylabel("Best fitness")
ax1.set_title("Experiment 5: Population dynamics — progress (fixed seed)")
ax1.legend(fontsize=8)
ax1.grid(True, alpha=0.3)
ax2.set_xlabel("Generation")
ax2.set_ylabel("Population size")
ax2.set_title("Experiment 5: Population dynamics — size (fixed seed)")
ax2.legend(fontsize=8)
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print("\nSummary (fixed seed vs randomness):")
for desc, _, _, _ in scenarios:
fs, rs = results_dynamics_fixed[desc]
mean_f, std_f, min_f, max_f, mean_r, std_r, min_r, max_r = results_dynamics_random[desc]
print(
f" {desc:<33} -> fixed seed: f={fs}, size={rs} | "
f"random f: mean={mean_f:.1f}, std={std_f:.1f}, min={min_f:.0f}, max={max_f:.0f}; "
f"size: mean={mean_r:.1f}, std={std_r:.1f}, min={min_r:.0f}, max={max_r:.0f}"
)In [ ]:
# --- EXPERIMENT 6: Elitism ---
plt.figure(figsize=(12, 5))
results_elitism_fixed = {}
results_elitism_random = {}
for elit in [True, False]:
parameters = dict(
population_size=20,
n_iterations=200,
p_crossover=0.8,
p_mutation=0.05,
n_pairs=8,
offspring_per_pair=2,
n_remove=16,
selection_method="tournament",
elitism=elit,
)
fitness_fixed, hist_best_fixed, _ = run_variant(parameters, FIXED_SEED)
label = "with elitism" if elit else "without elitism"
results_elitism_fixed[label] = fitness_fixed
random_fitness = [run_variant(parameters, s)[0] for s in RANDOM_SEEDS]
mean, std, minimum, maximum = statistics(random_fitness)
results_elitism_random[label] = (mean, std, minimum, maximum)
plt.plot(hist_best_fixed, label=f"{label} (fixed seed={fitness_fixed})")
plt.xlabel("Generation")
plt.ylabel("Best fitness")
plt.title("Experiment 6: Effect of elitism (fixed seed)")
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print("\nSummary (fixed seed vs randomness):")
for e in ["with elitism", "without elitism"]:
fs = results_elitism_fixed[e]
mean, std, mn, mx = results_elitism_random[e]
print(
f" {e:<20} -> fixed seed: f={fs:<5} | "
f"random: mean={mean:.1f}, std={std:.1f}, min={mn:.0f}, max={mx:.0f}"
)In [ ]:
# --- SUMMARY TABLE: fixed seed + randomness ---
print(f"\n{'='*110}")
print(f"{'AGGREGATED EXPERIMENT RESULTS (FIXED SEED VS RANDOMNESS)':^110}")
print(f"{'='*110}")
print(f"\n--- Experiment 1: Mutation probability ---")
for pm in mutation_rates:
fs = results_mutation_fixed[pm]
mean, std, mn, mx = results_mutation_random[pm]
print(f" p_mutation={pm:<6} -> seed={fs:<5} | random: mean={mean:.1f}, std={std:.1f}, min={mn:.0f}, max={mx:.0f}")
print(f"\n--- Experiment 2: Crossover probability ---")
for pc in crossover_rates:
fs = results_crossover_fixed[pc]
mean, std, mn, mx = results_crossover_random[pc]
print(f" p_crossover={pc:<4} -> seed={fs:<5} | random: mean={mean:.1f}, std={std:.1f}, min={mn:.0f}, max={mx:.0f}")
print(f"\n--- Experiment 3: Population size ---")
for ps in population_sizes:
fs = results_population_fixed[ps]
mean, std, mn, mx = results_population_random[ps]
print(f" population_size={ps:<4} -> seed={fs:<5} | random: mean={mean:.1f}, std={std:.1f}, min={mn:.0f}, max={mx:.0f}")
print(f"\n--- Experiment 4: Selection method ---")
for m in methods:
fs = results_selection_fixed[m]
mean, std, mn, mx = results_selection_random[m]
print(f" selection {m:<12} -> seed={fs:<5} | random: mean={mean:.1f}, std={std:.1f}, min={mn:.0f}, max={mx:.0f}")
print(f"\n--- Experiment 5: Population dynamics ---")
for desc, _, _, _ in scenarios:
fs, rs = results_dynamics_fixed[desc]
mean_f, std_f, min_f, max_f, mean_r, std_r, min_r, max_r = results_dynamics_random[desc]
print(
f" {desc:<33} -> seed: f={fs}, size={rs} | "
f"random f: mean={mean_f:.1f}, std={std_f:.1f}, min={min_f:.0f}, max={max_f:.0f}; "
f"size: mean={mean_r:.1f}, std={std_r:.1f}, min={min_r:.0f}, max={max_r:.0f}"
)
print(f"\n--- Experiment 6: Elitism ---")
for e in ["with elitism", "without elitism"]:
fs = results_elitism_fixed[e]
mean, std, mn, mx = results_elitism_random[e]
print(f" {e:<20} -> seed={fs:<5} | random: mean={mean:.1f}, std={std:.1f}, min={mn:.0f}, max={mx:.0f}")In [ ]:
individual_opt, fitness_opt, hist_best_opt, hist_avg_opt, hist_size_opt = genetic_algorithm(
items=items,
capacity=BACKPACK_CAPACITY,
population_size=100, # <- change this
n_iterations=300, # <- change this
p_crossover=0.8, # <- change this
p_mutation=0.05, # <- change this
n_pairs=2, # <- change this (number of parent pairs)
offspring_per_pair=2, # <- change this (1 or 2)
n_remove=4, # <- change this (how many weakest to remove)
selection_method="tournament", # <- change this
tournament_size=3, # <- change this
elitism=False, # <- change this
seed=666
)
print(f"\nYour optimized fitness: {fitness_opt} PLN")
print(f"Final population size: {hist_size_opt[-1]}")
display_result(individual_opt, items, BACKPACK_CAPACITY)
plot_convergence(hist_best_opt, hist_avg_opt, "Your optimized model")
plot_population_size(hist_size_opt, "Your optimized model")