Skip to main content

tenflowers_core/ops/
numpy_array_creation.rs

1//! NumPy-style array creation functions
2//!
3//! This module provides NumPy-compatible array creation functions with the same
4//! APIs and behaviors as NumPy.
5
6use crate::{Result, Tensor, TensorError};
7use scirs2_core::numeric::{Float, FromPrimitive, One, Zero};
8use std::ops::Range;
9
10/// Create an array of zeros with NumPy-compatible API
11pub fn zeros<T>(shape: &[usize]) -> Tensor<T>
12where
13    T: Clone + Default + Zero + Send + Sync + 'static,
14{
15    Tensor::zeros(shape)
16}
17
18/// Create an array of ones with NumPy-compatible API
19pub fn ones<T>(shape: &[usize]) -> Tensor<T>
20where
21    T: Clone + Default + One + Send + Sync + 'static,
22{
23    Tensor::ones(shape)
24}
25
26/// Create an array filled with a specific value
27pub fn full<T>(shape: &[usize], fill_value: T) -> Result<Tensor<T>>
28where
29    T: Clone + Default + Send + Sync + 'static,
30{
31    let size = shape.iter().product();
32    let data = vec![fill_value; size];
33    Tensor::from_vec(data, shape)
34}
35
36/// Create an array with evenly spaced values within a given interval
37pub fn arange<T>(start: T, stop: T, step: T) -> Result<Tensor<T>>
38where
39    T: Clone + Default + Send + Sync + 'static + PartialOrd + std::ops::Add<Output = T>,
40{
41    // Pre-calculate capacity to avoid reallocations
42    let estimated_size = {
43        let mut count = 0;
44        let mut current = start.clone();
45        while current < stop {
46            count += 1;
47            current = current + step.clone();
48            // Safety break to avoid infinite loops
49            if count > 1_000_000 {
50                break;
51            }
52        }
53        count.max(1)
54    };
55
56    let mut values = Vec::with_capacity(estimated_size);
57    let mut current = start.clone();
58
59    while current < stop {
60        values.push(current.clone());
61        current = current + step.clone();
62    }
63
64    if values.is_empty() {
65        values.push(start);
66    }
67
68    let len = values.len();
69    Tensor::from_vec(values, &[len])
70}
71
72/// Create an array with evenly spaced values over a specified interval
73pub fn linspace<T>(start: T, stop: T, num: usize, endpoint: bool) -> Result<Tensor<T>>
74where
75    T: Clone + Default + Send + Sync + 'static + Float + FromPrimitive,
76{
77    if num == 0 {
78        return Tensor::from_vec(Vec::new(), &[0]);
79    }
80
81    if num == 1 {
82        return Tensor::from_vec(vec![start], &[1]);
83    }
84
85    let step = if endpoint {
86        (stop - start) / T::from_usize(num - 1).expect("step count should convert to float")
87    } else {
88        (stop - start) / T::from_usize(num).expect("step count should convert to float")
89    };
90
91    let mut values = Vec::with_capacity(num);
92    for i in 0..num {
93        let value = start + step * T::from_usize(i).expect("array index should convert to float");
94        values.push(value);
95    }
96
97    Tensor::from_vec(values, &[num])
98}
99
100/// Create an array with values spaced evenly on a log scale
101pub fn logspace<T>(start: T, stop: T, num: usize, base: T, endpoint: bool) -> Result<Tensor<T>>
102where
103    T: Clone + Default + Send + Sync + 'static + Float + FromPrimitive,
104{
105    let linear = linspace(start, stop, num, endpoint)?;
106    let linear_data = linear.as_slice().ok_or_else(|| {
107        TensorError::unsupported_operation_simple("Cannot access linear data".to_string())
108    })?;
109
110    let log_data: Vec<T> = linear_data.iter().map(|&x| base.powf(x)).collect();
111
112    Tensor::from_vec(log_data, &[num])
113}
114
115/// Create an array with values spaced evenly on a log scale (base 10)
116pub fn geomspace<T>(start: T, stop: T, num: usize, endpoint: bool) -> Result<Tensor<T>>
117where
118    T: Clone + Default + Send + Sync + 'static + Float + FromPrimitive,
119{
120    if start <= T::zero() || stop <= T::zero() {
121        return Err(TensorError::invalid_argument(
122            "geomspace requires positive start and stop values".to_string(),
123        ));
124    }
125
126    let log_start = start.ln();
127    let log_stop = stop.ln();
128
129    let log_linear = linspace(log_start, log_stop, num, endpoint)?;
130    let log_data = log_linear.as_slice().ok_or_else(|| {
131        TensorError::unsupported_operation_simple("Cannot access log linear data".to_string())
132    })?;
133
134    let geom_data: Vec<T> = log_data.iter().map(|&x| x.exp()).collect();
135
136    Tensor::from_vec(geom_data, &[num])
137}
138
139/// Create a 2D identity matrix
140pub fn eye<T>(n: usize, m: Option<usize>, k: i32) -> Result<Tensor<T>>
141where
142    T: Clone + Default + Zero + One + Send + Sync + 'static,
143{
144    let m = m.unwrap_or(n);
145    let mut data = vec![T::zero(); n * m];
146
147    // Fill diagonal with ones
148    for i in 0..n {
149        let j = (i as i32 + k) as usize;
150        if j < m {
151            data[i * m + j] = T::one();
152        }
153    }
154
155    Tensor::from_vec(data, &[n, m])
156}
157
158/// Create a 2D identity matrix (simplified version)
159pub fn identity<T>(n: usize) -> Result<Tensor<T>>
160where
161    T: Clone + Default + Zero + One + Send + Sync + 'static,
162{
163    eye(n, None, 0)
164}
165
166/// Create an array from a range (Python-style)
167pub fn from_range<T>(range: Range<T>) -> Result<Tensor<T>>
168where
169    T: Clone + Default + Send + Sync + 'static + PartialOrd + std::ops::Add<Output = T> + One,
170{
171    // Pre-calculate capacity to avoid reallocations
172    let estimated_size = {
173        let mut count = 0;
174        let mut current = range.start.clone();
175        let step = T::one();
176        while current < range.end {
177            count += 1;
178            current = current + step.clone();
179            // Safety break to avoid infinite loops
180            if count > 1_000_000 {
181                break;
182            }
183        }
184        count.max(1)
185    };
186
187    let mut values = Vec::with_capacity(estimated_size);
188    let mut current = range.start;
189    let step = T::one();
190
191    while current < range.end {
192        values.push(current.clone());
193        current = current + step.clone();
194    }
195
196    let len = values.len();
197    Tensor::from_vec(values, &[len])
198}
199
200/// Create a diagonal matrix from a 1D array
201pub fn diag<T>(v: &Tensor<T>, k: i32) -> Result<Tensor<T>>
202where
203    T: Clone + Default + Zero + Send + Sync + 'static,
204{
205    if v.shape().rank() != 1 {
206        return Err(TensorError::invalid_argument(
207            "diag requires a 1D input tensor".to_string(),
208        ));
209    }
210
211    let n = v.shape().dims()[0];
212    let size = n + k.unsigned_abs() as usize;
213    let mut data = vec![T::zero(); size * size];
214
215    let v_data = v.as_slice().ok_or_else(|| {
216        TensorError::unsupported_operation_simple("Cannot access input data".to_string())
217    })?;
218
219    for (i, value) in v_data.iter().enumerate() {
220        let row = if k >= 0 { i } else { i + (-k) as usize };
221        let col = if k >= 0 { i + k as usize } else { i };
222
223        if row < size && col < size {
224            data[row * size + col] = value.clone();
225        }
226    }
227
228    Tensor::from_vec(data, &[size, size])
229}
230
231/// Extract diagonal from a 2D array
232pub fn diagonal<T>(matrix: &Tensor<T>, offset: i32) -> Result<Tensor<T>>
233where
234    T: Clone + Default + Send + Sync + 'static,
235{
236    if matrix.shape().rank() != 2 {
237        return Err(TensorError::invalid_argument(
238            "diagonal requires a 2D input tensor".to_string(),
239        ));
240    }
241
242    let dims = matrix.shape().dims();
243    let rows = dims[0];
244    let cols = dims[1];
245
246    let matrix_data = matrix.as_slice().ok_or_else(|| {
247        TensorError::unsupported_operation_simple("Cannot access matrix data".to_string())
248    })?;
249
250    let mut diag_data = Vec::new();
251
252    if offset >= 0 {
253        let offset = offset as usize;
254        for i in 0..rows {
255            let j = i + offset;
256            if j < cols {
257                diag_data.push(matrix_data[i * cols + j].clone());
258            }
259        }
260    } else {
261        let offset = (-offset) as usize;
262        for i in offset..rows {
263            let j = i - offset;
264            if j < cols {
265                diag_data.push(matrix_data[i * cols + j].clone());
266            }
267        }
268    }
269
270    let len = diag_data.len();
271    Tensor::from_vec(diag_data, &[len])
272}
273
274/// Create a triangular matrix (upper or lower)
275pub fn tri<T>(n: usize, m: Option<usize>, k: i32, lower: bool) -> Result<Tensor<T>>
276where
277    T: Clone + Default + Zero + One + Send + Sync + 'static,
278{
279    let m = m.unwrap_or(n);
280    let mut data = vec![T::zero(); n * m];
281
282    for i in 0..n {
283        for j in 0..m {
284            let condition = if lower {
285                j as i32 <= i as i32 + k
286            } else {
287                j as i32 >= i as i32 + k
288            };
289
290            if condition {
291                data[i * m + j] = T::one();
292            }
293        }
294    }
295
296    Tensor::from_vec(data, &[n, m])
297}
298
299/// Create an upper triangular matrix
300pub fn triu<T>(n: usize, m: Option<usize>, k: i32) -> Result<Tensor<T>>
301where
302    T: Clone + Default + Zero + One + Send + Sync + 'static,
303{
304    tri(n, m, k, false)
305}
306
307/// Create a lower triangular matrix
308pub fn tril<T>(n: usize, m: Option<usize>, k: i32) -> Result<Tensor<T>>
309where
310    T: Clone + Default + Zero + One + Send + Sync + 'static,
311{
312    tri(n, m, k, true)
313}
314
315/// Create meshgrid like NumPy
316pub fn meshgrid<T>(x: &Tensor<T>, y: &Tensor<T>, indexing: &str) -> Result<(Tensor<T>, Tensor<T>)>
317where
318    T: Clone + Default + Send + Sync + 'static,
319{
320    if x.shape().rank() != 1 || y.shape().rank() != 1 {
321        return Err(TensorError::invalid_argument(
322            "meshgrid requires 1D input tensors".to_string(),
323        ));
324    }
325
326    let x_data = x.as_slice().ok_or_else(|| {
327        TensorError::unsupported_operation_simple("Cannot access x data".to_string())
328    })?;
329    let y_data = y.as_slice().ok_or_else(|| {
330        TensorError::unsupported_operation_simple("Cannot access y data".to_string())
331    })?;
332
333    let (x_len, y_len) = (x_data.len(), y_data.len());
334
335    let (xx_data, yy_data, shape) = match indexing {
336        "xy" => {
337            // Cartesian indexing: X has shape (len(y), len(x)), Y has shape (len(y), len(x))
338            let mut xx = Vec::with_capacity(y_len * x_len);
339            let mut yy = Vec::with_capacity(y_len * x_len);
340
341            for y_val in y_data {
342                for x_val in x_data {
343                    xx.push(x_val.clone());
344                    yy.push(y_val.clone());
345                }
346            }
347
348            (xx, yy, vec![y_len, x_len])
349        }
350        "ij" => {
351            // Matrix indexing: X has shape (len(x), len(y)), Y has shape (len(x), len(y))
352            let mut xx = Vec::with_capacity(x_len * y_len);
353            let mut yy = Vec::with_capacity(x_len * y_len);
354
355            for x_val in x_data {
356                for y_val in y_data {
357                    xx.push(x_val.clone());
358                    yy.push(y_val.clone());
359                }
360            }
361
362            (xx, yy, vec![x_len, y_len])
363        }
364        _ => {
365            return Err(TensorError::invalid_argument(format!(
366                "Unknown indexing: '{indexing}'. Use 'xy' or 'ij'"
367            )));
368        }
369    };
370
371    let xx_tensor = Tensor::from_vec(xx_data, &shape)?;
372    let yy_tensor = Tensor::from_vec(yy_data, &shape)?;
373
374    Ok((xx_tensor, yy_tensor))
375}
376
377/// Create array from function
378pub fn fromfunction<T, F>(func: F, shape: &[usize]) -> Result<Tensor<T>>
379where
380    T: Clone + Default + Send + Sync + 'static,
381    F: Fn(&[usize]) -> T + Send + Sync,
382{
383    let total_size = shape.iter().product();
384    let mut data = Vec::with_capacity(total_size);
385
386    // Generate all possible indices
387    let mut indices = vec![0; shape.len()];
388
389    for _ in 0..total_size {
390        data.push(func(&indices));
391
392        // Increment indices (like odometer)
393        let mut carry = true;
394        for i in (0..shape.len()).rev() {
395            if carry {
396                indices[i] += 1;
397                if indices[i] < shape[i] {
398                    carry = false;
399                } else {
400                    indices[i] = 0;
401                }
402            }
403        }
404    }
405
406    Tensor::from_vec(data, shape)
407}
408
409#[cfg(test)]
410mod tests {
411    use super::*;
412
413    #[test]
414    fn test_arange() {
415        let result = arange(0.0f32, 5.0, 1.0).expect("test: arange should succeed");
416        if let Some(data) = result.as_slice() {
417            assert_eq!(data, &[0.0, 1.0, 2.0, 3.0, 4.0]);
418        }
419    }
420
421    #[test]
422    fn test_linspace() {
423        let result = linspace(0.0f32, 1.0, 6, true).expect("test: linspace should succeed");
424        if let Some(data) = result.as_slice() {
425            assert_eq!(data.len(), 6);
426            assert!((data[0] - 0.0).abs() < 1e-6);
427            assert!((data[5] - 1.0).abs() < 1e-6);
428        }
429    }
430
431    #[test]
432    fn test_eye() {
433        let result = eye::<f32>(3, None, 0).expect("test: operation should succeed");
434        assert_eq!(result.shape().dims(), &[3, 3]);
435
436        if let Some(data) = result.as_slice() {
437            let expected = &[1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
438            assert_eq!(data, expected);
439        }
440    }
441
442    #[test]
443    fn test_diag() {
444        let v = Tensor::<f32>::from_vec(vec![1.0, 2.0, 3.0], &[3])
445            .expect("test: from_vec should succeed");
446        let result = diag(&v, 0).expect("test: diag should succeed");
447        assert_eq!(result.shape().dims(), &[3, 3]);
448
449        if let Some(data) = result.as_slice() {
450            let expected = &[1.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 3.0];
451            assert_eq!(data, expected);
452        }
453    }
454
455    #[test]
456    fn test_diagonal() {
457        let matrix =
458            Tensor::<f32>::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0], &[3, 3])
459                .expect("test: operation should succeed");
460
461        let result = diagonal(&matrix, 0).expect("test: diagonal should succeed");
462        if let Some(data) = result.as_slice() {
463            assert_eq!(data, &[1.0, 5.0, 9.0]);
464        }
465    }
466
467    #[test]
468    fn test_meshgrid() {
469        let x =
470            Tensor::<f32>::from_vec(vec![1.0, 2.0], &[2]).expect("test: from_vec should succeed");
471        let y = Tensor::<f32>::from_vec(vec![3.0, 4.0, 5.0], &[3])
472            .expect("test: from_vec should succeed");
473
474        let (xx, yy) = meshgrid(&x, &y, "xy").expect("test: meshgrid should succeed");
475        assert_eq!(xx.shape().dims(), &[3, 2]);
476        assert_eq!(yy.shape().dims(), &[3, 2]);
477    }
478
479    #[test]
480    fn test_fromfunction() {
481        let result = fromfunction(|indices| (indices[0] + indices[1]) as f32, &[2, 3])
482            .expect("test: operation should succeed");
483        assert_eq!(result.shape().dims(), &[2, 3]);
484
485        if let Some(data) = result.as_slice() {
486            assert_eq!(data, &[0.0, 1.0, 2.0, 1.0, 2.0, 3.0]);
487        }
488    }
489
490    #[test]
491    fn test_triu_tril() {
492        let upper = triu::<f32>(3, None, 0).expect("test: operation should succeed");
493        if let Some(data) = upper.as_slice() {
494            let expected = &[1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0];
495            assert_eq!(data, expected);
496        }
497
498        let lower = tril::<f32>(3, None, 0).expect("test: operation should succeed");
499        if let Some(data) = lower.as_slice() {
500            let expected = &[1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0];
501            assert_eq!(data, expected);
502        }
503    }
504}