MAP-Elites¶
Illuminates a 2-D behavior grid of Rastrigin with GridArchive and
ea_map_elites. Fitness is on ind.fitness; the descriptor is
the first two genes.
import numpy
from deap_er import Fitness, Toolbox, creator, tools
tools.rng.seed(1234) # disables randomization
DIM = 6
LOW, UP = -5.12, 5.12
BINS = 8
def evaluate(individual):
(score,) = tools.bm_rastrigin(individual)
return (-score,) # maximize; GridArchive qd_score sums wvalues
def descriptor(individual):
return (float(individual[0]), float(individual[1]))
def setup():
creator.create_type("FitnessMax", Fitness, weights=(1.0,))
creator.create_type("Individual", list, fitness=creator.FitnessMax)
toolbox = Toolbox()
toolbox.register("attr_float", tools.rng.uniform, LOW, UP)
toolbox.register(
"individual",
tools.init_repeat,
creator.Individual,
toolbox.attr_float,
DIM,
)
toolbox.register("population", tools.init_repeat, list, toolbox.individual)
toolbox.register("mate", tools.cx_blend_bounded, alpha=0.5, low=LOW, up=UP)
toolbox.register(
"mutate",
tools.mut_polynomial_bounded,
low=LOW,
up=UP,
eta=20.0,
mut_prob=1.0 / DIM,
)
toolbox.register("evaluate", evaluate)
stats = tools.Statistics(lambda ind: ind.fitness.values)
stats.register("avg", numpy.mean)
stats.register("max", numpy.max)
return toolbox, stats
def print_results(archive):
summary = archive.stats
elites = list(archive)
best = max(elites, key=lambda ind: ind.fitness.values) if elites else None
print(f"\nCoverage: {summary.coverage:.3f} ({summary.num_elites}/{summary.num_cells})")
print(f"QD score: {summary.qd_score:.3f}")
if best is not None:
print(f"Best elite fitness: {best.fitness.values[0]:.4f}")
print(f"Best elite behavior: {descriptor(best)}")
def main():
toolbox, stats = setup()
archive = tools.GridArchive(ranges=[(LOW, UP), (LOW, UP)], bins=BINS)
initial = toolbox.population(size=40)
tools.ea_map_elites(
toolbox,
archive,
descriptor,
initial,
generations=15,
batch_size=20,
cx_prob=0.5,
mut_prob=0.4,
stats=stats,
verbose=True,
log_time=True,
)
print_results(archive)
if __name__ == "__main__":
main()
CVT archive¶
CvtArchive.from_samples builds Voronoi cells from k-means centroids.
The same ea_map_elites loop fills those cells.
import numpy
from deap_er import Fitness, Toolbox, creator, tools
tools.rng.seed(1234) # disables randomization
DIM = 6
LOW, UP = -5.12, 5.12
CELLS = 16
def evaluate(individual):
(score,) = tools.bm_rastrigin(individual)
return (-score,)
def descriptor(individual):
return (float(individual[0]), float(individual[1]))
def setup():
creator.create_type("FitnessMax", Fitness, weights=(1.0,))
creator.create_type("Individual", list, fitness=creator.FitnessMax)
toolbox = Toolbox()
toolbox.register("attr_float", tools.rng.uniform, LOW, UP)
toolbox.register(
"individual",
tools.init_repeat,
creator.Individual,
toolbox.attr_float,
DIM,
)
toolbox.register("population", tools.init_repeat, list, toolbox.individual)
toolbox.register("mate", tools.cx_blend_bounded, alpha=0.5, low=LOW, up=UP)
toolbox.register(
"mutate",
tools.mut_polynomial_bounded,
low=LOW,
up=UP,
eta=20.0,
mut_prob=1.0 / DIM,
)
toolbox.register("evaluate", evaluate)
toolbox.register("clone", tools.clone_individual)
stats = tools.Statistics(lambda ind: ind.fitness.values)
stats.register("max", numpy.max)
return toolbox, stats
def print_results(archive):
if archive.stats.num_elites == 0:
raise RuntimeError("CVT archive stored no elites.")
print(f"\nCVT elites: {archive.stats.num_elites}")
print(f"QD score: {archive.stats.qd_score:.3f}")
def main():
toolbox, stats = setup()
samples = [[tools.rng.uniform(LOW, UP), tools.rng.uniform(LOW, UP)] for _ in range(80)]
archive = tools.CvtArchive.from_samples(samples, k=CELLS)
initial = toolbox.population(size=40)
tools.ea_map_elites(
toolbox,
archive,
descriptor,
initial,
generations=12,
batch_size=20,
cx_prob=0.5,
mut_prob=0.4,
stats=stats,
verbose=True,
)
print_results(archive)
if __name__ == "__main__":
main()
Novelty and iso+line¶
UnstructuredArchive keeps elites by descriptor distance.
sel_novelty ranks parents by distance to the archive; mut_iso_line
interpolates toward a donor elite. See roadmap
item 29
and the logging tutorial.
import numpy
from deap_er import Fitness, Toolbox, creator, tools
tools.rng.seed(1234) # disables randomization
DIM = 4
LOW, UP = 0.0, 1.0
def evaluate(individual):
return (sum(individual),)
def descriptor(individual):
return (float(individual[0]), float(individual[1]))
def setup(archive):
creator.create_type("FitnessMax", Fitness, weights=(1.0,))
creator.create_type("Individual", list, fitness=creator.FitnessMax)
toolbox = Toolbox()
toolbox.register("attr_float", tools.rng.uniform, LOW, UP)
toolbox.register(
"individual",
tools.init_repeat,
creator.Individual,
toolbox.attr_float,
DIM,
)
toolbox.register("population", tools.init_repeat, list, toolbox.individual)
toolbox.register("mate", tools.cx_blend_bounded, alpha=0.5, low=LOW, up=UP)
toolbox.register("evaluate", evaluate)
toolbox.register("clone", tools.clone_individual)
def mutate(individual):
donor = archive.random_elites(1)[0] if len(archive) else individual
return tools.mut_iso_line(individual, donor, iso=0.05, sigma=0.08, low=LOW, up=UP)
toolbox.register("mutate", mutate)
toolbox.register(
"select",
tools.sel_novelty,
archive=archive,
descriptor_fn=descriptor,
k=2,
)
stats = tools.Statistics(lambda ind: ind.fitness.values)
stats.register("max", numpy.max)
return toolbox, stats
def print_results(archive):
if archive.stats.num_elites <= 1:
raise RuntimeError("Novelty MAP-Elites failed to illuminate more than one elite.")
print(f"\nUnstructured elites: {archive.stats.num_elites}")
print(f"QD score: {archive.stats.qd_score:.3f}")
def main():
archive = tools.UnstructuredArchive(2, min_distance=0.12, max_elites=20)
toolbox, stats = setup(archive)
initial = toolbox.population(size=24)
tools.ea_map_elites(
toolbox,
archive,
descriptor,
initial,
generations=12,
batch_size=16,
cx_prob=0.3,
mut_prob=0.7,
stats=stats,
verbose=True,
)
print_results(archive)
if __name__ == "__main__":
main()