Skip to main content

num_dual/datatypes/
derivative.rs

1use crate::DualNum;
2use nalgebra::allocator::Allocator;
3use nalgebra::constraint::{SameNumberOfRows, ShapeConstraint};
4use nalgebra::*;
5use num_traits::Zero;
6use std::fmt;
7use std::mem::MaybeUninit;
8use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
9
10/// Wrapper struct for a derivative vector or matrix.
11#[derive(PartialEq, Eq, Clone, Debug)]
12pub struct Derivative<T: Scalar, R: Dim, C: Dim>(pub Option<OMatrix<T, R, C>>)
13where
14    DefaultAllocator: Allocator<R, C>;
15
16impl<T: Scalar + Copy, const R: usize, const C: usize> Copy for Derivative<T, Const<R>, Const<C>> {}
17
18impl<T: DualNum, R: Dim, C: Dim> Derivative<T, R, C>
19where
20    DefaultAllocator: Allocator<R, C>,
21{
22    pub fn new(derivative: Option<OMatrix<T, R, C>>) -> Self {
23        Self(derivative)
24    }
25
26    pub fn some(derivative: OMatrix<T, R, C>) -> Self {
27        Self::new(Some(derivative))
28    }
29
30    pub fn none() -> Self {
31        Self::new(None)
32    }
33
34    pub(crate) fn map<T2>(&self, f: impl FnMut(T) -> T2) -> Derivative<T2, R, C>
35    where
36        T2: DualNum,
37        DefaultAllocator: Allocator<R, C>,
38    {
39        let opt = self.0.as_ref().map(|eps| eps.map(f));
40        Derivative::new(opt)
41    }
42
43    // A version of map that doesn't clone values before mapping. Useful for the SimdValue impl,
44    // which would be redundantly cloning all the lanes of each epsilon value before extracting
45    // just one of them.
46    //
47    // To implement, we inline a copy of Matrix::map, which implicitly clones values, and remove
48    // the cloning.
49    pub(crate) fn map_borrowed<T2>(&self, mut f: impl FnMut(&T) -> T2) -> Derivative<T2, R, C>
50    where
51        T2: DualNum,
52        DefaultAllocator: Allocator<R, C>,
53    {
54        let opt = self.0.as_ref().map(move |eps| {
55            let (nrows, ncols) = eps.shape_generic();
56            let mut res: Matrix<MaybeUninit<T2>, R, C, _> = Matrix::uninit(nrows, ncols);
57
58            for j in 0..ncols.value() {
59                for i in 0..nrows.value() {
60                    // Safety: all indices are in range.
61                    unsafe {
62                        let a = eps.data.get_unchecked(i, j);
63                        *res.data.get_unchecked_mut(i, j) = MaybeUninit::new(f(a));
64                    }
65                }
66            }
67
68            // Safety: res is now fully initialized.
69            unsafe { res.assume_init() }
70        });
71        Derivative::new(opt)
72    }
73
74    /// Same but bails out if the closure returns None
75    pub(crate) fn try_map_borrowed<T2>(
76        &self,
77        mut f: impl FnMut(&T) -> Option<T2>,
78    ) -> Option<Derivative<T2, R, C>>
79    where
80        T2: DualNum,
81        DefaultAllocator: Allocator<R, C>,
82    {
83        self.0
84            .as_ref()
85            .and_then(move |eps| {
86                let (nrows, ncols) = eps.shape_generic();
87                let mut res: Matrix<MaybeUninit<T2>, R, C, _> = Matrix::uninit(nrows, ncols);
88
89                for j in 0..ncols.value() {
90                    for i in 0..nrows.value() {
91                        // Safety: all indices are in range.
92                        unsafe {
93                            let a = eps.data.get_unchecked(i, j);
94                            *res.data.get_unchecked_mut(i, j) = MaybeUninit::new(f(a)?);
95                        }
96                    }
97                }
98
99                // Safety: res is now fully initialized.
100                Some(unsafe { res.assume_init() })
101            })
102            .map(Derivative::some)
103    }
104
105    pub fn derivative_generic(r: R, c: C, i: usize) -> Self {
106        let mut m = OMatrix::zeros_generic(r, c);
107        m[i] = T::one();
108        Self::some(m)
109    }
110
111    pub fn unwrap_generic(self, r: R, c: C) -> OMatrix<T, R, C> {
112        self.0.unwrap_or_else(|| OMatrix::zeros_generic(r, c))
113    }
114
115    pub fn fmt(&self, f: &mut fmt::Formatter, symbol: &str) -> fmt::Result {
116        if let Some(m) = self.0.as_ref() {
117            write!(f, " + ")?;
118            match m.shape() {
119                (1, 1) => write!(f, "{}", m[0])?,
120                (1, _) | (_, 1) => {
121                    let x: Vec<_> = m.iter().map(T::to_string).collect();
122                    write!(f, "[{}]", x.join(", "))?
123                }
124                (_, _) => write!(f, "{m}")?,
125            };
126            write!(f, "{symbol}")?;
127        }
128        write!(f, "")
129    }
130}
131
132impl<T: DualNum> Derivative<T, U1, U1> {
133    #[expect(clippy::self_named_constructors)]
134    pub fn derivative() -> Self {
135        Self::some(SVector::identity())
136    }
137
138    pub fn unwrap(self) -> T {
139        self.0.map_or_else(
140            || T::zero(),
141            |s| {
142                let [[r]] = s.data.0;
143                r
144            },
145        )
146    }
147}
148
149impl<T: DualNum, R: Dim, C: Dim> Mul<T> for Derivative<T, R, C>
150where
151    DefaultAllocator: Allocator<R, C>,
152{
153    type Output = Self;
154
155    fn mul(self, rhs: T) -> Self::Output {
156        Derivative::new(self.0.map(|x| x * rhs))
157    }
158}
159
160impl<T: DualNum, R: Dim, C: Dim> Mul<T> for &Derivative<T, R, C>
161where
162    DefaultAllocator: Allocator<R, C>,
163{
164    type Output = Derivative<T, R, C>;
165
166    fn mul(self, rhs: T) -> Self::Output {
167        Derivative::new(self.0.as_ref().map(|x| x * rhs))
168    }
169}
170
171impl<T: DualNum, R: Dim, C: Dim, R2: Dim, C2: Dim> Mul<&Derivative<T, R2, C2>>
172    for &Derivative<T, R, C>
173where
174    DefaultAllocator: Allocator<R, C> + Allocator<R2, C2> + Allocator<R, C2>,
175    ShapeConstraint: SameNumberOfRows<C, R2>,
176{
177    type Output = Derivative<T, R, C2>;
178
179    fn mul(self, rhs: &Derivative<T, R2, C2>) -> Derivative<T, R, C2> {
180        Derivative::new(self.0.as_ref().zip(rhs.0.as_ref()).map(|(s, r)| s * r))
181    }
182}
183
184impl<T: DualNum, R: Dim, C: Dim> Div<T> for Derivative<T, R, C>
185where
186    DefaultAllocator: Allocator<R, C>,
187{
188    type Output = Self;
189
190    fn div(self, rhs: T) -> Self::Output {
191        Derivative::new(self.0.map(|x| x / rhs))
192    }
193}
194
195impl<T: DualNum, R: Dim, C: Dim> Div<T> for &Derivative<T, R, C>
196where
197    DefaultAllocator: Allocator<R, C>,
198{
199    type Output = Derivative<T, R, C>;
200
201    fn div(self, rhs: T) -> Self::Output {
202        Derivative::new(self.0.as_ref().map(|x| x / rhs))
203    }
204}
205
206impl<T: DualNum, R: Dim, C: Dim> Derivative<T, R, C>
207where
208    DefaultAllocator: Allocator<R, C>,
209{
210    pub fn tr_mul<R2: Dim, C2: Dim>(&self, rhs: &Derivative<T, R2, C2>) -> Derivative<T, C, C2>
211    where
212        DefaultAllocator: Allocator<R2, C2> + Allocator<C, C2>,
213        ShapeConstraint: SameNumberOfRows<R, R2>,
214    {
215        Derivative::new(
216            self.0
217                .as_ref()
218                .zip(rhs.0.as_ref())
219                .map(|(s, r)| s.tr_mul(r)),
220        )
221    }
222}
223
224impl<T: DualNum, R: Dim, C: Dim> Add for Derivative<T, R, C>
225where
226    DefaultAllocator: Allocator<R, C>,
227{
228    type Output = Self;
229
230    fn add(self, rhs: Self) -> Self::Output {
231        Self::new(match (self.0, rhs.0) {
232            (Some(s), Some(r)) => Some(s + r),
233            (Some(s), None) => Some(s),
234            (None, Some(r)) => Some(r),
235            (None, None) => None,
236        })
237    }
238}
239
240impl<T: DualNum, R: Dim, C: Dim> Add<&Derivative<T, R, C>> for Derivative<T, R, C>
241where
242    DefaultAllocator: Allocator<R, C>,
243{
244    type Output = Derivative<T, R, C>;
245
246    fn add(self, rhs: &Derivative<T, R, C>) -> Self::Output {
247        Derivative::new(match (&self.0, &rhs.0) {
248            (Some(s), Some(r)) => Some(s + r),
249            (Some(s), None) => Some(s.clone()),
250            (None, Some(r)) => Some(r.clone()),
251            (None, None) => None,
252        })
253    }
254}
255
256impl<T: DualNum, R: Dim, C: Dim> Add for &Derivative<T, R, C>
257where
258    DefaultAllocator: Allocator<R, C>,
259{
260    type Output = Derivative<T, R, C>;
261
262    fn add(self, rhs: Self) -> Self::Output {
263        Derivative::new(match (&self.0, &rhs.0) {
264            (Some(s), Some(r)) => Some(s + r),
265            (Some(s), None) => Some(s.clone()),
266            (None, Some(r)) => Some(r.clone()),
267            (None, None) => None,
268        })
269    }
270}
271
272impl<T: DualNum, R: Dim, C: Dim> Sub for Derivative<T, R, C>
273where
274    DefaultAllocator: Allocator<R, C>,
275{
276    type Output = Self;
277
278    fn sub(self, rhs: Self) -> Self::Output {
279        Self::new(match (self.0, rhs.0) {
280            (Some(s), Some(r)) => Some(s - r),
281            (Some(s), None) => Some(s),
282            (None, Some(r)) => Some(-r),
283            (None, None) => None,
284        })
285    }
286}
287
288impl<T: DualNum, R: Dim, C: Dim> Sub<&Derivative<T, R, C>> for Derivative<T, R, C>
289where
290    DefaultAllocator: Allocator<R, C>,
291{
292    type Output = Derivative<T, R, C>;
293
294    fn sub(self, rhs: &Derivative<T, R, C>) -> Self::Output {
295        Derivative::new(match (&self.0, &rhs.0) {
296            (Some(s), Some(r)) => Some(s - r),
297            (Some(s), None) => Some(s.clone()),
298            (None, Some(r)) => Some(-r.clone()),
299            (None, None) => None,
300        })
301    }
302}
303
304impl<T: DualNum, R: Dim, C: Dim> Sub for &Derivative<T, R, C>
305where
306    DefaultAllocator: Allocator<R, C>,
307{
308    type Output = Derivative<T, R, C>;
309
310    fn sub(self, rhs: Self) -> Self::Output {
311        Derivative::new(match (&self.0, &rhs.0) {
312            (Some(s), Some(r)) => Some(s - r),
313            (Some(s), None) => Some(s.clone()),
314            (None, Some(r)) => Some(-r),
315            (None, None) => None,
316        })
317    }
318}
319
320impl<T: DualNum, R: Dim, C: Dim> Neg for &Derivative<T, R, C>
321where
322    DefaultAllocator: Allocator<R, C>,
323{
324    type Output = Derivative<T, R, C>;
325
326    fn neg(self) -> Self::Output {
327        Derivative::new(self.0.as_ref().map(|x| -x))
328    }
329}
330
331impl<T: DualNum, R: Dim, C: Dim> Neg for Derivative<T, R, C>
332where
333    DefaultAllocator: Allocator<R, C>,
334{
335    type Output = Self;
336
337    fn neg(self) -> Self::Output {
338        Derivative::new(self.0.map(|x| -x))
339    }
340}
341
342impl<T: DualNum, R: Dim, C: Dim> AddAssign for Derivative<T, R, C>
343where
344    DefaultAllocator: Allocator<R, C>,
345{
346    fn add_assign(&mut self, rhs: Self) {
347        match (&mut self.0, rhs.0) {
348            (Some(s), Some(r)) => *s += &r,
349            (None, Some(r)) => self.0 = Some(r),
350            (_, None) => (),
351        };
352    }
353}
354
355impl<T: DualNum, R: Dim, C: Dim> SubAssign for Derivative<T, R, C>
356where
357    DefaultAllocator: Allocator<R, C>,
358{
359    fn sub_assign(&mut self, rhs: Self) {
360        match (&mut self.0, rhs.0) {
361            (Some(s), Some(r)) => *s -= &r,
362            (None, Some(r)) => self.0 = Some(-&r),
363            (_, None) => (),
364        };
365    }
366}
367
368impl<T: DualNum, R: Dim, C: Dim> MulAssign<T> for Derivative<T, R, C>
369where
370    DefaultAllocator: Allocator<R, C>,
371{
372    fn mul_assign(&mut self, rhs: T) {
373        if let Some(s) = &mut self.0 {
374            *s *= rhs
375        }
376    }
377}
378
379impl<T: DualNum, R: Dim, C: Dim> DivAssign<T> for Derivative<T, R, C>
380where
381    DefaultAllocator: Allocator<R, C>,
382{
383    fn div_assign(&mut self, rhs: T) {
384        if let Some(s) = &mut self.0 {
385            *s /= rhs
386        }
387    }
388}
389
390impl<T, R: Dim, C: Dim> nalgebra::SimdValue for Derivative<T, R, C>
391where
392    DefaultAllocator: Allocator<R, C>,
393    T: DualNum<Primitive = T::Element> + SimdValue + Scalar,
394    T::Element: DualNum<Primitive = T::Element> + Scalar + Zero,
395{
396    type Element = Derivative<T::Element, R, C>;
397
398    type SimdBool = T::SimdBool;
399
400    const LANES: usize = T::LANES;
401
402    #[inline]
403    fn splat(val: Self::Element) -> Self {
404        val.map(|e| T::splat(e))
405    }
406
407    #[inline]
408    fn extract(&self, i: usize) -> Self::Element {
409        self.map_borrowed(|e| T::extract(e, i))
410    }
411
412    #[inline]
413    unsafe fn extract_unchecked(&self, i: usize) -> Self::Element {
414        let opt = self
415            .map_borrowed(|e| unsafe { T::extract_unchecked(e, i) })
416            .0
417            // Now check it's all zeros.
418            // Unfortunately there is no way to use the vectorized version of `is_zero`, which is
419            // only for matrices with statically known dimensions. Specialization would be
420            // required.
421            .filter(|x| Iterator::any(&mut x.iter(), |e| !e.is_zero()));
422        Derivative::new(opt)
423    }
424
425    // SIMD code will expect to be able to replace one lane with another Self::Element,
426    // even with a None Derivative, e.g.
427    //
428    // let single = Derivative::none();
429    // let mut x4 = Derivative::splat(single);
430    // let one = Derivative::some(...);
431    // x4.replace(1, one);
432    //
433    // So the implementation of `replace` will need to auto-upgrade to Some(zeros) in
434    // order to satisfy requests like that.
435    fn replace(&mut self, i: usize, val: Self::Element) {
436        match (&mut self.0, val.0) {
437            (Some(ours), Some(theirs)) => {
438                ours.zip_apply(&theirs, |e, replacement| e.replace(i, replacement));
439            }
440            (ours @ None, Some(theirs)) => {
441                let (r, c) = theirs.shape_generic();
442                let mut init: OMatrix<T, R, C> = OMatrix::zeros_generic(r, c);
443                init.zip_apply(&theirs, |e, replacement| e.replace(i, replacement));
444                *ours = Some(init);
445            }
446            (Some(ours), None) => {
447                ours.apply(|e| e.replace(i, T::Element::zero()));
448            }
449            _ => {}
450        }
451    }
452
453    unsafe fn replace_unchecked(&mut self, i: usize, val: Self::Element) {
454        match (&mut self.0, val.0) {
455            (Some(ours), Some(theirs)) => {
456                ours.zip_apply(&theirs, |e, replacement| unsafe {
457                    e.replace_unchecked(i, replacement)
458                });
459            }
460            (ours @ None, Some(theirs)) => {
461                let (r, c) = theirs.shape_generic();
462                let mut init: OMatrix<T, R, C> = OMatrix::zeros_generic(r, c);
463                init.zip_apply(&theirs, |e, replacement| unsafe {
464                    e.replace_unchecked(i, replacement)
465                });
466                *ours = Some(init);
467            }
468            (Some(ours), None) => {
469                ours.apply(|e| unsafe { e.replace_unchecked(i, T::Element::zero()) });
470            }
471            _ => {}
472        }
473    }
474
475    fn select(mut self, cond: Self::SimdBool, other: Self) -> Self {
476        // If cond is mixed, then we may need to generate big zero matrices to do the
477        // component-wise select on. So check if cond is all-true or all-first to avoid that.
478        if cond.all() {
479            self
480        } else if cond.none() {
481            other
482        } else {
483            match (&mut self.0, other.0) {
484                (Some(ours), Some(theirs)) => {
485                    ours.zip_apply(&theirs, |e, other_e| {
486                        // this will probably get optimized out
487                        let e_ = std::mem::replace(e, T::zero());
488                        *e = e_.select(cond, other_e)
489                    });
490                    self
491                }
492                (Some(ours), None) => {
493                    ours.apply(|e| {
494                        // this will probably get optimized out
495                        let e_ = std::mem::replace(e, T::zero());
496                        *e = e_.select(cond, T::zero());
497                    });
498                    self
499                }
500                (ours @ None, Some(mut theirs)) => {
501                    use std::ops::Not;
502                    let inverted: T::SimdBool = cond.not();
503                    theirs.apply(|e| {
504                        // this will probably get optimized out
505                        let e_ = std::mem::replace(e, T::zero());
506                        *e = e_.select(inverted, T::zero());
507                    });
508                    *ours = Some(theirs);
509                    self
510                }
511                _ => self,
512            }
513        }
514    }
515}
516
517use simba::scalar::{SubsetOf, SupersetOf};
518
519impl<TSuper, T, R: Dim, C: Dim> SubsetOf<Derivative<TSuper, R, C>> for Derivative<T, R, C>
520where
521    TSuper: DualNum + SupersetOf<T>,
522    T: DualNum,
523    DefaultAllocator: Allocator<R, C>,
524{
525    #[inline(always)]
526    fn to_superset(&self) -> Derivative<TSuper, R, C> {
527        self.map_borrowed(|elem| TSuper::from_subset(elem))
528    }
529    #[inline(always)]
530    fn from_superset(element: &Derivative<TSuper, R, C>) -> Option<Self> {
531        element.try_map_borrowed(|elem| TSuper::to_subset(elem))
532    }
533    #[inline(always)]
534    fn from_superset_unchecked(element: &Derivative<TSuper, R, C>) -> Self {
535        element.map_borrowed(|elem| TSuper::to_subset_unchecked(elem))
536    }
537    #[inline(always)]
538    fn is_in_subset(element: &Derivative<TSuper, R, C>) -> bool {
539        element
540            .0
541            .as_ref()
542            .is_none_or(|matrix| matrix.iter().all(|elem| TSuper::is_in_subset(elem)))
543    }
544}