Skip to content

Commit 901d7e1

Browse files
author
Ahmed Gad
committed
More tests
1 parent 2c6c6d8 commit 901d7e1

25 files changed

+531
-44
lines changed

.DS_Store

2 KB
Binary file not shown.

example2.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ def fitness_func(ga_instance, solution, solution_idx):
2828
allow_duplicate_genes=False,
2929
# gene_space=numpy.unique(numpy.random.uniform(1, 100, size=100)),
3030
gene_space=[range(0, 100), {"low": 0, "high": 100, 'step': 1}, 2.5891221, [1,2,3,4], None, numpy.unique(numpy.random.uniform(1, 100, size=4))],
31-
gene_constraint=[lambda x: x[0]>=95,lambda x: x[1]>=95,lambda x: x[2]<98,lambda x: x[3]<98,lambda x: x[4]<98,lambda x: x[5]<98],
31+
gene_constraint=[lambda x: x[0]>=70,lambda x: x[1]>=70,lambda x: x[2]<98,lambda x: x[3]<98,lambda x: x[4]<98,lambda x: x[5]<98],
3232
)
3333

3434
print(ga_instance.initial_population)
3535

36-
# ga_instance.run()
36+
ga_instance.run()
3737

3838
# print(ga_instance.gene_space_unpacked)
3939
# print(ga_instance.population)

pygad/.DS_Store

8 KB
Binary file not shown.
-227 Bytes
Binary file not shown.
-77.8 KB
Binary file not shown.

pygad/helper/.DS_Store

6 KB
Binary file not shown.
-282 Bytes
Binary file not shown.
-14.8 KB
Binary file not shown.
-14.2 KB
Binary file not shown.

pygad/helper/misc.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def change_population_dtype_and_round(self,
1919
It returns the iterable with the data type changed for all genes.
2020
"""
2121

22-
population_new = population.copy()
22+
population_new = numpy.array(population.copy(), dtype=object)
2323

2424
# Forcing the iterable to have the data type assigned to the gene_type parameter.
2525
if self.gene_type_single == True:
@@ -49,8 +49,10 @@ def change_population_dtype_and_round(self,
4949
population_new[:, gene_idx] = numpy.round(numpy.array(population[:, gene_idx], float),
5050
self.gene_type[gene_idx][1])
5151
# Once rounding is done, change the data type.
52-
population_new[:, gene_idx] = numpy.asarray(population_new[:, gene_idx],
53-
dtype=self.gene_type[gene_idx][0])
52+
# population_new[:, gene_idx] = numpy.asarray(population_new[:, gene_idx], dtype=self.gene_type[gene_idx][0])
53+
# Use a for loop to maintain the data type of each individual gene.
54+
for sol_idx in range(population.shape[0]):
55+
population_new[sol_idx, gene_idx] = self.gene_type[gene_idx][0](population_new[sol_idx, gene_idx])
5456
return population_new
5557

5658
def change_gene_dtype_and_round(self,
@@ -153,7 +155,7 @@ def filter_gene_values_by_constraint(self,
153155
else:
154156
# No value found for the current gene that satisfies the constraint.
155157
if not self.suppress_warnings:
156-
warnings.warn(f"No value found for the gene at index {gene_idx} that satisfies its gene constraint.")
158+
warnings.warn(f"No value found for the gene at index {gene_idx} with value {solution[gene_idx]} that satisfies its gene constraint.")
157159
return None
158160

159161
filtered_values = values[filtered_values_indices]

pygad/helper/unique.py

Lines changed: 65 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ def solve_duplicate_genes_randomly(self,
8484
def solve_duplicate_genes_by_space(self,
8585
solution,
8686
gene_type,
87-
num_trials=10,
87+
mutation_by_replacement,
88+
sample_size=100,
8889
build_initial_pop=False):
8990

9091
"""
@@ -93,7 +94,7 @@ def solve_duplicate_genes_by_space(self,
9394
Args:
9495
solution (list): A solution containing genes, potentially with duplicate values.
9596
gene_type (type): The data type of the gene (e.g., int, float).
96-
num_trials (int): The maximum number of attempts to resolve duplicates by selecting values from the gene space.
97+
sample_size (int): The maximum number of attempts to resolve duplicates by selecting values from the gene space.
9798
9899
Returns:
99100
tuple:
@@ -106,16 +107,16 @@ def solve_duplicate_genes_by_space(self,
106107

107108
_, unique_gene_indices = numpy.unique(solution, return_index=True)
108109
not_unique_indices = set(range(len(solution))) - set(unique_gene_indices)
109-
# self.logger.info("not_unique_indices OUTSIDE", not_unique_indices)
110110

111111
# First try to solve the duplicates.
112112
# For a solution like [3 2 0 0], the indices of the 2 duplicating genes are 2 and 3.
113113
# The next call to the find_unique_value() method tries to change the value of the gene with index 3 to solve the duplicate.
114114
if len(not_unique_indices) > 0:
115-
new_solution, not_unique_indices, num_unsolved_duplicates = self.unique_genes_by_space(new_solution=new_solution,
115+
new_solution, not_unique_indices, num_unsolved_duplicates = self.unique_genes_by_space(solution=new_solution,
116116
gene_type=gene_type,
117117
not_unique_indices=not_unique_indices,
118-
num_trials=10,
118+
sample_size=sample_size,
119+
mutation_by_replacement=mutation_by_replacement,
119120
build_initial_pop=build_initial_pop)
120121
else:
121122
return new_solution, not_unique_indices, len(not_unique_indices)
@@ -260,30 +261,34 @@ def select_unique_value(self, gene_values, solution, gene_index):
260261
values_to_select_from = list(set(list(gene_values)) - set(solution))
261262

262263
if len(values_to_select_from) == 0:
264+
print("@@@@@@@@")
265+
print(solution)
266+
print(gene_values)
263267
# If there are no values, then keep the current gene value.
264-
if not self.suppress_warnings: warnings.warn(f"'allow_duplicate_genes=False' but cannot find a unique value for the gene at index {gene_index}.")
268+
if not self.suppress_warnings: warnings.warn(f"'allow_duplicate_genes=False' but cannot find a unique value for the gene at index {gene_index} with value {solution[gene_index]}.")
265269
selected_value = solution[gene_index]
266270
else:
267271
selected_value = random.choice(values_to_select_from)
268272

269273
return selected_value
270274

271275
def unique_genes_by_space(self,
272-
new_solution,
276+
solution,
273277
gene_type,
274-
not_unique_indices,
275-
num_trials=10,
278+
not_unique_indices,
279+
mutation_by_replacement,
280+
sample_size=100,
276281
build_initial_pop=False):
277282

278283
"""
279284
Iterates through all duplicate genes to find unique values from their gene spaces and resolve duplicates.
280285
For each duplicate gene, a call is made to the `unique_gene_by_space()` function.
281286
282287
Args:
283-
new_solution (list): A solution containing genes with duplicate values.
288+
solution (list): A solution containing genes with duplicate values.
284289
gene_type (type): The data type of the all the genes (e.g., int, float).
285290
not_unique_indices (list): The indices of genes with duplicate values.
286-
num_trials (int): The maximum number of attempts to resolve duplicates for each gene. Only works for floating-point numbers.
291+
sample_size (int): The maximum number of attempts to resolve duplicates for each gene. Only works for floating-point numbers.
287292
288293
Returns:
289294
tuple:
@@ -294,31 +299,33 @@ def unique_genes_by_space(self,
294299

295300
num_unsolved_duplicates = 0
296301
for duplicate_index in not_unique_indices:
297-
temp_val = self.unique_gene_by_space(solution=new_solution,
302+
temp_val = self.unique_gene_by_space(solution=solution,
298303
gene_idx=duplicate_index,
299304
gene_type=gene_type,
300-
build_initial_pop=build_initial_pop,
301-
num_trials=num_trials)
305+
mutation_by_replacement=mutation_by_replacement,
306+
sample_size=sample_size,
307+
build_initial_pop=build_initial_pop)
302308

303-
if temp_val in new_solution:
304-
# self.logger.info("temp_val, duplicate_index", temp_val, duplicate_index, new_solution)
309+
if temp_val in solution:
310+
# self.logger.info("temp_val, duplicate_index", temp_val, duplicate_index, solution)
305311
num_unsolved_duplicates = num_unsolved_duplicates + 1
306-
if not self.suppress_warnings: warnings.warn(f"Failed to find a unique value for gene with index {duplicate_index} whose value is {new_solution[duplicate_index]}. Consider adding more values in the gene space or use a wider range for initial population or random mutation.")
312+
if not self.suppress_warnings: warnings.warn(f"Failed to find a unique value for gene with index {duplicate_index} whose value is {solution[duplicate_index]}. Consider adding more values in the gene space or use a wider range for initial population or random mutation.")
307313
else:
308-
new_solution[duplicate_index] = temp_val
314+
solution[duplicate_index] = temp_val
309315

310316
# Update the list of duplicate indices after each iteration.
311-
_, unique_gene_indices = numpy.unique(new_solution, return_index=True)
312-
not_unique_indices = set(range(len(new_solution))) - set(unique_gene_indices)
317+
_, unique_gene_indices = numpy.unique(solution, return_index=True)
318+
not_unique_indices = set(range(len(solution))) - set(unique_gene_indices)
313319

314-
return new_solution, not_unique_indices, num_unsolved_duplicates
320+
return solution, not_unique_indices, num_unsolved_duplicates
315321

316322
def unique_gene_by_space(self,
317323
solution,
318324
gene_idx,
319-
gene_type,
320-
build_initial_pop=False,
321-
num_trials=10):
325+
gene_type,
326+
mutation_by_replacement,
327+
sample_size=100,
328+
build_initial_pop=False):
322329

323330
"""
324331
Returns a unique value for a specific gene based on its value space to resolve duplicates.
@@ -327,15 +334,47 @@ def unique_gene_by_space(self,
327334
solution (list): A solution containing genes with duplicate values.
328335
gene_idx (int): The index of the gene that has a duplicate value.
329336
gene_type (type): The data type of the gene (e.g., int, float).
330-
num_trials (int): The maximum number of attempts to resolve duplicates for each gene. Only works for floating-point numbers.
337+
sample_size (int): The maximum number of attempts to resolve duplicates for each gene. Only works for floating-point numbers.
331338
332339
Returns:
333340
Any: A unique value for the gene, if one exists; otherwise, the original gene value.
334341
"""
335342

343+
# When gene_value is None, this forces the gene value generators to select a value for use by the initial population.
344+
# Otherwise, it considers selecting a value for mutation.
345+
if build_initial_pop:
346+
gene_value = None
347+
else:
348+
gene_value = solution[gene_idx]
336349

350+
if self.gene_constraint and self.gene_constraint[gene_idx]:
351+
# A unique value is created out of the values that satisfy the constraint.
352+
values = self.get_valid_gene_constraint_values(range_min=None,
353+
range_max=None,
354+
gene_value=gene_value,
355+
gene_idx=gene_idx,
356+
mutation_by_replacement=mutation_by_replacement,
357+
solution=solution,
358+
sample_size=sample_size)
359+
# If there is no value satisfying the constraint, then return the current gene value.
360+
if values is None:
361+
return solution[gene_idx]
362+
else:
363+
pass
364+
else:
365+
# There is no constraint for the current gene. Return the same range.
366+
values = self.generate_gene_value(range_min=None,
367+
range_max=None,
368+
gene_value=gene_value,
369+
gene_idx=gene_idx,
370+
mutation_by_replacement=mutation_by_replacement,
371+
sample_size=sample_size)
337372

338-
return value_from_space
373+
selected_value = self.select_unique_value(gene_values=values,
374+
solution=solution,
375+
gene_index=gene_idx)
376+
377+
return selected_value
339378

340379
def find_two_duplicates(self,
341380
solution,

pygad/pygad.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,8 @@ def __init__(self,
469469
self.initial_population[initial_solution_idx], _, _ = self.solve_duplicate_genes_by_space(solution=initial_solution,
470470
gene_type=self.gene_type,
471471
sample_size=100,
472-
mutation_by_replacement=True)
472+
mutation_by_replacement=True,
473+
build_initial_pop=True)
473474

474475
# Change the data type and round all genes within the initial population.
475476
self.initial_population = self.change_population_dtype_and_round(initial_population)
@@ -1382,7 +1383,7 @@ def initialize_population(self,
13821383
# 4) Solve duplicates if not allowed.
13831384

13841385
# Create an empty population.
1385-
self.population = numpy.zeros(shape=self.pop_size)
1386+
self.population = numpy.zeros(shape=self.pop_size, dtype=object)
13861387

13871388
# 1) Create the initial population either randomly or using the gene space.
13881389
if self.gene_space is None:
@@ -1436,7 +1437,7 @@ def initialize_population(self,
14361437
sample_size=100)
14371438
if values_filtered is None:
14381439
if not self.suppress_warnings:
1439-
warnings.warn(f"No value satisfied the constraint for the gene at index {gene_idx} while creating the initial population.")
1440+
warnings.warn(f"No value satisfied the constraint for the gene at index {gene_idx} with value {solution[gene_idx]} while creating the initial population.")
14401441
else:
14411442
self.population[sol_idx, gene_idx] = random.choice(values_filtered)
14421443

@@ -1454,7 +1455,8 @@ def initialize_population(self,
14541455
self.population[sol_idx], _, _ = self.solve_duplicate_genes_by_space(solution=self.population[solution_idx],
14551456
gene_type=self.gene_type,
14561457
sample_size=100,
1457-
mutation_by_replacement=True)
1458+
mutation_by_replacement=True,
1459+
build_initial_pop=True)
14581460

14591461
# Keeping the initial population in the initial_population attribute.
14601462
self.initial_population = self.population.copy()

pygad/utils/.DS_Store

6 KB
Binary file not shown.
-354 Bytes
Binary file not shown.
-5.96 KB
Binary file not shown.
-17.3 KB
Binary file not shown.
-7.13 KB
Binary file not shown.
Binary file not shown.

pygad/utils/crossover.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ def single_point_crossover(self, parents, offspring_size):
7474
else:
7575
offspring[k], _, _ = self.solve_duplicate_genes_by_space(solution=offspring[k],
7676
gene_type=self.gene_type,
77-
num_trials=10)
77+
sample_size=100,
78+
mutation_by_replacement=self.mutation_by_replacement,
79+
build_initial_pop=False)
7880

7981
return offspring
8082

@@ -146,7 +148,9 @@ def two_points_crossover(self, parents, offspring_size):
146148
else:
147149
offspring[k], _, _ = self.solve_duplicate_genes_by_space(solution=offspring[k],
148150
gene_type=self.gene_type,
149-
num_trials=10)
151+
sample_size=100,
152+
mutation_by_replacement=self.mutation_by_replacement,
153+
build_initial_pop=False)
150154
return offspring
151155

152156
def uniform_crossover(self, parents, offspring_size):
@@ -213,7 +217,9 @@ def uniform_crossover(self, parents, offspring_size):
213217
else:
214218
offspring[k], _, _ = self.solve_duplicate_genes_by_space(solution=offspring[k],
215219
gene_type=self.gene_type,
216-
num_trials=10)
220+
sample_size=100,
221+
mutation_by_replacement=self.mutation_by_replacement,
222+
build_initial_pop=False)
217223

218224
return offspring
219225

@@ -277,5 +283,7 @@ def scattered_crossover(self, parents, offspring_size):
277283
else:
278284
offspring[k], _, _ = self.solve_duplicate_genes_by_space(solution=offspring[k],
279285
gene_type=self.gene_type,
280-
num_trials=10)
286+
sample_size=100,
287+
mutation_by_replacement=self.mutation_by_replacement,
288+
build_initial_pop=False)
281289
return offspring

0 commit comments

Comments
 (0)