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