Skip to main content

torsh_nn/
init.rs

1//! Parameter initialization functions
2
3// ✅ SciRS2 Policy Compliant - Using scirs2_core::random instead of direct rand
4use scirs2_core::random::quick::random_f32;
5use scirs2_core::slice_random::shuffle;
6use torsh_core::error::{Result, TorshError};
7use torsh_tensor::{creation::*, Tensor};
8
9/// Unified initialization interface
10pub trait Initializer {
11    /// Initialize a tensor with the given shape
12    fn initialize(&self, shape: &[usize]) -> Result<Tensor>;
13}
14
15/// Enumeration of initialization methods
16#[derive(Debug, Clone)]
17pub enum InitMethod {
18    /// Xavier/Glorot uniform initialization
19    XavierUniform { gain: f32 },
20    /// Xavier/Glorot normal initialization
21    XavierNormal { gain: f32 },
22    /// Kaiming/He uniform initialization
23    KaimingUniform {
24        mode: FanMode,
25        nonlinearity: Nonlinearity,
26    },
27    /// Kaiming/He normal initialization
28    KaimingNormal {
29        mode: FanMode,
30        nonlinearity: Nonlinearity,
31    },
32    /// Uniform random initialization
33    Uniform { low: f32, high: f32 },
34    /// Normal random initialization
35    Normal { mean: f32, std: f32 },
36    /// Zero initialization
37    Zeros,
38    /// Ones initialization
39    Ones,
40    /// Constant initialization
41    Constant { value: f32 },
42    /// Orthogonal initialization
43    Orthogonal { gain: f32 },
44    /// Sparse initialization
45    Sparse { sparsity: f32, std: f32 },
46    /// Identity/Eye initialization
47    Eye,
48    /// Lecun uniform initialization
49    LecunUniform,
50    /// Lecun normal initialization
51    LecunNormal,
52    /// Truncated normal initialization
53    TruncatedNormal { mean: f32, std: f32, a: f32, b: f32 },
54    /// Variance scaling initialization (generalization of Xavier/Kaiming)
55    VarianceScaling {
56        scale: f32,
57        mode: FanMode,
58        distribution: Distribution,
59    },
60    /// Dirac initialization for convolutional layers
61    Dirac,
62    /// SIREN initialization for periodic activation functions
63    /// Recommended for networks using sine activations
64    SIREN { c: f32, w0: f32 },
65}
66
67/// Distribution type for variance scaling initialization
68#[derive(Debug, Clone, Copy)]
69pub enum Distribution {
70    /// Uniform distribution
71    Uniform,
72    /// Normal (Gaussian) distribution
73    Normal,
74    /// Truncated normal distribution
75    TruncatedNormal,
76}
77
78/// Fan mode for Kaiming initialization
79#[derive(Debug, Clone, Copy)]
80pub enum FanMode {
81    FanIn,
82    FanOut,
83    FanAvg,
84}
85
86/// Nonlinearity types for calculating gains
87#[derive(Debug, Clone, Copy)]
88pub enum Nonlinearity {
89    ReLU,
90    LeakyReLU { negative_slope: f32 },
91    Tanh,
92    Sigmoid,
93    SELU,
94    ELU,
95    Swish,
96    Linear,
97}
98
99impl Nonlinearity {
100    /// Recommended gain for this nonlinearity.
101    ///
102    /// The values match `torch.nn.init.calculate_gain` where PyTorch defines
103    /// one — note that SELU's gain is `3/4`, *not* `sqrt(3/4)`. `ELU` and
104    /// `Swish` are not covered by PyTorch; the values documented below are the
105    /// ones ToRSh uses.
106    pub fn gain(&self) -> f32 {
107        match self {
108            Nonlinearity::ReLU => (2.0_f32).sqrt(),
109            Nonlinearity::LeakyReLU { negative_slope } => {
110                (2.0 / (1.0 + negative_slope.powi(2))).sqrt()
111            }
112            Nonlinearity::Tanh => (5.0_f32 / 3.0_f32).sqrt(),
113            Nonlinearity::Sigmoid => 1.0,
114            // torch.nn.init.calculate_gain('selu') == 3/4.
115            Nonlinearity::SELU => 3.0_f32 / 4.0_f32,
116            // ELU behaves like the identity for x > 0 and saturates below, so
117            // ToRSh uses the linear gain rather than borrowing Tanh's.
118            Nonlinearity::ELU => 1.0,
119            // Swish/SiLU is close to a (leaky) ReLU in the positive half-plane.
120            Nonlinearity::Swish => (2.0_f32).sqrt(),
121            Nonlinearity::Linear => 1.0,
122        }
123    }
124}
125
126impl InitMethod {
127    /// Create Xavier/Glorot uniform initialization with default gain (1.0)
128    pub fn xavier_uniform() -> Self {
129        InitMethod::XavierUniform { gain: 1.0 }
130    }
131
132    /// Create Xavier/Glorot normal initialization with default gain (1.0)
133    pub fn xavier_normal() -> Self {
134        InitMethod::XavierNormal { gain: 1.0 }
135    }
136
137    /// Create Kaiming/He uniform initialization for ReLU activations
138    pub fn kaiming_uniform() -> Self {
139        InitMethod::KaimingUniform {
140            mode: FanMode::FanIn,
141            nonlinearity: Nonlinearity::ReLU,
142        }
143    }
144
145    /// Create Kaiming/He normal initialization for ReLU activations
146    pub fn kaiming_normal() -> Self {
147        InitMethod::KaimingNormal {
148            mode: FanMode::FanIn,
149            nonlinearity: Nonlinearity::ReLU,
150        }
151    }
152
153    /// Create uniform initialization with specified range
154    pub fn uniform_range(low: f32, high: f32) -> Self {
155        InitMethod::Uniform { low, high }
156    }
157
158    /// Create normal initialization with specified mean and standard deviation
159    pub fn normal_dist(mean: f32, std: f32) -> Self {
160        InitMethod::Normal { mean, std }
161    }
162
163    /// Create zero initialization
164    pub fn zeros() -> Self {
165        InitMethod::Zeros
166    }
167
168    /// Create ones initialization
169    pub fn ones() -> Self {
170        InitMethod::Ones
171    }
172
173    /// Create constant initialization with specified value
174    pub fn constant(value: f32) -> Self {
175        InitMethod::Constant { value }
176    }
177
178    /// Create orthogonal initialization with default gain (1.0)
179    pub fn orthogonal() -> Self {
180        InitMethod::Orthogonal { gain: 1.0 }
181    }
182
183    /// Create LeCun uniform initialization
184    pub fn lecun_uniform() -> Self {
185        InitMethod::LecunUniform
186    }
187
188    /// Create LeCun normal initialization
189    pub fn lecun_normal() -> Self {
190        InitMethod::LecunNormal
191    }
192
193    /// Create Dirac initialization for convolutional layers
194    pub fn dirac() -> Self {
195        InitMethod::Dirac
196    }
197
198    /// Create SIREN initialization for first layer (w0=30.0, c=1.0)
199    pub fn siren_first_layer() -> Self {
200        InitMethod::SIREN { c: 1.0, w0: 30.0 }
201    }
202
203    /// Create SIREN initialization for hidden layers (w0=1.0, c=6.0)
204    pub fn siren_hidden_layer() -> Self {
205        InitMethod::SIREN { c: 6.0, w0: 1.0 }
206    }
207
208    /// Set the gain for applicable initialization methods
209    pub fn with_gain(self, gain: f32) -> Self {
210        match self {
211            InitMethod::XavierUniform { .. } => InitMethod::XavierUniform { gain },
212            InitMethod::XavierNormal { .. } => InitMethod::XavierNormal { gain },
213            InitMethod::Orthogonal { .. } => InitMethod::Orthogonal { gain },
214            other => other,
215        }
216    }
217
218    /// Set the fan mode for applicable initialization methods
219    pub fn with_fan_mode(self, mode: FanMode) -> Self {
220        match self {
221            InitMethod::KaimingUniform {
222                nonlinearity,
223                mode: _,
224            } => InitMethod::KaimingUniform { mode, nonlinearity },
225            InitMethod::KaimingNormal {
226                nonlinearity,
227                mode: _,
228            } => InitMethod::KaimingNormal { mode, nonlinearity },
229            InitMethod::VarianceScaling {
230                scale,
231                distribution,
232                mode: _,
233            } => InitMethod::VarianceScaling {
234                scale,
235                mode,
236                distribution,
237            },
238            other => other,
239        }
240    }
241
242    /// Set the nonlinearity for applicable initialization methods
243    pub fn with_nonlinearity(self, nonlinearity: Nonlinearity) -> Self {
244        match self {
245            InitMethod::KaimingUniform { mode, .. } => {
246                InitMethod::KaimingUniform { mode, nonlinearity }
247            }
248            InitMethod::KaimingNormal { mode, .. } => {
249                InitMethod::KaimingNormal { mode, nonlinearity }
250            }
251            other => other,
252        }
253    }
254
255    /// Get a human-readable name for this initialization method
256    pub fn name(&self) -> &str {
257        match self {
258            InitMethod::XavierUniform { .. } => "Xavier Uniform",
259            InitMethod::XavierNormal { .. } => "Xavier Normal",
260            InitMethod::KaimingUniform { .. } => "Kaiming Uniform",
261            InitMethod::KaimingNormal { .. } => "Kaiming Normal",
262            InitMethod::Uniform { .. } => "Uniform",
263            InitMethod::Normal { .. } => "Normal",
264            InitMethod::Zeros => "Zeros",
265            InitMethod::Ones => "Ones",
266            InitMethod::Constant { .. } => "Constant",
267            InitMethod::Orthogonal { .. } => "Orthogonal",
268            InitMethod::Sparse { .. } => "Sparse",
269            InitMethod::Eye => "Eye/Identity",
270            InitMethod::LecunUniform => "LeCun Uniform",
271            InitMethod::LecunNormal => "LeCun Normal",
272            InitMethod::TruncatedNormal { .. } => "Truncated Normal",
273            InitMethod::VarianceScaling { .. } => "Variance Scaling",
274            InitMethod::Dirac => "Dirac",
275            InitMethod::SIREN { .. } => "SIREN",
276        }
277    }
278}
279
280impl Initializer for InitMethod {
281    fn initialize(&self, shape: &[usize]) -> Result<Tensor> {
282        match self {
283            InitMethod::XavierUniform { gain } => xavier_uniform_with_gain(shape, *gain),
284            InitMethod::XavierNormal { gain } => xavier_normal_with_gain(shape, *gain),
285            InitMethod::KaimingUniform { mode, nonlinearity } => {
286                kaiming_uniform_with_nonlinearity(shape, *mode, *nonlinearity)
287            }
288            InitMethod::KaimingNormal { mode, nonlinearity } => {
289                kaiming_normal_with_nonlinearity(shape, *mode, *nonlinearity)
290            }
291            InitMethod::Uniform { low, high } => uniform(shape, *low, *high),
292            InitMethod::Normal { mean, std } => normal(shape, *mean, *std),
293            InitMethod::Zeros => zeros(shape),
294            InitMethod::Ones => ones(shape),
295            InitMethod::Constant { value } => constant(shape, *value),
296            InitMethod::Orthogonal { gain } => orthogonal_init(shape, *gain),
297            InitMethod::Sparse { sparsity, std } => sparse_init(shape, *sparsity, *std),
298            InitMethod::Eye => eye_init_tensor(shape),
299            InitMethod::LecunUniform => lecun_uniform(shape),
300            InitMethod::LecunNormal => lecun_normal(shape),
301            InitMethod::TruncatedNormal { mean, std, a, b } => {
302                truncated_normal(shape, *mean, *std, *a, *b)
303            }
304            InitMethod::VarianceScaling {
305                scale,
306                mode,
307                distribution,
308            } => variance_scaling(shape, *scale, *mode, *distribution),
309            InitMethod::Dirac => dirac_init(shape),
310            InitMethod::SIREN { c, w0 } => siren_init(shape, *c, *w0),
311        }
312    }
313}
314
315/// Create a constant tensor filled with a specific value
316pub fn constant(shape: &[usize], value: f32) -> Result<Tensor> {
317    let size = shape.iter().product();
318    let values = vec![value; size];
319    Tensor::from_vec(values, shape)
320        .map_err(|e| TorshError::RuntimeError(format!("Failed to create constant tensor: {}", e)))
321}
322
323/// Helper function to create an initializer
324pub fn init(method: InitMethod) -> impl Initializer {
325    method
326}
327
328/// Calculate fan-in and fan-out for a tensor shape
329pub fn calculate_fan_in_fan_out(shape: &[usize]) -> Result<(usize, usize)> {
330    let dimensions = shape.len();
331
332    if dimensions < 2 {
333        return Err(TorshError::InvalidArgument(
334            "Fan in and fan out can not be computed for tensor with fewer than 2 dimensions"
335                .to_string(),
336        ));
337    }
338
339    let num_input_fmaps = shape[1];
340    let num_output_fmaps = shape[0];
341
342    let mut receptive_field_size = 1;
343    if dimensions > 2 {
344        for &size in shape.iter().skip(2).take(dimensions - 2) {
345            receptive_field_size *= size;
346        }
347    }
348
349    let fan_in = num_input_fmaps * receptive_field_size;
350    let fan_out = num_output_fmaps * receptive_field_size;
351
352    Ok((fan_in, fan_out))
353}
354
355/// Calculate the appropriate fan value based on mode
356pub fn calculate_fan(shape: &[usize], mode: FanMode) -> Result<usize> {
357    let (fan_in, fan_out) = calculate_fan_in_fan_out(shape)?;
358
359    match mode {
360        FanMode::FanIn => Ok(fan_in),
361        FanMode::FanOut => Ok(fan_out),
362        FanMode::FanAvg => Ok((fan_in + fan_out) / 2),
363    }
364}
365
366/// Xavier/Glorot uniform initialization
367pub fn xavier_uniform(shape: &[usize]) -> Result<Tensor> {
368    xavier_uniform_with_gain(shape, 1.0)
369}
370
371/// Xavier/Glorot uniform initialization with custom gain
372pub fn xavier_uniform_with_gain(shape: &[usize], gain: f32) -> Result<Tensor> {
373    let (fan_in, fan_out) = calculate_fan_in_fan_out(shape)?;
374    let std = gain * (2.0 / (fan_in + fan_out) as f32).sqrt();
375    let bound = std * 3.0_f32.sqrt();
376
377    uniform(shape, -bound, bound)
378}
379
380/// Xavier/Glorot normal initialization
381pub fn xavier_normal(shape: &[usize]) -> Result<Tensor> {
382    xavier_normal_with_gain(shape, 1.0)
383}
384
385/// Xavier/Glorot normal initialization with custom gain
386pub fn xavier_normal_with_gain(shape: &[usize], gain: f32) -> Result<Tensor> {
387    let (fan_in, fan_out) = calculate_fan_in_fan_out(shape)?;
388    let std = gain * (2.0 / (fan_in + fan_out) as f32).sqrt();
389
390    normal(shape, 0.0, std)
391}
392
393/// Kaiming/He uniform initialization
394pub fn kaiming_uniform(shape: &[usize], mode: &str) -> Result<Tensor> {
395    let fan_mode = match mode {
396        "fan_in" => FanMode::FanIn,
397        "fan_out" => FanMode::FanOut,
398        "fan_avg" => FanMode::FanAvg,
399        _ => {
400            return Err(TorshError::InvalidArgument(format!(
401                "Mode {} not supported, please use one of 'fan_in', 'fan_out', or 'fan_avg'.",
402                mode
403            )))
404        }
405    };
406
407    kaiming_uniform_with_nonlinearity(shape, fan_mode, Nonlinearity::ReLU)
408}
409
410/// Kaiming/He uniform initialization with nonlinearity specification
411pub fn kaiming_uniform_with_nonlinearity(
412    shape: &[usize],
413    mode: FanMode,
414    nonlinearity: Nonlinearity,
415) -> Result<Tensor> {
416    let fan = calculate_fan(shape, mode)?;
417    let gain = nonlinearity.gain();
418    let std = gain / (fan as f32).sqrt();
419    let bound = std * 3.0_f32.sqrt();
420
421    uniform(shape, -bound, bound)
422}
423
424/// Kaiming/He normal initialization
425pub fn kaiming_normal(shape: &[usize], mode: &str) -> Result<Tensor> {
426    let fan_mode = match mode {
427        "fan_in" => FanMode::FanIn,
428        "fan_out" => FanMode::FanOut,
429        "fan_avg" => FanMode::FanAvg,
430        _ => {
431            return Err(TorshError::InvalidArgument(format!(
432                "Mode {} not supported, please use one of 'fan_in', 'fan_out', or 'fan_avg'.",
433                mode
434            )))
435        }
436    };
437
438    kaiming_normal_with_nonlinearity(shape, fan_mode, Nonlinearity::ReLU)
439}
440
441/// Kaiming/He normal initialization with nonlinearity specification
442pub fn kaiming_normal_with_nonlinearity(
443    shape: &[usize],
444    mode: FanMode,
445    nonlinearity: Nonlinearity,
446) -> Result<Tensor> {
447    let fan = calculate_fan(shape, mode)?;
448    let gain = nonlinearity.gain();
449    let std = gain / (fan as f32).sqrt();
450
451    normal(shape, 0.0, std)
452}
453
454/// Uniform initialization
455pub fn uniform(shape: &[usize], low: f32, high: f32) -> Result<Tensor> {
456    if low >= high {
457        return Err(TorshError::InvalidArgument(
458            "Low bound must be less than high bound for uniform initialization".to_string(),
459        ));
460    }
461
462    let size = shape.iter().product();
463    let range = high - low;
464    let values: Vec<f32> = (0..size).map(|_| low + random_f32() * range).collect();
465
466    Tensor::from_vec(values, shape)
467        .map_err(|e| TorshError::RuntimeError(format!("Failed to create uniform tensor: {}", e)))
468}
469
470/// Box-Muller transform of two uniform draws into one standard-normal sample.
471///
472/// `random_f32` samples the half-open interval `[0, 1)`, so `u1` can be exactly
473/// `0.0`; `0f32.ln()` is `-inf` and would poison the sample (and through it the
474/// whole initialized layer). Clamping `u1` to the smallest positive normal keeps
475/// the transform finite while perturbing the distribution by less than one part
476/// in 2^24.
477fn box_muller(u1: f32, u2: f32) -> f32 {
478    let u1 = u1.max(f32::MIN_POSITIVE);
479    (-2.0 * u1.ln()).sqrt() * (2.0 * std::f32::consts::PI * u2).cos()
480}
481
482/// Draw a single standard-normal sample with the Box-Muller transform.
483fn standard_normal() -> f32 {
484    box_muller(random_f32(), random_f32())
485}
486
487/// Normal initialization
488pub fn normal(shape: &[usize], mean: f32, std: f32) -> Result<Tensor> {
489    if std <= 0.0 {
490        return Err(TorshError::InvalidArgument(
491            "Standard deviation must be positive for normal initialization".to_string(),
492        ));
493    }
494
495    let size = shape.iter().product();
496    let values: Vec<f32> = (0..size).map(|_| mean + standard_normal() * std).collect();
497
498    Tensor::from_vec(values, shape)
499        .map_err(|e| TorshError::RuntimeError(format!("Failed to create normal tensor: {}", e)))
500}
501
502/// Lecun uniform initialization
503pub fn lecun_uniform(shape: &[usize]) -> Result<Tensor> {
504    let fan_in = calculate_fan(shape, FanMode::FanIn)?;
505    let limit = (3.0 / fan_in as f32).sqrt();
506    uniform(shape, -limit, limit)
507}
508
509/// Lecun normal initialization
510pub fn lecun_normal(shape: &[usize]) -> Result<Tensor> {
511    let fan_in = calculate_fan(shape, FanMode::FanIn)?;
512    let std = (1.0 / fan_in as f32).sqrt();
513    normal(shape, 0.0, std)
514}
515
516/// Truncated normal initialization
517pub fn truncated_normal(shape: &[usize], mean: f32, std: f32, a: f32, b: f32) -> Result<Tensor> {
518    if std <= 0.0 {
519        return Err(TorshError::InvalidArgument(
520            "Standard deviation must be positive for truncated normal initialization".to_string(),
521        ));
522    }
523
524    if a >= b {
525        return Err(TorshError::InvalidArgument(
526            "Lower bound must be less than upper bound for truncated normal initialization"
527                .to_string(),
528        ));
529    }
530
531    let size = shape.iter().product();
532    let mut values = Vec::with_capacity(size);
533
534    for _ in 0..size {
535        loop {
536            let sample = mean + standard_normal() * std;
537            if sample >= a && sample <= b {
538                values.push(sample);
539                break;
540            }
541        }
542    }
543
544    Tensor::from_vec(values, shape).map_err(|e| {
545        TorshError::RuntimeError(format!("Failed to create truncated normal tensor: {}", e))
546    })
547}
548
549/// Eye/Identity initialization for square matrices
550pub fn eye_init(n: usize) -> Result<Tensor> {
551    eye(n).map_err(|e| TorshError::RuntimeError(format!("Failed to create eye tensor: {}", e)))
552}
553
554/// Eye/Identity initialization for arbitrary tensor shapes
555pub fn eye_init_tensor(shape: &[usize]) -> Result<Tensor> {
556    if shape.len() < 2 {
557        return Err(TorshError::InvalidArgument(
558            "Eye initialization requires at least 2D tensor".to_string(),
559        ));
560    }
561
562    let rows = shape[0];
563    let cols = shape[1];
564
565    if rows != cols {
566        return Err(TorshError::InvalidArgument(
567            "Eye initialization requires square matrices (rows == cols)".to_string(),
568        ));
569    }
570
571    eye_init(rows)
572}
573
574/// Orthogonal initialization using QR decomposition
575///
576/// Generates an orthogonal matrix (or semi-orthogonal for non-square matrices)
577/// using QR decomposition of a random Gaussian matrix. This initialization
578/// helps preserve gradient norms during backpropagation, improving training stability.
579///
580/// Tensors of rank > 2 are handled the way `torch.nn.init.orthogonal_` does:
581/// the trailing axes are flattened, so a conv weight `[O, I, kh, kw]` is
582/// orthogonalized as an `[O, I*kh*kw]` matrix and then reshaped back.
583///
584/// The result satisfies `Q Q^T = I` when `rows <= cols` and `Q^T Q = I`
585/// otherwise, scaled by `gain`.
586///
587/// # Arguments
588/// * `shape` - Shape of the tensor (must be at least 2D)
589/// * `gain` - Scaling factor applied to the orthogonal matrix
590///
591/// # Returns
592/// An orthogonal (or semi-orthogonal) tensor scaled by `gain`
593pub fn orthogonal_init(shape: &[usize], gain: f32) -> Result<Tensor> {
594    if shape.len() < 2 {
595        return Err(TorshError::InvalidArgument(
596            "Orthogonal initialization requires at least 2D tensor".to_string(),
597        ));
598    }
599
600    let num_rows = shape[0];
601    let num_cols: usize = shape[1..].iter().product();
602
603    if num_rows == 0 || num_cols == 0 {
604        return Err(TorshError::InvalidArgument(format!(
605            "Orthogonal initialization requires a non-empty shape, got {shape:?}"
606        )));
607    }
608
609    // QR needs a tall matrix, so the flattened weight is orthogonalized in its
610    // tall orientation and transposed afterwards when it is actually wide.
611    let transposed = num_rows < num_cols;
612    let (qr_rows, qr_cols) = if transposed {
613        (num_cols, num_rows)
614    } else {
615        (num_rows, num_cols)
616    };
617
618    let random_tensor = normal(&[qr_rows, qr_cols], 0.0, 1.0)?;
619    let (q, r) = torsh_linalg::decomposition::qr(&random_tensor)?;
620
621    // Householder QR is only unique up to the signs of the diagonal of R.
622    // Multiplying column j of Q by sign(R[j][j]) makes the draw uniform over
623    // the (semi-)orthogonal matrices, matching `torch.nn.init.orthogonal_`.
624    let mut signs = Vec::with_capacity(qr_cols);
625    for j in 0..qr_cols {
626        let diagonal = r.get(&[j, j])?;
627        signs.push(if diagonal < 0.0 { -1.0f32 } else { 1.0f32 });
628    }
629
630    // `values[row * num_cols + col]` is the (row, col) entry of the result.
631    let mut values = vec![0.0f32; num_rows * num_cols];
632    for row in 0..num_rows {
633        for col in 0..num_cols {
634            // When transposed, entry (row, col) of the result is entry
635            // (col, row) of Q.
636            let (q_row, q_col) = if transposed { (col, row) } else { (row, col) };
637            values[row * num_cols + col] = q.get(&[q_row, q_col])? * signs[q_col] * gain;
638        }
639    }
640
641    Tensor::from_vec(values, shape)
642        .map_err(|e| TorshError::RuntimeError(format!("Failed to create orthogonal tensor: {e}")))
643}
644
645/// Sparse initialization
646pub fn sparse_init(shape: &[usize], sparsity: f32, std: f32) -> Result<Tensor> {
647    if shape.len() != 2 {
648        return Err(TorshError::InvalidArgument(
649            "Only tensors with 2 dimensions are supported for sparse initialization".to_string(),
650        ));
651    }
652
653    if !(0.0..=1.0).contains(&sparsity) {
654        return Err(TorshError::InvalidArgument(
655            "Sparsity must be between 0.0 and 1.0".to_string(),
656        ));
657    }
658
659    let rows = shape[0];
660    let cols = shape[1];
661    let total_elements = rows * cols;
662    let num_zeros = (total_elements as f32 * sparsity) as usize;
663
664    // Start with normal initialization
665    let mut values = Vec::with_capacity(total_elements);
666
667    for _ in 0..total_elements {
668        values.push(standard_normal() * std); // mean = 0.0
669    }
670
671    // Randomly zero out elements
672    // ✅ SciRS2 Policy Compliant - Using scirs2_core random shuffling
673    let mut indices: Vec<usize> = (0..total_elements).collect();
674    // Use scirs2_core's shuffle functionality
675    shuffle(&mut indices);
676
677    for &idx in indices.iter().take(num_zeros) {
678        values[idx] = 0.0;
679    }
680
681    Tensor::from_vec(values, shape)
682        .map_err(|e| TorshError::RuntimeError(format!("Failed to create sparse tensor: {}", e)))
683}
684
685/// Initialize a tensor with a specific initialization method
686pub fn init_tensor(
687    tensor: &mut Tensor,
688    method: &str,
689    gain: Option<f32>,
690    mode: Option<&str>,
691) -> Result<()> {
692    let binding = tensor.shape();
693    let shape = binding.dims();
694    let gain = gain.unwrap_or(1.0);
695    let mode = mode.unwrap_or("fan_in");
696
697    let initialized = match method {
698        "xavier_uniform" | "glorot_uniform" => xavier_uniform_with_gain(shape, gain),
699        "xavier_normal" | "glorot_normal" => xavier_normal_with_gain(shape, gain),
700        "kaiming_uniform" | "he_uniform" => kaiming_uniform(shape, mode),
701        "kaiming_normal" | "he_normal" => kaiming_normal(shape, mode),
702        "orthogonal" => orthogonal_init(shape, gain),
703        "lecun_uniform" => lecun_uniform(shape),
704        "lecun_normal" => lecun_normal(shape),
705        "zeros" => zeros(shape),
706        "ones" => ones(shape),
707        "eye" => eye_init_tensor(shape),
708        _ => {
709            return Err(TorshError::InvalidArgument(format!(
710                "Unknown initialization method: {}",
711                method
712            )))
713        }
714    }?;
715
716    *tensor = initialized;
717    Ok(())
718}
719
720/// Reset parameters of a module using default initialization
721pub trait Initializable {
722    fn reset_parameters(&mut self);
723}
724
725/// Variance scaling initialization
726///
727/// A general initialization method that covers Xavier and Kaiming as special cases.
728/// The variance of the initialized weights is controlled by the scale and fan mode.
729///
730/// # Arguments
731/// * `shape` - Shape of the tensor
732/// * `scale` - Scaling factor for the variance
733/// * `mode` - Which fan to use (fan_in, fan_out, or fan_avg)
734/// * `distribution` - Distribution type (uniform, normal, or truncated_normal)
735///
736/// # Examples
737/// - Xavier uniform: variance_scaling(shape, 1.0, FanMode::FanAvg, Distribution::Uniform)
738/// - Kaiming normal: variance_scaling(shape, 2.0, FanMode::FanIn, Distribution::Normal)
739pub fn variance_scaling(
740    shape: &[usize],
741    scale: f32,
742    mode: FanMode,
743    distribution: Distribution,
744) -> Result<Tensor> {
745    let fan = calculate_fan(shape, mode)?;
746    let variance = scale / fan as f32;
747
748    match distribution {
749        Distribution::Uniform => {
750            let limit = (3.0 * variance).sqrt();
751            uniform(shape, -limit, limit)
752        }
753        Distribution::Normal => {
754            let std = variance.sqrt();
755            normal(shape, 0.0, std)
756        }
757        Distribution::TruncatedNormal => {
758            let std = variance.sqrt();
759            // Truncate at 2 standard deviations
760            truncated_normal(shape, 0.0, std, -2.0 * std, 2.0 * std)
761        }
762    }
763}
764
765/// Dirac initialization for convolutional layers
766///
767/// Initializes convolutional kernels with the Dirac delta function, creating
768/// identity-like convolutions that preserve input features. Particularly useful
769/// for residual connections in very deep networks.
770///
771/// For 3D kernels (out_channels, in_channels, kernel_size), this creates
772/// identity mappings where possible, with zeros elsewhere.
773///
774/// # Arguments
775/// * `shape` - Shape of the convolutional kernel (must be at least 3D)
776///
777/// # Note
778/// - For kernels where in_channels != out_channels, only min(in_channels, out_channels) are initialized as identity
779/// - The center position of the kernel contains the identity mapping
780pub fn dirac_init(shape: &[usize]) -> Result<Tensor> {
781    if shape.len() < 3 {
782        return Err(TorshError::InvalidArgument(
783            "Dirac initialization requires at least 3D tensor (out_channels, in_channels, kernel_size)".to_string(),
784        ));
785    }
786
787    let out_channels = shape[0];
788    let in_channels = shape[1];
789
790    // Calculate total size and kernel size
791    let total_size: usize = shape.iter().product();
792    let kernel_spatial_size: usize = shape[2..].iter().product();
793
794    // Start with zeros
795    let mut values = vec![0.0_f32; total_size];
796
797    // Find center position in spatial dimensions
798    let mut center_offset = 0;
799    let mut stride = 1;
800    for &dim_size in shape[2..].iter().rev() {
801        center_offset += (dim_size / 2) * stride;
802        stride *= dim_size;
803    }
804
805    // Set diagonal elements to 1.0 at the center of the kernel
806    let min_channels = out_channels.min(in_channels);
807    for i in 0..min_channels {
808        let idx = i * in_channels * kernel_spatial_size + i * kernel_spatial_size + center_offset;
809        if idx < total_size {
810            values[idx] = 1.0;
811        }
812    }
813
814    Tensor::from_vec(values, shape)
815        .map_err(|e| TorshError::RuntimeError(format!("Failed to create Dirac tensor: {}", e)))
816}
817
818/// SIREN initialization
819///
820/// Initialization method designed for networks using sine activations (SIREN: Sinusoidal Representation Networks).
821/// This initialization ensures that activations are properly distributed for sine activation functions.
822///
823/// # Arguments
824/// * `shape` - Shape of the tensor
825/// * `c` - Constant factor (typically 6.0 for hidden layers)
826/// * `w0` - Frequency parameter for the first layer (typically 30.0)
827///
828/// # Recommendations
829/// - First layer: Use c=1.0, w0=30.0, then multiply weights by w0
830/// - Hidden layers: Use c=6.0, w0=1.0
831///
832/// # Reference
833/// Sitzmann et al., "Implicit Neural Representations with Periodic Activation Functions", NeurIPS 2020
834pub fn siren_init(shape: &[usize], c: f32, w0: f32) -> Result<Tensor> {
835    if shape.len() < 2 {
836        return Err(TorshError::InvalidArgument(
837            "SIREN initialization requires at least 2D tensor".to_string(),
838        ));
839    }
840
841    let fan_in = calculate_fan(shape, FanMode::FanIn)?;
842
843    // For the first layer, use uniform distribution in [-1/n, 1/n]
844    // For hidden layers, use uniform distribution in [-sqrt(c/n)/w0, sqrt(c/n)/w0]
845    let bound = if (w0 - 1.0).abs() < 1e-6 {
846        // Hidden layer
847        (c / fan_in as f32).sqrt()
848    } else {
849        // First layer: sample from [-1/n, 1/n] then multiply by w0
850        1.0 / fan_in as f32
851    };
852
853    let mut tensor = uniform(shape, -bound, bound)?;
854
855    // For first layer, multiply by w0
856    if (w0 - 1.0).abs() > 1e-6 {
857        let values: Vec<f32> = tensor.to_vec()?.iter().map(|&v| v * w0).collect();
858        tensor = Tensor::from_vec(values, shape)?;
859    }
860
861    Ok(tensor)
862}
863
864// =============================================================================
865// MODERN INITIALIZATION TECHNIQUES
866// =============================================================================
867
868/// Fixup initialization for very deep networks without normalization
869///
870/// Fixup initialization is designed for training very deep residual networks (100+ layers)
871/// without batch normalization. It scales weights based on network depth to prevent
872/// gradient explosion/vanishing.
873///
874/// # Arguments
875/// * `shape` - Shape of the tensor
876/// * `num_layers` - Total number of layers in the network
877/// * `num_residual_blocks` - Number of residual blocks in the network
878///
879/// # Formula
880/// For residual branch weights: scale by (2 * num_layers)^(-1/(2*num_residual_blocks-2))
881/// For other weights: standard initialization
882///
883/// # Reference
884/// Zhang et al., "Fixup Initialization: Residual Learning Without Normalization", ICLR 2019
885pub fn fixup_init(
886    shape: &[usize],
887    num_layers: usize,
888    num_residual_blocks: usize,
889    is_residual_branch: bool,
890) -> Result<Tensor> {
891    // Start with He/Kaiming normal initialization
892    let mut tensor = kaiming_normal_with_nonlinearity(shape, FanMode::FanIn, Nonlinearity::ReLU)?;
893
894    if is_residual_branch && num_residual_blocks > 1 {
895        // Calculate Fixup scaling factor
896        let exponent = -1.0 / (2.0 * num_residual_blocks as f32 - 2.0);
897        let scale = (2.0 * num_layers as f32).powf(exponent);
898
899        // Scale the tensor
900        let values: Vec<f32> = tensor.to_vec()?.iter().map(|&v| v * scale).collect();
901        tensor = Tensor::from_vec(values, shape).map_err(|e| {
902            TorshError::RuntimeError(format!("Failed to create Fixup tensor: {}", e))
903        })?;
904    }
905
906    Ok(tensor)
907}
908
909/// ReZero initialization for ReZero-style residual connections
910///
911/// ReZero initialization sets a learnable scalar parameter (alpha) to zero initially,
912/// allowing the network to start as an identity function and gradually learn representations.
913/// This enables training of very deep networks without normalization.
914///
915/// # Arguments
916/// * `shape` - Shape of the tensor
917///
918/// # Note
919/// This returns a weight tensor initialized normally, meant to be multiplied by a zero-initialized
920/// scalar (alpha) parameter. The alpha parameter should be initialized separately to 0.
921///
922/// # Reference
923/// Bachlechner et al., "ReZero is All You Need: Fast Convergence at Large Depth", UAI 2021
924pub fn rezero_init(shape: &[usize]) -> Result<Tensor> {
925    // Use standard initialization for the weight tensor
926    // The alpha (residual weight) should be initialized to 0 separately
927    kaiming_normal_with_nonlinearity(shape, FanMode::FanIn, Nonlinearity::ReLU)
928}
929
930/// Create zero-initialized scalar for ReZero residual weight
931pub fn rezero_alpha_init() -> Result<Tensor> {
932    Tensor::from_vec(vec![0.0_f32], &[1])
933        .map_err(|e| TorshError::RuntimeError(format!("Failed to create ReZero alpha: {}", e)))
934}
935
936/// Delta-Orthogonal initialization
937///
938/// An improved variant of orthogonal initialization that handles non-square matrices better
939/// and provides better gradient flow properties. Particularly effective for RNNs and deep networks.
940///
941/// # Arguments
942/// * `shape` - Shape of the tensor (must be at least 2D)
943/// * `gain` - Scaling factor applied to the orthogonal matrix
944///
945/// # Reference
946/// Xiao et al., "Dynamical Isometry and a Mean Field Theory of CNNs", ICML 2018
947pub fn delta_orthogonal_init(shape: &[usize], gain: f32) -> Result<Tensor> {
948    if shape.len() < 2 {
949        return Err(TorshError::InvalidArgument(
950            "Delta-Orthogonal initialization requires at least 2D tensor".to_string(),
951        ));
952    }
953
954    // For now, use standard orthogonal initialization
955    // Full delta-orthogonal would require convolution-aware initialization
956    orthogonal_init(shape, gain)
957}
958
959/// Meta-learning inspired initialization (MetaInit)
960///
961/// Initialization method inspired by meta-learning that aims to put parameters
962/// in a region that enables fast adaptation with few gradient steps.
963/// Uses a combination of small magnitude with strategic sparsity.
964///
965/// # Arguments
966/// * `shape` - Shape of the tensor
967/// * `sparsity` - Fraction of weights to set to zero (typically 0.7-0.9)
968/// * `scale` - Magnitude of non-zero weights (typically 0.01-0.1)
969///
970/// # Reference
971/// Inspired by MAML and Reptile meta-learning algorithms
972pub fn metainit(shape: &[usize], sparsity: f32, scale: f32) -> Result<Tensor> {
973    if sparsity < 0.0 || sparsity >= 1.0 {
974        return Err(TorshError::InvalidArgument(format!(
975            "Sparsity must be in [0, 1), got {}",
976            sparsity
977        )));
978    }
979
980    if scale <= 0.0 {
981        return Err(TorshError::InvalidArgument(format!(
982            "Scale must be positive, got {}",
983            scale
984        )));
985    }
986
987    let size = shape.iter().product();
988    let mut values = Vec::with_capacity(size);
989
990    for _ in 0..size {
991        if random_f32() < sparsity {
992            values.push(0.0);
993        } else {
994            // Use small random values for non-zero weights
995            let sign = if random_f32() < 0.5 { -1.0 } else { 1.0 };
996            values.push(sign * scale * random_f32());
997        }
998    }
999
1000    Tensor::from_vec(values, shape)
1001        .map_err(|e| TorshError::RuntimeError(format!("Failed to create MetaInit tensor: {}", e)))
1002}
1003
1004/// Layer-Sequential Unit-Variance (LSUV) initialization helper
1005///
1006/// LSUV is a data-driven initialization method that normalizes layer outputs to unit variance.
1007/// This function provides the initial orthogonal initialization; the normalization step
1008/// requires forward passes with actual data.
1009///
1010/// # Arguments
1011/// * `shape` - Shape of the tensor
1012///
1013/// # Note
1014/// This only provides the first step (orthogonal initialization). The full LSUV algorithm
1015/// requires iterative normalization with forward passes on real data batches.
1016///
1017/// # Reference
1018/// Mishkin & Matas, "All you need is a good init", ICLR 2016
1019pub fn lsuv_init(shape: &[usize]) -> Result<Tensor> {
1020    // Start with orthogonal initialization
1021    // The actual layer-sequential unit-variance normalization requires forward passes
1022    orthogonal_init(shape, 1.0)
1023}
1024
1025/// Zero-centered initialization with controlled variance
1026///
1027/// Initializes weights with zero mean and carefully controlled variance based on
1028/// the layer's position in the network and its fan-in/fan-out.
1029///
1030/// # Arguments
1031/// * `shape` - Shape of the tensor
1032/// * `target_variance` - Target variance for the initialization
1033///
1034/// # Use Case
1035/// Useful for layers that need precise variance control, such as in normalizing flows
1036/// or when specific signal propagation properties are required.
1037pub fn zero_centered_variance_init(shape: &[usize], target_variance: f32) -> Result<Tensor> {
1038    if target_variance <= 0.0 {
1039        return Err(TorshError::InvalidArgument(format!(
1040            "Target variance must be positive, got {}",
1041            target_variance
1042        )));
1043    }
1044
1045    let std = target_variance.sqrt();
1046    normal(shape, 0.0, std)
1047}
1048
1049/// Balanced initialization for GANs
1050///
1051/// Specialized initialization for GAN training that balances generator and discriminator
1052/// learning rates. Uses smaller initial weights to prevent early collapse.
1053///
1054/// # Arguments
1055/// * `shape` - Shape of the tensor
1056/// * `is_generator` - Whether this is for generator (true) or discriminator (false)
1057///
1058/// # Formula
1059/// - Generator: Smaller weights (Xavier with 0.5 gain) for stable learning
1060/// - Discriminator: Standard Xavier with 1.0 gain
1061pub fn gan_balanced_init(shape: &[usize], is_generator: bool) -> Result<Tensor> {
1062    let gain = if is_generator { 0.5 } else { 1.0 };
1063
1064    let fan_in = calculate_fan(shape, FanMode::FanIn)?;
1065    let fan_out = calculate_fan(shape, FanMode::FanOut)?;
1066    let fan_avg = (fan_in + fan_out) / 2;
1067
1068    let std = gain * (2.0 / fan_avg as f32).sqrt();
1069    normal(shape, 0.0, std)
1070}
1071
1072/// Coordinate-based network initialization (for NeRF-style architectures)
1073///
1074/// Specialized initialization for coordinate-based neural networks (like NeRF)
1075/// that map coordinates to properties. Uses geometric priors to initialize weights.
1076///
1077/// # Arguments
1078/// * `shape` - Shape of the tensor
1079/// * `omega_0` - Frequency scaling parameter (typical values: 1.0-30.0)
1080///
1081/// # Reference
1082/// Inspired by NeRF and Instant-NGP positional encoding strategies
1083pub fn coordinate_mlp_init(shape: &[usize], omega_0: f32) -> Result<Tensor> {
1084    if shape.len() < 2 {
1085        return Err(TorshError::InvalidArgument(
1086            "Coordinate MLP initialization requires at least 2D tensor".to_string(),
1087        ));
1088    }
1089
1090    let fan_in = calculate_fan(shape, FanMode::FanIn)?;
1091    let std = 1.0 / (fan_in as f32 * omega_0).sqrt();
1092
1093    normal(shape, 0.0, std)
1094}
1095
1096// =============================================================================
1097// AUTOMATIC INITIALIZATION SELECTION UTILITIES
1098// =============================================================================
1099
1100/// Architecture hint for automatic initialization selection
1101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1102pub enum ArchitectureHint {
1103    /// Standard feedforward network
1104    Feedforward,
1105    /// Convolutional network
1106    Convolutional,
1107    /// Recurrent network (RNN, LSTM, GRU)
1108    Recurrent,
1109    /// Transformer or attention-based architecture
1110    Transformer,
1111    /// Residual network (ResNet-style)
1112    Residual,
1113    /// Very deep network (100+ layers)
1114    VeryDeep,
1115    /// Generative Adversarial Network
1116    GAN,
1117    /// Coordinate-based network (NeRF-style)
1118    CoordinateBased,
1119    /// Network with periodic activations (SIREN)
1120    Periodic,
1121    /// Auto-encoder or VAE
1122    Autoencoder,
1123}
1124
1125/// Activation function hint for automatic initialization
1126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1127pub enum ActivationHint {
1128    /// ReLU or variants (LeakyReLU, PReLU)
1129    ReLU,
1130    /// Tanh activation
1131    Tanh,
1132    /// Sigmoid activation
1133    Sigmoid,
1134    /// SELU activation
1135    SELU,
1136    /// Swish/SiLU activation
1137    Swish,
1138    /// GELU activation
1139    GELU,
1140    /// Sine activation (for SIREN)
1141    Sine,
1142    /// Linear/no activation
1143    Linear,
1144}
1145
1146/// Automatic initialization selection based on architecture and activation
1147///
1148/// Selects an appropriate initialization method based on the network architecture,
1149/// activation function, and layer properties. This provides a convenient way to
1150/// get good default initializations without manually specifying the method.
1151///
1152/// # Arguments
1153/// * `shape` - Shape of the tensor to initialize
1154/// * `arch` - Architecture hint (feedforward, convolutional, recurrent, etc.)
1155/// * `activation` - Activation function hint (ReLU, Tanh, etc.)
1156/// * `layer_depth` - Optional layer depth in the network (for depth-aware initialization)
1157///
1158/// # Returns
1159/// An initialized tensor with an appropriate initialization method
1160///
1161/// # Example
1162/// ```ignore
1163/// use torsh_nn::init::{auto_init, ArchitectureHint, ActivationHint};
1164///
1165/// // Initialize weights for a convolutional layer with ReLU
1166/// let weights = auto_init(&[64, 32, 3, 3], ArchitectureHint::Convolutional, ActivationHint::ReLU, None)?;
1167///
1168/// // Initialize weights for a deep residual network
1169/// let weights = auto_init(&[256, 256], ArchitectureHint::Residual, ActivationHint::ReLU, Some(50))?;
1170/// ```
1171pub fn auto_init(
1172    shape: &[usize],
1173    arch: ArchitectureHint,
1174    activation: ActivationHint,
1175    layer_depth: Option<usize>,
1176) -> Result<Tensor> {
1177    match (arch, activation) {
1178        // SIREN / Periodic activations
1179        (ArchitectureHint::Periodic, ActivationHint::Sine) | (_, ActivationHint::Sine) => {
1180            let is_first_layer = layer_depth.unwrap_or(0) == 0;
1181            if is_first_layer {
1182                siren_init(shape, 1.0, 30.0)
1183            } else {
1184                siren_init(shape, 6.0, 1.0)
1185            }
1186        }
1187
1188        // Coordinate-based networks (NeRF-style)
1189        (ArchitectureHint::CoordinateBased, _) => coordinate_mlp_init(shape, 1.0),
1190
1191        // Very deep networks
1192        (ArchitectureHint::VeryDeep, ActivationHint::ReLU) => {
1193            if let Some(depth) = layer_depth {
1194                // Use Fixup for very deep networks
1195                fixup_init(shape, depth, depth / 2, true)
1196            } else {
1197                kaiming_normal_with_nonlinearity(shape, FanMode::FanIn, Nonlinearity::ReLU)
1198            }
1199        }
1200
1201        // Residual networks
1202        (ArchitectureHint::Residual, _) => {
1203            if layer_depth.is_some() {
1204                // Use ReZero or Fixup for deep residual networks
1205                rezero_init(shape)
1206            } else {
1207                kaiming_normal_with_nonlinearity(shape, FanMode::FanIn, Nonlinearity::ReLU)
1208            }
1209        }
1210
1211        // Recurrent networks
1212        (ArchitectureHint::Recurrent, _) => orthogonal_init(shape, 1.0),
1213
1214        // Transformer/Attention
1215        (ArchitectureHint::Transformer, _) => xavier_uniform(shape),
1216
1217        // GANs
1218        (ArchitectureHint::GAN, _) => {
1219            // Default to generator initialization
1220            gan_balanced_init(shape, true)
1221        }
1222
1223        // Convolutional networks with specific activations
1224        (ArchitectureHint::Convolutional, ActivationHint::ReLU) => {
1225            kaiming_normal_with_nonlinearity(shape, FanMode::FanIn, Nonlinearity::ReLU)
1226        }
1227        (ArchitectureHint::Convolutional, ActivationHint::Tanh) => xavier_normal(shape),
1228        (ArchitectureHint::Convolutional, ActivationHint::Sigmoid) => xavier_normal(shape),
1229        (ArchitectureHint::Convolutional, ActivationHint::SELU) => lecun_normal(shape),
1230        (ArchitectureHint::Convolutional, ActivationHint::Swish)
1231        | (ArchitectureHint::Convolutional, ActivationHint::GELU) => {
1232            kaiming_normal_with_nonlinearity(shape, FanMode::FanIn, Nonlinearity::Swish)
1233        }
1234
1235        // Feedforward networks with specific activations
1236        (ArchitectureHint::Feedforward, ActivationHint::ReLU)
1237        | (ArchitectureHint::Autoencoder, ActivationHint::ReLU) => {
1238            kaiming_uniform_with_nonlinearity(shape, FanMode::FanIn, Nonlinearity::ReLU)
1239        }
1240        (ArchitectureHint::Feedforward, ActivationHint::Tanh)
1241        | (ArchitectureHint::Autoencoder, ActivationHint::Tanh) => xavier_uniform(shape),
1242        (ArchitectureHint::Feedforward, ActivationHint::Sigmoid)
1243        | (ArchitectureHint::Autoencoder, ActivationHint::Sigmoid) => xavier_uniform(shape),
1244        (ArchitectureHint::Feedforward, ActivationHint::SELU)
1245        | (ArchitectureHint::Autoencoder, ActivationHint::SELU) => lecun_uniform(shape),
1246        (ArchitectureHint::Feedforward, ActivationHint::Swish)
1247        | (ArchitectureHint::Feedforward, ActivationHint::GELU)
1248        | (ArchitectureHint::Autoencoder, ActivationHint::Swish)
1249        | (ArchitectureHint::Autoencoder, ActivationHint::GELU) => {
1250            kaiming_uniform_with_nonlinearity(shape, FanMode::FanIn, Nonlinearity::Swish)
1251        }
1252        (ArchitectureHint::Feedforward, ActivationHint::Linear) | (_, ActivationHint::Linear) => {
1253            xavier_uniform(shape)
1254        }
1255
1256        // Catch-all: use Xavier as safe default
1257        _ => xavier_uniform(shape),
1258    }
1259}
1260
1261/// Get recommended initialization method as InitMethod enum
1262///
1263/// Similar to `auto_init` but returns an `InitMethod` enum instead of an initialized tensor.
1264/// Useful when you want to know the recommended method without immediately initializing.
1265///
1266/// # Arguments
1267/// * `arch` - Architecture hint
1268/// * `activation` - Activation function hint
1269/// * `layer_depth` - Optional layer depth in the network
1270///
1271/// # Returns
1272/// The recommended `InitMethod` for the given configuration
1273pub fn recommend_init_method(
1274    arch: ArchitectureHint,
1275    activation: ActivationHint,
1276    layer_depth: Option<usize>,
1277) -> InitMethod {
1278    match (arch, activation) {
1279        // SIREN / Periodic activations
1280        (ArchitectureHint::Periodic, ActivationHint::Sine) | (_, ActivationHint::Sine) => {
1281            let is_first_layer = layer_depth.unwrap_or(0) == 0;
1282            if is_first_layer {
1283                InitMethod::SIREN { c: 1.0, w0: 30.0 }
1284            } else {
1285                InitMethod::SIREN { c: 6.0, w0: 1.0 }
1286            }
1287        }
1288
1289        // Very deep or residual networks with ReLU
1290        (ArchitectureHint::VeryDeep, ActivationHint::ReLU)
1291        | (ArchitectureHint::Residual, ActivationHint::ReLU) => InitMethod::KaimingNormal {
1292            mode: FanMode::FanIn,
1293            nonlinearity: Nonlinearity::ReLU,
1294        },
1295
1296        // Recurrent networks
1297        (ArchitectureHint::Recurrent, _) => InitMethod::Orthogonal { gain: 1.0 },
1298
1299        // Transformer/Attention
1300        (ArchitectureHint::Transformer, _) => InitMethod::XavierUniform { gain: 1.0 },
1301
1302        // Convolutional with ReLU
1303        (ArchitectureHint::Convolutional, ActivationHint::ReLU) => InitMethod::KaimingNormal {
1304            mode: FanMode::FanIn,
1305            nonlinearity: Nonlinearity::ReLU,
1306        },
1307
1308        // SELU networks
1309        (_, ActivationHint::SELU) => InitMethod::LecunNormal,
1310
1311        // Tanh or Sigmoid
1312        (_, ActivationHint::Tanh) | (_, ActivationHint::Sigmoid) => {
1313            InitMethod::XavierUniform { gain: 1.0 }
1314        }
1315
1316        // Default: Xavier uniform
1317        _ => InitMethod::XavierUniform { gain: 1.0 },
1318    }
1319}
1320
1321#[cfg(test)]
1322mod tests {
1323    use super::*;
1324
1325    #[test]
1326    fn test_fan_calculation() {
1327        let (fan_in, fan_out) = calculate_fan_in_fan_out(&[64, 32, 3, 3]).unwrap();
1328        assert_eq!(fan_in, 32 * 3 * 3);
1329        assert_eq!(fan_out, 64 * 3 * 3);
1330    }
1331
1332    /// F304: `random_f32` samples `[0, 1)`, so the first uniform can be exactly
1333    /// zero and the unguarded transform would return `-inf`/`NaN`.
1334    #[test]
1335    fn test_box_muller_survives_a_zero_uniform() {
1336        assert!(box_muller(0.0, 0.5).is_finite());
1337        assert!(box_muller(0.0, 0.0).is_finite());
1338        assert!(box_muller(0.0, 1.0).is_finite());
1339        // The guarded transform still agrees with the plain formula elsewhere.
1340        let expected = (-2.0f32 * 0.25f32.ln()).sqrt() * (2.0 * std::f32::consts::PI * 0.75).cos();
1341        assert!((box_muller(0.25, 0.75) - expected).abs() < 1e-6);
1342    }
1343
1344    #[test]
1345    fn test_xavier_uniform() {
1346        let tensor = xavier_uniform(&[10, 5]).unwrap();
1347        assert_eq!(tensor.shape().dims(), &[10, 5]);
1348    }
1349
1350    #[test]
1351    fn test_init_method_enum() {
1352        let method = InitMethod::XavierUniform { gain: 1.0 };
1353        let tensor = method.initialize(&[5, 3]).unwrap();
1354        assert_eq!(tensor.shape().dims(), &[5, 3]);
1355    }
1356
1357    #[test]
1358    fn test_nonlinearity_gains() {
1359        assert!((Nonlinearity::ReLU.gain() - (2.0_f32).sqrt()).abs() < 1e-6);
1360        assert!((Nonlinearity::Linear.gain() - 1.0).abs() < 1e-6);
1361        assert!(
1362            (Nonlinearity::LeakyReLU {
1363                negative_slope: 0.01
1364            }
1365            .gain()
1366                - (2.0 / (1.0 + 0.01_f32.powi(2))).sqrt())
1367            .abs()
1368                < 1e-6
1369        );
1370    }
1371
1372    #[test]
1373    fn test_sparse_initialization() {
1374        let tensor = sparse_init(&[10, 10], 0.5, 1.0).unwrap();
1375        assert_eq!(tensor.shape().dims(), &[10, 10]);
1376
1377        // Test with invalid sparsity
1378        assert!(sparse_init(&[10, 10], 1.5, 1.0).is_err());
1379        assert!(sparse_init(&[10, 10], -0.1, 1.0).is_err());
1380    }
1381
1382    #[test]
1383    fn test_variance_scaling() {
1384        // Test uniform distribution
1385        let tensor =
1386            variance_scaling(&[10, 5], 2.0, FanMode::FanIn, Distribution::Uniform).unwrap();
1387        assert_eq!(tensor.shape().dims(), &[10, 5]);
1388
1389        // Test normal distribution
1390        let tensor = variance_scaling(&[10, 5], 2.0, FanMode::FanIn, Distribution::Normal).unwrap();
1391        assert_eq!(tensor.shape().dims(), &[10, 5]);
1392
1393        // Test truncated normal distribution
1394        let tensor =
1395            variance_scaling(&[10, 5], 2.0, FanMode::FanIn, Distribution::TruncatedNormal).unwrap();
1396        assert_eq!(tensor.shape().dims(), &[10, 5]);
1397    }
1398
1399    #[test]
1400    fn test_dirac_initialization() {
1401        // Test 3D convolutional kernel
1402        let tensor = dirac_init(&[16, 16, 3]).unwrap();
1403        assert_eq!(tensor.shape().dims(), &[16, 16, 3]);
1404
1405        // Test with invalid dimensions
1406        assert!(dirac_init(&[10, 10]).is_err());
1407    }
1408
1409    #[test]
1410    fn test_siren_initialization() {
1411        // Test first layer
1412        let tensor = siren_init(&[10, 5], 1.0, 30.0).unwrap();
1413        assert_eq!(tensor.shape().dims(), &[10, 5]);
1414
1415        // Test hidden layer
1416        let tensor = siren_init(&[10, 5], 6.0, 1.0).unwrap();
1417        assert_eq!(tensor.shape().dims(), &[10, 5]);
1418
1419        // Test with invalid dimensions
1420        assert!(siren_init(&[10], 6.0, 1.0).is_err());
1421    }
1422
1423    #[test]
1424    fn test_init_method_builders() {
1425        // Test builder pattern
1426        let method = InitMethod::xavier_uniform();
1427        assert_eq!(method.name(), "Xavier Uniform");
1428
1429        let method = InitMethod::kaiming_normal().with_fan_mode(FanMode::FanOut);
1430        assert_eq!(method.name(), "Kaiming Normal");
1431
1432        let method = InitMethod::orthogonal().with_gain(2.0);
1433        assert_eq!(method.name(), "Orthogonal");
1434
1435        let method = InitMethod::siren_first_layer();
1436        assert_eq!(method.name(), "SIREN");
1437
1438        let method = InitMethod::dirac();
1439        assert_eq!(method.name(), "Dirac");
1440    }
1441
1442    #[test]
1443    fn test_init_method_enum_variants() {
1444        // Test new enum variants
1445        let method = InitMethod::VarianceScaling {
1446            scale: 2.0,
1447            mode: FanMode::FanIn,
1448            distribution: Distribution::Normal,
1449        };
1450        let tensor = method.initialize(&[10, 5]).unwrap();
1451        assert_eq!(tensor.shape().dims(), &[10, 5]);
1452
1453        let method = InitMethod::Dirac;
1454        let tensor = method.initialize(&[8, 8, 3]).unwrap();
1455        assert_eq!(tensor.shape().dims(), &[8, 8, 3]);
1456
1457        let method = InitMethod::SIREN { c: 6.0, w0: 1.0 };
1458        let tensor = method.initialize(&[10, 5]).unwrap();
1459        assert_eq!(tensor.shape().dims(), &[10, 5]);
1460    }
1461
1462    #[test]
1463    fn test_fixup_initialization() {
1464        // Test Fixup initialization for residual branch
1465        let tensor = fixup_init(&[10, 10], 50, 10, true).unwrap();
1466        assert_eq!(tensor.shape().dims(), &[10, 10]);
1467
1468        // Test Fixup initialization for non-residual branch
1469        let tensor = fixup_init(&[10, 10], 50, 10, false).unwrap();
1470        assert_eq!(tensor.shape().dims(), &[10, 10]);
1471
1472        // Test with minimal layers
1473        let tensor = fixup_init(&[5, 5], 2, 1, true).unwrap();
1474        assert_eq!(tensor.shape().dims(), &[5, 5]);
1475    }
1476
1477    #[test]
1478    fn test_rezero_initialization() {
1479        // Test ReZero weight initialization
1480        let tensor = rezero_init(&[10, 5]).unwrap();
1481        assert_eq!(tensor.shape().dims(), &[10, 5]);
1482
1483        // Test ReZero alpha initialization
1484        let alpha = rezero_alpha_init().unwrap();
1485        assert_eq!(alpha.shape().dims(), &[1]);
1486        // Alpha should be initialized to 0
1487        let alpha_val: Vec<f32> = alpha
1488            .to_vec()
1489            .expect("tensor to vec conversion should succeed");
1490        assert_eq!(alpha_val[0], 0.0);
1491    }
1492
1493    #[test]
1494    fn test_delta_orthogonal_initialization() {
1495        // Test Delta-Orthogonal initialization
1496        let tensor = delta_orthogonal_init(&[10, 10], 1.0).unwrap();
1497        assert_eq!(tensor.shape().dims(), &[10, 10]);
1498
1499        // Test with gain
1500        let tensor = delta_orthogonal_init(&[8, 8], 2.0).unwrap();
1501        assert_eq!(tensor.shape().dims(), &[8, 8]);
1502
1503        // Test error handling for 1D tensor
1504        assert!(delta_orthogonal_init(&[10], 1.0).is_err());
1505    }
1506
1507    #[test]
1508    fn test_metainit() {
1509        // Test MetaInit with typical parameters
1510        let tensor = metainit(&[10, 10], 0.8, 0.05).unwrap();
1511        assert_eq!(tensor.shape().dims(), &[10, 10]);
1512
1513        // Verify sparsity is roughly correct
1514        let values: Vec<f32> = tensor
1515            .to_vec()
1516            .expect("tensor to vec conversion should succeed");
1517        let zero_count = values.iter().filter(|&&v| v == 0.0).count();
1518        let sparsity_ratio = zero_count as f32 / values.len() as f32;
1519        // Allow generous tolerance in sparsity (60-95% for 80% target)
1520        // due to random nature of initialization
1521        assert!(sparsity_ratio > 0.6 && sparsity_ratio < 0.95);
1522
1523        // Test error handling
1524        assert!(metainit(&[10, 10], 1.5, 0.05).is_err()); // Invalid sparsity
1525        assert!(metainit(&[10, 10], -0.1, 0.05).is_err()); // Negative sparsity
1526        assert!(metainit(&[10, 10], 0.8, -0.05).is_err()); // Negative scale
1527    }
1528
1529    #[test]
1530    fn test_lsuv_initialization() {
1531        // Test LSUV initialization (first step)
1532        let tensor = lsuv_init(&[10, 10]).unwrap();
1533        assert_eq!(tensor.shape().dims(), &[10, 10]);
1534
1535        // Test with different shapes
1536        let tensor = lsuv_init(&[64, 32]).unwrap();
1537        assert_eq!(tensor.shape().dims(), &[64, 32]);
1538    }
1539
1540    #[test]
1541    fn test_zero_centered_variance_init() {
1542        // Test with specific target variance
1543        let tensor = zero_centered_variance_init(&[10, 10], 1.0).unwrap();
1544        assert_eq!(tensor.shape().dims(), &[10, 10]);
1545
1546        let tensor = zero_centered_variance_init(&[20, 20], 0.5).unwrap();
1547        assert_eq!(tensor.shape().dims(), &[20, 20]);
1548
1549        // Test error handling for invalid variance
1550        assert!(zero_centered_variance_init(&[10, 10], 0.0).is_err());
1551        assert!(zero_centered_variance_init(&[10, 10], -1.0).is_err());
1552    }
1553
1554    #[test]
1555    fn test_gan_balanced_initialization() {
1556        // Test generator initialization
1557        let gen_tensor = gan_balanced_init(&[10, 10], true).unwrap();
1558        assert_eq!(gen_tensor.shape().dims(), &[10, 10]);
1559
1560        // Test discriminator initialization
1561        let disc_tensor = gan_balanced_init(&[10, 10], false).unwrap();
1562        assert_eq!(disc_tensor.shape().dims(), &[10, 10]);
1563
1564        // Generator weights should generally be smaller in magnitude than discriminator
1565        // (though this is probabilistic, so we just test shape and successful initialization)
1566    }
1567
1568    #[test]
1569    fn test_coordinate_mlp_initialization() {
1570        // Test coordinate MLP initialization
1571        let tensor = coordinate_mlp_init(&[10, 3], 1.0).unwrap();
1572        assert_eq!(tensor.shape().dims(), &[10, 3]);
1573
1574        // Test with different omega_0
1575        let tensor = coordinate_mlp_init(&[64, 32], 30.0).unwrap();
1576        assert_eq!(tensor.shape().dims(), &[64, 32]);
1577
1578        // Test error handling for 1D tensor
1579        assert!(coordinate_mlp_init(&[10], 1.0).is_err());
1580    }
1581
1582    #[test]
1583    fn test_auto_init() {
1584        // Test auto initialization for feedforward + ReLU
1585        let tensor = auto_init(
1586            &[10, 5],
1587            ArchitectureHint::Feedforward,
1588            ActivationHint::ReLU,
1589            None,
1590        )
1591        .unwrap();
1592        assert_eq!(tensor.shape().dims(), &[10, 5]);
1593
1594        // Test for convolutional + ReLU
1595        let tensor = auto_init(
1596            &[64, 32, 3, 3],
1597            ArchitectureHint::Convolutional,
1598            ActivationHint::ReLU,
1599            None,
1600        )
1601        .unwrap();
1602        assert_eq!(tensor.shape().dims(), &[64, 32, 3, 3]);
1603
1604        // Test for recurrent networks
1605        let tensor = auto_init(
1606            &[128, 256],
1607            ArchitectureHint::Recurrent,
1608            ActivationHint::Tanh,
1609            None,
1610        )
1611        .unwrap();
1612        assert_eq!(tensor.shape().dims(), &[128, 256]);
1613
1614        // Test for transformer
1615        let tensor = auto_init(
1616            &[512, 512],
1617            ArchitectureHint::Transformer,
1618            ActivationHint::GELU,
1619            None,
1620        )
1621        .unwrap();
1622        assert_eq!(tensor.shape().dims(), &[512, 512]);
1623
1624        // Test for SIREN (periodic activations)
1625        let tensor = auto_init(
1626            &[32, 16],
1627            ArchitectureHint::Periodic,
1628            ActivationHint::Sine,
1629            Some(0),
1630        )
1631        .unwrap();
1632        assert_eq!(tensor.shape().dims(), &[32, 16]);
1633
1634        // Test for very deep networks
1635        let tensor = auto_init(
1636            &[256, 256],
1637            ArchitectureHint::VeryDeep,
1638            ActivationHint::ReLU,
1639            Some(100),
1640        )
1641        .unwrap();
1642        assert_eq!(tensor.shape().dims(), &[256, 256]);
1643
1644        // Test for GAN
1645        let tensor = auto_init(
1646            &[100, 784],
1647            ArchitectureHint::GAN,
1648            ActivationHint::ReLU,
1649            None,
1650        )
1651        .unwrap();
1652        assert_eq!(tensor.shape().dims(), &[100, 784]);
1653
1654        // Test for coordinate-based networks
1655        let tensor = auto_init(
1656            &[64, 3],
1657            ArchitectureHint::CoordinateBased,
1658            ActivationHint::ReLU,
1659            None,
1660        )
1661        .unwrap();
1662        assert_eq!(tensor.shape().dims(), &[64, 3]);
1663    }
1664
1665    #[test]
1666    fn test_recommend_init_method() {
1667        // Test recommendation for feedforward + ReLU
1668        let method =
1669            recommend_init_method(ArchitectureHint::Feedforward, ActivationHint::ReLU, None);
1670        matches!(method, InitMethod::KaimingNormal { .. });
1671
1672        // Test recommendation for transformer
1673        let method =
1674            recommend_init_method(ArchitectureHint::Transformer, ActivationHint::GELU, None);
1675        matches!(method, InitMethod::XavierUniform { .. });
1676
1677        // Test recommendation for recurrent
1678        let method = recommend_init_method(ArchitectureHint::Recurrent, ActivationHint::Tanh, None);
1679        matches!(method, InitMethod::Orthogonal { .. });
1680
1681        // Test recommendation for SIREN
1682        let method =
1683            recommend_init_method(ArchitectureHint::Periodic, ActivationHint::Sine, Some(0));
1684        matches!(method, InitMethod::SIREN { .. });
1685
1686        // Test recommendation for SELU
1687        let method =
1688            recommend_init_method(ArchitectureHint::Feedforward, ActivationHint::SELU, None);
1689        matches!(method, InitMethod::LecunNormal);
1690    }
1691
1692    #[test]
1693    fn test_architecture_hints() {
1694        // Test that all architecture hints are distinct
1695        assert_ne!(
1696            ArchitectureHint::Feedforward,
1697            ArchitectureHint::Convolutional
1698        );
1699        assert_ne!(ArchitectureHint::Recurrent, ArchitectureHint::Transformer);
1700        assert_ne!(ArchitectureHint::Residual, ArchitectureHint::VeryDeep);
1701        assert_ne!(ArchitectureHint::GAN, ArchitectureHint::CoordinateBased);
1702        assert_ne!(ArchitectureHint::Periodic, ArchitectureHint::Autoencoder);
1703    }
1704
1705    #[test]
1706    fn test_activation_hints() {
1707        // Test that all activation hints are distinct
1708        assert_ne!(ActivationHint::ReLU, ActivationHint::Tanh);
1709        assert_ne!(ActivationHint::Sigmoid, ActivationHint::SELU);
1710        assert_ne!(ActivationHint::Swish, ActivationHint::GELU);
1711        assert_ne!(ActivationHint::Sine, ActivationHint::Linear);
1712    }
1713}