LEAP Cookbook

This is a collection of “recipes” in the spirit of the O’Reilly “Cookbook” series. That is, it’s a collection of common howtos and examples for using LEAP.

Enforcing problem bounds constraints

There are two overall types of bounds enforcement within EAs, soft bounds and hard bounds:

soft bounds

where the boundaries are enforced only at initialization, but mutation allows for exploring beyond those initial boundaries

hard bounds

boundaries are strictly enforced at initialization as well as during mutation and crossover. In the latter case this can be done by clamping new values to a given range, or flagging an individual that violates such constraints as non-viable by throwing an exception during fitness evaluation. (That is, during evaluation, exceptions are caught, which causes the individual’s fitness to be set to NaN and its is_viable internal flag set to false; then selection should hopefully weed out this individual from the population.)

Bounds for initialization

When initializing a population with genomes of numeric values, such as integers or real-valued numbers, the bounds for each gene needs to be specified so that we know in what range to initialize the genes.

For real-valued genomes, leap_ec.real_rep.create_real_vector() takes a list of tuples where each tuple is a pair of lower and upper bounds for each gene. For example, the following initializes a genome with three genes, where each gene is in the range [0, 1], [0, 1], and [-1, 100], respectively:

from leap_ec.real_rep import create_real_vector
bounds = [(0, 1), (0, 1), (-1, 100)]
genome = create_real_vector(bounds)

For integer-valued genomes, leap_ec.int_rep.create_int_vector() works identically. That is, create_int_vector accepts a collection of tuples of pairs of lower and upper bounds for each gene.

Enforcing bounds during mutation

That’s great for _initializing_ a population, but what about when we mutate? If no precautions are taken, then mutation is free to create genes that are well outside the initialization bounds. Fortunately for any numeric mutation operator, we can specify a bounds constraint that will be enforced during mutation. The functions leap_ec.int_rep.ops.mutate_randint(), leap_ec.int_rep.ops.binomial(), leap_ec.real_rep.ops.mutate_gaussian() accept a bounds parameter that, as with the initializers, is a list of tuples of lower and upper bounds for each gene. Mutations that stray outside of these bounds are clamped to the nearest boundary. numpy.clip is used to efficiently clip the values to the bounds.