Repository navigation
Expand file tree
/
Copy pathhypercube_migration.py
More file actions
123 lines (97 loc) · 4.42 KB
/
Copy pathhypercube_migration.py
File metadata and controls
123 lines (97 loc) · 4.42 KB
1
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
""" Hypercube migration module. """
from typing import Callable
from operator import attrgetter
# Custom code imports.
from pygenalgo.genome.chromosome import Chromosome
from pygenalgo.utils.auxiliary import SubPopulation
from pygenalgo.operators.migration.ring_migration import RingMigration
from pygenalgo.operators.migration.migration_operator import MigrationOperator
class HypercubeMigration(MigrationOperator):
"""
Hypercube island migration.
For N = pow(2, d) islands, each island is assigned an integer ID
from 0 to N - 1. Two islands are neighbors when their IDs differ
in exactly one binary bit.
If the number of active islands is not a power of two, migration
falls back to ring migration to preserve gene flow among islands.
"""
def __init__(self, migration_probability: float = 0.95) -> None:
"""
Construct a HypercubeMigration object.
:param migration_probability: (float) in [0, 1].
"""
super().__init__(migration_probability=migration_probability)
# Create an auxiliary ring migration operator with 100%
# probability. It is important because it will act as a
# safety fallback operator.
self._items: MigrationOperator = RingMigration(1.0)
# _end_def_
def migrate(self, islands: list[SubPopulation]) -> None:
"""
Perform hypercube migration.
Each island sends its best chromosome to every hypercube neighbor.
The receiving island replaces one randomly selected chromosome for
each incoming migrant.
:param islands: list[SubPopulation].
:return: None.
"""
# Get the size of active islands.
n_active: int = len(islands)
# Perform the migration with a specified probability
# and only if we have more than 1 active populations.
if self.is_operator_applicable() and n_active > 1:
# Use a fallback migration policy when
# a hypercube can't be formed from the
# current number of active islands.
if (n_active & (n_active - 1)) != 0:
# Local reference of the operator.
fallback_operator = self._items
# Call its fallback migration policy.
fallback_operator.migrate(islands)
# Increase the self migration counter.
self.inc_counter()
# Exit the call.
return
# _end_if_
# Obtain the hypercube dimension.
dimension: int = n_active.bit_length() - 1
# Define the key.
key_sort: Callable = attrgetter("fitness")
# Snapshot the best chromosome from every island before any
# migration occurs. This prevents migrations in the current
# round from affecting later source selections.
best_chromosomes: list[tuple[int, Chromosome]] = [
(n, max(island.population, key=key_sort).clone())
for n, island in enumerate(islands)
]
# Each dimension corresponds to one bit position.
# Flipping that bit identifies one hypercube neighbor.
#
# For island i:
#
# neighbor = i XOR (1 << bit)
#
# Example:
#
# i = 2 -> binary 010
# bit = 0 -> 010 XOR 001 = 011 -> island 3
# bit = 1 -> 010 XOR 010 = 000 -> island 0
# bit = 2 -> 010 XOR 100 = 110 -> island 6
for source_id, best_chromosome in best_chromosomes:
# Find the destination islands for each source.
for bit in range(dimension):
# Compute the destination index.
dest_k: int = source_id ^ (1 << bit)
# Local copy of the destination population.
dest_population = islands[dest_k].population
# Select the individual with the lowest (worst)
# fitness to be replaced.
idx: int = self._find_worst_index(dest_population)
# Insert a clone so that islands do not share
# the same mutable chromosome object.
dest_population[idx] = best_chromosome.clone()
# _end_for_
# Increase the migration counter.
self.inc_counter()
# _end_def_
# _end_class_