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}