Skip to content

feat: add --tournament-size to control NSGA selection pressure - #21

Merged
rasros merged 3 commits into
mainfrom
feat/tournament-size
Jul 28, 2026
Merged

feat: add --tournament-size to control NSGA selection pressure#21
rasros merged 3 commits into
mainfrom
feat/tournament-size

Conversation

@rasros

@rasros rasros commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Parent selection is a tournament, and its size was a literal 2 inside _tournament. That is the strongest lever on how hard the search biases toward visual quality — far stronger than the feasibility gate tuned in #19 — and it was the one selection knob not exposed.

Measured through the real select_parent, share of parents drawn from the better-scoring half of a 20-node pool:

--tournament-size 2:  80.6%   (default, unchanged)
--tournament-size 3:  91.8%
--tournament-size 4:  97.1%
--tournament-size 5:  98.7%

Exposed as an absolute count, not a fraction of the pool. Selection intensity depends only on the tournament size — the winner's expected quantile is about 1/(size+1) — so an absolute value means the same thing at any --pool-size and composes with it instead of reinterpreting it. Verified across pool sizes 5 to 100, where a fixed size converges on the theoretical value. A fraction would have been the intuitive choice and would have been wrong.

The default stays 2, so this is a pure addition with no behavioural change. Sharper selection converges faster but spends pool diversity, which also pulls --epoch-diversity transitions forward, and I have no real-run evidence about where that trade lands. What this does is make the question answerable: the value was previously unsweepable.

_tournament now samples min(tournament_size, len(candidates)) and takes the best by (rank, -crowding). At size 2 that is the same rule as the old explicit two-way comparison, differing only in which candidate wins when both rank and crowding tie — arbitrary either way, since sample order is random. Sizes below 2 are clamped: a tournament of one selects uniformly at random and would silently remove all selection pressure.

The nsga-only guard needed restructuring. It flagged a parameter as "set" when its value was not None, 0, or 0.0, which works only because every existing nsga-only flag defaults to zero — a default of 2 would have looked set on every run and broken all beam runs. It now compares against a defaults map, mirroring how the beam-only check already worked. Behaviour for the existing flags is unchanged, including that an explicit --epoch-variance 0 still counts as unset.

Tests cover the default, acceptance, rejection below 2, nsga-only rejection under --strategy beam, that a default value does not trip that guard, clamping, a size larger than the pool (random.sample raises otherwise), a single-node pool, and that a larger tournament measurably increases score bias.

README documents it next to the feasibility gate as the stronger of the two levers, with the measured percentages, why it is an absolute count, and the caveat that it is not perfectly orthogonal to pool size.

444 passed, 13 skipped, ruff clean.

@rasros
rasros merged commit ccdd6e3 into main Jul 28, 2026
1 check passed
@rasros
rasros deleted the feat/tournament-size branch July 28, 2026 15:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant