├── 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 | 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 | 6 | 7 | 8 | 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 | 14 | 15 | 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 | --------------------------------------------------------------------------------