use scirs2_core::ndarray::{Array, Dimension, Ix2};
use scirs2_core::random::prelude::*;
use scirs2_core::random::rngs::StdRng;
use std::collections::HashMap;
use super::energy::hobo_energy_full_dispatch;
use super::{SampleResult, Sampler, SamplerResult};
pub struct GASampler {
seed: Option<u64>,
max_generations: usize,
population_size: usize,
}
#[derive(Debug, Clone, Copy)]
pub enum CrossoverStrategy {
Uniform,
SinglePoint,
TwoPoint,
Adaptive,
}
#[derive(Debug, Clone, Copy)]
pub enum MutationStrategy {
FixedRate(f64),
Annealing(f64, f64), Adaptive(f64, f64), }
impl GASampler {
#[must_use]
pub const fn new(seed: Option<u64>) -> Self {
Self {
seed,
max_generations: 1000,
population_size: 100,
}
}
#[must_use]
pub const fn with_params(
seed: Option<u64>,
max_generations: usize,
population_size: usize,
) -> Self {
Self {
seed,
max_generations,
population_size,
}
}
pub const fn with_population_size(mut self, size: usize) -> Self {
self.population_size = size;
self
}
pub const fn with_elite_fraction(self, _fraction: f64) -> Self {
self
}
pub const fn with_mutation_rate(self, _rate: f64) -> Self {
self
}
pub const fn with_advanced_params(
seed: Option<u64>,
max_generations: usize,
population_size: usize,
_crossover: CrossoverStrategy, _mutation: MutationStrategy, ) -> Self {
Self {
seed,
max_generations,
population_size,
}
}
fn crossover(
&self,
parent1: &[bool],
parent2: &[bool],
strategy: CrossoverStrategy,
rng: &mut impl Rng,
) -> (Vec<bool>, Vec<bool>) {
let n_vars = parent1.len();
let mut child1 = vec![false; n_vars];
let mut child2 = vec![false; n_vars];
match strategy {
CrossoverStrategy::Uniform => {
for i in 0..n_vars {
if rng.random_bool(0.5) {
child1[i] = parent1[i];
child2[i] = parent2[i];
} else {
child1[i] = parent2[i];
child2[i] = parent1[i];
}
}
}
CrossoverStrategy::SinglePoint => {
let crossover_point = rng.random_range(1..n_vars);
for i in 0..n_vars {
if i < crossover_point {
child1[i] = parent1[i];
child2[i] = parent2[i];
} else {
child1[i] = parent2[i];
child2[i] = parent1[i];
}
}
}
CrossoverStrategy::TwoPoint => {
let point1 = rng.random_range(1..(n_vars - 1));
let point2 = rng.random_range((point1 + 1)..n_vars);
for i in 0..n_vars {
if i < point1 || i >= point2 {
child1[i] = parent1[i];
child2[i] = parent2[i];
} else {
child1[i] = parent2[i];
child2[i] = parent1[i];
}
}
}
CrossoverStrategy::Adaptive => {
let mut hamming_distance = 0;
for i in 0..n_vars {
if parent1[i] != parent2[i] {
hamming_distance += 1;
}
}
let similarity = 1.0 - (hamming_distance as f64 / n_vars as f64);
if similarity > 0.8 {
for i in 0..n_vars {
if rng.random_bool(0.5) {
child1[i] = parent1[i];
child2[i] = parent2[i];
} else {
child1[i] = parent2[i];
child2[i] = parent1[i];
}
}
} else if similarity > 0.4 {
let point1 = rng.random_range(1..(n_vars - 1));
let point2 = rng.random_range((point1 + 1)..n_vars);
for i in 0..n_vars {
if i < point1 || i >= point2 {
child1[i] = parent1[i];
child2[i] = parent2[i];
} else {
child1[i] = parent2[i];
child2[i] = parent1[i];
}
}
} else {
let crossover_point = rng.random_range(1..n_vars);
for i in 0..n_vars {
if i < crossover_point {
child1[i] = parent1[i];
child2[i] = parent2[i];
} else {
child1[i] = parent2[i];
child2[i] = parent1[i];
}
}
}
}
}
(child1, child2)
}
fn mutate(
&self,
individual: &mut [bool],
strategy: MutationStrategy,
generation: usize,
max_generations: usize,
diversity: Option<f64>,
rng: &mut impl Rng,
) {
match strategy {
MutationStrategy::FixedRate(rate) => {
for bit in individual.iter_mut() {
if rng.random_bool(rate) {
*bit = !*bit;
}
}
}
MutationStrategy::Annealing(initial_rate, final_rate) => {
let progress = generation as f64 / max_generations as f64;
let current_rate = (final_rate - initial_rate).mul_add(progress, initial_rate);
for bit in individual.iter_mut() {
if rng.random_bool(current_rate) {
*bit = !*bit;
}
}
}
MutationStrategy::Adaptive(min_rate, max_rate) => {
if let Some(diversity) = diversity {
let rate = (max_rate - min_rate).mul_add(1.0 - diversity, min_rate);
for bit in individual.iter_mut() {
if rng.random_bool(rate) {
*bit = !*bit;
}
}
} else {
let rate = f64::midpoint(min_rate, max_rate);
for bit in individual.iter_mut() {
if rng.random_bool(rate) {
*bit = !*bit;
}
}
}
}
}
}
fn calculate_diversity(&self, population: &[Vec<bool>]) -> f64 {
if population.len() <= 1 {
return 0.0;
}
let n_individuals = population.len();
let n_vars = population[0].len();
let mut sum_distances = 0;
let mut pair_count = 0;
for i in 0..n_individuals {
for j in (i + 1)..n_individuals {
let mut distance = 0;
for k in 0..n_vars {
if population[i][k] != population[j][k] {
distance += 1;
}
}
sum_distances += distance;
pair_count += 1;
}
}
if pair_count > 0 {
(sum_distances as f64) / (pair_count as f64 * n_vars as f64)
} else {
0.0
}
}
}
impl Sampler for GASampler {
fn run_hobo(
&self,
hobo: &(
Array<f64, scirs2_core::ndarray::IxDyn>,
HashMap<String, usize>,
),
shots: usize,
) -> SamplerResult<Vec<SampleResult>> {
let (tensor, var_map) = hobo;
let actual_shots = std::cmp::max(shots, 10);
let n_vars = var_map.len();
let idx_to_var: HashMap<usize, String> = var_map
.iter()
.map(|(var, &idx)| (idx, var.clone()))
.collect();
let mut rng = if let Some(seed) = self.seed {
StdRng::seed_from_u64(seed)
} else {
let seed: u64 = thread_rng().random();
StdRng::seed_from_u64(seed)
};
if self.population_size <= 2 || n_vars == 0 {
let mut assignments = HashMap::new();
for var in var_map.keys() {
assignments.insert(var.clone(), false);
}
return Ok(vec![SampleResult {
assignments,
energy: 0.0,
occurrences: 1,
}]);
}
if tensor.ndim() == 2 && tensor.shape() == [n_vars, n_vars] {
let matrix = tensor
.clone()
.into_dimensionality::<scirs2_core::ndarray::Ix2>()
.map_err(|e| {
super::SamplerError::InvalidModel(format!(
"Failed to convert tensor to 2D matrix: {}",
e
))
})?;
let qubo = (matrix, var_map.clone());
return self.run_qubo(&qubo, shots);
}
let evaluate_energy = |state: &[bool]| -> f64 { hobo_energy_full_dispatch(state, tensor) };
let mut solution_counts: HashMap<Vec<bool>, (f64, usize)> = HashMap::new();
let pop_size = self.population_size.clamp(10, 100);
let mut population: Vec<Vec<bool>> = (0..pop_size)
.map(|_| (0..n_vars).map(|_| rng.random_bool(0.5)).collect())
.collect();
let mut fitness: Vec<f64> = population
.iter()
.map(|indiv| evaluate_energy(indiv))
.collect();
let mut best_solution = population[0].clone();
let mut best_fitness = fitness[0];
for (idx, fit) in fitness.iter().enumerate() {
if *fit < best_fitness {
best_fitness = *fit;
best_solution = population[idx].clone();
}
}
for _ in 0..30 {
let mut next_population = Vec::with_capacity(pop_size);
next_population.push(best_solution.clone());
while next_population.len() < pop_size {
let parent1_idx = tournament_selection(&fitness, 3, &mut rng);
let parent2_idx = tournament_selection(&fitness, 3, &mut rng);
let (mut child1, mut child2) =
simple_crossover(&population[parent1_idx], &population[parent2_idx], &mut rng);
mutate(&mut child1, 0.05, &mut rng);
mutate(&mut child2, 0.05, &mut rng);
next_population.push(child1);
if next_population.len() < pop_size {
next_population.push(child2);
}
}
population = next_population;
fitness = population
.iter()
.map(|indiv| evaluate_energy(indiv))
.collect();
for (idx, fit) in fitness.iter().enumerate() {
if *fit < best_fitness {
best_fitness = *fit;
best_solution = population[idx].clone();
}
}
for (idx, indiv) in population.iter().enumerate() {
let entry = solution_counts
.entry(indiv.clone())
.or_insert((fitness[idx], 0));
entry.1 += 1;
}
}
let mut results: Vec<SampleResult> = solution_counts
.into_iter()
.filter_map(|(state, (energy, count))| {
let assignments: HashMap<String, bool> = state
.iter()
.enumerate()
.filter_map(|(idx, &value)| {
idx_to_var
.get(&idx)
.map(|var_name| (var_name.clone(), value))
})
.collect();
if assignments.len() != state.len() {
return None;
}
Some(SampleResult {
assignments,
energy,
occurrences: count,
})
})
.collect();
results.sort_by(|a, b| {
a.energy
.partial_cmp(&b.energy)
.unwrap_or(std::cmp::Ordering::Equal)
});
if results.len() > actual_shots {
results.truncate(actual_shots);
}
Ok(results)
}
fn run_qubo(
&self,
qubo: &(
Array<f64, scirs2_core::ndarray::Ix2>,
HashMap<String, usize>,
),
shots: usize,
) -> SamplerResult<Vec<SampleResult>> {
let (matrix, var_map) = qubo;
let actual_shots = std::cmp::max(shots, 10);
let n_vars = var_map.len();
let idx_to_var: HashMap<usize, String> = var_map
.iter()
.map(|(var, &idx)| (idx, var.clone()))
.collect();
let mut rng = if let Some(seed) = self.seed {
StdRng::seed_from_u64(seed)
} else {
let seed: u64 = thread_rng().random();
StdRng::seed_from_u64(seed)
};
if self.population_size <= 2 || n_vars == 0 {
let mut assignments = HashMap::new();
for var in var_map.keys() {
assignments.insert(var.clone(), false);
}
return Ok(vec![SampleResult {
assignments,
energy: 0.0,
occurrences: 1,
}]);
}
let crossover_strategy = CrossoverStrategy::Adaptive;
let mutation_strategy = MutationStrategy::Annealing(0.1, 0.01);
let selection_pressure = 3; let use_elitism = true;
let mut population: Vec<Vec<bool>> = (0..self.population_size)
.map(|_| (0..n_vars).map(|_| rng.random_bool(0.5)).collect())
.collect();
let mut fitness: Vec<f64> = population
.iter()
.map(|indiv| calculate_energy(indiv, matrix))
.collect();
let mut best_idx = 0;
let mut best_fitness = fitness[0];
for (idx, &fit) in fitness.iter().enumerate() {
if fit < best_fitness {
best_idx = idx;
best_fitness = fit;
}
}
let mut best_individual = population[best_idx].clone();
let mut best_individual_fitness = best_fitness;
let mut solution_counts: HashMap<Vec<bool>, usize> = HashMap::new();
for generation in 0..self.max_generations {
let diversity = self.calculate_diversity(&population);
let mut next_population = Vec::with_capacity(self.population_size);
let mut next_fitness = Vec::with_capacity(self.population_size);
if use_elitism {
next_population.push(best_individual.clone());
next_fitness.push(best_individual_fitness);
}
while next_population.len() < self.population_size {
let parent1_idx = tournament_selection(&fitness, selection_pressure, &mut rng);
let parent2_idx = tournament_selection(&fitness, selection_pressure, &mut rng);
let parent1 = &population[parent1_idx];
let parent2 = &population[parent2_idx];
let (mut child1, mut child2) =
self.crossover(parent1, parent2, crossover_strategy, &mut rng);
self.mutate(
&mut child1,
mutation_strategy,
generation,
self.max_generations,
Some(diversity),
&mut rng,
);
self.mutate(
&mut child2,
mutation_strategy,
generation,
self.max_generations,
Some(diversity),
&mut rng,
);
let child1_fitness = calculate_energy(&child1, matrix);
let child2_fitness = calculate_energy(&child2, matrix);
next_population.push(child1);
next_fitness.push(child1_fitness);
if next_population.len() < self.population_size {
next_population.push(child2);
next_fitness.push(child2_fitness);
}
}
population = next_population;
fitness = next_fitness;
best_idx = 0;
best_fitness = fitness[0];
for (idx, &fit) in fitness.iter().enumerate() {
if fit < best_fitness {
best_idx = idx;
best_fitness = fit;
}
}
if best_fitness < best_individual_fitness {
best_individual = population[best_idx].clone();
best_individual_fitness = best_fitness;
}
for individual in &population {
*solution_counts.entry(individual.clone()).or_insert(0) += 1;
}
}
let mut results = Vec::new();
for (solution, count) in &solution_counts {
if *count < 2 {
continue;
}
let energy = calculate_energy(solution, matrix);
let assignments: HashMap<String, bool> = solution
.iter()
.enumerate()
.filter_map(|(idx, &value)| {
idx_to_var
.get(&idx)
.map(|var_name| (var_name.clone(), value))
})
.collect();
if assignments.len() != solution.len() {
continue;
}
results.push(SampleResult {
assignments,
energy,
occurrences: *count,
});
}
results.sort_by(|a, b| {
a.energy
.partial_cmp(&b.energy)
.unwrap_or(std::cmp::Ordering::Equal)
});
if results.len() > actual_shots {
results.truncate(actual_shots);
}
Ok(results)
}
}
fn calculate_energy(solution: &[bool], matrix: &Array<f64, Ix2>) -> f64 {
let n = solution.len();
let mut energy = 0.0;
for i in 0..n {
if solution[i] {
energy += matrix[[i, i]];
}
}
for i in 0..n {
if solution[i] {
for j in (i + 1)..n {
if solution[j] {
energy += matrix[[i, j]];
}
}
}
}
energy
}
fn simple_crossover(
parent1: &[bool],
parent2: &[bool],
rng: &mut impl Rng,
) -> (Vec<bool>, Vec<bool>) {
let n_vars = parent1.len();
let mut child1 = vec![false; n_vars];
let mut child2 = vec![false; n_vars];
let crossover_point = if n_vars > 1 {
rng.random_range(1..n_vars)
} else {
0 };
for i in 0..n_vars {
if i < crossover_point {
child1[i] = parent1[i];
child2[i] = parent2[i];
} else {
child1[i] = parent2[i];
child2[i] = parent1[i];
}
}
(child1, child2)
}
fn mutate(individual: &mut [bool], rate: f64, rng: &mut impl Rng) {
for bit in individual.iter_mut() {
if rng.random_bool(rate) {
*bit = !*bit;
}
}
}
fn tournament_selection(fitness: &[f64], tournament_size: usize, rng: &mut impl Rng) -> usize {
assert!(
!fitness.is_empty(),
"Cannot perform tournament selection on an empty fitness array"
);
if fitness.len() == 1 || tournament_size <= 1 {
return 0; }
let effective_tournament_size = std::cmp::min(tournament_size, fitness.len());
let mut best_idx = rng.random_range(0..fitness.len());
let mut best_fitness = fitness[best_idx];
for _ in 1..(effective_tournament_size) {
let candidate_idx = rng.random_range(0..fitness.len());
let candidate_fitness = fitness[candidate_idx];
if candidate_fitness < best_fitness {
best_idx = candidate_idx;
best_fitness = candidate_fitness;
}
}
best_idx
}