1use crate::common::util::round_p;
10use crate::complex_special::half_c;
11use crate::complex_special::neg_c;
12use crate::complex_special::pi_c;
13use crate::complex_special::series_term_cap;
14use crate::complex_special::term_negligible;
15use crate::complex_special::two_c;
16use crate::complex_special::ziv_complex;
17use crate::Consts;
18use crate::ExactComplex;
19use crate::ExactNum;
20use crate::RoundingMode;
21
22const AIRY_SERIES_THRESHOLD: u32 = 8;
24
25fn abs_below(z: &ExactComplex, bound: u32, p: usize) -> bool {
26 let a = z.abs(p, RoundingMode::None);
27 let b = ExactNum::from_u32(bound, p);
28 matches!(a.cmp(&b), Some(c) if c < 0)
29}
30
31fn arg_abs_le(z: &ExactComplex, bound: &ExactNum, p: usize, cc: &mut Consts) -> bool {
32 let a = z.arg(p, RoundingMode::None, cc).abs();
33 matches!(a.cmp(bound), Some(c) if c <= 0)
34}
35
36fn omega_pair(p: usize) -> (ExactComplex, ExactComplex) {
37 let half = ExactNum::from_u8(1, p).div(&ExactNum::from_u8(2, p), p, RoundingMode::None);
38 let s3h = ExactNum::from_u8(3, p)
39 .sqrt(p, RoundingMode::None)
40 .mul(&half, p, RoundingMode::None);
41 let re = half.neg();
42 (
43 ExactComplex::new(re.clone(), s3h.clone()),
44 ExactComplex::new(re, s3h.neg()),
45 )
46}
47
48fn exp_i_pi_6(p: usize, cc: &mut Consts) -> (ExactComplex, ExactComplex) {
49 let a = cc
50 .pi(p, RoundingMode::None)
51 .div(&ExactNum::from_u8(6, p), p, RoundingMode::None);
52 let (s, c) = a.sin_cos(p, RoundingMode::None, cc);
53 (
54 ExactComplex::new(c.clone(), s.clone()),
55 ExactComplex::new(c, s.neg()),
56 )
57}
58
59fn two_pi_over_three(p: usize, cc: &mut Consts) -> ExactNum {
60 two_c(p)
61 .re()
62 .mul(&cc.pi(p, RoundingMode::None), p, RoundingMode::None)
63 .div(&ExactNum::from_u8(3, p), p, RoundingMode::None)
64}
65
66fn pi_over_three(p: usize, cc: &mut Consts) -> ExactNum {
67 cc.pi(p, RoundingMode::None)
68 .div(&ExactNum::from_u8(3, p), p, RoundingMode::None)
69}
70
71fn airy_cs_real(p: usize, cc: &mut Consts) -> (ExactNum, ExactNum, ExactNum) {
72 let one = ExactNum::from_u8(1, p);
73 let two = ExactNum::from_u8(2, p);
74 let three = ExactNum::from_u8(3, p);
75 let third = one.div(&three, p, RoundingMode::None);
76 let two_third = two.div(&three, p, RoundingMode::None);
77 let g23 = two_third.gamma(p, RoundingMode::None, cc);
78 let g13 = third.gamma(p, RoundingMode::None, cc);
79 let c1 =
80 three
81 .pow(&two_third.neg(), p, RoundingMode::None, cc)
82 .div(&g23, p, RoundingMode::None);
83 let c2 = three
84 .pow(&third.neg(), p, RoundingMode::None, cc)
85 .div(&g13, p, RoundingMode::None);
86 let sqrt3 = three.sqrt(p, RoundingMode::None);
87 (c1, c2, sqrt3)
88}
89
90impl ExactComplex {
91 pub fn ai(&self, p: usize, rm: RoundingMode, cc: &mut Consts) -> Self {
99 if self.is_nan() {
100 return ExactComplex::new(self.re().clone(), self.im().clone());
101 }
102 let dest = round_p(p);
103 ziv_complex(dest, rm, |pw| self.ai_at(pw, dest, cc, true))
104 }
105
106 pub fn bi(&self, p: usize, rm: RoundingMode, cc: &mut Consts) -> Self {
114 if self.is_nan() {
115 return ExactComplex::new(self.re().clone(), self.im().clone());
116 }
117 let dest = round_p(p);
118 ziv_complex(dest, rm, |pw| self.bi_at(pw, dest, cc))
119 }
120
121 fn ai_at(&self, work_p: usize, dest_p: usize, cc: &mut Consts, connect: bool) -> Self {
122 if self.im().is_zero() {
123 return ExactComplex::from_real(self.re().ai(work_p, RoundingMode::None, cc), work_p);
124 }
125 if abs_below(self, AIRY_SERIES_THRESHOLD, dest_p) {
126 return self.airy_series_ai(work_p, cc);
127 }
128 let sector = two_pi_over_three(work_p, cc);
129 if arg_abs_le(self, §or, work_p, cc) {
130 return self.airy_asymp_ai(work_p, cc);
131 }
132 if connect {
133 let (w, w2) = omega_pair(work_p);
134 let zw = self.mul(&w, work_p, RoundingMode::None);
135 let zw2 = self.mul(&w2, work_p, RoundingMode::None);
136 let t1 = w.mul(
137 &zw.ai_at(work_p, dest_p, cc, false),
138 work_p,
139 RoundingMode::None,
140 );
141 let t2 = w2.mul(
142 &zw2.ai_at(work_p, dest_p, cc, false),
143 work_p,
144 RoundingMode::None,
145 );
146 return neg_c(&t1.add(&t2, work_p, RoundingMode::None));
147 }
148 self.airy_asymp_ai(work_p, cc)
149 }
150
151 fn bi_at(&self, work_p: usize, dest_p: usize, cc: &mut Consts) -> Self {
152 if self.im().is_zero() {
153 return ExactComplex::from_real(self.re().bi(work_p, RoundingMode::None, cc), work_p);
154 }
155 if abs_below(self, AIRY_SERIES_THRESHOLD, dest_p) {
156 return self.airy_series_bi(work_p, cc);
157 }
158 let sector = pi_over_three(work_p, cc);
159 if arg_abs_le(self, §or, work_p, cc) {
160 return self.airy_asymp_bi(work_p, cc);
161 }
162 let (w, w2) = omega_pair(work_p);
163 let (ep, em) = exp_i_pi_6(work_p, cc);
164 let a1 = self
165 .mul(&w2, work_p, RoundingMode::None)
166 .ai_at(work_p, dest_p, cc, true);
167 let a2 = self
168 .mul(&w, work_p, RoundingMode::None)
169 .ai_at(work_p, dest_p, cc, true);
170 ep.mul(&a1, work_p, RoundingMode::None).add(
171 &em.mul(&a2, work_p, RoundingMode::None),
172 work_p,
173 RoundingMode::None,
174 )
175 }
176
177 fn airy_series_pair(&self, p: usize) -> (ExactComplex, ExactComplex) {
178 let x3 = self
179 .mul(self, p, RoundingMode::None)
180 .mul(self, p, RoundingMode::None);
181 let one = ExactComplex::one(p);
182 let mut tf = one.clone();
183 let mut f = one;
184 let mut tg = self.clone();
185 let mut g = self.clone();
186 let cap = series_term_cap(p);
187 for k in 1..=cap {
188 let k3 = ExactComplex::from_real(ExactNum::from_u32((3 * k) as u32, p), p);
189 let k3m1 = ExactComplex::from_real(ExactNum::from_u32((3 * k - 1) as u32, p), p);
190 let k3p1 = ExactComplex::from_real(ExactNum::from_u32((3 * k + 1) as u32, p), p);
191 tf = tf.mul(&x3, p, RoundingMode::None).div(
192 &k3.mul(&k3m1, p, RoundingMode::None),
193 p,
194 RoundingMode::None,
195 );
196 f = f.add(&tf, p, RoundingMode::None);
197 tg = tg.mul(&x3, p, RoundingMode::None).div(
198 &k3p1.mul(&k3, p, RoundingMode::None),
199 p,
200 RoundingMode::None,
201 );
202 g = g.add(&tg, p, RoundingMode::None);
203 if term_negligible(&tf, p) && term_negligible(&tg, p) {
204 break;
205 }
206 }
207 (f, g)
208 }
209
210 fn airy_series_ai(&self, p: usize, cc: &mut Consts) -> Self {
211 let (c1, c2, _) = airy_cs_real(p, cc);
212 let (f, g) = self.airy_series_pair(p);
213 let c1c = ExactComplex::from_real(c1, p);
214 let c2c = ExactComplex::from_real(c2, p);
215 c1c.mul(&f, p, RoundingMode::None).sub(
216 &c2c.mul(&g, p, RoundingMode::None),
217 p,
218 RoundingMode::None,
219 )
220 }
221
222 fn airy_series_bi(&self, p: usize, cc: &mut Consts) -> Self {
223 let (c1, c2, sqrt3) = airy_cs_real(p, cc);
224 let (f, g) = self.airy_series_pair(p);
225 let c1c = ExactComplex::from_real(c1, p);
226 let c2c = ExactComplex::from_real(c2, p);
227 let s3 = ExactComplex::from_real(sqrt3, p);
228 s3.mul(
229 &c1c.mul(&f, p, RoundingMode::None).add(
230 &c2c.mul(&g, p, RoundingMode::None),
231 p,
232 RoundingMode::None,
233 ),
234 p,
235 RoundingMode::None,
236 )
237 }
238
239 fn airy_xi_z(&self, p: usize, cc: &mut Consts) -> (ExactComplex, ExactComplex) {
240 let two_thirds = two_c(p).div(
241 &ExactComplex::from_real(ExactNum::from_u8(3, p), p),
242 p,
243 RoundingMode::None,
244 );
245 let sz = self.sqrt(p, RoundingMode::None, cc);
246 let xi = two_thirds.mul(&self.mul(&sz, p, RoundingMode::None), p, RoundingMode::None);
247 let z14 = sz.sqrt(p, RoundingMode::None, cc);
248 (xi, z14)
249 }
250
251 fn airy_u_sum(&self, xi: &ExactComplex, p: usize, alt: bool) -> ExactComplex {
252 let one = ExactComplex::one(p);
253 let mut u = one.clone();
254 let mut sum = one;
255 let mut prev_abs = ExactNum::from_u8(1, p);
256 let mut xi_pow = xi.clone();
257 let cap = series_term_cap(p).min(p.saturating_add(8));
258 for k in 1..=cap {
259 let num = ExactNum::from_u32((6 * k - 5) as u32, p).mul(
260 &ExactNum::from_u32((6 * k - 1) as u32, p),
261 p,
262 RoundingMode::None,
263 );
264 let den = ExactNum::from_u32(72, p).mul(
265 &ExactNum::from_u32(k as u32, p),
266 p,
267 RoundingMode::None,
268 );
269 let ratio = ExactComplex::from_real(num.div(&den, p, RoundingMode::None), p);
270 u = u.mul(&ratio, p, RoundingMode::None);
271 let mut t = u.div(&xi_pow, p, RoundingMode::None);
272 if alt && k % 2 == 1 {
273 t = neg_c(&t);
274 }
275 let ta = t.abs(p, RoundingMode::None);
276 if matches!(ta.cmp(&prev_abs), Some(c) if c > 0) {
277 break;
278 }
279 sum = sum.add(&t, p, RoundingMode::None);
280 if term_negligible(&t, p) {
281 break;
282 }
283 prev_abs = ta;
284 xi_pow = xi_pow.mul(xi, p, RoundingMode::None);
285 }
286 sum
287 }
288
289 fn airy_asymp_ai(&self, p: usize, cc: &mut Consts) -> Self {
290 let (xi, z14) = self.airy_xi_z(p, cc);
291 let half = half_c(p);
292 let pi = pi_c(p, cc);
293 let pi_m12 =
294 ExactComplex::one(p).div(&pi.sqrt(p, RoundingMode::None, cc), p, RoundingMode::None);
295 let z_m14 = ExactComplex::one(p).div(&z14, p, RoundingMode::None);
296 let exp_m = neg_c(&xi).exp(p, RoundingMode::None, cc);
297 let su = self.airy_u_sum(&xi, p, true);
298 half.mul(&pi_m12, p, RoundingMode::None)
299 .mul(&z_m14, p, RoundingMode::None)
300 .mul(&exp_m, p, RoundingMode::None)
301 .mul(&su, p, RoundingMode::None)
302 }
303
304 fn airy_asymp_bi(&self, p: usize, cc: &mut Consts) -> Self {
305 let (xi, z14) = self.airy_xi_z(p, cc);
306 let pi = pi_c(p, cc);
307 let pi_m12 =
308 ExactComplex::one(p).div(&pi.sqrt(p, RoundingMode::None, cc), p, RoundingMode::None);
309 let z_m14 = ExactComplex::one(p).div(&z14, p, RoundingMode::None);
310 let exp_p = xi.exp(p, RoundingMode::None, cc);
311 let su = self.airy_u_sum(&xi, p, false);
312 pi_m12
313 .mul(&z_m14, p, RoundingMode::None)
314 .mul(&exp_p, p, RoundingMode::None)
315 .mul(&su, p, RoundingMode::None)
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322
323 fn near_bits(a: &ExactNum, b: &ExactNum, p: usize, slack: i32) -> bool {
324 let d = a.sub(b, p, RoundingMode::None).abs();
325 d.is_zero() || d.exponent().is_some_and(|e| e < -((p as i32) / slack))
326 }
327
328 fn tiny(x: &ExactNum, p: usize) -> bool {
329 x.is_zero() || x.exponent().is_some_and(|e| e < -((p as i32) / 4))
330 }
331
332 #[test]
333 fn test_complex_airy_golds() {
334 let p = 256;
335 let rm = RoundingMode::ToEven;
336 let mut cc = Consts::new().unwrap();
337
338 let z0 = ExactComplex::zero(p);
339 let a0 = z0.ai(p, rm, &mut cc);
340 let r0 = ExactNum::new(p).ai(p, rm, &mut cc);
341 assert!(near_bits(a0.re(), &r0, p, 4));
342 assert!(tiny(a0.im(), p));
343 let b0 = z0.bi(p, rm, &mut cc);
344 let rb0 = ExactNum::new(p).bi(p, rm, &mut cc);
345 assert!(near_bits(b0.re(), &rb0, p, 4));
346 assert!(tiny(b0.im(), p));
347
348 let one = ExactComplex::one(p);
349 let a1 = one.ai(p, rm, &mut cc);
350 let r1 = ExactNum::from_u8(1, p).ai(p, rm, &mut cc);
351 assert!(near_bits(a1.re(), &r1, p, 4));
352 assert!(tiny(a1.im(), p));
353
354 let i = ExactComplex::i(p);
355 let zi = i.ai(p, rm, &mut cc);
356 assert!(!zi.is_nan());
357 let (w, w2) = omega_pair(p);
358 let t0 = zi;
360 let t1 = w.mul(&i.mul(&w, p, rm).ai(p, rm, &mut cc), p, rm);
361 let t2 = w2.mul(&i.mul(&w2, p, rm).ai(p, rm, &mut cc), p, rm);
362 let sum = t0.add(&t1, p, rm).add(&t2, p, rm);
363 assert!(
364 tiny(sum.re(), p) && tiny(sum.im(), p),
365 "connection identity"
366 );
367
368 let z = ExactComplex::new(ExactNum::from_u8(1, p), ExactNum::from_u8(1, p));
369 let az = z.ai(p, rm, &mut cc);
370 let bz = z.bi(p, rm, &mut cc);
371 assert!(!az.is_nan() && !bz.is_nan());
372
373 let nan = ExactComplex::new(crate::NAN.clone(), ExactNum::new(p));
374 assert!(nan.ai(p, rm, &mut cc).is_nan());
375 assert!(nan.bi(p, rm, &mut cc).is_nan());
376 }
377}