Skip to main content

konjure_sdk/data/
tensor.rs

1//! Immutable, bounded, type-erased tensors.
2
3use alloc::{string::ToString, sync::Arc, vec, vec::Vec};
4use core::{fmt, marker::PhantomData};
5use serde::{
6    Deserialize, Deserializer, Serialize, Serializer,
7    de::{Error as _, SeqAccess, Visitor},
8    ser::{SerializeSeq, SerializeStruct},
9};
10
11use super::{BinaryOp, DType, DataError, Scalar};
12
13/// Maximum number of elements admitted by one tensor.
14pub const MAX_ELEMENTS: usize = 16 * 1024 * 1024;
15/// Maximum number of axes admitted by one tensor.
16pub const MAX_RANK: usize = 32;
17
18/// One item in a tensor indexing expression.
19#[derive(Clone, Debug, PartialEq, Eq)]
20pub enum AxisIndex {
21    /// Select one axis element, removing that axis from the result.
22    Index(isize),
23    /// Select an axis interval using Python's half-open slicing rules.
24    Slice {
25        /// Inclusive starting position, or the direction-dependent default.
26        start: Option<isize>,
27        /// Exclusive stopping position, or the direction-dependent default.
28        stop: Option<isize>,
29        /// Non-zero distance between successive selected elements.
30        step: isize,
31    },
32    /// Insert a length-one axis into the result.
33    NewAxis,
34    /// Expand to enough full slices to consume the remaining input axes.
35    Ellipsis,
36}
37
38/// An immutable, strided tensor whose packed backing storage is shared by views.
39///
40/// Construction accepts at most [`MAX_RANK`] axes and [`MAX_ELEMENTS`] elements.
41/// Slices and transposes retain the same storage; reshaping a noncontiguous view
42/// materializes row-major bytes. Binary operations require matching types and use
43/// trailing-axis broadcasting. Serialization is `{dtype, shape, data}`, where
44/// every `data` entry is an exact decimal scalar string.
45#[derive(Clone, Debug)]
46pub struct Tensor {
47    dtype: DType,
48    shape: Arc<[usize]>,
49    strides: Arc<[isize]>,
50    offset: usize,
51    data: Arc<[u8]>,
52}
53
54impl PartialEq for Tensor {
55    fn eq(&self, other: &Self) -> bool {
56        self.dtype == other.dtype
57            && self.shape == other.shape
58            && self.len() == other.len()
59            && (0..self.len())
60                .all(|index| self.scalar_at_linear(index) == other.scalar_at_linear(index))
61    }
62}
63
64impl Eq for Tensor {}
65
66impl Tensor {
67    /// Creates a contiguous tensor from scalars of `dtype`.
68    pub fn from_scalars(
69        dtype: DType,
70        shape: Vec<usize>,
71        values: Vec<Scalar>,
72    ) -> Result<Self, DataError> {
73        let count = checked_count(&shape)?;
74        if count != values.len() {
75            return Err(DataError::ShapeMismatch);
76        }
77        if values.iter().any(|value| value.dtype() != dtype) {
78            return Err(DataError::TypeMismatch);
79        }
80        let bytes = values_to_bytes(dtype, &values)?;
81        Self::from_le_bytes(dtype, shape, bytes)
82    }
83
84    /// Creates a contiguous tensor from packed little-endian scalar bytes.
85    pub fn from_le_bytes(
86        dtype: DType,
87        shape: Vec<usize>,
88        bytes: Vec<u8>,
89    ) -> Result<Self, DataError> {
90        let count = checked_count(&shape)?;
91        let width = dtype.bytes();
92        if width == 0
93            || width > 16
94            || count.checked_mul(width).ok_or(DataError::AllocationLimit)? != bytes.len()
95        {
96            return Err(DataError::InvalidMetadata);
97        }
98        // Decode once: this validates the representation, including non-finite floats.
99        for index in 0..count {
100            let _ = scalar_at_bytes(dtype, &bytes, index)?;
101        }
102        let strides = contiguous_strides(&shape)?;
103        Ok(Self {
104            dtype,
105            shape: Arc::from(shape),
106            strides: Arc::from(strides),
107            offset: 0,
108            data: Arc::from(bytes),
109        })
110    }
111
112    /// Creates a zero-filled contiguous tensor.
113    pub fn zeros(dtype: DType, shape: Vec<usize>) -> Result<Self, DataError> {
114        let count = checked_count(&shape)?;
115        let bytes = count
116            .checked_mul(dtype.bytes())
117            .ok_or(DataError::AllocationLimit)?;
118        Self::from_le_bytes(dtype, shape, vec![0; bytes])
119    }
120
121    /// Returns the scalar type.
122    pub fn dtype(&self) -> DType {
123        self.dtype
124    }
125    /// Returns the dimensions in logical axis order.
126    pub fn shape(&self) -> &[usize] {
127        &self.shape
128    }
129    /// Returns element strides for each logical axis.
130    pub fn strides(&self) -> &[isize] {
131        &self.strides
132    }
133    /// Returns the logical element count.
134    pub fn len(&self) -> usize {
135        self.shape.iter().copied().product()
136    }
137    /// Reports whether the tensor has no logical elements.
138    pub fn is_empty(&self) -> bool {
139        self.len() == 0
140    }
141    /// Returns the packed byte count of the logical tensor.
142    pub fn byte_len(&self) -> usize {
143        self.len().saturating_mul(self.dtype.bytes())
144    }
145    /// Returns the physical byte length of the shared backing allocation.
146    pub fn storage_byte_len(&self) -> usize {
147        self.data.len()
148    }
149
150    /// Reads one scalar using Python-style negative indices.
151    pub fn get(&self, indices: &[isize]) -> Result<Scalar, DataError> {
152        if indices.len() != self.shape.len() {
153            return Err(DataError::Bounds);
154        }
155        let mut position = self.offset as isize;
156        for ((&index, &dimension), &stride) in indices
157            .iter()
158            .zip(self.shape.iter())
159            .zip(self.strides.iter())
160        {
161            let index = normalize_index(index, dimension)?;
162            position = position
163                .checked_add(index.checked_mul(stride).ok_or(DataError::Bounds)?)
164                .ok_or(DataError::Bounds)?;
165        }
166        scalar_at_bytes(
167            self.dtype,
168            &self.data,
169            usize::try_from(position).map_err(|_| DataError::Bounds)?,
170        )
171    }
172
173    /// Produces a shared-storage slice using Python indexing rules.
174    pub fn slice(&self, indices: &[AxisIndex]) -> Result<Self, DataError> {
175        let layout = slice_layout(&self.shape, &self.strides, self.offset, indices)?;
176        Ok(Self {
177            dtype: self.dtype,
178            shape: Arc::from(layout.shape),
179            strides: Arc::from(layout.strides),
180            offset: layout.offset,
181            data: self.data.clone(),
182        })
183    }
184
185    /// Replaces a selection in a new contiguous tensor of the original shape.
186    ///
187    /// Replacement values broadcast across the selected shape using trailing
188    /// axes. A rank-zero replacement fills the selection with a single scalar.
189    /// Source and replacement may share storage: every replacement value is read
190    /// from its original immutable allocation, so overlapping assignment is safe.
191    /// Noncontiguous source views are copied in logical row-major order.
192    ///
193    /// # Errors
194    /// Rejects invalid selectors, different element types, or replacement shapes
195    /// that cannot broadcast into exactly the selection's shape. On error, source
196    /// and replacement remain unchanged.
197    ///
198    /// ```
199    /// use konjure_sdk::data::{AxisIndex, DType, Scalar, Tensor};
200    /// let original = Tensor::zeros(DType::I16, vec![2, 3])?;
201    /// let row = Tensor::from_scalars(DType::I16, vec![3],
202    ///     ["1", "2", "3"].into_iter().map(|text| Scalar::parse(DType::I16, text))
203    ///         .collect::<Result<Vec<_>, _>>()?)?;
204    /// let updated = original.with_slice(&[AxisIndex::Index(-1)], &row)?;
205    /// assert_eq!(updated.get(&[1, 2])?.to_string(), "3");
206    /// assert_eq!(original.get(&[1, 2])?.to_string(), "0");
207    /// # Ok::<(), konjure_sdk::data::DataError>(())
208    /// ```
209    pub fn with_slice(&self, indices: &[AxisIndex], replacement: &Self) -> Result<Self, DataError> {
210        self.replace_selection(core::iter::once(indices), replacement)
211    }
212
213    /// Replaces a selection formed by successive indexing expressions.
214    ///
215    /// Each path entry indexes the preceding view. All entries are composed
216    /// before one logical payload copy, including for a noncontiguous source.
217    /// An empty path selects the entire tensor. Broadcasting and immutability
218    /// follow [`Self::with_slice`].
219    ///
220    /// # Errors
221    /// Rejects paths longer than [`MAX_RANK`], invalid selectors, mixed types,
222    /// and replacement shapes that cannot broadcast into the final selection.
223    pub fn with_index_path(
224        &self,
225        path: &[Vec<AxisIndex>],
226        replacement: &Self,
227    ) -> Result<Self, DataError> {
228        self.replace_selection(path.iter().map(Vec::as_slice), replacement)
229    }
230
231    fn replace_selection<'a>(
232        &self,
233        path: impl IntoIterator<Item = &'a [AxisIndex]>,
234        replacement: &Self,
235    ) -> Result<Self, DataError> {
236        if self.dtype != replacement.dtype {
237            return Err(DataError::TypeMismatch);
238        }
239        let output_strides = contiguous_strides(&self.shape)?;
240        let mut selection = SliceLayout {
241            shape: self.shape.to_vec(),
242            strides: output_strides.clone(),
243            offset: 0,
244        };
245        for (depth, indices) in path.into_iter().enumerate() {
246            if depth == MAX_RANK {
247                return Err(DataError::InvalidSlice);
248            }
249            selection = slice_layout(
250                &selection.shape,
251                &selection.strides,
252                selection.offset,
253                indices,
254            )?;
255        }
256        if broadcast_shape(&selection.shape, &replacement.shape)? != selection.shape {
257            return Err(DataError::ShapeMismatch);
258        }
259        let prefix = selection.shape.len() - replacement.shape.len();
260        let selected_count = checked_count(&selection.shape)?;
261        let width = self.dtype.bytes();
262        let mut bytes = self.to_le_bytes();
263        for mut flat in 0..selected_count {
264            let mut destination =
265                isize::try_from(selection.offset).map_err(|_| DataError::Bounds)?;
266            let mut source = isize::try_from(replacement.offset).map_err(|_| DataError::Bounds)?;
267            for axis in (0..selection.shape.len()).rev() {
268                let coordinate = flat % selection.shape[axis];
269                flat /= selection.shape[axis];
270                let coordinate = isize::try_from(coordinate).map_err(|_| DataError::Bounds)?;
271                destination = destination
272                    .checked_add(
273                        coordinate
274                            .checked_mul(selection.strides[axis])
275                            .ok_or(DataError::Bounds)?,
276                    )
277                    .ok_or(DataError::Bounds)?;
278                if axis >= prefix && replacement.shape[axis - prefix] != 1 {
279                    source = source
280                        .checked_add(
281                            coordinate
282                                .checked_mul(replacement.strides[axis - prefix])
283                                .ok_or(DataError::Bounds)?,
284                        )
285                        .ok_or(DataError::Bounds)?;
286                }
287            }
288            let value = scalar_at_bytes(
289                self.dtype,
290                &replacement.data,
291                usize::try_from(source).map_err(|_| DataError::Bounds)?,
292            )?;
293            let start = usize::try_from(destination)
294                .map_err(|_| DataError::Bounds)?
295                .checked_mul(width)
296                .ok_or(DataError::Bounds)?;
297            let end = start.checked_add(width).ok_or(DataError::Bounds)?;
298            bytes
299                .get_mut(start..end)
300                .ok_or(DataError::Bounds)?
301                .copy_from_slice(&value.bits().to_le_bytes()[..width]);
302        }
303        // Every written scalar came from validated immutable storage; the output
304        // uses the original logical shape with fresh contiguous byte ownership.
305        Ok(Self {
306            dtype: self.dtype,
307            shape: self.shape.clone(),
308            strides: Arc::from(output_strides),
309            offset: 0,
310            data: Arc::from(bytes),
311        })
312    }
313
314    /// Changes dimensions while preserving logical row-major order.
315    pub fn reshape(&self, shape: Vec<usize>) -> Result<Self, DataError> {
316        if checked_count(&shape)? != self.len() {
317            return Err(DataError::ShapeMismatch);
318        }
319        let data = if self.is_contiguous() {
320            self.data.clone()
321        } else {
322            Arc::from(self.to_le_bytes())
323        };
324        let offset = if self.is_contiguous() { self.offset } else { 0 };
325        let strides = contiguous_strides(&shape)?;
326        Ok(Self {
327            dtype: self.dtype,
328            shape: Arc::from(shape),
329            strides: Arc::from(strides),
330            offset,
331            data,
332        })
333    }
334
335    /// Reorders axes; each old axis must occur exactly once.
336    pub fn transpose(&self, axes: &[usize]) -> Result<Self, DataError> {
337        if axes.len() != self.shape.len() {
338            return Err(DataError::InvalidShape);
339        }
340        let mut seen = vec![false; axes.len()];
341        let mut shape = Vec::with_capacity(axes.len());
342        let mut strides = Vec::with_capacity(axes.len());
343        for &axis in axes {
344            if axis >= axes.len() || seen[axis] {
345                return Err(DataError::InvalidShape);
346            }
347            seen[axis] = true;
348            shape.push(self.shape[axis]);
349            strides.push(self.strides[axis]);
350        }
351        Ok(Self {
352            dtype: self.dtype,
353            shape: Arc::from(shape),
354            strides: Arc::from(strides),
355            offset: self.offset,
356            data: self.data.clone(),
357        })
358    }
359
360    /// Applies a scalar operation with NumPy trailing-axis broadcasting.
361    pub fn binary(&self, operation: BinaryOp, rhs: &Self) -> Result<Self, DataError> {
362        if self.dtype != rhs.dtype {
363            return Err(DataError::TypeMismatch);
364        }
365        let shape = broadcast_shape(&self.shape, &rhs.shape)?;
366        let count = checked_count(&shape)?;
367        let mut bytes = Vec::with_capacity(count * self.dtype.bytes());
368        for mut flat in 0..count {
369            let mut indices = vec![0isize; shape.len()];
370            for axis in (0..shape.len()).rev() {
371                indices[axis] = (flat % shape[axis]) as isize;
372                flat /= shape[axis];
373            }
374            let value = self
375                .value_broadcast(&indices, &shape)?
376                .binary(operation, self_or_rhs(rhs, &indices, &shape)?)?;
377            bytes.extend_from_slice(&value.bits().to_le_bytes()[..self.dtype.bytes()]);
378        }
379        Self::from_le_bytes(self.dtype, shape, bytes)
380    }
381
382    /// Applies a scalar operation to every element.
383    pub fn scalar_binary(&self, operation: BinaryOp, rhs: Scalar) -> Result<Self, DataError> {
384        if rhs.dtype() != self.dtype {
385            return Err(DataError::TypeMismatch);
386        }
387        let mut bytes = Vec::with_capacity(self.byte_len());
388        for index in 0..self.len() {
389            let value = self.scalar_at_linear(index).binary(operation, rhs)?;
390            bytes.extend_from_slice(&value.bits().to_le_bytes()[..self.dtype.bytes()]);
391        }
392        Self::from_le_bytes(self.dtype, self.shape.to_vec(), bytes)
393    }
394
395    /// Converts every scalar to `dtype`.
396    pub fn convert(&self, dtype: DType) -> Result<Self, DataError> {
397        let mut bytes = Vec::with_capacity(
398            self.len()
399                .checked_mul(dtype.bytes())
400                .ok_or(DataError::AllocationLimit)?,
401        );
402        for index in 0..self.len() {
403            let value = self.scalar_at_linear(index).convert(dtype)?;
404            bytes.extend_from_slice(&value.bits().to_le_bytes()[..dtype.bytes()]);
405        }
406        Self::from_le_bytes(dtype, self.shape.to_vec(), bytes)
407    }
408
409    /// Returns the additive reduction, or zero for an empty tensor.
410    pub fn sum(&self) -> Result<Scalar, DataError> {
411        let mut total = Scalar::zero(self.dtype);
412        for index in 0..self.len() {
413            total = total.binary(BinaryOp::Add, self.scalar_at_linear(index))?;
414        }
415        Ok(total)
416    }
417
418    /// Returns the element-wise arithmetic negation.
419    pub fn neg(&self) -> Result<Self, DataError> {
420        let mut bytes = Vec::with_capacity(self.byte_len());
421        for index in 0..self.len() {
422            let value = self.scalar_at_linear(index).neg()?;
423            bytes.extend_from_slice(&value.bits().to_le_bytes()[..self.dtype.bytes()]);
424        }
425        Self::from_le_bytes(self.dtype, self.shape.to_vec(), bytes)
426    }
427
428    /// Multiplies rank-two-or-greater tensors, broadcasting leading batch axes.
429    pub fn matmul(&self, rhs: &Self) -> Result<Self, DataError> {
430        if self.dtype != rhs.dtype {
431            return Err(DataError::TypeMismatch);
432        }
433        if self.shape.len() < 2 || rhs.shape.len() < 2 {
434            return Err(DataError::InvalidShape);
435        }
436        let (m, k) = (
437            self.shape[self.shape.len() - 2],
438            self.shape[self.shape.len() - 1],
439        );
440        if k != rhs.shape[rhs.shape.len() - 2] {
441            return Err(DataError::ShapeMismatch);
442        }
443        let n = rhs.shape[rhs.shape.len() - 1];
444        let batch = broadcast_shape(
445            &self.shape[..self.shape.len() - 2],
446            &rhs.shape[..rhs.shape.len() - 2],
447        )?;
448        let mut shape = batch.clone();
449        shape.push(m);
450        shape.push(n);
451        let count = checked_count(&shape)?;
452        let mut bytes = Vec::with_capacity(count * self.dtype.bytes());
453        for flat in 0..count {
454            let mut out = linear_indices(flat, &shape);
455            let column = out.pop().unwrap_or(0);
456            let row = out.pop().unwrap_or(0);
457            let mut total = Scalar::zero(self.dtype);
458            for inner in 0..k {
459                let left = self.matmul_value(&out, row, inner)?;
460                let right = rhs.matmul_value(&out, inner, column)?;
461                total = total.binary(BinaryOp::Add, left.binary(BinaryOp::Mul, right)?)?;
462            }
463            bytes.extend_from_slice(&total.bits().to_le_bytes()[..self.dtype.bytes()]);
464        }
465        Self::from_le_bytes(self.dtype, shape, bytes)
466    }
467
468    /// Materializes logical elements in row-major order.
469    pub fn to_scalars(&self) -> Vec<Scalar> {
470        let mut values = Vec::with_capacity(self.len());
471        for flat in 0..self.len() {
472            values.push(self.scalar_at_linear(flat));
473        }
474        values
475    }
476
477    /// Materializes logical elements as packed little-endian scalar bytes.
478    pub fn to_le_bytes(&self) -> Vec<u8> {
479        let width = self.dtype.bytes();
480        let mut bytes = Vec::with_capacity(self.byte_len());
481        for index in 0..self.len() {
482            bytes.extend_from_slice(&self.scalar_at_linear(index).bits().to_le_bytes()[..width]);
483        }
484        bytes
485    }
486
487    fn is_contiguous(&self) -> bool {
488        contiguous_strides(&self.shape).is_ok_and(|strides| strides == self.strides.as_ref())
489    }
490    fn value_broadcast(
491        &self,
492        output: &[isize],
493        output_shape: &[usize],
494    ) -> Result<Scalar, DataError> {
495        let prefix = output_shape
496            .len()
497            .checked_sub(self.shape.len())
498            .ok_or(DataError::ShapeMismatch)?;
499        let mut indices = Vec::with_capacity(self.shape.len());
500        for (axis, &dimension) in self.shape.iter().enumerate() {
501            indices.push(if dimension == 1 {
502                0
503            } else {
504                output[prefix + axis]
505            });
506        }
507        self.get(&indices)
508    }
509    fn matmul_value(
510        &self,
511        output_batch: &[usize],
512        row: usize,
513        column: usize,
514    ) -> Result<Scalar, DataError> {
515        let batch_rank = self.shape.len() - 2;
516        let output_prefix = output_batch
517            .len()
518            .checked_sub(batch_rank)
519            .ok_or(DataError::ShapeMismatch)?;
520        let mut indices = Vec::with_capacity(self.shape.len());
521        for axis in 0..batch_rank {
522            indices.push(if self.shape[axis] == 1 {
523                0
524            } else {
525                output_batch[output_prefix + axis] as isize
526            });
527        }
528        indices.push(row as isize);
529        indices.push(column as isize);
530        self.get(&indices)
531    }
532    fn scalar_at_linear(&self, mut flat: usize) -> Scalar {
533        let mut position = self.offset as isize;
534        for axis in (0..self.shape.len()).rev() {
535            let dimension = self.shape[axis];
536            let index = flat % dimension;
537            flat /= dimension;
538            position += (index as isize) * self.strides[axis];
539        }
540        scalar_at_bytes(self.dtype, &self.data, position as usize)
541            .expect("Tensor constructors and view operations preserve packed-data bounds")
542    }
543}
544
545struct SliceLayout {
546    shape: Vec<usize>,
547    strides: Vec<isize>,
548    offset: usize,
549}
550
551fn slice_layout(
552    input_shape: &[usize],
553    input_strides: &[isize],
554    initial_offset: usize,
555    indices: &[AxisIndex],
556) -> Result<SliceLayout, DataError> {
557    if indices.len() > 2 * MAX_RANK + 1 {
558        return Err(DataError::InvalidSlice);
559    }
560    let ellipses = indices
561        .iter()
562        .filter(|item| matches!(item, AxisIndex::Ellipsis))
563        .count();
564    if ellipses > 1 {
565        return Err(DataError::InvalidSlice);
566    }
567    let consumed = indices
568        .iter()
569        .filter(|item| !matches!(item, AxisIndex::NewAxis | AxisIndex::Ellipsis))
570        .count();
571    if consumed > input_shape.len() {
572        return Err(DataError::InvalidSlice);
573    }
574    let fill = input_shape.len() - consumed;
575    let mut expanded = Vec::new();
576    for item in indices {
577        if matches!(item, AxisIndex::Ellipsis) {
578            expanded.extend((0..fill).map(|_| AxisIndex::Slice {
579                start: None,
580                stop: None,
581                step: 1,
582            }));
583        } else {
584            expanded.push(item.clone());
585        }
586    }
587    if ellipses == 0 {
588        expanded.extend((0..fill).map(|_| AxisIndex::Slice {
589            start: None,
590            stop: None,
591            step: 1,
592        }));
593    }
594    let mut source_axis = 0usize;
595    let mut offset = initial_offset as isize;
596    let mut shape = Vec::new();
597    let mut strides = Vec::new();
598    for item in expanded {
599        match item {
600            AxisIndex::NewAxis => {
601                shape.push(1);
602                strides.push(0);
603            }
604            AxisIndex::Index(index) => {
605                let index = normalize_index(index, input_shape[source_axis])?;
606                offset = offset
607                    .checked_add(
608                        index
609                            .checked_mul(input_strides[source_axis])
610                            .ok_or(DataError::Bounds)?,
611                    )
612                    .ok_or(DataError::Bounds)?;
613                source_axis += 1;
614            }
615            AxisIndex::Slice { start, stop, step } => {
616                let (start, length) = normalize_slice(start, stop, step, input_shape[source_axis])?;
617                offset = offset
618                    .checked_add(
619                        start
620                            .checked_mul(input_strides[source_axis])
621                            .ok_or(DataError::Bounds)?,
622                    )
623                    .ok_or(DataError::Bounds)?;
624                shape.push(length);
625                strides.push(
626                    input_strides[source_axis]
627                        .checked_mul(step)
628                        .ok_or(DataError::Bounds)?,
629                );
630                source_axis += 1;
631            }
632            AxisIndex::Ellipsis => return Err(DataError::InvalidSlice),
633        }
634    }
635    let count = checked_count(&shape)?;
636    Ok(SliceLayout {
637        shape,
638        strides,
639        offset: if count == 0 {
640            0
641        } else {
642            usize::try_from(offset).map_err(|_| DataError::Bounds)?
643        },
644    })
645}
646
647fn self_or_rhs(tensor: &Tensor, indices: &[isize], shape: &[usize]) -> Result<Scalar, DataError> {
648    tensor.value_broadcast(indices, shape)
649}
650fn checked_count(shape: &[usize]) -> Result<usize, DataError> {
651    if shape.len() > MAX_RANK {
652        return Err(DataError::InvalidShape);
653    }
654    let count = shape.iter().try_fold(1usize, |a, &d| {
655        if d > isize::MAX as usize {
656            return Err(DataError::InvalidShape);
657        }
658        a.checked_mul(d).ok_or(DataError::AllocationLimit)
659    })?;
660    if count > MAX_ELEMENTS {
661        Err(DataError::AllocationLimit)
662    } else {
663        Ok(count)
664    }
665}
666fn contiguous_strides(shape: &[usize]) -> Result<Vec<isize>, DataError> {
667    checked_count(shape)?;
668    let mut result = vec![0; shape.len()];
669    let mut stride = 1usize;
670    for axis in (0..shape.len()).rev() {
671        result[axis] = isize::try_from(stride).map_err(|_| DataError::AllocationLimit)?;
672        stride = stride
673            .checked_mul(shape[axis])
674            .ok_or(DataError::AllocationLimit)?;
675    }
676    Ok(result)
677}
678fn normalize_index(index: isize, len: usize) -> Result<isize, DataError> {
679    let len = isize::try_from(len).map_err(|_| DataError::Bounds)?;
680    let index = if index < 0 {
681        index.checked_add(len).ok_or(DataError::Bounds)?
682    } else {
683        index
684    };
685    if index < 0 || index >= len {
686        Err(DataError::Bounds)
687    } else {
688        Ok(index)
689    }
690}
691fn normalize_slice(
692    start: Option<isize>,
693    stop: Option<isize>,
694    step: isize,
695    len: usize,
696) -> Result<(isize, usize), DataError> {
697    if step == 0 {
698        return Err(DataError::InvalidSlice);
699    }
700    let len = isize::try_from(len).map_err(|_| DataError::InvalidSlice)?;
701    if len == 0 {
702        return Ok((0, 0));
703    }
704    if step > 0 {
705        let mut a = start.unwrap_or(0);
706        let mut b = stop.unwrap_or(len);
707        if a < 0 {
708            a += len;
709        }
710        if b < 0 {
711            b += len;
712        }
713        a = a.clamp(0, len);
714        b = b.clamp(0, len);
715        let n = if b <= a { 0 } else { (b - a - 1) / step + 1 };
716        Ok((a, n as usize))
717    } else {
718        let mut a = start.unwrap_or(len - 1);
719        let mut b = stop.unwrap_or(-1);
720        if start.is_some() && a < 0 {
721            a += len;
722        }
723        if stop.is_some() && b < 0 {
724            b += len;
725        }
726        a = a.clamp(-1, len - 1);
727        b = b.clamp(-1, len - 1);
728        let n = if a <= b {
729            0
730        } else {
731            usize::try_from(a - b - 1).map_err(|_| DataError::InvalidSlice)? / step.unsigned_abs()
732                + 1
733        };
734        Ok((a, n))
735    }
736}
737fn broadcast_shape(left: &[usize], right: &[usize]) -> Result<Vec<usize>, DataError> {
738    let rank = left.len().max(right.len());
739    if rank > MAX_RANK {
740        return Err(DataError::InvalidShape);
741    }
742    let mut shape = vec![1; rank];
743    for out in 0..rank {
744        let a = if out < rank - left.len() {
745            1
746        } else {
747            left[out - (rank - left.len())]
748        };
749        let b = if out < rank - right.len() {
750            1
751        } else {
752            right[out - (rank - right.len())]
753        };
754        shape[out] = if a == b {
755            a
756        } else if a == 1 {
757            b
758        } else if b == 1 {
759            a
760        } else {
761            return Err(DataError::ShapeMismatch);
762        };
763    }
764    checked_count(&shape)?;
765    Ok(shape)
766}
767fn linear_indices(mut flat: usize, shape: &[usize]) -> Vec<usize> {
768    let mut indices = vec![0; shape.len()];
769    for axis in (0..shape.len()).rev() {
770        if shape[axis] != 0 {
771            indices[axis] = flat % shape[axis];
772            flat /= shape[axis];
773        }
774    }
775    indices
776}
777fn scalar_at_bytes(dtype: DType, bytes: &[u8], index: usize) -> Result<Scalar, DataError> {
778    let width = dtype.bytes();
779    let start = index.checked_mul(width).ok_or(DataError::Bounds)?;
780    let slice = bytes
781        .get(start..start.checked_add(width).ok_or(DataError::Bounds)?)
782        .ok_or(DataError::Bounds)?;
783    let mut raw = [0u8; 16];
784    raw[..width].copy_from_slice(slice);
785    Scalar::from_bits(dtype, u128::from_le_bytes(raw))
786}
787fn values_to_bytes(dtype: DType, values: &[Scalar]) -> Result<Vec<u8>, DataError> {
788    let width = dtype.bytes();
789    let mut bytes = Vec::with_capacity(
790        values
791            .len()
792            .checked_mul(width)
793            .ok_or(DataError::AllocationLimit)?,
794    );
795    for value in values {
796        if value.dtype() != dtype {
797            return Err(DataError::TypeMismatch);
798        }
799        bytes.extend_from_slice(&value.bits().to_le_bytes()[..width]);
800    }
801    Ok(bytes)
802}
803
804impl Serialize for Tensor {
805    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
806        // Keep this streaming: a tensor may contain MAX_ELEMENTS values. The
807        // generated `wire::Tensor` declares this exact object shape for hosts.
808        let mut state = serializer.serialize_struct("Tensor", 3)?;
809        state.serialize_field("dtype", &self.dtype)?;
810        state.serialize_field("shape", &self.shape.as_ref())?;
811        state.serialize_field("data", &DecimalValues(self))?;
812        state.end()
813    }
814}
815
816struct DecimalValues<'a>(&'a Tensor);
817
818impl Serialize for DecimalValues<'_> {
819    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
820        let mut sequence = serializer.serialize_seq(Some(self.0.len()))?;
821        for index in 0..self.0.len() {
822            sequence.serialize_element(&self.0.scalar_at_linear(index).to_string())?;
823        }
824        sequence.end()
825    }
826}
827
828impl<'de> Deserialize<'de> for Tensor {
829    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
830        #[derive(Deserialize)]
831        #[serde(deny_unknown_fields)]
832        struct Wire {
833            dtype: DType,
834            #[serde(deserialize_with = "deserialize_shape")]
835            shape: Vec<usize>,
836            #[serde(deserialize_with = "deserialize_decimal_data")]
837            data: Vec<alloc::string::String>,
838        }
839        let wire = Wire::deserialize(deserializer)?;
840        let count = checked_count(&wire.shape).map_err(D::Error::custom)?;
841        if wire.data.len() != count {
842            return Err(D::Error::custom(DataError::ShapeMismatch));
843        }
844        let mut values = Vec::with_capacity(count);
845        for text in wire.data {
846            values.push(Scalar::parse(wire.dtype, &text).map_err(D::Error::custom)?);
847        }
848        Tensor::from_scalars(wire.dtype, wire.shape, values).map_err(D::Error::custom)
849    }
850}
851
852fn deserialize_shape<'de, D>(deserializer: D) -> Result<Vec<usize>, D::Error>
853where
854    D: Deserializer<'de>,
855{
856    deserialize_limited_sequence(deserializer, MAX_RANK, PhantomData)
857}
858
859fn deserialize_decimal_data<'de, D>(deserializer: D) -> Result<Vec<alloc::string::String>, D::Error>
860where
861    D: Deserializer<'de>,
862{
863    deserialize_limited_sequence(deserializer, MAX_ELEMENTS, PhantomData)
864}
865
866fn deserialize_limited_sequence<'de, D, T>(
867    deserializer: D,
868    limit: usize,
869    marker: PhantomData<T>,
870) -> Result<Vec<T>, D::Error>
871where
872    D: Deserializer<'de>,
873    T: Deserialize<'de>,
874{
875    struct Limited<T> {
876        limit: usize,
877        marker: PhantomData<T>,
878    }
879    impl<'de, T> Visitor<'de> for Limited<T>
880    where
881        T: Deserialize<'de>,
882    {
883        type Value = Vec<T>;
884
885        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
886            write!(formatter, "an array with at most {} values", self.limit)
887        }
888
889        fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
890        where
891            A: SeqAccess<'de>,
892        {
893            let mut values = Vec::new();
894            while let Some(value) = sequence.next_element()? {
895                if values.len() == self.limit {
896                    return Err(A::Error::custom(DataError::AllocationLimit));
897                }
898                values.push(value);
899            }
900            Ok(values)
901        }
902    }
903    deserializer.deserialize_seq(Limited { limit, marker })
904}
905
906impl fmt::Display for Tensor {
907    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
908        write!(formatter, "Tensor<{:?}, {:?}>", self.dtype, self.shape)
909    }
910}
911
912#[cfg(test)]
913mod tests {
914    use super::*;
915    #[test]
916    fn negative_slice_reverses() {
917        let t = Tensor::from_scalars(
918            DType::I32,
919            vec![4],
920            vec![
921                Scalar::parse(DType::I32, "1").unwrap(),
922                Scalar::parse(DType::I32, "2").unwrap(),
923                Scalar::parse(DType::I32, "3").unwrap(),
924                Scalar::parse(DType::I32, "4").unwrap(),
925            ],
926        )
927        .unwrap();
928        let r = t
929            .slice(&[AxisIndex::Slice {
930                start: None,
931                stop: None,
932                step: -1,
933            }])
934            .unwrap();
935        assert_eq!(r.get(&[0]).unwrap().to_string(), "4");
936        assert_eq!(r.get(&[3]).unwrap().to_string(), "1");
937        let minimum_step = t
938            .slice(&[AxisIndex::Slice {
939                start: None,
940                stop: None,
941                step: isize::MIN,
942            }])
943            .unwrap();
944        assert_eq!(strings(&minimum_step), ["4"]);
945    }
946    #[test]
947    fn broadcast_adds_trailing_axes() {
948        let a = Tensor::from_scalars(
949            DType::I32,
950            vec![2, 1],
951            vec![
952                Scalar::parse(DType::I32, "1").unwrap(),
953                Scalar::parse(DType::I32, "2").unwrap(),
954            ],
955        )
956        .unwrap();
957        let b = Tensor::from_scalars(
958            DType::I32,
959            vec![3],
960            (3..6)
961                .map(|n| Scalar::parse(DType::I32, &n.to_string()).unwrap())
962                .collect(),
963        )
964        .unwrap();
965        let r = a.binary(BinaryOp::Add, &b).unwrap();
966        assert_eq!(r.shape(), &[2, 3]);
967        assert_eq!(r.get(&[1, 2]).unwrap().to_string(), "7");
968    }
969
970    #[test]
971    fn zero_axis_broadcast_has_no_division_or_elements() {
972        let empty = Tensor::zeros(DType::I8, vec![0, 3]).unwrap();
973        let row = Tensor::zeros(DType::I8, vec![1, 3]).unwrap();
974        let result = empty.binary(BinaryOp::Add, &row).unwrap();
975        assert_eq!(result.shape(), &[0, 3]);
976        assert!(result.is_empty());
977    }
978
979    #[test]
980    fn noncontiguous_negative_slice_materializes_in_logical_order() {
981        let source = ints(DType::I16, &[2, 3], &[1, 2, 3, 4, 5, 6]);
982        let reverse_columns = source
983            .slice(&[
984                AxisIndex::Slice {
985                    start: None,
986                    stop: None,
987                    step: 1,
988                },
989                AxisIndex::Slice {
990                    start: None,
991                    stop: None,
992                    step: -1,
993                },
994            ])
995            .unwrap();
996        let reshaped = reverse_columns.reshape(vec![3, 2]).unwrap();
997        assert_eq!(strings(&reshaped), ["3", "2", "1", "6", "5", "4"]);
998        assert_eq!(
999            reverse_columns,
1000            ints(DType::I16, &[2, 3], &[3, 2, 1, 6, 5, 4])
1001        );
1002        let empty = Tensor::zeros(DType::I16, vec![0]).unwrap();
1003        assert!(
1004            empty
1005                .slice(&[AxisIndex::Slice {
1006                    start: None,
1007                    stop: None,
1008                    step: -1,
1009                }])
1010                .unwrap()
1011                .is_empty()
1012        );
1013    }
1014
1015    #[test]
1016    fn failed_i8_arithmetic_does_not_mutate_shared_view() {
1017        let source = ints(DType::I8, &[2], &[126, 127]);
1018        let view = source
1019            .slice(&[AxisIndex::Slice {
1020                start: Some(1),
1021                stop: None,
1022                step: 1,
1023            }])
1024            .unwrap();
1025        let one = Scalar::parse(DType::I8, "1").unwrap();
1026        assert_eq!(
1027            view.scalar_binary(BinaryOp::Add, one),
1028            Err(DataError::Overflow)
1029        );
1030        assert_eq!(view.get(&[0]).unwrap().to_string(), "127");
1031        assert_eq!(source.get(&[1]).unwrap().to_string(), "127");
1032    }
1033
1034    #[test]
1035    fn reductions_and_matmul_report_checked_overflow() {
1036        let values = ints(DType::I16, &[3], &[1, 2, 3]);
1037        assert_eq!(values.sum().unwrap().to_string(), "6");
1038        let left = ints(DType::I8, &[1, 1], &[127]);
1039        let right = ints(DType::I8, &[1, 1], &[2]);
1040        assert_eq!(left.matmul(&right), Err(DataError::Overflow));
1041    }
1042
1043    #[test]
1044    fn matmul_handles_values_batches_and_empty_inner_axes() {
1045        let left = ints(DType::I16, &[2, 3], &[1, 2, 3, 4, 5, 6]);
1046        let right = ints(DType::I16, &[3, 2], &[7, 8, 9, 10, 11, 12]);
1047        assert_eq!(
1048            strings(&left.matmul(&right).unwrap()),
1049            ["58", "64", "139", "154"]
1050        );
1051
1052        let batched = ints(
1053            DType::I16,
1054            &[2, 2, 3],
1055            &[1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6],
1056        );
1057        let result = batched.matmul(&right).unwrap();
1058        assert_eq!(result.shape(), &[2, 2, 2]);
1059        assert_eq!(
1060            strings(&result),
1061            ["58", "64", "139", "154", "58", "64", "139", "154"]
1062        );
1063
1064        let empty_left = Tensor::zeros(DType::I16, vec![2, 0]).unwrap();
1065        let empty_right = Tensor::zeros(DType::I16, vec![0, 3]).unwrap();
1066        assert_eq!(
1067            strings(&empty_left.matmul(&empty_right).unwrap()),
1068            ["0", "0", "0", "0", "0", "0"]
1069        );
1070    }
1071
1072    #[test]
1073    fn views_retain_storage_and_support_extended_indexing() {
1074        let source = ints(DType::I16, &[2, 3], &[1, 2, 3, 4, 5, 6]);
1075        let transposed = source.transpose(&[1, 0]).unwrap();
1076        let view = transposed
1077            .slice(&[
1078                AxisIndex::NewAxis,
1079                AxisIndex::Ellipsis,
1080                AxisIndex::Index(-1),
1081            ])
1082            .unwrap();
1083        assert_eq!(view.shape(), &[1, 3]);
1084        assert_eq!(view.get(&[-1, -1]).unwrap().to_string(), "6");
1085        assert_eq!(view.storage_byte_len(), source.storage_byte_len());
1086        assert!(view.byte_len() < view.storage_byte_len());
1087        assert_eq!(source.transpose(&[0, 0]), Err(DataError::InvalidShape));
1088        assert_eq!(source.transpose(&[0]), Err(DataError::InvalidShape));
1089    }
1090
1091    #[test]
1092    fn slice_replacement_broadcasts_into_reversed_columns_without_mutation() {
1093        let original = ints(DType::I16, &[2, 3], &[1, 2, 3, 4, 5, 6]);
1094        let alias = original.clone();
1095        let row = ints(DType::I16, &[3], &[10, 20, 30]);
1096        let updated = original
1097            .with_slice(&[AxisIndex::Ellipsis, reverse()], &row)
1098            .unwrap();
1099        assert_eq!(strings(&updated), ["30", "20", "10", "30", "20", "10"]);
1100        assert_eq!(updated.shape(), original.shape());
1101        assert_eq!(original, alias);
1102        assert_eq!(strings(&original), ["1", "2", "3", "4", "5", "6"]);
1103        assert!(!Arc::ptr_eq(&updated.data, &original.data));
1104
1105        let scalar = ints(DType::I16, &[], &[99]);
1106        let indexed = original
1107            .with_slice(&[AxisIndex::Index(-1), AxisIndex::Index(-1)], &scalar)
1108            .unwrap();
1109        assert_eq!(strings(&indexed), ["1", "2", "3", "4", "5", "99"]);
1110        let expanded = original
1111            .with_slice(&[AxisIndex::NewAxis, AxisIndex::Ellipsis], &row)
1112            .unwrap();
1113        assert_eq!(strings(&expanded), ["10", "20", "30", "10", "20", "30"]);
1114    }
1115
1116    #[test]
1117    fn overlapping_and_noncontiguous_replacements_read_original_storage() {
1118        let original = ints(DType::I16, &[5], &[1, 2, 3, 4, 5]);
1119        let replacement = original
1120            .slice(&[AxisIndex::Slice {
1121                start: None,
1122                stop: Some(4),
1123                step: 1,
1124            }])
1125            .unwrap();
1126        let updated = original
1127            .with_slice(
1128                &[AxisIndex::Slice {
1129                    start: Some(1),
1130                    stop: None,
1131                    step: 1,
1132                }],
1133                &replacement,
1134            )
1135            .unwrap();
1136        assert_eq!(strings(&updated), ["1", "1", "2", "3", "4"]);
1137        assert_eq!(strings(&original), ["1", "2", "3", "4", "5"]);
1138        assert_eq!(strings(&replacement), ["1", "2", "3", "4"]);
1139        let reversed = original.slice(&[reverse()]).unwrap();
1140        assert_eq!(
1141            strings(&original.with_slice(&[], &reversed).unwrap()),
1142            ["5", "4", "3", "2", "1"]
1143        );
1144
1145        let matrix = ints(DType::I16, &[2, 3], &[1, 2, 3, 4, 5, 6]);
1146        let transposed = matrix.transpose(&[1, 0]).unwrap();
1147        let row = ints(DType::I16, &[2], &[8, 9]);
1148        let changed = transposed.with_slice(&[AxisIndex::Index(1)], &row).unwrap();
1149        assert_eq!(changed.shape(), &[3, 2]);
1150        assert_eq!(strings(&changed), ["1", "4", "8", "9", "3", "6"]);
1151        assert_eq!(strings(&transposed), ["1", "4", "2", "5", "3", "6"]);
1152        assert_eq!(strings(&matrix), ["1", "2", "3", "4", "5", "6"]);
1153    }
1154
1155    #[test]
1156    fn replacing_a_subview_materializes_only_its_logical_tensor() {
1157        let original = ints(DType::I16, &[2, 4], &[1, 2, 3, 4, 5, 6, 7, 8]);
1158        let view = original
1159            .slice(&[
1160                AxisIndex::Index(1),
1161                AxisIndex::Slice {
1162                    start: None,
1163                    stop: None,
1164                    step: 2,
1165                },
1166            ])
1167            .unwrap();
1168        let updated = view
1169            .with_slice(&[AxisIndex::Index(0)], &ints(DType::I16, &[], &[99]))
1170            .unwrap();
1171        assert_eq!(strings(&updated), ["99", "7"]);
1172        assert_eq!(updated.shape(), &[2]);
1173        assert_eq!(updated.storage_byte_len(), 4);
1174        assert_eq!(view.storage_byte_len(), 16);
1175        assert_eq!(strings(&view), ["5", "7"]);
1176        assert_eq!(original.get(&[1, 0]).unwrap().to_string(), "5");
1177    }
1178
1179    #[test]
1180    fn nested_index_paths_compose_before_replacement() {
1181        let original = ints(DType::I16, &[2, 3], &[1, 2, 3, 4, 5, 6]);
1182        let path = vec![
1183            vec![AxisIndex::Ellipsis, reverse()],
1184            vec![AxisIndex::Index(1)],
1185            vec![AxisIndex::Slice {
1186                start: None,
1187                stop: Some(2),
1188                step: 1,
1189            }],
1190        ];
1191        let replacement = ints(DType::I16, &[2], &[8, 9]);
1192        let updated = original.with_index_path(&path, &replacement).unwrap();
1193        assert_eq!(strings(&updated), ["1", "2", "3", "4", "9", "8"]);
1194        assert_eq!(strings(&original), ["1", "2", "3", "4", "5", "6"]);
1195        let transposed = original.transpose(&[1, 0]).unwrap();
1196        let updated = transposed
1197            .with_index_path(&[vec![reverse()], vec![AxisIndex::Index(0)]], &replacement)
1198            .unwrap();
1199        assert_eq!(strings(&updated), ["1", "4", "2", "5", "8", "9"]);
1200        assert_eq!(
1201            original.with_index_path(&vec![vec![]; MAX_RANK + 1], &replacement),
1202            Err(DataError::InvalidSlice)
1203        );
1204        let scalar = ints(DType::I16, &[], &[7]);
1205        assert_eq!(
1206            strings(&original.with_index_path(&[], &scalar).unwrap()),
1207            ["7", "7", "7", "7", "7", "7"]
1208        );
1209    }
1210
1211    #[test]
1212    fn slice_replacement_checks_empty_shapes_scalars_types_and_bounds() {
1213        let empty = Tensor::zeros(DType::I16, vec![0, 3]).unwrap();
1214        let row = ints(DType::I16, &[3], &[1, 2, 3]);
1215        assert!(empty.with_slice(&[], &row).unwrap().is_empty());
1216        assert!(
1217            empty
1218                .with_slice(&[reverse()], &ints(DType::I16, &[], &[1]))
1219                .unwrap()
1220                .is_empty()
1221        );
1222        assert_eq!(
1223            empty.with_slice(&[], &ints(DType::I16, &[2], &[1, 2])),
1224            Err(DataError::ShapeMismatch)
1225        );
1226        assert_eq!(
1227            empty.with_slice(&[], &ints(DType::I32, &[3], &[1, 2, 3])),
1228            Err(DataError::TypeMismatch)
1229        );
1230        assert_eq!(
1231            empty.with_slice(&[AxisIndex::Index(0)], &row),
1232            Err(DataError::Bounds)
1233        );
1234
1235        let scalar = ints(DType::I16, &[], &[7]);
1236        assert_eq!(
1237            strings(
1238                &scalar
1239                    .with_slice(&[], &ints(DType::I16, &[], &[9]))
1240                    .unwrap()
1241            ),
1242            ["9"]
1243        );
1244        assert_eq!(
1245            scalar.with_slice(&[], &ints(DType::I16, &[1], &[9])),
1246            Err(DataError::ShapeMismatch)
1247        );
1248        let original = ints(DType::I16, &[3], &[1, 2, 3]);
1249        assert_eq!(
1250            original.with_slice(&[AxisIndex::Ellipsis, AxisIndex::Ellipsis], &scalar),
1251            Err(DataError::InvalidSlice)
1252        );
1253        assert_eq!(
1254            original.with_slice(
1255                &[AxisIndex::Slice {
1256                    start: None,
1257                    stop: None,
1258                    step: 0
1259                }],
1260                &scalar
1261            ),
1262            Err(DataError::InvalidSlice)
1263        );
1264        assert_eq!(
1265            original.with_slice(&[], &ints(DType::I16, &[1, 3], &[1, 2, 3])),
1266            Err(DataError::ShapeMismatch)
1267        );
1268        let minimum_step = original
1269            .with_slice(
1270                &[AxisIndex::Slice {
1271                    start: None,
1272                    stop: None,
1273                    step: isize::MIN,
1274                }],
1275                &scalar,
1276            )
1277            .unwrap();
1278        assert_eq!(strings(&minimum_step), ["1", "2", "7"]);
1279        assert_eq!(strings(&original), ["1", "2", "3"]);
1280    }
1281
1282    fn reverse() -> AxisIndex {
1283        AxisIndex::Slice {
1284            start: None,
1285            stop: None,
1286            step: -1,
1287        }
1288    }
1289
1290    #[test]
1291    fn serde_rejects_malformed_counts_nonfinite_values_and_large_shapes() {
1292        let count = serde_json::from_str::<Tensor>(r#"{"dtype":"i8","shape":[2],"data":["1"]}"#);
1293        assert!(count.is_err());
1294        let nonfinite =
1295            serde_json::from_str::<Tensor>(r#"{"dtype":"f32","shape":[1],"data":["NaN"]}"#);
1296        assert!(nonfinite.is_err());
1297        let large =
1298            serde_json::from_str::<Tensor>(r#"{"dtype":"u8","shape":[16777217],"data":[]}"#);
1299        assert!(large.is_err());
1300    }
1301
1302    #[test]
1303    fn streaming_serde_matches_generated_tensor_wire_shape() {
1304        let tensor = ints(DType::I128, &[2], &[1, i128::MAX]);
1305        let expected = serde_json::to_value(crate::data::wire::Tensor {
1306            dtype: tensor.dtype(),
1307            shape: tensor.shape().to_vec(),
1308            data: strings(&tensor),
1309        })
1310        .unwrap();
1311        assert_eq!(serde_json::to_value(&tensor).unwrap(), expected);
1312    }
1313
1314    fn ints(dtype: DType, shape: &[usize], values: &[i128]) -> Tensor {
1315        Tensor::from_scalars(
1316            dtype,
1317            shape.to_vec(),
1318            values
1319                .iter()
1320                .map(|value| Scalar::parse(dtype, &value.to_string()).unwrap())
1321                .collect(),
1322        )
1323        .unwrap()
1324    }
1325
1326    fn strings(tensor: &Tensor) -> Vec<alloc::string::String> {
1327        tensor
1328            .to_scalars()
1329            .iter()
1330            .map(ToString::to_string)
1331            .collect()
1332    }
1333}