1pub mod device;
14pub mod memory;
15pub mod storage;
16pub mod tensor;
17pub mod types;
18
19pub 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
26pub use tensor::PyTensor as Tensor;
28
29use crate::error::FfiResult;
30use pyo3::prelude::*;
31
32pub fn init_tensor_module(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
34 m.add_class::<PyTensor>()?;
35 Ok(())
36}
37
38pub 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 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
59pub fn get_memory_stats() -> FfiResult<MemoryPoolStats> {
61 MEMORY_POOL.stats()
62}
63
64pub fn clear_memory_pool() -> FfiResult<()> {
66 MEMORY_POOL.clear()
67}
68
69pub 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
118pub fn check_device_availability(device_str: &str) -> FfiResult<bool> {
120 let device = DeviceType::from_string(device_str)?;
121 Ok(device.is_available())
122}
123
124pub 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
139pub 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
149pub 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
157pub 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 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
188pub 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
200pub 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
241pub 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
267pub 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 for i in 0..3 {
346 assert_eq!(tensor.data[i * 3 + i], 1.0);
347 }
348
349 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}