Skip to main content

metatensor/data/
array.rs

1use std::os::raw::c_void;
2
3use once_cell::sync::Lazy;
4
5use dlpk::sys::{DLDevice, DLManagedTensorVersioned, DLPackVersion, DLDataType};
6use dlpk::DLPackTensor;
7
8use crate::errors::Error;
9use crate::c_api::{mts_array_t, mts_data_origin_t, mts_data_movement_t, mts_status_t};
10
11use super::MtsArray;
12
13/// The Array trait is used by metatensor to manage different kind of data array
14/// with a single API. Metatensor only knows about `Box<dyn Array>`, and
15/// manipulate the data through the functions on this trait.
16///
17/// This corresponds to the `mts_array_t` struct in metatensor-core.
18pub trait Array: std::any::Any + Send + Sync {
19    /// Get the array as a `Any` reference
20    fn as_any(&self) -> &dyn std::any::Any;
21
22    /// Get the array as a mutable `Any` reference
23    fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
24
25    /// Create a new array with the same array origin, data type, and device as
26    /// the current one, but with the requested `shape`.
27    ///
28    /// The new array should be filled with the scalar value from `fill_value`,
29    /// which must be an `MtsArray` with shape `(1,)` and the same dtype as this
30    /// array.
31    fn create(&self, shape: &[usize], fill_value: MtsArray) -> Box<dyn Array>;
32
33    /// Make a copy of this `array`
34    ///
35    /// The new array is expected to have the same array origin and data type,
36    /// but live on the given device.
37    fn copy(&self, device: DLDevice) -> Box<dyn Array>;
38
39    /// Get the shape of the array. This can be empty if the array has no shape
40    /// (e.g. a scalar).
41    fn shape(&self) -> Vec<usize>;
42
43    /// Change the shape of the array to the given `shape`
44    fn reshape(&mut self, shape: &[usize]);
45
46    /// Swap the axes `axis_1` and `axis_2` in this array
47    fn swap_axes(&mut self, axis_1: usize, axis_2: usize);
48
49    /// Set entries in `self` taking data from the `input` array.
50    ///
51    /// The `output` array is guaranteed to be created by calling
52    /// `mts_array_t::create` with one of the arrays in the same block or tensor
53    /// map as the `input`.
54    ///
55    /// The `movements` indicate where the data should be moved from `input` to
56    /// `output`.
57    ///
58    /// This function should copy data from `input[movements[i].sample_in, ...,
59    /// movements[i].properties_start_in + x]` to
60    /// `array[movements[i].sample_out, ..., movements[i].properties_start_out +
61    /// x]` for `i` up to `movements_count` and `x` up to
62    /// `movements[i].properties_length`. All indexes are 0-based.
63    fn move_data(
64        &mut self,
65        input: &dyn Array,
66        movements: &[mts_data_movement_t],
67    );
68
69    /// Get the device where this array's data resides.
70    ///
71    /// For CPU arrays this should return `DLDevice::cpu()`.
72    fn device(&self) -> DLDevice;
73
74    /// Get the data type of this array.
75    ///
76    /// This populates the `dtype` vtable slot for fast dtype queries.
77    /// Implementations should return the appropriate `DLDataType` for their
78    /// element type (e.g. float64 = `DLDataType { code: kDLFloat, bits: 64, lanes: 1 }`).
79    fn dtype(&self) -> DLDataType;
80
81    /// Convert the array to a `DLPack` tensor.
82    /// The returned pointer is owned by the caller (and cleaned up via its deleter).
83    fn as_dlpack(
84        &self,
85        device: DLDevice,
86        stream: Option<i64>,
87        max_version: DLPackVersion
88    ) -> Result<DLPackTensor, Error>;
89
90    /// Create a new array from a `DLPack` tensor, taking ownership of the
91    /// tensor's data.
92    #[allow(clippy::wrong_self_convention)]
93    fn from_dlpack(&self, dl_tensor: DLPackTensor) -> Result<Box<dyn Array>, Error>;
94}
95
96pub (super) struct RustArray {
97    impl_: Box<dyn Array>,
98    shape: Vec<usize>,
99}
100
101impl std::ops::Deref for RustArray {
102    type Target = dyn Array;
103
104    fn deref(&self) -> &Self::Target {
105        &*self.impl_
106    }
107}
108
109impl std::ops::DerefMut for RustArray {
110    fn deref_mut(&mut self) -> &mut Self::Target {
111        &mut *self.impl_
112    }
113}
114
115impl From<Box<dyn Array>> for MtsArray {
116    fn from(value: Box<dyn Array>) -> Self {
117        let shape = value.shape();
118        let array = RustArray {
119            impl_: value,
120            shape,
121        };
122
123        let raw = mts_array_t {
124            ptr: Box::into_raw(Box::new(array)).cast(),
125            origin: Some(rust_array_origin),
126            device: Some(rust_array_device),
127            dtype: Some(rust_array_dtype),
128            as_dlpack: Some(rust_array_as_dlpack),
129            from_dlpack: Some(rust_array_from_dlpack),
130            shape: Some(rust_array_shape),
131            reshape: Some(rust_array_reshape),
132            swap_axes: Some(rust_array_swap_axes),
133            create: Some(rust_array_create),
134            copy: Some(rust_array_copy),
135            destroy: Some(rust_array_destroy),
136            move_data: Some(rust_array_move_data),
137        };
138
139        return MtsArray::from_raw(raw);
140    }
141}
142
143impl<T> From<T> for MtsArray where T: Array + 'static {
144    fn from(value: T) -> Self {
145        let boxed = Box::new(value) as Box<dyn Array>;
146        return MtsArray::from(boxed);
147    }
148}
149
150macro_rules! check_pointers {
151    ($pointer: ident) => {
152        if $pointer.is_null() {
153            panic!(
154                "got invalid NULL pointer for {} at {}:{}",
155                stringify!($pointer), file!(), line!()
156            );
157        }
158    };
159    ($($pointer: ident),* $(,)?) => {
160        $(check_pointers!($pointer);)*
161    }
162}
163
164pub(super) static RUST_DATA_ORIGIN: Lazy<mts_data_origin_t> = Lazy::new(|| {
165    super::origin::register_data_origin("RustArray".into()).expect("failed to register a new origin")
166});
167
168/******************************************************************************/
169/// Implementation of `mts_array_t.origin` using `RustArray`
170unsafe extern "C" fn rust_array_origin(
171    array: *const c_void,
172    origin: *mut mts_data_origin_t
173) -> mts_status_t {
174    crate::errors::catch_unwind(|| {
175        check_pointers!(array, origin);
176        unsafe {
177            *origin = *RUST_DATA_ORIGIN;
178        }
179
180        Ok(())
181    })
182}
183
184/// Implementation of `mts_array_t.device` using `RustArray`
185unsafe extern "C" fn rust_array_device(
186    array: *const c_void,
187    device: *mut DLDevice,
188) -> mts_status_t {
189    crate::errors::catch_unwind(|| {
190        check_pointers!(array, device);
191        let array = array.cast::<RustArray>();
192        unsafe {
193            *device = (*array).impl_.device();
194        }
195
196        Ok(())
197    })
198}
199
200/// Implementation of `mts_array_t.dtype` using `RustArray`
201unsafe extern "C" fn rust_array_dtype(
202    array: *const c_void,
203    dtype: *mut DLDataType,
204) -> mts_status_t {
205    crate::errors::catch_unwind(|| {
206        check_pointers!(array, dtype);
207        let array = array.cast::<RustArray>();
208        unsafe {
209            *dtype = (*array).impl_.dtype();
210        }
211
212        Ok(())
213    })
214}
215
216/// Implementation of `mts_array_t.shape` using `RustArray`
217unsafe extern "C" fn rust_array_shape(
218    array: *const c_void,
219    shape: *mut *const usize,
220    shape_count: *mut usize,
221) -> mts_status_t {
222    crate::errors::catch_unwind(|| {
223        check_pointers!(array, shape, shape_count);
224        let array = array.cast::<RustArray>();
225        unsafe {
226            let rust_shape = &(*array).shape;
227
228            *shape = rust_shape.as_ptr();
229            *shape_count = rust_shape.len();
230        }
231
232        Ok(())
233    })
234}
235
236/// Implementation of `mts_array_t.reshape` using `RustArray`
237#[allow(clippy::cast_possible_truncation)]
238unsafe extern "C" fn rust_array_reshape(
239    array: *mut c_void,
240    shape: *const usize,
241    shape_count: usize,
242) -> mts_status_t {
243    crate::errors::catch_unwind(|| {
244        check_pointers!(array);
245        let array = array.cast::<RustArray>();
246
247        let shape = if shape_count == 0 {
248            &[]
249        } else {
250            check_pointers!(shape);
251            unsafe { std::slice::from_raw_parts(shape, shape_count) }
252        };
253
254        unsafe {
255            (*array).impl_.reshape(shape);
256            (*array).shape = shape.to_vec();
257        }
258
259        Ok(())
260    })
261}
262
263/// Implementation of `mts_array_t.swap_axes` using `RustArray`
264#[allow(clippy::cast_possible_truncation)]
265unsafe extern "C" fn rust_array_swap_axes(
266    array: *mut c_void,
267    axis_1: usize,
268    axis_2: usize,
269) -> mts_status_t {
270    crate::errors::catch_unwind(|| {
271        check_pointers!(array);
272        let array = array.cast::<RustArray>();
273        unsafe {
274            (*array).impl_.swap_axes(axis_1, axis_2);
275            (*array).shape.swap(axis_1, axis_2);
276        }
277
278        Ok(())
279    })
280}
281
282/// Implementation of `mts_array_t.create` using `RustArray`
283#[allow(clippy::cast_possible_truncation)]
284unsafe extern "C" fn rust_array_create(
285    array: *const c_void,
286    shape: *const usize,
287    shape_count: usize,
288    fill_value: mts_array_t,
289    array_storage: *mut mts_array_t,
290) -> mts_status_t {
291    crate::errors::catch_unwind(|| {
292        check_pointers!(array, array_storage);
293        let array = array.cast::<RustArray>();
294
295        let shape = if shape_count == 0 {
296            &[]
297        } else {
298            check_pointers!(shape);
299            unsafe { std::slice::from_raw_parts(shape, shape_count) }
300        };
301
302        unsafe {
303            let new_array = (*array).impl_.create(shape, MtsArray::from_raw(fill_value));
304            let new_array = MtsArray::from(new_array);
305
306            *array_storage = new_array.into_raw();
307        }
308
309        Ok(())
310    })
311}
312
313/// Implementation of `mts_array_t.copy` using `RustArray`
314unsafe extern "C" fn rust_array_copy(
315    array: *const c_void,
316    device: DLDevice,
317    new_array: *mut mts_array_t
318) -> mts_status_t {
319    crate::errors::catch_unwind(|| {
320        check_pointers!(array, new_array);
321        let array = array.cast::<RustArray>();
322
323        unsafe {
324            let copy = (*array).impl_.copy(device);
325            let copy = MtsArray::from(copy);
326            *new_array = copy.into_raw();
327        }
328
329        Ok(())
330    })
331}
332
333/// Implementation of `mts_array_t.destroy` for `RustArray`
334unsafe extern "C" fn rust_array_destroy(
335    array: *mut c_void,
336) {
337    if !array.is_null() {
338        let array = array.cast::<RustArray>();
339        unsafe {
340            std::mem::drop(Box::from_raw(array));
341        }
342    }
343}
344
345/// Implementation of `mts_array_t.move_sample` using `RustArray`
346#[allow(clippy::cast_possible_truncation)]
347unsafe extern "C" fn rust_array_move_data(
348    output: *mut c_void,
349    input: *const c_void,
350    movements: *const mts_data_movement_t,
351    movements_count: usize,
352) -> mts_status_t {
353    crate::errors::catch_unwind(|| {
354        check_pointers!(output, input);
355        let output = output.cast::<RustArray>();
356        let input = input.cast::<RustArray>();
357
358        let movements = if movements_count == 0 {
359            &[]
360        } else {
361            check_pointers!(movements);
362            unsafe { std::slice::from_raw_parts(movements, movements_count) }
363        };
364
365        unsafe {
366            (*output).impl_.move_data(&*(*input).impl_, movements);
367        }
368
369        Ok(())
370    })
371}
372
373/// Implementation of `mts_array_t.as_dlpack` using `RustArray`
374unsafe extern "C" fn rust_array_as_dlpack(
375    array: *mut c_void,
376    dl_tensor: *mut *mut DLManagedTensorVersioned,
377    device: DLDevice,
378    stream: *const i64,
379    max_version: DLPackVersion,
380) -> mts_status_t {
381    crate::errors::catch_unwind(|| {
382        check_pointers!(array, dl_tensor);
383        let array = array.cast::<RustArray>();
384        unsafe {
385            let stream_opt = stream.as_ref().copied();
386            let tensor = (*array).impl_.as_dlpack(device, stream_opt, max_version)?;
387
388            *dl_tensor = tensor.into_raw().as_ptr();
389        }
390        Ok(())
391    })
392}
393
394/// Implementation of `mts_array_t.from_dlpack` using `RustArray`
395unsafe extern "C" fn rust_array_from_dlpack(
396    array: *const c_void,
397    dl_tensor: *mut DLManagedTensorVersioned,
398    new_array: *mut mts_array_t,
399) -> mts_status_t {
400    crate::errors::catch_unwind(|| {
401        check_pointers!(array, dl_tensor, new_array);
402        let array = array.cast::<RustArray>();
403        unsafe {
404            let dl_tensor = DLPackTensor::from_ptr(dl_tensor);
405            let new_rust_array = (*array).impl_.from_dlpack(dl_tensor)?;
406            *new_array = MtsArray::from(new_rust_array).into_raw();
407        }
408
409        Ok(())
410    })
411}