8000 Merge pull request #265 from ahmedfgad/github-actions · ahmedfgad/GeneticAlgorithmPython@fe5b002 · GitHub
[go: up one dir, main page]

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

Appearance settings

Commit fe5b002

Browse files
authored
Merge pull request #265 from ahmedfgad/github-actions
GitHub actions
2 parents e946f04 + b3183c2 commit fe5b002

12 files changed

+329
-56
lines changed

docs/source/conf.py

Lines changed: 2 additions & 2 deletions
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.rst

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -920,9 +920,7 @@ It accepts the following parameters:
920920
- ``pop_fitness=None``: An optional parameter that accepts a list of
921921
the fitness values of the solutions in the population. If ``None``,
922922
then the ``cal_pop_fitness()`` method is called to calculate the
923-
fitness values of the ``self.population``. Use
924-
``ga_instance.last_generation_fitness`` to use latest fitness value
925-
and skip recalculation of the population fitness.
923+
fitness values of the population.
926924

927925
It returns the following:
928926

@@ -1060,15 +1058,15 @@ optimization problem is single-objective or multi-objective.
10601058
``pygad.GA`` class.
10611059

10621060
- If the fitness function returns a ``list``, ``tuple``, or
1063-
``numpy.ndarray``, then the problem is multi-objective. Even if
1064-
there is only one element, the problem is still considered
1065-
multi-objective. Each element represents the fitness value of its
1066-
corresponding objective.
1061+
``numpy.ndarray``, then the problem is multi-objective. Even if there
1062+
is only one element, the problem is still considered multi-objective.
1063+
Each element represents the fitness value of its corresponding
1064+
objective.
10671065

10681066
Using a user-defined fitness function allows the user to freely use
1069-
PyGAD solves any problem by passing the appropriate fitness
1070-
function/method. It is very important to understand the problem well before
1071-
creating it.
1067+
PyGAD to solve any problem by passing the appropriate fitness
1068+
function/method. It is very important to understand the problem well
1069+
before creating it.
10721070

10731071
Let's discuss an example:
10741072

docs/source/pygad_more.rst

Lines changed: 104 additions & 5 deletions
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

@@ -596,7 +632,6 @@ After running the code again, it will find the same result.
596632
0.04872203136549972
597633
598634
Continue without Losing Progress
599-
=================================
600635

601636
In `PyGAD
602637
2.18.0 <https://pygad.readthedocs.io/en/latest/releases.html#pygad-2-18-0>`__,
@@ -663,6 +698,70 @@ Note that the 2 attributes (``self.best_solutions`` and
663698
attributes (``self.solutions`` and ``self.solutions_fitness``) only work
664699
if the ``save_solutions`` parameter is ``True``.
665700

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

docs/source/releases.rst

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1464,6 +1464,74 @@ 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-330:
1468+
1469+
PyGAD 3.3.0
1470+
-----------
1471+
1472+
Release Date 29 January 2024
1473+
1474+
1. Solve bugs when multi-objective optimization is used.
1475+
https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/238
1476+
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
1487+
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.
1492+
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]
1534+
14671535
PyGAD Projects at GitHub
14681536
========================
14691537

examples/example_dynamic_population_size.py

Lines changed: 63 additions & 24 deletions
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+

0 commit comments

Comments
 (0)
0