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}