Skip to main content

numr/ops/cpu/
utility.rs

1//! CPU implementation of utility operations.
2
3use crate::dtype::{DType, Element};
4use crate::error::Result;
5use crate::ops::UtilityOps;
6use crate::runtime::cpu::{
7    CpuClient, CpuRuntime,
8    helpers::{dispatch_dtype, ensure_contiguous},
9    kernels,
10};
11use crate::runtime::validate_arange;
12use crate::tensor::Tensor;
13
14use crate::error::Error;
15
16/// UtilityOps implementation for CPU runtime.
17impl UtilityOps<CpuRuntime> for CpuClient {
18    fn clamp(
19        &self,
20        a: &Tensor<CpuRuntime>,
21        min_val: f64,
22        max_val: f64,
23    ) -> Result<Tensor<CpuRuntime>> {
24        let dtype = a.dtype();
25        let a_contig = ensure_contiguous(a)?;
26        let out = Tensor::<CpuRuntime>::empty(a.shape(), dtype, &self.device);
27
28        let a_ptr = a_contig.ptr();
29        let out_ptr = out.ptr();
30        let numel = a.numel();
31
32        dispatch_dtype!(dtype, T => {
33            unsafe {
34                kernels::clamp_kernel::<T>(
35                    a_ptr as *const T,
36                    out_ptr as *mut T,
37                    numel,
38                    min_val,
39                    max_val,
40                );
41            }
42        }, "clamp");
43
44        Ok(out)
45    }
46
47    fn fill(&self, shape: &[usize], value: f64, dtype: DType) -> Result<Tensor<CpuRuntime>> {
48        let out = Tensor::<CpuRuntime>::empty(shape, dtype, &self.device);
49        let out_ptr = out.ptr();
50        let numel = out.numel();
51
52        dispatch_dtype!(dtype, T => {
53            unsafe {
54                kernels::fill_kernel::<T>(
55                    out_ptr as *mut T,
56                    T::from_f64(value),
57                    numel,
58                );
59            }
60        }, "fill");
61
62        Ok(out)
63    }
64
65    fn arange(&self, start: f64, stop: f64, step: f64, dtype: DType) -> Result<Tensor<CpuRuntime>> {
66        // Use shared validation
67        let numel = validate_arange(start, stop, step)?;
68
69        // Handle empty tensor case
70        if numel == 0 {
71            return Ok(Tensor::<CpuRuntime>::empty(&[0], dtype, &self.device));
72        }
73
74        let out = Tensor::<CpuRuntime>::empty(&[numel], dtype, &self.device);
75        let out_ptr = out.ptr();
76
77        dispatch_dtype!(dtype, T => {
78            unsafe {
79                kernels::arange_kernel::<T>(out_ptr as *mut T, start, step, numel);
80            }
81        }, "arange");
82
83        Ok(out)
84    }
85
86    fn linspace(
87        &self,
88        start: f64,
89        stop: f64,
90        steps: usize,
91        dtype: DType,
92    ) -> Result<Tensor<CpuRuntime>> {
93        // linspace supports all numeric dtypes - computation is done in f64,
94        // then converted to the output dtype. This matches NumPy behavior.
95
96        // Handle edge cases
97        if steps == 0 {
98            return Ok(Tensor::<CpuRuntime>::empty(&[0], dtype, &self.device));
99        }
100
101        if steps == 1 {
102            let out = Tensor::<CpuRuntime>::empty(&[1], dtype, &self.device);
103            let out_ptr = out.ptr();
104
105            dispatch_dtype!(dtype, T => {
106                unsafe {
107                    *(out_ptr as *mut T) = T::from_f64(start);
108                }
109            }, "linspace");
110
111            return Ok(out);
112        }
113
114        let out = Tensor::<CpuRuntime>::empty(&[steps], dtype, &self.device);
115        let out_ptr = out.ptr();
116
117        dispatch_dtype!(dtype, T => {
118            unsafe {
119                kernels::linspace_kernel::<T>(out_ptr as *mut T, start, stop, steps);
120            }
121        }, "linspace");
122
123        Ok(out)
124    }
125
126    fn eye(&self, n: usize, m: Option<usize>, dtype: DType) -> Result<Tensor<CpuRuntime>> {
127        // Use shared validation
128        use crate::runtime::validate_eye;
129        let (rows, cols) = validate_eye(n, m);
130
131        // Handle edge cases
132        if rows == 0 || cols == 0 {
133            return Ok(Tensor::<CpuRuntime>::empty(
134                &[rows, cols],
135                dtype,
136                &self.device,
137            ));
138        }
139
140        let out = Tensor::<CpuRuntime>::empty(&[rows, cols], dtype, &self.device);
141        let out_ptr = out.ptr();
142
143        dispatch_dtype!(dtype, T => {
144            unsafe {
145                kernels::eye_kernel::<T>(out_ptr as *mut T, rows, cols);
146            }
147        }, "eye");
148
149        Ok(out)
150    }
151
152    fn one_hot(
153        &self,
154        indices: &Tensor<CpuRuntime>,
155        num_classes: usize,
156    ) -> Result<Tensor<CpuRuntime>> {
157        let dtype = indices.dtype();
158
159        // Validate indices are integer type
160        if !dtype.is_int() {
161            return Err(Error::UnsupportedDType {
162                dtype,
163                op: "one_hot",
164            });
165        }
166
167        if num_classes == 0 {
168            return Err(Error::InvalidArgument {
169                arg: "num_classes",
170                reason: "one_hot requires num_classes > 0".to_string(),
171            });
172        }
173
174        let indices = ensure_contiguous(indices)?;
175        let numel = indices.numel();
176
177        // Output shape = indices.shape() + [num_classes]
178        let mut out_shape = indices.shape().to_vec();
179        out_shape.push(num_classes);
180
181        let out = Tensor::<CpuRuntime>::empty(&out_shape, DType::F32, &self.device);
182        let out_ptr = out.ptr() as *mut f32;
183
184        // Zero-fill output
185        unsafe {
186            std::ptr::write_bytes(out_ptr, 0, numel * num_classes);
187        }
188
189        let indices_ptr = indices.ptr();
190
191        // Dispatch on index dtype to read indices, write into f32 output
192        dispatch_dtype!(dtype, T => {
193            unsafe {
194                kernels::one_hot_kernel::<T>(
195                    indices_ptr as *const T,
196                    out_ptr,
197                    numel,
198                    num_classes,
199                );
200            }
201        }, "one_hot");
202
203        Ok(out)
204    }
205
206    fn meshgrid(
207        &self,
208        tensors: &[&Tensor<CpuRuntime>],
209        indexing: crate::ops::MeshgridIndexing,
210    ) -> Result<Vec<Tensor<CpuRuntime>>> {
211        crate::ops::impl_generic::meshgrid_impl(tensors, indexing)
212    }
213}