numerics_ode/
adams_bashforth.rs1pub fn solve(f: &dyn Fn(f64, f64) -> f64, x0: f64, y0: f64, x_end: f64, n: usize) -> (Vec<f64>, Vec<f64>) {
27 assert!(n >= 1, "number of steps must be ≥ 1");
28 let h = (x_end - x0) / n as f64;
29 let mut xs = Vec::with_capacity(n + 1);
30 let mut ys = Vec::with_capacity(n + 1);
31 xs.push(x0);
32 ys.push(y0);
33
34 if n == 1 {
35 let y1 = y0 + h * f(x0, y0);
37 xs.push(x0 + h);
38 ys.push(y1);
39 return (xs, ys);
40 }
41
42 let x0_val = x0;
44 let y0_val = y0;
45 let k1 = f(x0_val, y0_val);
46 let k2 = f(x0_val + h / 2.0, y0_val + h * k1 / 2.0);
47 let k3 = f(x0_val + h / 2.0, y0_val + h * k2 / 2.0);
48 let k4 = f(x0_val + h, y0_val + h * k3);
49 let y1 = y0_val + (h / 6.0) * (k1 + 2.0 * k2 + 2.0 * k3 + k4);
50 let x1 = x0_val + h;
51 xs.push(x1);
52 ys.push(y1);
53
54 let mut f_prev = f(x0_val, y0_val);
55 let mut x_curr = x1;
56 let mut y_curr = y1;
57
58 for _ in 1..n {
59 let f_curr = f(x_curr, y_curr);
60 let y_next = y_curr + (h / 2.0) * (3.0 * f_curr - f_prev);
61 x_curr += h;
62 xs.push(x_curr);
63 ys.push(y_next);
64 f_prev = f_curr;
65 y_curr = y_next;
66 }
67
68 (xs, ys)
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn exponential_growth() {
77 let f = |_x: f64, y: f64| y;
78 let (_, ys) = solve(&f, 0.0, 1.0, 1.0, 100);
79 let exact = 1.0_f64.exp();
80 assert!((ys[100] - exact).abs() < 5e-4);
81 }
82
83 #[test]
84 fn convergence_order() {
85 let f = |_x: f64, y: f64| y;
86 let exact = 1.0_f64.exp();
87 let e1 = (solve(&f, 0.0, 1.0, 1.0, 50).1[50] - exact).abs();
88 let e2 = (solve(&f, 0.0, 1.0, 1.0, 100).1[100] - exact).abs();
89 let ratio = e1 / e2;
90 assert!(ratio > 2.5 && ratio < 7.0, "ratio = {ratio}");
92 }
93
94 #[test]
95 fn constant_rhs() {
96 let f = |_x, _y| 0.0;
97 let (_, ys) = solve(&f, 0.0, 10.0, 1.0, 10);
98 for &y in &ys {
99 assert!((y - 10.0).abs() < 1e-12);
100 }
101 }
102
103 #[test]
104 fn linear_rhs() {
105 let f = |_x, _y| 3.0;
106 let (_, ys) = solve(&f, 0.0, 0.0, 2.0, 100);
107 assert!((ys[100] - 6.0).abs() < 1e-8);
108 }
109
110 #[test]
111 fn sinusoidal_rhs() {
112 let f = |x: f64, _y: f64| x.cos();
113 let (_, ys) = solve(&f, 0.0, 0.0, std::f64::consts::PI / 2.0, 200);
114 assert!((ys[200] - 1.0).abs() < 1e-4);
115 }
116
117 #[test]
118 fn exponential_decay() {
119 let f = |_x: f64, y: f64| -y;
120 let (_, ys) = solve(&f, 0.0, 1.0, 1.0, 200);
121 assert!((ys[200] - (-1.0_f64).exp()).abs() < 1e-5);
122 }
123
124 #[test]
125 fn single_step_fallback() {
126 let f = |_x, y| y;
127 let (xs, ys) = solve(&f, 0.0, 1.0, 1.0, 1);
128 assert_eq!(xs.len(), 2);
129 assert_eq!(ys.len(), 2);
130 }
131
132 #[test]
133 #[should_panic(expected = "number of steps must be ≥ 1")]
134 fn panics_on_zero_steps() {
135 let f = |_x, _y| 0.0;
136 solve(&f, 0.0, 1.0, 1.0, 0);
137 }
138
139 #[test]
140 fn quadratic_rhs() {
141 let f = |x: f64, _y: f64| 2.0 * x;
142 let (_, ys) = solve(&f, 0.0, 0.0, 3.0, 200);
143 assert!((ys[200] - 9.0).abs() < 1e-4);
144 }
145
146 #[test]
147 fn negative_direction() {
148 let f = |_x, y| y;
149 let (_, ys) = solve(&f, 1.0, 1.0_f64.exp(), 0.0, 200);
150 assert!((ys[200] - 1.0).abs() < 1e-3);
151 }
152}