Optimisation · Genetic Algorithms · Evolution
📅 Квітень 2026 ⏱ ≈ 14 хв читання 🎯 Beginner–Intermediate

Evolutionary Algorithms — From Biology to Code

Darwinian evolution — variation, selection, and heredity — is one of the most powerful optimisation processes in nature. Evolutionary algorithms (EAs) abstract this process into code to solve hard combinatorial and continuous optimisation problems. They require no gradient information and can escape local optima. This article covers Genetic Algorithms, Evolution Strategies, CMA-ES, and Differential Evolution with complete JavaScript examples.

1. The Evolutionary Computation Landscape

All EAs maintain a population of candidate solutions and iteratively improve it using nature-inspired operators. The main families are:

Genetic Algorithms (GA)

Binary or permutation chromosomes. Crossover-dominant. Good for combinatorial problems, feature selection, hyperparameter search.

Evolution Strategies (ES)

Real-valued parameters + adaptive step sizes. Mutation-dominant. Strong for continuous black-box optimisation.

CMA-ES

Estimates the full covariance matrix of promising solutions. State-of-the-art for medium-dimensional (n ≤ 1000) continuous optimisation.

Differential Evolution (DE)

Creates new candidates by combining three random population members. Very simple to implement, strong on noisy landscapes.

The generic loop shared by all EAs:

1. Initialise population P of N individuals randomly 2. Evaluate fitness f(x) for each individual x ∈ P 3. Select parents (proportional to fitness, tournament, rank, …) 4. Apply variation (crossover + mutation) → offspring P′ 5. Replace all or part of P with P′ 6. If termination criterion met → return best individual; else goto 2

2. Genetic Algorithms — Representation & Operators

Representation

A chromosome encodes a candidate solution. Common encodings include:

Crossover

Crossover combines genes from two parents. For permutation representations, standard crossover breaks the ordering constraint; specialised operators are needed:

Mutation

Mutation introduces diversity to escape local optima. For permutations:

3. Selection Pressure and Fitness Scaling

Tournament selection: pick k individuals at random and keep the best. k = 2 is common — easy to implement, controllable pressure (larger k = more pressure).

Roulette wheel (fitness-proportionate) selection: each individual is selected with probability proportional to its fitness. Susceptible to domination by a single super-fit individual early in the run (premature convergence).

Rank-based selection: sort by fitness; assign selection probability by rank position rather than raw fitness. More robust than roulette; controllable linear or exponential rank scaling.

Elitism: always copy the best 1–5% of individuals unchanged into the next generation. Guarantees that the best solution found so far is never lost. Small elitism fractions (1–2%) consistently improve performance with negligible cost.

4. Evolution Strategies and Self-Adaptation

Evolution Strategies (ES) work directly in ℝⁿ with Gaussian mutation. Each individual carries both the parameter vector x and a step-size vector σ that evolves alongside it:

x′ᵢ = xᵢ + σᵢ · N(0, 1) // uncorrelated mutations per dimension σ′ᵢ = σᵢ · exp(τ₀ · N(0,1) + τ · Nᵢ(0,1)) // log-normal self-adaptation

τ₀ = 1/√(2n), τ = 1/√(2√n) // standard learning rates

The common notation (μ + λ)-ES maintains μ parents and generates λ offspring; all μ + λ individuals compete for the next generation. (μ, λ)-ES discards all parents, enforcing more aggressive exploration.

Rechenberg's 1/5 success rule is a simpler alternative: if more than 20% of mutations improve fitness, increase σ by a factor of 1.22; below 20%, decrease by the same factor. When no self-adaptation is available, this rule maintains a good step size automatically.

5. CMA-ES — Covariance Matrix Adaptation

CMA-ES (covariance matrix adaptation evolution strategy, Hansen & Ostermeier 2001) is the current gold standard for derivative-free continuous optimisation up to a few hundred dimensions. It learns the full covariance matrix of the search distribution:

x_k ~ m + σ · N(0, C) // sample from multivariate Gaussian

Update rules (simplified): m′ = weighted mean of top-μ offspring σ′ = σ · exp(cumulative step-size adaptation) C′ = (1-c₁-cμ)C + c₁(rank-1 update) + cμ(rank-μ update)

The mathematical details are involved, but the high-level intuition is elegant: C captures the shape of the fitness landscape locally. After many iterations, the eigenvectors of C align with the "easy directions" of the landscape, allowing CMA-ES to make much more effective steps than a fixed spherical distribution.

CMA-ES has O(n²) cost per generation and is recommended for n ≤ 200–300. For higher dimensions, separable CMA-ES (diagonal C only) extends it to thousands of variables.

6. JavaScript: GA for the Travelling Salesman Problem

// TSP: find the shortest tour through N cities
// Chromosome: permutation of city indices [0 .. N-1]

function tourDistance(cities, tour) {
  let d = 0;
  for (let i = 0; i < tour.length; i++) {
    const a = cities[tour[i]];
    const b = cities[tour[(i + 1) % tour.length]];
    d += Math.hypot(b.x - a.x, b.y - a.y);
  }
  return d;
}

// Order Crossover (OX)
function oxCrossover(p1, p2) {
  const n  = p1.length;
  const i1 = Math.floor(Math.random() * n);
  const i2 = Math.floor(Math.random() * n);
  const [lo, hi] = [Math.min(i1, i2), Math.max(i1, i2)];
  const segment = new Set(p1.slice(lo, hi));
  const child = p1.slice(lo, hi);
  for (const gene of p2) if (!segment.has(gene)) child.push(gene);
  return [...child.slice(n - lo), ...child.slice(0, n - lo)];
}

// 2-opt inversion mutation
function mutate(tour, rate) {
  if (Math.random() > rate) return tour;
  const i = Math.floor(Math.random() * tour.length);
  const j = Math.floor(Math.random() * tour.length);
  const [lo, hi] = [Math.min(i, j), Math.max(i, j)];
  return [
    ...tour.slice(0, lo),
    ...tour.slice(lo, hi + 1).reverse(),
    ...tour.slice(hi + 1),
  ];
}

function runGA(cities, { popSize = 100, generations = 500, mutRate = 0.1, eliteN = 5 } = {}) {
  const n = cities.length;

  // Initialise with random permutations
  let pop = Array.from({ length: popSize }, () =>
    [...Array(n).keys()].sort(() => Math.random() - 0.5));

  for (let gen = 0; gen < generations; gen++) {
    // Evaluate (lower distance = higher fitness)
    pop.sort((a, b) => tourDistance(cities, a) - tourDistance(cities, b));

    // Elitism: keep top eliteN unchanged
    const nextPop = pop.slice(0, eliteN);

    while (nextPop.length < popSize) {
      // Tournament selection (k=3)
      const tournament = () => {
        let best = pop[Math.floor(Math.random() * pop.length)];
        for (let k = 1; k < 3; k++) {
          const c = pop[Math.floor(Math.random() * pop.length)];
          if (tourDistance(cities, c) < tourDistance(cities, best)) best = c;
        }
        return best;
      };
      const child = mutate(oxCrossover(tournament(), tournament()), mutRate);
      nextPop.push(child);
    }
    pop = nextPop;
  }
  return pop[0];   // best tour
}

// Example: 10 random cities
const cities = Array.from({ length: 10 }, () =>
  ({ x: Math.random() * 100, y: Math.random() * 100 }));
const best = runGA(cities);
console.log('Best tour:', best);
console.log('Distance:', tourDistance(cities, best).toFixed(2));

7. Common Pitfalls and Tuning

Premature Convergence

The population loses diversity and gets stuck in a local optimum before finding the global one. Remedies: reduce selection pressure (smaller tournament size); increase mutation rate; use geographic niching (island model with migration); restart. Population diversity can be monitored cheaply by computing average pairwise Hamming distance.

Bloat (Genetic Programming)

In grammatical or tree-based representations, programs grow without bound ("bloat") while fitness stagnates. Parsimony pressure (penalising longer programs) or strict size limits prevent this.

Choosing Hyperparameters

8. Applications

Neural Architecture Search (NAS)

Evolutionary methods (NEAT, regularised evolution) discover neural network topologies and weights simultaneously — encoding network graph structure as a chromosome. Google's AmoebaNet found state-of-the-art image classifiers with evolutionary search.

Drug Discovery

Molecular structures are represented as SMILES strings or molecular graphs. EAs explore chemical space by mutating functional groups and crossing molecular scaffolds, guided by predicted binding affinity from a surrogate model (often a graph neural network).

Scheduling and Logistics

Job-shop scheduling, vehicle routing, satellite observation scheduling, and airline crew pairing are all complex combinatorial problems where GAs with domain-specific operators are competitive with (or superior to) specialised exact solvers.

See it in action

Watch a genetic algorithm evolve a population of creatures that learn to navigate obstacles — fitness landscape updates live as the run progresses.

Open simulation →