Skip to main content

torsh_optim/
lib.rs

1//! Optimization algorithms for ToRSh
2//!
3//! This crate provides PyTorch-compatible optimizers built on top of scirs2-optim.
4//!
5//! # Features
6//!
7//! - **80+ optimizers**: Comprehensive collection including Adam, SGD, RAdam, Ranger, Lion, Sophia, and more
8//! - **Modern optimizers**: Latest research including Schedule-Free AdamW and Prodigy
9//! - **Second-order methods**: L-BFGS, Newton-CG, Trust Region, K-FAC, AdaHessian
10//! - **Learning rate schedulers**: Step, exponential, cosine annealing, one-cycle, and more
11//! - **Mixed precision training**: Full fp16/fp32 support with loss scaling
12//! - **Distributed optimization**: AsyncSGD, Elastic Averaging, Federated Learning
13//! - **Advanced features**: Gradient accumulation, fused kernels, memory-efficient implementations
14//! - **Research features**: Quantum-inspired, neuromorphic, continual learning, green AI optimizers
15//!
16//! # Quick Start
17//!
18//! ```rust,no_run
19//! use torsh_optim::prelude::*;
20//! use torsh_tensor::Tensor;
21//! use std::sync::Arc;
22//! use parking_lot::RwLock;
23//!
24//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
25//! // Create parameters
26//! let params = vec![Arc::new(RwLock::new(Tensor::scalar(1.0)?))];
27//!
28//! // Create optimizer
29//! let mut optimizer = Adam::new(params, Some(0.001), None, None, None, false);
30//!
31//! // Training loop
32//! for _ in 0..100 {
33//!     // ... compute gradients ...
34//!     optimizer.step()?;
35//!     optimizer.zero_grad();
36//! }
37//! # Ok(())
38//! # }
39//! ```
40
41#![cfg_attr(not(feature = "std"), no_std)]
42// Note: These allows are necessary for maintaining compatibility with diverse optimizer implementations
43// and reducing noise from legitimate design patterns used across the codebase
44#![allow(dead_code)] // Many optimizers have internal methods not called externally
45#![allow(unused_imports)] // Conditional compilation features may leave some imports unused
46#![allow(unused_variables)] // Some optimizer variants have parameters used only in specific configurations
47#![allow(unused_mut)] // Mutability annotations required for consistency even when not always modified
48
49#[cfg(not(feature = "std"))]
50extern crate alloc;
51
52pub mod adabelief;
53pub mod adabound;
54pub mod adadelta;
55pub mod adagrad;
56pub mod adahessian;
57pub mod adam;
58pub mod adamax;
59pub mod advanced;
60pub mod asgd;
61pub mod bayesian_optimization;
62pub mod benchmarks;
63pub mod checkpointing;
64pub mod composition;
65pub mod continual_learning;
66pub mod cross_framework_validation;
67pub mod debugging;
68pub mod differential_privacy;
69pub mod distributed;
70pub mod evolutionary_strategies;
71pub mod ftrl;
72pub mod fused_kernels;
73pub mod grad_accumulation;
74pub mod gradient_free;
75pub mod green_ai;
76pub mod hyperparameter_tuning;
77pub mod kfac;
78pub mod lamb;
79pub mod lazy_updates;
80pub mod lbfgs;
81pub mod lion;
82pub mod lookahead;
83pub mod low_precision;
84pub mod lr_scheduler;
85pub mod lr_scheduler_additional;
86pub mod lr_scheduler_enhanced;
87pub mod memory_efficient;
88pub mod memory_mapped;
89pub mod mixed_precision;
90pub mod nadam;
91pub mod natural_gradient;
92pub mod neural_optimizer;
93pub mod neuromorphic;
94pub mod newton_cg;
95pub mod numerical_stability_tests;
96pub mod online_learning;
97pub mod optimizer;
98pub mod param_update;
99pub mod prodigy;
100pub mod quantum_inspired;
101pub mod radam;
102pub mod ranger;
103pub mod rmsprop;
104pub mod robustness;
105pub mod rprop;
106pub mod schedule_free;
107pub mod sgd;
108pub mod shampoo;
109pub mod sophia;
110pub mod sparse_adam;
111pub mod sparse_updates;
112pub mod state_dict_ops;
113pub mod stress_tests;
114pub mod trust_region;
115pub mod yellowfin;
116
117use parking_lot::RwLock;
118use std::collections::HashMap;
119use std::sync::Arc;
120use torsh_core::error::{Result, TorshError};
121use torsh_tensor::Tensor;
122
123/// Optimizer-specific error type
124#[derive(Debug, thiserror::Error)]
125pub enum OptimizerError {
126    #[error("Tensor operation failed: {0}")]
127    TensorError(#[from] torsh_core::error::TorshError),
128
129    #[error("Invalid parameter: {0}")]
130    InvalidParameter(String),
131
132    #[error("Serialization error: {0}")]
133    SerializationError(String),
134
135    #[error("IO error: {0}")]
136    IoError(#[from] std::io::Error),
137
138    #[error("Checkpoint error: {0}")]
139    CheckpointError(String),
140
141    #[error("Configuration error: {0}")]
142    ConfigError(String),
143
144    #[error("State error: {0}")]
145    StateError(String),
146
147    #[error("Invalid input: {0}")]
148    InvalidInput(String),
149
150    #[error("Numerical error: {0}")]
151    NumericalError(String),
152
153    #[error("Memory map error: {0}")]
154    MemoryMapError(String),
155}
156
157impl From<OptimizerError> for torsh_core::error::TorshError {
158    fn from(err: OptimizerError) -> Self {
159        match err {
160            OptimizerError::TensorError(e) => e,
161            OptimizerError::InvalidParameter(msg) => {
162                torsh_core::error::TorshError::InvalidArgument(msg)
163            }
164            OptimizerError::SerializationError(msg) => {
165                torsh_core::error::TorshError::SerializationError(msg)
166            }
167            OptimizerError::IoError(e) => torsh_core::error::TorshError::IoError(e.to_string()),
168            OptimizerError::CheckpointError(msg) => {
169                torsh_core::error::TorshError::RuntimeError(msg)
170            }
171            OptimizerError::ConfigError(msg) => torsh_core::error::TorshError::ConfigError(msg),
172            OptimizerError::StateError(msg) => torsh_core::error::TorshError::RuntimeError(msg),
173            OptimizerError::InvalidInput(msg) => {
174                torsh_core::error::TorshError::InvalidArgument(msg)
175            }
176            OptimizerError::NumericalError(msg) => torsh_core::error::TorshError::RuntimeError(msg),
177            OptimizerError::MemoryMapError(msg) => torsh_core::error::TorshError::RuntimeError(msg),
178        }
179    }
180}
181
182/// Result type for optimizer operations
183pub type OptimizerResult<T> = std::result::Result<T, OptimizerError>;
184
185// Version information
186pub const VERSION: &str = env!("CARGO_PKG_VERSION");
187pub const VERSION_MAJOR: u32 = 0;
188pub const VERSION_MINOR: u32 = 1;
189pub const VERSION_PATCH: u32 = 0;
190
191// Re-export scirs2 optimizer functionality
192// use scirs2::optim as sci_optim;
193
194/// Base optimizer trait
195pub trait Optimizer {
196    /// Perform a single optimization step
197    fn step(&mut self) -> OptimizerResult<()>;
198
199    /// Zero all gradients
200    fn zero_grad(&mut self);
201
202    /// Get the current learning rate
203    fn get_lr(&self) -> Vec<f32>;
204
205    /// Set one learning rate for every parameter group (broadcast).
206    fn set_lr(&mut self, lr: f32);
207
208    /// Set the learning rate of each parameter group individually.
209    ///
210    /// `lrs[i]` is applied to parameter group `i`; extra entries are ignored and
211    /// groups beyond `lrs.len()` keep their current rate. This is what learning
212    /// rate schedulers call, so that the differential-LR recipe (e.g. a lower
213    /// rate for a pretrained backbone than for a freshly initialised head,
214    /// configured through [`Optimizer::add_param_group`]) survives a scheduler
215    /// step instead of being collapsed onto a single rate.
216    ///
217    /// # Default Implementation
218    ///
219    /// The default broadcasts `lrs[0]` through [`Optimizer::set_lr`], which is
220    /// correct for optimizers that expose exactly one parameter group. Any
221    /// optimizer that can hold several groups must override this.
222    fn set_lrs(&mut self, lrs: &[f32]) {
223        if let Some(&lr) = lrs.first() {
224            self.set_lr(lr);
225        }
226    }
227
228    /// Add a parameter group
229    fn add_param_group(&mut self, params: Vec<Arc<RwLock<Tensor>>>, options: HashMap<String, f32>);
230
231    /// Get the parameter tensors managed by this optimizer.
232    ///
233    /// Returns clones of the `Arc<RwLock<Tensor>>` handles. Because they are
234    /// reference-counted, the returned handles point at the *same* underlying
235    /// tensors the optimizer updates, so callers can both read parameters and
236    /// access their gradients via [`Tensor::grad`].
237    ///
238    /// Meta-optimizers such as [`crate::lookahead::Lookahead`] and the gradient
239    /// accumulation wrappers in [`crate::grad_accumulation`] rely on this to
240    /// inspect and update the wrapped optimizer's parameters.
241    ///
242    /// # Default Implementation
243    ///
244    /// The default returns an empty vector. Concrete optimizers that own
245    /// parameter groups override this to expose their parameters, and wrapper
246    /// optimizers delegate to the optimizer they wrap. An empty result therefore
247    /// means "this optimizer does not expose parameters", *not* "this optimizer
248    /// has no parameters"; callers that require parameters must treat an empty
249    /// result as an error rather than silently doing nothing.
250    fn parameters(&self) -> Vec<Arc<RwLock<Tensor>>> {
251        Vec::new()
252    }
253
254    /// Get state dict for serialization
255    fn state_dict(&self) -> OptimizerResult<OptimizerState>;
256
257    /// Load state dict
258    fn load_state_dict(&mut self, state: OptimizerState) -> OptimizerResult<()>;
259}
260
261/// Optimizer state for serialization
262#[derive(Debug, Clone)]
263pub struct OptimizerState {
264    /// Optimizer type identifier
265    pub optimizer_type: String,
266    /// Version of the state format
267    pub version: String,
268    /// Parameter group states
269    pub param_groups: Vec<ParamGroupState>,
270    /// Per-parameter optimizer state (keyed by parameter ID)
271    pub state: HashMap<String, HashMap<String, Tensor>>,
272    /// Global optimizer state
273    pub global_state: HashMap<String, f32>,
274}
275
276/// Parameter group state
277#[derive(Debug, Clone)]
278pub struct ParamGroupState {
279    /// Learning rate for this group
280    pub lr: f32,
281    /// Additional options for this group
282    pub options: HashMap<String, f32>,
283    /// Number of parameters in this group (for validation)
284    pub param_count: usize,
285}
286
287impl OptimizerState {
288    /// Create a new empty optimizer state
289    pub fn new(optimizer_type: String) -> Self {
290        Self {
291            optimizer_type,
292            version: VERSION.to_string(),
293            param_groups: Vec::new(),
294            state: HashMap::new(),
295            global_state: HashMap::new(),
296        }
297    }
298
299    /// Validate the state structure
300    pub fn validate(&self) -> Result<()> {
301        if self.optimizer_type.is_empty() {
302            return Err(TorshError::InvalidArgument(
303                "Optimizer type cannot be empty".to_string(),
304            ));
305        }
306
307        // Check that all parameter groups are valid
308        for (i, group) in self.param_groups.iter().enumerate() {
309            if !group.lr.is_finite() || group.lr <= 0.0 {
310                return Err(TorshError::InvalidArgument(format!(
311                    "Invalid learning rate in group {}",
312                    i
313                )));
314            }
315        }
316
317        // Check that all state values are finite
318        for (param_id, param_state) in &self.state {
319            for (state_name, tensor) in param_state {
320                // For now, just check that the keys are valid
321                if param_id.is_empty() || state_name.is_empty() {
322                    return Err(TorshError::InvalidArgument(
323                        "State keys cannot be empty".to_string(),
324                    ));
325                }
326            }
327        }
328
329        Ok(())
330    }
331
332    /// Get the total number of parameters across all groups
333    pub fn total_param_count(&self) -> usize {
334        self.param_groups.iter().map(|g| g.param_count).sum()
335    }
336
337    /// Check if state is compatible with another state (same structure)
338    pub fn is_compatible_with(&self, other: &OptimizerState) -> bool {
339        self.optimizer_type == other.optimizer_type
340            && self.param_groups.len() == other.param_groups.len()
341            && self
342                .param_groups
343                .iter()
344                .zip(other.param_groups.iter())
345                .all(|(a, b)| a.param_count == b.param_count)
346    }
347}
348
349impl ParamGroupState {
350    /// Create a new parameter group state
351    pub fn new(lr: f32, param_count: usize) -> Self {
352        Self {
353            lr,
354            options: HashMap::new(),
355            param_count,
356        }
357    }
358
359    /// Create from a ParamGroup
360    pub fn from_param_group(group: &ParamGroup) -> Self {
361        Self {
362            lr: group.lr,
363            options: group.options.clone(),
364            param_count: group.params.len(),
365        }
366    }
367
368    /// Get an option value with a default
369    pub fn get_option(&self, key: &str, default: f32) -> f32 {
370        self.options.get(key).copied().unwrap_or(default)
371    }
372
373    /// Set an option value
374    pub fn set_option(&mut self, key: String, value: f32) {
375        self.options.insert(key, value);
376    }
377}
378
379/// Parameter group
380#[derive(Debug, Clone)]
381pub struct ParamGroup {
382    pub params: Vec<Arc<RwLock<Tensor>>>,
383    pub lr: f32,
384    pub options: HashMap<String, f32>,
385}
386
387/// Builder for creating parameter groups with various options
388#[derive(Debug)]
389pub struct ParamGroupBuilder {
390    params: Vec<Arc<RwLock<Tensor>>>,
391    lr: f32,
392    options: HashMap<String, f32>,
393}
394
395impl ParamGroupBuilder {
396    /// Create a new parameter group builder
397    pub fn new(lr: f32) -> Self {
398        Self {
399            params: Vec::new(),
400            lr,
401            options: HashMap::new(),
402        }
403    }
404
405    /// Add parameters to the group
406    pub fn params(mut self, params: Vec<Arc<RwLock<Tensor>>>) -> Self {
407        self.params = params;
408        self
409    }
410
411    /// Add a single parameter to the group
412    pub fn add_param(mut self, param: Arc<RwLock<Tensor>>) -> Self {
413        self.params.push(param);
414        self
415    }
416
417    /// Set weight decay
418    pub fn weight_decay(mut self, weight_decay: f32) -> Self {
419        self.options
420            .insert("weight_decay".to_string(), weight_decay);
421        self
422    }
423
424    /// Set epsilon
425    pub fn eps(mut self, eps: f32) -> Self {
426        self.options.insert("eps".to_string(), eps);
427        self
428    }
429
430    /// Set a custom option
431    pub fn option(mut self, key: String, value: f32) -> Self {
432        self.options.insert(key, value);
433        self
434    }
435
436    /// Set options from OptimizerOptions
437    pub fn from_options(mut self, options: &OptimizerOptions) -> Self {
438        self.lr = options.lr;
439        self.options = options.to_hashmap();
440        self.options.remove("lr"); // lr is stored separately
441        self
442    }
443
444    /// Build the parameter group
445    pub fn build(self) -> ParamGroup {
446        ParamGroup {
447            params: self.params,
448            lr: self.lr,
449            options: self.options,
450        }
451    }
452}
453
454impl ParamGroup {
455    pub fn new(params: Vec<Arc<RwLock<Tensor>>>, lr: f32) -> Self {
456        Self {
457            params,
458            lr,
459            options: HashMap::new(),
460        }
461    }
462
463    pub fn with_options(mut self, options: HashMap<String, f32>) -> Self {
464        self.options = options;
465        self
466    }
467
468    /// Add a single parameter to the group
469    pub fn add_param(&mut self, param: Arc<RwLock<Tensor>>) {
470        self.params.push(param);
471    }
472
473    /// Get a specific option value, falling back to a default
474    pub fn get_option(&self, key: &str, default: f32) -> f32 {
475        self.options.get(key).copied().unwrap_or(default)
476    }
477
478    /// Set a specific option value
479    pub fn set_option(&mut self, key: String, value: f32) {
480        self.options.insert(key, value);
481    }
482
483    /// Get the number of parameters in this group
484    pub fn param_count(&self) -> usize {
485        self.params.len()
486    }
487
488    /// Check if this group has any parameters
489    pub fn is_empty(&self) -> bool {
490        self.params.is_empty()
491    }
492
493    /// Get all parameters that have gradients
494    pub fn params_with_grads(&self) -> Vec<&Arc<RwLock<Tensor>>> {
495        self.params
496            .iter()
497            .filter(|param| param.read().has_grad())
498            .collect()
499    }
500
501    /// Validate that all parameters in the group are valid
502    pub fn validate(&self) -> bool {
503        !self.params.is_empty() && self.lr.is_finite() && self.lr > 0.0
504    }
505
506    /// Get parameter count for each unique shape in the group
507    pub fn get_shape_counts(&self) -> HashMap<Vec<usize>, usize> {
508        let mut shape_counts = HashMap::new();
509        for param in &self.params {
510            let shape = param.read().shape().dims().to_vec();
511            *shape_counts.entry(shape).or_insert(0) += 1;
512        }
513        shape_counts
514    }
515
516    /// Get total number of parameters (not tensors, but individual parameters)
517    pub fn total_param_count(&self) -> usize {
518        self.params.iter().map(|param| param.read().numel()).sum()
519    }
520
521    /// Clear gradients for all parameters in this group
522    pub fn zero_grad(&self) {
523        for param in &self.params {
524            param.write().zero_grad();
525        }
526    }
527
528    /// Check if any parameter in the group has gradients
529    pub fn has_any_grads(&self) -> bool {
530        self.params.iter().any(|param| param.read().has_grad())
531    }
532
533    /// Get gradient norm for all parameters in the group
534    pub fn grad_norm(&self) -> Result<f32> {
535        let mut total_norm_sq = 0.0f32;
536
537        for param in &self.params {
538            let param_guard = param.read();
539            if let Some(grad) = param_guard.grad() {
540                let grad_norm = grad.norm().map_err(|e| {
541                    TorshError::Other(format!("Failed to compute gradient norm: {}", e))
542                })?;
543                let norm_value = grad_norm.to_vec().map_err(|e| {
544                    TorshError::Other(format!("Failed to extract norm value: {}", e))
545                })?[0];
546                total_norm_sq += norm_value * norm_value;
547            }
548        }
549
550        Ok(total_norm_sq.sqrt())
551    }
552
553    /// Apply gradient clipping to all parameters in the group
554    pub fn clip_grads(&self, max_norm: f32) -> Result<f32> {
555        let total_norm = self.grad_norm()?;
556
557        if total_norm > max_norm {
558            let scale = max_norm / total_norm;
559            for param in &self.params {
560                let mut param_guard = param.write();
561                if let Some(grad) = param_guard.grad() {
562                    let clipped_grad = grad.mul_scalar(scale).map_err(|e| {
563                        TorshError::Other(format!("Failed to clip gradient: {}", e))
564                    })?;
565                    param_guard.set_grad(Some(clipped_grad));
566                }
567            }
568        }
569
570        Ok(total_norm)
571    }
572}
573
574/// Common optimizer options
575#[derive(Debug, Clone)]
576pub struct OptimizerOptions {
577    pub lr: f32,
578    pub weight_decay: f32,
579    pub eps: f32,
580    pub maximize: bool,
581}
582
583impl Default for OptimizerOptions {
584    fn default() -> Self {
585        Self {
586            lr: 1e-3,
587            weight_decay: 0.0,
588            eps: 1e-8,
589            maximize: false,
590        }
591    }
592}
593
594impl OptimizerOptions {
595    /// Create new optimizer options with specified learning rate
596    pub fn new(lr: f32) -> Self {
597        Self {
598            lr,
599            ..Default::default()
600        }
601    }
602
603    /// Set weight decay
604    pub fn with_weight_decay(mut self, weight_decay: f32) -> Self {
605        self.weight_decay = weight_decay;
606        self
607    }
608
609    /// Set epsilon value for numerical stability
610    pub fn with_eps(mut self, eps: f32) -> Self {
611        self.eps = eps;
612        self
613    }
614
615    /// Set maximize flag (for maximization problems)
616    pub fn with_maximize(mut self, maximize: bool) -> Self {
617        self.maximize = maximize;
618        self
619    }
620
621    /// Convert to HashMap for compatibility with parameter groups
622    pub fn to_hashmap(&self) -> HashMap<String, f32> {
623        let mut map = HashMap::new();
624        map.insert("lr".to_string(), self.lr);
625        map.insert("weight_decay".to_string(), self.weight_decay);
626        map.insert("eps".to_string(), self.eps);
627        map.insert(
628            "maximize".to_string(),
629            if self.maximize { 1.0 } else { 0.0 },
630        );
631        map
632    }
633
634    /// Create from HashMap
635    pub fn from_hashmap(map: &HashMap<String, f32>) -> Self {
636        Self {
637            lr: map.get("lr").copied().unwrap_or(1e-3),
638            weight_decay: map.get("weight_decay").copied().unwrap_or(0.0),
639            eps: map.get("eps").copied().unwrap_or(1e-8),
640            maximize: map.get("maximize").copied().unwrap_or(0.0) > 0.0,
641        }
642    }
643
644    /// Validate the options are reasonable
645    pub fn validate(&self) -> Result<()> {
646        if !self.lr.is_finite() || self.lr <= 0.0 {
647            return Err(TorshError::InvalidArgument(
648                "Learning rate must be positive and finite".to_string(),
649            ));
650        }
651        if !self.weight_decay.is_finite() || self.weight_decay < 0.0 {
652            return Err(TorshError::InvalidArgument(
653                "Weight decay must be non-negative and finite".to_string(),
654            ));
655        }
656        if !self.eps.is_finite() || self.eps <= 0.0 {
657            return Err(TorshError::InvalidArgument(
658                "Epsilon must be positive and finite".to_string(),
659            ));
660        }
661        Ok(())
662    }
663
664    /// Create standardized state dict for any optimizer
665    pub fn create_standard_state_dict(
666        optimizer_type: &str,
667        version: Option<&str>,
668        param_groups: &[ParamGroup],
669        state: &HashMap<String, HashMap<String, Tensor>>,
670        global_state: Option<HashMap<String, f32>>,
671    ) -> OptimizerState {
672        let param_group_states = param_groups
673            .iter()
674            .map(|g| ParamGroupState::from_param_group(g))
675            .collect();
676
677        let mut optimizer_state = OptimizerState {
678            optimizer_type: optimizer_type.to_string(),
679            version: version.unwrap_or("1.0").to_string(),
680            param_groups: param_group_states,
681            state: state.clone(),
682            global_state: global_state.unwrap_or_default(),
683        };
684
685        optimizer_state
686    }
687
688    /// Validate state dict compatibility between optimizers
689    pub fn validate_state_compatibility(
690        current_groups: &[ParamGroup],
691        state_groups: &[ParamGroupState],
692    ) -> Result<()> {
693        if current_groups.len() != state_groups.len() {
694            return Err(TorshError::InvalidArgument(format!(
695                "Parameter group count mismatch: expected {}, got {}",
696                current_groups.len(),
697                state_groups.len()
698            )));
699        }
700
701        for (i, (current_group, state_group)) in
702            current_groups.iter().zip(state_groups.iter()).enumerate()
703        {
704            if current_group.params.len() != state_group.param_count {
705                return Err(TorshError::InvalidArgument(format!(
706                    "Parameter count mismatch in group {}: expected {}, got {}",
707                    i,
708                    current_group.params.len(),
709                    state_group.param_count
710                )));
711            }
712        }
713
714        Ok(())
715    }
716}
717
718/// Prelude module for convenient imports
719/// Convergence testing utilities
720#[cfg(test)]
721pub mod convergence_tests {
722    use super::*;
723    use parking_lot::RwLock;
724    use std::ops::Add;
725    use std::sync::Arc;
726    use torsh_tensor::{
727        creation::{randn, zeros},
728        Tensor,
729    };
730
731    /// Test that an optimizer can minimize a simple quadratic function
732    pub fn test_quadratic_convergence<O: Optimizer>(
733        create_optimizer: impl Fn(Vec<Arc<RwLock<Tensor>>>) -> O,
734        tolerance: f32,
735        max_iterations: usize,
736    ) -> Result<()> {
737        // Create a simple quadratic function: f(x) = x^2 + y^2
738        let x = Arc::new(RwLock::new(Tensor::scalar(2.0)?));
739        let y = Arc::new(RwLock::new(Tensor::scalar(2.0)?));
740        let params = vec![x.clone(), y.clone()];
741
742        let mut optimizer = create_optimizer(params);
743
744        for i in 0..max_iterations {
745            // Compute gradients: df/dx = 2x, df/dy = 2y
746            {
747                let x_val = x.read().clone();
748                let y_val = y.read().clone();
749
750                let x_grad = x_val.mul_scalar(2.0)?;
751                let y_grad = y_val.mul_scalar(2.0)?;
752
753                x.write().set_grad(Some(x_grad));
754                y.write().set_grad(Some(y_grad));
755            }
756
757            // Optimizer step
758            optimizer
759                .step()
760                .map_err(|e| TorshError::Other(format!("Optimizer step failed: {}", e)))?;
761
762            // Check convergence
763            let x_val = x.read().to_vec()?[0];
764            let y_val = y.read().to_vec()?[0];
765            let loss = x_val * x_val + y_val * y_val;
766
767            if loss < tolerance {
768                return Ok(());
769            }
770
771            // Clear gradients for next iteration
772            optimizer.zero_grad();
773        }
774
775        Err(TorshError::Other(format!(
776            "Failed to converge within {} iterations",
777            max_iterations
778        )))
779    }
780
781    /// Test that an optimizer can minimize a linear regression problem
782    pub fn test_linear_regression_convergence<O: Optimizer>(
783        create_optimizer: impl Fn(Vec<Arc<RwLock<Tensor>>>) -> O,
784        tolerance: f32,
785        max_iterations: usize,
786    ) -> Result<()> {
787        // Create a simple linear regression problem: y = 2x + 1 + noise
788        let true_weight = 2.0;
789        let true_bias = 1.0;
790
791        // Generate synthetic data
792        let n_samples = 100;
793        let x_data = randn::<f32>(&[n_samples, 1])?;
794        let noise = randn::<f32>(&[n_samples, 1])?.mul_scalar(0.1)?;
795        let y_data = x_data
796            .mul_scalar(true_weight)?
797            .add_scalar(true_bias)?
798            .add(&noise)?;
799
800        // Initialize parameters
801        let weight = Arc::new(RwLock::new(zeros(&[1, 1])?));
802        let bias = Arc::new(RwLock::new(zeros(&[1])?));
803        let params = vec![weight.clone(), bias.clone()];
804
805        let mut optimizer = create_optimizer(params);
806
807        for i in 0..max_iterations {
808            // Forward pass: y_pred = x * weight + bias
809            let w_val = weight.read().clone();
810            let b_val = bias.read().clone();
811
812            let y_pred = x_data.matmul(&w_val)?.add(&b_val)?;
813
814            // Compute loss: MSE = mean((y_pred - y_true)^2)
815            let diff = y_pred.sub(&y_data)?;
816            let loss_tensor = diff.pow(2.0)?.mean(Some(&[0]), false)?;
817            let loss = loss_tensor.to_vec()?[0];
818
819            // Compute gradients
820            let grad_scale = 2.0 / n_samples as f32;
821            let weight_grad = x_data
822                .transpose(0, 1)?
823                .matmul(&diff)?
824                .mul_scalar(grad_scale)?;
825            let bias_grad = diff.sum()?.mul_scalar(grad_scale)?;
826
827            weight.write().set_grad(Some(weight_grad));
828            bias.write().set_grad(Some(bias_grad));
829
830            // Optimizer step
831            optimizer
832                .step()
833                .map_err(|e| TorshError::Other(format!("Optimizer step failed: {}", e)))?;
834
835            // Check convergence
836            if loss < tolerance {
837                // Verify the learned parameters are close to true values
838                let learned_weight = weight.read().to_vec()?[0];
839                let learned_bias = bias.read().to_vec()?[0];
840
841                if (learned_weight - true_weight).abs() < 0.1
842                    && (learned_bias - true_bias).abs() < 0.1
843                {
844                    return Ok(());
845                }
846            }
847
848            // Clear gradients for next iteration
849            optimizer.zero_grad();
850        }
851
852        Err(TorshError::Other(format!(
853            "Failed to converge within {} iterations",
854            max_iterations
855        )))
856    }
857
858    /// Test that an optimizer maintains consistent behavior across multiple runs
859    pub fn test_optimizer_consistency<O: Optimizer>(
860        create_optimizer: impl Fn(Vec<Arc<RwLock<Tensor>>>) -> O,
861        n_runs: usize,
862        tolerance: f32,
863    ) -> Result<()> {
864        let mut final_values = Vec::new();
865
866        for run in 0..n_runs {
867            let param = Arc::new(RwLock::new(Tensor::scalar(1.0)?));
868            let params = vec![param.clone()];
869            let mut optimizer = create_optimizer(params);
870
871            // Run for a fixed number of steps
872            for _ in 0..10 {
873                {
874                    let param_val = param.read().clone();
875                    let grad = param_val.mul_scalar(2.0)?; // Simple gradient
876                    param.write().set_grad(Some(grad));
877                }
878
879                optimizer
880                    .step()
881                    .map_err(|e| TorshError::Other(format!("Optimizer step failed: {}", e)))?;
882                optimizer.zero_grad();
883            }
884
885            final_values.push(param.read().to_vec()?[0]);
886        }
887
888        // Check that all runs produce similar results
889        let mean_value = final_values.iter().sum::<f32>() / final_values.len() as f32;
890        for &value in &final_values {
891            if (value - mean_value).abs() > tolerance {
892                return Err(TorshError::Other(format!(
893                    "Inconsistent optimizer behavior: values vary by more than {}",
894                    tolerance
895                )));
896            }
897        }
898
899        Ok(())
900    }
901}
902
903pub mod prelude {
904    pub use crate::adabelief::AdaBelief;
905    pub use crate::adabound::AdaBound;
906    pub use crate::adadelta::AdaDelta;
907    pub use crate::adagrad::AdaGrad;
908    pub use crate::adahessian::{AdaHessian, AdaHessianBuilder};
909    pub use crate::adam::{Adam, AdamW};
910    pub use crate::adamax::AdaMax;
911    pub use crate::asgd::ASGD;
912    pub use crate::checkpointing::{
913        Checkpoint, CheckpointConfig, CheckpointManager, CheckpointMetadata, CheckpointStatistics,
914        CheckpointSupport, CheckpointingOptimizer,
915    };
916    pub use crate::composition::{
917        CombinationMethod, ComposedOptimizer, CompositionBuilder, CompositionStrategy,
918        OptimizerMetrics, SwitchCriterion, VotingMethod,
919    };
920    pub use crate::debugging::{
921        AnalysisReport, AnalyzerConfig, ConvergenceTracker, GradientFlowPoint, GradientStatistics,
922        HyperparameterSensitivity, OptimizationRecommendation, OptimizationStep, OptimizerAnalyzer,
923        ParameterStatistics, RecommendationCategory, SensitivityReport, SensitivityResult,
924        Severity,
925    };
926    pub use crate::distributed::{
927        utils as distributed_utils, AsyncConfig, AsyncSGD, CommunicationStats, DistributedBackend,
928        DistributedConfig, DistributedOptimizer, ElasticAveragingSGD, SyncStrategy,
929    };
930    pub use crate::ftrl::{FTRLBuilder, FTRL};
931    pub use crate::fused_kernels::{
932        fused_adadelta_step, fused_adagrad_step, fused_adam_step, fused_rmsprop_step,
933        fused_sgd_step, FusedKernelSupport, FusedStats,
934    };
935    pub use crate::grad_accumulation::{
936        with_gradient_accumulation, AccumulatingOptimizer, GradientAccumulationSupport,
937        GradientAccumulator,
938    };
939    pub use crate::kfac::{KFACBuilder, KFAC};
940    pub use crate::lamb::LAMB;
941    pub use crate::lazy_updates::{
942        LazyUpdateConfig, LazyUpdateDecision, LazyUpdateManager, LazyUpdateOptimizer,
943        LazyUpdateStatistics, LazyUpdateSupport, ParameterImportance, PendingUpdate,
944        UpdatePriority,
945    };
946    pub use crate::lbfgs::LBFGS;
947    pub use crate::lion::{Lion, LionBuilder, LionConfig};
948    pub use crate::lookahead::{lookahead_adam, lookahead_radam, lookahead_sgd, Lookahead};
949    pub use crate::low_precision::{
950        LowPrecisionConvertible, LowPrecisionOptimizer, LowPrecisionState, PrecisionType,
951        StateStatistics,
952    };
953    pub use crate::lr_scheduler::{
954        CosineAnnealingLR, ExponentialLR, LRScheduler, OneCycleLR, ReduceLROnPlateau, StepLR,
955    };
956    pub use crate::lr_scheduler_additional::{
957        ConstantLR, CosineAnnealingWarmRestarts, CyclicLR, LinearLR, MultiStepLR, PolynomialLR,
958    };
959    pub use crate::lr_scheduler_enhanced::{
960        utils as lr_enhanced_utils, AdaptiveLRScheduler, AdaptiveSchedulerStats, AdaptiveStrategy,
961        CosineAnnealingWarmRestartsWithWarmup, PolynomialDecayWithWarmup, WarmupStrategy,
962    };
963    pub use crate::memory_efficient::{
964        CircularBuffer, MemoryConfig, MemoryEfficientAdam, MemoryEfficientLBFGS,
965        MemoryEfficientOptimizerBuilder, MemoryPool,
966    };
967    pub use crate::memory_mapped::{
968        MemoryMappedConfig, MemoryMappedFile, MemoryMappedOptimizer, MemoryMappedStateStorage,
969        MemoryMappedSupport, StorageStatistics,
970    };
971    pub use crate::mixed_precision::{
972        with_mixed_precision, MixedPrecisionConfig, MixedPrecisionOptimizer,
973    };
974    pub use crate::nadam::NAdam;
975    pub use crate::natural_gradient::{NaturalGradient, NaturalGradientBuilder};
976    pub use crate::newton_cg::{NewtonCG, NewtonCGBuilder, NewtonCGConfig};
977    pub use crate::online_learning::{
978        OnlineGradientDescent, ProximalGradient, ProximalOperator, SAGA, SVRG,
979    };
980    pub use crate::prodigy::{Prodigy, ProdigyBuilder, ProdigyConfig};
981    pub use crate::radam::RAdam;
982    pub use crate::ranger::{Ranger, RangerBuilder};
983    pub use crate::rmsprop::RMSprop;
984    pub use crate::rprop::Rprop;
985    pub use crate::schedule_free::{ScheduleFreeAdamW, ScheduleFreeAdamWBuilder};
986    pub use crate::sgd::SGD;
987    pub use crate::shampoo::{Shampoo, ShampooBuilder};
988    pub use crate::sophia::{Sophia, SophiaBuilder, SophiaConfig};
989    pub use crate::sparse_adam::SparseAdam;
990    pub use crate::state_dict_ops::{
991        CompressionMethod, CompressionStats, MemoryEstimate, SerializationFormat, StateDictConfig,
992        StateDictManager,
993    };
994    pub use crate::trust_region::{
995        SubproblemSolver, TrustRegionBuilder, TrustRegionConfig, TrustRegionMethod,
996        TrustRegionStrategy,
997    };
998    pub use crate::yellowfin::{YellowFin, YellowFinBuilder, YellowFinConfig};
999    pub use crate::{Optimizer, OptimizerOptions, OptimizerState, ParamGroup, ParamGroupBuilder};
1000    pub use crate::{OptimizerError, OptimizerResult};
1001}
1002
1003// Re-export commonly used types
1004pub use adam::{Adam, AdamW};
1005pub use distributed::{DistributedBackend, DistributedConfig, DistributedOptimizer, SyncStrategy};
1006pub use rmsprop::RMSprop;
1007pub use sgd::SGD;
1008
1009#[cfg(test)]
1010mod tests {
1011    use super::*;
1012
1013    #[test]
1014    fn test_param_group() {
1015        let params = vec![];
1016        let group = ParamGroup::new(params, 0.01);
1017        assert_eq!(group.lr, 0.01);
1018    }
1019}