Skip to content

Commit f83c973

Browse files
committed
PyGAD 3.3.0
Release Date 29 January 2024 1. Solve bugs when multi-objective optimization is used. #238 2. When the `stop_ciiteria` parameter is used with the `reach` keyword, then multiple numeric values can be passed when solving a multi-objective problem. For example, if a problem has 3 objective functions, then `stop_criteria="reach_10_20_30"` means the GA stops if the fitness of the 3 objectives are at least 10, 20, and 30, respectively. The number values must match the number of objective functions. If a single value found (e.g. `stop_criteria=reach_5`) when solving a multi-objective problem, then it is used across all the objectives. #238 3. The `delay_after_gen` parameter is now deprecated and will be removed in a future release. If it is necessary to have a time delay after each generation, then assign a callback function/method to the `on_generation` parameter to pause the evolution. 4. Parallel processing now supports calculating the fitness during adaptive mutation. #201 5. The population size can be changed during runtime by changing all the parameters that would affect the size of any thing used by the GA. For more information, check the [Change Population Size during Runtime](https://pygad.readthedocs.io/en/latest/pygad_more.html#change-population-size-during-runtime) section. #234 6. When a dictionary exists in the `gene_space` parameter without a step, then mutation occurs by adding a random value to the gene value. The random vaue is generated based on the 2 parameters `random_mutation_min_val` and `random_mutation_max_val`. For more information, check the [How Mutation Works with the gene_space Parameter?](https://pygad.readthedocs.io/en/latest/pygad_more.html#how-mutation-works-with-the-gene-space-parameter) section. #229 7. Add `object` as a supported data type for int (GA.supported_int_types) and float (GA.supported_float_types). #174 8. Use the `raise` clause instead of the `sys.exit(-1)` to terminate the execution. #213 9. Fix a bug when multi-objective optimization is used with batch fitness calculation (e.g. `fitness_batch_size` set to a non-zero number). 10. Fix a bug in the `pygad.py` script when finding the index of the best solution. It does not work properly with multi-objective optimization where `self.best_solutions_fitness` have multiple columns.
1 parent 1615baf commit f83c973

File tree

6 files changed

+231
-45
lines changed

6 files changed

+231
-45
lines changed

docs/source/conf.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
# -- Project information -----------------------------------------------------
1919

2020
project = 'PyGAD'
21-
copyright = '2023, Ahmed Fawzy Gad'
21+
copyright = '2024, Ahmed Fawzy Gad'
2222
author = 'Ahmed Fawzy Gad'
2323

2424
# The full version, including alpha/beta/rc tags
25-
release = '3.2.0'
25+
release = '3.3.0'
2626

2727
master_doc = 'index'
2828

docs/source/pygad_more.rst

+104-4
Original file line numberDiff line numberDiff line change
@@ -344,10 +344,13 @@ is applied based on this parameter.
344344
How Mutation Works with the ``gene_space`` Parameter?
345345
-----------------------------------------------------
346346

347-
If a gene has its static space defined in the ``gene_space`` parameter,
348-
then mutation works by replacing the gene value by a value randomly
349-
selected from the gene space. This happens for both ``int`` and
350-
``float`` data types.
347+
Mutation changes based on whether the ``gene_space`` has a continuous
348+
range or discrete set of values.
349+
350+
If a gene has its **static/discrete space** defined in the
351+
``gene_space`` parameter, then mutation works by replacing the gene
352+
value by a value randomly selected from the gene space. This happens for
353+
both ``int`` and ``float`` data types.
351354

352355
For example, the following ``gene_space`` has the static space
353356
``[1, 2, 3]`` defined for the first gene. So, this gene can only have a
@@ -377,6 +380,39 @@ If its current value is 5 and the random value is ``-0.5``, then the new
377380
value is 4.5. If the gene type is integer, then the value will be
378381
rounded.
379382

383+
On the other hand, if a gene has a **continuous space** defined in the
384+
``gene_space`` parameter, then mutation occurs by adding a random value
385+
to the current gene value.
386+
387+
For example, the following ``gene_space`` has the continuous space
388+
defined by the dictionary ``{'low': 1, 'high': 5}``. This applies to all
389+
genes. So, mutation is applied to one or more selected genes by adding a
390+
random value to the current gene value.
391+
392+
.. code:: python
393+
394+
Gene space: {'low': 1, 'high': 5}
395+
Solution: [1.5, 3.4]
396+
397+
Assuming ``random_mutation_min_val=-1`` and
398+
``random_mutation_max_val=1``, then a random value such as ``0.3`` can
399+
be added to the gene(s) participating in mutation. If only the first
400+
gene is mutated, then its new value changes from ``1.5`` to
401+
``1.5+0.3=1.8``. Note that PyGAD verifies that the new value is within
402+
the range. In the worst scenarios, the value will be set to either
403+
boundary of the continuous range. For example, if the gene value is 1.5
404+
and the random value is -0.55, then the new value is 0.95 which smaller
405+
than the lower boundary 1. Thus, the gene value will be rounded to 1.
406+
407+
If the dictionary has a step like the example below, then it is
408+
considered a discrete range and mutation occurs by randomly selecting a
409+
value from the set of values. In other words, no random value is added
410+
to the gene value.
411+
412+
.. code:: python
413+
414+
Gene space: {'low': 1, 'high': 5, 'step': 0.5}
415+
380416
Stop at Any Generation
381417
======================
382418

@@ -663,6 +699,70 @@ Note that the 2 attributes (``self.best_solutions`` and
663699
attributes (``self.solutions`` and ``self.solutions_fitness``) only work
664700
if the ``save_solutions`` parameter is ``True``.
665701

702+
Change Population Size during Runtime
703+
=====================================
704+
705+
Starting from `PyGAD
706+
3.3.0 <https://pygad.readthedocs.io/en/latest/releases.html#pygad-3-3-0>`__,
707+
the population size can changed during runtime. In other words, the
708+
number of solutions/chromosomes and number of genes can be changed.
709+
710+
The user has to carefully arrange the list of *parameters* and *instance
711+
attributes* that have to be changed to keep the GA consistent before and
712+
after changing the population size. Generally, change everything that
713+
would be used during the GA evolution.
714+
715+
CAUTION: If the user failed to change a parameter or an instance
716+
attributes necessary to keep the GA running after the population size
717+
changed, errors will arise.
718+
719+
These are examples of the parameters that the user should decide whether
720+
to change. The user should check the `list of
721+
parameters <https://pygad.readthedocs.io/en/latest/pygad.html#init>`__
722+
and decide what to change.
723+
724+
1. ``population``: The population. It *must* be changed.
725+
726+
2. ``num_offspring``: The number of offspring to produce out of the
727+
crossover and mutation operations. Change this parameter if the
728+
number of offspring have to be changed to be consistent with the new
729+
population size.
730+
731+
3. ``num_parents_mating``: The number of solutions to select as parents.
732+
Change this parameter if the number of parents have to be changed to
733+
be consistent with the new population size.
734+
735+
4. ``fitness_func``: If the way of calculating the fitness changes after
736+
the new population size, then the fitness function have to be
737+
changed.
738+
739+
5. ``sol_per_pop``: The number of solutions per population. It is not
740+
critical to change it but it is recommended to keep this number
741+
consistent with the number of solutions in the ``population``
742+
parameter.
743+
744+
These are examples of the instance attributes that might be changed. The
745+
user should check the `list of instance
746+
attributes <https://pygad.readthedocs.io/en/latest/pygad.html#other-instance-attributes-methods>`__
747+
and decide what to change.
748+
749+
1. All the ``last_generation_*`` parameters
750+
751+
1. ``last_generation_fitness``: A 1D NumPy array of fitness values of
752+
the population.
753+
754+
2. ``last_generation_parents`` and
755+
``last_generation_parents_indices``: Two NumPy arrays: 2D array
756+
representing the parents and 1D array of the parents indices.
757+
758+
3. ``last_generation_elitism`` and
759+
``last_generation_elitism_indices``: Must be changed if
760+
``keep_elitism != 0``. The default value of ``keep_elitism`` is 1.
761+
Two NumPy arrays: 2D array representing the elitism and 1D array
762+
of the elitism indices.
763+
764+
2. ``pop_size``: The population size.
765+
666766
Prevent Duplicates in Gene Values
667767
=================================
668768

docs/source/releases.rst

+60-13
Original file line numberDiff line numberDiff line change
@@ -1464,26 +1464,73 @@ Release Date 7 September 2023
14641464
class is removed. Instead, please use the ``plot_fitness()`` if you
14651465
did not upgrade yet.
14661466

1467-
.. _pygad-321:
1467+
.. _pygad-330:
14681468

1469-
PyGAD 3.2.1
1469+
PyGAD 3.3.0
14701470
-----------
14711471

1472-
Release Date ... 2023
1472+
Release Date 29 January 2024
14731473

1474-
1. Fix a bug when multi-objective optimization is used with batch
1475-
fitness calculation (e.g. ``fitness_batch_size`` set to a non-zero
1476-
number).
1474+
1. Solve bugs when multi-objective optimization is used.
1475+
https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/238
14771476

1478-
2. Fix a bug in the ``pygad.py`` script when finding the index of the
1479-
best solution. It does not work properly with multi-objective
1480-
optimization where ``self.best_solutions_fitness`` have multiple
1481-
columns.
1477+
2. When the ``stop_ciiteria`` parameter is used with the ``reach``
1478+
keyword, then multiple numeric values can be passed when solving a
1479+
multi-objective problem. For example, if a problem has 3 objective
1480+
functions, then ``stop_criteria="reach_10_20_30"`` means the GA
1481+
stops if the fitness of the 3 objectives are at least 10, 20, and
1482+
30, respectively. The number values must match the number of
1483+
objective functions. If a single value found (e.g.
1484+
``stop_criteria=reach_5``) when solving a multi-objective problem,
1485+
then it is used across all the objectives.
1486+
https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/238
14821487

1483-
.. code:: python
1488+
3. The ``delay_after_gen`` parameter is now deprecated and will be
1489+
removed in a future release. If it is necessary to have a time delay
1490+
after each generation, then assign a callback function/method to the
1491+
``on_generation`` parameter to pause the evolution.
14841492

1485-
self.best_solution_generation = numpy.where(numpy.array(
1486-
self.best_solutions_fitness) == numpy.max(numpy.array(self.best_solutions_fitness)))[0][0]
1493+
4. Parallel processing now supports calculating the fitness during
1494+
adaptive mutation.
1495+
https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/201
1496+
1497+
5. The population size can be changed during runtime by changing all
1498+
the parameters that would affect the size of any thing used by the
1499+
GA. For more information, check the `Change Population Size during
1500+
Runtime <https://pygad.readthedocs.io/en/latest/pygad_more.html#change-population-size-during-runtime>`__
1501+
section.
1502+
https://github.com/ahmedfgad/GeneticAlgorithmPython/discussions/234
1503+
1504+
6. When a dictionary exists in the ``gene_space`` parameter without a
1505+
step, then mutation occurs by adding a random value to the gene
1506+
value. The random vaue is generated based on the 2 parameters
1507+
``random_mutation_min_val`` and ``random_mutation_max_val``. For
1508+
more information, check the `How Mutation Works with the gene_space
1509+
Parameter? <https://pygad.readthedocs.io/en/latest/pygad_more.html#how-mutation-works-with-the-gene-space-parameter>`__
1510+
section.
1511+
https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/229
1512+
1513+
7. Add ``object`` as a supported data type for int
1514+
(GA.supported_int_types) and float (GA.supported_float_types).
1515+
https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/174
1516+
1517+
8. Use the ``raise`` clause instead of the ``sys.exit(-1)`` to
1518+
terminate the execution.
1519+
https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/213
1520+
1521+
9. Fix a bug when multi-objective optimization is used with batch
1522+
fitness calculation (e.g. ``fitness_batch_size`` set to a non-zero
1523+
number).
1524+
1525+
10. Fix a bug in the ``pygad.py`` script when finding the index of the
1526+
best solution. It does not work properly with multi-objective
1527+
optimization where ``self.best_solutions_fitness`` have multiple
1528+
columns.
1529+
1530+
.. code:: python
1531+
1532+
self.best_solution_generation = numpy.where(numpy.array(
1533+
self.best_solutions_fitness) == numpy.max(numpy.array(self.best_solutions_fitness)))[0][0]
14871534
14881535
PyGAD Projects at GitHub
14891536
========================

examples/example_dynamic_population_size.py

+63-24
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,83 @@
33

44
"""
55
This is an example to dynamically change the population size (i.e. number of solutions/chromosomes per population) during runtime.
6-
The following 2 instance attributes must be changed to meet the new desired population size:
7-
1) population: This is a NumPy array holding the population.
8-
2) num_offspring: This represents the number of offspring to produce during crossover.
9-
For example, if the population initially has 20 solutions and 6 genes. To change it to have 30 solutions, then:
10-
1)population: Create a new NumPy array with the desired size (30, 6) and assign it to the population instance attribute.
11-
2)num_offspring: Set the num_offspring attribute accordingly (e.g. 29 assuming that keep_elitism has the default value of 1).
6+
7+
The user has to carefully inspect the parameters and instance attributes to select those that must be changed to be consistent with the new population size.
8+
Check this link for more information: https://pygad.readthedocs.io/en/latest/pygad_more.html#change-population-size-during-runtime
129
"""
1310

11+
def update_GA(ga_i,
12+
pop_size):
13+
"""
14+
Update the parameters and instance attributes to match the new population size.
15+
16+
Parameters
17+
----------
18+
ga_i : TYPE
19+
The pygad.GA instance.
20+
pop_size : TYPE
21+
The new population size.
22+
23+
Returns
24+
-------
25+
None.
26+
"""
27+
28+
ga_i.pop_size = pop_size
29+
ga_i.sol_per_pop = ga_i.pop_size[0]
30+
ga_i.num_parents_mating = int(ga_i.pop_size[0]/2)
31+
32+
# Calculate the new value for the num_offspring parameter.
33+
if ga_i.keep_elitism != 0:
34+
ga_i.num_offspring = ga_i.sol_per_pop - ga_i.keep_elitism
35+
elif ga_i.keep_parents != 0:
36+
if ga_i.keep_parents == -1:
37+
ga_i.num_offspring = ga_i.sol_per_pop - ga_i.num_parents_mating
38+
else:
39+
ga_i.num_offspring = ga_i.sol_per_pop - ga_i.keep_parents
40+
41+
ga_i.num_genes = ga_i.pop_size[1]
42+
ga_i.population = numpy.random.uniform(low=ga_i.init_range_low,
43+
high=ga_i.init_range_low,
44+
size=ga_i.pop_size)
45+
fitness = []
46+
for solution, solution_idx in enumerate(ga_i.population):
47+
fitness.append(fitness_func(ga_i, solution, solution_idx))
48+
ga_i.last_generation_fitness = numpy.array(fitness)
49+
parents, parents_fitness = ga_i.steady_state_selection(ga_i.last_generation_fitness,
50+
ga_i.num_parents_mating)
51+
ga_i.last_generation_elitism = parents[:ga_i.keep_elitism]
52+
ga_i.last_generation_elitism_indices = parents_fitness[:ga_i.keep_elitism]
53+
54+
ga_i.last_generation_parents = parents
55+
ga_i.last_generation_parents_indices = parents_fitness
56+
1457
def fitness_func(ga_instance, solution, solution_idx):
15-
return [numpy.random.rand(), numpy.random.rand()]
58+
return numpy.sum(solution)
1659

1760
def on_generation(ga_i):
1861
# The population starts with 20 solutions.
19-
print(ga_i.generations_completed, ga_i.num_offspring, ga_i.population.shape)
20-
# At generation 15, increase the population size to 40 solutions.
62+
print(ga_i.generations_completed, ga_i.population.shape)
63+
# At generation 15, set the population size to 30 solutions and 10 genes.
2164
if ga_i.generations_completed >= 15:
22-
ga_i.num_offspring = 49
23-
new_population = numpy.zeros(shape=(ga_i.num_offspring+1, ga_i.population.shape[1]), dtype=ga_i.population.dtype)
24-
new_population[:ga_i.population.shape[0], :] = ga_i.population
25-
ga_i.population = new_population
65+
ga_i.pop_size = (30, 10)
66+
update_GA(ga_i=ga_i,
67+
pop_size=(30, 10))
68+
# At generation 10, set the population size to 15 solutions and 8 genes.
2669
elif ga_i.generations_completed >= 10:
27-
ga_i.num_offspring = 39
28-
new_population = numpy.zeros(shape=(ga_i.num_offspring+1, ga_i.population.shape[1]), dtype=ga_i.population.dtype)
29-
new_population[:ga_i.population.shape[0], :] = ga_i.population
30-
ga_i.population = new_population
31-
# At generation 10, increase the population size to 30 solutions.
70+
update_GA(ga_i=ga_i,
71+
pop_size=(15, 8))
72+
# At generation 5, set the population size to 10 solutions and 3 genes.
3273
elif ga_i.generations_completed >= 5:
33-
ga_i.num_offspring = 29
34-
new_population = numpy.zeros(shape=(ga_i.num_offspring+1, ga_i.population.shape[1]), dtype=ga_i.population.dtype)
35-
new_population[:ga_i.population.shape[0], :] = ga_i.population
36-
ga_i.population = new_population
74+
update_GA(ga_i=ga_i,
75+
pop_size=(10, 3))
3776

3877
ga_instance = pygad.GA(num_generations=20,
3978
sol_per_pop=20,
4079
num_genes=6,
4180
num_parents_mating=10,
4281
fitness_func=fitness_func,
43-
on_generation=on_generation,
44-
parent_selection_type='nsga2')
82+
on_generation=on_generation)
4583

4684
ga_instance.run()
85+

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ build-backend = "setuptools.build_meta"
99

1010
[project]
1111
name = "pygad"
12-
version = "3.2.0"
12+
version = "3.3.0"
1313
description = "PyGAD: A Python Library for Building the Genetic Algorithm and Training Machine Learning Algoithms (Keras & PyTorch)."
1414
readme = {file = "README.md", content-type = "text/markdown"}
1515
requires-python = ">=3"

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setuptools.setup(
77
name="pygad",
8-
version="3.2.0",
8+
version="3.3.0",
99
author="Ahmed Fawzy Gad",
1010
install_requires=["numpy", "matplotlib", "cloudpickle",],
1111
author_email="ahmed.f.gad@gmail.com",

0 commit comments

Comments
 (0)