Repository navigation
Expand file tree
/
Copy pathtest_best_solution.py
More file actions
374 lines (318 loc) · 14.4 KB
/
Copy pathtest_best_solution.py
File metadata and controls
374 lines (318 loc) · 14.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
import numpy
import pygad
import random
# Global constants for testing
num_generations = 100
num_parents_mating = 5
sol_per_pop = 10
num_genes = 3
random_seed = 42
def fitness_func(ga_instance, solution, solution_idx):
"""Single-objective fitness function."""
return numpy.sum(solution**2)
def fitness_func_multi(ga_instance, solution, solution_idx):
"""Multi-objective fitness function."""
return [numpy.sum(solution**2), numpy.sum(solution)]
def test_best_solution_consistency_single_objective():
"""
Test best_solution() consistency for single-objective optimization.
"""
ga_instance = pygad.GA(num_generations=num_generations,
num_parents_mating=num_parents_mating,
fitness_func=fitness_func,
sol_per_pop=sol_per_pop,
num_genes=num_genes,
random_seed=random_seed,
suppress_warnings=True
)
ga_instance.run()
# Call with last_generation_fitness
sol1, fitness1, idx1 = ga_instance.best_solution(pop_fitness=ga_instance.last_generation_fitness)
# Call without pop_fitness
sol2, fitness2, idx2 = ga_instance.best_solution()
assert numpy.array_equal(sol1, sol2)
assert fitness1 == fitness2
assert idx1 == idx2
print("test_best_solution_consistency_single_objective passed.")
def test_best_solution_consistency_multi_objective():
"""
Test best_solution() consistency for multi-objective optimization.
"""
ga_instance = pygad.GA(num_generations=num_generations,
num_parents_mating=num_parents_mating,
fitness_func=fitness_func_multi,
sol_per_pop=sol_per_pop,
num_genes=num_genes,
random_seed=random_seed,
parent_selection_type="nsga2",
suppress_warnings=True
)
ga_instance.run()
# Call with last_generation_fitness
sol1, fitness1, idx1 = ga_instance.best_solution(pop_fitness=ga_instance.last_generation_fitness)
# Call without pop_fitness
sol2, fitness2, idx2 = ga_instance.best_solution()
assert numpy.array_equal(sol1, sol2)
assert numpy.array_equal(fitness1, fitness2)
assert idx1 == idx2
print("test_best_solution_consistency_multi_objective passed.")
def test_best_solution_before_run():
"""
Test best_solution() consistency before run() is called.
"""
ga_instance = pygad.GA(num_generations=num_generations,
num_parents_mating=num_parents_mating,
fitness_func=fitness_func,
sol_per_pop=sol_per_pop,
num_genes=num_genes,
random_seed=random_seed,
suppress_warnings=True
)
# Before run(), last_generation_fitness is None
# We can still call best_solution(), it should call cal_pop_fitness()
sol2, fitness2, idx2 = ga_instance.best_solution()
# Now cal_pop_fitness() should match ga_instance.best_solution() output if we pass it
pop_fitness = ga_instance.cal_pop_fitness()
sol1, fitness1, idx1 = ga_instance.best_solution(pop_fitness=pop_fitness)
assert numpy.array_equal(sol1, sol2)
assert fitness1 == fitness2
assert idx1 == idx2
print("test_best_solution_before_run passed.")
def test_best_solution_with_save_solutions():
"""
Test best_solution() consistency when save_solutions=True.
This tests the caching mechanism in cal_pop_fitness().
"""
ga_instance = pygad.GA(num_generations=num_generations,
num_parents_mating=num_parents_mating,
fitness_func=fitness_func,
sol_per_pop=sol_per_pop,
num_genes=num_genes,
random_seed=random_seed,
save_solutions=True,
suppress_warnings=True
)
ga_instance.run()
# Call with last_generation_fitness
sol1, fitness1, idx1 = ga_instance.best_solution(pop_fitness=ga_instance.last_generation_fitness)
# Call without pop_fitness (this will trigger cal_pop_fitness which uses saved solutions)
sol2, fitness2, idx2 = ga_instance.best_solution()
assert numpy.array_equal(sol1, sol2)
assert fitness1 == fitness2
assert idx1 == idx2
print("test_best_solution_with_save_solutions passed.")
def test_best_solution_with_save_best_solutions():
"""
Test best_solution() consistency when save_best_solutions=True.
"""
ga_instance = pygad.GA(num_generations=num_generations,
num_parents_mating=num_parents_mating,
fitness_func=fitness_func,
sol_per_pop=sol_per_pop,
num_genes=num_genes,
random_seed=random_seed,
save_best_solutions=True,
suppress_warnings=True
)
ga_instance.run()
# Call with last_generation_fitness
sol1, fitness1, idx1 = ga_instance.best_solution(pop_fitness=ga_instance.last_generation_fitness)
# Call without pop_fitness
sol2, fitness2, idx2 = ga_instance.best_solution()
assert numpy.array_equal(sol1, sol2)
assert fitness1 == fitness2
assert idx1 == idx2
print("test_best_solution_with_save_best_solutions passed.")
def test_best_solution_with_keep_elitism():
"""
Test best_solution() consistency when keep_elitism > 0.
"""
ga_instance = pygad.GA(num_generations=num_generations,
num_parents_mating=num_parents_mating,
fitness_func=fitness_func,
sol_per_pop=sol_per_pop,
num_genes=num_genes,
random_seed=random_seed,
keep_elitism=2,
suppress_warnings=True
)
ga_instance.run()
# Call with last_generation_fitness
sol1, fitness1, idx1 = ga_instance.best_solution(pop_fitness=ga_instance.last_generation_fitness)
# Call without pop_fitness
sol2, fitness2, idx2 = ga_instance.best_solution()
assert numpy.array_equal(sol1, sol2)
assert fitness1 == fitness2
assert idx1 == idx2
print("test_best_solution_with_keep_elitism passed.")
def test_best_solution_with_keep_parents():
"""
Test best_solution() consistency when keep_parents > 0.
Note: keep_parents is ignored if keep_elitism > 0 (default is 1).
So this tests the case where keep_parents is passed but effectively ignored by population update,
yet we check if best_solution() still works consistently.
"""
ga_instance = pygad.GA(num_generations=num_generations,
num_parents_mating=num_parents_mating,
fitness_func=fitness_func,
sol_per_pop=sol_per_pop,
num_genes=num_genes,
random_seed=random_seed,
keep_parents=2,
suppress_warnings=True
)
ga_instance.run()
# Call with last_generation_fitness
sol1, fitness1, idx1 = ga_instance.best_solution(pop_fitness=ga_instance.last_generation_fitness)
# Call without pop_fitness
sol2, fitness2, idx2 = ga_instance.best_solution()
assert numpy.array_equal(sol1, sol2)
assert fitness1 == fitness2
assert idx1 == idx2
print("test_best_solution_with_keep_parents passed.")
def test_best_solution_with_keep_parents_elitism_0():
"""
Test best_solution() consistency when keep_parents > 0 and keep_elitism = 0.
This ensures the 'keep_parents' logic in cal_pop_fitness is exercised.
"""
ga_instance = pygad.GA(num_generations=num_generations,
num_parents_mating=num_parents_mating,
fitness_func=fitness_func,
sol_per_pop=sol_per_pop,
num_genes=num_genes,
random_seed=random_seed,
keep_elitism=0,
keep_parents=2,
suppress_warnings=True
)
ga_instance.run()
# Call with last_generation_fitness
sol1, fitness1, idx1 = ga_instance.best_solution(pop_fitness=ga_instance.last_generation_fitness)
# Call without pop_fitness
sol2, fitness2, idx2 = ga_instance.best_solution()
assert numpy.array_equal(sol1, sol2)
assert fitness1 == fitness2
assert idx1 == idx2
print("test_best_solution_with_keep_parents_elitism_0 passed.")
def test_best_solution_pop_fitness_validation():
"""
Test validation of the pop_fitness parameter in best_solution().
Note: num_generations=1 is used for speed as evolution is not needed.
sol_per_pop=5 is used to provide a small population for testing invalid lengths.
"""
ga_instance = pygad.GA(num_generations=1,
num_parents_mating=1,
fitness_func=fitness_func,
sol_per_pop=5,
num_genes=3,
suppress_warnings=True
)
# Test invalid type
try:
ga_instance.best_solution(pop_fitness="invalid")
except ValueError as e:
assert "expected to be list, tuple, or numpy.ndarray" in str(e)
print("Validation: Invalid type caught.")
# Test invalid length
try:
ga_instance.best_solution(pop_fitness=[1, 2, 3]) # Length 3, but sol_per_pop is 5
except ValueError as e:
assert "must match the length of the 'self.population' attribute" in str(e)
print("Validation: Invalid length caught.")
def test_best_solution_single_objective_tie():
"""
Test best_solution() when there is a tie in fitness values.
It should return the first solution with the maximum fitness.
Note: sol_per_pop=5 must match the length of the manual pop_fitness array below.
num_generations=1 is sufficient for testing selection logic.
"""
ga_instance = pygad.GA(num_generations=1,
num_parents_mating=1,
fitness_func=fitness_func,
sol_per_pop=5,
num_genes=3,
suppress_warnings=True
)
# Mock fitness with a tie at index 1 and 3
pop_fitness = numpy.array([10, 50, 20, 50, 5])
sol, fitness, idx = ga_instance.best_solution(pop_fitness=pop_fitness)
assert fitness == 50
assert idx == 1 # First occurrence
print("test_best_solution_single_objective_tie passed.")
def test_best_solution_with_parallel_processing():
"""
Test best_solution() with parallel_processing enabled.
Note: num_generations=5 is used to ensure the initial population and first generation
trigger parallel fitness calculation.
"""
ga_instance = pygad.GA(num_generations=5,
num_parents_mating=2,
fitness_func=fitness_func,
sol_per_pop=10,
num_genes=3,
random_seed=random_seed,
parallel_processing=["thread", 2],
suppress_warnings=True
)
# best_solution() should work and trigger cal_pop_fitness() internally
sol, fitness, idx = ga_instance.best_solution()
assert sol is not None
assert fitness is not None
print("test_best_solution_with_parallel_processing passed.")
def test_best_solution_with_fitness_batch_size():
"""
Test best_solution() with fitness_batch_size > 1.
Note: num_generations=5 and sol_per_pop=10 provide enough work for batch processing.
"""
def fitness_func_batch(ga_instance, solutions, indices):
return [numpy.sum(s**2) for s in solutions]
ga_instance = pygad.GA(num_generations=5,
num_parents_mating=2,
fitness_func=fitness_func_batch,
sol_per_pop=10,
num_genes=3,
random_seed=random_seed,
fitness_batch_size=2,
suppress_warnings=True
)
sol, fitness, idx = ga_instance.best_solution()
assert sol is not None
assert fitness is not None
print("test_best_solution_with_fitness_batch_size passed.")
def test_best_solution_pop_fitness_types():
"""
Test best_solution() with different types for the pop_fitness parameter.
Note: sol_per_pop=3 must match the length of fitness_vals below.
num_generations=1 is sufficient for this type-check test.
"""
ga_instance = pygad.GA(num_generations=1,
num_parents_mating=1,
fitness_func=fitness_func,
sol_per_pop=3,
num_genes=3,
suppress_warnings=True
)
fitness_vals = [1.0, 5.0, 2.0]
# Test list
_, _, idx_list = ga_instance.best_solution(pop_fitness=fitness_vals)
# Test tuple
_, _, idx_tuple = ga_instance.best_solution(pop_fitness=tuple(fitness_vals))
# Test numpy array
_, _, idx_ndarray = ga_instance.best_solution(pop_fitness=numpy.array(fitness_vals))
assert idx_list == idx_tuple == idx_ndarray == 1
print("test_best_solution_pop_fitness_types passed.")
if __name__ == "__main__":
test_best_solution_consistency_single_objective()
test_best_solution_consistency_multi_objective()
test_best_solution_before_run()
test_best_solution_with_save_solutions()
test_best_solution_with_save_best_solutions()
test_best_solution_with_keep_elitism()
test_best_solution_with_keep_parents()
test_best_solution_with_keep_parents_elitism_0()
test_best_solution_pop_fitness_validation()
test_best_solution_single_objective_tie()
test_best_solution_with_parallel_processing()
test_best_solution_with_fitness_batch_size()
test_best_solution_pop_fitness_types()
print("\nAll tests passed!")