Skip to main content

torsh_ffi/python/tensor/
mod.rs

1//! Python tensor wrapper module
2//!
3//! This module provides comprehensive Python FFI support for ToRSh tensors,
4//! including memory management, type mapping, device management, and tensor operations.
5//!
6//! The module is organized into focused submodules:
7//! - `memory`: Memory pool for efficient tensor allocation
8//! - `types`: Cross-framework type mapping and compatibility
9//! - `device`: Device management and properties
10//! - `storage`: Reference-counted tensor storage
11//! - `tensor`: Main PyTensor implementation
12
13pub mod device;
14pub mod memory;
15pub mod storage;
16pub mod tensor;
17pub mod types;
18
19// Re-export core types for backward compatibility
20pub use device::{DeviceProperties, DeviceType};
21pub use memory::{MemoryPool, MemoryPoolStats, MEMORY_POOL};
22pub use storage::TensorStorage;
23pub use tensor::PyTensor;
24pub use types::{FrameworkTypeInfo, TypeMapper, TYPE_MAPPER};
25
26// Re-export for Python module registration
27pub use tensor::PyTensor as Tensor;
28
29use crate::error::FfiResult;
30use pyo3::prelude::*;
31
32/// Initialize the tensor module for Python
33pub fn init_tensor_module(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
34    m.add_class::<PyTensor>()?;
35    Ok(())
36}
37
38/// Utility functions for tensor creation and management
39
40/// Create a new tensor from raw data with validation
41pub fn create_tensor_validated(
42    data: Vec<f32>,
43    shape: Vec<usize>,
44    dtype: torsh_core::DType,
45    requires_grad: bool,
46) -> FfiResult<PyTensor> {
47    // Validate shape consistency
48    let expected_size: usize = shape.iter().product();
49    if data.len() != expected_size {
50        return Err(crate::error::FfiError::ShapeMismatch {
51            expected: vec![expected_size],
52            actual: vec![data.len()],
53        });
54    }
55
56    Ok(PyTensor::from_raw(data, shape, dtype, requires_grad))
57}
58
59/// Get memory pool statistics
60pub fn get_memory_stats() -> FfiResult<MemoryPoolStats> {
61    MEMORY_POOL.stats()
62}
63
64/// Clear memory pool
65pub fn clear_memory_pool() -> FfiResult<()> {
66    MEMORY_POOL.clear()
67}
68
69/// Convert between framework type names
70pub fn convert_dtype_name(
71    dtype_name: &str,
72    from_framework: &str,
73    to_framework: &str,
74) -> FfiResult<String> {
75    let torsh_dtype = match from_framework {
76        "numpy" => TYPE_MAPPER.numpy_to_torsh(dtype_name)?,
77        "pytorch" => TYPE_MAPPER.pytorch_to_torsh(dtype_name)?,
78        "torsh" => match dtype_name {
79            "f32" => torsh_core::DType::F32,
80            "f64" => torsh_core::DType::F64,
81            "i32" => torsh_core::DType::I32,
82            "i64" => torsh_core::DType::I64,
83            "i16" => torsh_core::DType::I16,
84            "i8" => torsh_core::DType::I8,
85            "u8" => torsh_core::DType::U8,
86            "f16" => torsh_core::DType::F16,
87            "bool" => torsh_core::DType::Bool,
88            _ => {
89                return Err(crate::error::FfiError::DTypeMismatch {
90                    expected: "f32, f64, i32, i64, i16, i8, u8, f16, bool".to_string(),
91                    actual: dtype_name.to_string(),
92                })
93            }
94        },
95        _ => {
96            return Err(crate::error::FfiError::DTypeMismatch {
97                expected: "numpy, pytorch, torsh".to_string(),
98                actual: from_framework.to_string(),
99            })
100        }
101    };
102
103    let result = match to_framework {
104        "numpy" => TYPE_MAPPER.torsh_to_numpy(torsh_dtype),
105        "pytorch" => TYPE_MAPPER.torsh_to_pytorch(torsh_dtype),
106        "torsh" => format!("{:?}", torsh_dtype).to_lowercase(),
107        _ => {
108            return Err(crate::error::FfiError::DTypeMismatch {
109                expected: "numpy, pytorch, torsh".to_string(),
110                actual: to_framework.to_string(),
111            })
112        }
113    };
114
115    Ok(result)
116}
117
118/// Check device availability
119pub fn check_device_availability(device_str: &str) -> FfiResult<bool> {
120    let device = DeviceType::from_string(device_str)?;
121    Ok(device.is_available())
122}
123
124/// Get device properties as a formatted string
125pub fn get_device_info(device_str: &str) -> FfiResult<String> {
126    let device = DeviceType::from_string(device_str)?;
127    let props = device.properties();
128    Ok(format!(
129        "Device: {}\nTotal Memory: {} GB\nAvailable Memory: {} GB\nCompute Capability: {}\nMulti-processor Count: {}\nIntegrated: {}",
130        props.name,
131        props.memory_total / (1024 * 1024 * 1024),
132        props.memory_available / (1024 * 1024 * 1024),
133        props.compute_capability,
134        props.multi_processor_count,
135        props.is_integrated
136    ))
137}
138
139/// Tensor creation convenience functions
140
141/// Create a zeros tensor
142pub fn zeros(shape: Vec<usize>, dtype: Option<torsh_core::DType>) -> FfiResult<PyTensor> {
143    let tensor_dtype = dtype.unwrap_or(torsh_core::DType::F32);
144    let size: usize = shape.iter().product();
145    let data = vec![0.0; size];
146    create_tensor_validated(data, shape, tensor_dtype, false)
147}
148
149/// Create a ones tensor
150pub fn ones(shape: Vec<usize>, dtype: Option<torsh_core::DType>) -> FfiResult<PyTensor> {
151    let tensor_dtype = dtype.unwrap_or(torsh_core::DType::F32);
152    let size: usize = shape.iter().product();
153    let data = vec![1.0; size];
154    create_tensor_validated(data, shape, tensor_dtype, false)
155}
156
157/// Create a random tensor with normal distribution
158pub fn randn(shape: Vec<usize>, dtype: Option<torsh_core::DType>) -> FfiResult<PyTensor> {
159    let tensor_dtype = dtype.unwrap_or(torsh_core::DType::F32);
160    let size: usize = shape.iter().product();
161
162    let data: Vec<f32> = (0..size)
163        .map(|_| {
164            // Simple box-muller transform for normal distribution
165            use std::f32::consts::PI;
166            static mut SPARE: Option<f32> = None;
167            static mut HAS_SPARE: bool = false;
168
169            unsafe {
170                if HAS_SPARE {
171                    HAS_SPARE = false;
172                    SPARE.unwrap()
173                } else {
174                    HAS_SPARE = true;
175                    let u = fastrand::f32();
176                    let v = fastrand::f32();
177                    let mag = 0.1 * (-2.0 * u.ln()).sqrt();
178                    SPARE = Some(mag * (2.0 * PI * v).sin());
179                    mag * (2.0 * PI * v).cos()
180                }
181            }
182        })
183        .collect();
184
185    create_tensor_validated(data, shape, tensor_dtype, false)
186}
187
188/// Create an identity matrix
189pub fn eye(size: usize, dtype: Option<torsh_core::DType>) -> FfiResult<PyTensor> {
190    let tensor_dtype = dtype.unwrap_or(torsh_core::DType::F32);
191    let mut data = vec![0.0; size * size];
192
193    for i in 0..size {
194        data[i * size + i] = 1.0;
195    }
196
197    create_tensor_validated(data, vec![size, size], tensor_dtype, false)
198}
199
200/// Advanced tensor operations
201
202/// Perform matrix multiplication between tensors
203pub fn matmul(a: &PyTensor, b: &PyTensor) -> FfiResult<PyTensor> {
204    if a.shape.len() != 2 || b.shape.len() != 2 {
205        return Err(crate::error::FfiError::ShapeMismatch {
206            expected: vec![2],
207            actual: vec![a.shape.len(), b.shape.len()],
208        });
209    }
210
211    let [m, k] = [a.shape[0], a.shape[1]];
212    let [k2, n] = [b.shape[0], b.shape[1]];
213
214    if k != k2 {
215        return Err(crate::error::FfiError::ShapeMismatch {
216            expected: vec![k],
217            actual: vec![k2],
218        });
219    }
220
221    let mut result = vec![0.0; m * n];
222
223    for i in 0..m {
224        for j in 0..n {
225            let mut sum = 0.0;
226            for l in 0..k {
227                sum += a.data[i * k + l] * b.data[l * n + j];
228            }
229            result[i * n + j] = sum;
230        }
231    }
232
233    create_tensor_validated(
234        result,
235        vec![m, n],
236        a.dtype,
237        a.requires_grad || b.requires_grad,
238    )
239}
240
241/// Broadcasting utilities
242
243/// Check if two shapes are broadcastable
244pub fn are_broadcastable(shape1: &[usize], shape2: &[usize]) -> bool {
245    let max_len = shape1.len().max(shape2.len());
246
247    for i in 0..max_len {
248        let dim1 = if i < shape1.len() {
249            shape1[shape1.len() - 1 - i]
250        } else {
251            1
252        };
253        let dim2 = if i < shape2.len() {
254            shape2[shape2.len() - 1 - i]
255        } else {
256            1
257        };
258
259        if dim1 != dim2 && dim1 != 1 && dim2 != 1 {
260            return false;
261        }
262    }
263
264    true
265}
266
267/// Compute broadcast shape
268pub fn broadcast_shape(shape1: &[usize], shape2: &[usize]) -> FfiResult<Vec<usize>> {
269    if !are_broadcastable(shape1, shape2) {
270        return Err(crate::error::FfiError::ShapeMismatch {
271            expected: shape1.to_vec(),
272            actual: shape2.to_vec(),
273        });
274    }
275
276    let max_len = shape1.len().max(shape2.len());
277    let mut result = Vec::with_capacity(max_len);
278
279    for i in 0..max_len {
280        let dim1 = if i < shape1.len() {
281            shape1[shape1.len() - 1 - i]
282        } else {
283            1
284        };
285        let dim2 = if i < shape2.len() {
286            shape2[shape2.len() - 1 - i]
287        } else {
288            1
289        };
290
291        result.push(dim1.max(dim2));
292    }
293
294    result.reverse();
295    Ok(result)
296}
297
298#[cfg(test)]
299mod tests {
300    use super::*;
301    use torsh_core::DType;
302
303    #[test]
304    fn test_tensor_creation() {
305        let data = vec![1.0, 2.0, 3.0, 4.0];
306        let shape = vec![2, 2];
307        let tensor = create_tensor_validated(data, shape, DType::F32, false);
308        assert!(tensor.is_ok());
309
310        let tensor = tensor.unwrap();
311        assert_eq!(tensor.shape, vec![2, 2]);
312        assert_eq!(tensor.dtype, DType::F32);
313        assert!(!tensor.requires_grad);
314    }
315
316    #[test]
317    fn test_zeros_creation() {
318        let tensor = zeros(vec![3, 3], Some(DType::F32));
319        assert!(tensor.is_ok());
320
321        let tensor = tensor.unwrap();
322        assert_eq!(tensor.shape, vec![3, 3]);
323        assert!(tensor.data.iter().all(|&x| x == 0.0));
324    }
325
326    #[test]
327    fn test_ones_creation() {
328        let tensor = ones(vec![2, 3], Some(DType::F32));
329        assert!(tensor.is_ok());
330
331        let tensor = tensor.unwrap();
332        assert_eq!(tensor.shape, vec![2, 3]);
333        assert!(tensor.data.iter().all(|&x| x == 1.0));
334    }
335
336    #[test]
337    fn test_eye_creation() {
338        let tensor = eye(3, Some(DType::F32));
339        assert!(tensor.is_ok());
340
341        let tensor = tensor.unwrap();
342        assert_eq!(tensor.shape, vec![3, 3]);
343
344        // Check diagonal elements are 1.0
345        for i in 0..3 {
346            assert_eq!(tensor.data[i * 3 + i], 1.0);
347        }
348
349        // Check off-diagonal elements are 0.0
350        for i in 0..3 {
351            for j in 0..3 {
352                if i != j {
353                    assert_eq!(tensor.data[i * 3 + j], 0.0);
354                }
355            }
356        }
357    }
358
359    #[test]
360    fn test_broadcasting() {
361        assert!(are_broadcastable(&[3, 4], &[4]));
362        assert!(are_broadcastable(&[2, 1, 4], &[3, 4]));
363        assert!(!are_broadcastable(&[3, 4], &[3, 5]));
364
365        let result = broadcast_shape(&[3, 4], &[4]);
366        assert!(result.is_ok());
367        assert_eq!(result.unwrap(), vec![3, 4]);
368    }
369
370    #[test]
371    fn test_dtype_conversion() {
372        let result = convert_dtype_name("float32", "numpy", "pytorch");
373        assert!(result.is_ok());
374        assert_eq!(result.unwrap(), "torch.float32");
375
376        let result = convert_dtype_name("torch.int64", "pytorch", "numpy");
377        assert!(result.is_ok());
378        assert_eq!(result.unwrap(), "int64");
379    }
380
381    #[test]
382    fn test_device_operations() {
383        assert!(check_device_availability("cpu").unwrap());
384
385        let info = get_device_info("cpu");
386        assert!(info.is_ok());
387        assert!(info.unwrap().contains("Device: CPU"));
388    }
389
390    #[test]
391    fn test_memory_pool() {
392        let stats = get_memory_stats();
393        assert!(stats.is_ok());
394
395        let clear_result = clear_memory_pool();
396        assert!(clear_result.is_ok());
397    }
398}