eckity-dts 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. eckity_dts-0.1.0/LICENSE +29 -0
  2. eckity_dts-0.1.0/PKG-INFO +167 -0
  3. eckity_dts-0.1.0/README.md +136 -0
  4. eckity_dts-0.1.0/deep_tournament_selection/__init__.py +4 -0
  5. eckity_dts-0.1.0/deep_tournament_selection/caching_evaluator.py +36 -0
  6. eckity_dts-0.1.0/deep_tournament_selection/config.py +99 -0
  7. eckity_dts-0.1.0/deep_tournament_selection/elitist_breeder.py +28 -0
  8. eckity_dts-0.1.0/deep_tournament_selection/logging_utils.py +64 -0
  9. eckity_dts-0.1.0/deep_tournament_selection/selection/__init__.py +4 -0
  10. eckity_dts-0.1.0/deep_tournament_selection/selection/dts_aux.py +96 -0
  11. eckity_dts-0.1.0/deep_tournament_selection/selection/dts_policy.py +322 -0
  12. eckity_dts-0.1.0/deep_tournament_selection/selection/eckity_adapter.py +36 -0
  13. eckity_dts-0.1.0/deep_tournament_selection/selection/population_to_vec_transformer.py +135 -0
  14. eckity_dts-0.1.0/deep_tournament_selection/selection/self_attention_pointer.py +103 -0
  15. eckity_dts-0.1.0/deep_tournament_selection/selection/simple_self_attention.py +37 -0
  16. eckity_dts-0.1.0/deep_tournament_selection/selection/tournament_utils.py +61 -0
  17. eckity_dts-0.1.0/eckity_dts/__init__.py +5 -0
  18. eckity_dts-0.1.0/eckity_dts.egg-info/PKG-INFO +167 -0
  19. eckity_dts-0.1.0/eckity_dts.egg-info/SOURCES.txt +23 -0
  20. eckity_dts-0.1.0/eckity_dts.egg-info/dependency_links.txt +1 -0
  21. eckity_dts-0.1.0/eckity_dts.egg-info/requires.txt +11 -0
  22. eckity_dts-0.1.0/eckity_dts.egg-info/top_level.txt +2 -0
  23. eckity_dts-0.1.0/pyproject.toml +63 -0
  24. eckity_dts-0.1.0/setup.cfg +4 -0
  25. eckity_dts-0.1.0/tests/test_package.py +117 -0
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Eliad Shem-Tov, Ron Edri, Achiya Elyasaf and the EC-KitY project
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,167 @@
1
+ Metadata-Version: 2.4
2
+ Name: eckity-dts
3
+ Version: 0.1.0
4
+ Summary: Deep Tournament Selection operator for EC-KitY genetic algorithms
5
+ Author: Eliad Shem-Tov, Ron Edri, Achiya Elyasaf
6
+ License-Expression: BSD-3-Clause
7
+ Project-URL: Homepage, https://github.com/EC-KitY/DTS-for-GA
8
+ Project-URL: Repository, https://github.com/EC-KitY/DTS-for-GA
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
17
+ Requires-Python: >=3.9
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: eckity~=0.4.1
21
+ Requires-Dist: numpy>=2.0.2
22
+ Requires-Dist: overrides>=7.7.0
23
+ Requires-Dist: scipy>=1.13.0
24
+ Requires-Dist: torch>=2.7.1
25
+ Provides-Extra: dev
26
+ Requires-Dist: build>=1.2; extra == "dev"
27
+ Requires-Dist: pytest>=8.0; extra == "dev"
28
+ Requires-Dist: ruff>=0.5; extra == "dev"
29
+ Requires-Dist: twine>=5.0; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ # Deep Tournament Selection for EC-KitY
33
+
34
+ `eckity-dts` provides Deep Tournament Selection (DTS), a learned selection operator for genetic algorithms built on [EC-KitY](https://github.com/EC-KitY/EC-KitY).
35
+
36
+ DTS uses a Transformer encoder and a self-attention pointer network trained online with REINFORCE. It was introduced in **“Deep Tournament Selection for Genetic Algorithms”** by Eliad Shem-Tov, Ron Edri, and Achiya Elyasaf. The paper has not yet been published; a formal citation will be added when available.
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ pip install eckity-dts
42
+ ```
43
+
44
+ ## Public API
45
+
46
+ ```python
47
+ from eckity_dts import CachingEvaluator, DeepTournamentSelection, DTSPolicy
48
+ ```
49
+
50
+ ## Constructing DTS
51
+
52
+ The policy combines a population encoder with a pointer network:
53
+
54
+ ```python
55
+ from eckity_dts import DeepTournamentSelection, DTSPolicy
56
+ from deep_tournament_selection.selection.population_to_vec_transformer import (
57
+ PopulationToVecTransformer,
58
+ )
59
+ from deep_tournament_selection.selection.self_attention_pointer import (
60
+ SelfAttentionPointer,
61
+ )
62
+
63
+ population_size = 100
64
+ vocab_size = 2 # maximum gene value + 1
65
+
66
+ encoder = PopulationToVecTransformer(
67
+ vocab_size=vocab_size,
68
+ emb_dim=32,
69
+ latent_dim=32,
70
+ n_heads=4,
71
+ n_layers=2,
72
+ dim_feedforward=256,
73
+ max_pointers=population_size,
74
+ )
75
+
76
+ pointer = SelfAttentionPointer(
77
+ pointer_len=population_size,
78
+ d_model=32,
79
+ )
80
+
81
+ policy = DTSPolicy(
82
+ pop_to_vec_transformer=encoder,
83
+ pointer_transformer=pointer,
84
+ device="cpu",
85
+ train_every_n_gens=10,
86
+ learning_rate=2e-3,
87
+ final_lr=1e-3,
88
+ epsilon_greedy=1.0,
89
+ epsilon_greedy_decay=0.999,
90
+ min_epsilon=0.2,
91
+ )
92
+
93
+ dts = DeepTournamentSelection(policy, higher_is_better=True)
94
+ ```
95
+
96
+ Use it as the EC-KitY selection method:
97
+
98
+ ```python
99
+ selection_methods=[(dts, 1)]
100
+ ```
101
+
102
+ Important parameters:
103
+
104
+ - `population_size` determines the rank-embedding and pointer-table capacities.
105
+ - `vocab_size` is the maximum integer gene value plus one.
106
+ - `train_every_n_gens` controls how often accumulated trajectories train the policy.
107
+ - `epsilon_greedy` and its decay control teacher-forced tournament selection versus learned selection.
108
+ - `custom_reward_function`, when supplied, receives current fitness, previous fitness, and population arrays.
109
+ - `device` can be `"cpu"` or `"cuda"` when a compatible PyTorch installation is available.
110
+
111
+ ## Fitness caching
112
+
113
+ EC-KitY may evaluate unchanged vectors again across generations. `CachingEvaluator` avoids recomputing fitness for vectors it has already seen:
114
+
115
+ ```python
116
+ from eckity_dts import CachingEvaluator
117
+
118
+ evaluator = CachingEvaluator(MyEvaluator())
119
+ print(evaluator.cache_stats())
120
+ ```
121
+
122
+ ## Compatibility
123
+
124
+ - Python 3.9 or newer
125
+ - EC-KitY 0.4.x
126
+ - NumPy 2.0.2 or newer
127
+ - SciPy 1.13.0 or newer
128
+ - PyTorch 2.7.1 or newer
129
+ - overrides 7.7.0 or newer
130
+
131
+ These bounds are compatible with `eckity-dnc`, `eckity-bert-ga`, and `eckity-bert-gp`. None of the operator packages depends directly on another operator package.
132
+
133
+ ## Research repository
134
+
135
+ The repository contains the full paper experiments for Graph Coloring, Set Cover, and TSP, together with benchmark instances, notebook-style runners, and result figures. These research resources are not included in the `eckity-dts` wheel.
136
+
137
+ For repository development:
138
+
139
+ ```bash
140
+ uv sync --extra dev --resolution lowest-direct
141
+ uv run pytest
142
+ ```
143
+
144
+ Experiment entry points remain available from a source checkout:
145
+
146
+ ```bash
147
+ python -m deep_tournament_selection.experiments.graph_coloring --instance queen8_12.col.txt --generations 200
148
+ python -m deep_tournament_selection.experiments.set_cover --instance scp41.txt --generations 200
149
+ python -m deep_tournament_selection.experiments.tsp --instance att48.tsp --generations 200
150
+ python run_experiments.py --selection both --runs 3 --generations 500
151
+ ```
152
+
153
+ Results are written under `runs/`. Paper figures are stored under `figures/`, and the architecture diagram is under `images/`.
154
+
155
+ ## Development and release
156
+
157
+ ```bash
158
+ uv run pytest
159
+ uv run ruff check .
160
+ uv build
161
+ ```
162
+
163
+ Release preparation and manual PyPI upload commands are documented in [`RELEASING.md`](RELEASING.md).
164
+
165
+ ## License
166
+
167
+ This project is licensed under the BSD 3-Clause License. See [`LICENSE`](LICENSE).
@@ -0,0 +1,136 @@
1
+ # Deep Tournament Selection for EC-KitY
2
+
3
+ `eckity-dts` provides Deep Tournament Selection (DTS), a learned selection operator for genetic algorithms built on [EC-KitY](https://github.com/EC-KitY/EC-KitY).
4
+
5
+ DTS uses a Transformer encoder and a self-attention pointer network trained online with REINFORCE. It was introduced in **“Deep Tournament Selection for Genetic Algorithms”** by Eliad Shem-Tov, Ron Edri, and Achiya Elyasaf. The paper has not yet been published; a formal citation will be added when available.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install eckity-dts
11
+ ```
12
+
13
+ ## Public API
14
+
15
+ ```python
16
+ from eckity_dts import CachingEvaluator, DeepTournamentSelection, DTSPolicy
17
+ ```
18
+
19
+ ## Constructing DTS
20
+
21
+ The policy combines a population encoder with a pointer network:
22
+
23
+ ```python
24
+ from eckity_dts import DeepTournamentSelection, DTSPolicy
25
+ from deep_tournament_selection.selection.population_to_vec_transformer import (
26
+ PopulationToVecTransformer,
27
+ )
28
+ from deep_tournament_selection.selection.self_attention_pointer import (
29
+ SelfAttentionPointer,
30
+ )
31
+
32
+ population_size = 100
33
+ vocab_size = 2 # maximum gene value + 1
34
+
35
+ encoder = PopulationToVecTransformer(
36
+ vocab_size=vocab_size,
37
+ emb_dim=32,
38
+ latent_dim=32,
39
+ n_heads=4,
40
+ n_layers=2,
41
+ dim_feedforward=256,
42
+ max_pointers=population_size,
43
+ )
44
+
45
+ pointer = SelfAttentionPointer(
46
+ pointer_len=population_size,
47
+ d_model=32,
48
+ )
49
+
50
+ policy = DTSPolicy(
51
+ pop_to_vec_transformer=encoder,
52
+ pointer_transformer=pointer,
53
+ device="cpu",
54
+ train_every_n_gens=10,
55
+ learning_rate=2e-3,
56
+ final_lr=1e-3,
57
+ epsilon_greedy=1.0,
58
+ epsilon_greedy_decay=0.999,
59
+ min_epsilon=0.2,
60
+ )
61
+
62
+ dts = DeepTournamentSelection(policy, higher_is_better=True)
63
+ ```
64
+
65
+ Use it as the EC-KitY selection method:
66
+
67
+ ```python
68
+ selection_methods=[(dts, 1)]
69
+ ```
70
+
71
+ Important parameters:
72
+
73
+ - `population_size` determines the rank-embedding and pointer-table capacities.
74
+ - `vocab_size` is the maximum integer gene value plus one.
75
+ - `train_every_n_gens` controls how often accumulated trajectories train the policy.
76
+ - `epsilon_greedy` and its decay control teacher-forced tournament selection versus learned selection.
77
+ - `custom_reward_function`, when supplied, receives current fitness, previous fitness, and population arrays.
78
+ - `device` can be `"cpu"` or `"cuda"` when a compatible PyTorch installation is available.
79
+
80
+ ## Fitness caching
81
+
82
+ EC-KitY may evaluate unchanged vectors again across generations. `CachingEvaluator` avoids recomputing fitness for vectors it has already seen:
83
+
84
+ ```python
85
+ from eckity_dts import CachingEvaluator
86
+
87
+ evaluator = CachingEvaluator(MyEvaluator())
88
+ print(evaluator.cache_stats())
89
+ ```
90
+
91
+ ## Compatibility
92
+
93
+ - Python 3.9 or newer
94
+ - EC-KitY 0.4.x
95
+ - NumPy 2.0.2 or newer
96
+ - SciPy 1.13.0 or newer
97
+ - PyTorch 2.7.1 or newer
98
+ - overrides 7.7.0 or newer
99
+
100
+ These bounds are compatible with `eckity-dnc`, `eckity-bert-ga`, and `eckity-bert-gp`. None of the operator packages depends directly on another operator package.
101
+
102
+ ## Research repository
103
+
104
+ The repository contains the full paper experiments for Graph Coloring, Set Cover, and TSP, together with benchmark instances, notebook-style runners, and result figures. These research resources are not included in the `eckity-dts` wheel.
105
+
106
+ For repository development:
107
+
108
+ ```bash
109
+ uv sync --extra dev --resolution lowest-direct
110
+ uv run pytest
111
+ ```
112
+
113
+ Experiment entry points remain available from a source checkout:
114
+
115
+ ```bash
116
+ python -m deep_tournament_selection.experiments.graph_coloring --instance queen8_12.col.txt --generations 200
117
+ python -m deep_tournament_selection.experiments.set_cover --instance scp41.txt --generations 200
118
+ python -m deep_tournament_selection.experiments.tsp --instance att48.tsp --generations 200
119
+ python run_experiments.py --selection both --runs 3 --generations 500
120
+ ```
121
+
122
+ Results are written under `runs/`. Paper figures are stored under `figures/`, and the architecture diagram is under `images/`.
123
+
124
+ ## Development and release
125
+
126
+ ```bash
127
+ uv run pytest
128
+ uv run ruff check .
129
+ uv build
130
+ ```
131
+
132
+ Release preparation and manual PyPI upload commands are documented in [`RELEASING.md`](RELEASING.md).
133
+
134
+ ## License
135
+
136
+ This project is licensed under the BSD 3-Clause License. See [`LICENSE`](LICENSE).
@@ -0,0 +1,4 @@
1
+ from .selection import DeepTournamentSelection, DTSPolicy
2
+ from .caching_evaluator import CachingEvaluator
3
+
4
+ __all__ = ["DeepTournamentSelection", "DTSPolicy", "CachingEvaluator"]
@@ -0,0 +1,36 @@
1
+ from collections import OrderedDict
2
+ from eckity.evaluators.simple_individual_evaluator import SimpleIndividualEvaluator
3
+
4
+
5
+ class CachingEvaluator(SimpleIndividualEvaluator):
6
+ def __init__(self, inner: SimpleIndividualEvaluator, max_size=None, events=None):
7
+ super().__init__(events=events)
8
+ self.inner = inner
9
+ self.max_size = max_size
10
+ self.fitness_cache = OrderedDict()
11
+ self.hits = 0
12
+ self.misses = 0
13
+
14
+ def evaluate_individual(self, individual):
15
+ key = tuple(individual.vector)
16
+ if key in self.fitness_cache:
17
+ self.hits += 1
18
+ self.fitness_cache.move_to_end(key)
19
+ return self.fitness_cache[key]
20
+
21
+ self.misses += 1
22
+ value = self.inner.evaluate_individual(individual)
23
+ self.fitness_cache[key] = value
24
+ if self.max_size is not None and len(self.fitness_cache) > self.max_size:
25
+ self.fitness_cache.popitem(last=False)
26
+ return value
27
+
28
+ def cache_stats(self):
29
+ total = self.hits + self.misses
30
+ hit_rate = self.hits / total if total else 0.0
31
+ return {
32
+ "hits": self.hits,
33
+ "misses": self.misses,
34
+ "size": len(self.fitness_cache),
35
+ "hit_rate": hit_rate,
36
+ }
@@ -0,0 +1,99 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import List
3
+
4
+
5
+ @dataclass(frozen=True)
6
+ class DTSConfig:
7
+ """Hyperparameters for the Deep Tournament Selection operator (paper setting)."""
8
+
9
+ tournament_size: int = 5
10
+ latent_dim: int = 32
11
+ dim_feedforward: int = 256
12
+ n_layers: int = 2
13
+ n_heads: int = 4
14
+ learning_rate: float = 2e-3
15
+ final_lr: float = 1e-3
16
+ train_every_n_gens: int = 10
17
+ epsilon_greedy: float = 1.0
18
+ epsilon_greedy_decay: float = 0.999
19
+ min_epsilon: float = 0.2
20
+
21
+
22
+ POPULATION_SIZE = 100
23
+ ELITISM = 2
24
+ CROSSOVER_PROB = 0.5
25
+ MUTATION_PROB = 0.5
26
+ FLIP_MUTATION_PROB = 0.1
27
+ RUNS = 15
28
+
29
+
30
+ @dataclass(frozen=True)
31
+ class TSPConfig:
32
+ instances: List[str] = field(
33
+ default_factory=lambda: [
34
+ "att48.tsp",
35
+ "berlin52.tsp",
36
+ "st70.tsp",
37
+ "lin105.tsp",
38
+ "pr107.tsp",
39
+ "pcb442.tsp",
40
+ "d1291.tsp",
41
+ ]
42
+ )
43
+ population_size: int = POPULATION_SIZE
44
+ generations: int = 1000
45
+ crossover_prob: float = CROSSOVER_PROB
46
+ mutation_prob: float = MUTATION_PROB
47
+ elitism: int = ELITISM
48
+ runs: int = RUNS
49
+
50
+
51
+ @dataclass(frozen=True)
52
+ class GraphColoringConfig:
53
+ instances: List[str] = field(
54
+ default_factory=lambda: [
55
+ "instances_games120.col.txt",
56
+ "instances_myciel7.col.txt",
57
+ "miles1000.col.txt",
58
+ "miles1500.col.txt",
59
+ "mulsol.i.2.col",
60
+ "queen8_12.col.txt",
61
+ "zeroin.i.1.col",
62
+ "zeroin.i.2.col",
63
+ ]
64
+ )
65
+ population_size: int = POPULATION_SIZE
66
+ generations: int = 6000
67
+ crossover_prob: float = CROSSOVER_PROB
68
+ mutation_prob: float = MUTATION_PROB
69
+ flip_mutation_prob: float = FLIP_MUTATION_PROB
70
+ penalty: float = 100.0
71
+ colors_margin: int = 10
72
+ elitism: int = ELITISM
73
+ runs: int = RUNS
74
+
75
+
76
+ @dataclass(frozen=True)
77
+ class SetCoverConfig:
78
+ instances: List[str] = field(
79
+ default_factory=lambda: [
80
+ "scp41.txt",
81
+ "scp51.txt",
82
+ "scp52.txt",
83
+ "scp53.txt",
84
+ "scp54.txt",
85
+ "scp56.txt",
86
+ "scp57.txt",
87
+ "scp64.txt",
88
+ "scp65.txt",
89
+ ]
90
+ )
91
+ population_size: int = POPULATION_SIZE
92
+ generations: int = 6000
93
+ crossover_prob: float = CROSSOVER_PROB
94
+ mutation_prob: float = MUTATION_PROB
95
+ flip_mutation_prob: float = FLIP_MUTATION_PROB
96
+ penalty: float = 100.0
97
+ weighted: bool = False
98
+ elitism: int = ELITISM
99
+ runs: int = RUNS
@@ -0,0 +1,28 @@
1
+ from eckity.breeders.simple_breeder import SimpleBreeder
2
+ from eckity.genetic_operators.selections.elitism_selection import ElitismSelection
3
+
4
+
5
+ class ElitistBreeder(SimpleBreeder):
6
+ def apply_breed(self, population):
7
+ for subpopulation in population.sub_populations:
8
+ nextgen_population = []
9
+
10
+ num_elites = subpopulation.n_elite
11
+ if num_elites > 0:
12
+ ElitismSelection(
13
+ num_elites=num_elites,
14
+ higher_is_better=subpopulation.higher_is_better,
15
+ ).apply_operator((subpopulation.individuals, nextgen_population))
16
+
17
+ selection = subpopulation.get_selection_methods()[0][0]
18
+ nextgen_population = selection.select(
19
+ subpopulation.individuals, nextgen_population
20
+ )
21
+ self.selected_individuals = nextgen_population
22
+
23
+ elites = nextgen_population[:num_elites]
24
+ offspring = self._apply_operators(
25
+ subpopulation.get_operators_sequence(),
26
+ nextgen_population[num_elites:],
27
+ )
28
+ subpopulation.individuals = elites + offspring
@@ -0,0 +1,64 @@
1
+ import json
2
+ import os
3
+ import time
4
+
5
+ import numpy as np
6
+ from eckity.statistics.statistics import Statistics
7
+
8
+
9
+ class FileLogger(Statistics):
10
+ BASE_KEYS = ("mean", "std", "median", "max", "min", "time")
11
+
12
+ def __init__(
13
+ self,
14
+ output_path=None,
15
+ save_every_n_generations=50,
16
+ diversity_fn=None,
17
+ format_string=None,
18
+ ):
19
+ super().__init__(format_string or "")
20
+ self.output_path = output_path
21
+ self.save_every_n_generations = save_every_n_generations
22
+ self.diversity_fn = diversity_fn
23
+ keys = self.BASE_KEYS + (("population_diversity",) if diversity_fn else ())
24
+ self.generation_metrics = {key: [] for key in keys}
25
+ self._last_time = time.time()
26
+
27
+ def write_statistics(self, sender, data_dict):
28
+ sub_pop = data_dict["population"].sub_populations[0]
29
+ fitness_values = np.array(
30
+ [ind.get_pure_fitness() for ind in sub_pop.individuals], dtype=float
31
+ )
32
+ self.generation_metrics["mean"].append(float(np.mean(fitness_values)))
33
+ self.generation_metrics["std"].append(float(np.std(fitness_values)))
34
+ self.generation_metrics["median"].append(float(np.median(fitness_values)))
35
+ self.generation_metrics["max"].append(float(np.max(fitness_values)))
36
+ self.generation_metrics["min"].append(float(np.min(fitness_values)))
37
+
38
+ if self.diversity_fn is not None:
39
+ population = np.array([ind.vector for ind in sub_pop.individuals])
40
+ self.generation_metrics["population_diversity"].append(
41
+ float(self.diversity_fn(population))
42
+ )
43
+
44
+ now = time.time()
45
+ self.generation_metrics["time"].append(now - self._last_time)
46
+ self._last_time = now
47
+
48
+ if (
49
+ self.output_path
50
+ and self.save_every_n_generations
51
+ and data_dict["generation_num"] % self.save_every_n_generations == 0
52
+ ):
53
+ self.save()
54
+
55
+ def save(self, extra=None):
56
+ if self.output_path is None:
57
+ return None
58
+ os.makedirs(os.path.dirname(os.path.abspath(self.output_path)), exist_ok=True)
59
+ payload = dict(self.generation_metrics)
60
+ if extra:
61
+ payload.update(extra)
62
+ with open(self.output_path, "w") as f:
63
+ json.dump(payload, f, indent=2)
64
+ return self.output_path
@@ -0,0 +1,4 @@
1
+ from .eckity_adapter import DeepTournamentSelection
2
+ from .dts_policy import DTSPolicy
3
+
4
+ __all__ = ["DeepTournamentSelection", "DTSPolicy"]
@@ -0,0 +1,96 @@
1
+ from __future__ import annotations
2
+
3
+ import numpy as np
4
+ import torch
5
+
6
+
7
+ def get_ranks_from_fitness_values(fitness_values: np.ndarray):
8
+ order = np.argsort(-fitness_values, axis=1)
9
+ ranks = np.argsort(order, axis=1)
10
+ return ranks
11
+
12
+
13
+ def get_ranking_based_rewards(fitness_values: np.ndarray, gamma=1.25):
14
+ fitness_values = fitness_values.reshape(1, -1)
15
+ ranks = get_ranks_from_fitness_values(fitness_values)
16
+ ranks_weights = 1 / (ranks + 1) ** gamma
17
+ rewards = ranks_weights * fitness_values
18
+ rewards = rewards.sum(axis=1)
19
+ return rewards
20
+
21
+
22
+ def get_exponential_ranking_based_rewards(fitness_values: np.ndarray, alpha=0.2):
23
+ fitness_values = fitness_values.reshape(1, -1)
24
+ ranks = get_ranks_from_fitness_values(fitness_values)
25
+ ranks_weights = np.exp(-alpha * ranks)
26
+ rewards = ranks_weights * fitness_values
27
+ rewards = rewards.sum(axis=1)
28
+ return rewards
29
+
30
+
31
+ def get_average_pairwise_hamming_distance(population: np.ndarray) -> float:
32
+ population = np.asarray(population)
33
+
34
+ if population.ndim != 2 or len(population) < 2:
35
+ return 0.0
36
+
37
+ diffs = population[:, None, :] != population[None, :, :]
38
+ pairwise_dist = diffs.sum(axis=2)
39
+ triu_indices = np.triu_indices(len(population), k=1)
40
+ return float(pairwise_dist[triu_indices].mean() / population.shape[1])
41
+
42
+
43
+ def get_reward_from_fitness_scores(
44
+ cur_gen_fitness_values: np.ndarray,
45
+ prev_gen_fitness_values: np.ndarray,
46
+ population: np.ndarray | None = None,
47
+ top_k_to_consider: int = 5,
48
+ best_weight: float = 0.25,
49
+ top_k_weight: float = 1.0,
50
+ mean_weight: float = 0.25,
51
+ diversity_weight: float = 0.05,
52
+ eps: float = 1e-8,
53
+ ):
54
+ cur_gen_fitness_values = np.asarray(cur_gen_fitness_values, dtype=np.float64)
55
+ prev_gen_fitness_values = np.asarray(prev_gen_fitness_values, dtype=np.float64)
56
+
57
+ k = min(
58
+ top_k_to_consider, len(cur_gen_fitness_values), len(prev_gen_fitness_values)
59
+ )
60
+
61
+ def normalized_delta(cur_value: float, prev_value: float) -> float:
62
+ scale = max(abs(prev_value), eps)
63
+ return (cur_value - prev_value) / scale
64
+
65
+ cur_top_k_mean = np.partition(cur_gen_fitness_values, -k)[-k:].mean()
66
+ prev_top_k_mean = np.partition(prev_gen_fitness_values, -k)[-k:].mean()
67
+
68
+ cur_best = cur_gen_fitness_values.max()
69
+ prev_best = prev_gen_fitness_values.max()
70
+
71
+ cur_mean = cur_gen_fitness_values.mean()
72
+ prev_mean = prev_gen_fitness_values.mean()
73
+
74
+ diversity_reward = 0.0
75
+ if population is not None:
76
+ diversity_reward = get_average_pairwise_hamming_distance(population)
77
+
78
+ reward = (
79
+ top_k_weight * normalized_delta(cur_top_k_mean, prev_top_k_mean)
80
+ + best_weight * normalized_delta(cur_best, prev_best)
81
+ + mean_weight * normalized_delta(cur_mean, prev_mean)
82
+ + diversity_weight * diversity_reward
83
+ )
84
+ return float(reward)
85
+
86
+
87
+ def get_trajectory_probability_from_log_probs(log_probs, selected_population_indices):
88
+ """
89
+ calculate total probability for the trajectory by summing the log probabilities over the selected indices
90
+ """
91
+ trajectory_prob = (
92
+ torch.gather(log_probs, 2, selected_population_indices.unsqueeze(-1))
93
+ .squeeze(-1)
94
+ .sum(dim=-1)
95
+ )
96
+ return trajectory_prob