Skip to main content

optirs_learned/transformer_based_optimizer/
state.rs

1// State management for transformer-based optimizer
2
3use super::config::TransformerBasedOptimizerConfig;
4use super::meta_learning::MetaState;
5use crate::common::cast_scalar;
6use crate::error::{OptimError, Result};
7use scirs2_core::ndarray::{Array1, Array2};
8use scirs2_core::numeric::Float;
9use serde::{Deserialize, Serialize};
10use std::collections::{HashMap, VecDeque};
11use std::fmt::Debug;
12use std::time::{Duration, Instant, SystemTime};
13
14/// Transformer optimizer state
15pub struct TransformerOptimizerState<
16    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
17> {
18    /// Current model parameters
19    pub current_parameters: Array1<T>,
20
21    /// Parameter history
22    parameter_history: ParameterHistory<T>,
23
24    /// Optimization state
25    optimization_state: OptimizationState<T>,
26
27    /// Learning state
28    learning_state: LearningState<T>,
29
30    /// Memory state
31    memory_state: MemoryState<T>,
32
33    /// Checkpoint manager
34    checkpoint_manager: CheckpointManager<T>,
35
36    /// State configuration
37    config: StateConfig,
38
39    /// State statistics
40    statistics: StateStatistics<T>,
41
42    /// State version for tracking changes
43    version: usize,
44
45    /// Creation timestamp
46    created_at: std::time::Instant,
47
48    /// Last update timestamp
49    last_updated: std::time::Instant,
50}
51
52impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
53    TransformerOptimizerState<T>
54{
55    /// Create new optimizer state
56    pub fn new(config: &TransformerBasedOptimizerConfig<T>) -> Result<Self> {
57        let parameter_count = config.model_dimension * config.num_transformer_layers;
58        let current_parameters = Array1::zeros(parameter_count);
59
60        let parameter_history = ParameterHistory::new(1000, parameter_count)?;
61        let optimization_state = OptimizationState::new(config)?;
62        let learning_state = LearningState::new(config)?;
63        let memory_state = MemoryState::new()?;
64        let checkpoint_manager = CheckpointManager::new(config)?;
65        let state_config = StateConfig::from_optimizer_config(config);
66        let statistics = StateStatistics::new();
67
68        let now = std::time::Instant::now();
69
70        Ok(Self {
71            current_parameters,
72            parameter_history,
73            optimization_state,
74            learning_state,
75            memory_state,
76            checkpoint_manager,
77            config: state_config,
78            statistics,
79            version: 0,
80            created_at: now,
81            last_updated: now,
82        })
83    }
84
85    /// Update state with optimization step.
86    ///
87    /// `update` need not already match `current_parameters`'s length: the
88    /// meta-learned update network produces one "layer's worth" of update
89    /// (`model_dimension` elements), which is applied identically to every
90    /// transformer-layer segment of the (`model_dimension *
91    /// num_transformer_layers`)-sized parameter vector. See
92    /// `Self::project_update`.
93    pub fn update_with_step(&mut self, update: &Array1<T>, loss: Option<T>) -> Result<()> {
94        let projected = Self::project_update(update, self.current_parameters.len())?;
95        let update = &projected;
96
97        // Apply parameter update
98        self.current_parameters = &self.current_parameters + update;
99
100        // Record parameter history
101        self.parameter_history
102            .record_parameters(&self.current_parameters)?;
103
104        // Update optimization state
105        self.optimization_state.update_with_step(update, loss)?;
106
107        // Update learning state
108        if let Some(loss_val) = loss {
109            self.learning_state.update_with_loss(loss_val)?;
110        }
111
112        // Update statistics
113        self.statistics.record_update(update, loss);
114
115        // Increment version and update timestamp
116        self.version += 1;
117        self.last_updated = std::time::Instant::now();
118
119        Ok(())
120    }
121
122    /// Reconcile an update vector's length with the parameter vector it must
123    /// be added to.
124    ///
125    /// * If the lengths already match, the update is used as-is.
126    /// * If `target_len` is an exact multiple of `update.len()` (the normal
127    ///   case: a per-layer update tiled across every transformer layer), the
128    ///   update is repeated to fill `target_len`.
129    /// * Any other mismatch is a genuine configuration inconsistency and is
130    ///   reported as an error instead of panicking inside the `ndarray`
131    ///   addition (`current_parameters + update` panics on shape mismatch).
132    fn project_update(update: &Array1<T>, target_len: usize) -> Result<Array1<T>> {
133        let update_len = update.len();
134        if update_len == target_len {
135            return Ok(update.clone());
136        }
137        if update_len == 0 || !target_len.is_multiple_of(update_len) {
138            return Err(OptimError::InvalidConfig(format!(
139                "optimizer update has {update_len} elements, which cannot be \
140                 tiled to fill the {target_len}-element parameter vector \
141                 (expected {update_len} to evenly divide {target_len})"
142            )));
143        }
144        let repeats = target_len / update_len;
145        let mut tiled = Vec::with_capacity(target_len);
146        for _ in 0..repeats {
147            tiled.extend(update.iter().copied());
148        }
149        Ok(Array1::from_vec(tiled))
150    }
151
152    /// Create state snapshot
153    pub fn create_snapshot(&self) -> Result<OptimizerStateSnapshot<T>> {
154        Ok(OptimizerStateSnapshot {
155            parameters: self.current_parameters.clone(),
156            optimization_state: self.optimization_state.clone(),
157            learning_state: self.learning_state.clone(),
158            memory_state: self.memory_state.clone(),
159            version: self.version,
160            timestamp: self.last_updated,
161            metadata: SnapshotMetadata {
162                parameter_count: self.current_parameters.len(),
163                total_updates: self.statistics.total_updates,
164                session_duration: self.last_updated.duration_since(self.created_at),
165            },
166        })
167    }
168
169    /// Restore from snapshot
170    pub fn restore_from_snapshot(&mut self, snapshot: &OptimizerStateSnapshot<T>) -> Result<()> {
171        self.current_parameters = snapshot.parameters.clone();
172        self.optimization_state = snapshot.optimization_state.clone();
173        self.learning_state = snapshot.learning_state.clone();
174        self.memory_state = snapshot.memory_state.clone();
175        self.version = snapshot.version;
176        self.last_updated = snapshot.timestamp;
177
178        Ok(())
179    }
180
181    /// Save checkpoint
182    pub fn save_checkpoint(&mut self, name: String) -> Result<String> {
183        let snapshot = self.create_snapshot()?;
184        let checkpoint_id = self.checkpoint_manager.save_checkpoint(name, snapshot)?;
185        Ok(checkpoint_id)
186    }
187
188    /// Load checkpoint
189    pub fn load_checkpoint(&mut self, checkpoint_id: &str) -> Result<()> {
190        let snapshot = self.checkpoint_manager.load_checkpoint(checkpoint_id)?;
191        self.restore_from_snapshot(&snapshot)?;
192        Ok(())
193    }
194
195    /// Get parameter statistics
196    pub fn get_parameter_stats(&self) -> ParameterStatistics<T> {
197        self.parameter_history.get_statistics()
198    }
199
200    /// Get optimization progress
201    pub fn get_optimization_progress(&self) -> OptimizationProgress<T> {
202        self.optimization_state.get_progress()
203    }
204
205    /// Get learning statistics
206    pub fn get_learning_stats(&self) -> LearningStatistics<T> {
207        self.learning_state.get_statistics()
208    }
209
210    /// Reset state
211    pub fn reset(&mut self) -> Result<()> {
212        self.current_parameters.fill(T::zero());
213        self.parameter_history.clear();
214        self.optimization_state.reset()?;
215        self.learning_state.reset()?;
216        self.memory_state.reset()?;
217        self.statistics.reset();
218        self.version = 0;
219        self.last_updated = std::time::Instant::now();
220        Ok(())
221    }
222
223    /// Validate state consistency
224    pub fn validate_state(&self) -> Result<StateValidationReport> {
225        let mut issues = Vec::new();
226
227        // Check parameter validity
228        if self.current_parameters.iter().any(|&x| !x.is_finite()) {
229            issues.push("Invalid parameters detected (NaN or infinity)".to_string());
230        }
231
232        // Check version consistency
233        if self.version == 0 && self.statistics.total_updates > 0 {
234            issues.push("Version mismatch with update count".to_string());
235        }
236
237        // Check timestamp consistency
238        if self.last_updated < self.created_at {
239            issues.push("Invalid timestamp ordering".to_string());
240        }
241
242        // Validate optimization state
243        let opt_validation = self.optimization_state.validate()?;
244        issues.extend(opt_validation.issues);
245
246        // Validate learning state
247        let learning_validation = self.learning_state.validate()?;
248        issues.extend(learning_validation.issues);
249
250        Ok(StateValidationReport {
251            is_valid: issues.is_empty(),
252            issues,
253            validation_timestamp: std::time::Instant::now(),
254        })
255    }
256
257    /// Get state summary
258    pub fn get_state_summary(&self) -> StateSummary<T> {
259        StateSummary {
260            version: self.version,
261            parameter_count: self.current_parameters.len(),
262            parameter_norm: self.compute_parameter_norm(),
263            total_updates: self.statistics.total_updates,
264            session_duration: self.last_updated.duration_since(self.created_at),
265            last_update_magnitude: self.statistics.last_update_magnitude,
266            average_loss: self.learning_state.get_average_loss(),
267            convergence_rate: self.learning_state.get_convergence_rate(),
268            memory_usage: self.memory_state.get_total_usage(),
269            checkpoint_count: self.checkpoint_manager.get_checkpoint_count(),
270        }
271    }
272
273    /// Compute parameter norm
274    fn compute_parameter_norm(&self) -> T {
275        self.current_parameters
276            .iter()
277            .map(|&x| x * x)
278            .fold(T::zero(), |acc, x| acc + x)
279            .sqrt()
280    }
281
282    /// Get state metadata
283    pub fn get_metadata(&self) -> StateMetadata {
284        StateMetadata {
285            version: self.version,
286            created_at: SystemTime::now(), // Convert from Instant for serialization
287            last_updated: SystemTime::now(), // Convert from Instant for serialization
288            total_updates: self.statistics.total_updates,
289            configuration: self.config.clone(),
290        }
291    }
292
293    /// Export state to serializable format
294    pub fn export_state(&self) -> Result<SerializableState<T>> {
295        Ok(SerializableState {
296            parameters: self.current_parameters.to_vec(),
297            parameter_shape: self.current_parameters.shape().to_vec(),
298            optimization_state: self.optimization_state.to_serializable()?,
299            learning_state: self.learning_state.to_serializable()?,
300            metadata: self.get_metadata(),
301            statistics: self.statistics.clone(),
302        })
303    }
304
305    /// Import state from serializable format
306    pub fn import_state(&mut self, state: SerializableState<T>) -> Result<()> {
307        // Reconstruct parameters
308        if state.parameter_shape.len() != 1 {
309            return Err(crate::error::OptimError::Other(
310                "Invalid parameter shape for 1D array".to_string(),
311            ));
312        }
313
314        self.current_parameters = Array1::from_vec(state.parameters);
315
316        // Restore other state components
317        self.optimization_state
318            .from_serializable(state.optimization_state)?;
319        self.learning_state
320            .from_serializable(state.learning_state)?;
321        self.statistics = state.statistics;
322        self.version = state.metadata.version;
323        self.last_updated = Instant::now(); // Convert from SystemTime for internal use
324
325        Ok(())
326    }
327}
328
329/// Parameter history management
330pub struct ParameterHistory<
331    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
332> {
333    /// Parameter snapshots
334    snapshots: VecDeque<ParameterSnapshot<T>>,
335
336    /// Maximum history size
337    max_size: usize,
338
339    /// Parameter dimension
340    parameter_dimension: usize,
341
342    /// Statistics
343    statistics: ParameterStatistics<T>,
344}
345
346impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
347    ParameterHistory<T>
348{
349    pub fn new(max_size: usize, parameter_dimension: usize) -> Result<Self> {
350        Ok(Self {
351            snapshots: VecDeque::new(),
352            max_size,
353            parameter_dimension,
354            statistics: ParameterStatistics::new(),
355        })
356    }
357
358    /// Record a parameter snapshot.
359    ///
360    /// # Errors
361    /// Returns `Err` when `parameters` is not `parameter_dimension` long. The
362    /// history is later averaged and differenced across snapshots, so a
363    /// mixed-width history silently produces meaningless statistics; the
364    /// declared dimension used to be stored and never checked against anything.
365    pub fn record_parameters(&mut self, parameters: &Array1<T>) -> Result<()> {
366        if parameters.len() != self.parameter_dimension {
367            return Err(crate::error::OptimError::InvalidConfig(format!(
368                "ParameterHistory holds {}-dimensional snapshots but was given {}",
369                self.parameter_dimension,
370                parameters.len()
371            )));
372        }
373        let snapshot = ParameterSnapshot {
374            parameters: parameters.clone(),
375            timestamp: std::time::Instant::now(),
376            norm: parameters
377                .iter()
378                .map(|&x| x * x)
379                .fold(T::zero(), |acc, x| acc + x)
380                .sqrt(),
381        };
382
383        self.snapshots.push_back(snapshot.clone());
384        if self.snapshots.len() > self.max_size {
385            self.snapshots.pop_front();
386        }
387
388        self.statistics.update_with_snapshot(&snapshot);
389        Ok(())
390    }
391
392    pub fn get_recent_parameters(&self, count: usize) -> Vec<Array1<T>> {
393        self.snapshots
394            .iter()
395            .rev()
396            .take(count)
397            .map(|snapshot| snapshot.parameters.clone())
398            .collect()
399    }
400
401    pub fn get_statistics(&self) -> ParameterStatistics<T> {
402        self.statistics.clone()
403    }
404
405    pub fn clear(&mut self) {
406        self.snapshots.clear();
407        self.statistics = ParameterStatistics::new();
408    }
409}
410
411/// Optimization state tracking
412#[derive(Debug, Clone)]
413pub struct OptimizationState<
414    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
415> {
416    /// Current learning rate
417    pub learning_rate: T,
418
419    /// Momentum state
420    pub momentum: Option<Array1<T>>,
421
422    /// Adaptive learning rate state (e.g., Adam)
423    pub adaptive_state: Option<AdaptiveState<T>>,
424
425    /// Gradient accumulation
426    pub gradient_accumulator: GradientAccumulator<T>,
427
428    /// Step count
429    pub step_count: usize,
430
431    /// Last update magnitude
432    pub last_update_magnitude: T,
433
434    /// Convergence tracking
435    pub convergence_tracker: ConvergenceTracker<T>,
436}
437
438impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
439    OptimizationState<T>
440{
441    pub fn new(config: &TransformerBasedOptimizerConfig<T>) -> Result<Self> {
442        let parameter_count = config.model_dimension * config.num_transformer_layers;
443
444        Ok(Self {
445            learning_rate: config.learning_rate,
446            momentum: None,
447            adaptive_state: Some(AdaptiveState::new(parameter_count)?),
448            gradient_accumulator: GradientAccumulator::new(parameter_count)?,
449            step_count: 0,
450            last_update_magnitude: T::zero(),
451            convergence_tracker: ConvergenceTracker::new(),
452        })
453    }
454
455    pub fn update_with_step(&mut self, update: &Array1<T>, loss: Option<T>) -> Result<()> {
456        self.step_count += 1;
457        self.last_update_magnitude = update
458            .iter()
459            .map(|&x| x * x)
460            .fold(T::zero(), |acc, x| acc + x)
461            .sqrt();
462
463        if let Some(loss_val) = loss {
464            self.convergence_tracker.record_loss(loss_val);
465        }
466
467        // Update adaptive state
468        if let Some(ref mut adaptive) = self.adaptive_state {
469            adaptive.update_with_step(update)?;
470        }
471
472        Ok(())
473    }
474
475    pub fn get_progress(&self) -> OptimizationProgress<T> {
476        OptimizationProgress {
477            step_count: self.step_count,
478            current_learning_rate: self.learning_rate,
479            last_update_magnitude: self.last_update_magnitude,
480            convergence_rate: self.convergence_tracker.get_convergence_rate(),
481            stability_score: self.convergence_tracker.get_stability_score(),
482        }
483    }
484
485    pub fn reset(&mut self) -> Result<()> {
486        self.step_count = 0;
487        self.last_update_magnitude = T::zero();
488        self.convergence_tracker.reset();
489
490        if let Some(ref mut adaptive) = self.adaptive_state {
491            adaptive.reset()?;
492        }
493
494        self.gradient_accumulator.reset()?;
495        Ok(())
496    }
497
498    pub fn validate(&self) -> Result<ValidationResult> {
499        let mut issues = Vec::new();
500
501        if self.learning_rate <= T::zero() {
502            issues.push("Invalid learning rate".to_string());
503        }
504
505        if !self.last_update_magnitude.is_finite() {
506            issues.push("Invalid update magnitude".to_string());
507        }
508
509        Ok(ValidationResult { issues })
510    }
511
512    pub fn to_serializable(&self) -> Result<SerializableOptimizationState<T>> {
513        Ok(SerializableOptimizationState {
514            learning_rate: self.learning_rate,
515            step_count: self.step_count,
516            last_update_magnitude: self.last_update_magnitude,
517            momentum: self.momentum.as_ref().map(|m| m.to_vec()),
518            convergence_metrics: self.convergence_tracker.to_serializable(),
519        })
520    }
521
522    pub fn from_serializable(&mut self, state: SerializableOptimizationState<T>) -> Result<()> {
523        self.learning_rate = state.learning_rate;
524        self.step_count = state.step_count;
525        self.last_update_magnitude = state.last_update_magnitude;
526
527        if let Some(momentum_vec) = state.momentum {
528            self.momentum = Some(Array1::from_vec(momentum_vec));
529        }
530
531        self.convergence_tracker
532            .from_serializable(state.convergence_metrics)?;
533        Ok(())
534    }
535}
536
537/// Learning state tracking
538#[derive(Debug, Clone)]
539pub struct LearningState<
540    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
541> {
542    /// Loss history
543    loss_history: VecDeque<T>,
544
545    /// Meta-learning state
546    meta_state: Option<MetaState<T>>,
547
548    /// Task adaptation history
549    adaptation_history: VecDeque<TaskAdaptationRecord<T>>,
550
551    /// Learning rate schedule
552    learning_schedule: LearningSchedule<T>,
553
554    /// Performance metrics
555    performance_metrics: LearningPerformanceMetrics<T>,
556}
557
558impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
559    LearningState<T>
560{
561    pub fn new(config: &TransformerBasedOptimizerConfig<T>) -> Result<Self> {
562        let meta_state = Some(MetaState::new(config.model_dimension)?);
563        let learning_schedule = LearningSchedule::new(config.learning_rate, config.warmup_steps);
564
565        Ok(Self {
566            loss_history: VecDeque::new(),
567            meta_state,
568            adaptation_history: VecDeque::new(),
569            learning_schedule,
570            performance_metrics: LearningPerformanceMetrics::new(),
571        })
572    }
573
574    pub fn update_with_loss(&mut self, loss: T) -> Result<()> {
575        self.loss_history.push_back(loss);
576        if self.loss_history.len() > 1000 {
577            self.loss_history.pop_front();
578        }
579
580        self.performance_metrics.record_loss(loss);
581        self.learning_schedule.step();
582
583        if let Some(ref mut meta) = self.meta_state {
584            meta.update_loss_history(loss);
585        }
586
587        Ok(())
588    }
589
590    /// Learning rate the schedule is currently at.
591    pub fn current_learning_rate(&self) -> T {
592        self.learning_schedule.current_rate
593    }
594
595    /// The learning-rate schedule this state advances on every recorded loss.
596    pub fn learning_schedule(&self) -> &LearningSchedule<T> {
597        &self.learning_schedule
598    }
599
600    pub fn get_statistics(&self) -> LearningStatistics<T> {
601        LearningStatistics {
602            total_episodes: self.loss_history.len(),
603            average_loss: self.get_average_loss(),
604            best_loss: self.get_best_loss(),
605            convergence_rate: self.get_convergence_rate(),
606            learning_stability: self.performance_metrics.get_stability_score(),
607        }
608    }
609
610    pub fn get_average_loss(&self) -> T {
611        let Some(count) = cast_scalar::<T, _>(self.loss_history.len())
612            .ok()
613            .filter(|c| *c > T::zero())
614        else {
615            return T::zero();
616        };
617        self.loss_history
618            .iter()
619            .fold(T::zero(), |acc, &loss| acc + loss)
620            / count
621    }
622
623    pub fn get_best_loss(&self) -> T {
624        self.loss_history
625            .iter()
626            .fold(T::infinity(), |min, &loss| min.min(loss))
627    }
628
629    pub fn get_convergence_rate(&self) -> T {
630        if self.loss_history.len() < 2 {
631            return T::zero();
632        }
633
634        let recent_losses: Vec<_> = self.loss_history.iter().rev().take(10).cloned().collect();
635        if recent_losses.len() < 2 {
636            return T::zero();
637        }
638
639        // `recent_losses.len() >= 2` was checked above; the fallible reads keep
640        // that reasoning next to the accesses it justifies.
641        let (Some(&initial), Some(&final_loss)) = (recent_losses.last(), recent_losses.first())
642        else {
643            return T::zero();
644        };
645
646        if initial > T::zero() {
647            (initial - final_loss) / initial
648        } else {
649            T::zero()
650        }
651    }
652
653    pub fn reset(&mut self) -> Result<()> {
654        self.loss_history.clear();
655        self.adaptation_history.clear();
656        self.performance_metrics.reset();
657
658        if let Some(ref mut meta) = self.meta_state {
659            *meta = MetaState::new(meta.get_parameters().len())?;
660        }
661
662        Ok(())
663    }
664
665    pub fn validate(&self) -> Result<ValidationResult> {
666        let mut issues = Vec::new();
667
668        if self.loss_history.iter().any(|&loss| !loss.is_finite()) {
669            issues.push("Invalid loss values detected".to_string());
670        }
671
672        Ok(ValidationResult { issues })
673    }
674
675    pub fn to_serializable(&self) -> Result<SerializableLearningState<T>> {
676        Ok(SerializableLearningState {
677            loss_history: self.loss_history.iter().cloned().collect(),
678            average_loss: self.get_average_loss(),
679            best_loss: self.get_best_loss(),
680            convergence_rate: self.get_convergence_rate(),
681        })
682    }
683
684    pub fn from_serializable(&mut self, state: SerializableLearningState<T>) -> Result<()> {
685        self.loss_history = VecDeque::from(state.loss_history);
686        Ok(())
687    }
688}
689
690/// Memory state management
691#[derive(Debug, Clone)]
692pub struct MemoryState<
693    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
694> {
695    /// Attention caches
696    attention_caches: HashMap<String, AttentionCache<T>>,
697
698    /// Memory usage tracking
699    memory_usage: MemoryUsageTracker,
700
701    /// Cache statistics
702    cache_statistics: CacheStatistics,
703}
704
705impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
706    MemoryState<T>
707{
708    pub fn new() -> Result<Self> {
709        Ok(Self {
710            attention_caches: HashMap::new(),
711            memory_usage: MemoryUsageTracker::new(),
712            cache_statistics: CacheStatistics::new(),
713        })
714    }
715
716    pub fn get_total_usage(&self) -> usize {
717        self.memory_usage.total_usage
718    }
719
720    pub fn reset(&mut self) -> Result<()> {
721        self.attention_caches.clear();
722        self.memory_usage.reset();
723        self.cache_statistics.reset();
724        Ok(())
725    }
726}
727
728/// Explicitly-saved checkpoints retained before the oldest is evicted. The
729/// optimizer configuration carries no checkpoint-retention setting, so this is a
730/// documented default rather than something derived from it.
731const DEFAULT_MAX_CHECKPOINTS: usize = 10;
732
733/// Name prefix that marks a checkpoint as produced by
734/// [`CheckpointManager::maybe_auto_save`] rather than an explicit save, so the
735/// two are retained under separate limits.
736const AUTO_SAVE_PREFIX: &str = "auto_";
737
738/// Checkpoint management
739pub struct CheckpointManager<
740    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
741> {
742    /// Stored checkpoints
743    checkpoints: HashMap<String, OptimizerStateSnapshot<T>>,
744
745    /// Checkpoint metadata
746    metadata: HashMap<String, CheckpointMetadata>,
747
748    /// Maximum checkpoints to keep
749    max_checkpoints: usize,
750
751    /// Auto-save configuration
752    auto_save_config: AutoSaveConfig,
753}
754
755impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
756    CheckpointManager<T>
757{
758    /// Build a checkpoint manager whose auto-save cadence follows `config`.
759    ///
760    /// The auto-save frequency used to be a fixed 100 steps regardless of the
761    /// optimizer's configuration; it now tracks
762    /// [`StateConfig::from_optimizer_config`], which derives it from
763    /// `performance_config.metrics_interval`.
764    pub fn new(config: &TransformerBasedOptimizerConfig<T>) -> Result<Self> {
765        let state_config = StateConfig::from_optimizer_config(config);
766        Ok(Self {
767            checkpoints: HashMap::new(),
768            metadata: HashMap::new(),
769            max_checkpoints: DEFAULT_MAX_CHECKPOINTS,
770            auto_save_config: AutoSaveConfig {
771                enabled: state_config.auto_save_enabled,
772                frequency: state_config.checkpoint_frequency,
773                ..AutoSaveConfig::default()
774            },
775        })
776    }
777
778    /// The auto-save policy this manager was built with.
779    pub fn auto_save_config(&self) -> &AutoSaveConfig {
780        &self.auto_save_config
781    }
782
783    /// Save `snapshot` if `step` falls on the configured auto-save cadence.
784    ///
785    /// Returns the new checkpoint id, or `None` when auto-save is disabled or
786    /// `step` is not a save point. Auto-saves are named `auto_<step>` and are
787    /// capped at [`AutoSaveConfig::max_auto_saves`] independently of the
788    /// explicit-checkpoint limit, oldest evicted first, so a long run cannot
789    /// grow the store without bound. `step == 0` saves the initial state.
790    ///
791    /// Without this, `auto_save_config` was populated at construction and never
792    /// read by anything: the auto-save policy existed only as a value.
793    pub fn maybe_auto_save(
794        &mut self,
795        step: usize,
796        snapshot: OptimizerStateSnapshot<T>,
797    ) -> Result<Option<String>> {
798        if !self.auto_save_config.enabled
799            || self.auto_save_config.frequency == 0
800            || !step.is_multiple_of(self.auto_save_config.frequency)
801        {
802            return Ok(None);
803        }
804        let max_auto_saves = self.auto_save_config.max_auto_saves;
805        let id = self.save_checkpoint(format!("{AUTO_SAVE_PREFIX}{step}"), snapshot)?;
806        self.evict_surplus_auto_saves(max_auto_saves);
807        Ok(Some(id))
808    }
809
810    /// Number of auto-saved checkpoints currently retained.
811    pub fn auto_save_count(&self) -> usize {
812        self.metadata
813            .values()
814            .filter(|m| m.name.starts_with(AUTO_SAVE_PREFIX))
815            .count()
816    }
817
818    /// Drop the oldest auto-saves until at most `max_auto_saves` remain.
819    /// Explicit checkpoints are untouched.
820    fn evict_surplus_auto_saves(&mut self, max_auto_saves: usize) {
821        loop {
822            let mut autos: Vec<(String, std::time::Instant)> = self
823                .metadata
824                .iter()
825                .filter(|(_, m)| m.name.starts_with(AUTO_SAVE_PREFIX))
826                .map(|(id, m)| (id.clone(), m.created_at))
827                .collect();
828            if autos.len() <= max_auto_saves {
829                return;
830            }
831            autos.sort_by_key(|(_, created)| *created);
832            match autos.first() {
833                Some((oldest, _)) => {
834                    let oldest = oldest.clone();
835                    self.checkpoints.remove(&oldest);
836                    self.metadata.remove(&oldest);
837                }
838                None => return,
839            }
840        }
841    }
842
843    pub fn save_checkpoint(
844        &mut self,
845        name: String,
846        snapshot: OptimizerStateSnapshot<T>,
847    ) -> Result<String> {
848        let checkpoint_id = format!("{}_{}", name, snapshot.version);
849
850        // Add metadata
851        let metadata = CheckpointMetadata {
852            id: checkpoint_id.clone(),
853            name: name.clone(),
854            created_at: std::time::Instant::now(),
855            size_estimate: snapshot.parameters.len() * std::mem::size_of::<T>(),
856            description: format!("Checkpoint at version {}", snapshot.version),
857        };
858
859        self.checkpoints.insert(checkpoint_id.clone(), snapshot);
860        self.metadata.insert(checkpoint_id.clone(), metadata);
861
862        // Cleanup old checkpoints if necessary
863        if self.checkpoints.len() > self.max_checkpoints {
864            self.cleanup_old_checkpoints()?;
865        }
866
867        Ok(checkpoint_id)
868    }
869
870    pub fn load_checkpoint(&self, checkpoint_id: &str) -> Result<OptimizerStateSnapshot<T>> {
871        self.checkpoints.get(checkpoint_id).cloned().ok_or_else(|| {
872            crate::error::OptimError::Other(format!("Checkpoint {} not found", checkpoint_id))
873        })
874    }
875
876    pub fn list_checkpoints(&self) -> Vec<CheckpointMetadata> {
877        self.metadata.values().cloned().collect()
878    }
879
880    pub fn delete_checkpoint(&mut self, checkpoint_id: &str) -> Result<bool> {
881        let removed_checkpoint = self.checkpoints.remove(checkpoint_id).is_some();
882        let removed_metadata = self.metadata.remove(checkpoint_id).is_some();
883        Ok(removed_checkpoint && removed_metadata)
884    }
885
886    pub fn get_checkpoint_count(&self) -> usize {
887        self.checkpoints.len()
888    }
889
890    fn cleanup_old_checkpoints(&mut self) -> Result<()> {
891        // Remove oldest checkpoints if over limit
892        while self.checkpoints.len() > self.max_checkpoints {
893            if let Some((oldest_id, _)) = self
894                .metadata
895                .iter()
896                .min_by_key(|(_, metadata)| metadata.created_at)
897                .map(|(id, metadata)| (id.clone(), metadata.clone()))
898            {
899                self.checkpoints.remove(&oldest_id);
900                self.metadata.remove(&oldest_id);
901            } else {
902                break;
903            }
904        }
905        Ok(())
906    }
907}
908
909/// Supporting data structures and types
910
911#[derive(Debug, Clone)]
912pub struct ParameterSnapshot<
913    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
914> {
915    pub parameters: Array1<T>,
916    pub timestamp: std::time::Instant,
917    pub norm: T,
918}
919
920#[derive(Debug, Clone)]
921pub struct ParameterStatistics<
922    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
923> {
924    pub total_snapshots: usize,
925    pub average_norm: T,
926    pub max_norm: T,
927    pub min_norm: T,
928    pub norm_trend: T,
929}
930
931impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static> Default
932    for ParameterStatistics<T>
933{
934    fn default() -> Self {
935        Self::new()
936    }
937}
938
939impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
940    ParameterStatistics<T>
941{
942    pub fn new() -> Self {
943        Self {
944            total_snapshots: 0,
945            average_norm: T::zero(),
946            max_norm: T::zero(),
947            min_norm: T::infinity(),
948            norm_trend: T::zero(),
949        }
950    }
951
952    pub fn update_with_snapshot(&mut self, snapshot: &ParameterSnapshot<T>) {
953        self.total_snapshots += 1;
954        self.average_norm = (self.average_norm
955            * scirs2_core::numeric::NumCast::from(self.total_snapshots - 1)
956                .unwrap_or_else(|| T::zero())
957            + snapshot.norm)
958            / scirs2_core::numeric::NumCast::from(self.total_snapshots)
959                .unwrap_or_else(|| T::zero());
960        self.max_norm = self.max_norm.max(snapshot.norm);
961        self.min_norm = self.min_norm.min(snapshot.norm);
962    }
963}
964
965#[derive(Debug, Clone)]
966pub struct AdaptiveState<
967    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
968> {
969    /// First moment estimates
970    pub m: Array1<T>,
971    /// Second moment estimates
972    pub v: Array1<T>,
973    /// Step count for bias correction
974    pub step_count: usize,
975    /// Beta parameters
976    pub beta1: T,
977    pub beta2: T,
978    /// Epsilon for numerical stability
979    pub epsilon: T,
980}
981
982impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
983    AdaptiveState<T>
984{
985    pub fn new(parameter_count: usize) -> Result<Self> {
986        Ok(Self {
987            m: Array1::zeros(parameter_count),
988            v: Array1::zeros(parameter_count),
989            step_count: 0,
990            beta1: scirs2_core::numeric::NumCast::from(0.9).unwrap_or_else(|| T::zero()),
991            beta2: scirs2_core::numeric::NumCast::from(0.999).unwrap_or_else(|| T::zero()),
992            epsilon: scirs2_core::numeric::NumCast::from(1e-8).unwrap_or_else(|| T::zero()),
993        })
994    }
995
996    pub fn update_with_step(&mut self, _update: &Array1<T>) -> Result<()> {
997        self.step_count += 1;
998        // Adam-style update logic would go here
999        Ok(())
1000    }
1001
1002    pub fn reset(&mut self) -> Result<()> {
1003        self.m.fill(T::zero());
1004        self.v.fill(T::zero());
1005        self.step_count = 0;
1006        Ok(())
1007    }
1008}
1009
1010#[derive(Debug, Clone)]
1011pub struct GradientAccumulator<
1012    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
1013> {
1014    /// Accumulated gradients
1015    pub accumulated_gradients: Array1<T>,
1016    /// Accumulation count
1017    pub accumulation_count: usize,
1018}
1019
1020impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
1021    GradientAccumulator<T>
1022{
1023    pub fn new(parameter_count: usize) -> Result<Self> {
1024        Ok(Self {
1025            accumulated_gradients: Array1::zeros(parameter_count),
1026            accumulation_count: 0,
1027        })
1028    }
1029
1030    pub fn reset(&mut self) -> Result<()> {
1031        self.accumulated_gradients.fill(T::zero());
1032        self.accumulation_count = 0;
1033        Ok(())
1034    }
1035}
1036
1037#[derive(Debug, Clone)]
1038pub struct ConvergenceTracker<
1039    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
1040> {
1041    /// Recent loss values
1042    recent_losses: VecDeque<T>,
1043    /// Convergence threshold
1044    convergence_threshold: T,
1045    /// Stability window size
1046    stability_window: usize,
1047}
1048
1049impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static> Default
1050    for ConvergenceTracker<T>
1051{
1052    fn default() -> Self {
1053        Self::new()
1054    }
1055}
1056
1057impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
1058    ConvergenceTracker<T>
1059{
1060    pub fn new() -> Self {
1061        Self {
1062            recent_losses: VecDeque::new(),
1063            convergence_threshold: scirs2_core::numeric::NumCast::from(1e-6)
1064                .unwrap_or_else(|| T::zero()),
1065            stability_window: 10,
1066        }
1067    }
1068
1069    pub fn record_loss(&mut self, loss: T) {
1070        self.recent_losses.push_back(loss);
1071        if self.recent_losses.len() > self.stability_window {
1072            self.recent_losses.pop_front();
1073        }
1074    }
1075
1076    /// Whether the tracked stream has converged: the mean absolute step-to-step
1077    /// loss change over the stability window is at or below
1078    /// `convergence_threshold`.
1079    ///
1080    /// Returns `false` until the window has at least two observations. The
1081    /// threshold used to be stored at construction and consulted by nothing, so
1082    /// the tracker could report a rate and a stability score but never a verdict.
1083    pub fn has_converged(&self) -> bool {
1084        if self.recent_losses.len() < 2 {
1085            return false;
1086        }
1087        let mut total = T::zero();
1088        for pair in self.recent_losses.iter().collect::<Vec<_>>().windows(2) {
1089            total = total + (*pair[1] - *pair[0]).abs();
1090        }
1091        let steps: T = scirs2_core::numeric::NumCast::from(self.recent_losses.len() - 1)
1092            .unwrap_or_else(|| T::one());
1093        (total / steps) <= self.convergence_threshold
1094    }
1095
1096    /// The configured convergence threshold.
1097    pub fn convergence_threshold(&self) -> T {
1098        self.convergence_threshold
1099    }
1100
1101    pub fn get_convergence_rate(&self) -> T {
1102        if self.recent_losses.len() < 2 {
1103            return T::zero();
1104        }
1105
1106        let (Some(&first), Some(&last)) = (self.recent_losses.front(), self.recent_losses.back())
1107        else {
1108            return T::zero();
1109        };
1110
1111        if first > T::zero() {
1112            (first - last) / first
1113        } else {
1114            T::zero()
1115        }
1116    }
1117
1118    pub fn get_stability_score(&self) -> T {
1119        if self.recent_losses.len() < 2 {
1120            return T::zero();
1121        }
1122
1123        let Some(count) = cast_scalar::<T, _>(self.recent_losses.len())
1124            .ok()
1125            .filter(|c| *c > T::zero())
1126        else {
1127            return T::zero();
1128        };
1129        let mean = self.recent_losses.iter().fold(T::zero(), |acc, &x| acc + x) / count;
1130        let variance = self
1131            .recent_losses
1132            .iter()
1133            .map(|&x| (x - mean) * (x - mean))
1134            .fold(T::zero(), |acc, x| acc + x)
1135            / count;
1136
1137        T::one() / (T::one() + variance.sqrt())
1138    }
1139
1140    pub fn reset(&mut self) {
1141        self.recent_losses.clear();
1142    }
1143
1144    pub fn to_serializable(&self) -> SerializableConvergenceState<T> {
1145        SerializableConvergenceState {
1146            recent_losses: self.recent_losses.iter().cloned().collect(),
1147            convergence_rate: self.get_convergence_rate(),
1148            stability_score: self.get_stability_score(),
1149        }
1150    }
1151
1152    pub fn from_serializable(&mut self, state: SerializableConvergenceState<T>) -> Result<()> {
1153        self.recent_losses = VecDeque::from(state.recent_losses);
1154        Ok(())
1155    }
1156}
1157
1158/// State snapshots and serialization
1159#[derive(Debug, Clone)]
1160pub struct OptimizerStateSnapshot<
1161    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
1162> {
1163    pub parameters: Array1<T>,
1164    pub optimization_state: OptimizationState<T>,
1165    pub learning_state: LearningState<T>,
1166    pub memory_state: MemoryState<T>,
1167    pub version: usize,
1168    pub timestamp: std::time::Instant,
1169    pub metadata: SnapshotMetadata,
1170}
1171
1172#[derive(Debug, Clone)]
1173pub struct SnapshotMetadata {
1174    pub parameter_count: usize,
1175    pub total_updates: usize,
1176    pub session_duration: Duration,
1177}
1178
1179/// Configuration and metadata structures
1180#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1181pub struct StateConfig {
1182    pub max_history_size: usize,
1183    pub checkpoint_frequency: usize,
1184    pub auto_save_enabled: bool,
1185    pub validation_enabled: bool,
1186}
1187
1188impl StateConfig {
1189    /// Derive the state-tracking configuration from the optimizer's own
1190    /// performance-tracking settings.
1191    ///
1192    /// Every field used to be a hard-coded literal, so a caller who shrank
1193    /// `performance_config.max_history_size` to bound memory still got a
1194    /// 1000-entry state history. `max_history_size` and `checkpoint_frequency`
1195    /// now follow the optimizer's configured history bound and metric cadence.
1196    /// `auto_save_enabled` and `validation_enabled` stay `true`: the optimizer
1197    /// config carries no corresponding switch, so there is nothing honest to
1198    /// derive them from.
1199    pub fn from_optimizer_config<
1200        T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
1201    >(
1202        config: &TransformerBasedOptimizerConfig<T>,
1203    ) -> Self {
1204        Self {
1205            max_history_size: config.performance_config.max_history_size,
1206            checkpoint_frequency: config.performance_config.metrics_interval.max(1),
1207            auto_save_enabled: true,
1208            validation_enabled: true,
1209        }
1210    }
1211}
1212
1213#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1214pub struct StateMetadata {
1215    pub version: usize,
1216    pub created_at: std::time::SystemTime,
1217    pub last_updated: std::time::SystemTime,
1218    pub total_updates: usize,
1219    pub configuration: StateConfig,
1220}
1221
1222/// Statistics and tracking structures
1223#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1224pub struct StateStatistics<
1225    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
1226> {
1227    pub total_updates: usize,
1228    pub last_update_magnitude: T,
1229    pub average_update_magnitude: T,
1230    pub parameter_change_rate: T,
1231    pub update_frequency: f64,
1232}
1233
1234impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static> Default
1235    for StateStatistics<T>
1236{
1237    fn default() -> Self {
1238        Self::new()
1239    }
1240}
1241
1242impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
1243    StateStatistics<T>
1244{
1245    pub fn new() -> Self {
1246        Self {
1247            total_updates: 0,
1248            last_update_magnitude: T::zero(),
1249            average_update_magnitude: T::zero(),
1250            parameter_change_rate: T::zero(),
1251            update_frequency: 0.0,
1252        }
1253    }
1254
1255    pub fn record_update(&mut self, update: &Array1<T>, _loss: Option<T>) {
1256        self.total_updates += 1;
1257        let magnitude = update
1258            .iter()
1259            .map(|&x| x * x)
1260            .fold(T::zero(), |acc, x| acc + x)
1261            .sqrt();
1262        self.last_update_magnitude = magnitude;
1263        self.average_update_magnitude = (self.average_update_magnitude
1264            * scirs2_core::numeric::NumCast::from(self.total_updates - 1)
1265                .unwrap_or_else(|| T::zero())
1266            + magnitude)
1267            / scirs2_core::numeric::NumCast::from(self.total_updates).unwrap_or_else(|| T::zero());
1268    }
1269
1270    pub fn reset(&mut self) {
1271        self.total_updates = 0;
1272        self.last_update_magnitude = T::zero();
1273        self.average_update_magnitude = T::zero();
1274        self.parameter_change_rate = T::zero();
1275        self.update_frequency = 0.0;
1276    }
1277}
1278
1279/// Validation and reporting structures
1280#[derive(Debug, Clone)]
1281pub struct StateValidationReport {
1282    pub is_valid: bool,
1283    pub issues: Vec<String>,
1284    pub validation_timestamp: Instant,
1285}
1286
1287#[derive(Debug, Clone)]
1288pub struct ValidationResult {
1289    pub issues: Vec<String>,
1290}
1291
1292#[derive(Debug, Clone)]
1293pub struct StateSummary<
1294    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
1295> {
1296    pub version: usize,
1297    pub parameter_count: usize,
1298    pub parameter_norm: T,
1299    pub total_updates: usize,
1300    pub session_duration: Duration,
1301    pub last_update_magnitude: T,
1302    pub average_loss: T,
1303    pub convergence_rate: T,
1304    pub memory_usage: usize,
1305    pub checkpoint_count: usize,
1306}
1307
1308/// Serializable state structures
1309#[derive(Debug, Clone, Serialize, Deserialize)]
1310pub struct SerializableState<
1311    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
1312> {
1313    pub parameters: Vec<T>,
1314    pub parameter_shape: Vec<usize>,
1315    pub optimization_state: SerializableOptimizationState<T>,
1316    pub learning_state: SerializableLearningState<T>,
1317    pub metadata: StateMetadata,
1318    pub statistics: StateStatistics<T>,
1319}
1320
1321#[derive(Debug, Clone, Serialize, Deserialize)]
1322pub struct SerializableOptimizationState<
1323    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
1324> {
1325    pub learning_rate: T,
1326    pub step_count: usize,
1327    pub last_update_magnitude: T,
1328    pub momentum: Option<Vec<T>>,
1329    pub convergence_metrics: SerializableConvergenceState<T>,
1330}
1331
1332#[derive(Debug, Clone, Serialize, Deserialize)]
1333pub struct SerializableLearningState<
1334    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
1335> {
1336    pub loss_history: Vec<T>,
1337    pub average_loss: T,
1338    pub best_loss: T,
1339    pub convergence_rate: T,
1340}
1341
1342#[derive(Debug, Clone, Serialize, Deserialize)]
1343pub struct SerializableConvergenceState<
1344    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
1345> {
1346    pub recent_losses: Vec<T>,
1347    pub convergence_rate: T,
1348    pub stability_score: T,
1349}
1350
1351/// Additional supporting structures
1352#[derive(Debug, Clone)]
1353pub struct TaskAdaptationRecord<
1354    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
1355> {
1356    pub task_id: String,
1357    pub adaptation_steps: usize,
1358    pub final_loss: T,
1359    pub adaptation_time: Duration,
1360}
1361
1362#[derive(Debug, Clone)]
1363/// Linear-warmup-then-exponential-decay learning-rate schedule.
1364///
1365/// `initial_rate`, `warmup_steps` and `decay_factor` used to be stored and never
1366/// consulted: the schedule had no way to advance, so `current_rate` stayed at
1367/// `initial_rate` forever and [`LearningState`] held a schedule it never used.
1368pub struct LearningSchedule<
1369    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
1370> {
1371    pub initial_rate: T,
1372    pub current_rate: T,
1373    pub warmup_steps: usize,
1374    pub decay_factor: T,
1375    /// Steps taken so far, advanced by [`LearningSchedule::step`].
1376    steps_taken: usize,
1377}
1378
1379impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
1380    LearningSchedule<T>
1381{
1382    pub fn new(initial_rate: T, warmup_steps: usize) -> Self {
1383        Self {
1384            initial_rate,
1385            current_rate: initial_rate,
1386            warmup_steps,
1387            decay_factor: scirs2_core::numeric::NumCast::from(0.95).unwrap_or_else(|| T::zero()),
1388            steps_taken: 0,
1389        }
1390    }
1391
1392    /// Rate at `step`: `initial_rate · step / warmup_steps` while warming up,
1393    /// then `initial_rate · decay_factor^(step - warmup_steps)`.
1394    ///
1395    /// With `warmup_steps == 0` the warmup phase is skipped entirely.
1396    pub fn rate_at(&self, step: usize) -> T {
1397        if step < self.warmup_steps {
1398            let progress: T =
1399                scirs2_core::numeric::NumCast::from((step + 1) as f64 / self.warmup_steps as f64)
1400                    .unwrap_or_else(T::one);
1401            return self.initial_rate * progress;
1402        }
1403        let decayed: T = scirs2_core::numeric::NumCast::from((step - self.warmup_steps) as f64)
1404            .unwrap_or_else(T::zero);
1405        self.initial_rate * self.decay_factor.powf(decayed)
1406    }
1407
1408    /// Advance one step and return the new rate.
1409    pub fn step(&mut self) -> T {
1410        self.current_rate = self.rate_at(self.steps_taken);
1411        self.steps_taken += 1;
1412        self.current_rate
1413    }
1414
1415    /// Steps taken so far.
1416    pub fn steps_taken(&self) -> usize {
1417        self.steps_taken
1418    }
1419}
1420
1421#[derive(Debug, Clone)]
1422pub struct LearningPerformanceMetrics<
1423    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
1424> {
1425    pub loss_trend: T,
1426    pub convergence_stability: T,
1427    pub adaptation_efficiency: T,
1428}
1429
1430impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static> Default
1431    for LearningPerformanceMetrics<T>
1432{
1433    fn default() -> Self {
1434        Self::new()
1435    }
1436}
1437
1438impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
1439    LearningPerformanceMetrics<T>
1440{
1441    pub fn new() -> Self {
1442        Self {
1443            loss_trend: T::zero(),
1444            convergence_stability: T::zero(),
1445            adaptation_efficiency: T::zero(),
1446        }
1447    }
1448
1449    pub fn record_loss(&mut self, _loss: T) {
1450        // Update performance metrics logic
1451    }
1452
1453    pub fn get_stability_score(&self) -> T {
1454        self.convergence_stability
1455    }
1456
1457    pub fn reset(&mut self) {
1458        self.loss_trend = T::zero();
1459        self.convergence_stability = T::zero();
1460        self.adaptation_efficiency = T::zero();
1461    }
1462}
1463
1464#[derive(Debug, Clone)]
1465pub struct OptimizationProgress<
1466    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
1467> {
1468    pub step_count: usize,
1469    pub current_learning_rate: T,
1470    pub last_update_magnitude: T,
1471    pub convergence_rate: T,
1472    pub stability_score: T,
1473}
1474
1475#[derive(Debug, Clone)]
1476pub struct LearningStatistics<
1477    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
1478> {
1479    pub total_episodes: usize,
1480    pub average_loss: T,
1481    pub best_loss: T,
1482    pub convergence_rate: T,
1483    pub learning_stability: T,
1484}
1485
1486#[derive(Debug, Clone)]
1487pub struct AttentionCache<
1488    T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
1489> {
1490    pub cached_keys: Array2<T>,
1491    pub cached_values: Array2<T>,
1492    pub cache_size: usize,
1493}
1494
1495#[derive(Debug, Clone)]
1496pub struct MemoryUsageTracker {
1497    pub total_usage: usize,
1498    pub peak_usage: usize,
1499    pub allocation_count: usize,
1500}
1501
1502impl Default for MemoryUsageTracker {
1503    fn default() -> Self {
1504        Self::new()
1505    }
1506}
1507
1508impl MemoryUsageTracker {
1509    pub fn new() -> Self {
1510        Self {
1511            total_usage: 0,
1512            peak_usage: 0,
1513            allocation_count: 0,
1514        }
1515    }
1516
1517    pub fn reset(&mut self) {
1518        self.total_usage = 0;
1519        self.peak_usage = 0;
1520        self.allocation_count = 0;
1521    }
1522}
1523
1524#[derive(Debug, Clone)]
1525pub struct CacheStatistics {
1526    pub hit_count: usize,
1527    pub miss_count: usize,
1528    pub eviction_count: usize,
1529}
1530
1531impl Default for CacheStatistics {
1532    fn default() -> Self {
1533        Self::new()
1534    }
1535}
1536
1537impl CacheStatistics {
1538    pub fn new() -> Self {
1539        Self {
1540            hit_count: 0,
1541            miss_count: 0,
1542            eviction_count: 0,
1543        }
1544    }
1545
1546    pub fn reset(&mut self) {
1547        self.hit_count = 0;
1548        self.miss_count = 0;
1549        self.eviction_count = 0;
1550    }
1551}
1552
1553#[derive(Debug, Clone)]
1554pub struct CheckpointMetadata {
1555    pub id: String,
1556    pub name: String,
1557    pub created_at: Instant,
1558    pub size_estimate: usize,
1559    pub description: String,
1560}
1561
1562#[derive(Debug, Clone)]
1563pub struct AutoSaveConfig {
1564    pub enabled: bool,
1565    pub frequency: usize,
1566    pub max_auto_saves: usize,
1567}
1568
1569impl Default for AutoSaveConfig {
1570    fn default() -> Self {
1571        Self {
1572            enabled: true,
1573            frequency: 100,
1574            max_auto_saves: 5,
1575        }
1576    }
1577}
1578
1579#[cfg(test)]
1580mod tests {
1581    use super::*;
1582
1583    #[test]
1584    fn test_optimizer_state_creation() {
1585        let config = super::super::config::TransformerBasedOptimizerConfig::<f32>::default();
1586        let state = TransformerOptimizerState::new(&config);
1587        assert!(state.is_ok());
1588
1589        let s = state.expect("TransformerOptimizerState::new should succeed");
1590        assert_eq!(s.version, 0);
1591        assert!(!s.current_parameters.is_empty());
1592    }
1593
1594    #[test]
1595    fn test_state_update() {
1596        let config = super::super::config::TransformerBasedOptimizerConfig::<f32>::default();
1597        let mut state = TransformerOptimizerState::new(&config)
1598            .expect("TransformerOptimizerState::new should succeed");
1599
1600        let update = Array1::<f32>::ones(state.current_parameters.len());
1601        let result = state.update_with_step(&update, Some(1.5));
1602        assert!(result.is_ok());
1603        assert_eq!(state.version, 1);
1604    }
1605
1606    /// F3 regression: on any config where `num_transformer_layers != 1` (the
1607    /// default is 6), `current_parameters.len() == model_dimension *
1608    /// num_transformer_layers` while the update network only ever produces
1609    /// `model_dimension` elements. Adding the two mismatched-length arrays
1610    /// directly used to panic inside `ndarray`'s `Add`; the update must
1611    /// instead be tiled across every layer segment without panicking.
1612    #[test]
1613    fn test_update_with_step_tiles_per_layer_update_on_default_config() {
1614        let config = super::super::config::TransformerBasedOptimizerConfig::<f32>::default();
1615        assert_ne!(
1616            config.num_transformer_layers, 1,
1617            "test is only meaningful when layers > 1"
1618        );
1619        let mut state = TransformerOptimizerState::new(&config)
1620            .expect("TransformerOptimizerState::new should succeed");
1621        let total_len = state.current_parameters.len();
1622        assert_eq!(
1623            total_len,
1624            config.model_dimension * config.num_transformer_layers
1625        );
1626
1627        // Exactly what the adaptation network actually produces: one layer's
1628        // worth of update, not the full parameter length.
1629        let per_layer_update = Array1::<f32>::from_elem(config.model_dimension, 0.5);
1630        let result = state.update_with_step(&per_layer_update, Some(1.0));
1631        assert!(
1632            result.is_ok(),
1633            "a per-layer update must not panic or error on the default config: {result:?}"
1634        );
1635
1636        // Every layer segment must have received the same per-layer update.
1637        for layer in 0..config.num_transformer_layers {
1638            let start = layer * config.model_dimension;
1639            let segment = &state.current_parameters.as_slice().expect("contiguous")
1640                [start..start + config.model_dimension];
1641            for &v in segment {
1642                approx::assert_abs_diff_eq!(v, 0.5, epsilon = 1e-6);
1643            }
1644        }
1645    }
1646
1647    /// F3 regression: a genuinely incompatible update length (one that cannot
1648    /// be tiled onto the parameter vector) must be a typed error, not a panic.
1649    #[test]
1650    fn test_update_with_step_rejects_untileable_length() {
1651        let config = super::super::config::TransformerBasedOptimizerConfig::<f32>::default();
1652        let mut state = TransformerOptimizerState::new(&config)
1653            .expect("TransformerOptimizerState::new should succeed");
1654        let bad_update = Array1::<f32>::ones(state.current_parameters.len() + 3);
1655        let result = state.update_with_step(&bad_update, None);
1656        assert!(result.is_err(), "an untileable update length must error");
1657    }
1658
1659    #[test]
1660    fn test_snapshot_creation() {
1661        let config = super::super::config::TransformerBasedOptimizerConfig::<f32>::default();
1662        let state = TransformerOptimizerState::new(&config)
1663            .expect("TransformerOptimizerState::new should succeed");
1664
1665        let snapshot = state.create_snapshot();
1666        assert!(snapshot.is_ok());
1667
1668        let snap = snapshot.expect("create_snapshot should succeed");
1669        assert_eq!(snap.version, 0);
1670        assert_eq!(snap.parameters.len(), state.current_parameters.len());
1671    }
1672
1673    #[test]
1674    fn test_checkpoint_management() {
1675        let config = super::super::config::TransformerBasedOptimizerConfig::<f32>::default();
1676        let mut state = TransformerOptimizerState::new(&config)
1677            .expect("TransformerOptimizerState::new should succeed");
1678
1679        let checkpoint_id = state.save_checkpoint("test_checkpoint".to_string());
1680        assert!(checkpoint_id.is_ok());
1681
1682        let id = checkpoint_id.expect("save_checkpoint should succeed");
1683        let load_result = state.load_checkpoint(&id);
1684        assert!(load_result.is_ok());
1685    }
1686
1687    #[test]
1688    fn test_parameter_history() {
1689        let history = ParameterHistory::<f32>::new(10, 5);
1690        assert!(history.is_ok());
1691
1692        let mut h = history.expect("ParameterHistory::new should succeed");
1693        let params = Array1::<f32>::ones(5);
1694        assert!(h.record_parameters(&params).is_ok());
1695
1696        let recent = h.get_recent_parameters(1);
1697        assert_eq!(recent.len(), 1);
1698    }
1699
1700    #[test]
1701    fn test_convergence_tracker() {
1702        let mut tracker = ConvergenceTracker::<f32>::new();
1703
1704        tracker.record_loss(2.0);
1705        tracker.record_loss(1.5);
1706        tracker.record_loss(1.0);
1707
1708        let convergence = tracker.get_convergence_rate();
1709        assert!(convergence > 0.0);
1710
1711        let stability = tracker.get_stability_score();
1712        assert!(stability > 0.0 && stability <= 1.0);
1713    }
1714
1715    #[test]
1716    fn test_state_validation() {
1717        let config = super::super::config::TransformerBasedOptimizerConfig::<f32>::default();
1718        let state = TransformerOptimizerState::new(&config)
1719            .expect("TransformerOptimizerState::new should succeed");
1720
1721        let validation = state.validate_state();
1722        assert!(validation.is_ok());
1723
1724        let report = validation.expect("validate_state should succeed");
1725        assert!(report.is_valid);
1726    }
1727
1728    /// `StateConfig::from_optimizer_config` used to ignore its argument entirely
1729    /// and return four hard-coded literals, so shrinking the optimizer's history
1730    /// bound had no effect on the state tracker.
1731    #[test]
1732    fn state_config_follows_the_optimizer_performance_config() {
1733        let mut config = super::super::config::TransformerBasedOptimizerConfig::<f32>::default();
1734        config.performance_config.max_history_size = 42;
1735        config.performance_config.metrics_interval = 7;
1736
1737        let derived = StateConfig::from_optimizer_config(&config);
1738        assert_eq!(derived.max_history_size, 42);
1739        assert_eq!(derived.checkpoint_frequency, 7);
1740
1741        // A zero interval would make the auto-save modulus undefined, so it is
1742        // floored at 1 rather than propagated.
1743        config.performance_config.metrics_interval = 0;
1744        assert_eq!(
1745            StateConfig::from_optimizer_config(&config).checkpoint_frequency,
1746            1
1747        );
1748    }
1749
1750    /// `CheckpointManager::auto_save_config` was written at construction and
1751    /// never read: there was no auto-save path at all, and the cadence was a
1752    /// fixed 100 steps regardless of configuration.
1753    #[test]
1754    fn checkpoint_manager_auto_saves_on_the_configured_cadence() {
1755        let mut config = super::super::config::TransformerBasedOptimizerConfig::<f32>::default();
1756        config.performance_config.metrics_interval = 3;
1757        let state = TransformerOptimizerState::new(&config).expect("state");
1758
1759        let mut manager = CheckpointManager::<f32>::new(&config).expect("manager");
1760        assert_eq!(manager.auto_save_config().frequency, 3);
1761
1762        // Off-cadence steps must not save.
1763        for step in [1_usize, 2, 4, 5] {
1764            assert_eq!(
1765                manager
1766                    .maybe_auto_save(step, state.create_snapshot().expect("snapshot"))
1767                    .expect("auto save"),
1768                None,
1769                "step {step} is not a multiple of 3"
1770            );
1771        }
1772        assert_eq!(manager.auto_save_count(), 0);
1773
1774        // On-cadence steps must save, and be capped at `max_auto_saves`.
1775        let cap = manager.auto_save_config().max_auto_saves;
1776        assert!(cap > 0, "default policy should retain some auto-saves");
1777        for step in (0..).step_by(3).take(cap + 3) {
1778            let id = manager
1779                .maybe_auto_save(step, state.create_snapshot().expect("snapshot"))
1780                .expect("auto save")
1781                .expect("on-cadence step must produce a checkpoint");
1782            assert!(id.starts_with("auto_"), "unexpected id {id}");
1783        }
1784        assert_eq!(
1785            manager.auto_save_count(),
1786            cap,
1787            "surplus auto-saves must be evicted oldest-first"
1788        );
1789
1790        // An explicit checkpoint is retained under its own limit and is not
1791        // counted as (or evicted by) an auto-save.
1792        manager
1793            .save_checkpoint("manual".to_string(), state.create_snapshot().expect("snap"))
1794            .expect("manual save");
1795        manager
1796            .maybe_auto_save(3_000, state.create_snapshot().expect("snap"))
1797            .expect("auto save");
1798        assert_eq!(manager.auto_save_count(), cap);
1799        assert!(manager
1800            .list_checkpoints()
1801            .iter()
1802            .any(|m| m.name == "manual"));
1803    }
1804
1805    /// Auto-save must be a no-op when the policy is disabled.
1806    #[test]
1807    fn checkpoint_manager_auto_save_respects_a_disabled_policy() {
1808        let config = super::super::config::TransformerBasedOptimizerConfig::<f32>::default();
1809        let state = TransformerOptimizerState::new(&config).expect("state");
1810        let mut manager = CheckpointManager::<f32>::new(&config).expect("manager");
1811        manager.auto_save_config.enabled = false;
1812
1813        assert_eq!(
1814            manager
1815                .maybe_auto_save(0, state.create_snapshot().expect("snapshot"))
1816                .expect("auto save"),
1817            None
1818        );
1819        assert_eq!(manager.get_checkpoint_count(), 0);
1820    }
1821}