use std::f64::consts::PI;
use crate::fitness::traits::Fitness;
use crate::genome::bit_string::BitString;
use crate::genome::real_vector::RealVector;
use crate::genome::traits::{BinaryGenome, RealValuedGenome};
pub trait BenchmarkFunction: Send + Sync {
fn name(&self) -> &'static str;
fn dimension(&self) -> usize;
fn bounds(&self) -> (f64, f64);
fn optimal_fitness(&self) -> f64;
fn optimal_solution(&self) -> Option<Vec<f64>>;
fn evaluate_raw(&self, x: &[f64]) -> f64;
}
#[derive(Clone, Debug)]
pub struct Sphere {
dimension: usize,
}
impl Sphere {
pub fn new(dimension: usize) -> Self {
Self { dimension }
}
}
impl BenchmarkFunction for Sphere {
fn name(&self) -> &'static str {
"Sphere"
}
fn dimension(&self) -> usize {
self.dimension
}
fn bounds(&self) -> (f64, f64) {
(-5.12, 5.12)
}
fn optimal_fitness(&self) -> f64 {
0.0
}
fn optimal_solution(&self) -> Option<Vec<f64>> {
Some(vec![0.0; self.dimension])
}
fn evaluate_raw(&self, x: &[f64]) -> f64 {
x.iter().map(|xi| xi * xi).sum()
}
}
impl Fitness for Sphere {
type Genome = RealVector;
type Value = f64;
fn evaluate(&self, genome: &Self::Genome) -> f64 {
-self.evaluate_raw(genome.genes())
}
}
#[derive(Clone, Debug)]
pub struct Rastrigin {
dimension: usize,
}
impl Rastrigin {
pub fn new(dimension: usize) -> Self {
Self { dimension }
}
}
impl BenchmarkFunction for Rastrigin {
fn name(&self) -> &'static str {
"Rastrigin"
}
fn dimension(&self) -> usize {
self.dimension
}
fn bounds(&self) -> (f64, f64) {
(-5.12, 5.12)
}
fn optimal_fitness(&self) -> f64 {
0.0
}
fn optimal_solution(&self) -> Option<Vec<f64>> {
Some(vec![0.0; self.dimension])
}
fn evaluate_raw(&self, x: &[f64]) -> f64 {
let a = 10.0;
let n = x.len() as f64;
a * n
+ x.iter()
.map(|xi| xi * xi - a * (2.0 * PI * xi).cos())
.sum::<f64>()
}
}
impl Fitness for Rastrigin {
type Genome = RealVector;
type Value = f64;
fn evaluate(&self, genome: &Self::Genome) -> f64 {
-self.evaluate_raw(genome.genes())
}
}
#[derive(Clone, Debug)]
pub struct Rosenbrock {
dimension: usize,
}
impl Rosenbrock {
pub fn new(dimension: usize) -> Self {
assert!(dimension >= 2, "Rosenbrock requires at least 2 dimensions");
Self { dimension }
}
}
impl BenchmarkFunction for Rosenbrock {
fn name(&self) -> &'static str {
"Rosenbrock"
}
fn dimension(&self) -> usize {
self.dimension
}
fn bounds(&self) -> (f64, f64) {
(-5.0, 10.0)
}
fn optimal_fitness(&self) -> f64 {
0.0
}
fn optimal_solution(&self) -> Option<Vec<f64>> {
Some(vec![1.0; self.dimension])
}
fn evaluate_raw(&self, x: &[f64]) -> f64 {
x.windows(2)
.map(|w| {
let xi = w[0];
let xi1 = w[1];
100.0 * (xi1 - xi * xi).powi(2) + (1.0 - xi).powi(2)
})
.sum()
}
}
impl Fitness for Rosenbrock {
type Genome = RealVector;
type Value = f64;
fn evaluate(&self, genome: &Self::Genome) -> f64 {
-self.evaluate_raw(genome.genes())
}
}
#[derive(Clone, Debug)]
pub struct Ackley {
dimension: usize,
a: f64,
b: f64,
c: f64,
}
impl Ackley {
pub fn new(dimension: usize) -> Self {
Self {
dimension,
a: 20.0,
b: 0.2,
c: 2.0 * PI,
}
}
pub fn with_params(dimension: usize, a: f64, b: f64, c: f64) -> Self {
Self { dimension, a, b, c }
}
}
impl BenchmarkFunction for Ackley {
fn name(&self) -> &'static str {
"Ackley"
}
fn dimension(&self) -> usize {
self.dimension
}
fn bounds(&self) -> (f64, f64) {
(-32.768, 32.768)
}
fn optimal_fitness(&self) -> f64 {
0.0
}
fn optimal_solution(&self) -> Option<Vec<f64>> {
Some(vec![0.0; self.dimension])
}
fn evaluate_raw(&self, x: &[f64]) -> f64 {
let n = x.len() as f64;
let sum_sq = x.iter().map(|xi| xi * xi).sum::<f64>();
let sum_cos = x.iter().map(|xi| (self.c * xi).cos()).sum::<f64>();
-self.a * (-self.b * (sum_sq / n).sqrt()).exp() - (sum_cos / n).exp()
+ self.a
+ std::f64::consts::E
}
}
impl Fitness for Ackley {
type Genome = RealVector;
type Value = f64;
fn evaluate(&self, genome: &Self::Genome) -> f64 {
-self.evaluate_raw(genome.genes())
}
}
#[derive(Clone, Debug)]
pub struct Griewank {
dimension: usize,
}
impl Griewank {
pub fn new(dimension: usize) -> Self {
Self { dimension }
}
}
impl BenchmarkFunction for Griewank {
fn name(&self) -> &'static str {
"Griewank"
}
fn dimension(&self) -> usize {
self.dimension
}
fn bounds(&self) -> (f64, f64) {
(-600.0, 600.0)
}
fn optimal_fitness(&self) -> f64 {
0.0
}
fn optimal_solution(&self) -> Option<Vec<f64>> {
Some(vec![0.0; self.dimension])
}
fn evaluate_raw(&self, x: &[f64]) -> f64 {
let sum_sq: f64 = x.iter().map(|xi| xi * xi).sum::<f64>() / 4000.0;
let prod_cos: f64 = x
.iter()
.enumerate()
.map(|(i, xi)| (xi / ((i + 1) as f64).sqrt()).cos())
.product();
sum_sq - prod_cos + 1.0
}
}
impl Fitness for Griewank {
type Genome = RealVector;
type Value = f64;
fn evaluate(&self, genome: &Self::Genome) -> f64 {
-self.evaluate_raw(genome.genes())
}
}
#[derive(Clone, Debug)]
pub struct Schwefel {
dimension: usize,
}
impl Schwefel {
pub fn new(dimension: usize) -> Self {
Self { dimension }
}
}
impl BenchmarkFunction for Schwefel {
fn name(&self) -> &'static str {
"Schwefel"
}
fn dimension(&self) -> usize {
self.dimension
}
fn bounds(&self) -> (f64, f64) {
(-500.0, 500.0)
}
fn optimal_fitness(&self) -> f64 {
0.0
}
fn optimal_solution(&self) -> Option<Vec<f64>> {
Some(vec![420.9687; self.dimension])
}
fn evaluate_raw(&self, x: &[f64]) -> f64 {
let n = x.len() as f64;
418.9829 * n - x.iter().map(|xi| xi * xi.abs().sqrt().sin()).sum::<f64>()
}
}
impl Fitness for Schwefel {
type Genome = RealVector;
type Value = f64;
fn evaluate(&self, genome: &Self::Genome) -> f64 {
-self.evaluate_raw(genome.genes())
}
}
#[derive(Clone, Debug)]
pub struct OneMax {
length: usize,
}
impl OneMax {
pub fn new(length: usize) -> Self {
Self { length }
}
pub fn length(&self) -> usize {
self.length
}
}
impl Fitness for OneMax {
type Genome = BitString;
type Value = usize;
fn evaluate(&self, genome: &Self::Genome) -> usize {
genome.count_ones()
}
}
#[derive(Clone, Debug)]
pub struct LeadingOnes {
#[allow(dead_code)]
length: usize,
}
impl LeadingOnes {
pub fn new(length: usize) -> Self {
Self { length }
}
}
impl Fitness for LeadingOnes {
type Genome = BitString;
type Value = usize;
fn evaluate(&self, genome: &Self::Genome) -> usize {
genome.bits().iter().take_while(|&&b| b).count()
}
}
#[derive(Clone, Debug)]
pub struct Zdt1 {
dimension: usize,
}
impl Zdt1 {
pub fn new(dimension: usize) -> Self {
assert!(dimension >= 2, "ZDT1 requires at least 2 dimensions");
Self { dimension }
}
pub fn evaluate(&self, x: &[f64]) -> [f64; 2] {
let n = x.len() as f64;
let f1 = x[0];
let g = 1.0 + 9.0 * x[1..].iter().sum::<f64>() / (n - 1.0);
let f2 = g * (1.0 - (f1 / g).sqrt());
[f1, f2]
}
pub fn bounds(&self) -> (f64, f64) {
(0.0, 1.0)
}
pub fn dimension(&self) -> usize {
self.dimension
}
}
#[derive(Clone, Debug)]
pub struct Zdt2 {
dimension: usize,
}
impl Zdt2 {
pub fn new(dimension: usize) -> Self {
assert!(dimension >= 2, "ZDT2 requires at least 2 dimensions");
Self { dimension }
}
pub fn evaluate(&self, x: &[f64]) -> [f64; 2] {
let n = x.len() as f64;
let f1 = x[0];
let g = 1.0 + 9.0 * x[1..].iter().sum::<f64>() / (n - 1.0);
let f2 = g * (1.0 - (f1 / g).powi(2));
[f1, f2]
}
pub fn bounds(&self) -> (f64, f64) {
(0.0, 1.0)
}
pub fn dimension(&self) -> usize {
self.dimension
}
}
#[derive(Clone, Debug)]
pub struct Zdt3 {
dimension: usize,
}
impl Zdt3 {
pub fn new(dimension: usize) -> Self {
assert!(dimension >= 2, "ZDT3 requires at least 2 dimensions");
Self { dimension }
}
pub fn evaluate(&self, x: &[f64]) -> [f64; 2] {
let n = x.len() as f64;
let f1 = x[0];
let g = 1.0 + 9.0 * x[1..].iter().sum::<f64>() / (n - 1.0);
let h = 1.0 - (f1 / g).sqrt() - (f1 / g) * (10.0 * PI * f1).sin();
let f2 = g * h;
[f1, f2]
}
pub fn bounds(&self) -> (f64, f64) {
(0.0, 1.0)
}
pub fn dimension(&self) -> usize {
self.dimension
}
}
#[derive(Clone, Debug)]
pub struct SchafferN1;
impl SchafferN1 {
pub fn new() -> Self {
Self
}
pub fn evaluate(&self, x: f64) -> [f64; 2] {
let f1 = x * x;
let f2 = (x - 2.0) * (x - 2.0);
[f1, f2]
}
pub fn bounds(&self) -> (f64, f64) {
(-10.0, 10.0)
}
}
impl Default for SchafferN1 {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct Levy {
dimension: usize,
}
impl Levy {
pub fn new(dimension: usize) -> Self {
Self { dimension }
}
}
impl BenchmarkFunction for Levy {
fn name(&self) -> &'static str {
"Levy"
}
fn dimension(&self) -> usize {
self.dimension
}
fn bounds(&self) -> (f64, f64) {
(-10.0, 10.0)
}
fn optimal_fitness(&self) -> f64 {
0.0
}
fn optimal_solution(&self) -> Option<Vec<f64>> {
Some(vec![1.0; self.dimension])
}
fn evaluate_raw(&self, x: &[f64]) -> f64 {
let w: Vec<f64> = x.iter().map(|xi| 1.0 + (xi - 1.0) / 4.0).collect();
let n = w.len();
let term1 = (PI * w[0]).sin().powi(2);
let sum: f64 = w[..n - 1]
.iter()
.map(|wi| (wi - 1.0).powi(2) * (1.0 + 10.0 * (PI * wi + 1.0).sin().powi(2)))
.sum();
let term3 = (w[n - 1] - 1.0).powi(2) * (1.0 + (2.0 * PI * w[n - 1]).sin().powi(2));
term1 + sum + term3
}
}
impl Fitness for Levy {
type Genome = RealVector;
type Value = f64;
fn evaluate(&self, genome: &Self::Genome) -> f64 {
-self.evaluate_raw(genome.genes())
}
}
#[derive(Clone, Debug)]
pub struct DixonPrice {
dimension: usize,
}
impl DixonPrice {
pub fn new(dimension: usize) -> Self {
Self { dimension }
}
}
impl BenchmarkFunction for DixonPrice {
fn name(&self) -> &'static str {
"Dixon-Price"
}
fn dimension(&self) -> usize {
self.dimension
}
fn bounds(&self) -> (f64, f64) {
(-10.0, 10.0)
}
fn optimal_fitness(&self) -> f64 {
0.0
}
fn optimal_solution(&self) -> Option<Vec<f64>> {
let optimal: Vec<f64> = (0..self.dimension)
.map(|i| {
let two_pow_j = (1u64 << (i + 1)) as f64; let exp_num = two_pow_j - 2.0; let exp_den = two_pow_j; 2.0_f64.powf(-exp_num / exp_den)
})
.collect();
Some(optimal)
}
fn evaluate_raw(&self, x: &[f64]) -> f64 {
let term1 = (x[0] - 1.0).powi(2);
let sum: f64 = x
.windows(2)
.enumerate()
.map(|(i, w)| (i + 2) as f64 * (2.0 * w[1] * w[1] - w[0]).powi(2))
.sum();
term1 + sum
}
}
impl Fitness for DixonPrice {
type Genome = RealVector;
type Value = f64;
fn evaluate(&self, genome: &Self::Genome) -> f64 {
-self.evaluate_raw(genome.genes())
}
}
#[derive(Clone, Debug)]
pub struct StyblinskiTang {
dimension: usize,
}
impl StyblinskiTang {
pub fn new(dimension: usize) -> Self {
Self { dimension }
}
}
impl BenchmarkFunction for StyblinskiTang {
fn name(&self) -> &'static str {
"Styblinski-Tang"
}
fn dimension(&self) -> usize {
self.dimension
}
fn bounds(&self) -> (f64, f64) {
(-5.0, 5.0)
}
fn optimal_fitness(&self) -> f64 {
-39.16617 * self.dimension as f64
}
fn optimal_solution(&self) -> Option<Vec<f64>> {
Some(vec![-2.903534; self.dimension])
}
fn evaluate_raw(&self, x: &[f64]) -> f64 {
x.iter()
.map(|xi| xi.powi(4) - 16.0 * xi.powi(2) + 5.0 * xi)
.sum::<f64>()
/ 2.0
}
}
impl Fitness for StyblinskiTang {
type Genome = RealVector;
type Value = f64;
fn evaluate(&self, genome: &Self::Genome) -> f64 {
-self.evaluate_raw(genome.genes())
}
}
#[derive(Clone, Debug)]
pub struct RoyalRoad {
pub schema_size: usize,
pub num_schemas: usize,
}
impl RoyalRoad {
pub fn new(schema_size: usize, num_schemas: usize) -> Self {
Self {
schema_size,
num_schemas,
}
}
pub fn standard() -> Self {
Self::new(8, 8)
}
pub fn genome_length(&self) -> usize {
self.schema_size * self.num_schemas
}
fn is_complete_schema(&self, bits: &[bool], schema_index: usize) -> bool {
let start = schema_index * self.schema_size;
let end = start + self.schema_size;
if end > bits.len() {
return false;
}
bits[start..end].iter().all(|&b| b)
}
pub fn count_complete_schemas(&self, bits: &[bool]) -> usize {
(0..self.num_schemas)
.filter(|&i| self.is_complete_schema(bits, i))
.count()
}
}
impl Fitness for RoyalRoad {
type Genome = BitString;
type Value = usize;
fn evaluate(&self, genome: &Self::Genome) -> usize {
self.count_complete_schemas(genome.bits())
}
}
#[derive(Clone, Debug)]
pub struct NkLandscape {
n: usize,
k: usize,
neighbors: Vec<Vec<usize>>,
contributions: Vec<std::collections::HashMap<Vec<bool>, f64>>,
}
impl NkLandscape {
pub fn new(n: usize, k: usize, seed: u64) -> Self {
assert!(k < n, "K must be less than N");
use rand::SeedableRng;
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
let mut neighbors = Vec::with_capacity(n);
for i in 0..n {
let mut gene_neighbors: Vec<usize> = (0..n).filter(|&j| j != i).collect();
use rand::seq::SliceRandom;
gene_neighbors.shuffle(&mut rng);
gene_neighbors.truncate(k);
gene_neighbors.sort();
neighbors.push(gene_neighbors);
}
let mut contributions = Vec::with_capacity(n);
for _i in 0..n {
let num_configs = 1 << (k + 1); let mut table = std::collections::HashMap::with_capacity(num_configs);
for config_bits in 0..num_configs {
let config: Vec<bool> = (0..=k).map(|j| (config_bits >> j) & 1 == 1).collect();
use rand::Rng;
table.insert(config, rng.gen::<f64>());
}
contributions.push(table);
}
Self {
n,
k,
neighbors,
contributions,
}
}
pub fn with_adjacent_neighbors(n: usize, k: usize, seed: u64) -> Self {
assert!(k < n, "K must be less than N");
use rand::SeedableRng;
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
let mut neighbors = Vec::with_capacity(n);
for i in 0..n {
let gene_neighbors: Vec<usize> = (1..=k).map(|offset| (i + offset) % n).collect();
neighbors.push(gene_neighbors);
}
let mut contributions = Vec::with_capacity(n);
for _i in 0..n {
let num_configs = 1 << (k + 1);
let mut table = std::collections::HashMap::with_capacity(num_configs);
for config_bits in 0..num_configs {
let config: Vec<bool> = (0..=k).map(|j| (config_bits >> j) & 1 == 1).collect();
use rand::Rng;
table.insert(config, rng.gen::<f64>());
}
contributions.push(table);
}
Self {
n,
k,
neighbors,
contributions,
}
}
pub fn genome_length(&self) -> usize {
self.n
}
pub fn epistasis(&self) -> usize {
self.k
}
pub fn evaluate_bits(&self, bits: &[bool]) -> f64 {
assert!(bits.len() >= self.n);
let mut total = 0.0;
for i in 0..self.n {
let mut config = Vec::with_capacity(self.k + 1);
config.push(bits[i]);
for &j in &self.neighbors[i] {
config.push(bits[j]);
}
if let Some(&contribution) = self.contributions[i].get(&config) {
total += contribution;
}
}
total / self.n as f64
}
}
impl Fitness for NkLandscape {
type Genome = BitString;
type Value = f64;
fn evaluate(&self, genome: &Self::Genome) -> f64 {
self.evaluate_bits(genome.bits())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fitness::traits::Fitness;
use approx::assert_relative_eq;
#[test]
fn test_sphere_at_optimum() {
let sphere = Sphere::new(3);
let optimum = RealVector::new(vec![0.0, 0.0, 0.0]);
assert_relative_eq!(sphere.evaluate(&optimum), 0.0);
}
#[test]
fn test_sphere_non_optimum() {
let sphere = Sphere::new(3);
let point = RealVector::new(vec![1.0, 2.0, 3.0]);
assert_relative_eq!(sphere.evaluate(&point), -14.0);
}
#[test]
fn test_sphere_metadata() {
let sphere = Sphere::new(5);
assert_eq!(sphere.name(), "Sphere");
assert_eq!(sphere.dimension(), 5);
assert_eq!(sphere.bounds(), (-5.12, 5.12));
assert_relative_eq!(sphere.optimal_fitness(), 0.0);
assert_eq!(sphere.optimal_solution(), Some(vec![0.0; 5]));
}
#[test]
fn test_rastrigin_at_optimum() {
let rastrigin = Rastrigin::new(3);
let optimum = RealVector::new(vec![0.0, 0.0, 0.0]);
assert_relative_eq!(rastrigin.evaluate(&optimum), 0.0, epsilon = 1e-10);
}
#[test]
fn test_rastrigin_non_optimum() {
let rastrigin = Rastrigin::new(2);
let point = RealVector::new(vec![1.0, 1.0]);
let expected = 10.0 * 2.0
+ (1.0 * 1.0 - 10.0 * (2.0 * PI * 1.0).cos())
+ (1.0 * 1.0 - 10.0 * (2.0 * PI * 1.0).cos());
assert_relative_eq!(rastrigin.evaluate(&point), -expected, epsilon = 1e-10);
}
#[test]
fn test_rastrigin_metadata() {
let rastrigin = Rastrigin::new(10);
assert_eq!(rastrigin.name(), "Rastrigin");
assert_eq!(rastrigin.dimension(), 10);
assert_eq!(rastrigin.bounds(), (-5.12, 5.12));
}
#[test]
fn test_rosenbrock_at_optimum() {
let rosenbrock = Rosenbrock::new(3);
let optimum = RealVector::new(vec![1.0, 1.0, 1.0]);
assert_relative_eq!(rosenbrock.evaluate(&optimum), 0.0, epsilon = 1e-10);
}
#[test]
fn test_rosenbrock_non_optimum() {
let rosenbrock = Rosenbrock::new(2);
let point = RealVector::new(vec![0.0, 0.0]);
assert_relative_eq!(rosenbrock.evaluate(&point), -1.0, epsilon = 1e-10);
}
#[test]
fn test_rosenbrock_metadata() {
let rosenbrock = Rosenbrock::new(5);
assert_eq!(rosenbrock.name(), "Rosenbrock");
assert_eq!(rosenbrock.dimension(), 5);
assert_eq!(rosenbrock.bounds(), (-5.0, 10.0));
assert_eq!(rosenbrock.optimal_solution(), Some(vec![1.0; 5]));
}
#[test]
fn test_ackley_at_optimum() {
let ackley = Ackley::new(3);
let optimum = RealVector::new(vec![0.0, 0.0, 0.0]);
assert_relative_eq!(ackley.evaluate(&optimum), 0.0, epsilon = 1e-10);
}
#[test]
fn test_ackley_metadata() {
let ackley = Ackley::new(10);
assert_eq!(ackley.name(), "Ackley");
assert_eq!(ackley.dimension(), 10);
assert_eq!(ackley.bounds(), (-32.768, 32.768));
}
#[test]
fn test_griewank_at_optimum() {
let griewank = Griewank::new(3);
let optimum = RealVector::new(vec![0.0, 0.0, 0.0]);
assert_relative_eq!(griewank.evaluate(&optimum), 0.0, epsilon = 1e-10);
}
#[test]
fn test_griewank_metadata() {
let griewank = Griewank::new(10);
assert_eq!(griewank.name(), "Griewank");
assert_eq!(griewank.dimension(), 10);
assert_eq!(griewank.bounds(), (-600.0, 600.0));
}
#[test]
fn test_schwefel_metadata() {
let schwefel = Schwefel::new(10);
assert_eq!(schwefel.name(), "Schwefel");
assert_eq!(schwefel.dimension(), 10);
assert_eq!(schwefel.bounds(), (-500.0, 500.0));
}
#[test]
fn test_onemax_all_ones() {
let onemax = OneMax::new(10);
let genome = BitString::ones(10);
assert_eq!(onemax.evaluate(&genome), 10);
}
#[test]
fn test_onemax_all_zeros() {
let onemax = OneMax::new(10);
let genome = BitString::zeros(10);
assert_eq!(onemax.evaluate(&genome), 0);
}
#[test]
fn test_onemax_mixed() {
let onemax = OneMax::new(5);
let genome = BitString::new(vec![true, false, true, false, true]);
assert_eq!(onemax.evaluate(&genome), 3);
}
#[test]
fn test_leadingones_all_ones() {
let lo = LeadingOnes::new(10);
let genome = BitString::ones(10);
assert_eq!(lo.evaluate(&genome), 10);
}
#[test]
fn test_leadingones_all_zeros() {
let lo = LeadingOnes::new(10);
let genome = BitString::zeros(10);
assert_eq!(lo.evaluate(&genome), 0);
}
#[test]
fn test_leadingones_mixed() {
let lo = LeadingOnes::new(5);
let genome = BitString::new(vec![true, true, false, true, true]);
assert_eq!(lo.evaluate(&genome), 2); }
#[test]
fn test_leadingones_starts_with_zero() {
let lo = LeadingOnes::new(5);
let genome = BitString::new(vec![false, true, true, true, true]);
assert_eq!(lo.evaluate(&genome), 0);
}
#[test]
fn test_zdt1_pareto_front() {
let zdt1 = Zdt1::new(10);
let x = vec![0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
let [f1, f2] = zdt1.evaluate(&x);
assert_relative_eq!(f1, 0.5, epsilon = 1e-10);
assert_relative_eq!(f2, 1.0 - f1.sqrt(), epsilon = 1e-10);
}
#[test]
fn test_zdt2_pareto_front() {
let zdt2 = Zdt2::new(10);
let x = vec![0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
let [f1, f2] = zdt2.evaluate(&x);
assert_relative_eq!(f1, 0.5, epsilon = 1e-10);
assert_relative_eq!(f2, 1.0 - f1 * f1, epsilon = 1e-10);
}
#[test]
fn test_schaffer_n1() {
let schaffer = SchafferN1::new();
let [f1, f2] = schaffer.evaluate(0.0);
assert_relative_eq!(f1, 0.0, epsilon = 1e-10);
assert_relative_eq!(f2, 4.0, epsilon = 1e-10);
}
#[test]
fn test_levy_at_optimum() {
let levy = Levy::new(3);
let optimum = RealVector::new(vec![1.0, 1.0, 1.0]);
assert_relative_eq!(levy.evaluate(&optimum), 0.0, epsilon = 1e-10);
}
#[test]
fn test_dixonprice_metadata() {
let dp = DixonPrice::new(5);
assert_eq!(dp.name(), "Dixon-Price");
assert_eq!(dp.dimension(), 5);
}
#[test]
fn test_dixonprice_optimal_solution_is_optimum() {
for dim in 2..=6 {
let dp = DixonPrice::new(dim);
let opt = dp
.optimal_solution()
.expect("Dixon-Price exposes an optimal solution");
let value = dp.evaluate_raw(&opt);
assert!(
value < 1e-12,
"f(optimal_solution()) for dim={dim} was {value}, expected < 1e-12"
);
}
}
#[test]
fn test_styblinskitang_near_optimum() {
let st = StyblinskiTang::new(2);
let near_opt = RealVector::new(vec![-2.9, -2.9]);
let fitness = st.evaluate(&near_opt);
let optimal = st.optimal_fitness();
assert!(
fitness > optimal - 1.0,
"Fitness {} should be close to optimal {}",
fitness,
optimal
);
}
#[test]
fn test_royal_road_all_ones() {
let rr = RoyalRoad::new(4, 4); let genome = BitString::ones(16);
let fitness: usize = rr.evaluate(&genome);
assert_eq!(fitness, 4); }
#[test]
fn test_royal_road_all_zeros() {
let rr = RoyalRoad::new(4, 4);
let genome = BitString::zeros(16);
let fitness: usize = rr.evaluate(&genome);
assert_eq!(fitness, 0); }
#[test]
fn test_royal_road_partial() {
let rr = RoyalRoad::new(4, 4);
let bits = vec![
true, true, true, true, false, false, false, false, true, true, true, true, true, true, true, false, ];
let genome = BitString::new(bits);
let fitness: usize = rr.evaluate(&genome);
assert_eq!(fitness, 2);
}
#[test]
fn test_royal_road_standard() {
let rr = RoyalRoad::standard();
assert_eq!(rr.genome_length(), 64);
assert_eq!(rr.schema_size, 8);
assert_eq!(rr.num_schemas, 8);
}
#[test]
fn test_nk_landscape_creation() {
let nk = NkLandscape::new(10, 2, 42);
assert_eq!(nk.genome_length(), 10);
assert_eq!(nk.epistasis(), 2);
}
#[test]
fn test_nk_landscape_deterministic() {
let nk1 = NkLandscape::new(8, 2, 123);
let nk2 = NkLandscape::new(8, 2, 123);
let genome = BitString::new(vec![true, false, true, false, true, false, true, false]);
let f1: f64 = nk1.evaluate(&genome);
let f2: f64 = nk2.evaluate(&genome);
assert_relative_eq!(f1, f2);
}
#[test]
fn test_nk_landscape_fitness_range() {
let nk = NkLandscape::new(10, 3, 42);
let genome = BitString::new(vec![true; 10]);
let fitness: f64 = nk.evaluate(&genome);
assert!((0.0..=1.0).contains(&fitness));
}
#[test]
fn test_nk_landscape_adjacent() {
let nk = NkLandscape::with_adjacent_neighbors(10, 2, 42);
let genome = BitString::ones(10);
let fitness: f64 = nk.evaluate(&genome);
assert!((0.0..=1.0).contains(&fitness));
}
#[test]
#[should_panic(expected = "K must be less than N")]
fn test_nk_landscape_invalid_k() {
let _nk = NkLandscape::new(5, 5, 42); }
}