Skip to main content

sciforge_lib/maths/integration/
multidim.rs

1pub fn monte_carlo_integrate(
2    f: impl Fn(&[f64]) -> f64,
3    bounds: &[(f64, f64)],
4    samples: usize,
5    seed: u64,
6) -> f64 {
7    let dim = bounds.len();
8    assert!(dim > 0);
9    let volume: f64 = bounds.iter().map(|(a, b)| b - a).product();
10    let mut rng = LcgRng::new(seed);
11    let mut sum = 0.0;
12
13    for _ in 0..samples {
14        let point: Vec<f64> = bounds
15            .iter()
16            .map(|(a, b)| a + rng.next_f64() * (b - a))
17            .collect();
18        sum += f(&point);
19    }
20    volume * sum / samples as f64
21}
22
23pub fn double_integral(
24    f: impl Fn(f64, f64) -> f64,
25    x_range: (f64, f64),
26    y_range: impl Fn(f64) -> (f64, f64),
27    nx: usize,
28    ny: usize,
29) -> f64 {
30    let hx = (x_range.1 - x_range.0) / nx as f64;
31    let mut total = 0.0;
32    for i in 0..=nx {
33        let x = x_range.0 + i as f64 * hx;
34        let wx = if i == 0 || i == nx { 0.5 } else { 1.0 };
35        let (ya, yb) = y_range(x);
36        let hy = (yb - ya) / ny as f64;
37        let mut inner = 0.0;
38        for j in 0..=ny {
39            let y = ya + j as f64 * hy;
40            let wy = if j == 0 || j == ny { 0.5 } else { 1.0 };
41            inner += wy * f(x, y);
42        }
43        total += wx * inner * hy;
44    }
45    total * hx
46}
47
48pub fn triple_integral(
49    f: impl Fn(f64, f64, f64) -> f64,
50    x_range: (f64, f64),
51    y_range: impl Fn(f64) -> (f64, f64),
52    z_range: impl Fn(f64, f64) -> (f64, f64),
53    nx: usize,
54    ny: usize,
55    nz: usize,
56) -> f64 {
57    let hx = (x_range.1 - x_range.0) / nx as f64;
58    let mut total = 0.0;
59    for i in 0..=nx {
60        let x = x_range.0 + i as f64 * hx;
61        let wx = if i == 0 || i == nx { 0.5 } else { 1.0 };
62        let (ya, yb) = y_range(x);
63        let hy = (yb - ya) / ny as f64;
64        let mut mid = 0.0;
65        for j in 0..=ny {
66            let y = ya + j as f64 * hy;
67            let wy = if j == 0 || j == ny { 0.5 } else { 1.0 };
68            let (za, zb) = z_range(x, y);
69            let hz = (zb - za) / nz as f64;
70            let mut inner = 0.0;
71            for k in 0..=nz {
72                let z = za + k as f64 * hz;
73                let wz = if k == 0 || k == nz { 0.5 } else { 1.0 };
74                inner += wz * f(x, y, z);
75            }
76            mid += wy * inner * hz;
77        }
78        total += wx * mid * hy;
79    }
80    total * hx
81}
82
83pub fn stratified_monte_carlo(
84    f: impl Fn(&[f64]) -> f64,
85    bounds: &[(f64, f64)],
86    strata_per_dim: usize,
87    samples_per_stratum: usize,
88    seed: u64,
89) -> f64 {
90    let dim = bounds.len();
91    let volume: f64 = bounds.iter().map(|(a, b)| b - a).product();
92    let total_strata = strata_per_dim.pow(dim as u32);
93    let mut rng = LcgRng::new(seed);
94    let mut sum = 0.0;
95    let mut count = 0usize;
96
97    for stratum in 0..total_strata {
98        let mut idx = stratum;
99        let point_base: Vec<(f64, f64)> = (0..dim)
100            .map(|d| {
101                let s = idx % strata_per_dim;
102                idx /= strata_per_dim;
103                let (a, b) = bounds[d];
104                let w = (b - a) / strata_per_dim as f64;
105                (a + s as f64 * w, a + (s + 1) as f64 * w)
106            })
107            .collect();
108        for _ in 0..samples_per_stratum {
109            let pt: Vec<f64> = point_base
110                .iter()
111                .map(|(lo, hi)| lo + rng.next_f64() * (hi - lo))
112                .collect();
113            sum += f(&pt);
114            count += 1;
115        }
116    }
117    volume * sum / count as f64
118}
119
120pub fn halton_sequence(index: usize, base: usize) -> f64 {
121    let mut result = 0.0;
122    let mut f = 1.0 / base as f64;
123    let mut i = index;
124    while i > 0 {
125        result += f * (i % base) as f64;
126        i /= base;
127        f /= base as f64;
128    }
129    result
130}
131
132pub fn quasi_monte_carlo_halton(
133    f: impl Fn(&[f64]) -> f64,
134    bounds: &[(f64, f64)],
135    samples: usize,
136) -> f64 {
137    let dim = bounds.len();
138    let primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29];
139    let volume: f64 = bounds.iter().map(|(a, b)| b - a).product();
140    let mut sum = 0.0;
141    for i in 1..=samples {
142        let pt: Vec<f64> = (0..dim)
143            .map(|d| {
144                let base = if d < primes.len() {
145                    primes[d]
146                } else {
147                    31 + 2 * d
148                };
149                let h = halton_sequence(i, base);
150                bounds[d].0 + h * (bounds[d].1 - bounds[d].0)
151            })
152            .collect();
153        sum += f(&pt);
154    }
155    volume * sum / samples as f64
156}
157
158pub fn polar_integral(
159    f: impl Fn(f64, f64) -> f64,
160    r_range: (f64, f64),
161    theta_range: (f64, f64),
162    nr: usize,
163    ntheta: usize,
164) -> f64 {
165    let hr = (r_range.1 - r_range.0) / nr as f64;
166    let ht = (theta_range.1 - theta_range.0) / ntheta as f64;
167    let mut total = 0.0;
168    for i in 0..=nr {
169        let r = r_range.0 + i as f64 * hr;
170        let wr = if i == 0 || i == nr { 0.5 } else { 1.0 };
171        for j in 0..=ntheta {
172            let theta = theta_range.0 + j as f64 * ht;
173            let wt = if j == 0 || j == ntheta { 0.5 } else { 1.0 };
174            total += wr * wt * f(r, theta) * r;
175        }
176    }
177    total * hr * ht
178}
179
180pub fn spherical_integral(
181    f: impl Fn(f64, f64, f64) -> f64,
182    r_range: (f64, f64),
183    nr: usize,
184    ntheta: usize,
185    nphi: usize,
186) -> f64 {
187    let pi = std::f64::consts::PI;
188    let hr = (r_range.1 - r_range.0) / nr as f64;
189    let ht = pi / ntheta as f64;
190    let hp = 2.0 * pi / nphi as f64;
191    let mut total = 0.0;
192    for i in 0..=nr {
193        let r = r_range.0 + i as f64 * hr;
194        let wr = if i == 0 || i == nr { 0.5 } else { 1.0 };
195        for j in 0..=ntheta {
196            let theta = j as f64 * ht;
197            let wt = if j == 0 || j == ntheta { 0.5 } else { 1.0 };
198            for k in 0..=nphi {
199                let phi = k as f64 * hp;
200                let wp = if k == 0 || k == nphi { 0.5 } else { 1.0 };
201                total += wr * wt * wp * f(r, theta, phi) * r * r * theta.sin();
202            }
203        }
204    }
205    total * hr * ht * hp
206}
207
208pub fn cylindrical_integral(
209    f: impl Fn(f64, f64, f64) -> f64,
210    r_range: (f64, f64),
211    theta_range: (f64, f64),
212    z_range: (f64, f64),
213    nr: usize,
214    ntheta: usize,
215    nz: usize,
216) -> f64 {
217    let hr = (r_range.1 - r_range.0) / nr as f64;
218    let ht = (theta_range.1 - theta_range.0) / ntheta as f64;
219    let hz = (z_range.1 - z_range.0) / nz as f64;
220    let mut total = 0.0;
221    for i in 0..=nr {
222        let r = r_range.0 + i as f64 * hr;
223        let wr = if i == 0 || i == nr { 0.5 } else { 1.0 };
224        for j in 0..=ntheta {
225            let theta = theta_range.0 + j as f64 * ht;
226            let wt = if j == 0 || j == ntheta { 0.5 } else { 1.0 };
227            for k in 0..=nz {
228                let z = z_range.0 + k as f64 * hz;
229                let wz = if k == 0 || k == nz { 0.5 } else { 1.0 };
230                total += wr * wt * wz * f(r, theta, z) * r;
231            }
232        }
233    }
234    total * hr * ht * hz
235}
236
237pub fn line_integral(
238    f: impl Fn(f64, f64) -> f64,
239    x: impl Fn(f64) -> f64,
240    y: impl Fn(f64) -> f64,
241    dx: impl Fn(f64) -> f64,
242    dy: impl Fn(f64) -> f64,
243    t_range: (f64, f64),
244    n: usize,
245) -> f64 {
246    let h = (t_range.1 - t_range.0) / n as f64;
247    let mut sum = 0.0;
248    for i in 0..=n {
249        let t = t_range.0 + i as f64 * h;
250        let w = if i == 0 || i == n { 0.5 } else { 1.0 };
251        let speed = (dx(t) * dx(t) + dy(t) * dy(t)).sqrt();
252        sum += w * f(x(t), y(t)) * speed;
253    }
254    sum * h
255}
256
257pub fn surface_integral_parametric(
258    f: impl Fn(f64, f64, f64) -> f64,
259    x: impl Fn(f64, f64) -> f64,
260    y: impl Fn(f64, f64) -> f64,
261    z: impl Fn(f64, f64) -> f64,
262    u_range: (f64, f64),
263    v_range: (f64, f64),
264    nu: usize,
265    nv: usize,
266) -> f64 {
267    let hu = (u_range.1 - u_range.0) / nu as f64;
268    let hv = (v_range.1 - v_range.0) / nv as f64;
269    let eps = 1e-8;
270    let mut total = 0.0;
271    for i in 0..=nu {
272        let u = u_range.0 + i as f64 * hu;
273        let wu = if i == 0 || i == nu { 0.5 } else { 1.0 };
274        for j in 0..=nv {
275            let v = v_range.0 + j as f64 * hv;
276            let wv = if j == 0 || j == nv { 0.5 } else { 1.0 };
277            let xu = (x(u + eps, v) - x(u - eps, v)) / (2.0 * eps);
278            let yu = (y(u + eps, v) - y(u - eps, v)) / (2.0 * eps);
279            let zu = (z(u + eps, v) - z(u - eps, v)) / (2.0 * eps);
280            let xv = (x(u, v + eps) - x(u, v - eps)) / (2.0 * eps);
281            let yv = (y(u, v + eps) - y(u, v - eps)) / (2.0 * eps);
282            let zv = (z(u, v + eps) - z(u, v - eps)) / (2.0 * eps);
283            let nx = yu * zv - zu * yv;
284            let ny = zu * xv - xu * zv;
285            let nz = xu * yv - yu * xv;
286            let ds = (nx * nx + ny * ny + nz * nz).sqrt();
287            total += wu * wv * f(x(u, v), y(u, v), z(u, v)) * ds;
288        }
289    }
290    total * hu * hv
291}
292
293pub fn importance_sampling(
294    f: impl Fn(f64) -> f64,
295    pdf: impl Fn(f64) -> f64,
296    sample_gen: impl Fn(&mut LcgRng) -> f64,
297    samples: usize,
298    seed: u64,
299) -> f64 {
300    let mut rng = LcgRng::new(seed);
301    let mut sum = 0.0;
302    for _ in 0..samples {
303        let x = sample_gen(&mut rng);
304        let p = pdf(x);
305        if p.abs() > 1e-30 {
306            sum += f(x) / p;
307        }
308    }
309    sum / samples as f64
310}
311
312pub struct LcgRng {
313    state: u64,
314}
315
316impl LcgRng {
317    fn new(seed: u64) -> Self {
318        Self { state: seed }
319    }
320    fn next_u64(&mut self) -> u64 {
321        self.state = self
322            .state
323            .wrapping_mul(6364136223846793005)
324            .wrapping_add(1442695040888963407);
325        self.state
326    }
327    fn next_f64(&mut self) -> f64 {
328        (self.next_u64() >> 11) as f64 / (1u64 << 53) as f64
329    }
330}