Skip to content

Commit 8e91a5c

Browse files
[pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
1 parent 632be90 commit 8e91a5c

File tree

1 file changed

+37
-14
lines changed

1 file changed

+37
-14
lines changed

genetic_algorithm/knapsack.py

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,24 @@
77

88
# =========================== Problem setup: Knapsack ===========================
99

10-
KNAPSACK_N_ITEMS = 42 # Number of items in the knapsack problem
11-
KNAPSACK_VALUE_RANGE = (10, 100) # Range of item values
12-
KNAPSACK_WEIGHT_RANGE = (5, 50) # Range of item weights
13-
KNAPSACK_CAPACITY_RATIO = 0.5 # Capacity as a fraction of total weight
10+
KNAPSACK_N_ITEMS = 42 # Number of items in the knapsack problem
11+
KNAPSACK_VALUE_RANGE = (10, 100) # Range of item values
12+
KNAPSACK_WEIGHT_RANGE = (5, 50) # Range of item weights
13+
KNAPSACK_CAPACITY_RATIO = 0.5 # Capacity as a fraction of total weight
14+
1415

1516
@dataclass
1617
class Item:
1718
value: int
1819
weight: int
1920

20-
def generate_knapsack_instance(n_items: int, value_range: tuple[int, int], weight_range: tuple[int, int], capacity_ratio=float) -> tuple[list[Item], int]:
21+
22+
def generate_knapsack_instance(
23+
n_items: int,
24+
value_range: tuple[int, int],
25+
weight_range: tuple[int, int],
26+
capacity_ratio=float,
27+
) -> tuple[list[Item], int]:
2128
"""Generates a random knapsack problem instance."""
2229
items = []
2330
for _ in range(n_items):
@@ -28,8 +35,13 @@ def generate_knapsack_instance(n_items: int, value_range: tuple[int, int], weigh
2835
capacity = int(sum(it.weight for it in items) * capacity_ratio)
2936
return items, capacity
3037

31-
items, capacity = generate_knapsack_instance(n_items=KNAPSACK_N_ITEMS, value_range=KNAPSACK_VALUE_RANGE, weight_range=KNAPSACK_WEIGHT_RANGE, capacity_ratio=KNAPSACK_CAPACITY_RATIO)
3238

39+
items, capacity = generate_knapsack_instance(
40+
n_items=KNAPSACK_N_ITEMS,
41+
value_range=KNAPSACK_VALUE_RANGE,
42+
weight_range=KNAPSACK_WEIGHT_RANGE,
43+
capacity_ratio=KNAPSACK_CAPACITY_RATIO,
44+
)
3345

3446

3547
# ============================== GA Representation ==============================
@@ -45,7 +57,8 @@ def generate_knapsack_instance(n_items: int, value_range: tuple[int, int], weigh
4557

4658
OVERWEIGHT_PENALTY_FACTOR = 10
4759

48-
Genome = list[int] # An index list where 1 means item is included, 0 means excluded
60+
Genome = list[int] # An index list where 1 means item is included, 0 means excluded
61+
4962

5063
def evaluate(genome: Genome, items: list[Item], capacity: int) -> tuple[int, int]:
5164
"""Evaluation function - calculates the fitness of each candidate based on total value and weight."""
@@ -57,13 +70,15 @@ def evaluate(genome: Genome, items: list[Item], capacity: int) -> tuple[int, int
5770
total_weight += item.weight
5871
if total_weight > capacity:
5972
# Penalize overweight solutions: return small value scaled by overflow
60-
overflow = (total_weight - capacity)
73+
overflow = total_weight - capacity
6174
total_value = max(0, total_value - overflow * OVERWEIGHT_PENALTY_FACTOR)
6275
return total_value, total_weight
6376

77+
6478
def random_genome(n: int) -> Genome:
6579
"""Generates a random genome of length n."""
66-
return [random.randint(0,1) for _ in range(n)]
80+
return [random.randint(0, 1) for _ in range(n)]
81+
6782

6883
def selection(population: list[Genome], fitnesses: list[int], k: int) -> Genome:
6984
"""Performs tournament selection to choose genomes from the population.
@@ -73,21 +88,24 @@ def selection(population: list[Genome], fitnesses: list[int], k: int) -> Genome:
7388
get_fitness = lambda x: x[1]
7489
return max(contenders, key=get_fitness)[0][:]
7590

91+
7692
def crossover(a: Genome, b: Genome, p_crossover: float) -> tuple[Genome, Genome]:
7793
"""Performs single-point crossover between two genomes.
7894
Note that other crossover strategies exist such as two-point crossover, uniform crossover, etc."""
7995
min_length = min(len(a), len(b))
8096
if random.random() > p_crossover or min_length < 2:
8197
return a[:], b[:]
8298
cutoff_point = random.randint(1, min_length - 1)
83-
return a[:cutoff_point]+b[cutoff_point:], b[:cutoff_point]+a[cutoff_point:]
99+
return a[:cutoff_point] + b[cutoff_point:], b[:cutoff_point] + a[cutoff_point:]
100+
84101

85102
def mutation(g: Genome, p_mutation: int) -> Genome:
86103
"""Performs bit-flip mutation on a genome.
87104
Note that other mutation strategies exist such as swap mutation, scramble mutation, etc.
88105
"""
89106
return [(1 - gene) if random.random() < p_mutation else gene for gene in g]
90107

108+
91109
def run_ga(
92110
items: list[Item],
93111
capacity: int,
@@ -120,8 +138,10 @@ def run_ga(
120138

121139
# Elitism
122140
get_fitness = lambda i: fitnesses[i]
123-
elite_indices = sorted(range(pop_size), key=get_fitness, reverse=True)[:elitism] # Sort the population by fitness and get the top `elitism` indices
124-
elites = [population[i][:] for i in elite_indices] # Make nepo babies
141+
elite_indices = sorted(range(pop_size), key=get_fitness, reverse=True)[
142+
:elitism
143+
] # Sort the population by fitness and get the top `elitism` indices
144+
elites = [population[i][:] for i in elite_indices] # Make nepo babies
125145

126146
# New generation
127147
new_pop = elites[:]
@@ -145,12 +165,15 @@ def run_ga(
145165
"avg_history": avg_history,
146166
}
147167

168+
148169
result = run_ga(items, capacity)
149170

150171
best_items = [items[i] for i, bit in enumerate(result["best_genome"]) if bit == 1]
151172

152-
print(f"Knapsack capacity: {result["capacity"]}")
153-
print(f"Best solution: value = {result["best_value"]}, weight = {result["best_weight"]}")
173+
print(f"Knapsack capacity: {result['capacity']}")
174+
print(
175+
f"Best solution: value = {result['best_value']}, weight = {result['best_weight']}"
176+
)
154177

155178
# print("Items included in the best solution:", best_items)
156179

0 commit comments

Comments
 (0)