use std::collections::HashMap;
use std::time::{Duration, Instant};
use super::architecture::ArchitectureCandidate;
pub struct SearchHistory<T: scirs2_core::numeric::Float> {
pub records: Vec<SearchRecord<T>>,
pub best_architectures: Vec<ArchitectureCandidate>,
pub stats: SearchStatistics<T>,
pub config: HistoryConfig,
}
#[derive(Debug, Clone)]
pub struct SearchRecord<T: scirs2_core::numeric::Float> {
pub id: usize,
pub timestamp: Instant,
pub architecture: ArchitectureCandidate,
pub performance: T,
pub iteration: usize,
pub strategy: String,
pub metadata: HashMap<String, String>,
}
#[derive(Debug, Clone)]
pub struct SearchStatistics<T: scirs2_core::numeric::Float> {
pub total_evaluations: usize,
pub total_time: Duration,
pub best_performance: T,
pub avg_performance: T,
pub improvement_rate: T,
pub convergence: ConvergenceMetrics<T>,
}
#[derive(Debug, Clone)]
pub struct ConvergenceMetrics<T: scirs2_core::numeric::Float> {
pub converged: bool,
pub convergence_iter: Option<usize>,
pub stagnation_count: usize,
pub last_improvement: T,
}
#[derive(Debug, Clone)]
pub struct HistoryConfig {
pub max_records: usize,
pub detailed_tracking: bool,
pub persistence: bool,
pub convergence_detection: ConvergenceConfig,
}
#[derive(Debug, Clone)]
pub struct ConvergenceConfig {
pub enabled: bool,
pub patience: usize,
pub threshold: f64,
pub window_size: usize,
}
impl<T: scirs2_core::numeric::Float + Default + std::fmt::Debug + Clone + Send + Sync> SearchHistory<T> {
pub fn new(config: HistoryConfig) -> Self {
Self {
records: Vec::new(),
best_architectures: Vec::new(),
stats: SearchStatistics::default(),
config,
}
}
pub fn add_record(
&mut self,
architecture: ArchitectureCandidate,
performance: T,
iteration: usize,
strategy: String,
) {
let record = SearchRecord {
id: self.records.len(),
timestamp: Instant::now(),
architecture: architecture.clone(),
performance,
iteration,
strategy,
metadata: HashMap::new(),
};
self.records.push(record);
if performance > self.stats.best_performance {
self.stats.best_performance = performance;
self.best_architectures.insert(0, architecture);
if self.best_architectures.len() > 10 {
self.best_architectures.truncate(10);
}
}
self.update_statistics();
if self.config.convergence_detection.enabled {
self.check_convergence();
}
if self.records.len() > self.config.max_records {
self.records.remove(0);
}
}
fn update_statistics(&mut self) {
self.stats.total_evaluations = self.records.len();
if !self.records.is_empty() {
let performances: Vec<T> = self.records.iter().map(|r| r.performance).collect();
let sum = performances.iter().fold(T::zero(), |acc, &x| acc + x);
self.stats.avg_performance = sum / T::from(performances.len()).expect("unwrap failed");
if self.records.len() > 1 {
let recent = self.records[self.records.len() - 1].performance;
let initial = self.records[0].performance;
self.stats.improvement_rate = recent - initial;
}
}
}
fn check_convergence(&mut self) {
let config = &self.config.convergence_detection;
if self.records.len() < config.window_size {
return;
}
let recent_records = &self.records[self.records.len() - config.window_size..];
let recent_best = recent_records.iter()
.map(|r| r.performance)
.fold(T::neg_infinity(), T::max);
let improvement = recent_best - self.stats.convergence.last_improvement;
if improvement < scirs2_core::numeric::NumCast::from(config.threshold).unwrap_or_else(|| T::zero()) {
self.stats.convergence.stagnation_count += 1;
} else {
self.stats.convergence.stagnation_count = 0;
self.stats.convergence.last_improvement = recent_best;
}
if self.stats.convergence.stagnation_count >= config.patience {
self.stats.convergence.converged = true;
self.stats.convergence.convergence_iter = Some(self.records.len());
}
}
pub fn get_summary(&self) -> SearchSummary<T> {
SearchSummary {
total_evaluations: self.stats.total_evaluations,
best_performance: self.stats.best_performance,
avg_performance: self.stats.avg_performance,
converged: self.stats.convergence.converged,
convergence_iter: self.stats.convergence.convergence_iter,
}
}
}
#[derive(Debug, Clone)]
pub struct SearchSummary<T: scirs2_core::numeric::Float> {
pub total_evaluations: usize,
pub best_performance: T,
pub avg_performance: T,
pub converged: bool,
pub convergence_iter: Option<usize>,
}
impl Default for HistoryConfig {
fn default() -> Self {
Self {
max_records: 10000,
detailed_tracking: true,
persistence: false,
convergence_detection: ConvergenceConfig {
enabled: true,
patience: 50,
threshold: 0.001,
window_size: 20,
},
}
}
}
impl<T: scirs2_core::numeric::Float + Default + Send + Sync> Default for SearchStatistics<T> {
fn default() -> Self {
Self {
total_evaluations: 0,
total_time: Duration::from_secs(0),
best_performance: T::neg_infinity(),
avg_performance: T::zero(),
improvement_rate: T::zero(),
convergence: ConvergenceMetrics {
converged: false,
convergence_iter: None,
stagnation_count: 0,
last_improvement: T::neg_infinity(),
},
}
}
}