The multiplexer problem is another extensively used GP problem. Basically, it trains a program to reproduce the behavior of an electronic multiplexer (mux). Usually, a 3-8 multiplexer is used (3 address entries, from A0 to A2, and 8 data entries, from D0 to D7), but virtually any size of multiplexer can be used.
This problem was first defined by Koza (see Reference).
The primitive set is almost the same as the set used in Parity. Three Boolean operators (and, or and not), imported from operator, and a specific if-then-else primitive, which return either its second or third argument depending on the value of the first one.
pset = gp.PrimitiveSet("MAIN", MUX_TOTAL_LINES, "IN")
pset.addPrimitive(operator.and_, 2)
pset.addPrimitive(operator.or_, 2)
pset.addPrimitive(operator.not_, 1)
pset.addPrimitive(if_then_else, 3)
pset.addTerminal(1)
pset.addTerminal(0)
As usual, we also add two terminals, a Boolean true and a Boolean false.
To speed up the evaluation, the computation of the input/output pairs is done at start up, instead of at each evaluation call. This pre-computation also allows to easily tune the multiplexer size, by changing the value of MUX_SELECT_LINES.
MUX_SELECT_LINES = 3
MUX_IN_LINES = 2 ** MUX_SELECT_LINES
MUX_TOTAL_LINES = MUX_SELECT_LINES + MUX_IN_LINES
# input : [A0 A1 A2 D0 D1 D2 D3 D4 D5 D6 D7] for a 8-3 mux
inputs = [[0] * MUX_TOTAL_LINES for i in range(2 ** MUX_TOTAL_LINES)]
outputs = [None] * (2 ** MUX_TOTAL_LINES)
for i in range(2 ** MUX_TOTAL_LINES):
value = i
divisor = 2 ** MUX_TOTAL_LINES
# Fill the input bits
for j in range(MUX_TOTAL_LINES):
divisor /= 2
if value >= divisor:
inputs[i][j] = 1
value -= divisor
# Determine the corresponding output
indexOutput = MUX_SELECT_LINES
for j, k in enumerate(inputs[i][:MUX_SELECT_LINES]):
if k: indexOutput += 2 ** j
outputs[i] = inputs[i][indexOutput]
After that, the evaluation function is trivial, as we have both inputs and output values. The fitness is then the number of well predicted outputs over the 2048 cases (for a 3-8 multiplexer).
def evalMultiplexer(individual):
func = toolbox.lambdify(expr=individual)
good = sum((func(*(inputs[i])) == outputs[i] for i in range(2 ** MUX_TOTAL_LINES)))
return good,
from deap import algorithms
from deap import base
from deap import creator
from deap import tools
from deap import gp
logging.basicConfig(level=logging.DEBUG, stream=sys.stdout)
def if_then_else(condition, out1, out2):
return out1 if condition else out2
# Initialize Multiplexer problem input and output vectors
MUX_SELECT_LINES = 3
MUX_IN_LINES = 2 ** MUX_SELECT_LINES
MUX_TOTAL_LINES = MUX_SELECT_LINES + MUX_IN_LINES
# input : [A0 A1 A2 D0 D1 D2 D3 D4 D5 D6 D7] for a 8-3 mux
inputs = [[0] * MUX_TOTAL_LINES for i in range(2 ** MUX_TOTAL_LINES)]
outputs = [None] * (2 ** MUX_TOTAL_LINES)
for i in range(2 ** MUX_TOTAL_LINES):
value = i
divisor = 2 ** MUX_TOTAL_LINES
# Fill the input bits
for j in range(MUX_TOTAL_LINES):
divisor /= 2
if value >= divisor:
inputs[i][j] = 1
value -= divisor
# Determine the corresponding output
indexOutput = MUX_SELECT_LINES
for j, k in enumerate(inputs[i][:MUX_SELECT_LINES]):
if k: indexOutput += 2 ** j
outputs[i] = inputs[i][indexOutput]
pset = gp.PrimitiveSet("MAIN", MUX_TOTAL_LINES, "IN")
pset.addPrimitive(operator.and_, 2)
pset.addPrimitive(operator.or_, 2)
pset.addPrimitive(operator.not_, 1)
pset.addPrimitive(if_then_else, 3)
pset.addTerminal(1)
pset.addTerminal(0)
creator.create("FitnessMax", base.Fitness, weights=(1.0,))
creator.create("Individual", gp.PrimitiveTree, fitness=creator.FitnessMax, pset=pset)
toolbox = base.Toolbox()
toolbox.register("expr", gp.genFull, pset=pset, min_=2, max_=4)
toolbox.register("individual", tools.initIterate, creator.Individual, toolbox.expr)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
toolbox.register("lambdify", gp.lambdify, pset=pset)
def evalMultiplexer(individual):
func = toolbox.lambdify(expr=individual)
good = sum((func(*(inputs[i])) == outputs[i] for i in range(2 ** MUX_TOTAL_LINES)))
return good,
toolbox.register("evaluate", evalMultiplexer)
toolbox.register("select", tools.selTournament, tournsize=7)
toolbox.register("mate", gp.cxUniformOnePoint)
toolbox.register("expr_mut", gp.genGrow, min_=0, max_=2)
toolbox.register("mutate", gp.mutUniform, expr=toolbox.expr_mut)
def main():
random.seed()
pop = toolbox.population(n=500)
hof = tools.HallOfFame(1)
stats = tools.Statistics(lambda ind: ind.fitness.values)
stats.register("Avg", tools.mean)
stats.register("Std", tools.std)
stats.register("Min", min)
stats.register("Max", max)
algorithms.eaSimple(toolbox, pop, 0.8, 0.1, 40, stats, halloffame=hof)
logging.info("Best individual is %s, %s", gp.evaluate(hof[0]), hof[0].fitness)
return pop, stats, hof
if __name__ == "__main__":
main()
Note
The import of the Python standard library modules are not shown.
John R. Koza, “Genetic Programming I: On the Programming of Computers by Means of Natural Selection”, MIT Press, 1992, pages 170-187.