1#![allow(clippy::suspicious_arithmetic_impl, clippy::indexing_slicing)] use std::{
7 iter::Sum,
8 ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign},
9};
10
11use crate::{Complex, numerics::ratio};
12
13#[derive(Clone, Copy, Debug)]
23pub(crate) struct Jet<const N: usize> {
24 pub(crate) value: Complex,
26 pub(crate) derivative: [Complex; N],
28}
29impl<const N: usize> Jet<N> {
30 pub(crate) fn constant(value: impl Into<Complex>) -> Self {
32 Self {
33 value: value.into(),
34 derivative: [Complex::default(); N],
35 }
36 }
37 pub(crate) fn variable(value: impl Into<Complex>, index: usize) -> Self {
40 let mut result = Self::constant(value);
41 if let Some(d) = result.derivative.get_mut(index) {
42 *d = Complex::new(1.0, 0.0);
43 }
44 result
45 }
46 pub(crate) fn chain(self, value: Complex, derivative: Complex) -> Self {
49 Self {
50 value,
51 derivative: self.derivative.map(|g| g * derivative),
52 }
53 }
54 pub(crate) fn powi(self, n: i32) -> Self {
56 if n == 0 {
57 return Self::constant(1.0);
58 }
59 if N == 0 {
60 return Self::constant(self.value.powi(n));
61 }
62 self.chain(self.value.powi(n), f64::from(n) * self.value.powi(n - 1))
63 }
64 pub(crate) fn exp(self) -> Self {
66 let value = self.value.exp();
67 self.chain(value, value)
68 }
69 pub(crate) fn sqrt(self) -> Self {
71 let root = crate::numerics::complex_sqrt(self.value);
72 self.chain(root, 0.5 / root)
73 }
74 pub(crate) fn norm(self) -> f64 {
76 self.derivative
77 .iter()
78 .map(|g| g.norm())
79 .fold(self.value.norm(), f64::max)
80 }
81 pub(crate) fn finite(self) -> bool {
83 crate::numerics::finite(self.value)
84 && self.derivative.iter().all(|&g| crate::numerics::finite(g))
85 }
86 pub(crate) fn is_zero(self) -> bool {
88 let zero = Complex::default();
89 self.value == zero && self.derivative.iter().all(|&g| g == zero)
90 }
91}
92impl Jet<3> {
93 pub(crate) fn compose<const N: usize>(self, inner: [Jet<N>; 3]) -> Jet<N> {
98 Jet {
99 value: self.value,
100 derivative: std::array::from_fn(|a| {
101 (0..3)
102 .map(|b| self.derivative[b] * inner[b].derivative[a])
103 .sum()
104 }),
105 }
106 }
107}
108impl<const N: usize> Default for Jet<N> {
109 fn default() -> Self {
110 Self::constant(0.0)
111 }
112}
113impl<const N: usize> Add for Jet<N> {
114 type Output = Self;
115 fn add(self, rhs: Self) -> Self {
116 Self {
117 value: self.value + rhs.value,
118 derivative: std::array::from_fn(|i| self.derivative[i] + rhs.derivative[i]),
119 }
120 }
121}
122impl<const N: usize> Sub for Jet<N> {
123 type Output = Self;
124 fn sub(self, rhs: Self) -> Self {
125 Self {
126 value: self.value - rhs.value,
127 derivative: std::array::from_fn(|i| self.derivative[i] - rhs.derivative[i]),
128 }
129 }
130}
131impl<const N: usize> Mul for Jet<N> {
132 type Output = Self;
133 fn mul(self, rhs: Self) -> Self {
134 Self {
135 value: self.value * rhs.value,
136 derivative: std::array::from_fn(|i| {
137 self.derivative[i] * rhs.value + self.value * rhs.derivative[i]
138 }),
139 }
140 }
141}
142impl<const N: usize> Div for Jet<N> {
143 type Output = Self;
144 fn div(self, rhs: Self) -> Self {
145 let value = ratio(self.value, rhs.value);
146 Self {
147 value,
148 derivative: std::array::from_fn(|i| {
149 ratio(self.derivative[i] - value * rhs.derivative[i], rhs.value)
150 }),
151 }
152 }
153}
154impl<const N: usize> Neg for Jet<N> {
155 type Output = Self;
156 fn neg(self) -> Self {
157 Self {
158 value: -self.value,
159 derivative: self.derivative.map(|g| -g),
160 }
161 }
162}
163impl<const N: usize> Sum for Jet<N> {
164 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
165 iter.fold(Self::default(), Add::add)
166 }
167}
168macro_rules! scalar_ops {
173 ($scalar:ty) => {
174 impl<const N: usize> Add<$scalar> for Jet<N> {
175 type Output = Self;
176 fn add(self, rhs: $scalar) -> Self {
177 Self {
178 value: self.value + Complex::from(rhs),
179 derivative: self.derivative,
180 }
181 }
182 }
183 impl<const N: usize> Add<Jet<N>> for $scalar {
184 type Output = Jet<N>;
185 fn add(self, rhs: Jet<N>) -> Jet<N> {
186 Jet {
187 value: Complex::from(self) + rhs.value,
188 derivative: rhs.derivative,
189 }
190 }
191 }
192 impl<const N: usize> Sub<$scalar> for Jet<N> {
193 type Output = Self;
194 fn sub(self, rhs: $scalar) -> Self {
195 Self {
196 value: self.value - Complex::from(rhs),
197 derivative: self.derivative,
198 }
199 }
200 }
201 impl<const N: usize> Sub<Jet<N>> for $scalar {
202 type Output = Jet<N>;
203 fn sub(self, rhs: Jet<N>) -> Jet<N> {
204 Jet {
205 value: Complex::from(self) - rhs.value,
206 derivative: rhs.derivative.map(|g| -g),
207 }
208 }
209 }
210 impl<const N: usize> Mul<$scalar> for Jet<N> {
211 type Output = Self;
212 fn mul(self, rhs: $scalar) -> Self {
213 Self {
214 value: self.value * rhs,
215 derivative: self.derivative.map(|g| g * rhs),
216 }
217 }
218 }
219 impl<const N: usize> Mul<Jet<N>> for $scalar {
220 type Output = Jet<N>;
221 fn mul(self, rhs: Jet<N>) -> Jet<N> {
222 rhs * self
223 }
224 }
225 impl<const N: usize> Div<Jet<N>> for $scalar {
226 type Output = Jet<N>;
227 fn div(self, rhs: Jet<N>) -> Jet<N> {
228 Jet::constant(self) / rhs
229 }
230 }
231 };
232}
233macro_rules! assignments {
234 ($trait:ident, $method:ident, $optrait:ident, $method2:ident) => {
235 impl<const N: usize, Rhs> $trait<Rhs> for Jet<N>
236 where
237 Self: $optrait<Rhs, Output = Self>,
238 {
239 fn $method(&mut self, rhs: Rhs) {
240 *self = self.$method2(rhs);
241 }
242 }
243 };
244}
245scalar_ops!(f64);
246scalar_ops!(Complex);
247impl<const N: usize> Div<f64> for Jet<N> {
250 type Output = Self;
251 fn div(self, rhs: f64) -> Self {
252 Self {
253 value: self.value / rhs,
254 derivative: self.derivative.map(|g| g / rhs),
255 }
256 }
257}
258impl<const N: usize> Div<Complex> for Jet<N> {
259 type Output = Self;
260 fn div(self, rhs: Complex) -> Self {
261 Self {
262 value: ratio(self.value, rhs),
263 derivative: self.derivative.map(|g| ratio(g, rhs)),
264 }
265 }
266}
267assignments!(AddAssign, add_assign, Add, add);
268assignments!(SubAssign, sub_assign, Sub, sub);
269assignments!(MulAssign, mul_assign, Mul, mul);
270
271#[cfg(test)]
273mod tests {
274 use std::f64::consts::PI;
275
276 use proptest::{prelude::*, test_runner::TestCaseError};
277
278 use super::Jet;
279 use crate::{
280 Complex,
281 numerics::complex_sqrt,
282 test_support::{ALGEBRA_CASES, DEFAULT_CASES, complex, log_polar, prop_assert_close},
283 };
284
285 fn jet() -> impl Strategy<Value = Jet<2>> {
287 (log_polar(-1.0..1.0, -PI..PI), complex(5.0), complex(5.0)).prop_map(|(value, a, b)| Jet {
288 value,
289 derivative: [a, b],
290 })
291 }
292
293 proptest! {
294 #![proptest_config(ProptestConfig::with_cases(ALGEBRA_CASES))]
295
296 #[test]
297 fn scalar_operations_equal_constant_jets(
298 a in jet(), b in jet(), re in -100.0_f64..100.0, im in -100.0_f64..100.0,
299 ) {
300 check_scalar_operations(a, b, re, Complex::new(re, im))?;
301 }
302
303 #[test]
304 fn jet_operations_invert(a in jet(), b in jet(), n in -4_i32..=6) {
305 check_inverses(a, b, n)?;
306 }
307
308 #[test]
309 fn chain_rule_matches_closed_form(
310 z in log_polar(-0.52..0.48, -PI..PI), w in complex(2.0), n in -4_i32..=4,
311 ) {
312 check_chain_rule(z, w, n)?;
313 }
314 }
315
316 proptest! {
317 #![proptest_config(ProptestConfig::with_cases(DEFAULT_CASES))]
318
319 #[test]
320 fn local_quotient_common_scale(
321 x in -0.3_f64..0.3, exponent in prop_oneof![Just(-300), Just(0), Just(300)],
322 ) {
323 check_local_quotient_common_scale(Complex::new(0.7, x), 10_f64.powi(exponent))?;
324 }
325 }
326
327 fn assert_equal(
328 actual: Jet<2>,
329 expected: Jet<2>,
330 operation: &str,
331 ) -> Result<(), TestCaseError> {
332 prop_assert_eq!(actual.value, expected.value, "{}", operation);
333 prop_assert_eq!(actual.derivative, expected.derivative, "{}", operation);
334 Ok(())
335 }
336
337 fn check_scalar_operations(
340 a: Jet<2>,
341 b: Jet<2>,
342 real: f64,
343 scalar: Complex,
344 ) -> Result<(), TestCaseError> {
345 let (r, c) = (Jet::constant(real), Jet::constant(scalar));
346 for (actual, expected, operation) in [
347 (a + real, a + r, "jet + real"),
348 (real + a, r + a, "real + jet"),
349 (a - real, a - r, "jet - real"),
350 (real - a, r - a, "real - jet"),
351 (a * real, a * r, "jet * real"),
352 (real * a, r * a, "real * jet"),
353 (a / real, a / r, "jet / real"),
354 (a + scalar, a + c, "jet + complex"),
355 (scalar + a, c + a, "complex + jet"),
356 (a - scalar, a - c, "jet - complex"),
357 (scalar - a, c - a, "complex - jet"),
358 (a * scalar, a * c, "jet * complex"),
359 (scalar * a, c * a, "complex * jet"),
360 (a / scalar, a / c, "jet / complex"),
361 ([a, b].into_iter().sum(), a + b, "sum"),
362 ] {
363 assert_equal(actual, expected, operation)?;
364 }
365 let mut assigned = a;
366 assigned *= real;
367 assigned += scalar;
368 assigned -= b;
369 assert_equal(assigned, a * real + scalar - b, "assignments")?;
370 let x = Jet::<2>::variable(a.value, 0);
371 let y = Jet::<2>::variable(b.value, 1);
372 let combination = scalar * x + y;
373 prop_assert_eq!(combination.derivative, [scalar, Complex::new(1.0, 0.0)]);
374 Ok(())
375 }
376
377 fn check_inverses(a: Jet<2>, b: Jet<2>, n: i32) -> Result<(), TestCaseError> {
380 let one = Jet::<2>::constant(1.0);
381 let scale = a.norm() * (1.0 + b.norm() / b.value.norm());
383 prop_assert_close!(((a * b) / b).value, a.value, 1e-14 * scale);
384 prop_assert_close!(((a * b) / b).derivative, a.derivative, 1e-14 * scale);
385 let root = if a.value.re > 0.0 { a } else { -a }.sqrt();
386 let square = root * root;
387 let expected = if a.value.re > 0.0 { a } else { -a };
388 prop_assert_close!(square.value, expected.value, 1e-14 * a.norm());
389 prop_assert_close!(square.derivative, expected.derivative, 1e-14 * a.norm());
390 let product = a.exp() * (-a).exp();
391 prop_assert_close!(product.value, one.value, 1e-14);
392 prop_assert_close!(product.derivative, one.derivative, 1e-14 * a.norm());
393 let repeated = (0..n.abs()).fold(one, |power, _| power * a);
394 let expected = if n < 0 { 1.0 / repeated } else { repeated };
395 let power = a.powi(n);
396 let scale = expected.norm() * f64::from(1 + n.abs());
397 prop_assert_close!(power.value, expected.value, 1e-14 * scale, "n = {}", n);
398 prop_assert_close!(
399 power.derivative,
400 expected.derivative,
401 1e-14 * scale,
402 "n = {}",
403 n
404 );
405 Ok(())
406 }
407
408 fn check_chain_rule(z: Complex, w: Complex, n: i32) -> Result<(), TestCaseError> {
411 let i = Complex::i();
412 prop_assume!((z - i).norm() > 0.2 && (z + i).norm() > 0.2);
413 let (x, y) = (Jet::<2>::variable(z, 0), Jet::<2>::variable(w, 1));
414 let f = ((x.powi(n) * x.exp()) / (1.0 + x * x)).sqrt() - 3.0 / x + y * x;
415 let g = z.powi(n) * z.exp() / (1.0 + z * z);
416 let root = complex_sqrt(g);
417 let dg = g * (f64::from(n) / z + 1.0 - 2.0 * z / (1.0 + z * z));
418 let expected = [dg / (2.0 * root) + 3.0 / (z * z) + w, z];
419 let scale = (dg / root).norm() + 3.0 / z.norm_sqr() + w.norm() + z.norm();
420 prop_assert_close!(f.value, root - 3.0 / z + w * z, 1e-13 * scale);
421 prop_assert_close!(f.derivative, expected, 1e-13 * scale);
422 Ok(())
423 }
424
425 fn check_local_quotient_common_scale(a: Complex, scale: f64) -> Result<(), TestCaseError> {
427 let b = Complex::new(1.3, 0.4);
428 let result = Jet::<2>::variable(a * scale, 0) / Jet::<2>::variable(b * scale, 1);
429 prop_assert_close!(result.value, a / b, 1e-14);
430 prop_assert_close!(
431 result.derivative[0] * scale,
432 Complex::new(1.0, 0.0) / b,
433 1e-14
434 );
435 prop_assert_close!(result.derivative[1] * scale, -a / b / b, 1e-14);
436 Ok(())
437 }
438}