use burn::backend::{Flex, Wgpu};
use rlevo_core::bounds::Bounds;
use rlevo_core::fitness::FitnessEvaluable;
use rlevo_core::rate::NonNegativeRate;
use rlevo_evolution::algorithms::ga::{
GaConfig, GaCrossover, GaReplacement, GaSelection, GeneticAlgorithm,
};
use rlevo_evolution::algorithms::metaheuristic::pso::{ParticleSwarm, PsoConfig};
use rlevo_evolution::fitness::FromFitnessEvaluable;
use rlevo_evolution::strategy::{EvolutionaryHarness, Strategy};
struct Sphere;
struct SphereFit;
impl FitnessEvaluable for SphereFit {
type Individual = Vec<f64>;
type Landscape = Sphere;
fn evaluate(&self, x: &Self::Individual, _: &Self::Landscape) -> f64 {
x.iter().map(|v| v * v).sum()
}
}
fn run_sphere_ga<B>(seed: u64, gens: usize, device: B::Device) -> f32
where
B: burn::tensor::backend::Backend,
B::Device: Clone,
GeneticAlgorithm<B>: Strategy<
B,
Params = GaConfig,
State = rlevo_evolution::algorithms::ga::GaState<B>,
Genome = burn::tensor::Tensor<B, 2>,
>,
{
let params = GaConfig {
pop_size: 64,
genome_dim: 10,
bounds: Bounds::new(-5.12, 5.12),
mutation_sigma: NonNegativeRate::new(0.2),
selection: GaSelection::Tournament { size: 2 },
crossover: GaCrossover::BlxAlpha {
alpha: NonNegativeRate::new(0.5),
},
replacement: GaReplacement::Elitist { elitism_k: 2 },
};
let mut harness = EvolutionaryHarness::<B, _, _>::new(
GeneticAlgorithm::<B>::new(),
params,
FromFitnessEvaluable::new(SphereFit, Sphere),
seed,
device,
gens,
)
.expect("valid params");
harness.reset();
loop {
if harness.step(()).done {
break;
}
}
harness.latest_metrics().unwrap().best_fitness_ever()
}
fn run_sphere_pso<B>(seed: u64, gens: usize, device: B::Device) -> f32
where
B: burn::tensor::backend::Backend,
B::Device: Clone,
ParticleSwarm<B>: Strategy<
B,
Params = PsoConfig,
State = rlevo_evolution::algorithms::metaheuristic::pso::PsoState<B>,
Genome = burn::tensor::Tensor<B, 2>,
>,
{
let params = PsoConfig::default_for(32, 10);
let mut harness = EvolutionaryHarness::<B, _, _>::new(
ParticleSwarm::<B>::new(),
params,
FromFitnessEvaluable::new(SphereFit, Sphere),
seed,
device,
gens,
)
.expect("valid params");
harness.reset();
loop {
if harness.step(()).done {
break;
}
}
harness.latest_metrics().unwrap().best_fitness_ever()
}
#[test]
#[ignore = "requires a wgpu/Vulkan adapter; CI runners have no GPU and cubecl-wgpu aborts on device init — run on a GPU host with `cargo test -p rlevo-evolution --test backend_parity -- --ignored`"]
fn wgpu_matches_flex_on_sphere_d10() {
const SEED: u64 = 999;
const GENS: usize = 400;
let flex_ga = run_sphere_ga::<Flex>(SEED, GENS, Default::default());
let flex_pso = run_sphere_pso::<Flex>(SEED, GENS, Default::default());
let wgpu_device: burn::backend::wgpu::WgpuDevice = Default::default();
let wgpu_ga = run_sphere_ga::<Wgpu>(SEED, GENS, wgpu_device.clone());
let wgpu_pso = run_sphere_pso::<Wgpu>(SEED, GENS, wgpu_device);
assert!(
flex_ga.is_finite() && wgpu_ga.is_finite(),
"non-finite GA result: flex={flex_ga}, wgpu={wgpu_ga}",
);
assert!(
flex_ga < 1.0,
"Flex GA did not converge on Sphere-D10: {flex_ga}",
);
assert!(
wgpu_ga < 1.0,
"wgpu GA did not converge on Sphere-D10: {wgpu_ga}",
);
assert!(
flex_pso.is_finite() && wgpu_pso.is_finite(),
"non-finite PSO result: flex={flex_pso}, wgpu={wgpu_pso}",
);
assert!(
flex_pso < 1e-2,
"Flex PSO did not converge on Sphere-D10: {flex_pso}",
);
assert!(
wgpu_pso < 1e-2,
"wgpu PSO did not converge on Sphere-D10: {wgpu_pso}",
);
}