Skip to content

Homologous Crossover

Columnar symbolic regression with gp.cx_homologous instead of one-point crossover. Semantic crossover remains in the semantic variation example. See roadmap item 39.

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

tools.rng.seed(1234)  # disables randomization

COLUMNS = ["first", "second"]
ROWS = 48


def make_columns():
    steps = numpy.linspace(0.0, 4.0, ROWS)
    first = numpy.sin(steps)
    second = numpy.cos(steps * 0.7)
    return tuple(numpy.ascontiguousarray(c, dtype=numpy.float64) for c in (first, second))


def evaluate(individual, toolbox, columns):
    func = toolbox.compile(expr=individual)
    predicted = numpy.asarray(func(*columns), dtype=numpy.float64)
    target = columns[0] + columns[1]
    valid = numpy.isfinite(predicted) & numpy.isfinite(target)
    if valid.sum() < ROWS // 2:
        return (1.0e6,)
    return (float(numpy.mean((predicted[valid] - target[valid]) ** 2)),)


def setup(columns):
    pset = gp.columnar_pset(COLUMNS, window=(2, 8))
    creator.create_type("FitnessMin", Fitness, weights=(-1.0,))
    creator.create_type("Individual", gp.PrimitiveTree, fitness=creator.FitnessMin)

    toolbox = Toolbox()
    gp.register_gp(
        toolbox,
        pset,
        individual=creator.Individual,
        min_depth=1,
        max_depth=3,
        height_limit=8,
        select=False,
    )
    toolbox.register("mate", gp.cx_homologous)
    toolbox.register("evaluate", evaluate, toolbox=toolbox, columns=columns)
    toolbox.register("select", tools.sel_tournament, contestants=3)

    stats = tools.Statistics(lambda ind: ind.fitness.values)
    stats.register("min", numpy.min)
    return toolbox, stats


def print_results(best_ind):
    if best_ind.fitness.values[0] >= 0.05:
        raise RuntimeError("Homologous crossover failed to reach the seed-fixed MSE floor.")
    print(f"\nBest program: {best_ind}")
    print(f"MSE: {best_ind.fitness.values[0]:.6g}")


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


if __name__ == "__main__":
    main()