use std::fmt;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum PainleveEquation {
PI,
PII {
alpha: f64,
},
PIII {
alpha: f64,
beta: f64,
gamma: f64,
delta: f64,
},
PIV {
alpha: f64,
beta: f64,
},
PV {
alpha: f64,
beta: f64,
gamma: f64,
delta: f64,
},
PVI {
alpha: f64,
beta: f64,
gamma: f64,
delta: f64,
},
}
impl fmt::Display for PainleveEquation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PainleveEquation::PI => write!(f, "Painleve I"),
PainleveEquation::PII { alpha } => write!(f, "Painleve II (alpha={alpha})"),
PainleveEquation::PIII {
alpha,
beta,
gamma,
delta,
} => write!(
f,
"Painleve III (alpha={alpha}, beta={beta}, gamma={gamma}, delta={delta})"
),
PainleveEquation::PIV { alpha, beta } => {
write!(f, "Painleve IV (alpha={alpha}, beta={beta})")
}
PainleveEquation::PV {
alpha,
beta,
gamma,
delta,
} => write!(
f,
"Painleve V (alpha={alpha}, beta={beta}, gamma={gamma}, delta={delta})"
),
PainleveEquation::PVI {
alpha,
beta,
gamma,
delta,
} => write!(
f,
"Painleve VI (alpha={alpha}, beta={beta}, gamma={gamma}, delta={delta})"
),
}
}
}
#[derive(Debug, Clone)]
pub struct PainleveConfig {
pub equation: PainleveEquation,
pub t_start: f64,
pub t_end: f64,
pub y0: f64,
pub dy0: f64,
pub tolerance: f64,
pub max_steps: usize,
pub pole_threshold: f64,
}
impl Default for PainleveConfig {
fn default() -> Self {
Self {
equation: PainleveEquation::PI,
t_start: 0.0,
t_end: 1.0,
y0: 0.0,
dy0: 0.0,
tolerance: 1e-10,
max_steps: 100_000,
pole_threshold: 1e10,
}
}
}
#[derive(Debug, Clone)]
pub struct PainleveSolution {
pub t_values: Vec<f64>,
pub y_values: Vec<f64>,
pub dy_values: Vec<f64>,
pub poles: Vec<f64>,
pub converged: bool,
pub steps_taken: usize,
}
impl PainleveSolution {
pub fn interpolate(&self, t: f64) -> Option<f64> {
if self.t_values.is_empty() {
return None;
}
let t0 = self.t_values[0];
let tn = self.t_values[self.t_values.len() - 1];
let (t_lo, t_hi) = if t0 <= tn { (t0, tn) } else { (tn, t0) };
if t < t_lo - 1e-14 || t > t_hi + 1e-14 {
return None;
}
let ascending = t0 <= tn;
let idx = if ascending {
match self
.t_values
.binary_search_by(|v| v.partial_cmp(&t).unwrap_or(std::cmp::Ordering::Equal))
{
Ok(i) => return Some(self.y_values[i]),
Err(i) => i,
}
} else {
match self
.t_values
.binary_search_by(|v| t.partial_cmp(v).unwrap_or(std::cmp::Ordering::Equal))
{
Ok(i) => return Some(self.y_values[i]),
Err(i) => i,
}
};
if idx == 0 {
return Some(self.y_values[0]);
}
if idx >= self.t_values.len() {
return Some(self.y_values[self.t_values.len() - 1]);
}
let t0_local = self.t_values[idx - 1];
let t1_local = self.t_values[idx];
let y0_local = self.y_values[idx - 1];
let y1_local = self.y_values[idx];
let frac = (t - t0_local) / (t1_local - t0_local);
Some(y0_local + frac * (y1_local - y0_local))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_painleve_equation_display() {
let eq = PainleveEquation::PI;
assert_eq!(format!("{eq}"), "Painleve I");
let eq2 = PainleveEquation::PII { alpha: 0.5 };
assert!(format!("{eq2}").contains("alpha=0.5"));
}
#[test]
fn test_default_config() {
let cfg = PainleveConfig::default();
assert_eq!(cfg.t_start, 0.0);
assert_eq!(cfg.t_end, 1.0);
assert_eq!(cfg.tolerance, 1e-10);
assert_eq!(cfg.max_steps, 100_000);
}
#[test]
fn test_solution_interpolate_empty() {
let sol = PainleveSolution {
t_values: vec![],
y_values: vec![],
dy_values: vec![],
poles: vec![],
converged: true,
steps_taken: 0,
};
assert!(sol.interpolate(0.5).is_none());
}
#[test]
fn test_solution_interpolate_basic() {
let sol = PainleveSolution {
t_values: vec![0.0, 1.0, 2.0],
y_values: vec![0.0, 1.0, 4.0],
dy_values: vec![0.0, 1.0, 4.0],
poles: vec![],
converged: true,
steps_taken: 2,
};
let v = sol.interpolate(1.0);
assert!((v.unwrap_or(f64::NAN) - 1.0).abs() < 1e-14);
let v2 = sol.interpolate(0.5);
assert!((v2.unwrap_or(f64::NAN) - 0.5).abs() < 1e-14);
}
#[test]
fn test_solution_interpolate_out_of_range() {
let sol = PainleveSolution {
t_values: vec![0.0, 1.0],
y_values: vec![0.0, 1.0],
dy_values: vec![0.0, 1.0],
poles: vec![],
converged: true,
steps_taken: 1,
};
assert!(sol.interpolate(-1.0).is_none());
assert!(sol.interpolate(2.0).is_none());
}
#[test]
fn test_painleve_equation_clone() {
let eq = PainleveEquation::PIII {
alpha: 1.0,
beta: -1.0,
gamma: 1.0,
delta: -1.0,
};
let eq2 = eq.clone();
assert_eq!(eq, eq2);
}
}