Skip to content

Commit 6a3b58b

Browse files
author
Ahmed Gad
committed
Apply constraints on gene space
1 parent c5d35dc commit 6a3b58b

28 files changed

+2214
-1121
lines changed

.DS_Store

8 KB
Binary file not shown.

example.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,15 @@ def fitness_func(ga_instance, solution, solution_idx):
2323
random_mutation_min_val=1,
2424
random_mutation_max_val=100,
2525
mutation_by_replacement=True,
26-
gene_type=int,
26+
gene_type=[float, 1],
27+
save_solutions=True,
2728
allow_duplicate_genes=False,
28-
# mutation_probability=0.4,
29-
# gene_constraint=[lambda x: x[0]>=8,None,None,None,None,None],
29+
# gene_space=numpy.unique(numpy.random.uniform(1, 100, size=100)),
30+
gene_space=[range(10), {"low": 1, "high": 5}, 2.5891221, [1,2,3,4], None, numpy.unique(numpy.random.uniform(1, 100, size=4))],
3031
gene_constraint=[lambda x: x[0]>=98,lambda x: x[1]>=98,lambda x: x[2]<98,lambda x: x[3]<98,lambda x: x[4]<98,lambda x: x[5]<98],
3132
)
3233

33-
# ga_instance.run()
34+
ga_instance.run()
35+
36+
print(ga_instance.gene_space_unpacked)
37+
print(ga_instance.population)

pygad/.DS_Store

6 KB
Binary file not shown.

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.3.1"
3+
__version__ = "3.5.0"
-22 Bytes
Binary file not shown.
-21 Bytes
Binary file not shown.
-22 Bytes
Binary file not shown.
7.26 KB
Binary file not shown.
-20 Bytes
Binary file not shown.

pygad/helper/misc.py

Lines changed: 300 additions & 37 deletions
Large diffs are not rendered by default.

pygad/helper/unique.py

Lines changed: 41 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def solve_duplicate_genes_randomly(self,
1515
max_val,
1616
mutation_by_replacement,
1717
gene_type,
18-
num_values=100):
18+
sample_size=100):
1919
"""
2020
Resolves duplicates in a solution by randomly selecting new values for the duplicate genes.
2121
@@ -25,7 +25,7 @@ def solve_duplicate_genes_randomly(self,
2525
max_val (int): The maximum value of the range to sample a number randomly.
2626
mutation_by_replacement (bool): Indicates if mutation is performed by replacement.
2727
gene_type (type): The data type of the gene (e.g., int, float).
28-
num_values (int): The maximum number of random values to generate to find a unique value.
28+
sample_size (int): The maximum number of random values to generate to find a unique value.
2929
3030
Returns:
3131
tuple:
@@ -58,7 +58,7 @@ def solve_duplicate_genes_randomly(self,
5858
max_val=max_val,
5959
mutation_by_replacement=mutation_by_replacement,
6060
gene_type=gene_type,
61-
num_values=num_values)
61+
sample_size=sample_size)
6262

6363
if temp_val in new_solution:
6464
num_unsolved_duplicates = num_unsolved_duplicates + 1
@@ -162,14 +162,14 @@ def unique_int_gene_from_range(self,
162162

163163
if self.gene_constraint and self.gene_constraint[gene_index]:
164164
# A unique value is created out of the values that satisfy the constraint.
165-
# num_values=None to return all the values.
165+
# sample_size=None to return all the values.
166166
random_values = self.get_valid_gene_constraint_values(range_min=min_val,
167167
range_max=max_val,
168168
gene_value=solution[gene_index],
169169
gene_idx=gene_index,
170170
mutation_by_replacement=mutation_by_replacement,
171171
solution=solution,
172-
num_values=None,
172+
sample_size=None,
173173
step=step)
174174
# If there is no value satisfying the constraint, then return the current gene value.
175175
if random_values is None:
@@ -178,14 +178,14 @@ def unique_int_gene_from_range(self,
178178
pass
179179
else:
180180
# There is no constraint for the current gene. Return the same range.
181-
# num_values=None to return all the values.
182-
random_values = self.generate_gene_random_value(range_min=min_val,
183-
range_max=max_val,
184-
gene_value=solution[gene_index],
185-
gene_idx=gene_index,
186-
mutation_by_replacement=mutation_by_replacement,
187-
num_values=None,
188-
step=step)
181+
# sample_size=None to return all the values.
182+
random_values = self.generate_gene_value(range_min=min_val,
183+
range_max=max_val,
184+
gene_value=solution[gene_index],
185+
gene_idx=gene_index,
186+
mutation_by_replacement=mutation_by_replacement,
187+
sample_size=None,
188+
step=step)
189189

190190
"""
191191
# For non-integer steps, the numpy.arange() function returns zeros if the dtype parameter is set to an integer data type. So, this returns zeros if step is non-integer and dtype is set to an int data type: numpy.arange(min_val, max_val, step, dtype=gene_type[0])
@@ -223,7 +223,7 @@ def unique_float_gene_from_range(self,
223223
max_val,
224224
mutation_by_replacement,
225225
gene_type,
226-
num_values=100):
226+
sample_size=100):
227227

228228
"""
229229
Finds a unique floating-point value for a specific gene in a solution.
@@ -235,66 +235,36 @@ def unique_float_gene_from_range(self,
235235
max_val (int): The maximum value of the range to sample a floating-point number randomly.
236236
mutation_by_replacement (bool): Indicates if mutation is performed by replacement.
237237
gene_type (type): The data type of the gene (e.g., float, float16, float32, etc).
238-
num_values (int): The maximum number of random values to generate to find a unique value.
238+
sample_size (int): The maximum number of random values to generate to find a unique value.
239239
240240
Returns:
241241
int: The new floating-point value of the gene. If no unique value can be found, the original gene value is returned.
242242
"""
243243

244244
if self.gene_constraint and self.gene_constraint[gene_index]:
245245
# A unique value is created out of the values that satisfy the constraint.
246-
random_values = self.get_valid_gene_constraint_values(range_min=min_val,
247-
range_max=max_val,
248-
gene_value=solution[gene_index],
249-
gene_idx=gene_index,
250-
mutation_by_replacement=mutation_by_replacement,
251-
solution=solution,
252-
num_values=num_values)
246+
values = self.get_valid_gene_constraint_values(range_min=min_val,
247+
range_max=max_val,
248+
gene_value=solution[gene_index],
249+
gene_idx=gene_index,
250+
mutation_by_replacement=mutation_by_replacement,
251+
solution=solution,
252+
sample_size=sample_size)
253253
# If there is no value satisfying the constraint, then return the current gene value.
254-
if random_values is None:
254+
if values is None:
255255
return solution[gene_index]
256256
else:
257257
pass
258258
else:
259259
# There is no constraint for the current gene. Return the same range.
260-
random_values = self.generate_gene_random_value(range_min=min_val,
261-
range_max=max_val,
262-
gene_value=solution[gene_index],
263-
gene_idx=gene_index,
264-
mutation_by_replacement=mutation_by_replacement,
265-
num_values=num_values)
266-
267-
"""
268-
# The gene_type is of the form [type, precision]
269-
dtype = gene_type
270-
271-
# We cannot have a list of all values out of a continous range.
272-
# Solution is to create a subset (e.g. 100) of some random values out of the range.
273-
some_gene_values = numpy.random.uniform(low=min_val,
274-
high=max_val,
275-
size=num_values)
276-
277-
# If mutation is by replacement, do not add the current gene value into the list.
278-
# This is to avoid replacing the value by itself again. We are doing nothing in this case.
279-
if mutation_by_replacement:
280-
pass
281-
else:
282-
some_gene_values = some_gene_values + solution[gene_index]
283-
284-
if not dtype[1] is None:
285-
# Precision is available and we have to round the number.
286-
# Convert the data type and round the number.
287-
some_gene_values = numpy.round(numpy.asarray(some_gene_values,
288-
dtype[0]),
289-
dtype[1])
290-
else:
291-
# There is no precision and rounding the number is not needed. The type is [type, None]
292-
# Just convert the data type.
293-
some_gene_values = numpy.asarray(some_gene_values,
294-
dtype[0])
295-
"""
296-
297-
selected_value = self.select_unique_value(gene_values=random_values,
260+
values = self.generate_gene_value(range_min=min_val,
261+
range_max=max_val,
262+
gene_value=solution[gene_index],
263+
gene_idx=gene_index,
264+
mutation_by_replacement=mutation_by_replacement,
265+
sample_size=sample_size)
266+
267+
selected_value = self.select_unique_value(gene_values=values,
298268
solution=solution,
299269
gene_index=gene_index)
300270
return selected_value
@@ -439,7 +409,7 @@ def unique_gene_by_space(self,
439409
max_val=high,
440410
mutation_by_replacement=True,
441411
gene_type=dtype,
442-
num_values=num_trials)
412+
sample_size=num_trials)
443413

444414

445415
elif type(curr_gene_space) is dict:
@@ -532,8 +502,10 @@ def unique_gene_by_space(self,
532502
# If the space type is not of type dict, then a value is randomly selected from the gene_space attribute.
533503
# Remove all the genes in the current solution from the gene_space.
534504
# This only leaves the unique values that could be selected for the gene.
535-
values_to_select_from = list(set(self.gene_space) - set(solution))
536-
505+
506+
# Before using the gene_space, use gene_space_unpacked instead of gene_space to make sure the numbers has the right data type and its values are rounded.
507+
values_to_select_from = list(set(self.gene_space_unpacked) - set(solution))
508+
537509
if len(values_to_select_from) == 0:
538510
if not self.suppress_warnings: warnings.warn("You set 'allow_duplicate_genes=False' but the gene space does not have enough values to prevent duplicates.")
539511
value_from_space = solution[gene_idx]
@@ -591,14 +563,14 @@ def find_two_duplicates(self,
591563
def unpack_gene_space(self,
592564
range_min,
593565
range_max,
594-
num_values_from_inf_range=100):
566+
sample_size_from_inf_range=100):
595567
"""
596568
Unpacks the gene space for selecting a value to resolve duplicates by converting ranges into lists of values.
597569
598570
Args:
599571
range_min (float or int): The minimum value of the range.
600572
range_max (float or int): The maximum value of the range.
601-
num_values_from_inf_range (int): The number of values to generate for an infinite range of float values using `numpy.linspace()`.
573+
sample_size_from_inf_range (int): The number of values to generate for an infinite range of float values using `numpy.linspace()`.
602574
603575
Returns:
604576
list: A list representing the unpacked gene space.
@@ -621,7 +593,7 @@ def unpack_gene_space(self,
621593
else:
622594
gene_space_unpacked = numpy.linspace(start=self.gene_space['low'],
623595
stop=self.gene_space['high'],
624-
num=num_values_from_inf_range,
596+
num=sample_size_from_inf_range,
625597
endpoint=False)
626598

627599
if self.gene_type_single == True:
@@ -662,7 +634,7 @@ def unpack_gene_space(self,
662634
elif type(space) is dict:
663635
# Create a list of values using the dict range.
664636
# Use numpy.linspace()
665-
dtype = self.get_gene_dtype(gene_index=gene_idx)
637+
dtype = self.get_gene_dtype(gene_index=space_idx)
666638

667639
if dtype[0] in pygad.GA.supported_int_types:
668640
if 'step' in space.keys():
@@ -681,7 +653,7 @@ def unpack_gene_space(self,
681653
else:
682654
gene_space_unpacked[space_idx] = numpy.linspace(start=space['low'],
683655
stop=space['high'],
684-
num=num_values_from_inf_range,
656+
num=sample_size_from_inf_range,
685657
endpoint=False)
686658
elif type(space) in [numpy.ndarray, list, tuple]:
687659
# list/tuple/numpy.ndarray

pygad/pygad.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ def __init__(self,
491491
max_val=self.init_range_high,
492492
mutation_by_replacement=self.mutation_by_replacement,
493493
gene_type=self.gene_type,
494-
num_values=100)
494+
sample_size=100)
495495
else:
496496
self.initial_population[initial_solution_idx], _, _ = self.solve_duplicate_genes_by_space(solution=initial_solution,
497497
gene_type=self.gene_type,
@@ -1450,7 +1450,7 @@ def initialize_population(self,
14501450
gene_idx=gene_idx,
14511451
mutation_by_replacement=True,
14521452
solution=solution,
1453-
num_values=100)
1453+
sample_size=100)
14541454
if random_values_filtered is None:
14551455
if not self.suppress_warnings:
14561456
warnings.warn(f"No value satisfied the constraint for the gene at index {gene_idx} while creating the initial population.")
@@ -1465,7 +1465,7 @@ def initialize_population(self,
14651465
max_val=high,
14661466
mutation_by_replacement=True,
14671467
gene_type=gene_type,
1468-
num_values=100)
1468+
sample_size=100)
14691469
# self.logger.info("After", self.population[solution_idx])
14701470

14711471
elif self.gene_space_nested:

pygad/utils/.DS_Store

6 KB
Binary file not shown.

pygad/utils/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
from pygad.utils import mutation
44
from pygad.utils import nsga2
55

6-
__version__ = "1.2.1"
6+
__version__ = "1.3.0"
-22 Bytes
Binary file not shown.
-21 Bytes
Binary file not shown.
Binary file not shown.
-22 Bytes
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)