use rand::distributions::WeightedIndex;
use rand::prelude::Distribution;
use rand::Rng;
use std::collections::VecDeque;
#[derive(Clone, Debug)]
pub struct OneFifthRule {
pub increase_factor: f64,
pub decrease_factor: f64,
pub window_size: usize,
pub target_success_rate: f64,
success_history: VecDeque<bool>,
}
impl OneFifthRule {
pub fn new() -> Self {
Self {
increase_factor: 1.22,
decrease_factor: 0.82,
window_size: 10,
target_success_rate: 0.2,
success_history: VecDeque::with_capacity(10),
}
}
pub fn with_factors(mut self, increase: f64, decrease: f64) -> Self {
self.increase_factor = increase;
self.decrease_factor = decrease;
self
}
pub fn with_window_size(mut self, size: usize) -> Self {
self.window_size = size;
self.success_history = VecDeque::with_capacity(size);
self
}
pub fn with_target_rate(mut self, rate: f64) -> Self {
self.target_success_rate = rate;
self
}
pub fn record(&mut self, success: bool) {
self.success_history.push_back(success);
if self.success_history.len() > self.window_size {
self.success_history.pop_front();
}
}
pub fn success_rate(&self) -> Option<f64> {
if self.success_history.is_empty() {
return None;
}
let successes = self.success_history.iter().filter(|&&s| s).count();
Some(successes as f64 / self.success_history.len() as f64)
}
pub fn adapt(&self, sigma: f64) -> f64 {
if self.success_history.len() < self.window_size {
return sigma;
}
let success_rate = self.success_rate().unwrap_or(self.target_success_rate);
if success_rate > self.target_success_rate {
sigma * self.increase_factor
} else if success_rate < self.target_success_rate {
sigma * self.decrease_factor
} else {
sigma
}
}
pub fn reset(&mut self) {
self.success_history.clear();
}
}
impl Default for OneFifthRule {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct AdaptiveOperatorSelection {
pub num_operators: usize,
pub weights: Vec<f64>,
pub learning_rate: f64,
pub min_probability: f64,
pub decay: f64,
}
impl AdaptiveOperatorSelection {
pub fn new(num_operators: usize) -> Self {
assert!(num_operators > 0, "Must have at least one operator");
Self {
num_operators,
weights: vec![1.0 / num_operators as f64; num_operators],
learning_rate: 0.1,
min_probability: 0.05,
decay: 0.99,
}
}
pub fn with_learning_rate(mut self, rate: f64) -> Self {
self.learning_rate = rate;
self
}
pub fn with_min_probability(mut self, prob: f64) -> Self {
self.min_probability = prob;
self
}
pub fn with_decay(mut self, decay: f64) -> Self {
self.decay = decay;
self
}
pub fn select<R: Rng>(&self, rng: &mut R) -> usize {
let dist = WeightedIndex::new(&self.weights).unwrap();
dist.sample(rng)
}
pub fn update(&mut self, operator_idx: usize, fitness_improvement: f64) {
assert!(operator_idx < self.num_operators);
for w in &mut self.weights {
*w *= self.decay;
}
let reward = fitness_improvement.max(0.0);
self.weights[operator_idx] += self.learning_rate * reward;
self.normalize_weights();
}
fn normalize_weights(&mut self) {
let sum: f64 = self.weights.iter().sum();
if sum <= 0.0 {
for w in &mut self.weights {
*w = 1.0 / self.num_operators as f64;
}
return;
}
for w in &mut self.weights {
*w /= sum;
}
let n = self.num_operators as f64;
let mut deficit = 0.0;
let mut excess_count = 0;
for w in &mut self.weights {
if *w < self.min_probability / n {
deficit += self.min_probability / n - *w;
*w = self.min_probability / n;
} else {
excess_count += 1;
}
}
if deficit > 0.0 && excess_count > 0 {
let reduction = deficit / excess_count as f64;
for w in &mut self.weights {
if *w > self.min_probability / n + reduction {
*w -= reduction;
}
}
}
let sum: f64 = self.weights.iter().sum();
for w in &mut self.weights {
*w /= sum;
}
}
pub fn probabilities(&self) -> &[f64] {
&self.weights
}
pub fn reset(&mut self) {
for w in &mut self.weights {
*w = 1.0 / self.num_operators as f64;
}
}
}
#[derive(Clone, Debug)]
pub struct SlidingWindowStats {
values: VecDeque<f64>,
window_size: usize,
}
impl SlidingWindowStats {
pub fn new(window_size: usize) -> Self {
Self {
values: VecDeque::with_capacity(window_size),
window_size,
}
}
pub fn push(&mut self, value: f64) {
self.values.push_back(value);
if self.values.len() > self.window_size {
self.values.pop_front();
}
}
pub fn mean(&self) -> Option<f64> {
if self.values.is_empty() {
return None;
}
Some(self.values.iter().sum::<f64>() / self.values.len() as f64)
}
pub fn variance(&self) -> Option<f64> {
if self.values.len() < 2 {
return None;
}
let mean = self.mean()?;
let sum_sq: f64 = self.values.iter().map(|v| (v - mean).powi(2)).sum();
Some(sum_sq / (self.values.len() - 1) as f64)
}
pub fn std_dev(&self) -> Option<f64> {
self.variance().map(|v| v.sqrt())
}
pub fn min(&self) -> Option<f64> {
self.values.iter().copied().reduce(f64::min)
}
pub fn max(&self) -> Option<f64> {
self.values.iter().copied().reduce(f64::max)
}
pub fn is_full(&self) -> bool {
self.values.len() >= self.window_size
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
pub fn clear(&mut self) {
self.values.clear();
}
}
#[derive(Clone, Debug)]
pub struct AdaptiveMutationRate {
pub rate: f64,
pub min_rate: f64,
pub max_rate: f64,
pub increase_factor: f64,
pub decrease_factor: f64,
stats: SlidingWindowStats,
improvement_threshold: f64,
}
impl AdaptiveMutationRate {
pub fn new(initial_rate: f64) -> Self {
Self {
rate: initial_rate,
min_rate: 0.001,
max_rate: 0.5,
increase_factor: 1.1,
decrease_factor: 0.9,
stats: SlidingWindowStats::new(20),
improvement_threshold: 0.3, }
}
pub fn record(&mut self, improved: bool) {
self.stats.push(if improved { 1.0 } else { 0.0 });
}
pub fn adapt(&mut self) {
if !self.stats.is_full() {
return;
}
let improvement_rate = self.stats.mean().unwrap_or(0.0);
if improvement_rate < self.improvement_threshold {
self.rate = (self.rate * self.increase_factor).min(self.max_rate);
} else if improvement_rate > self.improvement_threshold * 1.5 {
self.rate = (self.rate * self.decrease_factor).max(self.min_rate);
}
}
pub fn current_rate(&self) -> f64 {
self.rate
}
}
#[derive(Clone, Debug)]
pub struct DiversityBasedAdaptation {
diversity_history: SlidingWindowStats,
pub target_diversity: f64,
pub tolerance: f64,
}
impl DiversityBasedAdaptation {
pub fn new(target_diversity: f64) -> Self {
Self {
diversity_history: SlidingWindowStats::new(10),
target_diversity,
tolerance: 0.1,
}
}
pub fn record_diversity(&mut self, diversity: f64) {
self.diversity_history.push(diversity);
}
pub fn mutation_multiplier(&self) -> f64 {
let Some(current_diversity) = self.diversity_history.mean() else {
return 1.0;
};
if current_diversity < self.target_diversity * (1.0 - self.tolerance) {
1.5
} else if current_diversity > self.target_diversity * (1.0 + self.tolerance) {
0.8
} else {
1.0
}
}
pub fn selection_pressure_multiplier(&self) -> f64 {
let Some(current_diversity) = self.diversity_history.mean() else {
return 1.0;
};
if current_diversity < self.target_diversity * (1.0 - self.tolerance) {
0.8
} else if current_diversity > self.target_diversity * (1.0 + self.tolerance) {
1.2
} else {
1.0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_one_fifth_rule_increase() {
let mut rule = OneFifthRule::new().with_window_size(5);
for _ in 0..5 {
rule.record(true);
}
let sigma = 1.0;
let new_sigma = rule.adapt(sigma);
assert!(new_sigma > sigma);
}
#[test]
fn test_one_fifth_rule_decrease() {
let mut rule = OneFifthRule::new().with_window_size(5);
for _ in 0..5 {
rule.record(false);
}
let sigma = 1.0;
let new_sigma = rule.adapt(sigma);
assert!(new_sigma < sigma);
}
#[test]
fn test_one_fifth_rule_at_target() {
let mut rule = OneFifthRule::new()
.with_window_size(5)
.with_target_rate(0.2);
rule.record(true);
for _ in 0..4 {
rule.record(false);
}
let sigma = 1.0;
let new_sigma = rule.adapt(sigma);
assert!((new_sigma - sigma).abs() < 1e-10);
}
#[test]
fn test_adaptive_operator_selection() {
let mut aos = AdaptiveOperatorSelection::new(3);
let mut rng = rand::thread_rng();
assert_eq!(aos.probabilities().len(), 3);
for &p in aos.probabilities() {
assert!((p - 1.0 / 3.0).abs() < 1e-10);
}
aos.update(0, 10.0);
assert!(aos.probabilities()[0] > aos.probabilities()[1]);
let _ = aos.select(&mut rng);
}
#[test]
fn test_sliding_window_stats() {
let mut stats = SlidingWindowStats::new(5);
assert!(stats.mean().is_none());
stats.push(1.0);
stats.push(2.0);
stats.push(3.0);
assert!((stats.mean().unwrap() - 2.0).abs() < 1e-10);
assert!((stats.min().unwrap() - 1.0).abs() < 1e-10);
assert!((stats.max().unwrap() - 3.0).abs() < 1e-10);
stats.push(4.0);
stats.push(5.0);
assert!(stats.is_full());
stats.push(6.0);
assert_eq!(stats.len(), 5);
assert!((stats.min().unwrap() - 2.0).abs() < 1e-10);
}
#[test]
fn test_adaptive_mutation_rate() {
let mut amr = AdaptiveMutationRate::new(0.1);
for _ in 0..25 {
amr.record(false);
}
amr.adapt();
assert!(amr.current_rate() > 0.1);
}
#[test]
fn test_diversity_based_adaptation() {
let mut dba = DiversityBasedAdaptation::new(0.5);
for _ in 0..10 {
dba.record_diversity(0.2);
}
assert!(dba.mutation_multiplier() > 1.0);
assert!(dba.selection_pressure_multiplier() < 1.0);
}
}