Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 1305536

Browse files
Apply constraints in initial population
1 parent 01a614b commit 1305536

File tree

9 files changed

+187
-106
lines changed

9 files changed

+187
-106
lines changed

‎example.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ def fitness_func(ga_instance, solution, solution_idx):
1717
num_genes=num_genes,
1818
mutation_num_genes=6,
1919
fitness_func=fitness_func,
20+
init_range_low=4,
21+
init_range_high=10,
2022
# suppress_warnings=True,
2123
random_mutation_min_val=4,
2224
random_mutation_max_val=10,
@@ -25,4 +27,4 @@ def fitness_func(ga_instance, solution, solution_idx):
2527
# mutation_probability=0.4,
2628
gene_constraint=[lambda x: x[0]>=8,None,None,None,None,None])
2729

28-
ga_instance.run()
30+
# ga_instance.run()

‎pygad/__pycache__/pygad.cpython-310.pyc

514 Bytes
Binary file not shown.

‎pygad/helper/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
from pygad.helper import unique
2+
from pygad.helper import misc
23

3-
__version__ = "1.1.0"
4+
__version__ = "1.2.0"
27 Bytes
Binary file not shown.
4.82 KB
Binary file not shown.

‎pygad/helper/misc.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
"""
2+
The pygad.helper.helper module has some generic helper methods.
3+
"""
4+
5+
import numpy
6+
import warnings
7+
import random
8+
import pygad
9+
10+
class Helper:
11+
12+
def get_random_mutation_range(self, gene_index):
13+
14+
"""
15+
Returns the minimum and maximum values of the mutation range.
16+
It accepts a single parameter:
17+
-gene_index: The index of the gene to get its range. Only used if the gene has a specific mutation range
18+
It returns the minimum and maximum values of the gene mutation range.
19+
"""
20+
21+
# We can use either random_mutation_min_val or random_mutation_max_val.
22+
if type(self.random_mutation_min_val) in self.supported_int_float_types:
23+
range_min = self.random_mutation_min_val
24+
range_max = self.random_mutation_max_val
25+
else:
26+
range_min = self.random_mutation_min_val[gene_index]
27+
range_max = self.random_mutation_max_val[gene_index]
28+
return range_min, range_max
29+
30+
def get_initial_population_range(self, gene_index):
31+
32+
"""
33+
Returns the minimum and maximum values of the initial population range.
34+
It accepts a single parameter:
35+
-gene_index: The index of the gene to get its range. Only used if the gene has a specific range
36+
It returns the minimum and maximum values of the gene initial population range.
37+
"""
38+
39+
# We can use either init_range_low or init_range_high.
40+
if type(self.init_range_low) in self.supported_int_float_types:
41+
range_min = self.init_range_low
42+
range_max = self.init_range_high
43+
else:
44+
range_min = self.init_range_low[gene_index]
45+
range_max = self.init_range_high[gene_index]
46+
return range_min, range_max
47+
48+
def generate_gene_random_value(self,
49+
range_min,
50+
range_max,
51+
gene_value,
52+
gene_idx,
53+
mutation_by_replacement,
54+
num_values=1):
55+
"""
56+
Randomly generate one or more values for the gene.
57+
It accepts:
58+
-range_min: The minimum value in the range from which a value is selected.
59+
-range_max: The maximum value in the range from which a value is selected.
60+
-gene_value: The original gene value before applying mutation.
61+
-gene_idx: The index of the gene in the solution.
62+
-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.
63+
-num_values: The number of random valus to generate. It tries to generate a number of values up to a maximum of num_values. But it is not always guranteed because the total number of values might not be enough or the random generator creates duplicate random values.
64+
If num_values=1, it returns a single numeric value. If num_values>1, it returns an array with number of values equal to num_values.
65+
"""
66+
67+
# Generating a random value.
68+
random_value = numpy.asarray(numpy.random.uniform(low=range_min,
69+
high=range_max,
70+
size=num_values),
71+
dtype=object)
72+
73+
# Change the random mutation value data type.
74+
for idx, val in enumerate(random_value):
75+
random_value[idx] = self.change_random_mutation_value_dtype(random_value[idx],
76+
gene_idx,
77+
gene_value,
78+
mutation_by_replacement=mutation_by_replacement)
79+
80+
# Round the gene.
81+
random_value[idx] = self.round_random_mutation_value(random_value[idx], gene_idx)
82+
83+
# Rounding different values could return the same value multiple times.
84+
# For example, 2.8 and 2.7 will be 3.0.
85+
# Use the unique() function to avoid any duplicates.
86+
random_value = numpy.unique(random_value)
87+
88+
if num_values == 1:
89+
random_value = random_value[0]
90+
91+
return random_value
92+
93+
def get_valid_gene_constraint_values(self,
94+
range_min,
95+
range_max,
96+
gene_value,
97+
gene_idx,
98+
mutation_by_replacement,
99+
solution,
100+
num_values=100):
101+
"""
102+
Randomly generate values for the gene that satisfy the constraint.
103+
It accepts:
104+
-range_min: The minimum value in the range from which a value is selected.
105+
-range_max: The maximum value in the range from which a value is selected.
106+
-gene_value: The original gene value before applying mutation.
107+
-gene_idx: The index of the gene in the solution.
108+
-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.
109+
-solution: The solution in which the gene exists.
110+
-num_values: The number of random valus to generate. It tries to generate a number of values up to a maximum of num_values. But it is not always guranteed because the total number of values might not be enough or the random generator creates duplicate random values.
111+
If num_values=1, it returns a single numeric value. If num_values>1, it returns an array with number of values equal to num_values.
112+
"""
113+
random_values = self.generate_gene_random_value(range_min=range_min,
114+
range_max=range_max,
115+
gene_value=gene_value,
116+
gene_idx=gene_idx,
117+
mutation_by_replacement=mutation_by_replacement,
118+
num_values=num_values)
119+
random_values_filtered = self.mutation_filter_values_by_constraint(random_values=random_values,
120+
solution=solution,
121+
gene_idx=gene_idx)
122+
return random_values_filtered

‎pygad/pygad.py

Lines changed: 42 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class GA(utils.parent_selection.ParentSelection,
1515
utils.mutation.Mutation,
1616
utils.nsga2.NSGA2,
1717
helper.unique.Unique,
18+
helper.misc.Helper,
1819
visualize.plot.Plot):
1920

2021
supported_int_types = [int, numpy.int8, numpy.int16, numpy.int32, numpy.int64,
@@ -435,7 +436,8 @@ def __init__(self,
435436
high=self.init_range_high,
436437
allow_duplicate_genes=allow_duplicate_genes,
437438
mutation_by_replacement=True,
438-
gene_type=self.gene_type)
439+
gene_type=self.gene_type,
440+
gene_constraint=gene_constraint)
439441
else:
440442
self.valid_parameters = False
441443
raise TypeError(f"The expected type of both the sol_per_pop and num_genes parameters is int but {type(sol_per_pop)} and {type(num_genes)} found.")
@@ -1377,12 +1379,18 @@ def initialize_population(self,
13771379
high,
13781380
allow_duplicate_genes,
13791381
mutation_by_replacement,
1380-
gene_type):
1382+
gene_type,
1383+
gene_constraint):
13811384
"""
13821385
Creates an initial population randomly as a NumPy array. The array is saved in the instance attribute named 'population'.
13831386
1384-
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.
1385-
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.
1387+
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.
1390+
-allow_duplicate_genes: Whether duplicate genes are allowed or not.
1391+
-mutation_by_replacement: Whether mutation by replacement is enabled or not.
1392+
-gene_type: The data type of the genes.
1393+
-gene_constraint: The constraints of the genes.
13861394
13871395
This method assigns the values of the following 3 instance attributes:
13881396
1. pop_size: Size of the population.
@@ -1397,23 +1405,19 @@ def initialize_population(self,
13971405
if self.gene_space is None:
13981406
# Creating the initial population randomly.
13991407
if self.gene_type_single == True:
1408+
# A NumPy array holding the initial population.
14001409
self.population = numpy.asarray(numpy.random.uniform(low=low,
14011410
high=high,
14021411
size=self.pop_size),
1403-
dtype=self.gene_type[0])# A NumPy array holding the initial population.
1412+
dtype=self.gene_type[0])
14041413
else:
14051414
# Create an empty population of dtype=object to support storing mixed data types within the same array.
14061415
self.population = numpy.zeros(
14071416
shape=self.pop_size, dtype=object)
14081417
# 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.
14091418
for gene_idx in range(self.num_genes):
14101419

1411-
if type(self.init_range_low) in self.supported_int_float_types:
1412-
range_min = self.init_range_low
1413-
range_max = self.init_range_high
1414-
else:
1415-
range_min = self.init_range_low[gene_idx]
1416-
range_max = self.init_range_high[gene_idx]
1420+
range_min, range_max = self.get_initial_population_range(gene_index=gene_idx)
14171421

14181422
# A vector of all values of this single gene across all solutions in the population.
14191423
gene_values = numpy.asarray(numpy.random.uniform(low=range_min,
@@ -1423,6 +1427,30 @@ def initialize_population(self,
14231427
# Adding the current gene values to the population.
14241428
self.population[:, gene_idx] = gene_values
14251429

1430+
# Enforce the gene constraints as much as possible.
1431+
if gene_constraint is None:
1432+
pass
1433+
else:
1434+
# Note that gene_constraint is not validated yet.
1435+
# We have to set it as a propery of the pygad.GA instance to retrieve without passing it as an additional parameter.
1436+
self.gene_constraint = gene_constraint
1437+
for solution in self.population:
1438+
for gene_idx in range(self.num_genes):
1439+
# Check that a constraint is available for the gene and that the current value does not satisfy that constraint
1440+
if self.gene_constraint[gene_idx]:
1441+
print(gene_idx, solution[gene_idx])
1442+
if not self.gene_constraint[gene_idx](solution):
1443+
range_min, range_max = self.get_initial_population_range(gene_index=gene_idx)
1444+
# While initializing the population, we follow a mutation by replacement approach. So, the gene value is not needed.
1445+
random_values_filtered = self.get_valid_gene_constraint_values(range_min=range_min,
1446+
range_max=range_max,
1447+
gene_value=None,
1448+
gene_idx=gene_idx,
1449+
mutation_by_replacement=True,
1450+
solution=solution,
1451+
num_values=100)
1452+
print(gene_idx, random_values_filtered)
1453+
14261454
if allow_duplicate_genes == False:
14271455
for solution_idx in range(self.population.shape[0]):
14281456
# self.logger.info("Before", self.population[solution_idx])
@@ -1444,12 +1472,7 @@ def initialize_population(self,
14441472
for sol_idx in range(self.sol_per_pop):
14451473
for gene_idx in range(self.num_genes):
14461474

1447-
if type(self.init_range_low) in self.supported_int_float_types:
1448-
range_min = self.init_range_low
1449-
range_max = self.init_range_high
1450-
else:
1451-
range_min = self.init_range_low[gene_idx]
1452-
range_max = self.init_range_high[gene_idx]
1475+
range_min, range_max = self.get_initial_population_range(gene_index=gene_idx)
14531476

14541477
if self.gene_space[gene_idx] is None:
14551478

@@ -1524,12 +1547,7 @@ def initialize_population(self,
15241547
for sol_idx in range(self.sol_per_pop):
15251548
for gene_idx in range(self.num_genes):
15261549

1527-
if type(self.init_range_low) in self.supported_int_float_types:
1528-
range_min = self.init_range_low
1529-
range_max = self.init_range_high
1530-
else:
1531-
range_min = self.init_range_low[gene_idx]
1532-
range_max = self.init_range_high[gene_idx]
1550+
range_min, range_max = self.get_initial_population_range(gene_index=gene_idx)
15331551

15341552
if type(self.gene_space[gene_idx]) in [numpy.ndarray, list, tuple, range]:
15351553
# Convert to list because tuple and range do not have copy().
@@ -1584,12 +1602,7 @@ def initialize_population(self,
15841602
# Replace all the None values with random values using the init_range_low, init_range_high, and gene_type attributes.
15851603
for gene_idx, curr_gene_space in enumerate(self.gene_space):
15861604

1587-
if type(self.init_range_low) in self.supported_int_float_types:
1588-
range_min = self.init_range_low
1589-
range_max = self.init_range_high
1590-
else:
1591-
range_min = self.init_range_low[gene_idx]
1592-
range_max = self.init_range_high[gene_idx]
1605+
range_min, range_max = self.get_initial_population_range(gene_index=gene_idx)
15931606

15941607
if curr_gene_space is None:
15951608
self.gene_space[gene_idx] = numpy.asarray(numpy.random.uniform(low=range_min,
-1.39 KB
Binary file not shown.

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /