use super::config::*;
use super::performance::PerformanceSnapshot;
use crate::OptimizerError as OptimError;
use scirs2_core::ndarray::{Array1, Array2};
use scirs2_core::numeric::Float;
use std::collections::{HashMap, VecDeque};
use std::fmt::Debug;
use std::time::{Duration, SystemTime};
type Result<T> = std::result::Result<T, OptimError>;
#[derive(Debug)]
pub struct ConvergenceAnalyzer<T: Float + Debug + Send + Sync + Debug> {
config: ConvergenceAnalysisConfig,
detectors: Vec<ConvergenceDetector<T>>,
analysis_history: VecDeque<ConvergenceAnalysis<T>>,
current_state: ConvergenceState<T>,
early_stopping: EarlyStoppingController<T>,
evidence_tracker: ConvergenceEvidenceTracker<T>,
}
#[derive(Debug)]
pub struct ConvergenceDetector<T: Float + Debug + Send + Sync + Debug> {
method: ConvergenceDetectionMethod,
parameters: ConvergenceParameters<T>,
detection_history: VecDeque<ConvergenceDetection<T>>,
current_detection: Option<ConvergenceDetection<T>>,
method_state: DetectionMethodState<T>,
}
#[derive(Debug, Clone)]
pub struct ConvergenceDetection<T: Float + Debug + Send + Sync + 'static> {
pub timestamp: SystemTime,
pub step: usize,
pub method: ConvergenceDetectionMethod,
pub converged: bool,
pub confidence: T,
pub evidence: ConvergenceEvidence<T>,
pub metrics: ConvergenceMetrics<T>,
}
#[derive(Debug, Clone)]
pub struct ConvergenceEvidence<T: Float + Debug + Send + Sync + 'static> {
pub loss_evidence: LossEvidence<T>,
pub gradient_evidence: GradientEvidence<T>,
pub parameter_evidence: ParameterEvidence<T>,
pub statistical_evidence: StatisticalEvidence<T>,
pub validation_evidence: ValidationEvidence<T>,
}
#[derive(Debug, Clone)]
pub struct LossEvidence<T: Float + Debug + Send + Sync + 'static> {
pub loss_change_rate: T,
pub loss_stability: T,
pub plateau_duration: usize,
pub relative_improvement: T,
pub absolute_improvement: T,
}
#[derive(Debug, Clone)]
pub struct GradientEvidence<T: Float + Debug + Send + Sync + 'static> {
pub gradient_norm: T,
pub gradient_norm_change_rate: T,
pub gradient_stability: T,
pub direction_consistency: T,
}
#[derive(Debug, Clone)]
pub struct ParameterEvidence<T: Float + Debug + Send + Sync + 'static> {
pub parameter_change_magnitude: T,
pub parameter_change_rate: T,
pub parameter_stability: T,
pub convergence_ratio: T,
}
#[derive(Debug, Clone)]
pub struct StatisticalEvidence<T: Float + Debug + Send + Sync + 'static> {
pub p_value: T,
pub effect_size: T,
pub confidence_interval: (T, T),
pub test_statistic: T,
pub test_type: StatisticalTestType,
}
#[derive(Debug, Clone, Copy)]
pub enum StatisticalTestType {
TTest,
WilcoxonSignedRank,
KolmogorovSmirnov,
AndersonDarling,
MannWhitney,
}
#[derive(Debug, Clone)]
pub struct ValidationEvidence<T: Float + Debug + Send + Sync + 'static> {
pub val_loss_change: T,
pub val_metric_change: T,
pub generalization_gap: T,
pub overfitting_indicator: T,
}
#[derive(Debug, Clone)]
pub struct ConvergenceMetrics<T: Float + Debug + Send + Sync + 'static> {
pub sensitivity: T,
pub specificity: T,
pub false_positive_rate: T,
pub false_negative_rate: T,
pub detection_latency: usize,
}
#[derive(Debug, Clone)]
pub struct ConvergenceAnalysis<T: Float + Debug + Send + Sync + 'static> {
pub timestamp: SystemTime,
pub step: usize,
pub detections: Vec<ConvergenceDetection<T>>,
pub consensus: ConvergenceConsensus<T>,
pub summary: ConvergenceAnalysisSummary<T>,
pub recommendations: Vec<ConvergenceRecommendation>,
}
#[derive(Debug, Clone)]
pub struct ConvergenceConsensus<T: Float + Debug + Send + Sync + 'static> {
pub convergence_probability: T,
pub consensus_confidence: T,
pub voting_results: HashMap<ConvergenceDetectionMethod, bool>,
pub weighted_score: T,
pub method_agreement: T,
}
#[derive(Debug, Clone)]
pub struct ConvergenceAnalysisSummary<T: Float + Debug + Send + Sync + 'static> {
pub estimated_convergence_time: Option<Duration>,
pub convergence_quality: T,
pub stability_assessment: StabilityAssessment<T>,
pub performance_trajectory: TrajectoryAnalysis<T>,
}
#[derive(Debug, Clone)]
pub struct StabilityAssessment<T: Float + Debug + Send + Sync + 'static> {
pub loss_stability: T,
pub gradient_stability: T,
pub parameter_stability: T,
pub overall_stability: T,
pub stability_trend: StabilityTrend,
}
#[derive(Debug, Clone, Copy)]
pub enum StabilityTrend {
Improving,
Stable,
Degrading,
Oscillating,
Unknown,
}
#[derive(Debug, Clone)]
pub struct TrajectoryAnalysis<T: Float + Debug + Send + Sync + 'static> {
pub trajectory_type: TrajectoryType,
pub convergence_rate: T,
pub trajectory_smoothness: T,
pub phase_transitions: Vec<PhaseTransition<T>>,
}
#[derive(Debug, Clone, Copy)]
pub enum TrajectoryType {
Monotonic,
Oscillatory,
Stepped,
Exponential,
Linear,
Plateau,
Unknown,
}
#[derive(Debug, Clone)]
pub struct PhaseTransition<T: Float + Debug + Send + Sync + 'static> {
pub step: usize,
pub transition_type: TransitionType,
pub performance_change: T,
pub confidence: T,
}
#[derive(Debug, Clone, Copy)]
pub enum TransitionType {
LinearToExponential,
ExponentialToLinear,
FastToSlow,
SlowToFast,
StableToOscillatory,
OscillatoryToStable,
NoiseReduction,
NoiseIncrease,
}
#[derive(Debug, Clone)]
pub enum ConvergenceRecommendation {
ContinueTraining,
EarlyStop,
AdjustLearningRate { factor: f64 },
IncreaseBatchSize { new_size: usize },
AddRegularization { l2_lambda: f64 },
ChangeOptimizer { optimizer: String },
ReduceComplexity,
IncreaseCapacity,
}
#[derive(Debug, Clone)]
pub struct ConvergenceState<T: Float + Debug + Send + Sync + 'static> {
pub converged: bool,
pub confidence: T,
pub steps_since_detection: usize,
pub current_phase: OptimizationPhase,
pub timestamp: SystemTime,
}
#[derive(Debug, Clone, Copy)]
pub enum OptimizationPhase {
Initialization,
FastLearning,
SlowLearning,
FineTuning,
Converged,
Diverged,
Oscillating,
}
#[derive(Debug)]
pub struct EarlyStoppingController<T: Float + Debug + Send + Sync + Debug> {
config: EarlyStoppingConfig<T>,
monitoring_history: VecDeque<T>,
best_metric: Option<T>,
steps_since_improvement: usize,
triggered: bool,
best_weights: Option<Array1<T>>,
}
#[derive(Debug)]
pub struct ConvergenceEvidenceTracker<T: Float + Debug + Send + Sync + Debug> {
evidence_history: VecDeque<ConvergenceEvidence<T>>,
evidence_weights: HashMap<String, T>,
cumulative_score: T,
evidence_trends: HashMap<String, Vec<T>>,
}
#[derive(Debug)]
pub enum DetectionMethodState<T: Float + Debug + Send + Sync + 'static> {
LossBased {
recent_losses: VecDeque<T>,
loss_window: VecDeque<T>,
improvement_history: VecDeque<T>,
},
GradientNorm {
gradient_norms: VecDeque<T>,
norm_changes: VecDeque<T>,
},
ParameterChange {
parameter_changes: VecDeque<T>,
parameter_history: VecDeque<Array1<T>>,
},
ValidationMetric {
validation_metrics: VecDeque<T>,
generalization_gaps: VecDeque<T>,
},
StatisticalTest {
test_statistics: VecDeque<T>,
p_values: VecDeque<T>,
},
MLBased {
features: VecDeque<Array1<T>>,
predictions: VecDeque<T>,
},
}
impl<T: Float + Debug + Send + Sync + 'static + Default + Clone> ConvergenceAnalyzer<T> {
pub fn new(config: ConvergenceAnalysisConfig) -> Result<Self> {
let mut detectors = Vec::new();
for method in &config.detection_methods {
let detector = ConvergenceDetector::new(*method, config.detection_parameters.clone())?;
detectors.push(detector);
}
Ok(Self {
config: config.clone(),
detectors,
analysis_history: VecDeque::new(),
current_state: ConvergenceState::default(),
early_stopping: EarlyStoppingController::new(config.early_stopping_config)?,
evidence_tracker: ConvergenceEvidenceTracker::new(),
})
}
pub fn analyze_convergence(&mut self, snapshot: &PerformanceSnapshot<T>) -> Result<ConvergenceAnalysis<T>> {
let mut detections = Vec::new();
for detector in &mut self.detectors {
let detection = detector.detect_convergence(snapshot)?;
if let Some(detection) = detection {
detections.push(detection);
}
}
let consensus = self.build_consensus(&detections)?;
if !detections.is_empty() {
let evidence = &detections[0].evidence; self.evidence_tracker.update_evidence(evidence.clone())?;
}
let summary = self.generate_summary(&detections, &consensus)?;
let recommendations = self.generate_recommendations(&detections, &consensus, &summary)?;
let analysis = ConvergenceAnalysis {
timestamp: SystemTime::now(),
step: snapshot.step,
detections,
consensus: consensus.clone(),
summary,
recommendations,
};
self.update_convergence_state(&consensus, snapshot.step)?;
if self.config.enable_early_stopping {
self.early_stopping.update(&snapshot.metrics.val_loss.unwrap_or(snapshot.loss))?;
}
if self.analysis_history.len() >= self.config.analysis_window_size {
self.analysis_history.pop_front();
}
self.analysis_history.push_back(analysis.clone());
Ok(analysis)
}
pub fn should_early_stop(&self) -> bool {
self.early_stopping.should_stop()
}
pub fn get_convergence_state(&self) -> &ConvergenceState<T> {
&self.current_state
}
pub fn get_convergence_history(&self) -> &VecDeque<ConvergenceAnalysis<T>> {
&self.analysis_history
}
fn build_consensus(&self, detections: &[ConvergenceDetection<T>]) -> Result<ConvergenceConsensus<T>> {
if detections.is_empty() {
return Ok(ConvergenceConsensus::default());
}
let mut voting_results = HashMap::new();
let mut confidence_sum = T::zero();
let mut converged_count = 0;
for detection in detections {
voting_results.insert(detection.method, detection.converged);
confidence_sum = confidence_sum + detection.confidence;
if detection.converged {
converged_count += 1;
}
}
let convergence_probability = scirs2_core::numeric::NumCast::from(converged_count).unwrap_or_else(|| T::zero()) / T::from(detections.len()).expect("unwrap failed");
let consensus_confidence = confidence_sum / T::from(detections.len()).expect("unwrap failed");
let method_agreement = self.calculate_method_agreement(&voting_results)?;
Ok(ConvergenceConsensus {
convergence_probability,
consensus_confidence,
voting_results,
weighted_score: convergence_probability * consensus_confidence,
method_agreement,
})
}
fn calculate_method_agreement(&self, voting_results: &HashMap<ConvergenceDetectionMethod, bool>) -> Result<T> {
if voting_results.len() < 2 {
return Ok(T::one());
}
let values: Vec<bool> = voting_results.values().cloned().collect();
let agree_count = values.windows(2).filter(|w| w[0] == w[1]).count();
let total_pairs = values.len() - 1;
Ok(scirs2_core::numeric::NumCast::from(agree_count).unwrap_or_else(|| T::zero()) / scirs2_core::numeric::NumCast::from(total_pairs).unwrap_or_else(|| T::zero()))
}
fn generate_summary(
&self,
detections: &[ConvergenceDetection<T>],
consensus: &ConvergenceConsensus<T>,
) -> Result<ConvergenceAnalysisSummary<T>> {
let estimated_convergence_time = if consensus.convergence_probability > scirs2_core::numeric::NumCast::from(0.8).unwrap_or_else(|| T::zero()) {
Some(Duration::from_secs(0)) } else {
self.estimate_convergence_time(detections)?
};
let convergence_quality = consensus.consensus_confidence * consensus.method_agreement;
let stability_assessment = self.assess_stability(detections)?;
let performance_trajectory = self.analyze_trajectory()?;
Ok(ConvergenceAnalysisSummary {
estimated_convergence_time,
convergence_quality,
stability_assessment,
performance_trajectory,
})
}
fn generate_recommendations(
&self,
detections: &[ConvergenceDetection<T>],
consensus: &ConvergenceConsensus<T>,
summary: &ConvergenceAnalysisSummary<T>,
) -> Result<Vec<ConvergenceRecommendation>> {
let mut recommendations = Vec::new();
if consensus.convergence_probability > scirs2_core::numeric::NumCast::from(0.9).unwrap_or_else(|| T::zero()) {
recommendations.push(ConvergenceRecommendation::EarlyStop);
}
else if consensus.convergence_probability < scirs2_core::numeric::NumCast::from(0.3).unwrap_or_else(|| T::zero()) {
recommendations.push(ConvergenceRecommendation::ContinueTraining);
if matches!(summary.stability_assessment.stability_trend, StabilityTrend::Oscillating) {
recommendations.push(ConvergenceRecommendation::AdjustLearningRate { factor: 0.5 });
}
if summary.performance_trajectory.convergence_rate < scirs2_core::numeric::NumCast::from(0.01).unwrap_or_else(|| T::zero()) {
recommendations.push(ConvergenceRecommendation::AdjustLearningRate { factor: 1.5 });
}
}
else {
recommendations.push(ConvergenceRecommendation::ContinueTraining);
}
Ok(recommendations)
}
fn update_convergence_state(&mut self, consensus: &ConvergenceConsensus<T>, step: usize) -> Result<()> {
let converged = consensus.convergence_probability > scirs2_core::numeric::NumCast::from(0.8).unwrap_or_else(|| T::zero());
if converged && !self.current_state.converged {
self.current_state.converged = true;
self.current_state.steps_since_detection = 0;
} else if converged {
self.current_state.steps_since_detection += 1;
} else {
self.current_state.converged = false;
self.current_state.steps_since_detection = 0;
}
self.current_state.confidence = consensus.consensus_confidence;
self.current_state.timestamp = SystemTime::now();
Ok(())
}
fn estimate_convergence_time(&self, _detections: &[ConvergenceDetection<T>]) -> Result<Option<Duration>> {
Ok(Some(Duration::from_secs(3600))) }
fn assess_stability(&self, _detections: &[ConvergenceDetection<T>]) -> Result<StabilityAssessment<T>> {
Ok(StabilityAssessment {
loss_stability: scirs2_core::numeric::NumCast::from(0.8).unwrap_or_else(|| T::zero()),
gradient_stability: scirs2_core::numeric::NumCast::from(0.7).unwrap_or_else(|| T::zero()),
parameter_stability: scirs2_core::numeric::NumCast::from(0.9).unwrap_or_else(|| T::zero()),
overall_stability: scirs2_core::numeric::NumCast::from(0.8).unwrap_or_else(|| T::zero()),
stability_trend: StabilityTrend::Stable,
})
}
fn analyze_trajectory(&self) -> Result<TrajectoryAnalysis<T>> {
Ok(TrajectoryAnalysis {
trajectory_type: TrajectoryType::Monotonic,
convergence_rate: scirs2_core::numeric::NumCast::from(0.05).unwrap_or_else(|| T::zero()),
trajectory_smoothness: scirs2_core::numeric::NumCast::from(0.8).unwrap_or_else(|| T::zero()),
phase_transitions: Vec::new(),
})
}
}
impl<T: Float + Debug + Send + Sync + 'static + Default + Clone> ConvergenceDetector<T> {
pub fn new(method: ConvergenceDetectionMethod, parameters: ConvergenceParameters<T>) -> Result<Self> {
let method_state = match method {
ConvergenceDetectionMethod::LossBased => DetectionMethodState::LossBased {
recent_losses: VecDeque::new(),
loss_window: VecDeque::new(),
improvement_history: VecDeque::new(),
},
ConvergenceDetectionMethod::GradientNorm => DetectionMethodState::GradientNorm {
gradient_norms: VecDeque::new(),
norm_changes: VecDeque::new(),
},
ConvergenceDetectionMethod::ParameterChange => DetectionMethodState::ParameterChange {
parameter_changes: VecDeque::new(),
parameter_history: VecDeque::new(),
},
ConvergenceDetectionMethod::ValidationMetric => DetectionMethodState::ValidationMetric {
validation_metrics: VecDeque::new(),
generalization_gaps: VecDeque::new(),
},
ConvergenceDetectionMethod::StatisticalTest => DetectionMethodState::StatisticalTest {
test_statistics: VecDeque::new(),
p_values: VecDeque::new(),
},
ConvergenceDetectionMethod::MLBased => DetectionMethodState::MLBased {
features: VecDeque::new(),
predictions: VecDeque::new(),
},
};
Ok(Self {
method,
parameters,
detection_history: VecDeque::new(),
current_detection: None,
method_state,
})
}
pub fn detect_convergence(&mut self, snapshot: &PerformanceSnapshot<T>) -> Result<Option<ConvergenceDetection<T>>> {
let detection = match self.method {
ConvergenceDetectionMethod::LossBased => self.detect_loss_based(snapshot)?,
ConvergenceDetectionMethod::GradientNorm => self.detect_gradient_norm_based(snapshot)?,
ConvergenceDetectionMethod::ParameterChange => self.detect_parameter_change_based(snapshot)?,
ConvergenceDetectionMethod::ValidationMetric => self.detect_validation_metric_based(snapshot)?,
ConvergenceDetectionMethod::StatisticalTest => self.detect_statistical_test_based(snapshot)?,
ConvergenceDetectionMethod::MLBased => self.detect_ml_based(snapshot)?,
};
if let Some(detection) = &detection {
self.current_detection = Some(detection.clone());
self.detection_history.push_back(detection.clone());
if self.detection_history.len() > 100 {
self.detection_history.pop_front();
}
}
Ok(detection)
}
fn detect_loss_based(&mut self, snapshot: &PerformanceSnapshot<T>) -> Result<Option<ConvergenceDetection<T>>> {
if let DetectionMethodState::LossBased { recent_losses, loss_window, improvement_history } = &mut self.method_state {
recent_losses.push_back(snapshot.loss);
if recent_losses.len() > self.parameters.window_size {
recent_losses.pop_front();
}
if recent_losses.len() < self.parameters.window_size {
return Ok(None);
}
let first_loss = recent_losses.front().expect("unwrap failed");
let last_loss = recent_losses.back().expect("unwrap failed");
let loss_change = (*first_loss - *last_loss).abs();
let relative_change = loss_change / *first_loss;
let converged = relative_change < self.parameters.loss_tolerance;
let confidence = if converged {
T::one() - relative_change / self.parameters.loss_tolerance
} else {
relative_change / self.parameters.loss_tolerance
};
let evidence = ConvergenceEvidence {
loss_evidence: LossEvidence {
loss_change_rate: relative_change,
loss_stability: scirs2_core::numeric::NumCast::from(0.8).unwrap_or_else(|| T::zero()), plateau_duration: 0,
relative_improvement: relative_change,
absolute_improvement: loss_change,
},
gradient_evidence: GradientEvidence::default(),
parameter_evidence: ParameterEvidence::default(),
statistical_evidence: StatisticalEvidence::default(),
validation_evidence: ValidationEvidence::default(),
};
Ok(Some(ConvergenceDetection {
timestamp: SystemTime::now(),
step: snapshot.step,
method: self.method,
converged,
confidence,
evidence,
metrics: ConvergenceMetrics::default(),
}))
} else {
Err(OptimError::InvalidState("Invalid method state for loss-based detection".to_string()))
}
}
fn detect_gradient_norm_based(&mut self, snapshot: &PerformanceSnapshot<T>) -> Result<Option<ConvergenceDetection<T>>> {
if let Some(gradient_norm) = snapshot.gradient_norm {
if let DetectionMethodState::GradientNorm { gradient_norms, norm_changes } = &mut self.method_state {
gradient_norms.push_back(gradient_norm);
if gradient_norms.len() > self.parameters.window_size {
gradient_norms.pop_front();
}
if gradient_norms.len() < 2 {
return Ok(None);
}
let prev_norm = gradient_norms[gradient_norms.len() - 2];
let norm_change = (gradient_norm - prev_norm).abs();
norm_changes.push_back(norm_change);
if norm_changes.len() > self.parameters.window_size {
norm_changes.pop_front();
}
let converged = gradient_norm < self.parameters.gradient_tolerance;
let confidence = if converged {
T::one() - gradient_norm / self.parameters.gradient_tolerance
} else {
self.parameters.gradient_tolerance / gradient_norm
};
let evidence = ConvergenceEvidence {
loss_evidence: LossEvidence::default(),
gradient_evidence: GradientEvidence {
gradient_norm,
gradient_norm_change_rate: norm_change,
gradient_stability: scirs2_core::numeric::NumCast::from(0.8).unwrap_or_else(|| T::zero()),
direction_consistency: scirs2_core::numeric::NumCast::from(0.9).unwrap_or_else(|| T::zero()),
},
parameter_evidence: ParameterEvidence::default(),
statistical_evidence: StatisticalEvidence::default(),
validation_evidence: ValidationEvidence::default(),
};
return Ok(Some(ConvergenceDetection {
timestamp: SystemTime::now(),
step: snapshot.step,
method: self.method,
converged,
confidence,
evidence,
metrics: ConvergenceMetrics::default(),
}));
}
}
Ok(None)
}
fn detect_parameter_change_based(&mut self, _snapshot: &PerformanceSnapshot<T>) -> Result<Option<ConvergenceDetection<T>>> {
Ok(None)
}
fn detect_validation_metric_based(&mut self, snapshot: &PerformanceSnapshot<T>) -> Result<Option<ConvergenceDetection<T>>> {
if let Some(val_loss) = snapshot.metrics.val_loss {
let converged = val_loss < self.parameters.loss_tolerance;
let confidence = if converged { scirs2_core::numeric::NumCast::from(0.8).unwrap_or_else(|| T::zero()) } else { scirs2_core::numeric::NumCast::from(0.2).unwrap_or_else(|| T::zero()) };
let evidence = ConvergenceEvidence {
loss_evidence: LossEvidence::default(),
gradient_evidence: GradientEvidence::default(),
parameter_evidence: ParameterEvidence::default(),
statistical_evidence: StatisticalEvidence::default(),
validation_evidence: ValidationEvidence {
val_loss_change: val_loss - snapshot.loss,
val_metric_change: T::zero(),
generalization_gap: val_loss - snapshot.loss,
overfitting_indicator: if val_loss > snapshot.loss { T::one() } else { T::zero() },
},
};
return Ok(Some(ConvergenceDetection {
timestamp: SystemTime::now(),
step: snapshot.step,
method: self.method,
converged,
confidence,
evidence,
metrics: ConvergenceMetrics::default(),
}));
}
Ok(None)
}
fn detect_statistical_test_based(&mut self, _snapshot: &PerformanceSnapshot<T>) -> Result<Option<ConvergenceDetection<T>>> {
Ok(None)
}
fn detect_ml_based(&mut self, _snapshot: &PerformanceSnapshot<T>) -> Result<Option<ConvergenceDetection<T>>> {
Ok(None)
}
}
impl<T: Float + Debug + Send + Sync + 'static + Default + Clone> EarlyStoppingController<T> {
pub fn new(config: EarlyStoppingConfig<T>) -> Result<Self> {
Ok(Self {
config,
monitoring_history: VecDeque::new(),
best_metric: None,
steps_since_improvement: 0,
triggered: false,
best_weights: None,
})
}
pub fn update(&mut self, metric_value: &T) -> Result<()> {
self.monitoring_history.push_back(*metric_value);
let improved = match self.best_metric {
Some(best) => match self.config.mode {
EarlyStoppingMode::Min => *metric_value < best - self.config.min_delta,
EarlyStoppingMode::Max => *metric_value > best + self.config.min_delta,
},
None => true,
};
if improved {
self.best_metric = Some(*metric_value);
self.steps_since_improvement = 0;
} else {
self.steps_since_improvement += 1;
}
if self.steps_since_improvement >= self.config.patience {
self.triggered = true;
}
Ok(())
}
pub fn should_stop(&self) -> bool {
self.triggered && self.config.enabled
}
pub fn reset(&mut self) {
self.best_metric = None;
self.steps_since_improvement = 0;
self.triggered = false;
self.monitoring_history.clear();
}
}
impl<T: Float + Debug + Send + Sync + 'static + Default + Clone> ConvergenceEvidenceTracker<T> {
pub fn new() -> Self {
Self {
evidence_history: VecDeque::new(),
evidence_weights: HashMap::new(),
cumulative_score: T::zero(),
evidence_trends: HashMap::new(),
}
}
pub fn update_evidence(&mut self, evidence: ConvergenceEvidence<T>) -> Result<()> {
self.evidence_history.push_back(evidence.clone());
if self.evidence_history.len() > 1000 {
self.evidence_history.pop_front();
}
self.cumulative_score = self.cumulative_score + evidence.loss_evidence.loss_stability;
Ok(())
}
}
impl<T: Float + Debug + Send + Sync + 'static> Default for ConvergenceState<T> {
fn default() -> Self {
Self {
converged: false,
confidence: T::zero(),
steps_since_detection: 0,
current_phase: OptimizationPhase::Initialization,
timestamp: SystemTime::now(),
}
}
}
impl<T: Float + Debug + Send + Sync + 'static> Default for ConvergenceConsensus<T> {
fn default() -> Self {
Self {
convergence_probability: T::zero(),
consensus_confidence: T::zero(),
voting_results: HashMap::new(),
weighted_score: T::zero(),
method_agreement: T::zero(),
}
}
}
impl<T: Float + Debug + Send + Sync + 'static> Default for LossEvidence<T> {
fn default() -> Self {
Self {
loss_change_rate: T::zero(),
loss_stability: T::zero(),
plateau_duration: 0,
relative_improvement: T::zero(),
absolute_improvement: T::zero(),
}
}
}
impl<T: Float + Debug + Send + Sync + 'static> Default for GradientEvidence<T> {
fn default() -> Self {
Self {
gradient_norm: T::zero(),
gradient_norm_change_rate: T::zero(),
gradient_stability: T::zero(),
direction_consistency: T::zero(),
}
}
}
impl<T: Float + Debug + Send + Sync + 'static> Default for ParameterEvidence<T> {
fn default() -> Self {
Self {
parameter_change_magnitude: T::zero(),
parameter_change_rate: T::zero(),
parameter_stability: T::zero(),
convergence_ratio: T::zero(),
}
}
}
impl<T: Float + Debug + Send + Sync + 'static> Default for StatisticalEvidence<T> {
fn default() -> Self {
Self {
p_value: T::one(),
effect_size: T::zero(),
confidence_interval: (T::zero(), T::zero()),
test_statistic: T::zero(),
test_type: StatisticalTestType::TTest,
}
}
}
impl<T: Float + Debug + Send + Sync + 'static> Default for ValidationEvidence<T> {
fn default() -> Self {
Self {
val_loss_change: T::zero(),
val_metric_change: T::zero(),
generalization_gap: T::zero(),
overfitting_indicator: T::zero(),
}
}
}
impl<T: Float + Debug + Send + Sync + 'static> Default for ConvergenceMetrics<T> {
fn default() -> Self {
Self {
sensitivity: T::from(0.8).unwrap_or(T::zero()),
specificity: T::from(0.9).unwrap_or(T::zero()),
false_positive_rate: T::from(0.1).unwrap_or(T::zero()),
false_negative_rate: T::from(0.2).unwrap_or(T::zero()),
detection_latency: 10,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::super::performance::*;
#[test]
fn test_convergence_analyzer_creation() {
let config = ConvergenceAnalysisConfig::default();
let analyzer = ConvergenceAnalyzer::<f32>::new(config);
assert!(analyzer.is_ok());
}
#[test]
fn test_convergence_detector_creation() {
let params = ConvergenceParameters::<f32>::default();
let detector = ConvergenceDetector::new(ConvergenceDetectionMethod::LossBased, params);
assert!(detector.is_ok());
}
#[test]
fn test_early_stopping_controller() {
let config = EarlyStoppingConfig::<f32>::default();
let mut controller = EarlyStoppingController::new(config).expect("unwrap failed");
assert!(!controller.should_stop());
controller.update(&1.0).expect("unwrap failed");
controller.update(&0.9).expect("unwrap failed");
controller.update(&0.8).expect("unwrap failed");
assert!(!controller.should_stop());
for _ in 0..15 {
controller.update(&0.9).expect("unwrap failed");
}
assert!(controller.should_stop());
}
#[test]
fn test_convergence_evidence_default() {
let evidence = ConvergenceEvidence::<f32>::default();
assert_eq!(evidence.loss_evidence.loss_change_rate, 0.0);
assert_eq!(evidence.gradient_evidence.gradient_norm, 0.0);
}
#[test]
fn test_convergence_state_default() {
let state = ConvergenceState::<f32>::default();
assert!(!state.converged);
assert_eq!(state.confidence, 0.0);
assert_eq!(state.steps_since_detection, 0);
}
}