Skip to main content

diffsol_la/vector/
mod.rs

1use crate::matrix::DenseMatrix;
2use crate::scalar::Scale;
3use crate::{Context, IndexType, Scalar};
4use num_traits::Zero;
5use std::fmt::Debug;
6use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Sub, SubAssign};
7
8#[cfg(feature = "faer")]
9pub mod faer_serial;
10#[cfg(feature = "nalgebra")]
11pub mod nalgebra_serial;
12
13#[cfg(feature = "cuda")]
14pub mod cuda;
15
16#[cfg(feature = "cuda-oxide")]
17pub mod cuda_oxide;
18
19/// A trait for types that represent a collection of indices into a vector.
20///
21/// This is used to represent subsets of vector elements, typically for algebraic constraints
22/// or when operating on specific vector components.
23pub trait VectorIndex: Sized + Debug + Clone {
24    type C: Context;
25    fn context(&self) -> &Self::C;
26    fn zeros(len: IndexType, ctx: Self::C) -> Self;
27    fn len(&self) -> IndexType;
28    fn clone_as_vec(&self) -> Vec<IndexType>;
29    fn is_empty(&self) -> bool {
30        self.len() == 0
31    }
32    fn from_vec(v: Vec<IndexType>, ctx: Self::C) -> Self;
33}
34
35/// Common interface for vector-like types, providing access to scalar type, context, and inner representation.
36pub trait VectorCommon: Sized + Debug {
37    type T: Scalar;
38    type C: Context;
39    type Inner;
40
41    fn inner(&self) -> &Self::Inner;
42}
43
44impl<V> VectorCommon for &V
45where
46    V: VectorCommon,
47{
48    type T = V::T;
49    type C = V::C;
50    type Inner = V::Inner;
51    fn inner(&self) -> &Self::Inner {
52        V::inner(self)
53    }
54}
55
56impl<V> VectorCommon for &mut V
57where
58    V: VectorCommon,
59{
60    type T = V::T;
61    type C = V::C;
62    type Inner = V::Inner;
63    fn inner(&self) -> &Self::Inner {
64        V::inner(self)
65    }
66}
67
68/// Operations on vectors by value (addition and subtraction).
69///
70/// This trait defines vector addition and subtraction when both operands are owned or references.
71pub trait VectorOpsByValue<Rhs = Self, Output = Self>:
72    VectorCommon + Add<Rhs, Output = Output> + Sub<Rhs, Output = Output>
73{
74}
75
76impl<V, Rhs, Output> VectorOpsByValue<Rhs, Output> for V where
77    V: VectorCommon + Add<Rhs, Output = Output> + Sub<Rhs, Output = Output>
78{
79}
80
81/// In-place operations on vectors (addition and subtraction).
82///
83/// This trait defines in-place vector addition and subtraction (self += rhs, self -= rhs).
84pub trait VectorMutOpsByValue<Rhs = Self>: VectorCommon + AddAssign<Rhs> + SubAssign<Rhs> {}
85
86impl<V, Rhs> VectorMutOpsByValue<Rhs> for V where V: VectorCommon + AddAssign<Rhs> + SubAssign<Rhs> {}
87
88/// Operations on a reference to a vector, supporting addition, subtraction, and scalar multiplication.
89///
90/// This trait ensures that vector references can be used in arithmetic operations with owned vectors,
91/// other references, and vector views, enabling flexible composition of vector operations.
92pub trait VectorRef<V: Vector>:
93    VectorOpsByValue<V, V>
94    + for<'a> VectorOpsByValue<&'a V, V>
95    + for<'a> VectorOpsByValue<V::View<'a>, V>
96    + for<'a, 'b> VectorOpsByValue<&'a V::View<'b>, V>
97    + Mul<Scale<V::T>, Output = V>
98{
99}
100
101impl<RefT, V: Vector> VectorRef<V> for RefT where
102    RefT: VectorOpsByValue<V, V>
103        + for<'a> VectorOpsByValue<&'a V, V>
104        + for<'a> VectorOpsByValue<V::View<'a>, V>
105        + for<'a, 'b> VectorOpsByValue<&'a V::View<'b>, V>
106        + Mul<Scale<V::T>, Output = V>
107{
108}
109
110/// A mutable view into a vector, supporting in-place operations and modifications.
111///
112/// This trait represents a temporary mutable reference to a vector's data, allowing in-place
113/// arithmetic operations (+=, -=, *=) and other modifications. Mutable views can be created
114/// via the `as_view_mut()` method on a `Vector`.
115pub trait VectorViewMut<'a>:
116    VectorMutOpsByValue<Self::View>
117    + VectorMutOpsByValue<Self::Owned>
118    + for<'b> VectorMutOpsByValue<&'b Self::View>
119    + for<'b> VectorMutOpsByValue<&'b Self::Owned>
120    + MulAssign<Scale<Self::T>>
121{
122    type Owned;
123    type View;
124    type Index: VectorIndex;
125    /// Copy values from an owned vector into this view.
126    fn copy_from(&mut self, other: &Self::Owned);
127    /// Copy values from another vector view into this view.
128    fn copy_from_view(&mut self, other: &Self::View);
129    /// Compute the AXPY operation: self = alpha * x + beta * self
130    fn axpy(&mut self, alpha: Self::T, x: &Self::Owned, beta: Self::T);
131    /// Set the value at the specified index. Panics unless `nbatch == 1`; to write the same value
132    /// into every batch use [`Self::fill_index`].
133    fn set_index(&mut self, index: IndexType, value: Self::T);
134    /// Set the value at the specified index in **every** batch.
135    fn fill_index(&mut self, index: IndexType, value: Self::T);
136}
137
138/// A borrowed immutable view of a vector, supporting read-only arithmetic operations.
139///
140/// This trait represents a temporary immutable reference to a vector's data, allowing read-only
141/// operations like addition, subtraction, and scalar multiplication. Vector views can be created
142/// via the `as_view()` method on a `Vector` and are cheaper to create than cloning.
143pub trait VectorView<'a>:
144    VectorOpsByValue<Self, Self::Owned>
145    + VectorOpsByValue<Self::Owned, Self::Owned>
146    + for<'b> VectorOpsByValue<&'b Self::Owned, Self::Owned>
147    + for<'b> VectorOpsByValue<&'b Self, Self::Owned>
148    + Mul<Scale<Self::T>, Output = Self::Owned>
149{
150    type Owned;
151    /// Get the value at the specified index (panics if nbatch > 1).
152    fn get_index(&self, index: IndexType) -> Self::T;
153    /// Compute the squared weighted norm: sum_i^n ((self_i) / (|y_i| * rtol + atol_i))^2 / n
154    ///
155    /// This is commonly used for error control in ODE solvers.
156    fn squared_norm(&self, y: &Self::Owned, atol: &Self::Owned, rtol: Self::T) -> Self::T;
157    /// Convert this view into an owned vector, cloning the data if necessary.
158    fn into_owned(self) -> Self::Owned;
159}
160
161/// A complete vector abstraction supporting arithmetic operations, norms, and index operations.
162///
163/// This is the main vector trait used throughout diffsol. Implementing vectors can be hosted on CPU or GPU.
164/// Users typically do not need to implement this trait; use provided implementations like
165/// `NalgebraVec` or `FaerVec`.
166pub trait Vector:
167    VectorOpsByValue<Self>
168    + for<'b> VectorOpsByValue<&'b Self>
169    + for<'a> VectorOpsByValue<Self::View<'a>>
170    + for<'a, 'b> VectorOpsByValue<&'b Self::View<'a>>
171    + Mul<Scale<Self::T>, Output = Self>
172    + Div<Scale<Self::T>, Output = Self>
173    + VectorMutOpsByValue<Self>
174    + for<'a> VectorMutOpsByValue<Self::View<'a>>
175    + for<'b> VectorMutOpsByValue<&'b Self>
176    + for<'a, 'b> VectorMutOpsByValue<&'b Self::View<'a>>
177    + MulAssign<Scale<Self::T>>
178    + Clone
179    + Send
180{
181    type View<'a>: VectorView<'a, T = Self::T, Owned = Self>
182    where
183        Self: 'a;
184    type ViewMut<'a>: VectorViewMut<'a, T = Self::T, Owned = Self, View = Self::View<'a>>
185    where
186        Self: 'a;
187    type Index: VectorIndex;
188
189    /// Get the context associated with this vector (for device placement, threading, etc.).
190    fn context(&self) -> &Self::C;
191
192    /// Get a mutable reference to the inner representation of the vector.
193    fn inner_mut(&mut self) -> &mut Self::Inner;
194
195    /// Set the value at the specified index to `value`. Panics unless `nbatch == 1`; to write the
196    /// same value into every batch use [`Self::fill_index`].
197    fn set_index(&mut self, index: IndexType, value: Self::T);
198
199    /// Set the value at the specified index to `value` in **every** batch.
200    ///
201    /// This is what a basis or probe vector wants — the same scalar in every lane — and is the only
202    /// way to write a single index of a batched vector; [`Self::set_index`] panics on one.
203    fn fill_index(&mut self, index: IndexType, value: Self::T);
204
205    /// Get the value at the specified index. Panics unless `nbatch == 1`; for a batched vector read
206    /// one lane with [`Self::get_batch`].
207    fn get_index(&self, index: IndexType) -> Self::T;
208
209    /// Compute the $\ell_k$ norm: $(\sum_i |x_i|^k)^{1/k}$
210    fn norm(&self, k: i32) -> Self::T;
211
212    /// Compute the squared weighted norm for error control: $\sum_i (x_i / (|y_i| \cdot \text{rtol} + \text{atol}_i))^2$
213    ///
214    /// This norm is used by ODE solvers for adaptive error control.
215    fn squared_norm(&self, y: &Self, atol: &Self, rtol: Self::T) -> Self::T;
216
217    /// Get the per-batch length (number of states) in this vector.
218    /// For batched vectors, this returns `nstates`, not `nstates * nbatch`.
219    fn len(&self) -> IndexType;
220
221    /// Get the total number of elements stored, including all batches.
222    /// Returns `self.len() * self.context().nbatch()`.
223    fn total_len(&self) -> IndexType {
224        self.len() * self.context().nbatch()
225    }
226
227    /// Check if the vector is empty.
228    fn is_empty(&self) -> bool {
229        self.len() == 0
230    }
231
232    /// Create a vector of length `nstates` with all elements initialized to `value`.
233    fn from_element(nstates: usize, value: Self::T, ctx: Self::C) -> Self;
234
235    /// Create a vector of length `nstates` with all elements set to zero.
236    fn zeros(nstates: usize, ctx: Self::C) -> Self {
237        Self::from_element(nstates, Self::T::zero(), ctx)
238    }
239
240    /// Fill all elements of this vector with `value`.
241    fn fill(&mut self, value: Self::T);
242
243    /// Create an immutable view of this vector.
244    fn as_view(&self) -> Self::View<'_>;
245
246    /// Create a mutable view of this vector.
247    fn as_view_mut(&mut self) -> Self::ViewMut<'_>;
248
249    /// Get an immutable view of a single batch (with nbatch=1 context).
250    fn get_batch(&self, batch: usize) -> Self::View<'_>;
251
252    /// Get a mutable view of a single batch (with nbatch=1 context).
253    fn get_batch_mut(&mut self, batch: usize) -> Self::ViewMut<'_>;
254
255    /// Run `f` once per batch lane **on the host**.
256    ///
257    /// The lane count (i.e. number of batches) is governed by `mut_args[0]`, the primary output.
258    /// Every other operand, mutable or not, broadcasts. Panics if an operand's batch count is not
259    /// broadcastable into the output's, or if `mut_args` is empty.
260    ///
261    /// The closure's last argument is the lane of the output being written, in
262    /// `0..mut_args[0].context().nbatch()`.
263    ///
264    /// A device-backed vector stages every operand through host memory to run `f`, so prefer
265    /// [`Self::for_each_batch_mut`] when `f` is device-compilable.
266    ///
267    /// ```ignore
268    /// // y = A^T v, via a scratch buffer the backend requires to be mutable
269    /// V::for_each_batch_mut_host([y, &mut scratch], [x, v], |[y, scratch], [x, v], _lane| {
270    ///     scratch.copy_from_slice(v);
271    ///     op.transpose_mul(x, scratch, y);
272    /// });
273    /// ```
274    fn for_each_batch_mut_host<const M: usize, const N: usize>(
275        mut_args: [&mut Self; M],
276        args: [&Self; N],
277        f: impl FnMut([&mut [Self::T]; M], [&[Self::T]; N], usize),
278    );
279
280    /// Run `f` once per batch lane of `self` **on the host**, passing that lane of `self` as a
281    /// mutable slice and the corresponding lane of each operand in `args` as an immutable slice.
282    ///
283    /// The single-output case of [`Self::for_each_batch_mut_host`].
284    ///
285    /// ```ignore
286    /// // y_0 = x_0 v_0 + x_1 v_1, y_1 = 0, for every batch of y
287    /// y.for_each_batch_host([x, v], |y, [x, v], _lane| {
288    ///     y[0] = x[0] * v[0] + x[1] * v[1];
289    ///     y[1] = T::zero();
290    /// });
291    /// ```
292    fn for_each_batch_host<const N: usize>(
293        &mut self,
294        args: [&Self; N],
295        mut f: impl FnMut(&mut [Self::T], [&[Self::T]; N], usize),
296    ) {
297        Self::for_each_batch_mut_host([self], args, |[y], ins, lane| f(y, ins, lane));
298    }
299
300    /// Run `f` once per batch lane **on the device where the backend has one**, falling back to
301    /// [`Self::for_each_batch_mut_host`] otherwise.
302    ///
303    /// Same lane, broadcast and panic semantics as [`Self::for_each_batch_mut_host`]; which side
304    /// ran it is not observable beyond timing.
305    ///
306    /// `f` must therefore be device-compilable Rust — arithmetic and control flow over the lane
307    /// slices, no host function pointers, no allocation, no `std .A
308    /// closure that cannot meet this should use
309    /// [`Self::for_each_batch_mut_host`] instead.
310    fn for_each_batch_mut<const M: usize, const N: usize>(
311        mut_args: [&mut Self; M],
312        args: [&Self; N],
313        f: impl Fn([&mut [Self::T]; M], [&[Self::T]; N], usize) + Copy + Send,
314    ) {
315        Self::for_each_batch_mut_host(mut_args, args, f);
316    }
317
318    /// Run `f` once per batch lane of `self` **on the device where the backend has one**.
319    ///
320    /// The single-output case of [`Self::for_each_batch_mut`], and subject to the same
321    /// device-compilable requirement on `f`.
322    ///
323    /// ```ignore
324    /// // y_0 = x_0 v_0 + x_1 v_1, y_1 = 0, for every batch of y
325    /// y.for_each_batch([x, v], |y, [x, v], _lane| {
326    ///     y[0] = x[0] * v[0] + x[1] * v[1];
327    ///     y[1] = T::zero();
328    /// });
329    /// ```
330    fn for_each_batch<const N: usize>(
331        &mut self,
332        args: [&Self; N],
333        f: impl Fn(&mut [Self::T], [&[Self::T]; N], usize) + Copy + Send,
334    ) {
335        Self::for_each_batch_mut([self], args, move |[y], ins, lane| f(y, ins, lane));
336    }
337
338    /// Run `f` once per element of every batch lane **on the device where the backend has one**,
339    /// falling back to a host loop otherwise.
340    ///
341    /// The element-parallel counterpart of [`Self::for_each_batch_mut`]: `f` is handed one
342    /// element of each output rather than the whole lane, so a backend with a device can run
343    /// every `(lane, element)` pair concurrently.
344    ///
345    /// The inputs are still full lane slices, so reads may go anywhere in the lane.
346    ///
347    /// `f` must be device-compilable Rust — arithmetic and control flow over the slices, no host
348    /// function pointers, no allocation, no `std`.
349    fn for_each_elem_mut<const M: usize, const N: usize>(
350        mut_args: [&mut Self; M],
351        args: [&Self; N],
352        f: impl Fn([&mut Self::T; M], [&[Self::T]; N], usize, usize) + Copy + Send,
353    ) {
354        Self::for_each_batch_mut_host(mut_args, args, |mut outs, ins, lane| {
355            let n = outs[0].len();
356            assert!(
357                outs.iter().all(|o| o.len() == n),
358                "for_each_elem_mut needs every mutable operand to have the same length"
359            );
360            for i in 0..n {
361                f(outs.each_mut().map(|o| &mut o[i]), ins, lane, i);
362            }
363        });
364    }
365
366    /// Run `f` once per element of every batch lane of `self` **on the device where the backend
367    /// has one**.
368    ///
369    /// The single-output case of [`Self::for_each_elem_mut`], and subject to the same
370    /// device-compilable requirement on `f`.
371    ///
372    /// ```ignore
373    /// // y_i = x_i p_0 + x_{i+1}, wrapping, for every batch of y
374    /// y.for_each_elem([x, p], |y, [x, p], _lane, i| {
375    ///     *y = x[i] * p[0] + x[(i + 1) % x.len()];
376    /// });
377    /// ```
378    fn for_each_elem<const N: usize>(
379        &mut self,
380        args: [&Self; N],
381        f: impl Fn(&mut Self::T, [&[Self::T]; N], usize, usize) + Copy + Send,
382    ) {
383        Self::for_each_elem_mut([self], args, move |[y], ins, lane, elem| {
384            f(y, ins, lane, elem)
385        });
386    }
387
388    /// Reduce `args` over the batch dimension, elementwise, **on the host**.
389    ///
390    /// `dest[i]` becomes `map(.., b, i)` folded over every lane `b` with `combine`, starting
391    /// from `init`. `dest` is the one operand the batch dimension is removed from, so it must
392    /// be unbatched (`context().nbatch() == 1`); its length is the element range. The lane
393    /// count comes from `args[0]`, and the other operands broadcast into it.
394    ///
395    /// `combine` must be associative with `init` as its identity. This host path folds in lane
396    /// order, but [`Self::reduce_batch`] folds on a device and needs commutativity too.
397    ///
398    /// `dest` must not alias any of `args`.
399    ///
400    /// ```ignore
401    /// // dest_i = max_b |x[b, i]|
402    /// V::reduce_batch_host(&mut dest, [x], T::zero(),
403    ///     |[x], _lane, i| x[i].abs(),
404    ///     |a, b| a.max(b));
405    /// ```
406    fn reduce_batch_host<const N: usize>(
407        dest: &mut Self,
408        args: [&Self; N],
409        init: Self::T,
410        mut map: impl FnMut([&[Self::T]; N], usize, usize) -> Self::T,
411        mut combine: impl FnMut(Self::T, Self::T) -> Self::T,
412    ) {
413        assert!(N > 0, "reduce_batch takes the lane count from args[0]");
414        assert_eq!(
415            dest.context().nbatch(),
416            1,
417            "reduce_batch removes the batch dimension, so dest must be unbatched"
418        );
419        dest.fill(init);
420        // `for_each_batch_mut_host` takes its lane count from `mut_args[0]`, and `dest` is
421        // deliberately narrower than the operands, so a scalar per lane drives the iteration
422        // and `dest` rides along as a broadcast output seeing the same slice every lane
423        let mut lanes = Self::zeros(1, args[0].context().clone());
424        Self::for_each_batch_mut_host([&mut lanes, dest], args, |[_, d], ins, lane| {
425            for (i, d) in d.iter_mut().enumerate() {
426                *d = combine(*d, map(ins, lane, i));
427            }
428        });
429    }
430
431    /// Reduce `args` over the batch dimension, elementwise, **on the device where the backend
432    /// has one**, falling back to [`Self::reduce_batch_host`] otherwise.
433    ///
434    /// Same shape and broadcast rules; `map` and `combine` must be device-compilable Rust, as
435    /// for [`Self::for_each_batch_mut`]. `combine` must also be associative *and commutative*:
436    /// the device folds with warp shuffles that pair a lane with `lane ^ delta`, so neither the
437    /// order nor the grouping of the operands is the caller's. Sum, max and min all qualify.
438    fn reduce_batch<const N: usize>(
439        dest: &mut Self,
440        args: [&Self; N],
441        init: Self::T,
442        map: impl Fn([&[Self::T]; N], usize, usize) -> Self::T + Copy + Send,
443        combine: impl Fn(Self::T, Self::T) -> Self::T + Copy + Send,
444    ) {
445        Self::reduce_batch_host(dest, args, init, map, combine);
446    }
447
448    /// Reduce each batch lane of `args` over its elements, **on the host**.
449    ///
450    /// Lane `b` of `dest` becomes `map(.., b, i)` folded over every element `i` with
451    /// `combine`, starting from `init`. `dest` is a batched scalar — `len() == 1` with the
452    /// operands' lane count — the same shape [`Self::batched_axpy`] takes. The element range
453    /// comes from `args[0]`, and the other operands broadcast into the lanes.
454    ///
455    /// `combine` must be associative with `init` as its identity. This host path folds in
456    /// index order, but [`Self::reduce_elem`] folds on a device and needs commutativity too.
457    ///
458    /// `dest` must not alias any of `args`.
459    ///
460    /// ```ignore
461    /// // dest_b = sum_i x[b, i] * v[b, i]
462    /// V::reduce_elem_host(&mut dest, [x, v], T::zero(),
463    ///     |[x, v], _lane, i| x[i] * v[i],
464    ///     |a, b| a + b);
465    /// ```
466    fn reduce_elem_host<const N: usize>(
467        dest: &mut Self,
468        args: [&Self; N],
469        init: Self::T,
470        mut map: impl FnMut([&[Self::T]; N], usize, usize) -> Self::T,
471        mut combine: impl FnMut(Self::T, Self::T) -> Self::T,
472    ) {
473        assert!(N > 0, "reduce_elem takes the element range from args[0]");
474        assert_eq!(
475            dest.len(),
476            1,
477            "reduce_elem removes the element dimension, so dest must be a batched scalar"
478        );
479        Self::for_each_batch_mut_host([dest], args, |[d], ins, lane| {
480            let mut acc = init;
481            for i in 0..ins[0].len() {
482                acc = combine(acc, map(ins, lane, i));
483            }
484            d[0] = acc;
485        });
486    }
487
488    /// Reduce each batch lane of `args` over its elements, **on the device where the backend
489    /// has one**, falling back to [`Self::reduce_elem_host`] otherwise.
490    ///
491    /// Same shape and broadcast rules; `map` and `combine` must be device-compilable Rust, as
492    /// for [`Self::for_each_elem_mut`]. `combine` must also be associative *and commutative*:
493    /// the device folds with warp shuffles that pair a lane with `lane ^ delta`, so neither the
494    /// order nor the grouping of the operands is the caller's. Sum, max and min all qualify.
495    fn reduce_elem<const N: usize>(
496        dest: &mut Self,
497        args: [&Self; N],
498        init: Self::T,
499        map: impl Fn([&[Self::T]; N], usize, usize) -> Self::T + Copy + Send,
500        combine: impl Fn(Self::T, Self::T) -> Self::T + Copy + Send,
501    ) {
502        Self::reduce_elem_host(dest, args, init, map, combine);
503    }
504
505    /// Copy all values from `other` into this vector.
506    fn copy_from(&mut self, other: &Self);
507
508    /// Copy all values from a vector view into this vector.
509    fn copy_from_view(&mut self, other: &Self::View<'_>);
510
511    // TODO: would prefer to use From trait but not implemented for faer::Col
512    /// Create a vector from a Rust `Vec`.
513    fn from_vec(vec: Vec<Self::T>, ctx: Self::C) -> Self;
514
515    /// Create a vector from a slice.
516    fn from_slice(slice: &[Self::T], ctx: Self::C) -> Self;
517
518    // TODO: would prefer to use From trait but not implemented for faer::Col
519    /// Clone this vector as a Rust `Vec`.
520    fn clone_as_vec(&self) -> Vec<Self::T>;
521
522    /// Compute the AXPY operation: self = alpha * x + beta * self
523    fn axpy(&mut self, alpha: Self::T, x: &Self, beta: Self::T);
524
525    /// Compute the AXPY operation with a vector view: self = alpha * x + beta * self
526    fn axpy_v(&mut self, alpha: Self::T, x: &Self::View<'_>, beta: Self::T);
527
528    /// Per-batch AXPY: `self[i]_b = alpha_b * x[i]_b + beta * self[i]_b`
529    ///
530    /// `alpha` is a *batched scalar*: one value per batch, so `alpha.len() == 1` and its batch
531    /// count equals `self`'s. A uniform multiplier is plain [`Self::axpy`] instead.
532    /// The `x` operand may broadcast if its `nbatch == 1`.
533    fn batched_axpy(&mut self, alpha: &Self, x: &Self, beta: Self::T);
534
535    /// Element-wise multiplication: self_i *= other_i
536    fn component_mul_assign(&mut self, other: &Self);
537
538    /// Element-wise division: self_i /= other_i
539    fn component_div_assign(&mut self, other: &Self);
540
541    /// Detect roots (zero crossings) between this vector (as g0) and another vector (g1).
542    ///
543    /// Returns a tuple of:
544    /// - `bool`: true if a zero crossing is found (g1_i == 0 for some i)
545    /// - `T`: the interpolation factor at the maximum crossing (0 if none found) (given by maxmimum |g0_i / (g1_i - g0_i)|)
546    /// - `i32`: the index of the maximum crossing (-1 if none found)
547    fn root_finding(&self, g1: &Self) -> (bool, Self::T, i32);
548
549    /// Assign `value` to all elements at the specified indices.
550    fn assign_at_indices(&mut self, indices: &Self::Index, value: Self::T);
551
552    /// Copy values from `other` at the specified indices: self\[indices\[i\]\] = other\[indices\[i\]\]
553    fn copy_from_indices(&mut self, other: &Self, indices: &Self::Index);
554
555    /// Gather values from `other` at indices: self\[i\] = other\[indices\[i\]\]
556    fn gather(&mut self, other: &Self, indices: &Self::Index);
557
558    /// Scatter values to `other` at indices: other\[indices\[i\]\] = self\[i\]
559    fn scatter(&self, indices: &Self::Index, other: &mut Self);
560
561    /// Assert that this vector equals `other` within a scalar tolerance `tol`.
562    fn assert_eq_st(&self, other: &Self, tol: Self::T) {
563        let tol = vec![tol; self.total_len()];
564        Self::assert_eq_vec(self.clone_as_vec(), other.clone_as_vec(), tol);
565    }
566
567    /// Assert that this vector equals `other` using a weighted norm (same as used by ODE solvers).
568    ///
569    /// Uses `squared_norm` internally with the scaling factors, and asserts that the resulting
570    /// norm is less than `factor`.
571    fn assert_eq_norm(&self, other: &Self, atol: &Self, rtol: Self::T, factor: Self::T) {
572        let error = self.clone() - other.clone();
573        let error_norm = error.squared_norm(other, atol, rtol).sqrt();
574        assert!(
575            error_norm < factor,
576            "error_norm: {error_norm}. self: {self:?}, other: {other:?}",
577        );
578    }
579
580    /// Assert that this vector equals `other` using a vector of per-element tolerances.
581    fn assert_eq(&self, other: &Self, tol: &Self) {
582        assert_eq!(
583            self.len(),
584            other.len(),
585            "Vector length mismatch: {} != {}",
586            self.len(),
587            other.len()
588        );
589        let s = self.clone_as_vec();
590        let other = other.clone_as_vec();
591        let tol = tol.clone_as_vec();
592        Self::assert_eq_vec(s, other, tol);
593    }
594
595    fn assert_eq_vec(s: Vec<Self::T>, other: Vec<Self::T>, tol: Vec<Self::T>) {
596        for i in 0..s.len() {
597            if num_traits::abs(s[i] - other[i]) > tol[i] {
598                eprintln!(
599                    "Vector element mismatch at index {i}: {} != {}",
600                    s[i], other[i]
601                );
602                if s.len() <= 3 {
603                    eprintln!("left: {s:?}");
604                    eprintln!("right: {other:?}");
605                } else if i == 0 {
606                    eprintln!(
607                        "left: [{}, {}, {}] != [{}, {}, {}]",
608                        s[0], s[1], s[2], other[0], other[1], other[2]
609                    );
610                } else if i == s.len() - 1 {
611                    eprintln!(
612                        "left: [..., {}, {}, {}] != [..., {}, {}, {}]",
613                        s[i - 2],
614                        s[i - 1],
615                        s[i],
616                        other[i - 2],
617                        other[i - 1],
618                        other[i]
619                    );
620                } else {
621                    eprintln!(
622                        "left: [..., {}, {}, {}, ...] != [..., {}, {}, {}, ...]",
623                        s[i - 1],
624                        s[i],
625                        s[i + 1],
626                        other[i - 1],
627                        other[i],
628                        other[i + 1]
629                    );
630                }
631                panic!(
632                    "Vector element mismatch at index {}: {} != {}",
633                    i, s[i], other[i]
634                );
635            }
636        }
637    }
638}
639
640/// Marker trait for vectors that have a default associated dense matrix type.
641///
642/// This trait associates a vector type with its corresponding dense matrix representation,
643/// enabling vectors to be easily combined with matrix types for linear algebra operations.
644pub trait DefaultDenseMatrix: Vector {
645    type M: DenseMatrix<V = Self, T = Self::T, C = Self::C>;
646}
647
648#[cfg(test)]
649macro_rules! generate_vector_tests_nonbatched {
650    ($suffix:ident, $V:ty) => {
651        paste::paste! {
652            #[test]
653            fn [<test_root_finding_ $suffix>]() {
654                $crate::vector::tests::test_root_finding::<$V>();
655            }
656            #[test]
657            fn [<test_from_slice_ $suffix>]() {
658                $crate::vector::tests::test_from_slice::<$V>();
659            }
660            #[test]
661            fn [<test_mul_scalar_ $suffix>]() {
662                $crate::vector::tests::test_mul_scalar::<$V>();
663            }
664            #[test]
665            fn [<test_div_scalar_ $suffix>]() {
666                $crate::vector::tests::test_div_scalar::<$V>();
667            }
668            #[test]
669            fn [<test_axpy_ $suffix>]() {
670                $crate::vector::tests::test_axpy::<$V>();
671            }
672            #[test]
673            fn [<test_copy_from_indices_ $suffix>]() {
674                $crate::vector::tests::test_copy_from_indices::<$V>();
675            }
676            #[test]
677            fn [<test_gather_ $suffix>]() {
678                $crate::vector::tests::test_gather::<$V>();
679            }
680            #[test]
681            fn [<test_scatter_ $suffix>]() {
682                $crate::vector::tests::test_scatter::<$V>();
683            }
684            #[test]
685            fn [<test_copy_from_via_view_mut_ $suffix>]() {
686                $crate::vector::tests::test_copy_from_via_view_mut::<$V>();
687            }
688            #[test]
689            fn [<test_for_each_batch_ $suffix>]() {
690                $crate::vector::tests::test_for_each_batch::<$V>();
691            }
692            #[test]
693            fn [<test_for_each_batch_index_ $suffix>]() {
694                $crate::vector::tests::test_for_each_batch_index::<$V>();
695            }
696            #[test]
697            fn [<test_for_each_batch_mut_ $suffix>]() {
698                $crate::vector::tests::test_for_each_batch_mut::<$V>();
699            }
700            #[test]
701            fn [<test_for_each_elem_mut_ $suffix>]() {
702                $crate::vector::tests::test_for_each_elem_mut::<$V>();
703            }
704            #[test]
705            fn [<test_reduce_unbatched_ $suffix>]() {
706                $crate::vector::tests::test_reduce_unbatched::<$V>();
707            }
708            #[test]
709            fn [<test_set_index_ $suffix>]() {
710                $crate::vector::tests::test_set_index::<$V>();
711            }
712            #[test]
713            fn [<test_get_index_ $suffix>]() {
714                $crate::vector::tests::test_get_index::<$V>();
715            }
716            #[test]
717            fn [<test_norm_ $suffix>]() {
718                $crate::vector::tests::test_norm::<$V>();
719            }
720            #[test]
721            fn [<test_norm_l1_ $suffix>]() {
722                $crate::vector::tests::test_norm_l1::<$V>();
723            }
724            #[test]
725            fn [<test_squared_norm_ $suffix>]() {
726                $crate::vector::tests::test_squared_norm::<$V>();
727            }
728            #[test]
729            fn [<test_fill_ $suffix>]() {
730                $crate::vector::tests::test_fill::<$V>();
731            }
732            #[test]
733            fn [<test_from_element_ $suffix>]() {
734                $crate::vector::tests::test_from_element::<$V>();
735            }
736            #[test]
737            fn [<test_copy_from_ $suffix>]() {
738                $crate::vector::tests::test_copy_from::<$V>();
739            }
740            #[test]
741            fn [<test_from_vec_ $suffix>]() {
742                $crate::vector::tests::test_from_vec::<$V>();
743            }
744            #[test]
745            fn [<test_component_mul_assign_ $suffix>]() {
746                $crate::vector::tests::test_component_mul_assign::<$V>();
747            }
748            #[test]
749            fn [<test_component_div_assign_ $suffix>]() {
750                $crate::vector::tests::test_component_div_assign::<$V>();
751            }
752            #[test]
753            fn [<test_assign_at_indices_ $suffix>]() {
754                $crate::vector::tests::test_assign_at_indices::<$V>();
755            }
756            #[test]
757            fn [<test_add_ $suffix>]() {
758                $crate::vector::tests::test_add::<$V>();
759            }
760            #[test]
761            fn [<test_sub_ $suffix>]() {
762                $crate::vector::tests::test_sub::<$V>();
763            }
764            #[test]
765            fn [<test_owned_rhs_ $suffix>]() {
766                $crate::vector::tests::test_owned_rhs::<$V>();
767            }
768            #[test]
769            fn [<test_add_assign_ $suffix>]() {
770                $crate::vector::tests::test_add_assign::<$V>();
771            }
772            #[test]
773            fn [<test_sub_assign_ $suffix>]() {
774                $crate::vector::tests::test_sub_assign::<$V>();
775            }
776            #[test]
777            fn [<test_axpy_v_ $suffix>]() {
778                $crate::vector::tests::test_axpy_v::<$V>();
779            }
780            #[test]
781            fn [<test_mul_assign_scalar_ $suffix>]() {
782                $crate::vector::tests::test_mul_assign_scalar::<$V>();
783            }
784            #[test]
785            fn [<test_copy_from_view_ $suffix>]() {
786                $crate::vector::tests::test_copy_from_view::<$V>();
787            }
788            #[test]
789            fn [<test_view_squared_norm_ $suffix>]() {
790                $crate::vector::tests::test_view_squared_norm::<$V>();
791            }
792            #[test]
793            fn [<test_view_into_owned_ $suffix>]() {
794                $crate::vector::tests::test_view_into_owned::<$V>();
795            }
796            #[test]
797            fn [<test_view_get_index_ $suffix>]() {
798                $crate::vector::tests::test_view_get_index::<$V>();
799            }
800            #[test]
801            fn [<test_view_mut_axpy_ $suffix>]() {
802                $crate::vector::tests::test_view_mut_axpy::<$V>();
803            }
804            #[test]
805            fn [<test_view_mut_set_index_ $suffix>]() {
806                $crate::vector::tests::test_view_mut_set_index::<$V>();
807            }
808            #[test]
809            fn [<test_view_mut_mul_assign_scalar_ $suffix>]() {
810                $crate::vector::tests::test_view_mut_mul_assign_scalar::<$V>();
811            }
812            #[test]
813            fn [<test_view_mut_copy_from_view_ $suffix>]() {
814                $crate::vector::tests::test_view_mut_copy_from_view::<$V>();
815            }
816            #[test]
817            fn [<test_view_add_sub_ $suffix>]() {
818                $crate::vector::tests::test_view_add_sub::<$V>();
819            }
820            #[test]
821            fn [<test_index_zeros_and_is_empty_ $suffix>]() {
822                $crate::vector::tests::test_index_zeros_and_is_empty::<$V>();
823            }
824        }
825    };
826}
827
828#[cfg(test)]
829#[cfg_attr(not(feature = "cuda"), allow(unused_macros))]
830macro_rules! generate_vector_tests_batched {
831    ($suffix:ident, $V:ty, $ctx2:expr, $ctx3:expr) => {
832        paste::paste! {
833            #[test]
834            fn [<test_batched_len_and_total_len_ $suffix>]() {
835                $crate::vector::tests::test_batched_len_and_total_len::<$V>($ctx3);
836            }
837            #[test]
838            fn [<test_batched_from_vec_ $suffix>]() {
839                $crate::vector::tests::test_batched_from_vec::<$V>($ctx2);
840            }
841            #[test]
842            #[should_panic(expected = "divisible by nbatch")]
843            fn [<test_batched_from_vec_bad_length_ $suffix>]() {
844                $crate::vector::tests::test_batched_from_vec_bad_length::<$V>($ctx2);
845            }
846            #[test]
847            fn [<test_batched_from_element_ $suffix>]() {
848                $crate::vector::tests::test_batched_from_element::<$V>($ctx3);
849            }
850            #[test]
851            fn [<test_batched_axpy_ $suffix>]() {
852                $crate::vector::tests::test_batched_axpy::<$V>($ctx2);
853            }
854            #[test]
855            fn [<test_batched_add_ $suffix>]() {
856                $crate::vector::tests::test_batched_add::<$V>($ctx2);
857            }
858            #[test]
859            fn [<test_batched_norm_max_across_batches_ $suffix>]() {
860                $crate::vector::tests::test_batched_norm_max_across_batches::<$V>($ctx2);
861            }
862            #[test]
863            fn [<test_batched_norm_l1_ $suffix>]() {
864                $crate::vector::tests::test_batched_norm_l1::<$V>($ctx2);
865            }
866            #[test]
867            fn [<test_batched_squared_norm_ $suffix>]() {
868                $crate::vector::tests::test_batched_squared_norm::<$V>($ctx2);
869            }
870            #[test]
871            fn [<test_batched_fill_index_ $suffix>]() {
872                $crate::vector::tests::test_batched_fill_index::<$V>($ctx3);
873            }
874            #[test]
875            #[should_panic(expected = "not supported for batched")]
876            fn [<test_batched_get_index_panics_ $suffix>]() {
877                $crate::vector::tests::test_batched_get_index_panics::<$V>($ctx2);
878            }
879            #[test]
880            #[should_panic(expected = "not supported for batched")]
881            fn [<test_batched_set_index_panics_ $suffix>]() {
882                $crate::vector::tests::test_batched_set_index_panics::<$V>($ctx2);
883            }
884            #[test]
885            fn [<test_batched_fill_ $suffix>]() {
886                $crate::vector::tests::test_batched_fill::<$V>($ctx2);
887            }
888            #[test]
889            fn [<test_batched_component_mul_ $suffix>]() {
890                $crate::vector::tests::test_batched_component_mul::<$V>($ctx2);
891            }
892            #[test]
893            fn [<test_batched_assign_at_indices_ $suffix>]() {
894                $crate::vector::tests::test_batched_assign_at_indices::<$V>($ctx2);
895            }
896            #[test]
897            fn [<test_batched_root_finding_consistent_ $suffix>]() {
898                $crate::vector::tests::test_batched_root_finding_consistent::<$V>($ctx2);
899            }
900            #[test]
901            #[should_panic(expected = "differ across batches")]
902            fn [<test_batched_root_finding_inconsistent_ $suffix>]() {
903                $crate::vector::tests::test_batched_root_finding_inconsistent::<$V>($ctx2);
904            }
905            #[test]
906            fn [<test_batched_for_each_batch_ $suffix>]() {
907                $crate::vector::tests::test_batched_for_each_batch::<$V>($ctx2);
908            }
909            #[test]
910            fn [<test_batched_for_each_batch_index_ $suffix>]() {
911                $crate::vector::tests::test_batched_for_each_batch_index::<$V>($ctx3);
912            }
913            #[test]
914            fn [<test_batched_for_each_batch_mut_ $suffix>]() {
915                $crate::vector::tests::test_batched_for_each_batch_mut::<$V>($ctx2);
916            }
917            #[test]
918            fn [<test_batched_for_each_elem_mut_ $suffix>]() {
919                $crate::vector::tests::test_batched_for_each_elem_mut::<$V>($ctx2);
920            }
921            #[test]
922            #[should_panic(expected = "for_each_batch")]
923            fn [<test_batched_for_each_batch_bad_nbatch_ $suffix>]() {
924                $crate::vector::tests::test_batched_for_each_batch_bad_nbatch::<$V>($ctx2);
925            }
926            #[test]
927            fn [<test_batched_reduce_batch_ $suffix>]() {
928                $crate::vector::tests::test_batched_reduce_batch::<$V>($ctx2);
929            }
930            #[test]
931            fn [<test_batched_reduce_elem_ $suffix>]() {
932                $crate::vector::tests::test_batched_reduce_elem::<$V>($ctx2);
933            }
934            #[test]
935            fn [<test_batched_reduce_broadcast_ $suffix>]() {
936                $crate::vector::tests::test_batched_reduce_broadcast::<$V>($ctx2);
937            }
938            #[test]
939            fn [<test_batched_reduce_host_stateful_ $suffix>]() {
940                $crate::vector::tests::test_batched_reduce_host_stateful::<$V>($ctx2);
941            }
942            #[test]
943            fn [<test_batched_reduce_long_lane_ $suffix>]() {
944                $crate::vector::tests::test_batched_reduce_long_lane::<$V>($ctx3);
945            }
946            #[test]
947            #[should_panic(expected = "dest must be unbatched")]
948            fn [<test_batched_reduce_batch_bad_dest_ $suffix>]() {
949                $crate::vector::tests::test_batched_reduce_batch_bad_dest::<$V>($ctx2);
950            }
951            #[test]
952            #[should_panic(expected = "batched scalar")]
953            fn [<test_batched_reduce_elem_bad_dest_ $suffix>]() {
954                $crate::vector::tests::test_batched_reduce_elem_bad_dest::<$V>($ctx2);
955            }
956            #[test]
957            fn [<test_batched_axpy_broadcast_ $suffix>]() {
958                $crate::vector::tests::test_batched_axpy_broadcast::<$V>($ctx2);
959            }
960            #[test]
961            fn [<test_batched_copy_from_broadcast_ $suffix>]() {
962                $crate::vector::tests::test_batched_copy_from_broadcast::<$V>($ctx2);
963            }
964            #[test]
965            fn [<test_batched_component_div_ $suffix>]() {
966                $crate::vector::tests::test_batched_component_div::<$V>($ctx2);
967            }
968            #[test]
969            fn [<test_batched_component_mul_broadcast_ $suffix>]() {
970                $crate::vector::tests::test_batched_component_mul_broadcast::<$V>($ctx2);
971            }
972            #[test]
973            fn [<test_batched_component_div_broadcast_ $suffix>]() {
974                $crate::vector::tests::test_batched_component_div_broadcast::<$V>($ctx2);
975            }
976            #[test]
977            fn [<test_batched_add_assign_broadcast_ $suffix>]() {
978                $crate::vector::tests::test_batched_add_assign_broadcast::<$V>($ctx2);
979            }
980            #[test]
981            fn [<test_batched_sub_assign_broadcast_ $suffix>]() {
982                $crate::vector::tests::test_batched_sub_assign_broadcast::<$V>($ctx2);
983            }
984            #[test]
985            fn [<test_batched_sub_ $suffix>]() {
986                $crate::vector::tests::test_batched_sub::<$V>($ctx2);
987            }
988            #[test]
989            fn [<test_batched_owned_rhs_broadcast_ $suffix>]() {
990                $crate::vector::tests::test_batched_owned_rhs_broadcast::<$V>($ctx2);
991            }
992            #[test]
993            fn [<test_batched_owned_lhs_broadcast_ $suffix>]() {
994                $crate::vector::tests::test_batched_owned_lhs_broadcast::<$V>($ctx2);
995            }
996            #[test]
997            fn [<test_batched_view_add_broadcast_ $suffix>]() {
998                $crate::vector::tests::test_batched_view_add_broadcast::<$V>($ctx2);
999            }
1000            #[test]
1001            fn [<test_batched_sub_assign_ $suffix>]() {
1002                $crate::vector::tests::test_batched_sub_assign::<$V>($ctx2);
1003            }
1004            #[test]
1005            fn [<test_batched_from_slice_ $suffix>]() {
1006                $crate::vector::tests::test_batched_from_slice::<$V>($ctx2);
1007            }
1008            #[test]
1009            fn [<test_batched_mul_scalar_ $suffix>]() {
1010                $crate::vector::tests::test_batched_mul_scalar::<$V>($ctx2);
1011            }
1012            #[test]
1013            fn [<test_batched_div_scalar_ $suffix>]() {
1014                $crate::vector::tests::test_batched_div_scalar::<$V>($ctx2);
1015            }
1016            #[test]
1017            fn [<test_batched_copy_from_indices_ $suffix>]() {
1018                $crate::vector::tests::test_batched_copy_from_indices::<$V>($ctx2);
1019            }
1020            #[test]
1021            fn [<test_batched_gather_ $suffix>]() {
1022                $crate::vector::tests::test_batched_gather::<$V>($ctx2);
1023            }
1024            #[test]
1025            fn [<test_batched_scatter_ $suffix>]() {
1026                $crate::vector::tests::test_batched_scatter::<$V>($ctx2);
1027            }
1028            #[test]
1029            fn [<test_batched_get_batch_ $suffix>]() {
1030                $crate::vector::tests::test_batched_get_batch::<$V>($ctx2);
1031            }
1032            #[test]
1033            fn [<test_batched_get_batch_mut_ $suffix>]() {
1034                $crate::vector::tests::test_batched_get_batch_mut::<$V>($ctx2);
1035            }
1036            #[test]
1037            fn [<test_batched_axpy_v_ $suffix>]() {
1038                $crate::vector::tests::test_batched_axpy_v::<$V>($ctx2);
1039            }
1040            #[test]
1041            fn [<test_batched_mul_assign_scalar_ $suffix>]() {
1042                $crate::vector::tests::test_batched_mul_assign_scalar::<$V>($ctx2);
1043            }
1044            #[test]
1045            fn [<test_batched_copy_from_view_ $suffix>]() {
1046                $crate::vector::tests::test_batched_copy_from_view::<$V>($ctx2);
1047            }
1048            #[test]
1049            fn [<test_batched_axpy_new_ $suffix>]() {
1050                $crate::vector::tests::test_batched_axpy_new::<$V>($ctx2);
1051            }
1052            #[test]
1053            fn [<test_batched_axpy_new_broadcast_ $suffix>]() {
1054                $crate::vector::tests::test_batched_axpy_new_broadcast::<$V>($ctx2);
1055            }
1056            #[test]
1057            #[should_panic(expected = "alpha must be a batched scalar")]
1058            fn [<test_batched_axpy_new_bad_length_ $suffix>]() {
1059                $crate::vector::tests::test_batched_axpy_new_bad_length::<$V>($ctx2);
1060            }
1061            #[test]
1062            #[should_panic(expected = "alpha nbatch must equal")]
1063            fn [<test_batched_axpy_new_bad_nbatch_ $suffix>]() {
1064                $crate::vector::tests::test_batched_axpy_new_bad_nbatch::<$V>($ctx2);
1065            }
1066            #[test]
1067            fn [<test_batched_empty_ $suffix>]() {
1068                $crate::vector::tests::test_batched_empty::<$V>($ctx2);
1069            }
1070            #[test]
1071            fn [<test_batched_binary_forms_broadcast_ $suffix>]() {
1072                $crate::vector::tests::test_batched_binary_forms_broadcast::<$V>($ctx2);
1073            }
1074
1075            // --- Governing-operand tests: the operand that governs must be the wider one ---
1076            #[test]
1077            #[should_panic(expected = "incompatible nbatch")]
1078            fn [<test_batched_owned_lhs_narrow_ $suffix>]() {
1079                $crate::vector::tests::test_batched_owned_lhs_narrow::<$V>($ctx2);
1080            }
1081            #[test]
1082            #[should_panic(expected = "incompatible nbatch")]
1083            fn [<test_batched_owned_rhs_narrow_ $suffix>]() {
1084                $crate::vector::tests::test_batched_owned_rhs_narrow::<$V>($ctx2);
1085            }
1086            #[test]
1087            #[should_panic(expected = "incompatible nbatch")]
1088            fn [<test_batched_view_add_narrow_ $suffix>]() {
1089                $crate::vector::tests::test_batched_view_add_narrow::<$V>($ctx2);
1090            }
1091            #[test]
1092            #[should_panic(expected = "incompatible nbatch")]
1093            fn [<test_grouped_add_narrow_lhs_ $suffix>]() {
1094                $crate::vector::tests::test_grouped_add_narrow_lhs::<$V>($ctx2);
1095            }
1096            #[test]
1097            #[should_panic(expected = "incompatible nbatch")]
1098            fn [<test_grouped_owned_lhs_narrow_ $suffix>]() {
1099                $crate::vector::tests::test_grouped_owned_lhs_narrow::<$V>($ctx2);
1100            }
1101
1102            // --- Narrow-destination tests (source wider than destination panics) ---
1103            #[test]
1104            #[should_panic(expected = "incompatible nbatch")]
1105            fn [<test_narrow_dest_copy_from_ $suffix>]() {
1106                $crate::vector::tests::test_narrow_dest_copy_from::<$V>($ctx2);
1107            }
1108            #[test]
1109            #[should_panic(expected = "incompatible nbatch")]
1110            fn [<test_narrow_dest_add_assign_ $suffix>]() {
1111                $crate::vector::tests::test_narrow_dest_add_assign::<$V>($ctx2);
1112            }
1113            #[test]
1114            #[should_panic(expected = "incompatible nbatch")]
1115            fn [<test_narrow_dest_axpy_ $suffix>]() {
1116                $crate::vector::tests::test_narrow_dest_axpy::<$V>($ctx2);
1117            }
1118            #[test]
1119            #[should_panic(expected = "incompatible nbatch")]
1120            fn [<test_narrow_dest_component_mul_ $suffix>]() {
1121                $crate::vector::tests::test_narrow_dest_component_mul::<$V>($ctx2);
1122            }
1123            #[test]
1124            #[should_panic(expected = "incompatible nbatch")]
1125            fn [<test_narrow_dest_gather_ $suffix>]() {
1126                $crate::vector::tests::test_narrow_dest_gather::<$V>($ctx2);
1127            }
1128            #[test]
1129            #[should_panic(expected = "incompatible nbatch")]
1130            fn [<test_narrow_dest_copy_from_indices_ $suffix>]() {
1131                $crate::vector::tests::test_narrow_dest_copy_from_indices::<$V>($ctx2);
1132            }
1133            #[test]
1134            #[should_panic(expected = "incompatible nbatch")]
1135            fn [<test_narrow_dest_scatter_ $suffix>]() {
1136                $crate::vector::tests::test_narrow_dest_scatter::<$V>($ctx2);
1137            }
1138            #[test]
1139            #[should_panic(expected = "incompatible nbatch")]
1140            fn [<test_narrow_dest_squared_norm_ $suffix>]() {
1141                $crate::vector::tests::test_narrow_dest_squared_norm::<$V>($ctx2);
1142            }
1143            #[test]
1144            #[should_panic(expected = "incompatible nbatch")]
1145            fn [<test_unbatched_dest_squared_norm_ $suffix>]() {
1146                $crate::vector::tests::test_unbatched_dest_squared_norm::<$V>($ctx2);
1147            }
1148
1149            // --- Grouped broadcast tests (B -> B * P, using $ctx2 widened to 4) ---
1150            #[test]
1151            fn [<test_grouped_owned_lhs_ref_rhs_ $suffix>]() {
1152                $crate::vector::tests::test_grouped_owned_lhs_ref_rhs::<$V>($ctx2);
1153            }
1154            #[test]
1155            fn [<test_grouped_axpy_ $suffix>]() {
1156                $crate::vector::tests::test_grouped_axpy::<$V>($ctx2);
1157            }
1158            #[test]
1159            fn [<test_grouped_add_assign_ $suffix>]() {
1160                $crate::vector::tests::test_grouped_add_assign::<$V>($ctx2);
1161            }
1162            #[test]
1163            fn [<test_grouped_add_ $suffix>]() {
1164                $crate::vector::tests::test_grouped_add::<$V>($ctx2);
1165            }
1166            #[test]
1167            fn [<test_grouped_copy_from_ $suffix>]() {
1168                $crate::vector::tests::test_grouped_copy_from::<$V>($ctx2);
1169            }
1170            #[test]
1171            fn [<test_grouped_component_mul_ $suffix>]() {
1172                $crate::vector::tests::test_grouped_component_mul::<$V>($ctx2);
1173            }
1174            #[test]
1175            fn [<test_grouped_squared_norm_ $suffix>]() {
1176                $crate::vector::tests::test_grouped_squared_norm::<$V>($ctx2);
1177            }
1178            #[test]
1179            fn [<test_grouped_gather_ $suffix>]() {
1180                $crate::vector::tests::test_grouped_gather::<$V>($ctx2);
1181            }
1182            #[test]
1183            #[should_panic(expected = "incompatible nbatch")]
1184            fn [<test_grouped_incompatible_ $suffix>]() {
1185                $crate::vector::tests::test_grouped_incompatible::<$V>($ctx3);
1186            }
1187            #[test]
1188            #[should_panic(expected = "incompatible nbatch")]
1189            fn [<test_batched_axpy_incompatible_ $suffix>]() {
1190                $crate::vector::tests::test_batched_axpy_incompatible::<$V>($ctx2, $ctx3);
1191            }
1192            #[test]
1193            #[should_panic(expected = "incompatible nbatch")]
1194            fn [<test_batched_copy_from_incompatible_ $suffix>]() {
1195                $crate::vector::tests::test_batched_copy_from_incompatible::<$V>($ctx2, $ctx3);
1196            }
1197            #[test]
1198            #[should_panic(expected = "incompatible nbatch")]
1199            fn [<test_batched_add_assign_incompatible_ $suffix>]() {
1200                $crate::vector::tests::test_batched_add_assign_incompatible::<$V>($ctx2, $ctx3);
1201            }
1202            #[test]
1203            #[should_panic(expected = "incompatible nbatch")]
1204            fn [<test_batched_component_mul_incompatible_ $suffix>]() {
1205                $crate::vector::tests::test_batched_component_mul_incompatible::<$V>($ctx2, $ctx3);
1206            }
1207
1208            // --- Strided view tests (batched, using $ctx2) ---
1209            #[test]
1210            fn [<test_strided_view_fill_index_ $suffix>]() {
1211                $crate::vector::tests::test_strided_view_fill_index::<<$V as $crate::DefaultDenseMatrix>::M>($ctx2);
1212            }
1213            #[test]
1214            #[should_panic(expected = "not supported for batched")]
1215            fn [<test_strided_view_set_index_panics_ $suffix>]() {
1216                $crate::vector::tests::test_strided_view_set_index_panics::<<$V as $crate::DefaultDenseMatrix>::M>($ctx2);
1217            }
1218            #[test]
1219            fn [<test_strided_view_mut_copy_from_ $suffix>]() {
1220                $crate::vector::tests::test_strided_view_mut_copy_from::<<$V as $crate::DefaultDenseMatrix>::M>($ctx2);
1221            }
1222            #[test]
1223            fn [<test_strided_view_mut_axpy_ $suffix>]() {
1224                $crate::vector::tests::test_strided_view_mut_axpy::<<$V as $crate::DefaultDenseMatrix>::M>($ctx2);
1225            }
1226            #[test]
1227            fn [<test_strided_view_mut_mul_assign_scalar_ $suffix>]() {
1228                $crate::vector::tests::test_strided_view_mut_mul_assign_scalar::<<$V as $crate::DefaultDenseMatrix>::M>($ctx2);
1229            }
1230            #[test]
1231            fn [<test_strided_view_mut_add_assign_ $suffix>]() {
1232                $crate::vector::tests::test_strided_view_mut_add_assign::<<$V as $crate::DefaultDenseMatrix>::M>($ctx2);
1233            }
1234            #[test]
1235            fn [<test_strided_view_mut_sub_assign_ $suffix>]() {
1236                $crate::vector::tests::test_strided_view_mut_sub_assign::<<$V as $crate::DefaultDenseMatrix>::M>($ctx2);
1237            }
1238            #[test]
1239            fn [<test_strided_view_add_assign_broadcast_ $suffix>]() {
1240                $crate::vector::tests::test_strided_view_add_assign_broadcast::<<$V as $crate::DefaultDenseMatrix>::M>($ctx2);
1241            }
1242            #[test]
1243            fn [<test_strided_view_add_owned_ $suffix>]() {
1244                $crate::vector::tests::test_strided_view_add_owned::<<$V as $crate::DefaultDenseMatrix>::M>($ctx2);
1245            }
1246            #[test]
1247            fn [<test_strided_view_squared_norm_ $suffix>]() {
1248                $crate::vector::tests::test_strided_view_squared_norm::<<$V as $crate::DefaultDenseMatrix>::M>($ctx2);
1249            }
1250            #[test]
1251            fn [<test_strided_view_into_owned_ $suffix>]() {
1252                $crate::vector::tests::test_strided_view_into_owned::<<$V as $crate::DefaultDenseMatrix>::M>($ctx2);
1253            }
1254            #[test]
1255            fn [<test_strided_view_component_mul_ $suffix>]() {
1256                $crate::vector::tests::test_strided_view_component_mul::<<$V as $crate::DefaultDenseMatrix>::M>($ctx2);
1257            }
1258            #[test]
1259            fn [<test_strided_view_component_div_ $suffix>]() {
1260                $crate::vector::tests::test_strided_view_component_div::<<$V as $crate::DefaultDenseMatrix>::M>($ctx2);
1261            }
1262            #[test]
1263            fn [<test_strided_view_mul_scalar_ $suffix>]() {
1264                $crate::vector::tests::test_strided_view_mul_scalar::<<$V as $crate::DefaultDenseMatrix>::M>($ctx2);
1265            }
1266            #[test]
1267            fn [<test_strided_view_fill_ $suffix>]() {
1268                $crate::vector::tests::test_strided_view_fill::<<$V as $crate::DefaultDenseMatrix>::M>($ctx2);
1269            }
1270            #[test]
1271            fn [<test_strided_view_assign_at_indices_ $suffix>]() {
1272                $crate::vector::tests::test_strided_view_assign_at_indices::<<$V as $crate::DefaultDenseMatrix>::M>($ctx2);
1273            }
1274            #[test]
1275            fn [<test_strided_view_copy_from_indices_ $suffix>]() {
1276                $crate::vector::tests::test_strided_view_copy_from_indices::<<$V as $crate::DefaultDenseMatrix>::M>($ctx2);
1277            }
1278            #[test]
1279            fn [<test_strided_view_gather_ $suffix>]() {
1280                $crate::vector::tests::test_strided_view_gather::<<$V as $crate::DefaultDenseMatrix>::M>($ctx2);
1281            }
1282            #[test]
1283            fn [<test_strided_view_scatter_ $suffix>]() {
1284                $crate::vector::tests::test_strided_view_scatter::<<$V as $crate::DefaultDenseMatrix>::M>($ctx2);
1285            }
1286        }
1287    };
1288}
1289#[cfg(test)]
1290#[cfg_attr(not(feature = "cuda"), allow(unused_imports))]
1291pub(crate) use generate_vector_tests_batched;
1292#[cfg(test)]
1293pub(crate) use generate_vector_tests_nonbatched;
1294
1295#[cfg(test)]
1296pub(crate) mod tests {
1297    use std::ops::{Add, Sub};
1298    use std::panic::{catch_unwind, AssertUnwindSafe};
1299
1300    use super::{Vector, VectorCommon, VectorIndex, VectorView, VectorViewMut};
1301    use crate::context::nalgebra::NalgebraContext;
1302    use crate::scalar::Scalar as _;
1303    use crate::scalar::Scale;
1304    use crate::vector::nalgebra_serial::NalgebraVec;
1305    use crate::Context;
1306    use crate::IndexType;
1307    use num_traits::FromPrimitive;
1308    use std::ops::{Index, IndexMut};
1309
1310    fn f<V: Vector>(x: f64) -> V::T {
1311        V::T::from_f64(x).unwrap()
1312    }
1313
1314    fn fv<V: Vector>(xs: &[f64]) -> Vec<V::T> {
1315        xs.iter().map(|&x| f::<V>(x)).collect()
1316    }
1317
1318    /// `Index`/`IndexMut` operator syntax: only the host backends offer it, so this isn't wired
1319    /// into the shared (CUDA-inclusive) macro suite.
1320    pub fn test_host_only<V>()
1321    where
1322        V: Vector + Index<IndexType, Output = V::T> + IndexMut<IndexType, Output = V::T>,
1323    {
1324        let mut v = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1325        assert_eq!(v[0], f::<V>(1.0));
1326        v[1] = f::<V>(20.0);
1327        assert_eq!(v.clone_as_vec(), fv::<V>(&[1.0, 20.0, 3.0]));
1328    }
1329
1330    pub fn test_root_finding<V: Vector>() {
1331        let g0 = V::from_vec(fv::<V>(&[1.0, -2.0, 3.0]), Default::default());
1332        let g1 = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1333        let (found_root, max_frac, max_frac_index) = g0.root_finding(&g1);
1334        assert!(!found_root);
1335        assert_eq!(max_frac, f::<V>(0.5));
1336        assert_eq!(max_frac_index, 1);
1337
1338        let g0 = V::from_vec(fv::<V>(&[1.0, -2.0, 3.0]), Default::default());
1339        let g1 = V::from_vec(fv::<V>(&[1.0, 2.0, 0.0]), Default::default());
1340        let (found_root, max_frac, max_frac_index) = g0.root_finding(&g1);
1341        assert!(found_root);
1342        assert_eq!(max_frac, f::<V>(0.5));
1343        assert_eq!(max_frac_index, 1);
1344
1345        let g0 = V::from_vec(fv::<V>(&[1.0, -2.0, 3.0]), Default::default());
1346        let g1 = V::from_vec(fv::<V>(&[1.0, -2.0, 3.0]), Default::default());
1347        let (found_root, max_frac, max_frac_index) = g0.root_finding(&g1);
1348        assert!(!found_root);
1349        assert_eq!(max_frac, f::<V>(0.0));
1350        assert_eq!(max_frac_index, -1);
1351    }
1352
1353    pub fn test_from_slice<V: Vector>() {
1354        let slice = fv::<V>(&[1.0, 2.0, 3.0]);
1355        let v = V::from_slice(&slice, Default::default());
1356        assert_eq!(v.clone_as_vec(), slice);
1357    }
1358
1359    pub fn test_mul_scalar<V: Vector>() {
1360        let v = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1361        let result = v * Scale(f::<V>(2.0));
1362        assert_eq!(result.clone_as_vec(), fv::<V>(&[2.0, 4.0, 6.0]));
1363    }
1364
1365    pub fn test_div_scalar<V: Vector>() {
1366        let v = V::from_vec(fv::<V>(&[2.0, 4.0, 6.0]), Default::default());
1367        let result = v / Scale(f::<V>(2.0));
1368        assert_eq!(result.clone_as_vec(), fv::<V>(&[1.0, 2.0, 3.0]));
1369    }
1370
1371    pub fn test_axpy<V: Vector>() {
1372        let mut y = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1373        let x = V::from_vec(fv::<V>(&[4.0, 5.0, 6.0]), Default::default());
1374        y.axpy(f::<V>(2.0), &x, f::<V>(1.0));
1375        assert_eq!(y.clone_as_vec(), fv::<V>(&[9.0, 12.0, 15.0]));
1376        y.axpy(f::<V>(2.0), &x, f::<V>(0.0));
1377        assert_eq!(y.clone_as_vec(), fv::<V>(&[8.0, 10.0, 12.0]));
1378        y.axpy(f::<V>(0.0), &x, f::<V>(1.0));
1379        assert_eq!(y.clone_as_vec(), fv::<V>(&[8.0, 10.0, 12.0]));
1380    }
1381
1382    pub fn test_copy_from_indices<V: Vector>() {
1383        let mut v1 = V::zeros(5, Default::default());
1384        let v2 = V::from_vec(fv::<V>(&[10.0, 20.0, 30.0, 40.0, 50.0]), Default::default());
1385        let indices = V::Index::from_vec(vec![0, 2, 4], Default::default());
1386        v1.copy_from_indices(&v2, &indices);
1387        assert_eq!(v1.clone_as_vec(), fv::<V>(&[10.0, 0.0, 30.0, 0.0, 50.0]));
1388    }
1389
1390    pub fn test_gather<V: Vector>() {
1391        let mut result = V::zeros(3, Default::default());
1392        let v = V::from_vec(fv::<V>(&[10.0, 20.0, 30.0, 40.0]), Default::default());
1393        let indices = V::Index::from_vec(vec![3, 0, 2], Default::default());
1394        result.gather(&v, &indices);
1395        assert_eq!(result.clone_as_vec(), fv::<V>(&[40.0, 10.0, 30.0]));
1396    }
1397
1398    pub fn test_scatter<V: Vector>() {
1399        let v = V::from_vec(fv::<V>(&[40.0, 10.0, 30.0]), Default::default());
1400        let indices = V::Index::from_vec(vec![3, 0, 2], Default::default());
1401        let mut result = V::zeros(4, Default::default());
1402        v.scatter(&indices, &mut result);
1403        assert_eq!(result.clone_as_vec(), fv::<V>(&[10.0, 0.0, 30.0, 40.0]));
1404    }
1405
1406    pub fn test_copy_from_via_view_mut<V: Vector>() {
1407        let mut v1 = V::zeros(3, Default::default());
1408        let v2 = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1409        v1.as_view_mut().copy_from(&v2);
1410        assert_eq!(v1.clone_as_vec(), fv::<V>(&[1.0, 2.0, 3.0]));
1411    }
1412
1413    pub fn test_set_index<V: Vector>() {
1414        let mut v = V::zeros(3, Default::default());
1415        v.set_index(1, f::<V>(42.0));
1416        assert_eq!(v.clone_as_vec(), fv::<V>(&[0.0, 42.0, 0.0]));
1417    }
1418
1419    pub fn test_get_index<V: Vector>() {
1420        let v = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1421        assert_eq!(v.get_index(0), f::<V>(1.0));
1422        assert_eq!(v.get_index(2), f::<V>(3.0));
1423    }
1424
1425    pub fn test_norm<V: Vector>() {
1426        let v = V::from_vec(fv::<V>(&[3.0, 4.0]), Default::default());
1427        let norm = v.norm(2);
1428        let diff = norm - f::<V>(5.0);
1429        assert!(num_traits::abs(diff) < f::<V>(1e-12));
1430    }
1431
1432    pub fn test_norm_l1<V: Vector>() {
1433        let v = V::from_vec(fv::<V>(&[3.0, -4.0]), Default::default());
1434        let norm = v.norm(1);
1435        let diff = norm - f::<V>(7.0);
1436        assert!(num_traits::abs(diff) < f::<V>(1e-12));
1437    }
1438
1439    pub fn test_squared_norm<V: Vector>() {
1440        let x = V::from_vec(fv::<V>(&[1.0, 2.0]), Default::default());
1441        let y = V::from_vec(fv::<V>(&[1.0, 1.0]), Default::default());
1442        let atol = V::from_vec(fv::<V>(&[1e-3, 1e-3]), Default::default());
1443        let rtol = f::<V>(1e-2);
1444        let norm = x.squared_norm(&y, &atol, rtol);
1445        let denom = f::<V>(1.0) * rtol + f::<V>(1e-3);
1446        let err0 = f::<V>(1.0) / denom;
1447        let err1 = f::<V>(2.0) / denom;
1448        let expected = (err0 * err0 + err1 * err1) / f::<V>(2.0);
1449        assert!(num_traits::abs(norm - expected) < f::<V>(1e-12));
1450    }
1451
1452    pub fn test_fill<V: Vector>() {
1453        let mut v = V::zeros(3, Default::default());
1454        v.fill(f::<V>(7.0));
1455        assert_eq!(v.clone_as_vec(), fv::<V>(&[7.0, 7.0, 7.0]));
1456    }
1457
1458    pub fn test_from_element<V: Vector>() {
1459        let v = V::from_element(2, f::<V>(5.0), Default::default());
1460        assert_eq!(v.clone_as_vec(), fv::<V>(&[5.0, 5.0]));
1461    }
1462
1463    pub fn test_copy_from<V: Vector>() {
1464        let mut v1 = V::zeros(3, Default::default());
1465        let v2 = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1466        v1.copy_from(&v2);
1467        assert_eq!(v1.clone_as_vec(), fv::<V>(&[1.0, 2.0, 3.0]));
1468    }
1469
1470    pub fn test_from_vec<V: Vector>() {
1471        let v = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1472        assert_eq!(v.clone_as_vec(), fv::<V>(&[1.0, 2.0, 3.0]));
1473    }
1474
1475    pub fn test_component_mul_assign<V: Vector>() {
1476        let mut a = V::from_vec(fv::<V>(&[2.0, 3.0]), Default::default());
1477        let b = V::from_vec(fv::<V>(&[10.0, 20.0]), Default::default());
1478        a.component_mul_assign(&b);
1479        assert_eq!(a.clone_as_vec(), fv::<V>(&[20.0, 60.0]));
1480    }
1481
1482    pub fn test_component_div_assign<V: Vector>() {
1483        let mut a = V::from_vec(fv::<V>(&[6.0, 12.0]), Default::default());
1484        let b = V::from_vec(fv::<V>(&[2.0, 3.0]), Default::default());
1485        a.component_div_assign(&b);
1486        assert_eq!(a.clone_as_vec(), fv::<V>(&[3.0, 4.0]));
1487    }
1488
1489    pub fn test_assign_at_indices<V: Vector>() {
1490        let mut v = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1491        let indices = V::Index::from_vec(vec![0, 2], Default::default());
1492        v.assign_at_indices(&indices, f::<V>(0.0));
1493        assert_eq!(v.clone_as_vec(), fv::<V>(&[0.0, 2.0, 0.0]));
1494    }
1495
1496    pub fn test_add<V: Vector>() {
1497        let a = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1498        let b = V::from_vec(fv::<V>(&[10.0, 20.0, 30.0]), Default::default());
1499        let c = a + b;
1500        assert_eq!(c.clone_as_vec(), fv::<V>(&[11.0, 22.0, 33.0]));
1501    }
1502
1503    pub fn test_sub<V: Vector>() {
1504        let a = V::from_vec(fv::<V>(&[10.0, 20.0, 30.0]), Default::default());
1505        let b = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1506        let c = a - b;
1507        assert_eq!(c.clone_as_vec(), fv::<V>(&[9.0, 18.0, 27.0]));
1508    }
1509
1510    /// A borrowed left-hand side with an owned right-hand side: backends may write the result
1511    /// into the right-hand side's storage, which must not flip the operands of `sub`.
1512    pub fn test_owned_rhs<V: Vector>()
1513    where
1514        for<'a> &'a V: Add<V, Output = V> + Sub<V, Output = V>,
1515    {
1516        let a = V::from_vec(fv::<V>(&[10.0, 20.0, 30.0]), Default::default());
1517        let b = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1518        let c = &a - b;
1519        assert_eq!(c.clone_as_vec(), fv::<V>(&[9.0, 18.0, 27.0]));
1520
1521        let b = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1522        let c = &a + b;
1523        assert_eq!(c.clone_as_vec(), fv::<V>(&[11.0, 22.0, 33.0]));
1524
1525        let b = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1526        let c = a.as_view() - b;
1527        assert_eq!(c.clone_as_vec(), fv::<V>(&[9.0, 18.0, 27.0]));
1528
1529        let b = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1530        let c = a.as_view() + b;
1531        assert_eq!(c.clone_as_vec(), fv::<V>(&[11.0, 22.0, 33.0]));
1532    }
1533
1534    /// Both sides borrowed via `as_view()`: exercises the view+view arithmetic impls that
1535    /// `VectorView` requires but the owned/ref-hand-side tests above never reach.
1536    pub fn test_view_add_sub<V: Vector>() {
1537        let a = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1538        let b = V::from_vec(fv::<V>(&[10.0, 20.0, 30.0]), Default::default());
1539        let c = a.as_view() + b.as_view();
1540        assert_eq!(c.clone_as_vec(), fv::<V>(&[11.0, 22.0, 33.0]));
1541        let d = a.as_view() - b.as_view();
1542        assert_eq!(d.clone_as_vec(), fv::<V>(&[-9.0, -18.0, -27.0]));
1543    }
1544
1545    /// `VectorIndex::is_empty` and `VectorIndex::zeros` are default/trait methods never called
1546    /// through the index-construction helpers used elsewhere.
1547    pub fn test_index_zeros_and_is_empty<V: Vector>() {
1548        let empty = V::Index::zeros(0, Default::default());
1549        assert!(empty.is_empty());
1550        let non_empty = V::Index::zeros(3, Default::default());
1551        assert!(!non_empty.is_empty());
1552        assert_eq!(non_empty.clone_as_vec(), vec![0; 3]);
1553    }
1554
1555    pub fn test_add_assign<V: Vector>() {
1556        let mut a = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1557        let b = V::from_vec(fv::<V>(&[10.0, 20.0, 30.0]), Default::default());
1558        a += b;
1559        assert_eq!(a.clone_as_vec(), fv::<V>(&[11.0, 22.0, 33.0]));
1560    }
1561
1562    pub fn test_sub_assign<V: Vector>() {
1563        let mut a = V::from_vec(fv::<V>(&[10.0, 20.0, 30.0]), Default::default());
1564        let b = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1565        a -= b;
1566        assert_eq!(a.clone_as_vec(), fv::<V>(&[9.0, 18.0, 27.0]));
1567    }
1568
1569    pub fn test_axpy_v<V: Vector>() {
1570        let mut y = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1571        let x = V::from_vec(fv::<V>(&[4.0, 5.0, 6.0]), Default::default());
1572        let x_view = x.as_view();
1573        y.axpy_v(f::<V>(2.0), &x_view, f::<V>(1.0));
1574        assert_eq!(y.clone_as_vec(), fv::<V>(&[9.0, 12.0, 15.0]));
1575    }
1576
1577    pub fn test_mul_assign_scalar<V: Vector>() {
1578        let mut v = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1579        v *= Scale(f::<V>(2.0));
1580        assert_eq!(v.clone_as_vec(), fv::<V>(&[2.0, 4.0, 6.0]));
1581    }
1582
1583    pub fn test_copy_from_view<V: Vector>() {
1584        let mut v1 = V::zeros(3, Default::default());
1585        let v2 = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1586        let view = v2.as_view();
1587        v1.copy_from_view(&view);
1588        assert_eq!(v1.clone_as_vec(), fv::<V>(&[1.0, 2.0, 3.0]));
1589    }
1590
1591    pub fn test_view_squared_norm<V: Vector>() {
1592        let x = V::from_vec(fv::<V>(&[1.0, 2.0]), Default::default());
1593        let y = V::from_vec(fv::<V>(&[1.0, 1.0]), Default::default());
1594        let atol = V::from_vec(fv::<V>(&[1e-3, 1e-3]), Default::default());
1595        let rtol = f::<V>(1e-2);
1596        let view = x.as_view();
1597        let norm = VectorView::squared_norm(&view, &y, &atol, rtol);
1598        let denom = f::<V>(1.0) * rtol + f::<V>(1e-3);
1599        let err0 = f::<V>(1.0) / denom;
1600        let err1 = f::<V>(2.0) / denom;
1601        let expected = (err0 * err0 + err1 * err1) / f::<V>(2.0);
1602        assert!(num_traits::abs(norm - expected) < f::<V>(1e-12));
1603    }
1604
1605    pub fn test_view_into_owned<V: Vector>() {
1606        let v = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1607        let view = v.as_view();
1608        let owned = view.into_owned();
1609        assert_eq!(owned.clone_as_vec(), fv::<V>(&[1.0, 2.0, 3.0]));
1610    }
1611
1612    pub fn test_view_get_index<V: Vector>() {
1613        let v = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1614        let view = v.as_view();
1615        assert_eq!(view.get_index(1), f::<V>(2.0));
1616        assert_eq!(view.get_index(2), f::<V>(3.0));
1617    }
1618
1619    pub fn test_view_mut_axpy<V: Vector>() {
1620        let mut y = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1621        let x = V::from_vec(fv::<V>(&[4.0, 5.0, 6.0]), Default::default());
1622        {
1623            let mut y_view = y.as_view_mut();
1624            y_view.axpy(f::<V>(2.0), &x, f::<V>(1.0));
1625        }
1626        assert_eq!(y.clone_as_vec(), fv::<V>(&[9.0, 12.0, 15.0]));
1627    }
1628
1629    pub fn test_view_mut_set_index<V: Vector>() {
1630        let mut v = V::zeros(3, Default::default());
1631        {
1632            let mut view = v.as_view_mut();
1633            view.set_index(1, f::<V>(42.0));
1634        }
1635        assert_eq!(v.clone_as_vec(), fv::<V>(&[0.0, 42.0, 0.0]));
1636    }
1637
1638    pub fn test_view_mut_mul_assign_scalar<V: Vector>() {
1639        let mut v = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1640        {
1641            let mut view = v.as_view_mut();
1642            view *= Scale(f::<V>(2.0));
1643        }
1644        assert_eq!(v.clone_as_vec(), fv::<V>(&[2.0, 4.0, 6.0]));
1645    }
1646
1647    pub fn test_view_mut_copy_from_view<V: Vector>() {
1648        let mut v1 = V::zeros(3, Default::default());
1649        let v2 = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), Default::default());
1650        {
1651            let v2_view = v2.as_view();
1652            let mut v1_view = v1.as_view_mut();
1653            v1_view.copy_from_view(&v2_view);
1654        }
1655        assert_eq!(v1.clone_as_vec(), fv::<V>(&[1.0, 2.0, 3.0]));
1656    }
1657
1658    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
1659    pub fn test_batched_len_and_total_len<V: Vector>(ctx: V::C) {
1660        let nbatch = ctx.nbatch();
1661        assert!(nbatch > 1);
1662        let v = V::zeros(4, ctx);
1663        assert_eq!(v.len(), 4);
1664        assert_eq!(v.total_len(), 4 * nbatch);
1665    }
1666
1667    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
1668    pub fn test_batched_from_vec<V: Vector>(ctx: V::C) {
1669        assert_eq!(ctx.nbatch(), 2);
1670        let v = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]), ctx);
1671        assert_eq!(v.len(), 3);
1672        assert_eq!(v.total_len(), 6);
1673        assert_eq!(v.clone_as_vec(), fv::<V>(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]));
1674    }
1675
1676    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
1677    pub fn test_batched_from_vec_bad_length<V: Vector>(ctx: V::C) {
1678        assert_eq!(ctx.nbatch(), 2);
1679        let _v = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), ctx);
1680    }
1681
1682    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
1683    pub fn test_batched_from_element<V: Vector>(ctx: V::C) {
1684        assert_eq!(ctx.nbatch(), 3);
1685        let v = V::from_element(2, f::<V>(5.0), ctx);
1686        assert_eq!(v.len(), 2);
1687        assert_eq!(v.clone_as_vec(), fv::<V>(&[5.0, 5.0, 5.0, 5.0, 5.0, 5.0]));
1688    }
1689
1690    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
1691    pub fn test_batched_axpy<V: Vector>(ctx: V::C) {
1692        assert_eq!(ctx.nbatch(), 2);
1693        let mut y = V::from_vec(fv::<V>(&[1.0, 2.0, 10.0, 20.0]), ctx.clone());
1694        let x = V::from_vec(fv::<V>(&[3.0, 4.0, 30.0, 40.0]), ctx);
1695        y.axpy(f::<V>(2.0), &x, f::<V>(1.0));
1696        assert_eq!(y.clone_as_vec(), fv::<V>(&[7.0, 10.0, 70.0, 100.0]));
1697    }
1698
1699    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
1700    pub fn test_batched_add<V: Vector>(ctx: V::C) {
1701        assert_eq!(ctx.nbatch(), 2);
1702        let a = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0, 4.0]), ctx.clone());
1703        let b = V::from_vec(fv::<V>(&[10.0, 20.0, 30.0, 40.0]), ctx);
1704        let c = a + b;
1705        assert_eq!(c.len(), 2);
1706        assert_eq!(c.clone_as_vec(), fv::<V>(&[11.0, 22.0, 33.0, 44.0]));
1707    }
1708
1709    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
1710    pub fn test_batched_norm_max_across_batches<V: Vector>(ctx: V::C) {
1711        assert_eq!(ctx.nbatch(), 2);
1712        let v = V::from_vec(fv::<V>(&[1.0, 0.0, 0.0, 3.0]), ctx);
1713        let norm = v.norm(2);
1714        let diff = norm - f::<V>(3.0);
1715        assert!(num_traits::abs(diff) < f::<V>(1e-12));
1716    }
1717
1718    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
1719    pub fn test_batched_norm_l1<V: Vector>(ctx: V::C) {
1720        assert_eq!(ctx.nbatch(), 2);
1721        let v = V::from_vec(fv::<V>(&[1.0, -2.0, 3.0, 0.0]), ctx);
1722        let norm = v.norm(1);
1723        // batch0: |1|+|-2| = 3, batch1: |3|+|0| = 3, max = 3
1724        let diff = norm - f::<V>(3.0);
1725        assert!(num_traits::abs(diff) < f::<V>(1e-12));
1726    }
1727
1728    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
1729    pub fn test_batched_squared_norm<V: Vector>(ctx: V::C) {
1730        assert_eq!(ctx.nbatch(), 2);
1731        let x = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0, 4.0]), ctx.clone());
1732        let y = V::from_vec(fv::<V>(&[1.0, 1.0, 1.0, 1.0]), ctx);
1733        let atol = V::from_vec(fv::<V>(&[1e-3, 1e-3]), V::C::default());
1734        let rtol = f::<V>(1e-2);
1735        let norm = x.squared_norm(&y, &atol, rtol);
1736        let denom = f::<V>(1.0) * rtol + f::<V>(1e-3);
1737        let err3 = f::<V>(3.0) / denom;
1738        let err4 = f::<V>(4.0) / denom;
1739        let batch1 = (err3 * err3 + err4 * err4) / f::<V>(2.0);
1740        let diff = norm - batch1;
1741        assert!(num_traits::abs(diff) < f::<V>(1e-12));
1742    }
1743
1744    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
1745    pub fn test_batched_fill_index<V: Vector>(ctx: V::C) {
1746        assert_eq!(ctx.nbatch(), 3);
1747        let mut v = V::zeros(2, ctx);
1748        v.fill_index(0, f::<V>(42.0));
1749        assert_eq!(
1750            v.clone_as_vec(),
1751            fv::<V>(&[42.0, 0.0, 42.0, 0.0, 42.0, 0.0])
1752        );
1753    }
1754
1755    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
1756    pub fn test_batched_get_index_panics<V: Vector>(ctx: V::C) {
1757        assert!(ctx.nbatch() > 1);
1758        let v = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0, 4.0]), ctx);
1759        let _val = v.get_index(0);
1760    }
1761
1762    /// Scalar writes are single-system too: a batched vector needs `fill_index` or a batch view.
1763    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
1764    pub fn test_batched_set_index_panics<V: Vector>(ctx: V::C) {
1765        assert!(ctx.nbatch() > 1);
1766        let mut v = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0, 4.0]), ctx);
1767        v.set_index(0, f::<V>(5.0));
1768    }
1769
1770    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
1771    pub fn test_batched_fill<V: Vector>(ctx: V::C) {
1772        assert_eq!(ctx.nbatch(), 2);
1773        let mut v = V::zeros(3, ctx);
1774        v.fill(f::<V>(7.0));
1775        assert_eq!(v.clone_as_vec(), fv::<V>(&[7.0, 7.0, 7.0, 7.0, 7.0, 7.0]));
1776    }
1777
1778    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
1779    pub fn test_batched_component_mul<V: Vector>(ctx: V::C) {
1780        assert_eq!(ctx.nbatch(), 2);
1781        let mut a = V::from_vec(fv::<V>(&[2.0, 3.0, 4.0, 5.0]), ctx.clone());
1782        let b = V::from_vec(fv::<V>(&[10.0, 20.0, 30.0, 40.0]), ctx);
1783        a.component_mul_assign(&b);
1784        assert_eq!(a.clone_as_vec(), fv::<V>(&[20.0, 60.0, 120.0, 200.0]));
1785    }
1786
1787    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
1788    pub fn test_batched_assign_at_indices<V: Vector>(ctx: V::C) {
1789        assert_eq!(ctx.nbatch(), 2);
1790        let mut v = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]), ctx);
1791        let indices = V::Index::from_vec(vec![0, 2], Default::default());
1792        v.assign_at_indices(&indices, f::<V>(0.0));
1793        assert_eq!(v.clone_as_vec(), fv::<V>(&[0.0, 2.0, 0.0, 0.0, 5.0, 0.0]));
1794    }
1795
1796    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
1797    pub fn test_batched_root_finding_consistent<V: Vector>(ctx: V::C) {
1798        assert_eq!(ctx.nbatch(), 2);
1799        let g0 = V::from_vec(fv::<V>(&[1.0, -1.0, 1.0, -1.0]), ctx.clone());
1800        let g1 = V::from_vec(fv::<V>(&[-1.0, 1.0, -1.0, 1.0]), ctx);
1801        let (found, _frac, idx) = g0.root_finding(&g1);
1802        assert!(!found);
1803        assert!(idx >= 0);
1804    }
1805
1806    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
1807    pub fn test_batched_root_finding_inconsistent<V: Vector>(ctx: V::C) {
1808        assert_eq!(ctx.nbatch(), 2);
1809        let g0 = V::from_vec(fv::<V>(&[1.0, 1.0, 1.0, -1.0]), ctx.clone());
1810        let g1 = V::from_vec(fv::<V>(&[-1.0, 1.0, 1.0, 1.0]), ctx);
1811        let _result = g0.root_finding(&g1);
1812    }
1813
1814    // --- Broadcasting tests ---
1815
1816    #[cfg_attr(not(any(feature = "cuda", feature = "cuda-oxide")), allow(dead_code))]
1817    /// `for_each_batch` with 0, 1 and 2 operands on an unbatched vector.
1818    ///
1819    /// Every lane body is bound once and run through both the device-preferring method and the
1820    /// host one, so a backend with a device path is checked against its own staging path.
1821    pub fn test_for_each_batch<V: Vector>() {
1822        let x = V::from_vec(fv::<V>(&[1.0, 2.0]), V::C::default());
1823        let v = V::from_vec(fv::<V>(&[3.0, 4.0]), V::C::default());
1824        let mut y = V::zeros(2, V::C::default());
1825        let mut y_host = V::zeros(2, V::C::default());
1826
1827        let set = |y: &mut [V::T], _: [&[V::T]; 0], _lane: usize| y[0] = f::<V>(7.0);
1828        y.for_each_batch([], set);
1829        y_host.for_each_batch_host([], set);
1830        assert_eq!(y.clone_as_vec(), fv::<V>(&[7.0, 0.0]));
1831        assert_eq!(y_host.clone_as_vec(), y.clone_as_vec());
1832
1833        let take_second = |y: &mut [V::T], [x]: [&[V::T]; 1], _lane: usize| y[1] = x[1];
1834        y.for_each_batch([&x], take_second);
1835        y_host.for_each_batch_host([&x], take_second);
1836        assert_eq!(y.clone_as_vec(), fv::<V>(&[7.0, 2.0]));
1837        assert_eq!(y_host.clone_as_vec(), y.clone_as_vec());
1838
1839        let combine = |y: &mut [V::T], [x, v]: [&[V::T]; 2], _lane: usize| {
1840            y[0] = x[0] * v[0] + x[1] * v[1];
1841            y[1] = x[0] - v[0];
1842        };
1843        y.for_each_batch([&x, &v], combine);
1844        y_host.for_each_batch_host([&x, &v], combine);
1845        assert_eq!(y.clone_as_vec(), fv::<V>(&[11.0, -2.0]));
1846        assert_eq!(y_host.clone_as_vec(), y.clone_as_vec());
1847    }
1848
1849    /// Each lane of `self` sees its own slice, and operands broadcast into it.
1850    pub fn test_batched_for_each_batch<V: Vector>(ctx: V::C) {
1851        assert_eq!(ctx.nbatch(), 2);
1852        // operand with nbatch == 2 matches lane for lane
1853        let x = V::from_vec(fv::<V>(&[1.0, 2.0, 10.0, 20.0]), ctx.clone());
1854        // operand with nbatch == 1 broadcasts to every lane
1855        let v = V::from_vec(fv::<V>(&[3.0, 4.0]), V::C::default());
1856        let mut y = V::zeros(2, ctx.clone());
1857        let mut y_host = V::zeros(2, ctx.clone());
1858        let combine = |y: &mut [V::T], [x, v]: [&[V::T]; 2], _lane: usize| {
1859            y[0] = x[0] + v[0];
1860            y[1] = x[1] * v[1];
1861        };
1862        y.for_each_batch([&x, &v], combine);
1863        y_host.for_each_batch_host([&x, &v], combine);
1864        assert_eq!(y.clone_as_vec(), fv::<V>(&[4.0, 8.0, 13.0, 80.0]));
1865        assert_eq!(y_host.clone_as_vec(), y.clone_as_vec());
1866
1867        // grouped broadcast: lanes 0,1 of a 4-lane destination read batch 0 of a 2-lane operand
1868        let ctx4 = ctx.clone_with_nbatch(4).unwrap();
1869        let mut y4 = V::zeros(1, ctx4.clone());
1870        let mut y4_host = V::zeros(1, ctx4);
1871        let first = |y: &mut [V::T], [x]: [&[V::T]; 1], _lane: usize| y[0] = x[0];
1872        y4.for_each_batch([&x], first);
1873        y4_host.for_each_batch_host([&x], first);
1874        assert_eq!(y4.clone_as_vec(), fv::<V>(&[1.0, 1.0, 10.0, 10.0]));
1875        assert_eq!(y4_host.clone_as_vec(), y4.clone_as_vec());
1876
1877        // a lane body that reduces over the lane rather than indexing it, as the sensitivity
1878        // corrections in `diffsol`'s `ode_solver::state` do
1879        let mut dot = V::zeros(1, ctx.clone());
1880        let mut dot_host = V::zeros(1, ctx);
1881        let reduce = |y: &mut [V::T], [x, v]: [&[V::T]; 2], _lane: usize| {
1882            y[0] = x
1883                .iter()
1884                .zip(v.iter())
1885                .fold(f::<V>(0.0), |acc, (x, v)| acc + *x * *v);
1886        };
1887        dot.for_each_batch([&x, &v], reduce);
1888        dot_host.for_each_batch_host([&x, &v], reduce);
1889        assert_eq!(dot.clone_as_vec(), fv::<V>(&[11.0, 110.0]));
1890        assert_eq!(dot_host.clone_as_vec(), dot.clone_as_vec());
1891    }
1892
1893    #[cfg_attr(not(any(feature = "cuda", feature = "cuda-oxide")), allow(dead_code))]
1894    /// Both reductions on an unbatched vector: `reduce_batch` degenerates to the map, and
1895    /// `reduce_elem` folds the whole vector to one value.
1896    pub fn test_reduce_unbatched<V: Vector>() {
1897        let x = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), V::C::default());
1898        let sum = |a: V::T, b: V::T| a + b;
1899
1900        let mut dest = V::zeros(3, V::C::default());
1901        let mut dest_host = V::zeros(3, V::C::default());
1902        let double = |[x]: [&[V::T]; 1], _lane: usize, i: usize| x[i] + x[i];
1903        V::reduce_batch(&mut dest, [&x], f::<V>(0.0), double, sum);
1904        V::reduce_batch_host(&mut dest_host, [&x], f::<V>(0.0), double, sum);
1905        assert_eq!(dest.clone_as_vec(), fv::<V>(&[2.0, 4.0, 6.0]));
1906        assert_eq!(dest_host.clone_as_vec(), dest.clone_as_vec());
1907
1908        let mut total = V::zeros(1, V::C::default());
1909        let mut total_host = V::zeros(1, V::C::default());
1910        let pick = |[x]: [&[V::T]; 1], _lane: usize, i: usize| x[i];
1911        V::reduce_elem(&mut total, [&x], f::<V>(0.0), pick, sum);
1912        V::reduce_elem_host(&mut total_host, [&x], f::<V>(0.0), pick, sum);
1913        assert_eq!(total.clone_as_vec(), fv::<V>(&[6.0]));
1914        assert_eq!(total_host.clone_as_vec(), total.clone_as_vec());
1915    }
1916
1917    /// `reduce_batch` collapses the lanes elementwise, leaving an unbatched vector.
1918    pub fn test_batched_reduce_batch<V: Vector>(ctx: V::C) {
1919        assert_eq!(ctx.nbatch(), 2);
1920        let x = V::from_vec(fv::<V>(&[1.0, 2.0, 10.0, 20.0]), ctx.clone());
1921        let pick = |[x]: [&[V::T]; 1], _lane: usize, i: usize| x[i];
1922
1923        let mut sum = V::zeros(2, V::C::default());
1924        let mut sum_host = V::zeros(2, V::C::default());
1925        V::reduce_batch(&mut sum, [&x], f::<V>(0.0), pick, |a, b| a + b);
1926        V::reduce_batch_host(&mut sum_host, [&x], f::<V>(0.0), pick, |a, b| a + b);
1927        assert_eq!(sum.clone_as_vec(), fv::<V>(&[11.0, 22.0]));
1928        assert_eq!(sum_host.clone_as_vec(), sum.clone_as_vec());
1929
1930        let mut max = V::zeros(2, V::C::default());
1931        let mut max_host = V::zeros(2, V::C::default());
1932        V::reduce_batch(&mut max, [&x], f::<V>(0.0), pick, |a, b| a.max(b));
1933        V::reduce_batch_host(&mut max_host, [&x], f::<V>(0.0), pick, |a, b| a.max(b));
1934        assert_eq!(max.clone_as_vec(), fv::<V>(&[10.0, 20.0]));
1935        assert_eq!(max_host.clone_as_vec(), max.clone_as_vec());
1936    }
1937
1938    /// `reduce_elem` collapses each lane to one value, leaving a batched scalar.
1939    ///
1940    /// The dot product here is the one `test_batched_for_each_batch` builds by hand, so the
1941    /// expected values are pinned by an independent route.
1942    pub fn test_batched_reduce_elem<V: Vector>(ctx: V::C) {
1943        assert_eq!(ctx.nbatch(), 2);
1944        let x = V::from_vec(fv::<V>(&[1.0, 2.0, 10.0, 20.0]), ctx.clone());
1945        let v = V::from_vec(fv::<V>(&[3.0, 4.0]), V::C::default());
1946
1947        let mut dot = V::zeros(1, ctx.clone());
1948        let mut dot_host = V::zeros(1, ctx.clone());
1949        let prod = |[x, v]: [&[V::T]; 2], _lane: usize, i: usize| x[i] * v[i];
1950        V::reduce_elem(&mut dot, [&x, &v], f::<V>(0.0), prod, |a, b| a + b);
1951        V::reduce_elem_host(&mut dot_host, [&x, &v], f::<V>(0.0), prod, |a, b| a + b);
1952        assert_eq!(dot.clone_as_vec(), fv::<V>(&[11.0, 110.0]));
1953        assert_eq!(dot_host.clone_as_vec(), dot.clone_as_vec());
1954
1955        let mut max = V::zeros(1, ctx.clone());
1956        let mut max_host = V::zeros(1, ctx);
1957        let pick = |[x]: [&[V::T]; 1], _lane: usize, i: usize| x[i];
1958        V::reduce_elem(&mut max, [&x], f::<V>(0.0), pick, |a, b| a.max(b));
1959        V::reduce_elem_host(&mut max_host, [&x], f::<V>(0.0), pick, |a, b| a.max(b));
1960        assert_eq!(max.clone_as_vec(), fv::<V>(&[2.0, 20.0]));
1961        assert_eq!(max_host.clone_as_vec(), max.clone_as_vec());
1962    }
1963
1964    /// An `nbatch == 1` operand broadcasts into every lane of a reduction.
1965    pub fn test_batched_reduce_broadcast<V: Vector>(ctx: V::C) {
1966        assert_eq!(ctx.nbatch(), 2);
1967        let x = V::from_vec(fv::<V>(&[1.0, 2.0, 10.0, 20.0]), ctx.clone());
1968        let v = V::from_vec(fv::<V>(&[3.0, 4.0]), V::C::default());
1969        let sum = |a: V::T, b: V::T| a + b;
1970        let add = |[x, v]: [&[V::T]; 2], _lane: usize, i: usize| x[i] + v[i];
1971
1972        // (1+3) + (10+3) = 17, (2+4) + (20+4) = 30
1973        let mut dest = V::zeros(2, V::C::default());
1974        let mut dest_host = V::zeros(2, V::C::default());
1975        V::reduce_batch(&mut dest, [&x, &v], f::<V>(0.0), add, sum);
1976        V::reduce_batch_host(&mut dest_host, [&x, &v], f::<V>(0.0), add, sum);
1977        assert_eq!(dest.clone_as_vec(), fv::<V>(&[17.0, 30.0]));
1978        assert_eq!(dest_host.clone_as_vec(), dest.clone_as_vec());
1979
1980        // (1+3) + (2+4) = 10, (10+3) + (20+4) = 37
1981        let mut lane = V::zeros(1, ctx.clone());
1982        let mut lane_host = V::zeros(1, ctx);
1983        V::reduce_elem(&mut lane, [&x, &v], f::<V>(0.0), add, sum);
1984        V::reduce_elem_host(&mut lane_host, [&x, &v], f::<V>(0.0), add, sum);
1985        assert_eq!(lane.clone_as_vec(), fv::<V>(&[10.0, 37.0]));
1986        assert_eq!(lane_host.clone_as_vec(), lane.clone_as_vec());
1987    }
1988
1989    /// A lane longer than a device block, so the strided load and the in-block tree both run.
1990    pub fn test_batched_reduce_long_lane<V: Vector>(ctx: V::C) {
1991        let nbatch = ctx.nbatch();
1992        assert_eq!(nbatch, 3);
1993        // past `REDUCE_BATCH_SMALL_NSTATES` as well as `SMALL_NSTATES`, so that the device
1994        // backends run the large arm of both reductions here and the small arm of both in the
1995        // tests above
1996        let nstates = 13_000;
1997        let data: Vec<f64> = (0..nstates * nbatch).map(|i| (i % 97) as f64).collect();
1998        let x = V::from_vec(fv::<V>(&data), ctx.clone());
1999        let pick = |[x]: [&[V::T]; 1], _lane: usize, i: usize| x[i];
2000        let sum = |a: V::T, b: V::T| a + b;
2001
2002        let mut per_elem = V::zeros(nstates, V::C::default());
2003        let mut per_elem_host = V::zeros(nstates, V::C::default());
2004        V::reduce_batch(&mut per_elem, [&x], f::<V>(0.0), pick, sum);
2005        V::reduce_batch_host(&mut per_elem_host, [&x], f::<V>(0.0), pick, sum);
2006        assert_eq!(per_elem.clone_as_vec(), per_elem_host.clone_as_vec());
2007
2008        let mut per_lane = V::zeros(1, ctx.clone());
2009        let mut per_lane_host = V::zeros(1, ctx.clone());
2010        // max is exact whatever order the fold takes, unlike a sum of many terms
2011        V::reduce_elem(&mut per_lane, [&x], f::<V>(0.0), pick, |a, b| a.max(b));
2012        V::reduce_elem_host(&mut per_lane_host, [&x], f::<V>(0.0), pick, |a, b| a.max(b));
2013        assert_eq!(per_lane.clone_as_vec(), per_lane_host.clone_as_vec());
2014
2015        // the sums agree to rounding, the device folding in tree order and the host in index
2016        // order
2017        let mut sums = V::zeros(1, ctx.clone());
2018        let mut sums_host = V::zeros(1, ctx);
2019        V::reduce_elem(&mut sums, [&x], f::<V>(0.0), pick, sum);
2020        V::reduce_elem_host(&mut sums_host, [&x], f::<V>(0.0), pick, sum);
2021        sums.assert_eq_st(&sums_host, f::<V>(1e-9));
2022    }
2023
2024    /// The host reductions take `FnMut`, so a closure may carry state across calls.
2025    ///
2026    /// This cannot compile against the device-preferring methods, whose closures must be
2027    /// `Fn + Copy + Send` to be monomorphised into a kernel.
2028    pub fn test_batched_reduce_host_stateful<V: Vector>(ctx: V::C) {
2029        assert_eq!(ctx.nbatch(), 2);
2030        let x = V::from_vec(fv::<V>(&[1.0, 2.0, 10.0, 20.0]), ctx.clone());
2031
2032        let mut calls = 0usize;
2033        let mut dest = V::zeros(1, ctx);
2034        V::reduce_elem_host(
2035            &mut dest,
2036            [&x],
2037            f::<V>(0.0),
2038            |[x], _lane, i| {
2039                calls += 1;
2040                x[i]
2041            },
2042            |a, b| a + b,
2043        );
2044        assert_eq!(dest.clone_as_vec(), fv::<V>(&[3.0, 30.0]));
2045        // two lanes of two elements, each mapped once
2046        assert_eq!(calls, 4);
2047    }
2048
2049    /// `reduce_batch` removes the batch dimension, so a batched `dest` is a mistake.
2050    pub fn test_batched_reduce_batch_bad_dest<V: Vector>(ctx: V::C) {
2051        let x = V::from_vec(fv::<V>(&[1.0, 2.0, 10.0, 20.0]), ctx.clone());
2052        let mut dest = V::zeros(2, ctx);
2053        V::reduce_batch(
2054            &mut dest,
2055            [&x],
2056            f::<V>(0.0),
2057            |[x], _lane, i| x[i],
2058            |a, b| a + b,
2059        );
2060    }
2061
2062    /// `reduce_elem` removes the element dimension, so `dest` has to be a batched scalar.
2063    pub fn test_batched_reduce_elem_bad_dest<V: Vector>(ctx: V::C) {
2064        let x = V::from_vec(fv::<V>(&[1.0, 2.0, 10.0, 20.0]), ctx.clone());
2065        let mut dest = V::zeros(2, ctx);
2066        V::reduce_elem(
2067            &mut dest,
2068            [&x],
2069            f::<V>(0.0),
2070            |[x], _lane, i| x[i],
2071            |a, b| a + b,
2072        );
2073    }
2074
2075    #[cfg_attr(not(any(feature = "cuda", feature = "cuda-oxide")), allow(dead_code))]
2076    /// `for_each_batch_mut` writes every mutable operand, not just the first.
2077    pub fn test_for_each_batch_mut<V: Vector>() {
2078        let x = V::from_vec(fv::<V>(&[1.0, 2.0]), V::C::default());
2079        let mut y = V::zeros(2, V::C::default());
2080        let mut scratch = V::zeros(2, V::C::default());
2081        let mut y_host = V::zeros(2, V::C::default());
2082        let mut scratch_host = V::zeros(2, V::C::default());
2083
2084        let via_scratch = |[y, scratch]: [&mut [V::T]; 2], [x]: [&[V::T]; 1], _lane: usize| {
2085            scratch.copy_from_slice(x);
2086            scratch[0] += f::<V>(1.0);
2087            y[0] = scratch[0] * scratch[1];
2088            y[1] = scratch[1];
2089        };
2090        V::for_each_batch_mut([&mut y, &mut scratch], [&x], via_scratch);
2091        V::for_each_batch_mut_host([&mut y_host, &mut scratch_host], [&x], via_scratch);
2092        assert_eq!(y.clone_as_vec(), fv::<V>(&[4.0, 2.0]));
2093        assert_eq!(scratch.clone_as_vec(), fv::<V>(&[2.0, 2.0]));
2094        assert_eq!(y_host.clone_as_vec(), y.clone_as_vec());
2095        assert_eq!(scratch_host.clone_as_vec(), scratch.clone_as_vec());
2096    }
2097
2098    #[cfg_attr(not(any(feature = "cuda", feature = "cuda-oxide")), allow(dead_code))]
2099    /// `for_each_elem_mut` writes element `i` of every mutable operand, and reads anywhere in the
2100    /// input lane.
2101    pub fn test_for_each_elem_mut<V: Vector>() {
2102        let x = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0]), V::C::default());
2103        let mut y = V::zeros(3, V::C::default());
2104        let mut z = V::zeros(3, V::C::default());
2105        let mut y_host = V::zeros(3, V::C::default());
2106        let mut z_host = V::zeros(3, V::C::default());
2107
2108        let wrap = |[y, z]: [&mut V::T; 2], [x]: [&[V::T]; 1], _lane: usize, i: usize| {
2109            *y = x[i] + x[(i + 1) % x.len()];
2110            *z = *y * f::<V>(2.0);
2111        };
2112        V::for_each_elem_mut([&mut y, &mut z], [&x], wrap);
2113        V::for_each_batch_mut_host([&mut y_host, &mut z_host], [&x], |[y, z], [x], lane| {
2114            for i in 0..x.len() {
2115                wrap([&mut y[i], &mut z[i]], [x], lane, i);
2116            }
2117        });
2118        assert_eq!(y.clone_as_vec(), fv::<V>(&[3.0, 5.0, 4.0]));
2119        assert_eq!(z.clone_as_vec(), fv::<V>(&[6.0, 10.0, 8.0]));
2120        assert_eq!(y_host.clone_as_vec(), y.clone_as_vec());
2121        assert_eq!(z_host.clone_as_vec(), z.clone_as_vec());
2122    }
2123
2124    /// `for_each_elem_mut` broadcasts an `nbatch == 1` input over the output's lanes, and hands
2125    /// the closure the lane it is writing.
2126    pub fn test_batched_for_each_elem_mut<V: Vector>(ctx: V::C) {
2127        assert_eq!(ctx.nbatch(), 2);
2128        let x = V::from_vec(fv::<V>(&[1.0, 2.0]), V::C::default());
2129        let mut y = V::zeros(2, ctx.clone());
2130        let mut z = V::zeros(2, ctx);
2131
2132        V::for_each_elem_mut([&mut y, &mut z], [&x], |[y, z], [x], lane, i| {
2133            *y = x[i] + f::<V>(lane as f64 * 10.0);
2134            *z = x[(i + 1) % x.len()];
2135        });
2136        assert_eq!(y.clone_as_vec(), fv::<V>(&[1.0, 2.0, 11.0, 12.0]));
2137        assert_eq!(z.clone_as_vec(), fv::<V>(&[2.0, 1.0, 2.0, 1.0]));
2138    }
2139
2140    /// An `nbatch == 1` scratch is shared by every lane of a batched output.
2141    ///
2142    /// Host-only: the lanes write the shared operand in lane order, which is not something
2143    /// concurrent lanes can reproduce, so `for_each_batch_mut` rejects this shape on a backend
2144    /// with a device path.
2145    pub fn test_batched_for_each_batch_mut<V: Vector>(ctx: V::C) {
2146        assert_eq!(ctx.nbatch(), 2);
2147        let x = V::from_vec(fv::<V>(&[1.0, 2.0, 10.0, 20.0]), ctx.clone());
2148        let mut y = V::zeros(2, ctx);
2149        let mut scratch = V::zeros(2, V::C::default());
2150
2151        V::for_each_batch_mut_host([&mut y, &mut scratch], [&x], |[y, scratch], [x], lane| {
2152            scratch.copy_from_slice(x);
2153            scratch[1] += f::<V>(lane as f64);
2154            y[0] = scratch[0];
2155            y[1] = scratch[1];
2156        });
2157        assert_eq!(y.clone_as_vec(), fv::<V>(&[1.0, 2.0, 10.0, 21.0]));
2158        // the scratch holds whatever the last lane left in it
2159        assert_eq!(scratch.clone_as_vec(), fv::<V>(&[10.0, 21.0]));
2160    }
2161
2162    /// The closure's last argument is the lane being written.
2163    pub fn test_for_each_batch_index<V: Vector>() {
2164        let mut y = V::zeros(1, V::C::default());
2165        let mut y_host = V::zeros(1, V::C::default());
2166        let write_lane = |y: &mut [V::T], _: [&[V::T]; 0], lane: usize| y[0] = f::<V>(lane as f64);
2167        y.for_each_batch([], write_lane);
2168        y_host.for_each_batch_host([], write_lane);
2169        assert_eq!(y.clone_as_vec(), fv::<V>(&[0.0]));
2170        assert_eq!(y_host.clone_as_vec(), y.clone_as_vec());
2171    }
2172
2173    /// Every lane is visited exactly once, and gets its own index.
2174    pub fn test_batched_for_each_batch_index<V: Vector>(ctx: V::C) {
2175        let nbatch = ctx.nbatch();
2176        assert!(nbatch > 1);
2177        let mut y = V::zeros(2, ctx.clone());
2178        let mut y_host = V::zeros(2, ctx);
2179        let write_lane = |y: &mut [V::T], _: [&[V::T]; 0], lane: usize| {
2180            y[0] = f::<V>(lane as f64);
2181            y[1] += f::<V>(1.0);
2182        };
2183        y.for_each_batch([], write_lane);
2184        y_host.for_each_batch_host([], write_lane);
2185        let expected: Vec<f64> = (0..nbatch).flat_map(|b| [b as f64, 1.0]).collect();
2186        assert_eq!(y.clone_as_vec(), fv::<V>(&expected));
2187        assert_eq!(y_host.clone_as_vec(), y.clone_as_vec());
2188    }
2189
2190    pub fn test_batched_for_each_batch_bad_nbatch<V: Vector>(ctx: V::C) {
2191        assert_eq!(ctx.nbatch(), 2);
2192        let ctx3 = ctx.clone_with_nbatch(3).unwrap();
2193        let x = V::zeros(1, ctx3);
2194        let mut y = V::zeros(1, ctx);
2195        y.for_each_batch([&x], |y: &mut [V::T], [x]: [&[V::T]; 1], _lane: usize| {
2196            y[0] = x[0]
2197        });
2198    }
2199
2200    pub fn test_batched_axpy_broadcast<V: Vector>(ctx: V::C) {
2201        assert_eq!(ctx.nbatch(), 2);
2202        // self has nbatch=2, x has nbatch=1
2203        let mut y = V::from_vec(fv::<V>(&[1.0, 2.0, 10.0, 20.0]), ctx);
2204        let x = V::from_vec(fv::<V>(&[3.0, 4.0]), V::C::default());
2205        y.axpy(f::<V>(2.0), &x, f::<V>(1.0));
2206        // batch0: [1+6, 2+8]=[7,10], batch1: [10+6, 20+8]=[16,28]
2207        assert_eq!(y.clone_as_vec(), fv::<V>(&[7.0, 10.0, 16.0, 28.0]));
2208    }
2209
2210    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2211    pub fn test_batched_copy_from_broadcast<V: Vector>(ctx: V::C) {
2212        assert_eq!(ctx.nbatch(), 2);
2213        let mut y = V::zeros(2, ctx);
2214        let x = V::from_vec(fv::<V>(&[5.0, 7.0]), V::C::default());
2215        y.copy_from(&x);
2216        // both batches get [5, 7]
2217        assert_eq!(y.clone_as_vec(), fv::<V>(&[5.0, 7.0, 5.0, 7.0]));
2218    }
2219
2220    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2221    pub fn test_batched_component_div<V: Vector>(ctx: V::C) {
2222        assert_eq!(ctx.nbatch(), 2);
2223        let mut a = V::from_vec(fv::<V>(&[6.0, 8.0, 12.0, 20.0]), ctx.clone());
2224        let b = V::from_vec(fv::<V>(&[2.0, 4.0, 3.0, 5.0]), ctx);
2225        a.component_div_assign(&b);
2226        assert_eq!(a.clone_as_vec(), fv::<V>(&[3.0, 2.0, 4.0, 4.0]));
2227    }
2228
2229    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2230    pub fn test_batched_component_mul_broadcast<V: Vector>(ctx: V::C) {
2231        assert_eq!(ctx.nbatch(), 2);
2232        let mut a = V::from_vec(fv::<V>(&[2.0, 3.0, 4.0, 5.0]), ctx);
2233        let b = V::from_vec(fv::<V>(&[10.0, 20.0]), V::C::default());
2234        a.component_mul_assign(&b);
2235        // batch0: [20, 60], batch1: [40, 100]
2236        assert_eq!(a.clone_as_vec(), fv::<V>(&[20.0, 60.0, 40.0, 100.0]));
2237    }
2238
2239    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2240    pub fn test_batched_component_div_broadcast<V: Vector>(ctx: V::C) {
2241        assert_eq!(ctx.nbatch(), 2);
2242        let mut a = V::from_vec(fv::<V>(&[6.0, 8.0, 12.0, 20.0]), ctx);
2243        let b = V::from_vec(fv::<V>(&[2.0, 4.0]), V::C::default());
2244        a.component_div_assign(&b);
2245        // batch0: [3, 2], batch1: [6, 5]
2246        assert_eq!(a.clone_as_vec(), fv::<V>(&[3.0, 2.0, 6.0, 5.0]));
2247    }
2248
2249    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2250    pub fn test_batched_add_assign_broadcast<V: Vector>(ctx: V::C) {
2251        assert_eq!(ctx.nbatch(), 2);
2252        let mut a = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0, 4.0]), ctx);
2253        let b = V::from_vec(fv::<V>(&[10.0, 20.0]), V::C::default());
2254        a += &b;
2255        assert_eq!(a.clone_as_vec(), fv::<V>(&[11.0, 22.0, 13.0, 24.0]));
2256    }
2257
2258    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2259    pub fn test_batched_sub_assign_broadcast<V: Vector>(ctx: V::C) {
2260        assert_eq!(ctx.nbatch(), 2);
2261        let mut a = V::from_vec(fv::<V>(&[10.0, 20.0, 30.0, 40.0]), ctx);
2262        let b = V::from_vec(fv::<V>(&[1.0, 2.0]), V::C::default());
2263        a -= &b;
2264        assert_eq!(a.clone_as_vec(), fv::<V>(&[9.0, 18.0, 29.0, 38.0]));
2265    }
2266
2267    /// Owned right-hand side with broadcasting in both directions: the right-hand side can
2268    /// only hold the result when it already has the result's batch count.
2269    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2270    pub fn test_batched_owned_rhs_broadcast<V: Vector>(ctx: V::C)
2271    where
2272        for<'a> &'a V: Sub<V, Output = V>,
2273    {
2274        assert_eq!(ctx.nbatch(), 2);
2275        // `rhs` is the owned operand, so it governs and `lhs` broadcasts into its batches
2276        let a = V::from_vec(fv::<V>(&[10.0, 20.0]), V::C::default());
2277        let b = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0, 4.0]), ctx);
2278        let c = &a - b;
2279        assert_eq!(c.clone_as_vec(), fv::<V>(&[9.0, 18.0, 7.0, 16.0]));
2280        assert_eq!(c.context().nbatch(), 2);
2281    }
2282
2283    /// The owned right-hand side governs, so a *wider* borrowed left-hand side is an error.
2284    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2285    pub fn test_batched_owned_rhs_narrow<V: Vector>(ctx: V::C)
2286    where
2287        for<'a> &'a V: Sub<V, Output = V>,
2288    {
2289        let a = V::from_vec(fv::<V>(&[10.0, 20.0, 30.0, 40.0]), ctx);
2290        let b = V::from_vec(fv::<V>(&[1.0, 2.0]), V::C::default());
2291        let _ = &a - b;
2292    }
2293
2294    /// The owned left-hand side governs and is written into in place.
2295    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2296    pub fn test_batched_owned_lhs_broadcast<V: Vector>(ctx: V::C) {
2297        assert_eq!(ctx.nbatch(), 2);
2298        let a = V::from_vec(fv::<V>(&[10.0, 20.0, 30.0, 40.0]), ctx);
2299        let b = V::from_vec(fv::<V>(&[1.0, 2.0]), V::C::default());
2300        let c = a - b;
2301        assert_eq!(c.clone_as_vec(), fv::<V>(&[9.0, 18.0, 29.0, 38.0]));
2302        assert_eq!(c.context().nbatch(), 2);
2303    }
2304
2305    /// The owned left-hand side governs, so a *wider* right-hand side is an error.
2306    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2307    pub fn test_batched_owned_lhs_narrow<V: Vector>(ctx: V::C) {
2308        let a = V::from_vec(fv::<V>(&[10.0, 20.0]), V::C::default());
2309        let b = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0, 4.0]), ctx);
2310        let _ = a - b;
2311    }
2312
2313    /// View+view addition with mismatched batch counts: neither operand is owned, so the
2314    /// left-hand side governs and `rhs` broadcasts into it.  Exercises the general
2315    /// (non-equal-ncols) branch of the view/ref arithmetic impls.
2316    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2317    pub fn test_batched_view_add_broadcast<V: Vector>(ctx: V::C) {
2318        assert_eq!(ctx.nbatch(), 2);
2319        let a = V::from_vec(fv::<V>(&[10.0, 20.0, 30.0, 40.0]), ctx);
2320        let b = V::from_vec(fv::<V>(&[1.0, 2.0]), V::C::default());
2321        let c = a.as_view() + b.as_view();
2322        assert_eq!(c.clone_as_vec(), fv::<V>(&[11.0, 22.0, 31.0, 42.0]));
2323        assert_eq!(c.context().nbatch(), 2);
2324    }
2325
2326    /// Neither operand owned, so the left-hand side governs: a wider right-hand side is an
2327    /// error rather than a wider result.
2328    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2329    pub fn test_batched_view_add_narrow<V: Vector>(ctx: V::C) {
2330        let a = V::from_vec(fv::<V>(&[10.0, 20.0]), V::C::default());
2331        let b = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0, 4.0]), ctx);
2332        let _ = a.as_view() + b.as_view();
2333    }
2334
2335    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2336    pub fn test_batched_sub<V: Vector>(ctx: V::C) {
2337        assert_eq!(ctx.nbatch(), 2);
2338        let a = V::from_vec(fv::<V>(&[10.0, 20.0, 30.0, 40.0]), ctx.clone());
2339        let b = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0, 4.0]), ctx);
2340        let c = a - b;
2341        assert_eq!(c.clone_as_vec(), fv::<V>(&[9.0, 18.0, 27.0, 36.0]));
2342    }
2343
2344    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2345    pub fn test_batched_sub_assign<V: Vector>(ctx: V::C) {
2346        assert_eq!(ctx.nbatch(), 2);
2347        let mut a = V::from_vec(fv::<V>(&[10.0, 20.0, 30.0, 40.0]), ctx.clone());
2348        let b = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0, 4.0]), ctx);
2349        a -= b;
2350        assert_eq!(a.clone_as_vec(), fv::<V>(&[9.0, 18.0, 27.0, 36.0]));
2351    }
2352
2353    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2354    pub fn test_batched_from_slice<V: Vector>(ctx: V::C) {
2355        assert_eq!(ctx.nbatch(), 2);
2356        let slice = fv::<V>(&[1.0, 2.0, 3.0, 1.0, 2.0, 3.0]);
2357        let v = V::from_slice(&slice, ctx);
2358        assert_eq!(v.clone_as_vec(), fv::<V>(&[1.0, 2.0, 3.0, 1.0, 2.0, 3.0]));
2359    }
2360
2361    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2362    pub fn test_batched_mul_scalar<V: Vector>(ctx: V::C) {
2363        assert_eq!(ctx.nbatch(), 2);
2364        let v = V::from_vec(fv::<V>(&[1.0, 2.0, 10.0, 20.0]), ctx);
2365        let result = v * Scale(f::<V>(2.0));
2366        assert_eq!(result.clone_as_vec(), fv::<V>(&[2.0, 4.0, 20.0, 40.0]));
2367    }
2368
2369    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2370    pub fn test_batched_div_scalar<V: Vector>(ctx: V::C) {
2371        assert_eq!(ctx.nbatch(), 2);
2372        let v = V::from_vec(fv::<V>(&[2.0, 4.0, 20.0, 40.0]), ctx);
2373        let result = v / Scale(f::<V>(2.0));
2374        assert_eq!(result.clone_as_vec(), fv::<V>(&[1.0, 2.0, 10.0, 20.0]));
2375    }
2376
2377    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2378    pub fn test_batched_copy_from_indices<V: Vector>(ctx: V::C) {
2379        assert_eq!(ctx.nbatch(), 2);
2380        let mut v1 = V::zeros(4, ctx.clone());
2381        let v2 = V::from_vec(
2382            fv::<V>(&[10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0]),
2383            ctx,
2384        );
2385        let indices = V::Index::from_vec(vec![0, 2, 3], Default::default());
2386        v1.copy_from_indices(&v2, &indices);
2387        assert_eq!(
2388            v1.clone_as_vec(),
2389            fv::<V>(&[10.0, 0.0, 30.0, 40.0, 50.0, 0.0, 70.0, 80.0])
2390        );
2391    }
2392
2393    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2394    pub fn test_batched_gather<V: Vector>(ctx: V::C) {
2395        assert_eq!(ctx.nbatch(), 2);
2396        let mut result = V::zeros(3, ctx.clone());
2397        let v = V::from_vec(
2398            fv::<V>(&[10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0]),
2399            ctx,
2400        );
2401        let indices = V::Index::from_vec(vec![3, 0, 2], Default::default());
2402        result.gather(&v, &indices);
2403        assert_eq!(
2404            result.clone_as_vec(),
2405            fv::<V>(&[40.0, 10.0, 30.0, 80.0, 50.0, 70.0])
2406        );
2407    }
2408
2409    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2410    pub fn test_batched_scatter<V: Vector>(ctx: V::C) {
2411        assert_eq!(ctx.nbatch(), 2);
2412        let v = V::from_vec(fv::<V>(&[40.0, 10.0, 30.0, 80.0, 50.0, 70.0]), ctx.clone());
2413        let indices = V::Index::from_vec(vec![3, 0, 2], Default::default());
2414        let mut result = V::zeros(4, ctx);
2415        v.scatter(&indices, &mut result);
2416        assert_eq!(
2417            result.clone_as_vec(),
2418            fv::<V>(&[10.0, 0.0, 30.0, 40.0, 50.0, 0.0, 70.0, 80.0])
2419        );
2420    }
2421
2422    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2423    pub fn test_batched_get_batch<V: Vector>(ctx: V::C) {
2424        assert_eq!(ctx.nbatch(), 2);
2425        let v = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0, 10.0, 20.0, 30.0]), ctx);
2426        let batch0 = v.get_batch(0);
2427        assert_eq!(batch0.get_index(0), f::<V>(1.0));
2428        assert_eq!(batch0.get_index(1), f::<V>(2.0));
2429        assert_eq!(batch0.get_index(2), f::<V>(3.0));
2430        let batch1 = v.get_batch(1);
2431        assert_eq!(batch1.get_index(0), f::<V>(10.0));
2432        assert_eq!(batch1.get_index(1), f::<V>(20.0));
2433        assert_eq!(batch1.get_index(2), f::<V>(30.0));
2434    }
2435
2436    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2437    pub fn test_batched_get_batch_mut<V: Vector>(ctx: V::C) {
2438        assert_eq!(ctx.nbatch(), 2);
2439        let mut v = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0, 10.0, 20.0, 30.0]), ctx);
2440        {
2441            let mut batch0 = v.get_batch_mut(0);
2442            batch0.set_index(1, f::<V>(99.0));
2443        }
2444        assert_eq!(
2445            v.clone_as_vec(),
2446            fv::<V>(&[1.0, 99.0, 3.0, 10.0, 20.0, 30.0])
2447        );
2448    }
2449
2450    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2451    pub fn test_batched_axpy_v<V: Vector>(ctx: V::C) {
2452        assert_eq!(ctx.nbatch(), 2);
2453        let mut y = V::from_vec(fv::<V>(&[1.0, 2.0, 10.0, 20.0]), ctx.clone());
2454        let x = V::from_vec(fv::<V>(&[3.0, 4.0, 30.0, 40.0]), ctx);
2455        let x_view = x.as_view();
2456        y.axpy_v(f::<V>(2.0), &x_view, f::<V>(1.0));
2457        assert_eq!(y.clone_as_vec(), fv::<V>(&[7.0, 10.0, 70.0, 100.0]));
2458    }
2459
2460    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2461    pub fn test_batched_mul_assign_scalar<V: Vector>(ctx: V::C) {
2462        assert_eq!(ctx.nbatch(), 2);
2463        let mut v = V::from_vec(fv::<V>(&[1.0, 2.0, 10.0, 20.0]), ctx);
2464        v *= Scale(f::<V>(2.0));
2465        assert_eq!(v.clone_as_vec(), fv::<V>(&[2.0, 4.0, 20.0, 40.0]));
2466    }
2467
2468    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2469    pub fn test_batched_copy_from_view<V: Vector>(ctx: V::C) {
2470        assert_eq!(ctx.nbatch(), 2);
2471        let mut v1 = V::zeros(2, ctx);
2472        let v2 = V::from_vec(fv::<V>(&[5.0, 7.0]), V::C::default());
2473        let view = v2.as_view();
2474        v1.copy_from_view(&view);
2475        assert_eq!(v1.clone_as_vec(), fv::<V>(&[5.0, 7.0, 5.0, 7.0]));
2476    }
2477
2478    // --- batched_axpy tests ---
2479
2480    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2481    pub fn test_batched_axpy_new<V: Vector>(ctx: V::C) {
2482        assert_eq!(ctx.nbatch(), 2);
2483        let mut y = V::from_vec(fv::<V>(&[1.0, 2.0, 10.0, 20.0]), ctx.clone());
2484        let x = V::from_vec(fv::<V>(&[3.0, 4.0, 30.0, 40.0]), ctx.clone());
2485        // one value per lane: alpha_0 = 2, alpha_1 = 0.5
2486        let alpha = V::from_vec(fv::<V>(&[2.0, 0.5]), ctx);
2487        y.batched_axpy(&alpha, &x, f::<V>(1.0));
2488        // batch0: [1,2] + 2*[3,4] = [7,10]
2489        // batch1: [10,20] + 0.5*[30,40] = [25,40]
2490        assert_eq!(y.clone_as_vec(), fv::<V>(&[7.0, 10.0, 25.0, 40.0]));
2491    }
2492
2493    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2494    pub fn test_batched_axpy_new_broadcast<V: Vector>(ctx: V::C) {
2495        assert_eq!(ctx.nbatch(), 2);
2496        let mut y = V::from_vec(fv::<V>(&[1.0, 2.0, 10.0, 20.0]), ctx.clone());
2497        let x = V::from_vec(fv::<V>(&[3.0, 4.0]), V::C::default());
2498        let alpha = V::from_vec(fv::<V>(&[2.0, 0.5]), ctx);
2499        y.batched_axpy(&alpha, &x, f::<V>(1.0));
2500        // both batches: beta*y + alpha_b * x
2501        // batch0: [1,2] + 2*[3,4] = [7,10]
2502        // batch1: [10,20] + 0.5*[3,4] = [11.5,22]
2503        assert_eq!(y.clone_as_vec(), fv::<V>(&[7.0, 10.0, 11.5, 22.0]));
2504    }
2505
2506    #[allow(clippy::type_complexity)]
2507    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2508    pub fn test_batched_axpy_new_bad_length<V: Vector>(ctx: V::C) {
2509        assert_eq!(ctx.nbatch(), 2);
2510        let mut y = V::zeros(2, ctx.clone());
2511        let x = V::zeros(2, V::C::default());
2512        // alpha must be one value per lane, not one value per state
2513        let alpha = V::zeros(2, ctx);
2514        y.batched_axpy(&alpha, &x, f::<V>(0.0));
2515    }
2516
2517    /// `alpha` carries one lane per batch of `self`, so a narrower alpha is a mistake rather than
2518    /// something to broadcast -- a uniform multiplier is plain `axpy`.
2519    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2520    pub fn test_batched_axpy_new_bad_nbatch<V: Vector>(ctx: V::C) {
2521        assert_eq!(ctx.nbatch(), 2);
2522        let mut y = V::zeros(2, ctx);
2523        let x = V::zeros(2, V::C::default());
2524        let alpha = V::from_vec(fv::<V>(&[2.0]), V::C::default());
2525        y.batched_axpy(&alpha, &x, f::<V>(0.0));
2526    }
2527
2528    /// Every operand flavour of `+` and `-` (owned, reference, view, view reference on either
2529    /// side), each with the widths the governing rule requires: an owned operand governs, and
2530    /// with none owned the left-hand side does.  Each flavour is a separate `impl` that decides
2531    /// on its own whether it can write in place, so they all need exercising with mismatched
2532    /// batch counts.
2533    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2534    pub fn test_batched_binary_forms_broadcast<V: Vector>(ctx: V::C)
2535    where
2536        for<'a> &'a V: Add<V, Output = V> + Sub<V, Output = V>,
2537        for<'a, 'b> &'a V: Add<&'b V, Output = V> + Sub<&'b V, Output = V>,
2538        for<'a, 'b> &'a V: Add<V::View<'b>, Output = V> + Sub<V::View<'b>, Output = V>,
2539        for<'a, 'b, 'c> &'a V: Add<&'b V::View<'c>, Output = V> + Sub<&'b V::View<'c>, Output = V>,
2540        for<'a> V::View<'a>: Add<V, Output = V> + Sub<V, Output = V>,
2541        for<'a, 'b> V::View<'a>: Add<&'b V, Output = V> + Sub<&'b V, Output = V>,
2542        for<'a, 'b> V::View<'a>: Add<V::View<'b>, Output = V> + Sub<V::View<'b>, Output = V>,
2543        for<'a, 'b, 'c> V::View<'a>:
2544            Add<&'b V::View<'c>, Output = V> + Sub<&'b V::View<'c>, Output = V>,
2545    {
2546        assert_eq!(ctx.nbatch(), 2);
2547        let wide = || V::from_vec(fv::<V>(&[10.0, 20.0, 30.0, 40.0]), ctx.clone());
2548        let narrow = || V::from_vec(fv::<V>(&[1.0, 2.0]), V::C::default());
2549        let (n, w) = (narrow(), wide());
2550        // wide - narrow, narrow - wide, wide + narrow
2551        let ws = fv::<V>(&[9.0, 18.0, 29.0, 38.0]);
2552        let ns = fv::<V>(&[-9.0, -18.0, -29.0, -38.0]);
2553        let sum = fv::<V>(&[11.0, 22.0, 31.0, 42.0]);
2554
2555        // the owned left-hand side governs, so it is the wide one
2556        assert_eq!((wide() - narrow()).clone_as_vec(), ws);
2557        assert_eq!((wide() - &narrow()).clone_as_vec(), ws);
2558        assert_eq!((wide() - n.as_view()).clone_as_vec(), ws);
2559        assert_eq!((wide() - &n.as_view()).clone_as_vec(), ws);
2560        assert_eq!((wide() + narrow()).clone_as_vec(), sum);
2561        assert_eq!((wide() + &narrow()).clone_as_vec(), sum);
2562        assert_eq!((wide() + n.as_view()).clone_as_vec(), sum);
2563        assert_eq!((wide() + &n.as_view()).clone_as_vec(), sum);
2564
2565        // only the right-hand side is owned, so it governs and must be the wide one
2566        assert_eq!((&n - wide()).clone_as_vec(), ns);
2567        assert_eq!((n.as_view() - wide()).clone_as_vec(), ns);
2568        assert_eq!((&n + wide()).clone_as_vec(), sum);
2569        assert_eq!((n.as_view() + wide()).clone_as_vec(), sum);
2570
2571        // neither operand is owned, so the left-hand side governs
2572        assert_eq!((&w - &n).clone_as_vec(), ws);
2573        assert_eq!((&w - n.as_view()).clone_as_vec(), ws);
2574        assert_eq!((&w - &n.as_view()).clone_as_vec(), ws);
2575        assert_eq!((w.as_view() - &n).clone_as_vec(), ws);
2576        assert_eq!((w.as_view() - n.as_view()).clone_as_vec(), ws);
2577        assert_eq!((w.as_view() - &n.as_view()).clone_as_vec(), ws);
2578        assert_eq!((&w + &n).clone_as_vec(), sum);
2579        assert_eq!((&w + n.as_view()).clone_as_vec(), sum);
2580        assert_eq!((&w + &n.as_view()).clone_as_vec(), sum);
2581        assert_eq!((w.as_view() + &n).clone_as_vec(), sum);
2582        assert_eq!((w.as_view() + n.as_view()).clone_as_vec(), sum);
2583        assert_eq!((w.as_view() + &n.as_view()).clone_as_vec(), sum);
2584    }
2585
2586    /// Zero-length batched vectors: every kernel-launching op short-circuits rather than
2587    /// launching an empty grid, and that guard is per-op.
2588    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2589    pub fn test_batched_empty<V: Vector>(ctx: V::C)
2590    where
2591        for<'a, 'b> &'a V: Add<&'b V, Output = V>,
2592    {
2593        assert_eq!(ctx.nbatch(), 2);
2594        let empty = || V::from_vec(vec![], ctx.clone());
2595        let mut a = empty();
2596        let b = empty();
2597        assert_eq!(a.len(), 0);
2598        assert_eq!(a.total_len(), 0);
2599        a += &b;
2600        a -= &b;
2601        a.axpy(f::<V>(2.0), &b, f::<V>(1.0));
2602        a.axpy_v(f::<V>(2.0), &b.as_view(), f::<V>(1.0));
2603        a.component_mul_assign(&b);
2604        a.component_div_assign(&b);
2605        a.batched_axpy(
2606            &V::from_vec(fv::<V>(&[1.0, 1.0]), ctx.clone()),
2607            &b,
2608            f::<V>(1.0),
2609        );
2610        a.copy_from(&b);
2611        a.copy_from_view(&b.as_view());
2612        a.fill(f::<V>(1.0));
2613        a *= Scale(f::<V>(2.0));
2614        let c = empty() * Scale(f::<V>(2.0));
2615        let e = empty() + b.as_view();
2616        let g = &empty() + &b;
2617        assert!(g.clone_as_vec().is_empty());
2618        let idx = V::Index::from_vec(vec![], Default::default());
2619        a.gather(&b, &idx);
2620        a.scatter(&idx, &mut V::from_vec(vec![], ctx.clone()));
2621        a.assign_at_indices(&idx, f::<V>(1.0));
2622        a.copy_from_indices(&b, &idx);
2623        a.as_view_mut().copy_from_view(&b.as_view());
2624        a.as_view_mut().axpy(f::<V>(1.0), &b, f::<V>(1.0));
2625        assert!(a.clone_as_vec().is_empty());
2626        assert!(c.clone_as_vec().is_empty());
2627        assert!(e.clone_as_vec().is_empty());
2628        assert_eq!(a.norm(2), f::<V>(0.0));
2629        assert_eq!(a.squared_norm(&b, &empty(), f::<V>(1.0)), f::<V>(0.0));
2630        assert_eq!(a.root_finding(&b), (false, f::<V>(0.0), -1));
2631    }
2632
2633    // --- Grouped broadcast tests: `B` batches feeding `B * P` batches ---
2634    //
2635    // Every group holds a distinct value, so a cyclic mapping (`b % B`, source order
2636    // `[0, 1, 0, 1]`) fails these — grouped broadcasting reads `[0, 0, 1, 1]`.
2637
2638    /// `nbatch = 2` widened to 4, so each source batch covers two contiguous destinations.
2639    fn ctx4<V: Vector>(ctx2: &V::C) -> V::C {
2640        assert_eq!(ctx2.nbatch(), 2);
2641        ctx2.clone_with_nbatch(4).unwrap()
2642    }
2643
2644    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2645    pub fn test_grouped_axpy<V: Vector>(ctx2: V::C) {
2646        let wide = ctx4::<V>(&ctx2);
2647        let mut y = V::from_vec(
2648            fv::<V>(&[10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0]),
2649            wide,
2650        );
2651        let x = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0, 4.0]), ctx2);
2652        y.axpy(f::<V>(1.0), &x, f::<V>(1.0));
2653        assert_eq!(
2654            y.clone_as_vec(),
2655            fv::<V>(&[11.0, 22.0, 31.0, 42.0, 53.0, 64.0, 73.0, 84.0])
2656        );
2657    }
2658
2659    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2660    pub fn test_grouped_add_assign<V: Vector>(ctx2: V::C) {
2661        let wide = ctx4::<V>(&ctx2);
2662        let mut a = V::from_vec(
2663            fv::<V>(&[10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0]),
2664            wide,
2665        );
2666        let b = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0, 4.0]), ctx2);
2667        a += &b;
2668        assert_eq!(
2669            a.clone_as_vec(),
2670            fv::<V>(&[11.0, 22.0, 31.0, 42.0, 53.0, 64.0, 73.0, 84.0])
2671        );
2672    }
2673
2674    /// The allocating operator, with the narrow operand on each side in turn.
2675    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2676    pub fn test_grouped_add<V: Vector>(ctx2: V::C)
2677    where
2678        for<'b> &'b V: Add<&'b V, Output = V>,
2679    {
2680        let wide = ctx4::<V>(&ctx2);
2681        let a = V::from_vec(
2682            fv::<V>(&[10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0]),
2683            wide,
2684        );
2685        let b = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0, 4.0]), ctx2);
2686        // neither operand is owned, so the wide left-hand side governs
2687        let expected = fv::<V>(&[11.0, 22.0, 31.0, 42.0, 53.0, 64.0, 73.0, 84.0]);
2688        assert_eq!((&a + &b).clone_as_vec(), expected);
2689    }
2690
2691    /// The reversed orientation of [`test_grouped_add`]: the narrow side is on the left and
2692    /// neither operand is owned, so it governs and the wider right-hand side is an error.
2693    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2694    pub fn test_grouped_add_narrow_lhs<V: Vector>(ctx2: V::C)
2695    where
2696        for<'b> &'b V: Add<&'b V, Output = V>,
2697    {
2698        let wide = ctx4::<V>(&ctx2);
2699        let a = V::from_vec(
2700            fv::<V>(&[10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0]),
2701            wide,
2702        );
2703        let b = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0, 4.0]), ctx2);
2704        let _ = &b + &a;
2705    }
2706
2707    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2708    pub fn test_grouped_copy_from<V: Vector>(ctx2: V::C) {
2709        let wide = ctx4::<V>(&ctx2);
2710        let mut y = V::zeros(2, wide);
2711        let x = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0, 4.0]), ctx2);
2712        y.copy_from(&x);
2713        assert_eq!(
2714            y.clone_as_vec(),
2715            fv::<V>(&[1.0, 2.0, 1.0, 2.0, 3.0, 4.0, 3.0, 4.0])
2716        );
2717    }
2718
2719    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2720    pub fn test_grouped_component_mul<V: Vector>(ctx2: V::C) {
2721        let wide = ctx4::<V>(&ctx2);
2722        let mut a = V::from_vec(fv::<V>(&[1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 4.0, 4.0]), wide);
2723        let b = V::from_vec(fv::<V>(&[10.0, 20.0, 30.0, 40.0]), ctx2);
2724        a.component_mul_assign(&b);
2725        assert_eq!(
2726            a.clone_as_vec(),
2727            fv::<V>(&[10.0, 20.0, 20.0, 40.0, 90.0, 120.0, 120.0, 160.0])
2728        );
2729    }
2730
2731    /// The reduction runs over the widest operand, so the largest group's norm wins.
2732    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2733    pub fn test_grouped_squared_norm<V: Vector>(ctx2: V::C) {
2734        let wide = ctx4::<V>(&ctx2);
2735        let v = V::from_vec(
2736            fv::<V>(&[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),
2737            wide.clone(),
2738        );
2739        // atol is 1 in the first group and 1/3 in the second, so the second group dominates
2740        let y = V::zeros(2, ctx2.clone());
2741        let atol = V::from_vec(fv::<V>(&[1.0, 1.0, 1.0 / 3.0, 1.0 / 3.0]), ctx2.clone());
2742        let norm = v.squared_norm(&y, &atol, f::<V>(1.0));
2743        assert_eq!(norm, f::<V>(9.0));
2744        // and with the wide vector narrowed to the same batch count the answer is unchanged
2745        let narrow = V::from_vec(fv::<V>(&[1.0, 1.0, 1.0, 1.0]), ctx2);
2746        assert_eq!(narrow.squared_norm(&y, &atol, f::<V>(1.0)), f::<V>(9.0));
2747    }
2748
2749    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2750    pub fn test_grouped_gather<V: Vector>(ctx2: V::C) {
2751        let wide = ctx4::<V>(&ctx2);
2752        let idx = V::Index::from_vec(vec![1, 0], Default::default());
2753        let o = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0, 4.0]), ctx2);
2754        let mut a = V::zeros(2, wide);
2755        a.gather(&o, &idx);
2756        assert_eq!(
2757            a.clone_as_vec(),
2758            fv::<V>(&[2.0, 1.0, 2.0, 1.0, 4.0, 3.0, 4.0, 3.0])
2759        );
2760    }
2761
2762    /// A representative slice of the [`test_batched_binary_forms_broadcast`] forms, grouped.
2763    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2764    pub fn test_grouped_owned_lhs_ref_rhs<V: Vector>(ctx2: V::C) {
2765        let ctx = ctx4::<V>(&ctx2);
2766        let wide = || {
2767            V::from_vec(
2768                fv::<V>(&[10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0]),
2769                ctx.clone(),
2770            )
2771        };
2772        let narrow = || V::from_vec(fv::<V>(&[1.0, 2.0, 3.0, 4.0]), ctx2.clone());
2773        let wide_lhs = fv::<V>(&[9.0, 18.0, 29.0, 38.0, 47.0, 56.0, 67.0, 76.0]);
2774        let n = narrow();
2775
2776        // the owned left-hand side governs, so it must be the wider one
2777        assert_eq!((wide() - &narrow()).clone_as_vec(), wide_lhs);
2778        assert_eq!((wide() - &n.as_view()).clone_as_vec(), wide_lhs);
2779    }
2780
2781    /// The grouped flavour of [`test_batched_owned_lhs_narrow`].
2782    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2783    pub fn test_grouped_owned_lhs_narrow<V: Vector>(ctx2: V::C) {
2784        let ctx = ctx4::<V>(&ctx2);
2785        let wide = V::from_vec(
2786            fv::<V>(&[10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0]),
2787            ctx,
2788        );
2789        let narrow = V::from_vec(fv::<V>(&[1.0, 2.0, 3.0, 4.0]), ctx2);
2790        let _ = narrow - &wide;
2791    }
2792
2793    /// 4 is not a multiple of 3 (nor 3 of 4), so this is not an exact repeat group.
2794    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2795    pub fn test_grouped_incompatible<V: Vector>(ctx3: V::C) {
2796        assert_eq!(ctx3.nbatch(), 3);
2797        let wide = ctx3.clone_with_nbatch(4).unwrap();
2798        let mut y = V::zeros(2, wide);
2799        let x = V::zeros(2, ctx3);
2800        y.axpy(f::<V>(1.0), &x, f::<V>(1.0));
2801    }
2802
2803    // --- Narrow-destination tests: writing a wider operand into a narrower destination
2804    // would silently drop batches, so it panics instead (`assert_broadcastable_into`) ---
2805
2806    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2807    pub fn test_narrow_dest_copy_from<V: Vector>(ctx2: V::C) {
2808        let wide = ctx4::<V>(&ctx2);
2809        let mut y = V::zeros(2, ctx2);
2810        let x = V::zeros(2, wide);
2811        y.copy_from(&x);
2812    }
2813
2814    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2815    pub fn test_narrow_dest_add_assign<V: Vector>(ctx2: V::C) {
2816        let wide = ctx4::<V>(&ctx2);
2817        let mut y = V::zeros(2, ctx2);
2818        let x = V::zeros(2, wide);
2819        y += &x;
2820    }
2821
2822    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2823    pub fn test_narrow_dest_axpy<V: Vector>(ctx2: V::C) {
2824        let wide = ctx4::<V>(&ctx2);
2825        let mut y = V::zeros(2, ctx2);
2826        let x = V::zeros(2, wide);
2827        y.axpy(f::<V>(1.0), &x, f::<V>(1.0));
2828    }
2829
2830    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2831    pub fn test_narrow_dest_component_mul<V: Vector>(ctx2: V::C) {
2832        let wide = ctx4::<V>(&ctx2);
2833        let mut y = V::zeros(2, ctx2);
2834        let x = V::zeros(2, wide);
2835        y.component_mul_assign(&x);
2836    }
2837
2838    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2839    pub fn test_narrow_dest_gather<V: Vector>(ctx2: V::C) {
2840        let wide = ctx4::<V>(&ctx2);
2841        let idx = V::Index::from_vec(vec![1, 0], Default::default());
2842        let o = V::zeros(2, wide);
2843        let mut a = V::zeros(2, ctx2);
2844        a.gather(&o, &idx);
2845    }
2846
2847    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2848    pub fn test_narrow_dest_copy_from_indices<V: Vector>(ctx2: V::C) {
2849        let wide = ctx4::<V>(&ctx2);
2850        let idx = V::Index::from_vec(vec![1, 0], Default::default());
2851        let o = V::zeros(2, wide);
2852        let mut a = V::zeros(2, ctx2);
2853        a.copy_from_indices(&o, &idx);
2854    }
2855
2856    /// `scatter` writes into its argument, so the narrow operand is the one being written.
2857    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2858    pub fn test_narrow_dest_scatter<V: Vector>(ctx2: V::C) {
2859        let wide = ctx4::<V>(&ctx2);
2860        let idx = V::Index::from_vec(vec![1, 0], Default::default());
2861        let source = V::zeros(2, wide);
2862        let mut dest = V::zeros(2, ctx2);
2863        source.scatter(&idx, &mut dest);
2864    }
2865
2866    /// An unbatched `self` takes a fast path, which must still enforce the broadcast rule.
2867    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2868    pub fn test_unbatched_dest_squared_norm<V: Vector>(ctx2: V::C) {
2869        let ctx1 = ctx2.clone_with_nbatch(1).unwrap();
2870        let v = V::from_vec(fv::<V>(&[1.0, 1.0]), ctx1.clone());
2871        let y = V::zeros(2, ctx2);
2872        let atol = V::from_element(2, f::<V>(1.0), ctx1);
2873        v.squared_norm(&y, &atol, f::<V>(1.0));
2874    }
2875
2876    /// The reduction runs over `self`'s batches, so a wider `y`/`atol` is the same case.
2877    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2878    pub fn test_narrow_dest_squared_norm<V: Vector>(ctx2: V::C) {
2879        let wide = ctx4::<V>(&ctx2);
2880        let v = V::from_vec(fv::<V>(&[1.0, 1.0, 1.0, 1.0]), ctx2.clone());
2881        let y = V::zeros(2, wide);
2882        let atol = V::from_element(2, f::<V>(1.0), ctx2);
2883        v.squared_norm(&y, &atol, f::<V>(1.0));
2884    }
2885
2886    // --- Incompatible batch tests ---
2887
2888    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2889    pub fn test_batched_axpy_incompatible<V: Vector>(ctx2: V::C, ctx3: V::C) {
2890        assert_eq!(ctx2.nbatch(), 2);
2891        assert_eq!(ctx3.nbatch(), 3);
2892        let mut y = V::zeros(2, ctx2);
2893        let x = V::zeros(2, ctx3);
2894        y.axpy(f::<V>(1.0), &x, f::<V>(1.0));
2895    }
2896
2897    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2898    pub fn test_batched_copy_from_incompatible<V: Vector>(ctx2: V::C, ctx3: V::C) {
2899        assert_eq!(ctx2.nbatch(), 2);
2900        assert_eq!(ctx3.nbatch(), 3);
2901        let mut y = V::zeros(2, ctx2);
2902        let x = V::zeros(2, ctx3);
2903        y.copy_from(&x);
2904    }
2905
2906    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2907    pub fn test_batched_add_assign_incompatible<V: Vector>(ctx2: V::C, ctx3: V::C) {
2908        assert_eq!(ctx2.nbatch(), 2);
2909        assert_eq!(ctx3.nbatch(), 3);
2910        let mut a = V::zeros(2, ctx2);
2911        let b = V::zeros(2, ctx3);
2912        a += &b;
2913    }
2914
2915    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
2916    pub fn test_batched_component_mul_incompatible<V: Vector>(ctx2: V::C, ctx3: V::C) {
2917        assert_eq!(ctx2.nbatch(), 2);
2918        assert_eq!(ctx3.nbatch(), 3);
2919        let mut a = V::zeros(2, ctx2);
2920        let b = V::zeros(2, ctx3);
2921        a.component_mul_assign(&b);
2922    }
2923
2924    #[test]
2925    fn vector_common_for_references_and_default_helpers_work() {
2926        let mut v = NalgebraVec::from_vec(vec![1.0, 2.0], NalgebraContext::default());
2927        assert_eq!(<NalgebraVec<f64> as VectorCommon>::inner(&v).nrows(), 2);
2928        assert_eq!(<NalgebraVec<f64> as VectorCommon>::inner(&v).ncols(), 1);
2929        assert_eq!(<&NalgebraVec<f64> as VectorCommon>::inner(&&v).nrows(), 2);
2930        assert_eq!(
2931            <&mut NalgebraVec<f64> as VectorCommon>::inner(&&mut v).ncols(),
2932            1
2933        );
2934
2935        let empty = NalgebraVec::<f64>::zeros(0, NalgebraContext::default());
2936        assert!(empty.is_empty());
2937
2938        let non_empty = NalgebraVec::<f64>::zeros(2, NalgebraContext::default());
2939        assert!(!non_empty.is_empty());
2940        assert_eq!(non_empty.clone_as_vec(), vec![0.0, 0.0]);
2941    }
2942
2943    #[test]
2944    fn vector_assert_eq_panics_for_length_mismatch() {
2945        let left = NalgebraVec::from_vec(vec![1.0, 2.0], NalgebraContext::default());
2946        let right = NalgebraVec::from_vec(vec![1.0], NalgebraContext::default());
2947        let tol = NalgebraVec::from_vec(vec![0.0, 0.0], NalgebraContext::default());
2948        assert!(catch_unwind(AssertUnwindSafe(|| left.assert_eq(&right, &tol))).is_err());
2949    }
2950
2951    #[test]
2952    fn vector_assert_helpers_cover_success_and_failure_paths() {
2953        let left = NalgebraVec::from_vec(vec![1.0, 2.0, 3.0], NalgebraContext::default());
2954        let right = NalgebraVec::from_vec(vec![1.0, 2.0, 3.0], NalgebraContext::default());
2955        let tol = NalgebraVec::from_vec(vec![0.0, 0.0, 0.0], NalgebraContext::default());
2956        left.assert_eq(&right, &tol);
2957        left.assert_eq_st(&right, 0.0);
2958        left.assert_eq_norm(&right, &tol, 1e-6, 1.0);
2959
2960        let perturbed = NalgebraVec::from_vec(vec![1.1, 2.0, 3.0], NalgebraContext::default());
2961        assert!(catch_unwind(AssertUnwindSafe(
2962            || left.assert_eq_norm(&perturbed, &tol, 1e-6, 0.01)
2963        ))
2964        .is_err());
2965    }
2966
2967    #[test]
2968    fn vector_assert_eq_vec_panics_for_short_vector_mismatch() {
2969        assert!(catch_unwind(AssertUnwindSafe(|| {
2970            <NalgebraVec<f64> as Vector>::assert_eq_vec(
2971                vec![1.0, 2.0, 3.0],
2972                vec![0.0, 2.0, 3.0],
2973                vec![0.0, 0.0, 0.0],
2974            )
2975        }))
2976        .is_err());
2977    }
2978
2979    #[test]
2980    fn vector_assert_eq_vec_panics_for_first_middle_and_last_mismatch_in_long_vectors() {
2981        assert!(catch_unwind(AssertUnwindSafe(|| {
2982            <NalgebraVec<f64> as Vector>::assert_eq_vec(
2983                vec![1.0, 2.0, 3.0, 4.0],
2984                vec![0.0, 2.0, 3.0, 4.0],
2985                vec![0.0, 0.0, 0.0, 0.0],
2986            )
2987        }))
2988        .is_err());
2989        assert!(catch_unwind(AssertUnwindSafe(|| {
2990            <NalgebraVec<f64> as Vector>::assert_eq_vec(
2991                vec![1.0, 2.0, 3.0, 4.0, 5.0],
2992                vec![1.0, 2.0, 0.0, 4.0, 5.0],
2993                vec![0.0, 0.0, 0.0, 0.0, 0.0],
2994            )
2995        }))
2996        .is_err());
2997        assert!(catch_unwind(AssertUnwindSafe(|| {
2998            <NalgebraVec<f64> as Vector>::assert_eq_vec(
2999                vec![1.0, 2.0, 3.0, 4.0],
3000                vec![1.0, 2.0, 3.0, 0.0],
3001                vec![0.0, 0.0, 0.0, 0.0],
3002            )
3003        }))
3004        .is_err());
3005    }
3006
3007    use crate::matrix::DenseMatrix;
3008
3009    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
3010    fn make_strided_test_matrix<M: DenseMatrix>(nbatch: usize) -> M {
3011        let ctx = M::C::default().clone_with_nbatch(nbatch).unwrap();
3012        let nrows = 3;
3013        let ncols = 4;
3014        let mut data = Vec::with_capacity(nrows * ncols * nbatch);
3015        for b in 0..nbatch {
3016            for col in 0..ncols {
3017                for row in 0..nrows {
3018                    data.push(f::<M::V>(row as f64 + col as f64 * 10.0 + b as f64 * 100.0));
3019                }
3020            }
3021        }
3022        M::from_vec(nrows, ncols, data, ctx)
3023    }
3024
3025    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
3026    pub fn test_strided_view_fill_index<M: DenseMatrix>(ctx: M::C) {
3027        let mut matrix = make_strided_test_matrix::<M>(ctx.nbatch());
3028        {
3029            let mut col1 = matrix.column_mut(1);
3030            col1.fill_index(1, f::<M::V>(99.0));
3031        }
3032        let owned = matrix.column(1).into_owned();
3033        let b0 = owned.get_batch(0);
3034        let b1 = owned.get_batch(1);
3035        assert_eq!(b0.get_index(1), f::<M::V>(99.0));
3036        assert_eq!(b1.get_index(1), f::<M::V>(99.0));
3037    }
3038
3039    /// A column of a batched matrix is a batched view, so `set_index` must refuse it.
3040    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
3041    pub fn test_strided_view_set_index_panics<M: DenseMatrix>(ctx: M::C) {
3042        assert!(ctx.nbatch() > 1);
3043        let mut matrix = make_strided_test_matrix::<M>(ctx.nbatch());
3044        matrix.column_mut(1).set_index(1, f::<M::V>(99.0));
3045    }
3046
3047    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
3048    pub fn test_strided_view_mut_copy_from<M: DenseMatrix>(ctx: M::C) {
3049        let mut matrix = make_strided_test_matrix::<M>(ctx.nbatch());
3050        let owned = M::V::from_vec(
3051            vec![f::<M::V>(50.0), f::<M::V>(51.0), f::<M::V>(52.0)],
3052            M::C::default(),
3053        );
3054        {
3055            let mut col1 = matrix.column_mut(1);
3056            col1.copy_from(&owned);
3057        }
3058        let owned_v = matrix.column(1).into_owned();
3059        let b0 = owned_v.get_batch(0);
3060        let b1 = owned_v.get_batch(1);
3061        assert_eq!(b0.get_index(0), f::<M::V>(50.0));
3062        assert_eq!(b0.get_index(1), f::<M::V>(51.0));
3063        assert_eq!(b0.get_index(2), f::<M::V>(52.0));
3064        assert_eq!(b1.get_index(0), f::<M::V>(50.0));
3065        assert_eq!(b1.get_index(1), f::<M::V>(51.0));
3066        assert_eq!(b1.get_index(2), f::<M::V>(52.0));
3067    }
3068
3069    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
3070    pub fn test_strided_view_mut_axpy<M: DenseMatrix>(ctx: M::C) {
3071        let mut matrix = make_strided_test_matrix::<M>(ctx.nbatch());
3072        let x = M::V::from_vec(
3073            vec![f::<M::V>(10.0), f::<M::V>(10.0), f::<M::V>(10.0)],
3074            M::C::default(),
3075        );
3076        {
3077            let mut col1 = matrix.column_mut(1);
3078            col1.axpy(f::<M::V>(2.0), &x, f::<M::V>(1.0));
3079        }
3080        let owned_v = matrix.column(1).into_owned();
3081        let b1 = owned_v.get_batch(1);
3082        assert_eq!(b1.get_index(0), f::<M::V>(130.0));
3083        assert_eq!(b1.get_index(1), f::<M::V>(131.0));
3084        assert_eq!(b1.get_index(2), f::<M::V>(132.0));
3085    }
3086
3087    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
3088    pub fn test_strided_view_mut_mul_assign_scalar<M: DenseMatrix>(ctx: M::C) {
3089        let mut matrix = make_strided_test_matrix::<M>(ctx.nbatch());
3090        {
3091            let mut col1 = matrix.column_mut(1);
3092            col1 *= Scale(f::<M::V>(2.0));
3093        }
3094        let owned_v = matrix.column(1).into_owned();
3095        let b0 = owned_v.get_batch(0);
3096        let b1 = owned_v.get_batch(1);
3097        assert_eq!(b0.get_index(0), f::<M::V>(20.0));
3098        assert_eq!(b1.get_index(0), f::<M::V>(220.0));
3099    }
3100
3101    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
3102    pub fn test_strided_view_mut_add_assign<M: DenseMatrix>(ctx: M::C) {
3103        let mut matrix = make_strided_test_matrix::<M>(ctx.nbatch());
3104        let rhs = M::V::from_vec(
3105            vec![
3106                f::<M::V>(5.0),
3107                f::<M::V>(5.0),
3108                f::<M::V>(5.0),
3109                f::<M::V>(10.0),
3110                f::<M::V>(10.0),
3111                f::<M::V>(10.0),
3112            ],
3113            ctx.clone(),
3114        );
3115        {
3116            let mut col1 = matrix.column_mut(1);
3117            col1 += &rhs;
3118        }
3119        let owned_v = matrix.column(1).into_owned();
3120        let b0 = owned_v.get_batch(0);
3121        let b1 = owned_v.get_batch(1);
3122        assert_eq!(b0.get_index(0), f::<M::V>(15.0));
3123        assert_eq!(b0.get_index(1), f::<M::V>(16.0));
3124        assert_eq!(b0.get_index(2), f::<M::V>(17.0));
3125        assert_eq!(b1.get_index(0), f::<M::V>(120.0));
3126        assert_eq!(b1.get_index(1), f::<M::V>(121.0));
3127        assert_eq!(b1.get_index(2), f::<M::V>(122.0));
3128    }
3129
3130    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
3131    pub fn test_strided_view_mut_sub_assign<M: DenseMatrix>(ctx: M::C) {
3132        let mut matrix = make_strided_test_matrix::<M>(ctx.nbatch());
3133        let rhs = M::V::from_vec(
3134            vec![
3135                f::<M::V>(1.0),
3136                f::<M::V>(1.0),
3137                f::<M::V>(1.0),
3138                f::<M::V>(1.0),
3139                f::<M::V>(1.0),
3140                f::<M::V>(1.0),
3141            ],
3142            ctx.clone(),
3143        );
3144        {
3145            let mut col1 = matrix.column_mut(1);
3146            col1 -= &rhs;
3147        }
3148        let owned_v = matrix.column(1).into_owned();
3149        let b0 = owned_v.get_batch(0);
3150        let b1 = owned_v.get_batch(1);
3151        assert_eq!(b0.get_index(0), f::<M::V>(9.0));
3152        assert_eq!(b1.get_index(0), f::<M::V>(109.0));
3153    }
3154
3155    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
3156    pub fn test_strided_view_add_assign_broadcast<M: DenseMatrix>(ctx: M::C) {
3157        let mut matrix = make_strided_test_matrix::<M>(ctx.nbatch());
3158        let rhs = M::V::from_vec(
3159            vec![f::<M::V>(5.0), f::<M::V>(5.0), f::<M::V>(5.0)],
3160            M::C::default(),
3161        );
3162        {
3163            let mut col1 = matrix.column_mut(1);
3164            col1 += &rhs;
3165        }
3166        let owned_v = matrix.column(1).into_owned();
3167        let b0 = owned_v.get_batch(0);
3168        let b1 = owned_v.get_batch(1);
3169        assert_eq!(b0.get_index(0), f::<M::V>(15.0));
3170        assert_eq!(b1.get_index(0), f::<M::V>(115.0));
3171    }
3172
3173    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
3174    pub fn test_strided_view_add_owned<M: DenseMatrix>(ctx: M::C) {
3175        let matrix = make_strided_test_matrix::<M>(ctx.nbatch());
3176        let nbatch = ctx.nbatch();
3177        let mut rhs_data = Vec::with_capacity(3 * nbatch);
3178        for _ in 0..nbatch {
3179            rhs_data.extend_from_slice(&[f::<M::V>(5.0), f::<M::V>(5.0), f::<M::V>(5.0)]);
3180        }
3181        let rhs = M::V::from_vec(rhs_data, ctx.clone());
3182        let col1 = matrix.column(1);
3183        let result = col1 + &rhs;
3184        let b0 = result.get_batch(0);
3185        let b1 = result.get_batch(1);
3186        assert_eq!(b0.get_index(0), f::<M::V>(15.0));
3187        assert_eq!(b1.get_index(0), f::<M::V>(115.0));
3188    }
3189
3190    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
3191    pub fn test_strided_view_squared_norm<M: DenseMatrix>(ctx: M::C) {
3192        let matrix = make_strided_test_matrix::<M>(ctx.nbatch());
3193        let nbatch = ctx.nbatch();
3194        let mut y_data = Vec::with_capacity(3 * nbatch);
3195        for _ in 0..nbatch {
3196            y_data.extend_from_slice(&[f::<M::V>(1.0), f::<M::V>(1.0), f::<M::V>(1.0)]);
3197        }
3198        let y = M::V::from_vec(y_data, ctx.clone());
3199        let atol = M::V::from_vec(
3200            vec![f::<M::V>(1e-3), f::<M::V>(1e-3), f::<M::V>(1e-3)],
3201            M::C::default(),
3202        );
3203        let col1 = matrix.column(1);
3204        let norm = col1.squared_norm(&y, &atol, f::<M::V>(1e-2));
3205        assert!(norm > f::<M::V>(0.0));
3206    }
3207
3208    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
3209    pub fn test_strided_view_into_owned<M: DenseMatrix>(ctx: M::C) {
3210        let matrix = make_strided_test_matrix::<M>(ctx.nbatch());
3211        let col1 = matrix.column(1);
3212        let owned = col1.into_owned();
3213        let b0 = owned.get_batch(0);
3214        let b1 = owned.get_batch(1);
3215        assert_eq!(b0.get_index(0), f::<M::V>(10.0));
3216        assert_eq!(b0.get_index(1), f::<M::V>(11.0));
3217        assert_eq!(b1.get_index(0), f::<M::V>(110.0));
3218        assert_eq!(b1.get_index(1), f::<M::V>(111.0));
3219    }
3220
3221    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
3222    pub fn test_strided_view_component_mul<M: DenseMatrix>(ctx: M::C) {
3223        let matrix = make_strided_test_matrix::<M>(ctx.nbatch());
3224        let rhs = M::V::from_vec(
3225            vec![f::<M::V>(10.0), f::<M::V>(1.0), f::<M::V>(0.0)],
3226            M::C::default(),
3227        );
3228        let col1 = matrix.column(1);
3229        let mut owned = col1.into_owned();
3230        owned.component_mul_assign(&rhs);
3231        let b0 = owned.get_batch(0);
3232        let b1 = owned.get_batch(1);
3233        assert_eq!(b0.get_index(0), f::<M::V>(100.0));
3234        assert_eq!(b0.get_index(1), f::<M::V>(11.0));
3235        assert_eq!(b1.get_index(0), f::<M::V>(1100.0));
3236        assert_eq!(b1.get_index(1), f::<M::V>(111.0));
3237    }
3238
3239    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
3240    pub fn test_strided_view_component_div<M: DenseMatrix>(ctx: M::C) {
3241        let matrix = make_strided_test_matrix::<M>(ctx.nbatch());
3242        let rhs = M::V::from_vec(
3243            vec![f::<M::V>(10.0), f::<M::V>(11.0), f::<M::V>(12.0)],
3244            M::C::default(),
3245        );
3246        let col1 = matrix.column(1);
3247        let mut owned = col1.into_owned();
3248        owned.component_div_assign(&rhs);
3249        let b0 = owned.get_batch(0);
3250        let b1 = owned.get_batch(1);
3251        assert_eq!(b0.get_index(0), f::<M::V>(1.0));
3252        assert_eq!(b1.get_index(0), f::<M::V>(11.0));
3253    }
3254
3255    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
3256    pub fn test_strided_view_mul_scalar<M: DenseMatrix>(ctx: M::C) {
3257        let matrix = make_strided_test_matrix::<M>(ctx.nbatch());
3258        let col1 = matrix.column(1);
3259        let result = col1 * Scale(f::<M::V>(2.0));
3260        let b0 = result.get_batch(0);
3261        let b1 = result.get_batch(1);
3262        assert_eq!(b0.get_index(0), f::<M::V>(20.0));
3263        assert_eq!(b1.get_index(0), f::<M::V>(220.0));
3264    }
3265
3266    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
3267    pub fn test_strided_view_fill<M: DenseMatrix>(ctx: M::C) {
3268        let matrix = make_strided_test_matrix::<M>(ctx.nbatch());
3269        let col1 = matrix.column(1);
3270        let mut owned = col1.into_owned();
3271        owned.fill(f::<M::V>(7.0));
3272        let b0 = owned.get_batch(0);
3273        let b1 = owned.get_batch(1);
3274        assert_eq!(b0.get_index(0), f::<M::V>(7.0));
3275        assert_eq!(b1.get_index(0), f::<M::V>(7.0));
3276    }
3277
3278    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
3279    pub fn test_strided_view_assign_at_indices<M: DenseMatrix>(ctx: M::C) {
3280        let matrix = make_strided_test_matrix::<M>(ctx.nbatch());
3281        let indices = <M::V as Vector>::Index::from_vec(vec![0, 2], M::C::default());
3282        let col1 = matrix.column(1);
3283        let mut owned = col1.into_owned();
3284        owned.assign_at_indices(&indices, f::<M::V>(0.0));
3285        let b0 = owned.get_batch(0);
3286        let b1 = owned.get_batch(1);
3287        assert_eq!(b0.get_index(0), f::<M::V>(0.0));
3288        assert_eq!(b1.get_index(0), f::<M::V>(0.0));
3289    }
3290
3291    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
3292    pub fn test_strided_view_copy_from_indices<M: DenseMatrix>(ctx: M::C) {
3293        let matrix = make_strided_test_matrix::<M>(ctx.nbatch());
3294        let nbatch = ctx.nbatch();
3295        let mut other_data = Vec::with_capacity(3 * nbatch);
3296        for _ in 0..nbatch {
3297            other_data.extend_from_slice(&[f::<M::V>(50.0), f::<M::V>(0.0), f::<M::V>(0.0)]);
3298        }
3299        let other = M::V::from_vec(other_data, ctx.clone());
3300        let indices = <M::V as Vector>::Index::from_vec(vec![0], M::C::default());
3301        let col1 = matrix.column(1);
3302        let mut owned = col1.into_owned();
3303        owned.copy_from_indices(&other, &indices);
3304        let b0 = owned.get_batch(0);
3305        let b1 = owned.get_batch(1);
3306        assert_eq!(b0.get_index(0), f::<M::V>(50.0));
3307        assert_eq!(b1.get_index(0), f::<M::V>(50.0));
3308    }
3309
3310    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
3311    pub fn test_strided_view_gather<M: DenseMatrix>(ctx: M::C) {
3312        let matrix = make_strided_test_matrix::<M>(ctx.nbatch());
3313        let nbatch = ctx.nbatch();
3314        let mut result = M::V::zeros(2, M::C::default().clone_with_nbatch(nbatch).unwrap());
3315        let indices = <M::V as Vector>::Index::from_vec(vec![0, 2], M::C::default());
3316        let col1 = matrix.column(1);
3317        let owned = col1.into_owned();
3318        result.gather(&owned, &indices);
3319        let b1 = result.get_batch(1);
3320        assert_eq!(b1.get_index(0), f::<M::V>(110.0));
3321    }
3322
3323    #[cfg_attr(not(feature = "cuda"), allow(dead_code))]
3324    pub fn test_strided_view_scatter<M: DenseMatrix>(ctx: M::C) {
3325        let matrix = make_strided_test_matrix::<M>(ctx.nbatch());
3326        let nbatch = ctx.nbatch();
3327        let col1 = matrix.column(1);
3328        let owned = col1.into_owned();
3329        let indices = <M::V as Vector>::Index::from_vec(vec![0, 1, 2], M::C::default());
3330        let mut result = M::V::zeros(3, M::C::default().clone_with_nbatch(nbatch).unwrap());
3331        owned.scatter(&indices, &mut result);
3332        let b1 = result.get_batch(1);
3333        assert_eq!(b1.get_index(0), f::<M::V>(110.0));
3334    }
3335}