Finished bs3 (at least for now)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user