8000 New parameters added init_range_low and init_range_high · rifat17/GeneticAlgorithmPython@5a6fa14 · GitHub
[go: up one dir, main page]

Skip to content

Commit 5a6fa14

Browse files
authored
New parameters added init_range_low and init_range_high
* The attributes are moved from the class scope to the instance scope. * Raising a `ValueError` exception on passing incorrect values to the parameters. * Two new parameters are added (`init_rand_high` and `init_rand_high`) allowing the user to customize the range from which the genes values in the initial population are selected. * The code object `__code__` of the passed fitness function is checked to ensure it has the right number of parameters.
1 parent 7caec55 commit 5a6fa14

File tree

1 file changed

+81
-77
lines changed

1 file changed

+81
-77
lines changed

pygad.py

Lines changed: 81 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -4,51 +4,13 @@
44
import pickle
55

66
class GA:
7-
# Parameters of the genetic algorithm.
8-
sol_per_pop = None # Number of solutions in the population.
9-
num_parents_mating = None # Number of solutions to be selected as parents in the mating pool.
10-
num_generations = None # Number of generations.
11-
pop_size = None # Population size = (number of chromosomes, number of genes per chromosome)
12-
keep_parents = -1 # If 0, this means the parents of the current populaiton will not be used at all in the next population. If -1, this means all parents in the current population will be used in the next population. If set to a value > 0, then the specified value refers to the number of parents in the current population to be used in the next population. In some cases, the parents are of high quality and thus we do not want to loose such some high quality solutions. If some parent selection operators like roulette wheel selection (RWS), the parents may not be of high quality and thus keeping the parents might degarde the quality of the population.
13-
14-
fitness_func = None
15-
16-
# Parameters about parent selection.
17-
parent_selection_type = None # Type of parent selection.
18-
select_parents = None # Refers to a method that selects the parents based on the parent selection type specified in parent_selection_type.
19-
20-
K_tournament = None # For tournament selection, a parent is selected out of K randomly selected solutions.
21-
22-
population = None # A NumPy array holding the opulation.
23-
24-
crossover_type = None # Type of the crossover opreator.
25-
crossover = None # A method that applies the crossover operator based on the selected type of crossover in the crossover_type property.
26-
27-
mutation_type = None # Type of the mutation opreator.
28-
mutation = None # A method that applies the mutation operator based on the selected type of mutation in the mutation_type property.
29-
30-
best_solution_fitness = [] # A list holding the fitness value of the best solution for each generation.
31-
32-
# Parameters of the function to be optimized.
33-
function_inputs = None # Inputs of the function to be optimized.
34-
function_output = None # Desired outuput of the function.
35-
num_genes = None
36-
37-
# Mutation parameters.
38-
mutation_percent_genes=None # Percentage of genes to mutate. This parameter has no action if the parameter mutation_num_genes exists.
39-
mutation_num_genes=None # Number of genes to mutate. If the parameter mutation_num_genes exists, then no need for the parameter mutation_percent_genes.
40-
random_mutation_min_val=None
41-
random_mutation_max_val=None
42-
43-
# Some flags.
44-
run_completed = False # Set to True only when the run() method completes gracefully.
45-
valid_parameters = False # Set to True when all the paremeters passed in the GA class construtor are valid.
46-
477
def __init__(self, num_generations,
488
sol_per_pop,
499
num_parents_mating,
5010
num_genes,
5111
fitness_func,
12+
init_range_low=-4,
13+
init_range_high=4,
5214
parent_selection_type="sss",
5315
keep_parents=-1,
5416
K_tournament=3,
@@ -59,43 +21,85 @@ def __init__(self, num_generations,
5921
random_mutation_min_val=-1.0,
6022
random_mutation_max_val=1.0):
6123

24+
"""
25+
# A list of all parameters necessary for building an instance of the genetic algorithm.
26+
27+
# Parameters of the genetic algorithm:
28+
num_generations = None # Number of generations.
29+
sol_per_pop = None # Number of solutions in the population.
30+
num_parents_mating = None # Number of solutions to be selected as parents in the mating pool.
31+
pop_size = None # Population size = (number of chromosomes, number of genes per chromosome)
32+
keep_parents = -1 # If 0, this means the parents of the current populaiton will not be used at all in the next population. If -1, this means all parents in the current population will be used in the next population. If set to a value > 0, then the specified value refers to the number of parents in the current population to be used in the next population. In some cases, the parents are of high quality and thus we do not want to loose such some high quality solutions. If some parent selection operators like roulette wheel selection (RWS), the parents may not be of high quality and thus keeping the parents might degarde the quality of the population.
33+
34+
fitness_func = None
35+
36+
# Initial population parameters:
37+
# It is OK to set the value of any of the 2 parameters to be equal, higher or lower than the other parameter (i.e. init_range_low is not needed to be lower than init_range_high).
38+
init_range_low = -4 # The lower value of the random range from which the gene values in the initial population are selected.
39+
init_range_high = 4 # The upper value of the random range from which the gene values in the initial population are selected.
40+
41+
# Parameters about parent selection:
42+
parent_selection_type = None # Type of parent selection.
43+
select_parents = None # Refers to a method that selects the parents based on the parent selection type specified in parent_selection_type.
44+
45+
K_tournament = None # For tournament selection, a parent is selected out of K randomly selected solutions.
46+
47+
population = None # A NumPy array holding the opulation.
48+
49+
# Crossover parameters:
50+
crossover_type = None # Type of the crossover opreator.
51+
crossover = None # A method that applies the crossover operator based on the selected type of crossover in the crossover_type property.
52+
53+
# Mutation parameters:
54+
mutation_type = None # Type of the mutation opreator.
55+
mutation = None # A method that applies the mutation operator based on the selected type of mutation in the mutation_type property.
56+
57+
best_solution_fitness = [] # A list holding the fitness value of the best solution for each generation.
58+
59+
# Parameters of the function to be optimized:
60+
num_genes = None # Number of parameters in the function.
61+
62+
# Mutation parameters:
63+
mutation_percent_genes=None # Percentage of genes to mutate. This parameter has no action if the parameter mutation_num_genes exists.
64+
mutation_num_genes=None # Number of genes to mutate. If the parameter mutation_num_genes exists, then no need for the parameter mutation_percent_genes.
65+
random_mutation_min_val=None
66+
random_mutation_max_val=None
67+
68+
# Some flags:
69+
run_completed = False # Set to True only when the run() method completes gracefully.
70+
valid_parameters = False # Set to True when all the paremeters passed in the GA class construtor are valid.
71+
"""
72+
6273
# Validating the number of solutions in the population (sol_per_pop) and the number of parents to be selected for mating (num_parents_mating)
6374
if (sol_per_pop <= 0 or num_parents_mating <= 0):
64-
print("ERROR creating an instance of the GA class with invalid parameters. \nThe following parameters must be > 0: \n1) Population size (i.e. number of solutions per population) (sol_per_pop).\n2) Number of selected parents in the mating pool (num_parents_mating).\n")
6575
self.valid_parameters = False
66-
return
76+
raise ValueError("ERROR creating an instance of the GA class with invalid parameters. \nThe following parameters must be > 0: \n1) Population size (i.e. number of solutions per population) (sol_per_pop).\n2) Number of selected parents in the mating pool (num_parents_mating).\n")
6777

68-
# Validating the number of gene.
78+
# Validating the number of gene.
6979
if (num_genes <= 0):
70-
print("ERROR: Number of genes cannot be <= 0. \n")
7180
self.valid_parameters = False
72-
return
81+
raise ValueError("ERROR: Number of genes cannot be <= 0. \n")
7382

7483
self.num_genes = num_genes # Number of genes in the solution.
7584

7685
if (mutation_num_genes == None):
7786
if (mutation_percent_genes < 0 or mutation_percent_genes > 100):
78-
print("ERROR: Percentage of selected genes for mutation (mutation_percent_genes) must be >= 0 and <= 100 inclusive.\n")
7987
self.valid_parameters = False
80-
return
88+
raise ValueError("ERROR: Percentage of selected genes for mutation (mutation_percent_genes) must be >= 0 and <= 100 inclusive.\n")
8189
elif (mutation_num_genes <= 0 ):
82-
print("ERROR: Number of selected genes for mutation (mutation_num_genes) cannot be <= 0.\n")
8390
self.valid_parameters = False
84-
return
91+
raise ValueError("ERROR: Number of selected genes for mutation (mutation_num_genes) cannot be <= 0.\n")
8592
elif (mutation_num_genes > self.num_genes):
86-
print("ERROR: Number of selected genes for mutation (mutation_num_genes) cannot be greater than the number of parameters in the function.\n")
8793
self.valid_parameters = False
88-
return
94+
raise ValueError("ERROR: Number of selected genes for mutation (mutation_num_genes) cannot be greater than the number of parameters in the function.\n")
8995
elif (type(mutation_num_genes) is not int):
90-
print("Warning: Number of selected genes for mutation (mutation_num_genes) must be a positive integer >= 1.\n")
9196
self.valid_parameters = False
92-
return
97+
raise ValueError("Error: Number of selected genes for mutation (mutation_num_genes) must be a positive integer >= 1.\n")
9398

9499
# Validating the number of parents to be selected for mating: num_parents_mating
95100
if (num_parents_mating > sol_per_pop):
96-
print("ERROR creating an instance of the GA class with invalid parameters. \nThe number of parents to select for mating cannot be greater than the number of solutions in the population (i.e., num_parents_mating must always be <= sol_per_pop).\n")
97101
self.valid_parameters = False
98-
return
102+
raise ValueError("ERROR creating an instance of the GA class with invalid parameters. \nThe number of parents to select for mating cannot be greater than the number of solutions in the population (i.e., num_parents_mating must always be <= sol_per_pop).\n")
99103

100104
# Validating the crossover type: crossover_type
101105
if (crossover_type == "single_point"):
@@ -105,9 +109,8 @@ def __init__(self, num_generations,
105109
elif (crossover_type == "uniform"):
106110
self.crossover = self.uniform_crossover
107111
else:
108-
print("ERROR: undefined crossover type. \nThe assigned value to the crossover_type argument does not refer to one of the supported crossover types which are: \n-single_point (for single point crossover)\n-two_points (for two points crossover)\n-uniform (for uniform crossover).\n")
109112
self.valid_parameters = False
110-
return
113+
raise ValueError("ERROR: undefined crossover type. \nThe assigned value to the crossover_type argument does not refer to one of the supported crossover types which are: \n-single_point (for single point crossover)\n-two_points (for two points crossover)\n-uniform (for uniform crossover).\n")
111114

112115
self.crossover_type = crossover_type
113116

@@ -121,9 +124,8 @@ def __init__(self, num_generations,
121124
elif (mutation_type == "inversion"):
122125
self.mutation = self.inversion_mutation
123126
else:
124-
print("ERROR: undefined mutation type. \nThe assigned value to the mutation_type argument does not refer to one of the supported mutation types which are: \n-random (for random mutation)\n-swap (for swap mutation)\n-inversion (for inversion mutation)\n-scramble (for scramble mutation).\n")
125127
self.valid_parameters = False
126-
return
128+
raise ValueError("ERROR: undefined mutation type. \nThe assigned value to the mutation_type argument does not refer to one of the supported mutation types which are: \n-random (for random mutation)\n-swap (for swap mutation)\n-inversion (for inversion mutation)\n-scramble (for scramble mutation).\n")
127129

128130
self.mutation_type = mutation_type
129131

@@ -141,26 +143,23 @@ def __init__(self, num_generations,
141143
elif (parent_selection_type == "rank"):
142144
self.select_parents = self.rank_selection
143145
else:
144-
print("ERROR: undefined parent selection type. \nThe assigned value to the parent_selection_type argument does not refer to one of the supported parent selection techniques which are: \n-sss (for steady state selection)\n-rws (for roulette wheel selection)\n-sus (for stochastic universal selection)\n-rank (for rank selection)\n-random (for random selection)\n-tournament (for tournament selection).\n")
145146
self.valid_parameters = False
146-
return
147+
raise ValueError("ERROR: undefined parent selection type. \nThe assigned value to the parent_selection_type argument does not refer to one of the supported parent selection techniques which are: \n-sss (for steady state selection)\n-rws (for roulette wheel selection)\n-sus (for stochastic universal selection)\n-rank (for rank selection)\n-random (for random selection)\n-tournament (for tournament selection).\n")
147148

148149
if(parent_selection_type == "tournament"):
149150
if (K_tournament > sol_per_pop):
150151
K_tournament = sol_per_pop
151152
print("Warining: K of the tournament selection should not be greater than the number of solutions within the population.\nK will be clipped to be equal to the number of solutions in the population (sol_per_pop).\n")
152153
elif (K_tournament <= 0):
153-
print("ERROR: K of the tournament selection cannot be <=0.\n")
154154
self.valid_parameters = False
155-
return
155+
raise ValueError("ERROR: K of the tournament selection cannot be <=0.\n")
156156

157157
self.K_tournament = K_tournament
158158

159159
# Validating the number of parents to keep in the next population: keep_parents
160160
if (keep_parents > sol_per_pop or keep_parents > num_parents_mating or keep_parents < -1):
161-
print("ERROR: Incorrect value to the keep_parents parameter. \nThe assigned value to the keep_parent parameter must satisfy the following conditions: \n1) Less than or equal to sol_per_pop\n2) Less than or equal to num_parents_mating\n3) Greater than or equal to -1.\n")
162161
self.valid_parameters = False
163-
return
162+
raise ValueError("ERROR: Incorrect value to the keep_parents parameter. \nThe assigned value to the keep_parent parameter must satisfy the following conditions: \n1) Less than or equal to sol_per_pop\n2) Less than or equal to num_parents_mating\n3) Greater than or equal to -1.\n")
164163

165164
self.keep_parents = keep_parents
166165

@@ -171,11 +170,19 @@ def __init__(self, num_generations,
171170
elif (self.keep_parents > 0): # Keep the specified number of parents in the next population.
172171
self.num_offspring = sol_per_pop - self.keep_parents
173172

173+
# Check if the fitness function accepts only a single paramater.
174+
if (fitness_func.__code__.co_argcount == 1):
175+
self.fitness_func = fitness_func
176+
else:
177+
self.valid_parameters = False
178+
raise ValueError("The fitness function must accept only a single parameter representing the solution to which the fitness value is calculated.\nThe passed fitness function named '{funcname}' accepts {argcount} argument(s).".format(funcname=fitness_func.__code__.co_name, argcount=fitness_func.__code__.co_argcount))
179+
180+
self.init_range_low = init_range_low
181+
self.init_range_high = init_range_high
182+
174183
# At this point, all necessary parameters validation is done successfully and we are sure that the parameters are valid.
175184
self.valid_parameters = True
176185

177-
self.fitness_func = fitness_func
178-
179186
# Parameters of the genetic algorithm.
180187
self.sol_per_pop = sol_per_pop
181188
self.num_parents_mating = num_parents_mating
@@ -192,23 +199,22 @@ def __init__(self, num_generations,
192199
self.best_solution_fitness = []
193200

194201
# Initializing the population.
195-
self.initialize_population()
202+
self.initialize_population(self.init_range_low, self.init_range_high)
196203

197-
def initialize_population(self):
204+
def initialize_population(self, low, high):
198205
"""
199-
Creates an initial population.
206+
Creates an initial population randomly as a NumPy array. The array is saved in the instance attribute named 'population'.
200207
"""
201208
self.pop_size = (self.sol_per_pop,self.num_genes) # The population will have sol_per_pop chromosome where each chromosome has num_genes genes.
202209
# Creating the initial population randomly.
203-
self.population = numpy.random.uniform(low=-4.0, high=4.0, size=self.pop_size)
210+
self.population = numpy.random.uniform(low=low, high=high, size=self.pop_size)
204211

205212
def run(self):
206213
"""
207214
Running the genetic algorithm. This is the main method in which the genetic algorithm is evolved through a number of generations.
208215
"""
209216
if self.valid_parameters == False:
210-
print("ERROR calling the run() method: \nThe run() method cannot be executed with invalid parameters. Please check the parameters passed while creating an instance of the GA class.\n")
211-
return
217+
raise ValueError("ERROR calling the run() method: \nThe run() method cannot be executed with invalid parameters. Please check the parameters passed while creating an instance of the GA class.\n")
212218

213219
for generation in range(self.num_generations):
214220
# Measuring the fitness of each chromosome in the population.
@@ -245,8 +251,7 @@ def cal_pop_fitness(self):
245251
-fitness: An array of the calculated fitness values.
246252
"""
247253
if self.valid_parameters == False:
248-
print("ERROR calling the cal_pop_fitness() method: \nPlease check the parameters passed while creating an instance of the GA class.\n")
249-
return []
254+
raise ValueError("ERROR calling the cal_pop_fitness() method: \nPlease check the parameters passed while creating an instance of the GA class.\n")
250255

251256
pop_fitness = []
252257
# Calculating the fitness value of each solution in the current population.
@@ -565,8 +570,7 @@ def best_solution(self):
565570
-best_solution_fitness: Fitness value of the best solution.
566571
"""
567572
if self.run_completed == False:
568-
print("Warning calling the best_solution() method: \nThe run() method is not yet called and thus the GA did not evolve the solutions. Thus, the best solution is retireved from the initial random population without being evolved.\n")
569-
return [], []
573+
raise ValueError("Warning calling the best_solution() method: \nThe run() method is not yet called and thus the GA did not evolve the solutions. Thus, the best solution is retireved from the initial random population without being evolved.\n")
570574

571575
# Getting the best solution after finishing all generations.
572576
# At first, the fitness is calculated for each solution in the final generation.

0 commit comments

Comments
 (0)
0