Finished bs3 (at least for now)

This commit is contained in:
Connor Johnstone
2025-10-24 10:32:32 -04:00
parent bd6f3b8ee4
commit e1e6f8b4bb
7 changed files with 790 additions and 62 deletions

View File

@@ -295,4 +295,115 @@ mod tests {
// Cubic Hermite interpolation should be quite accurate
assert_relative_eq!(y_mid[0], exact, max_relative = 1e-10);
}
#[test]
fn test_bs3_accuracy() {
// Test BS3 on a simple problem with known solution
// y' = -y, y(0) = 1, solution is y(t) = e^(-t)
type Params = ();
fn derivative(_t: f64, y: Vector1<f64>, _p: &Params) -> Vector1<f64> {
Vector1::new(-y[0])
}
let y0 = Vector1::new(1.0);
let bs3 = BS3::new().a_tol(1e-10).r_tol(1e-10);
let h = 0.01;
// Take 100 steps to reach t = 1.0
let mut ode = ODE::new(&derivative, 0.0, 1.0, y0, ());
for _ in 0..100 {
let (y_new, _, _) = bs3.step(&ode, h);
ode.y = y_new;
ode.t += h;
}
// At t=1.0, exact solution is e^(-1) ≈ 0.36787944117
let exact = (-1.0_f64).exp();
assert_relative_eq!(ode.y[0], exact, max_relative = 1e-7);
}
#[test]
fn test_bs3_convergence() {
// Test that BS3 achieves 3rd order convergence
// For a 3rd order method, halving h should reduce error by factor of ~2^3 = 8
type Params = ();
fn derivative(_t: f64, y: Vector1<f64>, _p: &Params) -> Vector1<f64> {
Vector1::new(y[0]) // y' = y, solution is e^t
}
let bs3 = BS3::new();
let t_start = 0.0;
let t_end = 1.0;
let y0 = Vector1::new(1.0);
// Test with different step sizes
let step_sizes = [0.1, 0.05, 0.025];
let mut errors = Vec::new();
for &h in &step_sizes {
let mut ode = ODE::new(&derivative, t_start, t_end, y0, ());
// Take steps until we reach t_end
while ode.t < t_end - 1e-10 {
let (y_new, _, _) = bs3.step(&ode, h);
ode.y = y_new;
ode.t += h;
}
// Compute error at final time
let exact = t_end.exp();
let error = (ode.y[0] - exact).abs();
errors.push(error);
}
// Check convergence rate between consecutive step sizes
for i in 0..errors.len() - 1 {
let ratio = errors[i] / errors[i + 1];
// For order 3, we expect ratio ≈ 2^3 = 8 (since we halve the step size)
// Allow some tolerance due to floating point arithmetic
assert!(
ratio > 6.0 && ratio < 10.0,
"Expected convergence ratio ~8, got {:.2}",
ratio
);
}
// The error should decrease as step size decreases
for i in 0..errors.len() - 1 {
assert!(errors[i] > errors[i + 1]);
}
}
#[test]
fn test_bs3_fsal_property() {
// Test that BS3 correctly implements the FSAL (First Same As Last) property
// The last function evaluation of one step should equal the first of the next
type Params = ();
fn derivative(_t: f64, y: Vector1<f64>, _p: &Params) -> Vector1<f64> {
Vector1::new(2.0 * y[0]) // y' = 2y
}
let y0 = Vector1::new(1.0);
let bs3 = BS3::new();
let h = 0.1;
// First step
let ode1 = ODE::new(&derivative, 0.0, 1.0, y0, ());
let (y_new1, _, dense1) = bs3.step(&ode1, h);
let dense1 = dense1.unwrap();
// Extract f1 from first step (derivative at end of step)
let f1_end = &dense1[3]; // f(t1, y1)
// Second step starts where first ended
let ode2 = ODE::new(&derivative, h, 1.0, y_new1, ());
let (_, _, dense2) = bs3.step(&ode2, h);
let dense2 = dense2.unwrap();
// Extract f0 from second step (derivative at start of step)
let f0_start = &dense2[2]; // f(t0, y0) of second step
// These should be equal (FSAL property)
assert_relative_eq!(f1_end[0], f0_start[0], max_relative = 1e-14);
}
}