Advanced Extensions

Add a Selection Scheme

Choosing which members of a scope become parents. Six edits across five files, and no strategy or genome is touched: selection is one of three independent axes, and it answers only this question.

Not part of the v0.9 release. This chain isn't finished and documented yet; only adding an objective is ready to use today. This note is removed once the chain is finished and documented.

The One Question a Scheme Answers

Selection takes a scope, a list of population indices someone else chose, and returns count parent indices drawn from it. It does not decide who is eligible ([scope] does that) and it does not decide who dies ([replacement] does that). That separation is why every scheme works with every strategy, and why there is no scheme-by-strategy compatibility check to extend.

Three contracts the signature does not enforce. Nothing fails to compile if you break one, and two of the three fail silently.

Drawing with replacement is allowed: the tournament does it, so the same individual can be both parents. That is deliberate, not an oversight to correct in a new scheme.

Selection is an enum, not a trait, so it cannot be extended from outside the crate. Unlike an objective, a genome or a strategy, a program that merely depends on GET gets the schemes GET ships and no others; adding one means editing your own copy. The steps below assume that is what you are doing.

What the User Ends Up Writing

A GET run is configured by a TOML document. See the Python: TOML File for its full shape, and Python: Config Objects for how to run one. Selection is one section of it. Adding a scheme called Roulette is what makes this valid:

[selection]
type     = "roulette"
pressure = 1.8

You never write the string "roulette" anywhere in the code. It is derived from your variant's name, converted to snake_case, so Roulette becomes roulette, MyScheme becomes my_scheme. Name the variant and the config name follows; the keys under it are your struct's field names, the same way.

This is not specific to the command-line route. The same document is read by the CLI and by the Rust library, and a Python caller builds the same section through a config object instead of typing it. Skipping the two optional Python steps below is what makes a scheme TOML-only.

The Six Steps

Every location carries a literal marker in the source, so you can jump to any step by searching its number. The line numbers below were correct when this page was written; if one has moved, the marker is the anchor that has not.

git grep -n "ADD A SELECTION STEP" get/src config.example.toml    # 6 steps, 7 sites

Each step is named at more than one site, because a marker points forward to the next; go by the step numbers rather than the number of lines the grep prints.

StepWhereWhat
1get/src/evolver/common.rs:75 A variant on enum Selection, carrying any parameters the scheme reads out of the config.
2get/src/evolver/common.rs:159 The arm in Selection::pick that actually chooses parents.
3get/src/config.rs:112 The mirrored variant on enum SelectionConfig. This is the one whose name becomes type = "roulette".
4get/src/dispatch.rs:714 The arm mapping your config variant onto the engine variant. Steps 3 and 4 are one change split across two files.
5get/src/py_config.rs:203 and :689 Optional. The Python-side variant, and its matching arm.
6config.example.toml:173 Optional. A commented-out [selection] block naming your scheme: only the scheme's own parameters, since the scope sizes itself.

Steps 1 and 2: The Engine Side

Both are in get/src/evolver/common.rs. Add the variant, then the arm that performs it:

// step 1: evolver/common.rs:75
Roulette { pressure: f64 },

// step 2: evolver/common.rs:159
Selection::Roulette { pressure } => {
    let mut parents = Vec::with_capacity(count);
    for _ in 0..count {
        parents.push(spin(scope, fitnesses, *pressure, rng));
    }
    parents
}

Three contracts your arm must honour, none of which the compiler checks:

Steps 3 and 4: The Config Side

One change, two files. The config variant, and the arm that maps it onto the engine variant:

// step 3: config.rs:112
Roulette { pressure: f64 },

// step 4: dispatch.rs:714
SelectionConfig::Roulette { pressure } => {
    Selection::Roulette { pressure: *pressure }
}

If your scheme has parameters that can be wrong (a pressure that must be positive, a size that must be at least one), constrain them in validate_evolution_and_selection, in get/src/config.rs. Only your own parameters: there is no scheme-by-strategy check to extend there.

Steps 5 and 6: Python, and Optional

Leaving these out costs nothing elsewhere; the scheme is then configurable from a TOML file but absent from the Python API. Step 5 is the mirrored variant on PySelectionConfig (get/src/py_config.rs:203) and its matching arm (:689). Write your own parameters and no other block's: the arm is building the [selection] table alone. Step 6 is a commented-out block in config.example.toml:173.

Step 5 is the one nothing checks. A scheme missing its Python mirror does not fail to compile and does not fail a test; it is simply absent from the Python API, and the first person to notice is a user who cannot find it.

Testing It

Selection is unusually easy to test directly: it is a pure function from a scope, a fitness slice and an RNG to a list of indices. Assert that every returned index is in the scope, that a fixed seed gives a fixed answer, and that a population with one obviously-best member selects it more often than chance.

Step 6 is the other half of that: the commented-out block you add to config.example.toml is parsed and validated by the test suite, so a block that does not parse fails the suite rather than a user's afternoon.

The Other Two Axes

If what you actually want is to change who is eligible or who gets overwritten, you want a different chain: [scope] and [replacement] are their own sections and their own marker families:

git grep -n "ADD A SCOPE STEP" get/src config.example.toml          # 6 steps, 7 sites
git grep -n "ADD A REPLACEMENT STEP" get/src    # 3 steps, 6 sites

See Add a Scope and Add a Replacement Policy.