scirs2_special/parabolic.rs
1//! Parabolic Cylinder Functions
2//!
3//! This module provides implementations of the Parabolic Cylinder functions,
4//! which are solutions to the differential equation:
5//!
6//! d²y/dx² + (v + 1/2 - x²/4)y = 0
7//!
8//! These functions have applications in quantum mechanics, wave propagation,
9//! and other areas of mathematical physics.
10
11use std::f64::consts::{PI, SQRT_2};
12// use scirs2_core::numeric::Float;
13// use scirs2_core::numeric::Complex64;
14
15use crate::error::{SpecialError, SpecialResult};
16use crate::gamma::{gamma, gammaln};
17
18/// Parabolic cylinder function D_v(x) and its derivative.
19///
20/// The parabolic cylinder function D_v(x) is a solution to the differential equation:
21/// d²y/dx² + (v + 1/2 - x²/4)y = 0
22///
23/// # Arguments
24///
25/// * `v` - Order parameter
26/// * `x` - Real argument
27///
28/// # Returns
29///
30/// * A tuple containing (D_v(x), D_v'(x))
31///
32/// # Examples
33///
34/// ```
35/// use scirs2_special::pbdv;
36///
37/// let (d, dp) = pbdv(1.0, 0.5).unwrap();
38/// println!("D_1(0.5) = {}, D_1'(0.5) = {}", d, dp);
39/// ```
40#[allow(dead_code)]
41pub fn pbdv(v: f64, x: f64) -> SpecialResult<(f64, f64)> {
42 if v.is_nan() || x.is_nan() {
43 return Err(SpecialError::DomainError("NaN input to pbdv".to_string()));
44 }
45
46 // Special cases for v = 0, ±1, ±2
47 if v == 0.0 {
48 return pbdv_0(x);
49 } else if v == 1.0 {
50 return pbdv_1(x);
51 } else if v == 2.0 {
52 return pbdv_2(x);
53 } else if v == -1.0 {
54 return pbdv_m1(x);
55 } else if v == -2.0 {
56 return pbdv_m2(x);
57 }
58
59 // General case
60 if v.floor() == v && (-20.0..=20.0).contains(&v) {
61 // Integer case - use recurrence relations
62 pbdv_integer(v as i32, x)
63 } else {
64 // General case - use series expansion
65 pbdv_general(v, x)
66 }
67}
68
69/// Implementation of D_0(x) for v = 0
70#[allow(dead_code)]
71fn pbdv_0(x: f64) -> SpecialResult<(f64, f64)> {
72 // D_0(x) = e^(-x²/4)
73 let x2 = x * x;
74 let d = (-x2 / 4.0).exp();
75 // D_0'(x) = -x/2 * e^(-x²/4)
76 let dp = -x / 2.0 * d;
77
78 Ok((d, dp))
79}
80
81/// Implementation of D_1(x) for v = 1
82#[allow(dead_code)]
83fn pbdv_1(x: f64) -> SpecialResult<(f64, f64)> {
84 // D_1(x) = x * e^(-x²/4)
85 let x2 = x * x;
86 let exp_term = (-x2 / 4.0).exp();
87 let d = x * exp_term;
88 // D_1'(x) = (1 - x²/2) * e^(-x²/4)
89 let dp = (1.0 - x2 / 2.0) * exp_term;
90
91 Ok((d, dp))
92}
93
94/// Implementation of D_2(x) for v = 2
95#[allow(dead_code)]
96fn pbdv_2(x: f64) -> SpecialResult<(f64, f64)> {
97 // D_2(x) = (x² - 2) * e^(-x²/4)
98 let x2 = x * x;
99 let exp_term = (-x2 / 4.0).exp();
100 let d = (x2 - 2.0) * exp_term;
101 // D_2'(x) = x * (x² - 4) * e^(-x²/4) / 2
102 let dp = x * (x2 - 4.0) * exp_term / 2.0;
103
104 Ok((d, dp))
105}
106
107/// Implementation of D_{-1}(x) for v = -1
108#[allow(dead_code)]
109fn pbdv_m1(x: f64) -> SpecialResult<(f64, f64)> {
110 // D_{-1}(x) = (√(π/2) * e^(x²/4) * [1 - erf(x/√2)]) - x*e^(-x²/4)/2
111 let x2 = x * x;
112 let sqrt_pi_over_2 = (PI / 2.0).sqrt();
113 let exp_pos = (x2 / 4.0).exp();
114 let exp_neg = (-x2 / 4.0).exp();
115
116 // Error function approximation
117 let erf_val = erf_approx(x / SQRT_2);
118
119 let d = sqrt_pi_over_2 * exp_pos * (1.0 - erf_val) - x * exp_neg / 2.0;
120
121 // Derivative calculation
122 let dp = x * d / 2.0 + exp_neg;
123
124 Ok((d, dp))
125}
126
127/// Implementation of D_{-2}(x) for v = -2
128#[allow(dead_code)]
129fn pbdv_m2(x: f64) -> SpecialResult<(f64, f64)> {
130 // D_{-2}(x) can be calculated using recurrence relations with D_{-1} and D_0
131 let (d_m1, _) = pbdv_m1(x)?;
132 let (d_0, _) = pbdv_0(x)?;
133
134 let d = x * d_m1 - d_0;
135 let dp = d_m1 - x * d / 2.0;
136
137 Ok((d, dp))
138}
139
140/// Implementation of D_v(x) for integer v using recurrence relations
141#[allow(dead_code)]
142fn pbdv_integer(v: i32, x: f64) -> SpecialResult<(f64, f64)> {
143 if v >= 0 {
144 // Forward recurrence for positive v
145 let (d0, d0p) = pbdv_0(x)?;
146 let (d1, d1p) = pbdv_1(x)?;
147
148 let mut d_prev = d0;
149 let mut d = d1;
150 let mut dp_prev = d0p;
151 let mut dp = d1p;
152
153 for k in 1..v {
154 let d_next = x * d - k as f64 * d_prev;
155 let dp_next = x * dp - k as f64 * dp_prev - d;
156
157 d_prev = d;
158 d = d_next;
159 dp_prev = dp;
160 dp = dp_next;
161 }
162
163 Ok((d, dp))
164 } else {
165 // Backward recurrence for negative v
166 let (d0, d0p) = pbdv_0(x)?;
167 let (dm1, dm1p) = pbdv_m1(x)?;
168
169 let mut d_prev = dm1;
170 let mut d = d0;
171 let mut dp_prev = dm1p;
172 let mut dp = d0p;
173
174 for k in 0..(-v - 1) {
175 let d_next = (x * d + d_prev) / (k as f64 + 1.0);
176 let dp_next = (x * dp + dp_prev - d) / (k as f64 + 1.0);
177
178 d_prev = d;
179 d = d_next;
180 dp_prev = dp;
181 dp = dp_next;
182 }
183
184 Ok((d, dp))
185 }
186}
187
188/// General implementation of D_v(x) for any v using series expansions
189#[allow(dead_code)]
190fn pbdv_general(v: f64, x: f64) -> SpecialResult<(f64, f64)> {
191 // For small |x|, use power series
192 if x.abs() < 5.0 {
193 return pbdv_series(v, x);
194 }
195
196 // For large positive x, use asymptotic expansion
197 if x > 0.0 && x.abs() > (2.0 * v.abs()).max(20.0) {
198 return pbdv_asymptotic_pos(v, x);
199 }
200
201 // For large negative x, use relationship with V_v
202 if x < 0.0 && x.abs() > (2.0 * v.abs()).max(20.0) {
203 return pbdv_asymptotic_neg(v, x);
204 }
205
206 // Fallback to numerical integration using the series expansion
207 pbdv_series(v, x)
208}
209
210/// Power series implementation for D_v(x) with enhanced numerical stability
211#[allow(dead_code)]
212fn pbdv_series(v: f64, x: f64) -> SpecialResult<(f64, f64)> {
213 // Special case for x = 0
214 if x == 0.0 {
215 // When x = 0, D_v(0) = 2^(-v/2) * Γ((1+v)/2) / Γ(1)
216 // And D_v'(0) = 0 for v > 0, and infinity for v <= 0
217 let gamma_1_plus_v_half = gamma((1.0 + v) / 2.0);
218 let gamma_1 = 1.0; // gamma(1.0) is always 1.0
219 let d_at_zero = 2_f64.powf(-v / 2.0) * gamma_1_plus_v_half / gamma_1;
220 let dp_at_zero = if v > 0.0 { 0.0 } else { f64::INFINITY };
221 return Ok((d_at_zero, dp_at_zero));
222 }
223
224 // Special case for very large |x|
225 if x.abs() > 100.0 {
226 // For very large |x|, use the asymptotic form instead
227 return if x > 0.0 {
228 pbdv_asymptotic_pos(v, x)
229 } else {
230 pbdv_asymptotic_neg(v, x)
231 };
232 }
233
234 // For normal range of x, use enhanced series method
235 let x2 = x * x;
236
237 // Use log-based computation for the exponential term to avoid underflow
238 let log_exp_term = -x2 / 4.0;
239
240 // Only compute the actual exponent if it won't underflow
241 let exp_term = if log_exp_term < -700.0 {
242 0.0 // Effectively zero due to underflow
243 } else {
244 log_exp_term.exp()
245 };
246
247 // Series for D_v(x)
248 let mut d_sum; // Will be set in the loop
249 let mut dp_sum = 0.0;
250
251 // For numerical stability, keep track of previous sums to detect stagnation
252 let mut prev_d_sum = 0.0;
253 let mut prev_dp_sum = 0.0;
254 let mut stagnation_count = 0;
255
256 // Initial coefficient computation, using log-space for better precision
257 let v_half = v / 2.0;
258 let log_first_term = -v_half * 2.0_f64.ln() + gammaln(1.0 + v_half) - gammaln(1.0);
259 let first_term = log_first_term.exp();
260
261 let mut term = first_term;
262 let mut dp_term = 0.0;
263 d_sum = term;
264
265 // Extended iteration limit for more challenging cases
266 for k in 1..100 {
267 // Different handling for odd and even k
268 if k % 2 == 0 {
269 // Even k - computation in log space to avoid overflow/underflow
270 let k_f64 = k as f64;
271 let log_coef = (k / 2) as f64 * (-(k_f64) / 2.0).ln() - log_factorial(k);
272 let coef = log_coef.exp();
273
274 // For x^k, use log-space if k is large
275 let sign_correction = if x < 0.0 && k % 2 == 1 { -1.0 } else { 1.0 };
276 let log_x_pow_k = k_f64 * x.abs().ln();
277 let x_pow_k = sign_correction * log_x_pow_k.exp();
278
279 // Compute terms with protection against overflow
280 let new_term = coef * x_pow_k;
281
282 // For derivatives, we need x^(k-1)
283 let x_pow_kminus_1 = if k > 0 { x_pow_k / x } else { 1.0 };
284 let new_dp_term = coef * x_pow_kminus_1 * k_f64;
285
286 if new_term.is_finite() {
287 term = new_term;
288 d_sum += term;
289 }
290
291 if new_dp_term.is_finite() {
292 dp_term = new_dp_term;
293 dp_sum += dp_term;
294 }
295 } else {
296 // Odd k - similar approach
297 let k_f64 = k as f64;
298 let log_coef = ((k - 1) / 2) as f64 * (-(k_f64) / 2.0).ln() - log_factorial(k);
299 let coef = log_coef.exp();
300
301 // For x^k
302 let sign_correction = if x < 0.0 && k % 2 == 1 { -1.0 } else { 1.0 };
303 let log_x_pow_k = k_f64 * x.abs().ln();
304 let x_pow_k = sign_correction * log_x_pow_k.exp();
305
306 let new_term = coef * x_pow_k;
307
308 if new_term.is_finite() {
309 term = new_term;
310 d_sum += term;
311
312 // For odd k, the derivative term is calculated differently
313 dp_term = term * k_f64 / x;
314 dp_sum += dp_term;
315 }
316 }
317
318 // Multiple convergence criteria
319
320 // Absolute tolerance
321 let abs_tol = 1e-15;
322
323 // Relative tolerance (protecting against division by zero)
324 let d_rel_tol = 1e-15 * d_sum.abs().max(1e-300);
325 let dp_rel_tol = 1e-15 * dp_sum.abs().max(1e-300);
326
327 // First convergence check: terms becoming small relative to sum
328 if (term.abs() < abs_tol || term.abs() < d_rel_tol)
329 && (dp_term.abs() < abs_tol || dp_term.abs() < dp_rel_tol)
330 {
331 break;
332 }
333
334 // Second check: detect series stagnation
335 if (d_sum - prev_d_sum).abs() < d_rel_tol && (dp_sum - prev_dp_sum).abs() < dp_rel_tol {
336 stagnation_count += 1;
337 if stagnation_count >= 3 {
338 // Series has stopped making meaningful progress
339 break;
340 }
341 } else {
342 stagnation_count = 0;
343 }
344
345 // Safety check for very small terms that won't affect the result
346 if k > 50 && term.abs() < 1e-30 && dp_term.abs() < 1e-30 {
347 break;
348 }
349
350 prev_d_sum = d_sum;
351 prev_dp_sum = dp_sum;
352 }
353
354 // Final calculation with careful handling of potential overflow/underflow
355 let d = d_sum * exp_term;
356
357 // Derivative calculation: D_v'(x) = derivative of series - x/2 * D_v(x)
358 let dp = dp_sum * exp_term - x / 2.0 * d;
359
360 // Check for any NaN or Inf results (can happen for extreme parameter values)
361 if !d.is_finite() || !dp.is_finite() {
362 // Fall back to direct evaluation for specific orders if series fails
363 if v.floor() == v && (-5.0..=5.0).contains(&v) {
364 return pbdv_integer(v as i32, x);
365 }
366
367 // For non-integer v that causes overflow, make a best approximation
368 let d_approx = if v < 0.0 && x.abs() > 10.0 {
369 // For large |x| and negative v, D_v(x) grows very large
370 if x > 0.0 {
371 0.0 // Approaches 0
372 } else {
373 f64::INFINITY.copysign(if v.floor() as i32 % 2 == 0 { 1.0 } else { -1.0 })
374 }
375 } else if v > 0.0 && x.abs() > 10.0 {
376 // For large |x| and positive v, D_v(x) approaches 0
377 0.0
378 } else {
379 // Other cases - use direct formula for moderate v
380 let gamma_val = gamma((1.0 + v) / 2.0);
381 2_f64.powf(-v / 2.0) * gamma_val * (-x2 / 4.0).exp()
382 };
383
384 let dp_approx = -x / 2.0 * d_approx; // Simple approximation for the derivative
385
386 return Ok((d_approx, dp_approx));
387 }
388
389 Ok((d, dp))
390}
391
392/// Asymptotic expansion for D_v(x) for large positive x with enhanced numerical stability
393#[allow(dead_code)]
394fn pbdv_asymptotic_pos(v: f64, x: f64) -> SpecialResult<(f64, f64)> {
395 // For extremely large x
396 if x > 100.0 && v > 0.0 {
397 // For large positive x and v > 0, D_v(x) approaches 0 exponentially
398 return Ok((0.0, 0.0));
399 }
400
401 // Scale to avoid overflow
402 let z = x / SQRT_2;
403 let v2 = v * v;
404
405 // Calculate the asymptotic series in log space to avoid overflow/underflow
406 let log_pre_factor = -z * z / 2.0 - (v + 0.5) * z.abs().ln() - (v / 2.0) * 2.0_f64.ln();
407
408 // Compute the sum for the asymptotic expansion
409 let mut sum = 1.0;
410 let mut term = 1.0;
411 let mut prev_sum = 0.0;
412 let mut stagnation_count = 0;
413
414 // Keep track of the derivative term
415 let mut deriv_term = 0.0;
416
417 // Extended iteration limit for better accuracy
418 for k in 1..30 {
419 // Calculate the numerator carefully to avoid overflow
420 let numerator = v2 - (2 * k - 1) as f64 * (2 * k - 1) as f64;
421 let denominator = 2.0 * k as f64 * z * z;
422
423 // Check for potential numerical issues
424 if denominator.abs() < 1e-300 {
425 // Avoid division by zero
426 break;
427 }
428
429 let term_factor = numerator / denominator;
430 let new_term = term * term_factor;
431
432 // Check for numerical stability
433 if !new_term.is_finite() {
434 // If the term would cause overflow, stop here
435 break;
436 }
437
438 term = new_term;
439 sum += term;
440
441 // Save this term for derivative calculation
442 deriv_term = term;
443
444 // Multiple convergence criteria
445
446 // Absolute tolerance
447 let abs_tol = 1e-15;
448
449 // Relative tolerance with protection against zero division
450 let rel_tol = 1e-15 * sum.abs().max(1e-300);
451
452 // First convergence check
453 if term.abs() < abs_tol || term.abs() < rel_tol {
454 break;
455 }
456
457 // Check for series stagnation
458 if (sum - prev_sum).abs() < rel_tol {
459 stagnation_count += 1;
460 if stagnation_count >= 3 {
461 break;
462 }
463 } else {
464 stagnation_count = 0;
465 }
466
467 // Check for potential divergence in the asymptotic series
468 if k > 5 && term.abs() > prev_sum.abs() {
469 // Series may be starting to diverge, use the value before divergence
470 sum = prev_sum;
471 break;
472 }
473
474 prev_sum = sum;
475 }
476
477 // Calculate function value in log space, then exponentiate
478 // Only exponentiate if it won't overflow
479 let d = if log_pre_factor < -700.0 {
480 0.0 // Underflow to zero
481 } else if log_pre_factor > 700.0 {
482 f64::INFINITY.copysign(if v.floor() as i32 % 2 == 0 { 1.0 } else { -1.0 })
483 // Overflow
484 } else {
485 log_pre_factor.exp() * sum
486 };
487
488 // Compute the derivative more carefully
489 let dp = if d.abs() < 1e-300 {
490 // For very small function values, the derivative is also near zero
491 0.0
492 } else {
493 // Derivative calculation: D_v'(x) = D_v(x) * (-z - (v+0.5)/z + correction)
494 // where correction accounts for the derivative of the sum
495 let correction = deriv_term * z / sum;
496 -d * (z + (v + 0.5) / z - correction)
497 };
498
499 // Final validation for potential numerical errors
500 if !d.is_finite() || !dp.is_finite() {
501 // For extreme cases, approximation for v >> 0 or x >> 0
502 let d_approx = 0.0; // D_v(x) → 0 for large positive x
503 let dp_approx = 0.0; // Similarly for the derivative
504 return Ok((d_approx, dp_approx));
505 }
506
507 Ok((d, dp))
508}
509
510/// Asymptotic expansion for D_v(x) for large negative x
511#[allow(dead_code)]
512fn pbdv_asymptotic_neg(v: f64, x: f64) -> SpecialResult<(f64, f64)> {
513 // For large negative x, use relationship with V_v(x)
514 let (v_val, vp_val) = pbvv(v, -x)?;
515
516 // Relationship: D_v(-x) = (sin(πv) * V_v(x) + V_{v-1}(x) / cos(πv/2)) / sin(π(v+1)/2)
517 let pi_v = PI * v;
518 let sin_pi_v = pi_v.sin();
519 let sin_pi_v_plus_1_half = (PI * (v + 1.0) / 2.0).sin();
520 let cos_pi_v_half = (PI * v / 2.0).cos();
521
522 let (v_val_prev, _) = pbvv(v - 1.0, -x)?;
523
524 let d = (sin_pi_v * v_val + v_val_prev / cos_pi_v_half) / sin_pi_v_plus_1_half;
525 let dp = -vp_val; // Derivative needs sign change due to x → -x
526
527 Ok((d, dp))
528}
529
530/// Parabolic cylinder function V_v(x) and its derivative.
531///
532/// The parabolic cylinder function V_v(x) is another solution to the differential equation:
533/// d²y/dx² + (v + 1/2 - x²/4)y = 0
534///
535/// # Arguments
536///
537/// * `v` - Order parameter
538/// * `x` - Real argument
539///
540/// # Returns
541///
542/// * A tuple containing (V_v(x), V_v'(x))
543///
544/// # Examples
545///
546/// ```
547/// use scirs2_special::pbvv;
548///
549/// let (v, vp) = pbvv(1.0, 0.5).unwrap();
550/// println!("V_1(0.5) = {}, V_1'(0.5) = {}", v, vp);
551/// ```
552#[allow(dead_code)]
553pub fn pbvv(v: f64, x: f64) -> SpecialResult<(f64, f64)> {
554 if v.is_nan() || x.is_nan() {
555 return Err(SpecialError::DomainError("NaN input to pbvv".to_string()));
556 }
557
558 // V_v can be expressed in terms of D_v for certain cases
559 if v.floor() == v && (-20.0..=20.0).contains(&v) {
560 // Integer case - can be derived from D_v
561 pbvv_integer(v as i32, x)
562 } else {
563 // General case - use series expansion or asymptotic forms
564 pbvv_general(v, x)
565 }
566}
567
568/// Implementation of V_v(x) for integer v
569#[allow(dead_code)]
570fn pbvv_integer(v: i32, x: f64) -> SpecialResult<(f64, f64)> {
571 // For integer v, V_v can be expressed in terms of D_v and D_{-v-1}
572 let (d_v, d_v_prime) = pbdv(v as f64, x)?;
573
574 if v >= 0 {
575 // For v ≥ 0
576 let (d_neg_vminus_1, d_neg_vminus_1_prime) = pbdv(-(v as f64) - 1.0, x)?;
577
578 let gamma_arg1 = v as f64 + 1.0;
579 let gamma_v_plus_1 = gamma(gamma_arg1);
580
581 // Formula: V_v(x) = D_v(x)·cos(πv) - D_{-v-1}(x)·√(2/π)·Γ(v+1)
582 let v_val =
583 d_v * (PI * v as f64).cos() - d_neg_vminus_1 * (2.0 / PI).sqrt() * gamma_v_plus_1;
584 let vp_val = d_v_prime * (PI * v as f64).cos()
585 - d_neg_vminus_1_prime * (2.0 / PI).sqrt() * gamma_v_plus_1;
586
587 Ok((v_val, vp_val))
588 } else {
589 // For v < 0
590 let (d_neg_vminus_1, d_neg_vminus_1_prime) = pbdv(-(v as f64) - 1.0, x)?;
591
592 let gamma_arg1 = -(v as f64);
593 let _gamma_neg_v = gamma(gamma_arg1);
594
595 // Formula for v < 0
596 let v_val = d_neg_vminus_1;
597 let vp_val = d_neg_vminus_1_prime;
598
599 Ok((v_val, vp_val))
600 }
601}
602
603/// General implementation of V_v(x) using series or asymptotic forms
604#[allow(dead_code)]
605fn pbvv_general(v: f64, x: f64) -> SpecialResult<(f64, f64)> {
606 // For small |x|, use series expansion
607 if x.abs() < 5.0 {
608 return pbvv_series(v, x);
609 }
610
611 // For large |x|, use asymptotic forms
612 if x.abs() > (2.0 * v.abs()).max(20.0) {
613 return pbvv_asymptotic(v, x);
614 }
615
616 // Fallback to series implementation
617 pbvv_series(v, x)
618}
619
620/// Series implementation for V_v(x) with enhanced numerical stability
621#[allow(dead_code)]
622fn pbvv_series(v: f64, x: f64) -> SpecialResult<(f64, f64)> {
623 // Special case for x = 0
624 if x == 0.0 {
625 // For x = 0, V_v(0) depends on gamma function
626 let sqrt_2_pi = (2.0 / PI).sqrt();
627 let gamma_term = gamma(v + 0.5);
628
629 // V_v(0) = sqrt(2/π) / Γ(v+1/2)
630 let v_at_zero = sqrt_2_pi / gamma_term;
631
632 // V_v'(0) depends on whether v is odd or even
633 let vp_at_zero = if v.floor() == v && v >= 0.0 {
634 if (v as i32) % 2 == 0 {
635 0.0 // Even v
636 } else {
637 // Odd v, calculate from recurrence relation
638 let mut val = sqrt_2_pi;
639 for k in 1..((v as i32 + 1) / 2) {
640 val *= (2 * k - 1) as f64 / 2.0;
641 }
642 val
643 }
644 } else {
645 // For non-integer v, approximate
646 0.0
647 };
648
649 return Ok((v_at_zero, vp_at_zero));
650 }
651
652 // Special case for very large |x|
653 if x.abs() > 100.0 {
654 // For very large |x|, use the asymptotic form instead
655 return pbvv_asymptotic(v, x);
656 }
657
658 // For normal range of x, use enhanced series method
659 let x2 = x * x;
660
661 // Use log-based calculation for exponential to avoid overflow
662 let log_exp_term = x2 / 4.0;
663
664 // Check for potential overflow
665 let exp_term = if log_exp_term > 700.0 {
666 // For very large x², handle the exponential carefully
667 // V_v(x) grows very large for large |x|
668 f64::INFINITY
669 } else {
670 log_exp_term.exp()
671 };
672
673 // Coefficient calculation in log space for better precision
674 let sqrt_2_pi = (2.0 / PI).sqrt();
675
676 // Handle potential issues with gamma function
677 let gamma_term = if v + 0.5 > 170.0 {
678 // For large v, use logarithmic gamma
679 gammaln(v + 0.5).exp()
680 } else {
681 gamma(v + 0.5)
682 };
683
684 // Check for potential division by zero or infinity
685 let leading = if gamma_term.abs() < 1e-300 {
686 f64::INFINITY
687 } else if gamma_term.is_infinite() {
688 0.0
689 } else {
690 sqrt_2_pi * exp_term / gamma_term
691 };
692
693 // Series for V_v(x)
694 let mut v_sum; // Will be set below
695 let mut vp_sum = 0.0;
696
697 // For numerical stability, track previous sums
698 let mut prev_v_sum = 0.0;
699 let mut prev_vp_sum = 0.0;
700 let mut stagnation_count = 0;
701
702 // Initial term
703 let mut term = 1.0;
704 let mut vp_term = 0.0;
705 v_sum = term;
706
707 // Extended iteration limit for challenging cases
708 for k in 1..80 {
709 // Calculate coefficient in log space for better precision
710 let k_f64 = k as f64;
711
712 if k <= 20 {
713 // For small k, direct calculation is stable
714 let coef = (k_f64 / 2.0).powi(k / 2) / factorial(k as usize);
715
716 // Calculate x^k safely
717 let x_pow_k = x.powi(k);
718 let new_term = coef * x_pow_k;
719
720 // For derivatives, we need x^(k-1)
721 let x_pow_kminus_1 = if k > 0 { x_pow_k / x } else { 1.0 };
722 let new_vp_term = coef * x_pow_kminus_1 * k_f64;
723
724 if new_term.is_finite() {
725 term = new_term;
726 v_sum += term;
727 }
728
729 if new_vp_term.is_finite() {
730 vp_term = new_vp_term;
731 vp_sum += vp_term;
732 }
733 } else {
734 // For large k, use logarithmic calculation
735 let log_coef = (k / 2) as f64 * (k_f64 / 2.0).ln() - log_factorial(k as usize);
736 let coef = log_coef.exp();
737
738 // For x^k
739 let sign_correction = if x < 0.0 && k % 2 == 1 { -1.0 } else { 1.0 };
740 let log_x_pow_k = k_f64 * x.abs().ln();
741 let x_pow_k = sign_correction * log_x_pow_k.exp();
742
743 let new_term = coef * x_pow_k;
744
745 // For derivatives
746 let sign_correctionminus_1 = if x < 0.0 && (k - 1) % 2 == 1 {
747 -1.0
748 } else {
749 1.0
750 };
751 let log_x_pow_kminus_1 = (k_f64 - 1.0) * x.abs().ln();
752 let x_pow_kminus_1 = sign_correctionminus_1 * log_x_pow_kminus_1.exp();
753 let new_vp_term = coef * x_pow_kminus_1 * k_f64;
754
755 if new_term.is_finite() {
756 term = new_term;
757 v_sum += term;
758 }
759
760 if new_vp_term.is_finite() {
761 vp_term = new_vp_term;
762 vp_sum += vp_term;
763 }
764 }
765
766 // Multiple convergence criteria
767
768 // Absolute tolerance
769 let abs_tol = 1e-15;
770
771 // Relative tolerance with protection against zero division
772 let v_rel_tol = 1e-15 * v_sum.abs().max(1e-300);
773 let vp_rel_tol = 1e-15 * vp_sum.abs().max(1e-300);
774
775 // First check: terms becoming small
776 if (term.abs() < abs_tol || term.abs() < v_rel_tol)
777 && (vp_term.abs() < abs_tol || vp_term.abs() < vp_rel_tol)
778 {
779 break;
780 }
781
782 // Second check: sum stagnation
783 if (v_sum - prev_v_sum).abs() < v_rel_tol && (vp_sum - prev_vp_sum).abs() < vp_rel_tol {
784 stagnation_count += 1;
785 if stagnation_count >= 3 {
786 break;
787 }
788 } else {
789 stagnation_count = 0;
790 }
791
792 // Safety check for very small terms
793 if k > 50 && term.abs() < 1e-30 && vp_term.abs() < 1e-30 {
794 break;
795 }
796
797 prev_v_sum = v_sum;
798 prev_vp_sum = vp_sum;
799 }
800
801 // Final multiplication with the leading term
802 let v_val = leading * v_sum;
803
804 // Derivative calculation: V_v'(x) = V_v(x) * x/2 + derivative of series
805 let vp_val = leading * (vp_sum + v_sum * x / 2.0);
806
807 // Check for numerical issues
808 if !v_val.is_finite() || !vp_val.is_finite() {
809 // Handle extreme cases
810 if x.abs() > 20.0 {
811 // For large |x|, V_v(x) grows very rapidly
812 let sign = if x >= 0.0 { 1.0 } else { -1.0 };
813 let v_approx = sign * f64::INFINITY;
814 let vp_approx = sign * f64::INFINITY;
815 return Ok((v_approx, vp_approx));
816 } else if v > 100.0 {
817 // For large v, approximate behavior
818 let v_approx = 0.0;
819 let vp_approx = 0.0;
820 return Ok((v_approx, vp_approx));
821 }
822 }
823
824 Ok((v_val, vp_val))
825}
826
827/// Asymptotic expansion for V_v(x) with enhanced numerical stability
828#[allow(dead_code)]
829fn pbvv_asymptotic(v: f64, x: f64) -> SpecialResult<(f64, f64)> {
830 // For extremely large |x|
831 if x.abs() > 100.0 {
832 // For very large |x|, V_v(x) grows exponentially
833 let sign = if x >= 0.0 { 1.0 } else { -1.0 };
834
835 // For extremely large |x|, V_v(x) approaches sign * infinity
836 if x.abs() > 700.0 {
837 return Ok((sign * f64::INFINITY, sign * f64::INFINITY));
838 }
839 }
840
841 // Scale to avoid overflow
842 let z = x.abs() / SQRT_2;
843 let v2 = v * v;
844 let sign = if x >= 0.0 { 1.0 } else { -1.0 };
845
846 // Calculate the asymptotic series in log space to avoid overflow/underflow
847 // Compute the sum for the asymptotic expansion with enhanced stability
848 let mut sum = 1.0;
849 let mut term = 1.0;
850 let mut prev_sum = 0.0;
851 let mut stagnation_count = 0;
852
853 // Keep track of the derivative term
854 let mut deriv_term = 0.0;
855
856 // Extended iteration limit for better accuracy
857 for k in 1..30 {
858 // Calculate the numerator carefully to avoid overflow
859 let numerator = v2 - (2 * k - 1) as f64 * (2 * k - 1) as f64;
860 let denominator = 2.0 * k as f64 * z * z;
861
862 // Check for potential numerical issues
863 if denominator.abs() < 1e-300 {
864 // Avoid division by zero
865 break;
866 }
867
868 let term_factor = numerator / denominator;
869 let new_term = term * term_factor;
870
871 // Check for numerical stability
872 if !new_term.is_finite() {
873 // If the term would cause overflow, stop here
874 break;
875 }
876
877 term = new_term;
878 sum += term;
879
880 // Save this term for derivative calculation
881 deriv_term = term;
882
883 // Multiple convergence criteria
884
885 // Absolute tolerance
886 let abs_tol = 1e-15;
887
888 // Relative tolerance with protection against zero division
889 let rel_tol = 1e-15 * sum.abs().max(1e-300);
890
891 // First convergence check
892 if term.abs() < abs_tol || term.abs() < rel_tol {
893 break;
894 }
895
896 // Check for series stagnation
897 if (sum - prev_sum).abs() < rel_tol {
898 stagnation_count += 1;
899 if stagnation_count >= 3 {
900 break;
901 }
902 } else {
903 stagnation_count = 0;
904 }
905
906 // Check for potential divergence in the asymptotic series
907 if k > 5 && term.abs() > prev_sum.abs() {
908 // Series may be starting to diverge, use the value before divergence
909 sum = prev_sum;
910 break;
911 }
912
913 prev_sum = sum;
914 }
915
916 // For V_v, the leading term differs from D_v
917 let _sqrt_2_pi = (2.0 / PI).sqrt();
918
919 // Carefully handle gamma function for large v
920 let gamma_term = if v + 0.5 > 170.0 {
921 // For large v, use logarithmic gamma
922 gammaln(v + 0.5).exp()
923 } else {
924 gamma(v + 0.5)
925 };
926
927 // Calculate in log space to avoid overflow/underflow
928 // log(V_v(x)) = log(sqrt(2/π)) + log(exp(z²/2)) - log(z^(v+0.5)) + log(sum) - log(gamma_term)
929 let log_sqrt_2_pi = (2.0 / PI).sqrt().ln();
930 let log_exp_term = z * z / 2.0;
931 let log_z_term = (v + 0.5) * z.ln();
932 let log_sum = sum.ln();
933 let log_gamma = gamma_term.ln();
934
935 // Combined log calculation
936 let log_v_val = log_sqrt_2_pi + log_exp_term - log_z_term + log_sum - log_gamma;
937
938 // Only exponentiate if it won't overflow/underflow
939 let v_val = if log_v_val > 700.0 {
940 sign * f64::INFINITY
941 } else if log_v_val < -700.0 {
942 0.0
943 } else {
944 sign * log_v_val.exp()
945 };
946
947 // Enhanced derivative calculation that handles extreme cases
948 let vp_val = if !v_val.is_finite() {
949 // For infinite function values, the derivative is also infinite
950 v_val
951 } else if v_val.abs() < 1e-300 {
952 // For very small function values, the derivative is also near zero
953 0.0
954 } else {
955 // Standard derivative calculation with correction term
956 let correction = if sum.abs() < 1e-300 {
957 0.0 // Avoid division by zero
958 } else {
959 deriv_term * z / sum
960 };
961
962 v_val * (sign * z + (v + 0.5) / z - correction)
963 };
964
965 // Final validation for potential numerical errors
966 if !v_val.is_finite() && x.abs() < 100.0 {
967 // For cases where we get overflow but x isn't extremely large
968 // This can happen for some combinations of v and x
969 // Provide a reasoned approximation
970 let asymptotic_sign = if x >= 0.0 { 1.0 } else { -1.0 };
971 let v_approx = asymptotic_sign * f64::MAX * 0.1; // Large but not infinity
972 let vp_approx = v_approx; // Derivative grows at similar rate
973
974 return Ok((v_approx, vp_approx));
975 }
976
977 Ok((v_val, vp_val))
978}
979
980/// Compute a sequence of parabolic cylinder functions D_v(x) for v = 0, 1, ..., vmax.
981///
982/// # Arguments
983///
984/// * `vmax` - Maximum order (non-negative integer)
985/// * `x` - Real argument
986///
987/// # Returns
988///
989/// * A tuple of two vectors containing (D_0(x)...D_vmax(x), D_0'(x)...D_vmax'(x))
990///
991/// # Examples
992///
993/// ```
994/// use scirs2_special::pbdv_seq;
995///
996/// let (d_values, dp_values) = pbdv_seq(3, 0.5).unwrap();
997/// println!("D_0(0.5) = {}, D_1(0.5) = {}, D_2(0.5) = {}, D_3(0.5) = {}",
998/// d_values[0], d_values[1], d_values[2], d_values[3]);
999/// ```
1000#[allow(dead_code)]
1001pub fn pbdv_seq(vmax: usize, x: f64) -> SpecialResult<(Vec<f64>, Vec<f64>)> {
1002 if x.is_nan() {
1003 return Err(SpecialError::DomainError(
1004 "NaN input to pbdv_seq".to_string(),
1005 ));
1006 }
1007
1008 if vmax == 0 {
1009 let (d0, d0p) = pbdv(0.0, x)?;
1010 return Ok((vec![d0], vec![d0p]));
1011 }
1012
1013 // Initialize arrays to hold function values and derivatives
1014 let mut d_values = vec![0.0; vmax + 1];
1015 let mut dp_values = vec![0.0; vmax + 1];
1016
1017 // Compute D_0 and D_1 directly
1018 let (d0, d0p) = pbdv(0.0, x)?;
1019 let (d1, d1p) = pbdv(1.0, x)?;
1020
1021 d_values[0] = d0;
1022 dp_values[0] = d0p;
1023
1024 if vmax >= 1 {
1025 d_values[1] = d1;
1026 dp_values[1] = d1p;
1027 }
1028
1029 // Use recurrence relation to compute higher orders
1030 for v in 2..=vmax {
1031 // Recurrence relation: D_{v+1}(x) = x*D_v(x) - v*D_{v-1}(x)
1032 d_values[v] = x * d_values[v - 1] - (v as f64 - 1.0) * d_values[v - 2];
1033 // Derivative recurrence: D'_{v+1}(x) = x*D'_v(x) - v*D'_{v-1}(x) + D_v(x)
1034 dp_values[v] = x * dp_values[v - 1] - (v as f64 - 1.0) * dp_values[v - 2] + d_values[v - 1];
1035 }
1036
1037 Ok((d_values, dp_values))
1038}
1039
1040/// Compute a sequence of parabolic cylinder functions V_v(x) for v = 0, 1, ..., vmax.
1041///
1042/// # Arguments
1043///
1044/// * `vmax` - Maximum order (non-negative integer)
1045/// * `x` - Real argument
1046///
1047/// # Returns
1048///
1049/// * A tuple of two vectors containing (V_0(x)...V_vmax(x), V_0'(x)...V_vmax'(x))
1050///
1051/// # Examples
1052///
1053/// ```
1054/// use scirs2_special::pbvv_seq;
1055///
1056/// let (v_values, vp_values) = pbvv_seq(3, 0.5).unwrap();
1057/// println!("V_0(0.5) = {}, V_1(0.5) = {}, V_2(0.5) = {}, V_3(0.5) = {}",
1058/// v_values[0], v_values[1], v_values[2], v_values[3]);
1059/// ```
1060#[allow(dead_code)]
1061pub fn pbvv_seq(vmax: usize, x: f64) -> SpecialResult<(Vec<f64>, Vec<f64>)> {
1062 if x.is_nan() {
1063 return Err(SpecialError::DomainError(
1064 "NaN input to pbvv_seq".to_string(),
1065 ));
1066 }
1067
1068 // Initialize arrays to hold function values and derivatives
1069 let mut v_values = vec![0.0; vmax + 1];
1070 let mut vp_values = vec![0.0; vmax + 1];
1071
1072 // Compute each V_v individually - this can be optimized further
1073 for v in 0..=vmax {
1074 let (v_val, vp_val) = pbvv(v as f64, x)?;
1075 v_values[v] = v_val;
1076 vp_values[v] = vp_val;
1077 }
1078
1079 Ok((v_values, vp_values))
1080}
1081
1082/// Compute parabolic cylinder function W(a,x) and its derivative.
1083///
1084/// The function W(a,x) is related to the parabolic cylinder functions
1085/// D_v(x) and satisfies the differential equation:
1086/// d²W/dx² + (a - x²/4)W = 0
1087///
1088/// # Arguments
1089///
1090/// * `a` - Parameter
1091/// * `x` - Real argument
1092///
1093/// # Returns
1094///
1095/// * A tuple containing (W(a,x), W'(a,x))
1096///
1097/// # Examples
1098///
1099/// ```
1100/// use scirs2_special::pbwa;
1101///
1102/// let (w, wp) = pbwa(1.0, 0.5).unwrap();
1103/// println!("W(1.0, 0.5) = {}, W'(1.0, 0.5) = {}", w, wp);
1104/// ```
1105#[allow(dead_code)]
1106pub fn pbwa(a: f64, x: f64) -> SpecialResult<(f64, f64)> {
1107 if a.is_nan() || x.is_nan() {
1108 return Err(SpecialError::DomainError("NaN input to pbwa".to_string()));
1109 }
1110
1111 // W(a,x) is related to D_v(x) by: W(a,x) = D_{-a-1/2}(x)
1112 let v = -a - 0.5;
1113 pbdv(v, x)
1114}
1115
1116// Helper function to calculate factorial with overflow protection
1117#[allow(dead_code)]
1118fn factorial(n: usize) -> f64 {
1119 if n <= 1 {
1120 return 1.0;
1121 }
1122
1123 // For small values, direct computation is fine
1124 if n <= 20 {
1125 let mut result = 1.0;
1126 for i in 2..=n {
1127 result *= i as f64;
1128 }
1129 return result;
1130 }
1131
1132 // For larger values, use the logarithmic approach to avoid overflow
1133 // Switch to gammaln for large factorials, since n! = Γ(n+1)
1134 let result = gammaln(n as f64 + 1.0).exp();
1135
1136 // Check if the result is finite
1137 if result.is_finite() {
1138 result
1139 } else {
1140 // For extremely large n that would overflow even with gammaln,
1141 // use Stirling's approximation: n! ≈ sqrt(2πn) * (n/e)^n
1142 let n_f64 = n as f64;
1143 (2.0 * PI * n_f64).sqrt() * (n_f64 / std::f64::consts::E).powf(n_f64)
1144 }
1145}
1146
1147// Helper function to approximate error function with improved accuracy and stability
1148#[allow(dead_code)]
1149fn erf_approx(x: f64) -> f64 {
1150 // Special cases
1151 if x == 0.0 {
1152 return 0.0;
1153 }
1154
1155 if !x.is_finite() {
1156 return if x.is_sign_positive() { 1.0 } else { -1.0 };
1157 }
1158
1159 // Extremely large values of |x|
1160 if x.abs() > 6.0 {
1161 return if x > 0.0 { 1.0 } else { -1.0 };
1162 }
1163
1164 // A more accurate approximation based on Abramowitz and Stegun (1964)
1165 // with optimized parameters for floating-point arithmetic stability
1166 if x.abs() <= 0.5 {
1167 // Use Taylor series expansion for small |x|
1168 // erf(x) ≈ (2/sqrt(π)) * x * (1 - x²/3 + x⁴/10 - x⁶/42 + x⁸/216 - ...)
1169 let x2 = x * x;
1170 let x4 = x2 * x2;
1171 let x6 = x4 * x2;
1172 let x8 = x4 * x4;
1173
1174 let two_over_sqrt_pi = 2.0 / PI.sqrt();
1175
1176 x * two_over_sqrt_pi * (1.0 - x2 / 3.0 + x4 / 10.0 - x6 / 42.0 + x8 / 216.0)
1177 } else {
1178 // For larger |x|, use the approximation from Numerical Recipes
1179 let z = x.abs();
1180 let t = 1.0 / (1.0 + 0.5 * z);
1181
1182 // More accurate coefficients for better stability
1183 let tau = t
1184 * (-z * z - 1.26551223
1185 + t * (1.00002368
1186 + t * (0.37409196
1187 + t * (0.09678418
1188 + t * (-0.18628806
1189 + t * (0.27886807
1190 + t * (-1.13520398
1191 + t * (1.48851587
1192 + t * (-0.82215223 + t * 0.17087277)))))))));
1193
1194 let result = if x >= 0.0 { 1.0 - tau } else { tau - 1.0 };
1195
1196 // Check for underflow or near-zero values
1197 if result.abs() < 1e-16 {
1198 if x >= 0.0 {
1199 1.0
1200 } else {
1201 -1.0
1202 }
1203 } else {
1204 result
1205 }
1206 }
1207}
1208
1209// Log factorial function for use with very large factorials
1210#[allow(dead_code)]
1211fn log_factorial(n: usize) -> f64 {
1212 if n <= 1 {
1213 return 0.0; // log(1) = 0
1214 }
1215
1216 // Use gammaln for log(n!)
1217 gammaln(n as f64 + 1.0)
1218}