#[allow(unused_imports)]
use crate::prelude::*;
#[derive(Debug, Clone)]
pub struct Parameter {
pub name: String,
pub value: f64,
pub min: f64,
pub max: f64,
pub step: f64,
}
impl Parameter {
#[must_use]
pub fn new(name: String, value: f64, min: f64, max: f64, step: f64) -> Self {
Self {
name,
value: value.clamp(min, max),
min,
max,
step,
}
}
pub fn increase(&mut self) {
self.value = (self.value + self.step).min(self.max);
}
pub fn decrease(&mut self) {
self.value = (self.value - self.step).max(self.min);
}
pub fn set(&mut self, value: f64) {
self.value = value.clamp(self.min, self.max);
}
#[must_use]
pub fn get(&self) -> f64 {
self.value
}
}
#[derive(Debug, Clone, Default)]
pub struct PerformanceMetrics {
pub conflicts: u64,
pub decisions: u64,
pub propagations: u64,
pub time_ms: u64,
pub solved: bool,
pub satisfiable: Option<bool>,
}
impl PerformanceMetrics {
#[must_use]
pub fn score(&self) -> f64 {
if !self.solved {
return f64::MAX;
}
let mut score = self.time_ms as f64;
let conflict_rate = self.conflicts as f64 / self.time_ms.max(1) as f64;
if conflict_rate > 100.0 {
score *= 1.0 + (conflict_rate - 100.0) / 100.0;
}
let prop_rate = self.propagations as f64 / self.time_ms.max(1) as f64;
if prop_rate > 1000.0 {
score *= 0.95;
}
score
}
}
#[derive(Debug, Clone)]
pub struct Configuration {
pub parameters: HashMap<String, f64>,
pub metrics: Option<PerformanceMetrics>,
}
impl Configuration {
#[must_use]
pub fn new(parameters: HashMap<String, f64>) -> Self {
Self {
parameters,
metrics: None,
}
}
#[must_use]
pub fn score(&self) -> f64 {
self.metrics.as_ref().map_or(f64::MAX, |m| m.score())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TuningStrategy {
GridSearch,
RandomSearch,
HillClimbing,
Adaptive,
}
#[derive(Debug, Default, Clone)]
pub struct AutotuningStats {
pub configurations_tried: u64,
pub best_score: f64,
pub improvements: u64,
}
pub struct Autotuner {
parameters: HashMap<String, Parameter>,
strategy: TuningStrategy,
history: Vec<Configuration>,
best_config: Option<Configuration>,
stats: AutotuningStats,
}
impl Autotuner {
#[must_use]
pub fn new(strategy: TuningStrategy) -> Self {
Self {
parameters: HashMap::new(),
strategy,
history: Vec::new(),
best_config: None,
stats: AutotuningStats::default(),
}
}
pub fn add_parameter(&mut self, param: Parameter) {
self.parameters.insert(param.name.clone(), param);
}
#[must_use]
pub fn get_parameters(&self) -> HashMap<String, f64> {
self.parameters
.iter()
.map(|(name, param)| (name.clone(), param.value))
.collect()
}
#[must_use]
pub fn stats(&self) -> &AutotuningStats {
&self.stats
}
pub fn reset_stats(&mut self) {
self.stats = AutotuningStats::default();
self.stats.best_score = f64::MAX;
}
pub fn record_performance(&mut self, metrics: PerformanceMetrics) {
let config = Configuration {
parameters: self.get_parameters(),
metrics: Some(metrics),
};
let score = config.score();
self.stats.configurations_tried += 1;
if score < self.stats.best_score || self.best_config.is_none() {
self.stats.best_score = score;
self.stats.improvements += 1;
self.best_config = Some(config.clone());
}
self.history.push(config);
self.adjust_parameters();
}
#[must_use]
pub fn best_configuration(&self) -> Option<&Configuration> {
self.best_config.as_ref()
}
pub fn apply_best(&mut self) {
if let Some(best) = &self.best_config {
for (name, &value) in &best.parameters {
if let Some(param) = self.parameters.get_mut(name) {
param.set(value);
}
}
}
}
#[must_use]
pub fn next_configuration(&mut self) -> HashMap<String, f64> {
match self.strategy {
TuningStrategy::GridSearch => self.grid_search_next(),
TuningStrategy::RandomSearch => self.random_search_next(),
_ => self.get_parameters(),
}
}
fn adjust_parameters(&mut self) {
match self.strategy {
TuningStrategy::HillClimbing => self.hill_climbing_adjust(),
TuningStrategy::Adaptive => self.adaptive_adjust(),
_ => {}
}
}
fn hill_climbing_adjust(&mut self) {
if self.history.len() < 2 {
return;
}
let current = &self.history[self.history.len() - 1];
let previous = &self.history[self.history.len() - 2];
let current_score = current.score();
let previous_score = previous.score();
for (name, param) in &mut self.parameters {
let current_val = current.parameters.get(name).copied().unwrap_or(param.value);
let previous_val = previous
.parameters
.get(name)
.copied()
.unwrap_or(param.value);
if current_score < previous_score {
if current_val > previous_val {
param.increase();
} else if current_val < previous_val {
param.decrease();
}
} else {
if current_val > previous_val {
param.decrease();
} else if current_val < previous_val {
param.increase();
}
}
}
}
fn adaptive_adjust(&mut self) {
if self.history.is_empty() {
return;
}
let recent_window = 5.min(self.history.len());
let recent_configs: Vec<_> = self.history.iter().rev().take(recent_window).collect();
let mut total_conflicts = 0;
let mut total_decisions = 0;
let mut count = 0;
for config in &recent_configs {
if let Some(ref metrics) = config.metrics {
total_conflicts += metrics.conflicts;
total_decisions += metrics.decisions;
count += 1;
}
}
if count == 0 {
return;
}
let avg_conflicts = total_conflicts / count;
let avg_decisions = total_decisions / count;
if let Some(restart_param) = self.parameters.get_mut("restart_factor") {
if avg_conflicts > 10000 {
restart_param.increase();
} else if avg_conflicts < 1000 {
restart_param.decrease();
}
}
if let Some(decay_param) = self.parameters.get_mut("variable_decay") {
if avg_decisions > 5000 {
decay_param.increase();
} else if avg_decisions < 500 {
decay_param.decrease();
}
}
}
fn grid_search_next(&self) -> HashMap<String, f64> {
self.get_parameters()
}
fn random_search_next(&mut self) -> HashMap<String, f64> {
use core::hash::{BuildHasher, Hash, Hasher};
let mut result = HashMap::new();
let state = core::hash::BuildHasherDefault::<rustc_hash::FxHasher>::default();
for (name, param) in &self.parameters {
let mut hasher = state.build_hasher();
name.hash(&mut hasher);
self.history.len().hash(&mut hasher);
let hash = hasher.finish();
let range = param.max - param.min;
let random_factor = (hash % 1000) as f64 / 1000.0;
let value = param.min + range * random_factor;
result.insert(name.clone(), value);
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parameter_creation() {
let param = Parameter::new("test".to_string(), 0.5, 0.0, 1.0, 0.1);
assert_eq!(param.name, "test");
assert_eq!(param.value, 0.5);
}
#[test]
fn test_parameter_increase_decrease() {
let mut param = Parameter::new("test".to_string(), 0.5, 0.0, 1.0, 0.1);
param.increase();
assert_eq!(param.value, 0.6);
param.decrease();
assert_eq!(param.value, 0.5);
}
#[test]
fn test_parameter_bounds() {
let mut param = Parameter::new("test".to_string(), 0.9, 0.0, 1.0, 0.2);
param.increase();
assert_eq!(param.value, 1.0);
param.decrease();
param.decrease();
param.decrease();
param.decrease();
param.decrease();
param.decrease();
assert_eq!(param.value, 0.0); }
#[test]
fn test_autotuner_creation() {
let tuner = Autotuner::new(TuningStrategy::HillClimbing);
assert_eq!(tuner.stats().configurations_tried, 0);
}
#[test]
fn test_add_parameter() {
let mut tuner = Autotuner::new(TuningStrategy::HillClimbing);
let param = Parameter::new("test".to_string(), 0.5, 0.0, 1.0, 0.1);
tuner.add_parameter(param);
let params = tuner.get_parameters();
assert_eq!(params.get("test"), Some(&0.5));
}
#[test]
fn test_record_performance() {
let mut tuner = Autotuner::new(TuningStrategy::HillClimbing);
let param = Parameter::new("test".to_string(), 0.5, 0.0, 1.0, 0.1);
tuner.add_parameter(param);
let metrics = PerformanceMetrics {
conflicts: 100,
decisions: 200,
propagations: 1000,
time_ms: 50,
solved: true,
satisfiable: Some(true),
};
tuner.record_performance(metrics);
assert_eq!(tuner.stats().configurations_tried, 1);
}
#[test]
fn test_best_configuration() {
let mut tuner = Autotuner::new(TuningStrategy::HillClimbing);
let param = Parameter::new("test".to_string(), 0.5, 0.0, 1.0, 0.1);
tuner.add_parameter(param);
let metrics1 = PerformanceMetrics {
conflicts: 100,
decisions: 200,
propagations: 1000,
time_ms: 100,
solved: true,
satisfiable: Some(true),
};
tuner.record_performance(metrics1);
let metrics2 = PerformanceMetrics {
conflicts: 50,
decisions: 100,
propagations: 500,
time_ms: 50,
solved: true,
satisfiable: Some(true),
};
tuner.record_performance(metrics2);
assert_eq!(tuner.stats().improvements, 2);
assert!(tuner.best_configuration().is_some());
}
#[test]
fn test_performance_score() {
let metrics = PerformanceMetrics {
conflicts: 100,
decisions: 200,
propagations: 1000,
time_ms: 50,
solved: true,
satisfiable: Some(true),
};
let score = metrics.score();
assert!(score > 0.0);
assert!(score < f64::MAX);
}
#[test]
fn test_unsolved_score() {
let metrics = PerformanceMetrics {
conflicts: 100,
decisions: 200,
propagations: 1000,
time_ms: 50,
solved: false,
satisfiable: None,
};
let score = metrics.score();
assert_eq!(score, f64::MAX);
}
#[test]
fn test_apply_best() {
let mut tuner = Autotuner::new(TuningStrategy::HillClimbing);
let param = Parameter::new("test".to_string(), 0.5, 0.0, 1.0, 0.1);
tuner.add_parameter(param);
let metrics = PerformanceMetrics {
conflicts: 100,
decisions: 200,
propagations: 1000,
time_ms: 50,
solved: true,
satisfiable: Some(true),
};
tuner.record_performance(metrics);
if let Some(param) = tuner.parameters.get_mut("test") {
param.set(0.8);
}
tuner.apply_best();
let params = tuner.get_parameters();
assert_eq!(params.get("test"), Some(&0.5));
}
#[test]
fn test_different_strategies() {
let strategies = vec![
TuningStrategy::GridSearch,
TuningStrategy::RandomSearch,
TuningStrategy::HillClimbing,
TuningStrategy::Adaptive,
];
for strategy in strategies {
let tuner = Autotuner::new(strategy);
assert_eq!(tuner.strategy, strategy);
}
}
}