Skip to main content

copp\path/
autodiff.rs

1//! Third-order forward-mode automatic differentiation primitives.
2//!
3//! `Jet3` carries value and derivatives up to the 3rd order with respect
4//! to a single scalar seed variable, and supports common arithmetic plus
5//! elementary functions (`sin`, `cos`, `exp`, `ln`, `sqrt`, `powi`).
6
7use std::ops::{Add, Div, Mul, Neg, Sub};
8
9/// Third-order forward-mode automatic differentiation scalar.
10#[derive(Clone, Copy, Debug, Default)]
11pub struct Jet3 {
12    pub v: f64,
13    pub d1: f64,
14    pub d2: f64,
15    pub d3: f64,
16}
17
18impl Jet3 {
19    /// Construct a constant scalar with zero derivatives.
20    #[inline(always)]
21    pub fn constant(v: f64) -> Self {
22        Self {
23            v,
24            d1: 0.0,
25            d2: 0.0,
26            d3: 0.0,
27        }
28    }
29
30    /// Construct an independent variable seed (`d/ds = 1`).
31    #[inline(always)]
32    pub fn seed(v: f64) -> Self {
33        Self {
34            v,
35            d1: 1.0,
36            d2: 0.0,
37            d3: 0.0,
38        }
39    }
40
41    #[inline(always)]
42    pub fn sin(self) -> Self {
43        let sv = self.v.sin();
44        let cv = self.v.cos();
45        let d1sq = self.d1 * self.d1;
46        let d1 = cv * self.d1;
47        let d2 = -sv * d1sq + cv * self.d2;
48        let d3 = -cv * d1sq * self.d1 - 3.0 * sv * self.d1 * self.d2 + cv * self.d3;
49        Self { v: sv, d1, d2, d3 }
50    }
51
52    #[inline(always)]
53    pub fn cos(self) -> Self {
54        let sv = self.v.sin();
55        let cv = self.v.cos();
56        let d1sq = self.d1 * self.d1;
57        let d1 = -sv * self.d1;
58        let d2 = -cv * d1sq - sv * self.d2;
59        let d3 = sv * d1sq * self.d1 - 3.0 * cv * self.d1 * self.d2 - sv * self.d3;
60        Self { v: cv, d1, d2, d3 }
61    }
62
63    #[inline(always)]
64    pub fn exp(self) -> Self {
65        let ev = self.v.exp();
66        let d1sq = self.d1 * self.d1;
67        let d1 = ev * self.d1;
68        let d2 = ev * (d1sq + self.d2);
69        let d3 = ev * (d1sq * self.d1 + 3.0 * self.d1 * self.d2 + self.d3);
70        Self { v: ev, d1, d2, d3 }
71    }
72
73    #[inline(always)]
74    pub fn ln(self) -> Self {
75        let v = self.v.ln();
76        let inv_x = 1.0 / self.v;
77        let inv_x2 = inv_x * inv_x;
78        let inv_x3 = inv_x2 * inv_x;
79        let d1sq = self.d1 * self.d1;
80        let d1 = inv_x * self.d1;
81        let d2 = -inv_x2 * d1sq + inv_x * self.d2;
82        let d3 = 2.0 * inv_x3 * d1sq * self.d1 - 3.0 * inv_x2 * self.d1 * self.d2 + inv_x * self.d3;
83        Self { v, d1, d2, d3 }
84    }
85
86    #[inline(always)]
87    pub fn sqrt(self) -> Self {
88        let sqrtv = self.v.sqrt();
89        let inv_sqrt = 1.0 / sqrtv;
90        let inv_v_sqrt = inv_sqrt / self.v;
91        let inv_v2_sqrt = inv_v_sqrt / self.v;
92        let d1sq = self.d1 * self.d1;
93        let d1 = 0.5 * inv_sqrt * self.d1;
94        let d2 = -0.25 * inv_v_sqrt * d1sq + 0.5 * inv_sqrt * self.d2;
95        let d3 = 0.375 * inv_v2_sqrt * d1sq * self.d1 - 0.75 * inv_v_sqrt * self.d1 * self.d2
96            + 0.5 * inv_sqrt * self.d3;
97        Self {
98            v: sqrtv,
99            d1,
100            d2,
101            d3,
102        }
103    }
104
105    #[inline(always)]
106    pub fn powi(self, n: i32) -> Self {
107        if n == 0 {
108            return Self::constant(1.0);
109        }
110        let nf = n as f64;
111        // Compute v^(n-3) once; derive v^(n-2), v^(n-1), v^n by repeated multiplication.
112        // This replaces 4 independent `f64::powi` calls with 1 call + 3 multiplications,
113        // avoiding redundant repeated-squaring work for the same base.
114        let vn3 = self.v.powi(n - 3);
115        let vn2 = vn3 * self.v;
116        let vn1 = vn2 * self.v;
117        let vn = vn1 * self.v;
118        let dv = nf * vn1;
119        let ddv = nf * (nf - 1.0) * vn2;
120        let dddv = nf * (nf - 1.0) * (nf - 2.0) * vn3;
121        let d1sq = self.d1 * self.d1;
122        let d1 = dv * self.d1;
123        let d2 = ddv * d1sq + dv * self.d2;
124        let d3 = dddv * d1sq * self.d1 + 3.0 * ddv * self.d1 * self.d2 + dv * self.d3;
125        Self { v: vn, d1, d2, d3 }
126    }
127
128    #[inline(always)]
129    fn inv(self) -> Self {
130        let v = 1.0 / self.v;
131        let inv_x2 = v * v;
132        let inv_x3 = inv_x2 * v;
133        let inv_x4 = inv_x3 * v;
134        let d1sq = self.d1 * self.d1;
135        let d1 = -inv_x2 * self.d1;
136        let d2 = 2.0 * inv_x3 * d1sq - inv_x2 * self.d2;
137        let d3 =
138            -6.0 * inv_x4 * d1sq * self.d1 + 6.0 * inv_x3 * self.d1 * self.d2 - inv_x2 * self.d3;
139        Self { v, d1, d2, d3 }
140    }
141}
142
143#[inline(always)]
144pub fn sin(x: Jet3) -> Jet3 {
145    x.sin()
146}
147
148#[inline(always)]
149pub fn cos(x: Jet3) -> Jet3 {
150    x.cos()
151}
152
153#[inline(always)]
154pub fn exp(x: Jet3) -> Jet3 {
155    x.exp()
156}
157
158#[inline(always)]
159pub fn ln(x: Jet3) -> Jet3 {
160    x.ln()
161}
162
163#[inline(always)]
164pub fn sqrt(x: Jet3) -> Jet3 {
165    x.sqrt()
166}
167
168#[inline(always)]
169pub fn powi(x: Jet3, n: i32) -> Jet3 {
170    x.powi(n)
171}
172
173impl From<f64> for Jet3 {
174    #[inline(always)]
175    fn from(value: f64) -> Self {
176        Self::constant(value)
177    }
178}
179
180impl Add for Jet3 {
181    type Output = Self;
182
183    #[inline(always)]
184    fn add(self, rhs: Self) -> Self::Output {
185        Self {
186            v: self.v + rhs.v,
187            d1: self.d1 + rhs.d1,
188            d2: self.d2 + rhs.d2,
189            d3: self.d3 + rhs.d3,
190        }
191    }
192}
193
194impl Add<f64> for Jet3 {
195    type Output = Self;
196
197    #[inline(always)]
198    fn add(self, rhs: f64) -> Self::Output {
199        Self {
200            v: self.v + rhs,
201            ..self
202        }
203    }
204}
205
206impl Add<Jet3> for f64 {
207    type Output = Jet3;
208
209    #[inline(always)]
210    fn add(self, rhs: Jet3) -> Self::Output {
211        rhs + self
212    }
213}
214
215impl Sub for Jet3 {
216    type Output = Self;
217
218    #[inline(always)]
219    fn sub(self, rhs: Self) -> Self::Output {
220        Self {
221            v: self.v - rhs.v,
222            d1: self.d1 - rhs.d1,
223            d2: self.d2 - rhs.d2,
224            d3: self.d3 - rhs.d3,
225        }
226    }
227}
228
229impl Sub<f64> for Jet3 {
230    type Output = Self;
231
232    #[inline(always)]
233    fn sub(self, rhs: f64) -> Self::Output {
234        Self {
235            v: self.v - rhs,
236            ..self
237        }
238    }
239}
240
241impl Sub<Jet3> for f64 {
242    type Output = Jet3;
243
244    #[inline(always)]
245    fn sub(self, rhs: Jet3) -> Self::Output {
246        Jet3::constant(self) - rhs
247    }
248}
249
250impl Mul for Jet3 {
251    type Output = Self;
252
253    #[inline(always)]
254    fn mul(self, rhs: Self) -> Self::Output {
255        Self {
256            v: self.v * rhs.v,
257            d1: self.d1 * rhs.v + self.v * rhs.d1,
258            d2: self.d2 * rhs.v + 2.0 * self.d1 * rhs.d1 + self.v * rhs.d2,
259            d3: self.d3 * rhs.v + 3.0 * (self.d2 * rhs.d1 + self.d1 * rhs.d2) + self.v * rhs.d3,
260        }
261    }
262}
263
264impl Mul<f64> for Jet3 {
265    type Output = Self;
266
267    #[inline(always)]
268    fn mul(self, rhs: f64) -> Self::Output {
269        Self {
270            v: self.v * rhs,
271            d1: self.d1 * rhs,
272            d2: self.d2 * rhs,
273            d3: self.d3 * rhs,
274        }
275    }
276}
277
278impl Mul<Jet3> for f64 {
279    type Output = Jet3;
280
281    #[inline(always)]
282    fn mul(self, rhs: Jet3) -> Self::Output {
283        rhs * self
284    }
285}
286
287impl Div for Jet3 {
288    type Output = Self;
289
290    #[allow(clippy::suspicious_arithmetic_impl)]
291    #[inline(always)]
292    fn div(self, rhs: Self) -> Self::Output {
293        self * rhs.inv()
294    }
295}
296
297impl Div<f64> for Jet3 {
298    type Output = Self;
299
300    #[inline(always)]
301    fn div(self, rhs: f64) -> Self::Output {
302        self * (1.0 / rhs)
303    }
304}
305
306impl Div<Jet3> for f64 {
307    type Output = Jet3;
308
309    #[inline(always)]
310    fn div(self, rhs: Jet3) -> Self::Output {
311        Jet3::constant(self) / rhs
312    }
313}
314
315impl Neg for Jet3 {
316    type Output = Self;
317
318    #[inline(always)]
319    fn neg(self) -> Self::Output {
320        Self {
321            v: -self.v,
322            d1: -self.d1,
323            d2: -self.d2,
324            d3: -self.d3,
325        }
326    }
327}