Skip to content

Commit 30b2fbd

Browse files
Merge pull request #159 from godatadriven/elitist-mutation
Elitist mutation
2 parents 1adf857 + b88f7e4 commit 30b2fbd

File tree

4 files changed

+23
-4
lines changed

4 files changed

+23
-4
lines changed

evol/evolution.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ def breed(self,
147147
def mutate(self,
148148
mutate_function: Callable[..., Any],
149149
probability: float = 1.0,
150+
elitist: bool = False,
150151
name: Optional[str] = None,
151152
**kwargs) -> 'Evolution':
152153
"""Add a mutate step to the Evolution.
@@ -158,12 +159,17 @@ def mutate(self,
158159
:param probability: Probability that the individual mutates.
159160
The function is only applied in the given fraction of cases.
160161
Defaults to 1.0.
162+
:param elitist: If True, do not mutate the current best individual(s).
163+
Note that this only applies to evaluated individuals. Any unevaluated
164+
individual will be treated as normal.
165+
Defaults to False.
161166
:param name: Name of the mutate step.
162167
:param kwargs: Kwargs to pass to the parent_picker and combiner.
163168
Arguments are only passed to the functions if they accept them.
164169
:return: self
165170
"""
166-
return self._add_step(MutateStep(name=name, probability=probability, mutate_function=mutate_function, **kwargs))
171+
return self._add_step(MutateStep(name=name, probability=probability, elitist=elitist,
172+
mutate_function=mutate_function, **kwargs))
167173

168174
def repeat(self, evolution: 'Evolution', n: int = 1, name: Optional[str] = None,
169175
grouping_function: Optional[Callable] = None, **kwargs) -> 'Evolution':

evol/population.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -191,19 +191,26 @@ def breed(self,
191191

192192
def mutate(self,
193193
mutate_function: Callable[..., Any],
194-
probability: float = 1.0, **kwargs) -> 'BasePopulation':
194+
probability: float = 1.0,
195+
elitist: bool = False, **kwargs) -> 'BasePopulation':
195196
"""Mutate the chromosome of each individual.
196197
197198
:param mutate_function: Function that accepts a chromosome and returns
198199
a mutated chromosome.
199200
:param probability: Probability that the individual mutates.
200201
The function is only applied in the given fraction of cases.
201202
Defaults to 1.0.
203+
:param elitist: If True, do not mutate the current best individual(s).
204+
Note that this only applies to evaluated individuals. Any unevaluated
205+
individual will be treated as normal.
206+
Defaults to False.
202207
:param kwargs: Arguments to pass to the mutation function.
203208
:return: self
204209
"""
210+
elite_fitness = self.current_best if elitist else None
205211
for individual in self.individuals:
206-
individual.mutate(mutate_function, probability=probability, **kwargs)
212+
if elite_fitness is None or individual.fitness != elite_fitness:
213+
individual.mutate(mutate_function, probability=probability, **kwargs)
207214
return self
208215

209216
def map(self, func: Callable[..., Individual], **kwargs) -> 'BasePopulation':

examples/travelling_salesman.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def print_function(population: Population):
4848
island_evo = (Evolution()
4949
.survive(fraction=0.5)
5050
.breed(parent_picker=pick_random, combiner=cycle_crossover)
51-
.mutate(swap_elements))
51+
.mutate(swap_elements, elitist=True))
5252

5353
evo = (Evolution()
5454
.evaluate(lazy=True)

tests/test_population.py

+6
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,12 @@ def mutate_func(x, y=0):
241241
for chromosome in pop.chromosomes:
242242
assert chromosome == 17
243243

244+
def test_mutate_elitist(self):
245+
pop = Population([1, 1, 3], eval_function=lambda x: x).evaluate().mutate(lambda x: x + 1, elitist=True)
246+
for chromosome in pop.chromosomes:
247+
assert chromosome > 1
248+
assert len(pop) == 3
249+
244250

245251
class TestPopulationWeights:
246252

0 commit comments

Comments
 (0)