Skip to content

Commit 47b33dd

Browse files
author
Ahmed Gad
committed
Initialize population with gene constraints
1 parent 6a3b58b commit 47b33dd

File tree

10 files changed

+196
-159
lines changed

10 files changed

+196
-159
lines changed

.DS_Store

0 Bytes
Binary file not shown.

example.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ def fitness_func(ga_instance, solution, solution_idx):
1111

1212
num_genes = len(function_inputs)
1313

14+
calls = 0
15+
def const(x):
16+
global calls
17+
calls += 1
18+
return x[0] >= 90
19+
1420
ga_instance = pygad.GA(num_generations=100,
1521
num_parents_mating=10,
1622
sol_per_pop=20,
@@ -23,15 +29,21 @@ def fitness_func(ga_instance, solution, solution_idx):
2329
random_mutation_min_val=1,
2430
random_mutation_max_val=100,
2531
mutation_by_replacement=True,
26-
gene_type=[float, 1],
27-
save_solutions=True,
28-
allow_duplicate_genes=False,
32+
# gene_type=[float, 1],
33+
gene_type=[float, [float, 5], [float, 4], [float, 3], [float, 2], [float, 1]],
34+
# save_solutions=True,
35+
# allow_duplicate_genes=False,
2936
# 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))],
31-
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],
37+
gene_space=[range(100), {"low": 1, "high": 100}, 2.5891221, [1,2,3,4], None, numpy.unique(numpy.random.uniform(1, 100, size=4))],
38+
# gene_space=numpy.linspace(1, 100, 200),
39+
gene_constraint=[const,lambda x: x[1]>=90,lambda x: x[2]<98,lambda x: x[3]<98,lambda x: x[4]<98,lambda x: x[5]<98],
3240
)
3341

42+
# print(ga_instance.initial_population)
43+
3444
ga_instance.run()
3545

36-
print(ga_instance.gene_space_unpacked)
46+
# print(ga_instance.gene_space_unpacked)
3747
print(ga_instance.population)
48+
49+
print(calls)
-813 Bytes
Binary file not shown.
1.38 KB
Binary file not shown.
92 Bytes
Binary file not shown.

pygad/helper/misc.py

Lines changed: 135 additions & 74 deletions
Large diffs are not rendered by default.

pygad/helper/unique.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,25 @@ def solve_duplicate_genes_randomly(self,
4444
for duplicate_index in not_unique_indices:
4545
dtype = self.get_gene_dtype(gene_index=duplicate_index)
4646

47+
if type(min_val) in self.supported_int_float_types:
48+
min_val_gene = min_val
49+
max_val_gene = max_val
50+
else:
51+
min_val_gene = min_val[duplicate_index]
52+
max_val_gene = max_val[duplicate_index]
53+
4754
if dtype[0] in pygad.GA.supported_int_types:
4855
temp_val = self.unique_int_gene_from_range(solution=new_solution,
4956
gene_index=duplicate_index,
50-
min_val=min_val,
51-
max_val=max_val,
57+
min_val=min_val_gene,
58+
max_val=max_val_gene,
5259
mutation_by_replacement=mutation_by_replacement,
5360
gene_type=gene_type)
5461
else:
5562
temp_val = self.unique_float_gene_from_range(solution=new_solution,
5663
gene_index=duplicate_index,
57-
min_val=min_val,
58-
max_val=max_val,
64+
min_val=min_val_gene,
65+
max_val=max_val_gene,
5966
mutation_by_replacement=mutation_by_replacement,
6067
gene_type=gene_type,
6168
sample_size=sample_size)
@@ -504,7 +511,7 @@ def unique_gene_by_space(self,
504511
# This only leaves the unique values that could be selected for the gene.
505512

506513
# 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))
514+
values_to_select_from = list(set(self.gene_space_unpacked[gene_idx]) - set(solution))
508515

509516
if len(values_to_select_from) == 0:
510517
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.")

pygad/pygad.py

Lines changed: 31 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -432,9 +432,7 @@ def __init__(self,
432432

433433
# Number of solutions in the population.
434434
self.sol_per_pop = sol_per_pop
435-
self.initialize_population(low=self.init_range_low,
436-
high=self.init_range_high,
437-
allow_duplicate_genes=allow_duplicate_genes,
435+
self.initialize_population(allow_duplicate_genes=allow_duplicate_genes,
438436
mutation_by_replacement=True,
439437
gene_type=self.gene_type,
440438
gene_constraint=gene_constraint)
@@ -457,32 +455,7 @@ def __init__(self,
457455
self.valid_parameters = False
458456
raise TypeError(f"The values in the initial population can be integers or floats but the value ({initial_population[row_idx][col_idx]}) of type {type(initial_population[row_idx][col_idx])} found.")
459457

460-
# Forcing the initial_population array to have the data type assigned to the gene_type parameter.
461-
if self.gene_type_single == True:
462-
if self.gene_type[1] == None:
463-
self.initial_population = numpy.array(initial_population,
464-
dtype=self.gene_type[0])
465-
else:
466-
# This block is reached only for non-integer data types (i.e. float).
467-
self.initial_population = numpy.round(numpy.array(initial_population,
468-
dtype=self.gene_type[0]),
469-
self.gene_type[1])
470-
else:
471-
initial_population = numpy.array(initial_population)
472-
self.initial_population = numpy.zeros(shape=(initial_population.shape[0],
473-
initial_population.shape[1]),
474-
dtype=object)
475-
for gene_idx in range(initial_population.shape[1]):
476-
if self.gene_type[gene_idx][1] is None:
477-
self.initial_population[:, gene_idx] = numpy.asarray(initial_population[:, gene_idx],
478-
dtype=self.gene_type[gene_idx][0])
479-
else:
480-
# This block is reached only for non-integer data types (i.e. float).
481-
self.initial_population[:, gene_idx] = numpy.round(numpy.asarray(initial_population[:, gene_idx],
482-
dtype=self.gene_type[gene_idx][0]),
483-
self.gene_type[gene_idx][1])
484-
485-
# Check if duplicates are allowed. If not, then solve any exisiting duplicates in the passed initial population.
458+
# Check if duplicates are allowed. If not, then solve any existing duplicates in the passed initial population.
486459
if self.allow_duplicate_genes == False:
487460
for initial_solution_idx, initial_solution in enumerate(self.initial_population):
488461
if self.gene_space is None:
@@ -497,6 +470,9 @@ def __init__(self,
497470
gene_type=self.gene_type,
498471
num_trials=10)
499472

473+
# Change the data type and round all genes within the initial population.
474+
self.initial_population = self.change_population_dtype_and_round(initial_population)
475+
500476
# A NumPy array holding the initial population.
501477
self.population = self.initial_population.copy()
502478
# Number of genes in the solution.
@@ -506,9 +482,9 @@ def __init__(self,
506482
# The population size.
507483
self.pop_size = (self.sol_per_pop, self.num_genes)
508484

509-
# Round initial_population and population
510-
self.initial_population = self.round_genes(self.initial_population)
511-
self.population = self.round_genes(self.population)
485+
# Change the data type and round all genes within the initial population.
486+
self.initial_population = self.change_population_dtype_and_round(self.initial_population)
487+
self.population = self.initial_population.copy()
512488

513489
# In case the 'gene_space' parameter is nested, then make sure the number of its elements equals to the number of genes.
514490
if self.gene_space_nested:
@@ -1375,8 +1351,6 @@ def round_genes(self, solutions):
13751351
return solutions
13761352

13771353
def initialize_population(self,
1378-
low,
1379-
high,
13801354
allow_duplicate_genes,
13811355
mutation_by_replacement,
13821356
gene_type,
@@ -1385,8 +1359,6 @@ def initialize_population(self,
13851359
Creates an initial population randomly as a NumPy array. The array is saved in the instance attribute named 'population'.
13861360
13871361
It accepts:
1388-
-low: The lower value of the random range from which the gene values in the initial population are selected. It defaults to -4. Available in PyGAD 1.0.20 and higher.
1389-
-high: The upper value of the random range from which the gene values in the initial population are selected. It defaults to -4. Available in PyGAD 1.0.20.
13901362
-allow_duplicate_genes: Whether duplicate genes are allowed or not.
13911363
-mutation_by_replacement: Whether mutation by replacement is enabled or not.
13921364
-gene_type: The data type of the genes.
@@ -1403,39 +1375,31 @@ def initialize_population(self,
14031375
self.pop_size = (self.sol_per_pop, self.num_genes)
14041376

14051377
if self.gene_space is None:
1406-
# Creating the initial population randomly.
1407-
if self.gene_type_single == True:
1408-
# A NumPy array holding the initial population.
1409-
self.population = numpy.asarray(numpy.random.uniform(low=low,
1410-
high=high,
1411-
size=self.pop_size),
1412-
dtype=self.gene_type[0])
1413-
else:
1414-
# Create an empty population of dtype=object to support storing mixed data types within the same array.
1415-
self.population = numpy.zeros(
1416-
shape=self.pop_size, dtype=object)
1417-
# Loop through the genes, randomly generate the values of a single gene across the entire population, and add the values of each gene to the population.
1418-
for gene_idx in range(self.num_genes):
1378+
# Create the initial population randomly.
14191379

1420-
range_min, range_max = self.get_initial_population_range(gene_index=gene_idx)
1380+
# Create an empty population.
1381+
self.population = numpy.zeros(shape=self.pop_size)
1382+
1383+
# Loop through the genes, randomly generate the values of a single gene across the entire population, and add the values of each gene to the population.
1384+
for gene_idx in range(self.num_genes):
1385+
range_min, range_max = self.get_initial_population_range(gene_index=gene_idx)
14211386

1422-
# A vector of all values of this single gene across all solutions in the population.
1423-
gene_values = numpy.asarray(numpy.random.uniform(low=range_min,
1424-
high=range_max,
1425-
size=self.pop_size[0]),
1426-
dtype=self.gene_type[gene_idx][0])
1427-
# Adding the current gene values to the population.
1428-
self.population[:, gene_idx] = gene_values
1387+
# A vector of all values of this single gene across all solutions in the population.
1388+
gene_values = numpy.random.uniform(low=range_min,
1389+
high=range_max,
1390+
size=self.pop_size[0])
1391+
# Adding the current gene values to the population.
1392+
self.population[:, gene_idx] = gene_values
14291393

1430-
# Round the randomly generated values.
1431-
self.population = self.round_genes(self.population)
1394+
# Change the data type and round all genes within the initial population.
1395+
self.population = self.change_population_dtype_and_round(self.population)
14321396

14331397
# Enforce the gene constraints as much as possible.
14341398
if gene_constraint is None:
14351399
pass
14361400
else:
14371401
# Note that gene_constraint is not validated yet.
1438-
# We have to set it as a propery of the pygad.GA instance to retrieve without passing it as an additional parameter.
1402+
# We have to set it as a property of the pygad.GA instance to retrieve without passing it as an additional parameter.
14391403
self.gene_constraint = gene_constraint
14401404
for sol_idx, solution in enumerate(self.population):
14411405
for gene_idx in range(self.num_genes):
@@ -1461,12 +1425,11 @@ def initialize_population(self,
14611425
for solution_idx in range(self.population.shape[0]):
14621426
# self.logger.info("Before", self.population[solution_idx])
14631427
self.population[solution_idx], _, _ = self.solve_duplicate_genes_randomly(solution=self.population[solution_idx],
1464-
min_val=low,
1465-
max_val=high,
1428+
min_val=self.init_range_low,
1429+
max_val=self.init_range_high,
14661430
mutation_by_replacement=True,
14671431
gene_type=gene_type,
14681432
sample_size=100)
1469-
# self.logger.info("After", self.population[solution_idx])
14701433

14711434
elif self.gene_space_nested:
14721435
if self.gene_type_single == True:
@@ -1482,13 +1445,6 @@ def initialize_population(self,
14821445

14831446
if self.gene_space[gene_idx] is None:
14841447

1485-
# The following commented code replace the None value with a single number that will not change again.
1486-
# This means the gene value will be the same across all solutions.
1487-
# self.gene_space[gene_idx] = numpy.asarray(numpy.random.uniform(low=low,
1488-
# high=high,
1489-
# size=1), dtype=self.gene_type[0])[0]
1490-
# self.population[sol_idx, gene_idx] = list(self.gene_space[gene_idx]).copy()
1491-
14921448
# The above problem is solved by keeping the None value in the gene_space parameter. This forces PyGAD to generate this value for each solution.
14931449
self.population[sol_idx, gene_idx] = numpy.asarray(numpy.random.uniform(low=range_min,
14941450
high=range_max,
@@ -1503,8 +1459,7 @@ def initialize_population(self,
15031459
# We copy the gene_space to a temp variable to keep its original value.
15041460
# In the next for loop, the gene_space is changed.
15051461
# Later, the gene_space is restored to its original value using the temp variable.
1506-
temp_gene_space = list(
1507-
self.gene_space[gene_idx]).copy()
1462+
temp_gene_space = list(self.gene_space[gene_idx]).copy()
15081463

15091464
for idx, val in enumerate(self.gene_space[gene_idx]):
15101465
if val is None:
@@ -1586,7 +1541,7 @@ def initialize_population(self,
15861541
high=self.gene_space[gene_idx]['high'],
15871542
size=1),
15881543
dtype=self.gene_type[gene_idx][0])[0]
1589-
elif type(self.gene_space[gene_idx]) == type(None):
1544+
elif self.gene_space[gene_idx] is None:
15901545
temp_gene_value = numpy.asarray(numpy.random.uniform(low=range_min,
15911546
high=range_max,
15921547
size=1),
@@ -1676,7 +1631,9 @@ def initialize_population(self,
16761631
# Adding the current gene values to the population.
16771632
self.population[:, gene_idx] = gene_values
16781633

1679-
if not (self.gene_space is None):
1634+
if self.gene_space is None:
1635+
pass
1636+
else:
16801637
if allow_duplicate_genes == False:
16811638
for sol_idx in range(self.population.shape[0]):
16821639
self.population[sol_idx], _, _ = self.solve_duplicate_genes_by_space(solution=self.population[sol_idx],
Binary file not shown.
0 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)