use crate::airy;
use crate::error::{SpecialError, SpecialResult};
use crate::painleve::solver::solve_painleve;
use crate::painleve::types::{PainleveConfig, PainleveEquation};
pub fn hastings_mcleod(t: f64) -> SpecialResult<f64> {
if t >= 5.0 {
return Ok(ai(t));
}
let t_start = 5.0;
let y_start = ai(t_start);
let dy_start = aip(t_start);
let config = PainleveConfig {
equation: PainleveEquation::PII { alpha: 0.0 },
t_start,
t_end: t,
y0: y_start,
dy0: dy_start,
tolerance: 1e-12,
max_steps: 500_000,
pole_threshold: 1e10,
};
let sol = solve_painleve(&config)?;
if sol.y_values.is_empty() {
return Err(SpecialError::ComputationError(
"Hastings-McLeod solver produced no output".to_string(),
));
}
Ok(sol.y_values[sol.y_values.len() - 1])
}
pub fn ablowitz_segur(t: f64, k: f64) -> SpecialResult<f64> {
if k.abs() >= 1.0 {
return Err(SpecialError::ValueError(format!(
"Ablowitz-Segur parameter k must satisfy |k| < 1, got k={k}"
)));
}
if t >= 5.0 {
return Ok(k * ai(t));
}
let t_start = 5.0;
let y_start = k * ai(t_start);
let dy_start = k * aip(t_start);
let config = PainleveConfig {
equation: PainleveEquation::PII { alpha: 0.0 },
t_start,
t_end: t,
y0: y_start,
dy0: dy_start,
tolerance: 1e-12,
max_steps: 500_000,
pole_threshold: 1e10,
};
let sol = solve_painleve(&config)?;
if sol.y_values.is_empty() {
return Err(SpecialError::ComputationError(
"Ablowitz-Segur solver produced no output".to_string(),
));
}
Ok(sol.y_values[sol.y_values.len() - 1])
}
pub fn painleve_i_tritronquee(t: f64) -> SpecialResult<f64> {
if t < -20.0 {
return Ok(tritronquee_asymptotic(t));
}
let t_start = -30.0;
let y_start = tritronquee_asymptotic(t_start);
let dy_start = tritronquee_derivative_asymptotic(t_start);
let config = PainleveConfig {
equation: PainleveEquation::PI,
t_start,
t_end: t,
y0: y_start,
dy0: dy_start,
tolerance: 1e-12,
max_steps: 500_000,
pole_threshold: 1e10,
};
let sol = solve_painleve(&config)?;
if sol.y_values.is_empty() {
return Err(SpecialError::ComputationError(
"Tritronquee solver produced no output".to_string(),
));
}
if !sol.poles.is_empty() && !sol.converged {
return Err(SpecialError::ComputationError(format!(
"Tritronquee solution encountered a pole near t={}",
sol.poles[0]
)));
}
Ok(sol.y_values[sol.y_values.len() - 1])
}
pub fn painleve_iv_special(t: f64, n: u32) -> SpecialResult<f64> {
if n == 0 {
let alpha = -(2.0 * f64::from(n) + 1.0);
let beta = -2.0 * (f64::from(n)) * (f64::from(n));
return solve_piv_from_asymptotic(t, alpha, beta);
}
let alpha = -(2.0 * f64::from(n) + 1.0);
let beta = -2.0 * f64::from(n) * f64::from(n);
solve_piv_from_asymptotic(t, alpha, beta)
}
fn solve_piv_from_asymptotic(t: f64, alpha: f64, beta: f64) -> SpecialResult<f64> {
let t_start = -20.0;
let y_start = -2.0 * t_start - (2.0 * alpha + 1.0) / (2.0 * t_start);
let dy_start = -2.0 + (2.0 * alpha + 1.0) / (2.0 * t_start * t_start);
let config = PainleveConfig {
equation: PainleveEquation::PIV { alpha, beta },
t_start,
t_end: t,
y0: y_start,
dy0: dy_start,
tolerance: 1e-10,
max_steps: 500_000,
pole_threshold: 1e10,
};
let sol = solve_painleve(&config)?;
if sol.y_values.is_empty() {
return Err(SpecialError::ComputationError(
"P-IV special solution solver produced no output".to_string(),
));
}
Ok(sol.y_values[sol.y_values.len() - 1])
}
fn ai(t: f64) -> f64 {
airy::ai(t)
}
fn aip(t: f64) -> f64 {
airy::aip(t)
}
fn tritronquee_asymptotic(t: f64) -> f64 {
if t >= 0.0 {
return 0.0;
}
let mt = -t; let leading = -(mt / 6.0).sqrt();
let correction = 1.0 / (48.0 * mt * mt * 6.0_f64.sqrt());
leading * (1.0 - correction)
}
fn tritronquee_derivative_asymptotic(t: f64) -> f64 {
if t >= 0.0 {
return 0.0;
}
let mt = -t; let sqrt6 = 6.0_f64.sqrt();
let leading = 1.0 / (2.0 * sqrt6 * mt.sqrt());
let correction = 3.0 / (2.0 * 48.0 * 6.0 * mt.powf(2.5));
leading + correction
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hastings_mcleod_large_positive_t() {
let t = 8.0;
let hm = hastings_mcleod(t);
assert!(hm.is_ok());
let hm_val = hm.expect("should succeed");
let ai_val = ai(t);
assert!(
(hm_val - ai_val).abs() < 1e-15,
"H-M({t}) = {hm_val}, Ai({t}) = {ai_val}"
);
}
#[test]
fn test_hastings_mcleod_moderate_t() {
let hm = hastings_mcleod(3.0);
assert!(hm.is_ok());
let val = hm.expect("should succeed");
let ai_val = ai(3.0);
assert!(
val > 0.0 && val < 0.1,
"H-M(3) = {val}, should be small and positive"
);
let rel_err = ((val - ai_val) / ai_val).abs();
assert!(
rel_err < 0.25,
"H-M(3) = {val}, Ai(3) = {ai_val}, rel_err = {rel_err}"
);
}
#[test]
fn test_hastings_mcleod_near_zero() {
let hm = hastings_mcleod(0.0);
assert!(hm.is_ok());
let val = hm.expect("should succeed");
assert!(
val > 0.0 && val < 2.0,
"H-M(0) should be moderate and positive, got {val}"
);
}
#[test]
fn test_hastings_mcleod_positivity() {
for &t in &[5.0, 4.0, 3.0, 2.0] {
let hm = hastings_mcleod(t);
if let Ok(val) = hm {
assert!(val > -1e-6, "H-M({t}) should be positive, got {val}");
}
}
}
#[test]
fn test_ablowitz_segur_k_zero() {
let t = 5.0;
let val = ablowitz_segur(t, 0.0);
assert!(val.is_ok());
let v = val.expect("should succeed");
assert!(v.abs() < 1e-10, "A-S(t, k=0) should be ~0, got {v}");
}
#[test]
fn test_ablowitz_segur_invalid_k() {
let val = ablowitz_segur(0.0, 1.5);
assert!(val.is_err());
}
#[test]
fn test_ablowitz_segur_small_k() {
let t = 12.0;
let k = 0.5;
let val = ablowitz_segur(t, k);
assert!(val.is_ok());
let v = val.expect("should succeed");
let expected = k * ai(t);
let err = (v - expected).abs();
assert!(
err < 1e-6,
"A-S({t}, {k}) = {v}, expected {expected}, err = {err}"
);
}
#[test]
fn test_tritronquee_large_negative_t() {
let t = -50.0;
let val = painleve_i_tritronquee(t);
assert!(val.is_ok());
let v = val.expect("should succeed");
let asymp = -((-t) / 6.0).sqrt();
let rel_err = ((v - asymp) / asymp).abs();
assert!(
rel_err < 0.01,
"tritronquee({t}) = {v}, asymptotic = {asymp}, rel_err = {rel_err}"
);
}
#[test]
fn test_tritronquee_moderate_negative_t() {
let t = -5.0;
let val = painleve_i_tritronquee(t);
assert!(val.is_ok());
let v = val.expect("should succeed");
assert!(v < 0.0, "tritronquee({t}) should be negative, got {v}");
}
#[test]
fn test_tritronquee_asymptotic_derivative() {
let t = -200.0;
let eps = 1e-4;
let y1 = tritronquee_asymptotic(t - eps);
let y2 = tritronquee_asymptotic(t + eps);
let numerical_deriv = (y2 - y1) / (2.0 * eps);
let analytic_deriv = tritronquee_derivative_asymptotic(t);
let rel_err = if analytic_deriv.abs() > 1e-30 {
((numerical_deriv - analytic_deriv) / analytic_deriv).abs()
} else {
(numerical_deriv - analytic_deriv).abs()
};
assert!(
rel_err < 1e-4,
"derivative mismatch: numerical={numerical_deriv}, analytic={analytic_deriv}, rel_err={rel_err}"
);
}
#[test]
fn test_painleve_iv_special_n0() {
let val = painleve_iv_special(-5.0, 0);
assert!(val.is_ok());
}
}