sciforge_lib/maths/integration/
multidim.rs1pub 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}