Skip to main content

torsh_tensor/
creation.rs

1//! Tensor creation functions
2
3use crate::{FloatElement, Tensor, TensorElement};
4// ✅ SciRS2 Policy Compliant - Using scirs2_core::random instead of direct rand
5use scirs2_core::random::{Random, StdRng as SeededRng};
6use scirs2_core::RngExt;
7use std::cell::RefCell;
8use std::sync::atomic::{AtomicU64, Ordering};
9use torsh_core::{
10    device::DeviceType,
11    dtype::{Complex32, Complex64, ComplexElement},
12    error::{Result, TorshError},
13};
14
15// ============================================================================
16// Process-global random number generation (PyTorch-compatible semantics)
17// ============================================================================
18//
19// Every random constructor draws from a *thread-local* generator that is seeded
20// from OS entropy the first time it is used, so two calls never return the same
21// data by accident. [`manual_seed`] installs a process-wide seed (mirroring
22// `torch.manual_seed`) and bumps a generation counter; each thread notices the
23// new generation on its next draw and re-seeds its generator deterministically
24// from `(manual seed, thread stream index)`. That keeps single-threaded programs
25// bit-reproducible after `manual_seed` while never handing two threads the same
26// stream.
27
28/// Generation counter, bumped by [`manual_seed`]. `0` means "no manual seed yet".
29static SEED_GENERATION: AtomicU64 = AtomicU64::new(0);
30
31/// Seed installed by the most recent [`manual_seed`] call.
32static MANUAL_SEED: AtomicU64 = AtomicU64::new(0);
33
34/// Hands out a distinct stream index to every thread that draws random numbers.
35static NEXT_STREAM: AtomicU64 = AtomicU64::new(0);
36
37/// Per-thread generator state.
38struct ThreadRngState {
39    /// Seed generation this RNG was created for.
40    generation: u64,
41    /// Stable per-thread stream index (survives re-seeding).
42    stream: u64,
43    /// The generator itself.
44    rng: SeededRng,
45}
46
47thread_local! {
48    static THREAD_RNG: RefCell<Option<ThreadRngState>> = const { RefCell::new(None) };
49}
50
51/// SplitMix64 finaliser — mixes a counter/seed pair into a well-distributed seed.
52fn splitmix64(value: u64) -> u64 {
53    let mut z = value.wrapping_add(0x9E37_79B9_7F4A_7C15);
54    z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
55    z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
56    z ^ (z >> 31)
57}
58
59/// Sets the seed of the ToRSh random number generator for the whole process.
60///
61/// This mirrors `torch.manual_seed`: after calling it, the sequence of values
62/// produced by [`rand`], [`randn`], [`randint`] and the complex variants is
63/// reproducible for a single-threaded program. Threads that draw random numbers
64/// get distinct (but deterministically derived) streams, so results are
65/// reproducible per thread rather than dependent on scheduling.
66///
67/// Without a call to `manual_seed`, every thread seeds itself from OS entropy,
68/// so each process — and each tensor — gets genuinely different random data.
69///
70/// # Examples
71///
72/// ```
73/// use torsh_tensor::creation::{manual_seed, randn};
74///
75/// manual_seed(42);
76/// let a = randn::<f32>(&[4]).expect("operation should succeed");
77/// manual_seed(42);
78/// let b = randn::<f32>(&[4]).expect("operation should succeed");
79/// assert_eq!(a.to_vec().expect("to_vec"), b.to_vec().expect("to_vec"));
80/// ```
81pub fn manual_seed(seed: u64) {
82    MANUAL_SEED.store(seed, Ordering::SeqCst);
83    // Bump last so that a thread observing the new generation also observes the
84    // new seed value.
85    SEED_GENERATION.fetch_add(1, Ordering::SeqCst);
86}
87
88/// Run `f` with exclusive access to the calling thread's generator.
89fn with_rng<R>(f: impl FnOnce(&mut SeededRng) -> R) -> R {
90    THREAD_RNG.with(|cell| {
91        let mut slot = cell.borrow_mut();
92        let generation = SEED_GENERATION.load(Ordering::SeqCst);
93        let stale = slot
94            .as_ref()
95            .map(|state| state.generation != generation)
96            .unwrap_or(true);
97
98        if stale {
99            let stream = match slot.as_ref() {
100                Some(state) => state.stream,
101                None => NEXT_STREAM.fetch_add(1, Ordering::Relaxed),
102            };
103            let seed = if generation == 0 {
104                // No manual seed installed: draw a fresh seed from OS entropy.
105                scirs2_core::random::random::<u64>()
106            } else {
107                splitmix64(MANUAL_SEED.load(Ordering::SeqCst) ^ splitmix64(stream))
108            };
109            *slot = Some(ThreadRngState {
110                generation,
111                stream,
112                rng: Random::seed(seed),
113            });
114        }
115
116        // `slot` is `Some` at this point; the fallback never runs.
117        let state = slot.get_or_insert_with(|| ThreadRngState {
118            generation,
119            stream: 0,
120            rng: Random::seed(0),
121        });
122        f(&mut state.rng)
123    })
124}
125
126/// Draw a single standard-normal sample pair using the Box-Muller transform.
127fn box_muller(rng: &mut SeededRng) -> (f64, f64) {
128    // `u1` must be strictly positive: `ln(0)` is `-inf`, which would poison the
129    // sample with NaN.
130    let u1: f64 = rng.gen_range(f64::MIN_POSITIVE..1.0);
131    let u2: f64 = rng.gen_range(0.0..1.0);
132    let radius = (-2.0_f64 * u1.ln()).sqrt();
133    let angle = 2.0_f64 * std::f64::consts::PI * u2;
134    (radius * angle.cos(), radius * angle.sin())
135}
136
137/// Convert an `f64` sample into the tensor element type.
138fn sample_to_element<T: TensorElement>(value: f64) -> Result<T> {
139    T::from_f64(value).ok_or_else(|| {
140        TorshError::InvalidArgument(format!(
141            "cannot represent random sample {value} as {:?}",
142            T::dtype()
143        ))
144    })
145}
146
147/// Create a tensor from a scalar value
148pub fn tensor_scalar<T: TensorElement>(value: T) -> Result<Tensor<T>> {
149    Tensor::from_data(vec![value], vec![], DeviceType::Cpu)
150}
151
152/// Create a 1D tensor from a slice
153pub fn tensor_1d<T: TensorElement>(data: &[T]) -> Result<Tensor<T>> {
154    Tensor::from_data(data.to_vec(), vec![data.len()], DeviceType::Cpu)
155}
156
157/// Create a 2D tensor from nested slices
158pub fn tensor_2d<T: TensorElement>(data: &[&[T]]) -> Result<Tensor<T>> {
159    let rows = data.len();
160    let cols = if rows > 0 { data[0].len() } else { 0 };
161
162    let mut flat_data = Vec::with_capacity(rows * cols);
163    for row in data {
164        flat_data.extend_from_slice(row);
165    }
166
167    Tensor::from_data(flat_data, vec![rows, cols], DeviceType::Cpu)
168}
169
170/// Create a 2D tensor from nested arrays (for macro use)
171pub fn tensor_2d_arrays<T: TensorElement, const M: usize, const N: usize>(
172    data: &[[T; N]; M],
173) -> Result<Tensor<T>> {
174    let rows = M;
175    let cols = N;
176
177    let mut flat_data = Vec::with_capacity(rows * cols);
178    for row in data {
179        flat_data.extend_from_slice(row);
180    }
181
182    Tensor::from_data(flat_data, vec![rows, cols], DeviceType::Cpu)
183}
184
185/// Creates a tensor filled with zeros.
186///
187/// This is one of the most common tensor creation functions, useful for initializing
188/// tensors before filling them with computed values.
189///
190/// # Arguments
191///
192/// * `shape` - The shape of the tensor as a slice of dimensions
193///
194/// # Returns
195///
196/// A new tensor filled with zeros on the CPU device, or an error if creation fails.
197///
198/// # Examples
199///
200/// ```
201/// use torsh_tensor::creation::zeros;
202///
203/// // Create a 1D tensor with 5 elements
204/// let t = zeros::<f32>(&[5]).expect("operation should succeed");
205/// assert_eq!(t.shape().dims(), &[5]);
206///
207/// // Create a 2D tensor (matrix)
208/// let m = zeros::<f32>(&[3, 4]).expect("operation should succeed");
209/// assert_eq!(m.shape().dims(), &[3, 4]);
210/// assert_eq!(m.numel(), 12);
211///
212/// // Create a 3D tensor
213/// let cube = zeros::<f32>(&[2, 3, 4]).expect("operation should succeed");
214/// assert_eq!(cube.shape().dims(), &[2, 3, 4]);
215/// ```
216///
217/// # See Also
218///
219/// * [`ones`] - Create a tensor filled with ones
220/// * [`zeros_like`] - Create zeros matching another tensor's shape
221/// * [`zeros_device`] - Create zeros on a specific device
222pub fn zeros<T: TensorElement>(shape: &[usize]) -> Result<Tensor<T>> {
223    let size = shape.iter().product();
224    let data = vec![T::zero(); size];
225
226    Tensor::from_data(data, shape.to_vec(), DeviceType::Cpu)
227}
228
229/// Create a tensor of zeros backed by plain in-memory storage
230///
231/// `SimdOptimized` storage now supports mutation too (through a copy-on-write
232/// buffer), so this is no longer required for correctness — it just skips the
233/// SIMD alignment copy for tensors that are going to be written element by
234/// element anyway.
235pub fn zeros_mut<T: TensorElement>(shape: &[usize]) -> Tensor<T> {
236    let size = shape.iter().product();
237    let data = vec![T::zero(); size];
238
239    Tensor::from_data_fast(data, shape.to_vec(), DeviceType::Cpu)
240}
241
242/// Create a tensor of zeros on a specific device
243pub fn zeros_device<T: TensorElement>(shape: &[usize], device: DeviceType) -> Result<Tensor<T>> {
244    let size = shape.iter().product();
245    let data = vec![T::zero(); size];
246
247    Tensor::from_data(data, shape.to_vec(), device)
248}
249
250/// Creates a tensor filled with ones.
251///
252/// Commonly used for creating masks, initializing accumulators, or as a starting
253/// point for mathematical operations.
254///
255/// # Arguments
256///
257/// * `shape` - The shape of the tensor as a slice of dimensions
258///
259/// # Returns
260///
261/// A new tensor filled with ones on the CPU device, or an error if creation fails.
262///
263/// # Examples
264///
265/// ```
266/// use torsh_tensor::creation::ones;
267///
268/// // Create a 1D tensor
269/// let t = ones::<f32>(&[5]).expect("operation should succeed");
270/// assert_eq!(t.to_vec().expect("to_vec should succeed"), vec![1.0; 5]);
271///
272/// // Create a 2D tensor and use it as a mask
273/// let mask = ones::<f32>(&[2, 3]).expect("operation should succeed");
274/// assert_eq!(mask.shape().dims(), &[2, 3]);
275///
276/// // Different data types
277/// let int_ones = ones::<i32>(&[4]).expect("operation should succeed");
278/// assert_eq!(int_ones.to_vec().expect("to_vec should succeed"), vec![1; 4]);
279/// ```
280///
281/// # See Also
282///
283/// * [`zeros`] - Create a tensor filled with zeros
284/// * [`ones_like`] - Create ones matching another tensor's shape
285/// * [`full`] - Create a tensor filled with any value
286pub fn ones<T: TensorElement>(shape: &[usize]) -> Result<Tensor<T>> {
287    let size = shape.iter().product();
288    let data = vec![T::one(); size];
289
290    Tensor::from_data(data, shape.to_vec(), DeviceType::Cpu)
291}
292
293/// Create a tensor of ones on a specific device  
294pub fn ones_device<T: TensorElement>(shape: &[usize], device: DeviceType) -> Result<Tensor<T>> {
295    let size = shape.iter().product();
296    let data = vec![T::one(); size];
297
298    Tensor::from_data(data, shape.to_vec(), device)
299}
300
301/// Creates a tensor filled with a specific value.
302///
303/// Useful for initializing tensors with custom values, creating constant tensors,
304/// or setting initial biases in neural networks.
305///
306/// # Arguments
307///
308/// * `shape` - The shape of the tensor as a slice of dimensions
309/// * `value` - The value to fill the tensor with
310///
311/// # Returns
312///
313/// A new tensor filled with the specified value.
314///
315/// # Examples
316///
317/// ```
318/// use torsh_tensor::creation::full;
319///
320/// // Create a tensor filled with a specific value
321/// let t = full(&[2, 3], 7.0f32).expect("operation should succeed");
322/// assert_eq!(t.shape().dims(), &[2, 3]);
323/// assert_eq!(t.to_vec().expect("to_vec should succeed"), vec![7.0; 6]);
324///
325/// // Initialize bias tensor
326/// let bias = full(&[256], 0.01f32).expect("operation should succeed");
327/// assert_eq!(bias.numel(), 256);
328///
329/// // Create constant tensor for operations
330/// let pi_tensor = full(&[10], std::f32::consts::PI).expect("operation should succeed");
331/// ```
332///
333/// # See Also
334///
335/// * [`zeros`] - Create a tensor filled with zeros
336/// * [`ones`] - Create a tensor filled with ones
337/// * [`full_like`] - Create full tensor matching another's shape
338pub fn full<T: TensorElement>(shape: &[usize], value: T) -> Result<Tensor<T>> {
339    let size = shape.iter().product();
340    let data = vec![value; size];
341
342    Tensor::from_data(data, shape.to_vec(), DeviceType::Cpu)
343}
344
345/// Creates an identity matrix (2D tensor with ones on the diagonal).
346///
347/// An identity matrix is a square matrix with ones on the main diagonal and
348/// zeros elsewhere. It's fundamental in linear algebra and commonly used in
349/// neural network operations.
350///
351/// # Arguments
352///
353/// * `n` - The size of the square identity matrix (n × n)
354///
355/// # Returns
356///
357/// A new n×n tensor with ones on the diagonal and zeros elsewhere.
358///
359/// # Examples
360///
361/// ```
362/// use torsh_tensor::creation::eye;
363///
364/// // Create a 3x3 identity matrix
365/// let identity = eye::<f32>(3).expect("operation should succeed");
366/// assert_eq!(identity.shape().dims(), &[3, 3]);
367///
368/// let data = identity.to_vec().expect("to_vec should succeed");
369/// // Expected: [1, 0, 0,
370/// //            0, 1, 0,
371/// //            0, 0, 1]
372/// assert_eq!(data[0], 1.0);  // (0,0)
373/// assert_eq!(data[1], 0.0);  // (0,1)
374/// assert_eq!(data[4], 1.0);  // (1,1)
375/// assert_eq!(data[8], 1.0);  // (2,2)
376///
377/// // Use in linear algebra operations
378/// let matrix = eye::<f32>(4).expect("operation should succeed");
379/// // matrix @ vector preserves the vector (identity property)
380/// ```
381///
382/// # See Also
383///
384/// * [`zeros`] - Create a tensor filled with zeros
385/// * [`ones`] - Create a tensor filled with ones
386pub fn eye<T: TensorElement>(n: usize) -> Result<Tensor<T>> {
387    let mut data = vec![T::zero(); n * n];
388    for i in 0..n {
389        data[i * n + i] = T::one();
390    }
391
392    Tensor::from_data(data, vec![n, n], DeviceType::Cpu)
393}
394
395/// Creates a 1D tensor with evenly spaced values within a given interval.
396///
397/// Similar to Python's `range()` or NumPy's `arange()`, this function generates
398/// a sequence of values starting from `start` up to (but not including) `end`,
399/// incrementing by `step`.
400///
401/// # Arguments
402///
403/// * `start` - The starting value (inclusive)
404/// * `end` - The ending value (exclusive)
405/// * `step` - The spacing between values
406///
407/// # Returns
408///
409/// A 1D tensor containing the sequence of values.
410///
411/// # Examples
412///
413/// ```
414/// use torsh_tensor::creation::arange;
415///
416/// // Create a sequence from 0 to 9
417/// let t = arange(0, 10, 1).expect("operation should succeed");
418/// assert_eq!(t.to_vec().expect("to_vec should succeed"), vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
419///
420/// // Create a sequence with step size 2
421/// let evens = arange(0, 10, 2).expect("operation should succeed");
422/// assert_eq!(evens.to_vec().expect("to_vec should succeed"), vec![0, 2, 4, 6, 8]);
423///
424/// // Floating point sequences
425/// let floats = arange(0.0f32, 1.0, 0.25).expect("operation should succeed");
426/// assert_eq!(floats.shape().dims(), &[4]);
427///
428/// // Descending ranges use a negative step, like `torch.arange`
429/// let down = arange(5, 0, -1).expect("operation should succeed");
430/// assert_eq!(down.to_vec().expect("to_vec should succeed"), vec![5, 4, 3, 2, 1]);
431///
432/// // A zero step is rejected instead of looping forever
433/// assert!(arange(0.0f32, 1.0, 0.0).is_err());
434///
435/// // Use for indexing or creating coordinate grids
436/// let indices = arange(0, 100, 1).expect("operation should succeed");
437/// ```
438///
439/// # Errors
440///
441/// Returns [`TorshError::InvalidArgument`] if `step` is zero (or NaN), since no
442/// such sequence can terminate.
443///
444/// # See Also
445///
446/// * [`linspace`] - Create linearly spaced values with exact count
447/// * [`zeros`] - Create a tensor filled with zeros
448pub fn arange<T: TensorElement + std::cmp::PartialOrd + std::ops::Add<Output = T> + Copy>(
449    start: T,
450    end: T,
451    step: T,
452) -> Result<Tensor<T>> {
453    let zero = <T as TensorElement>::zero();
454    let ascending = step > zero;
455    let descending = step < zero;
456
457    // Catches both `step == 0` and a NaN step: neither comparison holds, and
458    // neither can ever reach `end`.
459    if !ascending && !descending {
460        return Err(TorshError::InvalidArgument(
461            "arange requires a non-zero, non-NaN step".to_string(),
462        ));
463    }
464
465    let mut values = Vec::new();
466    let mut current = start;
467
468    loop {
469        let in_range = if ascending {
470            current < end
471        } else {
472            current > end
473        };
474        if !in_range {
475            break;
476        }
477        values.push(current);
478        let next = current + step;
479        // Guard against a floating-point step too small to advance `current`
480        // (e.g. a subnormal step next to a large start), which would otherwise
481        // spin forever.
482        if next == current {
483            break;
484        }
485        current = next;
486    }
487
488    let len = values.len();
489    Tensor::from_data(values, vec![len], DeviceType::Cpu)
490}
491
492/// Create a tensor with linearly spaced values
493pub fn linspace<T: FloatElement>(start: T, end: T, steps: usize) -> Result<Tensor<T>> {
494    if steps == 0 {
495        return zeros(&[0]);
496    }
497
498    if steps == 1 {
499        return tensor_scalar(start);
500    }
501
502    let mut values = Vec::with_capacity(steps);
503    let step_size = (end - start) / T::from(steps - 1).expect("numeric conversion should succeed");
504
505    for i in 0..steps {
506        let value = start + step_size * T::from(i).expect("numeric conversion should succeed");
507        values.push(value);
508    }
509
510    Tensor::from_data(values, vec![steps], DeviceType::Cpu)
511}
512
513/// Creates a tensor with random values from uniform distribution [0, 1).
514///
515/// Useful for generating random data, initialization schemes that require uniform
516/// distribution, or Monte Carlo simulations.
517///
518/// **Randomness**: draws from the process-global generator, which seeds itself
519/// from OS entropy on first use. Call [`manual_seed`] first when you need a
520/// reproducible sequence.
521///
522/// # Arguments
523///
524/// * `shape` - The shape of the tensor as a slice of dimensions
525///
526/// # Returns
527///
528/// A new tensor with values uniformly distributed in [0, 1), or an error if creation fails.
529///
530/// # Examples
531///
532/// ```
533/// use torsh_tensor::creation::rand;
534///
535/// // Create random tensor
536/// let t = rand::<f32>(&[3, 3]).expect("operation should succeed");
537/// assert_eq!(t.shape().dims(), &[3, 3]);
538///
539/// // Values should be in [0, 1)
540/// let data = t.to_vec().expect("to_vec should succeed");
541/// for &val in &data {
542///     assert!(val >= 0.0 && val < 1.0);
543/// }
544///
545/// // Use for random initialization
546/// let weights = rand::<f32>(&[128, 64]).expect("operation should succeed");
547/// assert_eq!(weights.numel(), 128 * 64);
548/// ```
549///
550/// # See Also
551///
552/// * [`randn`] - Create tensor with normal distribution
553/// * [`randint`] - Create tensor with random integers
554/// * [`rand_like`] - Create random tensor matching another's shape
555/// * [`manual_seed`] - Make the sequence reproducible
556pub fn rand<T: FloatElement>(shape: &[usize]) -> Result<Tensor<T>>
557where
558    T: From<f32>,
559{
560    let size = shape.iter().product();
561    let values: Vec<T> = with_rng(|rng| {
562        (0..size)
563            .map(|_| <T as From<f32>>::from(rng.random::<f32>()))
564            .collect()
565    });
566
567    Tensor::from_data(values, shape.to_vec(), DeviceType::Cpu)
568}
569
570/// Create a uniform-random tensor from an explicit seed (reproducible).
571///
572/// Unlike [`rand`], this does not touch the process-global generator, so it is
573/// safe to use in tests that must not perturb other threads' sequences.
574pub fn rand_with_seed<T: FloatElement>(shape: &[usize], seed: u64) -> Result<Tensor<T>>
575where
576    T: From<f32>,
577{
578    let size = shape.iter().product();
579    let mut rng = Random::seed(seed);
580    let values: Vec<T> = (0..size)
581        .map(|_| <T as From<f32>>::from(rng.random::<f32>()))
582        .collect();
583
584    Tensor::from_data(values, shape.to_vec(), DeviceType::Cpu)
585}
586
587/// Creates a tensor with random values from standard normal distribution N(0, 1).
588///
589/// This is the most common initialization method for neural network weights,
590/// as it provides a good starting point for gradient-based optimization.
591/// Uses the Box-Muller transform to generate normally distributed values.
592///
593/// **Randomness**: draws from the process-global generator, which seeds itself
594/// from OS entropy on first use. Call [`manual_seed`] first when you need a
595/// reproducible sequence.
596///
597/// # Arguments
598///
599/// * `shape` - The shape of the tensor as a slice of dimensions
600///
601/// # Returns
602///
603/// A new tensor with values from N(0, 1) distribution, or an error if creation fails.
604///
605/// # Examples
606///
607/// ```
608/// use torsh_tensor::creation::{manual_seed, randn};
609///
610/// manual_seed(0);
611///
612/// // Create random normal tensor
613/// let t = randn::<f32>(&[1000]).expect("operation should succeed");
614/// assert_eq!(t.shape().dims(), &[1000]);
615///
616/// // Initialize neural network layer weights
617/// let weights = randn::<f32>(&[512, 256]).expect("operation should succeed");
618/// assert_eq!(weights.shape().dims(), &[512, 256]);
619///
620/// // The values follow normal distribution with mean~0 and std~1
621/// let data = t.to_vec().expect("to_vec should succeed");
622/// let mean: f32 = data.iter().sum::<f32>() / data.len() as f32;
623/// assert!((mean.abs() < 0.2), "Mean should be close to 0");
624/// ```
625///
626/// # Implementation Details
627///
628/// Uses the Box-Muller transform to convert uniform random numbers into
629/// normally distributed values. Sampling happens in `f64` and the result is
630/// converted through [`TensorElement::from_f64`], so narrow float types such as
631/// `f16`/`bf16` get a correctly rounded sample rather than a reinterpreted bit
632/// pattern.
633///
634/// # See Also
635///
636/// * [`rand`] - Create tensor with uniform distribution
637/// * [`randn_like`] - Create normal random tensor matching another's shape
638/// * [`zeros`] - Create tensor filled with zeros
639/// * [`manual_seed`] - Make the sequence reproducible
640pub fn randn<T: FloatElement>(shape: &[usize]) -> Result<Tensor<T>> {
641    let size = shape.iter().product();
642    let samples = with_rng(|rng| normal_samples(rng, size));
643    let values = samples
644        .into_iter()
645        .map(sample_to_element::<T>)
646        .collect::<Result<Vec<T>>>()?;
647
648    Tensor::from_data(values, shape.to_vec(), DeviceType::Cpu)
649}
650
651/// Create a standard-normal tensor from an explicit seed (reproducible).
652///
653/// Unlike [`randn`], this does not touch the process-global generator.
654pub fn randn_with_seed<T: FloatElement>(shape: &[usize], seed: u64) -> Result<Tensor<T>> {
655    let size = shape.iter().product();
656    let mut rng = Random::seed(seed);
657    let values = normal_samples(&mut rng, size)
658        .into_iter()
659        .map(sample_to_element::<T>)
660        .collect::<Result<Vec<T>>>()?;
661
662    Tensor::from_data(values, shape.to_vec(), DeviceType::Cpu)
663}
664
665/// Draw `size` independent N(0, 1) samples (two per Box-Muller evaluation).
666fn normal_samples(rng: &mut SeededRng, size: usize) -> Vec<f64> {
667    let mut samples = Vec::with_capacity(size);
668    while samples.len() < size {
669        let (first, second) = box_muller(rng);
670        samples.push(first);
671        if samples.len() < size {
672            samples.push(second);
673        }
674    }
675    samples
676}
677
678impl<T: FloatElement> Tensor<T> {
679    /// Fills this tensor in-place with samples from a normal (Gaussian)
680    /// distribution `N(mean, std^2)`.
681    ///
682    /// # PyTorch Compatibility
683    ///
684    /// Equivalent to `Tensor.normal_(mean, std)`. Like every other in-place
685    /// mutator on this type, it refuses to run on a tensor that
686    /// `requires_grad`, since autograd cannot track through a buffer
687    /// mutation.
688    ///
689    /// **Randomness**: draws from the process-global generator, which seeds
690    /// itself from OS entropy on first use. Call [`manual_seed`] first when
691    /// you need a reproducible sequence.
692    ///
693    /// # Errors
694    ///
695    /// Returns [`TorshError::InvalidArgument`] if:
696    /// - `self.requires_grad()` is `true`,
697    /// - `std` is negative or not finite,
698    /// - `mean` is not finite.
699    ///
700    /// # Examples
701    ///
702    /// ```
703    /// use torsh_tensor::creation::{manual_seed, zeros};
704    ///
705    /// manual_seed(0);
706    /// let mut t = zeros::<f32>(&[10, 10]).expect("operation should succeed");
707    /// t.normal_(0.0, 1.0).expect("operation should succeed");
708    ///
709    /// let data = t.to_vec().expect("to_vec should succeed");
710    /// assert!(
711    ///     data.iter().any(|&x| x != 0.0),
712    ///     "normal_ should fill the tensor with non-zero values"
713    /// );
714    /// ```
715    ///
716    /// # See Also
717    ///
718    /// * [`randn`] - Create a new standard-normal tensor
719    /// * [`manual_seed`] - Make the sequence reproducible
720    pub fn normal_(&mut self, mean: f64, std: f64) -> Result<()> {
721        if self.requires_grad {
722            return Err(TorshError::InvalidArgument(
723                "In-place operation `normal_` on tensor that requires grad is not allowed"
724                    .to_string(),
725            ));
726        }
727        if !std.is_finite() || std < 0.0 {
728            return Err(TorshError::InvalidArgument(format!(
729                "normal_ expects a finite std >= 0.0, got {std}"
730            )));
731        }
732        if !mean.is_finite() {
733            return Err(TorshError::InvalidArgument(format!(
734                "normal_ expects a finite mean, got {mean}"
735            )));
736        }
737
738        let numel = self.numel();
739        let raw = with_rng(|rng| normal_samples(rng, numel));
740        let values = raw
741            .into_iter()
742            .map(|z| sample_to_element::<T>(mean + std * z))
743            .collect::<Result<Vec<T>>>()?;
744
745        // `data_mut_apply` takes an infallible `FnMut(&mut T)`, so the
746        // fallible `f64 -> T` conversion happens eagerly above; this closure
747        // only ever assigns already-converted values. `written` is checked
748        // against `numel` afterwards so a mismatch surfaces as an honest
749        // error instead of silently leaving some elements at their old
750        // value.
751        let mut written = 0usize;
752        self.data_mut_apply(|item| {
753            if let Some(&value) = values.get(written) {
754                *item = value;
755            }
756            written += 1;
757        })?;
758
759        if written != numel {
760            return Err(TorshError::InvalidArgument(format!(
761                "normal_ wrote {written} elements but the tensor has {numel}"
762            )));
763        }
764
765        Ok(())
766    }
767}
768
769impl Tensor<f32> {
770    /// Draws samples from a multinomial (categorical) distribution defined by
771    /// `weights`.
772    ///
773    /// Each entry of `weights` is the unnormalized probability of drawing its
774    /// index; `weights` does not need to sum to 1. Returns a 1-D `i64` tensor
775    /// of `num_samples` category indices, on the same device as `weights`.
776    ///
777    /// # PyTorch Compatibility
778    ///
779    /// Equivalent to `weights.multinomial(num_samples, replacement)` for a
780    /// 1-D `weights` tensor. Batched (2-D) `weights` is not yet supported and
781    /// returns [`TorshError::InvalidArgument`] rather than silently
782    /// misinterpreting the input.
783    ///
784    /// **Randomness**: draws from the process-global generator; see
785    /// [`manual_seed`].
786    ///
787    /// # Errors
788    ///
789    /// Returns [`TorshError::InvalidArgument`] if:
790    /// - `weights` is not 1-dimensional,
791    /// - any weight is negative or not finite,
792    /// - every weight is zero (there is nothing to sample),
793    /// - `replacement` is `false` and `num_samples` exceeds the number of
794    ///   strictly-positive weights.
795    ///
796    /// # Examples
797    ///
798    /// ```
799    /// use torsh_tensor::creation::{manual_seed, tensor_1d};
800    /// use torsh_tensor::Tensor;
801    ///
802    /// manual_seed(0);
803    /// let weights = tensor_1d(&[0.1f32, 0.2, 0.3, 0.4]).expect("operation should succeed");
804    /// let samples = Tensor::multinomial(&weights, 10, true).expect("operation should succeed");
805    /// assert_eq!(samples.shape().dims(), &[10]);
806    /// ```
807    pub fn multinomial(
808        weights: &Self,
809        num_samples: usize,
810        replacement: bool,
811    ) -> Result<Tensor<i64>> {
812        if weights.ndim() != 1 {
813            return Err(TorshError::InvalidArgument(format!(
814                "multinomial expects a 1-D weights tensor, got {} dimensions",
815                weights.ndim()
816            )));
817        }
818
819        let mut pool = weights.to_vec()?;
820        for &w in &pool {
821            if !w.is_finite() || w < 0.0 {
822                return Err(TorshError::InvalidArgument(format!(
823                    "multinomial: weights must be finite and non-negative, found {w}"
824                )));
825            }
826        }
827
828        let num_positive = pool.iter().filter(|&&w| w > 0.0).count();
829        if num_positive == 0 {
830            return Err(TorshError::InvalidArgument(
831                "multinomial: weights sum must be positive".to_string(),
832            ));
833        }
834        if !replacement && num_samples > num_positive {
835            return Err(TorshError::InvalidArgument(format!(
836                "multinomial: cannot sample {num_samples} indices without replacement \
837                 from {num_positive} categories with positive weight"
838            )));
839        }
840
841        let mut samples = Vec::with_capacity(num_samples);
842        with_rng(|rng| -> Result<()> {
843            for _ in 0..num_samples {
844                let total: f32 = pool.iter().sum();
845                if total <= 0.0 {
846                    return Err(TorshError::InvalidArgument(
847                        "multinomial: ran out of positive-weight categories while \
848                         sampling without replacement"
849                            .to_string(),
850                    ));
851                }
852                let draw: f32 = rng.gen_range(0.0..total);
853
854                // Find the first category whose cumulative weight passes `draw`.
855                let mut cumulative = 0.0f32;
856                let mut chosen = None;
857                for (i, &w) in pool.iter().enumerate() {
858                    if w <= 0.0 {
859                        continue;
860                    }
861                    cumulative += w;
862                    if draw < cumulative {
863                        chosen = Some(i);
864                        break;
865                    }
866                }
867                // Floating-point rounding can (rarely) leave `draw` just past
868                // the last positive category's cumulative weight; fall back
869                // to that last positive category rather than risk selecting
870                // a zero-weight one.
871                let chosen = match chosen {
872                    Some(i) => i,
873                    None => pool
874                        .iter()
875                        .enumerate()
876                        .rev()
877                        .find(|&(_, &w)| w > 0.0)
878                        .map(|(i, _)| i)
879                        .ok_or_else(|| {
880                            TorshError::InvalidArgument(
881                                "multinomial: no positive-weight category available \
882                                 to sample"
883                                    .to_string(),
884                            )
885                        })?,
886                };
887
888                samples.push(chosen as i64);
889                if !replacement {
890                    pool[chosen] = 0.0;
891                }
892            }
893            Ok(())
894        })?;
895
896        let len = samples.len();
897        Tensor::from_data(samples, vec![len], weights.device())
898    }
899}
900
901/// Create a tensor with random integers in `[low, high)`
902///
903/// **Randomness**: draws from the process-global generator; see [`manual_seed`].
904pub fn randint(low: i32, high: i32, shape: &[usize]) -> Result<Tensor<i32>> {
905    let size = shape.iter().product();
906    use scirs2_core::random::Uniform;
907    let dist = Uniform::new(low, high)
908        .map_err(|e| TorshError::InvalidArgument(format!("Invalid range for randint: {}", e)))?;
909    let values: Vec<i32> = with_rng(|rng| (0..size).map(|_| rng.sample(&dist)).collect());
910
911    Tensor::from_data(values, shape.to_vec(), DeviceType::Cpu)
912}
913
914/// Create a tensor with random integers in `[low, high)` from an explicit seed.
915pub fn randint_with_seed(low: i32, high: i32, shape: &[usize], seed: u64) -> Result<Tensor<i32>> {
916    let size = shape.iter().product();
917    use scirs2_core::random::Uniform;
918    let dist = Uniform::new(low, high)
919        .map_err(|e| TorshError::InvalidArgument(format!("Invalid range for randint: {}", e)))?;
920    let mut rng = Random::seed(seed);
921    let values: Vec<i32> = (0..size).map(|_| rng.sample(&dist)).collect();
922
923    Tensor::from_data(values, shape.to_vec(), DeviceType::Cpu)
924}
925
926/// Create a tensor of zeros with the same shape as another tensor
927pub fn zeros_like<T: TensorElement>(tensor: &Tensor<T>) -> Result<Tensor<T>> {
928    zeros(tensor.shape().dims())
929}
930
931/// Create a tensor of ones with the same shape as another tensor
932pub fn ones_like<T: TensorElement>(tensor: &Tensor<T>) -> Result<Tensor<T>> {
933    ones(tensor.shape().dims())
934}
935
936/// Create a tensor filled with a value with the same shape as another tensor
937pub fn full_like<T: TensorElement>(tensor: &Tensor<T>, value: T) -> Result<Tensor<T>> {
938    full(tensor.shape().dims(), value)
939}
940
941/// Create a tensor with random values with the same shape as another tensor
942pub fn rand_like<T: FloatElement>(tensor: &Tensor<T>) -> Result<Tensor<T>>
943where
944    T: From<f32>,
945{
946    rand(tensor.shape().dims())
947}
948
949/// Create a tensor with random normal values with the same shape as another tensor
950pub fn randn_like<T: FloatElement>(tensor: &Tensor<T>) -> Result<Tensor<T>> {
951    randn(tensor.shape().dims())
952}
953
954// Complex tensor creation functions
955
956/// Create a complex tensor from real and imaginary parts
957pub fn complex_from_parts<T, C>(real: &Tensor<T>, imag: &Tensor<T>) -> Result<Tensor<C>>
958where
959    T: FloatElement,
960    C: ComplexElement<Real = T> + TensorElement,
961{
962    if real.shape() != imag.shape() {
963        return Err(torsh_core::error::TorshError::InvalidArgument(
964            "Real and imaginary parts must have the same shape".to_string(),
965        ));
966    }
967
968    let real_data = real.to_vec()?;
969    let imag_data = imag.to_vec()?;
970
971    let complex_data: Vec<C> = real_data
972        .iter()
973        .zip(imag_data.iter())
974        .map(|(&r, &i)| C::new(r, i))
975        .collect();
976
977    Tensor::from_data(complex_data, real.shape().dims().to_vec(), real.device())
978}
979
980/// Create a complex tensor of zeros
981pub fn complex_zeros<T, C>(shape: &[usize]) -> Result<Tensor<C>>
982where
983    T: FloatElement,
984    C: ComplexElement<Real = T> + TensorElement,
985{
986    let size = shape.iter().product();
987    let data = vec![C::new(<T as TensorElement>::zero(), <T as TensorElement>::zero()); size];
988
989    Tensor::from_data(data, shape.to_vec(), DeviceType::Cpu)
990}
991
992/// Create a complex tensor of ones
993pub fn complex_ones<T, C>(shape: &[usize]) -> Result<Tensor<C>>
994where
995    T: FloatElement,
996    C: ComplexElement<Real = T> + TensorElement,
997{
998    let size = shape.iter().product();
999    let data = vec![C::new(<T as TensorElement>::one(), <T as TensorElement>::zero()); size];
1000
1001    Tensor::from_data(data, shape.to_vec(), DeviceType::Cpu)
1002}
1003
1004/// Create a complex tensor filled with a specific value
1005pub fn complex_full<C: TensorElement>(shape: &[usize], value: C) -> Result<Tensor<C>> {
1006    let size = shape.iter().product();
1007    let data = vec![value; size];
1008
1009    Tensor::from_data(data, shape.to_vec(), DeviceType::Cpu)
1010}
1011
1012/// Create a complex tensor with random values (uniform distribution)
1013/// Real and imaginary parts are independently sampled from [0, 1)
1014pub fn complex_rand<T, C>(shape: &[usize]) -> Result<Tensor<C>>
1015where
1016    T: FloatElement + From<f32>,
1017    C: ComplexElement<Real = T> + TensorElement,
1018{
1019    let size = shape.iter().product();
1020    let values: Vec<C> = with_rng(|rng| {
1021        (0..size)
1022            .map(|_| {
1023                C::new(
1024                    <T as From<f32>>::from(rng.random::<f32>()),
1025                    <T as From<f32>>::from(rng.random::<f32>()),
1026                )
1027            })
1028            .collect()
1029    });
1030
1031    Tensor::from_data(values, shape.to_vec(), DeviceType::Cpu)
1032}
1033
1034/// Create a complex tensor with random values from standard normal distribution
1035/// Real and imaginary parts are independently sampled from N(0, 1)
1036pub fn complex_randn<T, C>(shape: &[usize]) -> Result<Tensor<C>>
1037where
1038    T: FloatElement,
1039    C: ComplexElement<Real = T> + TensorElement,
1040{
1041    let size = shape.iter().product();
1042    let pairs = with_rng(|rng| {
1043        (0..size)
1044            .map(|_| box_muller(rng))
1045            .collect::<Vec<(f64, f64)>>()
1046    });
1047
1048    let mut values = Vec::with_capacity(size);
1049    for (real, imag) in pairs {
1050        values.push(C::new(
1051            sample_to_element::<T>(real)?,
1052            sample_to_element::<T>(imag)?,
1053        ));
1054    }
1055
1056    Tensor::from_data(values, shape.to_vec(), DeviceType::Cpu)
1057}
1058
1059/// Create complex tensor with the same shape as another tensor, filled with zeros
1060pub fn complex_zeros_like<T, C>(tensor: &Tensor<T>) -> Result<Tensor<C>>
1061where
1062    T: FloatElement,
1063    C: ComplexElement<Real = T> + TensorElement,
1064{
1065    complex_zeros(tensor.shape().dims())
1066}
1067
1068/// Create complex tensor with the same shape as another tensor, filled with ones
1069pub fn complex_ones_like<T, C>(tensor: &Tensor<T>) -> Result<Tensor<C>>
1070where
1071    T: FloatElement,
1072    C: ComplexElement<Real = T> + TensorElement,
1073{
1074    complex_ones(tensor.shape().dims())
1075}
1076
1077/// Create complex tensor with random values with the same shape as another tensor
1078pub fn complex_rand_like<T, C>(tensor: &Tensor<T>) -> Result<Tensor<C>>
1079where
1080    T: FloatElement + From<f32>,
1081    C: ComplexElement<Real = T> + TensorElement,
1082{
1083    complex_rand(tensor.shape().dims())
1084}
1085
1086/// Create complex tensor with random normal values with the same shape as another tensor
1087pub fn complex_randn_like<T, C>(tensor: &Tensor<T>) -> Result<Tensor<C>>
1088where
1089    T: FloatElement,
1090    C: ComplexElement<Real = T> + TensorElement,
1091{
1092    complex_randn(tensor.shape().dims())
1093}
1094
1095// Convenience functions for specific complex types
1096
1097/// Create a Complex32 tensor of zeros
1098pub fn complex32_zeros(shape: &[usize]) -> Result<Tensor<Complex32>> {
1099    complex_zeros::<f32, Complex32>(shape)
1100}
1101
1102/// Create a Complex32 tensor of ones
1103pub fn complex32_ones(shape: &[usize]) -> Result<Tensor<Complex32>> {
1104    complex_ones::<f32, Complex32>(shape)
1105}
1106
1107/// Create a Complex32 tensor with random values
1108pub fn complex32_rand(shape: &[usize]) -> Result<Tensor<Complex32>> {
1109    complex_rand::<f32, Complex32>(shape)
1110}
1111
1112/// Create a Complex32 tensor with random normal values
1113pub fn complex32_randn(shape: &[usize]) -> Result<Tensor<Complex32>> {
1114    complex_randn::<f32, Complex32>(shape)
1115}
1116
1117/// Create a Complex64 tensor of zeros
1118pub fn complex64_zeros(shape: &[usize]) -> Result<Tensor<Complex64>> {
1119    complex_zeros::<f64, Complex64>(shape)
1120}
1121
1122/// Create a Complex64 tensor of ones
1123pub fn complex64_ones(shape: &[usize]) -> Result<Tensor<Complex64>> {
1124    complex_ones::<f64, Complex64>(shape)
1125}
1126
1127/// Create a Complex64 tensor with random values
1128pub fn complex64_rand(shape: &[usize]) -> Result<Tensor<Complex64>> {
1129    complex_rand::<f64, Complex64>(shape)
1130}
1131
1132/// Create a Complex64 tensor with random normal values
1133pub fn complex64_randn(shape: &[usize]) -> Result<Tensor<Complex64>> {
1134    complex_randn::<f64, Complex64>(shape)
1135}
1136
1137#[cfg(test)]
1138mod complex_tests {
1139    use super::*;
1140    use crate::tensor;
1141
1142    #[test]
1143    fn test_complex_zeros() {
1144        let tensor = complex32_zeros(&[2, 3]).expect("operation should succeed");
1145        assert_eq!(tensor.shape().dims(), &[2, 3]);
1146        assert_eq!(tensor.numel(), 6);
1147
1148        let data = tensor.to_vec().expect("to_vec should succeed");
1149        for &val in &data {
1150            assert_eq!(val.re, 0.0);
1151            assert_eq!(val.im, 0.0);
1152        }
1153    }
1154
1155    #[test]
1156    fn test_complex_ones() {
1157        let tensor = complex32_ones(&[2, 2]).expect("operation should succeed");
1158        assert_eq!(tensor.shape().dims(), &[2, 2]);
1159
1160        let data = tensor.to_vec().expect("to_vec should succeed");
1161        for &val in &data {
1162            assert_eq!(val.re, 1.0);
1163            assert_eq!(val.im, 0.0);
1164        }
1165    }
1166
1167    #[test]
1168    fn test_complex_from_parts() {
1169        let real = tensor![1.0f32, 2.0, 3.0].expect("operation should succeed");
1170        let imag = tensor![4.0f32, 5.0, 6.0].expect("operation should succeed");
1171
1172        let complex_tensor: Tensor<Complex32> =
1173            complex_from_parts(&real, &imag).expect("operation should succeed");
1174        assert_eq!(complex_tensor.shape().dims(), &[3]);
1175
1176        let data = complex_tensor.to_vec().expect("to_vec should succeed");
1177        assert_eq!(data[0].re, 1.0);
1178        assert_eq!(data[0].im, 4.0);
1179        assert_eq!(data[1].re, 2.0);
1180        assert_eq!(data[1].im, 5.0);
1181        assert_eq!(data[2].re, 3.0);
1182        assert_eq!(data[2].im, 6.0);
1183    }
1184
1185    #[test]
1186    fn test_complex_from_parts_shape_mismatch() {
1187        let real = tensor![1.0f32, 2.0].expect("operation should succeed");
1188        let imag = tensor![4.0f32, 5.0, 6.0].expect("operation should succeed");
1189
1190        let result: Result<Tensor<Complex32>> = complex_from_parts(&real, &imag);
1191        assert!(result.is_err());
1192    }
1193
1194    #[test]
1195    fn test_complex_rand() {
1196        let tensor = complex32_rand(&[10]).expect("operation should succeed");
1197        assert_eq!(tensor.shape().dims(), &[10]);
1198
1199        let data = tensor.to_vec().expect("to_vec should succeed");
1200        // Check that we have some variation in real and imaginary parts
1201        let all_same_real = data.iter().all(|&c| c.re == data[0].re);
1202        let all_same_imag = data.iter().all(|&c| c.im == data[0].im);
1203
1204        // With random data, this should be extremely unlikely
1205        assert!(!all_same_real || !all_same_imag);
1206    }
1207
1208    #[test]
1209    fn test_complex_randn() {
1210        let tensor = complex64_randn(&[5, 5]).expect("operation should succeed");
1211        assert_eq!(tensor.shape().dims(), &[5, 5]);
1212        assert_eq!(tensor.numel(), 25);
1213
1214        // Test that values are reasonably distributed (not all zeros)
1215        let data = tensor.to_vec().expect("to_vec should succeed");
1216        let has_nonzero_real = data.iter().any(|&c| c.re.abs() > 0.01);
1217        let has_nonzero_imag = data.iter().any(|&c| c.im.abs() > 0.01);
1218
1219        assert!(has_nonzero_real);
1220        assert!(has_nonzero_imag);
1221    }
1222
1223    #[test]
1224    fn test_complex_like_functions() {
1225        let base = tensor![1.0f32, 2.0, 3.0].expect("operation should succeed");
1226
1227        let zeros: Tensor<Complex32> = complex_zeros_like(&base).expect("operation should succeed");
1228        assert_eq!(zeros.shape().dims(), base.shape().dims());
1229
1230        let ones: Tensor<Complex32> = complex_ones_like(&base).expect("operation should succeed");
1231        assert_eq!(ones.shape().dims(), base.shape().dims());
1232
1233        let random: Tensor<Complex32> = complex_rand_like(&base).expect("operation should succeed");
1234        assert_eq!(random.shape().dims(), base.shape().dims());
1235
1236        let normal: Tensor<Complex32> =
1237            complex_randn_like(&base).expect("operation should succeed");
1238        assert_eq!(normal.shape().dims(), base.shape().dims());
1239    }
1240}
1241
1242/// Create a tensor from a vector and shape.
1243///
1244/// Rejects a `data` length that does not equal `shape`'s element count, so a
1245/// mismatch surfaces here instead of as a confusing out-of-bounds error deep in
1246/// a later operation.
1247pub fn from_vec<T: TensorElement>(
1248    data: Vec<T>,
1249    shape: &[usize],
1250    device: DeviceType,
1251) -> Result<Tensor<T>> {
1252    let numel: usize = shape.iter().product();
1253    if data.len() != numel {
1254        return Err(TorshError::InvalidArgument(format!(
1255            "from_vec: data has {} elements but shape {:?} requires {}",
1256            data.len(),
1257            shape,
1258            numel
1259        )));
1260    }
1261    Tensor::from_data(data, shape.to_vec(), device)
1262}