qualia_core_db/solvers/calculus/
dense.rs1use crate::solvers::linear_algebra::lu::lu_solve;
12
13pub fn rk4_step<F>(f: &F, t: f64, y: &[f64], h: f64) -> Vec<f64>
16where
17 F: Fn(f64, &[f64]) -> Vec<f64>,
18{
19 let n = y.len();
20 let k1 = f(t, y);
21 let y2: Vec<f64> = (0..n).map(|i| y[i] + 0.5 * h * k1[i]).collect();
22 let k2 = f(t + 0.5 * h, &y2);
23 let y3: Vec<f64> = (0..n).map(|i| y[i] + 0.5 * h * k2[i]).collect();
24 let k3 = f(t + 0.5 * h, &y3);
25 let y4: Vec<f64> = (0..n).map(|i| y[i] + h * k3[i]).collect();
26 let k4 = f(t + h, &y4);
27 (0..n)
28 .map(|i| y[i] + h * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]) / 6.0)
29 .collect()
30}
31
32pub fn rk4_integrate<F>(f: &F, t0: f64, y0: &[f64], t1: f64, steps: usize) -> Vec<f64>
35where
36 F: Fn(f64, &[f64]) -> Vec<f64>,
37{
38 let steps = steps.max(1);
39 let h = (t1 - t0) / steps as f64;
40 let mut y = y0.to_vec();
41 let mut t = t0;
42 for _ in 0..steps {
43 y = rk4_step(f, t, &y, h);
44 t += h;
45 }
46 y
47}
48
49pub fn rk4_solve<F>(f: &F, t0: f64, y0: &[f64], t1: f64, steps: usize) -> Vec<(f64, Vec<f64>)>
51where
52 F: Fn(f64, &[f64]) -> Vec<f64>,
53{
54 let steps = steps.max(1);
55 let h = (t1 - t0) / steps as f64;
56 let mut out = Vec::with_capacity(steps + 1);
57 let mut y = y0.to_vec();
58 let mut t = t0;
59 out.push((t, y.clone()));
60 for _ in 0..steps {
61 y = rk4_step(f, t, &y, h);
62 t += h;
63 out.push((t, y.clone()));
64 }
65 out
66}
67
68pub fn simpson<F: Fn(f64) -> f64>(f: &F, a: f64, b: f64, panels: usize) -> f64 {
71 let n = even_panels(panels);
72 let h = (b - a) / n as f64;
73 let mut sum = f(a) + f(b);
74 for i in 1..n {
75 let x = a + i as f64 * h;
76 sum += if i % 2 == 1 { 4.0 } else { 2.0 } * f(x);
77 }
78 sum * h / 3.0
79}
80
81pub fn simpson_vec<F: Fn(f64) -> Vec<f64>>(g: &F, a: f64, b: f64, panels: usize) -> Vec<f64> {
85 let n = even_panels(panels);
86 let h = (b - a) / n as f64;
87 let ga = g(a);
88 let gb = g(b);
89 let k = ga.len();
90 let mut acc: Vec<f64> = (0..k).map(|j| ga[j] + gb[j]).collect();
91 for i in 1..n {
92 let x = a + i as f64 * h;
93 let w = if i % 2 == 1 { 4.0 } else { 2.0 };
94 let gx = g(x);
95 for j in 0..k {
96 acc[j] += w * gx[j];
97 }
98 }
99 acc.iter().map(|v| v * h / 3.0).collect()
100}
101
102fn even_panels(panels: usize) -> usize {
103 let n = panels.max(2);
104 if n % 2 == 0 {
105 n
106 } else {
107 n + 1
108 }
109}
110
111pub fn shooting_bvp<F, R>(
121 f: &F,
122 t0: f64,
123 t1: f64,
124 steps: usize,
125 y0_init: &[f64],
126 free: &[usize],
127 residual: &R,
128 tol: f64,
129 max_iter: usize,
130) -> Option<Vec<f64>>
131where
132 F: Fn(f64, &[f64]) -> Vec<f64>,
133 R: Fn(&[f64]) -> Vec<f64>,
134{
135 let m = free.len();
136 if m == 0 || free.iter().any(|&i| i >= y0_init.len()) {
137 return None;
138 }
139 let mut y0 = y0_init.to_vec();
140
141 for _ in 0..max_iter {
142 let yf = rk4_integrate(f, t0, &y0, t1, steps);
143 let r = residual(&yf);
144 if r.len() != m {
145 return None;
146 }
147 let rnorm = r.iter().fold(0.0_f64, |a, &v| a.max(v.abs()));
148 if rnorm < tol {
149 return Some(y0);
150 }
151
152 let mut jac = vec![0.0; m * m];
154 for (j, &idx) in free.iter().enumerate() {
155 let h = 1e-6 * y0[idx].abs().max(1e-3);
156 let mut yp = y0.clone();
157 yp[idx] += h;
158 let yfp = rk4_integrate(f, t0, &yp, t1, steps);
159 let rp = residual(&yfp);
160 if rp.len() != m {
161 return None;
162 }
163 for i in 0..m {
164 jac[i * m + j] = (rp[i] - r[i]) / h;
165 }
166 }
167
168 let neg_r: Vec<f64> = r.iter().map(|v| -v).collect();
170 let delta = lu_solve(m, &jac, &neg_r)?;
171 for (j, &idx) in free.iter().enumerate() {
172 y0[idx] += delta[j];
173 }
174 }
175 None
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use core::f64::consts::PI;
182
183 #[test]
184 fn rk4_solves_a_3d_linear_system() {
185 let f = |_t: f64, y: &[f64]| vec![y[1], -y[0], -y[2]];
188 let yf = rk4_integrate(&f, 0.0, &[0.0, 1.0, 1.0], PI / 2.0, 2000);
189 assert!((yf[0] - 1.0).abs() < 1e-6, "y0 = {}", yf[0]);
190 assert!(yf[1].abs() < 1e-6, "y1 = {}", yf[1]);
191 assert!((yf[2] - (-PI / 2.0).exp()).abs() < 1e-6, "y2 = {}", yf[2]);
192 }
193
194 #[test]
195 fn rk4_trajectory_has_all_points() {
196 let f = |_t: f64, y: &[f64]| vec![-y[0]];
197 let traj = rk4_solve(&f, 0.0, &[1.0], 1.0, 10);
198 assert_eq!(traj.len(), 11);
199 assert_eq!(traj[0].0, 0.0);
200 assert!((traj.last().unwrap().1[0] - (-1.0_f64).exp()).abs() < 1e-6);
201 }
202
203 #[test]
204 fn simpson_scalar_and_vector() {
205 assert!((simpson(&|x: f64| x.sin(), 0.0, PI, 999) - 2.0).abs() < 1e-6);
207 let v = simpson_vec(&|x: f64| vec![1.0, x, x * x], 0.0, 1.0, 100);
209 assert!((v[0] - 1.0).abs() < 1e-9);
210 assert!((v[1] - 0.5).abs() < 1e-9);
211 assert!((v[2] - 1.0 / 3.0).abs() < 1e-9);
212 }
213
214 #[test]
215 fn shooting_bvp_recovers_sine() {
216 let f = |_t: f64, y: &[f64]| vec![y[1], -y[0]];
219 let residual = |yf: &[f64]| vec![yf[0] - 1.0];
220 let y0 = shooting_bvp(
221 &f,
222 0.0,
223 PI / 2.0,
224 400,
225 &[0.0, 0.0],
226 &[1],
227 &residual,
228 1e-10,
229 50,
230 )
231 .expect("BVP should converge");
232 assert!((y0[1] - 1.0).abs() < 1e-6, "initial slope = {}", y0[1]);
233 let yf = rk4_integrate(&f, 0.0, &y0, PI / 2.0, 400);
235 assert!((yf[0] - 1.0).abs() < 1e-8);
236 }
237
238 #[test]
239 fn shooting_bvp_rejects_bad_free_index() {
240 let f = |_t: f64, y: &[f64]| vec![y[1], -y[0]];
241 let residual = |yf: &[f64]| vec![yf[0] - 1.0];
242 assert!(shooting_bvp(&f, 0.0, 1.0, 10, &[0.0, 0.0], &[5], &residual, 1e-9, 10).is_none());
243 }
244}