Skip to main content

tenferro_tensor/
backend.rs

1use crate::config::{
2    CompareDir, DotGeneralConfig, GatherConfig, PadConfig, ScatterConfig, SliceConfig,
3};
4use crate::types::{
5    TensorRank, TensorScalar, TensorView, TypedTensor, TypedTensorView, TypedTensorViewMut,
6};
7use crate::validate::validate_convert_dtype;
8use crate::{
9    AllocationDomainId, AllocationId, DType, Error, ShapeMismatch, ShapeVec, Tensor, TensorRead,
10    TensorValue, TensorWrite, ValidationError,
11};
12use num_complex::{Complex32, Complex64};
13
14#[cfg(test)]
15mod tests;
16
17fn read_boundary_error(op: &'static str) -> crate::Error {
18    crate::Error::unsupported(
19        op,
20        "backend does not accept borrowed tensor views at this execution boundary",
21    )
22}
23
24fn validation(op: &'static str, source: ValidationError) -> crate::Error {
25    Error::validation(op, source)
26}
27
28/// Gather any host-owned view, including strided or offset layouts, into a compact owner.
29///
30/// This backs the default [`TensorStructural::to_contiguous_read`] for host-owned
31/// views. Backend-owned storage must be rejected by the caller first; the typed
32/// `to_col_major` gather is metadata-driven element copying and never transfers.
33fn default_materialize_host_view(view: TensorView<'_>) -> crate::Result<Tensor> {
34    fn typed<T: TensorScalar>(view: &TypedTensorView<'_, T>) -> crate::Result<Tensor> {
35        view.to_col_major().map(Tensor::from_typed)
36    }
37
38    match view {
39        TensorView::F32(view) => typed(&view),
40        TensorView::F64(view) => typed(&view),
41        TensorView::I32(view) => typed(&view),
42        TensorView::I64(view) => typed(&view),
43        TensorView::Bool(view) => typed(&view),
44        TensorView::C32(view) => typed(&view),
45        TensorView::C64(view) => typed(&view),
46    }
47}
48
49fn invalid_argument(op: &'static str, argument: &'static str, message: impl Into<String>) -> Error {
50    Error::invalid_argument(op, argument, message)
51}
52
53fn read_tensor<'a>(op: &'static str, input: TensorRead<'a>) -> crate::Result<&'a Tensor> {
54    input.as_tensor().ok_or_else(|| read_boundary_error(op))
55}
56
57/// Return the owned tensor behind `input`, or the read-boundary error for a borrowed view.
58///
59/// This is the read-half default a backend receives when it implements only the
60/// one-shot form of an operation: an owned tensor is delegated to the one-shot
61/// method, and a borrowed view is rejected with [`Error::Unsupported`] instead
62/// of being silently materialized.
63///
64/// Issue #1926 makes the `_read` halves required, so an implementor that relied
65/// on the previous default reproduces it by calling this function:
66///
67/// ```text
68/// fn reduce_sum_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> Result<Tensor> {
69///     self.reduce_sum(read_owned_tensor("reduce_sum", input)?, axes)
70/// }
71/// ```
72#[doc(hidden)]
73pub fn read_owned_tensor<'a>(op: &'static str, input: TensorRead<'a>) -> crate::Result<&'a Tensor> {
74    read_tensor(op, input)
75}
76
77fn validate_axis_list(
78    op: &'static str,
79    role: &'static str,
80    axes: &[usize],
81    rank: usize,
82) -> crate::Result<()> {
83    // A linear scan over the axes already seen keeps this path free of a
84    // rank-sized scratch allocation; the axis lists here are short.
85    for (index, &axis) in axes.iter().enumerate() {
86        if axis >= rank {
87            return Err(validation(
88                op,
89                ValidationError::AxisOutOfBounds { axis, rank },
90            ));
91        }
92        if axes[..index].contains(&axis) {
93            return Err(validation(
94                op,
95                ValidationError::DuplicateAxis { axis, role },
96            ));
97        }
98    }
99    Ok(())
100}
101
102fn validate_role_disjoint(
103    op: &'static str,
104    first_role: &'static str,
105    first_axes: &[usize],
106    second_role: &'static str,
107    second_axes: &[usize],
108) -> crate::Result<()> {
109    for &axis in first_axes {
110        if second_axes.contains(&axis) {
111            return Err(validation(
112                op,
113                ValidationError::AxisRoleConflict {
114                    axis,
115                    first_role,
116                    second_role,
117                },
118            ));
119        }
120    }
121    Ok(())
122}
123
124/// Infer the output shape for a validated dot-general operation.
125#[doc(hidden)]
126pub fn dot_general_output_shape(
127    lhs_shape: &[usize],
128    rhs_shape: &[usize],
129    config: &DotGeneralConfig,
130    op: &'static str,
131) -> crate::Result<ShapeVec> {
132    if config.lhs_contracting_dims.len() != config.rhs_contracting_dims.len() {
133        return Err(invalid_argument(
134            op,
135            "contracting_dims",
136            "lhs/rhs contracting dim counts differ",
137        ));
138    }
139    if config.lhs_batch_dims.len() != config.rhs_batch_dims.len() {
140        return Err(invalid_argument(
141            op,
142            "batch_dims",
143            "lhs/rhs batch dim counts differ",
144        ));
145    }
146
147    let lhs_rank = lhs_shape.len();
148    let rhs_rank = rhs_shape.len();
149    validate_axis_list(
150        op,
151        "lhs_contracting",
152        &config.lhs_contracting_dims,
153        lhs_rank,
154    )?;
155    validate_axis_list(
156        op,
157        "rhs_contracting",
158        &config.rhs_contracting_dims,
159        rhs_rank,
160    )?;
161    validate_axis_list(op, "lhs_batch", &config.lhs_batch_dims, lhs_rank)?;
162    validate_axis_list(op, "rhs_batch", &config.rhs_batch_dims, rhs_rank)?;
163    validate_role_disjoint(
164        op,
165        "lhs_contracting",
166        &config.lhs_contracting_dims,
167        "lhs_batch",
168        &config.lhs_batch_dims,
169    )?;
170    validate_role_disjoint(
171        op,
172        "rhs_contracting",
173        &config.rhs_contracting_dims,
174        "rhs_batch",
175        &config.rhs_batch_dims,
176    )?;
177
178    for (&lhs_axis, &rhs_axis) in config
179        .lhs_contracting_dims
180        .iter()
181        .zip(&config.rhs_contracting_dims)
182    {
183        if lhs_shape[lhs_axis] != rhs_shape[rhs_axis] {
184            return Err(validation(
185                op,
186                ShapeMismatch::ContractedDimensions {
187                    lhs_axis,
188                    lhs_size: lhs_shape[lhs_axis],
189                    rhs_axis,
190                    rhs_size: rhs_shape[rhs_axis],
191                }
192                .into(),
193            ));
194        }
195    }
196    for (&lhs_axis, &rhs_axis) in config.lhs_batch_dims.iter().zip(&config.rhs_batch_dims) {
197        if lhs_shape[lhs_axis] != rhs_shape[rhs_axis] {
198            return Err(validation(
199                op,
200                ShapeMismatch::ContractedDimensions {
201                    lhs_axis,
202                    lhs_size: lhs_shape[lhs_axis],
203                    rhs_axis,
204                    rhs_size: rhs_shape[rhs_axis],
205                }
206                .into(),
207            ));
208        }
209    }
210
211    let lhs_free = (0..lhs_rank)
212        .filter(|axis| {
213            !config.lhs_contracting_dims.contains(axis) && !config.lhs_batch_dims.contains(axis)
214        })
215        .map(|axis| lhs_shape[axis]);
216    let rhs_free = (0..rhs_rank)
217        .filter(|axis| {
218            !config.rhs_contracting_dims.contains(axis) && !config.rhs_batch_dims.contains(axis)
219        })
220        .map(|axis| rhs_shape[axis]);
221    let batch = config.lhs_batch_dims.iter().map(|&axis| lhs_shape[axis]);
222
223    // Inline for common ranks: the eager dot entry builds this on every call.
224    Ok(lhs_free.chain(rhs_free).chain(batch).collect())
225}
226
227/// Validate output dtype and shape for dot-general read-into dispatch.
228#[doc(hidden)]
229pub fn validate_dot_general_read_into(
230    lhs: &TensorRead<'_>,
231    rhs: &TensorRead<'_>,
232    config: &DotGeneralConfig,
233    out: &TensorWrite<'_>,
234    op: &'static str,
235) -> crate::Result<ShapeVec> {
236    if lhs.dtype() != rhs.dtype() {
237        return Err(validation(
238            op,
239            ValidationError::DTypeMismatch {
240                expected: lhs.dtype(),
241                actual: rhs.dtype(),
242            },
243        ));
244    }
245    if lhs.dtype() != out.dtype() {
246        return Err(validation(
247            op,
248            ValidationError::DTypeMismatch {
249                expected: lhs.dtype(),
250                actual: out.dtype(),
251            },
252        ));
253    }
254    let expected = dot_general_output_shape(lhs.shape(), rhs.shape(), config, op)?;
255    if out.shape() != expected.as_slice() {
256        return Err(validation(
257            op,
258            ShapeMismatch::ExpectedActual {
259                expected: expected.clone(),
260                actual: out.shape().iter().copied().collect(),
261            }
262            .into(),
263        ));
264    }
265    Ok(expected)
266}
267
268/// Scalar coefficient accepted by contraction accumulation backends.
269///
270/// `ContractionScalar` is intentionally narrower than [`crate::TensorScalar`]:
271/// dot-general accumulation is only defined for floating and complex tensor
272/// dtypes.
273///
274/// # Examples
275///
276/// ```rust
277/// use tenferro_tensor::{ContractionScalar, DType};
278///
279/// let alpha = ContractionScalar::F64(2.0);
280/// assert_eq!(alpha.dtype(), DType::F64);
281/// ```
282#[derive(Clone, Copy, Debug, PartialEq)]
283pub enum ContractionScalar {
284    F32(f32),
285    F64(f64),
286    C32(Complex32),
287    C64(Complex64),
288}
289
290impl ContractionScalar {
291    /// Return this scalar's tensor dtype.
292    ///
293    /// # Examples
294    ///
295    /// ```rust
296    /// use tenferro_tensor::{ContractionScalar, DType};
297    ///
298    /// assert_eq!(ContractionScalar::F32(1.0).dtype(), DType::F32);
299    /// ```
300    pub fn dtype(self) -> DType {
301        match self {
302            Self::F32(_) => DType::F32,
303            Self::F64(_) => DType::F64,
304            Self::C32(_) => DType::C32,
305            Self::C64(_) => DType::C64,
306        }
307    }
308
309    /// Return the multiplicative identity for a supported contraction dtype.
310    ///
311    /// # Examples
312    ///
313    /// ```rust
314    /// use tenferro_tensor::{ContractionScalar, DType};
315    ///
316    /// assert_eq!(ContractionScalar::one(DType::F64).unwrap(), ContractionScalar::F64(1.0));
317    /// assert!(ContractionScalar::one(DType::I32).is_err());
318    /// ```
319    /// # Errors
320    ///
321    /// Returns [`crate::Error::Validation`] with a
322    /// [`crate::ValidationError::DTypeMismatch`] source when `dtype` is `I32`,
323    /// `I64`, or `Bool`, which do not support contraction scalar identities.
324    pub fn one(dtype: DType) -> crate::Result<Self> {
325        match dtype {
326            DType::F32 => Ok(Self::F32(1.0)),
327            DType::F64 => Ok(Self::F64(1.0)),
328            DType::C32 => Ok(Self::C32(Complex32::new(1.0, 0.0))),
329            DType::C64 => Ok(Self::C64(Complex64::new(1.0, 0.0))),
330            DType::I32 | DType::I64 | DType::Bool | DType::External(_) => Err(validation(
331                "ContractionScalar::one",
332                ValidationError::DTypeMismatch {
333                    expected: dtype,
334                    actual: DType::F32,
335                },
336            )),
337        }
338    }
339
340    /// Return the additive identity for a supported contraction dtype.
341    ///
342    /// # Examples
343    ///
344    /// ```rust
345    /// use tenferro_tensor::{ContractionScalar, DType};
346    ///
347    /// assert_eq!(ContractionScalar::zero(DType::F64).unwrap(), ContractionScalar::F64(0.0));
348    /// ```
349    /// # Errors
350    ///
351    /// Returns [`crate::Error::Validation`] with a
352    /// [`crate::ValidationError::DTypeMismatch`] source when `dtype` is `I32`,
353    /// `I64`, or `Bool`, which do not support contraction scalar identities.
354    pub fn zero(dtype: DType) -> crate::Result<Self> {
355        match dtype {
356            DType::F32 => Ok(Self::F32(0.0)),
357            DType::F64 => Ok(Self::F64(0.0)),
358            DType::C32 => Ok(Self::C32(Complex32::new(0.0, 0.0))),
359            DType::C64 => Ok(Self::C64(Complex64::new(0.0, 0.0))),
360            DType::I32 | DType::I64 | DType::Bool | DType::External(_) => Err(validation(
361                "ContractionScalar::zero",
362                ValidationError::DTypeMismatch {
363                    expected: dtype,
364                    actual: DType::F32,
365                },
366            )),
367        }
368    }
369}
370
371/// Output-update semantics for dot-general accumulation.
372///
373/// This keeps contraction axes in [`DotGeneralConfig`] and output update
374/// semantics here, so cached and non-cached backend traits can share the same
375/// accumulation contract.
376///
377/// # Examples
378///
379/// ```rust
380/// use tenferro_tensor::{ContractionScalar, DotGeneralAccumulation, DType};
381///
382/// let accum = DotGeneralAccumulation::overwrite(DType::F64).unwrap();
383/// assert_eq!(accum.alpha, ContractionScalar::F64(1.0));
384/// assert_eq!(accum.beta, ContractionScalar::F64(0.0));
385/// ```
386#[derive(Clone, Copy, Debug, PartialEq)]
387pub struct DotGeneralAccumulation {
388    pub lhs_conj: bool,
389    pub rhs_conj: bool,
390    pub alpha: ContractionScalar,
391    pub beta: ContractionScalar,
392}
393
394impl DotGeneralAccumulation {
395    fn identity(
396        op: &'static str,
397        dtype: DType,
398        multiplicative: bool,
399    ) -> crate::Result<ContractionScalar> {
400        let result = if multiplicative {
401            ContractionScalar::one(dtype)
402        } else {
403            ContractionScalar::zero(dtype)
404        };
405        result.map_err(|error| match error {
406            Error::Validation { source, .. } => validation(op, source),
407            error => error,
408        })
409    }
410
411    /// Return overwrite semantics, `out = lhs dot rhs`, for `dtype`.
412    ///
413    /// # Examples
414    ///
415    /// ```rust
416    /// use tenferro_tensor::{ContractionScalar, DotGeneralAccumulation, DType};
417    ///
418    /// let accum = DotGeneralAccumulation::overwrite(DType::F64).unwrap();
419    /// assert_eq!(accum.alpha, ContractionScalar::F64(1.0));
420    /// assert_eq!(accum.beta, ContractionScalar::F64(0.0));
421    /// ```
422    ///
423    /// # Errors
424    ///
425    /// Returns [`crate::Error::Validation`] with a
426    /// [`crate::ValidationError::DTypeMismatch`] source when `dtype` does not
427    /// support contraction scalar identities.
428    pub fn overwrite(dtype: DType) -> crate::Result<Self> {
429        Ok(Self {
430            lhs_conj: false,
431            rhs_conj: false,
432            alpha: Self::identity("DotGeneralAccumulation::overwrite", dtype, true)?,
433            beta: Self::identity("DotGeneralAccumulation::overwrite", dtype, false)?,
434        })
435    }
436
437    /// Return additive update semantics, `out += lhs dot rhs`, for `dtype`.
438    ///
439    /// # Examples
440    ///
441    /// ```rust
442    /// use tenferro_tensor::{ContractionScalar, DType, DotGeneralAccumulation};
443    ///
444    /// let accum = DotGeneralAccumulation::add_to(DType::F64)?;
445    /// assert_eq!(accum.alpha, ContractionScalar::F64(1.0));
446    /// assert_eq!(accum.beta, ContractionScalar::F64(1.0));
447    /// # Ok::<(), tenferro_tensor::Error>(())
448    /// ```
449    /// # Errors
450    ///
451    /// Returns [`crate::Error::Validation`] with a
452    /// [`crate::ValidationError::DTypeMismatch`] source when `dtype` does not
453    /// support contraction scalar identities.
454    pub fn add_to(dtype: DType) -> crate::Result<Self> {
455        Ok(Self {
456            lhs_conj: false,
457            rhs_conj: false,
458            alpha: Self::identity("DotGeneralAccumulation::add_to", dtype, true)?,
459            beta: Self::identity("DotGeneralAccumulation::add_to", dtype, true)?,
460        })
461    }
462
463    /// Return scaled update semantics, `out = alpha * lhs dot rhs + beta * out`.
464    ///
465    /// # Examples
466    ///
467    /// ```rust
468    /// use tenferro_tensor::{ContractionScalar, DotGeneralAccumulation};
469    ///
470    /// let accum = DotGeneralAccumulation::scaled(
471    ///     ContractionScalar::F32(0.5),
472    ///     ContractionScalar::F32(2.0),
473    /// )?;
474    /// assert_eq!(accum.alpha, ContractionScalar::F32(0.5));
475    /// # Ok::<(), tenferro_tensor::Error>(())
476    /// ```
477    /// # Errors
478    ///
479    /// Returns [`crate::Error::Validation`] with a
480    /// [`crate::ValidationError::DTypeMismatch`] source when `alpha` and `beta`
481    /// have different dtypes.
482    pub fn scaled(alpha: ContractionScalar, beta: ContractionScalar) -> crate::Result<Self> {
483        if alpha.dtype() != beta.dtype() {
484            return Err(validation(
485                "DotGeneralAccumulation::scaled",
486                ValidationError::DTypeMismatch {
487                    expected: alpha.dtype(),
488                    actual: beta.dtype(),
489                },
490            ));
491        }
492        Ok(Self {
493            lhs_conj: false,
494            rhs_conj: false,
495            alpha,
496            beta,
497        })
498    }
499
500    fn validate_for_dtype(self, dtype: DType) -> crate::Result<()> {
501        for scalar in [self.alpha, self.beta] {
502            if scalar.dtype() != dtype {
503                return Err(validation(
504                    "dot_general",
505                    ValidationError::DTypeMismatch {
506                        expected: scalar.dtype(),
507                        actual: dtype,
508                    },
509                ));
510            }
511        }
512        Ok(())
513    }
514}
515
516#[doc(hidden)]
517pub fn validate_dot_general_accumulation(
518    lhs: &TensorRead<'_>,
519    rhs: &TensorRead<'_>,
520    config: &DotGeneralConfig,
521    accumulation: DotGeneralAccumulation,
522    out: &TensorWrite<'_>,
523    op: &'static str,
524) -> crate::Result<ShapeVec> {
525    let shape = validate_dot_general_read_into(lhs, rhs, config, out, op)?;
526    accumulation.validate_for_dtype(lhs.dtype())?;
527    Ok(shape)
528}
529
530#[doc(hidden)]
531pub fn dot_general_accum_via_temp<B: TensorDot + ?Sized>(
532    backend: &mut B,
533    lhs: TensorRead<'_>,
534    rhs: TensorRead<'_>,
535    config: &DotGeneralConfig,
536    accumulation: DotGeneralAccumulation,
537    mut out: TensorWrite<'_>,
538) -> crate::Result<()> {
539    validate_dot_general_accumulation(&lhs, &rhs, config, accumulation, &out, "dot_general")?;
540    let dot = backend.dot_general_with_conj_read(
541        lhs,
542        rhs,
543        config,
544        accumulation.lhs_conj,
545        accumulation.rhs_conj,
546    )?;
547    accumulate_dot_result_into(&dot, accumulation, &mut out)
548}
549
550#[doc(hidden)]
551pub fn accumulate_dot_result_into(
552    dot: &Tensor,
553    accumulation: DotGeneralAccumulation,
554    out: &mut TensorWrite<'_>,
555) -> crate::Result<()> {
556    macro_rules! dispatch {
557        ($variant:ident, $ty:ty) => {
558            if dot.dtype() == <$ty as crate::TensorScalar>::dtype() {
559                let (ContractionScalar::$variant(alpha), ContractionScalar::$variant(beta)) =
560                    (accumulation.alpha, accumulation.beta)
561                else {
562                    return Err(validation(
563                        "dot_general",
564                        ValidationError::DTypeMismatch {
565                            expected: dot.dtype(),
566                            actual: accumulation.alpha.dtype(),
567                        },
568                    ));
569                };
570                let dot = dot
571                    .as_typed::<$ty>()
572                    .expect("the dtype guard selects this arm");
573                match out {
574                    TensorWrite::Tensor(out) => {
575                        let out = out
576                            .as_typed_mut::<$ty>()
577                            .expect("the dtype guard selects this arm");
578                        let mut out = out.as_view_mut();
579                        accumulate_typed(dot.as_slice()?, alpha, beta, &mut out)?;
580                        return Ok(());
581                    }
582                    TensorWrite::View(crate::TensorViewMut::$variant(out)) => {
583                        accumulate_typed(dot.as_slice()?, alpha, beta, out)?;
584                        return Ok(());
585                    }
586                    _ => {}
587                }
588            }
589        };
590    }
591
592    dispatch!(F32, f32);
593    dispatch!(F64, f64);
594    dispatch!(C32, Complex32);
595    dispatch!(C64, Complex64);
596
597    Err(validation(
598        "dot_general",
599        ValidationError::DTypeMismatch {
600            expected: accumulation.alpha.dtype(),
601            actual: dot.dtype(),
602        },
603    ))
604}
605
606fn accumulate_typed<T>(
607    dot: &[T],
608    alpha: T,
609    beta: T,
610    out: &mut TypedTensorViewMut<'_, T>,
611) -> crate::Result<()>
612where
613    T: Copy
614        + PartialEq
615        + std::ops::Add<Output = T>
616        + std::ops::Mul<Output = T>
617        + num_traits::Zero
618        + 'static,
619{
620    let beta_is_zero = beta == T::zero();
621    if let Some(output) = compact_host_accumulation_slice(out, dot.len())? {
622        for (output, dot_value) in output.iter_mut().zip(dot.iter().copied()) {
623            // INVARIANT: beta == 0 follows BLAS GEMM semantics and does not read
624            // the existing output element; beta != 0 requires an initialized
625            // TensorWrite target and performs a read-modify-write update.
626            *output = if beta_is_zero {
627                alpha * dot_value
628            } else {
629                alpha * dot_value + beta * *output
630            };
631        }
632        return Ok(());
633    }
634
635    for (linear, dot_value) in dot.iter().copied().enumerate() {
636        let indices = flat_to_multi_for_shape(out.shape(), linear);
637        let output = out.get_mut(&indices).ok_or_else(|| {
638            invalid_argument(
639                "dot_general",
640                "output",
641                format!("index {indices:?} is outside accumulation target"),
642            )
643        })?;
644        // INVARIANT: beta == 0 follows BLAS GEMM semantics and does not read
645        // the existing output element; beta != 0 requires an initialized
646        // TensorWrite target and performs a read-modify-write update.
647        *output = if beta_is_zero {
648            alpha * dot_value
649        } else {
650            alpha * dot_value + beta * *output
651        };
652    }
653    Ok(())
654}
655
656fn compact_host_accumulation_slice<'a, T: 'static>(
657    out: &'a mut TypedTensorViewMut<'_, T>,
658    expected_len: usize,
659) -> crate::Result<Option<&'a mut [T]>> {
660    if out.backend_buffer().is_some()
661        || out.n_elements() != expected_len
662        || !out.is_col_major_contiguous()?
663    {
664        return Ok(None);
665    }
666
667    let start = usize::try_from(out.offset()).map_err(|_| {
668        invalid_argument("dot_general", "output", "compact output offset is negative")
669    })?;
670    let end = start
671        .checked_add(expected_len)
672        .ok_or_else(|| validation("dot_general", ValidationError::IntegerOverflow))?;
673    out.host_storage_mut()?
674        .get_mut(start..end)
675        .map(Some)
676        .ok_or_else(|| {
677            invalid_argument(
678                "dot_general",
679                "output",
680                "compact output is outside its backing storage",
681            )
682        })
683}
684
685fn flat_to_multi_for_shape(shape: &[usize], mut linear: usize) -> Vec<usize> {
686    let mut indices = Vec::with_capacity(shape.len());
687    for &dim in shape {
688        if dim == 0 {
689            indices.push(0);
690        } else {
691            indices.push(linear % dim);
692            linear /= dim;
693        }
694    }
695    indices
696}
697
698/// Canonical elementwise fusion plan shared between segmented execution and backends.
699#[doc(hidden)]
700#[derive(Clone, Debug, Hash, PartialEq, Eq)]
701pub struct ElementwiseFusionPlan {
702    dtype: crate::DType,
703    input_count: usize,
704    // Keep view metadata in Vecs. A/B benchmarking on the broadcast_mul
705    // path showed SmallVec made this metadata path about 6-7% slower.
706    input_views: Vec<ElementwiseFusionInputView>,
707    outputs: Vec<usize>,
708    ops: Vec<ElementwiseFusionInst>,
709}
710
711/// Metadata-only view applied to one backend fusion input.
712#[doc(hidden)]
713#[derive(Clone, Debug, Hash, PartialEq, Eq)]
714pub enum ElementwiseFusionInputView {
715    Identity,
716    BroadcastInDim {
717        // Vec is intentional here; see ElementwiseFusionPlan::input_views.
718        shape: Vec<usize>,
719        dims: Vec<usize>,
720    },
721}
722
723/// One node in a canonical elementwise fusion plan.
724#[doc(hidden)]
725#[derive(Clone, Debug, Hash, PartialEq, Eq)]
726pub struct ElementwiseFusionInst {
727    op: ElementwiseFusionOp,
728    inputs: Vec<usize>,
729}
730
731tenferro_core_ops::define_elementwise_fusion_op!();
732
733impl ElementwiseFusionPlan {
734    /// Build a backend elementwise fusion plan.
735    ///
736    /// # Examples
737    ///
738    /// ```rust
739    /// use tenferro_tensor::backend::{
740    ///     ElementwiseFusionInst, ElementwiseFusionOp, ElementwiseFusionPlan,
741    /// };
742    /// use tenferro_tensor::DType;
743    ///
744    /// let plan = ElementwiseFusionPlan::new(
745    ///     DType::F64,
746    ///     2,
747    ///     vec![2],
748    ///     vec![ElementwiseFusionInst::new(ElementwiseFusionOp::Add, vec![0, 1])],
749    /// );
750    /// assert_eq!(plan.input_count(), 2);
751    /// ```
752    pub fn new(
753        dtype: crate::DType,
754        input_count: usize,
755        outputs: Vec<usize>,
756        ops: Vec<ElementwiseFusionInst>,
757    ) -> Self {
758        Self::with_input_views(
759            dtype,
760            vec![ElementwiseFusionInputView::Identity; input_count],
761            outputs,
762            ops,
763        )
764    }
765
766    /// Build a backend elementwise fusion plan with input view metadata.
767    ///
768    /// # Examples
769    ///
770    /// ```rust
771    /// use tenferro_tensor::backend::{
772    ///     ElementwiseFusionInputView, ElementwiseFusionInst, ElementwiseFusionOp,
773    ///     ElementwiseFusionPlan,
774    /// };
775    /// use tenferro_tensor::DType;
776    ///
777    /// let plan = ElementwiseFusionPlan::with_input_views(
778    ///     DType::F64,
779    ///     vec![ElementwiseFusionInputView::broadcast_in_dim(vec![2, 3], vec![0])],
780    ///     vec![1],
781    ///     vec![ElementwiseFusionInst::new(ElementwiseFusionOp::Negate, vec![0])],
782    /// );
783    /// assert_eq!(plan.input_count(), 1);
784    /// ```
785    pub fn with_input_views(
786        dtype: crate::DType,
787        input_views: impl IntoIterator<Item = ElementwiseFusionInputView>,
788        outputs: Vec<usize>,
789        ops: Vec<ElementwiseFusionInst>,
790    ) -> Self {
791        let input_views = input_views.into_iter().collect::<Vec<_>>();
792        let input_count = input_views.len();
793        Self {
794            dtype,
795            input_count,
796            input_views,
797            outputs,
798            ops,
799        }
800    }
801
802    /// Return the scalar dtype expected by this fusion plan.
803    ///
804    /// # Examples
805    ///
806    /// ```rust
807    /// use tenferro_tensor::backend::ElementwiseFusionPlan;
808    /// use tenferro_tensor::DType;
809    ///
810    /// let plan = ElementwiseFusionPlan::new(DType::F32, 0, Vec::new(), Vec::new());
811    /// assert_eq!(plan.dtype(), DType::F32);
812    /// ```
813    pub fn dtype(&self) -> crate::DType {
814        self.dtype
815    }
816
817    /// Return the number of input tensors expected by this plan.
818    ///
819    /// # Examples
820    ///
821    /// ```rust
822    /// use tenferro_tensor::backend::ElementwiseFusionPlan;
823    /// use tenferro_tensor::DType;
824    ///
825    /// let plan = ElementwiseFusionPlan::new(DType::F64, 3, Vec::new(), Vec::new());
826    /// assert_eq!(plan.input_count(), 3);
827    /// ```
828    pub fn input_count(&self) -> usize {
829        self.input_count
830    }
831
832    /// Return metadata views applied to fusion inputs before executing ops.
833    ///
834    /// # Examples
835    ///
836    /// ```rust
837    /// use tenferro_tensor::backend::ElementwiseFusionPlan;
838    /// use tenferro_tensor::DType;
839    ///
840    /// let plan = ElementwiseFusionPlan::new(DType::F64, 2, Vec::new(), Vec::new());
841    /// assert_eq!(plan.input_views().len(), 2);
842    /// ```
843    pub fn input_views(&self) -> &[ElementwiseFusionInputView] {
844        &self.input_views
845    }
846
847    /// Return the value ids selected as fusion outputs.
848    ///
849    /// # Examples
850    ///
851    /// ```rust
852    /// use tenferro_tensor::backend::ElementwiseFusionPlan;
853    /// use tenferro_tensor::DType;
854    ///
855    /// let plan = ElementwiseFusionPlan::new(DType::F64, 0, vec![0], Vec::new());
856    /// assert_eq!(plan.outputs(), &[0]);
857    /// ```
858    pub fn outputs(&self) -> &[usize] {
859        &self.outputs
860    }
861
862    /// Return the fused elementwise instruction sequence.
863    ///
864    /// # Examples
865    ///
866    /// ```rust
867    /// use tenferro_tensor::backend::{
868    ///     ElementwiseFusionInst, ElementwiseFusionOp, ElementwiseFusionPlan,
869    /// };
870    /// use tenferro_tensor::DType;
871    ///
872    /// let inst = ElementwiseFusionInst::new(ElementwiseFusionOp::Negate, vec![0]);
873    /// let plan = ElementwiseFusionPlan::new(DType::F64, 1, vec![1], vec![inst]);
874    /// assert_eq!(plan.ops().len(), 1);
875    /// ```
876    pub fn ops(&self) -> &[ElementwiseFusionInst] {
877        &self.ops
878    }
879}
880
881impl ElementwiseFusionInputView {
882    /// Build metadata for a `BroadcastInDim` fusion input view.
883    ///
884    /// # Examples
885    ///
886    /// ```rust
887    /// use tenferro_tensor::backend::ElementwiseFusionInputView;
888    ///
889    /// let view = ElementwiseFusionInputView::broadcast_in_dim(vec![2, 3], vec![0]);
890    /// assert!(matches!(view, ElementwiseFusionInputView::BroadcastInDim { .. }));
891    /// ```
892    pub fn broadcast_in_dim(
893        shape: impl IntoIterator<Item = usize>,
894        dims: impl IntoIterator<Item = usize>,
895    ) -> Self {
896        Self::BroadcastInDim {
897            shape: shape.into_iter().collect(),
898            dims: dims.into_iter().collect(),
899        }
900    }
901
902    /// Return true when this fusion input is an identity view.
903    ///
904    /// # Examples
905    ///
906    /// ```rust
907    /// use tenferro_tensor::backend::ElementwiseFusionInputView;
908    ///
909    /// assert!(ElementwiseFusionInputView::Identity.is_identity());
910    /// ```
911    pub fn is_identity(&self) -> bool {
912        matches!(self, Self::Identity)
913    }
914}
915
916impl ElementwiseFusionInst {
917    /// Build a backend elementwise fusion instruction.
918    ///
919    /// # Examples
920    ///
921    /// ```rust
922    /// use tenferro_tensor::backend::{ElementwiseFusionInst, ElementwiseFusionOp};
923    ///
924    /// let inst = ElementwiseFusionInst::new(ElementwiseFusionOp::Add, vec![0, 1]);
925    /// assert_eq!(inst.inputs(), &[0, 1]);
926    /// ```
927    pub fn new(op: ElementwiseFusionOp, inputs: Vec<usize>) -> Self {
928        Self { op, inputs }
929    }
930
931    /// Return the elementwise op executed by this instruction.
932    ///
933    /// # Examples
934    ///
935    /// ```rust
936    /// use tenferro_tensor::backend::{ElementwiseFusionInst, ElementwiseFusionOp};
937    ///
938    /// let inst = ElementwiseFusionInst::new(ElementwiseFusionOp::Negate, vec![0]);
939    /// assert_eq!(inst.op(), ElementwiseFusionOp::Negate);
940    /// ```
941    pub fn op(&self) -> ElementwiseFusionOp {
942        self.op
943    }
944
945    /// Return this instruction's input value ids.
946    ///
947    /// # Examples
948    ///
949    /// ```rust
950    /// use tenferro_tensor::backend::{ElementwiseFusionInst, ElementwiseFusionOp};
951    ///
952    /// let inst = ElementwiseFusionInst::new(ElementwiseFusionOp::Multiply, vec![2, 0]);
953    /// assert_eq!(inst.inputs(), &[2, 0]);
954    /// ```
955    pub fn inputs(&self) -> &[usize] {
956        &self.inputs
957    }
958}
959
960/// Runtime operation selected by [`TensorElementwise::elementwise_read_into`].
961#[non_exhaustive]
962#[derive(Clone, Copy, Debug, Eq, PartialEq)]
963pub enum ElementwiseReadOp {
964    /// Binary addition.
965    Add,
966    /// Binary subtraction.
967    Subtract,
968    /// Binary multiplication.
969    Multiply,
970    /// Unary negation.
971    Negate,
972    /// Unary conjugation.
973    Conj,
974    /// Binary division.
975    Divide,
976}
977
978/// Fused real-float activation applied in one elementwise pass.
979///
980/// The variants name the same functions the composite activation vocabulary
981/// exposes; a backend that supports the fused form computes the whole
982/// expression in one traversal instead of materializing every intermediate.
983///
984/// # Examples
985///
986/// ```
987/// use tenferro_tensor::ActivationOp;
988/// assert_eq!(ActivationOp::Sigmoid.label(), "sigmoid");
989/// ```
990#[derive(Clone, Copy, Debug, PartialEq, Eq)]
991pub enum ActivationOp {
992    /// Logistic sigmoid `1 / (1 + exp(-x))`, overflow-free.
993    Sigmoid,
994    /// SiLU / swish `x * sigmoid(x)`.
995    Silu,
996    /// Softplus `max(x, 0) + log1p(exp(-|x|))`.
997    Softplus,
998    /// Exact GELU `x/2 * (1 + erf(x / sqrt(2)))`.
999    Gelu,
1000    /// GELU tanh approximation `x/2 * (1 + tanh(sqrt(2/pi) * (x + 0.044715 x^3)))`.
1001    GeluTanh,
1002}
1003
1004impl ActivationOp {
1005    /// Stable operation label used in errors and profiling.
1006    #[doc(hidden)]
1007    pub fn label(self) -> &'static str {
1008        match self {
1009            Self::Sigmoid => "sigmoid",
1010            Self::Silu => "silu",
1011            Self::Softplus => "softplus",
1012            Self::Gelu => "gelu",
1013            Self::GeluTanh => "gelu_tanh",
1014        }
1015    }
1016}
1017
1018impl ElementwiseReadOp {
1019    #[doc(hidden)]
1020    pub fn label(self) -> &'static str {
1021        match self {
1022            Self::Add => "add",
1023            Self::Subtract => "sub",
1024            Self::Multiply => "mul",
1025            Self::Negate => "neg",
1026            Self::Conj => "conj",
1027            Self::Divide => "div",
1028        }
1029    }
1030
1031    #[doc(hidden)]
1032    pub fn arity(self) -> usize {
1033        match self {
1034            Self::Negate | Self::Conj => 1,
1035            Self::Add | Self::Subtract | Self::Multiply | Self::Divide => 2,
1036        }
1037    }
1038}
1039
1040#[derive(Clone, Copy, Debug)]
1041enum StorageIdentity {
1042    Host {
1043        start: usize,
1044        end: usize,
1045    },
1046    Backend {
1047        domain: Option<AllocationDomainId>,
1048        allocation: Option<AllocationId>,
1049        family: &'static str,
1050        object: usize,
1051    },
1052}
1053
1054fn host_storage_identity<T>(data: &[T]) -> StorageIdentity {
1055    let start = data.as_ptr() as usize;
1056    let bytes = std::mem::size_of_val(data);
1057    StorageIdentity::Host {
1058        start,
1059        end: start.saturating_add(bytes),
1060    }
1061}
1062
1063fn backend_storage_identity<T: 'static>(buffer: &dyn crate::BackendStorage<T>) -> StorageIdentity {
1064    StorageIdentity::Backend {
1065        domain: buffer.allocation_domain(),
1066        allocation: buffer.allocation_id(),
1067        family: buffer.backend_family(),
1068        // INVARIANT: every backend buffer is borrowed from the single Box-owned
1069        // root allocation; the data pointer of this trait object is stable for
1070        // that owner and is used only as a fallback when provider identity is
1071        // unavailable.
1072        object: buffer as *const dyn crate::BackendStorage<T> as *const () as usize,
1073    }
1074}
1075
1076fn typed_tensor_storage_identity<T: crate::TensorScalar>(
1077    tensor: &TypedTensor<T>,
1078) -> crate::Result<StorageIdentity> {
1079    if tensor.backend_buffer().is_some() {
1080        let buffer = tensor.backend_buffer().ok_or_else(|| {
1081            crate::Error::runtime_state("typed_tensor_storage_identity", "backend buffer missing")
1082        })?;
1083        Ok(backend_storage_identity(buffer))
1084    } else {
1085        Ok(host_storage_identity(tensor.host_data()?))
1086    }
1087}
1088
1089fn typed_view_storage_identity<T: crate::TensorScalar + 'static>(
1090    view: &TypedTensorView<'_, T>,
1091) -> crate::Result<StorageIdentity> {
1092    match view.backend_buffer() {
1093        Some(buffer) => Ok(backend_storage_identity(buffer)),
1094        None => view.host_storage().map(host_storage_identity),
1095    }
1096}
1097
1098/// The typed tensor behind `value`, or the refusal this module reports for one.
1099///
1100/// Callers reach this from a match on the dtype, so `None` means the tag and the runtime dtype
1101/// disagree rather than a caller mistake.
1102fn identity_operand<T: TensorScalar>(value: &Tensor) -> crate::Result<&TypedTensor<T>> {
1103    value.as_typed::<T>().ok_or_else(|| {
1104        crate::Error::unsupported_dtype(
1105            "storage_identity",
1106            value.dtype(),
1107            "an externally defined payload has no storage identity",
1108        )
1109    })
1110}
1111
1112fn tensor_read_storage_identity(input: &TensorRead<'_>) -> crate::Result<StorageIdentity> {
1113    macro_rules! typed_identity {
1114        ($value:expr) => {
1115            match $value.dtype() {
1116                DType::F32 => typed_tensor_storage_identity(identity_operand::<f32>($value)?),
1117                DType::F64 => typed_tensor_storage_identity(identity_operand::<f64>($value)?),
1118                DType::I32 => typed_tensor_storage_identity(identity_operand::<i32>($value)?),
1119                DType::I64 => typed_tensor_storage_identity(identity_operand::<i64>($value)?),
1120                DType::Bool => typed_tensor_storage_identity(identity_operand::<bool>($value)?),
1121                DType::C32 => typed_tensor_storage_identity(identity_operand::<Complex32>($value)?),
1122                DType::C64 => typed_tensor_storage_identity(identity_operand::<Complex64>($value)?),
1123                // A caller-owned payload has no allocation identity, so it cannot
1124                // take part in an aliasing check.
1125                DType::External(_) => {
1126                    return Err(crate::Error::unsupported_dtype(
1127                        "storage_identity",
1128                        $value.dtype(),
1129                        "an externally defined payload has no storage identity",
1130                    ));
1131                }
1132            }
1133        };
1134    }
1135    macro_rules! view_identity {
1136        ($value:expr) => {
1137            match $value {
1138                TensorView::F32(value) => typed_view_storage_identity(value),
1139                TensorView::F64(value) => typed_view_storage_identity(value),
1140                TensorView::I32(value) => typed_view_storage_identity(value),
1141                TensorView::I64(value) => typed_view_storage_identity(value),
1142                TensorView::Bool(value) => typed_view_storage_identity(value),
1143                TensorView::C32(value) => typed_view_storage_identity(value),
1144                TensorView::C64(value) => typed_view_storage_identity(value),
1145            }
1146        };
1147    }
1148
1149    match input {
1150        TensorRead::Tensor(tensor) => typed_identity!(tensor),
1151        TensorRead::View(view) => view_identity!(view),
1152    }
1153}
1154
1155fn storage_overlaps(lhs: StorageIdentity, rhs: StorageIdentity) -> bool {
1156    match (lhs, rhs) {
1157        (
1158            StorageIdentity::Host {
1159                start: lhs_start,
1160                end: lhs_end,
1161            },
1162            StorageIdentity::Host {
1163                start: rhs_start,
1164                end: rhs_end,
1165            },
1166        ) => lhs_start < rhs_end && rhs_start < lhs_end,
1167        (
1168            StorageIdentity::Backend {
1169                domain: lhs_domain,
1170                allocation: lhs_allocation,
1171                family: lhs_family,
1172                object: lhs_object,
1173            },
1174            StorageIdentity::Backend {
1175                domain: rhs_domain,
1176                allocation: rhs_allocation,
1177                family: rhs_family,
1178                object: rhs_object,
1179            },
1180        ) => {
1181            lhs_object == rhs_object
1182                || matches!(
1183                    (lhs_domain, rhs_domain, lhs_allocation, rhs_allocation),
1184                    (Some(lhs_domain), Some(rhs_domain), Some(lhs), Some(rhs))
1185                        if lhs_domain == rhs_domain && lhs == rhs
1186                )
1187                || matches!(
1188                    (lhs_domain, rhs_domain, lhs_allocation, rhs_allocation),
1189                    (None, None, Some(lhs), Some(rhs)) if lhs_family == rhs_family && lhs == rhs
1190                )
1191        }
1192        _ => false,
1193    }
1194}
1195
1196/// Validate that a caller-owned destination does not overlap any read input.
1197///
1198/// The check is intentionally conservative for host views: two views backed by
1199/// the same host allocation are treated as overlapping because the allocation
1200/// identity is the only stable boundary contract available to erased backend
1201/// code. Backend allocations use their domain/allocation identity when the
1202/// provider exposes it.
1203///
1204/// # Errors
1205///
1206/// Returns `tenferro_tensor_core::ValidationError::InvalidArgument` when the
1207/// destination storage overlaps an input, or `Error::RuntimeState` when
1208/// storage identity cannot be established safely.
1209///
1210/// # Examples
1211///
1212/// ```rust
1213/// use tenferro_tensor::{Tensor, TensorRead, TensorWrite};
1214/// use tenferro_tensor::backend::validate_read_into_destination;
1215///
1216/// let input = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
1217/// let mut output = Tensor::from_vec_col_major(vec![1], vec![0.0_f64])?;
1218/// validate_read_into_destination(
1219///     "example",
1220///     &[TensorRead::from_tensor(&input)],
1221///     &TensorWrite::from_tensor(&mut output),
1222/// )?;
1223/// # Ok::<(), tenferro_tensor::Error>(())
1224/// ```
1225pub fn validate_read_into_destination(
1226    op: &'static str,
1227    inputs: &[TensorRead<'_>],
1228    out: &TensorWrite<'_>,
1229) -> crate::Result<()> {
1230    let output_identity = tensor_read_storage_identity(&out.as_read())?;
1231    for (index, input) in inputs.iter().enumerate() {
1232        if storage_overlaps(tensor_read_storage_identity(input)?, output_identity) {
1233            return Err(Error::invalid_argument(
1234                op,
1235                "out",
1236                format!("destination storage overlaps input {index}"),
1237            ));
1238        }
1239    }
1240    Ok(())
1241}
1242
1243/// Execute a read-into elementwise operation through a backend's allocating operations.
1244///
1245/// Backend implementations use this helper when their device-native operation
1246/// APIs already provide the correct placement and copy semantics. It deliberately
1247/// does not inspect or transfer storage across devices.
1248///
1249/// # Errors
1250///
1251/// Returns [`Error::Validation`] for wrong arity or overlapping output storage,
1252/// and propagates the selected backend operation and copy errors.
1253#[doc(hidden)]
1254pub fn elementwise_read_into_via_allocating_ops<B: TensorElementwise + ?Sized>(
1255    backend: &mut B,
1256    op: ElementwiseReadOp,
1257    inputs: &[TensorRead<'_>],
1258    out: TensorWrite<'_>,
1259) -> crate::Result<()> {
1260    if inputs.len() != op.arity() {
1261        return Err(Error::invalid_argument(
1262            op.label(),
1263            "inputs",
1264            format!("expected {} inputs, got {}", op.arity(), inputs.len()),
1265        ));
1266    }
1267    validate_read_into_destination(op.label(), inputs, &out)?;
1268    let result = match op {
1269        ElementwiseReadOp::Add => backend.add_read(inputs[0].clone(), inputs[1].clone())?,
1270        ElementwiseReadOp::Subtract => backend.sub_read(inputs[0].clone(), inputs[1].clone())?,
1271        ElementwiseReadOp::Multiply => backend.mul_read(inputs[0].clone(), inputs[1].clone())?,
1272        ElementwiseReadOp::Negate => backend.neg_read(inputs[0].clone())?,
1273        ElementwiseReadOp::Conj => backend.conj_read(inputs[0].clone())?,
1274        ElementwiseReadOp::Divide => backend.div_read(inputs[0].clone(), inputs[1].clone())?,
1275    };
1276    backend.copy_read_into(TensorRead::from_tensor(&result), out)
1277}
1278
1279/// Elementwise tensor operations.
1280///
1281/// # Examples
1282///
1283/// ```rust
1284/// use tenferro_tensor::TensorElementwise;
1285///
1286/// fn accepts_elementwise<B: TensorElementwise>(_backend: &mut B) {}
1287/// ```
1288pub trait TensorElementwise: TensorStructural {
1289    /// Execute an elementwise operation into caller-owned storage.
1290    ///
1291    /// Every backend must implement this hook with its explicit execution
1292    /// context, placement checks, and output-storage policy. The hook is the
1293    /// backend boundary for borrowed inputs and caller-owned output; it must not
1294    /// silently transfer data between devices.
1295    ///
1296    /// # Examples
1297    ///
1298    /// ```rust
1299    /// use tenferro_tensor::{ElementwiseReadOp, TensorElementwise, TensorRead, TensorWrite};
1300    ///
1301    /// fn run_into<B: TensorElementwise>(
1302    ///     backend: &mut B,
1303    ///     input: TensorRead<'_>,
1304    ///     out: TensorWrite<'_>,
1305    /// ) -> tenferro_tensor::Result<()> {
1306    ///     backend.elementwise_read_into(ElementwiseReadOp::Conj, &[input], out)
1307    /// }
1308    /// ```
1309    ///
1310    /// # Errors
1311    ///
1312    /// Returns [`crate::Error::Validation`] for invalid arity, metadata, or
1313    /// overlapping storage. Backend-specific placement, unsupported-operation,
1314    /// and execution errors are returned unchanged by the implementation.
1315    fn elementwise_read_into(
1316        &mut self,
1317        op: ElementwiseReadOp,
1318        inputs: &[TensorRead<'_>],
1319        out: TensorWrite<'_>,
1320    ) -> crate::Result<()>;
1321
1322    /// Elementwise addition accepting either owned tensors or borrowed views.
1323    ///
1324    /// Backends that implement this method must not silently move data across
1325    /// devices. A backend that cannot consume views should return an explicit
1326    /// backend error rather than materializing or transferring implicitly.
1327    ///
1328    /// # Examples
1329    ///
1330    /// ```rust
1331    /// use tenferro_tensor::{Tensor, TensorElementwise, TensorRead};
1332    ///
1333    /// fn add_owned<B: TensorElementwise>(
1334    ///     backend: &mut B,
1335    ///     lhs: &Tensor,
1336    ///     rhs: &Tensor,
1337    /// ) -> tenferro_tensor::Result<Tensor> {
1338    ///     backend.add_read(TensorRead::from_tensor(lhs), TensorRead::from_tensor(rhs))
1339    /// }
1340    /// ```
1341    /// # Errors
1342    ///
1343    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1344    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1345    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1346    /// backend execution or storage access cannot provide the requested result.
1347    fn add_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor>;
1348
1349    /// Overwrite caller-provided output with elementwise addition.
1350    ///
1351    /// `_into` methods never accumulate into the previous output value.
1352    ///
1353    /// # Examples
1354    ///
1355    /// ```rust
1356    /// use tenferro_tensor::{Tensor, TensorElementwise, TensorWrite};
1357    ///
1358    /// fn add_into<B: TensorElementwise>(
1359    ///     backend: &mut B,
1360    ///     lhs: &Tensor,
1361    ///     rhs: &Tensor,
1362    ///     mut out: Tensor,
1363    /// ) -> tenferro_tensor::Result<Tensor> {
1364    ///     backend.add_into(lhs, rhs, TensorWrite::from_tensor(&mut out))?;
1365    ///     Ok(out)
1366    /// }
1367    /// ```
1368    /// # Errors
1369    ///
1370    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1371    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1372    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1373    /// backend execution or storage access cannot provide the requested result.
1374    fn add_into(&mut self, lhs: &Tensor, rhs: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
1375        self.add_read_into(
1376            TensorRead::from_tensor(lhs),
1377            TensorRead::from_tensor(rhs),
1378            out,
1379        )
1380    }
1381
1382    /// Overwrite caller-provided output with elementwise addition from reads.
1383    ///
1384    /// # Examples
1385    ///
1386    /// ```rust
1387    /// use tenferro_tensor::{TensorElementwise, TensorRead, TensorWrite};
1388    ///
1389    /// fn add_read_into<B: TensorElementwise>(
1390    ///     backend: &mut B,
1391    ///     lhs: TensorRead<'_>,
1392    ///     rhs: TensorRead<'_>,
1393    ///     out: TensorWrite<'_>,
1394    /// ) -> tenferro_tensor::Result<()> {
1395    ///     backend.add_read_into(lhs, rhs, out)
1396    /// }
1397    /// ```
1398    /// # Errors
1399    ///
1400    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1401    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1402    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1403    /// backend execution or storage access cannot provide the requested result.
1404    fn add_read_into(
1405        &mut self,
1406        lhs: TensorRead<'_>,
1407        rhs: TensorRead<'_>,
1408        out: TensorWrite<'_>,
1409    ) -> crate::Result<()> {
1410        self.elementwise_read_into(ElementwiseReadOp::Add, &[lhs, rhs], out)
1411    }
1412
1413    /// Elementwise subtraction accepting either owned tensors or borrowed views.
1414    ///
1415    /// # Examples
1416    ///
1417    /// ```rust
1418    /// use tenferro_tensor::{Tensor, TensorElementwise, TensorRead};
1419    ///
1420    /// fn sub_owned<B: TensorElementwise>(
1421    ///     backend: &mut B,
1422    ///     lhs: &Tensor,
1423    ///     rhs: &Tensor,
1424    /// ) -> tenferro_tensor::Result<Tensor> {
1425    ///     backend.sub_read(TensorRead::from_tensor(lhs), TensorRead::from_tensor(rhs))
1426    /// }
1427    /// ```
1428    /// # Errors
1429    ///
1430    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1431    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1432    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1433    /// backend execution or storage access cannot provide the requested result.
1434    fn sub_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor>;
1435
1436    /// Overwrite caller-provided output with elementwise subtraction.
1437    /// # Errors
1438    ///
1439    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1440    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1441    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1442    /// backend execution or storage access cannot provide the requested result.
1443    fn sub_into(&mut self, lhs: &Tensor, rhs: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
1444        self.sub_read_into(
1445            TensorRead::from_tensor(lhs),
1446            TensorRead::from_tensor(rhs),
1447            out,
1448        )
1449    }
1450
1451    /// Overwrite caller-provided output with elementwise subtraction from reads.
1452    /// # Errors
1453    ///
1454    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1455    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1456    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1457    /// backend execution or storage access cannot provide the requested result.
1458    fn sub_read_into(
1459        &mut self,
1460        lhs: TensorRead<'_>,
1461        rhs: TensorRead<'_>,
1462        out: TensorWrite<'_>,
1463    ) -> crate::Result<()> {
1464        self.elementwise_read_into(ElementwiseReadOp::Subtract, &[lhs, rhs], out)
1465    }
1466
1467    /// # Examples
1468    ///
1469    /// ```rust
1470    /// use tenferro_tensor::{TensorElementwise, Tensor, TensorRead};
1471    ///
1472    /// fn mul_read_in_session<B: TensorElementwise>(
1473    ///     backend: &mut B,
1474    ///     lhs: TensorRead<'_>,
1475    ///     rhs: TensorRead<'_>,
1476    /// ) -> tenferro_tensor::Result<Tensor> {
1477    ///     backend.mul_read(lhs, rhs)
1478    /// }
1479    /// ```
1480    ///
1481    /// # Errors
1482    ///
1483    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1484    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1485    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1486    /// backend execution or storage access cannot provide the requested result.
1487    fn mul_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor>;
1488
1489    /// Overwrite caller-provided output with elementwise multiplication.
1490    /// # Errors
1491    ///
1492    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1493    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1494    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1495    /// backend execution or storage access cannot provide the requested result.
1496    fn mul_into(&mut self, lhs: &Tensor, rhs: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
1497        self.mul_read_into(
1498            TensorRead::from_tensor(lhs),
1499            TensorRead::from_tensor(rhs),
1500            out,
1501        )
1502    }
1503
1504    /// Overwrite caller-provided output with elementwise multiplication from reads.
1505    /// # Errors
1506    ///
1507    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1508    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1509    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1510    /// backend execution or storage access cannot provide the requested result.
1511    fn mul_read_into(
1512        &mut self,
1513        lhs: TensorRead<'_>,
1514        rhs: TensorRead<'_>,
1515        out: TensorWrite<'_>,
1516    ) -> crate::Result<()> {
1517        self.elementwise_read_into(ElementwiseReadOp::Multiply, &[lhs, rhs], out)
1518    }
1519
1520    /// # Examples
1521    ///
1522    /// ```rust
1523    /// use tenferro_tensor::{TensorElementwise, Tensor, TensorRead};
1524    ///
1525    /// fn neg_read_in_session<B: TensorElementwise>(
1526    ///     backend: &mut B,
1527    ///     input: TensorRead<'_>,
1528    /// ) -> tenferro_tensor::Result<Tensor> {
1529    ///     backend.neg_read(input)
1530    /// }
1531    /// ```
1532    ///
1533    /// # Errors
1534    ///
1535    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1536    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1537    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1538    /// backend execution or storage access cannot provide the requested result.
1539    fn neg_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor>;
1540
1541    /// Overwrite caller-provided output with elementwise negation.
1542    /// # Errors
1543    ///
1544    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1545    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1546    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1547    /// backend execution or storage access cannot provide the requested result.
1548    fn neg_into(&mut self, input: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
1549        self.neg_read_into(TensorRead::from_tensor(input), out)
1550    }
1551
1552    /// Overwrite caller-provided output with elementwise negation from a read.
1553    /// # Errors
1554    ///
1555    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1556    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1557    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1558    /// backend execution or storage access cannot provide the requested result.
1559    fn neg_read_into(&mut self, input: TensorRead<'_>, out: TensorWrite<'_>) -> crate::Result<()> {
1560        self.elementwise_read_into(ElementwiseReadOp::Negate, &[input], out)
1561    }
1562
1563    /// # Examples
1564    ///
1565    /// ```rust
1566    /// use tenferro_tensor::{TensorElementwise, Tensor, TensorRead};
1567    ///
1568    /// fn conj_read_in_session<B: TensorElementwise>(
1569    ///     backend: &mut B,
1570    ///     input: TensorRead<'_>,
1571    /// ) -> tenferro_tensor::Result<Tensor> {
1572    ///     backend.conj_read(input)
1573    /// }
1574    /// ```
1575    ///
1576    /// # Errors
1577    ///
1578    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1579    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1580    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1581    /// backend execution or storage access cannot provide the requested result.
1582    fn conj_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor>;
1583
1584    /// Overwrite caller-provided output with elementwise conjugation.
1585    /// # Errors
1586    ///
1587    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1588    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1589    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1590    /// backend execution or storage access cannot provide the requested result.
1591    fn conj_into(&mut self, input: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
1592        self.conj_read_into(TensorRead::from_tensor(input), out)
1593    }
1594
1595    /// Overwrite caller-provided output with elementwise conjugation from a read.
1596    /// # Errors
1597    ///
1598    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1599    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1600    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1601    /// backend execution or storage access cannot provide the requested result.
1602    fn conj_read_into(&mut self, input: TensorRead<'_>, out: TensorWrite<'_>) -> crate::Result<()> {
1603        self.elementwise_read_into(ElementwiseReadOp::Conj, &[input], out)
1604    }
1605
1606    /// # Examples
1607    ///
1608    /// ```rust
1609    /// use tenferro_tensor::{TensorElementwise, Tensor, TensorRead};
1610    ///
1611    /// fn div_read_in_session<B: TensorElementwise>(
1612    ///     backend: &mut B,
1613    ///     lhs: TensorRead<'_>,
1614    ///     rhs: TensorRead<'_>,
1615    /// ) -> tenferro_tensor::Result<Tensor> {
1616    ///     backend.div_read(lhs, rhs)
1617    /// }
1618    /// ```
1619    ///
1620    /// # Errors
1621    ///
1622    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1623    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1624    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1625    /// backend execution or storage access cannot provide the requested result.
1626    fn div_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor>;
1627
1628    /// Overwrite caller-provided output with elementwise division.
1629    /// # Errors
1630    ///
1631    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1632    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1633    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1634    /// backend execution or storage access cannot provide the requested result.
1635    fn div_into(&mut self, lhs: &Tensor, rhs: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
1636        self.div_read_into(
1637            TensorRead::from_tensor(lhs),
1638            TensorRead::from_tensor(rhs),
1639            out,
1640        )
1641    }
1642
1643    /// Overwrite caller-provided output with elementwise division from reads.
1644    /// # Errors
1645    ///
1646    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1647    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1648    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1649    /// backend execution or storage access cannot provide the requested result.
1650    fn div_read_into(
1651        &mut self,
1652        lhs: TensorRead<'_>,
1653        rhs: TensorRead<'_>,
1654        out: TensorWrite<'_>,
1655    ) -> crate::Result<()> {
1656        self.elementwise_read_into(ElementwiseReadOp::Divide, &[lhs, rhs], out)
1657    }
1658
1659    /// Elementwise remainder.
1660    ///
1661    /// The default is an explicit unsupported error so backend implementors can
1662    /// opt in without silent fallback.
1663    ///
1664    /// # Examples
1665    ///
1666    /// ```rust
1667    /// use tenferro_tensor::{Tensor, TensorElementwise};
1668    ///
1669    /// fn rem_owned<B: TensorElementwise>(
1670    ///     backend: &mut B,
1671    ///     lhs: &Tensor,
1672    ///     rhs: &Tensor,
1673    /// ) -> tenferro_tensor::Result<Tensor> {
1674    ///     backend.rem(lhs, rhs)
1675    /// }
1676    /// ```
1677    /// # Errors
1678    ///
1679    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1680    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1681    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1682    /// backend execution or storage access cannot provide the requested result.
1683    fn rem(&mut self, lhs: &Tensor, _rhs: &Tensor) -> crate::Result<Tensor> {
1684        Err(crate::Error::unsupported(
1685            "rem",
1686            format!("backend does not implement rem for dtype {:?}", lhs.dtype()),
1687        ))
1688    }
1689
1690    /// Elementwise remainder accepting owned tensors or borrowed views.
1691    ///
1692    /// # Examples
1693    ///
1694    /// ```rust
1695    /// use tenferro_tensor::{Tensor, TensorElementwise, TensorRead};
1696    ///
1697    /// fn rem_read<B: TensorElementwise>(
1698    ///     backend: &mut B,
1699    ///     lhs: &Tensor,
1700    ///     rhs: &Tensor,
1701    /// ) -> tenferro_tensor::Result<Tensor> {
1702    ///     backend.rem_read(TensorRead::from_tensor(lhs), TensorRead::from_tensor(rhs))
1703    /// }
1704    /// ```
1705    /// # Errors
1706    ///
1707    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1708    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1709    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1710    /// backend execution or storage access cannot provide the requested result.
1711    fn rem_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
1712        self.rem(read_tensor("rem", lhs)?, read_tensor("rem", rhs)?)
1713    }
1714
1715    /// # Examples
1716    ///
1717    /// ```rust
1718    /// use tenferro_tensor::{TensorElementwise, Tensor, TensorRead};
1719    ///
1720    /// fn abs_read_in_session<B: TensorElementwise>(
1721    ///     backend: &mut B,
1722    ///     input: TensorRead<'_>,
1723    /// ) -> tenferro_tensor::Result<Tensor> {
1724    ///     backend.abs_read(input)
1725    /// }
1726    /// ```
1727    ///
1728    /// # Errors
1729    ///
1730    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1731    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1732    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1733    /// backend execution or storage access cannot provide the requested result.
1734    fn abs_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor>;
1735
1736    /// # Examples
1737    ///
1738    /// ```rust
1739    /// use tenferro_tensor::{TensorElementwise, Tensor, TensorRead};
1740    ///
1741    /// fn sign_read_in_session<B: TensorElementwise>(
1742    ///     backend: &mut B,
1743    ///     input: TensorRead<'_>,
1744    /// ) -> tenferro_tensor::Result<Tensor> {
1745    ///     backend.sign_read(input)
1746    /// }
1747    /// ```
1748    ///
1749    /// # Errors
1750    ///
1751    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1752    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1753    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1754    /// backend execution or storage access cannot provide the requested result.
1755    fn sign_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor>;
1756
1757    /// # Examples
1758    ///
1759    /// ```rust
1760    /// use tenferro_tensor::{TensorElementwise, Tensor, TensorRead};
1761    ///
1762    /// fn maximum_read_in_session<B: TensorElementwise>(
1763    ///     backend: &mut B,
1764    ///     lhs: TensorRead<'_>,
1765    ///     rhs: TensorRead<'_>,
1766    /// ) -> tenferro_tensor::Result<Tensor> {
1767    ///     backend.maximum_read(lhs, rhs)
1768    /// }
1769    /// ```
1770    ///
1771    /// # Errors
1772    ///
1773    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1774    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1775    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1776    /// backend execution or storage access cannot provide the requested result.
1777    fn maximum_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor>;
1778
1779    /// # Examples
1780    ///
1781    /// ```rust
1782    /// use tenferro_tensor::{TensorElementwise, Tensor, TensorRead};
1783    ///
1784    /// fn minimum_read_in_session<B: TensorElementwise>(
1785    ///     backend: &mut B,
1786    ///     lhs: TensorRead<'_>,
1787    ///     rhs: TensorRead<'_>,
1788    /// ) -> tenferro_tensor::Result<Tensor> {
1789    ///     backend.minimum_read(lhs, rhs)
1790    /// }
1791    /// ```
1792    ///
1793    /// # Errors
1794    ///
1795    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1796    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1797    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1798    /// backend execution or storage access cannot provide the requested result.
1799    fn minimum_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor>;
1800
1801    /// # Errors
1802    ///
1803    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1804    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1805    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1806    /// backend execution or storage access cannot provide the requested result.
1807    fn compare_read(
1808        &mut self,
1809        lhs: TensorRead<'_>,
1810        rhs: TensorRead<'_>,
1811        dir: &CompareDir,
1812    ) -> crate::Result<Tensor>;
1813
1814    /// # Errors
1815    ///
1816    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1817    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1818    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1819    /// backend execution or storage access cannot provide the requested result.
1820    fn select_read(
1821        &mut self,
1822        pred: TensorRead<'_>,
1823        on_true: TensorRead<'_>,
1824        on_false: TensorRead<'_>,
1825    ) -> crate::Result<Tensor>;
1826
1827    /// # Errors
1828    ///
1829    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1830    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1831    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1832    /// backend execution or storage access cannot provide the requested result.
1833    fn clamp_read(
1834        &mut self,
1835        input: TensorRead<'_>,
1836        lower: TensorRead<'_>,
1837        upper: TensorRead<'_>,
1838    ) -> crate::Result<Tensor>;
1839}
1840
1841/// Analytic unary and binary tensor operations.
1842///
1843/// # Examples
1844///
1845/// ```rust
1846/// use tenferro_tensor::TensorAnalytic;
1847///
1848/// fn accepts_analytic<B: TensorAnalytic>(_backend: &mut B) {}
1849/// ```
1850pub trait TensorAnalytic {
1851    /// # Examples
1852    ///
1853    /// ```rust
1854    /// use tenferro_tensor::{TensorAnalytic, Tensor, TensorRead};
1855    ///
1856    /// fn exp_read_in_session<B: TensorAnalytic>(
1857    ///     backend: &mut B,
1858    ///     input: TensorRead<'_>,
1859    /// ) -> tenferro_tensor::Result<Tensor> {
1860    ///     backend.exp_read(input)
1861    /// }
1862    /// ```
1863    ///
1864    /// # Errors
1865    ///
1866    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1867    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1868    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1869    /// backend execution or storage access cannot provide the requested result.
1870    fn exp_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor>;
1871
1872    /// # Examples
1873    ///
1874    /// ```rust
1875    /// use tenferro_tensor::{TensorAnalytic, Tensor, TensorRead};
1876    ///
1877    /// fn log_read_in_session<B: TensorAnalytic>(
1878    ///     backend: &mut B,
1879    ///     input: TensorRead<'_>,
1880    /// ) -> tenferro_tensor::Result<Tensor> {
1881    ///     backend.log_read(input)
1882    /// }
1883    /// ```
1884    ///
1885    /// # Errors
1886    ///
1887    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1888    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1889    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1890    /// backend execution or storage access cannot provide the requested result.
1891    fn log_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor>;
1892
1893    /// # Examples
1894    ///
1895    /// ```rust
1896    /// use tenferro_tensor::{TensorAnalytic, Tensor, TensorRead};
1897    ///
1898    /// fn sin_read_in_session<B: TensorAnalytic>(
1899    ///     backend: &mut B,
1900    ///     input: TensorRead<'_>,
1901    /// ) -> tenferro_tensor::Result<Tensor> {
1902    ///     backend.sin_read(input)
1903    /// }
1904    /// ```
1905    ///
1906    /// # Errors
1907    ///
1908    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1909    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1910    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1911    /// backend execution or storage access cannot provide the requested result.
1912    fn sin_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor>;
1913
1914    /// # Examples
1915    ///
1916    /// ```rust
1917    /// use tenferro_tensor::{TensorAnalytic, Tensor, TensorRead};
1918    ///
1919    /// fn cos_read_in_session<B: TensorAnalytic>(
1920    ///     backend: &mut B,
1921    ///     input: TensorRead<'_>,
1922    /// ) -> tenferro_tensor::Result<Tensor> {
1923    ///     backend.cos_read(input)
1924    /// }
1925    /// ```
1926    ///
1927    /// # Errors
1928    ///
1929    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1930    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1931    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1932    /// backend execution or storage access cannot provide the requested result.
1933    fn cos_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor>;
1934
1935    /// # Examples
1936    ///
1937    /// ```rust
1938    /// use tenferro_tensor::{TensorAnalytic, Tensor, TensorRead};
1939    ///
1940    /// fn tanh_read_in_session<B: TensorAnalytic>(
1941    ///     backend: &mut B,
1942    ///     input: TensorRead<'_>,
1943    /// ) -> tenferro_tensor::Result<Tensor> {
1944    ///     backend.tanh_read(input)
1945    /// }
1946    /// ```
1947    ///
1948    /// # Errors
1949    ///
1950    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1951    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1952    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1953    /// backend execution or storage access cannot provide the requested result.
1954    fn tanh_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor>;
1955
1956    /// # Examples
1957    ///
1958    /// ```rust
1959    /// use tenferro_tensor::{TensorAnalytic, Tensor, TensorRead};
1960    ///
1961    /// fn sqrt_read_in_session<B: TensorAnalytic>(
1962    ///     backend: &mut B,
1963    ///     input: TensorRead<'_>,
1964    /// ) -> tenferro_tensor::Result<Tensor> {
1965    ///     backend.sqrt_read(input)
1966    /// }
1967    /// ```
1968    ///
1969    /// # Errors
1970    ///
1971    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1972    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1973    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1974    /// backend execution or storage access cannot provide the requested result.
1975    fn sqrt_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor>;
1976
1977    /// # Examples
1978    ///
1979    /// ```rust
1980    /// use tenferro_tensor::{TensorAnalytic, Tensor, TensorRead};
1981    ///
1982    /// fn rsqrt_read_in_session<B: TensorAnalytic>(
1983    ///     backend: &mut B,
1984    ///     input: TensorRead<'_>,
1985    /// ) -> tenferro_tensor::Result<Tensor> {
1986    ///     backend.rsqrt_read(input)
1987    /// }
1988    /// ```
1989    ///
1990    /// # Errors
1991    ///
1992    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1993    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1994    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1995    /// backend execution or storage access cannot provide the requested result.
1996    fn rsqrt_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor>;
1997
1998    /// # Examples
1999    ///
2000    /// ```rust
2001    /// use tenferro_tensor::{TensorAnalytic, Tensor, TensorRead};
2002    ///
2003    /// fn pow_read_in_session<B: TensorAnalytic>(
2004    ///     backend: &mut B,
2005    ///     lhs: TensorRead<'_>,
2006    ///     rhs: TensorRead<'_>,
2007    /// ) -> tenferro_tensor::Result<Tensor> {
2008    ///     backend.pow_read(lhs, rhs)
2009    /// }
2010    /// ```
2011    ///
2012    /// # Errors
2013    ///
2014    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2015    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2016    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2017    /// backend execution or storage access cannot provide the requested result.
2018    fn pow_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor>;
2019
2020    /// # Examples
2021    ///
2022    /// ```rust
2023    /// use tenferro_tensor::{TensorAnalytic, Tensor, TensorRead};
2024    ///
2025    /// fn expm1_read_in_session<B: TensorAnalytic>(
2026    ///     backend: &mut B,
2027    ///     input: TensorRead<'_>,
2028    /// ) -> tenferro_tensor::Result<Tensor> {
2029    ///     backend.expm1_read(input)
2030    /// }
2031    /// ```
2032    ///
2033    /// # Errors
2034    ///
2035    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2036    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2037    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2038    /// backend execution or storage access cannot provide the requested result.
2039    fn expm1_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor>;
2040
2041    /// # Examples
2042    ///
2043    /// ```rust
2044    /// use tenferro_tensor::{TensorAnalytic, Tensor, TensorRead};
2045    ///
2046    /// fn log1p_read_in_session<B: TensorAnalytic>(
2047    ///     backend: &mut B,
2048    ///     input: TensorRead<'_>,
2049    /// ) -> tenferro_tensor::Result<Tensor> {
2050    ///     backend.log1p_read(input)
2051    /// }
2052    /// ```
2053    ///
2054    /// # Errors
2055    ///
2056    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2057    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2058    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2059    /// backend execution or storage access cannot provide the requested result.
2060    fn log1p_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor>;
2061
2062    /// Elementwise error function `erf(x) = 2/sqrt(pi) * integral_0^x exp(-t^2) dt`.
2063    ///
2064    /// Defined for real `F32` and `F64` input only; the result has the input
2065    /// dtype and shape. `erf(+-0) = +-0`, `erf(+-inf) = +-1`, and `NaN` stays
2066    /// `NaN`.
2067    ///
2068    /// # Examples
2069    ///
2070    /// ```rust
2071    /// use tenferro_tensor::{TensorAnalytic, Tensor, TensorRead};
2072    ///
2073    /// fn erf_read_in_session<B: TensorAnalytic>(
2074    ///     backend: &mut B,
2075    ///     input: TensorRead<'_>,
2076    /// ) -> tenferro_tensor::Result<Tensor> {
2077    ///     backend.erf_read(input)
2078    /// }
2079    /// ```
2080    ///
2081    /// # Errors
2082    ///
2083    /// Returns [`crate::Error::UnsupportedDType`] for a complex, integer, or `Bool`
2084    /// input dtype, [`crate::Error::Validation`] with a typed `ValidationError`
2085    /// source for invalid shapes or output metadata, and
2086    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2087    /// backend execution or storage access cannot provide the requested result.
2088    fn erf_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor>;
2089}
2090
2091/// Shape, layout, and dtype transformation operations.
2092///
2093/// # Examples
2094///
2095/// ```rust
2096/// use tenferro_tensor::TensorStructural;
2097///
2098/// fn accepts_structural<B: TensorStructural>(_backend: &mut B) {}
2099/// ```
2100pub trait TensorStructural {
2101    /// Materialize an owned tensor or borrowed view into fresh compact storage.
2102    ///
2103    /// The result has the input's shape and dtype, uses compact column-major
2104    /// layout, and remains in the input's placement. This operation is a
2105    /// same-placement canonicalization boundary, never an implicit host/device
2106    /// transfer. The conservative default accepts host-owned tensors and host
2107    /// views; a view is gathered over its layout, so transposed, sliced, and
2108    /// negative-stride views materialize. It rejects backend buffers and device
2109    /// placement because only an owning backend can materialize those safely.
2110    ///
2111    /// Backend overrides may also accept backend-owned strided views. CUDA
2112    /// accepts numeric and complex views on its active device, including
2113    /// arbitrary valid strides; for `Bool` it copies owned tensors but
2114    /// reports an explicit unsupported-dtype error for strided views.
2115    ///
2116    /// # Examples
2117    ///
2118    /// ```rust
2119    /// use tenferro_tensor::{DType, Tensor, TensorRead, TensorStructural};
2120    ///
2121    /// struct HostDefaults;
2122    /// impl TensorStructural for HostDefaults {
2123    ///     fn transpose_read(&mut self, _: TensorRead<'_>, _: &[usize]) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2124    ///     fn reshape_read(&mut self, _: TensorRead<'_>, _: &[usize]) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2125    ///     fn broadcast_in_dim_read(&mut self, _: TensorRead<'_>, _: &[usize], _: &[usize]) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2126    ///     fn cast(&mut self, _: &Tensor, _: DType) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2127    ///     fn extract_diagonal(&mut self, _: &Tensor, _: usize, _: usize) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2128    ///     fn embed_diagonal(&mut self, _: &Tensor, _: usize, _: usize) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2129    ///     fn tril(&mut self, _: &Tensor, _: i64) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2130    ///     fn triu(&mut self, _: &Tensor, _: i64) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2131    /// }
2132    ///
2133    /// let input = Tensor::from_vec_col_major(vec![2], vec![1_i32, 2])?;
2134    /// let mut backend = HostDefaults;
2135    /// let structural: &mut dyn TensorStructural = &mut backend;
2136    /// let output = structural.to_contiguous_read(TensorRead::from_tensor(&input))?;
2137    /// assert_eq!(output.shape(), &[2]);
2138    /// assert_eq!(output.as_slice::<i32>()?, &[1, 2]);
2139    /// # Ok::<(), tenferro_tensor::Error>(())
2140    /// ```
2141    /// # Errors
2142    ///
2143    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2144    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2145    /// [`crate::Error::RuntimeState`] for backend-owned or device-placed input,
2146    /// which only the owning backend can materialize, or
2147    /// [`crate::Error::BackendFailure`] / [`crate::Error::BackendSource`] when
2148    /// backend execution or storage access cannot provide the requested result.
2149    fn to_contiguous_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2150        match input {
2151            TensorRead::Tensor(input) => {
2152                if input.is_backend_buffer()
2153                    || !matches!(
2154                        input.placement().memory_kind,
2155                        crate::MemoryKind::PinnedHost | crate::MemoryKind::UnpinnedHost
2156                    )
2157                {
2158                    return Err(crate::Error::runtime_state(
2159                        "to_contiguous_read",
2160                        "default materialization accepts only host-owned tensors; use the storage's owning backend",
2161                    ));
2162                }
2163                input.duplicate()
2164            }
2165            TensorRead::View(view) => {
2166                if view.backend_family().is_some()
2167                    || !matches!(
2168                        view.placement().memory_kind,
2169                        crate::MemoryKind::PinnedHost | crate::MemoryKind::UnpinnedHost
2170                    )
2171                {
2172                    return Err(crate::Error::runtime_state(
2173                        "to_contiguous_read",
2174                        "default materialization accepts only host-owned tensors; use the storage's owning backend",
2175                    ));
2176                }
2177                default_materialize_host_view(view)
2178            }
2179        }
2180    }
2181
2182    /// Overwrite caller-provided storage from a readable tensor or view.
2183    ///
2184    /// Source and destination must have identical dtype and shape and belong to
2185    /// the executing backend's placement. The destination is not resized, and
2186    /// every logical destination element is overwritten without reading its old
2187    /// value. Source and destination allocations must not alias. Implementations
2188    /// must not materialize through host memory or perform an implicit transfer.
2189    ///
2190    /// CPU and CUDA accept arbitrary valid source and destination strides: the
2191    /// source is read where it lives and a destination region is written in
2192    /// place, so neither side is copied. CUDA stages only a source view wider
2193    /// than its catalogue's by-value layout rank, through one compact copy. A
2194    /// destination *view* wider than that rank is a typed refusal there, while
2195    /// an owned destination of the same rank is served. The conservative
2196    /// default is explicitly unsupported.
2197    ///
2198    /// # Examples
2199    ///
2200    /// ```rust
2201    /// use tenferro_tensor::{DType, Tensor, TensorRead, TensorStructural, TensorWrite};
2202    ///
2203    /// struct ConservativeDefaults;
2204    /// impl TensorStructural for ConservativeDefaults {
2205    ///     fn transpose_read(&mut self, _: TensorRead<'_>, _: &[usize]) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2206    ///     fn reshape_read(&mut self, _: TensorRead<'_>, _: &[usize]) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2207    ///     fn broadcast_in_dim_read(&mut self, _: TensorRead<'_>, _: &[usize], _: &[usize]) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2208    ///     fn cast(&mut self, _: &Tensor, _: DType) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2209    ///     fn extract_diagonal(&mut self, _: &Tensor, _: usize, _: usize) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2210    ///     fn embed_diagonal(&mut self, _: &Tensor, _: usize, _: usize) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2211    ///     fn tril(&mut self, _: &Tensor, _: i64) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2212    ///     fn triu(&mut self, _: &Tensor, _: i64) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2213    /// }
2214    ///
2215    /// let src = Tensor::from_vec_col_major(vec![2], vec![1_i32, 2])?;
2216    /// let mut dst = Tensor::from_vec_col_major(vec![2], vec![0_i32, 0])?;
2217    /// let mut backend = ConservativeDefaults;
2218    /// let structural: &mut dyn TensorStructural = &mut backend;
2219    /// let error = structural.copy_read_into(
2220    ///     TensorRead::from_tensor(&src),
2221    ///     TensorWrite::from_tensor(&mut dst),
2222    /// ).unwrap_err();
2223    /// assert!(error.to_string().contains("unsupported"));
2224    /// assert_eq!(dst.as_slice::<i32>()?, &[0, 0]);
2225    /// # Ok::<(), tenferro_tensor::Error>(())
2226    /// ```
2227    /// # Errors
2228    ///
2229    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2230    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2231    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2232    /// backend execution or storage access cannot provide the requested result.
2233    fn copy_read_into(&mut self, _src: TensorRead<'_>, _dst: TensorWrite<'_>) -> crate::Result<()> {
2234        Err(crate::Error::unsupported(
2235            "copy_read_into",
2236            "backend-owned runtime copy is unsupported by this backend",
2237        ))
2238    }
2239
2240    /// # Examples
2241    ///
2242    /// ```rust
2243    /// use tenferro_tensor::{TensorStructural, Tensor, TensorRead};
2244    ///
2245    /// fn transpose_read_in_session<B: TensorStructural>(
2246    ///     backend: &mut B,
2247    ///     input: TensorRead<'_>,
2248    ///     perm: &[usize],
2249    /// ) -> tenferro_tensor::Result<Tensor> {
2250    ///     backend.transpose_read(input, perm)
2251    /// }
2252    /// ```
2253    ///
2254    /// # Errors
2255    ///
2256    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2257    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2258    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2259    /// backend execution or storage access cannot provide the requested result.
2260    fn transpose_read(&mut self, input: TensorRead<'_>, perm: &[usize]) -> crate::Result<Tensor>;
2261
2262    /// # Examples
2263    ///
2264    /// ```rust
2265    /// use tenferro_tensor::{TensorStructural, Tensor, TensorRead};
2266    ///
2267    /// fn reshape_read_in_session<B: TensorStructural>(
2268    ///     backend: &mut B,
2269    ///     input: TensorRead<'_>,
2270    ///     shape: &[usize],
2271    /// ) -> tenferro_tensor::Result<Tensor> {
2272    ///     backend.reshape_read(input, shape)
2273    /// }
2274    /// ```
2275    ///
2276    /// # Errors
2277    ///
2278    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2279    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2280    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2281    /// backend execution or storage access cannot provide the requested result.
2282    fn reshape_read(&mut self, input: TensorRead<'_>, shape: &[usize]) -> crate::Result<Tensor>;
2283
2284    /// # Errors
2285    ///
2286    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2287    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2288    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2289    /// backend execution or storage access cannot provide the requested result.
2290    fn broadcast_in_dim_read(
2291        &mut self,
2292        input: TensorRead<'_>,
2293        shape: &[usize],
2294        dims: &[usize],
2295    ) -> crate::Result<Tensor>;
2296
2297    /// Cast a tensor to another dtype using explicit dtype projection.
2298    ///
2299    /// Backends may truncate, narrow precision, project complex values, or use
2300    /// boolean truthiness according to their documented cast support.
2301    ///
2302    /// # Examples
2303    ///
2304    /// ```rust
2305    /// use tenferro_tensor::{DType, Tensor, TensorStructural};
2306    ///
2307    /// fn cast_to_i32<B: TensorStructural>(
2308    ///     backend: &mut B,
2309    ///     input: &Tensor,
2310    /// ) -> tenferro_tensor::Result<Tensor> {
2311    ///     backend.cast(input, DType::I32)
2312    /// }
2313    /// ```
2314    /// # Errors
2315    ///
2316    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2317    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2318    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2319    /// backend execution or storage access cannot provide the requested result.
2320    fn cast(&mut self, input: &Tensor, to: crate::DType) -> crate::Result<Tensor>;
2321
2322    /// Convert a tensor to another dtype using checked dtype conversion.
2323    ///
2324    /// `convert` accepts only conversions allowed by tenferro's dtype-promotion
2325    /// lattice. Use [`TensorStructural::cast`] for explicit lossy projection.
2326    ///
2327    /// # Examples
2328    ///
2329    /// ```rust
2330    /// use tenferro_tensor::{DType, Tensor, TensorStructural};
2331    ///
2332    /// fn convert_to_f64<B: TensorStructural>(
2333    ///     backend: &mut B,
2334    ///     input: &Tensor,
2335    /// ) -> tenferro_tensor::Result<Tensor> {
2336    ///     backend.convert(input, DType::F64)
2337    /// }
2338    /// ```
2339    /// # Errors
2340    ///
2341    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2342    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2343    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2344    /// backend execution or storage access cannot provide the requested result.
2345    fn convert(&mut self, input: &Tensor, to: crate::DType) -> crate::Result<Tensor> {
2346        validate_convert_dtype("convert", input.dtype(), to)?;
2347        self.cast(input, to)
2348    }
2349
2350    /// # Errors
2351    ///
2352    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2353    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2354    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2355    /// backend execution or storage access cannot provide the requested result.
2356    fn extract_diagonal(
2357        &mut self,
2358        input: &Tensor,
2359        axis_a: usize,
2360        axis_b: usize,
2361    ) -> crate::Result<Tensor>;
2362    /// Extract the diagonal with the input taken as a read.
2363    ///
2364    /// The default stages each borrowed view into compact storage (an owned
2365    /// tensor is passed through) and delegates to the owned-input method; a backend whose kernel consumes a borrowed operand
2366    /// should override this so a view is not copied (#2214).
2367    ///
2368    /// # Examples
2369    ///
2370    /// ```rust
2371    /// use tenferro_tensor::{Tensor, TensorStructural, TensorRead};
2372    ///
2373    /// fn run(session: &mut dyn TensorStructural, x: TensorRead<'_>)
2374    ///     -> tenferro_tensor::Result<Tensor>
2375    /// {
2376    ///     session.extract_diagonal_read(x, 0, 1)
2377    /// }
2378    /// ```
2379    ///
2380    /// # Errors
2381    ///
2382    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2383    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2384    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2385    /// backend execution or storage access cannot provide the requested result.
2386    fn extract_diagonal_read(
2387        &mut self,
2388        input: TensorRead<'_>,
2389        axis_a: usize,
2390        axis_b: usize,
2391    ) -> crate::Result<Tensor> {
2392        let input = stage_view(self, input)?;
2393        self.extract_diagonal(&input, axis_a, axis_b)
2394    }
2395
2396    /// # Errors
2397    ///
2398    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2399    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2400    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2401    /// backend execution or storage access cannot provide the requested result.
2402    fn embed_diagonal(
2403        &mut self,
2404        input: &Tensor,
2405        axis_a: usize,
2406        axis_b: usize,
2407    ) -> crate::Result<Tensor>;
2408    /// Embed the input as a diagonal with the input taken as a read.
2409    ///
2410    /// The default stages each borrowed view into compact storage (an owned
2411    /// tensor is passed through) and delegates to the owned-input method; a backend whose kernel consumes a borrowed operand
2412    /// should override this so a view is not copied (#2214).
2413    ///
2414    /// # Examples
2415    ///
2416    /// ```rust
2417    /// use tenferro_tensor::{Tensor, TensorStructural, TensorRead};
2418    ///
2419    /// fn run(session: &mut dyn TensorStructural, x: TensorRead<'_>)
2420    ///     -> tenferro_tensor::Result<Tensor>
2421    /// {
2422    ///     session.embed_diagonal_read(x, 0, 1)
2423    /// }
2424    /// ```
2425    ///
2426    /// # Errors
2427    ///
2428    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2429    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2430    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2431    /// backend execution or storage access cannot provide the requested result.
2432    fn embed_diagonal_read(
2433        &mut self,
2434        input: TensorRead<'_>,
2435        axis_a: usize,
2436        axis_b: usize,
2437    ) -> crate::Result<Tensor> {
2438        let input = stage_view(self, input)?;
2439        self.embed_diagonal(&input, axis_a, axis_b)
2440    }
2441
2442    /// # Errors
2443    ///
2444    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2445    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2446    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2447    /// backend execution or storage access cannot provide the requested result.
2448    fn tril(&mut self, input: &Tensor, k: i64) -> crate::Result<Tensor>;
2449    /// Lower-triangular mask with the input taken as a read.
2450    ///
2451    /// The default stages each borrowed view into compact storage (an owned
2452    /// tensor is passed through) and delegates to the owned-input method; a backend whose kernel consumes a borrowed operand
2453    /// should override this so a view is not copied (#2214).
2454    ///
2455    /// # Examples
2456    ///
2457    /// ```rust
2458    /// use tenferro_tensor::{Tensor, TensorStructural, TensorRead};
2459    ///
2460    /// fn run(session: &mut dyn TensorStructural, x: TensorRead<'_>)
2461    ///     -> tenferro_tensor::Result<Tensor>
2462    /// {
2463    ///     session.tril_read(x, 0)
2464    /// }
2465    /// ```
2466    ///
2467    /// # Errors
2468    ///
2469    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2470    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2471    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2472    /// backend execution or storage access cannot provide the requested result.
2473    fn tril_read(&mut self, input: TensorRead<'_>, k: i64) -> crate::Result<Tensor> {
2474        let input = stage_view(self, input)?;
2475        self.tril(&input, k)
2476    }
2477
2478    /// # Errors
2479    ///
2480    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2481    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2482    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2483    /// backend execution or storage access cannot provide the requested result.
2484    fn triu(&mut self, input: &Tensor, k: i64) -> crate::Result<Tensor>;
2485    /// Upper-triangular mask with the input taken as a read.
2486    ///
2487    /// The default stages each borrowed view into compact storage (an owned
2488    /// tensor is passed through) and delegates to the owned-input method; a backend whose kernel consumes a borrowed operand
2489    /// should override this so a view is not copied (#2214).
2490    ///
2491    /// # Examples
2492    ///
2493    /// ```rust
2494    /// use tenferro_tensor::{Tensor, TensorStructural, TensorRead};
2495    ///
2496    /// fn run(session: &mut dyn TensorStructural, x: TensorRead<'_>)
2497    ///     -> tenferro_tensor::Result<Tensor>
2498    /// {
2499    ///     session.triu_read(x, 0)
2500    /// }
2501    /// ```
2502    ///
2503    /// # Errors
2504    ///
2505    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2506    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2507    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2508    /// backend execution or storage access cannot provide the requested result.
2509    fn triu_read(&mut self, input: TensorRead<'_>, k: i64) -> crate::Result<Tensor> {
2510        let input = stage_view(self, input)?;
2511        self.triu(&input, k)
2512    }
2513}
2514
2515/// Reduction operations.
2516///
2517/// Reducing over an axis whose extent is zero returns an error for every
2518/// reduction operation. Passing an empty `axes` slice is a no-op for the public
2519/// reductions and returns the input values unchanged. Internal mapped
2520/// reductions document their own empty-axis semantics.
2521///
2522/// # Examples
2523///
2524/// ```rust
2525/// use tenferro_tensor::TensorReduction;
2526///
2527/// fn accepts_reduction<B: TensorReduction>(_backend: &mut B) {}
2528/// ```
2529pub trait TensorReduction {
2530    /// Sum elements across axes from an owned tensor or borrowed view.
2531    ///
2532    /// # Examples
2533    ///
2534    /// ```rust
2535    /// use tenferro_tensor::{Tensor, TensorRead, TensorReduction};
2536    ///
2537    /// fn sum_owned<B: TensorReduction>(
2538    ///     backend: &mut B,
2539    ///     input: &Tensor,
2540    /// ) -> tenferro_tensor::Result<Tensor> {
2541    ///     backend.reduce_sum_read(TensorRead::from_tensor(input), &[0])
2542    /// }
2543    /// ```
2544    /// # Errors
2545    ///
2546    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2547    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2548    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2549    /// backend execution or storage access cannot provide the requested result.
2550    fn reduce_sum_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor>;
2551
2552    /// Sum elementwise squares across axes.
2553    ///
2554    /// This execution hook is used by composite operations that avoid a
2555    /// materialized square. Empty axes produce an elementwise square. Backends
2556    /// that support this optimized path must override the hook directly.
2557    ///
2558    /// # Errors
2559    ///
2560    /// Returns the typed validation, unsupported, runtime-state, or backend
2561    /// error produced by multiplication or reduction.
2562    #[doc(hidden)]
2563    fn reduce_sum_squares_read(
2564        &mut self,
2565        _input: TensorRead<'_>,
2566        _axes: &[usize],
2567    ) -> crate::Result<Tensor> {
2568        Err(crate::Error::unsupported(
2569            "reduce_sum_squares",
2570            "backend does not implement fused sum-of-squares reduction",
2571        ))
2572    }
2573
2574    /// Multiply elements across axes from an owned tensor or borrowed view.
2575    ///
2576    /// # Overflow policy
2577    ///
2578    /// Backends may reassociate a floating-point or complex product across
2579    /// lanes, chunks and threads, and the CPU and CUDA kernels do so
2580    /// differently. Rounding and range behaviour therefore depend on the
2581    /// backend, the layout and the thread count: a partial product can overflow
2582    /// to `inf` or underflow to `0`, and combining the partials can then give
2583    /// `inf`, `0` or `NaN` even where the sequential product is representable.
2584    /// The `#2150` alternating `2.0, 0.5` axis is one example: an `f32`
2585    /// sequential product is exactly `1.0`, while a reassociated one may return
2586    /// `NaN`. This is the same trade-off `torch.prod` makes. Integer products
2587    /// are exact and wrap on overflow.
2588    ///
2589    /// # Examples
2590    ///
2591    /// ```rust
2592    /// use tenferro_tensor::{Tensor, TensorRead, TensorReduction};
2593    ///
2594    /// fn prod_owned<B: TensorReduction>(
2595    ///     backend: &mut B,
2596    ///     input: &Tensor,
2597    /// ) -> tenferro_tensor::Result<Tensor> {
2598    ///     backend.reduce_prod_read(TensorRead::from_tensor(input), &[0])
2599    /// }
2600    /// ```
2601    /// # Errors
2602    ///
2603    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2604    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2605    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2606    /// backend execution or storage access cannot provide the requested result.
2607    fn reduce_prod_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor>;
2608
2609    /// Take maximum values across axes from an owned tensor or borrowed view.
2610    ///
2611    /// # Examples
2612    ///
2613    /// ```rust
2614    /// use tenferro_tensor::{Tensor, TensorRead, TensorReduction};
2615    ///
2616    /// fn max_owned<B: TensorReduction>(
2617    ///     backend: &mut B,
2618    ///     input: &Tensor,
2619    /// ) -> tenferro_tensor::Result<Tensor> {
2620    ///     backend.reduce_max_read(TensorRead::from_tensor(input), &[0])
2621    /// }
2622    /// ```
2623    /// # Errors
2624    ///
2625    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2626    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2627    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2628    /// backend execution or storage access cannot provide the requested result.
2629    fn reduce_max_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor>;
2630
2631    /// Take minimum values across axes from an owned tensor or borrowed view.
2632    ///
2633    /// # Examples
2634    ///
2635    /// ```rust
2636    /// use tenferro_tensor::{Tensor, TensorRead, TensorReduction};
2637    ///
2638    /// fn min_owned<B: TensorReduction>(
2639    ///     backend: &mut B,
2640    ///     input: &Tensor,
2641    /// ) -> tenferro_tensor::Result<Tensor> {
2642    ///     backend.reduce_min_read(TensorRead::from_tensor(input), &[0])
2643    /// }
2644    /// ```
2645    /// # Errors
2646    ///
2647    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2648    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2649    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2650    /// backend execution or storage access cannot provide the requested result.
2651    fn reduce_min_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor>;
2652}
2653
2654/// Dot-general operations.
2655///
2656/// # Examples
2657///
2658/// ```rust
2659/// use tenferro_tensor::TensorDot;
2660///
2661/// fn accepts_dot<B: TensorDot>(_backend: &mut B) {}
2662/// ```
2663pub trait TensorDot: TensorElementwise {
2664    #[doc(hidden)]
2665    fn dot_general_read(
2666        &mut self,
2667        lhs: TensorRead<'_>,
2668        rhs: TensorRead<'_>,
2669        config: &DotGeneralConfig,
2670    ) -> crate::Result<Tensor>;
2671
2672    /// Overwrite caller-provided output with dot-general from read inputs.
2673    ///
2674    /// This is the dot/GEMM spelling of `_into`: the previous output value is
2675    /// not read. Use [`TensorDot::dot_general_read_into_accum`] for explicit
2676    /// read-modify-write accumulation.
2677    ///
2678    /// # Examples
2679    ///
2680    /// ```rust
2681    /// use tenferro_tensor::{DotGeneralConfig, TensorDot, TensorRead, TensorWrite};
2682    ///
2683    /// fn dot_into<B: TensorDot>(
2684    ///     backend: &mut B,
2685    ///     lhs: TensorRead<'_>,
2686    ///     rhs: TensorRead<'_>,
2687    ///     config: &DotGeneralConfig,
2688    ///     out: TensorWrite<'_>,
2689    /// ) -> tenferro_tensor::Result<()> {
2690    ///     backend.dot_general_read_into(lhs, rhs, config, out)
2691    /// }
2692    /// ```
2693    /// # Errors
2694    ///
2695    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2696    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2697    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2698    /// backend execution or storage access cannot provide the requested result.
2699    fn dot_general_read_into(
2700        &mut self,
2701        lhs: TensorRead<'_>,
2702        rhs: TensorRead<'_>,
2703        config: &DotGeneralConfig,
2704        out: TensorWrite<'_>,
2705    ) -> crate::Result<()> {
2706        let accumulation = DotGeneralAccumulation::overwrite(lhs.dtype())?;
2707        self.dot_general_read_into_accum(lhs, rhs, config, accumulation, out)
2708    }
2709
2710    #[doc(hidden)]
2711    fn dot_general_with_conj(
2712        &mut self,
2713        lhs: &Tensor,
2714        rhs: &Tensor,
2715        config: &DotGeneralConfig,
2716        lhs_conj: bool,
2717        rhs_conj: bool,
2718    ) -> crate::Result<Tensor> {
2719        if !lhs_conj && !rhs_conj {
2720            return self.dot_general_read(
2721                TensorRead::from_tensor(lhs),
2722                TensorRead::from_tensor(rhs),
2723                config,
2724            );
2725        }
2726
2727        let lhs_tmp;
2728        let lhs_ref = if lhs_conj {
2729            lhs_tmp = self.conj_read(TensorRead::from_tensor(lhs))?;
2730            &lhs_tmp
2731        } else {
2732            lhs
2733        };
2734        let rhs_tmp;
2735        let rhs_ref = if rhs_conj {
2736            rhs_tmp = self.conj_read(TensorRead::from_tensor(rhs))?;
2737            &rhs_tmp
2738        } else {
2739            rhs
2740        };
2741        self.dot_general_read(
2742            TensorRead::from_tensor(lhs_ref),
2743            TensorRead::from_tensor(rhs_ref),
2744            config,
2745        )
2746    }
2747
2748    #[allow(clippy::too_many_arguments)]
2749    #[doc(hidden)]
2750    fn dot_general_with_conj_read(
2751        &mut self,
2752        lhs: TensorRead<'_>,
2753        rhs: TensorRead<'_>,
2754        config: &DotGeneralConfig,
2755        lhs_conj: bool,
2756        rhs_conj: bool,
2757    ) -> crate::Result<Tensor> {
2758        if !lhs_conj && !rhs_conj {
2759            return self.dot_general_read(lhs, rhs, config);
2760        }
2761
2762        let lhs_tmp;
2763        let lhs_ref = if let Some(tensor) = lhs.as_tensor() {
2764            tensor
2765        } else {
2766            lhs_tmp = self.to_contiguous_read(lhs)?;
2767            &lhs_tmp
2768        };
2769        let rhs_tmp;
2770        let rhs_ref = if let Some(tensor) = rhs.as_tensor() {
2771            tensor
2772        } else {
2773            rhs_tmp = self.to_contiguous_read(rhs)?;
2774            &rhs_tmp
2775        };
2776        self.dot_general_with_conj(lhs_ref, rhs_ref, config, lhs_conj, rhs_conj)
2777    }
2778
2779    /// Apply scaled dot-general accumulation into caller-provided output.
2780    ///
2781    /// This is explicitly read-modify-write when `accumulation.beta` is nonzero:
2782    /// `out = alpha * dot_general(lhs, rhs) + beta * out`.
2783    ///
2784    /// # Examples
2785    ///
2786    /// ```rust
2787    /// use tenferro_tensor::{
2788    ///     DotGeneralAccumulation, DotGeneralConfig, TensorDot, TensorRead, TensorWrite,
2789    /// };
2790    ///
2791    /// fn dot_add_to<B: TensorDot>(
2792    ///     backend: &mut B,
2793    ///     lhs: TensorRead<'_>,
2794    ///     rhs: TensorRead<'_>,
2795    ///     config: &DotGeneralConfig,
2796    ///     out: TensorWrite<'_>,
2797    /// ) -> tenferro_tensor::Result<()> {
2798    ///     let accumulation = DotGeneralAccumulation::add_to(lhs.dtype())?;
2799    ///     backend.dot_general_read_into_accum(lhs, rhs, config, accumulation, out)
2800    /// }
2801    /// ```
2802    /// # Errors
2803    ///
2804    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2805    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2806    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2807    /// backend execution or storage access cannot provide the requested result.
2808    fn dot_general_read_into_accum(
2809        &mut self,
2810        lhs: TensorRead<'_>,
2811        rhs: TensorRead<'_>,
2812        config: &DotGeneralConfig,
2813        accumulation: DotGeneralAccumulation,
2814        out: TensorWrite<'_>,
2815    ) -> crate::Result<()> {
2816        dot_general_accum_via_temp(self, lhs, rhs, config, accumulation, out)
2817    }
2818}
2819
2820/// Session-scoped cached dot-general operations.
2821///
2822/// # Examples
2823///
2824/// ```rust
2825/// use tenferro_tensor::Engine;
2826///
2827/// fn accepts_session_dot<S: Engine + ?Sized>(_session: &mut S) {}
2828/// ```
2829pub trait SessionCachedDot: TensorDot {
2830    #[doc(hidden)]
2831    fn dot_general_cached(
2832        &mut self,
2833        _cache_slot: Option<usize>,
2834        lhs: &Tensor,
2835        rhs: &Tensor,
2836        config: &DotGeneralConfig,
2837    ) -> crate::Result<Tensor> {
2838        self.dot_general_read(
2839            TensorRead::from_tensor(lhs),
2840            TensorRead::from_tensor(rhs),
2841            config,
2842        )
2843    }
2844
2845    #[doc(hidden)]
2846    fn dot_general_read_cached(
2847        &mut self,
2848        cache_slot: Option<usize>,
2849        lhs: TensorRead<'_>,
2850        rhs: TensorRead<'_>,
2851        config: &DotGeneralConfig,
2852    ) -> crate::Result<Tensor> {
2853        match (lhs.as_tensor(), rhs.as_tensor()) {
2854            (Some(lhs), Some(rhs)) => self.dot_general_cached(cache_slot, lhs, rhs, config),
2855            _ => {
2856                let lhs = self.to_contiguous_read(lhs)?;
2857                let rhs = self.to_contiguous_read(rhs)?;
2858                self.dot_general_cached(cache_slot, &lhs, &rhs, config)
2859            }
2860        }
2861    }
2862
2863    // Mirrors the dot-general signature plus runtime-cache metadata.
2864    #[allow(clippy::too_many_arguments)]
2865    #[doc(hidden)]
2866    fn dot_general_with_conj_cached(
2867        &mut self,
2868        _cache_slot: Option<usize>,
2869        lhs: &Tensor,
2870        rhs: &Tensor,
2871        config: &DotGeneralConfig,
2872        lhs_conj: bool,
2873        rhs_conj: bool,
2874    ) -> crate::Result<Tensor> {
2875        self.dot_general_with_conj(lhs, rhs, config, lhs_conj, rhs_conj)
2876    }
2877
2878    // Mirrors the dot-general read signature plus runtime-cache metadata.
2879    #[allow(clippy::too_many_arguments)]
2880    #[doc(hidden)]
2881    fn dot_general_with_conj_read_cached(
2882        &mut self,
2883        cache_slot: Option<usize>,
2884        lhs: TensorRead<'_>,
2885        rhs: TensorRead<'_>,
2886        config: &DotGeneralConfig,
2887        lhs_conj: bool,
2888        rhs_conj: bool,
2889    ) -> crate::Result<Tensor> {
2890        if !lhs_conj && !rhs_conj {
2891            return self.dot_general_read_cached(cache_slot, lhs, rhs, config);
2892        }
2893
2894        let lhs_tmp;
2895        let lhs_ref = if let Some(tensor) = lhs.as_tensor() {
2896            tensor
2897        } else {
2898            lhs_tmp = self.to_contiguous_read(lhs)?;
2899            &lhs_tmp
2900        };
2901        let rhs_tmp;
2902        let rhs_ref = if let Some(tensor) = rhs.as_tensor() {
2903            tensor
2904        } else {
2905            rhs_tmp = self.to_contiguous_read(rhs)?;
2906            &rhs_tmp
2907        };
2908        self.dot_general_with_conj_cached(cache_slot, lhs_ref, rhs_ref, config, lhs_conj, rhs_conj)
2909    }
2910
2911    /// Apply session-cached scaled dot-general accumulation into output.
2912    ///
2913    /// The cache slot is session-local metadata; `accumulation` still controls
2914    /// overwrite versus read-modify-write semantics.
2915    ///
2916    /// # Examples
2917    ///
2918    /// ```rust
2919    /// use tenferro_tensor::{
2920    ///     DotGeneralAccumulation, DotGeneralConfig, SessionCachedDot, TensorRead, TensorWrite,
2921    /// };
2922    ///
2923    /// fn session_cached_dot_add_to<S: SessionCachedDot + ?Sized>(
2924    ///     session: &mut S,
2925    ///     lhs: TensorRead<'_>,
2926    ///     rhs: TensorRead<'_>,
2927    ///     config: &DotGeneralConfig,
2928    ///     out: TensorWrite<'_>,
2929    /// ) -> tenferro_tensor::Result<()> {
2930    ///     let accumulation = DotGeneralAccumulation::add_to(lhs.dtype())?;
2931    ///     session.dot_general_read_into_accum_cached(
2932    ///         Some(0),
2933    ///         lhs,
2934    ///         rhs,
2935    ///         config,
2936    ///         accumulation,
2937    ///         out,
2938    ///     )
2939    /// }
2940    /// ```
2941    /// # Errors
2942    ///
2943    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2944    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2945    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2946    /// backend execution or storage access cannot provide the requested result.
2947    fn dot_general_read_into_accum_cached(
2948        &mut self,
2949        _cache_slot: Option<usize>,
2950        lhs: TensorRead<'_>,
2951        rhs: TensorRead<'_>,
2952        config: &DotGeneralConfig,
2953        accumulation: DotGeneralAccumulation,
2954        out: TensorWrite<'_>,
2955    ) -> crate::Result<()> {
2956        self.dot_general_read_into_accum(lhs, rhs, config, accumulation, out)
2957    }
2958}
2959
2960/// Indexing, slicing, and padding operations.
2961///
2962/// # Examples
2963///
2964/// ```rust
2965/// use tenferro_tensor::TensorIndexing;
2966///
2967/// fn accepts_indexing<B: TensorIndexing>(_backend: &mut B) {}
2968/// ```
2969pub trait TensorIndexing: TensorStructural {
2970    /// # Errors
2971    ///
2972    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2973    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2974    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2975    /// backend execution or storage access cannot provide the requested result.
2976    fn gather(
2977        &mut self,
2978        operand: &Tensor,
2979        start_indices: &Tensor,
2980        config: &GatherConfig,
2981    ) -> crate::Result<Tensor>;
2982    /// # Errors
2983    ///
2984    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2985    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2986    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2987    /// backend execution or storage access cannot provide the requested result.
2988    fn scatter(
2989        &mut self,
2990        operand: &Tensor,
2991        scatter_indices: &Tensor,
2992        updates: &Tensor,
2993        config: &ScatterConfig,
2994    ) -> crate::Result<Tensor>;
2995    /// Scatter with every input taken as a read.
2996    ///
2997    /// The default stages each borrowed view into compact storage (an owned
2998    /// tensor is passed through) and delegates to the owned-input method; a backend whose kernel consumes a borrowed operand
2999    /// should override this so a view is not copied (#2214).
3000    ///
3001    /// # Examples
3002    ///
3003    /// ```rust
3004    /// use tenferro_tensor::{Tensor, TensorIndexing, ScatterConfig, TensorRead};
3005    ///
3006    /// fn run(session: &mut dyn TensorIndexing, x: TensorRead<'_>, idx: TensorRead<'_>, updates: TensorRead<'_>, config: &ScatterConfig)
3007    ///     -> tenferro_tensor::Result<Tensor>
3008    /// {
3009    ///     session.scatter_read(x, idx, updates, config)
3010    /// }
3011    /// ```
3012    ///
3013    /// # Errors
3014    ///
3015    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3016    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3017    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3018    /// backend execution or storage access cannot provide the requested result.
3019    fn scatter_read(
3020        &mut self,
3021        operand: TensorRead<'_>,
3022        scatter_indices: TensorRead<'_>,
3023        updates: TensorRead<'_>,
3024        config: &ScatterConfig,
3025    ) -> crate::Result<Tensor> {
3026        let operand = stage_view(self, operand)?;
3027        let scatter_indices = stage_view(self, scatter_indices)?;
3028        let updates = stage_view(self, updates)?;
3029        self.scatter(&operand, &scatter_indices, &updates, config)
3030    }
3031
3032    /// # Errors
3033    ///
3034    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3035    /// for invalid shapes, ranks, axes, dtypes, or output metadata. In
3036    /// particular, a limit greater than the corresponding input dimension is
3037    /// reported as [`crate::ValidationError::InvalidArgument`] with the
3038    /// `"configuration"` argument. It returns [`crate::Error::BackendFailure`]
3039    /// or [`crate::Error::BackendSource`] when backend execution or storage
3040    /// access cannot provide the requested result.
3041    fn slice(&mut self, input: &Tensor, config: &SliceConfig) -> crate::Result<Tensor>;
3042    /// Slice with the input taken as a read.
3043    ///
3044    /// The default stages each borrowed view into compact storage (an owned
3045    /// tensor is passed through) and delegates to the owned-input method; a backend whose kernel consumes a borrowed operand
3046    /// should override this so a view is not copied (#2214).
3047    ///
3048    /// # Examples
3049    ///
3050    /// ```rust
3051    /// use tenferro_tensor::{Tensor, TensorIndexing, SliceConfig, TensorRead};
3052    ///
3053    /// fn run(session: &mut dyn TensorIndexing, x: TensorRead<'_>, config: &SliceConfig)
3054    ///     -> tenferro_tensor::Result<Tensor>
3055    /// {
3056    ///     session.slice_read(x, config)
3057    /// }
3058    /// ```
3059    ///
3060    /// # Errors
3061    ///
3062    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3063    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3064    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3065    /// backend execution or storage access cannot provide the requested result.
3066    fn slice_read(&mut self, input: TensorRead<'_>, config: &SliceConfig) -> crate::Result<Tensor> {
3067        let input = stage_view(self, input)?;
3068        self.slice(&input, config)
3069    }
3070
3071    /// # Errors
3072    ///
3073    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3074    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3075    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3076    /// backend execution or storage access cannot provide the requested result.
3077    fn dynamic_slice(
3078        &mut self,
3079        input: &Tensor,
3080        starts: &Tensor,
3081        slice_sizes: &[usize],
3082    ) -> crate::Result<Tensor>;
3083    /// Dynamic slice with every input taken as a read.
3084    ///
3085    /// The default stages each borrowed view into compact storage (an owned
3086    /// tensor is passed through) and delegates to the owned-input method; a backend whose kernel consumes a borrowed operand
3087    /// should override this so a view is not copied (#2214).
3088    ///
3089    /// # Examples
3090    ///
3091    /// ```rust
3092    /// use tenferro_tensor::{Tensor, TensorIndexing, TensorRead};
3093    ///
3094    /// fn run(session: &mut dyn TensorIndexing, x: TensorRead<'_>, starts: TensorRead<'_>)
3095    ///     -> tenferro_tensor::Result<Tensor>
3096    /// {
3097    ///     session.dynamic_slice_read(x, starts, &[2])
3098    /// }
3099    /// ```
3100    ///
3101    /// # Errors
3102    ///
3103    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3104    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3105    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3106    /// backend execution or storage access cannot provide the requested result.
3107    fn dynamic_slice_read(
3108        &mut self,
3109        input: TensorRead<'_>,
3110        starts: TensorRead<'_>,
3111        slice_sizes: &[usize],
3112    ) -> crate::Result<Tensor> {
3113        let input = stage_view(self, input)?;
3114        let starts = stage_view(self, starts)?;
3115        self.dynamic_slice(&input, &starts, slice_sizes)
3116    }
3117
3118    /// # Errors
3119    ///
3120    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3121    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3122    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3123    /// backend execution or storage access cannot provide the requested result.
3124    fn dynamic_update_slice(
3125        &mut self,
3126        operand: &Tensor,
3127        update: &Tensor,
3128        starts: &Tensor,
3129    ) -> crate::Result<Tensor>;
3130    /// Dynamic-update-slice with every input taken as a read.
3131    ///
3132    /// The default stages each borrowed view into compact storage (an owned
3133    /// tensor is passed through) and delegates to the owned-input method; a
3134    /// backend whose kernel consumes a borrowed operand should override this so
3135    /// a view is not copied (#2214).
3136    ///
3137    /// # Examples
3138    ///
3139    /// ```rust
3140    /// use tenferro_tensor::{Tensor, TensorIndexing, TensorRead};
3141    ///
3142    /// fn run(
3143    ///     session: &mut dyn TensorIndexing,
3144    ///     x: TensorRead<'_>,
3145    ///     update: TensorRead<'_>,
3146    ///     starts: TensorRead<'_>,
3147    /// ) -> tenferro_tensor::Result<Tensor> {
3148    ///     session.dynamic_update_slice_read(x, update, starts)
3149    /// }
3150    /// ```
3151    ///
3152    /// # Errors
3153    ///
3154    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3155    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3156    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3157    /// backend execution or storage access cannot provide the requested result.
3158    fn dynamic_update_slice_read(
3159        &mut self,
3160        operand: TensorRead<'_>,
3161        update: TensorRead<'_>,
3162        starts: TensorRead<'_>,
3163    ) -> crate::Result<Tensor> {
3164        let operand = stage_view(self, operand)?;
3165        let update = stage_view(self, update)?;
3166        let starts = stage_view(self, starts)?;
3167        self.dynamic_update_slice(&operand, &update, &starts)
3168    }
3169    /// # Errors
3170    ///
3171    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3172    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3173    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3174    /// backend execution or storage access cannot provide the requested result.
3175    fn pad(&mut self, input: &Tensor, config: &PadConfig) -> crate::Result<Tensor>;
3176    /// Pad with the input taken as a read.
3177    ///
3178    /// The default stages each borrowed view into compact storage (an owned
3179    /// tensor is passed through) and delegates to the owned-input method; a backend whose kernel consumes a borrowed operand
3180    /// should override this so a view is not copied (#2214).
3181    ///
3182    /// # Examples
3183    ///
3184    /// ```rust
3185    /// use tenferro_tensor::{Tensor, TensorIndexing, PadConfig, TensorRead};
3186    ///
3187    /// fn run(session: &mut dyn TensorIndexing, x: TensorRead<'_>, config: &PadConfig)
3188    ///     -> tenferro_tensor::Result<Tensor>
3189    /// {
3190    ///     session.pad_read(x, config)
3191    /// }
3192    /// ```
3193    ///
3194    /// # Errors
3195    ///
3196    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3197    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3198    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3199    /// backend execution or storage access cannot provide the requested result.
3200    fn pad_read(&mut self, input: TensorRead<'_>, config: &PadConfig) -> crate::Result<Tensor> {
3201        let input = stage_view(self, input)?;
3202        self.pad(&input, config)
3203    }
3204
3205    /// # Errors
3206    ///
3207    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3208    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3209    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3210    /// backend execution or storage access cannot provide the requested result.
3211    fn concatenate(&mut self, inputs: &[&Tensor], axis: usize) -> crate::Result<Tensor>;
3212    /// Concatenate with every input taken as a read.
3213    ///
3214    /// The default stages each borrowed view into compact storage (an owned
3215    /// tensor is passed through) and delegates to the owned-input method; a backend whose kernel consumes a borrowed operand
3216    /// should override this so a view is not copied (#2214).
3217    ///
3218    /// # Examples
3219    ///
3220    /// ```rust
3221    /// use tenferro_tensor::{Tensor, TensorIndexing, TensorRead};
3222    ///
3223    /// fn run(session: &mut dyn TensorIndexing, a: TensorRead<'_>, b: TensorRead<'_>)
3224    ///     -> tenferro_tensor::Result<Tensor>
3225    /// {
3226    ///     session.concatenate_read(&[a, b], 0)
3227    /// }
3228    /// ```
3229    ///
3230    /// # Errors
3231    ///
3232    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3233    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3234    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3235    /// backend execution or storage access cannot provide the requested result.
3236    fn concatenate_read(
3237        &mut self,
3238        inputs: &[TensorRead<'_>],
3239        axis: usize,
3240    ) -> crate::Result<Tensor> {
3241        let staged = inputs
3242            .iter()
3243            .map(|input| stage_view(self, input.clone()))
3244            .collect::<crate::Result<Vec<_>>>()?;
3245        let refs: Vec<&Tensor> = staged.iter().map(|input| &**input).collect();
3246        self.concatenate(&refs, axis)
3247    }
3248
3249    /// # Errors
3250    ///
3251    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3252    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3253    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3254    /// backend execution or storage access cannot provide the requested result.
3255    fn reverse(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor>;
3256    /// Reverse with the input taken as a read.
3257    ///
3258    /// The default stages each borrowed view into compact storage (an owned
3259    /// tensor is passed through) and delegates to the owned-input method; a backend whose kernel consumes a borrowed operand
3260    /// should override this so a view is not copied (#2214).
3261    ///
3262    /// # Examples
3263    ///
3264    /// ```rust
3265    /// use tenferro_tensor::{Tensor, TensorIndexing, TensorRead};
3266    ///
3267    /// fn run(session: &mut dyn TensorIndexing, x: TensorRead<'_>)
3268    ///     -> tenferro_tensor::Result<Tensor>
3269    /// {
3270    ///     session.reverse_read(x, &[0])
3271    /// }
3272    /// ```
3273    ///
3274    /// # Errors
3275    ///
3276    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3277    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3278    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3279    /// backend execution or storage access cannot provide the requested result.
3280    fn reverse_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor> {
3281        let input = stage_view(self, input)?;
3282        self.reverse(&input, axes)
3283    }
3284}
3285
3286/// Backend-owned canonicalization for typed tensor views.
3287///
3288/// Implementations must preserve the input placement family. CPU backends
3289/// canonicalize host views through explicit host copies and reject backend
3290/// buffers with a diagnostic that asks the caller to download first. GPU
3291/// backends canonicalize GPU-resident views on the same device and reject host
3292/// buffers with an upload hint.
3293///
3294/// [`TensorViewCanonicalization::copy_into`] requires source and destination
3295/// shapes, scalar dtypes, and placement families to match. The destination
3296/// view must be internally non-overlapping, and source and destination backing
3297/// allocations must not alias unless an implementation explicitly documents
3298/// and supports that case. Implementations may reject layouts their native
3299/// kernels cannot consume.
3300///
3301/// CUDA currently accepts only a compact column-major source view with offset
3302/// zero that covers its full allocation; arbitrary-stride destinations remain
3303/// supported. Canonicalization and copying are same-placement operations: they
3304/// must not perform hidden host/device transfers or silently materialize an
3305/// unsupported source layout.
3306///
3307/// This trait is intentionally separate from [`Engine`] so generic
3308/// typed methods do not change the object-safety contract of `dyn Engine`.
3309///
3310/// # Examples
3311///
3312/// ```rust
3313/// use tenferro_tensor::{DynRank, TensorViewCanonicalization, TypedTensor};
3314///
3315/// fn compact_i32<B: TensorViewCanonicalization<i32, DynRank>>(
3316///     backend: &mut B,
3317///     tensor: &TypedTensor<i32>,
3318/// ) -> tenferro_tensor::Result<TypedTensor<i32>> {
3319///     backend.to_contiguous(&tensor.as_view())
3320/// }
3321///
3322/// fn copy_i32<B: TensorViewCanonicalization<i32, DynRank>>(
3323///     backend: &mut B,
3324///     src: &TypedTensor<i32>,
3325///     dst: &mut TypedTensor<i32>,
3326/// ) -> tenferro_tensor::Result<()> {
3327///     backend.copy_into(&src.as_view(), &mut dst.as_view_mut())
3328/// }
3329/// ```
3330pub trait TensorViewCanonicalization<T: TensorScalar, R: TensorRank> {
3331    /// # Errors
3332    ///
3333    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3334    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3335    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3336    /// backend execution or storage access cannot provide the requested result.
3337    fn to_contiguous(
3338        &mut self,
3339        view: &TypedTensorView<'_, T, R>,
3340    ) -> crate::Result<TypedTensor<T, R>>;
3341
3342    /// # Errors
3343    ///
3344    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3345    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3346    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3347    /// backend execution or storage access cannot provide the requested result.
3348    fn copy_into(
3349        &mut self,
3350        src: &TypedTensorView<'_, T, R>,
3351        dst: &mut TypedTensorViewMut<'_, T, R>,
3352    ) -> crate::Result<()>;
3353}
3354
3355/// Optional elementwise fusion execution.
3356///
3357/// # Examples
3358///
3359/// ```rust
3360/// use tenferro_tensor::TensorFusion;
3361///
3362/// fn accepts_fusion<B: TensorFusion>(_backend: &mut B) {}
3363/// ```
3364pub trait TensorFusion {
3365    #[doc(hidden)]
3366    fn execute_elementwise_fusion(
3367        &mut self,
3368        _inputs: &[&Tensor],
3369        _plan: &ElementwiseFusionPlan,
3370    ) -> crate::Result<Option<Vec<Tensor>>> {
3371        Ok(None)
3372    }
3373
3374    #[doc(hidden)]
3375    #[allow(clippy::too_many_arguments)]
3376    fn execute_broadcast_multiply(
3377        &mut self,
3378        _lhs: TensorRead<'_>,
3379        _lhs_shape: &[usize],
3380        _lhs_dims: &[usize],
3381        _rhs: TensorRead<'_>,
3382        _rhs_shape: &[usize],
3383        _rhs_dims: &[usize],
3384    ) -> crate::Result<Option<Tensor>> {
3385        Ok(None)
3386    }
3387
3388    #[doc(hidden)]
3389    #[allow(clippy::too_many_arguments)]
3390    fn execute_broadcast_multiply_value(
3391        &mut self,
3392        lhs: TensorRead<'_>,
3393        lhs_shape: &[usize],
3394        lhs_dims: &[usize],
3395        rhs: TensorRead<'_>,
3396        rhs_shape: &[usize],
3397        rhs_dims: &[usize],
3398    ) -> crate::Result<Option<TensorValue>> {
3399        self.execute_broadcast_multiply(lhs, lhs_shape, lhs_dims, rhs, rhs_shape, rhs_dims)
3400            .map(|tensor| tensor.map(TensorValue::from_tensor))
3401    }
3402}
3403
3404/// Backend buffer lifecycle operations.
3405///
3406/// # Examples
3407///
3408/// ```rust
3409/// use tenferro_tensor::TensorBuffer;
3410///
3411/// fn accepts_buffer<B: TensorBuffer>(_backend: &mut B) {}
3412/// ```
3413pub trait TensorBuffer {
3414    fn reclaim_buffer(&mut self, _tensor: Tensor) {}
3415}
3416
3417/// Device transfer operations on backend boundaries.
3418///
3419/// # Examples
3420///
3421/// ```rust
3422/// use tenferro_tensor::TensorDeviceTransfer;
3423///
3424/// fn accepts_transfer<B: TensorDeviceTransfer>(_backend: &mut B) {}
3425/// ```
3426pub trait TensorDeviceTransfer {
3427    /// Explicitly copy a provider-owned read target into host storage.
3428    ///
3429    /// Implementations must not return the input unchanged or stage through an
3430    /// unrelated provider. A backend that cannot transfer the requested read
3431    /// target returns a typed unsupported error.
3432    ///
3433    /// # Errors
3434    ///
3435    /// Returns [`crate::Error::Unsupported`] when the implementation cannot
3436    /// perform the requested transfer, or a typed validation/backend error when
3437    /// the source cannot be read.
3438    fn download_to_host(&mut self, tensor: TensorRead<'_>) -> crate::Result<Tensor>;
3439
3440    /// Explicitly copy a host read target into provider storage.
3441    ///
3442    /// # Errors
3443    ///
3444    /// Returns [`crate::Error::Unsupported`] when the implementation cannot
3445    /// perform the requested transfer, or a typed validation/backend error when
3446    /// the source cannot be read.
3447    fn upload_host_tensor(&mut self, tensor: TensorRead<'_>) -> crate::Result<Tensor>;
3448}
3449
3450#[doc(hidden)]
3451pub trait TensorBackendOps:
3452    TensorElementwise
3453    + TensorAnalytic
3454    + TensorStructural
3455    + TensorReduction
3456    + TensorIndexing
3457    + TensorDot
3458    + TensorFusion
3459    + TensorBuffer
3460{
3461}
3462
3463impl<T> TensorBackendOps for T where
3464    T: TensorElementwise
3465        + TensorAnalytic
3466        + TensorStructural
3467        + TensorReduction
3468        + TensorIndexing
3469        + TensorDot
3470        + TensorFusion
3471        + TensorBuffer
3472        + ?Sized
3473{
3474}
3475
3476/// Validate the shared input contract for [`Engine::vdot_read`].
3477#[doc(hidden)]
3478pub fn validate_vdot_read(lhs: &TensorRead<'_>, rhs: &TensorRead<'_>) -> crate::Result<()> {
3479    let op = "Engine::vdot_read";
3480    validate_supported_blas1_dtype(op, lhs.dtype())?;
3481    if rhs.dtype() != lhs.dtype() {
3482        return Err(Error::dtype_mismatch(op, lhs.dtype(), rhs.dtype()));
3483    }
3484    if lhs.shape() != rhs.shape() {
3485        return Err(Error::shape_mismatch(
3486            op,
3487            lhs.shape().to_vec(),
3488            rhs.shape().to_vec(),
3489        ));
3490    }
3491    validate_shape_product(op, lhs.shape())?;
3492    validate_compatible_placement(op, lhs, rhs)
3493}
3494
3495/// Validate the shared input contract for [`Engine::norm_squared_read`].
3496#[doc(hidden)]
3497pub fn validate_norm_squared_read(input: &TensorRead<'_>) -> crate::Result<()> {
3498    validate_supported_blas1_dtype("Engine::norm_squared_read", input.dtype())?;
3499    validate_shape_product("Engine::norm_squared_read", input.shape()).map(|_| ())
3500}
3501
3502/// Validate the shared input and destination contract for
3503/// [`Engine::axpby_read_into_accum`].
3504#[doc(hidden)]
3505pub fn validate_axpby_read_into_accum(
3506    alpha: ContractionScalar,
3507    x: &TensorRead<'_>,
3508    beta: ContractionScalar,
3509    y: &TensorWrite<'_>,
3510) -> crate::Result<()> {
3511    let op = "Engine::axpby_read_into_accum";
3512    validate_supported_blas1_dtype(op, x.dtype())?;
3513    if y.dtype() != x.dtype() {
3514        return Err(Error::dtype_mismatch(op, x.dtype(), y.dtype()));
3515    }
3516    if alpha.dtype() != x.dtype() {
3517        return Err(Error::dtype_mismatch(op, x.dtype(), alpha.dtype()));
3518    }
3519    if beta.dtype() != x.dtype() {
3520        return Err(Error::dtype_mismatch(op, x.dtype(), beta.dtype()));
3521    }
3522    if x.shape() != y.shape() {
3523        return Err(Error::shape_mismatch(
3524            op,
3525            x.shape().to_vec(),
3526            y.shape().to_vec(),
3527        ));
3528    }
3529    validate_shape_product(op, x.shape())?;
3530    // INVARIANT: write-injectivity is already proved when a destination view is
3531    // constructed (`validate_mutable_no_overlap`), and an owned destination is
3532    // compact by construction, so no element-wise re-check is needed here. Only
3533    // the `x`/`y` alias rejection below remains.
3534    validate_compatible_placement(op, x, &y.as_read())?;
3535    validate_read_into_destination(op, std::slice::from_ref(x), y)
3536}
3537
3538/// # Errors
3539///
3540/// Returns [`Error::Unsupported`] when `dtype` is not floating or complex.
3541fn validate_supported_blas1_dtype(op: &'static str, dtype: DType) -> crate::Result<()> {
3542    if matches!(dtype, DType::F32 | DType::F64 | DType::C32 | DType::C64) {
3543        Ok(())
3544    } else {
3545        Err(Error::unsupported(
3546            op,
3547            format!("unsupported dtype {dtype:?}; supported dtypes: F32/F64/C32/C64"),
3548        ))
3549    }
3550}
3551
3552/// # Errors
3553///
3554/// Returns [`Error::Validation`] with [`ValidationError::IntegerOverflow`] when
3555/// the checked shape product overflows.
3556fn validate_shape_product(op: &'static str, shape: &[usize]) -> crate::Result<usize> {
3557    crate::validate::checked_shape_product(op, "shape", shape)
3558}
3559
3560/// # Errors
3561///
3562/// Returns [`Error::Validation`] with [`ValidationError::InvalidArgument`] when
3563/// placements differ.
3564fn validate_compatible_placement(
3565    op: &'static str,
3566    lhs: &TensorRead<'_>,
3567    rhs: &TensorRead<'_>,
3568) -> crate::Result<()> {
3569    if lhs.placement() != rhs.placement() {
3570        return Err(Error::invalid_argument(
3571            op,
3572            "placement",
3573            "tensor inputs must have compatible placement",
3574        ));
3575    }
3576    Ok(())
3577}
3578
3579/// The execution trait every tensor operation runs on.
3580///
3581/// An engine is a caller-held value (a CPU engine owns a Rayon pool and a
3582/// workspace; a GPU engine owns a stream and a workspace). Operations take
3583/// `&mut` of the engine, so one computation uses an engine at a time without
3584/// any runtime lock. There is no separate session object.
3585///
3586/// # Examples
3587///
3588/// ```rust
3589/// use tenferro_tensor::{Engine, Tensor, TensorRead};
3590///
3591/// fn add_on(engine: &mut dyn Engine, a: &Tensor, b: &Tensor) -> tenferro_tensor::Result<Tensor> {
3592///     engine.add_read(TensorRead::from_tensor(a), TensorRead::from_tensor(b))
3593/// }
3594/// ```
3595///
3596/// The operation one-shot spelling is gone; an engine only answers to the read
3597/// form:
3598///
3599/// ```compile_fail
3600/// use tenferro_tensor::{Engine, Tensor};
3601///
3602/// fn add_on(engine: &mut dyn Engine, a: &Tensor, b: &Tensor) {
3603///     let _ = engine.add(a, b);
3604/// }
3605/// ```
3606pub trait Engine: TensorBackendOps + SessionCachedDot + TensorDeviceTransfer {
3607    /// Gather with both operands taken as reads.
3608    ///
3609    /// The default stages each borrowed view (an owned tensor is passed
3610    /// through) and delegates to [`TensorIndexing::gather`]; a backend whose gather kernel consumes the
3611    /// operand's strides overrides this so a borrowed operand needs no copy
3612    /// (invariant I1: the runtime must not materialize by default).
3613    ///
3614    /// # Errors
3615    ///
3616    /// Returns [`crate::Error::Validation`] with a typed `ValidationError`
3617    /// source for invalid shapes, ranks, axes, dtypes or output metadata, and
3618    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3619    /// backend execution or storage access cannot provide the requested result.
3620    /// The default implementation stages a view first, so it also returns
3621    /// whatever [`TensorStructural::to_contiguous_read`] reports for a view the backend
3622    /// cannot materialize.
3623    fn gather_read(
3624        &mut self,
3625        operand: TensorRead<'_>,
3626        start_indices: TensorRead<'_>,
3627        config: &GatherConfig,
3628    ) -> crate::Result<Tensor> {
3629        let operand = stage_view(self, operand)?;
3630        let start_indices = stage_view(self, start_indices)?;
3631        self.gather(&operand, &start_indices, config)
3632    }
3633
3634    /// Compute the all-axis conjugating dot product without transferring either input.
3635    ///
3636    /// The result is a rank-0 tensor with the input dtype and has the value
3637    /// `sum(conj(lhs) * rhs)`. Borrowed views remain borrowed through the
3638    /// backend's existing same-placement planning boundary.
3639    ///
3640    /// # Examples
3641    ///
3642    /// ```rust
3643    /// use tenferro_tensor::{Engine, TensorRead};
3644    ///
3645    /// fn vdot(session: &mut dyn Engine, x: TensorRead<'_>, y: TensorRead<'_>)
3646    ///     -> tenferro_tensor::Result<tenferro_tensor::Tensor>
3647    /// {
3648    ///     session.vdot_read(x, y)
3649    /// }
3650    /// ```
3651    ///
3652    /// # Errors
3653    ///
3654    /// Returns [`Error::Unsupported`] when the backend does not override this
3655    /// capability or the dtype is unsupported; [`Error::Validation`] with
3656    /// `DTypeMismatch`, `ShapeMismatch`, or `InvalidArgument` when dtype, shape,
3657    /// or placement differs; [`Error::RuntimeState`] for inaccessible backend
3658    /// storage; or [`Error::BackendSource`] when provider execution fails.
3659    fn vdot_read(&mut self, _lhs: TensorRead<'_>, _rhs: TensorRead<'_>) -> crate::Result<Tensor> {
3660        Err(Error::unsupported(
3661            "Engine::vdot_read",
3662            "backend session does not implement vdot_read",
3663        ))
3664    }
3665
3666    /// Compute the all-axis sum of squared magnitudes without taking a square root.
3667    ///
3668    /// The result is rank 0 and is F32 for F32/C32 input or F64 for F64/C64
3669    /// input. No transfer or full-size algebra temporary is implied by this
3670    /// session contract.
3671    ///
3672    /// # Examples
3673    ///
3674    /// ```rust
3675    /// use tenferro_tensor::{Engine, TensorRead};
3676    ///
3677    /// fn norm_squared(session: &mut dyn Engine, x: TensorRead<'_>)
3678    ///     -> tenferro_tensor::Result<tenferro_tensor::Tensor>
3679    /// {
3680    ///     session.norm_squared_read(x)
3681    /// }
3682    /// ```
3683    ///
3684    /// # Errors
3685    ///
3686    /// Returns [`Error::Unsupported`] when the backend does not override this
3687    /// capability or the dtype is unsupported, [`Error::RuntimeState`] when
3688    /// backend storage is not host-accessible, or [`Error::BackendSource`] when
3689    /// reduction execution fails.
3690    fn norm_squared_read(&mut self, _input: TensorRead<'_>) -> crate::Result<Tensor> {
3691        Err(Error::unsupported(
3692            "Engine::norm_squared_read",
3693            "backend session does not implement norm_squared_read",
3694        ))
3695    }
3696
3697    /// Compute a fused activation in one pass, or decline with `Ok(None)`.
3698    ///
3699    /// This is an optional fast path. A backend that returns `Ok(None)` keeps
3700    /// the shared composite formulation of the same function, so declining is
3701    /// always correct. A backend that returns `Ok(Some(output))` must produce
3702    /// the value
3703    /// defined by [`ActivationOp`] for an `F32`/`F64` input in one elementwise
3704    /// traversal.
3705    ///
3706    /// # Examples
3707    ///
3708    /// ```
3709    /// use tenferro_tensor::{ActivationOp, Engine, TensorRead};
3710    ///
3711    /// fn fused(session: &mut dyn Engine, x: TensorRead<'_>)
3712    ///     -> tenferro_tensor::Result<Option<tenferro_tensor::Tensor>>
3713    /// {
3714    ///     session.fused_activation_read(ActivationOp::Sigmoid, x)
3715    /// }
3716    /// ```
3717    ///
3718    /// # Errors
3719    ///
3720    /// Returns a typed validation error for an unsupported dtype, a backend
3721    /// error when storage cannot be read, or a kernel error from the fused
3722    /// traversal. Returning `Ok(None)` declines the fast path.
3723    fn fused_activation_read(
3724        &mut self,
3725        op: ActivationOp,
3726        input: TensorRead<'_>,
3727    ) -> crate::Result<Option<Tensor>> {
3728        let _ = (op, input);
3729        Ok(None)
3730    }
3731
3732    /// Fused real-float softmax along `axis`.
3733    ///
3734    /// This is an optional fast path for the shared runtime composite
3735    /// formulation. A backend that returns `Ok(Some(output))` must produce the
3736    /// same value as the composite: a max-subtracted softmax over `axis`, with
3737    /// an all-`-inf` slice yielding zeros and NaN propagating.
3738    ///
3739    /// # Examples
3740    ///
3741    /// ```
3742    /// use tenferro_tensor::{Engine, TensorRead};
3743    ///
3744    /// fn softmax(session: &mut dyn Engine, x: TensorRead<'_>, axis: usize)
3745    ///     -> tenferro_tensor::Result<Option<tenferro_tensor::Tensor>>
3746    /// {
3747    ///     session.softmax_read(x, axis)
3748    /// }
3749    /// ```
3750    ///
3751    /// # Errors
3752    ///
3753    /// Returns a typed validation error for an invalid axis or unsupported
3754    /// dtype, a backend error when storage cannot be read, or a kernel error.
3755    /// Returning `Ok(None)` declines the fast path.
3756    fn softmax_read(
3757        &mut self,
3758        input: TensorRead<'_>,
3759        axis: usize,
3760    ) -> crate::Result<Option<Tensor>> {
3761        let _ = (input, axis);
3762        Ok(None)
3763    }
3764
3765    /// Apply `y <- alpha * x + beta * y` in one pass into caller-owned storage.
3766    ///
3767    /// Scalars must have the exact tensor dtype; real coefficients for complex
3768    /// vectors are represented as complex values with zero imaginary part. The
3769    /// destination must be write-injective and disjoint from `x`; any x/y
3770    /// storage overlap is rejected before mutation. A strided or offset
3771    /// destination view is accepted, so a caller can accumulate into a region
3772    /// of a larger allocation.
3773    ///
3774    /// # Examples
3775    ///
3776    /// ```rust
3777    /// use tenferro_tensor::{Engine, ContractionScalar, TensorRead, TensorWrite};
3778    ///
3779    /// fn axpby(session: &mut dyn Engine, x: TensorRead<'_>, y: TensorWrite<'_>)
3780    ///     -> tenferro_tensor::Result<()>
3781    /// {
3782    ///     session.axpby_read_into_accum(
3783    ///         ContractionScalar::F64(1.0), x, ContractionScalar::F64(0.0), y,
3784    ///     )
3785    /// }
3786    /// ```
3787    ///
3788    /// # Errors
3789    ///
3790    /// Returns [`Error::Unsupported`] when the backend does not override this
3791    /// capability or the dtype is unsupported; [`Error::Validation`] with
3792    /// `DTypeMismatch`, `ShapeMismatch`, or `InvalidArgument` for scalar/dtype,
3793    /// shape, placement, injectivity, or overlap failures; or
3794    /// [`Error::RuntimeState`] for inaccessible backend storage. Invalid
3795    /// requests leave the destination unchanged.
3796    fn axpby_read_into_accum(
3797        &mut self,
3798        _alpha: ContractionScalar,
3799        _x: TensorRead<'_>,
3800        _beta: ContractionScalar,
3801        _y: TensorWrite<'_>,
3802    ) -> crate::Result<()> {
3803        Err(Error::unsupported(
3804            "Engine::axpby_read_into_accum",
3805            "backend session does not implement axpby_read_into_accum",
3806        ))
3807    }
3808
3809    /// Return this engine's backend-leaf native capability, if it has one.
3810    ///
3811    /// The standard engines return a token that their own safe visitors
3812    /// (`with_cpu_engine`, `with_cuda_engine`) recover. The default is `None`:
3813    /// a custom engine has no native services unless it forwards the token of a
3814    /// standard engine it owns. A wrapper that overrides operation dispatch
3815    /// (for example a custom GEMM) should keep the default, because an
3816    /// operation family that finds a native token may call the delegate's
3817    /// native services directly and so bypass the wrapper's override.
3818    ///
3819    /// # Examples
3820    ///
3821    /// ```rust
3822    /// use tenferro_tensor::Engine;
3823    ///
3824    /// fn native_services_available(session: &mut dyn Engine) -> bool {
3825    ///     session.native_engine().is_some()
3826    /// }
3827    /// ```
3828    fn native_engine(&mut self) -> Option<crate::NativeSessionRef<'_>> {
3829        None
3830    }
3831}
3832
3833/// A read handed to an owned-input method by a materializing `*_read` default.
3834///
3835/// An owned tensor is already compact, so it is borrowed as is; only a view is
3836/// staged.
3837enum StagedRead<'a> {
3838    Borrowed(&'a Tensor),
3839    Staged(Box<Tensor>),
3840}
3841
3842impl std::ops::Deref for StagedRead<'_> {
3843    type Target = Tensor;
3844
3845    fn deref(&self) -> &Tensor {
3846        match self {
3847            Self::Borrowed(tensor) => tensor,
3848            Self::Staged(tensor) => tensor,
3849        }
3850    }
3851}
3852
3853/// Stage `read` for an owned-input method, copying only when it is a view.
3854fn stage_view<'a, S: TensorStructural + ?Sized>(
3855    session: &mut S,
3856    read: TensorRead<'a>,
3857) -> crate::Result<StagedRead<'a>> {
3858    match read {
3859        TensorRead::Tensor(tensor) => Ok(StagedRead::Borrowed(tensor)),
3860        view => Ok(StagedRead::Staged(Box::new(
3861            session.to_contiguous_read(view)?,
3862        ))),
3863    }
3864}