Oxigen
Fast, parallel, extensible and adaptable genetic algorithms framework written in Rust
Install / Use
/learn @Martin1887/OxigenREADME
oxigen
Oxigen is a parallel genetic algorithm framework implemented in Rust. The name comes from the merge of OXIdación (Rust translated to Spanish) and GENetic.
The changes introduced in each version can be found in CHANGELOG.md.
To migrate between major version check the migration guide (MIGRATE.md).
Oxigen provides the following features:
- Fast and parallel genetic algorithm implementation (it solves the N Queens problem for N=255 in few seconds). For benchmarks view benchmarks section of this file.
- Customizable mutation and selection rates with constant, linear and quadratic functions according to generations built-in (you can implement your own functions via the
MutationRateandSelectionRatetraits). - Customizable age unfitness of individuals, with no unfitness, linear and quadratic unfitness with threshold according to generations of the individual built-in (you can implement your own age functions via the
Agetrait). - Accumulated
Roulette,TournamentsandCupbuilt-in selection functions (you can implement your own selection functions via theSelectiontrait). SingleCrossPoint,MultiCrossPointandUniformCrossbuilt-in crossover functions (you can implement your own crossover function via theCrossovertrait).- Many built-in survival pressure functions. You can implement your own survival pressure functions via the
SurvivalPressuretrait. Nichesbuilt-inPopulationRefitnessfunction. You can implement your own population refitness functions via thePopulationRefitnesstrait.SolutionFound,GenerationandProgressand more built-in stop criteria (you can implement your own stop criteria via theStopCriteriontrait).Genotypetrait to define the genotype of your genetic algorithm. Whatever struct can implement theGenotypetrait under the following restrictions:- It has a
iterfunction that returns ause std::slice::Iterover its genes. - It has a
into_iterfunction that consumes the individual and returns ause std::vec::IntoIterover its genes. - It has a
from_iterfunction that set the genes from an iterator. - It implements
Display,Clone,SendandSync. - It has functions to
generatea random individual, tomutatean individual, to get thefitnessof an individual and to know if and individualis_solutionof the problem.
- It has a
- Individual's fitness is cached to not do unnecessary recomputations (this can be disabled with
.cache_fitness(false)if your fitness function is stochastic and so you need to recompute fitness in each generation). - Progress statistics can be configured to be printed every certain number of generations to a file.
- Population individuals with their fitnesses can be configured to be printed every certain number of generations to a file.
- Specific initial individuals can be inserted in the genetic algorithm execution.
- Genetic executions can be resumed using the population of the last generation as initial population.
- Coevolution is possible executing little genetic algorithm re-executions inside the fitness function.
Optional feature global_cache
The optional feature global_cache adds a HashMap saving the evaluation of each individual in the full execution.
This cache is useful when the evaluation of each individual is expensive, and it complements the individual-based cache already existing in previous versions (if an individual has been evaluated it is not reevaluated unless cache_fitness is false). In other words, this global cache saves the evaluation of new individuals that are equal to another individual that was evaluated before.
Note that the global cache is not always better, since if the fitness function is cheap the cost of getting and inserting into the cache can be more expensive than it. Take also into account the increase of RAM usage of the global cache.
To enable the global cache add the feature global_cache in the Cargo.toml of your project and set to true the cache_fitness (always true by default) and global_cache (true by default when the global_cache is enabled) properties of your GeneticExecution. Example of Cargo.toml:
[dependencies]
oxigen = { version="2.1", features=["global_cache"] }
Usage
In your Cargo.toml file add the oxigen dependency. Oxigen follows the semver specification for the names of the versions, so major version changes will never break the existent API and the last version should always be used. If a minimum version is required specify that minor version to include that version and all minor versions bigger than it.
[dependencies]
oxigen = "2"
To use oxigen use oxigen::prelude::* and call the run method over a GeneticExecution instance overwriting the default hyperparameters and functions following your needs:
let n_queens: u8 = std::env::args()
.nth(1)
.expect("Enter a number between 4 and 255 as argument")
.parse()
.expect("Enter a number between 4 and 255 as argument");
let progress_log = File::create("progress.csv").expect("Error creating progress log file");
let population_log = File::create("population.txt").expect("Error creating population log file");
let log2 = (f64::from(n_queens) * 4_f64).log2().ceil();
let population_size = 2_i32.pow(log2 as u32) as usize;
let (solutions, generation, progress) = GeneticExecution::<u8, QueensBoard>::new()
.population_size(population_size)
.genotype_size(n_queens as u8)
.mutation_rate(Box::new(MutationRates::Linear(SlopeParams {
start: f64::from(n_queens) / (8_f64 + 2_f64 * log2) / 100_f64,
bound: 0.005,
coefficient: -0.0002,
})))
.selection_rate(Box::new(SelectionRates::Linear(SlopeParams {
start: log2 - 2_f64,
bound: log2 / 1.5,
coefficient: -0.0005,
})))
.select_function(Box::new(SelectionFunctions::Cup))
.age_function(Box::new(AgeFunctions::Quadratic(
AgeThreshold(50),
AgeSlope(1_f64),
)))
.progress_log(20, progress_log)
.population_log(2000, population_log)
.run();
For a full example visit the nqueens-oxigen example.
For more information visit the documentation.
Resuming a previous execution
Since version 1.1.0, genetic algorithm executions return the population of the last generation and new genetic executions accept a initial population. This permits to resume previous executions and it also enables coevolution, since little genetic algorithm re-executions can be launched in the fitness function.
In the following example a execution with 10000 generations is launched and after it is resumed until finding a solution with different rates.
let n_queens: u8 = std::env::args()
.nth(1)
.expect("Enter a number between 4 and 255 as argument")
.parse()
.expect("Enter a number between 4 and 255 as argument");
let progress_log = File::create("progress.csv").expect("Error creating progress log file");
let population_log = File::create("population.txt").expect("Error creating population log file");
let log2 = (f64::from(n_queens) * 4_f64).log2().ceil();
let population_size = 2_i32.pow(log2 as u32) as usize;
let (_solutions, _generation, _progress, population) = GeneticExecution::<u8, QueensBoard>::new()
.population_size(population_size)
.genotype_size(n_queens as u8)
.mutation_rate(Box::new(MutationRates::Linear(SlopeParams {
start: f64::from(n_queens) / (8_f64 + 2_f64 * log2) / 100_f64,
bound: 0.005,
coefficient: -0.0002,
})))
.selection_rate(Box::new(SelectionRates::Linear(SlopeParams {
start: log2 - 2_f64,
bound: log2 / 1.5,
coefficient: -0.0005,
})))
.select_function(Box::new(SelectionFunctions::Cup))
.age_function(Box::new(AgeFunctions::Quadratic(
AgeThreshold(50),
AgeSlope(1_f64),
)))
.stop_criterion(Box::new(StopCriteria::Generation(10000)))
.run();
let (solutions, generation, progress, _population) = GeneticExecution::<u8, QueensBoard>::new()
.population_size(population_size)
.genotype_size(n_queens as u8)
.mutation_rate(Box::new(MutationRates::Linear(SlopeParams {
start: f64::from(n_queens) / (8_f64 + 4_f64 * log2) / 100_f64,
bound: 0.005,
coefficient: -0.0002,
})))
.selection_rate(Box::new(SelectionRates::Linear(SlopeParams {
start: log2 - 4_f64,
bound: log2 / 1.5,
coefficient: -0.0005,
})))
.select_function(Box::new(SelectionFunctions::Cup))
.age_function(Box::new(AgeFunctions::Quadratic(
AgeThreshold(50),
AgeSlope(1_f64),
)))
.population(population)
.progress_log(20, progress_log)
.population_log(2000, population_log)
.run();
Building
To build oxigen, use cargo like for any Rust project:
cargo buildto build in debug mode.cargo build --releaseto build with optimizations.
Due to a bug in cargo workspaces with optional features errors appear building from the root folder (see #12, #19), but building inside each project works flawlessly.
To run benchmarks, you will need a nightly Rust compiler. Uncomment the lines // #![feature(test)] and // mod benchmarks; from lib.rs and then benchmarks can be run using cargo bench --jobs 1 --all-features.
Benchmarks
The following benchmarks have been created to measure the genetic algorithm functions performance:
running 29 tests
test benchmarks::bench_cross_multi_point_255inds ... bench: 895,332 ns/iter (+/- 34,409)
test benchmarks::bench_
Related Skills
himalaya
337.3kCLI to manage emails via IMAP/SMTP. Use `himalaya` to list, read, write, reply, forward, search, and organize emails from the terminal. Supports multiple accounts and message composition with MML (MIME Meta Language).
node-connect
337.3kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
83.2kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
coding-agent
337.3kDelegate coding tasks to Codex, Claude Code, or Pi agents via background process
