feat: add --tournament-size to control NSGA selection pressure - #21
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Parent selection is a tournament, and its size was a literal
2inside_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: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-sizeand 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-diversitytransitions 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._tournamentnow samplesmin(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, or0.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 0still 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.sampleraises 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.