Skip to main content

runmat_accelerate_api/
lib.rs

1use anyhow::anyhow;
2use once_cell::sync::{Lazy, OnceCell};
3use serde::{Deserialize, Serialize};
4#[cfg(not(target_arch = "wasm32"))]
5use std::cell::Cell;
6use std::collections::{HashMap, HashSet};
7use std::future::Future;
8use std::pin::Pin;
9use std::sync::atomic::{AtomicU32, Ordering};
10#[cfg(feature = "wgpu")]
11use std::sync::Arc;
12#[cfg(target_arch = "wasm32")]
13use std::sync::Mutex;
14use std::sync::RwLock;
15
16type ResidencyMarkFn = fn(&GpuTensorHandle);
17type ResidencyClearFn = fn(&GpuTensorHandle);
18type SequenceThresholdFn = fn() -> Option<usize>;
19type WorkgroupSizeHintFn = fn() -> Option<u32>;
20
21static RESIDENCY_MARK: OnceCell<ResidencyMarkFn> = OnceCell::new();
22static RESIDENCY_CLEAR: OnceCell<ResidencyClearFn> = OnceCell::new();
23static SEQUENCE_THRESHOLD_PROVIDER: OnceCell<SequenceThresholdFn> = OnceCell::new();
24static WORKGROUP_SIZE_HINT_PROVIDER: OnceCell<WorkgroupSizeHintFn> = OnceCell::new();
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27struct HandleMetadataKey {
28    device_id: u32,
29    buffer_id: u64,
30}
31
32fn handle_metadata_key(handle: &GpuTensorHandle) -> HandleMetadataKey {
33    HandleMetadataKey {
34        device_id: handle.device_id,
35        buffer_id: handle.buffer_id,
36    }
37}
38
39static LOGICAL_HANDLES: Lazy<RwLock<HashSet<HandleMetadataKey>>> =
40    Lazy::new(|| RwLock::new(HashSet::new()));
41static LOGICAL_HANDLE_HITS: Lazy<RwLock<HashMap<HandleMetadataKey, u64>>> =
42    Lazy::new(|| RwLock::new(HashMap::new()));
43static TRANSPOSED_HANDLES: Lazy<RwLock<HashMap<HandleMetadataKey, TransposeInfo>>> =
44    Lazy::new(|| RwLock::new(HashMap::new()));
45
46static HANDLE_PRECISIONS: Lazy<RwLock<HashMap<HandleMetadataKey, ProviderPrecision>>> =
47    Lazy::new(|| RwLock::new(HashMap::new()));
48static HANDLE_CLASS_NAMES: Lazy<RwLock<HashMap<HandleMetadataKey, String>>> =
49    Lazy::new(|| RwLock::new(HashMap::new()));
50static HANDLE_STORAGES: Lazy<RwLock<HashMap<HandleMetadataKey, GpuTensorStorage>>> =
51    Lazy::new(|| RwLock::new(HashMap::new()));
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub struct TransposeInfo {
55    pub base_rows: usize,
56    pub base_cols: usize,
57}
58
59/// Register a callback used to mark residency tracking when GPU tensors are
60/// created or returned by device-side execution paths.
61pub fn register_residency_mark(handler: ResidencyMarkFn) {
62    let _ = RESIDENCY_MARK.set(handler);
63}
64
65/// Mark residency metadata for the provided GPU tensor handle, if a backend
66/// has registered a handler via [`register_residency_mark`].
67pub fn mark_residency(handle: &GpuTensorHandle) {
68    if let Some(handler) = RESIDENCY_MARK.get() {
69        handler(handle);
70    }
71}
72
73/// Register a callback used to clear residency tracking when GPU tensors are
74/// gathered back to the host. Backends that maintain residency metadata should
75/// install this hook during initialization.
76pub fn register_residency_clear(handler: ResidencyClearFn) {
77    let _ = RESIDENCY_CLEAR.set(handler);
78}
79
80/// Clear residency metadata for the provided GPU tensor handle, if a backend
81/// has registered a handler via [`register_residency_clear`].
82pub fn clear_residency(handle: &GpuTensorHandle) {
83    if let Some(handler) = RESIDENCY_CLEAR.get() {
84        handler(handle);
85    }
86}
87
88/// Register a callback that exposes the current sequence length threshold
89/// derived from the auto-offload planner. Array constructors can use this hint
90/// to decide when to prefer GPU residency automatically.
91pub fn register_sequence_threshold_provider(provider: SequenceThresholdFn) {
92    let _ = SEQUENCE_THRESHOLD_PROVIDER.set(provider);
93}
94
95/// Query the currently registered sequence threshold hint, if any.
96pub fn sequence_threshold_hint() -> Option<usize> {
97    SEQUENCE_THRESHOLD_PROVIDER
98        .get()
99        .and_then(|provider| provider())
100}
101
102/// Register a callback that reports the calibrated workgroup size selected by
103/// the active acceleration provider (if any). Plotting kernels can reuse this
104/// hint to match backend tuning.
105pub fn register_workgroup_size_hint_provider(provider: WorkgroupSizeHintFn) {
106    let _ = WORKGROUP_SIZE_HINT_PROVIDER.set(provider);
107}
108
109/// Query the current workgroup size hint exposed by the provider.
110pub fn workgroup_size_hint() -> Option<u32> {
111    WORKGROUP_SIZE_HINT_PROVIDER
112        .get()
113        .and_then(|provider| provider())
114}
115
116/// Export a shared acceleration context (e.g., the active WGPU device) when the
117/// current provider exposes one.
118pub fn export_context(kind: AccelContextKind) -> Option<AccelContextHandle> {
119    provider().and_then(|p| p.export_context(kind))
120}
121
122/// Request a provider-owned WGPU buffer for zero-copy consumers. Returns `None`
123/// when the active provider does not expose buffers or does not support the
124/// supplied handle.
125#[cfg(feature = "wgpu")]
126pub fn export_wgpu_buffer(handle: &GpuTensorHandle) -> Option<WgpuBufferRef> {
127    provider_for_handle(handle).and_then(|p| p.export_wgpu_buffer(handle))
128}
129
130/// Record the precision associated with a GPU tensor handle so host operations can
131/// reconstruct the original dtype when gathering back to the CPU.
132pub fn set_handle_precision(handle: &GpuTensorHandle, precision: ProviderPrecision) {
133    if let Ok(mut guard) = HANDLE_PRECISIONS.write() {
134        guard.insert(handle_metadata_key(handle), precision);
135    }
136}
137
138/// Look up the recorded precision for a GPU tensor handle, if any.
139pub fn handle_precision(handle: &GpuTensorHandle) -> Option<ProviderPrecision> {
140    HANDLE_PRECISIONS
141        .read()
142        .ok()
143        .and_then(|guard| guard.get(&handle_metadata_key(handle)).copied())
144}
145
146/// Clear any recorded precision metadata for a GPU tensor handle.
147pub fn clear_handle_precision(handle: &GpuTensorHandle) {
148    if let Ok(mut guard) = HANDLE_PRECISIONS.write() {
149        guard.remove(&handle_metadata_key(handle));
150    }
151}
152
153/// Record the MATLAB underlying class associated with a GPU tensor handle.
154///
155/// Precision alone cannot represent integer gpuArray classes, so runtime
156/// introspection and validation use this metadata when the upload path knows
157/// the exact requested or inferred class.
158pub fn set_handle_class_name(handle: &GpuTensorHandle, class_name: impl Into<String>) {
159    if let Ok(mut guard) = HANDLE_CLASS_NAMES.write() {
160        guard.insert(handle_metadata_key(handle), class_name.into());
161    }
162}
163
164/// Look up the recorded MATLAB underlying class for a GPU tensor handle.
165pub fn handle_class_name(handle: &GpuTensorHandle) -> Option<String> {
166    HANDLE_CLASS_NAMES
167        .read()
168        .ok()
169        .and_then(|guard| guard.get(&handle_metadata_key(handle)).cloned())
170}
171
172/// Clear any recorded MATLAB underlying class metadata for a GPU tensor handle.
173pub fn clear_handle_class_name(handle: &GpuTensorHandle) {
174    if let Ok(mut guard) = HANDLE_CLASS_NAMES.write() {
175        guard.remove(&handle_metadata_key(handle));
176    }
177}
178
179/// Annotate a GPU tensor handle as logically-typed (`logical` in MATLAB terms)
180/// or clear the logical flag when `logical` is `false`.
181pub fn set_handle_logical(handle: &GpuTensorHandle, logical: bool) {
182    let key = handle_metadata_key(handle);
183    if let Ok(mut guard) = LOGICAL_HANDLES.write() {
184        if logical {
185            guard.insert(key);
186            if let Ok(mut hits) = LOGICAL_HANDLE_HITS.write() {
187                *hits.entry(key).or_insert(0) += 1;
188            }
189        } else {
190            guard.remove(&key);
191            if let Ok(mut hits) = LOGICAL_HANDLE_HITS.write() {
192                hits.remove(&key);
193            }
194        }
195    }
196}
197
198/// Convenience helper for clearing logical annotations explicitly.
199pub fn clear_handle_logical(handle: &GpuTensorHandle) {
200    set_handle_logical(handle, false);
201}
202
203/// Returns true when the supplied handle has been marked as logical.
204pub fn handle_is_logical(handle: &GpuTensorHandle) -> bool {
205    LOGICAL_HANDLES
206        .read()
207        .map(|guard| guard.contains(&handle_metadata_key(handle)))
208        .unwrap_or(false)
209}
210
211pub fn handle_logical_hits(handle: &GpuTensorHandle) -> Option<u64> {
212    LOGICAL_HANDLE_HITS
213        .read()
214        .ok()
215        .and_then(|guard| guard.get(&handle_metadata_key(handle)).copied())
216}
217
218pub fn record_handle_transpose(handle: &GpuTensorHandle, base_rows: usize, base_cols: usize) {
219    if let Ok(mut guard) = TRANSPOSED_HANDLES.write() {
220        guard.insert(
221            handle_metadata_key(handle),
222            TransposeInfo {
223                base_rows,
224                base_cols,
225            },
226        );
227    }
228}
229
230pub fn clear_handle_transpose(handle: &GpuTensorHandle) {
231    if let Ok(mut guard) = TRANSPOSED_HANDLES.write() {
232        guard.remove(&handle_metadata_key(handle));
233    }
234}
235
236pub fn handle_transpose_info(handle: &GpuTensorHandle) -> Option<TransposeInfo> {
237    TRANSPOSED_HANDLES
238        .read()
239        .ok()
240        .and_then(|guard| guard.get(&handle_metadata_key(handle)).copied())
241}
242
243pub fn handle_is_transposed(handle: &GpuTensorHandle) -> bool {
244    handle_transpose_info(handle).is_some()
245}
246
247#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
248pub enum GpuTensorStorage {
249    Real,
250    ComplexInterleaved,
251}
252
253impl Default for GpuTensorStorage {
254    fn default() -> Self {
255        Self::Real
256    }
257}
258
259#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
260pub struct GpuTensorHandle {
261    pub shape: Vec<usize>,
262    pub device_id: u32,
263    pub buffer_id: u64,
264}
265
266#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
267pub enum ProviderSpectralRange {
268    Onesided,
269    Twosided,
270    Centered,
271}
272
273#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
274pub enum ProviderSpectralFrameMode {
275    Sliding {
276        hop: usize,
277    },
278    ColumnSliding {
279        hop: usize,
280        input_rows: usize,
281        frames_per_column: usize,
282    },
283    FoldedColumns {
284        input_rows: usize,
285    },
286}
287
288#[derive(Clone, Debug)]
289pub struct ProviderSpectralRequest<'a> {
290    pub input: &'a GpuTensorHandle,
291    pub input_len: usize,
292    pub input_complex: bool,
293    pub window: &'a [f64],
294    pub nfft: usize,
295    pub frame_count: usize,
296    pub frame_mode: ProviderSpectralFrameMode,
297    pub range: ProviderSpectralRange,
298    pub denominator: f64,
299}
300
301#[derive(Clone, Debug)]
302pub struct ProviderSpectralResult {
303    pub s: GpuTensorHandle,
304    pub ps: GpuTensorHandle,
305    pub rows: usize,
306    pub cols: usize,
307}
308
309#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
310pub enum ProviderEnvelopeMethod {
311    Analytic,
312    AnalyticFir { filter_len: usize },
313    Rms { window_len: usize },
314}
315
316#[derive(Clone, Debug)]
317pub struct ProviderEnvelopeRequest<'a> {
318    pub input: &'a GpuTensorHandle,
319    pub channel_len: usize,
320    pub channel_count: usize,
321    pub output_shape: &'a [usize],
322    pub method: ProviderEnvelopeMethod,
323}
324
325#[derive(Clone, Debug)]
326pub struct ProviderEnvelopeResult {
327    pub upper: GpuTensorHandle,
328    pub lower: GpuTensorHandle,
329}
330
331#[derive(Clone, Debug)]
332pub struct ProviderHilbertRequest<'a> {
333    pub input: &'a GpuTensorHandle,
334    /// Optional FFT length along `dim`.
335    pub length: Option<usize>,
336    /// Zero-based transform dimension.
337    pub dim: usize,
338}
339
340#[derive(Clone, Debug)]
341pub struct ProviderModulationRequest<'a> {
342    pub input: &'a GpuTensorHandle,
343    /// `(real, imag)` pairs interleaved by symbol index.
344    pub constellation: &'a [f64],
345}
346
347#[derive(Clone, Debug)]
348pub struct ProviderBitModulationRequest<'a> {
349    pub input: &'a GpuTensorHandle,
350    /// Number of bit rows in the input before grouping.
351    pub input_rows: usize,
352    /// Number of input bits that form one output symbol.
353    pub bits_per_symbol: usize,
354    /// `(real, imag)` pairs interleaved by symbol index.
355    pub constellation: &'a [f64],
356}
357
358pub async fn uniform_spectral_estimate(
359    request: ProviderSpectralRequest<'_>,
360) -> anyhow::Result<ProviderSpectralResult> {
361    validate_uniform_spectral_request(&request)?;
362
363    let provider = provider_for_handle(request.input)
364        .ok_or_else(|| anyhow!("uniform_spectral_estimate: GPU provider unavailable"))?;
365    provider.uniform_spectral_estimate(&request).await
366}
367
368fn validate_uniform_spectral_request(request: &ProviderSpectralRequest<'_>) -> anyhow::Result<()> {
369    let invalid_frame_mode = matches!(
370        request.frame_mode,
371        ProviderSpectralFrameMode::Sliding { hop: 0 }
372            | ProviderSpectralFrameMode::ColumnSliding { hop: 0, .. }
373    );
374    let invalid_input_coverage = match request.frame_mode {
375        ProviderSpectralFrameMode::Sliding { hop } => {
376            let required = request
377                .frame_count
378                .checked_sub(1)
379                .and_then(|frames| frames.checked_mul(hop))
380                .and_then(|offset| offset.checked_add(request.window.len()));
381            required.is_none_or(|required| required > request.input_len)
382        }
383        ProviderSpectralFrameMode::ColumnSliding {
384            hop,
385            input_rows,
386            frames_per_column,
387        } => {
388            if input_rows == 0 || frames_per_column == 0 {
389                true
390            } else {
391                let last_frame = request.frame_count - 1;
392                let source_col = last_frame / frames_per_column;
393                let segment = last_frame % frames_per_column;
394                source_col
395                    .checked_mul(input_rows)
396                    .and_then(|base| {
397                        segment
398                            .checked_mul(hop)
399                            .and_then(|step| base.checked_add(step))
400                    })
401                    .and_then(|offset| offset.checked_add(request.window.len()))
402                    .is_none_or(|required| required > request.input_len)
403            }
404        }
405        ProviderSpectralFrameMode::FoldedColumns { input_rows } => {
406            input_rows == 0
407                || input_rows
408                    .checked_mul(request.frame_count)
409                    .is_none_or(|required| required > request.input_len)
410        }
411    };
412    if request.window.is_empty()
413        || request.nfft == 0
414        || request.frame_count == 0
415        || invalid_frame_mode
416        || invalid_input_coverage
417        || !request.denominator.is_finite()
418        || request.denominator <= 0.0
419    {
420        return Err(anyhow!("uniform_spectral_estimate: invalid request"));
421    }
422
423    Ok(())
424}
425
426pub async fn signal_envelope(
427    request: ProviderEnvelopeRequest<'_>,
428) -> anyhow::Result<ProviderEnvelopeResult> {
429    let expected_len = request
430        .channel_len
431        .checked_mul(request.channel_count)
432        .ok_or_else(|| anyhow!("signal_envelope: invalid request"))?;
433    let output_len = request
434        .output_shape
435        .iter()
436        .try_fold(1usize, |acc, &dim| acc.checked_mul(dim))
437        .ok_or_else(|| anyhow!("signal_envelope: invalid request"))?;
438    let input_len = request
439        .input
440        .shape
441        .iter()
442        .try_fold(1usize, |acc, &dim| acc.checked_mul(dim))
443        .ok_or_else(|| anyhow!("signal_envelope: invalid request"))?;
444    if request.channel_len == 0
445        || request.channel_count == 0
446        || request.output_shape.is_empty()
447        || output_len != expected_len
448        || input_len != expected_len
449        || !provider_envelope_input_shape_matches(
450            &request.input.shape,
451            request.channel_len,
452            request.channel_count,
453        )
454    {
455        return Err(anyhow!("signal_envelope: invalid request"));
456    }
457
458    match request.method {
459        ProviderEnvelopeMethod::AnalyticFir { filter_len }
460        | ProviderEnvelopeMethod::Rms {
461            window_len: filter_len,
462        } if filter_len == 0 => return Err(anyhow!("signal_envelope: invalid request")),
463        _ => {}
464    }
465
466    let provider = provider_for_handle(request.input)
467        .ok_or_else(|| anyhow!("signal_envelope: GPU provider unavailable"))?;
468    provider.signal_envelope(&request).await
469}
470
471fn provider_envelope_input_shape_matches(
472    shape: &[usize],
473    channel_len: usize,
474    channel_count: usize,
475) -> bool {
476    if channel_count == 1 {
477        return match shape {
478            [len] => *len == channel_len,
479            [rows, cols] => {
480                (*rows == channel_len && *cols == 1) || (*rows == 1 && *cols == channel_len)
481            }
482            _ => false,
483        };
484    }
485
486    matches!(shape, [rows, cols] if *rows == channel_len && *cols == channel_count)
487}
488
489pub async fn signal_hilbert(
490    request: ProviderHilbertRequest<'_>,
491) -> anyhow::Result<GpuTensorHandle> {
492    if request.length == Some(0) {
493        return Err(anyhow!("signal_hilbert: invalid request"));
494    }
495    if request.dim >= request.input.shape.len() {
496        return Err(anyhow!("signal_hilbert: invalid request"));
497    }
498
499    let provider = provider_for_handle(request.input)
500        .ok_or_else(|| anyhow!("signal_hilbert: GPU provider unavailable"))?;
501    provider.signal_hilbert(&request).await
502}
503
504#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
505pub struct ApiDeviceInfo {
506    pub device_id: u32,
507    pub name: String,
508    pub vendor: String,
509    pub memory_bytes: Option<u64>,
510    pub backend: Option<String>,
511}
512
513#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
514pub struct ReduceDimResult {
515    pub values: GpuTensorHandle,
516    pub indices: GpuTensorHandle,
517}
518
519#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
520pub struct ProviderCumminResult {
521    pub values: GpuTensorHandle,
522    pub indices: GpuTensorHandle,
523}
524
525/// Result payload returned by provider-side `cummax` scans.
526///
527/// Alias of [`ProviderCumminResult`] because both operations return the same pair of tensors
528/// (running values and MATLAB-compatible indices).
529pub type ProviderCummaxResult = ProviderCumminResult;
530
531/// Names a shared acceleration context that callers may request (e.g. plotting).
532#[derive(Debug, Clone, Copy, PartialEq, Eq)]
533pub enum AccelContextKind {
534    Plotting,
535}
536
537/// Handle returned by [`export_context`] that describes a shared GPU context.
538#[derive(Clone)]
539pub enum AccelContextHandle {
540    #[cfg(feature = "wgpu")]
541    Wgpu(WgpuContextHandle),
542}
543
544impl AccelContextHandle {
545    /// Returns the underlying WGPU context when available.
546    #[cfg(feature = "wgpu")]
547    pub fn as_wgpu(&self) -> Option<&WgpuContextHandle> {
548        match self {
549            AccelContextHandle::Wgpu(ctx) => Some(ctx),
550        }
551    }
552}
553
554/// Shared WGPU device/queue pair exported by the acceleration provider.
555#[cfg(feature = "wgpu")]
556#[derive(Clone)]
557pub struct WgpuContextHandle {
558    pub instance: Arc<wgpu::Instance>,
559    pub device: Arc<wgpu::Device>,
560    pub queue: Arc<wgpu::Queue>,
561    pub adapter: Arc<wgpu::Adapter>,
562    pub adapter_info: wgpu::AdapterInfo,
563    pub limits: wgpu::Limits,
564    pub features: wgpu::Features,
565}
566
567/// Borrowed reference to a provider-owned WGPU buffer corresponding to a `GpuTensorHandle`.
568///
569/// Providers that expose this handle must ensure the buffer was created with
570/// `wgpu::BufferUsages::COPY_SRC`, so zero-copy consumers can export or inspect
571/// tensor data without depending on provider-specific readback APIs.
572#[cfg(feature = "wgpu")]
573#[derive(Clone)]
574pub struct WgpuBufferRef {
575    pub buffer: Arc<wgpu::Buffer>,
576    pub len: usize,
577    pub shape: Vec<usize>,
578    pub element_size: usize,
579    pub precision: ProviderPrecision,
580}
581
582pub fn set_handle_storage(handle: &GpuTensorHandle, storage: GpuTensorStorage) {
583    if let Ok(mut guard) = HANDLE_STORAGES.write() {
584        guard.insert(handle_metadata_key(handle), storage);
585    }
586}
587
588pub fn handle_storage(handle: &GpuTensorHandle) -> GpuTensorStorage {
589    HANDLE_STORAGES
590        .read()
591        .ok()
592        .and_then(|guard| guard.get(&handle_metadata_key(handle)).cloned())
593        .unwrap_or(GpuTensorStorage::Real)
594}
595
596pub fn clear_handle_storage(handle: &GpuTensorHandle) {
597    if let Ok(mut guard) = HANDLE_STORAGES.write() {
598        guard.remove(&handle_metadata_key(handle));
599    }
600}
601
602#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
603pub enum PagefunOp {
604    Mtimes,
605}
606
607#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
608pub struct PagefunRequest {
609    pub op: PagefunOp,
610    pub inputs: Vec<GpuTensorHandle>,
611    pub output_shape: Vec<usize>,
612    pub page_dims: Vec<usize>,
613    pub input_page_dims: Vec<Vec<usize>>,
614}
615
616#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
617pub enum FindDirection {
618    First,
619    Last,
620}
621
622#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
623pub struct ProviderFindResult {
624    pub linear: GpuTensorHandle,
625    pub rows: GpuTensorHandle,
626    pub cols: GpuTensorHandle,
627    pub values: Option<GpuTensorHandle>,
628}
629
630#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
631pub struct ProviderBandwidth {
632    pub lower: u32,
633    pub upper: u32,
634}
635
636#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
637pub enum ProviderSymmetryKind {
638    Symmetric,
639    Skew,
640}
641
642#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
643pub enum ProviderHermitianKind {
644    Hermitian,
645    Skew,
646}
647
648#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
649pub struct ProviderLuResult {
650    pub combined: GpuTensorHandle,
651    pub lower: GpuTensorHandle,
652    pub upper: GpuTensorHandle,
653    pub perm_matrix: GpuTensorHandle,
654    pub perm_vector: GpuTensorHandle,
655}
656
657#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
658pub struct ProviderCholResult {
659    pub factor: GpuTensorHandle,
660    /// MATLAB-compatible failure index (0 indicates success).
661    pub info: u32,
662}
663
664#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
665pub struct ProviderQrResult {
666    pub q: GpuTensorHandle,
667    pub r: GpuTensorHandle,
668    pub perm_matrix: GpuTensorHandle,
669    pub perm_vector: GpuTensorHandle,
670}
671
672#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
673pub struct ProviderQrPowerIterResult {
674    pub q: GpuTensorHandle,
675    pub r: GpuTensorHandle,
676    pub perm_matrix: GpuTensorHandle,
677    pub perm_vector: GpuTensorHandle,
678}
679
680#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
681pub struct ProviderLinsolveOptions {
682    pub lower: bool,
683    pub upper: bool,
684    pub rectangular: bool,
685    pub transposed: bool,
686    pub conjugate: bool,
687    pub symmetric: bool,
688    pub posdef: bool,
689    pub need_rcond: bool,
690    pub rcond: Option<f64>,
691}
692
693#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
694pub struct ProviderLinsolveResult {
695    pub solution: GpuTensorHandle,
696    pub reciprocal_condition: f64,
697}
698
699#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
700pub struct ProviderPinvOptions {
701    pub tolerance: Option<f64>,
702}
703
704#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
705pub struct ProviderPolyvalMu {
706    pub mean: f64,
707    pub scale: f64,
708}
709
710#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
711pub struct ProviderPolyvalOptions {
712    pub mu: Option<ProviderPolyvalMu>,
713}
714
715#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
716pub struct ProviderInvOptions {}
717
718#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
719pub struct ProviderPolyfitResult {
720    pub coefficients: Vec<f64>,
721    pub r_matrix: Vec<f64>,
722    pub normr: f64,
723    pub df: f64,
724    pub mu: [f64; 2],
725}
726
727/// Numerator/denominator payload returned by provider-backed `polyder` quotient rule.
728#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
729pub struct ProviderPolyderQuotient {
730    pub numerator: GpuTensorHandle,
731    pub denominator: GpuTensorHandle,
732}
733
734/// Supported norm specifications for the `cond` builtin.
735#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
736pub enum ProviderCondNorm {
737    Two,
738    One,
739    Inf,
740    Fro,
741}
742
743/// Supported norm orders for the `norm` builtin.
744#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
745pub enum ProviderNormOrder {
746    Two,
747    One,
748    Inf,
749    NegInf,
750    Zero,
751    Fro,
752    Nuc,
753    P(f64),
754}
755
756#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
757pub enum ProviderInterp1Method {
758    Linear,
759    Nearest,
760}
761
762#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
763pub enum ProviderInterp1Extrapolation {
764    Nan,
765    Extrapolate,
766    Value,
767}
768
769#[derive(Debug, Clone)]
770pub struct ProviderInterp1Request<'a> {
771    /// Strictly increasing, finite sample coordinates validated by the runtime
772    /// before dispatch. Providers may assume this monotonic domain invariant.
773    pub x: &'a GpuTensorHandle,
774    pub y: &'a GpuTensorHandle,
775    pub xq: &'a GpuTensorHandle,
776    pub sample_len: usize,
777    pub series_count: usize,
778    pub query_len: usize,
779    pub output_shape: &'a [usize],
780    pub method: ProviderInterp1Method,
781    pub extrapolation: ProviderInterp1Extrapolation,
782    pub extrapolation_value: f64,
783}
784
785#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
786pub struct ProviderEigResult {
787    pub eigenvalues: GpuTensorHandle,
788    pub diagonal: GpuTensorHandle,
789    pub right: GpuTensorHandle,
790    pub left: Option<GpuTensorHandle>,
791}
792
793#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
794pub enum ProviderQrPivot {
795    Matrix,
796    Vector,
797}
798
799#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
800pub struct ProviderQrOptions {
801    pub economy: bool,
802    pub pivot: ProviderQrPivot,
803}
804
805impl Default for ProviderQrOptions {
806    fn default() -> Self {
807        Self {
808            economy: false,
809            pivot: ProviderQrPivot::Matrix,
810        }
811    }
812}
813
814#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
815pub enum ProviderPrecision {
816    F32,
817    F64,
818}
819
820/// Declares how provider-owned GPU handles may cross async spawn boundaries.
821///
822/// This is a runtime/provider policy surface (not a semantic type fact) used by
823/// VM/runtime spawn handling to prevent unsynchronized device-handle races.
824#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
825pub enum SpawnHandleConcurrency {
826    /// Provider supports immutable sharing of handle-backed values across spawned tasks.
827    ImmutableShare,
828    /// Provider supports copy-on-write semantics when spawned and parent tasks diverge.
829    CopyOnWrite,
830    /// Provider supports synchronized mutation for shared handles.
831    SynchronizedMutation,
832    /// Provider rejects spawned sharing of raw handles.
833    Reject,
834}
835
836impl SpawnHandleConcurrency {
837    pub fn as_str(self) -> &'static str {
838        match self {
839            SpawnHandleConcurrency::ImmutableShare => "immutable_share",
840            SpawnHandleConcurrency::CopyOnWrite => "copy_on_write",
841            SpawnHandleConcurrency::SynchronizedMutation => "synchronized_mutation",
842            SpawnHandleConcurrency::Reject => "reject",
843        }
844    }
845}
846
847#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
848pub enum ReductionTwoPassMode {
849    Auto,
850    ForceOn,
851    ForceOff,
852}
853
854impl ReductionTwoPassMode {
855    pub fn as_str(self) -> &'static str {
856        match self {
857            ReductionTwoPassMode::Auto => "auto",
858            ReductionTwoPassMode::ForceOn => "force_on",
859            ReductionTwoPassMode::ForceOff => "force_off",
860        }
861    }
862}
863
864#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
865pub enum ReductionFlavor {
866    Sum,
867    Mean,
868    CustomScale(f64),
869}
870
871impl ReductionFlavor {
872    pub fn is_mean(self) -> bool {
873        matches!(self, ReductionFlavor::Mean)
874    }
875
876    pub fn scale(self, reduce_len: usize) -> f64 {
877        match self {
878            ReductionFlavor::Sum => 1.0,
879            ReductionFlavor::Mean => {
880                if reduce_len == 0 {
881                    1.0
882                } else {
883                    1.0 / reduce_len as f64
884                }
885            }
886            ReductionFlavor::CustomScale(scale) => scale,
887        }
888    }
889}
890
891/// Normalisation mode for correlation coefficients.
892#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
893pub enum CorrcoefNormalization {
894    Unbiased,
895    Biased,
896}
897
898/// Row-selection strategy for correlation coefficients.
899#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
900pub enum CorrcoefRows {
901    All,
902    Complete,
903    Pairwise,
904}
905
906/// Options controlling provider-backed correlation coefficient computation.
907#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
908pub struct CorrcoefOptions {
909    pub normalization: CorrcoefNormalization,
910    pub rows: CorrcoefRows,
911}
912
913impl Default for CorrcoefOptions {
914    fn default() -> Self {
915        Self {
916            normalization: CorrcoefNormalization::Unbiased,
917            rows: CorrcoefRows::All,
918        }
919    }
920}
921
922/// Normalisation mode used by covariance computations.
923#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
924pub enum CovNormalization {
925    Unbiased,
926    Biased,
927}
928
929/// Row handling strategy for covariance computations.
930#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
931pub enum CovRows {
932    All,
933    OmitRows,
934    PartialRows,
935}
936
937/// Options controlling provider-backed covariance computation.
938#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
939pub struct CovarianceOptions {
940    pub normalization: CovNormalization,
941    pub rows: CovRows,
942    pub has_weight_vector: bool,
943}
944
945impl Default for CovarianceOptions {
946    fn default() -> Self {
947        Self {
948            normalization: CovNormalization::Unbiased,
949            rows: CovRows::All,
950            has_weight_vector: false,
951        }
952    }
953}
954
955/// Normalization strategy used by provider-backed standard deviation reductions.
956#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
957pub enum ProviderStdNormalization {
958    Sample,
959    Population,
960}
961
962/// NaN handling mode for provider-backed reductions.
963#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
964pub enum ProviderNanMode {
965    Include,
966    Omit,
967}
968
969/// Moving-window reduction operation executed by acceleration providers.
970#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
971pub enum ProviderMovingWindowOp {
972    Sum,
973    Mean,
974    Prod,
975    Min,
976    Max,
977    Median,
978    Std,
979    Var,
980}
981
982/// Endpoint handling for provider-backed moving-window reductions.
983#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
984pub enum ProviderMovingWindowEndpoints {
985    Shrink,
986    Discard,
987    Fill(f64),
988}
989
990/// Request for provider-backed count-window moving reductions.
991#[derive(Debug, Clone, Copy)]
992pub struct ProviderMovingWindowRequest<'a> {
993    pub input: &'a GpuTensorHandle,
994    pub output_shape: &'a [usize],
995    /// Zero-based dimension along which the moving window is applied.
996    pub dim: usize,
997    pub before: usize,
998    pub after: usize,
999    pub op: ProviderMovingWindowOp,
1000    pub endpoints: ProviderMovingWindowEndpoints,
1001    pub nan_mode: ProviderNanMode,
1002    pub normalization: ProviderStdNormalization,
1003}
1004
1005/// Dimension selection for provider-backed `mode` reductions.
1006#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1007pub enum ProviderModeAxes {
1008    /// First non-singleton dimension selected by MATLAB default rules.
1009    Default,
1010    /// Zero-based dimension supplied by `mode(A, dim)`.
1011    Dim(usize),
1012    /// Collapse every element, as in `mode(A, "all")`.
1013    All,
1014}
1015
1016/// Request for provider-backed modal reductions.
1017#[derive(Debug, Clone, Copy)]
1018pub struct ProviderModeRequest<'a> {
1019    pub input: &'a GpuTensorHandle,
1020    pub axes: ProviderModeAxes,
1021    pub want_frequency: bool,
1022    pub want_ties: bool,
1023}
1024
1025/// Sorted tied modal values for each output slice.
1026///
1027/// `values` stores all tied values for all slices in ascending per-slice order.
1028/// `offsets` and `counts` are one entry per output slice and describe the
1029/// slice-local range inside `values`. Runtime callers convert this ragged
1030/// representation into MATLAB cell output `C`.
1031#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1032pub struct ProviderModeTiedSets {
1033    pub values: HostTensorOwned,
1034    pub offsets: Vec<usize>,
1035    pub counts: Vec<usize>,
1036}
1037
1038/// Result of a provider-backed `mode` reduction.
1039///
1040/// `values` is the MATLAB `M` output and stays resident. `frequencies`, when
1041/// requested, is the MATLAB `F` output and stays resident. `ties`, when
1042/// requested, carries the MATLAB `C` tied-set data in a host ragged form because
1043/// the public runtime value is a cell array.
1044#[derive(Debug, Clone, PartialEq)]
1045pub struct ProviderModeResult {
1046    pub values: GpuTensorHandle,
1047    pub frequencies: Option<GpuTensorHandle>,
1048    pub ties: Option<ProviderModeTiedSets>,
1049}
1050
1051/// Direction used when computing prefix sums on the device.
1052#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1053pub enum ProviderScanDirection {
1054    Forward,
1055    Reverse,
1056}
1057
1058/// Spacing input for provider-backed trapezoidal integration.
1059#[derive(Debug, Clone, Copy)]
1060pub enum ProviderTrapezoidSpacing<'a> {
1061    Unit,
1062    Scalar(f64),
1063    ScalarHandle(&'a GpuTensorHandle),
1064    Vector(&'a GpuTensorHandle),
1065    Tensor(&'a GpuTensorHandle),
1066}
1067
1068/// Sort direction used by acceleration providers.
1069#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1070pub enum SortOrder {
1071    Ascend,
1072    Descend,
1073}
1074
1075/// Comparison strategy applied during sorting.
1076#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1077pub enum SortComparison {
1078    Auto,
1079    Real,
1080    Abs,
1081}
1082
1083/// Host-resident outputs returned by provider-backed sort operations.
1084#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1085pub struct SortResult {
1086    pub values: HostTensorOwned,
1087    pub indices: HostTensorOwned,
1088}
1089
1090#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1091pub struct SortRowsColumnSpec {
1092    pub index: usize,
1093    pub order: SortOrder,
1094}
1095
1096/// Ordering applied by provider-backed `unique` operations.
1097#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1098pub enum UniqueOrder {
1099    Sorted,
1100    Stable,
1101}
1102
1103/// Occurrence selection for provider-backed `unique` operations.
1104#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1105pub enum UniqueOccurrence {
1106    First,
1107    Last,
1108}
1109
1110/// Options controlling provider-backed `unique` operations.
1111#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1112pub struct UniqueOptions {
1113    pub rows: bool,
1114    pub order: UniqueOrder,
1115    pub occurrence: UniqueOccurrence,
1116}
1117
1118/// Host-resident outputs returned by provider-backed `unique` operations.
1119#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1120pub struct UniqueResult {
1121    pub values: HostTensorOwned,
1122    pub ia: HostTensorOwned,
1123    pub ic: HostTensorOwned,
1124}
1125
1126/// Ordering applied by provider-backed `union` operations.
1127#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1128pub enum UnionOrder {
1129    Sorted,
1130    Stable,
1131}
1132
1133/// Options controlling provider-backed `union` operations.
1134#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1135pub struct UnionOptions {
1136    pub rows: bool,
1137    pub order: UnionOrder,
1138}
1139
1140/// Host-resident outputs returned by provider-backed `union` operations.
1141#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1142pub struct UnionResult {
1143    pub values: HostTensorOwned,
1144    pub ia: HostTensorOwned,
1145    pub ib: HostTensorOwned,
1146}
1147
1148/// Parameterisation of 2-D filters generated by `fspecial`.
1149#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1150pub enum FspecialFilter {
1151    Average {
1152        rows: u32,
1153        cols: u32,
1154    },
1155    Disk {
1156        radius: f64,
1157        size: u32,
1158    },
1159    Gaussian {
1160        rows: u32,
1161        cols: u32,
1162        sigma: f64,
1163    },
1164    Laplacian {
1165        alpha: f64,
1166    },
1167    Log {
1168        rows: u32,
1169        cols: u32,
1170        sigma: f64,
1171    },
1172    Motion {
1173        length: u32,
1174        kernel_size: u32,
1175        angle_degrees: f64,
1176        oversample: u32,
1177    },
1178    Prewitt,
1179    Sobel,
1180    Unsharp {
1181        alpha: f64,
1182    },
1183}
1184
1185/// Request dispatched to acceleration providers for `fspecial` kernels.
1186#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1187pub struct FspecialRequest {
1188    pub filter: FspecialFilter,
1189}
1190
1191/// Padding strategy used by `imfilter`.
1192#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1193pub enum ImfilterPadding {
1194    Constant,
1195    Replicate,
1196    Symmetric,
1197    Circular,
1198}
1199
1200/// Output sizing mode used by `imfilter`.
1201#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1202pub enum ImfilterShape {
1203    Same,
1204    Full,
1205    Valid,
1206}
1207
1208/// Correlation vs convolution behaviour for `imfilter`.
1209#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1210pub enum ImfilterMode {
1211    Correlation,
1212    Convolution,
1213}
1214
1215/// Options supplied to acceleration providers for `imfilter`.
1216#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1217pub struct ImfilterOptions {
1218    pub padding: ImfilterPadding,
1219    pub constant_value: f64,
1220    pub shape: ImfilterShape,
1221    pub mode: ImfilterMode,
1222}
1223
1224impl Default for ImfilterOptions {
1225    fn default() -> Self {
1226        Self {
1227            padding: ImfilterPadding::Constant,
1228            constant_value: 0.0,
1229            shape: ImfilterShape::Same,
1230            mode: ImfilterMode::Correlation,
1231        }
1232    }
1233}
1234
1235/// Ordering applied by provider-backed `setdiff` operations.
1236#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1237pub enum SetdiffOrder {
1238    Sorted,
1239    Stable,
1240}
1241
1242/// Options controlling provider-backed `setdiff` operations.
1243#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1244pub struct SetdiffOptions {
1245    pub rows: bool,
1246    pub order: SetdiffOrder,
1247}
1248
1249/// Host-resident outputs returned by provider-backed `setdiff` operations.
1250#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1251pub struct SetdiffResult {
1252    pub values: HostTensorOwned,
1253    pub ia: HostTensorOwned,
1254}
1255
1256/// Options controlling provider-backed `ismember` operations.
1257#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1258pub struct IsMemberOptions {
1259    pub rows: bool,
1260}
1261
1262/// Host-resident logical output returned by providers.
1263#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1264pub struct HostLogicalOwned {
1265    pub data: Vec<u8>,
1266    pub shape: Vec<usize>,
1267}
1268
1269/// Host-resident outputs returned by provider-backed `ismember` operations.
1270#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1271pub struct IsMemberResult {
1272    pub mask: HostLogicalOwned,
1273    pub loc: HostTensorOwned,
1274}
1275
1276#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1277pub enum ProviderConvMode {
1278    Full,
1279    Same,
1280    Valid,
1281}
1282
1283#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1284pub enum ProviderConvOrientation {
1285    Row,
1286    Column,
1287}
1288
1289#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1290pub struct ProviderConv1dOptions {
1291    pub mode: ProviderConvMode,
1292    pub orientation: ProviderConvOrientation,
1293}
1294
1295#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1296pub struct ProviderIirFilterOptions {
1297    /// Zero-based dimension along which filtering should be applied.
1298    pub dim: usize,
1299    /// Optional initial conditions (state vector) residing on the device.
1300    pub zi: Option<GpuTensorHandle>,
1301    /// Caller has already validated that `a` is the scalar denominator `[1]`.
1302    ///
1303    /// Providers may use this FIR-only path to avoid reading coefficient buffers
1304    /// back to the host for normalization.
1305    pub unit_denominator: bool,
1306}
1307
1308#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1309pub struct ProviderIirFilterResult {
1310    /// Filtered output tensor, matching the input signal shape.
1311    pub output: GpuTensorHandle,
1312    /// Final conditions for the filter state (same shape as the requested `zi` layout).
1313    pub final_state: Option<GpuTensorHandle>,
1314}
1315
1316#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1317pub struct ProviderMoments2 {
1318    pub mean: GpuTensorHandle,
1319    pub ex2: GpuTensorHandle,
1320}
1321
1322#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
1323pub struct ProviderDispatchStats {
1324    /// Number of GPU dispatches recorded for this category.
1325    pub count: u64,
1326    /// Accumulated wall-clock time of dispatches in nanoseconds (host measured).
1327    pub total_wall_time_ns: u64,
1328}
1329
1330#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
1331pub struct ProviderFallbackStat {
1332    pub reason: String,
1333    pub count: u64,
1334}
1335
1336#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1337pub struct ProviderTelemetry {
1338    pub fused_elementwise: ProviderDispatchStats,
1339    pub fused_reduction: ProviderDispatchStats,
1340    pub matmul: ProviderDispatchStats,
1341    pub linsolve: ProviderDispatchStats,
1342    pub mldivide: ProviderDispatchStats,
1343    pub mrdivide: ProviderDispatchStats,
1344    pub upload_bytes: u64,
1345    pub download_bytes: u64,
1346    pub solve_fallbacks: Vec<ProviderFallbackStat>,
1347    pub fusion_cache_hits: u64,
1348    pub fusion_cache_misses: u64,
1349    pub bind_group_cache_hits: u64,
1350    pub bind_group_cache_misses: u64,
1351    /// Optional per-layout bind group cache counters (layout tags and their hit/miss counts)
1352    pub bind_group_cache_by_layout: Option<Vec<BindGroupLayoutTelemetry>>,
1353    /// Recent kernel launch metadata (bounded log; newest last)
1354    pub kernel_launches: Vec<KernelLaunchTelemetry>,
1355}
1356
1357#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1358pub struct BindGroupLayoutTelemetry {
1359    pub tag: String,
1360    pub hits: u64,
1361    pub misses: u64,
1362}
1363
1364#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1365pub struct KernelAttrTelemetry {
1366    pub key: String,
1367    pub value: u64,
1368}
1369
1370#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1371pub struct KernelLaunchTelemetry {
1372    pub kernel: String,
1373    pub precision: Option<String>,
1374    pub shape: Vec<KernelAttrTelemetry>,
1375    pub tuning: Vec<KernelAttrTelemetry>,
1376}
1377
1378pub type AccelProviderFuture<'a, T> = Pin<Box<dyn Future<Output = anyhow::Result<T>> + 'a>>;
1379pub type AccelDownloadFuture<'a> = AccelProviderFuture<'a, crate::HostTensorOwned>;
1380
1381fn unsupported_future<T>(message: &'static str) -> AccelProviderFuture<'static, T> {
1382    Box::pin(async move { Err(anyhow::anyhow!(message)) })
1383}
1384
1385/// Device/provider interface that backends implement and register into the runtime layer
1386pub trait AccelProvider: Send + Sync {
1387    fn upload(&self, host: &crate::HostTensorView) -> anyhow::Result<GpuTensorHandle>;
1388    fn download<'a>(&'a self, h: &'a GpuTensorHandle) -> AccelDownloadFuture<'a>;
1389    fn free(&self, h: &GpuTensorHandle) -> anyhow::Result<()>;
1390    fn device_info(&self) -> String;
1391    fn device_id(&self) -> u32 {
1392        0
1393    }
1394
1395    /// Declares provider policy for sharing `GpuTensorHandle` values across
1396    /// spawned async boundaries.
1397    ///
1398    /// Default is conservative rejection. Providers that can safely support
1399    /// cross-task sharing should override this.
1400    fn spawn_handle_concurrency(&self) -> SpawnHandleConcurrency {
1401        SpawnHandleConcurrency::Reject
1402    }
1403
1404    /// Export a shared GPU context handle, allowing downstream systems (plotting, visualization)
1405    /// to reuse the same device/queue without copying tensor data back to the host.
1406    fn export_context(&self, _kind: AccelContextKind) -> Option<AccelContextHandle> {
1407        None
1408    }
1409
1410    /// Export a provider-owned WGPU buffer for zero-copy integrations.
1411    #[cfg(feature = "wgpu")]
1412    fn export_wgpu_buffer(&self, _handle: &GpuTensorHandle) -> Option<WgpuBufferRef> {
1413        let _ = _handle;
1414        None
1415    }
1416
1417    /// Gather logical elements from `source` at the provided zero-based linear `indices`,
1418    /// materialising a dense tensor with the specified `output_shape`.
1419    ///
1420    /// Indices address MATLAB logical elements, not raw provider storage lanes. Providers that
1421    /// store complex values as interleaved lanes must copy both lanes for each selected element
1422    /// and preserve the source storage kind on the output handle.
1423    fn gather_linear(
1424        &self,
1425        _source: &GpuTensorHandle,
1426        _indices: &[u32],
1427        _output_shape: &[usize],
1428    ) -> anyhow::Result<GpuTensorHandle> {
1429        Err(anyhow::anyhow!("gather_linear not supported by provider"))
1430    }
1431
1432    /// Scatter logical elements from `values` into `target` at the provided zero-based linear
1433    /// `indices`.
1434    ///
1435    /// Indices address MATLAB logical elements, not raw provider storage lanes. Providers must
1436    /// ensure `values` has one logical element per index, copy all lanes for the handle storage
1437    /// kind, and update `target` in place without changing its shape or storage.
1438    fn scatter_linear(
1439        &self,
1440        _target: &GpuTensorHandle,
1441        _indices: &[u32],
1442        _values: &GpuTensorHandle,
1443    ) -> anyhow::Result<()> {
1444        Err(anyhow::anyhow!("scatter_linear not supported by provider"))
1445    }
1446
1447    /// Structured device information (optional to override). Default adapts from `device_info()`.
1448    fn device_info_struct(&self) -> ApiDeviceInfo {
1449        ApiDeviceInfo {
1450            device_id: 0,
1451            name: self.device_info(),
1452            vendor: String::new(),
1453            memory_bytes: None,
1454            backend: None,
1455        }
1456    }
1457
1458    fn precision(&self) -> ProviderPrecision {
1459        ProviderPrecision::F64
1460    }
1461
1462    /// Read a single scalar at linear index from a device tensor, returning it as f64.
1463    fn read_scalar(&self, _h: &GpuTensorHandle, _linear_index: usize) -> anyhow::Result<f64> {
1464        Err(anyhow::anyhow!("read_scalar not supported by provider"))
1465    }
1466
1467    /// Allocate a zero-initialised tensor with the provided shape on the device.
1468    fn zeros(&self, _shape: &[usize]) -> anyhow::Result<GpuTensorHandle> {
1469        Err(anyhow::anyhow!("zeros not supported by provider"))
1470    }
1471
1472    /// Allocate a zero-initialised tensor with the provided shape and storage layout.
1473    ///
1474    /// `shape` is the logical MATLAB shape. Providers must allocate enough raw lanes for the
1475    /// requested storage kind, e.g. two interleaved numeric lanes per logical element for complex
1476    /// tensors.
1477    fn zeros_with_storage(
1478        &self,
1479        shape: &[usize],
1480        storage: GpuTensorStorage,
1481    ) -> anyhow::Result<GpuTensorHandle> {
1482        if storage == GpuTensorStorage::Real {
1483            self.zeros(shape)
1484        } else {
1485            Err(anyhow::anyhow!(
1486                "zeros_with_storage not supported by provider for {storage:?}"
1487            ))
1488        }
1489    }
1490
1491    /// Allocate a one-initialised tensor with the provided shape on the device.
1492    fn ones(&self, _shape: &[usize]) -> anyhow::Result<GpuTensorHandle> {
1493        Err(anyhow::anyhow!("ones not supported by provider"))
1494    }
1495
1496    /// Allocate a zero-initialised tensor matching the prototype tensor.
1497    fn zeros_like(&self, prototype: &GpuTensorHandle) -> anyhow::Result<GpuTensorHandle> {
1498        self.zeros(&prototype.shape)
1499    }
1500
1501    /// Allocate a tensor filled with a constant value on the device.
1502    fn fill(&self, shape: &[usize], value: f64) -> anyhow::Result<GpuTensorHandle> {
1503        if value == 0.0 {
1504            return self.zeros(shape);
1505        }
1506        if let Ok(base) = self.zeros(shape) {
1507            match self.scalar_add(&base, value) {
1508                Ok(out) => {
1509                    let _ = self.free(&base);
1510                    return Ok(out);
1511                }
1512                Err(_) => {
1513                    let _ = self.free(&base);
1514                }
1515            }
1516        }
1517        let len: usize = shape.iter().copied().product();
1518        let data = vec![value; len];
1519        let view = HostTensorView { data: &data, shape };
1520        self.upload(&view)
1521    }
1522
1523    /// Allocate a tensor filled with a constant value, matching a prototype's residency.
1524    fn fill_like(
1525        &self,
1526        prototype: &GpuTensorHandle,
1527        value: f64,
1528    ) -> anyhow::Result<GpuTensorHandle> {
1529        if value == 0.0 {
1530            return self.zeros_like(prototype);
1531        }
1532        if let Ok(base) = self.zeros_like(prototype) {
1533            match self.scalar_add(&base, value) {
1534                Ok(out) => {
1535                    let _ = self.free(&base);
1536                    return Ok(out);
1537                }
1538                Err(_) => {
1539                    let _ = self.free(&base);
1540                }
1541            }
1542        }
1543        self.fill(&prototype.shape, value)
1544    }
1545
1546    /// Allocate a one-initialised tensor matching the prototype tensor.
1547    fn ones_like(&self, prototype: &GpuTensorHandle) -> anyhow::Result<GpuTensorHandle> {
1548        self.ones(&prototype.shape)
1549    }
1550
1551    /// Allocate an identity tensor with ones along the leading diagonal of the first two axes.
1552    fn eye(&self, _shape: &[usize]) -> anyhow::Result<GpuTensorHandle> {
1553        Err(anyhow::anyhow!("eye not supported by provider"))
1554    }
1555
1556    /// Allocate an identity tensor matching the prototype tensor's shape.
1557    fn eye_like(&self, prototype: &GpuTensorHandle) -> anyhow::Result<GpuTensorHandle> {
1558        self.eye(&prototype.shape)
1559    }
1560
1561    /// Construct MATLAB-style coordinate grids from axis vectors.
1562    fn meshgrid(&self, _axes: &[MeshgridAxisView<'_>]) -> anyhow::Result<ProviderMeshgridResult> {
1563        Err(anyhow::anyhow!("meshgrid not supported by provider"))
1564    }
1565
1566    /// Construct MATLAB-style N-D coordinate grids from resident GPU axis vectors.
1567    fn ndgrid(&self, _request: &ProviderNdgridRequest<'_>) -> anyhow::Result<ProviderNdgridResult> {
1568        Err(anyhow::anyhow!("ndgrid not supported by provider"))
1569    }
1570
1571    /// Compute vectorized Black-Scholes European call and put prices on resident GPU inputs.
1572    fn black_scholes_price(
1573        &self,
1574        _request: &ProviderBlackScholesPriceRequest<'_>,
1575    ) -> anyhow::Result<ProviderBlackScholesPriceResult> {
1576        Err(anyhow::anyhow!(
1577            "black_scholes_price not supported by provider"
1578        ))
1579    }
1580
1581    /// Apply the Adam optimizer update to resident parameter and state tensors.
1582    fn adam_update(
1583        &self,
1584        _request: &ProviderAdamUpdateRequest<'_>,
1585    ) -> anyhow::Result<ProviderAdamUpdateResult> {
1586        Err(anyhow::anyhow!("adam_update not supported by provider"))
1587    }
1588
1589    /// Compute per-element cross-entropy loss terms for resident prediction and target tensors.
1590    fn crossentropy_terms(
1591        &self,
1592        _request: &ProviderCrossentropyRequest<'_>,
1593    ) -> anyhow::Result<ProviderCrossentropyResult> {
1594        Err(anyhow::anyhow!(
1595            "crossentropy_terms not supported by provider"
1596        ))
1597    }
1598
1599    /// Construct a diagonal matrix from a vector-like tensor. `offset` matches MATLAB semantics.
1600    fn diag_from_vector(
1601        &self,
1602        _vector: &GpuTensorHandle,
1603        _offset: isize,
1604    ) -> anyhow::Result<GpuTensorHandle> {
1605        Err(anyhow::anyhow!(
1606            "diag_from_vector not supported by provider"
1607        ))
1608    }
1609
1610    /// Construct a diagonal matrix with an explicit shape from a vector-like tensor.
1611    /// `offset` matches MATLAB semantics; values that do not fit inside the requested
1612    /// rectangle are ignored.
1613    fn diag_from_vector_sized(
1614        &self,
1615        _vector: &GpuTensorHandle,
1616        _offset: isize,
1617        _rows: usize,
1618        _cols: usize,
1619    ) -> anyhow::Result<GpuTensorHandle> {
1620        Err(anyhow::anyhow!(
1621            "diag_from_vector_sized not supported by provider"
1622        ))
1623    }
1624
1625    /// Extract a diagonal from a matrix-like tensor. The result is always a column vector.
1626    fn diag_extract(
1627        &self,
1628        _matrix: &GpuTensorHandle,
1629        _offset: isize,
1630    ) -> anyhow::Result<GpuTensorHandle> {
1631        Err(anyhow::anyhow!("diag_extract not supported by provider"))
1632    }
1633
1634    /// Apply a lower-triangular mask to the first two dimensions of a tensor.
1635    fn tril<'a>(
1636        &'a self,
1637        _matrix: &'a GpuTensorHandle,
1638        _offset: isize,
1639    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
1640        Box::pin(async move { Err(anyhow!("tril not supported by provider")) })
1641    }
1642
1643    /// Apply an upper-triangular mask to the first two dimensions of a tensor.
1644    fn triu<'a>(
1645        &'a self,
1646        _matrix: &'a GpuTensorHandle,
1647        _offset: isize,
1648    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
1649        Box::pin(async move { Err(anyhow!("triu not supported by provider")) })
1650    }
1651
1652    /// Evaluate a polynomial expressed by `coefficients` at each element in `points`.
1653    fn polyval(
1654        &self,
1655        _coefficients: &GpuTensorHandle,
1656        _points: &GpuTensorHandle,
1657        _options: &ProviderPolyvalOptions,
1658    ) -> anyhow::Result<GpuTensorHandle> {
1659        Err(anyhow::anyhow!("polyval not supported by provider"))
1660    }
1661
1662    /// Fit a polynomial of degree `degree` to `(x, y)` samples. Optional weights must match `x`.
1663    fn polyfit<'a>(
1664        &'a self,
1665        _x: &'a GpuTensorHandle,
1666        _y: &'a GpuTensorHandle,
1667        _degree: usize,
1668        _weights: Option<&'a GpuTensorHandle>,
1669    ) -> AccelProviderFuture<'a, ProviderPolyfitResult> {
1670        Box::pin(async move { Err(anyhow::anyhow!("polyfit not supported by provider")) })
1671    }
1672
1673    /// Differentiate a polynomial represented as a vector of coefficients.
1674    fn polyder_single<'a>(
1675        &'a self,
1676        _polynomial: &'a GpuTensorHandle,
1677    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
1678        Box::pin(async move { Err(anyhow::anyhow!("polyder_single not supported by provider")) })
1679    }
1680
1681    /// Apply the product rule to polynomials `p` and `q`.
1682    fn polyder_product<'a>(
1683        &'a self,
1684        _p: &'a GpuTensorHandle,
1685        _q: &'a GpuTensorHandle,
1686    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
1687        Box::pin(async move { Err(anyhow::anyhow!("polyder_product not supported by provider")) })
1688    }
1689
1690    /// Apply the quotient rule to polynomials `u` and `v`.
1691    fn polyder_quotient<'a>(
1692        &'a self,
1693        _u: &'a GpuTensorHandle,
1694        _v: &'a GpuTensorHandle,
1695    ) -> AccelProviderFuture<'a, ProviderPolyderQuotient> {
1696        Box::pin(async move {
1697            Err(anyhow::anyhow!(
1698                "polyder_quotient not supported by provider"
1699            ))
1700        })
1701    }
1702
1703    /// Integrate a polynomial represented as a vector of coefficients and append a constant term.
1704    fn polyint(
1705        &self,
1706        _polynomial: &GpuTensorHandle,
1707        _constant: f64,
1708    ) -> anyhow::Result<GpuTensorHandle> {
1709        Err(anyhow::anyhow!("polyint not supported by provider"))
1710    }
1711
1712    /// Allocate a tensor filled with random values drawn from U(0, 1).
1713    fn random_uniform(&self, _shape: &[usize]) -> anyhow::Result<GpuTensorHandle> {
1714        Err(anyhow::anyhow!("random_uniform not supported by provider"))
1715    }
1716
1717    /// Allocate a tensor filled with random values matching the prototype shape.
1718    fn random_uniform_like(&self, prototype: &GpuTensorHandle) -> anyhow::Result<GpuTensorHandle> {
1719        self.random_uniform(&prototype.shape)
1720    }
1721
1722    /// Allocate a tensor filled with standard normal (mean 0, stddev 1) random values.
1723    fn random_normal(&self, _shape: &[usize]) -> anyhow::Result<GpuTensorHandle> {
1724        Err(anyhow::anyhow!("random_normal not supported by provider"))
1725    }
1726
1727    /// Allocate a tensor of standard normal values matching a prototype's shape.
1728    fn random_normal_like(&self, prototype: &GpuTensorHandle) -> anyhow::Result<GpuTensorHandle> {
1729        self.random_normal(&prototype.shape)
1730    }
1731
1732    /// Exponentially-distributed random values with mean `mu`.
1733    fn random_exponential(&self, _mu: f64, _shape: &[usize]) -> anyhow::Result<GpuTensorHandle> {
1734        Err(anyhow::anyhow!(
1735            "random_exponential not supported by provider"
1736        ))
1737    }
1738
1739    /// Normal random values with mean `mu` and standard deviation `sigma`.
1740    fn random_normrnd(
1741        &self,
1742        _mu: f64,
1743        _sigma: f64,
1744        _shape: &[usize],
1745    ) -> anyhow::Result<GpuTensorHandle> {
1746        Err(anyhow::anyhow!("random_normrnd not supported by provider"))
1747    }
1748
1749    /// Uniform random values on the interval `[a, b)`.
1750    fn random_unifrnd(
1751        &self,
1752        _a: f64,
1753        _b: f64,
1754        _shape: &[usize],
1755    ) -> anyhow::Result<GpuTensorHandle> {
1756        Err(anyhow::anyhow!("random_unifrnd not supported by provider"))
1757    }
1758
1759    fn stochastic_evolution(
1760        &self,
1761        _state: &GpuTensorHandle,
1762        _drift: f64,
1763        _scale: f64,
1764        _steps: u32,
1765    ) -> anyhow::Result<GpuTensorHandle> {
1766        Err(anyhow::anyhow!(
1767            "stochastic_evolution not supported by provider"
1768        ))
1769    }
1770
1771    /// Set the provider RNG state to align with the host RNG.
1772    fn set_rng_state(&self, _state: u64) -> anyhow::Result<()> {
1773        Err(anyhow::anyhow!("set_rng_state not supported by provider"))
1774    }
1775
1776    /// Generate a 2-D correlation kernel matching MATLAB's `fspecial` builtin.
1777    fn fspecial(&self, _request: &FspecialRequest) -> anyhow::Result<GpuTensorHandle> {
1778        Err(anyhow::anyhow!("fspecial not supported by provider"))
1779    }
1780
1781    /// Evaluate the `peaks` test surface on an n×n grid spanning [-3,3]×[-3,3].
1782    /// Returns the Z matrix (n×n) as a GPU tensor.
1783    fn peaks(&self, _n: usize) -> anyhow::Result<GpuTensorHandle> {
1784        Err(anyhow::anyhow!("peaks not supported by provider"))
1785    }
1786
1787    /// Evaluate the `peaks` formula element-wise on caller-supplied GPU coordinate tensors.
1788    /// X and Y must have the same shape. Returns a Z tensor of the same shape.
1789    fn peaks_xy(
1790        &self,
1791        _x: &GpuTensorHandle,
1792        _y: &GpuTensorHandle,
1793    ) -> anyhow::Result<GpuTensorHandle> {
1794        Err(anyhow::anyhow!("peaks_xy not supported by provider"))
1795    }
1796
1797    fn hann_window(&self, _len: usize, _periodic: bool) -> anyhow::Result<GpuTensorHandle> {
1798        Err(anyhow::anyhow!("hann_window not supported by provider"))
1799    }
1800
1801    fn hamming_window(&self, _len: usize, _periodic: bool) -> anyhow::Result<GpuTensorHandle> {
1802        Err(anyhow::anyhow!("hamming_window not supported by provider"))
1803    }
1804
1805    fn blackman_window(&self, _len: usize, _periodic: bool) -> anyhow::Result<GpuTensorHandle> {
1806        Err(anyhow::anyhow!("blackman_window not supported by provider"))
1807    }
1808
1809    /// Apply an N-D correlation/convolution with padding semantics matching MATLAB's `imfilter`.
1810    fn imfilter<'a>(
1811        &'a self,
1812        _image: &'a GpuTensorHandle,
1813        _kernel: &'a GpuTensorHandle,
1814        _options: &'a ImfilterOptions,
1815    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
1816        unsupported_future("imfilter not supported by provider")
1817    }
1818
1819    /// Allocate a tensor filled with random integers over an inclusive range.
1820    fn random_integer_range(
1821        &self,
1822        _lower: i64,
1823        _upper: i64,
1824        _shape: &[usize],
1825    ) -> anyhow::Result<GpuTensorHandle> {
1826        Err(anyhow::anyhow!(
1827            "random_integer_range not supported by provider"
1828        ))
1829    }
1830
1831    /// Allocate a random integer tensor matching the prototype shape.
1832    fn random_integer_like(
1833        &self,
1834        prototype: &GpuTensorHandle,
1835        lower: i64,
1836        upper: i64,
1837    ) -> anyhow::Result<GpuTensorHandle> {
1838        self.random_integer_range(lower, upper, &prototype.shape)
1839    }
1840
1841    /// Allocate a random permutation of 1..=n, returning the first k elements.
1842    fn random_permutation(&self, _n: usize, _k: usize) -> anyhow::Result<GpuTensorHandle> {
1843        Err(anyhow!("random_permutation not supported by provider"))
1844    }
1845
1846    /// Allocate a random permutation matching the prototype residency.
1847    fn random_permutation_like(
1848        &self,
1849        _prototype: &GpuTensorHandle,
1850        n: usize,
1851        k: usize,
1852    ) -> anyhow::Result<GpuTensorHandle> {
1853        self.random_permutation(n, k)
1854    }
1855
1856    /// Compute a covariance matrix across the columns of `matrix`.
1857    fn covariance<'a>(
1858        &'a self,
1859        _matrix: &'a GpuTensorHandle,
1860        _second: Option<&'a GpuTensorHandle>,
1861        _weights: Option<&'a GpuTensorHandle>,
1862        _options: &'a CovarianceOptions,
1863    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
1864        unsupported_future("covariance not supported by provider")
1865    }
1866
1867    /// Compute a correlation coefficient matrix across the columns of `matrix`.
1868    fn corrcoef<'a>(
1869        &'a self,
1870        _matrix: &'a GpuTensorHandle,
1871        _options: &'a CorrcoefOptions,
1872    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
1873        unsupported_future("corrcoef not supported by provider")
1874    }
1875
1876    /// Convert a resident covariance matrix into correlation and sigma outputs.
1877    fn covariance_to_correlation(
1878        &self,
1879        _matrix: &GpuTensorHandle,
1880    ) -> anyhow::Result<ProviderCovarianceToCorrelationResult> {
1881        Err(anyhow::anyhow!(
1882            "covariance_to_correlation not supported by provider"
1883        ))
1884    }
1885
1886    // Optional operator hooks (default to unsupported)
1887    fn linspace(&self, _start: f64, _stop: f64, _count: usize) -> anyhow::Result<GpuTensorHandle> {
1888        Err(anyhow::anyhow!("linspace not supported by provider"))
1889    }
1890    fn elem_add<'a>(
1891        &'a self,
1892        _a: &'a GpuTensorHandle,
1893        _b: &'a GpuTensorHandle,
1894    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
1895        unsupported_future("elem_add not supported by provider")
1896    }
1897    fn elem_mul<'a>(
1898        &'a self,
1899        _a: &'a GpuTensorHandle,
1900        _b: &'a GpuTensorHandle,
1901    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
1902        unsupported_future("elem_mul not supported by provider")
1903    }
1904    fn elem_max<'a>(
1905        &'a self,
1906        _a: &'a GpuTensorHandle,
1907        _b: &'a GpuTensorHandle,
1908    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
1909        unsupported_future("elem_max not supported by provider")
1910    }
1911    fn elem_min<'a>(
1912        &'a self,
1913        _a: &'a GpuTensorHandle,
1914        _b: &'a GpuTensorHandle,
1915    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
1916        unsupported_future("elem_min not supported by provider")
1917    }
1918    fn elem_sub<'a>(
1919        &'a self,
1920        _a: &'a GpuTensorHandle,
1921        _b: &'a GpuTensorHandle,
1922    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
1923        unsupported_future("elem_sub not supported by provider")
1924    }
1925    fn elem_div<'a>(
1926        &'a self,
1927        _a: &'a GpuTensorHandle,
1928        _b: &'a GpuTensorHandle,
1929    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
1930        unsupported_future("elem_div not supported by provider")
1931    }
1932    fn elem_pow<'a>(
1933        &'a self,
1934        _a: &'a GpuTensorHandle,
1935        _b: &'a GpuTensorHandle,
1936    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
1937        unsupported_future("elem_pow not supported by provider")
1938    }
1939
1940    /// Construct complex-interleaved GPU storage from a real-valued tensor,
1941    /// using zero for the imaginary lane.
1942    fn complex_from_real<'a>(
1943        &'a self,
1944        _real: &'a GpuTensorHandle,
1945    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
1946        unsupported_future("complex_from_real not supported by provider")
1947    }
1948
1949    /// Construct complex-interleaved GPU storage from real and imaginary tensors.
1950    ///
1951    /// Implementations should support equal shapes and scalar expansion for either
1952    /// operand, matching MATLAB's `complex(real, imag)` size rules.
1953    fn complex_from_real_imag<'a>(
1954        &'a self,
1955        _real: &'a GpuTensorHandle,
1956        _imag: &'a GpuTensorHandle,
1957    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
1958        unsupported_future("complex_from_real_imag not supported by provider")
1959    }
1960
1961    /// Map a resident real-valued symbol tensor through a complex constellation
1962    /// table and return complex-interleaved GPU storage.
1963    fn modulate_constellation<'a>(
1964        &'a self,
1965        _request: ProviderModulationRequest<'a>,
1966    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
1967        unsupported_future("modulate_constellation not supported by provider")
1968    }
1969
1970    /// Group a resident real/logical bit tensor into symbols, map through a complex
1971    /// constellation table, and return complex-interleaved GPU storage.
1972    fn modulate_bits_constellation<'a>(
1973        &'a self,
1974        _request: ProviderBitModulationRequest<'a>,
1975    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
1976        unsupported_future("modulate_bits_constellation not supported by provider")
1977    }
1978
1979    fn elem_hypot<'a>(
1980        &'a self,
1981        _a: &'a GpuTensorHandle,
1982        _b: &'a GpuTensorHandle,
1983    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
1984        unsupported_future("elem_hypot not supported by provider")
1985    }
1986    fn elem_ge<'a>(
1987        &'a self,
1988        _a: &'a GpuTensorHandle,
1989        _b: &'a GpuTensorHandle,
1990    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
1991        unsupported_future("elem_ge not supported by provider")
1992    }
1993    fn elem_le<'a>(
1994        &'a self,
1995        _a: &'a GpuTensorHandle,
1996        _b: &'a GpuTensorHandle,
1997    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
1998        unsupported_future("elem_le not supported by provider")
1999    }
2000    fn elem_lt<'a>(
2001        &'a self,
2002        _a: &'a GpuTensorHandle,
2003        _b: &'a GpuTensorHandle,
2004    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2005        unsupported_future("elem_lt not supported by provider")
2006    }
2007    fn elem_gt<'a>(
2008        &'a self,
2009        _a: &'a GpuTensorHandle,
2010        _b: &'a GpuTensorHandle,
2011    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2012        unsupported_future("elem_gt not supported by provider")
2013    }
2014    fn elem_eq<'a>(
2015        &'a self,
2016        _a: &'a GpuTensorHandle,
2017        _b: &'a GpuTensorHandle,
2018    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2019        unsupported_future("elem_eq not supported by provider")
2020    }
2021    fn elem_ne<'a>(
2022        &'a self,
2023        _a: &'a GpuTensorHandle,
2024        _b: &'a GpuTensorHandle,
2025    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2026        unsupported_future("elem_ne not supported by provider")
2027    }
2028    fn logical_and(
2029        &self,
2030        _a: &GpuTensorHandle,
2031        _b: &GpuTensorHandle,
2032    ) -> anyhow::Result<GpuTensorHandle> {
2033        Err(anyhow::anyhow!("logical_and not supported by provider"))
2034    }
2035    fn logical_or(
2036        &self,
2037        _a: &GpuTensorHandle,
2038        _b: &GpuTensorHandle,
2039    ) -> anyhow::Result<GpuTensorHandle> {
2040        Err(anyhow::anyhow!("logical_or not supported by provider"))
2041    }
2042    fn logical_xor(
2043        &self,
2044        _a: &GpuTensorHandle,
2045        _b: &GpuTensorHandle,
2046    ) -> anyhow::Result<GpuTensorHandle> {
2047        Err(anyhow::anyhow!("logical_xor not supported by provider"))
2048    }
2049    fn logical_not(&self, _a: &GpuTensorHandle) -> anyhow::Result<GpuTensorHandle> {
2050        Err(anyhow::anyhow!("logical_not not supported by provider"))
2051    }
2052    fn logical_islogical(&self, a: &GpuTensorHandle) -> anyhow::Result<bool> {
2053        Ok(handle_is_logical(a))
2054    }
2055    fn logical_isreal(&self, _a: &GpuTensorHandle) -> anyhow::Result<bool> {
2056        Err(anyhow::anyhow!("logical_isreal not supported by provider"))
2057    }
2058    fn logical_isfinite(&self, _a: &GpuTensorHandle) -> anyhow::Result<GpuTensorHandle> {
2059        Err(anyhow::anyhow!(
2060            "logical_isfinite not supported by provider"
2061        ))
2062    }
2063    fn logical_isnan(&self, _a: &GpuTensorHandle) -> anyhow::Result<GpuTensorHandle> {
2064        Err(anyhow::anyhow!("logical_isnan not supported by provider"))
2065    }
2066    fn logical_isinf(&self, _a: &GpuTensorHandle) -> anyhow::Result<GpuTensorHandle> {
2067        Err(anyhow::anyhow!("logical_isinf not supported by provider"))
2068    }
2069    fn elem_atan2<'a>(
2070        &'a self,
2071        _y: &'a GpuTensorHandle,
2072        _x: &'a GpuTensorHandle,
2073    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2074        unsupported_future("elem_atan2 not supported by provider")
2075    }
2076    // Unary elementwise operations (optional)
2077    fn unary_sin<'a>(
2078        &'a self,
2079        _a: &'a GpuTensorHandle,
2080    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2081        unsupported_future("unary_sin not supported by provider")
2082    }
2083    fn unary_sinc<'a>(
2084        &'a self,
2085        _a: &'a GpuTensorHandle,
2086    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2087        unsupported_future("unary_sinc not supported by provider")
2088    }
2089    fn unary_gamma<'a>(
2090        &'a self,
2091        _a: &'a GpuTensorHandle,
2092    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2093        unsupported_future("unary_gamma not supported by provider")
2094    }
2095    fn unary_gammaln<'a>(
2096        &'a self,
2097        _a: &'a GpuTensorHandle,
2098    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2099        unsupported_future("unary_gammaln not supported by provider")
2100    }
2101    fn unary_erf<'a>(
2102        &'a self,
2103        _a: &'a GpuTensorHandle,
2104    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2105        unsupported_future("unary_erf not supported by provider")
2106    }
2107    fn unary_erfcinv<'a>(
2108        &'a self,
2109        _a: &'a GpuTensorHandle,
2110    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2111        unsupported_future("unary_erfcinv not supported by provider")
2112    }
2113    fn unary_factorial<'a>(
2114        &'a self,
2115        _a: &'a GpuTensorHandle,
2116    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2117        unsupported_future("unary_factorial not supported by provider")
2118    }
2119    fn unary_asinh<'a>(
2120        &'a self,
2121        _a: &'a GpuTensorHandle,
2122    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2123        unsupported_future("unary_asinh not supported by provider")
2124    }
2125    fn unary_sinh<'a>(
2126        &'a self,
2127        _a: &'a GpuTensorHandle,
2128    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2129        unsupported_future("unary_sinh not supported by provider")
2130    }
2131    fn unary_cosh<'a>(
2132        &'a self,
2133        _a: &'a GpuTensorHandle,
2134    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2135        unsupported_future("unary_cosh not supported by provider")
2136    }
2137    fn unary_asin<'a>(
2138        &'a self,
2139        _a: &'a GpuTensorHandle,
2140    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2141        unsupported_future("unary_asin not supported by provider")
2142    }
2143    fn unary_acos<'a>(
2144        &'a self,
2145        _a: &'a GpuTensorHandle,
2146    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2147        unsupported_future("unary_acos not supported by provider")
2148    }
2149    fn unary_acosh<'a>(
2150        &'a self,
2151        _a: &'a GpuTensorHandle,
2152    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2153        unsupported_future("unary_acosh not supported by provider")
2154    }
2155    fn unary_tan<'a>(
2156        &'a self,
2157        _a: &'a GpuTensorHandle,
2158    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2159        unsupported_future("unary_tan not supported by provider")
2160    }
2161    fn unary_tanh<'a>(
2162        &'a self,
2163        _a: &'a GpuTensorHandle,
2164    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2165        unsupported_future("unary_tanh not supported by provider")
2166    }
2167    fn unary_atan<'a>(
2168        &'a self,
2169        _a: &'a GpuTensorHandle,
2170    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2171        unsupported_future("unary_atan not supported by provider")
2172    }
2173    fn unary_atanh<'a>(
2174        &'a self,
2175        _a: &'a GpuTensorHandle,
2176    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2177        unsupported_future("unary_atanh not supported by provider")
2178    }
2179    fn unary_ceil<'a>(
2180        &'a self,
2181        _a: &'a GpuTensorHandle,
2182    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2183        unsupported_future("unary_ceil not supported by provider")
2184    }
2185    fn unary_floor<'a>(
2186        &'a self,
2187        _a: &'a GpuTensorHandle,
2188    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2189        unsupported_future("unary_floor not supported by provider")
2190    }
2191    fn unary_round<'a>(
2192        &'a self,
2193        _a: &'a GpuTensorHandle,
2194    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2195        unsupported_future("unary_round not supported by provider")
2196    }
2197    fn round_digits<'a>(
2198        &'a self,
2199        _a: &'a GpuTensorHandle,
2200        _digits: i32,
2201        _significant: bool,
2202    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2203        unsupported_future("round_digits not supported by provider")
2204    }
2205    fn unary_fix<'a>(
2206        &'a self,
2207        _a: &'a GpuTensorHandle,
2208    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2209        unsupported_future("unary_fix not supported by provider")
2210    }
2211    fn unary_cos<'a>(
2212        &'a self,
2213        _a: &'a GpuTensorHandle,
2214    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2215        unsupported_future("unary_cos not supported by provider")
2216    }
2217    fn unary_angle<'a>(
2218        &'a self,
2219        _a: &'a GpuTensorHandle,
2220    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2221        unsupported_future("unary_angle not supported by provider")
2222    }
2223    fn unary_imag<'a>(
2224        &'a self,
2225        _a: &'a GpuTensorHandle,
2226    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2227        unsupported_future("unary_imag not supported by provider")
2228    }
2229    fn unary_real<'a>(
2230        &'a self,
2231        _a: &'a GpuTensorHandle,
2232    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2233        unsupported_future("unary_real not supported by provider")
2234    }
2235    fn unary_conj<'a>(
2236        &'a self,
2237        _a: &'a GpuTensorHandle,
2238    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2239        unsupported_future("unary_conj not supported by provider")
2240    }
2241    fn unary_abs<'a>(
2242        &'a self,
2243        _a: &'a GpuTensorHandle,
2244    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2245        unsupported_future("unary_abs not supported by provider")
2246    }
2247    fn unary_sign<'a>(
2248        &'a self,
2249        _a: &'a GpuTensorHandle,
2250    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2251        unsupported_future("unary_sign not supported by provider")
2252    }
2253    fn unary_heaviside<'a>(
2254        &'a self,
2255        _a: &'a GpuTensorHandle,
2256    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2257        unsupported_future("unary_heaviside not supported by provider")
2258    }
2259    fn unary_exp<'a>(
2260        &'a self,
2261        _a: &'a GpuTensorHandle,
2262    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2263        unsupported_future("unary_exp not supported by provider")
2264    }
2265    fn unary_expm1<'a>(
2266        &'a self,
2267        _a: &'a GpuTensorHandle,
2268    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2269        unsupported_future("unary_expm1 not supported by provider")
2270    }
2271    fn unary_log<'a>(
2272        &'a self,
2273        _a: &'a GpuTensorHandle,
2274    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2275        unsupported_future("unary_log not supported by provider")
2276    }
2277    fn unary_log2<'a>(
2278        &'a self,
2279        _a: &'a GpuTensorHandle,
2280    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2281        unsupported_future("unary_log2 not supported by provider")
2282    }
2283    fn unary_log10<'a>(
2284        &'a self,
2285        _a: &'a GpuTensorHandle,
2286    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2287        unsupported_future("unary_log10 not supported by provider")
2288    }
2289    fn unary_log1p<'a>(
2290        &'a self,
2291        _a: &'a GpuTensorHandle,
2292    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2293        unsupported_future("unary_log1p not supported by provider")
2294    }
2295    fn unary_sqrt<'a>(
2296        &'a self,
2297        _a: &'a GpuTensorHandle,
2298    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2299        unsupported_future("unary_sqrt not supported by provider")
2300    }
2301    fn unary_double<'a>(
2302        &'a self,
2303        _a: &'a GpuTensorHandle,
2304    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2305        unsupported_future("unary_double not supported by provider")
2306    }
2307    fn unary_single<'a>(
2308        &'a self,
2309        _a: &'a GpuTensorHandle,
2310    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2311        unsupported_future("unary_single not supported by provider")
2312    }
2313    fn unary_pow2<'a>(
2314        &'a self,
2315        _a: &'a GpuTensorHandle,
2316    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2317        unsupported_future("unary_pow2 not supported by provider")
2318    }
2319    fn unary_nextpow2<'a>(
2320        &'a self,
2321        _a: &'a GpuTensorHandle,
2322    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2323        unsupported_future("unary_nextpow2 not supported by provider")
2324    }
2325    fn pow2_scale(
2326        &self,
2327        _mantissa: &GpuTensorHandle,
2328        _exponent: &GpuTensorHandle,
2329    ) -> anyhow::Result<GpuTensorHandle> {
2330        Err(anyhow::anyhow!("pow2_scale not supported by provider"))
2331    }
2332    // Left-scalar operations (broadcast with scalar on the left)
2333    fn scalar_rsub(&self, _a: &GpuTensorHandle, _scalar: f64) -> anyhow::Result<GpuTensorHandle> {
2334        Err(anyhow::anyhow!("scalar_rsub not supported by provider"))
2335    }
2336    fn scalar_rdiv(&self, _a: &GpuTensorHandle, _scalar: f64) -> anyhow::Result<GpuTensorHandle> {
2337        Err(anyhow::anyhow!("scalar_rdiv not supported by provider"))
2338    }
2339    // Scalar operations: apply op with scalar right-hand side (broadcast over a)
2340    fn scalar_add(&self, _a: &GpuTensorHandle, _scalar: f64) -> anyhow::Result<GpuTensorHandle> {
2341        Err(anyhow::anyhow!("scalar_add not supported by provider"))
2342    }
2343    fn scalar_sub(&self, _a: &GpuTensorHandle, _scalar: f64) -> anyhow::Result<GpuTensorHandle> {
2344        Err(anyhow::anyhow!("scalar_sub not supported by provider"))
2345    }
2346    fn scalar_mul(&self, _a: &GpuTensorHandle, _scalar: f64) -> anyhow::Result<GpuTensorHandle> {
2347        Err(anyhow::anyhow!("scalar_mul not supported by provider"))
2348    }
2349    fn scalar_max(&self, _a: &GpuTensorHandle, _scalar: f64) -> anyhow::Result<GpuTensorHandle> {
2350        Err(anyhow::anyhow!("scalar_max not supported by provider"))
2351    }
2352    fn scalar_min(&self, _a: &GpuTensorHandle, _scalar: f64) -> anyhow::Result<GpuTensorHandle> {
2353        Err(anyhow::anyhow!("scalar_min not supported by provider"))
2354    }
2355    fn scalar_div(&self, _a: &GpuTensorHandle, _scalar: f64) -> anyhow::Result<GpuTensorHandle> {
2356        Err(anyhow::anyhow!("scalar_div not supported by provider"))
2357    }
2358    fn sort_dim<'a>(
2359        &'a self,
2360        _a: &'a GpuTensorHandle,
2361        _dim: usize,
2362        _order: SortOrder,
2363        _comparison: SortComparison,
2364    ) -> AccelProviderFuture<'a, SortResult> {
2365        unsupported_future("sort_dim not supported by provider")
2366    }
2367    fn sort_rows<'a>(
2368        &'a self,
2369        _a: &'a GpuTensorHandle,
2370        _columns: &'a [SortRowsColumnSpec],
2371        _comparison: SortComparison,
2372    ) -> AccelProviderFuture<'a, SortResult> {
2373        unsupported_future("sort_rows not supported by provider")
2374    }
2375    fn matmul<'a>(
2376        &'a self,
2377        _a: &'a GpuTensorHandle,
2378        _b: &'a GpuTensorHandle,
2379    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2380        unsupported_future("matmul not supported by provider")
2381    }
2382
2383    fn syrk(&self, _a: &GpuTensorHandle) -> anyhow::Result<GpuTensorHandle> {
2384        Err(anyhow::anyhow!("syrk not supported by provider"))
2385    }
2386    fn pagefun(&self, _request: &PagefunRequest) -> anyhow::Result<GpuTensorHandle> {
2387        Err(anyhow::anyhow!("pagefun not supported by provider"))
2388    }
2389
2390    /// Optional: matrix multiplication with an epilogue applied before store.
2391    ///
2392    /// The default implementation falls back to `matmul` when the epilogue is effectively a no-op
2393    /// (alpha=1, beta=0, no row/col scales), and otherwise returns `Err`.
2394    fn matmul_epilogue<'a>(
2395        &'a self,
2396        a: &'a GpuTensorHandle,
2397        b: &'a GpuTensorHandle,
2398        epilogue: &'a MatmulEpilogue,
2399    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2400        Box::pin(async move {
2401            if epilogue.is_noop() {
2402                return self.matmul(a, b).await;
2403            }
2404            Err(anyhow::anyhow!("matmul_epilogue not supported by provider"))
2405        })
2406    }
2407    fn image_normalize<'a>(
2408        &'a self,
2409        _input: &'a GpuTensorHandle,
2410        _desc: &'a ImageNormalizeDescriptor,
2411    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2412        unsupported_future("image_normalize fusion not supported by provider")
2413    }
2414    fn matmul_power_step<'a>(
2415        &'a self,
2416        _lhs: &'a GpuTensorHandle,
2417        _rhs: &'a GpuTensorHandle,
2418        _epilogue: &'a PowerStepEpilogue,
2419    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2420        unsupported_future("matmul_power_step normalization not supported by provider")
2421    }
2422    fn linsolve<'a>(
2423        &'a self,
2424        _lhs: &'a GpuTensorHandle,
2425        _rhs: &'a GpuTensorHandle,
2426        _options: &'a ProviderLinsolveOptions,
2427    ) -> AccelProviderFuture<'a, ProviderLinsolveResult> {
2428        unsupported_future("linsolve not supported by provider")
2429    }
2430    fn inv<'a>(
2431        &'a self,
2432        _matrix: &'a GpuTensorHandle,
2433        _options: ProviderInvOptions,
2434    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2435        unsupported_future("inv not supported by provider")
2436    }
2437    fn pinv<'a>(
2438        &'a self,
2439        _matrix: &'a GpuTensorHandle,
2440        _options: ProviderPinvOptions,
2441    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2442        unsupported_future("pinv not supported by provider")
2443    }
2444    fn cond<'a>(
2445        &'a self,
2446        _matrix: &'a GpuTensorHandle,
2447        _norm: ProviderCondNorm,
2448    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2449        Box::pin(async move { Err(anyhow::anyhow!("cond not supported by provider")) })
2450    }
2451    fn norm<'a>(
2452        &'a self,
2453        _tensor: &'a GpuTensorHandle,
2454        _order: ProviderNormOrder,
2455    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2456        Box::pin(async move { Err(anyhow::anyhow!("norm not supported by provider")) })
2457    }
2458    fn interp1<'a>(
2459        &'a self,
2460        _request: &'a ProviderInterp1Request<'a>,
2461    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2462        unsupported_future("interp1 not supported by provider")
2463    }
2464    fn rank<'a>(
2465        &'a self,
2466        _matrix: &'a GpuTensorHandle,
2467        _tolerance: Option<f64>,
2468    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2469        Box::pin(async move { Err(anyhow::anyhow!("rank not supported by provider")) })
2470    }
2471    fn rcond<'a>(
2472        &'a self,
2473        _matrix: &'a GpuTensorHandle,
2474    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2475        Box::pin(async move { Err(anyhow::anyhow!("rcond not supported by provider")) })
2476    }
2477    fn mldivide<'a>(
2478        &'a self,
2479        _lhs: &'a GpuTensorHandle,
2480        _rhs: &'a GpuTensorHandle,
2481    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2482        Box::pin(async move { Err(anyhow::anyhow!("mldivide not supported by provider")) })
2483    }
2484    fn mrdivide<'a>(
2485        &'a self,
2486        _lhs: &'a GpuTensorHandle,
2487        _rhs: &'a GpuTensorHandle,
2488    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2489        Box::pin(async move { Err(anyhow::anyhow!("mrdivide not supported by provider")) })
2490    }
2491    fn eig<'a>(
2492        &'a self,
2493        _a: &'a GpuTensorHandle,
2494        _compute_left: bool,
2495    ) -> AccelProviderFuture<'a, ProviderEigResult> {
2496        Box::pin(async move { Err(anyhow::anyhow!("eig not supported by provider")) })
2497    }
2498    fn lu<'a>(&'a self, _a: &'a GpuTensorHandle) -> AccelProviderFuture<'a, ProviderLuResult> {
2499        Box::pin(async move { Err(anyhow::anyhow!("lu not supported by provider")) })
2500    }
2501
2502    fn chol<'a>(
2503        &'a self,
2504        _a: &'a GpuTensorHandle,
2505        _lower: bool,
2506    ) -> AccelProviderFuture<'a, ProviderCholResult> {
2507        Box::pin(async move { Err(anyhow::anyhow!("chol not supported by provider")) })
2508    }
2509    fn qr<'a>(
2510        &'a self,
2511        _a: &'a GpuTensorHandle,
2512        _options: ProviderQrOptions,
2513    ) -> AccelProviderFuture<'a, ProviderQrResult> {
2514        Box::pin(async move { Err(anyhow::anyhow!("qr not supported by provider")) })
2515    }
2516    fn take_matmul_sources(
2517        &self,
2518        _product: &GpuTensorHandle,
2519    ) -> Option<(GpuTensorHandle, GpuTensorHandle)> {
2520        None
2521    }
2522    fn qr_power_iter<'a>(
2523        &'a self,
2524        product: &'a GpuTensorHandle,
2525        _product_lhs: Option<&'a GpuTensorHandle>,
2526        q_handle: &'a GpuTensorHandle,
2527        options: &'a ProviderQrOptions,
2528    ) -> AccelProviderFuture<'a, Option<ProviderQrPowerIterResult>> {
2529        let _ = (product, q_handle, options);
2530        Box::pin(async move { Ok(None) })
2531    }
2532    fn transpose(&self, _a: &GpuTensorHandle) -> anyhow::Result<GpuTensorHandle> {
2533        Err(anyhow::anyhow!("transpose not supported by provider"))
2534    }
2535    fn conv1d(
2536        &self,
2537        _signal: &GpuTensorHandle,
2538        _kernel: &GpuTensorHandle,
2539        _options: ProviderConv1dOptions,
2540    ) -> anyhow::Result<GpuTensorHandle> {
2541        Err(anyhow::anyhow!("conv1d not supported by provider"))
2542    }
2543    fn conv2d(
2544        &self,
2545        _signal: &GpuTensorHandle,
2546        _kernel: &GpuTensorHandle,
2547        _mode: ProviderConvMode,
2548    ) -> anyhow::Result<GpuTensorHandle> {
2549        Err(anyhow::anyhow!("conv2d not supported by provider"))
2550    }
2551    fn iir_filter<'a>(
2552        &'a self,
2553        _b: &'a GpuTensorHandle,
2554        _a: &'a GpuTensorHandle,
2555        _x: &'a GpuTensorHandle,
2556        _options: ProviderIirFilterOptions,
2557    ) -> AccelProviderFuture<'a, ProviderIirFilterResult> {
2558        Box::pin(async move { Err(anyhow::anyhow!("iir_filter not supported by provider")) })
2559    }
2560    fn uniform_spectral_estimate<'a>(
2561        &'a self,
2562        _request: &'a ProviderSpectralRequest<'a>,
2563    ) -> AccelProviderFuture<'a, ProviderSpectralResult> {
2564        unsupported_future("uniform_spectral_estimate not supported by provider")
2565    }
2566    fn signal_envelope<'a>(
2567        &'a self,
2568        _request: &'a ProviderEnvelopeRequest<'a>,
2569    ) -> AccelProviderFuture<'a, ProviderEnvelopeResult> {
2570        unsupported_future("signal_envelope not supported by provider")
2571    }
2572    fn signal_hilbert<'a>(
2573        &'a self,
2574        _request: &'a ProviderHilbertRequest<'a>,
2575    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2576        unsupported_future("signal_hilbert not supported by provider")
2577    }
2578    /// Reorder tensor dimensions according to `order`, expressed as zero-based indices.
2579    fn permute(
2580        &self,
2581        _handle: &GpuTensorHandle,
2582        _order: &[usize],
2583    ) -> anyhow::Result<GpuTensorHandle> {
2584        Err(anyhow::anyhow!("permute not supported by provider"))
2585    }
2586    fn flip(&self, _handle: &GpuTensorHandle, _axes: &[usize]) -> anyhow::Result<GpuTensorHandle> {
2587        Err(anyhow::anyhow!("flip not supported by provider"))
2588    }
2589    fn circshift(
2590        &self,
2591        _handle: &GpuTensorHandle,
2592        _shifts: &[isize],
2593    ) -> anyhow::Result<GpuTensorHandle> {
2594        Err(anyhow::anyhow!("circshift not supported by provider"))
2595    }
2596    fn diff_dim(
2597        &self,
2598        _handle: &GpuTensorHandle,
2599        _order: usize,
2600        _dim: usize,
2601    ) -> anyhow::Result<GpuTensorHandle> {
2602        Err(anyhow::anyhow!("diff_dim not supported by provider"))
2603    }
2604    fn gradient_dim(
2605        &self,
2606        _handle: &GpuTensorHandle,
2607        _dim: usize,
2608        _spacing: f64,
2609    ) -> anyhow::Result<GpuTensorHandle> {
2610        Err(anyhow::anyhow!("gradient_dim not supported by provider"))
2611    }
2612    fn gradient_dim_with_coordinates(
2613        &self,
2614        _handle: &GpuTensorHandle,
2615        _dim: usize,
2616        _coordinates: &GpuTensorHandle,
2617    ) -> anyhow::Result<GpuTensorHandle> {
2618        Err(anyhow::anyhow!(
2619            "gradient_dim_with_coordinates not supported by provider"
2620        ))
2621    }
2622    /// Perform an in-place FFT along a zero-based dimension, optionally padding/truncating to `len`.
2623    fn fft_dim<'a>(
2624        &'a self,
2625        _handle: &'a GpuTensorHandle,
2626        _len: Option<usize>,
2627        _dim: usize,
2628    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2629        unsupported_future("fft_dim not supported by provider")
2630    }
2631    fn ifft_dim<'a>(
2632        &'a self,
2633        _handle: &'a GpuTensorHandle,
2634        _len: Option<usize>,
2635        _dim: usize,
2636    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2637        unsupported_future("ifft_dim not supported by provider")
2638    }
2639    fn fft_extract_real<'a>(
2640        &'a self,
2641        _handle: &'a GpuTensorHandle,
2642    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2643        unsupported_future("fft_extract_real not supported by provider")
2644    }
2645    fn unique<'a>(
2646        &'a self,
2647        _handle: &'a GpuTensorHandle,
2648        _options: &'a UniqueOptions,
2649    ) -> AccelProviderFuture<'a, UniqueResult> {
2650        Box::pin(async move { Err(anyhow::anyhow!("unique not supported by provider")) })
2651    }
2652    fn union<'a>(
2653        &'a self,
2654        _a: &'a GpuTensorHandle,
2655        _b: &'a GpuTensorHandle,
2656        _options: &'a UnionOptions,
2657    ) -> AccelProviderFuture<'a, UnionResult> {
2658        Box::pin(async move { Err(anyhow::anyhow!("union not supported by provider")) })
2659    }
2660    fn setdiff<'a>(
2661        &'a self,
2662        _a: &'a GpuTensorHandle,
2663        _b: &'a GpuTensorHandle,
2664        _options: &'a SetdiffOptions,
2665    ) -> AccelProviderFuture<'a, SetdiffResult> {
2666        Box::pin(async move { Err(anyhow::anyhow!("setdiff not supported by provider")) })
2667    }
2668    fn ismember<'a>(
2669        &'a self,
2670        _a: &'a GpuTensorHandle,
2671        _b: &'a GpuTensorHandle,
2672        _options: &'a IsMemberOptions,
2673    ) -> AccelProviderFuture<'a, IsMemberResult> {
2674        Box::pin(async move { Err(anyhow::anyhow!("ismember not supported by provider")) })
2675    }
2676    fn reshape(
2677        &self,
2678        handle: &GpuTensorHandle,
2679        new_shape: &[usize],
2680    ) -> anyhow::Result<GpuTensorHandle> {
2681        let mut updated = handle.clone();
2682        updated.shape = new_shape.to_vec();
2683        Ok(updated)
2684    }
2685    /// Concatenate the provided tensors along the 1-based dimension `dim`.
2686    fn cat(&self, _dim: usize, _inputs: &[GpuTensorHandle]) -> anyhow::Result<GpuTensorHandle> {
2687        Err(anyhow::anyhow!("cat not supported by provider"))
2688    }
2689    fn repmat(
2690        &self,
2691        _handle: &GpuTensorHandle,
2692        _reps: &[usize],
2693    ) -> anyhow::Result<GpuTensorHandle> {
2694        Err(anyhow::anyhow!("repmat not supported by provider"))
2695    }
2696    /// Compute the Kronecker product of two tensors, matching MATLAB semantics.
2697    fn kron(&self, _a: &GpuTensorHandle, _b: &GpuTensorHandle) -> anyhow::Result<GpuTensorHandle> {
2698        Err(anyhow::anyhow!("kron not supported by provider"))
2699    }
2700    /// Compute the cross product of 3-element vectors along a matching dimension.
2701    fn cross(
2702        &self,
2703        _lhs: &GpuTensorHandle,
2704        _rhs: &GpuTensorHandle,
2705        _dim: Option<usize>,
2706    ) -> anyhow::Result<GpuTensorHandle> {
2707        Err(anyhow::anyhow!("cross not supported by provider"))
2708    }
2709    fn reduce_sum<'a>(
2710        &'a self,
2711        _a: &'a GpuTensorHandle,
2712    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2713        unsupported_future("reduce_sum not supported by provider")
2714    }
2715    fn reduce_sum_dim<'a>(
2716        &'a self,
2717        _a: &'a GpuTensorHandle,
2718        _dim: usize,
2719    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2720        unsupported_future("reduce_sum_dim not supported by provider")
2721    }
2722    fn dot<'a>(
2723        &'a self,
2724        _lhs: &'a GpuTensorHandle,
2725        _rhs: &'a GpuTensorHandle,
2726        _dim: Option<usize>,
2727    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2728        unsupported_future("dot not supported by provider")
2729    }
2730    fn reduce_nnz<'a>(
2731        &'a self,
2732        _a: &'a GpuTensorHandle,
2733    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2734        unsupported_future("reduce_nnz not supported by provider")
2735    }
2736    fn reduce_nnz_dim<'a>(
2737        &'a self,
2738        _a: &'a GpuTensorHandle,
2739        _dim: usize,
2740    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2741        unsupported_future("reduce_nnz_dim not supported by provider")
2742    }
2743    fn reduce_prod<'a>(
2744        &'a self,
2745        _a: &'a GpuTensorHandle,
2746    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2747        unsupported_future("reduce_prod not supported by provider")
2748    }
2749    fn reduce_prod_dim<'a>(
2750        &'a self,
2751        _a: &'a GpuTensorHandle,
2752        _dim: usize,
2753    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2754        unsupported_future("reduce_prod_dim not supported by provider")
2755    }
2756    fn reduce_mean<'a>(
2757        &'a self,
2758        _a: &'a GpuTensorHandle,
2759    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2760        unsupported_future("reduce_mean not supported by provider")
2761    }
2762    /// Reduce mean across multiple zero-based dimensions in one device pass.
2763    fn reduce_mean_nd<'a>(
2764        &'a self,
2765        _a: &'a GpuTensorHandle,
2766        _dims_zero_based: &'a [usize],
2767    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2768        unsupported_future("reduce_mean_nd not supported by provider")
2769    }
2770    /// Reduce moments across multiple zero-based dimensions in one device pass.
2771    /// Returns mean (E[x]) and mean of squares (E[x^2]).
2772    fn reduce_moments_nd<'a>(
2773        &'a self,
2774        _a: &'a GpuTensorHandle,
2775        _dims_zero_based: &'a [usize],
2776    ) -> AccelProviderFuture<'a, ProviderMoments2> {
2777        unsupported_future("reduce_moments_nd not supported by provider")
2778    }
2779    fn reduce_mean_dim<'a>(
2780        &'a self,
2781        _a: &'a GpuTensorHandle,
2782        _dim: usize,
2783    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2784        unsupported_future("reduce_mean_dim not supported by provider")
2785    }
2786    fn reduce_std<'a>(
2787        &'a self,
2788        _a: &'a GpuTensorHandle,
2789        _normalization: ProviderStdNormalization,
2790        _nan_mode: ProviderNanMode,
2791    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2792        unsupported_future("reduce_std not supported by provider")
2793    }
2794    fn reduce_std_dim<'a>(
2795        &'a self,
2796        _a: &'a GpuTensorHandle,
2797        _dim: usize,
2798        _normalization: ProviderStdNormalization,
2799        _nan_mode: ProviderNanMode,
2800    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2801        unsupported_future("reduce_std_dim not supported by provider")
2802    }
2803    fn reduce_any<'a>(
2804        &'a self,
2805        _a: &'a GpuTensorHandle,
2806        _omit_nan: bool,
2807    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2808        unsupported_future("reduce_any not supported by provider")
2809    }
2810    fn reduce_any_dim<'a>(
2811        &'a self,
2812        _a: &'a GpuTensorHandle,
2813        _dim: usize,
2814        _omit_nan: bool,
2815    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2816        unsupported_future("reduce_any_dim not supported by provider")
2817    }
2818    fn reduce_all<'a>(
2819        &'a self,
2820        _a: &'a GpuTensorHandle,
2821        _omit_nan: bool,
2822    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2823        unsupported_future("reduce_all not supported by provider")
2824    }
2825    fn reduce_all_dim<'a>(
2826        &'a self,
2827        _a: &'a GpuTensorHandle,
2828        _dim: usize,
2829        _omit_nan: bool,
2830    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2831        unsupported_future("reduce_all_dim not supported by provider")
2832    }
2833    fn reduce_median<'a>(
2834        &'a self,
2835        _a: &'a GpuTensorHandle,
2836    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2837        unsupported_future("reduce_median not supported by provider")
2838    }
2839    fn reduce_median_dim<'a>(
2840        &'a self,
2841        _a: &'a GpuTensorHandle,
2842        _dim: usize,
2843    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2844        unsupported_future("reduce_median_dim not supported by provider")
2845    }
2846    fn mode_values<'a>(
2847        &'a self,
2848        _request: &'a ProviderModeRequest<'a>,
2849    ) -> AccelProviderFuture<'a, ProviderModeResult> {
2850        unsupported_future("mode_values not supported by provider")
2851    }
2852    fn moving_window<'a>(
2853        &'a self,
2854        _request: &'a ProviderMovingWindowRequest<'a>,
2855    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2856        unsupported_future("moving_window not supported by provider")
2857    }
2858    fn reduce_min<'a>(
2859        &'a self,
2860        _a: &'a GpuTensorHandle,
2861    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2862        unsupported_future("reduce_min not supported by provider")
2863    }
2864    fn reduce_min_dim<'a>(
2865        &'a self,
2866        _a: &'a GpuTensorHandle,
2867        _dim: usize,
2868    ) -> AccelProviderFuture<'a, ReduceDimResult> {
2869        unsupported_future("reduce_min_dim not supported by provider")
2870    }
2871    fn reduce_max<'a>(
2872        &'a self,
2873        _a: &'a GpuTensorHandle,
2874    ) -> AccelProviderFuture<'a, GpuTensorHandle> {
2875        unsupported_future("reduce_max not supported by provider")
2876    }
2877    fn reduce_max_dim<'a>(
2878        &'a self,
2879        _a: &'a GpuTensorHandle,
2880        _dim: usize,
2881    ) -> AccelProviderFuture<'a, ReduceDimResult> {
2882        unsupported_future("reduce_max_dim not supported by provider")
2883    }
2884    fn cumsum_scan(
2885        &self,
2886        _input: &GpuTensorHandle,
2887        _dim: usize,
2888        _direction: ProviderScanDirection,
2889        _nan_mode: ProviderNanMode,
2890    ) -> anyhow::Result<GpuTensorHandle> {
2891        Err(anyhow::anyhow!("cumsum_scan not supported by provider"))
2892    }
2893    fn trapz_dim(
2894        &self,
2895        _input: &GpuTensorHandle,
2896        _dim: usize,
2897        _spacing: ProviderTrapezoidSpacing<'_>,
2898    ) -> anyhow::Result<GpuTensorHandle> {
2899        Err(anyhow::anyhow!("trapz_dim not supported by provider"))
2900    }
2901    fn cumtrapz_dim(
2902        &self,
2903        _input: &GpuTensorHandle,
2904        _dim: usize,
2905        _spacing: ProviderTrapezoidSpacing<'_>,
2906    ) -> anyhow::Result<GpuTensorHandle> {
2907        Err(anyhow::anyhow!("cumtrapz_dim not supported by provider"))
2908    }
2909    fn cumprod_scan(
2910        &self,
2911        _input: &GpuTensorHandle,
2912        _dim: usize,
2913        _direction: ProviderScanDirection,
2914        _nan_mode: ProviderNanMode,
2915    ) -> anyhow::Result<GpuTensorHandle> {
2916        Err(anyhow::anyhow!("cumprod_scan not supported by provider"))
2917    }
2918    fn cummin_scan(
2919        &self,
2920        _input: &GpuTensorHandle,
2921        _dim: usize,
2922        _direction: ProviderScanDirection,
2923        _nan_mode: ProviderNanMode,
2924    ) -> anyhow::Result<ProviderCumminResult> {
2925        Err(anyhow::anyhow!("cummin_scan not supported by provider"))
2926    }
2927    fn cummax_scan(
2928        &self,
2929        _input: &GpuTensorHandle,
2930        _dim: usize,
2931        _direction: ProviderScanDirection,
2932        _nan_mode: ProviderNanMode,
2933    ) -> anyhow::Result<ProviderCummaxResult> {
2934        Err(anyhow::anyhow!("cummax_scan not supported by provider"))
2935    }
2936
2937    fn find(
2938        &self,
2939        _a: &GpuTensorHandle,
2940        _limit: Option<usize>,
2941        _direction: FindDirection,
2942    ) -> anyhow::Result<ProviderFindResult> {
2943        Err(anyhow::anyhow!("find not supported by provider"))
2944    }
2945
2946    fn fused_elementwise(
2947        &self,
2948        _shader: &str,
2949        _inputs: &[GpuTensorHandle],
2950        _output_shape: &[usize],
2951        _len: usize,
2952    ) -> anyhow::Result<GpuTensorHandle> {
2953        Err(anyhow::anyhow!(
2954            "fused_elementwise not supported by provider"
2955        ))
2956    }
2957
2958    /// Execute a single fused elementwise kernel that writes `num_outputs` output buffers in one
2959    /// dispatch. The shader is expected to declare `output0`, `output1`, … `output{N-1}` storage
2960    /// bindings (at binding indices `inputs.len()` through `inputs.len() + num_outputs - 1`) and a
2961    /// uniform `params` binding at `inputs.len() + num_outputs`.
2962    ///
2963    /// Providers that do not override this method fall back to calling `fused_elementwise` once
2964    /// per output, which preserves correctness at the cost of the O(N²) dispatch overhead this
2965    /// method is designed to eliminate.
2966    fn fused_elementwise_multi(
2967        &self,
2968        _shader: &str,
2969        _inputs: &[GpuTensorHandle],
2970        _output_shape: &[usize],
2971        _len: usize,
2972        _num_outputs: usize,
2973    ) -> anyhow::Result<Vec<GpuTensorHandle>> {
2974        Err(anyhow::anyhow!(
2975            "fused_elementwise_multi not supported by provider"
2976        ))
2977    }
2978
2979    /// Build a numeric tensor where NaNs in `a` are replaced with 0.0 (device side).
2980    fn map_nan_to_zero(&self, _a: &GpuTensorHandle) -> anyhow::Result<GpuTensorHandle> {
2981        Err(anyhow::anyhow!("map_nan_to_zero not supported by provider"))
2982    }
2983
2984    /// Build a numeric mask tensor with 1.0 where value is not NaN and 0.0 where value is NaN.
2985    fn not_nan_mask(&self, _a: &GpuTensorHandle) -> anyhow::Result<GpuTensorHandle> {
2986        Err(anyhow::anyhow!("not_nan_mask not supported by provider"))
2987    }
2988
2989    /// Generic fused reduction entrypoint.
2990    ///
2991    /// The shader is expected to implement a column-major reduction across `reduce_len` with
2992    /// `num_slices` independent slices (e.g., columns). Providers should create a uniform buffer
2993    /// compatible with the expected `Params/MParams` struct in the shader and dispatch
2994    /// `num_slices` workgroups with `workgroup_size` threads, or an equivalent strategy.
2995    #[allow(clippy::too_many_arguments)]
2996    fn fused_reduction(
2997        &self,
2998        _shader: &str,
2999        _inputs: &[GpuTensorHandle],
3000        _output_shape: &[usize],
3001        _reduce_len: usize,
3002        _num_slices: usize,
3003        _workgroup_size: u32,
3004        _flavor: ReductionFlavor,
3005    ) -> anyhow::Result<GpuTensorHandle> {
3006        Err(anyhow::anyhow!("fused_reduction not supported by provider"))
3007    }
3008
3009    /// Optionally pre-compile commonly used pipelines to amortize first-dispatch costs.
3010    fn warmup(&self) {}
3011
3012    /// Returns (cache_hits, cache_misses) for fused pipeline cache, if supported.
3013    fn fused_cache_counters(&self) -> (u64, u64) {
3014        (0, 0)
3015    }
3016
3017    /// Returns the duration of the last provider warmup in milliseconds, if known.
3018    fn last_warmup_millis(&self) -> Option<u64> {
3019        None
3020    }
3021
3022    /// Returns a snapshot of provider telemetry counters if supported.
3023    fn telemetry_snapshot(&self) -> ProviderTelemetry {
3024        let (hits, misses) = self.fused_cache_counters();
3025        ProviderTelemetry {
3026            fused_elementwise: ProviderDispatchStats::default(),
3027            fused_reduction: ProviderDispatchStats::default(),
3028            matmul: ProviderDispatchStats::default(),
3029            linsolve: ProviderDispatchStats::default(),
3030            mldivide: ProviderDispatchStats::default(),
3031            mrdivide: ProviderDispatchStats::default(),
3032            upload_bytes: 0,
3033            download_bytes: 0,
3034            solve_fallbacks: Vec::new(),
3035            fusion_cache_hits: hits,
3036            fusion_cache_misses: misses,
3037            bind_group_cache_hits: 0,
3038            bind_group_cache_misses: 0,
3039            bind_group_cache_by_layout: None,
3040            kernel_launches: Vec::new(),
3041        }
3042    }
3043
3044    /// Reset all telemetry counters maintained by the provider, if supported.
3045    fn reset_telemetry(&self) {}
3046
3047    /// Default reduction workgroup size the provider prefers.
3048    fn default_reduction_workgroup_size(&self) -> u32 {
3049        256
3050    }
3051
3052    /// Threshold above which provider will prefer two-pass reduction.
3053    fn two_pass_threshold(&self) -> usize {
3054        1024
3055    }
3056
3057    /// Current two-pass mode preference (auto/forced on/off).
3058    fn reduction_two_pass_mode(&self) -> ReductionTwoPassMode {
3059        ReductionTwoPassMode::Auto
3060    }
3061
3062    /// Fast-path: write a GPU column in a matrix from a GPU vector, returning a new handle.
3063    /// Expected: `values.shape == [rows, 1]` (or `[rows]`) and `col_index < cols`.
3064    fn scatter_column(
3065        &self,
3066        _matrix: &GpuTensorHandle,
3067        _col_index: usize,
3068        _values: &GpuTensorHandle,
3069    ) -> anyhow::Result<GpuTensorHandle> {
3070        Err(anyhow::anyhow!("scatter_column not supported by provider"))
3071    }
3072
3073    /// Fast-path: write a GPU row in a matrix from a GPU vector, returning a new handle.
3074    /// Expected: `values.shape == [1, cols]` (or `[cols]`) and `row_index < rows`.
3075    fn scatter_row(
3076        &self,
3077        _matrix: &GpuTensorHandle,
3078        _row_index: usize,
3079        _values: &GpuTensorHandle,
3080    ) -> anyhow::Result<GpuTensorHandle> {
3081        Err(anyhow::anyhow!("scatter_row not supported by provider"))
3082    }
3083
3084    fn sub2ind(
3085        &self,
3086        _dims: &[usize],
3087        _strides: &[usize],
3088        _inputs: &[&GpuTensorHandle],
3089        _scalar_mask: &[bool],
3090        _len: usize,
3091        _output_shape: &[usize],
3092    ) -> anyhow::Result<GpuTensorHandle> {
3093        Err(anyhow::anyhow!("sub2ind not supported by provider"))
3094    }
3095
3096    /// Returns true if the provider offers a device-side `ind2sub` implementation.
3097    fn supports_ind2sub(&self) -> bool {
3098        false
3099    }
3100
3101    /// Convert linear indices into per-dimension subscripts on the device.
3102    fn ind2sub(
3103        &self,
3104        _dims: &[usize],
3105        _strides: &[usize],
3106        _indices: &GpuTensorHandle,
3107        _total: usize,
3108        _len: usize,
3109        _output_shape: &[usize],
3110    ) -> anyhow::Result<Vec<GpuTensorHandle>> {
3111        Err(anyhow::anyhow!("ind2sub not supported by provider"))
3112    }
3113
3114    /// Determine if a matrix is symmetric (or skew-symmetric) without gathering it to the host.
3115    fn issymmetric(
3116        &self,
3117        _matrix: &GpuTensorHandle,
3118        _kind: ProviderSymmetryKind,
3119        _tolerance: f64,
3120    ) -> anyhow::Result<bool> {
3121        Err(anyhow::anyhow!(
3122            "issymmetric predicate not supported by provider"
3123        ))
3124    }
3125
3126    /// Determine if a matrix is Hermitian (or skew-Hermitian) without gathering it to the host.
3127    fn ishermitian<'a>(
3128        &'a self,
3129        _matrix: &'a GpuTensorHandle,
3130        _kind: ProviderHermitianKind,
3131        _tolerance: f64,
3132    ) -> AccelProviderFuture<'a, bool> {
3133        Box::pin(async move {
3134            Err(anyhow::anyhow!(
3135                "ishermitian predicate not supported by provider"
3136            ))
3137        })
3138    }
3139
3140    /// Inspect the bandwidth of a matrix without gathering it back to the host.
3141    fn bandwidth(&self, _matrix: &GpuTensorHandle) -> anyhow::Result<ProviderBandwidth> {
3142        Err(anyhow::anyhow!("bandwidth not supported by provider"))
3143    }
3144
3145    /// Compute the symmetric reverse Cuthill-McKee permutation for the matrix.
3146    ///
3147    /// Implementations may execute on the device or gather to the host. The permutation should be
3148    /// returned as zero-based indices.
3149    fn sym_rcm<'a>(&'a self, _matrix: &'a GpuTensorHandle) -> AccelProviderFuture<'a, Vec<usize>> {
3150        Box::pin(async move { Err(anyhow::anyhow!("sym_rcm not supported by provider")) })
3151    }
3152}
3153
3154static GLOBAL_PROVIDER: Lazy<RwLock<Option<&'static dyn AccelProvider>>> =
3155    Lazy::new(|| RwLock::new(None));
3156static PROVIDER_REGISTRY: Lazy<RwLock<HashMap<u32, &'static dyn AccelProvider>>> =
3157    Lazy::new(|| RwLock::new(HashMap::new()));
3158static DEVICE_ID_COUNTER: AtomicU32 = AtomicU32::new(1);
3159
3160#[cfg(not(target_arch = "wasm32"))]
3161thread_local! {
3162    static THREAD_PROVIDER: Cell<Option<&'static dyn AccelProvider>> = Cell::new(None);
3163}
3164
3165#[cfg(target_arch = "wasm32")]
3166static WASM_THREAD_PROVIDER: Lazy<Mutex<Option<&'static dyn AccelProvider>>> =
3167    Lazy::new(|| Mutex::new(None));
3168
3169#[cfg(not(target_arch = "wasm32"))]
3170fn replace_thread_provider(
3171    provider: Option<&'static dyn AccelProvider>,
3172) -> Option<&'static dyn AccelProvider> {
3173    THREAD_PROVIDER.with(|cell| {
3174        let prev = cell.get();
3175        cell.set(provider);
3176        prev
3177    })
3178}
3179
3180#[cfg(target_arch = "wasm32")]
3181fn replace_thread_provider(
3182    provider: Option<&'static dyn AccelProvider>,
3183) -> Option<&'static dyn AccelProvider> {
3184    let mut slot = WASM_THREAD_PROVIDER
3185        .lock()
3186        .expect("wasm provider mutex poisoned");
3187    let prev = *slot;
3188    *slot = provider;
3189    prev
3190}
3191
3192#[cfg(not(target_arch = "wasm32"))]
3193fn current_thread_provider() -> Option<&'static dyn AccelProvider> {
3194    THREAD_PROVIDER.with(|cell| cell.get())
3195}
3196
3197#[cfg(target_arch = "wasm32")]
3198fn current_thread_provider() -> Option<&'static dyn AccelProvider> {
3199    WASM_THREAD_PROVIDER
3200        .lock()
3201        .expect("wasm provider mutex poisoned")
3202        .as_ref()
3203        .copied()
3204}
3205
3206/// Register a global acceleration provider.
3207///
3208/// # Safety
3209/// - The caller must guarantee that `p` is valid for the entire program lifetime
3210///   (e.g., a `'static` singleton), as the runtime stores a raw reference globally.
3211/// - Concurrent callers must ensure registration happens once or is properly
3212///   synchronized; this function does not enforce thread-safety for re-registration.
3213pub unsafe fn register_provider(p: &'static dyn AccelProvider) {
3214    replace_thread_provider(Some(p));
3215    if let Ok(mut guard) = GLOBAL_PROVIDER.write() {
3216        *guard = Some(p);
3217    }
3218    register_provider_for_device(p.device_id(), p);
3219}
3220
3221unsafe fn register_provider_for_device(device_id: u32, provider: &'static dyn AccelProvider) {
3222    if let Ok(mut guard) = PROVIDER_REGISTRY.write() {
3223        guard.insert(device_id, provider);
3224    }
3225}
3226
3227pub fn provider() -> Option<&'static dyn AccelProvider> {
3228    if let Some(p) = current_thread_provider() {
3229        return Some(p);
3230    }
3231    GLOBAL_PROVIDER
3232        .read()
3233        .ok()
3234        .and_then(|guard| guard.as_ref().copied())
3235}
3236
3237/// Clear the active provider selection without invalidating providers that own live handles.
3238///
3239/// Registered device owners are retained because a [`GpuTensorHandle`] may outlive its provider's
3240/// tenure as the global default. Removing its owner here would make that otherwise-valid handle
3241/// impossible to gather or operate on. Tests that need a different default can safely call this
3242/// function and register another provider; device ids keep the handle namespaces distinct.
3243pub fn clear_provider() {
3244    replace_thread_provider(None);
3245    if let Ok(mut guard) = GLOBAL_PROVIDER.write() {
3246        *guard = None;
3247    }
3248}
3249
3250pub fn provider_for_device(device_id: u32) -> Option<&'static dyn AccelProvider> {
3251    if let Some(registered) = PROVIDER_REGISTRY
3252        .read()
3253        .ok()
3254        .and_then(|guard| guard.get(&device_id).copied())
3255    {
3256        return Some(registered);
3257    }
3258    if let Some(thread_provider) = current_thread_provider() {
3259        if thread_provider.device_id() == device_id {
3260            return Some(thread_provider);
3261        }
3262    }
3263    // Preserve legacy behavior: when no explicit per-device registration exists,
3264    // fall back to the globally active provider regardless of handle device id.
3265    GLOBAL_PROVIDER
3266        .read()
3267        .ok()
3268        .and_then(|guard| guard.as_ref().copied())
3269}
3270
3271pub fn provider_for_handle(handle: &GpuTensorHandle) -> Option<&'static dyn AccelProvider> {
3272    provider_for_device(handle.device_id)
3273}
3274
3275pub fn spawn_handle_concurrency_for(handle: &GpuTensorHandle) -> Option<SpawnHandleConcurrency> {
3276    provider_for_handle(handle).map(AccelProvider::spawn_handle_concurrency)
3277}
3278
3279pub fn next_device_id() -> u32 {
3280    DEVICE_ID_COUNTER.fetch_add(1, Ordering::Relaxed)
3281}
3282
3283pub struct ThreadProviderGuard {
3284    prev: Option<&'static dyn AccelProvider>,
3285}
3286
3287impl ThreadProviderGuard {
3288    pub fn set(provider: Option<&'static dyn AccelProvider>) -> Self {
3289        let prev = replace_thread_provider(provider);
3290        ThreadProviderGuard { prev }
3291    }
3292}
3293
3294impl Drop for ThreadProviderGuard {
3295    fn drop(&mut self) {
3296        let prev = self.prev.take();
3297        replace_thread_provider(prev);
3298    }
3299}
3300
3301pub fn set_thread_provider(provider: Option<&'static dyn AccelProvider>) {
3302    replace_thread_provider(provider);
3303}
3304
3305/// Convenience: perform elementwise add via provider if possible; otherwise return None
3306pub async fn try_elem_add(a: &GpuTensorHandle, b: &GpuTensorHandle) -> Option<GpuTensorHandle> {
3307    if a.device_id == b.device_id {
3308        let p = provider_for_handle(a)?;
3309        if let Ok(h) = p.elem_add(a, b).await {
3310            return Some(h);
3311        }
3312    }
3313    None
3314}
3315
3316/// Convenience: perform elementwise hypot via provider if possible; otherwise return None
3317pub async fn try_elem_hypot(a: &GpuTensorHandle, b: &GpuTensorHandle) -> Option<GpuTensorHandle> {
3318    if a.device_id == b.device_id {
3319        let p = provider_for_handle(a)?;
3320        if let Ok(h) = p.elem_hypot(a, b).await {
3321            return Some(h);
3322        }
3323    }
3324    None
3325}
3326
3327/// Convenience: perform elementwise max via provider if possible; otherwise return None
3328pub async fn try_elem_max(a: &GpuTensorHandle, b: &GpuTensorHandle) -> Option<GpuTensorHandle> {
3329    if a.device_id == b.device_id {
3330        let p = provider_for_handle(a)?;
3331        if let Ok(h) = p.elem_max(a, b).await {
3332            return Some(h);
3333        }
3334    }
3335    None
3336}
3337
3338/// Convenience: perform elementwise min via provider if possible; otherwise return None
3339pub async fn try_elem_min(a: &GpuTensorHandle, b: &GpuTensorHandle) -> Option<GpuTensorHandle> {
3340    if a.device_id == b.device_id {
3341        let p = provider_for_handle(a)?;
3342        if let Ok(h) = p.elem_min(a, b).await {
3343            return Some(h);
3344        }
3345    }
3346    None
3347}
3348
3349/// Convenience: perform elementwise atan2 via provider if possible; otherwise return None
3350pub async fn try_elem_atan2(y: &GpuTensorHandle, x: &GpuTensorHandle) -> Option<GpuTensorHandle> {
3351    if y.device_id == x.device_id {
3352        let p = provider_for_handle(y)?;
3353        if let Ok(h) = p.elem_atan2(y, x).await {
3354            return Some(h);
3355        }
3356    }
3357    None
3358}
3359
3360// Minimal host tensor views to avoid depending on runmat-builtins and cycles
3361#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3362pub struct HostTensorOwned {
3363    pub data: Vec<f64>,
3364    pub shape: Vec<usize>,
3365    pub storage: GpuTensorStorage,
3366}
3367
3368#[derive(Debug)]
3369pub struct HostTensorView<'a> {
3370    pub data: &'a [f64],
3371    pub shape: &'a [usize],
3372}
3373
3374/// Lightweight 1-D axis view used by provider meshgrid hooks.
3375#[derive(Debug)]
3376pub struct MeshgridAxisView<'a> {
3377    pub data: &'a [f64],
3378}
3379
3380/// Provider-side meshgrid result containing coordinate tensor handles.
3381#[derive(Debug, Clone)]
3382pub struct ProviderMeshgridResult {
3383    pub outputs: Vec<GpuTensorHandle>,
3384}
3385
3386/// Single resident GPU axis supplied to provider-side `ndgrid`.
3387#[derive(Debug, Clone, Copy)]
3388pub struct ProviderNdgridAxis<'a> {
3389    pub handle: &'a GpuTensorHandle,
3390}
3391
3392/// Provider-side `ndgrid` request. `output_shape` is already MATLAB-normalized
3393/// by the runtime, including the single-axis `[n, 1]` case.
3394#[derive(Debug, Clone, Copy)]
3395pub struct ProviderNdgridRequest<'a> {
3396    pub axes: &'a [ProviderNdgridAxis<'a>],
3397    pub output_shape: &'a [usize],
3398    pub output_count: usize,
3399}
3400
3401/// Provider-side ndgrid result containing coordinate tensor handles.
3402#[derive(Debug, Clone)]
3403pub struct ProviderNdgridResult {
3404    pub outputs: Vec<GpuTensorHandle>,
3405}
3406
3407/// Single broadcastable resident GPU input supplied to provider-side Black-Scholes pricing.
3408#[derive(Debug, Clone, Copy)]
3409pub struct ProviderBlackScholesPriceInput<'a> {
3410    pub handle: &'a GpuTensorHandle,
3411    /// Input shape aligned to `output_shape` rank using MATLAB implicit-expansion rules.
3412    pub shape: &'a [usize],
3413    /// Column-major strides for `shape`.
3414    pub strides: &'a [usize],
3415}
3416
3417/// Provider-side Black-Scholes price request. Inputs are ordered as
3418/// Price, Strike, Rate, Time, Volatility, Yield.
3419#[derive(Debug, Clone, Copy)]
3420pub struct ProviderBlackScholesPriceRequest<'a> {
3421    pub inputs: &'a [ProviderBlackScholesPriceInput<'a>],
3422    pub output_shape: &'a [usize],
3423    pub len: usize,
3424}
3425
3426/// Provider-side Black-Scholes price result containing call and put handles.
3427#[derive(Debug, Clone)]
3428pub struct ProviderBlackScholesPriceResult {
3429    pub call: GpuTensorHandle,
3430    pub put: GpuTensorHandle,
3431}
3432
3433/// Provider-side covariance-to-correlation result containing the correlation
3434/// matrix and column-vector standard deviations.
3435#[derive(Debug, Clone)]
3436pub struct ProviderCovarianceToCorrelationResult {
3437    pub correlation: GpuTensorHandle,
3438    pub sigma: GpuTensorHandle,
3439}
3440
3441/// Provider-side Adam optimizer update request.
3442#[derive(Debug, Clone, Copy)]
3443pub struct ProviderAdamUpdateRequest<'a> {
3444    pub parameters: &'a GpuTensorHandle,
3445    pub gradient: &'a GpuTensorHandle,
3446    pub average_grad: Option<&'a GpuTensorHandle>,
3447    pub average_sq_grad: Option<&'a GpuTensorHandle>,
3448    pub iteration: usize,
3449    pub learn_rate: f64,
3450    pub gradient_decay_factor: f64,
3451    pub squared_gradient_decay_factor: f64,
3452    pub epsilon: f64,
3453}
3454
3455/// Provider-side Adam optimizer update result.
3456#[derive(Debug, Clone)]
3457pub struct ProviderAdamUpdateResult {
3458    pub parameters: GpuTensorHandle,
3459    pub average_grad: GpuTensorHandle,
3460    pub average_sq_grad: GpuTensorHandle,
3461}
3462
3463/// Provider-side cross-entropy mode.
3464#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3465pub enum ProviderCrossentropyMode {
3466    SingleLabel,
3467    MultiLabel,
3468}
3469
3470/// Provider-side cross-entropy request.
3471#[derive(Debug, Clone, Copy)]
3472pub struct ProviderCrossentropyRequest<'a> {
3473    pub predictions: &'a GpuTensorHandle,
3474    pub targets: &'a GpuTensorHandle,
3475    pub weights: Option<&'a GpuTensorHandle>,
3476    pub mask: Option<&'a GpuTensorHandle>,
3477    pub mode: ProviderCrossentropyMode,
3478}
3479
3480/// Provider-side cross-entropy result containing per-element loss terms.
3481#[derive(Debug, Clone)]
3482pub struct ProviderCrossentropyResult {
3483    pub losses: GpuTensorHandle,
3484}
3485
3486/// Descriptor for GEMM epilogues applied to `C = A * B` before storing to `C`.
3487///
3488/// Supported operations:
3489/// - Scale by `alpha` and add scalar `beta`.
3490/// - Multiply output by per-row and/or per-column scale vectors (broadcasted).
3491#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3492pub enum ScaleOp {
3493    Multiply,
3494    Divide,
3495}
3496
3497#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3498pub struct MatmulEpilogue {
3499    /// Scalar multiply applied to each output element.
3500    pub alpha: f64,
3501    /// Scalar add applied to each output element after scaling.
3502    pub beta: f64,
3503    /// Optional per-row scale (length m). When present, output[row, col] *= row_scale[row].
3504    pub row_scale: Option<GpuTensorHandle>,
3505    /// Optional per-column scale (length n). When present, output[row, col] *= col_scale[col].
3506    pub col_scale: Option<GpuTensorHandle>,
3507    /// Row scale operation (multiply or divide). Ignored when `row_scale` is None.
3508    pub row_op: ScaleOp,
3509    /// Column scale operation (multiply or divide). Ignored when `col_scale` is None.
3510    pub col_op: ScaleOp,
3511    /// Optional lower clamp bound applied after scale/bias.
3512    #[serde(default)]
3513    pub clamp_min: Option<f64>,
3514    /// Optional upper clamp bound applied after scale/bias.
3515    #[serde(default)]
3516    pub clamp_max: Option<f64>,
3517    /// Optional power exponent applied after clamp (final operation in the epilogue).
3518    #[serde(default)]
3519    pub pow_exponent: Option<f64>,
3520    /// Optional output buffer for the diagonal of the result (length min(m, n)).
3521    #[serde(default)]
3522    pub diag_output: Option<GpuTensorHandle>,
3523}
3524
3525impl MatmulEpilogue {
3526    pub fn noop() -> Self {
3527        Self {
3528            alpha: 1.0,
3529            beta: 0.0,
3530            row_scale: None,
3531            col_scale: None,
3532            row_op: ScaleOp::Multiply,
3533            col_op: ScaleOp::Multiply,
3534            clamp_min: None,
3535            clamp_max: None,
3536            pow_exponent: None,
3537            diag_output: None,
3538        }
3539    }
3540    pub fn is_noop(&self) -> bool {
3541        self.alpha == 1.0
3542            && self.beta == 0.0
3543            && self.row_scale.is_none()
3544            && self.col_scale.is_none()
3545            && self.clamp_min.is_none()
3546            && self.clamp_max.is_none()
3547            && self.pow_exponent.is_none()
3548            && self.diag_output.is_none()
3549    }
3550}
3551
3552#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
3553pub struct PowerStepEpilogue {
3554    pub epsilon: f64,
3555}
3556
3557impl Default for PowerStepEpilogue {
3558    fn default() -> Self {
3559        Self { epsilon: 0.0 }
3560    }
3561}
3562
3563#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
3564pub struct ImageNormalizeDescriptor {
3565    pub batch: usize,
3566    pub height: usize,
3567    pub width: usize,
3568    pub epsilon: f64,
3569    #[serde(default)]
3570    pub gain: Option<f64>,
3571    #[serde(default)]
3572    pub bias: Option<f64>,
3573    #[serde(default)]
3574    pub gamma: Option<f64>,
3575    #[serde(default = "default_image_normalize_clamp_zero")]
3576    pub clamp_zero: bool,
3577}
3578
3579fn default_image_normalize_clamp_zero() -> bool {
3580    true
3581}
3582
3583#[cfg(test)]
3584mod tests {
3585    use super::*;
3586
3587    struct TestProvider {
3588        device_id: u32,
3589        name: &'static str,
3590        spawn_concurrency: SpawnHandleConcurrency,
3591    }
3592
3593    impl AccelProvider for TestProvider {
3594        fn upload(&self, _host: &HostTensorView) -> anyhow::Result<GpuTensorHandle> {
3595            Err(anyhow!("test provider upload should not be called"))
3596        }
3597
3598        fn download<'a>(&'a self, _h: &'a GpuTensorHandle) -> AccelDownloadFuture<'a> {
3599            unsupported_future("test provider download should not be called")
3600        }
3601
3602        fn free(&self, _h: &GpuTensorHandle) -> anyhow::Result<()> {
3603            Err(anyhow!("test provider free should not be called"))
3604        }
3605
3606        fn device_info(&self) -> String {
3607            self.name.to_string()
3608        }
3609
3610        fn device_id(&self) -> u32 {
3611            self.device_id
3612        }
3613
3614        fn spawn_handle_concurrency(&self) -> SpawnHandleConcurrency {
3615            self.spawn_concurrency
3616        }
3617    }
3618
3619    static PROVIDER_TEST_LOCK: Lazy<std::sync::Mutex<()>> = Lazy::new(|| std::sync::Mutex::new(()));
3620    static PROVIDER_A: TestProvider = TestProvider {
3621        device_id: 101,
3622        name: "provider-a",
3623        spawn_concurrency: SpawnHandleConcurrency::ImmutableShare,
3624    };
3625    static PROVIDER_B: TestProvider = TestProvider {
3626        device_id: 202,
3627        name: "provider-b",
3628        spawn_concurrency: SpawnHandleConcurrency::Reject,
3629    };
3630    static PROVIDER_C: TestProvider = TestProvider {
3631        device_id: 303,
3632        name: "provider-c",
3633        spawn_concurrency: SpawnHandleConcurrency::CopyOnWrite,
3634    };
3635
3636    fn register_test_providers() {
3637        clear_provider();
3638        unsafe {
3639            register_provider(&PROVIDER_A);
3640            register_provider(&PROVIDER_B);
3641        }
3642    }
3643
3644    fn test_handle(device_id: u32) -> GpuTensorHandle {
3645        GpuTensorHandle {
3646            shape: vec![1],
3647            device_id,
3648            buffer_id: 42,
3649        }
3650    }
3651
3652    #[test]
3653    fn handle_metadata_is_namespaced_by_device_and_buffer() {
3654        let first = test_handle(PROVIDER_A.device_id());
3655        let second = test_handle(PROVIDER_B.device_id());
3656        assert_eq!(first.buffer_id, second.buffer_id);
3657
3658        set_handle_precision(&first, ProviderPrecision::F32);
3659        set_handle_precision(&second, ProviderPrecision::F64);
3660        set_handle_class_name(&first, "single");
3661        set_handle_class_name(&second, "uint64");
3662        set_handle_storage(&first, GpuTensorStorage::Real);
3663        set_handle_storage(&second, GpuTensorStorage::ComplexInterleaved);
3664        set_handle_logical(&first, true);
3665        record_handle_transpose(&second, 2, 3);
3666
3667        assert_eq!(handle_precision(&first), Some(ProviderPrecision::F32));
3668        assert_eq!(handle_precision(&second), Some(ProviderPrecision::F64));
3669        assert_eq!(handle_class_name(&first).as_deref(), Some("single"));
3670        assert_eq!(handle_class_name(&second).as_deref(), Some("uint64"));
3671        assert_eq!(handle_storage(&first), GpuTensorStorage::Real);
3672        assert_eq!(
3673            handle_storage(&second),
3674            GpuTensorStorage::ComplexInterleaved
3675        );
3676        assert!(handle_is_logical(&first));
3677        assert!(!handle_is_logical(&second));
3678        assert!(handle_transpose_info(&first).is_none());
3679        assert_eq!(
3680            handle_transpose_info(&second),
3681            Some(TransposeInfo {
3682                base_rows: 2,
3683                base_cols: 3
3684            })
3685        );
3686
3687        clear_handle_precision(&first);
3688        clear_handle_precision(&second);
3689        clear_handle_class_name(&first);
3690        clear_handle_class_name(&second);
3691        clear_handle_storage(&first);
3692        clear_handle_storage(&second);
3693        clear_handle_logical(&first);
3694        clear_handle_transpose(&second);
3695    }
3696
3697    fn spectral_request<'a>(
3698        input: &'a GpuTensorHandle,
3699        frame_mode: ProviderSpectralFrameMode,
3700    ) -> ProviderSpectralRequest<'a> {
3701        static WINDOW: [f64; 4] = [1.0, 1.0, 1.0, 1.0];
3702        ProviderSpectralRequest {
3703            input,
3704            input_len: 16,
3705            input_complex: false,
3706            window: &WINDOW,
3707            nfft: 8,
3708            frame_count: 3,
3709            frame_mode,
3710            range: ProviderSpectralRange::Onesided,
3711            denominator: 1.0,
3712        }
3713    }
3714
3715    #[test]
3716    fn provider_envelope_shape_guard_rejects_equal_len_layout_spoofing() {
3717        assert!(provider_envelope_input_shape_matches(&[2, 3], 2, 3));
3718        assert!(provider_envelope_input_shape_matches(&[6, 1], 6, 1));
3719        assert!(provider_envelope_input_shape_matches(&[1, 6], 6, 1));
3720        assert!(provider_envelope_input_shape_matches(&[6], 6, 1));
3721
3722        assert!(!provider_envelope_input_shape_matches(&[3, 2], 2, 3));
3723        assert!(!provider_envelope_input_shape_matches(&[6], 2, 3));
3724        assert!(!provider_envelope_input_shape_matches(&[2, 1, 3], 2, 3));
3725    }
3726
3727    #[test]
3728    fn provider_for_device_prefers_registered_device_over_thread_provider() {
3729        let _lock = PROVIDER_TEST_LOCK
3730            .lock()
3731            .expect("provider test lock poisoned");
3732        register_test_providers();
3733        let _thread_provider = ThreadProviderGuard::set(Some(&PROVIDER_B));
3734
3735        let provider = provider_for_device(PROVIDER_A.device_id()).expect("provider for device");
3736
3737        assert_eq!(provider.device_info(), PROVIDER_A.name);
3738        clear_provider();
3739    }
3740
3741    #[test]
3742    fn provider_for_handle_uses_handle_device_owner() {
3743        let _lock = PROVIDER_TEST_LOCK
3744            .lock()
3745            .expect("provider test lock poisoned");
3746        register_test_providers();
3747        let _thread_provider = ThreadProviderGuard::set(Some(&PROVIDER_B));
3748
3749        let provider =
3750            provider_for_handle(&test_handle(PROVIDER_A.device_id())).expect("provider for handle");
3751
3752        assert_eq!(provider.device_info(), PROVIDER_A.name);
3753        clear_provider();
3754    }
3755
3756    #[test]
3757    fn clearing_active_provider_preserves_live_handle_owner() {
3758        let _lock = PROVIDER_TEST_LOCK
3759            .lock()
3760            .expect("provider test lock poisoned");
3761        register_test_providers();
3762        let handle = test_handle(PROVIDER_A.device_id());
3763
3764        clear_provider();
3765
3766        assert!(provider().is_none());
3767        let owner = provider_for_handle(&handle).expect("registered handle owner survives clear");
3768        assert_eq!(owner.device_info(), PROVIDER_A.name);
3769    }
3770
3771    #[test]
3772    fn concurrent_registration_keeps_each_threads_active_provider() {
3773        let _lock = PROVIDER_TEST_LOCK
3774            .lock()
3775            .expect("provider test lock poisoned");
3776        clear_provider();
3777
3778        let first = std::thread::spawn(|| {
3779            unsafe { register_provider(&PROVIDER_A) };
3780            std::thread::yield_now();
3781            provider().map(AccelProvider::device_info)
3782        });
3783        let second = std::thread::spawn(|| {
3784            unsafe { register_provider(&PROVIDER_B) };
3785            std::thread::yield_now();
3786            provider().map(AccelProvider::device_info)
3787        });
3788
3789        assert_eq!(
3790            first.join().expect("first registration thread"),
3791            Some(PROVIDER_A.name.to_string())
3792        );
3793        assert_eq!(
3794            second.join().expect("second registration thread"),
3795            Some(PROVIDER_B.name.to_string())
3796        );
3797        clear_provider();
3798    }
3799
3800    #[test]
3801    fn spawn_handle_concurrency_for_uses_registered_owner() {
3802        let _lock = PROVIDER_TEST_LOCK
3803            .lock()
3804            .expect("provider test lock poisoned");
3805        register_test_providers();
3806        let _thread_provider = ThreadProviderGuard::set(Some(&PROVIDER_B));
3807
3808        let concurrency = spawn_handle_concurrency_for(&test_handle(PROVIDER_A.device_id()))
3809            .expect("spawn concurrency");
3810
3811        assert_eq!(concurrency, PROVIDER_A.spawn_concurrency);
3812        clear_provider();
3813    }
3814
3815    #[test]
3816    fn provider_keeps_thread_local_active_provider_semantics() {
3817        let _lock = PROVIDER_TEST_LOCK
3818            .lock()
3819            .expect("provider test lock poisoned");
3820        register_test_providers();
3821        let _thread_provider = ThreadProviderGuard::set(Some(&PROVIDER_A));
3822
3823        let active = provider().expect("active provider");
3824
3825        assert_eq!(active.device_info(), PROVIDER_A.name);
3826        clear_provider();
3827    }
3828
3829    #[test]
3830    fn unregistered_thread_provider_only_matches_own_device_before_global_fallback() {
3831        let _lock = PROVIDER_TEST_LOCK
3832            .lock()
3833            .expect("provider test lock poisoned");
3834        clear_provider();
3835        unsafe {
3836            register_provider(&PROVIDER_A);
3837        }
3838        let _thread_provider = ThreadProviderGuard::set(Some(&PROVIDER_C));
3839
3840        let own_device = provider_for_device(PROVIDER_C.device_id()).expect("own provider");
3841        let fallback = provider_for_device(404).expect("global fallback provider");
3842
3843        assert_eq!(own_device.device_info(), PROVIDER_C.name);
3844        assert_eq!(fallback.device_info(), PROVIDER_A.name);
3845        clear_provider();
3846    }
3847
3848    #[test]
3849    fn uniform_spectral_request_validates_sliding_input_coverage() {
3850        let input = test_handle(PROVIDER_A.device_id());
3851        let mut request = spectral_request(&input, ProviderSpectralFrameMode::Sliding { hop: 6 });
3852        assert!(validate_uniform_spectral_request(&request).is_ok());
3853
3854        request.input_len = 15;
3855        assert!(validate_uniform_spectral_request(&request).is_err());
3856    }
3857
3858    #[test]
3859    fn uniform_spectral_request_rejects_sliding_coverage_overflow() {
3860        let input = test_handle(PROVIDER_A.device_id());
3861        let mut request = spectral_request(&input, ProviderSpectralFrameMode::Sliding { hop: 2 });
3862        request.frame_count = usize::MAX;
3863
3864        assert!(validate_uniform_spectral_request(&request).is_err());
3865    }
3866
3867    #[test]
3868    fn uniform_spectral_request_validates_folded_input_coverage() {
3869        let input = test_handle(PROVIDER_A.device_id());
3870        let mut request = spectral_request(
3871            &input,
3872            ProviderSpectralFrameMode::FoldedColumns { input_rows: 5 },
3873        );
3874        assert!(validate_uniform_spectral_request(&request).is_ok());
3875
3876        request.frame_mode = ProviderSpectralFrameMode::FoldedColumns { input_rows: 0 };
3877        assert!(validate_uniform_spectral_request(&request).is_err());
3878
3879        request.frame_mode = ProviderSpectralFrameMode::FoldedColumns { input_rows: 6 };
3880        assert!(validate_uniform_spectral_request(&request).is_err());
3881    }
3882
3883    #[test]
3884    fn uniform_spectral_request_rejects_folded_coverage_overflow() {
3885        let input = test_handle(PROVIDER_A.device_id());
3886        let request = spectral_request(
3887            &input,
3888            ProviderSpectralFrameMode::FoldedColumns {
3889                input_rows: usize::MAX,
3890            },
3891        );
3892
3893        assert!(validate_uniform_spectral_request(&request).is_err());
3894    }
3895
3896    #[test]
3897    fn image_normalize_descriptor_omitted_clamp_zero_defaults_true() {
3898        let payload = r#"{
3899            "batch": 2,
3900            "height": 4,
3901            "width": 5,
3902            "epsilon": 0.000001
3903        }"#;
3904
3905        let desc: ImageNormalizeDescriptor =
3906            serde_json::from_str(payload).expect("deserialize descriptor");
3907
3908        assert!(
3909            desc.clamp_zero,
3910            "legacy serialized descriptors should default to clamped image normalize"
3911        );
3912    }
3913
3914    #[test]
3915    fn image_normalize_descriptor_explicit_false_preserves_unclamped() {
3916        let payload = r#"{
3917            "batch": 2,
3918            "height": 4,
3919            "width": 5,
3920            "epsilon": 0.000001,
3921            "clamp_zero": false
3922        }"#;
3923
3924        let desc: ImageNormalizeDescriptor =
3925            serde_json::from_str(payload).expect("deserialize descriptor");
3926
3927        assert!(
3928            !desc.clamp_zero,
3929            "explicit clamp_zero=false should preserve unclamped semantics"
3930        );
3931    }
3932}