1pub fn solve(f: &dyn Fn(f64, f64) -> f64, x0: f64, y0: f64, x_end: f64, n: usize) -> (Vec<f64>, Vec<f64>) {
32 assert!(n >= 1, "number of steps must be ≥ 1");
33 let h = (x_end - x0) / n as f64;
34 let mut xs = Vec::with_capacity(n + 1);
35 let mut ys = Vec::with_capacity(n + 1);
36 xs.push(x0);
37 ys.push(y0);
38 let mut x = x0;
39 let mut y = y0;
40 for _ in 0..n {
41 y = y + h * f(x, y);
42 x += h;
43 xs.push(x);
44 ys.push(y);
45 }
46 (xs, ys)
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52
53 #[test]
55 fn constant_rhs() {
56 let f = |_x: f64, _y: f64| 0.0_f64;
57 let (_xs, ys) = solve(&f, 0.0, 5.0, 1.0, 10);
58 assert_eq!(ys.len(), 11);
59 for &y in &ys {
60 assert!((y - 5.0).abs() < 1e-12, "y = {y}, expected 5.0");
61 }
62 }
63
64 #[test]
66 fn linear_rhs() {
67 let f = |_x: f64, _y: f64| 1.0;
68 let (_, ys) = solve(&f, 0.0, 0.0, 2.0, 200);
69 assert!((ys[200] - 2.0).abs() < 1e-4);
70 }
71
72 #[test]
74 fn exponential_growth() {
75 let f = |_x: f64, y: f64| y;
76 let (_, ys) = solve(&f, 0.0, 1.0, 1.0, 10_000);
77 let exact = 1.0_f64.exp();
78 assert!((ys[10_000] - exact).abs() < 2e-4, "got {}, exact {}", ys[10_000], exact);
79 }
80
81 #[test]
83 fn convergence_order_euler() {
84 let f = |_x: f64, y: f64| y;
85 let exact = 1.0_f64.exp();
86 let e1 = (solve(&f, 0.0, 1.0, 1.0, 100).1[100] - exact).abs();
87 let e2 = (solve(&f, 0.0, 1.0, 1.0, 200).1[200] - exact).abs();
88 let ratio = e1 / e2;
89 assert!(ratio > 1.6 && ratio < 2.6, "convergence ratio = {ratio}, expected ~2");
91 }
92
93 #[test]
95 fn exponential_decay() {
96 let f = |_x: f64, y: f64| -y;
97 let (_, ys) = solve(&f, 0.0, 1.0, 1.0, 10_000);
98 let exact = (-1.0_f64).exp();
99 assert!((ys[10_000] - exact).abs() < 1e-4);
100 }
101
102 #[test]
104 fn quadratic_rhs() {
105 let f = |x: f64, _y: f64| 2.0 * x;
106 let (_, ys) = solve(&f, 0.0, 0.0, 3.0, 10_000);
107 let exact = 9.0;
108 assert!((ys[10_000] - exact).abs() < 1e-2);
109 }
110
111 #[test]
112 #[should_panic(expected = "number of steps must be ≥ 1")]
113 fn panics_on_zero_steps() {
114 let f = |_x, _y| 0.0;
115 solve(&f, 0.0, 1.0, 1.0, 0);
116 }
117
118 #[test]
119 fn single_step() {
120 let f = |_x, y| y;
121 let (xs, ys) = solve(&f, 0.0, 1.0, 1.0, 1);
122 assert_eq!(xs.len(), 2);
123 assert_eq!(ys.len(), 2);
124 assert!((ys[1] - 2.0).abs() < 1e-12);
126 }
127
128 #[test]
129 fn negative_direction() {
130 let f = |_x, y| y;
132 let (_, ys) = solve(&f, 1.0, 1.0_f64.exp(), 0.0, 10_000);
133 assert!((ys[10_000] - 1.0).abs() < 1e-3);
135 }
136
137 #[test]
139 fn trigonometric_rhs() {
140 let f = |x: f64, _y: f64| x.cos();
141 let (_, ys) = solve(&f, 0.0, 0.0, std::f64::consts::PI / 2.0, 10_000);
142 let exact = 1.0;
143 assert!((ys[10_000] - exact).abs() < 1e-3);
144 }
145}