Skip to content

Mixed Encoding

A widget with a flag, an integer count, a material choice, and two boxed reals. Per-gene mut_heterogeneous mutators keep each type in range. cx_heterogeneous mates the discrete prefix with cx_uniform and the boxed tail with cx_blend_bounded.

from functools import partial

import numpy
from deap_er import Fitness, Toolbox, creator, tools

tools.rng.seed(1234)  # disables randomization

# Mixed genome: on/off flag, integer count, material multiplier, two boxed reals.
MATERIALS = (0.5, 1.0, 1.5)
N_DISCRETE = 3
LOW, UP = 0.0, 1.0


def attr_flag():
    return tools.rng.randint(0, 1)


def attr_qty():
    return tools.rng.randint(1, 8)


def attr_material():
    return tools.rng.choice(MATERIALS)


def attr_real():
    return tools.rng.uniform(LOW, UP)


def flip_bit(gene):
    return 1 - int(gene)


def nudge_qty(gene):
    return int(min(8, max(1, gene + tools.rng.choice((-1, 1)))))


def redraw_material(_gene):
    return tools.rng.choice(MATERIALS)


def nudge_real(gene):
    return min(UP, max(LOW, gene + tools.rng.gauss(0.0, 0.15)))


MUTATORS = (flip_bit, nudge_qty, redraw_material, nudge_real, nudge_real)
CROSSOVERS = (
    (slice(0, N_DISCRETE), partial(tools.cx_uniform, cx_prob=0.5)),
    (slice(N_DISCRETE, None), partial(tools.cx_blend_bounded, alpha=0.5, low=LOW, up=UP)),
)


def evaluate(individual):
    flag, qty, material, quality, waste = individual
    value = (1.0 + 0.5 * flag) * qty * material * quality - waste
    return (value,)


def setup():
    creator.create_type("FitnessMax", Fitness, weights=(1.0,))
    creator.create_type("Individual", list, fitness=creator.FitnessMax)

    toolbox = Toolbox()
    toolbox.register(
        "individual",
        tools.init_cycle,
        creator.Individual,
        (attr_flag, attr_qty, attr_material, attr_real, attr_real),
    )
    toolbox.register("population", tools.init_repeat, list, toolbox.individual)
    toolbox.register("mate", tools.cx_heterogeneous, crossovers=CROSSOVERS)
    toolbox.register("mutate", tools.mut_heterogeneous, mutators=MUTATORS, mut_prob=0.3)
    toolbox.register("select", tools.sel_tournament, contestants=3)
    toolbox.register("evaluate", evaluate)

    stats_fit = tools.Statistics(lambda ind: ind.fitness.values[0])
    stats_keys = tools.Statistics(lambda ind: tuple(ind))
    mstats = tools.MultiStatistics(fitness=stats_fit, variety=stats_keys)
    mstats.register("avg", numpy.mean, chapters="fitness")
    mstats.register("max", numpy.max, chapters="fitness")
    mstats.register("dups", tools.duplicate_count, chapters="variety")

    return toolbox, mstats


def print_results(best_ind):
    print(f"\nBest widget: {best_ind}")
    print(f"Value: {best_ind.fitness.values[0]:.4f}")


def main():
    toolbox, mstats = setup()
    pop = toolbox.population(size=80)
    hof = tools.HallOfFame(1)
    tools.ea_simple(
        toolbox,
        pop,
        generations=20,
        cx_prob=0.5,
        mut_prob=0.4,
        hof=hof,
        stats=mstats,
        verbose=True,
    )
    print_results(hof[0])


if __name__ == "__main__":
    main()