Skip to content

Repository files navigation

timetabling — swarms, genes, and the cost of combining them

CI Python 3.11+ License: MIT

Three ways to build a university timetable — particle swarm optimisation, a genetic algorithm, and the hyperheuristic that runs one after the other — implemented so you can tell whether the hybrid actually earns its keep.

If you came to use it, start at Install. If you came to learn how metaheuristics are honestly compared, start at The problem and read straight through.

Final quality over 30 seeds

Thirty seeds per method. The hybrid matches the genetic algorithm's median while spending about twice the objective evaluations — a negative result the original single-run report could not have produced. Reproduce with python scripts/make_figures.py.

Written in 2017 for the undergraduate course Proyecto Integrador at Universidad de Antioquia by John Edisson Tapias Zarrazola and Carlos A. Peña Montenegro. The original report (Spanish) is in docs/; the original scripts are one git checkout away.


Install

pip install git+https://github.com/Kemquiros/timetabling-metaheuristics

No runtime dependencies. Python 3.11 or newer.

from timetabling import CourseCode, Instance, SwarmConfig, pso

instance = Instance(
    groups=4,
    slots=4,
    teachers=8,
    catalogue={1: CourseCode(teacher=1, subject=2), 2: CourseCode(teacher=1, subject=4)},
)

result = pso.optimise(instance, SwarmConfig(iterations=60), seed=42)
print(result.best_fitness, result.evaluations)  # penalty, and what it cost to get there

The problem

A timetable assigns one course to each (group, time slot) pair. With 4 groups and 4 slots that is 16 decisions; with 18 available courses the space holds 18¹⁶ ≈ 1.2 × 10²⁰ timetables.

Enumerating that is not slow — it is impossible. This is the regime metaheuristics exist for: no gradient, no structure to exploit, and far too many candidates to visit. You are not going to find the optimum. You are going to find a good one, and be honest about how much you paid.

Every point in the space is a syntactically valid timetable. The difficulty is not building a legal encoding; it is that most legal timetables are bad. Quality is measured by three penalties, summed, lower being better:

Term What it punishes Why it matters
group_clashes the same course twice for one group this is what makes a timetable wrong, not merely awkward
idle_teachers leaving teachers on the roster unused a timetable that overworks two people and idles six is unusable in practice
load_imbalance leaning on a handful of courses spreads demand across the catalogue

A modelling choice worth arguing with. The three terms are summed unweighted, so one scheduling clash counts exactly as much as one unit of imbalance. That is almost certainly wrong — a clash is a hard failure, imbalance is a preference — but it is preserved from the 2017 formulation so results stay comparable. If you extend this, weighting the terms is the first experiment to run, and the README should be updated with what you find.


The three searches

1. Particle swarm — momentum through a continuous shadow

The intuition. Scatter particles across the space. Each remembers the best place it has been; each also knows the best place its neighbours have been. Every step, a particle is pulled toward both, and keeps some of its previous velocity:

$$v \leftarrow \chi\Big(v + c_1 r_1 (p_{\text{best}} - x) + c_2 r_2 (l_{\text{best}} - x)\Big), \qquad x \leftarrow x + v$$

The part that is easy to get wrong. A timetable is discrete — course codes are integers. But a swarm restricted to integers cannot accumulate velocity: every step rounds away the momentum, and the method collapses into random search wearing a costume.

So the positions here are continuous, and rounded onto course codes only at the moment of evaluation. The swarm flies through a continuous shadow of a discrete problem. That indirection is the whole trick, and it is why PSO works on combinatorial problems at all.

Why constriction = 0.72984. With $c_1 + c_2 = 4.1$, Clerc and Kennedy's factor $\chi = 0.72984$ is the value that provably keeps the swarm from exploding. It is not a tuning knob: change $c_1$ or $c_2$ and $\chi$ must be recomputed. That is why they live in one SwarmConfig object instead of as loose arguments, and why the config raises if $c_1 + c_2 \le 4$.

2. Genetic algorithm — recombining partial answers

The intuition. Keep a population. Repeatedly: pick good parents, mix them, mutate a little, replace the population. Good sub-timetables spread; bad ones die out.

Two design choices, and their reasons:

  • Uniform crossover, not single-point. Single-point crossover assumes neighbouring genes belong together. Here a gene is a (group, slot) pair — a contiguous slice carries no more meaning than a scattered one, so cutting at one point buys nothing.
  • Tournament selection, not fitness-proportionate. The objective is an unbounded penalty, not a probability. Proportionate selection would need a transformation whose scale silently controls the selection pressure — a hidden hyperparameter. Tournaments only need the ordering.

Elitism carries the best individuals through untouched, which is what makes the history monotone: the incumbent can never get worse. The test suite asserts exactly that.

3. The hybrid — and the comparison that keeps it honest

Run the swarm; hand its best timetable to the genetic algorithm as a seed. The swarm explores the continuous relaxation and finds promising basins fast; the GA then works in the discrete space, where crossover can recombine partial timetables the swarm has no way to express.

Here is the part most reports get wrong. A hybrid that runs 60 swarm iterations and 60 generations has spent roughly twice the objective evaluations of either component alone. If you compare "hybrid at 60 iterations" against "PSO at 60 iterations" and the hybrid wins, you have learned nothing: you gave it twice the budget.

That is why every result here carries evaluations:

result.evaluations  # objective evaluations spent — the real currency
result.iterations  # loop count — NOT comparable across methods

Compare at equal evaluations, never at equal iterations. If the hybrid still wins, the combination is doing real work. This single field is the difference between an experiment and an advertisement.

Iterations versus evaluations

Left: the hybrid's first phase is the swarm run — same seed, same particles — so the two curves coincide until the genetic stage begins, which is why PSO is drawn as a wide translucent underlay. Right: the same runs charged for what they spent.

And when you do compare fairly, the hybrid does not win

Over thirty seeds on the 2017 instance:

Method Median penalty Best Worst Relative cost
PSO 2.143 0.600 3.333
GA 0.333 0.143 2.143 ≈1×
PSO → GA 0.333 0.000 1.600 ≈2×

The hybrid matches the genetic algorithm's median while spending roughly twice the evaluations. It reaches a perfect timetable on its best seed, which the GA alone never does, and its worst case is better — so it buys reliability, not average quality.

That is a modest, honest claim. It is also the opposite of what a single run compared at equal iterations would have suggested, and it is the reason this repository reports evaluations at all.


Reading a result

result.best  # the timetable
result.best_fitness  # total penalty; 0 would be perfect
result.evaluations  # what it cost
result.history  # best-so-far after each iteration

Plot history for all three methods on the same axes, with evaluations on the x-axis rather than iterations. That plot is the actual finding of this project, and it looks very different from the same plot drawn against iterations.


The 2017 experiments

The original runs are preserved in legacy/experiments/ with their parameters and convergence plots, and the report is in docs/ (Spanish).

Treat them as a record, not as evidence. They were single runs, without seeds, compared at equal iterations rather than equal evaluations — so they cannot settle whether the hybrid beats plain PSO. Reproducing them properly under the protocol above is the obvious next piece of work, and it is stated as open here rather than quietly implied to be settled.


Development

git clone https://github.com/Kemquiros/timetabling-metaheuristics && cd spso-ga
pip install -e ".[dev]"

pytest                            # 19 tests
ruff check .                      # lint
mypy                              # strict type checking
python scripts/make_figures.py    # regenerate the figures, and print the seed table

Tests assert properties, not outputs: that a seeded run reproduces, that elitism makes the incumbent monotone, that the hybrid is never worse than its own swarm stage, that the evaluation count matches the arithmetic of the loop.


Provenance

The original coursework is preserved as an annotated git tag rather than a directory, so the repository reads as a tool while the evidence stays one command away:

git checkout coursework-2017
2017 Now Why
Python 2, from __future__ import division, from random import * Python 3.11+, explicit imports the scripts run on no supported interpreter
constrain2 summed per-teacher occurrence counts counts distinct teachers the sum always equalled the length of the solution vector, so the term was the same constant for every candidate and applied no selection pressure at all — one of the three constraints was inert
Instance hard-coded as module-level args and param dicts Instance and config dataclasses, validated no second instance could be run without editing the source
No seeding explicit seed on every search an unseeded metaheuristic result cannot be reproduced by a reader
Cost reported as iterations evaluations reported alongside iterations are not comparable between a swarm and a hybrid
Constriction factor and coefficients as loose globals one SwarmConfig, validated $\chi$ is only valid for a matching $c_1 + c_2$

Original coursework: John Edisson Tapias Zarrazola and Carlos A. Peña Montenegro, Universidad de Antioquia, 2017.

Where to go next

  • Clerc & Kennedy (2002), The particle swarm — explosion, stability, and convergence in a multidimensional complex space — where the constriction factor comes from, and why.
  • Eiben & Smith, Introduction to Evolutionary Computing — the standard text; chapters 3–6.
  • Burke et al. (2013), Hyper-heuristics: a survey of the state of the art — the framing this project's PSO→GA pipeline belongs to.

Citation

See CITATION.cff, or:

Tapias Zarrazola, J. E. & Peña Montenegro, C. A. timetabling: particle swarm, genetic and hybrid metaheuristics for university timetabling. Version 1.0.0, 2026. https://github.com/Kemquiros/timetabling-metaheuristics

License

MIT — see LICENSE.

About

Particle swarm, genetic algorithm and hybrid hyperheuristic for university timetabling — with the one field most papers omit: the objective evaluations each method actually spent, so hybrids can be compared at equal budget instead of equal iterations.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages