genoxide's Python package, from this repository's python/ folder: genoxide's algorithms, in Rust, calling fitness functions in Python and numpy. Its docs are python/README.md and the docstrings of python/genoxide/__init__.py.
The package runs the three problems of the matched suite with the same Rust implementations as the Rust library (genoxide.md): gx.Ga on OneMax 1000, gx.De on Rastrigin 30 and gx.Cmaes on Rosenbrock 10, set to the definitions of rule 6. A seed gives the same run as in Rust; what differs is the cost of calling Python fitness functions. Rule 6.6 applies, as for the Rust library.
Adapter: benchmarks/adapters/genoxide_python/. Found a setting that brings the package closer to a definition, or a difference this page misses? Open a benchmark issue.
- Fitness functions: numpy with
batch=True, a generation per call, for every problem (bench.py, lines 63-136): python/README.md's way to a fast fitness function ("Vectorized numpy [...] pays its cost per call once per generation, not once per genome", and its Rastrigin example), and what rule 3.4 asks of a Python library with a documented batch interface, as the PyGAD and pymoo adapters do. OneMax is README'sbits.sum()over the rows,x.sum(axis=1). A batch doesn't change the algorithm ("The same seed repeats a run exactly: one genome at a time, in batches or in parallel").bench.py --self-checkcompares every function with problems.py. - Evaluations: each function counts the rows of its batch, and records the first hit: the first row, in the batch's order, that reaches the target, numbered from the start of the run (the package asks for the same genomes in the same order either way). Any difference from
result.evaluationsis printed to stderr (solve, lines 222-287). - Stop:
run(..., target=..., evaluations=..., time=...), after every generation: the time cap is the package'stimestop, the time left of the run. No method has a convergence criterion or restarts. - Stalls (rule 2.2): a GA child identical to a parent isn't evaluated, so a GA can run generations with nothing to evaluate, and a batch function isn't called for them. The GA's
on_generationcallback, the package's only hook after every generation, counts them: after 10 in a row it returns False, which ends the attempt, and the adapter starts it again with the seeds of rule 2.2 and printsrestarts. DE and CMA-ES evaluate every trial and sample, and run without a callback. The callback costs the GA about 17% of its time on OneMax: the package builds theProgressit gets, with the population as a 300 × 1000 array, after every generation, though the adapter reads none of it. - Time: from before the algorithm object is created to the return of
run, which creates the initial population. - One thread:
paralleloff; numpy's BLAS set to 1 thread before import. - Seeds: the algorithm's
seed; a seed repeats a run exactly. - Separate tests: 2026-09-28, the package 0.9.1 built from this repository, seeds 0 to 2, the scenario's budget and cap. The runs were the Rust adapter's, evaluation for evaluation; the counts equalled
result.evaluations, andoutsidewas 0. 2026-09-29, the package 0.9.2 from this repository, after OneMax moved to a batch function and the GA's time cap totime: seeds 0 to 2 of every problem gave the same generations, evaluations, best values and first hits as before, andrun.py checkpassed.
Configuration (onemax_ga, lines 146-161), against rule 6.2: gx.Ga(gx.Binary(1000), population_size=300, select=gx.Tournament(3), crossover=gx.PointCrossover(2), crossover_rate=0.5, mutation=gx.BitFlip(rate=1/1000), mutation_rate=0.2, scheme=gx.Generational(elitism=0)), the Rust library's settings (genoxide.md).
Differences: as in Rust, a child identical to its parent isn't evaluated again.
Separate tests: 3 of 3 reached the target, first hits at 52,465 to 58,170 evaluations (median 52,659). With a batch function, about 0.04 s each; with the function per genome the adapter used before 2026-09-29, and the time cap in the callback, about 0.08 s, for the same runs.
Configuration (rastrigin_de, lines 164-178), against rule 6.3: gx.De(gx.Real((-5.12, 5.12), length=30), population_size=100, strategy="rand1", control={"f": 0.5, "cr": 0.9}, restarts="never"), the Rust library's De (genoxide.md).
Difference: as in Rust, a trial gene outside the box is set halfway between the target's gene and the bound, not drawn again uniformly.
No target: every run uses the fixed budget of 300,000 evaluations (rule 6.3); the adapter passes no target to run and prints "target": null.
Separate tests: seeds 0 and 1 took 0.19 and 0.18 s for the 300,000 evaluations and ended at errors of 154.7 and 123.1, the Rust adapter's runs.
Configuration (rosenbrock_cmaes, lines 181-188), against rule 6.4: gx.Cmaes(gx.Real((-5.0, 10.0), length=10), restarts="never") with its defaults: 10 samples, the best 5 with positive log weights, Hansen's 2016 learning rates, a uniform mean and a step of 0.3 of the range (genoxide.md).
Differences: as in Rust: a sample outside the box is drawn again, up to 100 times, then clipped; the search works in coordinates scaled to 0..=1, the same scaling for every gene here.
Separate tests: 3 of 3 reached the target, first hits at 5,837 to 7,451 evaluations (median 5,844), in about 8 ms each.
Nothing: the package has all three methods.
None.