Skip to content

Commit bf5367e

Browse files
committed
Refactor the pygad.py script
1 parent 74536c7 commit bf5367e

File tree

12 files changed

+2737
-2497
lines changed

12 files changed

+2737
-2497
lines changed

.github/workflows/scorecard.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ jobs:
6161
- name: "Upload artifact"
6262
uses: actions/upload-artifact@97a0fba1372883ab732affbe8f94b823f91727db # v3.pre.node20
6363
with:
64-
name: SARIF file
64+
name: SARIF-file
6565
path: results.sarif
6666
retention-days: 5
6767

examples/example_summary.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import pygad
2+
import numpy
3+
4+
function_inputs = [4,-2,3.5,5,-11,-4.7] # Function inputs.
5+
desired_output = 44 # Function output.
6+
7+
def fitness_func(ga_instance, solution, solution_idx):
8+
output = numpy.sum(solution*function_inputs)
9+
fitness = 1.0 / (numpy.abs(output - desired_output) + 0.000001)
10+
return fitness
11+
12+
num_generations = 100 # Number of generations.
13+
num_parents_mating = 10 # Number of solutions to be selected as parents in the mating pool.
14+
15+
sol_per_pop = 20 # Number of solutions in the population.
16+
num_genes = len(function_inputs)
17+
18+
ga_instance = pygad.GA(num_generations=num_generations,
19+
num_parents_mating=num_parents_mating,
20+
sol_per_pop=sol_per_pop,
21+
num_genes=num_genes,
22+
fitness_func=fitness_func)
23+
24+
# Running the GA to optimize the parameters of the function.
25+
ga_instance.run()
26+
27+
ga_instance.plot_fitness()
28+
29+
ga_instance.summary()
30+

pygad/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
from .pygad import * # Relative import.
22

3-
__version__ = "3.5.0"
3+
__version__ = "3.6.0"

pygad/helper/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
from pygad.helper import unique
22
from pygad.helper import misc
33

4-
__version__ = "1.2.0"
4+
__version__ = "1.3.0"

pygad/helper/misc.py

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,235 @@
99

1010
class Helper:
1111

12+
def summary(self,
13+
line_length=70,
14+
fill_character=" ",
15+
line_character="-",
16+
line_character2="=",
17+
columns_equal_len=False,
18+
print_step_parameters=True,
19+
print_parameters_summary=True):
20+
"""
21+
The summary() method prints a summary of the PyGAD lifecycle in a Keras style.
22+
The parameters are:
23+
line_length: An integer representing the length of the single line in characters.
24+
fill_character: A character to fill the lines.
25+
line_character: A character for creating a line separator.
26+
line_character2: A secondary character to create a line separator.
27+
columns_equal_len: The table rows are split into equal-sized columns or split subjective to the width needed.
28+
print_step_parameters: Whether to print extra parameters about each step inside the step. If print_step_parameters=False and print_parameters_summary=True, then the parameters of each step are printed at the end of the table.
29+
print_parameters_summary: Whether to print parameters summary at the end of the table. If print_step_parameters=False, then the parameters of each step are printed at the end of the table too.
30+
"""
31+
32+
summary_output = ""
33+
34+
def fill_message(msg, line_length=line_length, fill_character=fill_character):
35+
num_spaces = int((line_length - len(msg))/2)
36+
num_spaces = int(num_spaces / len(fill_character))
37+
msg = "{spaces}{msg}{spaces}".format(
38+
msg=msg, spaces=fill_character * num_spaces)
39+
return msg
40+
41+
def line_separator(line_length=line_length, line_character=line_character):
42+
num_characters = int(line_length / len(line_character))
43+
return line_character * num_characters
44+
45+
def create_row(columns, line_length=line_length, fill_character=fill_character, split_percentages=None):
46+
filled_columns = []
47+
if split_percentages is None:
48+
split_percentages = [int(100/len(columns))] * 3
49+
columns_lengths = [int((split_percentages[idx] * line_length) / 100)
50+
for idx in range(len(split_percentages))]
51+
for column_idx, column in enumerate(columns):
52+
current_column_length = len(column)
53+
extra_characters = columns_lengths[column_idx] - \
54+
current_column_length
55+
filled_column = column + fill_character * extra_characters
56+
filled_columns.append(filled_column)
57+
58+
return "".join(filled_columns)
59+
60+
def print_parent_selection_params():
61+
nonlocal summary_output
62+
m = f"Number of Parents: {self.num_parents_mating}"
63+
self.logger.info(m)
64+
summary_output = summary_output + m + "\n"
65+
if self.parent_selection_type == "tournament":
66+
m = f"K Tournament: {self.K_tournament}"
67+
self.logger.info(m)
68+
summary_output = summary_output + m + "\n"
69+
70+
def print_fitness_params():
71+
nonlocal summary_output
72+
if not self.fitness_batch_size is None:
73+
m = f"Fitness batch size: {self.fitness_batch_size}"
74+
self.logger.info(m)
75+
summary_output = summary_output + m + "\n"
76+
77+
def print_crossover_params():
78+
nonlocal summary_output
79+
if not self.crossover_probability is None:
80+
m = f"Crossover probability: {self.crossover_probability}"
81+
self.logger.info(m)
82+
summary_output = summary_output + m + "\n"
83+
84+
def print_mutation_params():
85+
nonlocal summary_output
86+
if not self.mutation_probability is None:
87+
m = f"Mutation Probability: {self.mutation_probability}"
88+
self.logger.info(m)
89+
summary_output = summary_output + m + "\n"
90+
if self.mutation_percent_genes == "default":
91+
m = f"Mutation Percentage: {self.mutation_percent_genes}"
92+
self.logger.info(m)
93+
summary_output = summary_output + m + "\n"
94+
# Number of mutation genes is already showed above.
95+
m = f"Mutation Genes: {self.mutation_num_genes}"
96+
self.logger.info(m)
97+
summary_output = summary_output + m + "\n"
98+
m = f"Random Mutation Range: ({self.random_mutation_min_val}, {self.random_mutation_max_val})"
99+
self.logger.info(m)
100+
summary_output = summary_output + m + "\n"
101+
if not self.gene_space is None:
102+
m = f"Gene Space: {self.gene_space}"
103+
self.logger.info(m)
104+
summary_output = summary_output + m + "\n"
105+
m = f"Mutation by Replacement: {self.mutation_by_replacement}"
106+
self.logger.info(m)
107+
summary_output = summary_output + m + "\n"
108+
m = f"Allow Duplicated Genes: {self.allow_duplicate_genes}"
109+
self.logger.info(m)
110+
summary_output = summary_output + m + "\n"
111+
112+
def print_on_generation_params():
113+
nonlocal summary_output
114+
if not self.stop_criteria is None:
115+
m = f"Stop Criteria: {self.stop_criteria}"
116+
self.logger.info(m)
117+
summary_output = summary_output + m + "\n"
118+
119+
def print_params_summary():
120+
nonlocal summary_output
121+
m = f"Population Size: ({self.sol_per_pop}, {self.num_genes})"
122+
self.logger.info(m)
123+
summary_output = summary_output + m + "\n"
124+
m = f"Number of Generations: {self.num_generations}"
125+
self.logger.info(m)
126+
summary_output = summary_output + m + "\n"
127+
m = f"Initial Population Range: ({self.init_range_low}, {self.init_range_high})"
128+
self.logger.info(m)
129+
summary_output = summary_output + m + "\n"
130+
131+
if not print_step_parameters:
132+
print_fitness_params()
133+
134+
if not print_step_parameters:
135+
print_parent_selection_params()
136+
137+
if self.keep_elitism != 0:
138+
m = f"Keep Elitism: {self.keep_elitism}"
139+
self.logger.info(m)
140+
summary_output = summary_output + m + "\n"
141+
else:
142+
m = f"Keep Parents: {self.keep_parents}"
143+
self.logger.info(m)
144+
summary_output = summary_output + m + "\n"
145+
m = f"Gene DType: {self.gene_type}"
146+
self.logger.info(m)
147+
summary_output = summary_output + m + "\n"
148+
149+
if not print_step_parameters:
150+
print_crossover_params()
151+
152+
if not print_step_parameters:
153+
print_mutation_params()
154+
155+
if not print_step_parameters:
156+
print_on_generation_params()
157+
158+
if not self.parallel_processing is None:
159+
m = f"Parallel Processing: {self.parallel_processing}"
160+
self.logger.info(m)
161+
summary_output = summary_output + m + "\n"
162+
if not self.random_seed is None:
163+
m = f"Random Seed: {self.random_seed}"
164+
self.logger.info(m)
165+
summary_output = summary_output + m + "\n"
166+
m = f"Save Best Solutions: {self.save_best_solutions}"
167+
self.logger.info(m)
168+
summary_output = summary_output + m + "\n"
169+
m = f"Save Solutions: {self.save_solutions}"
170+
self.logger.info(m)
171+
summary_output = summary_output + m + "\n"
172+
173+
m = line_separator(line_character=line_character)
174+
self.logger.info(m)
175+
summary_output = summary_output + m + "\n"
176+
m = fill_message("PyGAD Lifecycle")
177+
self.logger.info(m)
178+
summary_output = summary_output + m + "\n"
179+
m = line_separator(line_character=line_character2)
180+
self.logger.info(m)
181+
summary_output = summary_output + m + "\n"
182+
183+
lifecycle_steps = ["on_start()", "Fitness Function", "On Fitness", "Parent Selection", "On Parents",
184+
"Crossover", "On Crossover", "Mutation", "On Mutation", "On Generation", "On Stop"]
185+
lifecycle_functions = [self.on_start, self.fitness_func, self.on_fitness, self.select_parents, self.on_parents,
186+
self.crossover, self.on_crossover, self.mutation, self.on_mutation, self.on_generation, self.on_stop]
187+
lifecycle_functions = [getattr(
188+
lifecycle_func, '__name__', "None") for lifecycle_func in lifecycle_functions]
189+
lifecycle_functions = [lifecycle_func + "()" if lifecycle_func !=
190+
"None" else "None" for lifecycle_func in lifecycle_functions]
191+
lifecycle_output = ["None", "(1)", "None", f"({self.num_parents_mating}, {self.num_genes})", "None",
192+
f"({self.num_parents_mating}, {self.num_genes})", "None", f"({self.num_parents_mating}, {self.num_genes})", "None", "None", "None"]
193+
lifecycle_step_parameters = [None, print_fitness_params, None, print_parent_selection_params, None,
194+
print_crossover_params, None, print_mutation_params, None, print_on_generation_params, None]
195+
196+
if not columns_equal_len:
197+
max_lengthes = [max(list(map(len, lifecycle_steps))), max(
198+
list(map(len, lifecycle_functions))), max(list(map(len, lifecycle_output)))]
199+
split_percentages = [
200+
int((column_len / sum(max_lengthes)) * 100) for column_len in max_lengthes]
201+
else:
202+
split_percentages = None
203+
204+
header_columns = ["Step", "Handler", "Output Shape"]
205+
header_row = create_row(
206+
header_columns, split_percentages=split_percentages)
207+
m = header_row
208+
self.logger.info(m)
209+
summary_output = summary_output + m + "\n"
210+
m = line_separator(line_character=line_character2)
211+
self.logger.info(m)
212+
summary_output = summary_output + m + "\n"
213+
214+
for lifecycle_idx in range(len(lifecycle_steps)):
215+
lifecycle_column = [lifecycle_steps[lifecycle_idx],
216+
lifecycle_functions[lifecycle_idx], lifecycle_output[lifecycle_idx]]
217+
if lifecycle_column[1] == "None":
218+
continue
219+
lifecycle_row = create_row(
220+
lifecycle_column, split_percentages=split_percentages)
221+
m = lifecycle_row
222+
self.logger.info(m)
223+
summary_output = summary_output + m + "\n"
224+
if print_step_parameters:
225+
if not lifecycle_step_parameters[lifecycle_idx] is None:
226+
lifecycle_step_parameters[lifecycle_idx]()
227+
m = line_separator(line_character=line_character)
228+
self.logger.info(m)
229+
summary_output = summary_output + m + "\n"
230+
231+
m = line_separator(line_character=line_character2)
232+
self.logger.info(m)
233+
summary_output = summary_output + m + "\n"
234+
if print_parameters_summary:
235+
print_params_summary()
236+
m = line_separator(line_character=line_character2)
237+
self.logger.info(m)
238+
summary_output = summary_output + m + "\n"
239+
return summary_output
240+
12241
def change_population_dtype_and_round(self,
13242
population):
14243
"""

0 commit comments

Comments
 (0)