├── docs
├── requirements.txt
├── source
│ ├── reference
│ │ ├── base.rst
│ │ ├── creator.rst
│ │ ├── gp
│ │ │ ├── tools.rst
│ │ │ ├── algorithms.rst
│ │ │ ├── generators.rst
│ │ │ ├── primitives.rst
│ │ │ ├── mutation.rst
│ │ │ └── crossover.rst
│ │ ├── operators
│ │ │ ├── migration.rst
│ │ │ ├── mutation.rst
│ │ │ ├── crossover.rst
│ │ │ └── selection.rst
│ │ ├── benchmarks
│ │ │ ├── binary.rst
│ │ │ ├── multi_obj.rst
│ │ │ ├── single_obj.rst
│ │ │ ├── symb_regr.rst
│ │ │ └── moving_peaks.rst
│ │ ├── algorithms.rst
│ │ ├── persistence.rst
│ │ ├── operators.rst
│ │ ├── benchmarks.rst
│ │ ├── gp.rst
│ │ ├── strategies.rst
│ │ ├── records.rst
│ │ ├── aliases.rst
│ │ └── utilities.rst
│ ├── examples
│ │ ├── particle_swarm.rst
│ │ ├── particle_swarm
│ │ │ ├── multi_pso.rst
│ │ │ └── single_pso.rst
│ │ ├── evolution_strategies
│ │ │ ├── cma_strat.rst
│ │ │ ├── custom_strat.rst
│ │ │ └── mo_cma_strat.rst
│ │ ├── genetic_algorithms
│ │ │ ├── knapsack.rst
│ │ │ ├── nqueens.rst
│ │ │ ├── diff_evo.rst
│ │ │ ├── sorting_net.rst
│ │ │ └── onemax.rst
│ │ ├── genetic_programming
│ │ │ ├── even_parity.rst
│ │ │ ├── art_ant.rst
│ │ │ └── symb_regr.rst
│ │ ├── genetic_programming.rst
│ │ ├── evolution_strategies.rst
│ │ └── genetic_algorithms.rst
│ ├── overview
│ │ ├── getting_started.rst
│ │ ├── introduction.rst
│ │ └── differences.rst
│ ├── index.rst
│ ├── conf.py
│ ├── tutorials
│ │ ├── multiprocessing.rst
│ │ └── using_checkpoints.rst
│ └── _static
│ │ └── custom_style.py
├── Makefile
└── make.bat
├── .idea
├── vcs.xml
├── .gitignore
├── inspectionProfiles
│ └── profiles_settings.xml
├── modules.xml
├── misc.xml
└── deap-er.iml
├── .devtools
├── deap_er
├── persistence
│ └── __init__.py
├── base
│ ├── __init__.py
│ ├── dtypes.py
│ ├── lint_hints.py
│ └── toolbox.py
├── utilities
│ ├── hypervolume
│ │ ├── __init__.py
│ │ ├── node.py
│ │ ├── least_contrib.py
│ │ └── multi_list.py
│ ├── sorting
│ │ ├── __init__.py
│ │ └── sort_non_dominated.py
│ ├── __init__.py
│ ├── initializers.py
│ └── metrics.py
├── creator
│ ├── __init__.py
│ ├── lint_hints.py
│ ├── overrides.py
│ └── creator.py
├── dtypes.py
├── __init__.py
├── strategies
│ └── __init__.py
├── operators
│ ├── __init__.py
│ ├── selection
│ │ ├── __init__.py
│ │ ├── sel_nsga_2.py
│ │ ├── sel_helpers.py
│ │ └── sel_lexicase.py
│ └── migration.py
├── records
│ ├── __init__.py
│ ├── dtypes.py
│ ├── history.py
│ └── statistics.py
├── benchmarks
│ └── __init__.py
├── tools.py
├── algorithms
│ ├── __init__.py
│ ├── ea_generate_update.py
│ ├── ea_simple.py
│ ├── ea_mu_plus_lambda.py
│ ├── ea_mu_comma_lambda.py
│ └── variation.py
└── gp
│ ├── __init__.py
│ ├── dtypes.py
│ ├── semantic.py
│ └── crossover.py
├── .readthedocs.yaml
├── .coveragerc
├── LICENSE
├── examples
├── genetic_programming
│ ├── art_ant_trail.txt
│ ├── symb_regr_numpy.py
│ ├── symb_regr_eplex.py
│ ├── symb_regr_harm.py
│ ├── symb_regr.py
│ └── even_parity.py
├── evolution_strategies
│ ├── cma_strat.py
│ ├── custom_strat.py
│ └── mo_cma_strat.py
├── genetic_algorithms
│ ├── onemax_short.py
│ ├── onemax_numpy.py
│ ├── onemax_mp.py
│ ├── diff_evo_basic.py
│ ├── onemax_detailed.py
│ ├── nqueens.py
│ ├── knapsack.py
│ └── sn_evolution.py
└── particle_swarm
│ ├── numpy_pso.py
│ └── basic_pso.py
├── tests
├── test_records
│ └── test_statistics.py
├── test_gp
│ └── test_semantic.py
├── test_base
│ ├── test_toolbox.py
│ └── test_fitness.py
├── test_utilities
│ ├── test_initializers.py
│ └── test_hypervolume.py
├── test_controllers
│ └── test_checkpoint.py
└── test_creator
│ └── test_overrides.py
├── README.md
├── CONTRIBUTING.md
├── pyproject.toml
└── .gitignore
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | sphinx_design>=0.6.1
2 | sphinx_rtd_theme>=2.0.0
--------------------------------------------------------------------------------
/docs/source/reference/base.rst:
--------------------------------------------------------------------------------
1 | Base
2 | ====
3 |
4 | .. automodule:: deap_er.base
5 | :imported-members:
6 | :members:
7 |
--------------------------------------------------------------------------------
/docs/source/reference/creator.rst:
--------------------------------------------------------------------------------
1 | Creator
2 | =======
3 |
4 | .. automodule:: deap_er.creator
5 | :imported-members:
6 | :members:
7 |
--------------------------------------------------------------------------------
/docs/source/reference/gp/tools.rst:
--------------------------------------------------------------------------------
1 | GP Tools
2 | ========
3 |
4 | .. automodule:: deap_er.gp.tools
5 | :imported-members:
6 | :members:
7 |
--------------------------------------------------------------------------------
/docs/source/reference/operators/migration.rst:
--------------------------------------------------------------------------------
1 | Migration Operators
2 | ===================
3 |
4 | .. autofunction:: deap_er.operators.mig_ring
5 |
--------------------------------------------------------------------------------
/docs/source/reference/benchmarks/binary.rst:
--------------------------------------------------------------------------------
1 | Binary
2 | ======
3 |
4 | .. automodule:: deap_er.benchmarks.binary
5 | :imported-members:
6 | :members:
7 |
--------------------------------------------------------------------------------
/docs/source/reference/gp/algorithms.rst:
--------------------------------------------------------------------------------
1 | GP Algorithms
2 | =============
3 |
4 | .. automodule:: deap_er.gp.harm
5 | :imported-members:
6 | :members:
7 |
--------------------------------------------------------------------------------
/docs/source/reference/gp/generators.rst:
--------------------------------------------------------------------------------
1 | GP Generators
2 | =============
3 |
4 | .. automodule:: deap_er.gp.generators
5 | :imported-members:
6 | :members:
7 |
--------------------------------------------------------------------------------
/docs/source/reference/gp/primitives.rst:
--------------------------------------------------------------------------------
1 | GP Primitives
2 | =============
3 |
4 | .. automodule:: deap_er.gp.primitives
5 | :imported-members:
6 | :members:
7 |
--------------------------------------------------------------------------------
/docs/source/reference/algorithms.rst:
--------------------------------------------------------------------------------
1 | .. _algos:
2 |
3 | Algorithms
4 | ==========
5 |
6 | .. automodule:: deap_er.algorithms
7 | :imported-members:
8 | :members:
9 |
--------------------------------------------------------------------------------
/docs/source/reference/benchmarks/multi_obj.rst:
--------------------------------------------------------------------------------
1 | Multi-Objective
2 | ===============
3 |
4 | .. automodule:: deap_er.benchmarks.multi_obj
5 | :imported-members:
6 | :members:
7 |
--------------------------------------------------------------------------------
/docs/source/reference/benchmarks/single_obj.rst:
--------------------------------------------------------------------------------
1 | Single-Objective
2 | ================
3 |
4 | .. automodule:: deap_er.benchmarks.single_obj
5 | :imported-members:
6 | :members:
7 |
--------------------------------------------------------------------------------
/docs/source/reference/benchmarks/symb_regr.rst:
--------------------------------------------------------------------------------
1 | Symbolic Regression
2 | ===================
3 |
4 | .. automodule:: deap_er.benchmarks.symb_regr
5 | :imported-members:
6 | :members:
7 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/docs/source/examples/particle_swarm.rst:
--------------------------------------------------------------------------------
1 | Particle Swarms
2 | ===============
3 |
4 | .. toctree::
5 | :maxdepth: 2
6 |
7 | particle_swarm/single_pso.rst
8 | particle_swarm/multi_pso.rst
9 |
--------------------------------------------------------------------------------
/docs/source/examples/particle_swarm/multi_pso.rst:
--------------------------------------------------------------------------------
1 | Multi-Swarm
2 | ===========
3 |
4 | .. literalinclude:: ../../../../examples/particle_swarm/multi_pso.py
5 | :language: python
6 | :linenos:
7 |
--------------------------------------------------------------------------------
/docs/source/examples/evolution_strategies/cma_strat.rst:
--------------------------------------------------------------------------------
1 | Standard CMA
2 | ============
3 |
4 | .. literalinclude:: ../../../../examples/evolution_strategies/cma_strat.py
5 | :language: python
6 | :linenos:
7 |
--------------------------------------------------------------------------------
/docs/source/examples/genetic_algorithms/knapsack.rst:
--------------------------------------------------------------------------------
1 | Knapsack Problem
2 | ================
3 |
4 | .. literalinclude:: ../../../../examples/genetic_algorithms/knapsack.py
5 | :language: python
6 | :linenos:
7 |
--------------------------------------------------------------------------------
/docs/source/examples/genetic_algorithms/nqueens.rst:
--------------------------------------------------------------------------------
1 | N-Queens Problem
2 | ================
3 |
4 | .. literalinclude:: ../../../../examples/genetic_algorithms/nqueens.py
5 | :language: python
6 | :linenos:
7 |
--------------------------------------------------------------------------------
/docs/source/reference/gp/mutation.rst:
--------------------------------------------------------------------------------
1 | GP Mutation
2 | ============
3 |
4 | .. automodule:: deap_er.gp.mutation
5 | :imported-members:
6 | :members:
7 |
8 | .. autofunction:: deap_er.gp.semantic.mut_semantic
9 |
--------------------------------------------------------------------------------
/docs/source/reference/gp/crossover.rst:
--------------------------------------------------------------------------------
1 | GP Crossover
2 | ============
3 |
4 | .. automodule:: deap_er.gp.crossover
5 | :imported-members:
6 | :members:
7 |
8 | .. autofunction:: deap_er.gp.semantic.cx_semantic
9 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/docs/source/examples/evolution_strategies/custom_strat.rst:
--------------------------------------------------------------------------------
1 | Custom Strategy
2 | ===============
3 |
4 | .. literalinclude:: ../../../../examples/evolution_strategies/custom_strat.py
5 | :language: python
6 | :linenos:
7 |
--------------------------------------------------------------------------------
/docs/source/examples/genetic_programming/even_parity.rst:
--------------------------------------------------------------------------------
1 | Even-Parity Problem
2 | ===================
3 |
4 | .. literalinclude:: ../../../../examples/genetic_programming/even_parity.py
5 | :language: python
6 | :linenos:
7 |
--------------------------------------------------------------------------------
/docs/source/examples/evolution_strategies/mo_cma_strat.rst:
--------------------------------------------------------------------------------
1 | Multi-Objective CMA
2 | ===================
3 |
4 | .. literalinclude:: ../../../../examples/evolution_strategies/mo_cma_strat.py
5 | :language: python
6 | :linenos:
7 |
--------------------------------------------------------------------------------
/docs/source/reference/persistence.rst:
--------------------------------------------------------------------------------
1 | .. _persistence:
2 |
3 | State Persistence
4 | =================
5 |
6 | .. automodule:: deap_er.persistence.checkpoint
7 | :members:
8 |
9 | .. raw:: html
10 |
11 |
12 |
--------------------------------------------------------------------------------
/docs/source/reference/operators.rst:
--------------------------------------------------------------------------------
1 | Operators
2 | =========
3 |
4 | .. toctree::
5 | :maxdepth: 2
6 |
7 | operators/selection.rst
8 | operators/crossover.rst
9 | operators/mutation.rst
10 | operators/migration.rst
11 |
--------------------------------------------------------------------------------
/docs/source/examples/genetic_programming.rst:
--------------------------------------------------------------------------------
1 | Genetic Programming
2 | ===================
3 |
4 | .. toctree::
5 | :maxdepth: 2
6 |
7 | genetic_programming/even_parity.rst
8 | genetic_programming/art_ant.rst
9 | genetic_programming/symb_regr.rst
--------------------------------------------------------------------------------
/docs/source/examples/evolution_strategies.rst:
--------------------------------------------------------------------------------
1 | Evolution Strategies
2 | ====================
3 |
4 | .. toctree::
5 | :maxdepth: 2
6 |
7 | evolution_strategies/cma_strat.rst
8 | evolution_strategies/mo_cma_strat.rst
9 | evolution_strategies/custom_strat.rst
10 |
--------------------------------------------------------------------------------
/docs/source/reference/benchmarks.rst:
--------------------------------------------------------------------------------
1 | Benchmarks
2 | ==========
3 |
4 | .. toctree::
5 | :maxdepth: 2
6 |
7 | benchmarks/binary.rst
8 | benchmarks/single_obj.rst
9 | benchmarks/multi_obj.rst
10 | benchmarks/symb_regr.rst
11 | benchmarks/moving_peaks.rst
12 |
--------------------------------------------------------------------------------
/docs/source/reference/gp.rst:
--------------------------------------------------------------------------------
1 | Genetic Programming (GP)
2 | ========================
3 |
4 | .. toctree::
5 | :maxdepth: 2
6 |
7 | gp/tools.rst
8 | gp/primitives.rst
9 | gp/generators.rst
10 | gp/algorithms.rst
11 | gp/crossover.rst
12 | gp/mutation.rst
13 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/docs/source/examples/genetic_algorithms.rst:
--------------------------------------------------------------------------------
1 | Genetic Algorithms
2 | ==================
3 |
4 | .. toctree::
5 | :maxdepth: 2
6 |
7 | genetic_algorithms/onemax.rst
8 | genetic_algorithms/knapsack.rst
9 | genetic_algorithms/nqueens.rst
10 | genetic_algorithms/diff_evo.rst
11 | genetic_algorithms/sorting_net.rst
12 |
--------------------------------------------------------------------------------
/.devtools:
--------------------------------------------------------------------------------
1 | {
2 | "license_cmd": {
3 | "header": {
4 | "title": "MIT License",
5 | "year": "2022",
6 | "holder": "Mattias Aabmets",
7 | "spdx_id": "MIT",
8 | "spaces": 3,
9 | "oss": true
10 | },
11 | "paths": [
12 | "deap_er",
13 | "tests"
14 | ],
15 | "file_name": "mi.json"
16 | }
17 | }
--------------------------------------------------------------------------------
/deap_er/persistence/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from .checkpoint import *
12 |
--------------------------------------------------------------------------------
/deap_er/base/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from .toolbox import *
12 | from .fitness import *
13 |
--------------------------------------------------------------------------------
/docs/source/examples/particle_swarm/single_pso.rst:
--------------------------------------------------------------------------------
1 | Single-Swarm
2 | ============
3 |
4 | Using Builtins
5 | --------------
6 |
7 | .. literalinclude:: ../../../../examples/particle_swarm/basic_pso.py
8 | :language: python
9 | :linenos:
10 |
11 |
12 | Using Numpy
13 | -----------
14 |
15 | .. literalinclude:: ../../../../examples/particle_swarm/numpy_pso.py
16 | :language: python
17 | :linenos:
18 |
--------------------------------------------------------------------------------
/deap_er/utilities/hypervolume/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from .hypervolume import *
12 | from .least_contrib import *
13 |
--------------------------------------------------------------------------------
/docs/source/examples/genetic_programming/art_ant.rst:
--------------------------------------------------------------------------------
1 | Artificial Ant Problem
2 | ======================
3 |
4 | Source Code
5 | -----------
6 |
7 | .. literalinclude:: ../../../../examples/genetic_programming/art_ant.py
8 | :language: python
9 | :linenos:
10 |
11 |
12 | Food Trail
13 | ----------
14 |
15 | .. literalinclude:: ../../../../examples/genetic_programming/art_ant_trail.txt
16 | :language: text
17 | :linenos:
18 |
--------------------------------------------------------------------------------
/deap_er/creator/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from .creator import create
12 | from .lint_hints import *
13 | from . import creator
14 |
--------------------------------------------------------------------------------
/deap_er/dtypes.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from .base.dtypes import *
12 | from .records.dtypes import *
13 | from .gp.dtypes import *
14 |
--------------------------------------------------------------------------------
/docs/source/reference/operators/mutation.rst:
--------------------------------------------------------------------------------
1 | .. _mut_ops:
2 |
3 | Mutation Operators
4 | ==================
5 |
6 | .. autofunction:: deap_er.operators.mut_gaussian
7 | .. autofunction:: deap_er.operators.mut_polynomial_bounded
8 | .. autofunction:: deap_er.operators.mut_shuffle_indexes
9 | .. autofunction:: deap_er.operators.mut_flip_bit
10 | .. autofunction:: deap_er.operators.mut_uniform_int
11 | .. autofunction:: deap_er.operators.mut_es_log_normal
12 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/deap_er/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from .creator import creator
12 | from . import persistence as env
13 | from . import base
14 | from . import gp
15 |
--------------------------------------------------------------------------------
/deap_er/strategies/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from .cma_standard import *
12 | from .cma_multi_objective import *
13 | from .cma_one_plus_lambda import *
14 |
--------------------------------------------------------------------------------
/deap_er/operators/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from .selection import *
12 | from .crossover import *
13 | from .mutation import *
14 | from .migration import *
15 |
--------------------------------------------------------------------------------
/deap_er/records/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from .hall_of_fame import *
12 | from .history import *
13 | from .logbook import *
14 | from .statistics import *
15 |
--------------------------------------------------------------------------------
/deap_er/utilities/sorting/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from .sort_non_dominated import *
12 | from .sort_log_non_dominated import *
13 | from .sorting_network import *
14 |
--------------------------------------------------------------------------------
/docs/source/examples/genetic_algorithms/diff_evo.rst:
--------------------------------------------------------------------------------
1 | Differential Evolution
2 | ======================
3 |
4 | Basic DE
5 | --------
6 |
7 | .. literalinclude:: ../../../../examples/genetic_algorithms/diff_evo_basic.py
8 | :language: python
9 | :linenos:
10 |
11 | .. raw:: html
12 |
13 |
14 |
15 |
16 |
17 | Dynamic DE
18 | ----------
19 |
20 | .. literalinclude:: ../../../../examples/genetic_algorithms/diff_evo_dynamic.py
21 | :language: python
22 | :linenos:
23 |
--------------------------------------------------------------------------------
/deap_er/benchmarks/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from .moving_peaks import *
12 | from .single_obj import *
13 | from .multi_obj import *
14 | from .symb_regr import *
15 | from .binary import *
16 |
--------------------------------------------------------------------------------
/deap_er/tools.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from .algorithms import *
12 | from .strategies import *
13 | from .operators import *
14 | from .benchmarks import *
15 | from .utilities import *
16 | from .records import *
17 |
--------------------------------------------------------------------------------
/deap_er/algorithms/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from .ea_generate_update import *
12 | from .ea_mu_comma_lambda import *
13 | from .ea_mu_plus_lambda import *
14 | from .ea_simple import *
15 | from .variation import *
16 |
--------------------------------------------------------------------------------
/deap_er/utilities/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from .hypervolume import *
12 | from .sorting import *
13 | from .bm_decors import *
14 | from .constraints import *
15 | from .initializers import *
16 | from .metrics import *
17 |
--------------------------------------------------------------------------------
/deap_er/gp/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from .crossover import *
12 | from .generators import *
13 | from .harm import *
14 | from .mutation import *
15 | from .primitives import *
16 | from .semantic import *
17 | from .tools import *
18 |
--------------------------------------------------------------------------------
/docs/source/examples/genetic_algorithms/sorting_net.rst:
--------------------------------------------------------------------------------
1 | Sorting Networks
2 | ================
3 |
4 |
5 | Optimal Network Generation
6 | --------------------------
7 |
8 | .. literalinclude:: ../../../../examples/genetic_algorithms/sn_evolution.py
9 | :language: python
10 | :linenos:
11 |
12 | .. raw:: html
13 |
14 |
15 |
16 |
17 |
18 | Hillis Cooperative Co-Evolution
19 | -------------------------------
20 |
21 | .. literalinclude:: ../../../../examples/genetic_algorithms/sn_coop_coev.py
22 | :language: python
23 | :linenos:
24 |
--------------------------------------------------------------------------------
/docs/source/reference/strategies.rst:
--------------------------------------------------------------------------------
1 | Strategies
2 | ==========
3 |
4 | Standard CMA
5 | ------------
6 |
7 | .. autoclass:: deap_er.strategies.Strategy
8 | :members:
9 |
10 | .. raw:: html
11 |
12 |
13 |
14 |
15 |
16 | Multi-Objective CMA
17 | -------------------
18 |
19 | .. autoclass:: deap_er.strategies.StrategyMultiObjective
20 | :members:
21 |
22 | .. raw:: html
23 |
24 |
25 |
26 |
27 |
28 | One Plus Lambda CMA
29 | -------------------
30 |
31 | .. autoclass:: deap_er.strategies.StrategyOnePlusLambda
32 | :members:
33 |
--------------------------------------------------------------------------------
/deap_er/operators/selection/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from .sel_helpers import *
12 | from .sel_lexicase import *
13 | from .sel_nsga_2 import *
14 | from .sel_nsga_3 import *
15 | from .sel_spea_2 import *
16 | from .sel_tournament import *
17 | from .sel_various import *
18 |
--------------------------------------------------------------------------------
/docs/source/reference/records.rst:
--------------------------------------------------------------------------------
1 | Records
2 | =======
3 |
4 | For Evolution
5 | -------------
6 |
7 | .. autoclass:: deap_er.records.Logbook
8 | :members:
9 |
10 | .. autoclass:: deap_er.records.Statistics
11 | :members:
12 |
13 | .. autoclass:: deap_er.records.MultiStatistics
14 | :members:
15 |
16 | .. raw:: html
17 |
18 |
19 |
20 |
21 |
22 | For Individuals
23 | ---------------
24 |
25 | .. autoclass:: deap_er.records.HallOfFame
26 | :members:
27 |
28 | .. autoclass:: deap_er.records.ParetoFront
29 | :members:
30 |
31 | .. autoclass:: deap_er.records.History
32 | :members:
33 |
34 | .. raw:: html
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = source
9 | BUILDDIR = build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/.idea/deap-er.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/deap_er/records/dtypes.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from .hall_of_fame import *
12 | from .statistics import *
13 | from .logbook import *
14 | from typing import Union, Tuple
15 |
16 |
17 | __all__ = ['Hof', 'Stats', 'AlgoResult']
18 |
19 |
20 | Hof = Union[HallOfFame, ParetoFront]
21 | """:meta private:"""
22 |
23 | Stats = Union[Statistics, MultiStatistics]
24 | """:meta private:"""
25 |
26 | AlgoResult = Tuple[list, Logbook]
27 | """:meta private:"""
28 |
--------------------------------------------------------------------------------
/.readthedocs.yaml:
--------------------------------------------------------------------------------
1 | # .readthedocs.yaml
2 | # Read the Docs configuration file
3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
4 |
5 | # Required
6 | version: 2
7 |
8 | # Set the version of Python and other tools you might need
9 | build:
10 | os: ubuntu-22.04
11 | tools:
12 | python: "3.12"
13 |
14 | # Build documentation in the docs/ directory with Sphinx
15 | sphinx:
16 | builder: html
17 | configuration: docs/source/conf.py
18 | fail_on_warning: false
19 |
20 | # If using Sphinx, optionally build your docs in additional formats such as PDF
21 | #formats:
22 | # - epub
23 | # - pdf
24 |
25 | # Optionally declare the Python requirements required to build your docs
26 | python:
27 | install:
28 | - requirements: docs/requirements.txt
29 |
--------------------------------------------------------------------------------
/.coveragerc:
--------------------------------------------------------------------------------
1 | # coverage configuration file
2 |
3 | [run]
4 | branch = True
5 | source = deap_er
6 | omit =
7 | deap_er/__init__.py
8 | deap_er/creator/__init__.py
9 |
10 | [report]
11 | skip_empty = True
12 | ignore_errors = True
13 | exclude_lines =
14 | # Have to re-enable the standard pragma
15 | pragma: no cover
16 |
17 | # Don't complain about missing debug-only code:
18 | def __repr__
19 | if self\.debug
20 |
21 | # Don't complain if tests don't hit defensive assertion code:
22 | raise AssertionError
23 | raise NotImplementedError
24 |
25 | # Don't complain if non-runnable code isn't run:
26 | if __name__ == .__main__.:
27 |
28 | # Don't complain about abstract methods, they aren't run:
29 | @(abc\.)?abstractmethod
30 |
31 | [html]
32 | directory = .htmlcov
33 |
--------------------------------------------------------------------------------
/deap_er/base/dtypes.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from collections.abc import Sequence
12 | from typing import Union, Tuple
13 | import numpy
14 | import array
15 |
16 |
17 | __all__ = ['Individual', 'Mates', 'Mutant', 'NumOrSeq']
18 |
19 |
20 | Individual = Union[list, array.array, numpy.ndarray]
21 | """:meta private:"""
22 |
23 | Mates = Tuple[Individual, Individual]
24 | """:meta private:"""
25 |
26 | Mutant = Tuple[Individual]
27 | """:meta private:"""
28 |
29 | NumOrSeq = Union[int, float, Sequence[int], Sequence[float]]
30 | """:meta private:"""
31 |
--------------------------------------------------------------------------------
/docs/source/reference/benchmarks/moving_peaks.rst:
--------------------------------------------------------------------------------
1 | Moving Peaks
2 | ============
3 |
4 | .. autoclass:: deap_er.benchmarks.moving_peaks.MovingPeaks
5 | :members: global_maximum, sorted_maxima, offline_error, current_error
6 |
7 | .. automethod:: change_peaks
8 | .. automethod:: __call__
9 |
10 |
11 | .. raw:: html
12 |
13 |
14 |
15 |
16 | .. autoclass:: deap_er.benchmarks.moving_peaks.MPConfigs
17 | :members:
18 |
19 | .. raw:: html
20 |
21 |
22 |
23 |
24 | .. autoclass:: deap_er.benchmarks.moving_peaks.MPFuncs
25 |
26 | .. dropdown:: Table of Functions
27 | :margin: 0 5 0 0
28 |
29 | .. autofunction:: deap_er.benchmarks.moving_peaks.MPFuncs.pf1
30 | .. autofunction:: deap_er.benchmarks.moving_peaks.MPFuncs.pf2
31 | .. autofunction:: deap_er.benchmarks.moving_peaks.MPFuncs.pf3
32 |
33 | .. raw:: html
34 |
35 |
36 |
--------------------------------------------------------------------------------
/deap_er/creator/lint_hints.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from typing import Any
12 |
13 |
14 | __all__ = [
15 | 'Fitness', 'FitnessMax', 'FitnessMin', 'FitnessMulti', 'FitnessVaried',
16 | 'Individual', 'Population', 'Particle', 'Swarm', 'Tree', 'Strategy'
17 | ]
18 |
19 |
20 | # ====================================================================================== #
21 | Fitness = Any
22 | FitnessMax = Any
23 | FitnessMin = Any
24 | FitnessMulti = Any
25 | FitnessVaried = Any
26 | Individual = Any
27 | Population = Any
28 | Particle = Any
29 | Swarm = Any
30 | Tree = Any
31 | Strategy = Any
32 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=source
11 | set BUILDDIR=build
12 |
13 | %SPHINXBUILD% >NUL 2>NUL
14 | if errorlevel 9009 (
15 | echo.
16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
17 | echo.installed, then set the SPHINXBUILD environment variable to point
18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
19 | echo.may add the Sphinx directory to PATH.
20 | echo.
21 | echo.If you don't have Sphinx installed, grab it from
22 | echo.https://www.sphinx-doc.org/
23 | exit /b 1
24 | )
25 |
26 | if "%1" == "" goto help
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/deap_er/gp/dtypes.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from .primitives import *
12 | from typing import Tuple, Union
13 |
14 |
15 | __all__ = [
16 | 'GPIndividual', 'GPMates', 'GPMutant',
17 | 'GPExprTypes', 'GPTypedSets', 'GPGraph'
18 | ]
19 |
20 |
21 | GPIndividual = Union[list, PrimitiveTree]
22 | """:meta private:"""
23 |
24 | GPMates = Tuple[GPIndividual, GPIndividual]
25 | """:meta private:"""
26 |
27 | GPMutant = Tuple[GPIndividual]
28 | """:meta private:"""
29 |
30 | GPExprTypes = Union[str, PrimitiveTree]
31 | """:meta private:"""
32 |
33 | GPTypedSets = list[PrimitiveSetTyped]
34 | """:meta private:"""
35 |
36 | GPGraph = tuple[list, list, dict]
37 | """:meta private:"""
38 |
--------------------------------------------------------------------------------
/docs/source/examples/genetic_algorithms/onemax.rst:
--------------------------------------------------------------------------------
1 | One Max Problem
2 | ===============
3 |
4 | Detailed Version
5 | ----------------
6 |
7 | .. literalinclude:: ../../../../examples/genetic_algorithms/onemax_detailed.py
8 | :language: python
9 | :linenos:
10 |
11 | .. raw:: html
12 |
13 |
14 |
15 |
16 |
17 | Short Version
18 | -------------
19 |
20 | .. literalinclude:: ../../../../examples/genetic_algorithms/onemax_short.py
21 | :language: python
22 | :linenos:
23 |
24 | .. raw:: html
25 |
26 |
27 |
28 |
29 |
30 | Using Numpy
31 | -----------
32 |
33 | .. literalinclude:: ../../../../examples/genetic_algorithms/onemax_numpy.py
34 | :language: python
35 | :linenos:
36 |
37 | .. raw:: html
38 |
39 |
40 |
41 |
42 |
43 | .. _using_mp:
44 |
45 | Using Multiprocessing
46 | ---------------------
47 |
48 | .. literalinclude:: ../../../../examples/genetic_algorithms/onemax_mp.py
49 | :language: python
50 | :linenos:
51 |
52 | .. raw:: html
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/deap_er/base/lint_hints.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from typing import Callable
12 | from functools import partial
13 |
14 |
15 | # ====================================================================================== #
16 | class LintHints:
17 | __test__: Callable
18 |
19 | map: partial
20 | clone: partial
21 |
22 | attr_int: Callable
23 | attr_bool: Callable
24 | attr_float: Callable
25 | attr_item: Callable
26 |
27 | individual: Callable
28 | individuals: Callable
29 | population: Callable
30 | populations: Callable
31 | particle: Callable
32 | particles: Callable
33 | swarm: Callable
34 | swarms: Callable
35 |
36 | evaluate: Callable
37 | select: Callable
38 | mate: Callable
39 | mutate: Callable
40 | generate: Callable
41 | update: Callable
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 |
3 | MIT License
4 |
5 | Copyright (c) 2022 Mattias Aabmets
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.
24 |
--------------------------------------------------------------------------------
/docs/source/reference/aliases.rst:
--------------------------------------------------------------------------------
1 | .. _datatypes:
2 |
3 | Type Aliases
4 | ============
5 |
6 | Base
7 | ----
8 |
9 | .. py:data:: Individual
10 | :type: Union[list, array.array, numpy.ndarray]
11 |
12 | .. py:data:: Mates
13 | :type: tuple[Individual, Individual]
14 |
15 | .. py:data:: Mutant
16 | :type: tuple[Individual]
17 |
18 | .. py:data:: NumOrSeq
19 | :type: Union[int, float, Sequence[int], Sequence[float]]
20 |
21 |
22 | Records
23 | -------
24 |
25 | .. py:data:: Hof
26 | :type: Union[HallOfFame, ParetoFront]
27 |
28 | .. py:data:: Stats
29 | :type: Union[Statistics, MultiStatistics]
30 |
31 | .. py:data:: AlgoResult
32 | :type: tuple[list, Logbook]
33 |
34 |
35 | Genetic Programming (GP)
36 | ------------------------
37 |
38 | .. py:data:: GPIndividual
39 | :type: Union[list, PrimitiveTree]
40 |
41 | .. py:data:: GPMates
42 | :type: Tuple[GPIndividual, GPIndividual]
43 |
44 | .. py:data:: GPMutant
45 | :type: Tuple[GPIndividual]
46 |
47 | .. py:data:: GPExprTypes
48 | :type: Union[str, GPIndividual]
49 |
50 | .. py:data:: GPTypedSets
51 | :type: list[PrimitiveSetTyped]
52 |
53 | .. py:data:: GPGraph
54 | :type: tuple[list, list, dict]
55 |
--------------------------------------------------------------------------------
/examples/genetic_programming/art_ant_trail.txt:
--------------------------------------------------------------------------------
1 | S###............................
2 | ...#............................
3 | ...#.....................###....
4 | ...#....................#....#..
5 | ...#....................#....#..
6 | ...####.#####........##.........
7 | ............#................#..
8 | ............#.......#...........
9 | ............#.......#........#..
10 | ............#.......#...........
11 | ....................#...........
12 | ............#................#..
13 | ............#...................
14 | ............#.......#.....###...
15 | ............#.......#..#........
16 | .................#..............
17 | ................................
18 | ............#...........#.......
19 | ............#...#..........#....
20 | ............#...#...............
21 | ............#...#...............
22 | ............#...#.........#.....
23 | ............#..........#........
24 | ............#...................
25 | ...##..#####....#...............
26 | .#..............#...............
27 | .#..............#...............
28 | .#......#######.................
29 | .#.....#........................
30 | .......#........................
31 | ..####..........................
32 | ................................
33 |
--------------------------------------------------------------------------------
/docs/source/examples/genetic_programming/symb_regr.rst:
--------------------------------------------------------------------------------
1 | Symbolic Regression
2 | ===================
3 |
4 | Basic Version
5 | -------------
6 |
7 | .. literalinclude:: ../../../../examples/genetic_programming/symb_regr.py
8 | :language: python
9 | :linenos:
10 |
11 | .. raw:: html
12 |
13 |
14 |
15 |
16 |
17 | Using Numpy
18 | -----------
19 |
20 | .. literalinclude:: ../../../../examples/genetic_programming/symb_regr_numpy.py
21 | :language: python
22 | :linenos:
23 |
24 | .. raw:: html
25 |
26 |
27 |
28 |
29 |
30 | Using ADF-s
31 | -----------
32 |
33 | .. literalinclude:: ../../../../examples/genetic_programming/symb_regr_adf.py
34 | :language: python
35 | :linenos:
36 |
37 | .. raw:: html
38 |
39 |
40 |
41 |
42 |
43 | Using HARM-GP
44 | -------------
45 |
46 | .. literalinclude:: ../../../../examples/genetic_programming/symb_regr_harm.py
47 | :language: python
48 | :linenos:
49 |
50 | .. raw:: html
51 |
52 |
53 |
54 |
55 |
56 | Using Epsilon Lexicase
57 | ----------------------
58 |
59 | .. literalinclude:: ../../../../examples/genetic_programming/symb_regr_eplex.py
60 | :language: python
61 | :linenos:
62 |
63 | .. raw:: html
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/docs/source/overview/getting_started.rst:
--------------------------------------------------------------------------------
1 | Getting Started
2 | ===============
3 |
4 | Installation
5 | ++++++++++++
6 |
7 | This library can be installed with:
8 |
9 | .. code-block:: text
10 |
11 | pip install deap-er
12 |
13 | or if you're using the `Poetry `_ package manager:
14 |
15 | .. code-block:: text
16 |
17 | poetry add deap-er
18 |
19 |
20 | .. raw:: html
21 |
22 |
23 |
24 |
25 |
26 | Namespaces
27 | ++++++++++
28 |
29 | The functionality of this library is divided into the following namespaces:
30 |
31 | * **base** - The main *Toolbox* and *Fitness* classes.
32 | * **creator** - Type creator for use with toolboxes.
33 | * **tools** - Essential evolution components:
34 |
35 | - algorithms
36 | - operators
37 | - strategies
38 | - records
39 | - utilities
40 | - benchmarks
41 |
42 | * **gp** - Components for genetic programming.
43 | * **env** - The *Checkpoint* class for state persistence.
44 | * **dtypes** - Datatype aliases used by this library.
45 |
46 | These namespaces can be imported with:
47 |
48 | .. code-block::
49 |
50 | from deap_er import base, creator, tools, gp, env, dtypes
51 |
52 | .. raw:: html
53 |
54 |
55 |
--------------------------------------------------------------------------------
/docs/source/overview/introduction.rst:
--------------------------------------------------------------------------------
1 | Introduction
2 | ============
3 |
4 | `DEAP-ER `_ is a complete rework of the original
5 | `DEAP `_ library, which includes features such as:
6 |
7 | * Genetic algorithms using any imaginable containers like:
8 |
9 | * List, Array, Set, Dictionary, Tree, Numpy Array, etc.
10 |
11 | * Genetic programming using prefix trees
12 |
13 | * Loosely typed, Strongly typed
14 | * Automatically defined functions
15 |
16 | * Evolution Strategies (Covariance Matrix Adaptation)
17 | * Multi-objective optimisation (SPEA-II, NSGA-II, NSGA-III, MO-CMA)
18 | * Co-evolution (cooperative and competitive) of multiple populations
19 | * Parallelization of evolution processes using multiprocessing or with `Ray `_
20 | * Records to track the evolution and to collect the best individuals
21 | * Checkpoints to persist the progress of evolutions to disk
22 | * Benchmarks to test evolution algorithms against common test functions
23 | * Genealogy of an evolution, that is also compatible with `NetworkX `_
24 | * Examples of alternative algorithms:
25 |
26 | * Symbolic Regression,
27 | * Particle Swarm Optimization,
28 | * Differential Evolution,
29 | * Estimation of Distribution Algorithm
30 |
--------------------------------------------------------------------------------
/docs/source/overview/differences.rst:
--------------------------------------------------------------------------------
1 | .. role:: raw-html(raw)
2 | :format: html
3 |
4 | Important Differences
5 | =====================
6 |
7 | As this library is an *evolution* of the original `DEAP `_ library
8 | :raw-html:`(pun intended)`, there are a few important
9 | differences that the user should be aware of:
10 |
11 | #. The whole codebase has been completely refactored for better usability and maintainability.
12 | #. Algorithms, strategies and benchmarks have been moved into the **tools** namespace.
13 | #. All **camelCase** functions and methods have been renamed to **snake_case**.
14 | #. Some functions and/or methods have been completely renamed.
15 | #. All functions and methods have received proper type hints.
16 | #. The parameters of many functions have been reordered and/or renamed.
17 | #. Some properties of classes have been changed into method calls.
18 | #. The **least_contrib** hypervolume indicator is now capable of multiprocessing.
19 | #. The hypervolume class is now pure-python only. The C-implementation has been
20 | removed to eliminate complexities regarding external module compilation.
21 | #. State persistence has been implemented with the Checkpoint class.
22 | #. All deprecated and obsolete code is removed.
23 | #. The whole documentation has been reworked for better comprehensibility.
24 |
--------------------------------------------------------------------------------
/docs/source/reference/operators/crossover.rst:
--------------------------------------------------------------------------------
1 | .. _cx_ops:
2 |
3 | Crossover Operators
4 | ===================
5 |
6 | One Point
7 | ---------
8 | .. autofunction:: deap_er.operators.cx_one_point
9 | .. autofunction:: deap_er.operators.cx_messy_one_point
10 |
11 | .. raw:: html
12 |
13 |
14 |
15 |
16 |
17 | Two Point
18 | ---------
19 | .. autofunction:: deap_er.operators.cx_two_point
20 | .. autofunction:: deap_er.operators.cx_two_point_copy
21 | .. autofunction:: deap_er.operators.cx_es_two_point
22 | .. autofunction:: deap_er.operators.cx_es_two_point_copy
23 |
24 | .. raw:: html
25 |
26 |
27 |
28 |
29 |
30 | Matched
31 | -------
32 | .. autofunction:: deap_er.operators.cx_partially_matched
33 | .. autofunction:: deap_er.operators.cx_uniform_partially_matched
34 |
35 | .. raw:: html
36 |
37 |
38 |
39 |
40 |
41 | Blend
42 | -----
43 | .. autofunction:: deap_er.operators.cx_blend
44 | .. autofunction:: deap_er.operators.cx_es_blend
45 |
46 | .. raw:: html
47 |
48 |
49 |
50 |
51 |
52 | Binary
53 | ------
54 | .. autofunction:: deap_er.operators.cx_simulated_binary
55 | .. autofunction:: deap_er.operators.cx_simulated_binary_bounded
56 |
57 | .. raw:: html
58 |
59 |
60 |
61 |
62 |
63 | Other
64 | -----
65 | .. autofunction:: deap_er.operators.cx_uniform
66 | .. autofunction:: deap_er.operators.cx_ordered
67 |
--------------------------------------------------------------------------------
/docs/source/index.rst:
--------------------------------------------------------------------------------
1 | Welcome to the DEAP-ER's documentation!
2 | =======================================
3 |
4 | .. toctree::
5 | :maxdepth: 2
6 | :caption: Overview:
7 |
8 | overview/introduction.rst
9 | overview/differences.rst
10 | overview/getting_started.rst
11 |
12 |
13 | .. _tutorials:
14 |
15 | .. toctree::
16 | :maxdepth: 2
17 | :caption: Tutorials:
18 |
19 | tutorials/using_the_toolbox.rst
20 | tutorials/creating_individuals.rst
21 | tutorials/operators_and_algorithms.rst
22 | tutorials/logging_statistics.rst
23 | tutorials/multiprocessing.rst
24 | tutorials/using_checkpoints.rst
25 |
26 | ..
27 | tutorials/genetic_programming.rst
28 | tutorials/constraint_handling.rst
29 | tutorials/using_numpy.rst
30 |
31 |
32 | .. _examples:
33 |
34 | .. toctree::
35 | :maxdepth: 2
36 | :caption: Examples:
37 |
38 | examples/genetic_algorithms.rst
39 | examples/genetic_programming.rst
40 | examples/evolution_strategies.rst
41 | examples/particle_swarm.rst
42 |
43 |
44 | .. _reference:
45 |
46 | .. toctree::
47 | :maxdepth: 2
48 | :caption: Reference:
49 |
50 | reference/base.rst
51 | reference/creator.rst
52 | reference/algorithms.rst
53 | reference/operators.rst
54 | reference/strategies.rst
55 | reference/records.rst
56 | reference/utilities.rst
57 | reference/benchmarks.rst
58 | reference/gp.rst
59 | reference/persistence.rst
60 | reference/aliases.rst
61 |
--------------------------------------------------------------------------------
/tests/test_records/test_statistics.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from deap_er.records import Statistics, MultiStatistics
12 | from operator import itemgetter
13 | import numpy
14 |
15 |
16 | # ====================================================================================== #
17 | class TestStatistics:
18 | def test_statistics(self):
19 | s = Statistics()
20 | s.register("mean", numpy.mean)
21 | s.register("max", max)
22 | res = s.compile([1, 2, 3, 4])
23 | assert res == {'max': 4, 'mean': 2.5}
24 | res = s.compile([5, 6, 7, 8])
25 | assert res == {'mean': 6.5, 'max': 8}
26 |
27 | def test_multi_statistics(self):
28 | length_stats = Statistics(key=len)
29 | item_stats = Statistics(key=itemgetter(0))
30 | ms = MultiStatistics(length=length_stats, item=item_stats)
31 | ms.register("mean", numpy.mean, axis=0)
32 | ms.register("max", numpy.max, axis=0)
33 | res = ms.compile([[0.0, 1.0, 1.0, 5.0], [2.0, 5.0]])
34 | assert res == dict(
35 | length={'mean': 3.0, 'max': 4},
36 | item={'mean': 1.0, 'max': 2.0}
37 | )
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DEAP-ER
2 |
3 | DEAP-ER is a complete rewrite of the original DEAP library for Python 3.10 and up, which includes features such as:
4 |
5 | * Genetic algorithms using any imaginable containers like:
6 | * List, Array, Set, Dictionary, Tree, Numpy Array, etc.
7 | * Genetic programming using prefix trees
8 | * Loosely typed, Strongly typed
9 | * Automatically defined functions
10 | * Evolution Strategies (Covariance Matrix Adaptation)
11 | * Multi-objective optimisation (SPEA-II, NSGA-II, NSGA-III, MO-CMA)
12 | * Co-evolution (cooperative and competitive) of multiple populations
13 | * Parallelization of evolution processes using multiprocessing or with [Ray](https://github.com/ray-project/ray)
14 | * Records to track the evolution and to collect the best individuals
15 | * Checkpoints to persist the progress of evolutions to disk
16 | * Benchmarks to test evolution algorithms against common test functions
17 | * Genealogy of an evolution, that is also compatible with [NetworkX](https://github.com/networkx/networkx)
18 | * Examples of alternative algorithms:
19 | * Symbolic Regression,
20 | * Particle Swarm Optimization,
21 | * Differential Evolution,
22 | * Estimation of Distribution Algorithm
23 |
24 |
25 | ## Documentation
26 |
27 | See the [Documentation](http://deap-er.readthedocs.org/) for the complete guide to using this library.
28 |
29 |
30 | ## Contributing
31 |
32 | Please read the CONTRIBUTING.md file before submitting pull requests.
33 |
--------------------------------------------------------------------------------
/docs/source/reference/operators/selection.rst:
--------------------------------------------------------------------------------
1 | .. _sel_ops:
2 |
3 | Selection Operators
4 | ===================
5 |
6 | Genetic Selection
7 | -----------------
8 |
9 | .. autofunction:: deap_er.operators.sel_spea_2
10 | .. autofunction:: deap_er.operators.sel_nsga_2
11 | .. autofunction:: deap_er.operators.sel_nsga_3
12 | .. autoclass:: deap_er.operators.SelNSGA3WithMemory
13 | :members:
14 |
15 | .. raw:: html
16 |
17 |
18 |
19 |
20 |
21 | Tournament Selection
22 | --------------------
23 | .. autofunction:: deap_er.operators.sel_tournament
24 | .. autofunction:: deap_er.operators.sel_double_tournament
25 | .. autofunction:: deap_er.operators.sel_tournament_dcd
26 |
27 | .. raw:: html
28 |
29 |
30 |
31 |
32 |
33 | Lexicase Selection
34 | ------------------
35 | .. autofunction:: deap_er.operators.sel_lexicase
36 | .. autofunction:: deap_er.operators.sel_epsilon_lexicase
37 |
38 | .. raw:: html
39 |
40 |
41 |
42 |
43 |
44 | Various Selection
45 | -----------------
46 | .. autofunction:: deap_er.operators.sel_random
47 | .. autofunction:: deap_er.operators.sel_best
48 | .. autofunction:: deap_er.operators.sel_worst
49 | .. autofunction:: deap_er.operators.sel_roulette
50 | .. autofunction:: deap_er.operators.sel_stochastic_universal_sampling
51 |
52 | .. raw:: html
53 |
54 |
55 |
56 |
57 |
58 | Helpers
59 | -------
60 | .. autofunction:: deap_er.operators.assign_crowding_dist
61 | .. autofunction:: deap_er.operators.uniform_reference_points
62 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to DEAP-er
2 | We love your input! You can contribute to this project by:
3 |
4 | - Reporting bugs
5 | - Contributing source code
6 | - Discussing the current state of the code
7 | - Proposing new features
8 | - Becoming a maintainer
9 |
10 |
11 | ## Reporting Bugs
12 | We use GitHub issues to track bugs. Report a bug by [opening a new issue](https://github.com/aabmets/deap-er/issues/new).
13 |
14 | **Great Bug Reports** tend to have:
15 |
16 | - A quick summary and/or background.
17 | - Specific description of steps to reproduce.
18 | - What you expected would happen.
19 | - What actually happens.
20 | - Sample code.
21 | - Notes, observations or theories.
22 |
23 | Help us fix bugs faster by issuing **Great Bug Reports**.
24 |
25 |
26 | ## Contributing Code
27 | Use the [Github Flow](https://docs.github.com/en/get-started/quickstart/github-flow) to propose code changes to the project:
28 |
29 | 1. Fork the repo and create your branch from `master`.
30 | 2. If you've changed APIs, update the documentation.
31 | 3. If you've added code, add tests for that code.
32 | 4. Ensure all the tests pass.
33 | 5. Make sure your code passes linting.
34 | 6. Issue that pull request!
35 |
36 |
37 | ## Coding Style
38 | - Code must be formatted with the **black** formatter.
39 | - Indent with tabs, not spaces.
40 | - Use code logic separators such as:
41 | - \# =========== #
42 | - \# ------------------- #
43 |
44 |
45 | ## License
46 | By contributing, you agree that your contributions will be licensed under the [MIT License](https://github.com/aabmets/deap-er/blob/main/LICENSE).
47 |
--------------------------------------------------------------------------------
/docs/source/reference/utilities.rst:
--------------------------------------------------------------------------------
1 | Utilities
2 | =========
3 |
4 |
5 | Sorting Algorithms
6 | ------------------
7 |
8 | .. autofunction:: deap_er.utilities.sort_log_non_dominated
9 | .. autofunction:: deap_er.utilities.sort_non_dominated
10 | .. autoclass:: deap_er.utilities.SortingNetwork
11 | :members:
12 |
13 | .. raw:: html
14 |
15 |
16 |
17 |
18 |
19 | Population Initializers
20 | -----------------------
21 |
22 | .. autofunction:: deap_er.utilities.init_repeat
23 | .. autofunction:: deap_er.utilities.init_iterate
24 | .. autofunction:: deap_er.utilities.init_cycle
25 |
26 | .. raw:: html
27 |
28 |
29 |
30 |
31 |
32 | Hypervolume Indicators
33 | ----------------------
34 |
35 | .. autofunction:: deap_er.utilities.least_contrib
36 |
37 | .. autofunction:: deap_er.utilities.hypervolume
38 |
39 | .. autoclass:: deap_er.utilities.HyperVolume
40 | :members:
41 |
42 | .. raw:: html
43 |
44 |
45 |
46 |
47 |
48 | Constraint Decorators
49 | ---------------------
50 |
51 | .. autoclass:: deap_er.utilities.DeltaPenalty
52 | :members:
53 |
54 | .. autoclass:: deap_er.utilities.ClosestValidPenalty
55 | :members:
56 |
57 | .. raw:: html
58 |
59 |
60 |
61 |
62 |
63 | Benchmark Decorators
64 | --------------------
65 |
66 | .. automodule:: deap_er.utilities.bm_decors
67 | :members:
68 |
69 | .. raw:: html
70 |
71 |
72 |
73 |
74 |
75 | Various Metrics
76 | ---------------
77 |
78 | .. automodule:: deap_er.utilities.metrics
79 | :members:
80 |
81 | .. raw:: html
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/examples/evolution_strategies/cma_strat.py:
--------------------------------------------------------------------------------
1 | from deap_er import creator
2 | from deap_er import tools
3 | from deap_er import base
4 | import numpy
5 |
6 |
7 | numpy.random.seed(1234) # disables randomization
8 |
9 |
10 | def setup():
11 | creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
12 | creator.create("Individual", list, fitness=creator.FitnessMin)
13 | strategy = tools.Strategy(
14 | centroid=[5.0] * 30,
15 | sigma=5.0,
16 | offsprings=600,
17 | survivors=30
18 | )
19 | toolbox = base.Toolbox()
20 | toolbox.register("evaluate", tools.bm_rastrigin)
21 | toolbox.register("generate", strategy.generate, creator.Individual)
22 | toolbox.register("update", strategy.update)
23 |
24 | stats = tools.Statistics(lambda ind: ind.fitness.values)
25 | stats.register("avg", numpy.mean)
26 | stats.register("std", numpy.std)
27 | stats.register("min", numpy.min)
28 | stats.register("max", numpy.max)
29 |
30 | return toolbox, stats
31 |
32 |
33 | def print_results(best_ind):
34 | if not best_ind.fitness.values < (20,):
35 | raise RuntimeError('Evolution failed to converge.')
36 | print(f'\nEvolution converged correctly.')
37 |
38 |
39 | def main():
40 | toolbox, stats = setup()
41 | hof = tools.HallOfFame(1)
42 | args = dict(
43 | toolbox=toolbox,
44 | generations=250,
45 | hof=hof,
46 | stats=stats,
47 | verbose=True # prints stats
48 | )
49 | tools.ea_generate_update(**args)
50 | print_results(hof[0])
51 |
52 |
53 | if __name__ == "__main__":
54 | main()
55 |
--------------------------------------------------------------------------------
/tests/test_gp/test_semantic.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from deap_er.gp.primitives import PrimitiveSet
12 | from deap_er.gp.generators import gen_grow
13 | from deap_er.gp import cx_semantic, mut_semantic
14 | import operator
15 | import math
16 |
17 |
18 | def lf(x):
19 | return 1 / (1 + math.exp(-x))
20 |
21 |
22 | def test_semantic_crossover():
23 | pset = PrimitiveSet("main", 2)
24 | pset.add_primitive(operator.sub, 2)
25 | pset.add_terminal(3)
26 | pset.add_primitive(lf, 1, name="lf")
27 | pset.add_primitive(operator.add, 2)
28 | pset.add_primitive(operator.mul, 2)
29 | ind1 = gen_grow(pset, 1, 3)
30 | ind2 = gen_grow(pset, 1, 3)
31 | new_ind1, new_ind2 = cx_semantic(ind1, ind2, pset, max_depth=2)
32 | ctr = sum([n.name == ind1[i].name for i, n in enumerate(new_ind1)])
33 | assert ctr == len(ind1)
34 | ctr = sum([n.name == ind2[i].name for i, n in enumerate(new_ind2)])
35 | assert ctr == len(ind2)
36 |
37 |
38 | def test_semantic_mutation():
39 | pset = PrimitiveSet("main", 2)
40 | pset.add_primitive(operator.sub, 2)
41 | pset.add_terminal(3)
42 | pset.add_primitive(lf, 1, name="lf")
43 | pset.add_primitive(operator.add, 2)
44 | pset.add_primitive(operator.mul, 2)
45 | individual = gen_grow(pset, 1, 3)
46 | mutated = mut_semantic(individual, pset, max_depth=2)
47 | ctr = sum([m.name == individual[i].name for i, m in enumerate(mutated[0])])
48 | assert ctr == len(individual)
49 |
--------------------------------------------------------------------------------
/examples/genetic_algorithms/onemax_short.py:
--------------------------------------------------------------------------------
1 | from deap_er import creator
2 | from deap_er import tools
3 | from deap_er import base
4 | import random
5 | import numpy
6 | import array
7 |
8 |
9 | random.seed(1234) # disables randomization
10 |
11 |
12 | def setup():
13 | creator.create("FitnessMax", base.Fitness, weights=(1.0,))
14 | creator.create("Individual", array.array, typecode='b', fitness=creator.FitnessMax)
15 |
16 | toolbox = base.Toolbox()
17 | toolbox.register("attr_bool", random.randint, 0, 1)
18 | toolbox.register("individual", tools.init_repeat, creator.Individual, toolbox.attr_bool, 100)
19 | toolbox.register("population", tools.init_repeat, list, toolbox.individual)
20 | toolbox.register("mate", tools.cx_two_point)
21 | toolbox.register("mutate", tools.mut_flip_bit, mut_prob=0.05)
22 | toolbox.register("select", tools.sel_tournament, contestants=3)
23 | toolbox.register("evaluate", lambda x: sum(x))
24 |
25 | stats = tools.Statistics(lambda ind: ind.fitness.values)
26 | stats.register("avg", numpy.mean)
27 | stats.register("std", numpy.std)
28 | stats.register("min", numpy.min)
29 | stats.register("max", numpy.max)
30 |
31 | return toolbox, stats
32 |
33 |
34 | def print_results(best_ind):
35 | if not all(gene == 1 for gene in best_ind):
36 | raise RuntimeError('Evolution failed to converge.')
37 | print(f'\nEvolution converged correctly.')
38 |
39 |
40 | def main():
41 | toolbox, stats = setup()
42 | pop = toolbox.population(size=300)
43 | hof = tools.HallOfFame(maxsize=1)
44 | args = dict(
45 | toolbox=toolbox,
46 | population=pop,
47 | generations=50,
48 | cx_prob=0.5,
49 | mut_prob=0.2,
50 | hof=hof,
51 | stats=stats,
52 | verbose=True # prints stats
53 | )
54 | tools.ea_simple(**args)
55 | print_results(hof[0])
56 |
57 |
58 | if __name__ == "__main__":
59 | main()
60 |
--------------------------------------------------------------------------------
/examples/genetic_algorithms/onemax_numpy.py:
--------------------------------------------------------------------------------
1 | from deap_er import creator
2 | from deap_er import tools
3 | from deap_er import base
4 | import random
5 | import numpy
6 |
7 |
8 | random.seed(1234) # disables randomization
9 |
10 |
11 | def setup():
12 | creator.create("FitnessMax", base.Fitness, weights=(1.0,))
13 | creator.create("Individual", numpy.ndarray, fitness=creator.FitnessMax)
14 |
15 | toolbox = base.Toolbox()
16 | toolbox.register("attr_bool", random.randint, 0, 1)
17 | toolbox.register("individual", tools.init_repeat, creator.Individual, toolbox.attr_bool, 100)
18 | toolbox.register("population", tools.init_repeat, list, toolbox.individual)
19 |
20 | toolbox.register("mate", tools.cx_two_point_copy)
21 | toolbox.register("mutate", tools.mut_flip_bit, mut_prob=0.05)
22 | toolbox.register("select", tools.sel_tournament, contestants=3)
23 | toolbox.register("evaluate", lambda x: sum(x))
24 |
25 | stats = tools.Statistics(lambda ind: ind.fitness.values)
26 | stats.register("avg", numpy.mean)
27 | stats.register("std", numpy.std)
28 | stats.register("min", numpy.min)
29 | stats.register("max", numpy.max)
30 |
31 | return toolbox, stats
32 |
33 |
34 | def print_results(best_ind):
35 | if not all(gene == 1 for gene in best_ind):
36 | raise RuntimeError('Evolution failed to converge.')
37 | print(f'\nEvolution converged correctly.')
38 |
39 |
40 | def main():
41 | toolbox, stats = setup()
42 | pop = toolbox.population(size=300)
43 | hof = tools.HallOfFame(maxsize=1, similar=numpy.array_equal)
44 | args = dict(
45 | toolbox=toolbox,
46 | population=pop,
47 | generations=50,
48 | cx_prob=0.5,
49 | mut_prob=0.2,
50 | hof=hof,
51 | stats=stats,
52 | verbose=True # prints stats
53 | )
54 | tools.ea_simple(**args)
55 | print_results(hof[0])
56 |
57 |
58 | if __name__ == "__main__":
59 | main()
60 |
--------------------------------------------------------------------------------
/tests/test_base/test_toolbox.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from deap_er.base.toolbox import Toolbox
12 | from functools import partial
13 | from copy import deepcopy
14 |
15 |
16 | # ====================================================================================== #
17 | class TestToolbox:
18 |
19 | def test_clone_func(self):
20 | tb = Toolbox()
21 | assert isinstance(tb.clone, partial)
22 | assert tb.clone.func == deepcopy
23 |
24 | # -------------------------------------------------------------------------------------- #
25 | def test_map_func(self):
26 | tb = Toolbox()
27 | assert isinstance(tb.clone, partial)
28 | assert tb.map.func == map
29 |
30 | # -------------------------------------------------------------------------------------- #
31 | def test_registration(self):
32 | tb = Toolbox()
33 | tb.register('__test__', str, 1)
34 | assert hasattr(tb, '__test__')
35 | tb.unregister('__test__')
36 | assert not hasattr(tb, '__test__')
37 |
38 | # -------------------------------------------------------------------------------------- #
39 | def test_execution(self):
40 | tb = Toolbox()
41 | tb.register('__test__', str, 1)
42 | assert tb.__test__() == '1'
43 |
44 | # -------------------------------------------------------------------------------------- #
45 | def test_decorator(self):
46 | def test_deco(func):
47 | def wrapper(*args, **kwargs):
48 | result = func(*args, **kwargs)
49 | return result * 3
50 | return wrapper
51 |
52 | tb = Toolbox()
53 | tb.register('__test__', str, 1)
54 | tb.decorate('__test__', test_deco)
55 | assert tb.__test__() == '111'
56 |
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # Link to the configuration documentation:
4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
5 |
6 |
7 | # -- Paths setup --------------------------------------------------------------
8 | import os
9 | import sys
10 | sys.path.insert(0, os.path.abspath('.'))
11 | sys.path.insert(0, os.path.abspath('../'))
12 | sys.path.insert(0, os.path.abspath('../../'))
13 | sys.path.append(os.path.abspath("./_static"))
14 |
15 |
16 | # -- Project information -----------------------------------------------------
17 | project = 'deap-er'
18 | copyright = '2022, Mattias Aabmets'
19 | author = 'Mattias Aabmets'
20 | release = '2.0.0'
21 |
22 |
23 | # -- General configuration ---------------------------------------------------
24 | extensions = [
25 | 'sphinx.ext.intersphinx',
26 | 'sphinx.ext.autodoc',
27 | 'sphinx_rtd_theme',
28 | 'sphinx_design'
29 | ]
30 | exclude_patterns = []
31 | templates_path = ['_templates']
32 | add_module_names = False
33 |
34 |
35 | # -- Options for HTML output -------------------------------------------------
36 | html_theme = 'sphinx_rtd_theme'
37 | pygments_style = 'custom_style.CustomStyle'
38 | highlight_language = 'python3'
39 | html_static_path = ['_static']
40 |
41 |
42 | # -- Options for intersphinx -------------------------------------------------
43 | intersphinx_mapping = {
44 | 'python': ('https://docs.python.org/3', None),
45 | 'numpy': ('https://numpy.org/doc/stable/', None)
46 | }
47 |
48 |
49 | # -- Options for Autodoc -------------------------------------------------
50 | autodoc_typehints = 'description'
51 | autodoc_typehints_format = 'short'
52 | autodoc_typehints_description_target = 'documented'
53 | autodoc_preserve_defaults = True
54 | autodoc_warningiserror = True
55 | autodoc_inherit_docstrings = True
56 | autodoc_docstring_signature = True
57 | autoclass_content = "both"
58 | autodoc_class_signature = 'mixed'
59 | autodoc_member_order = 'bysource'
60 | autodoc_default_options = {}
61 | autodoc_type_aliases = {}
62 | autodoc_mock_imports = ['numpy', 'scipy', 'dill']
63 |
--------------------------------------------------------------------------------
/deap_er/creator/overrides.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from typing import Sequence
12 | from copy import deepcopy
13 | import array
14 | import numpy
15 |
16 |
17 | __all__ = ['_NumpyOverride', '_ArrayOverride']
18 |
19 |
20 | # ====================================================================================== #
21 | class _NumpyOverride(numpy.ndarray):
22 | """
23 | Class override for the 'numpy.ndarray' class, because
24 | the 'numpy.ndarray' class is problematic for DEAP-er.
25 | """
26 | @staticmethod
27 | def __new__(cls, seq: Sequence) -> numpy.array:
28 | return numpy.array(list(seq)).view(cls)
29 |
30 | def __deepcopy__(self, memo: dict, *_, **__):
31 | copy = numpy.ndarray.copy(self)
32 | dc = deepcopy(self.__dict__, memo)
33 | copy.__dict__.update(dc)
34 | return copy
35 |
36 | def __setstate__(self, state, *_, **__):
37 | self.__dict__.update(state)
38 |
39 | def __reduce__(self):
40 | return self.__class__, (list(self),), self.__dict__
41 |
42 |
43 | # ====================================================================================== #
44 | class _ArrayOverride(array.array):
45 | """
46 | Class override for the 'array.array' class, because
47 | the 'array.array' class is problematic for DEAP-er.
48 | """
49 | @staticmethod
50 | def __new__(cls, seq: Sequence) -> array.array:
51 | return super().__new__(cls, cls.typecode, seq)
52 |
53 | def __deepcopy__(self, memo: dict) -> object:
54 | cls = self.__class__
55 | copy = cls.__new__(cls, self)
56 | memo[id(self)] = copy
57 | dc = deepcopy(self.__dict__, memo)
58 | copy.__dict__.update(dc)
59 | return copy
60 |
61 | def __reduce__(self) -> tuple:
62 | return self.__class__, (list(self),), self.__dict__
63 |
--------------------------------------------------------------------------------
/deap_er/utilities/hypervolume/node.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from __future__ import annotations
12 | from typing import Callable
13 | from operator import gt, ge, le, lt, eq, ne
14 |
15 |
16 | # ====================================================================================== #
17 | class Node:
18 | def __init__(self, dimensions: int, cargo: tuple = None):
19 | self.cargo = cargo
20 | self.next = [None] * dimensions
21 | self.prev = [None] * dimensions
22 | self.ignore = 0
23 | self.area = [0.0] * dimensions
24 | self.volume = [0.0] * dimensions
25 |
26 | # -------------------------------------------------------- #
27 | def compare(self, other: Node, op: Callable) -> bool:
28 | if self.cargo is None or other.cargo is None:
29 | return False
30 | zipper = zip(self.cargo, other.cargo)
31 | true = [op(a, b) for a, b in zipper]
32 | return all(true)
33 |
34 | # -------------------------------------------------------- #
35 | def __gt__(self, other: Node) -> bool:
36 | return self.compare(other, gt)
37 |
38 | def __ge__(self, other: Node) -> bool:
39 | return self.compare(other, ge)
40 |
41 | def __le__(self, other: Node) -> bool:
42 | return self.compare(other, le)
43 |
44 | def __lt__(self, other: Node) -> bool:
45 | return self.compare(other, lt)
46 |
47 | def __eq__(self, other: Node) -> bool:
48 | return self.compare(other, eq)
49 |
50 | def __ne__(self, other: Node) -> bool:
51 | return self.compare(other, ne)
52 |
53 | # -------------------------------------------------------- #
54 | def __str__(self) -> str:
55 | return str(self.cargo)
56 |
57 | # -------------------------------------------------------- #
58 | def __hash__(self) -> int:
59 | return hash(self.cargo)
60 |
--------------------------------------------------------------------------------
/deap_er/operators/selection/sel_nsga_2.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from deap_er.utilities.sorting import *
12 | from .sel_helpers import assign_crowding_dist
13 | from operator import attrgetter
14 | from itertools import chain
15 |
16 |
17 | __all__ = ['sel_nsga_2']
18 |
19 |
20 | # ====================================================================================== #
21 | def sel_nsga_2(individuals: list, sel_count: int,
22 | sorting: str = 'standard') -> list:
23 | """
24 | Selects the next generation of individuals using the NSGA-II algorithm.
25 | Usually, the size of **individuals** should be larger than the **sel_count**
26 | parameter. If the size of **individuals** is equal to **sel_count**, the
27 | population will be sorted according to their pareto fronts.
28 |
29 | :param individuals: A list of individuals to select from.
30 | :param sel_count: The number of individuals to select.
31 | :param sorting: The algorithm to use for non-dominated
32 | sorting. Can be either 'log' or 'standard' string literal.
33 | :return: A list of selected individuals.
34 | """
35 | if sorting == 'standard':
36 | pareto_fronts = sort_non_dominated(individuals, sel_count)
37 | elif sorting == 'log':
38 | pareto_fronts = sort_log_non_dominated(individuals, sel_count)
39 | else:
40 | raise RuntimeError(
41 | f'selNSGA2: The choice of non-dominated '
42 | f'sorting method \'{sorting}\' is invalid.'
43 | )
44 |
45 | for front in pareto_fronts:
46 | assign_crowding_dist(front)
47 |
48 | chosen = list(chain(*pareto_fronts[:-1]))
49 | sel_count = sel_count - len(chosen)
50 | if sel_count > 0:
51 | attr = attrgetter("fitness.crowding_dist")
52 | sorted_front = sorted(pareto_fronts[-1], key=attr, reverse=True)
53 | chosen.extend(sorted_front[:sel_count])
54 |
55 | return chosen
56 |
--------------------------------------------------------------------------------
/deap_er/algorithms/ea_generate_update.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from deap_er.records.dtypes import *
12 | from deap_er.records import Logbook
13 | from deap_er.base import Toolbox
14 |
15 |
16 | __all__ = ['ea_generate_update']
17 |
18 |
19 | # ====================================================================================== #
20 | def ea_generate_update(toolbox: Toolbox, generations: int,
21 | hof: Hof = None, stats: Stats = None,
22 | verbose: bool = False) -> AlgoResult:
23 | """
24 | An evolutionary algorithm. This function expects the *'generate'*,
25 | *'update'*, and *'evaluate'* operators to be registered in the toolbox.
26 |
27 | :param toolbox: A Toolbox which contains the evolution operators.
28 | :param generations: The number of generations to compute.
29 | :param hof: A HallOfFame or a ParetoFront object, optional.
30 | :param stats: A Statistics or a MultiStatistics object, optional.
31 | :param verbose: Whether to print debug messages, optional.
32 | :return: The final population and the logbook.
33 |
34 | :type hof: :ref:`Hof `
35 | :type stats: :ref:`Stats `
36 | :rtype: :ref:`AlgoResult `
37 | """
38 | logbook = Logbook()
39 | logbook.header = ['gen', 'nevals'] + (stats.fields if stats else [])
40 |
41 | population = None
42 | for gen in range(generations):
43 | population = toolbox.generate()
44 |
45 | fitness = toolbox.map(toolbox.evaluate, population)
46 | for ind, fit in zip(population, fitness):
47 | ind.fitness.values = fit
48 |
49 | toolbox.update(population)
50 |
51 | if hof is not None:
52 | hof.update(population)
53 | record = stats.compile(population) if stats else {}
54 | logbook.record(gen=gen, nevals=len(population), **record)
55 | if verbose:
56 | print(logbook.stream)
57 |
58 | return population, logbook
59 |
--------------------------------------------------------------------------------
/examples/genetic_algorithms/onemax_mp.py:
--------------------------------------------------------------------------------
1 | from deap_er import creator
2 | from deap_er import tools
3 | from deap_er import base
4 | import multiprocessing as mp
5 | import random
6 | import numpy
7 | import array
8 |
9 |
10 | random.seed(1234) # disables randomization
11 |
12 |
13 | # Evaluator can't be a lambda, because lambdas can't be pickled.
14 | def evaluate(individual):
15 | return sum(individual)
16 |
17 |
18 | # Can't be in setup(), because subprocesses need these objects.
19 | creator.create("FitnessMax", base.Fitness, weights=(1.0,))
20 | creator.create("Individual", array.array, typecode='b', fitness=creator.FitnessMax)
21 |
22 |
23 | def setup():
24 | toolbox = base.Toolbox()
25 | toolbox.register("attr_bool", random.randint, 0, 1)
26 | toolbox.register("individual", tools.init_repeat, creator.Individual, toolbox.attr_bool, 100)
27 | toolbox.register("population", tools.init_repeat, list, toolbox.individual)
28 |
29 | toolbox.register("mate", tools.cx_two_point)
30 | toolbox.register("mutate", tools.mut_flip_bit, mut_prob=0.05)
31 | toolbox.register("select", tools.sel_tournament, contestants=3)
32 | toolbox.register("evaluate", evaluate)
33 |
34 | stats = tools.Statistics(lambda ind: ind.fitness.values)
35 | stats.register("avg", numpy.mean)
36 | stats.register("std", numpy.std)
37 | stats.register("min", numpy.min)
38 | stats.register("max", numpy.max)
39 |
40 | return toolbox, stats
41 |
42 |
43 | def print_results(best_ind):
44 | if not all(gene == 1 for gene in best_ind):
45 | raise RuntimeError('Evolution failed to converge.')
46 | print(f'\nEvolution converged correctly.')
47 |
48 |
49 | def main():
50 | toolbox, stats = setup()
51 | pop = toolbox.population(size=300)
52 | hof = tools.HallOfFame(maxsize=1)
53 | with mp.Pool() as pool:
54 | toolbox.register("map", pool.map)
55 | args = dict(
56 | toolbox=toolbox,
57 | population=pop,
58 | generations=50,
59 | cx_prob=0.5,
60 | mut_prob=0.2,
61 | hof=hof,
62 | stats=stats,
63 | verbose=True # prints stats
64 | )
65 | tools.ea_simple(**args)
66 | print_results(hof[0])
67 |
68 |
69 | if __name__ == "__main__":
70 | main()
71 |
--------------------------------------------------------------------------------
/deap_er/operators/migration.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from typing import Callable
12 |
13 |
14 | __all__ = ['mig_ring']
15 |
16 |
17 | # ====================================================================================== #
18 | def mig_ring(populations: list, mig_count: int, selection: Callable,
19 | replacement: Callable = None, mig_indices: list = None) -> None:
20 | """
21 | Performs a ring migration between the **populations**. The migration
22 | first selects **mig_count** emigrants from each population using the
23 | specified **selection** operator and then switches the selected
24 | individuals between the populations.
25 |
26 | :param populations: A list of populations on which to operate migration.
27 | :param mig_count: The number of individuals to migrate.
28 | :param selection: The function to select emigrants from each population.
29 | :param replacement: The function to select which individuals will be switched.
30 | :param mig_indices: A list of indices indicating where the individuals from a
31 | particular position in the list goes. Default is a ring migration.
32 | :return: Nothing.
33 | """
34 | nbr_demes = len(populations)
35 | if mig_indices is None:
36 | mig_indices = list(range(1, nbr_demes)) + [0]
37 |
38 | immigrants = [[] for _ in range(nbr_demes)]
39 | emigrants = [[] for _ in range(nbr_demes)]
40 |
41 | for from_deme in range(nbr_demes):
42 | emigrants[from_deme].extend(selection(populations[from_deme], mig_count))
43 | if replacement is None:
44 | immigrants[from_deme] = emigrants[from_deme]
45 | else:
46 | emigrants = replacement(populations[from_deme], mig_count)
47 | immigrants[from_deme].extend(emigrants)
48 |
49 | for from_deme, to_deme in enumerate(mig_indices):
50 | for i, immigrant in enumerate(immigrants[to_deme]):
51 | indx = populations[to_deme].index(immigrant)
52 | populations[to_deme][indx] = emigrants[from_deme][i]
53 |
--------------------------------------------------------------------------------
/deap_er/utilities/hypervolume/least_contrib.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from .hypervolume import HyperVolume
12 | from typing import Callable, Optional, Union
13 | import numpy
14 |
15 |
16 | __all__ = ['least_contrib']
17 |
18 |
19 | # ====================================================================================== #
20 | def _compute_hv(data: tuple) -> float:
21 | point_set, ref_point = data[0], data[1]
22 | hv = HyperVolume(ref_point)
23 | return hv.compute(point_set)
24 |
25 |
26 | # -------------------------------------------------------------------------------------- #
27 | def least_contrib(population: list, ref_point: Optional[list] = None,
28 | map_func: Optional[Callable] = map) -> Union[int, numpy.ndarray]:
29 | """
30 | Returns the index of the individual with the least hypervolume
31 | contribution. Minimization is implicitly assumed.
32 |
33 | :param population: A list of non-dominated individuals,
34 | where each individual has a Fitness attribute.
35 | :param ref_point: The reference point for the hypervolume, optional.
36 | :param map_func: Any map function which maps an iterable to a callable,
37 | optional. This can be used to speed up the computation by providing
38 | a multiprocess mapping function which is associated to a pool of
39 | workers. The default is the regular single-process map function.
40 | :return: The index of the individual with the least hypervolume contribution.
41 | """
42 | wvals = [ind.fitness.wvalues for ind in population]
43 | wvals = numpy.array(wvals) * -1
44 | if ref_point is None:
45 | ref_point = numpy.max(wvals, axis=0) + 1
46 | else:
47 | ref_point = numpy.array(ref_point)
48 |
49 | data = []
50 | for i in range(len(population)):
51 | point_set = (wvals[:i], wvals[i + 1:])
52 | point_set = numpy.concatenate(point_set)
53 | data.append((point_set, ref_point))
54 |
55 | contrib_values = list(map_func(_compute_hv, data))
56 | return numpy.argmax(contrib_values)
57 |
--------------------------------------------------------------------------------
/examples/genetic_algorithms/diff_evo_basic.py:
--------------------------------------------------------------------------------
1 | from deap_er import creator
2 | from deap_er import tools
3 | from deap_er import base
4 | import random
5 | import array
6 | import numpy
7 |
8 |
9 | random.seed(1234) # disables randomization
10 |
11 | NDIM = 10
12 | CR = 0.25
13 | F = 1
14 | MU = 300
15 | NGEN = 200
16 |
17 |
18 | def setup():
19 | creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
20 | creator.create("Individual", array.array, typecode='d', fitness=creator.FitnessMin)
21 |
22 | toolbox = base.Toolbox()
23 | toolbox.register("attr_float", random.uniform, -3, 3)
24 | toolbox.register("individual", tools.init_repeat, creator.Individual, toolbox.attr_float, NDIM)
25 | toolbox.register("population", tools.init_repeat, list, toolbox.individual)
26 | toolbox.register("select", tools.sel_random, sel_count=3)
27 | toolbox.register("evaluate", tools.bm_sphere)
28 |
29 | stats = tools.Statistics(lambda ind: ind.fitness.values)
30 | stats.register("avg", numpy.mean)
31 | stats.register("std", numpy.std)
32 | stats.register("min", numpy.min)
33 | stats.register("max", numpy.max)
34 |
35 | logbook = tools.Logbook()
36 | logbook.header = "gen", "evals", "std", "min", "avg", "max"
37 |
38 | return toolbox, stats, logbook
39 |
40 |
41 | def print_results(best_ind):
42 | if not best_ind.fitness.values < (1e-3,):
43 | raise RuntimeError('Evolution failed to converge.')
44 | print(f'\nEvolution converged correctly.')
45 |
46 |
47 | def main():
48 | toolbox, stats, logbook = setup()
49 | pop = toolbox.population(size=MU)
50 | hof = tools.HallOfFame(1)
51 |
52 | def log_stats(ngen=0):
53 | record = stats.compile(pop)
54 | logbook.record(gen=ngen, evals=len(pop), **record)
55 | print(logbook.stream)
56 |
57 | fitness = toolbox.map(toolbox.evaluate, pop)
58 | for ind, fit in zip(pop, fitness):
59 | ind.fitness.values = fit
60 |
61 | log_stats()
62 |
63 | for gen in range(1, NGEN):
64 | for k, agent in enumerate(pop):
65 | a, b, c = toolbox.select(pop)
66 | y = toolbox.clone(agent)
67 | index = random.randrange(NDIM)
68 | for i, value in enumerate(agent):
69 | if i == index or random.random() < CR:
70 | y[i] = a[i] + F * (b[i] - c[i])
71 | y.fitness.values = toolbox.evaluate(y)
72 | if y.fitness > agent.fitness:
73 | pop[k] = y
74 | hof.update(pop)
75 | log_stats(gen)
76 |
77 | print_results(hof[0])
78 |
79 |
80 | if __name__ == "__main__":
81 | main()
82 |
--------------------------------------------------------------------------------
/deap_er/algorithms/ea_simple.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from deap_er.records.dtypes import *
12 | from deap_er.records import Logbook
13 | from deap_er.base import Toolbox
14 | from .variation import *
15 |
16 |
17 | __all__ = ['ea_simple']
18 |
19 |
20 | # ====================================================================================== #
21 | def ea_simple(toolbox: Toolbox, population: list, generations: int,
22 | cx_prob: float, mut_prob: float, hof: Hof = None,
23 | stats: Stats = None, verbose: bool = False) -> AlgoResult:
24 | """
25 | An evolutionary algorithm. This function expects the *'mate'*, *'mutate'*,
26 | *'select'* and *'evaluate'* operators to be registered in the toolbox.
27 |
28 | :param toolbox: A Toolbox which contains the evolution operators.
29 | :param population: A list of individuals to evolve.
30 | :param generations: The number of generations to compute.
31 | :param cx_prob: The probability of mating two individuals.
32 | :param mut_prob: The probability of mutating an individual.
33 | :param hof: A HallOfFame or a ParetoFront object, optional.
34 | :param stats: A Statistics or a MultiStatistics object, optional.
35 | :param verbose: Whether to print debug messages, optional.
36 | :return: The final population and the logbook.
37 |
38 | :type hof: :ref:`Hof `
39 | :type stats: :ref:`Stats `
40 | :rtype: :ref:`AlgoResult `
41 | """
42 | logbook = Logbook()
43 | logbook.header = ['gen', 'nevals'] + (stats.fields if stats else [])
44 |
45 | for gen in range(1, generations + 1):
46 | offspring = toolbox.select(population, len(population))
47 | offspring = var_and(toolbox, offspring, cx_prob, mut_prob)
48 |
49 | invalids = [ind for ind in offspring if not ind.fitness.is_valid()]
50 | fitness = toolbox.map(toolbox.evaluate, invalids)
51 | for ind, fit in zip(invalids, fitness):
52 | ind.fitness.values = fit
53 |
54 | population[:] = offspring
55 |
56 | if hof is not None:
57 | hof.update(offspring)
58 | record = stats.compile(population) if stats else {}
59 | logbook.record(gen=gen, nevals=len(invalids), **record)
60 | if verbose:
61 | print(logbook.stream)
62 |
63 | return population, logbook
64 |
--------------------------------------------------------------------------------
/examples/genetic_algorithms/onemax_detailed.py:
--------------------------------------------------------------------------------
1 | from deap_er import creator
2 | from deap_er import tools
3 | from deap_er import base
4 | import random
5 |
6 |
7 | random.seed(1234) # disables randomization
8 |
9 | NGEN = 1000
10 | CX_PROB = 0.5
11 | MUT_PROB = 0.2
12 |
13 |
14 | def setup():
15 | creator.create("FitnessMax", base.Fitness, weights=(1.0,))
16 | creator.create("Individual", list, fitness=creator.FitnessMax)
17 |
18 | toolbox = base.Toolbox()
19 | toolbox.register("attr_bool", random.randint, 0, 1)
20 | toolbox.register("individual", tools.init_repeat, creator.Individual, toolbox.attr_bool, 100)
21 | toolbox.register("population", tools.init_repeat, list, toolbox.individual)
22 | toolbox.register("mate", tools.cx_two_point)
23 | toolbox.register("mutate", tools.mut_flip_bit, mut_prob=0.05)
24 | toolbox.register("select", tools.sel_tournament, contestants=3)
25 | toolbox.register("evaluate", lambda x: sum(x))
26 |
27 | return toolbox
28 |
29 |
30 | def print_results(best_ind):
31 | if not all(gene == 1 for gene in best_ind):
32 | raise RuntimeError('Evolution failed to converge.')
33 | print(f'\nEvolution converged correctly.')
34 |
35 |
36 | def main():
37 | toolbox = setup()
38 | population = toolbox.population(size=300)
39 |
40 | fitness = map(toolbox.evaluate, population)
41 | for ind, fit in zip(population, fitness):
42 | ind.fitness.values = fit
43 | fits = [ind.fitness.values[0] for ind in population]
44 |
45 | generation = 0
46 | while max(fits) < 100 and generation < NGEN:
47 | offspring = toolbox.select(population, len(population))
48 | offspring = list(map(toolbox.clone, offspring))
49 |
50 | for child1, child2 in zip(offspring[::2], offspring[1::2]):
51 | if random.random() < CX_PROB:
52 | toolbox.mate(child1, child2)
53 | del child1.fitness.values
54 | del child2.fitness.values
55 |
56 | for mutant in offspring:
57 | if random.random() < MUT_PROB:
58 | toolbox.mutate(mutant)
59 | del mutant.fitness.values
60 |
61 | invalid_ind = [ind for ind in offspring if not ind.fitness.is_valid()]
62 | fitness = map(toolbox.evaluate, invalid_ind)
63 | for ind, fit in zip(invalid_ind, fitness):
64 | ind.fitness.values = fit
65 | population[:] = offspring
66 | fits = [ind.fitness.values[0] for ind in population]
67 | generation += 1
68 |
69 | best_ind = tools.sel_best(population, sel_count=1)[0]
70 | print_results(best_ind)
71 |
72 |
73 | if __name__ == "__main__":
74 | main()
75 |
--------------------------------------------------------------------------------
/examples/genetic_algorithms/nqueens.py:
--------------------------------------------------------------------------------
1 | from deap_er import creator
2 | from deap_er import tools
3 | from deap_er import base
4 | import random
5 | import numpy
6 |
7 |
8 | random.seed(1234) # disables randomization
9 |
10 | BOARD_SIZE = 20
11 |
12 |
13 | def evaluate(individual):
14 | size = len(individual)
15 | left_diagonal = [0] * (2 * size - 1)
16 | right_diagonal = [0] * (2 * size - 1)
17 |
18 | for i in range(size):
19 | l_idx = i + individual[i]
20 | left_diagonal[l_idx] += 1
21 | r_idx = size - 1 - i + individual[i]
22 | right_diagonal[r_idx] += 1
23 |
24 | sum_ = 0
25 | for i in range(2*size-1):
26 | if left_diagonal[i] > 1:
27 | sum_ += left_diagonal[i] - 1
28 | if right_diagonal[i] > 1:
29 | sum_ += right_diagonal[i] - 1
30 |
31 | return sum_, # The comma is essential here.
32 |
33 |
34 | def setup():
35 | creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
36 | creator.create("Individual", list, fitness=creator.FitnessMin)
37 |
38 | toolbox = base.Toolbox()
39 | toolbox.register("permutation", random.sample, range(BOARD_SIZE), BOARD_SIZE)
40 | toolbox.register("individual", tools.init_iterate, creator.Individual, toolbox.permutation)
41 | toolbox.register("population", tools.init_repeat, list, toolbox.individual)
42 | toolbox.register("mate", tools.cx_partially_matched)
43 | toolbox.register("mutate", tools.mut_shuffle_indexes, mut_prob=2.0 / BOARD_SIZE)
44 | toolbox.register("select", tools.sel_tournament, contestants=3)
45 | toolbox.register("evaluate", evaluate)
46 |
47 | stats = tools.Statistics(lambda ind: ind.fitness.values)
48 | stats.register("Avg", numpy.mean)
49 | stats.register("Std", numpy.std)
50 | stats.register("Min", numpy.min)
51 | stats.register("Max", numpy.max)
52 |
53 | return toolbox, stats
54 |
55 |
56 | def print_results(best_ind):
57 | if not best_ind.fitness.values == (0.0,):
58 | raise RuntimeError('Evolution failed to converge.')
59 | print(f'\nRow numbers for each queen on each column of the chessboard: \n{best_ind}')
60 | print(f'\nEvolution converged correctly.')
61 |
62 |
63 | def main():
64 | toolbox, stats = setup()
65 | pop = toolbox.population(size=300)
66 | hof = tools.HallOfFame(1)
67 | args = dict(
68 | toolbox=toolbox,
69 | population=pop,
70 | generations=400,
71 | cx_prob=0.6,
72 | mut_prob=0.3,
73 | hof=hof,
74 | stats=stats,
75 | verbose=True # prints stats
76 | )
77 | tools.ea_simple(**args)
78 | print_results(hof[0])
79 |
80 |
81 | if __name__ == "__main__":
82 | main()
83 |
--------------------------------------------------------------------------------
/docs/source/tutorials/multiprocessing.rst:
--------------------------------------------------------------------------------
1 | Multiprocessing
2 | ===============
3 |
4 | In this section of the tutorial we will learn how to speed up the computation of evolutionary algorithms
5 | by using multiprocessing. The distribution of workloads over multiple cores or computing clusters requires
6 | the serialization of data objects, which is usually done by pickling, therefore all objects that are to be
7 | distributed *(e.g. functions and their arguments)* must be pickleable.
8 |
9 | The correct way of using multiprocessing with **DEAP-ER** is to override the default ``map`` function in the
10 | toolbox with one that supports parallel execution. The only requirement of this ``map`` function is that
11 | its signature and return type must match with the regular ``map`` function. This enables the use of any
12 | third-party distributed computing libraries, such as `Ray `_, that implement the same interface.
14 |
15 | .. code-block::
16 | :caption: With context manager
17 |
18 | # Using the multiprocessing library
19 | with multiprocessing.Pool() as pool:
20 | toolbox.register('map', pool.map)
21 | # Execute the evolution
22 |
23 |
24 | # Using the concurrent.futures library
25 | with concurrent.futures.ProcessPoolExecutor() as executor:
26 | toolbox.register('map', executor.map)
27 | # Execute the evolution
28 |
29 |
30 | .. code-block::
31 | :caption: Without context manager
32 |
33 | # Using the multiprocessing library
34 | pool = multiprocessing.Pool()
35 | toolbox.register('map', pool.map)
36 | # Execute the evolution
37 | pool.close()
38 | pool.join()
39 |
40 |
41 | # Using the concurrent.futures library
42 | executor = concurrent.futures.ProcessPoolExecutor()
43 | toolbox.register('map', executor.map)
44 | # Execute the evolution
45 | executor.shutdown()
46 |
47 | .. raw:: html
48 |
49 |
50 |
51 | .. note::
52 | It is also suggested to take a look at the :ref:`full example ` of using multiprocessing with **DEAP-ER**.
53 |
54 | .. attention::
55 | | When using multiprocessing on Windows, the main function needs to be guarded
56 | | with the :code:`if __name__ == '__main__'` statement.
57 |
58 | .. tip::
59 | | An excellent third-party tutorial about multiprocessing in Python is available
60 | `HERE `_.
61 | | The reference manuals of the `multiprocessing `_
62 | and the `concurrent.futures `_ libraries
63 | | should also prove useful.
64 |
65 | .. raw:: html
66 |
67 |
68 |
--------------------------------------------------------------------------------
/deap_er/utilities/sorting/sort_non_dominated.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from collections import defaultdict
12 |
13 |
14 | __all__ = ['sort_non_dominated']
15 |
16 |
17 | # ====================================================================================== #
18 | def sort_non_dominated(individuals: list, sel_count: int,
19 | ffo: bool = False) -> list:
20 | """
21 | Sorts the first 'sel_count' of 'individuals' into
22 | different non-domination levels using the
23 | "Fast Non-dominated Sorting Approach".
24 |
25 | :param individuals: A list of individuals to sort.
26 | :param sel_count: The number of individuals to select.
27 | :param ffo: If True, only the first front is returned, optional.
28 | :return: A list of Pareto fronts, where the
29 | first element is the true Pareto front.
30 | """
31 | if sel_count == 0:
32 | return []
33 |
34 | map_fit_ind = defaultdict(list)
35 | for ind in individuals:
36 | map_fit_ind[ind.fitness].append(ind)
37 | fits = list(map_fit_ind.keys())
38 |
39 | current_front = []
40 | next_front = []
41 | dominating_fits = defaultdict(int)
42 | dominated_fits = defaultdict(list)
43 |
44 | for i, fit_i in enumerate(fits):
45 | for fit_j in fits[i+1:]:
46 | if fit_i.dominates(fit_j):
47 | dominating_fits[fit_j] += 1
48 | dominated_fits[fit_i].append(fit_j)
49 | elif fit_j.dominates(fit_i):
50 | dominating_fits[fit_i] += 1
51 | dominated_fits[fit_j].append(fit_i)
52 | if dominating_fits[fit_i] == 0:
53 | current_front.append(fit_i)
54 |
55 | fronts = [[]]
56 | for fit in current_front:
57 | fronts[-1].extend(map_fit_ind[fit])
58 | pareto_sorted = len(fronts[-1])
59 |
60 | if not ffo:
61 | big_n = min(len(individuals), sel_count)
62 | while pareto_sorted < big_n:
63 | fronts.append([])
64 | for fit_p in current_front:
65 | for fit_d in dominated_fits[fit_p]:
66 | dominating_fits[fit_d] -= 1
67 | if dominating_fits[fit_d] == 0:
68 | next_front.append(fit_d)
69 | pareto_sorted += len(map_fit_ind[fit_d])
70 | fronts[-1].extend(map_fit_ind[fit_d])
71 | current_front = next_front
72 | next_front = []
73 |
74 | return fronts
75 |
--------------------------------------------------------------------------------
/deap_er/creator/creator.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from .overrides import *
12 | from typing import Optional, Union
13 | import warnings
14 |
15 |
16 | __all__ = ['create']
17 |
18 |
19 | # ====================================================================================== #
20 | def create(name: str, base: Union[type, object], **kwargs: Optional) -> None:
21 | """
22 | Creates a new class named **name**, which inherits from the **base** class, and
23 | registers it into the global namespace of the *creator* module. Any optional
24 | **kwargs** provided to this function will be set as attributes of the new class.
25 |
26 | :param name: The name of the new class to create.
27 | :param base: A type or an object from which to inherit.
28 | :param kwargs: One or more keyword arguments to add to the new class
29 | as attributes, optional. If a kwarg is an instance, it will
30 | be added as a class attribute. If a kwarg is a class, it
31 | will be instantiated and added as an instance attribute.
32 | :return: Nothing.
33 | """
34 | # warn about class definition overwrite
35 | if name in globals():
36 | msg = f"You are creating a new class named \'{name}\', " \
37 | f"which already exists. The old definition will " \
38 | f"be overwritten by the new one."
39 | warnings.warn(
40 | message=msg,
41 | category=RuntimeWarning
42 | )
43 |
44 | # set base to class if base is an instance
45 | if not hasattr(base, '__module__'):
46 | base = base.__class__
47 |
48 | # override numpy and array classes
49 | base = dict(
50 | array=_ArrayOverride,
51 | numpy=_NumpyOverride
52 | ).get(base.__module__, base)
53 |
54 | # separate kwargs by their type
55 | inst_attr, cls_attr = dict(), dict()
56 | for key, value in kwargs.items():
57 | condition = type(value) is type
58 | _dict = inst_attr if condition else cls_attr
59 | _dict[key] = value
60 |
61 | # create the new class
62 | new_class = type(name, tuple([base]), cls_attr)
63 |
64 | # define the replacement init func
65 | def new_init_func(self, *args_, **kwargs_):
66 | for attr_name, attr_obj in inst_attr.items():
67 | setattr(self, attr_name, attr_obj())
68 | if base.__init__ is not object.__init__:
69 | base.__init__(self, *args_, **kwargs_)
70 |
71 | # override the init func and set the global name
72 | new_class.__init__ = new_init_func
73 | globals()[name] = new_class
74 |
--------------------------------------------------------------------------------
/examples/evolution_strategies/custom_strat.py:
--------------------------------------------------------------------------------
1 | from deap_er import creator
2 | from deap_er import tools
3 | from deap_er import base
4 | import random
5 | import numpy
6 | import array
7 |
8 |
9 | random.seed(1234) # disables randomization
10 |
11 | IND_SIZE = 30
12 | MIN_VALUE = 4
13 | MAX_VALUE = 5
14 | MIN_STRATEGY = 0.5
15 | MAX_STRATEGY = 3
16 |
17 |
18 | def gen_evo_strat(icls, scls):
19 | ind = icls(random.uniform(MIN_VALUE, MAX_VALUE) for _ in range(IND_SIZE))
20 | ind.strategy = scls(random.uniform(MIN_STRATEGY, MAX_STRATEGY) for _ in range(IND_SIZE))
21 | return ind
22 |
23 |
24 | def check_strategy(strat):
25 | def wrapper(func):
26 | def wrapped(*args, **kwargs):
27 | children = func(*args, **kwargs)
28 | for child in children:
29 | for i, s in enumerate(child.strategy):
30 | if s < strat:
31 | child.strategy[i] = strat
32 | return children
33 | return wrapped
34 | return wrapper
35 |
36 |
37 | def setup():
38 | creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
39 | creator.create("Individual", array.array, typecode="d", fitness=creator.FitnessMin, strategy=None)
40 | creator.create("Strategy", array.array, typecode="d")
41 |
42 | toolbox = base.Toolbox()
43 | toolbox.register("individual", gen_evo_strat, creator.Individual, creator.Strategy)
44 | toolbox.register("population", tools.init_repeat, list, toolbox.individual)
45 | toolbox.register("mate", tools.cx_es_blend, alpha=0.1)
46 | toolbox.register("mutate", tools.mut_es_log_normal, learn_rate=1.0, mut_prob=0.03)
47 | toolbox.register("select", tools.sel_tournament, contestants=3)
48 | toolbox.register("evaluate", tools.bm_sphere)
49 | toolbox.decorate("mate", check_strategy(MIN_STRATEGY))
50 | toolbox.decorate("mutate", check_strategy(MIN_STRATEGY))
51 |
52 | stats = tools.Statistics(lambda ind: ind.fitness.values)
53 | stats.register("avg", numpy.mean)
54 | stats.register("std", numpy.std)
55 | stats.register("min", numpy.min)
56 | stats.register("max", numpy.max)
57 |
58 | return toolbox, stats
59 |
60 |
61 | def print_results(best_ind):
62 | if not best_ind.fitness.values < (0.5,):
63 | raise RuntimeError('Evolution failed to converge.')
64 | print(f'\nEvolution converged correctly.')
65 |
66 |
67 | def main():
68 | toolbox, stats = setup()
69 | pop = toolbox.population(size=100)
70 | hof = tools.HallOfFame(1)
71 | args = dict(
72 | toolbox=toolbox,
73 | population=pop,
74 | generations=500,
75 | offsprings=100,
76 | survivors=10,
77 | cx_prob=0.6,
78 | mut_prob=0.3,
79 | hof=hof,
80 | stats=stats,
81 | verbose=True # prints stats
82 | )
83 | tools.ea_mu_comma_lambda(**args)
84 | print_results(hof[0])
85 |
86 |
87 | if __name__ == "__main__":
88 | main()
89 |
--------------------------------------------------------------------------------
/tests/test_utilities/test_initializers.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from deap_er.utilities import initializers as init
12 |
13 |
14 | # ====================================================================================== #
15 | def test_func_a() -> str:
16 | return 'gene'
17 |
18 |
19 | def test_func_b() -> list:
20 | return [i for i in range(3)]
21 |
22 |
23 | # ====================================================================================== #
24 | class TestHelpers:
25 |
26 | def test_init_repeat_1(self):
27 | rtype = list
28 | count = 3
29 | result = init.init_repeat(rtype, test_func_a, count)
30 | assert isinstance(result, rtype)
31 | assert result.count('gene') == count
32 | assert len(result) == count
33 |
34 | # -------------------------------------------------------------------------------------- #
35 | def test_init_repeat_2(self):
36 | rtype = tuple
37 | count = 3
38 | result = init.init_repeat(rtype, test_func_a, count)
39 | assert isinstance(result, rtype)
40 | assert len(result) == count
41 | assert result.count('gene') == count
42 |
43 | # -------------------------------------------------------------------------------------- #
44 | def test_init_iterate_1(self):
45 | rtype = list
46 | result = init.init_iterate(rtype, test_func_b)
47 | assert isinstance(result, rtype)
48 | assert result == [0, 1, 2]
49 |
50 | # -------------------------------------------------------------------------------------- #
51 | def test_init_iterate_2(self):
52 | rtype = tuple
53 | result = init.init_iterate(rtype, test_func_b)
54 | assert isinstance(result, rtype)
55 | assert result == (0, 1, 2)
56 |
57 | # -------------------------------------------------------------------------------------- #
58 | def test_init_cycle_1(self):
59 | rtype = list
60 | count = 3
61 | funcs = {test_func_a, test_func_b}
62 | result = init.init_cycle(rtype, funcs, count)
63 | assert isinstance(result, rtype)
64 | assert len(result) == 6
65 | assert result.count('gene') == 3
66 | assert result.count([0, 1, 2]) == 3
67 |
68 | # -------------------------------------------------------------------------------------- #
69 | def test_init_cycle_2(self):
70 | rtype = tuple
71 | count = 3
72 | funcs = {test_func_a, test_func_b}
73 | result = init.init_cycle(rtype, funcs, count)
74 | assert isinstance(result, rtype)
75 | assert len(result) == 6
76 | assert result.count('gene') == 3
77 | assert result.count([0, 1, 2]) == 3
78 |
--------------------------------------------------------------------------------
/deap_er/algorithms/ea_mu_plus_lambda.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from deap_er.records.dtypes import *
12 | from deap_er.records import Logbook
13 | from deap_er.base import Toolbox
14 | from .variation import *
15 |
16 |
17 | __all__ = ['ea_mu_plus_lambda']
18 |
19 |
20 | # ====================================================================================== #
21 | def ea_mu_plus_lambda(toolbox: Toolbox, population: list,
22 | generations: int, offsprings: int,
23 | survivors: int, cx_prob: float,
24 | mut_prob: float, hof: Hof = None,
25 | stats: Stats = None, verbose: bool = False) -> AlgoResult:
26 | """
27 | An evolutionary algorithm. This function expects the *'mate'*, *'mutate'*,
28 | *'select'* and *'evaluate'* operators to be registered in the toolbox.
29 | The survivors are selected from the offspring and the parent populations.
30 |
31 | :param toolbox: A Toolbox which contains the evolution operators.
32 | :param population: A list of individuals to evolve.
33 | :param generations: The number of generations to compute.
34 | :param offsprings: The number of individuals to produce at each generation.
35 | :param survivors: The number of individuals to select from the offspring.
36 | :param cx_prob: The probability of mating two individuals.
37 | :param mut_prob: The probability of mutating an individual.
38 | :param hof: A HallOfFame or a ParetoFront object, optional.
39 | :param stats: A Statistics or a MultiStatistics object, optional.
40 | :param verbose: Whether to print debug messages, optional.
41 | :return: The final population and the logbook.
42 |
43 | :type hof: :ref:`Hof `
44 | :type stats: :ref:`Stats `
45 | :rtype: :ref:`AlgoResult `
46 | """
47 | logbook = Logbook()
48 | logbook.header = ['gen', 'nevals'] + (stats.fields if stats else [])
49 |
50 | for gen in range(1, generations + 1):
51 | offspring = var_or(toolbox, population, offsprings, cx_prob, mut_prob)
52 |
53 | invalids = [ind for ind in offspring if not ind.fitness.is_valid()]
54 | fitness = toolbox.map(toolbox.evaluate, invalids)
55 | for ind, fit in zip(invalids, fitness):
56 | ind.fitness.values = fit
57 |
58 | population[:] = toolbox.select(population + offspring, survivors)
59 |
60 | if hof is not None:
61 | hof.update(offspring)
62 | record = stats.compile(population) if stats else {}
63 | logbook.record(gen=gen, nevals=len(invalids), **record)
64 | if verbose:
65 | print(logbook.stream)
66 |
67 | return population, logbook
68 |
--------------------------------------------------------------------------------
/tests/test_base/test_fitness.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from deap_er.base.fitness import Fitness
12 | from copy import deepcopy
13 | import pytest
14 |
15 |
16 | # ====================================================================================== #
17 | class TestFitness:
18 |
19 | def test_instantiation(self):
20 | with pytest.raises(TypeError):
21 | Fitness()
22 | Fitness.weights = [1, 2, 3]
23 | Fitness()
24 |
25 | # -------------------------------------------------------------------------------------- #
26 | def test_values_bad_length(self):
27 | Fitness.weights = [1, 2, 3]
28 | with pytest.raises(TypeError):
29 | Fitness([1, 2, 3, 4])
30 |
31 | # -------------------------------------------------------------------------------------- #
32 | def test_values_access(self):
33 | Fitness.weights = [1, 2, 3]
34 |
35 | ft = Fitness()
36 | assert ft.is_valid() is False
37 | assert ft.values == tuple()
38 |
39 | ft.values = [2, 2, 2]
40 | assert ft.is_valid() is True
41 | assert ft.values == (2, 2, 2)
42 | assert ft.wvalues == (2, 4, 6)
43 |
44 | del ft.values
45 | assert ft.is_valid() is False
46 | assert ft.wvalues == tuple()
47 |
48 | # -------------------------------------------------------------------------------------- #
49 | def test_domination(self):
50 | Fitness.weights = [1, 1, 1]
51 | ft1 = Fitness([2, 2, 2])
52 | ft2 = Fitness([2, 2, 3])
53 | ft3 = Fitness([1, 2, 3])
54 |
55 | assert not ft1.dominates(ft2)
56 | assert not ft1.dominates(ft3)
57 |
58 | assert ft2.dominates(ft1)
59 | assert ft2.dominates(ft3)
60 |
61 | assert not ft3.dominates(ft1)
62 | assert not ft3.dominates(ft2)
63 |
64 | # -------------------------------------------------------------------------------------- #
65 | def test_comparison(self):
66 | Fitness.weights = [1, 1, 1]
67 | ft1 = Fitness([2, 2, 2])
68 | ft2 = Fitness([3, 3, 3])
69 | ft3 = Fitness([4, 4, 4])
70 |
71 | assert ft3 > ft2
72 | assert ft3 >= ft3
73 | assert ft2 <= ft2
74 | assert ft1 < ft2
75 | assert ft1 == ft1
76 | assert ft1 != ft3
77 |
78 | # -------------------------------------------------------------------------------------- #
79 | def test_helper_methods(self):
80 | Fitness.weights = [1, 1, 1]
81 | ft1 = Fitness([2, 2, 2])
82 | ft2 = Fitness([3, 3, 3])
83 |
84 | assert hash(ft1) != hash(ft2)
85 | assert ft1.__str__() == '(2.0, 2.0, 2.0)'
86 | assert ft1 == deepcopy(ft1)
87 |
--------------------------------------------------------------------------------
/deap_er/algorithms/ea_mu_comma_lambda.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from deap_er.records.dtypes import *
12 | from deap_er.records import Logbook
13 | from deap_er.base import Toolbox
14 | from .variation import *
15 |
16 |
17 | __all__ = ['ea_mu_comma_lambda']
18 |
19 |
20 | # ====================================================================================== #
21 | def ea_mu_comma_lambda(toolbox: Toolbox, population: list,
22 | generations: int, offsprings: int,
23 | survivors: int, cx_prob: float,
24 | mut_prob: float, hof: Hof = None,
25 | stats: Stats = None, verbose: bool = False) -> AlgoResult:
26 | """
27 | An evolutionary algorithm. This function expects the *'mate'*, *'mutate'*,
28 | *'select'* and *'evaluate'* operators to be registered in the toolbox.
29 | The survivors are selected only from the offspring population.
30 |
31 | :param toolbox: A Toolbox which contains the evolution operators.
32 | :param population: A list of individuals to evolve.
33 | :param generations: The number of generations to compute.
34 | :param offsprings: The number of individuals to produce at each generation.
35 | :param survivors: The number of individuals to select from the offspring.
36 | :param cx_prob: The probability of mating two individuals.
37 | :param mut_prob: The probability of mutating an individual.
38 | :param hof: A HallOfFame or a ParetoFront object, optional.
39 | :param stats: A Statistics or a MultiStatistics object, optional.
40 | :param verbose: Whether to print debug messages, optional.
41 | :return: The final population and the logbook.
42 |
43 | :type hof: :ref:`Hof `
44 | :type stats: :ref:`Stats `
45 | :rtype: :ref:`AlgoResult `
46 | """
47 | if survivors > offsprings: # pragma: no cover
48 | offsprings, survivors = survivors, offsprings
49 |
50 | logbook = Logbook()
51 | logbook.header = ['gen', 'nevals'] + (stats.fields if stats else [])
52 |
53 | for gen in range(1, generations + 1):
54 | offspring = var_or(toolbox, population, offsprings, cx_prob, mut_prob)
55 |
56 | invalids = [ind for ind in offspring if not ind.fitness.is_valid()]
57 | fitness = toolbox.map(toolbox.evaluate, invalids)
58 | for ind, fit in zip(invalids, fitness):
59 | ind.fitness.values = fit
60 |
61 | population[:] = toolbox.select(offspring, survivors)
62 |
63 | if hof is not None:
64 | hof.update(offspring)
65 | record = stats.compile(population) if stats else {}
66 | logbook.record(gen=gen, nevals=len(invalids), **record)
67 | if verbose:
68 | print(logbook.stream)
69 |
70 | return population, logbook
71 |
--------------------------------------------------------------------------------
/examples/particle_swarm/numpy_pso.py:
--------------------------------------------------------------------------------
1 | from deap_er import creator
2 | from deap_er import tools
3 | from deap_er import base
4 | import random
5 | import numpy
6 | import math
7 |
8 |
9 | random.seed(1234) # disables randomization
10 |
11 | NGEN = 1000
12 |
13 |
14 | def generate(size, pmin, pmax, smin, smax):
15 | p_list = numpy.random.uniform(pmin, pmax, size)
16 | s_list = numpy.random.uniform(smin, smax, size)
17 | part = creator.Particle(p_list)
18 | part.speed = s_list
19 | part.smin = smin
20 | part.smax = smax
21 | return part
22 |
23 |
24 | def update(part, best, phi1, phi2):
25 | u1 = numpy.random.uniform(0, phi1, len(part))
26 | u2 = numpy.random.uniform(0, phi2, len(part))
27 | v_u1 = u1 * (part.best - part)
28 | v_u2 = u2 * (best - part)
29 | part.speed += v_u1 + v_u2
30 | for i, speed in enumerate(part.speed):
31 | if abs(speed) < part.smin:
32 | part.speed[i] = math.copysign(part.smin, speed)
33 | elif abs(speed) > part.smax:
34 | part.speed[i] = math.copysign(part.smax, speed)
35 | part += part.speed
36 |
37 |
38 | def setup():
39 | creator.create("FitnessMax", base.Fitness, weights=(1.0,))
40 | creator.create("Particle", numpy.ndarray, fitness=creator.FitnessMax,
41 | speed=list, smin=None, smax=None, best=None)
42 |
43 | toolbox = base.Toolbox()
44 | toolbox.register("particle", generate, size=2, pmin=-6, pmax=6, smin=-3, smax=3)
45 | toolbox.register("population", tools.init_repeat, list, toolbox.particle)
46 | toolbox.register("update", update, phi1=2.0, phi2=2.0)
47 | toolbox.register("evaluate", tools.bm_h1)
48 |
49 | stats = tools.Statistics(lambda ind: ind.fitness.values)
50 | stats.register("avg", numpy.mean)
51 | stats.register("std", numpy.std)
52 | stats.register("min", numpy.min)
53 | stats.register("max", numpy.max)
54 |
55 | logbook = tools.Logbook()
56 | logbook.header = ["gen", "evals"] + stats.fields
57 |
58 | return toolbox, stats, logbook
59 |
60 |
61 | def print_results(best_ind):
62 | if not best_ind.fitness.values <= (2,):
63 | raise RuntimeError('Evolution failed to converge.')
64 | print(f'\nEvolution converged correctly.')
65 |
66 |
67 | def main():
68 | toolbox, stats, logbook = setup()
69 | pop = toolbox.population(size=5)
70 |
71 | best = None
72 | for g in range(NGEN):
73 | for part in pop:
74 | part.fitness.values = toolbox.evaluate(part)
75 | if part.best is None or part.best.fitness < part.fitness:
76 | part.best = creator.Particle(part)
77 | part.best.fitness.values = part.fitness.values
78 | if best is None or best.fitness < part.fitness:
79 | best = creator.Particle(part)
80 | best.fitness.values = part.fitness.values
81 | for part in pop:
82 | toolbox.update(part, best)
83 | logbook.record(gen=g, evals=len(pop), **stats.compile(pop))
84 | print(logbook.stream)
85 |
86 | print_results(best)
87 |
88 |
89 | if __name__ == "__main__":
90 | main()
91 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "deap-er"
3 | version = "2.0.0"
4 | description = "Distributed Evolutionary Algorithms in Python - Entirely Reworked"
5 | authors = ["Mattias Aabmets "]
6 | license = "MIT License"
7 | readme = "README.md"
8 | packages = [
9 | { include = "deap_er" }
10 | ]
11 | keywords = [
12 | "deap",
13 | "evolution",
14 | "evolutionary algorithms",
15 | "genetic algorithms",
16 | "genetic programming",
17 | "symbolic regression",
18 | "hypervolume"
19 | ]
20 | classifiers = [
21 | "Development Status :: 5 - Production/Stable",
22 | "Intended Audience :: Developers",
23 | "Intended Audience :: Science/Research",
24 | "Topic :: Scientific/Engineering",
25 | "Topic :: Scientific/Engineering :: Artificial Intelligence",
26 | "Topic :: Scientific/Engineering :: Artificial Life",
27 | "License :: OSI Approved :: MIT License",
28 | "Programming Language :: Python :: 3.10",
29 | "Programming Language :: Python :: 3.11",
30 | "Programming Language :: Python :: 3.12",
31 | "Programming Language :: Python :: 3.13",
32 | "Operating System :: MacOS",
33 | "Operating System :: Microsoft :: Windows",
34 | "Operating System :: POSIX :: Linux",
35 | "Operating System :: OS Independent"
36 | ]
37 |
38 | [tool.poetry.urls]
39 | "Repository" = "https://github.com/aabmets/deap-er"
40 | "Documentation" = "https://deap-er.readthedocs.io"
41 |
42 | [tool.poetry.dependencies]
43 | python = "^3.10"
44 | numpy = "^2.0.1"
45 | scipy = "^1.14.0"
46 | dill = "^0.3.8"
47 | setuptools = "^72.1.0"
48 |
49 | [tool.poetry.group.dev]
50 | optional = true
51 |
52 | [tool.poetry.group.dev.dependencies]
53 | pytest = "^8.3.2"
54 | pytest-cov = "^5.0.0"
55 | pytest-xdist = {extras = ["psutil"], version = "^3.6.1"}
56 | coverage = "^7.6.1"
57 | flake8 = "^7.1.1"
58 | isort = "^5.13.2"
59 | mypy = "^1.11.1"
60 | black = "^24.8.0"
61 | Sphinx = "^7.4.7"
62 | sphinx-rtd-theme = "2.0.0"
63 | sphinx_design = "^0.6.1"
64 |
65 | [tool.pytest.ini_options]
66 | cache_dir = ".cache"
67 | console_output_style = "count"
68 | filterwarnings = ["ignore::DeprecationWarning"]
69 | testpaths = ["tests"]
70 | addopts = [
71 | "--cov=deap_er",
72 | "--cov-report=html",
73 | "--no-cov-on-fail",
74 | "--import-mode=append",
75 | "--numprocesses=auto",
76 | "--maxprocesses=8",
77 | "--dist=worksteal"
78 | ]
79 | pythonpath = [
80 | ".",
81 | "./deap_er"
82 | ]
83 |
84 | [tool.coverage.run]
85 | branch = true
86 | source = ["deap_er/*"]
87 |
88 | [tool.coverage.report]
89 | fail_under = 90
90 | skip_empty = true
91 | ignore_errors = true
92 | exclude_lines = [
93 | "pragma: no cover",
94 | "def __repr__",
95 | "raise AssertionError",
96 | "raise NotImplementedError",
97 | "if __name__ == .__main__.:",
98 | "@abstractmethod"
99 | ]
100 |
101 | [tool.coverage.html]
102 | directory = ".htmlcov"
103 |
104 | [tool.mypy]
105 | files = ["deap_er"]
106 |
107 | [build-system]
108 | requires = ["poetry-core"]
109 | build-backend = "poetry.core.masonry.api"
110 |
--------------------------------------------------------------------------------
/deap_er/utilities/initializers.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from collections.abc import Callable, Iterable
12 |
13 |
14 | __all__ = ['init_repeat', 'init_iterate', 'init_cycle']
15 |
16 |
17 | # ====================================================================================== #
18 | def init_repeat(container: Callable, func: Callable, size: int) -> Iterable:
19 | """
20 | Calls the **func** argument **count** times and puts the results
21 | into an instance of **container**. This helper function can be used
22 | in conjunction with a Toolbox to register a generator of filled
23 | containers, such as individuals or a population.
24 |
25 | :param container: A callable which takes an iterable as argument
26 | and returns a :external+python:py:class:`~collections.abc.Collection`.
27 | :param func: The function to be called count times.
28 | :param size: The number of times to call the func.
29 | :return: An iterable filled with count results of func.
30 | """
31 | return container(func() for _ in range(size))
32 |
33 |
34 | # -------------------------------------------------------------------------------------- #
35 | def init_iterate(container: Callable, generator: Callable) -> Iterable:
36 | """
37 | Calls the **generator** function and puts the results into an instance
38 | of **container**. The **generator** function should return an iterable.
39 | This helper function can be used in conjunction with a Toolbox to register
40 | a generator of filled containers, as individuals or a population.
41 |
42 | :param container: A callable which takes an iterable as argument
43 | and returns a :external+python:py:class:`~collections.abc.Collection`.
44 | :param generator: A function returning an iterable to fill the container with.
45 | :return: An iterable filled with the results of the generator.
46 | """
47 | return container(generator())
48 |
49 |
50 | # -------------------------------------------------------------------------------------- #
51 | def init_cycle(container: Callable, funcs: Iterable, size: int = 1) -> Iterable:
52 | """
53 | Calls each function in the **funcs** iterable **count** times and stores
54 | the results from all function calls into the **container**. This helper
55 | function can be used in conjunction with a Toolbox to register a generator
56 | of filled containers, as individuals or a population.
57 |
58 | :param container: A callable which takes an iterable as argument
59 | and returns a :external+python:py:class:`~collections.abc.Collection`.
60 | :param funcs: A sequence of functions to be called.
61 | :param size: Number of times to iterate through the sequence of functions.
62 | :return: An iterable filled with the results of all function calls.
63 | """
64 | return container(func() for _ in range(size) for func in funcs)
65 |
--------------------------------------------------------------------------------
/docs/source/tutorials/using_checkpoints.rst:
--------------------------------------------------------------------------------
1 | Using Checkpoints
2 | =================
3 |
4 | In this tutorial, we will describe how persistence can be achieved for evolution algorithms.
5 | This library has a helper class named :class:`~deap_er.persistence.checkpoint.Checkpoint`, which can
6 | be used to save the current state of an evolution algorithm to disk and restore it later to resume
7 | the computation. You can read more about the Checkpoint object from the :ref:`State Persistence
8 | ` section of the Reference Manual.
9 |
10 | Checkpoint objects use the `dill `_ library for object (de-)serialization,
11 | because it supports more Python types like lambdas, than the default :mod:`pickle` library. Checkpoints can
12 | be used either manually with the :func:`save()` and :func:`load()` methods or automatically with the custom
13 | :func:`range()` generator. The builtin algorithms don't implement automatic checkpointing due to their
14 | simplistic nature, but the user is able to implement manual checkpointing around them.
15 |
16 | In the following example, we will use the :func:`range()` generator to save the progress to disk every
17 | **save_freq** seconds. If one should wish to resume the computation later, they would only have to pass
18 | the name or path of the checkpoint file to the checkpoint constructor, as the data is automatically
19 | loaded from the disk on object initialization by default.
20 |
21 |
22 | .. code-block::
23 |
24 | from deap_er import base, creator, tools, env
25 |
26 | # setup() definition is omitted for brevity
27 |
28 | def main(file=None):
29 | toolbox, stats = setup()
30 | cp = env.Checkpoint(file)
31 | cp.save_freq = 10 # every 10 seconds
32 |
33 | if not cp.is_loaded(): # skip if loaded
34 | cp.pop = toolbox.population(size=300)
35 | cp.hof = tools.HallOfFame(maxsize=1)
36 | cp.log = tools.Logbook()
37 | fields = stats.fields if stats else []
38 | cp.log.header = ['gen', 'nevals'] + fields
39 |
40 | for gen in cp.range(1000):
41 | # evolve new offspring from parent pop
42 | offspring = tools.var_and(
43 | toolbox=toolbox,
44 | population=cp.pop,
45 | cx_prob=0.5,
46 | mut_prob=0.2
47 | )
48 | # update fitness values of individuals
49 | invalids = [ind for ind in offspring if not ind.fitness.is_valid()]
50 | fitness = toolbox.map(toolbox.evaluate, invalids)
51 | for ind, fit in zip(invalids, fitness):
52 | ind.fitness.values = fit
53 |
54 | # persist the hof, log and offspring
55 | cp.hof.update(offspring)
56 | record = stats.compile(offspring)
57 | cp.log.record(gen=gen, nevals=len(invalids), **record)
58 | cp.pop = toolbox.select(offspring, sel_count=len(offspring))
59 |
60 | # the range() generator persists the cp to disk
61 | # if saving conditions are fulfilled
62 |
63 | .. attention::
64 | Only those objects that are attributes of the checkpoint object will be saved to disk.
65 |
66 | .. raw:: html
67 |
68 |
69 |
--------------------------------------------------------------------------------
/tests/test_controllers/test_checkpoint.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from deap_er import env
12 | from pathlib import Path
13 | import time
14 | import os
15 |
16 |
17 | # ====================================================================================== #
18 | class TestCheckpoint:
19 |
20 | work_dir = Path(os.getcwd()).resolve().joinpath('qwerty')
21 |
22 | def test_file_name(self):
23 | _dir = self.work_dir
24 | cpt = env.Checkpoint(
25 | dir_path=_dir,
26 | autoload=False
27 | )
28 | assert cpt.file_path.suffix == '.dcpf'
29 | assert cpt.file_path.parent == _dir
30 |
31 | # -------------------------------------------------------- #
32 | def test_dir_path(self):
33 | _dir = self.work_dir
34 | cpt = env.Checkpoint(
35 | file_name='asdfg.cpt',
36 | autoload=False
37 | )
38 | assert cpt.file_path.name == 'asdfg.cpt'
39 | assert cpt.file_path.parent == _dir.with_name('deap-er')
40 |
41 | # -------------------------------------------------------- #
42 | def test_saving(self, tmp_path):
43 | cpt1 = env.Checkpoint(
44 | file_name='asdfg.cpt',
45 | dir_path=tmp_path,
46 | autoload=False
47 | )
48 | cpt1.my_dict = {'key': 'value'}
49 |
50 | assert not cpt1.file_path.exists()
51 | cpt1.save()
52 | assert cpt1.file_path.exists()
53 |
54 | cpt2 = env.Checkpoint(
55 | file_name='asdfg.cpt',
56 | dir_path=tmp_path,
57 | autoload=False
58 | )
59 |
60 | assert not hasattr(cpt2, 'my_dict')
61 | cpt2.load()
62 | assert getattr(cpt2, 'my_dict') == {'key': 'value'}
63 |
64 | # -------------------------------------------------------- #
65 | def test_range_1(self, tmp_path):
66 | cpt1 = env.Checkpoint(
67 | file_name='asdfg.cpt',
68 | dir_path=tmp_path,
69 | autoload=False
70 | )
71 | assert cpt1.last_op == 'none'
72 | for i in cpt1.range(5):
73 | assert 0 < i < 6
74 | assert cpt1.last_op == 'save_success'
75 |
76 | cpt2 = env.Checkpoint(
77 | file_name='asdfg.cpt',
78 | dir_path=tmp_path,
79 | autoload=True
80 | )
81 | assert cpt2.last_op == 'load_success'
82 | for i in cpt2.range(5):
83 | assert 5 < i < 11
84 | assert cpt2.last_op == 'save_success'
85 |
86 | # -------------------------------------------------------- #
87 | def test_range_2(self, tmp_path):
88 | cpt = env.Checkpoint(
89 | file_name='asdfg.cpt',
90 | dir_path=tmp_path,
91 | autoload=False
92 | )
93 | cpt.save_freq = 0.1
94 | assert cpt.last_op == 'none'
95 | for i in cpt.range(10):
96 | time.sleep(0.021)
97 | if i == 5:
98 | assert cpt.last_op == 'save_success'
99 |
--------------------------------------------------------------------------------
/deap_er/operators/selection/sel_helpers.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | import numpy
12 |
13 |
14 | __all__ = ['assign_crowding_dist', 'uniform_reference_points']
15 |
16 |
17 | # ====================================================================================== #
18 | def assign_crowding_dist(individuals: list) -> None:
19 | """
20 | Assigns a crowding distance to each individual's fitness.
21 | The crowding distance can be retrieved via the *crowding_dist*
22 | attribute of each individual's fitness. The individuals
23 | are modified in-place.
24 |
25 | :param individuals: A list of individuals with Fitness attributes.
26 | :return: Nothing.
27 | """
28 |
29 | if len(individuals) == 0:
30 | return
31 |
32 | distances = [0.0] * len(individuals)
33 | crowd = [(ind.fitness.values, i) for i, ind in enumerate(individuals)]
34 | n_obj = len(individuals[0].fitness.values)
35 |
36 | for i in range(n_obj):
37 | crowd.sort(key=lambda element: element[0][i])
38 | distances[crowd[0][1]] = float("inf")
39 | distances[crowd[-1][1]] = float("inf")
40 | if crowd[-1][0][i] == crowd[0][0][i]:
41 | continue
42 | norm = n_obj * float(crowd[-1][0][i] - crowd[0][0][i])
43 | for prev, cur, next_ in zip(crowd[:-2], crowd[1:-1], crowd[2:]):
44 | distances[cur[1]] += (next_[0][i] - prev[0][i]) / norm
45 |
46 | for i, dist in enumerate(distances):
47 | individuals[i].fitness.crowding_dist = dist
48 |
49 |
50 | # -------------------------------------------------------------------------------------- #
51 | def uniform_reference_points(objectives: int, ref_ppo: int = 4,
52 | scaling: float = None) -> numpy.ndarray:
53 | """
54 | Generates reference points uniformly on the hyperplane
55 | intersecting each axis at 1. The scaling factor is used
56 | to combine multiple layers of reference points.
57 |
58 | :param objectives: Number of objectives.
59 | :param ref_ppo: Number of reference points per objective, optional.
60 | :param scaling: Scaling factor, optional.
61 | :return: Uniform reference points.
62 | """
63 | def _recursive(ref, ovs, left, total, depth) -> list:
64 | points = []
65 | if depth == ovs - 1:
66 | ref[depth] = left / total
67 | points.append(ref)
68 | else:
69 | for i in range(left + 1):
70 | ref[depth] = i / total
71 | rc = ref.copy()
72 | li = left - i
73 | d1 = depth + 1
74 | result = _recursive(rc, ovs, li, total, d1)
75 | points.extend(result)
76 | return points
77 |
78 | zeros = numpy.zeros(objectives)
79 | ref_points = _recursive(zeros, objectives, ref_ppo, ref_ppo, 0)
80 | ref_points = numpy.array(ref_points)
81 |
82 | if scaling is not None:
83 | ref_points *= scaling
84 | ref_points += (1 - scaling) / objectives
85 |
86 | return ref_points
87 |
--------------------------------------------------------------------------------
/examples/particle_swarm/basic_pso.py:
--------------------------------------------------------------------------------
1 | from deap_er import creator
2 | from deap_er import tools
3 | from deap_er import base
4 | import operator
5 | import random
6 | import numpy
7 | import math
8 |
9 |
10 | random.seed(1234) # disables randomization
11 |
12 | NGEN = 1000
13 |
14 |
15 | def generate(size, pmin, pmax, smin, smax):
16 | p_list = [random.uniform(pmin, pmax) for _ in range(size)]
17 | s_list = [random.uniform(smin, smax) for _ in range(size)]
18 | part = creator.Particle(p_list)
19 | part.speed = s_list
20 | part.smin = smin
21 | part.smax = smax
22 | return part
23 |
24 |
25 | def update(part, best, phi1, phi2):
26 | u1 = (random.uniform(0, phi1) for _ in range(len(part)))
27 | u2 = (random.uniform(0, phi2) for _ in range(len(part)))
28 | v_u1 = map(operator.mul, u1, map(operator.sub, part.best, part))
29 | v_u2 = map(operator.mul, u2, map(operator.sub, best, part))
30 | part.speed = list(map(operator.add, part.speed, map(operator.add, v_u1, v_u2)))
31 | for i, speed in enumerate(part.speed):
32 | if abs(speed) < part.smin:
33 | part.speed[i] = math.copysign(part.smin, speed)
34 | elif abs(speed) > part.smax:
35 | part.speed[i] = math.copysign(part.smax, speed)
36 | part[:] = list(map(operator.add, part, part.speed))
37 |
38 |
39 | def setup():
40 | creator.create("FitnessMax", base.Fitness, weights=(1.0,))
41 | creator.create("Particle", list, fitness=creator.FitnessMax,
42 | speed=list, smin=None, smax=None, best=None)
43 |
44 | toolbox = base.Toolbox()
45 | toolbox.register("particle", generate, size=2, pmin=-6, pmax=6, smin=-3, smax=3)
46 | toolbox.register("population", tools.init_repeat, list, toolbox.particle)
47 | toolbox.register("update", update, phi1=2.0, phi2=2.0)
48 | toolbox.register("evaluate", tools.bm_h1)
49 |
50 | stats = tools.Statistics(lambda ind: ind.fitness.values)
51 | stats.register("avg", numpy.mean)
52 | stats.register("std", numpy.std)
53 | stats.register("min", numpy.min)
54 | stats.register("max", numpy.max)
55 |
56 | logbook = tools.Logbook()
57 | logbook.header = ["gen", "evals"] + stats.fields
58 |
59 | return toolbox, stats, logbook
60 |
61 |
62 | def print_results(best_ind):
63 | if not best_ind.fitness.values <= (2,):
64 | raise RuntimeError('Evolution failed to converge.')
65 | print(f'\nEvolution converged correctly.')
66 |
67 |
68 | def main():
69 | toolbox, stats, logbook = setup()
70 | pop = toolbox.population(size=5)
71 |
72 | best = None
73 | for generation in range(NGEN):
74 | for part in pop:
75 | part.fitness.values = toolbox.evaluate(part)
76 | if not part.best or part.best.fitness < part.fitness:
77 | part.best = creator.Particle(part)
78 | part.best.fitness.values = part.fitness.values
79 | if not best or best.fitness < part.fitness:
80 | best = creator.Particle(part)
81 | best.fitness.values = part.fitness.values
82 | for part in pop:
83 | toolbox.update(part, best)
84 | logbook.record(gen=generation, evals=len(pop), **stats.compile(pop))
85 | print(logbook.stream)
86 |
87 | print_results(best)
88 |
89 |
90 | if __name__ == "__main__":
91 | main()
92 |
--------------------------------------------------------------------------------
/examples/genetic_programming/symb_regr_numpy.py:
--------------------------------------------------------------------------------
1 | from deap_er import creator
2 | from deap_er import tools
3 | from deap_er import base
4 | from deap_er import gp
5 | import random
6 | import numpy
7 |
8 |
9 | random.seed(1234) # disables randomization
10 |
11 |
12 | def safe_div(left, right):
13 | with numpy.errstate(divide='ignore', invalid='ignore'):
14 | x = numpy.divide(left, right)
15 | if isinstance(x, numpy.ndarray):
16 | x[numpy.isinf(x)] = 1
17 | x[numpy.isnan(x)] = 1
18 | elif numpy.isinf(x) or numpy.isnan(x):
19 | x = 1
20 | return x
21 |
22 |
23 | def evaluate(individual, toolbox, samples, values):
24 | func = toolbox.compile(expr=individual)
25 | diff = numpy.sum((func(samples) - values)**2)
26 | return diff,
27 |
28 |
29 | def setup():
30 | samples = numpy.linspace(-1, 1, 10000)
31 | values = samples ** 4 + samples ** 3 + samples ** 2 + samples
32 |
33 | pset = gp.PrimitiveSet("MAIN", 1)
34 | pset.add_primitive(numpy.add, 2, name="vadd")
35 | pset.add_primitive(numpy.subtract, 2, name="vsub")
36 | pset.add_primitive(numpy.multiply, 2, name="vmul")
37 | pset.add_primitive(safe_div, 2)
38 | pset.add_primitive(numpy.negative, 1, name="vneg")
39 | pset.add_primitive(numpy.cos, 1, name="vcos")
40 | pset.add_primitive(numpy.sin, 1, name="vsin")
41 | pset.add_ephemeral_constant("rand101", lambda: random.randint(-1, 1))
42 | pset.rename_arguments(ARG0='x')
43 |
44 | creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
45 | creator.create("Individual", gp.PrimitiveTree, fitness=creator.FitnessMin)
46 |
47 | toolbox = base.Toolbox()
48 | toolbox.register("expr", gp.gen_half_and_half, prim_set=pset, min_depth=1, max_depth=2)
49 | toolbox.register("individual", tools.init_iterate, creator.Individual, toolbox.expr)
50 | toolbox.register("population", tools.init_repeat, list, toolbox.individual)
51 | toolbox.register("compile", gp.compile_tree, prim_set=pset)
52 | toolbox.register("evaluate", evaluate, toolbox=toolbox, samples=samples, values=values)
53 | toolbox.register("select", tools.sel_tournament, contestants=3)
54 | toolbox.register("mate", gp.cx_one_point)
55 | toolbox.register("expr_mut", gp.gen_full, min_depth=0, max_depth=2)
56 | toolbox.register("mutate", gp.mut_uniform, expr=toolbox.expr_mut, prim_set=pset)
57 |
58 | stats = tools.Statistics(lambda ind: ind.fitness.values)
59 | stats.register("avg", numpy.mean)
60 | stats.register("std", numpy.std)
61 | stats.register("min", numpy.min)
62 | stats.register("max", numpy.max)
63 |
64 | return toolbox, stats
65 |
66 |
67 | def print_results(best_ind):
68 | if not best_ind.fitness.values < (1.0e-3,):
69 | raise RuntimeError('Evolution failed to converge.')
70 | print(f'\nEvolution converged correctly.')
71 |
72 |
73 | def main():
74 | toolbox, stats = setup()
75 | pop = toolbox.population(size=300)
76 | hof = tools.HallOfFame(1)
77 | args = dict(
78 | toolbox=toolbox,
79 | population=pop,
80 | generations=40,
81 | cx_prob=0.5,
82 | mut_prob=0.1,
83 | hof=hof,
84 | stats=stats,
85 | verbose=True # prints stats
86 | )
87 | tools.ea_simple(**args)
88 | print_results(hof[0])
89 |
90 |
91 | if __name__ == "__main__":
92 | main()
93 |
--------------------------------------------------------------------------------
/examples/genetic_programming/symb_regr_eplex.py:
--------------------------------------------------------------------------------
1 | from deap_er import creator
2 | from deap_er import tools
3 | from deap_er import base
4 | from deap_er import gp
5 | import operator
6 | import random
7 | import numpy
8 | import math
9 |
10 |
11 | random.seed(1234) # disables randomization
12 |
13 |
14 | def safe_div(left, right):
15 | try:
16 | return left / right
17 | except ZeroDivisionError:
18 | return 1
19 |
20 |
21 | def evaluate(individual, toolbox, points):
22 | func = toolbox.compile(expr=individual)
23 | sq_errors = ((func(x) - x**4 - x**3 - x**2 - x)**2 for x in points)
24 | result = math.fsum(sq_errors) / len(points)
25 | return result, # The comma is essential here.
26 |
27 |
28 | def setup():
29 | pset = gp.PrimitiveSet("MAIN", 1)
30 | pset.add_primitive(operator.add, 2)
31 | pset.add_primitive(operator.sub, 2)
32 | pset.add_primitive(operator.mul, 2)
33 | pset.add_primitive(safe_div, 2)
34 | pset.add_primitive(operator.neg, 1)
35 | pset.add_primitive(math.cos, 1)
36 | pset.add_primitive(math.sin, 1)
37 | pset.add_ephemeral_constant("rand101", lambda: random.randint(-1, 1))
38 | pset.rename_arguments(ARG0='x')
39 |
40 | creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
41 | creator.create("Individual", gp.PrimitiveTree, fitness=creator.FitnessMin)
42 |
43 | toolbox = base.Toolbox()
44 | toolbox.register("expr", gp.gen_half_and_half, prim_set=pset, min_depth=1, max_depth=2)
45 | toolbox.register("individual", tools.init_iterate, creator.Individual, toolbox.expr)
46 | toolbox.register("population", tools.init_repeat, list, toolbox.individual)
47 | toolbox.register("compile", gp.compile_tree, prim_set=pset)
48 | toolbox.register("mate", gp.cx_one_point)
49 | toolbox.register("expr_mut", gp.gen_full, min_depth=0, max_depth=2)
50 | toolbox.register("mutate", gp.mut_uniform, expr=toolbox.expr_mut, prim_set=pset)
51 | toolbox.register("select", tools.sel_epsilon_lexicase)
52 | toolbox.register("evaluate", evaluate, toolbox=toolbox, points=[x / 10. for x in range(-10, 10)])
53 | toolbox.decorate("mate", gp.static_limit(limiter=operator.attrgetter("height"), max_value=17))
54 | toolbox.decorate("mutate", gp.static_limit(limiter=operator.attrgetter("height"), max_value=17))
55 |
56 | stats_size = tools.Statistics(len)
57 | stats_fit = tools.Statistics(lambda ind: ind.fitness.values)
58 | mstats = tools.MultiStatistics(fitness=stats_fit, size=stats_size)
59 | mstats.register("avg", numpy.mean)
60 | mstats.register("std", numpy.std)
61 | mstats.register("min", numpy.min)
62 | mstats.register("max", numpy.max)
63 |
64 | return toolbox, mstats
65 |
66 |
67 | def print_results(best_ind):
68 | if not best_ind.fitness.values < (1.0e-3,):
69 | raise RuntimeError('Evolution failed to converge.')
70 | print(f'\nEvolution converged correctly.')
71 |
72 |
73 | def main():
74 | toolbox, mstats = setup()
75 | pop = toolbox.population(size=300)
76 | hof = tools.HallOfFame(1)
77 | args = dict(
78 | toolbox=toolbox,
79 | population=pop,
80 | generations=40,
81 | cx_prob=0.5,
82 | mut_prob=0.1,
83 | hof=hof,
84 | stats=mstats,
85 | verbose=True # prints stats
86 | )
87 | tools.ea_simple(**args)
88 | print_results(hof[0])
89 |
90 |
91 | if __name__ == "__main__":
92 | main()
93 |
--------------------------------------------------------------------------------
/examples/genetic_programming/symb_regr_harm.py:
--------------------------------------------------------------------------------
1 | from deap_er import creator
2 | from deap_er import tools
3 | from deap_er import base
4 | from deap_er import gp
5 | import operator
6 | import random
7 | import numpy
8 | import math
9 |
10 |
11 | random.seed(1234) # disables randomization
12 |
13 |
14 | def safe_div(left, right):
15 | try:
16 | return left / right
17 | except ZeroDivisionError:
18 | return 1
19 |
20 |
21 | def evaluate(individual, points, toolbox):
22 | func = toolbox.compile(expr=individual)
23 | sq_errors = ((func(x) - x**4 - x**3 - x**2 - x)**2 for x in points)
24 | result = math.fsum(sq_errors) / len(points)
25 | return result, # The comma is essential here.
26 |
27 |
28 | def setup():
29 | pset = gp.PrimitiveSet("MAIN", 1)
30 | pset.add_primitive(operator.add, 2)
31 | pset.add_primitive(operator.sub, 2)
32 | pset.add_primitive(operator.mul, 2)
33 | pset.add_primitive(safe_div, 2)
34 | pset.add_primitive(operator.neg, 1)
35 | pset.add_primitive(math.cos, 1)
36 | pset.add_primitive(math.sin, 1)
37 | pset.add_ephemeral_constant("rand101", lambda: random.randint(-1, 1))
38 | pset.rename_arguments(ARG0='x')
39 |
40 | creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
41 | creator.create("Individual", gp.PrimitiveTree, fitness=creator.FitnessMin)
42 |
43 | toolbox = base.Toolbox()
44 | toolbox.register("expr", gp.gen_half_and_half, prim_set=pset, min_depth=1, max_depth=2)
45 | toolbox.register("individual", tools.init_iterate, creator.Individual, toolbox.expr)
46 | toolbox.register("population", tools.init_repeat, list, toolbox.individual)
47 | toolbox.register("compile", gp.compile_tree, prim_set=pset)
48 | toolbox.register("mate", gp.cx_one_point)
49 | toolbox.register("expr_mut", gp.gen_full, min_depth=0, max_depth=2)
50 | toolbox.register("mutate", gp.mut_uniform, expr=toolbox.expr_mut, prim_set=pset)
51 | toolbox.register("select", tools.sel_tournament, contestants=3)
52 | toolbox.register("evaluate", evaluate, points=[x / 10. for x in range(-10, 10)], toolbox=toolbox)
53 | toolbox.decorate("mate", gp.static_limit(limiter=operator.attrgetter("height"), max_value=17))
54 | toolbox.decorate("mutate", gp.static_limit(limiter=operator.attrgetter("height"), max_value=17))
55 |
56 | stats_size = tools.Statistics(len)
57 | stats_fit = tools.Statistics(lambda ind: ind.fitness.values)
58 | mstats = tools.MultiStatistics(fitness=stats_fit, size=stats_size)
59 | mstats.register("avg", numpy.mean)
60 | mstats.register("std", numpy.std)
61 | mstats.register("min", numpy.min)
62 | mstats.register("max", numpy.max)
63 |
64 | return toolbox, mstats
65 |
66 |
67 | def print_results(best_ind):
68 | if not best_ind.fitness.values < (1.0e-3,):
69 | raise RuntimeError('Evolution failed to converge.')
70 | print(f'\nEvolution converged correctly.')
71 |
72 |
73 | def main():
74 | toolbox, mstats = setup()
75 | pop = toolbox.population(size=300)
76 | hof = tools.HallOfFame(1)
77 | args = dict(
78 | toolbox=toolbox,
79 | population=pop,
80 | generations=40,
81 | cx_prob=0.5,
82 | mut_prob=0.1,
83 | hof=hof,
84 | stats=mstats,
85 | verbose=True # prints stats
86 | )
87 | gp.harm(**args)
88 | print_results(hof[0])
89 |
90 |
91 | if __name__ == "__main__":
92 | main()
93 |
--------------------------------------------------------------------------------
/examples/genetic_programming/symb_regr.py:
--------------------------------------------------------------------------------
1 | from deap_er import creator
2 | from deap_er import tools
3 | from deap_er import base
4 | from deap_er import gp
5 | import operator
6 | import random
7 | import numpy
8 | import math
9 |
10 |
11 | random.seed(1234) # disables randomization
12 |
13 |
14 | def safe_div(left, right):
15 | try:
16 | return left / right
17 | except ZeroDivisionError:
18 | return 1
19 |
20 |
21 | def evaluate(individual, toolbox, points):
22 | func = toolbox.compile(expr=individual)
23 | sq_errors = ((func(x) - x**4 - x**3 - x**2 - x)**2 for x in points)
24 | result = math.fsum(sq_errors) / len(points)
25 | return result, # The comma is essential here.
26 |
27 |
28 | def setup():
29 | pset = gp.PrimitiveSet("MAIN", 1)
30 | pset.add_primitive(operator.add, 2)
31 | pset.add_primitive(operator.sub, 2)
32 | pset.add_primitive(operator.mul, 2)
33 | pset.add_primitive(safe_div, 2)
34 | pset.add_primitive(operator.neg, 1)
35 | pset.add_primitive(math.cos, 1)
36 | pset.add_primitive(math.sin, 1)
37 | pset.add_ephemeral_constant("rand101", lambda: random.randint(-1, 1))
38 | pset.rename_arguments(ARG0='x')
39 |
40 | creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
41 | creator.create("Individual", gp.PrimitiveTree, fitness=creator.FitnessMin)
42 |
43 | toolbox = base.Toolbox()
44 | toolbox.register("expr", gp.gen_half_and_half, prim_set=pset, min_depth=1, max_depth=2)
45 | toolbox.register("individual", tools.init_iterate, creator.Individual, toolbox.expr)
46 | toolbox.register("population", tools.init_repeat, list, toolbox.individual)
47 | toolbox.register("compile", gp.compile_tree, prim_set=pset)
48 | toolbox.register("mate", gp.cx_one_point)
49 | toolbox.register("expr_mut", gp.gen_full, min_depth=0, max_depth=2)
50 | toolbox.register("mutate", gp.mut_uniform, expr=toolbox.expr_mut, prim_set=pset)
51 | toolbox.register("select", tools.sel_tournament, contestants=3)
52 | toolbox.register("evaluate", evaluate, toolbox=toolbox, points=[x / 10. for x in range(-10, 10)])
53 | toolbox.decorate("mate", gp.static_limit(limiter=operator.attrgetter("height"), max_value=17))
54 | toolbox.decorate("mutate", gp.static_limit(limiter=operator.attrgetter("height"), max_value=17))
55 |
56 | stats_size = tools.Statistics(len)
57 | stats_fit = tools.Statistics(lambda ind: ind.fitness.values)
58 | mstats = tools.MultiStatistics(fitness=stats_fit, size=stats_size)
59 | mstats.register("avg", numpy.mean)
60 | mstats.register("std", numpy.std)
61 | mstats.register("min", numpy.min)
62 | mstats.register("max", numpy.max)
63 |
64 | return toolbox, mstats
65 |
66 |
67 | def print_results(best_ind):
68 | if not best_ind.fitness.values < (1.0e-3,):
69 | raise RuntimeError('Evolution failed to converge.')
70 | print(f'\nEvolution converged correctly.')
71 |
72 |
73 | def main():
74 | toolbox, mstats = setup()
75 | pop = toolbox.population(size=300)
76 | hof = tools.HallOfFame(1)
77 | args = dict(
78 | toolbox=toolbox,
79 | population=pop,
80 | generations=40,
81 | cx_prob=0.5,
82 | mut_prob=0.1,
83 | hof=hof,
84 | stats=mstats,
85 | verbose=True # prints stats
86 | )
87 | tools.ea_simple(**args)
88 | print_results(hof[0])
89 |
90 |
91 | if __name__ == "__main__":
92 | main()
93 |
--------------------------------------------------------------------------------
/examples/evolution_strategies/mo_cma_strat.py:
--------------------------------------------------------------------------------
1 | from deap_er import creator
2 | from deap_er import tools
3 | from deap_er import base
4 | import numpy
5 |
6 |
7 | numpy.random.seed(1234) # disables randomization
8 |
9 |
10 | SIZE = 5
11 | MU, LAMBDA = 10, 10
12 | MIN_BOUND = numpy.zeros(SIZE)
13 | MAX_BOUND = numpy.ones(SIZE)
14 | EPS_BOUND = 2.e-5
15 | NGEN = 500
16 |
17 |
18 | def validity(individual):
19 | if any(individual < MIN_BOUND) or any(individual > MAX_BOUND):
20 | return False
21 | return True
22 |
23 |
24 | def feasible(individual):
25 | feasible_ind = numpy.array(individual)
26 | feasible_ind = numpy.maximum(MIN_BOUND, feasible_ind)
27 | feasible_ind = numpy.minimum(MAX_BOUND, feasible_ind)
28 | return feasible_ind
29 |
30 |
31 | def distance(feasible_ind, original_ind):
32 | return sum((f - o)**2 for f, o in zip(feasible_ind, original_ind))
33 |
34 |
35 | def setup():
36 | creator.create("FitnessMin", base.Fitness, weights=(-1.0, -1.0))
37 | creator.create("Individual", list, fitness=creator.FitnessMin)
38 |
39 | toolbox = base.Toolbox()
40 | toolbox.register("evaluate", tools.bm_zdt_1)
41 | toolbox.decorate("evaluate", tools.ClosestValidPenalty(validity, feasible, 1.0e+6, distance))
42 |
43 | pop = [creator.Individual(x) for x in (numpy.random.uniform(0, 1, (MU, SIZE)))]
44 | for ind in pop:
45 | ind.fitness.values = toolbox.evaluate(ind)
46 | strategy = tools.StrategyMultiObjective(
47 | population=pop,
48 | sigma=1.0,
49 | offsprings=LAMBDA,
50 | survivors=MU
51 | )
52 | toolbox.register("generate", strategy.generate, creator.Individual)
53 | toolbox.register("update", strategy.update)
54 |
55 | stats = tools.Statistics(lambda x: x.fitness.values)
56 | stats.register("min", numpy.min, axis=0)
57 | stats.register("max", numpy.max, axis=0)
58 |
59 | logbook = tools.Logbook()
60 | logbook.header = ["gen", "nevals"] + (stats.fields if stats else [])
61 |
62 | return toolbox, strategy, stats, logbook
63 |
64 |
65 | def print_results(valid, parents):
66 | hv = tools.hypervolume(parents, [11.0, 11.0])
67 | if not hv > 110 and valid != len(parents):
68 | raise RuntimeError('Evolution failed to converge.')
69 | print(f"\nNumber of valid individuals is {valid}/{len(parents)}"
70 | f" with a hypervolume of {hv:.2f}.")
71 | print(f'Evolution converged correctly.')
72 |
73 |
74 | def main():
75 | toolbox, strategy, stats, logbook = setup()
76 |
77 | fitness_history = []
78 | for gen in range(NGEN):
79 | population = toolbox.generate()
80 | fitness = toolbox.map(toolbox.evaluate, population)
81 |
82 | for ind, fit in zip(population, fitness):
83 | ind.fitness.values = fit
84 | fitness_history.append(fit)
85 |
86 | toolbox.update(population)
87 | record = stats.compile(population) if stats is not None else {}
88 | logbook.record(gen=gen, nevals=len(population), **record)
89 | print(logbook.stream)
90 |
91 | num_valid = 0
92 | for ind in strategy.parents:
93 | dist = distance(feasible(ind), ind)
94 | if numpy.isclose(dist, 0.0, rtol=1.e-5, atol=1.e-5):
95 | num_valid += 1
96 |
97 | print_results(num_valid, strategy.parents)
98 |
99 |
100 | if __name__ == "__main__":
101 | main()
102 |
--------------------------------------------------------------------------------
/examples/genetic_programming/even_parity.py:
--------------------------------------------------------------------------------
1 | from deap_er import creator
2 | from deap_er import tools
3 | from deap_er import base
4 | from deap_er import gp
5 | import operator
6 | import random
7 | import numpy
8 |
9 |
10 | random.seed(1234) # disables randomization
11 |
12 |
13 | PARITY_FANIN_M = 6
14 | PARITY_SIZE_M = 2 ** PARITY_FANIN_M
15 |
16 | inputs: list = [None] * PARITY_SIZE_M
17 | outputs: list = [None] * PARITY_SIZE_M
18 |
19 |
20 | def fill_inputs_outputs():
21 | for i in range(PARITY_SIZE_M):
22 | inputs[i] = [None] * PARITY_FANIN_M
23 | value = i
24 | dividor = PARITY_SIZE_M
25 | parity = 1
26 | for j in range(PARITY_FANIN_M):
27 | dividor /= 2
28 | if value >= dividor:
29 | inputs[i][j] = 1
30 | parity = int(not parity)
31 | value -= dividor
32 | else:
33 | inputs[i][j] = 0
34 | outputs[i] = parity
35 |
36 |
37 | def evaluate(individual, toolbox):
38 | func = toolbox.compile(expr=individual)
39 | result = sum(func(*in_) == out for in_, out in zip(inputs, outputs))
40 | return result, # The comma is essential here.
41 |
42 |
43 | def setup():
44 | pset = gp.PrimitiveSet("MAIN", PARITY_FANIN_M, "IN")
45 | pset.add_primitive(operator.and_, 2)
46 | pset.add_primitive(operator.or_, 2)
47 | pset.add_primitive(operator.xor, 2)
48 | pset.add_primitive(operator.not_, 1)
49 | pset.add_terminal(1)
50 | pset.add_terminal(0)
51 |
52 | creator.create("FitnessMax", base.Fitness, weights=(1.0,))
53 | creator.create("Individual", gp.PrimitiveTree, fitness=creator.FitnessMax)
54 |
55 | toolbox = base.Toolbox()
56 | toolbox.register("expr", gp.gen_full, prim_set=pset, min_depth=3, max_depth=5)
57 | toolbox.register("individual", tools.init_iterate, creator.Individual, toolbox.expr)
58 | toolbox.register("population", tools.init_repeat, list, toolbox.individual)
59 | toolbox.register("compile", gp.compile_tree, prim_set=pset)
60 | toolbox.register("evaluate", evaluate, toolbox=toolbox)
61 | toolbox.register("select", tools.sel_tournament, contestants=3)
62 | toolbox.register("mate", gp.cx_one_point)
63 | toolbox.register("expr_mut", gp.gen_grow, min_depth=0, max_depth=2)
64 | toolbox.register("mutate", gp.mut_uniform, expr=toolbox.expr_mut, prim_set=pset)
65 |
66 | stats = tools.Statistics(lambda ind: ind.fitness.values)
67 | stats.register("avg", numpy.mean)
68 | stats.register("std", numpy.std)
69 | stats.register("min", numpy.min)
70 | stats.register("max", numpy.max)
71 |
72 | return toolbox, stats
73 |
74 |
75 | def print_results(best_ind):
76 | if not best_ind.fitness.values == (64,):
77 | raise RuntimeError('Evolution failed to converge.')
78 | print(f'\nEvolution converged correctly.')
79 |
80 |
81 | def main():
82 | fill_inputs_outputs()
83 | toolbox, stats = setup()
84 | pop = toolbox.population(size=300)
85 | hof = tools.HallOfFame(1)
86 | args = dict(
87 | toolbox=toolbox,
88 | population=pop,
89 | generations=40,
90 | cx_prob=0.5,
91 | mut_prob=0.1,
92 | hof=hof,
93 | stats=stats,
94 | verbose=True
95 | )
96 | tools.ea_simple(**args)
97 | print_results(hof[0])
98 |
99 |
100 | if __name__ == "__main__":
101 | main()
102 |
--------------------------------------------------------------------------------
/deap_er/operators/selection/sel_lexicase.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | import numpy as np
12 | import random
13 |
14 |
15 | __all__ = ['sel_lexicase', 'sel_epsilon_lexicase']
16 |
17 |
18 | # ====================================================================================== #
19 | def sel_lexicase(individuals: list, sel_count: int) -> list:
20 | """
21 | Returns an individual that does the best on the fitness
22 | cases when considered one at a time in random order.
23 |
24 | :param individuals: A list of individuals to select from.
25 | :param sel_count: The number of individuals to select.
26 | :return: A list of selected individuals.
27 | """
28 | selected = []
29 | for i in range(sel_count):
30 | fit_weights = individuals[0].fitness.weights
31 | candidates = individuals
32 | cases = list(range(len(individuals[0].fitness.values)))
33 | random.shuffle(cases)
34 | while len(cases) > 0 and len(candidates) > 1:
35 | fn = min
36 | if fit_weights[cases[0]] > 0:
37 | fn = max
38 | f_vals = [x.fitness.values[cases[0]] for x in candidates]
39 | best_val = fn(f_vals)
40 | candidates = [x for x in candidates if x.fitness.values[cases[0]] == best_val]
41 | cases.pop(0)
42 | choice = random.choice(candidates)
43 | selected.append(choice)
44 | return selected
45 |
46 |
47 | # -------------------------------------------------------------------------------------- #
48 | def sel_epsilon_lexicase(individuals: list, sel_count: int,
49 | epsilon: float = None) -> list:
50 | """
51 | Returns an individual that does the best on the fitness
52 | cases when considered one at a time in random order.
53 |
54 | :param individuals: A list of individuals to select from.
55 | :param sel_count: The number of individuals to select.
56 | :param epsilon: The epsilon parameter, optional. If not
57 | provided, the epsilon parameter is automatically
58 | calculated from the median of fitness values.
59 | :return: A list of selected individuals.
60 | """
61 | selected = []
62 | for i in range(sel_count):
63 | fit_weights = individuals[0].fitness.weights
64 | cases = list(range(len(individuals[0].fitness.values)))
65 | random.shuffle(cases)
66 | candidates = individuals
67 | while len(cases) > 0 and len(candidates) > 1:
68 | errors = [x.fitness.values[cases[0]] for x in candidates]
69 | if not epsilon:
70 | median = np.median(errors)
71 | epsilon = np.median([abs(x - median) for x in errors])
72 | if fit_weights[cases[0]] > 0:
73 | best_val = max(errors)
74 | min_val = best_val - epsilon
75 | candidates = [x for x in candidates if x.fitness.values[cases[0]] >= min_val]
76 | else:
77 | best_val = min(errors)
78 | max_val = best_val + epsilon
79 | candidates = [x for x in candidates if x.fitness.values[cases[0]] <= max_val]
80 | cases.pop(0)
81 | choice = random.choice(candidates)
82 | selected.append(choice)
83 | return selected
84 |
--------------------------------------------------------------------------------
/tests/test_creator/test_overrides.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from deap_er.creator import overrides as ovr
12 | from copy import deepcopy
13 | import pickle
14 | import array
15 | import numpy
16 |
17 |
18 | # ====================================================================================== #
19 | class TestNumpyOverrideClass:
20 |
21 | def test_numpy_override_instantiation(self):
22 | data = [x for x in range(0, 10)]
23 | obj = ovr._NumpyOverride(data)
24 | assert isinstance(obj, ovr._NumpyOverride)
25 | assert issubclass(ovr._NumpyOverride, numpy.ndarray)
26 |
27 | # -------------------------------------------------------- #
28 | def test_numpy_override_deepcopy(self):
29 | data = [x for x in range(0, 10)]
30 | obj = ovr._NumpyOverride(data)
31 | copy = deepcopy(obj)
32 | assert isinstance(copy, numpy.ndarray)
33 | assert all(obj == copy)
34 | assert obj.__dict__ == copy.__dict__
35 |
36 | # -------------------------------------------------------- #
37 | def test_numpy_override_pickling(self):
38 | data = [x for x in range(0, 10)]
39 | obj = ovr._NumpyOverride(data)
40 | jar = pickle.dumps(obj)
41 | copy = pickle.loads(jar)
42 | assert all(obj == copy)
43 | assert obj.__dict__ == copy.__dict__
44 |
45 | # -------------------------------------------------------- #
46 | def test_array_override_reduction(self):
47 | data = [x for x in range(0, 10)]
48 | obj = ovr._NumpyOverride(data)
49 | cls, args, state = obj.__reduce__()
50 | assert cls == ovr._NumpyOverride
51 | assert isinstance(args, tuple)
52 | assert isinstance(state, dict)
53 |
54 |
55 | # ====================================================================================== #
56 | class TestArrayOverrideClass:
57 | ovr._ArrayOverride.typecode = 'i'
58 |
59 | def test_array_override_instantiation(self):
60 | data = [x for x in range(0, 10)]
61 | obj = ovr._ArrayOverride(data)
62 | assert isinstance(obj, ovr._ArrayOverride)
63 | assert issubclass(ovr._ArrayOverride, array.array)
64 |
65 | # -------------------------------------------------------- #
66 | def test_array_override_deepcopy(self):
67 | data = [x for x in range(0, 10)]
68 | obj = ovr._ArrayOverride(data)
69 | copy = deepcopy(obj)
70 | assert isinstance(copy, array.array)
71 | assert obj == copy
72 | assert obj.__dict__ == copy.__dict__
73 |
74 | # -------------------------------------------------------- #
75 | def test_array_override_pickling(self):
76 | data = [x for x in range(0, 10)]
77 | obj = ovr._ArrayOverride(data)
78 | jar = pickle.dumps(obj)
79 | copy = pickle.loads(jar)
80 | assert obj == copy
81 | assert obj.__dict__ == copy.__dict__
82 |
83 | # -------------------------------------------------------- #
84 | def test_array_override_reduction(self):
85 | data = [x for x in range(0, 10)]
86 | obj = ovr._ArrayOverride(data)
87 | cls, args, state = obj.__reduce__()
88 | assert cls == ovr._ArrayOverride
89 | assert isinstance(args, tuple)
90 | assert isinstance(state, dict)
91 |
--------------------------------------------------------------------------------
/deap_er/base/toolbox.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from .lint_hints import LintHints
12 | from typing import Callable, Optional
13 | from functools import partial
14 | from copy import deepcopy
15 |
16 |
17 | __all__ = ['Toolbox']
18 |
19 |
20 | # ====================================================================================== #
21 | class Toolbox(LintHints):
22 | """
23 | A container for evolutionary operators. Toolboxes are essential
24 | components which facilitate the process of computational evolution.
25 | """
26 | # -------------------------------------------------------- #
27 | def __init__(self):
28 | self.register("clone", deepcopy)
29 | self.register("map", map)
30 |
31 | # -------------------------------------------------------- #
32 | def register(self, alias: str, func: Callable,
33 | *args: Optional, **kwargs: Optional) -> None:
34 | """
35 | Registers a **func** in the toolbox under the name **alias**.
36 | Any **args** or **kwargs** will be automatically passed to the
37 | registered function when it's called. Fixed arguments can
38 | be overridden at function call time.
39 |
40 | :param alias: The name to register the 'func' under.
41 | The alias will be overwritten if it already exists.
42 | :param func: The function to which the alias is going to refer.
43 | :param args: Positional arguments which are automatically
44 | passed to the 'func' when it's called, optional.
45 | :param kwargs: Keyword arguments which are automatically
46 | passed to the 'func' when it's called, optional.
47 | :return: Nothing.
48 | """
49 | p_func = partial(func, *args, **kwargs)
50 | p_func.__name__ = alias
51 | p_func.__doc__ = func.__doc__
52 |
53 | if hasattr(func, '__dict__') and not isinstance(func, type):
54 | p_func.__dict__.update(func.__dict__.copy())
55 | setattr(self, alias, p_func)
56 |
57 | # -------------------------------------------------------- #
58 | def unregister(self, alias: str) -> None:
59 | """
60 | Removes an operator with the name **alias** from the toolbox.
61 |
62 | :param alias: The name of the operator to remove from the toolbox.
63 | :return: Nothing.
64 | """
65 | delattr(self, alias)
66 |
67 | # -------------------------------------------------------- #
68 | def decorate(self, alias: str,
69 | *decorators: Optional[Callable]) -> None:
70 | """
71 | Decorates an operator **alias** with the provided **decorators**.
72 |
73 | :param alias: Name of the operator to decorate. The 'alias'
74 | must be a registered operator in the toolbox.
75 | :param decorators: Positional arguments of decorator functions
76 | to apply to the 'alias', optional. If none are provided,
77 | the operator is left unchanged. If multiple are provided,
78 | they are applied in order of iteration over the 'decorators'.
79 | """
80 | if not decorators:
81 | return
82 | p_func = getattr(self, alias)
83 | func = p_func.func
84 | args = p_func.args
85 | kwargs = p_func.keywords
86 | for decorator in decorators:
87 | func = decorator(func)
88 | self.register(alias, func, *args, **kwargs)
89 |
--------------------------------------------------------------------------------
/deap_er/utilities/hypervolume/multi_list.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from collections.abc import Iterable, MutableSequence
12 | from .node import Node
13 |
14 |
15 | # ====================================================================================== #
16 | class MultiList:
17 | """
18 | A special data structure needed by the Fonseca HyperVolume indicator.
19 | It consists of several doubly linked lists that share common nodes.
20 | Every node has multiple predecessors and successors, one in every list.
21 |
22 | :param dimensions: The number of dimensions in the multi-list.
23 | """
24 | # -------------------------------------------------------- #
25 | def __init__(self, dimensions: int) -> None:
26 | self.dimensions = dimensions
27 | self.sentinel = Node(dimensions)
28 | self.sentinel.next = [self.sentinel] * dimensions
29 | self.sentinel.prev = [self.sentinel] * dimensions
30 |
31 | # -------------------------------------------------------- #
32 | def __str__(self) -> str:
33 | strings = list()
34 | for i in range(self.dimensions):
35 | current_list = list()
36 | node = self.sentinel.next[i]
37 | while node != self.sentinel:
38 | current_list.append(str(node))
39 | node = node.next[i]
40 | strings.append(str(current_list))
41 | _repr = str()
42 | for string in strings:
43 | _repr += string + "\n"
44 | return _repr
45 |
46 | # -------------------------------------------------------- #
47 | def __len__(self):
48 | return self.dimensions
49 |
50 | # -------------------------------------------------------- #
51 | def get_length(self, index: int) -> int:
52 | length = 0
53 | node = self.sentinel.next[index]
54 | while node != self.sentinel:
55 | node = node.next[index]
56 | length += 1
57 | return length
58 |
59 | # -------------------------------------------------------- #
60 | def append(self, node: Node, index: int) -> None:
61 | penultimate = self.sentinel.prev[index]
62 | node.next[index] = self.sentinel
63 | node.prev[index] = penultimate
64 | self.sentinel.prev[index] = node
65 | penultimate.next[index] = node
66 |
67 | # -------------------------------------------------------- #
68 | def extend(self, nodes: Iterable[Node], index: int) -> None:
69 | for node in nodes:
70 | penultimate = self.sentinel.prev[index]
71 | node.next[index] = self.sentinel
72 | node.prev[index] = penultimate
73 | self.sentinel.prev[index] = node
74 | penultimate.next[index] = node
75 |
76 | # -------------------------------------------------------- #
77 | @staticmethod
78 | def remove(node: Node, index: int, bounds: MutableSequence) -> Node:
79 | for i in range(index):
80 | predecessor = node.prev[i]
81 | successor = node.next[i]
82 | predecessor.next[i] = successor
83 | successor.prev[i] = predecessor
84 | if bounds[i] > node.cargo[i]:
85 | bounds[i] = node.cargo[i]
86 | return node
87 |
88 | # -------------------------------------------------------- #
89 | @staticmethod
90 | def reinsert(node: Node, index: int, bounds: MutableSequence) -> None:
91 | for i in range(index):
92 | node.prev[i].next[i] = node
93 | node.next[i].prev[i] = node
94 | if bounds[i] > node.cargo[i]:
95 | bounds[i] = node.cargo[i]
96 |
--------------------------------------------------------------------------------
/examples/genetic_algorithms/knapsack.py:
--------------------------------------------------------------------------------
1 | from deap_er import creator
2 | from deap_er import tools
3 | from deap_er import base
4 | import string
5 | import random
6 |
7 |
8 | random.seed(1234) # disables randomization
9 |
10 | IND_INIT_SIZE = 5
11 | MAX_ITEM = 50
12 | MAX_WEIGHT = 50
13 | NBR_ITEMS = 20
14 | NAME_LEN = 3
15 |
16 | items = dict()
17 |
18 |
19 | def create_items():
20 | alphabet = list(string.ascii_uppercase)
21 | for _ in range(NBR_ITEMS):
22 | while True:
23 | name = ''.join(random.choice(alphabet) for _ in range(NAME_LEN))
24 | if name not in items:
25 | break
26 | weight = random.randint(1, 10)
27 | value = random.uniform(0, 100)
28 | items.update(
29 | {name: (weight, value)}
30 | )
31 |
32 |
33 | def evaluate(individual: set) -> tuple[int, int]:
34 | if len(individual) <= MAX_ITEM:
35 | _weight, _value = 0, 0
36 | for item in individual:
37 | _weight += items[item][0]
38 | _value += items[item][1]
39 | if _weight <= MAX_WEIGHT:
40 | return _weight, _value
41 | return 10000, 0
42 |
43 |
44 | def mate(ind1: set, ind2: set) -> tuple[set, set]:
45 | temp = set(ind1)
46 | ind1 &= ind2
47 | ind2 ^= temp
48 | return ind1, ind2
49 |
50 |
51 | def mutate(individual: set) -> tuple[set]:
52 | if random.random() < 0.5:
53 | if len(individual) > 0:
54 | items_ = sorted(tuple(individual))
55 | choice = random.choice(items_)
56 | individual.remove(choice)
57 | else:
58 | names = list(items.keys())
59 | individual.add(random.choice(names))
60 | return individual, # The comma is essential here.
61 |
62 |
63 | def setup():
64 | creator.create("Fitness", base.Fitness, weights=(-1.0, 1.0))
65 | creator.create("Individual", set, fitness=creator.Fitness)
66 |
67 | toolbox = base.Toolbox()
68 | toolbox.register("attr_item", random.choice, list(items.keys()))
69 | toolbox.register("individual", tools.init_repeat, creator.Individual, toolbox.attr_item, IND_INIT_SIZE)
70 | toolbox.register("population", tools.init_repeat, list, toolbox.individual)
71 | toolbox.register("mate", mate)
72 | toolbox.register("mutate", mutate)
73 | toolbox.register("select", tools.sel_nsga_2)
74 | toolbox.register("evaluate", evaluate)
75 |
76 | return toolbox
77 |
78 |
79 | def print_results(hof):
80 | best_ind = sorted(list(hof[-1]))
81 | best_weight, best_value = 0, 0
82 | for idx in best_ind:
83 | best_weight += items[idx][0]
84 | best_value += round(items[idx][1], 2)
85 |
86 | keys = sorted(list(items.keys()))
87 | for key in tuple(keys):
88 | if key not in best_ind:
89 | keys.remove(key)
90 | keys.append(key)
91 |
92 | print('\nAvailable items to choose from:')
93 | print('Names:\t\t' + '\t\t'.join([str(k) for k in keys]))
94 | print('Weights:\t' + '\t\t'.join([str(items[k][0]) for k in keys]))
95 | print('Values:\t\t' + '\t'.join([str(round(items[k][1], 2)) for k in keys]))
96 | print(f'\nItems chosen: {best_ind}')
97 | print(f'Total weight of chosen items: {best_weight}')
98 | print(f'Total value of chosen items: {best_value:.3f}.')
99 |
100 |
101 | def main():
102 | create_items()
103 | toolbox = setup()
104 | pop = toolbox.population(size=100)
105 | hof = tools.ParetoFront()
106 | args = dict(
107 | toolbox=toolbox,
108 | population=pop,
109 | generations=50,
110 | offsprings=100,
111 | survivors=50,
112 | cx_prob=0.5,
113 | mut_prob=0.2,
114 | hof=hof
115 | )
116 | tools.ea_mu_plus_lambda(**args)
117 | print_results(hof)
118 |
119 |
120 | if __name__ == "__main__":
121 | main()
122 |
--------------------------------------------------------------------------------
/deap_er/algorithms/variation.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from deap_er.base import Toolbox
12 | import random
13 |
14 |
15 | __all__ = ['var_and', 'var_or']
16 |
17 |
18 | # ====================================================================================== #
19 | def var_and(toolbox: Toolbox, population: list,
20 | cx_prob: float, mut_prob: float) -> list:
21 | """
22 | A subcomponent for evolutionary algorithms, which mates AND
23 | mutates each individual in the given population according to the
24 | given probabilities. Each of the two probabilities must be in
25 | the range of [0, 1]. The returned population is independent of
26 | the input population and has their fitness invalidated.
27 |
28 | :param toolbox: A Toolbox which contains the evolution operators.
29 | :param population: A list of individuals to evolve.
30 | :param cx_prob: The probability of mating two individuals.
31 | :param mut_prob: The probability of mutating an individual.
32 | :return: A list of evolved individuals.
33 | """
34 | err = "The {0} probability must be in the range of [0, 1]."
35 | if not (0 <= cx_prob <= 1):
36 | raise ValueError(err.format("crossover"))
37 | if not (0 <= mut_prob <= 1):
38 | raise ValueError(err.format("mutation"))
39 |
40 | offspring = [toolbox.clone(ind) for ind in population]
41 |
42 | for i in range(1, len(offspring), 2):
43 | if random.random() < cx_prob:
44 | offspring[i - 1], offspring[i] = toolbox.mate(offspring[i - 1], offspring[i])
45 | del offspring[i - 1].fitness.values, offspring[i].fitness.values
46 |
47 | for i in range(len(offspring)):
48 | if random.random() < mut_prob:
49 | offspring[i], = toolbox.mutate(offspring[i]) # don't remove the comma!
50 | del offspring[i].fitness.values
51 |
52 | return offspring
53 |
54 |
55 | # -------------------------------------------------------------------------------------- #
56 | def var_or(toolbox: Toolbox, population: list, offsprings: int,
57 | cx_prob: float, mut_prob: float) -> list:
58 | """
59 | A subcomponent for evolutionary algorithms, which mates OR
60 | mutates each individual in the given population according to the
61 | given probabilities. The sum of the two probabilities must be in
62 | the range of [0, 1]. The returned population is independent of
63 | the input population and has their fitness invalidated.
64 |
65 | :param toolbox: A Toolbox which contains the evolution operators.
66 | :param population: A list of individuals to evolve.
67 | :param offsprings: The number of individuals to produce.
68 | :param cx_prob: The probability of mating two individuals.
69 | :param mut_prob: The probability of mutating an individual.
70 | :return: A list of evolved individuals.
71 | """
72 | evolve_prob = cx_prob + mut_prob
73 | if evolve_prob > 1.0:
74 | raise ValueError(
75 | "The sum of the crossover and the mutation "
76 | "probabilities must be in the range of [0, 1]."
77 | )
78 |
79 | offspring = []
80 | for _ in range(offsprings):
81 | op_choice = random.random()
82 | if op_choice < cx_prob:
83 | ind1, ind2 = map(toolbox.clone, random.sample(population, 2))
84 | ind1, ind2 = toolbox.mate(ind1, ind2)
85 | del ind1.fitness.values
86 | offspring.append(ind1)
87 | elif op_choice < evolve_prob:
88 | ind = toolbox.clone(random.choice(population))
89 | ind, = toolbox.mutate(ind) # don't remove the comma!
90 | del ind.fitness.values
91 | offspring.append(ind)
92 | else:
93 | offspring.append(random.choice(population))
94 |
95 | return offspring
96 |
--------------------------------------------------------------------------------
/deap_er/gp/semantic.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from .dtypes import *
12 | from .primitives import *
13 | from .generators import gen_grow
14 | from typing import Callable
15 | import random
16 |
17 |
18 | __all__ = ['mut_semantic', 'cx_semantic']
19 |
20 |
21 | # ====================================================================================== #
22 | def mut_semantic(individual: list, prim_set: PrimitiveSetTyped,
23 | min_depth: int = 2, max_depth: int = 6, gen_func: Callable = None,
24 | mut_step: float = None) -> tuple[list]:
25 | """
26 | Perform a semantic mutation on the given individual.
27 |
28 | :param individual: The individual to be mutated.
29 | :param prim_set: Primitive set from which primitives are selected.
30 | :param gen_func: The function which generates the random tree.
31 | :param mut_step: The mutation step.
32 | :param min_depth: Minimum depth of the random tree.
33 | :param max_depth: Maximum depth of the random tree.
34 | :return: The mutated individual.
35 | """
36 | _check(prim_set, 'mutation')
37 |
38 | if gen_func is None:
39 | gen_func = gen_grow
40 |
41 | if mut_step is None:
42 | mut_step = random.uniform(0, 2)
43 |
44 | tr1 = gen_func(prim_set, min_depth, max_depth)
45 | tr2 = gen_func(prim_set, min_depth, max_depth)
46 |
47 | tr1.insert(0, prim_set.mapping['lf'])
48 | tr2.insert(0, prim_set.mapping['lf'])
49 |
50 | new_ind = individual
51 | new_ind.insert(0, prim_set.mapping["add"])
52 | new_ind.append(prim_set.mapping["mul"])
53 |
54 | mutation_step = Terminal(mut_step, False, object)
55 | new_ind.append(mutation_step)
56 | new_ind.append(prim_set.mapping["sub"])
57 |
58 | new_ind.extend(tr1)
59 | new_ind.extend(tr2)
60 |
61 | return new_ind,
62 |
63 |
64 | # -------------------------------------------------------------------------------------- #
65 | def cx_semantic(ind1: list, ind2: list,
66 | prim_set: PrimitiveSetTyped, min_depth: int = 2,
67 | max_depth: int = 6, gen_func: Callable = gen_grow) -> tuple[list, list]:
68 | """
69 | Perform a semantic crossover on the given individuals.
70 |
71 | :param ind1: The first individual to be mated.
72 | :param ind2: The second individual to be mated.
73 | :param prim_set: Primitive set from which primitives are selected.
74 | :param gen_func: The function which generates the random tree.
75 | :param min_depth: Minimum depth of the random tree.
76 | :param max_depth: Maximum depth of the random tree.
77 | :return: Two mated individuals.
78 | """
79 | _check(prim_set, 'crossover')
80 |
81 | tr = gen_func(prim_set, min_depth, max_depth)
82 | tr.insert(0, prim_set.mapping['lf'])
83 |
84 | def create_ind(ind, ind_ext):
85 | new_ind = ind
86 | new_ind.insert(0, prim_set.mapping["mul"])
87 | new_ind.insert(0, prim_set.mapping["add"])
88 | new_ind.extend(tr)
89 | new_ind.append(prim_set.mapping["mul"])
90 | new_ind.append(prim_set.mapping["sub"])
91 | new_ind.append(Terminal(1.0, False, object))
92 | new_ind.extend(tr)
93 | new_ind.extend(ind_ext)
94 | return new_ind
95 |
96 | new_ind1 = create_ind(ind1, ind2)
97 | new_ind2 = create_ind(ind2, ind1)
98 | return new_ind1, new_ind2
99 |
100 |
101 | # -------------------------------------------------------------------------------------- #
102 | def _check(p_set: PrimitiveSetTyped, op: str) -> None:
103 | for func in ['lf', 'mul', 'add', 'sub']:
104 | if func not in p_set.mapping:
105 | raise TypeError(
106 | f'A \'{func}\' function is required to perform semantic \'{op}\'.'
107 | )
108 |
--------------------------------------------------------------------------------
/deap_er/utilities/metrics.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from deap_er.base.dtypes import *
12 | from typing import Optional, Any
13 | from math import hypot, sqrt
14 | import numpy
15 |
16 |
17 | # ====================================================================================== #
18 | def nsga_diversity(population: list, first: Individual, last: Individual) -> float:
19 | """
20 | Given a Pareto front **population** and the two extreme points **first** and
21 | **last** of the optimal Pareto front, this function returns the diversity metric
22 | of the **population** as explained in the original NSGA-II article by K. Deb.
23 | Smaller values indicate better solutions.
24 |
25 | :param population: The Pareto front to be evaluated.
26 | :param first: The first extreme point of the optimal Pareto front.
27 | :param last: The last extreme point of the optimal Pareto front.
28 | :return: The diversity metric of the front.
29 |
30 | :type first: :ref:`Individual `
31 | :type last: :ref:`Individual `
32 | """
33 | df = hypot(
34 | population[0].fitness.values[0] - first[0],
35 | population[0].fitness.values[1] - first[1]
36 | )
37 | dl = hypot(
38 | population[-1].fitness.values[0] - last[0],
39 | population[-1].fitness.values[1] - last[1]
40 | )
41 |
42 | def fn(f_, s_):
43 | return hypot(
44 | f_.fitness.values[0] - s_.fitness.values[0],
45 | f_.fitness.values[1] - s_.fitness.values[1]
46 | )
47 | zipper = zip(population[:-1], population[1:])
48 | dt = [fn(first, second) for first, second in zipper]
49 |
50 | if len(population) == 1:
51 | return df + dl
52 |
53 | dm = sum(dt)/len(dt)
54 | di = sum(abs(d_i - dm) for d_i in dt)
55 | delta = (df + dl + di) / (df + dl + len(dt) * dm)
56 | return delta
57 |
58 |
59 | # -------------------------------------------------------------------------------------- #
60 | def nsga_convergence(population: list, optimal: list) -> float:
61 | """
62 | Given a Pareto **front** and the **optimal** Pareto front, this function
63 | returns the convergence metric of the front as explained in the original
64 | NSGA-II article by K. Deb. Smaller values indicate more optimal solutions.
65 |
66 | :param population: The Pareto front to be evaluated.
67 | :param optimal: The optimal Pareto front.
68 | :return: The convergence metric of the front.
69 | """
70 | distances = []
71 | for ind in population:
72 | distances.append(float("inf"))
73 | for opt_ind in optimal:
74 | dist = 0.
75 | for i in range(len(opt_ind)):
76 | dist += (ind.fitness.values[i] - opt_ind[i])**2
77 | if dist < distances[-1]:
78 | distances[-1] = dist
79 | distances[-1] = sqrt(distances[-1])
80 | return sum(distances) / len(distances)
81 |
82 |
83 | # -------------------------------------------------------------------------------------- #
84 | def inv_gen_dist(ind1: Individual, ind2: Individual) -> tuple[Any, Optional[Any]]:
85 | """
86 | Computes the Inverted Generational Distance (IGD) between the two individuals.
87 | The IGD is a metric for assessing the quality of approximations to the
88 | Pareto front obtained by multi-objective optimization algorithms.
89 |
90 | :param ind1: The first individual.
91 | :param ind2: The second individual.
92 | :return: The IGD between the two individuals.
93 |
94 | :type ind1: :ref:`Individual `
95 | :type ind2: :ref:`Individual `
96 | """
97 | from scipy import spatial
98 | distances = spatial.distance.cdist(ind1, ind2)
99 | minima = numpy.min(distances, axis=0)
100 | return numpy.average(minima)
101 |
--------------------------------------------------------------------------------
/deap_er/gp/crossover.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from .dtypes import *
12 | from collections import defaultdict
13 | from functools import partial
14 | from operator import eq, lt
15 | import random
16 |
17 |
18 | __all__ = ['cx_one_point', 'cx_one_point_leaf_biased']
19 |
20 |
21 | # ====================================================================================== #
22 | def cx_one_point(ind1: GPIndividual, ind2: GPIndividual) -> GPMates:
23 | """
24 | Randomly selects a crossover point in each individual and exchanges
25 | each subtree with the point as the root between each individual.
26 |
27 | :param ind1: The first individual to mate.
28 | :param ind2: The second individual to mate.
29 | :return: Two mated individuals.
30 |
31 | :type ind1: :ref:`GPIndividual `
32 | :type ind2: :ref:`GPIndividual `
33 | :rtype: :ref:`GPMates `
34 | """
35 | if len(ind1) < 2 or len(ind2) < 2:
36 | return ind1, ind2
37 |
38 | types1 = defaultdict(list)
39 | types2 = defaultdict(list)
40 | if ind1.root.ret == object:
41 | types1[object] = list(range(1, len(ind1)))
42 | types2[object] = list(range(1, len(ind2)))
43 | common_types = [object]
44 | else:
45 | for idx, node in enumerate(ind1[1:], 1):
46 | types1[node.ret].append(idx)
47 | for idx, node in enumerate(ind2[1:], 1):
48 | types2[node.ret].append(idx)
49 | common_types = set(types1.keys()).intersection(set(types2.keys()))
50 |
51 | if len(common_types) > 0:
52 | type_ = random.choice(list(common_types))
53 |
54 | index1 = random.choice(types1[type_])
55 | index2 = random.choice(types2[type_])
56 | slice1 = ind1.search_subtree(index1)
57 | slice2 = ind2.search_subtree(index2)
58 | ind1[slice1], ind2[slice2] = ind2[slice2], ind1[slice1]
59 |
60 | return ind1, ind2
61 |
62 |
63 | # -------------------------------------------------------------------------------------- #
64 | def cx_one_point_leaf_biased(ind1: GPIndividual, ind2: GPIndividual,
65 | term_prob: float) -> GPMates:
66 | """
67 | Randomly selects a crossover point in each individual and exchanges
68 | each subtree with the point as the root between each individual.
69 |
70 | :param ind1: The first individual to mate.
71 | :param ind2: The second individual to mate.
72 | :param term_prob: The probability of selecting
73 | a terminal node as the crossover point.
74 | :return: Two mated individuals.
75 |
76 | :type ind1: :ref:`GPIndividual `
77 | :type ind2: :ref:`GPIndividual `
78 | :rtype: :ref:`GPMates `
79 | """
80 | if len(ind1) < 2 or len(ind2) < 2:
81 | return ind1, ind2
82 |
83 | terminal_op = partial(eq, 0)
84 | primitive_op = partial(lt, 0)
85 | arity_op1 = terminal_op if random.random() < term_prob else primitive_op
86 | arity_op2 = terminal_op if random.random() < term_prob else primitive_op
87 |
88 | types1 = defaultdict(list)
89 | types2 = defaultdict(list)
90 |
91 | for idx, node in enumerate(ind1[1:], 1):
92 | if arity_op1(node.arity):
93 | types1[node.ret].append(idx)
94 |
95 | for idx, node in enumerate(ind2[1:], 1):
96 | if arity_op2(node.arity):
97 | types2[node.ret].append(idx)
98 |
99 | common_types = set(types1.keys()).intersection(set(types2.keys()))
100 |
101 | if len(common_types) > 0:
102 | type_ = random.sample(common_types, 1)[0]
103 |
104 | index1 = random.choice(types1[type_])
105 | index2 = random.choice(types2[type_])
106 | slice1 = ind1.search_subtree(index1)
107 | slice2 = ind2.search_subtree(index2)
108 | ind1[slice1], ind2[slice2] = ind2[slice2], ind1[slice1]
109 |
110 | return ind1, ind2
111 |
--------------------------------------------------------------------------------
/deap_er/records/history.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from deap_er.base.dtypes import *
12 | from typing import Callable
13 | from copy import deepcopy
14 |
15 |
16 | __all__ = ['History']
17 |
18 |
19 | # ====================================================================================== #
20 | class History:
21 | """
22 | Maintains a history of the individuals produced in the evolution.
23 | """
24 | # -------------------------------------------------------- #
25 | def __init__(self):
26 | self.genealogy_index = int()
27 | self.genealogy_history = dict()
28 | self.genealogy_tree = dict()
29 |
30 | # -------------------------------------------------------- #
31 | @property
32 | def decorator(self) -> Callable:
33 | """
34 | A decorator that adds genealogy history to the individuals.
35 | """
36 | def wrapper(func):
37 | def wrapped(*args, **kwargs):
38 | individuals = func(*args, **kwargs)
39 | self.update(individuals)
40 | return individuals
41 | return wrapped
42 | return wrapper
43 |
44 | # -------------------------------------------------------- #
45 | def update(self, individuals: list) -> None:
46 | """
47 | Update the genealogy history with the given **individuals**.
48 | This method should be called with the initial population
49 | to initialize the history and also after each variation.
50 |
51 | :param individuals: The individuals to update the genealogy history with.
52 | :return: Nothing.
53 | """
54 | try:
55 | parent_indices = tuple(ind.history_index for ind in individuals)
56 | except AttributeError:
57 | parent_indices = tuple()
58 |
59 | for ind in individuals:
60 | self.genealogy_index += 1
61 | ind.history_index = self.genealogy_index
62 | self.genealogy_history[self.genealogy_index] = deepcopy(ind)
63 | self.genealogy_tree[self.genealogy_index] = parent_indices
64 |
65 | # -------------------------------------------------------- #
66 | def get_genealogy(self, individual: Individual, max_depth: float = float("inf")) -> dict:
67 | """
68 | Get the genealogy of the given **individual**. The individual must have the
69 | *'history_index'* attribute which is set by the *'update'* method in order
70 | to retrieve its associated genealogy tree. The returned graph contains
71 | the parents up to **max_depth** variations before this individual. The
72 | default value of **max_depth** is up to the beginning of the evolution.
73 |
74 | :param individual: The individual at the root of the genealogy tree.
75 | :param max_depth: The maximum depth of the genealogy tree.
76 | :return: A dictionary where each key is an individual index and the
77 | values are tuples corresponding to the index of the parents.
78 |
79 | :type individual: :ref:`Individual `
80 | """
81 | def _recursive(index, depth):
82 | if index not in self.genealogy_tree:
83 | return
84 | depth += 1
85 | if depth > max_depth:
86 | return
87 | parent_indices = self.genealogy_tree[index]
88 | gtree[index] = parent_indices
89 | for ind in parent_indices:
90 | if ind not in visited:
91 | _recursive(ind, depth)
92 | visited.add(ind)
93 |
94 | if hasattr(individual, 'history_index'):
95 | visited = set()
96 | gtree = dict()
97 | _recursive(individual.history_index, 0)
98 | return gtree
99 | else:
100 | raise AttributeError(
101 | "The individual must have the 'history_index' attribute."
102 | )
103 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # poetry
98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99 | # This is especially recommended for binary packages to ensure reproducibility, and is more
100 | # commonly ignored for libraries.
101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102 | #poetry.lock
103 |
104 | # pdm
105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106 | #pdm.lock
107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108 | # in version control.
109 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
110 | .pdm.toml
111 | .pdm-python
112 | .pdm-build/
113 |
114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
115 | __pypackages__/
116 |
117 | # Celery stuff
118 | celerybeat-schedule
119 | celerybeat.pid
120 |
121 | # SageMath parsed files
122 | *.sage.py
123 |
124 | # Environments
125 | .env
126 | .venv
127 | env/
128 | venv/
129 | ENV/
130 | env.bak/
131 | venv.bak/
132 |
133 | # Spyder project settings
134 | .spyderproject
135 | .spyproject
136 |
137 | # Rope project settings
138 | .ropeproject
139 |
140 | # mkdocs documentation
141 | /site
142 |
143 | # mypy
144 | .mypy_cache/
145 | .dmypy.json
146 | dmypy.json
147 |
148 | # Pyre type checker
149 | .pyre/
150 |
151 | # pytype static type analyzer
152 | .pytype/
153 |
154 | # Cython debug symbols
155 | cython_debug/
156 |
157 | # PyCharm
158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
160 | # and can be added to the global gitignore or merged into this file. For a more nuclear
161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
162 | #.idea/
163 |
164 | # User-specific stuff
165 | .idea/**/workspace.xml
166 | .idea/**/tasks.xml
167 | .idea/**/usage.statistics.xml
168 | .idea/**/dictionaries
169 | .idea/**/shelf
170 |
171 | # AWS User-specific
172 | .idea/**/aws.xml
173 |
174 | # Generated files
175 | .idea/**/contentModel.xml
176 |
177 | # Sensitive or high-churn files
178 | .idea/**/dataSources/
179 | .idea/**/dataSources.ids
180 | .idea/**/dataSources.local.xml
181 | .idea/**/sqlDataSources.xml
182 | .idea/**/dynamic.xml
183 | .idea/**/uiDesigner.xml
184 | .idea/**/dbnavigator.xml
185 |
186 | # Others
187 | .exclude
188 | .htmlcov
189 | .deap
190 |
--------------------------------------------------------------------------------
/docs/source/_static/custom_style.py:
--------------------------------------------------------------------------------
1 | """
2 | pygments.styles.material
3 | ~~~~~~~~~~~~~~~~~~~~~~~~
4 |
5 | Mimic the Material theme color scheme.
6 |
7 | https://github.com/material-theme/vsc-material-theme
8 |
9 | :copyright: Copyright 2006-2022 by the Pygments team, see AUTHORS.
10 | :license: BSD, see LICENSE for details.
11 | """
12 |
13 | from pygments.style import Style
14 | from pygments.token import Keyword, Name, Comment, String, Escape, \
15 | Error, Text, Number, Operator, Generic, Punctuation, Literal
16 |
17 |
18 | class CustomStyle(Style):
19 | """
20 | This style mimics the Material Theme color scheme.
21 | """
22 | dark_teal = '#263238'
23 | white = '#FFFFFF'
24 | black = '#000000'
25 | red = '#FF5370'
26 | orange = '#F78C6C'
27 | yellow = '#FFCE29'
28 | green = '#A6CA6F'
29 | cyan = '#89DDFF'
30 | blue = '#82AAFF'
31 | paleblue = '#B2CCD6'
32 | purple = '#C792EA'
33 | brown = '#C17E70'
34 | pink = '#F07178'
35 | violet = '#BB80B3'
36 | foreground = '#EEEEEE'
37 | faded = '#728F9E'
38 |
39 | default_style = ""
40 | background_color = dark_teal
41 | highlight_color = '#3B4E57'
42 | line_number_color = faded
43 | line_number_background_color = dark_teal
44 | line_number_special_color = faded
45 | line_number_special_background_color = dark_teal
46 |
47 | styles = {
48 | Text: foreground,
49 | Escape: cyan,
50 | Error: red,
51 |
52 | Keyword: violet,
53 | Keyword.Constant: cyan,
54 | Keyword.Declaration: violet,
55 | Keyword.Namespace: 'italic ' + cyan,
56 | Keyword.Pseudo: cyan,
57 | Keyword.Type: violet,
58 |
59 | Name: foreground,
60 | Name.Attribute: violet,
61 | Name.Builtin: blue,
62 | Name.Builtin.Pseudo: cyan,
63 | Name.Class: yellow,
64 | Name.Constant: foreground,
65 | Name.Decorator: blue,
66 | Name.Entity: cyan,
67 | Name.Exception: yellow,
68 | Name.Function: blue,
69 | Name.Function.Magic: blue,
70 | Name.Label: blue,
71 | Name.Property: yellow,
72 | Name.Namespace: yellow,
73 | Name.Other: foreground,
74 | Name.Tag: red,
75 | Name.Variable: cyan,
76 | Name.Variable.Class: cyan,
77 | Name.Variable.Global: cyan,
78 | Name.Variable.Instance: cyan,
79 | Name.Variable.Magic: blue,
80 |
81 | Literal: green,
82 | Literal.Date: green,
83 |
84 | String: green,
85 | String.Affix: violet,
86 | String.Backtick: green,
87 | String.Char: green,
88 | String.Delimiter: foreground,
89 | String.Doc: 'italic ' + faded,
90 | String.Double: green,
91 | String.Escape: orange,
92 | String.Heredoc: green,
93 | String.Interpol: cyan,
94 | String.Other: green,
95 | String.Regex: cyan,
96 | String.Single: green,
97 | String.Symbol: cyan,
98 |
99 | Number: orange,
100 |
101 | Operator: cyan,
102 | Operator.Word: 'italic ' + cyan,
103 |
104 | Punctuation: cyan,
105 |
106 | Comment: 'italic ' + faded,
107 |
108 | Generic: foreground,
109 | Generic.Deleted: red,
110 | Generic.Emph: cyan,
111 | Generic.Error: red,
112 | Generic.Heading: green,
113 | Generic.Inserted: green,
114 | Generic.Output: faded,
115 | Generic.Prompt: yellow,
116 | Generic.Strong: red,
117 | Generic.Subheading: cyan,
118 | Generic.Traceback: red,
119 | }
120 |
--------------------------------------------------------------------------------
/examples/genetic_algorithms/sn_evolution.py:
--------------------------------------------------------------------------------
1 | from deap_er import creator
2 | from deap_er import tools
3 | from deap_er import base
4 | import random
5 | import numpy
6 |
7 |
8 | random.seed(1234) # disables randomization
9 |
10 | INPUTS = 6
11 | NGEN = 100
12 | CX_PROB = 0.5
13 | MUT_PROB = 0.2
14 | ADD_PROB = 0.01
15 | DEL_PROB = 0.01
16 |
17 |
18 | def eval_network(individual, dimension):
19 | network = tools.SortingNetwork(dimension, individual)
20 | return network.evaluate(), network.length, network.depth
21 |
22 |
23 | def gen_wire(dimension):
24 | wire1 = random.randrange(dimension)
25 | wire2 = random.randrange(dimension)
26 | return wire1, wire2
27 |
28 |
29 | def gen_network(dimension, min_size, max_size):
30 | size = random.randint(min_size, max_size)
31 | network = [gen_wire(dimension) for _ in range(size)]
32 | return network
33 |
34 |
35 | def mut_wire(individual, dimension, mut_prob):
36 | for index, elem in enumerate(individual):
37 | if random.random() < mut_prob:
38 | individual[index] = gen_wire(dimension)
39 |
40 |
41 | def mut_add_wire(individual, dimension):
42 | index = random.randint(0, len(individual))
43 | individual.insert(index, gen_wire(dimension))
44 |
45 |
46 | def mut_del_wire(individual):
47 | index = random.randrange(len(individual))
48 | del individual[index]
49 |
50 |
51 | def setup():
52 | creator.create("FitnessMin", base.Fitness, weights=(-1.0, -1.0, -1.0))
53 | creator.create("Individual", list, fitness=creator.FitnessMin)
54 |
55 | toolbox = base.Toolbox()
56 | toolbox.register("network", gen_network, dimension=INPUTS, min_size=9, max_size=12)
57 | toolbox.register("individual", tools.init_iterate, creator.Individual, toolbox.network)
58 | toolbox.register("population", tools.init_repeat, list, toolbox.individual)
59 |
60 | toolbox.register("evaluate", eval_network, dimension=INPUTS)
61 | toolbox.register("mate", tools.cx_two_point)
62 | toolbox.register("mutate", mut_wire, dimension=INPUTS, mut_prob=0.05)
63 | toolbox.register("addwire", mut_add_wire, dimension=INPUTS)
64 | toolbox.register("delwire", mut_del_wire)
65 | toolbox.register("select", tools.sel_nsga_2)
66 |
67 | stats = tools.Statistics(lambda ind: ind.fitness.values)
68 | stats.register("avg", numpy.mean, axis=0)
69 | stats.register("std", numpy.std, axis=0)
70 | stats.register("min", numpy.min, axis=0)
71 | stats.register("max", numpy.max, axis=0)
72 |
73 | logbook = tools.Logbook()
74 | logbook.header = "gen", "evals", "std", "min", "avg", "max"
75 |
76 | return toolbox, stats, logbook
77 |
78 |
79 | def print_results(best_network):
80 | print('\nBest sorting network schematic:')
81 | print(best_network.draw())
82 | print(f'\nEvolution converged correctly.')
83 |
84 |
85 | def main():
86 | toolbox, stats, logbook = setup()
87 | population = toolbox.population(size=300)
88 | hof = tools.ParetoFront()
89 |
90 | def log_stats(ngen=0):
91 | hof.update(population)
92 | record = stats.compile(population)
93 | logbook.record(gen=ngen, evals=len(population), **record)
94 | print(logbook.stream)
95 |
96 | fitness = toolbox.map(toolbox.evaluate, population)
97 | for ind, fit in zip(population, fitness):
98 | ind.fitness.values = fit
99 |
100 | log_stats()
101 |
102 | for generation in range(1, NGEN):
103 | offspring = [toolbox.clone(ind) for ind in population]
104 |
105 | for ind1, ind2 in zip(offspring[::2], offspring[1::2]):
106 | if random.random() < CX_PROB:
107 | toolbox.mate(ind1, ind2)
108 | del ind1.fitness.values
109 | del ind2.fitness.values
110 |
111 | for ind in offspring:
112 | if random.random() < MUT_PROB:
113 | toolbox.mutate(ind)
114 | del ind.fitness.values
115 | if random.random() < ADD_PROB:
116 | toolbox.addwire(ind)
117 | del ind.fitness.values
118 | if random.random() < DEL_PROB:
119 | toolbox.delwire(ind)
120 | del ind.fitness.values
121 |
122 | invalid_ind = [ind for ind in offspring if not ind.fitness.is_valid()]
123 | fitness = toolbox.map(toolbox.evaluate, invalid_ind)
124 | for ind, fit in zip(invalid_ind, fitness):
125 | ind.fitness.values = fit
126 |
127 | population = toolbox.select(population+offspring, len(offspring))
128 |
129 | log_stats(generation)
130 |
131 | best_network = tools.SortingNetwork(INPUTS, hof[0])
132 | print_results(best_network)
133 |
134 |
135 | if __name__ == "__main__":
136 | main()
137 |
--------------------------------------------------------------------------------
/tests/test_utilities/test_hypervolume.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from deap_er.utilities.hypervolume import HyperVolume
12 | from deap_er.utilities.hypervolume.node import Node
13 | import numpy
14 |
15 |
16 | # ====================================================================================== #
17 | class TestHyperVolume:
18 |
19 | def test_1(self):
20 | front = [(a, a) for a in numpy.arange(1, 0, -0.01)]
21 | front = numpy.array(front)
22 | ref = numpy.array([2, 2])
23 | hv = HyperVolume(ref)
24 | result = hv.compute(front)
25 | assert result == 3.9601000000000033
26 |
27 | # -------------------------------------------------------------------------------------- #
28 | def test_2(self):
29 | front = [(a, a) for a in numpy.arange(2, 0, -0.2)]
30 | front = numpy.array(front)
31 | ref = numpy.array([3, 3])
32 | hv = HyperVolume(ref)
33 | result = hv.compute(front)
34 | assert result == 7.839999999999998
35 |
36 | # -------------------------------------------------------------------------------------- #
37 | def test_3(self):
38 | front = [(a, a, a) for a in numpy.arange(3, 0, -0.03)]
39 | front = numpy.array(front)
40 | ref = numpy.array([4, 5, 6])
41 | hv = HyperVolume(ref)
42 | result = hv.compute(front)
43 | assert result == 117.7934729999985
44 |
45 | # -------------------------------------------------------------------------------------- #
46 | def test_4(self):
47 | front = [(a, a, a) for a in numpy.arange(4, 0, -0.4)]
48 | front = numpy.array(front)
49 | ref = numpy.array([4, 5, 6])
50 | hv = HyperVolume(ref)
51 | result = hv.compute(front)
52 | assert result == 92.73599999999996
53 |
54 | # -------------------------------------------------------------------------------------- #
55 | def test_5(self):
56 | front = [(a, a, a, a) for a in numpy.arange(5, 0, -0.567)]
57 | front = numpy.array(front)
58 | ref = numpy.array([9, 2, 7, 4])
59 | hv = HyperVolume(ref)
60 | result = hv.compute(front)
61 | assert result == 303.0190427996165
62 |
63 | # -------------------------------------------------------------------------------------- #
64 | def test_6(self):
65 | front = [(a, a, a, a) for a in numpy.arange(10, 0, -0.5)]
66 | front = numpy.array(front)
67 | ref = numpy.array([1])
68 | hv = HyperVolume(ref)
69 | result = hv.compute(front)
70 | assert result == 0.5
71 |
72 | # -------------------------------------------------------------------------------------- #
73 | def test_7(self):
74 | front = numpy.array([])
75 | ref = numpy.array([])
76 | hv = HyperVolume(ref)
77 | result = hv.compute(front)
78 | assert result == 0.0
79 |
80 |
81 | # ====================================================================================== #
82 | class TestNode:
83 |
84 | def test_1(self):
85 | n1 = Node(1)
86 | n2 = Node(1)
87 | assert not n1 == n2
88 | assert not n1 != n2
89 |
90 | # -------------------------------------------------------------------------------------- #
91 | def test_2(self):
92 | n1 = Node(1, (1, 2, 3))
93 | n2 = Node(1, (1, 2, 3))
94 | assert n1 == n2
95 | assert n1 >= n2
96 | assert n1 <= n2
97 |
98 | # -------------------------------------------------------------------------------------- #
99 | def test_3(self):
100 | n1 = Node(1, (1, 2, 3))
101 | n2 = Node(1, (2, 3, 4))
102 | assert n1 != n2
103 | assert n1 < n2
104 | assert n2 > n1
105 |
106 | # -------------------------------------------------------------------------------------- #
107 | def test_4(self):
108 | n1 = Node(1, (1, 2, 3))
109 | n2 = Node(1, (9, 2, 3))
110 | assert n1 <= n2
111 | assert n2 >= n1
112 |
113 | # -------------------------------------------------------------------------------------- #
114 | def test_5(self):
115 | n = Node(1, (1, 2, 3, 4, 5))
116 | assert str(n) == '(1, 2, 3, 4, 5)'
117 |
118 | # -------------------------------------------------------------------------------------- #
119 | def test_6(self):
120 | data = (1, 2, 3)
121 | n = Node(1, data)
122 | assert hash(n) == hash(data)
123 |
--------------------------------------------------------------------------------
/deap_er/records/statistics.py:
--------------------------------------------------------------------------------
1 | #
2 | # MIT License
3 | #
4 | # Copyright (c) 2022, Mattias Aabmets
5 | #
6 | # The contents of this file are subject to the terms and conditions defined in the License.
7 | # You may not use, modify, or distribute this file except in compliance with the License.
8 | #
9 | # SPDX-License-Identifier: MIT
10 | #
11 | from typing import Callable, Optional, Iterable
12 | from functools import partial
13 |
14 |
15 | __all__ = ['Statistics', 'MultiStatistics']
16 |
17 |
18 | # ====================================================================================== #
19 | class Statistics:
20 | """
21 | Object that compiles statistics on a list of arbitrary objects.
22 | When created, the statistics object receives a **key** argument that
23 | is used to get the values on which the statistics will be computed.
24 | If not provided, the **key** argument defaults to the identity function.
25 |
26 | The value returned by the key may be a multidimensional object, i.e.:
27 | a tuple or a list, as long as the registered statistical function
28 | supports it. For example, statistics can be computed directly on
29 | multi-objective fitness when using numpy statistical function.
30 |
31 | :param key: A function that takes an object and returns a
32 | value on which the statistics will be computed.
33 | """
34 | # -------------------------------------------------------- #
35 | def __init__(self, key: Optional[Callable] = None):
36 | self.key = key if key else lambda obj: obj
37 | self.functions = dict()
38 | self.fields = list()
39 |
40 | # -------------------------------------------------------- #
41 | def register(self, name: str, func: Callable,
42 | *args: Optional, **kwargs: Optional) -> None:
43 | """
44 | Registers a new statistical function that will be applied
45 | to the sequence each time the *record* method is called.
46 |
47 | :param name: The name of the statistics function as it would
48 | appear in the dictionary of the statistics object.
49 | :param func: A function that will compute the desired
50 | statistics on the data as preprocessed by the key.
51 | :param args: Positional arguments to be passed to the function, optional.
52 | :param kwargs: Keyword arguments to be passed to the function, optional.
53 | :return: Nothing.
54 | """
55 | self.functions[name] = partial(func, *args, **kwargs)
56 | self.fields.append(name)
57 |
58 | # -------------------------------------------------------- #
59 | def compile(self, data: Iterable) -> dict:
60 | """
61 | Compiles the statistics on the given data.
62 |
63 | :param data: The data on which the statistics will be computed.
64 | :return: A dictionary containing the statistics.
65 | """
66 | entry = dict()
67 | values = tuple(self.key(elem) for elem in data)
68 | for key, func in self.functions.items():
69 | entry[key] = func(values)
70 | return entry
71 |
72 |
73 | # ====================================================================================== #
74 | class MultiStatistics(dict):
75 | """
76 | Object that compiles statistics on a list of arbitrary objects.
77 | Allows computation of statistics on multiple keys using a single
78 | call to the 'compile' method.
79 | """
80 | # -------------------------------------------------------- #
81 | @property
82 | def fields(self):
83 | return sorted(self.keys())
84 |
85 | # -------------------------------------------------------- #
86 | def register(self, name: str, func: Callable,
87 | *args: Optional, **kwargs: Optional) -> None:
88 | """
89 | Registers a new statistical function that will be applied
90 | to the sequence each time the *record* method is called.
91 |
92 | :param name: The name of the statistics function as it would
93 | appear in the dictionary of the statistics object.
94 | :param func: A function that will compute the desired
95 | statistics on the data as preprocessed by the key.
96 | :param args: Positional arguments to be passed to the function, optional.
97 | :param kwargs: Keyword arguments to be passed to the function, optional.
98 | :return: Nothing.
99 | """
100 | for stats in self.values():
101 | stats.register(name, func, *args, **kwargs)
102 |
103 | # -------------------------------------------------------- #
104 | def compile(self, data: Iterable) -> dict:
105 | """
106 | Compiles the statistics on the given data.
107 |
108 | :param data: The data on which the statistics will be computed.
109 | :return: A dictionary containing the statistics.
110 | """
111 | record = dict()
112 | for name, stats in self.items():
113 | record[name] = stats.compile(data)
114 | return record
115 |
--------------------------------------------------------------------------------