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