Skip to content

Commit 3ba005f

Browse files
author
Ahmed Gad
committed
Fix selecting a unique value from gene space
1 parent 901d7e1 commit 3ba005f

27 files changed

+7022
-169
lines changed

.DS_Store

0 Bytes
Binary file not shown.

example2.py

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,41 @@
11
import pygad
22
import numpy
3+
import random
34

4-
function_inputs = [4,-2,3.5,5,-11,-4.7]
5-
desired_output = 44
5+
def fitness_func(ga, solution, idx):
6+
return random.random()
67

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_genes = len(function_inputs)
13-
14-
ga_instance = pygad.GA(num_generations=100,
15-
num_parents_mating=10,
16-
sol_per_pop=20,
17-
num_genes=num_genes,
18-
mutation_num_genes=6,
19-
fitness_func=fitness_func,
20-
init_range_low=1,
21-
init_range_high=100,
22-
# suppress_warnings=True,
8+
ga_instance = pygad.GA(num_generations=1,
9+
num_parents_mating=5,
10+
sol_per_pop=10,
11+
num_genes=10,
12+
random_seed=123,
13+
# mutation_type=None,
14+
# crossover_type=None,
2315
random_mutation_min_val=1,
2416
random_mutation_max_val=100,
25-
mutation_by_replacement=True,
26-
gene_type=[float, 1],
27-
save_solutions=True,
17+
fitness_func=fitness_func,
18+
gene_space=[30, None, 40, 50, None, 60, 70, None, None, None],
19+
gene_type=int,
2820
allow_duplicate_genes=False,
29-
# gene_space=numpy.unique(numpy.random.uniform(1, 100, size=100)),
30-
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]>=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],
32-
)
21+
save_solutions=True)
3322

3423
print(ga_instance.initial_population)
3524

3625
ga_instance.run()
3726

3827
# print(ga_instance.gene_space_unpacked)
3928
# print(ga_instance.population)
29+
30+
"""
31+
gene_space=[[0, 0],
32+
[1, 2],
33+
[2, 3],
34+
[3, 4],
35+
[4, 5],
36+
[5, 6],
37+
[6, 7],
38+
[7, 8],
39+
[8, 9],
40+
[9, 10]],
41+
"""
205 Bytes
Binary file not shown.
78 KB
Binary file not shown.
260 Bytes
Binary file not shown.
15.6 KB
Binary file not shown.
14.4 KB
Binary file not shown.

pygad/helper/misc.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -216,13 +216,15 @@ def get_initial_population_range(self, gene_index):
216216
def generate_gene_value_from_space(self,
217217
gene_idx,
218218
mutation_by_replacement,
219+
solution=None,
219220
gene_value=None,
220221
sample_size=1):
221222
"""
222223
Generate/select one or more values for the gene from the gene space.
223224
It accepts:
224225
-gene_idx: The index of the gene in the solution.
225226
-mutation_by_replacement: A flag indicating whether mutation by replacement is enabled or not. The reason is to make this helper method usable while generating the initial population. In this case, mutation_by_replacement does not matter and should be considered False.
227+
-solution (iterable, optional): The solution where we need to generate a gene. Needed if you are selecting a single value (sample_size=1) to select a value that respects the allow_duplicate_genes parameter instead of selecting a value randomly. If None, then the gene value is selected randomly.
226228
-gene_value (int, optional): The original gene value before applying mutation. Needed if you are calling this method to apply mutation. If None, then a sample is created from the gene space without being summed to the gene value.
227229
-sample_size (int, optional): The number of random values to generate. It tries to generate a number of values up to a maximum of sample_size. But it is not always guaranteed because the total number of values might not be enough or the random generator creates duplicate random values. For int data types, it could be None to keep all the values. For float data types, a None value returns only a single value.
228230
@@ -314,10 +316,13 @@ def generate_gene_value_from_space(self,
314316
high=self.gene_space['high'],
315317
size=sample_size)
316318
else:
317-
# Change the data type and round the generated values.
318-
# Pass a copy of the gene_space to avoid changing its value.
319+
curr_gene_space = list(self.gene_space).copy()
320+
for idx in range(len(curr_gene_space)):
321+
if curr_gene_space[idx] is None:
322+
curr_gene_space[idx] = numpy.random.uniform(low=range_min,
323+
high=range_max)
319324
curr_gene_space = self.change_gene_dtype_and_round(gene_index=gene_idx,
320-
gene_value=self.gene_space)
325+
gene_value=curr_gene_space)
321326

322327
if gene_value is None:
323328
# Just generate the value(s) without being added to the gene value specially when initializing the population.
@@ -334,8 +339,19 @@ def generate_gene_value_from_space(self,
334339
# After removing the current gene value from the space, there are no more values.
335340
# Then keep the current gene value.
336341
value_from_space = gene_value
342+
if sample_size > 1:
343+
value_from_space = numpy.array([gene_value])
337344
elif sample_size == 1:
338-
value_from_space = random.choice(value_from_space)
345+
if self.allow_duplicate_genes == True:
346+
# Select a value randomly from the current gene space.
347+
value_from_space = random.choice(value_from_space)
348+
else:
349+
# We must check if the selected value will respect the allow_duplicate_genes parameter.
350+
# Instead of selecting a value randomly, we have to select a value that will be unique if allow_duplicate_genes=False.
351+
# Only select a value from the current gene space that is, hopefully, unique.
352+
value_from_space = self.select_unique_value(gene_values=value_from_space,
353+
solution=solution,
354+
gene_index=gene_idx)
339355

340356
# The gene space might be [None, 1, 7].
341357
# It might happen that the value None is selected.
@@ -425,6 +441,7 @@ def generate_gene_value(self,
425441
gene_value,
426442
gene_idx,
427443
mutation_by_replacement,
444+
solution=None,
428445
range_min=None,
429446
range_max=None,
430447
sample_size=1,
@@ -435,6 +452,7 @@ def generate_gene_value(self,
435452
-gene_value: The original gene value before applying mutation.
436453
-gene_idx: The index of the gene in the solution.
437454
-mutation_by_replacement: A flag indicating whether mutation by replacement is enabled or not. The reason is to make this helper method usable while generating the initial population. In this case, mutation_by_replacement does not matter and should be considered False.
455+
-solution (iterable, optional): The solution where we need to generate a gene. Needed if you are selecting a single value (sample_size=1) to select a value that respects the allow_duplicate_genes parameter instead of selecting a value randomly. If None, then the gene value is selected randomly.
438456
-range_min (int, optional): The minimum value in the range from which a value is selected. It must be passed for generating the gene value randomly because we cannot decide whether it is the range for the initial population (init_range_low and init_range_high) or mutation (random_mutation_min_val and random_mutation_max_val).
439457
-range_max (int, optional): The maximum value in the range from which a value is selected. It must be passed for generating the gene value randomly because we cannot decide whether it is the range for the initial population (init_range_low and init_range_high) or mutation (random_mutation_min_val and random_mutation_max_val).
440458
-sample_size: The number of random values to generate/select and return. It tries to generate a number of values up to a maximum of sample_size. But it is not always guaranteed because the total number of values might not be enough or the random generator creates duplicate random values. For int data types, it could be None to keep all the values. For float data types, a None value returns only a single value.
@@ -456,6 +474,7 @@ def generate_gene_value(self,
456474
output = self.generate_gene_value_from_space(gene_value=gene_value,
457475
gene_idx=gene_idx,
458476
mutation_by_replacement=mutation_by_replacement,
477+
solution=solution,
459478
sample_size=sample_size)
460479
return output
461480

pygad/helper/unique.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def solve_duplicate_genes_by_space(self,
120120
build_initial_pop=build_initial_pop)
121121
else:
122122
return new_solution, not_unique_indices, len(not_unique_indices)
123-
123+
124124
# DEEP-DUPLICATE-REMOVAL-NEEDED
125125
# Search by this phrase to find where deep duplicates removal should be applied.
126126
# If there exist duplicate genes, then changing either of the 2 duplicating genes (with indices 2 and 3) will not solve the problem.
@@ -179,6 +179,7 @@ def unique_int_gene_from_range(self,
179179
range_max=max_val,
180180
gene_value=solution[gene_index],
181181
gene_idx=gene_index,
182+
solution=solution,
182183
mutation_by_replacement=mutation_by_replacement,
183184
sample_size=None,
184185
step=step)
@@ -237,6 +238,7 @@ def unique_float_gene_from_range(self,
237238
range_max=max_val,
238239
gene_value=solution[gene_index],
239240
gene_idx=gene_index,
241+
solution=solution,
240242
mutation_by_replacement=mutation_by_replacement,
241243
sample_size=sample_size)
242244

@@ -253,17 +255,15 @@ def select_unique_value(self, gene_values, solution, gene_index):
253255
Args:
254256
gene_values (NumPy Array): An array of values from which a unique value should be selected.
255257
solution (list): A solution containing genes, potentially with duplicate values.
258+
gene_index (int): The index of the gene for which to find a unique value.
256259
257260
Returns:
258261
selected_gene: The new (hopefully unique) value of the gene. If no unique value can be found, the original gene value is returned.
259262
"""
260263

261264
values_to_select_from = list(set(list(gene_values)) - set(solution))
262-
265+
263266
if len(values_to_select_from) == 0:
264-
print("@@@@@@@@")
265-
print(solution)
266-
print(gene_values)
267267
# If there are no values, then keep the current gene value.
268268
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]}.")
269269
selected_value = solution[gene_index]
@@ -367,6 +367,7 @@ def unique_gene_by_space(self,
367367
range_max=None,
368368
gene_value=gene_value,
369369
gene_idx=gene_idx,
370+
solution=solution,
370371
mutation_by_replacement=mutation_by_replacement,
371372
sample_size=sample_size)
372373

@@ -438,6 +439,10 @@ def unpack_gene_space(self,
438439

439440
if self.gene_type_single == True:
440441
# Change the data type.
442+
for idx in range(len(gene_space_unpacked)):
443+
if gene_space_unpacked[idx] is None:
444+
gene_space_unpacked[idx] = numpy.random.uniform(low=range_min,
445+
high=range_max)
441446
gene_space_unpacked = numpy.array(gene_space_unpacked,
442447
dtype=self.gene_type[0])
443448
if not self.gene_type[1] is None:

0 commit comments

Comments
 (0)