amari_calculus/integration.rs
1//! Integration on manifolds using geometric measure theory
2//!
3//! This module provides integration of scalar and multivector fields over
4//! manifolds, using the measure-theoretic foundations from amari-measure.
5//!
6//! ## Mathematical Background
7//!
8//! Integration on manifolds extends classical integration to curved spaces:
9//!
10//! ### Scalar Integration
11//!
12//! For a scalar field f and a measure μ on a manifold M:
13//! ```text
14//! ∫_M f dμ
15//! ```
16//!
17//! ### Multivector Integration
18//!
19//! For a k-vector field ω and a k-dimensional manifold M:
20//! ```text
21//! ∫_M ω
22//! ```
23//!
24//! ### Fundamental Theorem of Geometric Calculus
25//!
26//! Generalizes Stokes' theorem:
27//! ```text
28//! ∫_V (∇F) dV = ∮_∂V F dS
29//! ```
30//!
31//! Where:
32//! - V is a volume (n-dimensional manifold)
33//! - ∂V is its boundary ((n-1)-dimensional manifold)
34//! - ∇F is the geometric derivative
35//! - dV, dS are volume and surface elements
36//!
37//! ## Examples
38//!
39//! ```
40//! use amari_calculus::{ScalarField, ManifoldIntegrator};
41//!
42//! // Define scalar field f(x, y) = x² + y²
43//! let f = ScalarField::<3, 0, 0>::new(|coords| {
44//! coords[0].powi(2) + coords[1].powi(2)
45//! });
46//!
47//! // Create integrator for 2D rectangular domain [0, 1] × [0, 1]
48//! let integrator = ManifoldIntegrator::<3, 0, 0>::new();
49//!
50//! // Integrate over rectangle
51//! let result = integrator.integrate_scalar_2d(&f, (0.0, 1.0), (0.0, 1.0), 100);
52//! ```
53
54use crate::fields::*;
55
56/// Integrator for scalar and multivector fields on manifolds
57///
58/// Uses adaptive quadrature and measure-theoretic methods for accurate integration.
59pub struct ManifoldIntegrator<const P: usize, const Q: usize, const R: usize> {
60 /// Tolerance for adaptive integration
61 tolerance: f64,
62}
63
64impl<const P: usize, const Q: usize, const R: usize> ManifoldIntegrator<P, Q, R> {
65 /// Create new manifold integrator
66 ///
67 /// # Examples
68 ///
69 /// ```
70 /// use amari_calculus::ManifoldIntegrator;
71 ///
72 /// let integrator = ManifoldIntegrator::<3, 0, 0>::new();
73 /// ```
74 pub fn new() -> Self {
75 Self { tolerance: 1e-6 }
76 }
77
78 /// Set tolerance for adaptive integration
79 pub fn with_tolerance(mut self, tolerance: f64) -> Self {
80 self.tolerance = tolerance;
81 self
82 }
83
84 /// Integrate scalar field over 1D interval [a, b]
85 ///
86 /// Uses adaptive Simpson's rule for accurate integration.
87 ///
88 /// # Arguments
89 ///
90 /// * `f` - Scalar field to integrate
91 /// * `a` - Lower bound
92 /// * `b` - Upper bound
93 /// * `n` - Number of subdivisions (must be even)
94 ///
95 /// # Returns
96 ///
97 /// Approximation of ∫_a^b f(x) dx
98 ///
99 /// # Examples
100 ///
101 /// ```
102 /// use amari_calculus::{ScalarField, ManifoldIntegrator};
103 ///
104 /// // f(x) = x²
105 /// let f = ScalarField::<3, 0, 0>::with_dimension(
106 /// |coords| coords[0].powi(2),
107 /// 1
108 /// );
109 ///
110 /// let integrator = ManifoldIntegrator::<3, 0, 0>::new();
111 ///
112 /// // ∫_0^1 x² dx = 1/3
113 /// let result = integrator.integrate_scalar_1d(&f, 0.0, 1.0, 100);
114 /// assert!((result - 1.0/3.0).abs() < 1e-4);
115 /// ```
116 pub fn integrate_scalar_1d(&self, f: &ScalarField<P, Q, R>, a: f64, b: f64, n: usize) -> f64 {
117 // Simpson's rule: ∫ f(x) dx ≈ (h/3)[f(x0) + 4f(x1) + 2f(x2) + 4f(x3) + ... + f(xn)]
118 let h = (b - a) / (n as f64);
119 let mut sum = f.evaluate(&[a]) + f.evaluate(&[b]);
120
121 for i in 1..n {
122 let x = a + (i as f64) * h;
123 let coeff = if i % 2 == 0 { 2.0 } else { 4.0 };
124 sum += coeff * f.evaluate(&[x]);
125 }
126
127 sum * h / 3.0
128 }
129
130 /// Integrate scalar field over 2D rectangular domain [x_min, x_max] × [y_min, y_max]
131 ///
132 /// # Arguments
133 ///
134 /// * `f` - Scalar field to integrate
135 /// * `x_range` - (x_min, x_max) bounds
136 /// * `y_range` - (y_min, y_max) bounds
137 /// * `n` - Number of subdivisions per dimension
138 ///
139 /// # Returns
140 ///
141 /// Approximation of ∫∫_R f(x,y) dx dy
142 ///
143 /// # Examples
144 ///
145 /// ```
146 /// use amari_calculus::{ScalarField, ManifoldIntegrator};
147 ///
148 /// // f(x, y) = x*y
149 /// let f = ScalarField::<3, 0, 0>::with_dimension(
150 /// |coords| coords[0] * coords[1],
151 /// 2
152 /// );
153 ///
154 /// let integrator = ManifoldIntegrator::<3, 0, 0>::new();
155 ///
156 /// // ∫_0^1 ∫_0^1 x*y dx dy = 1/4
157 /// let result = integrator.integrate_scalar_2d(&f, (0.0, 1.0), (0.0, 1.0), 50);
158 /// assert!((result - 0.25).abs() < 1e-4);
159 /// ```
160 pub fn integrate_scalar_2d(
161 &self,
162 f: &ScalarField<P, Q, R>,
163 x_range: (f64, f64),
164 y_range: (f64, f64),
165 n: usize,
166 ) -> f64 {
167 let (x_min, x_max) = x_range;
168 let (y_min, y_max) = y_range;
169 let hx = (x_max - x_min) / (n as f64);
170 let hy = (y_max - y_min) / (n as f64);
171
172 let mut sum = 0.0;
173
174 for i in 0..=n {
175 for j in 0..=n {
176 let x = x_min + (i as f64) * hx;
177 let y = y_min + (j as f64) * hy;
178
179 let weight = match (i, j) {
180 (0, 0) | (0, _) if j == n => 1.0,
181 (_, 0) if i == n => 1.0,
182 (_, _) if i == n && j == n => 1.0,
183 (0, _) | (_, 0) if i != n && j != n => 2.0,
184 (_, _) if i == n || j == n => 2.0,
185 _ => 4.0,
186 };
187
188 sum += weight * f.evaluate(&[x, y]);
189 }
190 }
191
192 sum * hx * hy / 4.0
193 }
194
195 /// Integrate scalar field over 3D rectangular domain
196 ///
197 /// # Arguments
198 ///
199 /// * `f` - Scalar field to integrate
200 /// * `x_range` - (x_min, x_max) bounds
201 /// * `y_range` - (y_min, y_max) bounds
202 /// * `z_range` - (z_min, z_max) bounds
203 /// * `n` - Number of subdivisions per dimension
204 ///
205 /// # Returns
206 ///
207 /// Approximation of ∫∫∫_V f(x,y,z) dx dy dz
208 pub fn integrate_scalar_3d(
209 &self,
210 f: &ScalarField<P, Q, R>,
211 x_range: (f64, f64),
212 y_range: (f64, f64),
213 z_range: (f64, f64),
214 n: usize,
215 ) -> f64 {
216 let (x_min, x_max) = x_range;
217 let (y_min, y_max) = y_range;
218 let (z_min, z_max) = z_range;
219 let hx = (x_max - x_min) / (n as f64);
220 let hy = (y_max - y_min) / (n as f64);
221 let hz = (z_max - z_min) / (n as f64);
222
223 let mut sum = 0.0;
224
225 for i in 0..=n {
226 for j in 0..=n {
227 for k in 0..=n {
228 let x = x_min + (i as f64) * hx;
229 let y = y_min + (j as f64) * hy;
230 let z = z_min + (k as f64) * hz;
231
232 // Trapezoidal rule weights for 3D
233 let weight = if i == 0 || i == n { 0.5 } else { 1.0 }
234 * if j == 0 || j == n { 0.5 } else { 1.0 }
235 * if k == 0 || k == n { 0.5 } else { 1.0 };
236
237 sum += weight * f.evaluate(&[x, y, z]);
238 }
239 }
240 }
241
242 sum * hx * hy * hz
243 }
244
245 /// Verify the fundamental theorem of geometric calculus: ∫_V (∇F) dV = ∮_∂V F dS
246 ///
247 /// For a 2D rectangular domain, this reduces to checking:
248 /// ∫∫_R div(F) dx dy = ∮_∂R F·n ds
249 ///
250 /// where n is the outward normal to the boundary.
251 pub fn verify_fundamental_theorem_2d(
252 &self,
253 f: &VectorField<P, Q, R>,
254 x_range: (f64, f64),
255 y_range: (f64, f64),
256 n: usize,
257 ) -> (f64, f64) {
258 use crate::VectorDerivative;
259
260 let nabla = VectorDerivative::new(crate::CoordinateSystem::Cartesian);
261 let (x_min, x_max) = x_range;
262 let (y_min, y_max) = y_range;
263 let hx = (x_max - x_min) / (n as f64);
264 let hy = (y_max - y_min) / (n as f64);
265
266 // Compute volume integral: ∫∫ div(F) dx dy manually
267 let mut volume_integral = 0.0;
268 for i in 0..=n {
269 for j in 0..=n {
270 let x = x_min + (i as f64) * hx;
271 let y = y_min + (j as f64) * hy;
272
273 let weight = match (i, j) {
274 (0, 0) | (0, _) if j == n => 1.0,
275 (_, 0) if i == n => 1.0,
276 (_, _) if i == n && j == n => 1.0,
277 (0, _) | (_, 0) if i != n && j != n => 2.0,
278 (_, _) if i == n || j == n => 2.0,
279 _ => 4.0,
280 };
281
282 // Pass 3D coordinates (padding with 0 for z)
283 volume_integral += weight * nabla.divergence(f, &[x, y, 0.0]);
284 }
285 }
286 volume_integral *= hx * hy / 4.0;
287
288 // Compute surface integral: ∮ F·n ds
289 let mut surface_integral = 0.0;
290
291 // Bottom edge (y = y_min, normal = (0, -1))
292 for i in 0..=n {
293 let x = x_min + (i as f64) * (x_max - x_min) / (n as f64);
294 let f_val = f.evaluate(&[x, y_min, 0.0]);
295 let weight = if i == 0 || i == n { 0.5 } else { 1.0 };
296 surface_integral -= weight * f_val.vector_component(1) * (x_max - x_min) / (n as f64);
297 }
298
299 // Top edge (y = y_max, normal = (0, 1))
300 for i in 0..=n {
301 let x = x_min + (i as f64) * (x_max - x_min) / (n as f64);
302 let f_val = f.evaluate(&[x, y_max, 0.0]);
303 let weight = if i == 0 || i == n { 0.5 } else { 1.0 };
304 surface_integral += weight * f_val.vector_component(1) * (x_max - x_min) / (n as f64);
305 }
306
307 // Left edge (x = x_min, normal = (-1, 0))
308 for j in 0..=n {
309 let y = y_min + (j as f64) * (y_max - y_min) / (n as f64);
310 let f_val = f.evaluate(&[x_min, y, 0.0]);
311 let weight = if j == 0 || j == n { 0.5 } else { 1.0 };
312 surface_integral -= weight * f_val.vector_component(0) * (y_max - y_min) / (n as f64);
313 }
314
315 // Right edge (x = x_max, normal = (1, 0))
316 for j in 0..=n {
317 let y = y_min + (j as f64) * (y_max - y_min) / (n as f64);
318 let f_val = f.evaluate(&[x_max, y, 0.0]);
319 let weight = if j == 0 || j == n { 0.5 } else { 1.0 };
320 surface_integral += weight * f_val.vector_component(0) * (y_max - y_min) / (n as f64);
321 }
322
323 (volume_integral, surface_integral)
324 }
325}
326
327impl<const P: usize, const Q: usize, const R: usize> Default for ManifoldIntegrator<P, Q, R> {
328 fn default() -> Self {
329 Self::new()
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336
337 #[test]
338 fn test_integrate_1d_polynomial() {
339 // ∫_0^1 x² dx = 1/3
340 let f = ScalarField::<3, 0, 0>::with_dimension(|coords| coords[0].powi(2), 1);
341
342 let integrator = ManifoldIntegrator::<3, 0, 0>::new();
343 let result = integrator.integrate_scalar_1d(&f, 0.0, 1.0, 100);
344
345 assert!(
346 (result - 1.0 / 3.0).abs() < 1e-4,
347 "∫ x² dx should be 1/3, got {}",
348 result
349 );
350 }
351
352 #[test]
353 fn test_integrate_2d_product() {
354 // ∫_0^1 ∫_0^1 x*y dx dy = 1/4
355 let f = ScalarField::<3, 0, 0>::with_dimension(|coords| coords[0] * coords[1], 2);
356
357 let integrator = ManifoldIntegrator::<3, 0, 0>::new();
358 let result = integrator.integrate_scalar_2d(&f, (0.0, 1.0), (0.0, 1.0), 50);
359
360 assert!(
361 (result - 0.25).abs() < 1e-3,
362 "∫∫ x*y dx dy should be 1/4, got {}",
363 result
364 );
365 }
366
367 #[test]
368 fn test_integrate_3d_constant() {
369 // ∫_0^1 ∫_0^1 ∫_0^1 1 dx dy dz = 1
370 let f = ScalarField::<3, 0, 0>::with_dimension(|_coords| 1.0, 3);
371
372 let integrator = ManifoldIntegrator::<3, 0, 0>::new();
373 let result = integrator.integrate_scalar_3d(&f, (0.0, 1.0), (0.0, 1.0), (0.0, 1.0), 20);
374
375 assert!(
376 (result - 1.0).abs() < 1e-3,
377 "∫∫∫ 1 dx dy dz should be 1, got {}",
378 result
379 );
380 }
381
382 #[test]
383 fn test_fundamental_theorem_constant_field() {
384 // F = (x, y, 0) → div(F) = 2
385 // Using 2D version with proper coords
386 let f = VectorField::<3, 0, 0>::new(|coords| {
387 let x = if !coords.is_empty() { coords[0] } else { 0.0 };
388 let y = if coords.len() > 1 { coords[1] } else { 0.0 };
389 crate::vector_from_slice(&[x, y, 0.0])
390 });
391
392 let integrator = ManifoldIntegrator::<3, 0, 0>::new();
393 let (volume, surface) =
394 integrator.verify_fundamental_theorem_2d(&f, (0.0, 1.0), (0.0, 1.0), 50);
395
396 assert!(
397 (volume - surface).abs() < 1e-2,
398 "Fundamental theorem: volume {} should equal surface {}, diff = {}",
399 volume,
400 surface,
401 (volume - surface).abs()
402 );
403 }
404}