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
14pub const MAX_NDIM: usize = 3;
16
17pub(crate) trait ToTensor {
19 fn to_tensor(self) -> Tensor;
20}
21
22pub(crate) trait FromTensor: Sized {
25 type Context;
27 fn from_tensor(tensor: TensorView<'_>, ctx: &Self::Context) -> Result<Self, DiffsolRtError>;
28}
29
30pub(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
49fn 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
77fn 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
86fn 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
97impl<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
129fn faer_layout<T: Scalar + FaerScalar>(m: &faer::Mat<T>, ptr: *const T) -> RawLayout {
131 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
141fn nalgebra_layout<T: Scalar + NalgebraScalar>(
143 m: &nalgebra::DMatrix<T>,
144 ptr: *const T,
145) -> RawLayout {
146 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
158fn 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
169pub unsafe trait TensorLayout {
181 fn tensor_layout(&self) -> RawLayout;
183}
184
185pub unsafe trait TensorLayoutMut: TensorLayout {
193 fn tensor_layout_mut(&mut self) -> RawLayout;
195}
196
197unsafe 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
205unsafe 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
213unsafe 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
220unsafe 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
228unsafe 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
236unsafe 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
244unsafe 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 unsafe { Tensor::from_layout(self, layout) }
257 }
258}
259
260pub(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 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
288fn 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 let strides_aligned = shape
316 .iter()
317 .zip(strides)
318 .all(|(&n, &stride)| n <= 1 || stride.is_multiple_of(size));
319 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
326fn 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
334fn 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#[repr(C)]
361#[derive(Clone, Copy, Debug)]
362pub struct RawLayout {
363 ptr: *const u8,
365 dtype: ScalarType,
367 ndim: usize,
369 shape: [usize; MAX_NDIM],
371 strides: [usize; MAX_NDIM],
373}
374
375impl RawLayout {
376 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 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 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 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 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 assert!(last_byte(shape, strides) <= last_byte(self.shape(), self.strides()));
453 Self::valid(self.ptr, shape, strides, self.dtype)
454 }
455 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 Self::valid(self.ptr, &[n, ninner, nouter], &strides, self.dtype)
484 }
485 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 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 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 Ok(self.ptr.wrapping_add(offset) as *const T)
525 }
526 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 Ok((self.ptr.wrapping_add(offset) as *const T, self.shape[0]))
537 }
538 unsafe fn index<'a, T: Scalar>(&self, index: &[usize]) -> Result<&'a T, DiffsolRtError> {
544 let ptr = self.index_ptr::<T>(index)?;
545 Ok(unsafe { &*ptr })
548 }
549 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 unsafe { std::slice::from_raw_parts(self.ptr.add(offset) as *const T, len) }
563 }
564 unsafe fn column<'a, T: Scalar>(&self, index: &[usize]) -> Result<&'a [T], DiffsolRtError> {
569 check_dtype::<T>(self.dtype)?;
570 Ok(unsafe { self.column_of_type(index) })
572 }
573 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 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 unsafe { layout.column_of_type(&index[..layout.ndim - 1]) }
592 }))
593 }
594}
595
596pub struct Tensor {
632 layout: RawLayout,
633 _owner: Box<dyn Any>,
635}
636
637impl Tensor {
638 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 unsafe { Self::from_layout(owner, layout) }
659 }
660 unsafe fn from_layout<O: 'static>(owner: O, layout: RawLayout) -> Self {
665 Self {
666 layout,
667 _owner: Box::new(owner),
668 }
669 }
670 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 unsafe { Self::from_layout(values, layout) }
686 }
687 pub fn view(&self) -> TensorView<'_> {
689 TensorView {
691 layout: self.layout,
692 _data: PhantomData,
693 }
694 }
695 pub fn shape(&self) -> &[usize] {
697 self.layout.shape()
698 }
699 pub fn strides(&self) -> &[usize] {
701 self.layout.strides()
702 }
703 pub fn data_ptr(&self) -> *const u8 {
706 self.layout.ptr
707 }
708 pub fn dtype(&self) -> ScalarType {
710 self.layout.dtype
711 }
712 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 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 pub fn index<T: Scalar>(&self, index: impl AsRef<[usize]>) -> Result<&T, DiffsolRtError> {
736 self.view().index(index)
737 }
738 pub fn column<T: Scalar>(&self, index: &[usize]) -> Result<&[T], DiffsolRtError> {
750 self.view().column(index)
751 }
752 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#[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 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 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 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 unsafe { Self::new_col_major(values.as_ptr() as *const u8, shape, T::scalar_type()) }
834 }
835 pub fn shape(&self) -> &[usize] {
837 self.layout.shape()
838 }
839 pub fn strides(&self) -> &[usize] {
841 self.layout.strides()
842 }
843 pub fn data_ptr(&self) -> *const u8 {
846 self.layout.ptr
847 }
848 pub fn dtype(&self) -> ScalarType {
850 self.layout.dtype
851 }
852 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 pub fn index<T: Scalar>(&self, index: impl AsRef<[usize]>) -> Result<&'a T, DiffsolRtError> {
868 unsafe { self.layout.index(index.as_ref()) }
870 }
871 pub fn column<T: Scalar>(&self, index: &[usize]) -> Result<&'a [T], DiffsolRtError> {
878 unsafe { self.layout.column(index) }
880 }
881 pub fn columns<T: Scalar>(&self) -> Result<impl Iterator<Item = &'a [T]> + 'a, DiffsolRtError> {
883 unsafe { self.layout.columns() }
885 }
886}
887
888impl TensorView<'_> {
889 pub(crate) unsafe fn from_layout(layout: RawLayout) -> Self {
894 Self {
895 layout,
896 _data: PhantomData,
897 }
898 }
899}
900
901#[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 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 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 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 unsafe { Self::from_layout(layout) }
973 }
974 pub fn as_view(&self) -> TensorView<'_> {
976 TensorView {
977 layout: self.layout,
978 _data: PhantomData,
979 }
980 }
981 pub fn shape(&self) -> &[usize] {
983 self.layout.shape()
984 }
985 pub fn strides(&self) -> &[usize] {
987 self.layout.strides()
988 }
989 pub fn data_ptr(&mut self) -> *mut u8 {
991 self.layout.ptr as *mut u8
992 }
993 pub fn dtype(&self) -> ScalarType {
995 self.layout.dtype
996 }
997 pub fn index<T: Scalar>(&self, index: impl AsRef<[usize]>) -> Result<&T, DiffsolRtError> {
1002 unsafe { self.layout.index(index.as_ref()) }
1004 }
1005 pub fn column<T: Scalar>(&self, index: &[usize]) -> Result<&[T], DiffsolRtError> {
1011 unsafe { self.layout.column(index) }
1013 }
1014 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 Ok(unsafe { &mut *ptr })
1027 }
1028 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 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 fn vector_value(b: usize, i: usize) -> f64 {
1055 10.0 * b as f64 + i as f64
1056 }
1057
1058 fn at(tensor: &Tensor, index: &[usize]) -> f64 {
1060 *tensor.index::<f64>(index).unwrap()
1061 }
1062
1063 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 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 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 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 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 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 let data = [1.0f64, 2.0, -1.0, 3.0, 4.0, -1.0];
1229 let s = std::mem::size_of::<f64>();
1230 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 unsafe { TensorView::new(ptr, shape, strides, f64_type) }
1260 .unwrap_err()
1261 .to_string()
1262 };
1263 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}