Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit adf7758

Browse files
Merge pull request #241 from ahmedfgad/github-actions
Testing stop_criteria
2 parents 9137dd4 + 210c345 commit adf7758

File tree

2 files changed

+286
-13
lines changed

2 files changed

+286
-13
lines changed

‎pygad/pygad.py

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,23 +1171,40 @@ def __init__(self,
11711171
# reach_{target_fitness}: Stop if the target fitness value is reached.
11721172
# saturate_{num_generations}: Stop if the fitness value does not change (saturates) for the given number of generations.
11731173
criterion = stop_criteria.split("_")
1174-
if len(criterion) == 2:
1175-
stop_word = criterion[0]
1176-
number = criterion[1]
1177-
1178-
if stop_word in self.supported_stop_words:
1179-
pass
1180-
else:
1181-
self.valid_parameters = False
1182-
raise ValueError(f"In the 'stop_criteria' parameter, the supported stop words are '{self.supported_stop_words}' but '{stop_word}' found.")
1174+
stop_word = criterion[0]
1175+
# criterion[1] might be a single or multiple numbers.
1176+
number = criterion[1:]
1177+
if stop_word in self.supported_stop_words:
1178+
pass
1179+
else:
1180+
self.valid_parameters = False
1181+
raise ValueError(f"In the 'stop_criteria' parameter, the supported stop words are '{self.supported_stop_words}' but '{stop_word}' found.")
11831182

1183+
if len(criterion) == 2:
1184+
# There is only a single number.
1185+
number = number[0]
11841186
if number.replace(".", "").isnumeric():
11851187
number = float(number)
11861188
else:
11871189
self.valid_parameters = False
11881190
raise ValueError(f"The value following the stop word in the 'stop_criteria' parameter must be a number but the value ({number}) of type {type(number)} found.")
11891191

11901192
self.stop_criteria.append([stop_word, number])
1193+
elif len(criterion) > 2:
1194+
if stop_word == 'reach':
1195+
pass
1196+
else:
1197+
self.valid_parameters = False
1198+
raise ValueError(f"Passing multiple numbers following the keyword in the 'stop_criteria' parameter is expected only with the 'reach' keyword but the keyword ({stop_word}) found.")
1199+
1200+
for idx, num in enumerate(number):
1201+
if num.replace(".", "").isnumeric():
1202+
number[idx] = float(num)
1203+
else:
1204+
self.valid_parameters = False
1205+
raise ValueError(f"The value(s) following the stop word in the 'stop_criteria' parameter must be numeric but the value ({num}) of type {type(num)} found.")
1206+
1207+
self.stop_criteria.append([stop_word] + number)
11911208

11921209
else:
11931210
self.valid_parameters = False
@@ -2133,15 +2150,56 @@ def run(self):
21332150
if not self.stop_criteria is None:
21342151
for criterion in self.stop_criteria:
21352152
if criterion[0] == "reach":
2136-
if max(self.last_generation_fitness) >= criterion[1]:
2153+
# Single-objective problem.
2154+
if type(self.last_generation_fitness[0]) in GA.supported_int_float_types:
2155+
if max(self.last_generation_fitness) >= criterion[1]:
2156+
stop_run = True
2157+
break
2158+
# Multi-objective problem.
2159+
elif type(self.last_generation_fitness[0]) in [list, tuple, numpy.ndarray]:
2160+
# Validate the value passed to the criterion.
2161+
if len(criterion[1:]) == 1:
2162+
# There is a single value used across all the objectives.
2163+
pass
2164+
elif len(criterion[1:]) > 1:
2165+
# There are multiple values. The number of values must be equal to the number of objectives.
2166+
if len(criterion[1:]) == len(self.last_generation_fitness[0]):
2167+
pass
2168+
else:
2169+
self.valid_parameters = False
2170+
raise ValueError(f"When the the 'reach' keyword is used with the 'stop_criteria' parameter for solving a multi-objective problem, then the number of numeric values following the keyword can be:\n1) A single numeric value to be used across all the objective functions.\n2) A number of numeric values equal to the number of objective functions.\nBut the value {criterion} found with {len(criterion)-1} numeric values which is not equal to the number of objective functions {len(self.last_generation_fitness[0])}.")
2171+
21372172
stop_run = True
2138-
break
2173+
for obj_idx in range(len(self.last_generation_fitness[0])):
2174+
# Use the objective index to return the proper value for the criterion.
2175+
2176+
if len(criterion[1:]) == len(self.last_generation_fitness[0]):
2177+
reach_fitness_value = criterion[obj_idx + 1]
2178+
elif len(criterion[1:]) == 1:
2179+
reach_fitness_value = criterion[1]
2180+
2181+
if max(self.last_generation_fitness[:, obj_idx]) >= reach_fitness_value:
2182+
pass
2183+
else:
2184+
stop_run = False
2185+
break
21392186
elif criterion[0] == "saturate":
21402187
criterion[1] = int(criterion[1])
21412188
if (self.generations_completed >= criterion[1]):
2142-
if (self.best_solutions_fitness[self.generations_completed - criterion[1]] - self.best_solutions_fitness[self.generations_completed - 1]) == 0:
2189+
# Single-objective problem.
2190+
if type(self.last_generation_fitness[0]) in GA.supported_int_float_types:
2191+
if (self.best_solutions_fitness[self.generations_completed - criterion[1]] - self.best_solutions_fitness[self.generations_completed - 1]) == 0:
2192+
stop_run = True
2193+
break
2194+
# Multi-objective problem.
2195+
elif type(self.last_generation_fitness[0]) in [list, tuple, numpy.ndarray]:
21432196
stop_run = True
2144-
break
2197+
for obj_idx in range(len(self.last_generation_fitness[0])):
2198+
if (self.best_solutions_fitness[self.generations_completed - criterion[1]][obj_idx] - self.best_solutions_fitness[self.generations_completed - 1][obj_idx]) == 0:
2199+
pass
2200+
else:
2201+
stop_run = False
2202+
break
21452203

21462204
if stop_run:
21472205
break

‎tests/test_stop_criteria.py

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import pygad
2+
import numpy
3+
4+
actual_num_fitness_calls_default_keep = 0
5+
actual_num_fitness_calls_no_keep = 0
6+
actual_num_fitness_calls_keep_elitism = 0
7+
actual_num_fitness_calls_keep_parents = 0
8+
9+
num_generations = 100
10+
sol_per_pop = 10
11+
num_parents_mating = 5
12+
13+
def multi_objective_problem(keep_elitism=1,
14+
keep_parents=-1,
15+
fitness_batch_size=None,
16+
stop_criteria=None,
17+
parent_selection_type='sss',
18+
mutation_type="random",
19+
mutation_percent_genes="default",
20+
multi_objective=False):
21+
22+
function_inputs1 = [4,-2,3.5,5,-11,-4.7] # Function 1 inputs.
23+
function_inputs2 = [-2,0.7,-9,1.4,3,5] # Function 2 inputs.
24+
desired_output1 = 50 # Function 1 output.
25+
desired_output2 = 30 # Function 2 output.
26+
27+
def fitness_func_batch_multi(ga_instance, solution, solution_idx):
28+
f = []
29+
for sol in solution:
30+
output1 = numpy.sum(sol*function_inputs1)
31+
output2 = numpy.sum(sol*function_inputs2)
32+
fitness1 = 1.0 / (numpy.abs(output1 - desired_output1) + 0.000001)
33+
fitness2 = 1.0 / (numpy.abs(output2 - desired_output2) + 0.000001)
34+
f.append([fitness1, fitness2])
35+
return f
36+
37+
def fitness_func_no_batch_multi(ga_instance, solution, solution_idx):
38+
output1 = numpy.sum(solution*function_inputs1)
39+
output2 = numpy.sum(solution*function_inputs2)
40+
fitness1 = 1.0 / (numpy.abs(output1 - desired_output1) + 0.000001)
41+
fitness2 = 1.0 / (numpy.abs(output2 - desired_output2) + 0.000001)
42+
return [fitness1, fitness2]
43+
44+
def fitness_func_batch_single(ga_instance, solution, solution_idx):
45+
f = []
46+
for sol in solution:
47+
output = numpy.sum(solution*function_inputs1)
48+
fitness = 1.0 / (numpy.abs(output - desired_output1) + 0.000001)
49+
f.append(fitness)
50+
return f
51+
52+
def fitness_func_no_batch_single(ga_instance, solution, solution_idx):
53+
output = numpy.sum(solution*function_inputs1)
54+
fitness = 1.0 / (numpy.abs(output - desired_output1) + 0.000001)
55+
return fitness
56+
57+
if fitness_batch_size is None or (type(fitness_batch_size) in pygad.GA.supported_int_types and fitness_batch_size == 1):
58+
if multi_objective == True:
59+
fitness_func = fitness_func_no_batch_multi
60+
else:
61+
fitness_func = fitness_func_no_batch_single
62+
elif (type(fitness_batch_size) in pygad.GA.supported_int_types and fitness_batch_size > 1):
63+
if multi_objective == True:
64+
fitness_func = fitness_func_batch_multi
65+
else:
66+
fitness_func = fitness_func_batch_single
67+
68+
ga_optimizer = pygad.GA(num_generations=num_generations,
69+
sol_per_pop=sol_per_pop,
70+
num_genes=6,
71+
num_parents_mating=num_parents_mating,
72+
fitness_func=fitness_func,
73+
fitness_batch_size=fitness_batch_size,
74+
mutation_type=mutation_type,
75+
mutation_percent_genes=mutation_percent_genes,
76+
keep_elitism=keep_elitism,
77+
keep_parents=keep_parents,
78+
stop_criteria=stop_criteria,
79+
parent_selection_type=parent_selection_type,
80+
suppress_warnings=True)
81+
82+
ga_optimizer.run()
83+
84+
return ga_optimizer.generations_completed, ga_optimizer.best_solutions_fitness, ga_optimizer.last_generation_fitness, stop_criteria
85+
86+
def test_number_calls_fitness_function_default_keep():
87+
multi_objective_problem()
88+
89+
def test_number_calls_fitness_function_stop_criteria_reach(multi_objective=False,
90+
fitness_batch_size=None,
91+
num=10):
92+
generations_completed, best_solutions_fitness, last_generation_fitness, stop_criteria = multi_objective_problem(multi_objective=multi_objective,
93+
fitness_batch_size=fitness_batch_size,
94+
stop_criteria=f"reach_{num}")
95+
# Verify that the GA stops when meeting the criterion.
96+
criterion = stop_criteria.split('_')
97+
stop_word = criterion[0]
98+
if generations_completed < num_generations:
99+
if stop_word == 'reach':
100+
if len(criterion) > 2:
101+
# multi-objective problem.
102+
for idx, num in enumerate(criterion[1:]):
103+
criterion[idx + 1] = float(num)
104+
else:
105+
criterion[1] = float(criterion[1])
106+
107+
# Single-objective
108+
if type(last_generation_fitness[0]) in pygad.GA.supported_int_float_types:
109+
assert max(last_generation_fitness) >= criterion[1]
110+
# Multi-objective
111+
elif type(last_generation_fitness[0]) in [list, tuple, numpy.ndarray]:
112+
# Validate the value passed to the criterion.
113+
if len(criterion[1:]) == 1:
114+
# There is a single value used across all the objectives.
115+
pass
116+
elif len(criterion[1:]) > 1:
117+
# There are multiple values. The number of values must be equal to the number of objectives.
118+
if len(criterion[1:]) == len(last_generation_fitness[0]):
119+
pass
120+
else:
121+
raise ValueError("Error")
122+
123+
for obj_idx in range(len(last_generation_fitness[0])):
124+
# Use the objective index to return the proper value for the criterion.
125+
if len(criterion[1:]) == len(last_generation_fitness[0]):
126+
reach_fitness_value = criterion[obj_idx + 1]
127+
elif len(criterion[1:]) == 1:
128+
reach_fitness_value = criterion[1]
129+
130+
assert max(last_generation_fitness[:, obj_idx]) >= reach_fitness_value
131+
132+
def test_number_calls_fitness_function_stop_criteria_saturate(multi_objective=False,
133+
fitness_batch_size=None,
134+
num=5):
135+
generations_completed, best_solutions_fitness, last_generation_fitness, stop_criteria = multi_objective_problem(multi_objective=multi_objective,
136+
fitness_batch_size=fitness_batch_size,
137+
stop_criteria=f"saturate_{num}")
138+
# Verify that the GA stops when meeting the criterion.
139+
criterion = stop_criteria.split('_')
140+
stop_word = criterion[0]
141+
number = criterion[1]
142+
if generations_completed < num_generations:
143+
if stop_word == 'saturate':
144+
number = int(number)
145+
if type(last_generation_fitness[0]) in pygad.GA.supported_int_float_types:
146+
assert best_solutions_fitness[generations_completed - number] == best_solutions_fitness[generations_completed - 1]
147+
elif type(last_generation_fitness[0]) in [list, tuple, numpy.ndarray]:
148+
for obj_idx in range(len(best_solutions_fitness[0])):
149+
assert best_solutions_fitness[generations_completed - number][obj_idx] == best_solutions_fitness[generations_completed - 1][obj_idx]
150+
151+
if __name__ == "__main__":
152+
#### Single-objective problem with a single numeric value with stop_criteria.
153+
print()
154+
test_number_calls_fitness_function_default_keep()
155+
print()
156+
test_number_calls_fitness_function_stop_criteria_reach()
157+
print()
158+
test_number_calls_fitness_function_stop_criteria_reach(num=2)
159+
print()
160+
test_number_calls_fitness_function_stop_criteria_saturate()
161+
print()
162+
test_number_calls_fitness_function_stop_criteria_saturate(num=2)
163+
print()
164+
test_number_calls_fitness_function_stop_criteria_reach(fitness_batch_size=4)
165+
print()
166+
test_number_calls_fitness_function_stop_criteria_reach(fitness_batch_size=4,
167+
num=2)
168+
print()
169+
test_number_calls_fitness_function_stop_criteria_saturate(fitness_batch_size=4)
170+
print()
171+
test_number_calls_fitness_function_stop_criteria_saturate(fitness_batch_size=4,
172+
num=2)
173+
print()
174+
175+
176+
#### Multi-objective problem with a single numeric value with stop_criteria.
177+
test_number_calls_fitness_function_stop_criteria_reach(multi_objective=True)
178+
print()
179+
test_number_calls_fitness_function_stop_criteria_reach(multi_objective=True,
180+
num=2)
181+
print()
182+
test_number_calls_fitness_function_stop_criteria_saturate(multi_objective=True)
183+
print()
184+
test_number_calls_fitness_function_stop_criteria_saturate(multi_objective=True,
185+
num=2)
186+
print()
187+
test_number_calls_fitness_function_stop_criteria_reach(multi_objective=True,
188+
fitness_batch_size=4)
189+
print()
190+
test_number_calls_fitness_function_stop_criteria_reach(multi_objective=True,
191+
fitness_batch_size=4,
192+
num=2)
193+
print()
194+
test_number_calls_fitness_function_stop_criteria_saturate(multi_objective=True,
195+
fitness_batch_size=4)
196+
print()
197+
test_number_calls_fitness_function_stop_criteria_saturate(multi_objective=True,
198+
fitness_batch_size=4,
199+
num=50)
200+
print()
201+
202+
203+
#### Multi-objective problem with multiple numeric values with stop_criteria.
204+
test_number_calls_fitness_function_stop_criteria_reach(multi_objective=True)
205+
print()
206+
test_number_calls_fitness_function_stop_criteria_reach(multi_objective=True,
207+
num="2_5")
208+
print()
209+
test_number_calls_fitness_function_stop_criteria_reach(multi_objective=True,
210+
fitness_batch_size=4)
211+
print()
212+
test_number_calls_fitness_function_stop_criteria_reach(multi_objective=True,
213+
fitness_batch_size=4,
214+
num="10_20")
215+

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /