qualia_core_db/solvers/vector_calculus/
integrals.rs1const FD: f64 = 1e-6;
6
7fn dot(a: &[f64], b: &[f64]) -> f64 {
8 a.iter().zip(b).map(|(x, y)| x * y).sum()
9}
10fn norm(a: &[f64]) -> f64 {
11 dot(a, a).sqrt()
12}
13fn cross3(a: &[f64], b: &[f64]) -> [f64; 3] {
14 [
15 a[1] * b[2] - a[2] * b[1],
16 a[2] * b[0] - a[0] * b[2],
17 a[0] * b[1] - a[1] * b[0],
18 ]
19}
20
21fn dcurve<C: Fn(f64) -> Vec<f64>>(curve: &C, t: f64) -> Vec<f64> {
23 let p = curve(t + FD);
24 let m = curve(t - FD);
25 p.iter()
26 .zip(&m)
27 .map(|(a, b)| (a - b) / (2.0 * FD))
28 .collect()
29}
30
31pub fn line_integral_scalar<F, C>(f: F, curve: C, t0: f64, t1: f64, steps: usize) -> f64
33where
34 F: Fn(&[f64]) -> f64,
35 C: Fn(f64) -> Vec<f64>,
36{
37 let h = (t1 - t0) / steps as f64;
38 let g = |t: f64| f(&curve(t)) * norm(&dcurve(&curve, t));
39 let mut sum = 0.5 * (g(t0) + g(t1));
40 for i in 1..steps {
41 sum += g(t0 + i as f64 * h);
42 }
43 sum * h
44}
45
46pub fn line_integral_work<F, C>(field: F, curve: C, t0: f64, t1: f64, steps: usize) -> f64
48where
49 F: Fn(&[f64]) -> Vec<f64>,
50 C: Fn(f64) -> Vec<f64>,
51{
52 let h = (t1 - t0) / steps as f64;
53 let g = |t: f64| dot(&field(&curve(t)), &dcurve(&curve, t));
54 let mut sum = 0.5 * (g(t0) + g(t1));
55 for i in 1..steps {
56 sum += g(t0 + i as f64 * h);
57 }
58 sum * h
59}
60
61pub fn surface_flux<F, S>(
64 field: F,
65 surf: S,
66 u0: f64,
67 u1: f64,
68 v0: f64,
69 v1: f64,
70 steps: usize,
71) -> f64
72where
73 F: Fn(&[f64]) -> Vec<f64>,
74 S: Fn(f64, f64) -> Vec<f64>,
75{
76 let hu = (u1 - u0) / steps as f64;
77 let hv = (v1 - v0) / steps as f64;
78 let integrand = |u: f64, v: f64| -> f64 {
79 let ru: Vec<f64> = {
80 let p = surf(u + FD, v);
81 let m = surf(u - FD, v);
82 p.iter()
83 .zip(&m)
84 .map(|(a, b)| (a - b) / (2.0 * FD))
85 .collect()
86 };
87 let rv: Vec<f64> = {
88 let p = surf(u, v + FD);
89 let m = surf(u, v - FD);
90 p.iter()
91 .zip(&m)
92 .map(|(a, b)| (a - b) / (2.0 * FD))
93 .collect()
94 };
95 let n = cross3(&ru, &rv);
96 dot(&field(&surf(u, v)), &n)
97 };
98 let mut sum = 0.0;
100 for i in 0..=steps {
101 for j in 0..=steps {
102 let w = {
103 let wu = if i == 0 || i == steps { 0.5 } else { 1.0 };
104 let wv = if j == 0 || j == steps { 0.5 } else { 1.0 };
105 wu * wv
106 };
107 sum += w * integrand(u0 + i as f64 * hu, v0 + j as f64 * hv);
108 }
109 }
110 sum * hu * hv
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116 use core::f64::consts::PI;
117
118 #[test]
119 fn conservative_work_is_potential_difference() {
120 let f = |p: &[f64]| vec![p[1], p[0]];
122 let curve = |t: f64| vec![t, t];
123 let w = line_integral_work(f, curve, 0.0, 1.0, 400);
124 assert!((w - 1.0).abs() < 1e-6);
125 }
126
127 #[test]
128 fn greens_theorem_on_the_unit_circle() {
129 let f = |p: &[f64]| vec![-p[1], p[0]];
131 let circle = |t: f64| vec![t.cos(), t.sin()];
132 let w = line_integral_work(f, circle, 0.0, 2.0 * PI, 2000);
133 assert!((w - 2.0 * PI).abs() < 1e-4);
134 }
135
136 #[test]
137 fn arc_length_via_scalar_line_integral() {
138 let len = line_integral_scalar(
140 |_| 1.0,
141 |t: f64| vec![t.cos(), t.sin()],
142 0.0,
143 2.0 * PI,
144 2000,
145 );
146 assert!((len - 2.0 * PI).abs() < 1e-4);
147 }
148
149 #[test]
150 fn divergence_theorem_flux_through_sphere() {
151 let f = |p: &[f64]| vec![p[0], p[1], p[2]];
155 let sphere = |u: f64, v: f64| vec![u.sin() * v.cos(), u.sin() * v.sin(), u.cos()];
156 let flux = surface_flux(f, sphere, 0.0, PI, 0.0, 2.0 * PI, 120);
157 assert!((flux - 4.0 * PI).abs() < 1e-2, "flux {flux} vs 4π");
158 }
159}