Skip to main content

thermite_dual/
vector.rs

1//! Element and vector-trait integration for [`Dual`].
2//!
3//! - `Dual<E, N>` (where `E` is a scalar float element) implements
4//!   [`Element`]/[`SignedElement`]/[`FloatElement`], so it can be the element
5//!   type of a dual vector.
6//! - `Dual<V, N>` (where `V` is a real [`FloatVector`]) implements the full
7//!   [`GenericVector`] -> [`FloatVector`] stack, with `Element =
8//!   Dual<V::Element, N>` and the same mask/lanes as `V`.
9//!
10//! ## Compile-time splats
11//!
12//! Building a `Dual` vector from a *compile-time constant* element (via
13//! [`SplatConst`]/[`const_splat!`](thermite::const_splat) and the
14//! [`NewVector`]/[`const_new!`](thermite::const_new) machinery) splats the
15//! primal `re` correctly and sets the derivative parts to zero -- i.e. it treats
16//! a compile-time constant as having zero derivative, which is the correct and
17//! desired behaviour for every constant the library actually produces (`ZERO`,
18//! `ONE`, `PI`, polynomial coefficients, ...). The runtime [`splat`] and [`new`]
19//! constructors preserve derivative parts fully. (A non-array const path that
20//! preserved arbitrary per-component derivatives would require const generic
21//! recursion over `[V; N]`, which is unstable.)
22//!
23//! [`splat`]: GenericVector::splat
24//! [`new`]: GenericVector::new
25
26use core::marker::PhantomData;
27use core::ops::{Add, Div, Mul, Rem, Sub};
28
29use num_traits::Bounded;
30
31use thermite::Swizzle;
32use thermite::element::{Element, FloatElement, SignedElement};
33use thermite::generic_array::{GenericArray, IntoArrayLength, typenum::Const};
34use thermite::mask::{GenericMask, GenericSelectable};
35use thermite::math::algorithms::reduce_in_place;
36use thermite::register::SwizzleIndices;
37use thermite::vector::ops::{AddSubExt, AddSubExtMasked, NegMasked, Square, SquareMasked};
38use thermite::vector::{NewConst, NewVector, SplatConst, SplatVector, VectorValue, const_new, const_splat};
39use thermite::{LargeInt, prelude::*};
40
41use crate::{Dual, DualValue};
42
43/// Build a `[_; $n]` array from a per-index expression without a closure, so it
44/// always inlines under `#[target_feature]` -- unlike `core::array::from_fn` /
45/// `array::map`, which are only `#[inline]` and can be left out-of-line (dropping
46/// the target feature). The body is pasted directly into a `while` loop.
47macro_rules! array_each {
48    ([$init:expr; $n:expr], |$j:ident| $body:expr) => {{
49        let mut out = [$init; $n];
50        let mut $j = 0usize;
51        while $j < $n {
52            out[$j] = $body;
53            $j += 1;
54        }
55        out
56    }};
57}
58
59/// A real [`FloatVector`] usable as the inner storage of a [`Dual`] vector.
60///
61/// Requires the value type to support dual arithmetic ([`DualValue`]), to be a
62/// real float vector whose element is itself a [`DualValue`], and to be castable
63/// to itself.
64pub trait DualFloatVector: DualValue + FloatVector<Element: DualValue> + CastVector<Self> + SwizzleVector {}
65impl<V> DualFloatVector for V where V: DualValue + FloatVector<Element: DualValue> + CastVector<V> + SwizzleVector {}
66
67// Lane swizzles apply to every component: the primal and each derivative move
68// through the same permutation, so a swizzled dual is the dual of the
69// swizzled inputs.
70impl<V: DualFloatVector, const N: usize> Swizzle<V::Lanes> for Dual<V, N> {
71    #[inline(always)]
72    fn swizzle(self, other: Self, indices: GenericArray<u32, V::Lanes>) -> Self {
73        let mut out = Self {
74            re: self.re.swizzle(other.re, indices.clone()),
75            dual: [V::ZERO; N],
76        };
77        let mut i = 0;
78        while i < N {
79            out.dual[i] = self.dual[i].swizzle(other.dual[i], indices.clone());
80            i += 1;
81        }
82        out
83    }
84
85    #[inline(always)]
86    fn permute(self, indices: GenericArray<u32, V::Lanes>) -> Self {
87        let mut out = Self {
88            re: self.re.permute(indices.clone()),
89            dual: [V::ZERO; N],
90        };
91        let mut i = 0;
92        while i < N {
93            out.dual[i] = self.dual[i].permute(indices.clone());
94            i += 1;
95        }
96        out
97    }
98
99    // The `_const` forms must forward per component rather than take the trait
100    // defaults: the defaults route through the runtime-index methods, losing
101    // the immediate-encoded shuffles the component vectors' own `_const`
102    // overrides produce.
103    #[inline(always)]
104    fn swizzle_const<I: SwizzleIndices<V::Lanes>>(self, other: Self) -> Self {
105        let mut out = Self {
106            re: self.re.swizzle_const::<I>(other.re),
107            dual: [V::ZERO; N],
108        };
109        let mut i = 0;
110        while i < N {
111            out.dual[i] = self.dual[i].swizzle_const::<I>(other.dual[i]);
112            i += 1;
113        }
114        out
115    }
116
117    #[inline(always)]
118    fn permute_const<I: SwizzleIndices<V::Lanes>>(self) -> Self {
119        let mut out = Self {
120            re: self.re.permute_const::<I>(),
121            dual: [V::ZERO; N],
122        };
123        let mut i = 0;
124        while i < N {
125            out.dual[i] = self.dual[i].permute_const::<I>();
126            i += 1;
127        }
128        out
129    }
130}
131
132// =====================================================================================
133// Element stack: Dual<E, N> as a scalar element
134// =====================================================================================
135
136#[rustfmt::skip]
137impl<E: DualValue + Element, const N: usize> Element for Dual<E, N> {
138    type Signed = <E as Element>::Signed;
139    type Unsigned = <E as Element>::Unsigned;
140
141    const ZERO: Self = Self::ZERO;
142    const ONE: Self = Self::ONE;
143
144    // Ordering is by the primal value, so the order extremes are constants
145    // (zero derivative) at the primal's extremes, and unordered values (NaN)
146    // exist exactly when the primal type has them.
147    const ORDER_MAX: Self = Self::constant(E::ORDER_MAX);
148    const ORDER_MIN: Self = Self::constant(E::ORDER_MIN);
149    const HAS_UNORDERED: bool = E::HAS_UNORDERED;
150    const IS_FLOAT: bool = E::IS_FLOAT;
151
152    #[inline(always)] fn from_i8(value: i8) -> Self { Self::constant(E::from_i8(value)) }
153    #[inline(always)] fn from_u8(value: u8) -> Self { Self::constant(E::from_u8(value)) }
154    #[inline(always)] fn from_u16(value: u16) -> Self { Self::constant(E::from_u16(value)) }
155}
156
157impl<E: DualValue + SignedElement, const N: usize> SignedElement for Dual<E, N> {
158    #[inline(always)]
159    fn abs(self) -> Self {
160        if self.re < E::ZERO { -self } else { self }
161    }
162
163    #[inline(always)]
164    fn signum(self) -> Self {
165        Self::constant(self.re.signum())
166    }
167}
168
169/// Marker type splatting a compile-time integer constant as `Dual<E, DN>`
170/// (with zero derivative).
171pub struct DualIntConst<E, const DN: usize, const VAL: LargeInt>(PhantomData<E>);
172
173/// Marker type splatting a compile-time rational constant `N/D` as `Dual<E, DN>`
174/// (with zero derivative).
175pub struct DualRatioConst<E, const DN: usize, const NUM: LargeInt, const DEN: LargeInt>(PhantomData<E>);
176
177impl<E: DualValue + FloatElement, const DN: usize, const VAL: LargeInt> SplatConst<Dual<E, DN>>
178    for DualIntConst<E, DN, VAL>
179{
180    const VALUE: Dual<E, DN> = Dual::constant(<E::ConstInt<VAL> as SplatConst<E>>::VALUE);
181}
182
183impl<E: DualValue + FloatElement, const DN: usize, const NUM: LargeInt, const DEN: LargeInt> SplatConst<Dual<E, DN>>
184    for DualRatioConst<E, DN, NUM, DEN>
185{
186    const VALUE: Dual<E, DN> = Dual::constant(<E::ConstRatio<NUM, DEN> as SplatConst<E>>::VALUE);
187}
188
189#[rustfmt::skip]
190impl<E: DualValue + FloatElement, const N: usize> FloatElement for Dual<E, N> {
191    #[inline(always)]
192    fn sqrt(this: Self) -> Self {
193        let s = E::sqrt(this.re);
194        // d/dx sqrt(x) = 1 / (2 sqrt(x))
195        let factor = E::VAL_ONE / (s + s);
196        this.chain(s, factor)
197    }
198
199    #[inline(always)] fn floor(this: Self) -> Self { Self::constant(E::floor(this.re)) }
200    #[inline(always)] fn ceil(this: Self) -> Self { Self::constant(E::ceil(this.re)) }
201    #[inline(always)] fn round(this: Self) -> Self { Self::constant(E::round(this.re)) }
202    #[inline(always)] fn trunc(this: Self) -> Self { Self::constant(E::trunc(this.re)) }
203
204    // next_up/next_down only perturb the representation, not the derivative.
205    #[inline(always)] fn next_up(this: Self) -> Self { Self { re: E::next_up(this.re), dual: this.dual } }
206    #[inline(always)] fn next_down(this: Self) -> Self { Self { re: E::next_down(this.re), dual: this.dual } }
207
208    #[inline(always)]
209    fn try_from_int(value: LargeInt) -> Option<Self> {
210        E::try_from_int(value).map(Self::constant)
211    }
212
213    #[inline(always)]
214    fn try_from_ratio(n: LargeInt, d: LargeInt) -> Option<Self> {
215        E::try_from_ratio(n, d).map(Self::constant)
216    }
217
218    const HAS_INFINITY: bool = E::HAS_INFINITY;
219    const HAS_SIGNED_ZERO: bool = E::HAS_SIGNED_ZERO;
220    const HAS_SUBNORMALS: bool = E::HAS_SUBNORMALS;
221
222    type ConstInt<const VAL: LargeInt> = DualIntConst<E, N, VAL>;
223    type ConstRatio<const NUM: LargeInt, const DEN: LargeInt> = DualRatioConst<E, N, NUM, DEN>;
224}
225
226// =====================================================================================
227// const_default / HasIsa / Selectable / Interleave
228// =====================================================================================
229
230impl<V: DualValue, const N: usize> thermite::const_default::ConstDefault for Dual<V, N> {
231    const DEFAULT: Self = Self::ZERO;
232}
233
234macro_rules! impl_float_consts {
235    ($($name:ident),* $(,)?) => {
236        impl<V: DualValue + thermite::math::FloatConsts, const N: usize> thermite::math::FloatConsts for Dual<V, N> {
237            $(const $name: Self = Self::constant(<V as thermite::math::FloatConsts>::$name);)*
238        }
239    };
240}
241
242impl_float_consts!(
243    NEG_ZERO,
244    E,
245    EULER_GAMMA,
246    PI_SQUARED,
247    PI_CUBED,
248    PI_FOURTH,
249    FRAC_1_PI,
250    FRAC_1_SQRT_2,
251    FRAC_1_SQRT_3,
252    FRAC_2_PI,
253    FRAC_1_SQRT_PI,
254    FRAC_2_SQRT_PI,
255    FRAC_SQRT_PI_2,
256    FRAC_1_SQRT_TAU,
257    FRAC_PI_2,
258    FRAC_PI_3,
259    FRAC_PI_4,
260    FRAC_PI_6,
261    FRAC_PI_8,
262    FRAC_PI_180,
263    FRAC_180_PI,
264    LN_2,
265    LN_10,
266    LN_PI,
267    FRAC_LN_PI_2,
268    LOG2_10,
269    LOG2_E,
270    LOG10_2,
271    LOG10_E,
272    PI,
273    SQRT_2,
274    SQRT_3,
275    SQRT_E,
276    EPSILON,
277    SQRT_EPSILON,
278    FOURTH_ROOT_EPSILON,
279    TAU,
280    SQRT_FRAC_PI_2,
281    SQRT_TAU,
282    PHI,
283    FRAC_1_3,
284    FRAC_2_3,
285    FRAC_1_4,
286    FRAC_1_6,
287    FRAC_NEG_1_E
288);
289
290impl<V: thermite::simd::HasIsa, const N: usize> thermite::simd::HasIsa for Dual<V, N> {
291    type Native = V::Native;
292
293    const ISA: thermite::isa::InstructionSet = V::ISA;
294}
295
296impl<V: DualFloatVector, const N: usize> GenericSelectable for Dual<V, N> {
297    type SelectableMask = <V as GenericSelectable>::SelectableMask;
298
299    #[inline(always)]
300    fn select<M>(mask: M, t: Self, f: Self) -> Self
301    where
302        Self::SelectableMask: CastMask<M>,
303    {
304        let mask = <Self::SelectableMask as CastMask<M>>::mask_from(mask);
305
306        // Per-component blend via a hand-rolled loop (this is the primitive behind min/max,
307        // clamp and every masked `_c`/`_m`/`_z` op, so keep it allocation/closure-free).
308        let mut dual = t.dual;
309        let mut i = 0;
310        while i < N {
311            dual[i] = mask.select(t.dual[i], f.dual[i]);
312            i += 1;
313        }
314        Self {
315            re: mask.select(t.re, f.re),
316            dual,
317        }
318    }
319}
320
321#[rustfmt::skip]
322impl<V: DualFloatVector, const N: usize> Interleave for Dual<V, N> {
323    #[inline(always)]
324    fn interleave(self, other: Self) -> (Self, Self) {
325        let (re_lo, re_hi) = self.re.interleave(other.re);
326        let mut lo = Self { re: re_lo, dual: [V::ZERO; N] };
327        let mut hi = Self { re: re_hi, dual: [V::ZERO; N] };
328        for i in 0..N {
329            let (d_lo, d_hi) = self.dual[i].interleave(other.dual[i]);
330            lo.dual[i] = d_lo;
331            hi.dual[i] = d_hi;
332        }
333        (lo, hi)
334    }
335
336    #[inline(always)]
337    fn deinterleave(self, other: Self) -> (Self, Self) {
338        let (re_lo, re_hi) = self.re.deinterleave(other.re);
339        let mut lo = Self { re: re_lo, dual: [V::ZERO; N] };
340        let mut hi = Self { re: re_hi, dual: [V::ZERO; N] };
341        for i in 0..N {
342            let (d_lo, d_hi) = self.dual[i].deinterleave(other.dual[i]);
343            lo.dual[i] = d_lo;
344            hi.dual[i] = d_hi;
345        }
346        (lo, hi)
347    }
348}
349
350/// The lane-sort key: strictly-before by the primal alone. See
351/// `thermite::sort::SortKey` for why this is a static trait method and not a
352/// closure.
353impl<V: DualFloatVector, const N: usize> thermite::sort::SortKey<Self> for Dual<V, N> {
354    #[inline(always)]
355    fn key_lt(a: Self, b: Self) -> V::Mask {
356        a.re.cmp_lt(b.re)
357    }
358}
359
360/// Scalar insertion walk over whole lanes, for widths past the network ladder.
361/// Quadratic, like core's `sort_any`; compares composite elements through
362/// `PartialOrd`.
363#[inline(always)]
364fn sort_lanes_scalar<V: NumericVector, O: thermite::sort::SortOrder>(v: V) -> V
365where
366    V::Element: PartialOrd,
367{
368    let mut out = v;
369    let mut i = 1;
370    while i < V::LANES {
371        let key = out.extractv(i);
372        let mut j = i;
373        while j > 0 {
374            let prev = out.extractv(j - 1);
375            let misplaced = if O::IS_ASCENDING { prev > key } else { prev < key };
376            if !misplaced {
377                break;
378            }
379            out = out.insertv(j, prev);
380            j -= 1;
381        }
382        out = out.insertv(j, key);
383        i += 1;
384    }
385    out
386}
387
388// =====================================================================================
389// Compile-time splat / new machinery (re correct, derivatives zeroed -- see module docs)
390// =====================================================================================
391
392/// `SplatConst<V::Element>` carrier extracting the `re` part of a `Dual` element constant.
393struct DualReSplat<E, V, const N: usize>(PhantomData<(E, V)>);
394
395impl<E, V: DualFloatVector, const N: usize> SplatConst<V::Element> for DualReSplat<E, V, N>
396where
397    E: SplatConst<Dual<V::Element, N>>,
398{
399    const VALUE: V::Element = <E as SplatConst<Dual<V::Element, N>>>::VALUE.re;
400}
401
402impl<V: DualFloatVector, const N: usize> SplatVector<Dual<V::Element, N>> for Dual<V, N> {
403    type Splat<T: SplatConst<Dual<V::Element, N>>> = Self;
404}
405
406impl<V: DualFloatVector, const N: usize, E: SplatConst<Dual<V::Element, N>>> VectorValue<E, Dual<V, N>> for Dual<V, N> {
407    const VALUE: Dual<V, N> = Dual {
408        re: const_splat::<V, DualReSplat<E, V, N>>(),
409        dual: [<V as NumericVector>::ZERO; N],
410    };
411}
412
413/// `NewConst<V::Element, Lanes>` carrier extracting the per-lane `re` parts of a `Dual` element array.
414struct DualReNew<C, V, const N: usize>(PhantomData<(C, V)>);
415
416impl<C, V: DualFloatVector, const N: usize> NewConst<V::Element, V::Lanes> for DualReNew<C, V, N>
417where
418    C: NewConst<Dual<V::Element, N>, V::Lanes>,
419{
420    const VALUES: GenericArray<V::Element, V::Lanes> = const {
421        let c_vals = C::VALUES;
422        let src = c_vals.as_slice();
423        let mut out: GenericArray<V::Element, V::Lanes> = unsafe { core::mem::zeroed() };
424        let dst = out.as_mut_slice();
425        let mut i = 0;
426        while i < V::LANES {
427            dst[i] = src[i].re;
428            i += 1;
429        }
430        core::mem::forget(c_vals);
431        out
432    };
433}
434
435/// `VectorValue` implementor for per-lane (`new`) construction of `Dual` vectors.
436pub struct DualNewImpl;
437
438impl<T, V: DualFloatVector, const N: usize> VectorValue<T, Dual<V, N>> for DualNewImpl
439where
440    T: NewConst<Dual<V::Element, N>, V::Lanes>,
441{
442    const VALUE: Dual<V, N> = Dual {
443        re: const_new::<V, V::Lanes, DualReNew<T, V, N>>(),
444        dual: [<V as NumericVector>::ZERO; N],
445    };
446}
447
448impl<V: DualFloatVector, const N: usize> NewVector<Dual<V::Element, N>, V::Lanes> for Dual<V, N> {
449    type New<T: NewConst<Dual<V::Element, N>, V::Lanes>> = DualNewImpl;
450}
451
452// =====================================================================================
453// CastVector
454// =====================================================================================
455
456impl<FROM, TO, const N: usize> CastVector<Dual<FROM, N>> for Dual<TO, N>
457where
458    FROM: DualFloatVector + CastVector<TO>,
459    TO: DualFloatVector + CastVector<FROM>,
460{
461    #[inline(always)]
462    fn cast_into(self) -> Dual<FROM, N> {
463        Dual::<FROM, N>::cast_from(self)
464    }
465
466    #[inline(always)]
467    fn cast_from(from: Dual<FROM, N>) -> Self {
468        Self {
469            re: TO::cast_from(from.re),
470            dual: array_each!([TO::ZERO; N], |i| TO::cast_from(from.dual[i])),
471        }
472    }
473}
474
475// =====================================================================================
476// GenericVector
477// =====================================================================================
478
479impl<V: DualFloatVector, const N: usize> GenericVector for Dual<V, N> {
480    type Element = Dual<V::Element, N>;
481
482    const EMPTY: Self = Self::ZERO;
483    const LANES: usize = V::LANES;
484
485    type Lanes = V::Lanes;
486
487    type Unsigned = V::Unsigned;
488    type Signed = V::Signed;
489    type Mask = V::Mask;
490
491    #[inline(always)]
492    fn new<const M: usize>(value: [Self::Element; M]) -> Self
493    where
494        Const<M>: IntoArrayLength<ArrayLength = Self::Lanes>,
495    {
496        Self {
497            re: V::new(array_each!([<V::Element as Element>::ZERO; M], |m| value[m].re)),
498            dual: array_each!([V::ZERO; N], |j| V::new(array_each!(
499                [<V::Element as Element>::ZERO; M],
500                |m| value[m].dual[j]
501            ))),
502        }
503    }
504
505    #[inline(always)]
506    fn into_array(self) -> GenericArray<Self::Element, Self::Lanes> {
507        let mut arr = GenericArray::default();
508        for i in 0..Self::LANES {
509            arr[i] = Dual {
510                re: self.re.extractv(i),
511                dual: array_each!([<V::Element as Element>::ZERO; N], |j| self.dual[j].extractv(i)),
512            };
513        }
514        arr
515    }
516
517    #[inline(always)]
518    fn splat(value: Self::Element) -> Self {
519        Self {
520            re: V::splat(value.re),
521            dual: array_each!([V::ZERO; N], |j| V::splat(value.dual[j])),
522        }
523    }
524
525    #[inline(always)]
526    fn single(value: Self::Element) -> Self {
527        Self {
528            re: V::single(value.re),
529            dual: array_each!([V::ZERO; N], |j| V::single(value.dual[j])),
530        }
531    }
532
533    // Composite element alignment only guarantees the alignment of a single
534    // `V::Element`, so aligned load/store just forward to the unaligned path -
535    // there is no separate "aligned" fast path to take.
536    #[inline(always)]
537    unsafe fn load(ptr: *const Self::Element) -> Self {
538        unsafe { Self::load_unaligned(ptr) }
539    }
540
541    /// A `Dual` element is `#[repr(C)]` over `1 + N` floats, so a single
542    /// element is exactly [`load_deinterleaved::<1>`](Self::load_deinterleaved)
543    /// - which routes through the inner vector's tuned register engine, not a
544    /// scalar lane-by-lane loop.
545    #[inline(always)]
546    unsafe fn load_unaligned(ptr: *const Self::Element) -> Self {
547        let [out] = unsafe { Self::load_deinterleaved::<1>(ptr) };
548        out
549    }
550
551    #[inline(always)]
552    unsafe fn load_streaming(ptr: *const Self::Element) -> Self {
553        unsafe { Self::load(ptr) }
554    }
555
556    /// A `Dual` element is `#[repr(C)]` over `1 + N` floats (the primal, then
557    /// the `N` derivative parts), so `M` interleaved `Dual` streams are
558    /// exactly `M * (N + 1)` interleaved float streams. That is precisely the
559    /// factorization [`StreamGroup`] exists for: this hands `M` and `N`
560    /// straight to the inner vector's [`GenericVector::load_deinterleaved_grouped`]
561    /// (a NEON `LD2`/`LD3`/`LD4`, or a shuffle network on x86), for any `M`
562    /// and `N` - no dispatch ladder, no scalar fallback.
563    #[inline(always)]
564    unsafe fn load_deinterleaved<const M: usize>(ptr: *const Self::Element) -> [Self; M] {
565        let groups = unsafe { V::load_deinterleaved_grouped::<M, N>(ptr as *const V::Element) };
566
567        let mut out = [Self::EMPTY; M];
568        let mut j = 0;
569        while j < M {
570            out[j] = Dual {
571                re: groups[j].head,
572                dual: groups[j].tail,
573            };
574            j += 1;
575        }
576        out
577    }
578
579    /// The exact inverse of [`load_deinterleaved`](Self::load_deinterleaved).
580    #[inline(always)]
581    unsafe fn store_interleaved<const M: usize>(ptr: *mut Self::Element, values: [Self; M]) {
582        let mut groups = [StreamGroup {
583            head: V::ZERO,
584            tail: [V::ZERO; N],
585        }; M];
586        let mut j = 0;
587        while j < M {
588            groups[j] = StreamGroup {
589                head: values[j].re,
590                tail: values[j].dual,
591            };
592            j += 1;
593        }
594        unsafe { V::store_interleaved_grouped::<M, N>(ptr as *mut V::Element, groups) }
595    }
596
597    #[inline(always)]
598    unsafe fn load_m(src: Self, mask: Self::Mask, ptr: *const Self::Element) -> Self {
599        let flags = mask.select(<V::Signed as NumericVector>::ONE, <V::Signed as NumericVector>::ZERO);
600        let zero = <<V::Signed as GenericVector>::Element as Element>::ZERO;
601        let mut out = src;
602        for i in 0..Self::LANES {
603            if flags.extractv(i) != zero {
604                out = out.insertv(i, unsafe { ptr.add(i).read() });
605            }
606        }
607        out
608    }
609
610    #[inline(always)]
611    unsafe fn load_z(mask: Self::Mask, ptr: *const Self::Element) -> Self {
612        unsafe { Self::load_m(Self::EMPTY, mask, ptr) }
613    }
614
615    // See the note on `load` above: aligned store forwards to unaligned.
616    #[inline(always)]
617    unsafe fn store(self, ptr: *mut Self::Element) {
618        unsafe { self.store_unaligned(ptr) }
619    }
620
621    #[inline(always)]
622    unsafe fn store_unaligned(self, ptr: *mut Self::Element) {
623        unsafe { Self::store_interleaved::<1>(ptr, [self]) }
624    }
625
626    #[inline(always)]
627    unsafe fn store_streaming(self, ptr: *mut Self::Element) {
628        unsafe { self.store(ptr) }
629    }
630
631    #[inline(always)]
632    fn interleave_by<const GROUP: usize>(self, other: Self) -> (Self, Self) {
633        let (re_lo, re_hi) = self.re.interleave_by::<GROUP>(other.re);
634        let mut lo = Self {
635            re: re_lo,
636            dual: [V::ZERO; N],
637        };
638        let mut hi = Self {
639            re: re_hi,
640            dual: [V::ZERO; N],
641        };
642        for i in 0..N {
643            let (d_lo, d_hi) = self.dual[i].interleave_by::<GROUP>(other.dual[i]);
644            lo.dual[i] = d_lo;
645            hi.dual[i] = d_hi;
646        }
647        (lo, hi)
648    }
649
650    #[inline(always)]
651    fn deinterleave_by<const GROUP: usize>(self, other: Self) -> (Self, Self) {
652        let (re_lo, re_hi) = self.re.deinterleave_by::<GROUP>(other.re);
653        let mut lo = Self {
654            re: re_lo,
655            dual: [V::ZERO; N],
656        };
657        let mut hi = Self {
658            re: re_hi,
659            dual: [V::ZERO; N],
660        };
661        for i in 0..N {
662            let (d_lo, d_hi) = self.dual[i].deinterleave_by::<GROUP>(other.dual[i]);
663            lo.dual[i] = d_lo;
664            hi.dual[i] = d_hi;
665        }
666        (lo, hi)
667    }
668
669    // `M` is the radix (input count); `N` is the (fixed) derivative-part count.
670    // Each component - `re` and each of the `N` duals - is radix-interleaved
671    // independently across the `M` inputs.
672    #[inline(always)]
673    fn interleave_radix<const M: usize>(inputs: [Self; M]) -> [Self; M] {
674        let mut re = [V::EMPTY; M];
675        for i in 0..M {
676            re[i] = inputs[i].re;
677        }
678        let re = V::interleave_radix::<M>(re);
679
680        let mut out = [Self::EMPTY; M];
681        for i in 0..M {
682            out[i].re = re[i];
683        }
684        for d in 0..N {
685            let mut comp = [V::EMPTY; M];
686            for i in 0..M {
687                comp[i] = inputs[i].dual[d];
688            }
689            let comp = V::interleave_radix::<M>(comp);
690            for i in 0..M {
691                out[i].dual[d] = comp[i];
692            }
693        }
694        out
695    }
696
697    #[inline(always)]
698    fn deinterleave_radix<const M: usize>(inputs: [Self; M]) -> [Self; M] {
699        let mut re = [V::EMPTY; M];
700        for i in 0..M {
701            re[i] = inputs[i].re;
702        }
703        let re = V::deinterleave_radix::<M>(re);
704
705        let mut out = [Self::EMPTY; M];
706        for i in 0..M {
707            out[i].re = re[i];
708        }
709        for d in 0..N {
710            let mut comp = [V::EMPTY; M];
711            for i in 0..M {
712                comp[i] = inputs[i].dual[d];
713            }
714            let comp = V::deinterleave_radix::<M>(comp);
715            for i in 0..M {
716                out[i].dual[d] = comp[i];
717            }
718        }
719        out
720    }
721
722    #[inline(always)]
723    fn deinterleave_radix_by<const M: usize, const GROUP: usize>(inputs: [Self; M]) -> [Self; M] {
724        let mut re = [V::EMPTY; M];
725        for i in 0..M {
726            re[i] = inputs[i].re;
727        }
728        let re = V::deinterleave_radix_by::<M, GROUP>(re);
729
730        let mut out = [Self::EMPTY; M];
731        for i in 0..M {
732            out[i].re = re[i];
733        }
734        for d in 0..N {
735            let mut comp = [V::EMPTY; M];
736            for i in 0..M {
737                comp[i] = inputs[i].dual[d];
738            }
739            let comp = V::deinterleave_radix_by::<M, GROUP>(comp);
740            for i in 0..M {
741                out[i].dual[d] = comp[i];
742            }
743        }
744        out
745    }
746
747    #[inline(always)]
748    fn interleave_radix_by<const M: usize, const GROUP: usize>(inputs: [Self; M]) -> [Self; M] {
749        let mut re = [V::EMPTY; M];
750        for i in 0..M {
751            re[i] = inputs[i].re;
752        }
753        let re = V::interleave_radix_by::<M, GROUP>(re);
754
755        let mut out = [Self::EMPTY; M];
756        for i in 0..M {
757            out[i].re = re[i];
758        }
759        for d in 0..N {
760            let mut comp = [V::EMPTY; M];
761            for i in 0..M {
762                comp[i] = inputs[i].dual[d];
763            }
764            let comp = V::interleave_radix_by::<M, GROUP>(comp);
765            for i in 0..M {
766                out[i].dual[d] = comp[i];
767            }
768        }
769        out
770    }
771
772    #[inline(always)]
773    unsafe fn store_masked(self, mask: Self::Mask, ptr: *mut Self::Element) {
774        let flags = mask.select(<V::Signed as NumericVector>::ONE, <V::Signed as NumericVector>::ZERO);
775        let zero = <<V::Signed as GenericVector>::Element as Element>::ZERO;
776        for i in 0..Self::LANES {
777            if flags.extractv(i) != zero {
778                unsafe { ptr.add(i).write(self.extractv(i)) };
779            }
780        }
781    }
782
783    #[inline(always)]
784    unsafe fn lookup_unchecked(values: &[Self::Element], indices: Self::Unsigned) -> Self {
785        let mut res = Self::EMPTY;
786        for i in 0..Self::LANES {
787            let Ok(idx) = indices.extractv(i).try_into() else {
788                panic!("Index out of bounds for usize");
789            };
790            res = res.insertv(i, values[idx]);
791        }
792        res
793    }
794
795    #[inline(always)]
796    fn broadcast<const I: usize>(self) -> Self {
797        Self {
798            re: V::broadcast::<I>(self.re),
799            dual: array_each!([V::ZERO; N], |j| V::broadcast::<I>(self.dual[j])),
800        }
801    }
802
803    #[inline(always)]
804    fn broadcastv(self, idx: usize) -> Self {
805        Self {
806            re: self.re.broadcastv(idx),
807            dual: array_each!([V::ZERO; N], |j| self.dual[j].broadcastv(idx)),
808        }
809    }
810
811    #[inline(always)]
812    fn extract<const I: usize>(self) -> Self::Element {
813        Dual {
814            re: V::extract::<I>(self.re),
815            dual: array_each!([<V::Element as Element>::ZERO; N], |j| V::extract::<I>(self.dual[j])),
816        }
817    }
818
819    #[inline(always)]
820    fn extractv(self, idx: usize) -> Self::Element {
821        Dual {
822            re: self.re.extractv(idx),
823            dual: array_each!([<V::Element as Element>::ZERO; N], |j| self.dual[j].extractv(idx)),
824        }
825    }
826
827    #[inline(always)]
828    fn insert<const I: usize>(self, value: Self::Element) -> Self {
829        Self {
830            re: V::insert::<I>(self.re, value.re),
831            dual: array_each!([V::ZERO; N], |j| V::insert::<I>(self.dual[j], value.dual[j])),
832        }
833    }
834
835    #[inline(always)]
836    fn insertv(self, idx: usize, value: Self::Element) -> Self {
837        Self {
838            re: self.re.insertv(idx, value.re),
839            dual: array_each!([V::ZERO; N], |j| self.dual[j].insertv(idx, value.dual[j])),
840        }
841    }
842
843    #[inline(always)]
844    fn reverse(self) -> Self {
845        Self {
846            re: self.re.reverse(),
847            dual: array_each!([V::ZERO; N], |j| self.dual[j].reverse()),
848        }
849    }
850
851    #[inline(always)]
852    fn swap_bytes(self) -> Self {
853        Self {
854            re: self.re.swap_bytes(),
855            dual: array_each!([V::ZERO; N], |j| self.dual[j].swap_bytes()),
856        }
857    }
858
859    #[inline(always)]
860    fn zz(self, mask: Self::Mask) -> Self {
861        Self {
862            re: self.re.zz(mask),
863            dual: array_each!([V::ZERO; N], |j| self.dual[j].zz(mask)),
864        }
865    }
866
867    #[inline(always)]
868    fn nz(self, mask: Self::Mask) -> Self {
869        Self {
870            re: self.re.nz(mask),
871            dual: array_each!([V::ZERO; N], |j| self.dual[j].nz(mask)),
872        }
873    }
874
875    #[inline(always)]
876    fn compress(self, mask: Self::Mask) -> Self {
877        Self {
878            re: self.re.compress(mask),
879            dual: array_each!([V::ZERO; N], |j| self.dual[j].compress(mask)),
880        }
881    }
882
883    #[inline(always)]
884    fn compress_z(self, mask: Self::Mask) -> Self {
885        Self {
886            re: self.re.compress_z(mask),
887            dual: array_each!([V::ZERO; N], |j| self.dual[j].compress_z(mask)),
888        }
889    }
890
891    // The compaction family is pure lane movement driven by `mask` alone, so every
892    // component takes the same permutation and the value/derivative pairing survives
893    // it. That includes `compress_m`, whose keep-lanes are chosen by the population
894    // count of the shared mask and so land identically in each component.
895    #[inline(always)]
896    fn compress_m(self, src: Self, mask: Self::Mask) -> Self {
897        Self {
898            re: self.re.compress_m(src.re, mask),
899            dual: array_each!([V::ZERO; N], |j| self.dual[j].compress_m(src.dual[j], mask)),
900        }
901    }
902
903    #[inline(always)]
904    fn expand(self, mask: Self::Mask) -> Self {
905        Self {
906            re: self.re.expand(mask),
907            dual: array_each!([V::ZERO; N], |j| self.dual[j].expand(mask)),
908        }
909    }
910
911    #[inline(always)]
912    fn expand_z(self, mask: Self::Mask) -> Self {
913        Self {
914            re: self.re.expand_z(mask),
915            dual: array_each!([V::ZERO; N], |j| self.dual[j].expand_z(mask)),
916        }
917    }
918
919    #[inline(always)]
920    fn expand_m(self, src: Self, mask: Self::Mask) -> Self {
921        Self {
922            re: self.re.expand_m(src.re, mask),
923            dual: array_each!([V::ZERO; N], |j| self.dual[j].expand_m(src.dual[j], mask)),
924        }
925    }
926
927    #[inline(always)]
928    fn align<const OFFSET: usize>(self, other: Self) -> Self {
929        Self {
930            re: self.re.align::<OFFSET>(other.re),
931            dual: array_each!([V::ZERO; N], |j| self.dual[j].align::<OFFSET>(other.dual[j])),
932        }
933    }
934
935    // Every component aligns through `V`, so this is only as native as `V` is.
936    const HAS_NATIVE_ALIGN: bool = V::HAS_NATIVE_ALIGN;
937
938    #[inline(always)]
939    fn map<F>(mut self, f: F) -> Self
940    where
941        F: Fn(Self::Element) -> Self::Element,
942    {
943        for i in 0..Self::LANES {
944            self = self.insertv(i, f(self.extractv(i)));
945        }
946        self
947    }
948
949    #[inline(always)]
950    fn fold<F>(self, mut init: Self::Element, f: F) -> Self::Element
951    where
952        F: Fn(Self::Element, Self::Element) -> Self::Element,
953    {
954        for i in 0..Self::LANES {
955            init = f(init, self.extractv(i));
956        }
957        init
958    }
959
960    #[inline(always)]
961    fn reduce<F>(self, f: F) -> Self::Element
962    where
963        F: Fn(Self::Element, Self::Element) -> Self::Element,
964    {
965        let mut result = self.extractv(0);
966        for i in 1..Self::LANES {
967            result = f(result, self.extractv(i));
968        }
969        result
970    }
971
972    #[inline(always)]
973    fn splat_m(src: Self, mask: Self::Mask, value: Self::Element) -> Self {
974        mask.select(Self::splat(value), src)
975    }
976    #[inline(always)]
977    fn splat_z(mask: Self::Mask, value: Self::Element) -> Self {
978        mask.select(Self::splat(value), Self::EMPTY)
979    }
980    #[inline(always)]
981    fn broadcast_c<const I: usize>(self, mask: Self::Mask) -> Self {
982        mask.select(self.broadcast::<I>(), self)
983    }
984    #[inline(always)]
985    fn broadcast_m<const I: usize>(self, src: Self, mask: Self::Mask) -> Self {
986        mask.select(self.broadcast::<I>(), src)
987    }
988    #[inline(always)]
989    fn broadcast_z<const I: usize>(self, mask: Self::Mask) -> Self {
990        mask.select(self.broadcast::<I>(), Self::EMPTY)
991    }
992    #[inline(always)]
993    fn broadcastv_c(self, mask: Self::Mask, idx: usize) -> Self {
994        mask.select(self.broadcastv(idx), self)
995    }
996    #[inline(always)]
997    fn broadcastv_m(self, src: Self, mask: Self::Mask, idx: usize) -> Self {
998        mask.select(self.broadcastv(idx), src)
999    }
1000    #[inline(always)]
1001    fn broadcastv_z(self, mask: Self::Mask, idx: usize) -> Self {
1002        mask.select(self.broadcastv(idx), Self::EMPTY)
1003    }
1004    #[inline(always)]
1005    fn reverse_c(self, mask: Self::Mask) -> Self {
1006        mask.select(self.reverse(), self)
1007    }
1008    #[inline(always)]
1009    fn reverse_m(self, src: Self, mask: Self::Mask) -> Self {
1010        mask.select(self.reverse(), src)
1011    }
1012    #[inline(always)]
1013    fn reverse_z(self, mask: Self::Mask) -> Self {
1014        mask.select(self.reverse(), Self::EMPTY)
1015    }
1016    #[inline(always)]
1017    fn swap_bytes_c(self, mask: Self::Mask) -> Self {
1018        mask.select(self.swap_bytes(), self)
1019    }
1020    #[inline(always)]
1021    fn swap_bytes_m(self, src: Self, mask: Self::Mask) -> Self {
1022        mask.select(self.swap_bytes(), src)
1023    }
1024    #[inline(always)]
1025    fn swap_bytes_z(self, mask: Self::Mask) -> Self {
1026        mask.select(self.swap_bytes(), Self::EMPTY)
1027    }
1028}
1029
1030// =====================================================================================
1031// PartialOrdVector -- compare by primal value
1032// =====================================================================================
1033
1034#[rustfmt::skip]
1035impl<V: DualFloatVector, const N: usize> PartialOrdVector for Dual<V, N> {
1036    #[inline(always)] fn cmp_eq(self, other: Self) -> Self::Mask { self.re.cmp_eq(other.re) }
1037    #[inline(always)] fn cmp_ne(self, other: Self) -> Self::Mask { self.re.cmp_ne(other.re) }
1038    #[inline(always)] fn cmp_lt(self, other: Self) -> Self::Mask { self.re.cmp_lt(other.re) }
1039    #[inline(always)] fn cmp_gt(self, other: Self) -> Self::Mask { self.re.cmp_gt(other.re) }
1040    #[inline(always)] fn cmp_le(self, other: Self) -> Self::Mask { self.re.cmp_le(other.re) }
1041    #[inline(always)] fn cmp_ge(self, other: Self) -> Self::Mask { self.re.cmp_ge(other.re) }
1042}
1043
1044// =====================================================================================
1045// Iterator Sum/Product + Bounded
1046// =====================================================================================
1047
1048impl<V: DualValue, const N: usize> core::iter::Sum for Dual<V, N> {
1049    #[inline]
1050    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
1051        iter.fold(Self::ZERO, |a, b| a + b)
1052    }
1053}
1054
1055impl<V: DualValue, const N: usize> core::iter::Product for Dual<V, N> {
1056    #[inline]
1057    fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
1058        iter.fold(Self::ONE, |a, b| a * b)
1059    }
1060}
1061
1062#[rustfmt::skip]
1063impl<V: DualFloatVector, const N: usize> Bounded for Dual<V, N> {
1064    #[inline(always)] fn min_value() -> Self { Self::constant(V::MIN) }
1065    #[inline(always)] fn max_value() -> Self { Self::constant(V::MAX) }
1066}
1067
1068// =====================================================================================
1069// Square (masked) -- needed by NumericVector
1070// =====================================================================================
1071
1072impl<V: DualFloatVector, const N: usize> SquareMasked<V::Mask> for Dual<V, N> {
1073    #[inline(always)]
1074    fn square_c(self, mask: V::Mask) -> Self::Output {
1075        mask.select(self.square(), self)
1076    }
1077
1078    #[inline(always)]
1079    fn square_m(self, src: Self, mask: V::Mask) -> Self::Output {
1080        mask.select(self.square(), src)
1081    }
1082
1083    #[inline(always)]
1084    fn square_z(self, mask: V::Mask) -> Self::Output {
1085        mask.select(self.square(), Self::ZERO)
1086    }
1087}
1088
1089// =====================================================================================
1090// Masked arithmetic ops (select-based, like thermite-compensated)
1091// =====================================================================================
1092
1093macro_rules! impl_masked {
1094    (MUL_ADD: $($method:ident),*) => {paste::paste! {
1095        impl<V: DualFloatVector, const N: usize, A, B> thermite::vector::ops::MulAddExtMasked<V::Mask, A, B> for Dual<V, N>
1096        where
1097            Dual<V, N>: thermite::vector::ops::MulAddExt<A, B, Output = Self>,
1098        {
1099            $(
1100                #[inline(always)]
1101                fn [<$method _c>](self, mask: V::Mask, a: A, b: B) -> Self {
1102                    mask.select(self.$method(a, b), self)
1103                }
1104                #[inline(always)]
1105                fn [<$method _m>](self, src: Self, mask: V::Mask, a: A, b: B) -> Self {
1106                    mask.select(self.$method(a, b), src)
1107                }
1108                #[inline(always)]
1109                fn [<$method _z>](self, mask: V::Mask, a: A, b: B) -> Self {
1110                    mask.select(self.$method(a, b), Self::EMPTY)
1111                }
1112            )*
1113        }
1114
1115        impl<V: DualFloatVector, const N: usize, A, B> thermite::vector::ops::MulAddAssignExtMasked<V::Mask, A, B> for Dual<V, N>
1116        where
1117            Dual<V, N>: thermite::vector::ops::MulAddExt<A, B, Output = Self>,
1118        {
1119            $(
1120                #[inline(always)]
1121                fn [<$method _assign_c>](&mut self, mask: V::Mask, a: A, b: B) {
1122                    *self = mask.select(self.$method(a, b), *self);
1123                }
1124                #[inline(always)]
1125                fn [<$method _assign_m>](&mut self, src: Self, mask: V::Mask, a: A, b: B) {
1126                    *self = mask.select(self.$method(a, b), src);
1127                }
1128                #[inline(always)]
1129                fn [<$method _assign_z>](&mut self, mask: V::Mask, a: A, b: B) {
1130                    *self = mask.select(self.$method(a, b), Self::EMPTY);
1131                }
1132            )*
1133        }
1134    }};
1135
1136    ($trait:ident::$method:ident) => {paste::paste! {
1137        impl<V: DualFloatVector, const N: usize, Rhs> thermite::vector::ops::[<$trait Masked>]<V::Mask, Rhs> for Dual<V, N>
1138        where
1139            Dual<V, N>: core::ops::$trait<Rhs, Output = Self>,
1140        {
1141            #[inline(always)]
1142            fn [<$method _c>](self, mask: V::Mask, rhs: Rhs) -> Self {
1143                mask.select(self.$method(rhs), self)
1144            }
1145            #[inline(always)]
1146            fn [<$method _m>](self, src: Self, mask: V::Mask, rhs: Rhs) -> Self {
1147                mask.select(self.$method(rhs), src)
1148            }
1149            #[inline(always)]
1150            fn [<$method _z>](self, mask: V::Mask, rhs: Rhs) -> Self {
1151                mask.select(self.$method(rhs), Self::EMPTY)
1152            }
1153        }
1154
1155        impl<V: DualFloatVector, const N: usize, Rhs> thermite::vector::ops::[<$trait AssignMasked>]<V::Mask, Rhs> for Dual<V, N>
1156        where
1157            Dual<V, N>: core::ops::$trait<Rhs, Output = Self>,
1158        {
1159            #[inline(always)]
1160            fn [<$method _assign_c>](&mut self, mask: V::Mask, rhs: Rhs) {
1161                *self = mask.select(self.$method(rhs), *self);
1162            }
1163            #[inline(always)]
1164            fn [<$method _assign_m>](&mut self, src: Self, mask: V::Mask, rhs: Rhs) {
1165                *self = mask.select(self.$method(rhs), src);
1166            }
1167            #[inline(always)]
1168            fn [<$method _assign_z>](&mut self, mask: V::Mask, rhs: Rhs) {
1169                *self = mask.select(self.$method(rhs), Self::EMPTY);
1170            }
1171        }
1172    }};
1173}
1174
1175impl_masked!(MUL_ADD: mul_add, mul_sub, nmul_add, nmul_sub, mul_adde, mul_sube, nmul_adde, nmul_sube);
1176impl_masked!(Add::add);
1177impl_masked!(Sub::sub);
1178impl_masked!(Mul::mul);
1179impl_masked!(Div::div);
1180impl_masked!(Rem::rem);
1181
1182// =====================================================================================
1183// Lane-alternating add/sub (`AddSubExt`). `addsub` is linear, so it differentiates
1184// exactly like an add: apply it component-wise. The building block is `neg_even`,
1185// which flips the sign of the even lanes of every stored component (exact) via the
1186// inner vector's `addsub(0, w) = [-w0, w1, -w2, ...]`. Then:
1187//   addsub(a, b)      = a + neg_even(b)
1188//   fmaddsub(a, b, c) = a*b + neg_even(c)   (via the inner product-rule mul_adde)
1189//   fmsubadd(a, b, c) = a*b - neg_even(c)
1190// =====================================================================================
1191
1192#[inline(always)]
1193fn neg_even_dual<V: DualFloatVector, const N: usize>(x: Dual<V, N>) -> Dual<V, N> {
1194    let mut dual = x.dual;
1195    let mut i = 0;
1196    while i < N {
1197        dual[i] = V::ZERO.addsub(dual[i]);
1198        i += 1;
1199    }
1200    Dual {
1201        re: V::ZERO.addsub(x.re),
1202        dual,
1203    }
1204}
1205
1206impl<V: DualFloatVector, const N: usize> AddSubExt for Dual<V, N> {
1207    type Output = Self;
1208
1209    #[inline(always)]
1210    fn addsub(self, b: Self) -> Self {
1211        self + neg_even_dual(b)
1212    }
1213    #[inline(always)]
1214    fn fmaddsub(self, b: Self, c: Self) -> Self {
1215        self.mul_adde(b, neg_even_dual(c))
1216    }
1217    #[inline(always)]
1218    fn fmsubadd(self, b: Self, c: Self) -> Self {
1219        self.mul_sube(b, neg_even_dual(c))
1220    }
1221}
1222
1223impl<V: DualFloatVector, const N: usize> AddSubExtMasked<V::Mask> for Dual<V, N> {
1224    #[inline(always)]
1225    fn addsub_c(self, mask: V::Mask, b: Self) -> Self {
1226        mask.select(self.addsub(b), self)
1227    }
1228    #[inline(always)]
1229    fn addsub_m(self, src: Self, mask: V::Mask, b: Self) -> Self {
1230        mask.select(self.addsub(b), src)
1231    }
1232    #[inline(always)]
1233    fn addsub_z(self, mask: V::Mask, b: Self) -> Self {
1234        mask.select(self.addsub(b), Self::EMPTY)
1235    }
1236
1237    #[inline(always)]
1238    fn fmaddsub_c(self, mask: V::Mask, b: Self, c: Self) -> Self {
1239        mask.select(self.fmaddsub(b, c), self)
1240    }
1241    #[inline(always)]
1242    fn fmaddsub_m(self, src: Self, mask: V::Mask, b: Self, c: Self) -> Self {
1243        mask.select(self.fmaddsub(b, c), src)
1244    }
1245    #[inline(always)]
1246    fn fmaddsub_z(self, mask: V::Mask, b: Self, c: Self) -> Self {
1247        mask.select(self.fmaddsub(b, c), Self::EMPTY)
1248    }
1249
1250    #[inline(always)]
1251    fn fmsubadd_c(self, mask: V::Mask, b: Self, c: Self) -> Self {
1252        mask.select(self.fmsubadd(b, c), self)
1253    }
1254    #[inline(always)]
1255    fn fmsubadd_m(self, src: Self, mask: V::Mask, b: Self, c: Self) -> Self {
1256        mask.select(self.fmsubadd(b, c), src)
1257    }
1258    #[inline(always)]
1259    fn fmsubadd_z(self, mask: V::Mask, b: Self, c: Self) -> Self {
1260        mask.select(self.fmsubadd(b, c), Self::EMPTY)
1261    }
1262}
1263
1264// `_c`/`_m`/`_z` masked variants of the inherent unary (`fn m(self) -> Self`) and
1265// binary (`fn m(self, Self) -> Self`) vector ops, as plain blends -- the same
1266// select pattern `impl_masked!` uses for the `core::ops` methods above. Invoked
1267// inside the relevant trait impls below.
1268macro_rules! dual_masked {
1269    (unary: $($m:ident),* $(,)?) => { paste::paste! {
1270        $(
1271            #[inline(always)] fn [<$m _c>](self, mask: Self::Mask) -> Self { mask.select(self.$m(), self) }
1272            #[inline(always)] fn [<$m _m>](self, src: Self, mask: Self::Mask) -> Self { mask.select(self.$m(), src) }
1273            #[inline(always)] fn [<$m _z>](self, mask: Self::Mask) -> Self { mask.select(self.$m(), Self::ZERO) }
1274        )*
1275    }};
1276    (binary: $($m:ident),* $(,)?) => { paste::paste! {
1277        $(
1278            #[inline(always)] fn [<$m _c>](self, mask: Self::Mask, rhs: Self) -> Self { mask.select(self.$m(rhs), self) }
1279            #[inline(always)] fn [<$m _m>](self, src: Self, mask: Self::Mask, rhs: Self) -> Self { mask.select(self.$m(rhs), src) }
1280            #[inline(always)] fn [<$m _z>](self, mask: Self::Mask, rhs: Self) -> Self { mask.select(self.$m(rhs), Self::ZERO) }
1281        )*
1282    }};
1283}
1284
1285// =====================================================================================
1286// NumericVector
1287// =====================================================================================
1288
1289#[rustfmt::skip]
1290impl<V: DualFloatVector, const N: usize> NumericVector for Dual<V, N> {
1291    // The integer conversions are real/value-only in both directions: an integer has no
1292    // derivative, no imaginary part and no error term, so converting one in yields a
1293    // constant, and converting out is the value part alone.
1294    #[inline(always)]
1295    fn to_signed_integer(self) -> Self::Signed {
1296        self.re.to_signed_integer()
1297    }
1298
1299    #[inline(always)]
1300    fn from_signed_integer(v: Self::Signed) -> Self {
1301        Self::constant(V::from_signed_integer(v))
1302    }
1303
1304    #[inline(always)]
1305    fn to_unsigned_integer(self) -> Self::Unsigned {
1306        self.re.to_unsigned_integer()
1307    }
1308
1309    #[inline(always)]
1310    fn from_unsigned_integer(v: Self::Unsigned) -> Self {
1311        Self::constant(V::from_unsigned_integer(v))
1312    }
1313
1314    const ZERO: Self = <Self as crate::DualValue>::VAL_ZERO;
1315    const ONE: Self = <Self as crate::DualValue>::VAL_ONE;
1316    const TWO: Self = Self::constant(V::TWO);
1317    const MIN: Self = Self::constant(V::MIN);
1318    const MAX: Self = Self::constant(V::MAX);
1319
1320    #[inline(always)] fn is_zero(self) -> Self::Mask { self.re.is_zero() }
1321    #[inline(always)] fn is_all_zero(self) -> bool { self.re.is_all_zero() }
1322
1323    #[inline(always)] fn min(self, other: Self) -> Self { self.cmp_lt(other).select(self, other) }
1324    #[inline(always)] fn max(self, other: Self) -> Self { self.cmp_gt(other).select(self, other) }
1325
1326    // Lane sorts are keyed on the PRIMAL alone: each compare-exchange derives its
1327    // routing mask from `re` and moves every derivative component through the same
1328    // permutation and select (`thermite::sort::sort_lanes_by_key`), so a sorted dual
1329    // is the dual of the sorted inputs. Widths past the network ladder take the
1330    // scalar walk (whose composite `PartialOrd` tie-breaks by derivatives - ties by
1331    // key are unspecified order either way).
1332    #[inline(always)]
1333    fn sort_by<O: thermite::sort::SortOrder>(self) -> Self {
1334        if const { Self::LANES <= 16 && Self::LANES.is_power_of_two() } {
1335            thermite::sort::sort_lanes_by_key::<Self, O, Self>(self)
1336        } else {
1337            sort_lanes_scalar::<Self, O>(self)
1338        }
1339    }
1340
1341    #[inline(always)]
1342    fn bitonic_clean_by<O: thermite::sort::SortOrder>(self) -> Self {
1343        if const { Self::LANES <= 16 && Self::LANES.is_power_of_two() } {
1344            thermite::sort::bitonic_clean_lanes_by_key::<Self, O, Self>(self)
1345        } else {
1346            // A full sort trivially cleans a bitonic input.
1347            sort_lanes_scalar::<Self, O>(self)
1348        }
1349    }
1350
1351    // Compare the primal against both bounds once, then select per component, rather than
1352    // `self.max(min).min(max)` which builds an intermediate `max` dual and recompares it.
1353    #[inline(always)]
1354    fn clamp(self, min: Self, max: Self) -> Self {
1355        let is_lt = self.re.cmp_lt(min.re);
1356        let is_gt = self.re.cmp_gt(max.re);
1357
1358        let re = is_lt.select(min.re, is_gt.select(max.re, self.re));
1359        let mut dual = self.dual;
1360        let mut i = 0;
1361        while i < N {
1362            dual[i] = is_lt.select(min.dual[i], is_gt.select(max.dual[i], self.dual[i]));
1363            i += 1;
1364        }
1365        Self { re, dual }
1366    }
1367
1368    // Ordering of a dual is by its primal, so let the inner vector's SIMD
1369    // arg_minmax locate the winning lanes, then extract that lane's primal and
1370    // derivative components -- never a scalar per-lane comparison.
1371    #[inline(always)]
1372    fn min_element(self) -> Self::Element {
1373        let (lo, _) = self.re.arg_minmax();
1374        self.extractv(lo)
1375    }
1376
1377    #[inline(always)]
1378    fn max_element(self) -> Self::Element {
1379        let (_, hi) = self.re.arg_minmax();
1380        self.extractv(hi)
1381    }
1382
1383    // One arg_minmax for both ends.
1384    #[inline(always)]
1385    fn min_max_element(self) -> (Self::Element, Self::Element) {
1386        let (lo, hi) = self.re.arg_minmax();
1387        (self.extractv(lo), self.extractv(hi))
1388    }
1389
1390    // Sum is linear, so it commutes with the value/derivative split: reduce each
1391    // component with the inner vector's native horizontal sum rather than
1392    // extracting and folding `LANES` scalar duals.
1393    #[inline(always)]
1394    fn sum_elements(self) -> Self::Element {
1395        let mut dual = [<V::Element as Element>::ZERO; N];
1396        let mut j = 0;
1397        while j < N {
1398            dual[j] = self.dual[j].sum_elements();
1399            j += 1;
1400        }
1401        Dual { re: self.re.sum_elements(), dual }
1402    }
1403
1404    // Same linearity as `sum_elements`: scanning each component with the inner
1405    // vector's own prefix sum is the scan of the duals.
1406    #[inline(always)]
1407    fn prefix_sum(self) -> Self {
1408        Self {
1409            re: self.re.prefix_sum(),
1410            dual: array_each!([V::ZERO; N], |j| self.dual[j].prefix_sum()),
1411        }
1412    }
1413
1414    #[inline(always)]
1415    fn reverse_prefix_sum(self) -> Self {
1416        Self {
1417            re: self.re.reverse_prefix_sum(),
1418            dual: array_each!([V::ZERO; N], |j| self.dual[j].reverse_prefix_sum()),
1419        }
1420    }
1421
1422    // min/max are *not* componentwise: a dual is ordered by its primal and the
1423    // derivative of the winner comes with it, so scanning `re` and `dual` separately
1424    // would pair a primal from one lane with a derivative from another. Run the
1425    // ladder over whole duals instead, on `Self::min`/`Self::max` above.
1426    #[inline(always)]
1427    fn prefix_min(self) -> Self {
1428        thermite::scan_ladder!(forward, self, self.broadcast::<0>(), Self::min)
1429    }
1430
1431    #[inline(always)]
1432    fn prefix_max(self) -> Self {
1433        thermite::scan_ladder!(forward, self, self.broadcast::<0>(), Self::max)
1434    }
1435
1436    #[inline(always)]
1437    fn reverse_prefix_min(self) -> Self {
1438        thermite::scan_ladder!(reverse, self, self.reverse().broadcast::<0>(), Self::min)
1439    }
1440
1441    #[inline(always)]
1442    fn reverse_prefix_max(self) -> Self {
1443        thermite::scan_ladder!(reverse, self, self.reverse().broadcast::<0>(), Self::max)
1444    }
1445
1446    // Product is *not* linear (the per-lane derivatives cross-multiply), so it
1447    // needs real dual multiplications across lanes. A log-depth tree reduction
1448    // shortens the dependency chain versus a sequential fold.
1449    #[inline(always)]
1450    fn prod_elements(self) -> Self::Element {
1451        let mut arr = self.into_array();
1452        reduce_in_place(&mut arr, |a, b| a * b);
1453        arr[0]
1454    }
1455
1456    #[inline(always)] fn offset() -> Self { Self::constant(V::offset()) }
1457    #[inline(always)] fn indexed() -> Self { Self::constant(V::indexed()) }
1458
1459    #[inline(always)] fn arg_minmax(self) -> (usize, usize) { self.re.arg_minmax() }
1460
1461    // Product rule by a (possibly-dual) scalar, splatting the scalar components directly into
1462    // the inner ops rather than building an intermediate splatted `Dual` and going through `Mul`
1463    // (which a width-1 / GPU backend may not optimize away).
1464    #[inline(always)]
1465    fn scale(self, factor: Self::Element) -> Self {
1466        let fr = V::splat(factor.re);
1467        let mut dual = self.dual;
1468        let mut i = 0;
1469        while i < N {
1470            // re*factor.dual + self.dual*factor.re
1471            dual[i] = self.re.mul_adde(V::splat(factor.dual[i]), self.dual[i] * fr);
1472            i += 1;
1473        }
1474        Self { re: self.re * fr, dual }
1475    }
1476
1477    #[inline(always)] fn scale_c(self, mask: Self::Mask, factor: Self::Element) -> Self { mask.select(self.scale(factor), self) }
1478    #[inline(always)] fn scale_m(self, src: Self, mask: Self::Mask, factor: Self::Element) -> Self { mask.select(self.scale(factor), src) }
1479    #[inline(always)] fn scale_z(self, mask: Self::Mask, factor: Self::Element) -> Self { mask.select(self.scale(factor), Self::ZERO) }
1480    dual_masked!(binary: min, max);
1481
1482    // pairwise_sum is a linear rearrange-and-add, so the derivative is the
1483    // pairwise_sum of the corresponding component parts.
1484    #[inline(always)]
1485    fn pairwise_sum(lo: Self, hi: Self) -> Self {
1486        let mut dual = lo.dual;
1487        let mut i = 0;
1488        while i < N {
1489            dual[i] = V::pairwise_sum(lo.dual[i], hi.dual[i]);
1490            i += 1;
1491        }
1492        Self { re: V::pairwise_sum(lo.re, hi.re), dual }
1493    }
1494
1495    #[inline(always)]
1496    fn relaxed_pairwise_sum(lo: Self, hi: Self) -> Self {
1497        let mut dual = lo.dual;
1498        let mut i = 0;
1499        while i < N {
1500            dual[i] = V::relaxed_pairwise_sum(lo.dual[i], hi.dual[i]);
1501            i += 1;
1502        }
1503        Self { re: V::relaxed_pairwise_sum(lo.re, hi.re), dual }
1504    }
1505}
1506
1507// =====================================================================================
1508// SignedVector
1509// =====================================================================================
1510
1511impl<V: DualFloatVector, const N: usize> NegMasked<V::Mask> for Dual<V, N> {
1512    // Blend the negation per component in a single loop, rather than `select(-self, src)` which
1513    // first builds the whole negated `Dual` (one loop) and then blends it (another loop).
1514    #[inline(always)]
1515    fn neg_c(self, mask: V::Mask) -> Self {
1516        let re = mask.select(-self.re, self.re);
1517        let mut dual = self.dual;
1518        let mut i = 0;
1519        while i < N {
1520            dual[i] = mask.select(-dual[i], dual[i]);
1521            i += 1;
1522        }
1523        Self { re, dual }
1524    }
1525
1526    #[inline(always)]
1527    fn neg_m(self, src: Self, mask: V::Mask) -> Self {
1528        let re = mask.select(-self.re, src.re);
1529        let mut dual = self.dual;
1530        let mut i = 0;
1531        while i < N {
1532            dual[i] = mask.select(-dual[i], src.dual[i]);
1533            i += 1;
1534        }
1535        Self { re, dual }
1536    }
1537
1538    #[inline(always)]
1539    fn neg_z(self, mask: V::Mask) -> Self {
1540        let re = mask.select(-self.re, V::ZERO);
1541        let mut dual = self.dual;
1542        let mut i = 0;
1543        while i < N {
1544            dual[i] = mask.select(-dual[i], V::ZERO);
1545            i += 1;
1546        }
1547        Self { re, dual }
1548    }
1549}
1550
1551#[rustfmt::skip]
1552impl<V: DualFloatVector, const N: usize> SignedVector for Dual<V, N> {
1553    const NEG_ONE: Self = Self::constant(V::NEG_ONE);
1554    const MIN_POSITIVE: Self = Self::constant(V::MIN_POSITIVE);
1555
1556    #[inline(always)]
1557    fn abs(self) -> Self {
1558        // |x|' = sign(x) * x'
1559        self.neg_c(self.re.cmp_lt(V::ZERO))
1560    }
1561
1562    #[inline(always)] fn signum(self) -> Self { Self::constant(self.re.signum()) }
1563    #[inline(always)] fn is_positive(self) -> Self::Mask { self.re.is_positive() }
1564    #[inline(always)] fn is_negative(self) -> Self::Mask { self.re.is_negative() }
1565    #[inline(always)] fn select_negative(self, if_neg: Self, if_pos: Self) -> Self { self.is_negative().select(if_neg, if_pos) }
1566
1567    #[inline(always)]
1568    fn copysign(self, sign: Self) -> Self {
1569        self.neg_c(self.is_negative() ^ sign.is_negative())
1570    }
1571
1572    dual_masked!(unary: abs);
1573    dual_masked!(binary: copysign);
1574}
1575
1576// =====================================================================================
1577// FloatVector
1578// =====================================================================================
1579
1580#[rustfmt::skip]
1581impl<V: DualFloatVector, const N: usize> FloatVector for Dual<V, N> {
1582    const HALF: Self = Self::constant(<V as FloatVector>::HALF);
1583    const NEG_ZERO: Self = Self::constant(<V as FloatVector>::NEG_ZERO);
1584    const INFINITY: Self = Self::constant(<V as FloatVector>::INFINITY);
1585    const NEG_INFINITY: Self = Self::constant(<V as FloatVector>::NEG_INFINITY);
1586    const NAN: Self = Self::constant(<V as FloatVector>::NAN);
1587    const EPSILON: Self = Self::constant(<V as FloatVector>::EPSILON);
1588
1589    type ExtendedPrecision = Self;
1590
1591    // The dual `rcp`/`rsqrt` derivatives are built from the inner primal estimate,
1592    // so they're approximate exactly when the inner vector's are.
1593    const HAS_APPROX_RCP: bool = V::HAS_APPROX_RCP;
1594    const HAS_APPROX_RSQRT: bool = V::HAS_APPROX_RSQRT;
1595
1596    #[inline(always)] fn is_infinite(self) -> Self::Mask { self.re.is_infinite() }
1597    #[inline(always)] fn is_finite(self) -> Self::Mask { self.re.is_finite() }
1598    #[inline(always)] fn is_nan(self) -> Self::Mask { self.re.is_nan() }
1599    #[inline(always)] fn is_zero_or_subnormal(self) -> Self::Mask { self.re.is_zero_or_subnormal() }
1600    #[inline(always)] fn is_normal(self) -> Self::Mask { self.re.is_normal() }
1601    #[inline(always)] fn is_subnormal(self) -> Self::Mask { self.re.is_subnormal() }
1602
1603    #[inline(always)]
1604    fn sqrt(self) -> Self {
1605        let s = self.re.sqrt();
1606        // d/dx sqrt(x) = 1 / (2 sqrt(x))
1607        self.chain(s, V::HALF / s)
1608    }
1609
1610    #[inline(always)]
1611    fn rcp(self) -> Self {
1612        let r = self.re.rcp();
1613        // d/dx (1/x) = -1/x^2
1614        self.chain(r, -(r * r))
1615    }
1616
1617    #[inline(always)]
1618    fn rsqrt(self) -> Self {
1619        let r = self.re.rsqrt();
1620        // d/dx x^(-1/2) = -1/2 x^(-3/2) = -1/2 * rsqrt(x) / x
1621        self.chain(r, (V::HALF * r * r * r).neg())
1622    }
1623
1624    #[inline(always)] fn floor(self) -> Self { Self::constant(self.re.floor()) }
1625    #[inline(always)] fn ceil(self) -> Self { Self::constant(self.re.ceil()) }
1626    #[inline(always)] fn round(self) -> Self { Self::constant(self.re.round()) }
1627    #[inline(always)] fn trunc(self) -> Self { Self::constant(self.re.trunc()) }
1628    // fract(x) = x - trunc(x); derivative 1, so the dual parts pass through unchanged.
1629    // Avoids the full dual subtract (N subtractions of zero) the `self - self.trunc()` form does.
1630    #[inline(always)] fn fract(self) -> Self { Self { re: self.re.fract(), dual: self.dual } }
1631
1632    #[inline(always)]
1633    fn mul_sign(self, sign: Self) -> Self {
1634        Self {
1635            re: self.re.mul_sign(sign.re),
1636            dual: array_each!([V::ZERO; N], |i| self.dual[i].mul_sign(sign.re)),
1637        }
1638    }
1639
1640    #[inline(always)] fn signed_zero(self) -> Self { Self::constant(self.re.signed_zero()) }
1641
1642    #[inline(always)] fn next_up(self) -> Self { Self { re: self.re.next_up(), dual: self.dual } }
1643    #[inline(always)] fn next_down(self) -> Self { Self { re: self.re.next_down(), dual: self.dual } }
1644
1645    #[inline(always)] unsafe fn block_autovectorization(&mut self) {
1646        unsafe {
1647            self.re.block_autovectorization();
1648            for i in 0..N {
1649                self.dual[i].block_autovectorization();
1650            }
1651        }
1652    }
1653
1654    // mix(t) = a*(1 - t) + b*t = a + (b - a)*t, composed through dual arithmetic.
1655    #[inline(always)] fn mix(self, a: Self, b: Self) -> Self { a + (b - a) * self }
1656
1657    dual_masked!(unary: sqrt, rsqrt, rcp, floor, ceil, round, trunc, fract, signed_zero, next_up, next_down);
1658    dual_masked!(binary: mul_sign);
1659}