Skip to main content

diffsol_c/
tensor.rs

1use crate::{
2    error::DiffsolRtError,
3    scalar_type::{Scalar, ScalarType},
4};
5use diffsol::{
6    error::DiffsolError, Context, DenseMatrix, FaerScalar, Matrix, MatrixCommon, NalgebraScalar,
7    Vector, VectorCommon,
8};
9use num_traits::FromPrimitive;
10use std::any::Any;
11use std::marker::PhantomData;
12use std::sync::Arc;
13
14/// The maximum number of axes of a [Tensor] or a [TensorView].
15pub const MAX_NDIM: usize = 3;
16
17/// Convert a value into a [Tensor] that owns it, with no copy of the data.
18pub(crate) trait ToTensor {
19    fn to_tensor(self) -> Tensor;
20}
21
22/// Convert a [TensorView] to a diffsol vector or dense matrix. The scalar type of the view can be
23/// `f32` or `f64`.
24pub(crate) trait FromTensor: Sized {
25    /// The diffsol context of the new value.
26    type Context;
27    fn from_tensor(tensor: TensorView<'_>, ctx: &Self::Context) -> Result<Self, DiffsolRtError>;
28}
29
30/// All the values of `tensor` in column-major order (the first axis changes fastest), as `T`
31/// values.
32pub(crate) fn column_major_values<T: FromPrimitive>(
33    tensor: TensorView<'_>,
34) -> Result<Vec<T>, DiffsolRtError> {
35    Ok(match tensor.dtype() {
36        ScalarType::F32 => tensor
37            .columns::<f32>()?
38            .flatten()
39            .map(|&x| T::from_f32(x).unwrap())
40            .collect(),
41        ScalarType::F64 => tensor
42            .columns::<f64>()?
43            .flatten()
44            .map(|&x| T::from_f64(x).unwrap())
45            .collect(),
46    })
47}
48
49/// Read the values of `tensor` for a diffsol value with `nbatch` lanes.
50///
51/// The column-major order of a tensor with the lane axis last is the storage order of diffsol:
52/// a vector stores lane `b`, value `i` at `b * n + i`, and a dense matrix stores lane `b`, row
53/// `i`, column `j` at `(b * ncols + j) * nrows + i`. `tensor` must have `nbatch` lanes or 1 lane
54/// on its last axis. The values of one lane are copied to all `nbatch` lanes, so the result
55/// always has a value for each lane.
56fn lane_values<T: FromPrimitive + Clone>(
57    tensor: TensorView<'_>,
58    nbatch: usize,
59) -> Result<Vec<T>, DiffsolRtError> {
60    let nlanes = *tensor.shape().last().unwrap();
61    if nlanes != nbatch && nlanes != 1 {
62        return Err(
63            DiffsolError::Other(format!("Expected 1 or {nbatch} lanes, got {nlanes}")).into(),
64        );
65    }
66    let values: Vec<T> = column_major_values(tensor)?;
67    if nlanes == nbatch {
68        return Ok(values);
69    }
70    let mut lanes = Vec::with_capacity(values.len() * nbatch);
71    for _ in 0..nbatch {
72        lanes.extend_from_slice(&values);
73    }
74    Ok(lanes)
75}
76
77/// Convert an `[n, nlanes]` array to a diffsol vector with the context `ctx`.
78fn vector_from<V: Vector<T: Scalar>>(
79    tensor: TensorView<'_>,
80    ctx: &V::C,
81) -> Result<V, DiffsolRtError> {
82    tensor.expect_ndim(2)?;
83    Ok(V::from_vec(lane_values(tensor, ctx.nbatch())?, ctx.clone()))
84}
85
86/// Convert an `[nrows, ncols, nlanes]` array to a diffsol dense matrix with the context `ctx`.
87fn dense_matrix_from<M: DenseMatrix<T: Scalar>>(
88    tensor: TensorView<'_>,
89    ctx: &M::C,
90) -> Result<M, DiffsolRtError> {
91    tensor.expect_ndim(3)?;
92    let (nrows, ncols) = (tensor.shape()[0], tensor.shape()[1]);
93    let data = lane_values(tensor, ctx.nbatch())?;
94    Ok(M::from_vec(nrows, ncols, data, ctx.clone()))
95}
96
97// A diffsol vector converts from an `[n, nlanes]` array, and a diffsol dense matrix from an
98// `[nrows, ncols, nlanes]` array. `nlanes` must be the number of lanes of the context, or 1. If
99// it is 1, all the lanes get the values of the one lane.
100
101impl<T: Scalar + FaerScalar> FromTensor for diffsol::FaerVec<T> {
102    type Context = diffsol::FaerContext;
103    fn from_tensor(tensor: TensorView<'_>, ctx: &Self::Context) -> Result<Self, DiffsolRtError> {
104        vector_from(tensor, ctx)
105    }
106}
107
108impl<T: Scalar + NalgebraScalar> FromTensor for diffsol::NalgebraVec<T> {
109    type Context = diffsol::NalgebraContext;
110    fn from_tensor(tensor: TensorView<'_>, ctx: &Self::Context) -> Result<Self, DiffsolRtError> {
111        vector_from(tensor, ctx)
112    }
113}
114
115impl<T: Scalar + FaerScalar> FromTensor for diffsol::FaerMat<T> {
116    type Context = diffsol::FaerContext;
117    fn from_tensor(tensor: TensorView<'_>, ctx: &Self::Context) -> Result<Self, DiffsolRtError> {
118        dense_matrix_from(tensor, ctx)
119    }
120}
121
122impl<T: Scalar + NalgebraScalar> FromTensor for diffsol::NalgebraMat<T> {
123    type Context = diffsol::NalgebraContext;
124    fn from_tensor(tensor: TensorView<'_>, ctx: &Self::Context) -> Result<Self, DiffsolRtError> {
125        dense_matrix_from(tensor, ctx)
126    }
127}
128
129/// The layout of a faer matrix, with the data pointer `ptr` of `m`.
130fn faer_layout<T: Scalar + FaerScalar>(m: &faer::Mat<T>, ptr: *const T) -> RawLayout {
131    // faer gives the strides in values; a matrix that owns its data has positive strides
132    let size = std::mem::size_of::<T>();
133    let strides = [
134        m.row_stride() as usize * size,
135        m.col_stride() as usize * size,
136    ];
137    let shape = [m.nrows(), m.ncols()];
138    RawLayout::valid(ptr as *const u8, &shape, &strides, T::scalar_type())
139}
140
141/// The layout of a nalgebra matrix, with the data pointer `ptr` of `m`.
142fn nalgebra_layout<T: Scalar + NalgebraScalar>(
143    m: &nalgebra::DMatrix<T>,
144    ptr: *const T,
145) -> RawLayout {
146    // nalgebra gives the strides in values
147    let size = std::mem::size_of::<T>();
148    let (rs, cs) = m.strides();
149    let shape = [m.nrows(), m.ncols()];
150    RawLayout::valid(
151        ptr as *const u8,
152        &shape,
153        &[rs * size, cs * size],
154        T::scalar_type(),
155    )
156}
157
158/// Give the storage of a diffsol dense matrix, an `nrows × (nbatch * ncols)` matrix where the
159/// columns of lane `b` are the column block `b`, the shape `[nrows, ncols, nbatch]`.
160fn matrix_lanes(storage: RawLayout, nbatch: usize) -> RawLayout {
161    let (nrows, ncols) = (storage.shape[0], storage.shape[1] / nbatch);
162    let (row_stride, col_stride) = (storage.strides[0], storage.strides[1]);
163    storage.with_layout(
164        &[nrows, ncols, nbatch],
165        &[row_stride, col_stride, ncols * col_stride],
166    )
167}
168
169/// A value that a [Tensor] can show with no copy of the data.
170///
171/// A diffsol vector or dense matrix shows its storage with the lane axis last. The storage of a
172/// diffsol vector is an `n × nbatch` matrix with one column for each lane, which is already the
173/// shape `[n, nbatch]`. A dense matrix has the shape `[nrows, ncols, nbatch]`. A `Vec<T>` has the
174/// shape `[len]`.
175///
176/// # Safety
177/// [TensorLayout::tensor_layout] must give the layout of data that the value holds on the heap,
178/// so that the data does not move when the value moves. The data must not change while the value
179/// is borrowed (no interior mutability).
180pub unsafe trait TensorLayout {
181    /// The layout of the data.
182    fn tensor_layout(&self) -> RawLayout;
183}
184
185/// A diffsol vector that a [TensorViewMut] can show, so that a C callback can write its values
186/// with no copy. The layout is `[n, nbatch]`, the same as [TensorLayout::tensor_layout].
187///
188/// # Safety
189/// As for [TensorLayout]. [TensorLayoutMut::tensor_layout_mut] must give the same layout as
190/// [TensorLayout::tensor_layout], with a pointer that allows writes to each value of the layout
191/// while the value is mutably borrowed.
192pub unsafe trait TensorLayoutMut: TensorLayout {
193    /// The layout of the data, with a pointer that allows writes.
194    fn tensor_layout_mut(&mut self) -> RawLayout;
195}
196
197// SAFETY: faer and nalgebra keep the values of a vector or a matrix on the heap, and change them
198// only through `&mut`
199unsafe impl<T: Scalar + FaerScalar> TensorLayout for diffsol::FaerVec<T> {
200    fn tensor_layout(&self) -> RawLayout {
201        faer_layout(self.inner(), self.inner().as_ptr())
202    }
203}
204
205// SAFETY: the pointer comes from `&mut` of the faer matrix, so writes through it are allowed
206unsafe impl<T: Scalar + FaerScalar> TensorLayoutMut for diffsol::FaerVec<T> {
207    fn tensor_layout_mut(&mut self) -> RawLayout {
208        let ptr = self.inner_mut().as_ptr_mut();
209        faer_layout(self.inner(), ptr)
210    }
211}
212
213// SAFETY: as for `FaerVec`
214unsafe impl<T: Scalar + NalgebraScalar> TensorLayout for diffsol::NalgebraVec<T> {
215    fn tensor_layout(&self) -> RawLayout {
216        nalgebra_layout(self.inner(), self.inner().as_ptr())
217    }
218}
219
220// SAFETY: the pointer comes from `&mut` of the nalgebra matrix, so writes through it are allowed
221unsafe impl<T: Scalar + NalgebraScalar> TensorLayoutMut for diffsol::NalgebraVec<T> {
222    fn tensor_layout_mut(&mut self) -> RawLayout {
223        let ptr = self.inner_mut().as_mut_ptr();
224        nalgebra_layout(self.inner(), ptr)
225    }
226}
227
228// SAFETY: as for `FaerVec`
229unsafe impl<T: Scalar + FaerScalar> TensorLayout for diffsol::FaerMat<T> {
230    fn tensor_layout(&self) -> RawLayout {
231        let layout = faer_layout(self.inner(), self.inner().as_ptr());
232        matrix_lanes(layout, self.context().nbatch())
233    }
234}
235
236// SAFETY: as for `FaerVec`
237unsafe impl<T: Scalar + NalgebraScalar> TensorLayout for diffsol::NalgebraMat<T> {
238    fn tensor_layout(&self) -> RawLayout {
239        let layout = nalgebra_layout(self.inner(), self.inner().as_ptr());
240        matrix_lanes(layout, self.context().nbatch())
241    }
242}
243
244// SAFETY: a `Vec` keeps its values on the heap, and changes them only through `&mut`
245unsafe impl<T: Scalar> TensorLayout for Vec<T> {
246    fn tensor_layout(&self) -> RawLayout {
247        RawLayout::valid_col_major(self.as_ptr() as *const u8, &[self.len()], T::scalar_type())
248    }
249}
250
251impl<V: TensorLayout + 'static> ToTensor for V {
252    fn to_tensor(self) -> Tensor {
253        let layout = self.tensor_layout();
254        // SAFETY: `TensorLayout` guarantees that the data is on the heap of `self`, so it does
255        // not move when `self` moves into the tensor, and that it does not change
256        unsafe { Tensor::from_layout(self, layout) }
257    }
258}
259
260/// A tensor of `part(&owner)`, a value inside `owner`, with no copy of the data.
261///
262/// The tensor holds a clone of `owner`, so the data stays valid after all the other clones of
263/// `owner` are dropped. An `Arc` gives only shared access, so the data does not change while the
264/// tensor exists.
265pub(crate) fn shared_tensor<O, V>(owner: Arc<O>, part: impl FnOnce(&O) -> &V) -> Tensor
266where
267    O: ?Sized + 'static,
268    V: TensorLayout,
269{
270    let layout = part(&owner).tensor_layout();
271    // SAFETY: `part` gives a value that `owner` holds (or a static value), and the `Arc`
272    // allocation does not move when `owner` moves into the tensor. `TensorLayout` guarantees that
273    // the data of that value is on the heap and does not change while it is borrowed
274    unsafe { Tensor::from_layout(owner, layout) }
275}
276
277fn scalar_size(dtype: ScalarType) -> usize {
278    match dtype {
279        ScalarType::F32 => std::mem::size_of::<f32>(),
280        ScalarType::F64 => std::mem::size_of::<f64>(),
281    }
282}
283
284fn layout_error(message: String) -> DiffsolRtError {
285    DiffsolError::Other(message).into()
286}
287
288/// Check the layout rules of [Tensor] and [TensorView] (the [Tensor] docs give the rules).
289fn check_layout(
290    ptr: *const u8,
291    shape: &[usize],
292    strides: &[usize],
293    dtype: ScalarType,
294) -> Result<(), DiffsolRtError> {
295    let ndim = shape.len();
296    if ndim == 0 || ndim > MAX_NDIM {
297        return Err(layout_error(format!(
298            "A tensor has 1 to {MAX_NDIM} axes, got {ndim}"
299        )));
300    }
301    if strides.len() != ndim {
302        return Err(layout_error(format!(
303            "Expected {ndim} strides, got {}",
304            strides.len()
305        )));
306    }
307    let size = scalar_size(dtype);
308    if shape[0] > 1 && strides[0] != size {
309        return Err(layout_error(format!(
310            "The values of a column are not adjacent: strides[0] is {} bytes, expected {size}",
311            strides[0]
312        )));
313    }
314    // the stride of an axis of length 1 is never used
315    let strides_aligned = shape
316        .iter()
317        .zip(strides)
318        .all(|(&n, &stride)| n <= 1 || stride.is_multiple_of(size));
319    // the alignment of `f32` and `f64` is their size
320    if !(ptr as usize).is_multiple_of(size) || !strides_aligned {
321        return Err(layout_error("The data is not aligned".to_string()));
322    }
323    Ok(())
324}
325
326/// Check that the scalar type of the values is `T`.
327fn check_dtype<T: Scalar>(dtype: ScalarType) -> Result<(), DiffsolRtError> {
328    if dtype != T::scalar_type() {
329        return Err(layout_error("Data type mismatch".to_string()));
330    }
331    Ok(())
332}
333
334/// The byte offset from the data pointer of the value at `index`, on the axes that `index`
335/// covers. `shape` and `strides` are those of the same axes.
336///
337/// # Panics
338/// If `index` does not have one value for each axis, or a value is out of range.
339fn byte_offset(shape: &[usize], strides: &[usize], index: &[usize]) -> usize {
340    assert_eq!(index.len(), shape.len(), "wrong number of indices");
341    index
342        .iter()
343        .zip(shape)
344        .zip(strides)
345        .map(|((&i, &n), &stride)| {
346            assert!(i < n, "index {i} is out of range for an axis of length {n}");
347            i * stride
348        })
349        .sum()
350}
351
352/// The data pointer, the scalar type, the shape and the byte strides of a [Tensor], a
353/// [TensorView] or a [TensorViewMut]. A `RawLayout` always obeys the layout rules (the [Tensor]
354/// docs give the rules): each constructor checks them. It does not own or borrow the data, so the
355/// reads are `unsafe`.
356///
357/// It has the C layout, so a C callback can read the fields of a [TensorView] directly. The value
358/// at the index `(i0, i1, ...)` is at `ptr + i0 * strides[0] + i1 * strides[1] + ...` (in bytes).
359/// Only the first `ndim` values of `shape` and `strides` are used.
360#[repr(C)]
361#[derive(Clone, Copy, Debug)]
362pub struct RawLayout {
363    /// The pointer to the first value.
364    ptr: *const u8,
365    /// The scalar type of the values.
366    dtype: ScalarType,
367    /// The number of axes, 1 to [MAX_NDIM].
368    ndim: usize,
369    /// The length of each axis. Only the first `ndim` values are used.
370    shape: [usize; MAX_NDIM],
371    /// The stride of each axis, in bytes. Only the first `ndim` values are used.
372    strides: [usize; MAX_NDIM],
373}
374
375impl RawLayout {
376    /// A layout with a shape and strides (in bytes), or an error if they do not obey the layout
377    /// rules.
378    fn new(
379        ptr: *const u8,
380        shape: &[usize],
381        strides: &[usize],
382        dtype: ScalarType,
383    ) -> Result<Self, DiffsolRtError> {
384        check_layout(ptr, shape, strides, dtype)?;
385        let ndim = shape.len();
386        let mut layout = Self {
387            ptr,
388            dtype,
389            ndim,
390            shape: [0; MAX_NDIM],
391            strides: [0; MAX_NDIM],
392        };
393        layout.shape[..ndim].copy_from_slice(shape);
394        layout.strides[..ndim].copy_from_slice(strides);
395        Ok(layout)
396    }
397    /// A column-major (Fortran) layout of `shape`, or an error if it does not obey the layout
398    /// rules (the number of axes, or the alignment of `ptr`).
399    fn col_major(
400        ptr: *const u8,
401        shape: &[usize],
402        dtype: ScalarType,
403    ) -> Result<Self, DiffsolRtError> {
404        let mut strides = [0; MAX_NDIM];
405        let mut stride = scalar_size(dtype);
406        for (s, &n) in strides.iter_mut().zip(shape) {
407            *s = stride;
408            stride *= n;
409        }
410        Self::new(ptr, shape, &strides[..shape.len().min(MAX_NDIM)], dtype)
411    }
412    /// As [RawLayout::new], for a layout that the caller knows obeys the layout rules.
413    ///
414    /// # Panics
415    /// If the layout does not obey the layout rules.
416    fn valid(ptr: *const u8, shape: &[usize], strides: &[usize], dtype: ScalarType) -> Self {
417        Self::new(ptr, shape, strides, dtype)
418            .unwrap_or_else(|err| panic!("invalid tensor layout: {err}"))
419    }
420    /// As [RawLayout::col_major], for a layout that the caller knows obeys the layout rules.
421    ///
422    /// # Panics
423    /// If the layout does not obey the layout rules.
424    fn valid_col_major(ptr: *const u8, shape: &[usize], dtype: ScalarType) -> Self {
425        Self::col_major(ptr, shape, dtype)
426            .unwrap_or_else(|err| panic!("invalid tensor layout: {err}"))
427    }
428    fn shape(&self) -> &[usize] {
429        &self.shape[..self.ndim]
430    }
431    fn strides(&self) -> &[usize] {
432        &self.strides[..self.ndim]
433    }
434    /// The same data with a new shape and new strides (in bytes).
435    ///
436    /// # Panics
437    /// If the new layout has a different number of values, can reach a byte past the last value
438    /// of the old layout, or does not obey the layout rules.
439    fn with_layout(self, shape: &[usize], strides: &[usize]) -> Self {
440        assert_eq!(
441            shape.iter().product::<usize>(),
442            self.shape().iter().product::<usize>()
443        );
444        let last_byte = |shape: &[usize], strides: &[usize]| -> usize {
445            shape
446                .iter()
447                .zip(strides)
448                .map(|(n, s)| n.saturating_sub(1) * s)
449                .sum()
450        };
451        // a layout that reads past the old data would make the reads unsound
452        assert!(last_byte(shape, strides) <= last_byte(self.shape(), self.strides()));
453        Self::valid(self.ptr, shape, strides, self.dtype)
454    }
455    /// The `[n, ninner, nouter]` layout of a diffsol vector with the `[n, nlanes]` layout `self`,
456    /// in an operation that writes `ninner * nouter` lanes.
457    ///
458    /// diffsol broadcasts an operand with fewer lanes in groups: each lane of an operand with
459    /// `nouter` lanes repeats over `ninner` adjacent output lanes. The new layout shows this with
460    /// strides of 0, so lane `(k, b)` of each operand is at `k * strides[1] + b * strides[2]`:
461    /// - `nlanes == ninner * nouter`: the strides `[s, c, ninner * c]`;
462    /// - `nlanes == nouter`: the strides `[s, 0, c]`;
463    /// - `nlanes == 1`: the strides `[s, 0, 0]`.
464    ///
465    /// `s` is the size of one value, and `c` is the column stride of `self`.
466    ///
467    /// # Panics
468    /// If `self` does not have 2 axes, or `nlanes` is not one of the values above.
469    pub(crate) fn batch_lanes(self, ninner: usize, nouter: usize) -> Self {
470        assert_eq!(self.ndim, 2, "a vector layout has 2 axes");
471        let (n, nlanes) = (self.shape[0], self.shape[1]);
472        let (s, c) = (self.strides[0], self.strides[1]);
473        let strides = if nlanes == ninner * nouter {
474            [s, c, ninner * c]
475        } else if nlanes == nouter {
476            [s, 0, c]
477        } else if nlanes == 1 {
478            [s, 0, 0]
479        } else {
480            panic!("cannot broadcast {nlanes} lanes into {ninner} x {nouter} lanes");
481        };
482        // each value of the new layout is a value of `self`, so the reads stay in the data
483        Self::valid(self.ptr, &[n, ninner, nouter], &strides, self.dtype)
484    }
485    /// Check that no two indices of the layout give the same value, so that a mutable view can
486    /// give each value one time. In column-major order, the stride of each axis must be at least
487    /// the extent of the axes before it.
488    fn check_no_overlap(&self) -> Result<(), DiffsolRtError> {
489        let mut extent = scalar_size(self.dtype);
490        for (&n, &stride) in self.shape().iter().zip(self.strides()) {
491            if n > 1 {
492                if stride < extent {
493                    return Err(layout_error(
494                        "The values of a mutable array overlap".to_string(),
495                    ));
496                }
497                extent += (n - 1) * stride;
498            }
499        }
500        Ok(())
501    }
502    /// Keep only the first `len` indices of the axis `axis`. The strides do not change, so each
503    /// index that stays gives the same value.
504    ///
505    /// # Panics
506    /// If `len` is larger than the length of the axis.
507    fn truncate_axis(mut self, axis: usize, len: usize) -> Self {
508        let n = self.shape()[axis];
509        assert!(
510            len <= n,
511            "cannot keep {len} indices of an axis of length {n}"
512        );
513        self.shape[axis] = len;
514        self
515    }
516    /// The pointer to the value at `index`, which is aligned (from the layout rules).
517    ///
518    /// # Panics
519    /// If `index` does not have one value for each axis, or a value is out of range.
520    fn index_ptr<T: Scalar>(&self, index: &[usize]) -> Result<*const T, DiffsolRtError> {
521        check_dtype::<T>(self.dtype)?;
522        let offset = byte_offset(self.shape(), self.strides(), index);
523        // the index is in range, so the offset stays in the data of the layout
524        Ok(self.ptr.wrapping_add(offset) as *const T)
525    }
526    /// The pointer to the first value of the column `[:, index...]`, and its length. The layout
527    /// rules make the values of the column adjacent and aligned.
528    ///
529    /// # Panics
530    /// If `index` does not have one value for each axis after the first, or a value is out of
531    /// range.
532    fn column_ptr<T: Scalar>(&self, index: &[usize]) -> Result<(*const T, usize), DiffsolRtError> {
533        check_dtype::<T>(self.dtype)?;
534        let offset = byte_offset(&self.shape()[1..], &self.strides()[1..], index);
535        // the index is in range, so the offset stays in the data of the layout
536        Ok((self.ptr.wrapping_add(offset) as *const T, self.shape[0]))
537    }
538    /// A reference to the value at `index`.
539    ///
540    /// # Safety
541    /// For each index in the shape, the data must have a valid value of the type `dtype`, which
542    /// stays valid and does not change during `'a`.
543    unsafe fn index<'a, T: Scalar>(&self, index: &[usize]) -> Result<&'a T, DiffsolRtError> {
544        let ptr = self.index_ptr::<T>(index)?;
545        // SAFETY: the index is in range, so the caller guarantees a valid value of type `T` there
546        // during `'a`, and the layout rules make it aligned
547        Ok(unsafe { &*ptr })
548    }
549    /// The column `[:, index...]`, for a scalar type `T` that the caller checked.
550    ///
551    /// # Safety
552    /// As for [RawLayout::index], and the scalar type must be `T`.
553    unsafe fn column_of_type<'a, T: Scalar>(&self, index: &[usize]) -> &'a [T] {
554        let offset = byte_offset(&self.shape()[1..], &self.strides()[1..], index);
555        let len = self.shape[0];
556        if len == 0 {
557            return &[];
558        }
559        // SAFETY: the index is in range, and the layout rules make the `len` values of the column
560        // adjacent and aligned. The caller guarantees that they are valid values of type `T`
561        // during `'a`
562        unsafe { std::slice::from_raw_parts(self.ptr.add(offset) as *const T, len) }
563    }
564    /// The column `[:, index...]`, one slice of `shape[0]` values.
565    ///
566    /// # Safety
567    /// As for [RawLayout::index].
568    unsafe fn column<'a, T: Scalar>(&self, index: &[usize]) -> Result<&'a [T], DiffsolRtError> {
569        check_dtype::<T>(self.dtype)?;
570        // SAFETY: the scalar type is `T`, and the caller guarantees the data
571        Ok(unsafe { self.column_of_type(index) })
572    }
573    /// All the columns, in column-major order (the index on axis 1 changes fastest).
574    ///
575    /// # Safety
576    /// As for [RawLayout::index].
577    unsafe fn columns<'a, T: Scalar>(
578        &self,
579    ) -> Result<impl Iterator<Item = &'a [T]> + 'a, DiffsolRtError> {
580        check_dtype::<T>(self.dtype)?;
581        let layout = *self;
582        let ncolumns: usize = layout.shape()[1..].iter().product();
583        Ok((0..ncolumns).map(move |mut c| {
584            // the index of column `c` on the axes `1..ndim`, in column-major order
585            let mut index = [0; MAX_NDIM];
586            for (i, &n) in index.iter_mut().zip(&layout.shape()[1..]) {
587                *i = c % n;
588                c /= n;
589            }
590            // SAFETY: the scalar type is `T`, and the caller guarantees the data
591            unsafe { layout.column_of_type(&index[..layout.ndim - 1]) }
592        }))
593    }
594}
595
596/// An N-D array of `f32` or `f64` values that Rust owns, and that the host language (C, Python)
597/// reads without a copy. The outputs of this crate are `Tensor` values. The inputs are
598/// [TensorView] values.
599///
600/// # Data layout
601/// A `Tensor` has a data pointer, a scalar type (`dtype`), a `shape` and a `strides` value for
602/// each axis. The strides are in bytes. The value at the index `(i0, i1, ...)` is at the byte
603/// offset `i0 * strides[0] + i1 * strides[1] + ...` from the data pointer.
604///
605/// The arrays use the column-major (Fortran) order of diffsol: the first axis changes fastest, and
606/// the lane axis is the last axis. The arrays that this crate makes from diffsol values have these
607/// layouts (`s` is the size of one value, and `c` is the column stride):
608/// - A diffsol vector is `[n, nbatch]`, with the strides `[s, c]`. Each lane is one column.
609/// - A diffsol dense matrix is `[nrows, ncols, nbatch]`, with the strides `[s, c, ncols * c]`.
610///   Each lane is a column-major matrix.
611/// - A Rust `Vec<T>`, for example `ts`, is `[n]`, with the stride `[s]`.
612///
613/// `c` is `nrows * s` for nalgebra. It can be larger for faer, which pads its columns. The padding
614/// is not part of the array, and its memory is not initialized. A solution can also have spare
615/// columns after the last time of each lane, which are not part of the array.
616///
617/// # Layout rules
618/// Each `Tensor` and [TensorView] obeys these rules, which the constructors check:
619/// - It has 1 to [MAX_NDIM] axes.
620/// - The values of a column, `[:, j, k]`, are adjacent: `strides[0]` is the size of one value
621///   (or the first axis has 1 value or none).
622/// - The data pointer is aligned, and each stride is a multiple of the size of one value.
623///
624/// So each column is one slice, which [Tensor::column] and [Tensor::columns] give with no copy.
625/// The padding is only after a column, so a column never contains it. [Tensor::index] gives one
626/// value.
627///
628/// # Ownership
629/// A `Tensor` always owns the value that holds its data (the `owner`), so the data stays valid
630/// while the array exists.
631pub struct Tensor {
632    layout: RawLayout,
633    /// The value that holds the data. It is only kept alive, never read.
634    _owner: Box<dyn Any>,
635}
636
637impl Tensor {
638    /// Make an array that owns `owner`, with a shape and strides (in bytes) that obey the layout
639    /// rules. The [Tensor] docs give the data layout and the rules.
640    ///
641    /// # Panics
642    /// If the layout does not obey the layout rules.
643    ///
644    /// # Safety
645    /// `ptr` must point into data that `owner` holds on the heap, so that the data does not move
646    /// when `owner` moves into the array. For each index in `shape`, `ptr` plus the byte offset
647    /// from `strides` must point to a valid value of the type `dtype`. The data must not change
648    /// while the array exists.
649    pub unsafe fn new<O: 'static>(
650        owner: O,
651        ptr: *const u8,
652        shape: &[usize],
653        strides: &[usize],
654        dtype: ScalarType,
655    ) -> Self {
656        let layout = RawLayout::valid(ptr, shape, strides, dtype);
657        // SAFETY: the caller guarantees the data of `layout`
658        unsafe { Self::from_layout(owner, layout) }
659    }
660    /// Make an array that owns `owner`, with the layout `layout`.
661    ///
662    /// # Safety
663    /// As for [Tensor::new], for the data of `layout`.
664    unsafe fn from_layout<O: 'static>(owner: O, layout: RawLayout) -> Self {
665        Self {
666            layout,
667            _owner: Box::new(owner),
668        }
669    }
670    /// Make an array that owns `values`, with the shape `shape` in column-major (Fortran) order.
671    ///
672    /// # Panics
673    /// If the number of values is not the product of `shape`, or `shape` does not have 1 to
674    /// [MAX_NDIM] axes.
675    pub fn from_vec<T: Scalar + 'static>(values: Vec<T>, shape: &[usize]) -> Self {
676        assert_eq!(
677            values.len(),
678            shape.iter().product::<usize>(),
679            "the number of values must be the product of the shape"
680        );
681        let layout =
682            RawLayout::valid_col_major(values.as_ptr() as *const u8, shape, T::scalar_type());
683        // SAFETY: `values` has one value for each index of `shape`, and its data is on the heap,
684        // so it does not move when `values` moves into the array
685        unsafe { Self::from_layout(values, layout) }
686    }
687    /// A view of the array, with no copy.
688    pub fn view(&self) -> TensorView<'_> {
689        // the data stays valid and does not change while `self` is borrowed
690        TensorView {
691            layout: self.layout,
692            _data: PhantomData,
693        }
694    }
695    /// The length of each axis.
696    pub fn shape(&self) -> &[usize] {
697        self.layout.shape()
698    }
699    /// The stride of each axis, in bytes. The [Tensor] docs give the data layout.
700    pub fn strides(&self) -> &[usize] {
701        self.layout.strides()
702    }
703    /// The pointer to the first value. It is valid while `self` exists, and the data must not
704    /// be changed through it.
705    pub fn data_ptr(&self) -> *const u8 {
706        self.layout.ptr
707    }
708    /// The scalar type of the values.
709    pub fn dtype(&self) -> ScalarType {
710        self.layout.dtype
711    }
712    /// The same data with a new shape and new strides (in bytes). There is no copy.
713    ///
714    /// # Panics
715    /// If the new layout has a different number of values, can reach a byte past the last value
716    /// of the old layout, or does not obey the layout rules.
717    pub(crate) fn with_layout(mut self, shape: &[usize], strides: &[usize]) -> Self {
718        self.layout = self.layout.with_layout(shape, strides);
719        self
720    }
721    /// Keep only the first `len` indices of the axis `axis`. There is no copy: the strides do not
722    /// change, so each index that stays gives the same value.
723    ///
724    /// # Panics
725    /// If `len` is larger than the length of the axis.
726    pub(crate) fn truncate_axis(mut self, axis: usize, len: usize) -> Self {
727        self.layout = self.layout.truncate_axis(axis, len);
728        self
729    }
730    /// A reference to the value at `index`, with one value for each axis, for example
731    /// `tensor.index::<f64>([0, i, 0])`. The scalar type must be `T`.
732    ///
733    /// # Panics
734    /// If `index` does not have one value for each axis, or a value is out of range.
735    pub fn index<T: Scalar>(&self, index: impl AsRef<[usize]>) -> Result<&T, DiffsolRtError> {
736        self.view().index(index)
737    }
738    /// The column at `index`: the values `[:, index[0], index[1], ...]`, as one slice of
739    /// `shape[0]` values, with no copy. `index` has one value for each axis after the first. The
740    /// scalar type must be `T`.
741    ///
742    /// For a vector `[n, nbatch]`, `column(&[b])` is lane `b`. For a matrix
743    /// `[nrows, ncols, nbatch]`, `column(&[j, b])` is column `j` of lane `b`. For a 1D array,
744    /// `column(&[])` is all the values.
745    ///
746    /// # Panics
747    /// If `index` does not have one value for each axis after the first, or a value is out of
748    /// range.
749    pub fn column<T: Scalar>(&self, index: &[usize]) -> Result<&[T], DiffsolRtError> {
750        self.view().column(index)
751    }
752    /// All the columns (see [Tensor::column]), in column-major order: the index on axis 1
753    /// changes fastest. Together, they give all the values in column-major order. The scalar
754    /// type must be `T`.
755    pub fn columns<T: Scalar>(&self) -> Result<impl Iterator<Item = &[T]> + '_, DiffsolRtError> {
756        self.view().columns()
757    }
758}
759
760impl<'a> From<&'a Tensor> for TensorView<'a> {
761    fn from(tensor: &'a Tensor) -> Self {
762        tensor.view()
763    }
764}
765
766/// A view of an N-D array of `f32` or `f64` values that the view does not own. The inputs of
767/// this crate are `TensorView` values.
768///
769/// A `TensorView` is small and `Copy`, and it is only on the stack: the shape and the strides are
770/// fixed-size arrays. It has the same data layout and the same layout rules as [Tensor] (strides
771/// in bytes, column-major order, lane axis last, adjacent values in each column). So a view of a
772/// row-major array is not possible: make a column-major (Fortran-ordered) copy first. The lifetime
773/// `'a` is the lifetime of the data.
774///
775/// A `TensorView` has the C layout of [RawLayout], so a C callback that gets a
776/// `const TensorView *` can read the fields `ptr`, `dtype`, `ndim`, `shape` and `strides`.
777#[repr(transparent)]
778#[derive(Clone, Copy, Debug)]
779pub struct TensorView<'a> {
780    layout: RawLayout,
781    _data: PhantomData<&'a [u8]>,
782}
783
784impl<'a> TensorView<'a> {
785    /// Make a view of data, with a shape and strides (in bytes) that obey the layout rules. The
786    /// [Tensor] docs give the data layout and the rules.
787    ///
788    /// # Errors
789    /// If the layout does not obey the layout rules: for example, the strides of a row-major
790    /// array, more than [MAX_NDIM] axes, or data that is not aligned.
791    ///
792    /// # Safety
793    /// For each index in `shape`, `ptr` plus the byte offset from `strides` must point to a valid
794    /// value of the type `dtype`. The data must stay valid, and must not change, during `'a`.
795    pub unsafe fn new(
796        ptr: *const u8,
797        shape: &[usize],
798        strides: &[usize],
799        dtype: ScalarType,
800    ) -> Result<Self, DiffsolRtError> {
801        Ok(Self {
802            layout: RawLayout::new(ptr, shape, strides, dtype)?,
803            _data: PhantomData,
804        })
805    }
806    /// Make a view of data with the shape `shape` in column-major (Fortran) order.
807    ///
808    /// # Panics
809    /// If `shape` does not have 1 to [MAX_NDIM] axes, or `ptr` is not aligned for `dtype`.
810    ///
811    /// # Safety
812    /// `ptr` must point to `product(shape)` valid, contiguous values of the type `dtype`. The data
813    /// must stay valid, and must not change, during `'a`.
814    pub unsafe fn new_col_major(ptr: *const u8, shape: &[usize], dtype: ScalarType) -> Self {
815        Self {
816            layout: RawLayout::valid_col_major(ptr, shape, dtype),
817            _data: PhantomData,
818        }
819    }
820    /// Make a view of `values`, with the shape `shape` in column-major (Fortran) order.
821    ///
822    /// # Panics
823    /// If the number of values is not the product of `shape`, or `shape` does not have 1 to
824    /// [MAX_NDIM] axes.
825    pub fn from_slice<T: Scalar>(values: &'a [T], shape: &[usize]) -> Self {
826        assert_eq!(
827            values.len(),
828            shape.iter().product::<usize>(),
829            "the number of values must be the product of the shape"
830        );
831        // SAFETY: `values` has one aligned value for each index of `shape`, and the borrow keeps
832        // it valid and unchanged during `'a`
833        unsafe { Self::new_col_major(values.as_ptr() as *const u8, shape, T::scalar_type()) }
834    }
835    /// The length of each axis.
836    pub fn shape(&self) -> &[usize] {
837        self.layout.shape()
838    }
839    /// The stride of each axis, in bytes. The [Tensor] docs give the data layout.
840    pub fn strides(&self) -> &[usize] {
841        self.layout.strides()
842    }
843    /// The pointer to the first value. It is valid during `'a`, and the data must not be changed
844    /// through it.
845    pub fn data_ptr(&self) -> *const u8 {
846        self.layout.ptr
847    }
848    /// The scalar type of the values.
849    pub fn dtype(&self) -> ScalarType {
850        self.layout.dtype
851    }
852    /// Check that the view has `expected` axes.
853    pub(crate) fn expect_ndim(&self, expected: usize) -> Result<(), DiffsolRtError> {
854        let ndim = self.layout.ndim;
855        if ndim != expected {
856            return Err(layout_error(format!(
857                "Expected a {expected}D array, got a {ndim}D array"
858            )));
859        }
860        Ok(())
861    }
862    /// A reference to the value at `index`, with one value for each axis. The scalar type must be
863    /// `T`.
864    ///
865    /// # Panics
866    /// If `index` does not have one value for each axis, or a value is out of range.
867    pub fn index<T: Scalar>(&self, index: impl AsRef<[usize]>) -> Result<&'a T, DiffsolRtError> {
868        // SAFETY: the constructor guarantees the data during `'a`
869        unsafe { self.layout.index(index.as_ref()) }
870    }
871    /// The column at `index`: the values `[:, index[0], index[1], ...]`, as one slice of
872    /// `shape[0]` values, with no copy. [Tensor::column] gives the details.
873    ///
874    /// # Panics
875    /// If `index` does not have one value for each axis after the first, or a value is out of
876    /// range.
877    pub fn column<T: Scalar>(&self, index: &[usize]) -> Result<&'a [T], DiffsolRtError> {
878        // SAFETY: the constructor guarantees the data during `'a`
879        unsafe { self.layout.column(index) }
880    }
881    /// All the columns, in column-major order. [Tensor::columns] gives the details.
882    pub fn columns<T: Scalar>(&self) -> Result<impl Iterator<Item = &'a [T]> + 'a, DiffsolRtError> {
883        // SAFETY: the constructor guarantees the data during `'a`
884        unsafe { self.layout.columns() }
885    }
886}
887
888impl TensorView<'_> {
889    /// A view of the layout `layout`.
890    ///
891    /// # Safety
892    /// As for [TensorView::new], for the data of `layout`.
893    pub(crate) unsafe fn from_layout(layout: RawLayout) -> Self {
894        Self {
895            layout,
896            _data: PhantomData,
897        }
898    }
899}
900
901/// A mutable view of an N-D array of `f32` or `f64` values that the view does not own.
902///
903/// It has the same data layout and the same layout rules as [TensorView]. Also, no two indices
904/// give the same value (there is no stride of 0 on an axis with more than one value), so each
905/// value can be changed through the view. The C callbacks of [crate::ode_callbacks] write their
906/// outputs through a `TensorViewMut`.
907///
908/// A `TensorViewMut` has the C layout of [RawLayout]. The C type of `ptr` is `const uint8_t *`:
909/// a C callback casts it to `double *` (or `float *`) to write the values.
910#[repr(transparent)]
911#[derive(Debug)]
912pub struct TensorViewMut<'a> {
913    layout: RawLayout,
914    _data: PhantomData<&'a mut [u8]>,
915}
916
917impl<'a> TensorViewMut<'a> {
918    /// Make a mutable view of data, with a shape and strides (in bytes) that obey the layout
919    /// rules, and no two indices that give the same value.
920    ///
921    /// # Errors
922    /// If the layout does not obey the rules.
923    ///
924    /// # Safety
925    /// For each index in `shape`, `ptr` plus the byte offset from `strides` must point to a valid
926    /// value of the type `dtype`. The data must stay valid during `'a`, and nothing else can read
927    /// or change it during `'a`.
928    pub unsafe fn new(
929        ptr: *mut u8,
930        shape: &[usize],
931        strides: &[usize],
932        dtype: ScalarType,
933    ) -> Result<Self, DiffsolRtError> {
934        let layout = RawLayout::new(ptr, shape, strides, dtype)?;
935        layout.check_no_overlap()?;
936        Ok(Self {
937            layout,
938            _data: PhantomData,
939        })
940    }
941    /// A mutable view of the layout `layout`.
942    ///
943    /// # Panics
944    /// If two indices of `layout` give the same value.
945    ///
946    /// # Safety
947    /// As for [TensorViewMut::new], for the data of `layout`, which must allow writes.
948    pub(crate) unsafe fn from_layout(layout: RawLayout) -> Self {
949        layout
950            .check_no_overlap()
951            .unwrap_or_else(|err| panic!("invalid mutable tensor layout: {err}"));
952        Self {
953            layout,
954            _data: PhantomData,
955        }
956    }
957    /// Make a mutable view of `values`, with the shape `shape` in column-major (Fortran) order.
958    ///
959    /// # Panics
960    /// If the number of values is not the product of `shape`, or `shape` does not have 1 to
961    /// [MAX_NDIM] axes.
962    pub fn from_slice_mut<T: Scalar>(values: &'a mut [T], shape: &[usize]) -> Self {
963        assert_eq!(
964            values.len(),
965            shape.iter().product::<usize>(),
966            "the number of values must be the product of the shape"
967        );
968        let layout =
969            RawLayout::valid_col_major(values.as_mut_ptr() as *const u8, shape, T::scalar_type());
970        // SAFETY: `values` has one aligned value for each index of `shape`, a column-major layout
971        // has no overlap, and the mutable borrow gives the only access during `'a`
972        unsafe { Self::from_layout(layout) }
973    }
974    /// A view of the same data, which borrows `self`.
975    pub fn as_view(&self) -> TensorView<'_> {
976        TensorView {
977            layout: self.layout,
978            _data: PhantomData,
979        }
980    }
981    /// The length of each axis.
982    pub fn shape(&self) -> &[usize] {
983        self.layout.shape()
984    }
985    /// The stride of each axis, in bytes. The [Tensor] docs give the data layout.
986    pub fn strides(&self) -> &[usize] {
987        self.layout.strides()
988    }
989    /// The pointer to the first value. It is valid during `'a`.
990    pub fn data_ptr(&mut self) -> *mut u8 {
991        self.layout.ptr as *mut u8
992    }
993    /// The scalar type of the values.
994    pub fn dtype(&self) -> ScalarType {
995        self.layout.dtype
996    }
997    /// A reference to the value at `index`. [TensorView::index] gives the details.
998    ///
999    /// # Panics
1000    /// If `index` does not have one value for each axis, or a value is out of range.
1001    pub fn index<T: Scalar>(&self, index: impl AsRef<[usize]>) -> Result<&T, DiffsolRtError> {
1002        // SAFETY: the constructor guarantees the data while `self` is borrowed
1003        unsafe { self.layout.index(index.as_ref()) }
1004    }
1005    /// The column at `index`. [Tensor::column] gives the details.
1006    ///
1007    /// # Panics
1008    /// If `index` does not have one value for each axis after the first, or a value is out of
1009    /// range.
1010    pub fn column<T: Scalar>(&self, index: &[usize]) -> Result<&[T], DiffsolRtError> {
1011        // SAFETY: the constructor guarantees the data while `self` is borrowed
1012        unsafe { self.layout.column(index) }
1013    }
1014    /// A mutable reference to the value at `index`, with one value for each axis. The scalar
1015    /// type must be `T`.
1016    ///
1017    /// # Panics
1018    /// If `index` does not have one value for each axis, or a value is out of range.
1019    pub fn index_mut<T: Scalar>(
1020        &mut self,
1021        index: impl AsRef<[usize]>,
1022    ) -> Result<&mut T, DiffsolRtError> {
1023        let ptr = self.layout.index_ptr::<T>(index.as_ref())? as *mut T;
1024        // SAFETY: the constructor guarantees a valid value that allows writes, and the only
1025        // access during `'a`. The mutable borrow of `self` gives one reference at a time
1026        Ok(unsafe { &mut *ptr })
1027    }
1028    /// The column at `index`, as one mutable slice of `shape[0]` values. [Tensor::column] gives
1029    /// the details.
1030    ///
1031    /// # Panics
1032    /// If `index` does not have one value for each axis after the first, or a value is out of
1033    /// range.
1034    pub fn column_mut<T: Scalar>(&mut self, index: &[usize]) -> Result<&mut [T], DiffsolRtError> {
1035        let (ptr, len) = self.layout.column_ptr::<T>(index)?;
1036        if len == 0 {
1037            return Ok(&mut []);
1038        }
1039        // SAFETY: as for `index_mut`, for the `len` adjacent, aligned values of the column
1040        Ok(unsafe { std::slice::from_raw_parts_mut(ptr as *mut T, len) })
1041    }
1042}
1043
1044#[cfg(test)]
1045mod tests {
1046    use super::{FromTensor, Tensor, TensorLayout, TensorView, TensorViewMut, ToTensor};
1047
1048    use diffsol::{
1049        Context, DenseMatrix, FaerContext, FaerMat, FaerVec, NalgebraContext, NalgebraMat,
1050        NalgebraVec, Vector,
1051    };
1052
1053    /// Lane `b`, element `i` of a test vector.
1054    fn vector_value(b: usize, i: usize) -> f64 {
1055        10.0 * b as f64 + i as f64
1056    }
1057
1058    /// The `f64` value of `tensor` at `index`.
1059    fn at(tensor: &Tensor, index: &[usize]) -> f64 {
1060        *tensor.index::<f64>(index).unwrap()
1061    }
1062
1063    /// All the `f64` values of `tensor`, in column-major order of its shape (the first axis
1064    /// changes fastest).
1065    fn all_values(tensor: &Tensor) -> Vec<f64> {
1066        let mut indices = vec![vec![]];
1067        for &n in tensor.shape().iter().rev() {
1068            indices = indices
1069                .into_iter()
1070                .flat_map(|index: Vec<usize>| {
1071                    (0..n).map(move |i| [vec![i], index.clone()].concat())
1072                })
1073                .collect();
1074        }
1075        indices.iter().map(|index| at(tensor, index)).collect()
1076    }
1077
1078    /// Lane `b`, row `i`, column `j` of a test matrix.
1079    fn matrix_value(b: usize, i: usize, j: usize) -> f64 {
1080        100.0 * b as f64 + 10.0 * i as f64 + j as f64
1081    }
1082
1083    fn check_vector_round_trip<V>(ctx: V::C, nbatch: usize)
1084    where
1085        V: Vector<T = f64> + ToTensor + FromTensor<Context = V::C>,
1086    {
1087        let n = 3;
1088        let ctx = ctx.clone_with_nbatch(nbatch).unwrap();
1089        let values = (0..nbatch)
1090            .flat_map(|b| (0..n).map(move |i| vector_value(b, i)))
1091            .collect();
1092        let v = V::from_vec(values, ctx.clone());
1093        let tensor = v.clone().to_tensor();
1094        assert_eq!(tensor.shape(), &[n, nbatch]);
1095        for b in 0..nbatch {
1096            let lane: Vec<f64> = (0..n).map(|i| vector_value(b, i)).collect();
1097            assert_eq!(
1098                (0..n).map(|i| at(&tensor, &[i, b])).collect::<Vec<_>>(),
1099                lane
1100            );
1101        }
1102        let back = V::from_tensor(tensor.view(), &ctx).unwrap();
1103        assert_eq!(back.context().nbatch(), nbatch);
1104        assert_eq!(back.clone_as_vec(), v.clone_as_vec());
1105    }
1106
1107    fn check_matrix_round_trip<M>(ctx: M::C, nbatch: usize)
1108    where
1109        M: DenseMatrix<T = f64> + ToTensor + FromTensor<Context = M::C>,
1110    {
1111        let (nrows, ncols) = (2, 3);
1112        let ctx = ctx.clone_with_nbatch(nbatch).unwrap();
1113        // diffsol stores lane `b`, row `i`, column `j` at `(b * ncols + j) * nrows + i`
1114        let mut values = vec![0.0; nbatch * nrows * ncols];
1115        for b in 0..nbatch {
1116            for i in 0..nrows {
1117                for j in 0..ncols {
1118                    values[(b * ncols + j) * nrows + i] = matrix_value(b, i, j);
1119                }
1120            }
1121        }
1122        let m = M::from_vec(nrows, ncols, values, ctx.clone());
1123        let tensor = m.to_tensor();
1124        assert_eq!(tensor.shape(), &[nrows, ncols, nbatch]);
1125        for b in 0..nbatch {
1126            for i in 0..nrows {
1127                for j in 0..ncols {
1128                    assert_eq!(at(&tensor, &[i, j, b]), matrix_value(b, i, j));
1129                }
1130            }
1131        }
1132        let back = M::from_tensor(tensor.view(), &ctx).unwrap();
1133        let again = back.to_tensor();
1134        assert_eq!(all_values(&again), all_values(&tensor));
1135    }
1136
1137    #[test]
1138    fn vectors_round_trip_with_a_lane_axis() {
1139        for nbatch in [1, 3] {
1140            check_vector_round_trip::<NalgebraVec<f64>>(NalgebraContext::default(), nbatch);
1141            check_vector_round_trip::<FaerVec<f64>>(FaerContext::default(), nbatch);
1142        }
1143    }
1144
1145    #[test]
1146    fn dense_matrices_round_trip_with_a_lane_axis() {
1147        for nbatch in [1, 3] {
1148            check_matrix_round_trip::<NalgebraMat<f64>>(NalgebraContext::default(), nbatch);
1149            check_matrix_round_trip::<FaerMat<f64>>(FaerContext::default(), nbatch);
1150        }
1151    }
1152
1153    #[test]
1154    fn one_lane_broadcasts_and_other_lane_counts_are_rejected() {
1155        let ctx = NalgebraContext::default().clone_with_nbatch(3).unwrap();
1156        let one_lane = Tensor::from_vec(vec![1.0, 2.0], &[2, 1]);
1157        let v = NalgebraVec::<f64>::from_tensor(one_lane.view(), &ctx).unwrap();
1158        assert_eq!(v.context().nbatch(), 3);
1159        assert_eq!(v.clone_as_vec(), vec![1.0, 2.0, 1.0, 2.0, 1.0, 2.0]);
1160
1161        // a matrix with one lane also gets a copy of its values in each lane
1162        let one_lane = Tensor::from_vec(vec![1.0, 2.0, 3.0, 4.0], &[2, 2, 1]);
1163        let m = NalgebraMat::<f64>::from_tensor(one_lane.view(), &ctx).unwrap();
1164        assert_eq!(diffsol::Matrix::context(&m).nbatch(), 3);
1165        let all = all_values(&m.to_tensor());
1166        assert_eq!(all, [1.0, 2.0, 3.0, 4.0].repeat(3));
1167
1168        let two_lanes = Tensor::from_vec(vec![1.0, 2.0, 3.0, 4.0], &[2, 2]);
1169        let err = NalgebraVec::<f64>::from_tensor(two_lanes.view(), &ctx)
1170            .unwrap_err()
1171            .to_string();
1172        assert!(err.contains("Expected 1 or 3 lanes, got 2"), "{err}");
1173
1174        let no_lane = TensorView::from_slice(&[1.0, 2.0], &[2]);
1175        let err = NalgebraVec::<f64>::from_tensor(no_lane, &ctx)
1176            .unwrap_err()
1177            .to_string();
1178        assert!(err.contains("Expected a 2D array, got a 1D array"), "{err}");
1179    }
1180
1181    #[test]
1182    fn shared_tensors_keep_the_owner_alive() {
1183        struct Owner {
1184            values: Vec<f64>,
1185        }
1186        let owner = std::sync::Arc::new(Owner {
1187            values: vec![1.0, 2.0, 3.0],
1188        });
1189        let tensor = super::shared_tensor(owner.clone(), |o| &o.values);
1190        assert_eq!(tensor.data_ptr(), owner.values.as_ptr() as *const u8);
1191        assert_eq!(std::sync::Arc::strong_count(&owner), 2);
1192        drop(owner);
1193        assert_eq!(all_values(&tensor), vec![1.0, 2.0, 3.0]);
1194    }
1195
1196    #[test]
1197    fn truncate_axis_keeps_the_first_indices() {
1198        // `[2, 3, 2]`: keep the first 2 columns of each lane
1199        let values: Vec<f64> = (0..12).map(f64::from).collect();
1200        let tensor = Tensor::from_vec(values, &[2, 3, 2]).truncate_axis(1, 2);
1201        assert_eq!(tensor.shape(), &[2, 2, 2]);
1202        assert_eq!(
1203            all_values(&tensor),
1204            vec![0.0, 1.0, 2.0, 3.0, 6.0, 7.0, 8.0, 9.0]
1205        );
1206    }
1207
1208    #[test]
1209    #[should_panic(expected = "cannot keep 4 indices of an axis of length 3")]
1210    fn truncate_axis_cannot_make_an_axis_longer() {
1211        Tensor::from_vec(vec![0.0f64; 3], &[3]).truncate_axis(0, 4);
1212    }
1213
1214    #[test]
1215    fn with_layout_keeps_the_values() {
1216        // `[6, 2]` column-major, with the first axis split into `[3, 2]`
1217        let values: Vec<f64> = (0..12).map(f64::from).collect();
1218        let s = std::mem::size_of::<f64>();
1219        let tensor =
1220            Tensor::from_vec(values.clone(), &[6, 2]).with_layout(&[3, 2, 2], &[s, 3 * s, 6 * s]);
1221        assert_eq!(tensor.shape(), &[3, 2, 2]);
1222        assert_eq!(all_values(&tensor), values);
1223    }
1224
1225    #[test]
1226    fn column_gives_contiguous_columns_and_index_uses_the_strides() {
1227        // a column-major `[2, 2]` view with a gap of one value after each column
1228        let data = [1.0f64, 2.0, -1.0, 3.0, 4.0, -1.0];
1229        let s = std::mem::size_of::<f64>();
1230        // SAFETY: `data` has a value at each index of the layout, and lives longer than `view`
1231        let view = unsafe {
1232            TensorView::new(
1233                data.as_ptr() as *const u8,
1234                &[2, 2],
1235                &[s, 3 * s],
1236                super::ScalarType::F64,
1237            )
1238        }
1239        .unwrap();
1240        assert_eq!(view.column::<f64>(&[0]).unwrap(), &[1.0, 2.0]);
1241        assert_eq!(view.column::<f64>(&[1]).unwrap(), &[3.0, 4.0]);
1242        let columns: Vec<&[f64]> = view.columns::<f64>().unwrap().collect();
1243        assert_eq!(columns, vec![&[1.0, 2.0][..], &[3.0, 4.0][..]]);
1244        assert_eq!(*view.index::<f64>([1, 1]).unwrap(), 4.0);
1245        let error = view.column::<f32>(&[0]).unwrap_err().to_string();
1246        assert!(error.contains("Data type mismatch"), "{error}");
1247        assert!(view.columns::<f32>().is_err());
1248        assert!(view.index::<f32>([0, 0]).is_err());
1249    }
1250
1251    #[test]
1252    fn views_reject_layouts_without_contiguous_columns() {
1253        let data = [1.0f64, 2.0, 3.0, 4.0];
1254        let ptr = data.as_ptr() as *const u8;
1255        let s = std::mem::size_of::<f64>();
1256        let f64_type = super::ScalarType::F64;
1257        let error = |shape: &[usize], strides: &[usize], ptr: *const u8| {
1258            // SAFETY: the layouts are rejected, so no view reads the data
1259            unsafe { TensorView::new(ptr, shape, strides, f64_type) }
1260                .unwrap_err()
1261                .to_string()
1262        };
1263        // a row-major `[2, 2]` view
1264        let err = error(&[2, 2], &[2 * s, s], ptr);
1265        assert!(
1266            err.contains("The values of a column are not adjacent"),
1267            "{err}"
1268        );
1269        // a pointer that is not aligned, and a stride that is not a multiple of the value size
1270        let err = error(&[2], &[s], ptr.wrapping_add(1));
1271        assert!(err.contains("The data is not aligned"), "{err}");
1272        let err = error(&[1, 2], &[s, s + 1], ptr);
1273        assert!(err.contains("The data is not aligned"), "{err}");
1274        // 4 axes, or a stride count that does not agree with the shape
1275        let err = error(&[1, 1, 1, 1], &[s; 4], ptr);
1276        assert!(err.contains("A tensor has 1 to 3 axes, got 4"), "{err}");
1277        let err = error(&[2, 2], &[s], ptr);
1278        assert!(err.contains("Expected 2 strides, got 1"), "{err}");
1279        // the stride of an axis of length 1 is never used, so it can have any value
1280        // SAFETY: `data` has a value at each index of the layout, and lives longer than `view`
1281        let view = unsafe { TensorView::new(ptr, &[1, 2], &[3, 2 * s], f64_type) }.unwrap();
1282        assert_eq!(view.column::<f64>(&[1]).unwrap(), &[3.0]);
1283    }
1284
1285    #[test]
1286    fn views_from_slices_have_1_to_3_axes() {
1287        let values: Vec<f64> = (0..12).map(f64::from).collect();
1288        let s = std::mem::size_of::<f64>();
1289        let v1 = TensorView::from_slice(&values, &[12]);
1290        assert_eq!(v1.shape(), &[12]);
1291        assert_eq!(v1.strides(), &[s]);
1292        let v2 = TensorView::from_slice(&values, &[3, 4]);
1293        assert_eq!(v2.shape(), &[3, 4]);
1294        assert_eq!(v2.strides(), &[s, 3 * s]);
1295        assert_eq!(*v2.index::<f64>([2, 1]).unwrap(), 5.0);
1296        let v3 = TensorView::from_slice(&values, &[2, 3, 2]);
1297        assert_eq!(v3.shape(), &[2, 3, 2]);
1298        assert_eq!(v3.strides(), &[s, 2 * s, 6 * s]);
1299        assert_eq!(*v3.index::<f64>([1, 2, 1]).unwrap(), 11.0);
1300        v3.expect_ndim(3).unwrap();
1301        assert!(v3.expect_ndim(2).is_err());
1302    }
1303
1304    #[test]
1305    #[should_panic(expected = "A tensor has 1 to 3 axes, got 4")]
1306    fn views_from_slices_with_4_axes_panic() {
1307        let values = [0.0f64; 16];
1308        TensorView::from_slice(&values, &[2, 2, 2, 2]);
1309    }
1310
1311    #[test]
1312    fn view_of_an_array_has_the_same_layout_and_values() {
1313        let ctx = FaerContext::default().clone_with_nbatch(2).unwrap();
1314        let m = FaerMat::<f64>::from_vec(2, 3, (0..12).map(f64::from).collect(), ctx);
1315        let tensor = m.to_tensor();
1316        let view = tensor.view();
1317        assert_eq!(view.shape(), tensor.shape());
1318        assert_eq!(view.strides(), tensor.strides());
1319        assert_eq!(view.dtype(), tensor.dtype());
1320        assert_eq!(view.data_ptr(), tensor.data_ptr());
1321        // the columns do not contain the padding of faer, which is not initialized
1322        let view_columns: Vec<&[f64]> = view.columns().unwrap().collect();
1323        let tensor_columns: Vec<&[f64]> = tensor.columns().unwrap().collect();
1324        assert_eq!(view_columns, tensor_columns);
1325        assert_eq!(
1326            all_values(&tensor),
1327            (0..12).map(f64::from).collect::<Vec<_>>()
1328        );
1329        let from: TensorView<'_> = (&tensor).into();
1330        assert_eq!(from.shape(), tensor.shape());
1331    }
1332
1333    #[test]
1334    fn vectors_convert_from_views_with_padded_lanes() {
1335        // a `[2, 3]` view with a gap of two values after each lane
1336        let data = [1.0f64, 2.0, 0.0, 0.0, 10.0, 20.0, 0.0, 0.0, 100.0, 200.0];
1337        let s = std::mem::size_of::<f64>();
1338        // SAFETY: `data` has a value at each index of the layout, and lives longer than `view`
1339        let view = unsafe {
1340            TensorView::new(
1341                data.as_ptr() as *const u8,
1342                &[2, 3],
1343                &[s, 4 * s],
1344                super::ScalarType::F64,
1345            )
1346        }
1347        .unwrap();
1348        let ctx = NalgebraContext::default().clone_with_nbatch(3).unwrap();
1349        let v = NalgebraVec::<f64>::from_tensor(view, &ctx).unwrap();
1350        assert_eq!(v.clone_as_vec(), vec![1.0, 2.0, 10.0, 20.0, 100.0, 200.0]);
1351    }
1352
1353    #[test]
1354    fn f32_views_convert_to_f64_vectors() {
1355        let data = [1.0f32, 2.0];
1356        let view = TensorView::from_slice(&data, &[2, 1]);
1357        let v = NalgebraVec::<f64>::from_tensor(view, &NalgebraContext::default()).unwrap();
1358        assert_eq!(v.clone_as_vec(), vec![1.0, 2.0]);
1359    }
1360
1361    #[test]
1362    fn columns_skip_the_padding_of_faer_storage() {
1363        // faer pads the columns of its storage, so the lanes of a faer vector are not adjacent
1364        let ctx = FaerContext::default().clone_with_nbatch(2).unwrap();
1365        let v = FaerVec::<f64>::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], ctx);
1366        let tensor = v.to_tensor();
1367        assert!(tensor.strides()[1] >= 3 * std::mem::size_of::<f64>());
1368        let columns: Vec<&[f64]> = tensor.columns().unwrap().collect();
1369        assert_eq!(columns, vec![&[1.0, 2.0, 3.0][..], &[4.0, 5.0, 6.0][..]]);
1370        assert_eq!(tensor.column::<f64>(&[1]).unwrap(), &[4.0, 5.0, 6.0]);
1371
1372        // a `[2, 3, 2]` matrix: column `j` of lane `b` is number `b * 3 + j`
1373        let m = FaerMat::<f64>::from_vec(2, 3, (0..12).map(f64::from).collect(), ctx);
1374        let tensor = m.to_tensor();
1375        let columns: Vec<Vec<f64>> = tensor.columns().unwrap().map(<[f64]>::to_vec).collect();
1376        let expected: Vec<Vec<f64>> = (0..6)
1377            .map(|c| vec![2.0 * c as f64, 2.0 * c as f64 + 1.0])
1378            .collect();
1379        assert_eq!(columns, expected);
1380        assert_eq!(tensor.column::<f64>(&[2, 1]).unwrap(), &[10.0, 11.0]);
1381    }
1382
1383    #[test]
1384    fn empty_axes_give_empty_columns_or_no_columns() {
1385        // no rows: each column is empty
1386        let no_rows = Tensor::from_vec(Vec::<f64>::new(), &[0, 2]);
1387        let columns: Vec<&[f64]> = no_rows.columns().unwrap().collect();
1388        assert_eq!(columns, vec![&[] as &[f64]; 2]);
1389        assert_eq!(no_rows.column::<f64>(&[1]).unwrap(), &[] as &[f64]);
1390        // no lanes: there are no columns
1391        let no_lanes = Tensor::from_vec(Vec::<f64>::new(), &[3, 0]);
1392        assert_eq!(no_lanes.columns::<f64>().unwrap().count(), 0);
1393    }
1394
1395    #[test]
1396    fn metadata_gives_the_layout() {
1397        let tensor = Tensor::from_vec((0..6).map(f64::from).collect(), &[2, 3]);
1398        let s = std::mem::size_of::<f64>();
1399        assert_eq!(tensor.shape(), &[2, 3]);
1400        assert_eq!(tensor.strides(), &[s, 2 * s]);
1401        assert_eq!(tensor.dtype(), super::ScalarType::F64);
1402        // the value at (1, 2) is at the byte offset 1 * s + 2 * 2s from the data pointer
1403        let value = unsafe { *(tensor.data_ptr().add(s + 4 * s) as *const f64) };
1404        assert_eq!(value, 5.0);
1405    }
1406
1407    #[test]
1408    fn batch_lanes_broadcast_with_strides_of_zero() {
1409        let s = std::mem::size_of::<f64>();
1410        let ctx = |nbatch| FaerContext::default().clone_with_nbatch(nbatch).unwrap();
1411        let lanes = |nbatch: usize| -> Vec<f64> {
1412            (0..nbatch)
1413                .flat_map(|b| (0..2).map(move |i| vector_value(b, i)))
1414                .collect()
1415        };
1416        // 2 outer lanes with 3 inner lanes each: 6 output lanes
1417        let (ninner, nouter) = (3, 2);
1418        let full = FaerVec::<f64>::from_vec(lanes(6), ctx(6));
1419        let outer = FaerVec::<f64>::from_vec(lanes(2), ctx(2));
1420        let one = FaerVec::<f64>::from_vec(lanes(1), ctx(1));
1421        for (v, expected_lane) in [
1422            (
1423                &full,
1424                Box::new(|k, b| b * ninner + k) as Box<dyn Fn(usize, usize) -> usize>,
1425            ),
1426            (&outer, Box::new(|_, b| b)),
1427            (&one, Box::new(|_, _| 0)),
1428        ] {
1429            let layout = v.tensor_layout().batch_lanes(ninner, nouter);
1430            // SAFETY: the layout shows the data of `v`, which lives longer than `view`
1431            let view = unsafe { TensorView::from_layout(layout) };
1432            assert_eq!(view.shape(), &[2, ninner, nouter]);
1433            assert_eq!(view.strides()[0], s);
1434            for b in 0..nouter {
1435                for k in 0..ninner {
1436                    let lane = expected_lane(k, b);
1437                    let expected = [vector_value(lane, 0), vector_value(lane, 1)];
1438                    assert_eq!(view.column::<f64>(&[k, b]).unwrap(), &expected);
1439                }
1440            }
1441        }
1442        let c = full.tensor_layout().strides()[1];
1443        let strides = |v: &FaerVec<f64>| v.tensor_layout().batch_lanes(ninner, nouter).strides;
1444        assert_eq!(strides(&full), [s, c, ninner * c]);
1445        assert_eq!(
1446            strides(&outer)[1..],
1447            [0, outer.tensor_layout().strides()[1]]
1448        );
1449        assert_eq!(strides(&one)[1..], [0, 0]);
1450    }
1451
1452    #[test]
1453    #[should_panic(expected = "cannot broadcast 4 lanes into 3 x 2 lanes")]
1454    fn batch_lanes_reject_other_lane_counts() {
1455        let ctx = NalgebraContext::default().clone_with_nbatch(4).unwrap();
1456        let v = NalgebraVec::<f64>::zeros(2, ctx);
1457        v.tensor_layout().batch_lanes(3, 2);
1458    }
1459
1460    #[test]
1461    fn mutable_views_write_each_lane_of_a_padded_vector() {
1462        use super::TensorLayoutMut;
1463        let ctx = FaerContext::default().clone_with_nbatch(4).unwrap();
1464        let mut v = FaerVec::<f64>::zeros(3, ctx);
1465        let layout = v.tensor_layout_mut().batch_lanes(2, 2);
1466        // SAFETY: the layout shows the data of `v`, which is mutably borrowed while `view` lives
1467        let mut view = unsafe { TensorViewMut::from_layout(layout) };
1468        for b in 0..2 {
1469            for k in 0..2 {
1470                let lane = (b * 2 + k) as f64;
1471                view.column_mut::<f64>(&[k, b]).unwrap().copy_from_slice(&[
1472                    lane,
1473                    lane + 0.5,
1474                    lane + 0.25,
1475                ]);
1476            }
1477        }
1478        *view.index_mut::<f64>([2, 1, 1]).unwrap() = -1.0;
1479        assert_eq!(*view.as_view().index::<f64>([2, 1, 1]).unwrap(), -1.0);
1480        let expected: Vec<f64> = (0..4)
1481            .flat_map(|l| [l as f64, l as f64 + 0.5, l as f64 + 0.25])
1482            .collect::<Vec<_>>()
1483            .into_iter()
1484            .enumerate()
1485            .map(|(i, x)| if i == 11 { -1.0 } else { x })
1486            .collect();
1487        assert_eq!(v.clone_as_vec(), expected);
1488    }
1489
1490    #[test]
1491    fn mutable_views_reject_overlapping_layouts() {
1492        let mut data = [0.0f64; 4];
1493        let s = std::mem::size_of::<f64>();
1494        let ptr = data.as_mut_ptr() as *mut u8;
1495        let f64_type = super::ScalarType::F64;
1496        // SAFETY: the layouts are rejected, so no view reads or writes the data
1497        let err = unsafe { TensorViewMut::new(ptr, &[2, 2], &[s, 0], f64_type) }.unwrap_err();
1498        assert!(err.to_string().contains("overlap"), "{err}");
1499        let err = unsafe { TensorViewMut::new(ptr, &[2, 2], &[s, s], f64_type) }.unwrap_err();
1500        assert!(err.to_string().contains("overlap"), "{err}");
1501        // a stride of 0 on an axis of length 1 is never used
1502        // SAFETY: `data` has a value at each index of the layout, and lives longer than `view`
1503        let mut view =
1504            unsafe { TensorViewMut::new(ptr, &[2, 1, 2], &[s, 0, 2 * s], f64_type) }.unwrap();
1505        view.column_mut::<f64>(&[0, 1]).unwrap().fill(3.0);
1506        let view = TensorViewMut::from_slice_mut(&mut data, &[4]);
1507        assert_eq!(view.column::<f64>(&[]).unwrap(), &[0.0, 0.0, 3.0, 3.0]);
1508    }
1509
1510    #[test]
1511    fn vec_converts_to_a_1d_array() {
1512        let tensor = vec![1.0f64, 2.0, 3.0].to_tensor();
1513        assert_eq!(tensor.shape(), &[3]);
1514        assert_eq!(tensor.column::<f64>(&[]).unwrap(), &[1.0, 2.0, 3.0]);
1515        assert_eq!(tensor.columns::<f64>().unwrap().count(), 1);
1516    }
1517}