amari_functional/sobolev/
mod.rs

1//! Sobolev spaces for multivector-valued functions.
2//!
3//! This module provides Sobolev spaces W^{k,p}(Ω, Cl(P,Q,R)) of multivector-valued
4//! functions with weak derivatives up to order k in L^p.
5//!
6//! # Mathematical Background
7//!
8//! The Sobolev space W^{k,p}(Ω, V) consists of functions f: Ω → V such that
9//! all weak derivatives up to order k exist and belong to L^p.
10//!
11//! For p = 2, these are Hilbert spaces (denoted H^k) with inner product:
12//!
13//! ⟨f, g⟩_{H^k} = Σ_{|α| ≤ k} ∫_Ω ⟨D^α f, D^α g⟩ dx
14//!
15//! # Key Properties
16//!
17//! - **Sobolev embedding**: W^{k,p} ⊂ C^m for k > m + n/p
18//! - **Poincaré inequality**: ||f||_{L^p} ≤ C ||∇f||_{L^p} for f with zero boundary
19//! - **Trace theorem**: Boundary values are well-defined in W^{k-1/p,p}
20
21use crate::phantom::HkRegularity;
22use crate::space::Domain;
23use amari_core::Multivector;
24use std::marker::PhantomData;
25use std::sync::Arc;
26
27/// A function in a Sobolev space with weak derivatives.
28///
29/// Stores the function and its weak derivatives up to order k.
30#[derive(Clone)]
31pub struct SobolevFunction<const P: usize, const Q: usize, const R: usize, const K: usize> {
32    /// The function f.
33    func: Arc<dyn Fn(&[f64]) -> Multivector<P, Q, R> + Send + Sync>,
34    /// Weak derivatives (indexed by multi-index).
35    /// For K=1, this is just the gradient.
36    derivatives: Vec<Arc<dyn Fn(&[f64]) -> Multivector<P, Q, R> + Send + Sync>>,
37    /// Spatial dimension.
38    spatial_dim: usize,
39}
40
41impl<const P: usize, const Q: usize, const R: usize, const K: usize> std::fmt::Debug
42    for SobolevFunction<P, Q, R, K>
43{
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        f.debug_struct("SobolevFunction")
46            .field("regularity", &K)
47            .field("spatial_dim", &self.spatial_dim)
48            .finish()
49    }
50}
51
52impl<const P: usize, const Q: usize, const R: usize, const K: usize> SobolevFunction<P, Q, R, K> {
53    /// Create a new Sobolev function with its derivatives.
54    pub fn new<F>(
55        func: F,
56        derivatives: Vec<Arc<dyn Fn(&[f64]) -> Multivector<P, Q, R> + Send + Sync>>,
57        spatial_dim: usize,
58    ) -> Self
59    where
60        F: Fn(&[f64]) -> Multivector<P, Q, R> + Send + Sync + 'static,
61    {
62        Self {
63            func: Arc::new(func),
64            derivatives,
65            spatial_dim,
66        }
67    }
68
69    /// Evaluate the function at a point.
70    pub fn eval(&self, point: &[f64]) -> Multivector<P, Q, R> {
71        (self.func)(point)
72    }
73
74    /// Evaluate the i-th derivative at a point.
75    pub fn eval_derivative(&self, i: usize, point: &[f64]) -> Option<Multivector<P, Q, R>> {
76        self.derivatives.get(i).map(|d| d(point))
77    }
78
79    /// Get the number of derivatives stored.
80    pub fn derivative_count(&self) -> usize {
81        self.derivatives.len()
82    }
83
84    /// Get the spatial dimension.
85    pub fn spatial_dimension(&self) -> usize {
86        self.spatial_dim
87    }
88}
89
90/// A Sobolev space W^{k,2}(Ω, Cl(P,Q,R)) = H^k(Ω, Cl(P,Q,R)).
91///
92/// This is the Hilbert space of multivector-valued functions with
93/// k weak derivatives in L^2.
94#[derive(Clone)]
95pub struct SobolevSpace<const P: usize, const Q: usize, const R: usize, const K: usize> {
96    /// The domain of the space.
97    domain: Domain<f64>,
98    /// Number of quadrature points for numerical integration.
99    quadrature_points: usize,
100    _phantom: PhantomData<HkRegularity<K>>,
101}
102
103impl<const P: usize, const Q: usize, const R: usize, const K: usize> std::fmt::Debug
104    for SobolevSpace<P, Q, R, K>
105{
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        f.debug_struct("SobolevSpace")
108            .field("order", &K)
109            .field("signature", &(P, Q, R))
110            .field("domain", &self.domain)
111            .finish()
112    }
113}
114
115impl<const P: usize, const Q: usize, const R: usize, const K: usize> SobolevSpace<P, Q, R, K> {
116    /// Create a new Sobolev space H^k over a domain.
117    pub fn new(domain: Domain<f64>) -> Self {
118        Self {
119            domain,
120            quadrature_points: 32,
121            _phantom: PhantomData,
122        }
123    }
124
125    /// Create H^k over the unit interval [0, 1].
126    pub fn unit_interval() -> Self {
127        Self::new(Domain::interval(0.0, 1.0))
128    }
129
130    /// Set the number of quadrature points.
131    pub fn with_quadrature_points(mut self, n: usize) -> Self {
132        self.quadrature_points = n;
133        self
134    }
135
136    /// Get the Sobolev order k.
137    pub fn order(&self) -> usize {
138        K
139    }
140
141    /// Get the domain.
142    pub fn domain(&self) -> &Domain<f64> {
143        &self.domain
144    }
145
146    /// Compute the H^k inner product.
147    ///
148    /// ⟨f, g⟩_{H^k} = ⟨f, g⟩_{L^2} + Σ_{|α|=1}^k ⟨D^α f, D^α g⟩_{L^2}
149    pub fn hk_inner_product(
150        &self,
151        f: &SobolevFunction<P, Q, R, K>,
152        g: &SobolevFunction<P, Q, R, K>,
153    ) -> f64 {
154        let (a, b) = self.domain.bounds_1d().unwrap_or((0.0, 1.0));
155        let h = (b - a) / self.quadrature_points as f64;
156
157        let mut sum = 0.0;
158
159        for i in 0..self.quadrature_points {
160            let x = a + (i as f64 + 0.5) * h;
161
162            // L² part: ⟨f(x), g(x)⟩
163            let fx = f.eval(&[x]);
164            let gx = g.eval(&[x]);
165            let fx_coeffs = fx.to_vec();
166            let gx_coeffs = gx.to_vec();
167            let l2_part: f64 = fx_coeffs
168                .iter()
169                .zip(gx_coeffs.iter())
170                .map(|(a, b)| a * b)
171                .sum();
172            sum += l2_part;
173
174            // Derivative parts
175            let num_derivs = f.derivative_count().min(g.derivative_count());
176            for j in 0..num_derivs {
177                if let (Some(df), Some(dg)) =
178                    (f.eval_derivative(j, &[x]), g.eval_derivative(j, &[x]))
179                {
180                    let df_coeffs = df.to_vec();
181                    let dg_coeffs = dg.to_vec();
182                    let deriv_part: f64 = df_coeffs
183                        .iter()
184                        .zip(dg_coeffs.iter())
185                        .map(|(a, b)| a * b)
186                        .sum();
187                    sum += deriv_part;
188                }
189            }
190        }
191
192        sum * h
193    }
194
195    /// Compute the H^k norm.
196    pub fn hk_norm(&self, f: &SobolevFunction<P, Q, R, K>) -> f64 {
197        self.hk_inner_product(f, f).sqrt()
198    }
199
200    /// Compute the H^k seminorm (only includes derivatives of order k).
201    pub fn hk_seminorm(&self, f: &SobolevFunction<P, Q, R, K>) -> f64 {
202        let (a, b) = self.domain.bounds_1d().unwrap_or((0.0, 1.0));
203        let h = (b - a) / self.quadrature_points as f64;
204
205        let mut sum = 0.0;
206
207        // Only the k-th derivative (or derivatives of order k)
208        if f.derivative_count() > 0 {
209            let last_deriv_idx = f.derivative_count() - 1;
210            for i in 0..self.quadrature_points {
211                let x = a + (i as f64 + 0.5) * h;
212                if let Some(df) = f.eval_derivative(last_deriv_idx, &[x]) {
213                    let df_coeffs = df.to_vec();
214                    let norm_sq: f64 = df_coeffs.iter().map(|c| c * c).sum();
215                    sum += norm_sq;
216                }
217            }
218        }
219
220        (sum * h).sqrt()
221    }
222}
223
224/// H^1 Sobolev space (first-order derivatives in L^2).
225pub type H1Space<const P: usize, const Q: usize, const R: usize> = SobolevSpace<P, Q, R, 1>;
226
227/// H^2 Sobolev space (second-order derivatives in L^2).
228pub type H2Space<const P: usize, const Q: usize, const R: usize> = SobolevSpace<P, Q, R, 2>;
229
230/// Compute the Poincaré constant estimate for a domain.
231///
232/// For a bounded domain Ω, there exists C > 0 such that:
233/// ||f||_{L^2} ≤ C ||∇f||_{L^2} for f ∈ H^1_0(Ω)
234pub fn poincare_constant_estimate(domain: &Domain<f64>) -> f64 {
235    match domain {
236        Domain::Interval { a, b } => {
237            // For [a,b], Poincaré constant is (b-a)/π
238            (b - a) / std::f64::consts::PI
239        }
240        Domain::Rectangle { bounds } => {
241            // For rectangles, use the smallest dimension
242            bounds
243                .iter()
244                .map(|(a, b)| (b - a) / std::f64::consts::PI)
245                .fold(f64::MAX, f64::min)
246        }
247        Domain::RealLine => f64::INFINITY,
248    }
249}
250
251#[cfg(test)]
252mod tests {
253    use super::*;
254
255    #[test]
256    fn test_sobolev_space_creation() {
257        let h1: H1Space<2, 0, 0> = H1Space::unit_interval();
258        assert_eq!(h1.order(), 1);
259    }
260
261    #[test]
262    fn test_sobolev_function() {
263        // f(x) = x, f'(x) = 1
264        let f: SobolevFunction<2, 0, 0, 1> = SobolevFunction::new(
265            |x| Multivector::<2, 0, 0>::scalar(x[0]),
266            vec![Arc::new(|_x| Multivector::<2, 0, 0>::scalar(1.0))],
267            1,
268        );
269
270        let val = f.eval(&[0.5]);
271        assert!((val.scalar_part() - 0.5).abs() < 1e-10);
272
273        let deriv = f.eval_derivative(0, &[0.5]).unwrap();
274        assert!((deriv.scalar_part() - 1.0).abs() < 1e-10);
275    }
276
277    #[test]
278    fn test_hk_norm() {
279        let h1: H1Space<2, 0, 0> = H1Space::unit_interval().with_quadrature_points(64);
280
281        // f(x) = 0, f'(x) = 0 -> ||f||_{H^1} = 0
282        let zero: SobolevFunction<2, 0, 0, 1> = SobolevFunction::new(
283            |_| Multivector::<2, 0, 0>::zero(),
284            vec![Arc::new(|_| Multivector::<2, 0, 0>::zero())],
285            1,
286        );
287
288        let norm = h1.hk_norm(&zero);
289        assert!(norm < 1e-10);
290    }
291
292    #[test]
293    fn test_h1_inner_product() {
294        let h1: H1Space<2, 0, 0> = H1Space::unit_interval().with_quadrature_points(64);
295
296        // f(x) = x, f'(x) = 1
297        let f: SobolevFunction<2, 0, 0, 1> = SobolevFunction::new(
298            |x| Multivector::<2, 0, 0>::scalar(x[0]),
299            vec![Arc::new(|_| Multivector::<2, 0, 0>::scalar(1.0))],
300            1,
301        );
302
303        // ||f||²_{H^1} = ||f||²_{L^2} + ||f'||²_{L^2}
304        //             = ∫₀¹ x² dx + ∫₀¹ 1 dx
305        //             = 1/3 + 1 = 4/3
306        let norm_sq = h1.hk_inner_product(&f, &f);
307        assert!((norm_sq - 4.0 / 3.0).abs() < 0.1);
308    }
309
310    #[test]
311    fn test_poincare_constant() {
312        let domain = Domain::interval(0.0, 1.0);
313        let c = poincare_constant_estimate(&domain);
314
315        // For [0,1], Poincaré constant is 1/π ≈ 0.318
316        assert!((c - 1.0 / std::f64::consts::PI).abs() < 1e-10);
317    }
318
319    #[test]
320    fn test_h2_space() {
321        let h2: H2Space<2, 0, 0> = H2Space::unit_interval();
322        assert_eq!(h2.order(), 2);
323    }
324
325    #[test]
326    fn test_seminorm() {
327        let h1: H1Space<2, 0, 0> = H1Space::unit_interval().with_quadrature_points(64);
328
329        // f(x) = x, f'(x) = 1
330        let f: SobolevFunction<2, 0, 0, 1> = SobolevFunction::new(
331            |x| Multivector::<2, 0, 0>::scalar(x[0]),
332            vec![Arc::new(|_| Multivector::<2, 0, 0>::scalar(1.0))],
333            1,
334        );
335
336        // |f|_{H^1}² = ||f'||²_{L^2} = ∫₀¹ 1 dx = 1
337        let seminorm_sq = h1.hk_seminorm(&f).powi(2);
338        assert!((seminorm_sq - 1.0).abs() < 0.1);
339    }
340}