Interactive companion · Chapters 7 and 8 · Genetic algorithms

Genetic Algorithm Lab

A population of candidate solutions evolves through selection, crossover and mutation, and the fittest pass on their genes. Every operator and example below is interactive.

Based on Chapters 7, “Genetic algorithms,” and 8, “Genetic algorithm variants,” in Alaa Khamis, Optimization Algorithms: AI techniques for design, planning, and control problems, Manning Publications, 2024 (ISBN 978-1633438835).

A live GA evolving a round trip through 20 US cities: order crossover plus inversion mutation.

Part 1 · Source of inspiration

Evolution as a search algorithm

Genetic algorithms mimic evolution: fitter individuals reproduce more, and offspring inherit a mix of their parents’ genes. A chromosome is a candidate solution, its positions are genes, and their values are alleles.

Chapter 7 of the book traces the GA back to John Holland’s simple GA and maps every term of natural evolution onto its GA counterpart.

Genotype and phenotype

The genotype is the combination of alleles; the phenotype is what it produces. Click an allele for each of the four genes and see the creature it encodes.

Encode a creature

4 genes · 3 × 4 × 4 × 3 = 144 genotypes

Population-based metaheuristics

Population-based metaheuristics repeatedly generate a new population and replace the old one; GAs build new solutions directly from existing ones.

Chapter 7 compares the main population-based metaheuristics and the search memory each one keeps.

Part 2 · Section 7.3

Genetic algorithm building blocks

Algorithm 7.1: Genetic algorithm Initialization: randomly generate an initial population M(0) Evaluate all individuals: compute f(m) for each m in M(t) while termination criteria are not met Select parents # selection probabilities p(m) Apply crossover # offspring from pairs of parents Apply mutation # random changes Check feasibility and repair Evaluate the new individuals Select survivors # form the next generation

The loop repeats selection, crossover, mutation and survivor selection until a stopping criterion is met.

Section 7.3 explains each building block of Algorithm 7.1 and the usual termination criteria.

Fitness function

GAs maximize fitness, so a minimization objective is transformed first, for example negated.

Section 7.3 gives the reciprocal and shifting transformations (equations 7.1 and 7.2).

Ticket pricing (equation 7.3):
profit(x) = −20x² + 6200x − 350,000,   75 ≤ x ≤ 235

An event organizer chooses the ticket price x.

Representation schemes

The main encodings are binary, value (real) and permutation. Set a range and precision to see how many bits a binary gene needs.

How many bits?

Binary encoding
Part 3 · Sections 7.3 and 7.4

Binary GA: the ticket pricing problem

Each price from 75 to 235 is an 8-bit chromosome. The panels start from the book’s population (Table 7.2):

Parent selection

Operators differ in selective pressure: high pressure converges fast but loses diversity. Pick one and click Select parents.

Section 7.4 works each selection operator by hand on Table 7.2.

Selection explorer

Table 7.2 population

Crossover

Crossover exchanges genes between two parents. Choose a type and watch the children.

Binary crossover

Figures 7.13 to 7.15

Erratum: in Figure 7.13, child 2 of the 1-point crossover should be 10001101 (141).

Mutation and survivor selection

Bit-flip mutation flips each gene whose random number falls below pm. Draw new numbers to try it.

Bit-flip mutation

Figure 7.16

The table forms the next generation with elitist survivor selection, which always keeps the best (Table 7.7).

Section 7.4 contrasts generational and steady-state GAs and the other survivor selection schemes.

Ticket pricing lab

A complete binary GA: dots are the population on the profit curve, gold is the best. Pick settings and Run.

Listings 7.2 and 7.3 solve this problem from scratch and with pymoo.

Binary GA lab

Maximize profit

Best profit so far (solid) and the population mean (faint).

Part 4 · Section 8.1

Gray-coded GA and the Hamming cliff

In binary, neighbors such as 7 = 0111 and 8 = 1000 can differ in every bit: a Hamming cliff. In Gray code they differ by one bit. Move the slider to see the XOR conversion.

Section 8.1 explains when Gray coding helps; try it in the ticket pricing lab.

Binary to Gray

XOR gates

Hamming distance between each value and the next, 0 to 15: binary (blue) jumps to 4 at 7→8; Gray (gold) is always 1.

Part 5 · Section 8.2

Real-valued GA

Real-valued genes suit continuous variables; crossover and mutation work on real numbers. Adjust the operators below.

Section 8.2 compares real and binary encodings and covers simulated binary crossover (SBX).

Arithmetic crossover

Mutation methods

Curve fitting

Fit y = ax³ + bx² + cx + d to five points (Table 8.2) by minimizing the mean squared error, then Run the lab below.

Curve fitting lab

Minimize MSE

log₁₀ of the best MSE per generation.

Mutation step size σ per generation (see Adaptive GA).

Part 6 · Sections 8.3 and 8.6

Permutation-based GA

In a permutation each value appears once, so special operators are needed. Pick one to see the children.

Section 8.3 works through each permutation operator by hand.

Crossover

Mutation

TSP of 20 major US cities (Listing 8.4)

Evolve a round trip from New York City. Choose the operators and Run; the dashed line is the book’s result.

Permutation GA lab

Minimize tour length

Best tour length per generation; dashed: the book’s optimum.

Part 7 · Section 8.4

Multi-objective optimization

X dominates Y if it is no worse in every objective and better in one. Non-dominated points form the Pareto front. Click a point.

Section 8.4 introduces Pareto optimality and surveys multi-objective approaches.

Pareto dominance

Click a point · both objectives minimized

NSGA-II on the manufacturing problem (Listing 8.3)

Trade cost against time imbalance. NSGA-II sorts solutions into non-dominated fronts and keeps diversity with the crowding distance. Run it and watch the front form.

Listing 8.3 sets up this problem and shows how to pick one trade-off.

minimize f₁ = 100x₁ + 150x₂ + 200y₁ + 50y₂  (total cost)
minimize f₂ = |(2x₁ + 4y₁) − (3x₂ + y₂)|  (time imbalance)
subject to x₁ + x₂ ≥ 80,   y₁ + y₂ ≥ 300

NSGA-II lab

Objective space

The gray crosses show the book’s ten reported solutions for comparison.
Part 8 · Section 8.5

Adaptive GA

Parameter control changes settings such as the mutation step σ during the run: deterministic, adaptive or self-adaptive. Compare the modes on curve fitting.

Section 8.5 gives the formula for each control mode.

Mutation step size during a run

Curve fitting

log₁₀ of the best MSE per generation, averaged over 5 runs.

Part 9 · Section 8.7

PID tuning with a GA

A PID controller sets u(t) = Kpe + Ki∫e dt + Kd de/dt. Tune the gains by hand, then let a GA do it.

Section 8.7 derives the HVAC closed loop, compares error criteria with Listing 8.5, and Table 8.6 shows how each gain shapes the response.

Step response lab

Tune by hand, then let the GA try

Part 10 · Section 8.8 and appendix C

More applications

Political districting

Group Toronto neighborhoods into balanced electoral districts.

Listing 8.6

Facility allocation

Choose facility locations and assignments with a GA.

Listing C.9

All ones

The classic warm-up for binary GAs: evolve a string of all 1s.

Listing C.10

Cargo bike loading

Load a cargo bike with the most valuable set of items within its capacity.

Listing C.11

Opencast mining

Plan which blocks of an opencast mine to extract.

Listing C.12

Part 11 · Code

Python in Google Colab

Open the book’s notebooks in Colab, or the companion notebook with the from-scratch code below.

Notebooks

From-scratch implementations

# Binary GA for the ticket pricing problem (Chapter 7, Sections 7.3 and 7.4)
# Maximize profit(x) = -20x^2 + 6200x - 350000 for 75 <= x <= 235, with 8-bit chromosomes.
import numpy as np

profit = lambda x: -20 * x * x + 6200 * x - 350000
BITS, LB, UB = 8, 75, 235
to_bits = lambda x: [int(b) for b in format(x, f"0{BITS}b")]
to_int = lambda bits: int("".join(map(str, bits)), 2)
gray = lambda b: [b[0]] + [b[i - 1] ^ b[i] for i in range(1, len(b))]      # binary -> Gray (XOR)
def from_gray(g):                                                            # Gray -> binary
    b = [g[0]]
    for i in range(1, len(g)):
        b.append(b[-1] ^ g[i])
    return b

# Table 7.2 population and the roulette wheel of Tables 7.4 and 7.5
pop = [77, 203, 110, 145, 230]
f = np.array([profit(x) for x in pop], dtype=float)
p = f / f.sum()
print("selection probabilities:", np.round(p, 2), " cumulative:", np.round(np.cumsum(p), 2))

# linear ranking with selection pressure SP (rank 1 = worst, N = best)
SP, N = 1.5, len(pop)
rank = np.argsort(np.argsort(f)) + 1
p_rank = (2 - SP + 2 * (SP - 1) * (rank - 1) / (N - 1)) / N
print("rank-based probabilities:", np.round(p_rank, 3))

# crossover and mutation on parents 77 and 145 (Figures 7.13 to 7.16)
P1, P2 = to_bits(77), to_bits(145)
c1, c2 = P1[:3] + P2[3:], P2[:3] + P1[3:]
print("1-point crossover:", to_int(c1), to_int(c2), "profits", profit(to_int(c1)), profit(to_int(c2)))
mut = P1.copy()
for j in (2, 4, 6):
    mut[j] ^= 1
print("mutated 77 ->", to_int(mut), "profit", profit(to_int(mut)))


def bga(pop_size=20, n_gen=60, pc=0.7, pm=0.05, selection="tournament", elite=1,
        use_gray=False, seed=0):
    rng = np.random.default_rng(seed)
    encode = (lambda x: gray(to_bits(x))) if use_gray else to_bits
    decode = (lambda c: to_int(from_gray(c))) if use_gray else to_int
    fit = lambda c: profit(decode(c)) if LB <= decode(c) <= UB else -1e9   # infeasible: rejected
    P = [encode(int(x)) for x in rng.integers(LB, UB + 1, pop_size)]
    history = []
    for g in range(n_gen):
        F = np.array([fit(c) for c in P])
        order = np.argsort(-F)
        history.append(F[order[0]])

        def pick():
            if selection == "roulette":
                w = F - F.min() + 1e-9
                return P[rng.choice(pop_size, p=w / w.sum())]
            if selection == "rank":
                r = np.argsort(np.argsort(F)) + 1
                w = (2 - SP + 2 * (SP - 1) * (r - 1) / (pop_size - 1)) / pop_size
                return P[rng.choice(pop_size, p=w / w.sum())]
            if selection == "random":
                return P[rng.integers(pop_size)]
            k = rng.choice(pop_size, 3, replace=False)                    # tournament, k = 3
            return P[k[np.argmax(F[k])]]

        nxt = [P[i].copy() for i in order[:elite]]                          # elitism
        while len(nxt) < pop_size:
            a, b = pick().copy(), pick().copy()
            if rng.random() < pc:                                           # 1-point crossover
                cut = rng.integers(1, BITS)
                a, b = a[:cut] + b[cut:], b[:cut] + a[cut:]
            for child in (a, b):
                for j in range(BITS):                                       # bit-flip mutation
                    if rng.random() < pm:
                        child[j] ^= 1
                nxt.append(child)
        P = nxt[:pop_size]
    best = max(P, key=fit)
    return decode(best), fit(best), history


x, best_profit, hist = bga(seed=1)
print(f"optimal ticket price: ${x}, profit: ${best_profit:,.0f}")      # $155, $130,500

Open the companion notebook in Colab, or paste a tab’s code into any notebook.

Part 12 · Summary

What to remember

Building blocks

Selective pressure trades convergence speed against diversity.

Encodings

Match the encoding (binary, Gray, real, permutation) to the problem.

Many objectives

Conflicting objectives yield a Pareto front, not one answer.

Adaptation

Parameter control shifts the search from exploration to exploitation.

References: J. H. Holland, Adaptation in Natural and Artificial Systems, 1975. A. E. Eiben, R. Hinterding and Z. Michalewicz, “Parameter control in evolutionary algorithms,” 1999. K. Deb, Multi-objective Optimization Using Evolutionary Algorithms, 2001. E. Zitzler, Evolutionary Algorithms for Multiobjective Optimization, 1999. E.-G. Talbi, Metaheuristics: From Design to Implementation, 2009. A. Khamis, Optimization Algorithms: AI techniques for design, planning, and control problems, Manning, 2024, Chapters 7 and 8.

Part 13 · Quiz

Check your understanding

Two quick questions on Chapters 7 and 8. Pick an answer to check it.

Q1. What is the main risk of a parent selection operator with very high selective pressure?

Q2. Why does a permutation-based GA for the TSP need special crossover operators such as order crossover instead of ordinary 1-point crossover?

For more exercises with full solutions, see Appendix C, “Exercises and solutions,” in Optimization Algorithms. The coding solutions are also on GitHub (Chapter 7 and Chapter 8).