amari_functional/space/
hilbert.rs

1//! Hilbert space structure for multivector-valued functions.
2//!
3//! This module provides the `MultivectorHilbertSpace` type, which represents
4//! the Hilbert space of square-integrable multivector-valued functions.
5
6use crate::error::{FunctionalError, Result};
7use crate::phantom::Complete;
8use crate::space::traits::{
9    BanachSpace, HilbertSpace, InnerProductSpace, NormedSpace, VectorSpace,
10};
11use amari_core::Multivector;
12use core::marker::PhantomData;
13
14/// A Hilbert space of multivector elements.
15///
16/// This represents the finite-dimensional Hilbert space Cl(P,Q,R) with
17/// the standard inner product inherited from the coefficient representation.
18///
19/// # Type Parameters
20///
21/// * `P` - Number of positive signature basis vectors
22/// * `Q` - Number of negative signature basis vectors
23/// * `R` - Number of zero signature basis vectors
24///
25/// # Mathematical Background
26///
27/// The Clifford algebra Cl(P,Q,R) is a 2^(P+Q+R)-dimensional real vector space.
28/// We equip it with the standard L² inner product on the coefficients:
29///
30/// ⟨x, y⟩ = Σᵢ xᵢ yᵢ
31///
32/// This makes Cl(P,Q,R) into a finite-dimensional Hilbert space.
33#[derive(Debug, Clone)]
34pub struct MultivectorHilbertSpace<const P: usize, const Q: usize, const R: usize> {
35    _phantom: PhantomData<(Multivector<P, Q, R>,)>,
36}
37
38impl<const P: usize, const Q: usize, const R: usize> Default for MultivectorHilbertSpace<P, Q, R> {
39    fn default() -> Self {
40        Self::new()
41    }
42}
43
44impl<const P: usize, const Q: usize, const R: usize> MultivectorHilbertSpace<P, Q, R> {
45    /// The dimension of the Clifford algebra.
46    pub const DIM: usize = 1 << (P + Q + R);
47
48    /// Create a new multivector Hilbert space.
49    pub fn new() -> Self {
50        Self {
51            _phantom: PhantomData,
52        }
53    }
54
55    /// Get the signature (P, Q, R) of the algebra.
56    pub fn signature(&self) -> (usize, usize, usize) {
57        (P, Q, R)
58    }
59
60    /// Get the dimension of the algebra.
61    pub fn algebra_dimension(&self) -> usize {
62        Self::DIM
63    }
64
65    /// Create a multivector from coefficients.
66    pub fn from_coefficients(&self, coefficients: &[f64]) -> Result<Multivector<P, Q, R>> {
67        if coefficients.len() != Self::DIM {
68            return Err(FunctionalError::dimension_mismatch(
69                Self::DIM,
70                coefficients.len(),
71            ));
72        }
73
74        Ok(Multivector::<P, Q, R>::from_slice(coefficients))
75    }
76
77    /// Get the coefficients of a multivector.
78    pub fn to_coefficients(&self, mv: &Multivector<P, Q, R>) -> Vec<f64> {
79        mv.to_vec()
80    }
81
82    /// Create a basis vector (unit multivector in direction i).
83    pub fn basis_vector(&self, index: usize) -> Result<Multivector<P, Q, R>> {
84        if index >= Self::DIM {
85            return Err(FunctionalError::invalid_parameters(format!(
86                "Basis index {} out of range [0, {})",
87                index,
88                Self::DIM
89            )));
90        }
91
92        let mut coeffs = vec![0.0; Self::DIM];
93        coeffs[index] = 1.0;
94        self.from_coefficients(&coeffs)
95    }
96
97    /// Get all basis vectors.
98    pub fn basis(&self) -> Vec<Multivector<P, Q, R>> {
99        (0..Self::DIM)
100            .map(|i| self.basis_vector(i).unwrap())
101            .collect()
102    }
103}
104
105impl<const P: usize, const Q: usize, const R: usize> VectorSpace<Multivector<P, Q, R>, f64>
106    for MultivectorHilbertSpace<P, Q, R>
107{
108    fn add(&self, x: &Multivector<P, Q, R>, y: &Multivector<P, Q, R>) -> Multivector<P, Q, R> {
109        x.add(y)
110    }
111
112    fn sub(&self, x: &Multivector<P, Q, R>, y: &Multivector<P, Q, R>) -> Multivector<P, Q, R> {
113        x - y
114    }
115
116    fn scale(&self, scalar: f64, x: &Multivector<P, Q, R>) -> Multivector<P, Q, R> {
117        x * scalar
118    }
119
120    fn zero(&self) -> Multivector<P, Q, R> {
121        Multivector::<P, Q, R>::zero()
122    }
123
124    fn dimension(&self) -> Option<usize> {
125        Some(Self::DIM)
126    }
127}
128
129impl<const P: usize, const Q: usize, const R: usize> NormedSpace<Multivector<P, Q, R>, f64>
130    for MultivectorHilbertSpace<P, Q, R>
131{
132    fn norm(&self, x: &Multivector<P, Q, R>) -> f64 {
133        // L² norm on coefficients
134        let coeffs = x.to_vec();
135        coeffs.iter().map(|c| c * c).sum::<f64>().sqrt()
136    }
137
138    fn normalize(&self, x: &Multivector<P, Q, R>) -> Option<Multivector<P, Q, R>> {
139        let n = self.norm(x);
140        if n < 1e-15 {
141            None
142        } else {
143            Some(self.scale(1.0 / n, x))
144        }
145    }
146}
147
148impl<const P: usize, const Q: usize, const R: usize> InnerProductSpace<Multivector<P, Q, R>, f64>
149    for MultivectorHilbertSpace<P, Q, R>
150{
151    fn inner_product(&self, x: &Multivector<P, Q, R>, y: &Multivector<P, Q, R>) -> f64 {
152        let x_coeffs = x.to_vec();
153        let y_coeffs = y.to_vec();
154        x_coeffs
155            .iter()
156            .zip(y_coeffs.iter())
157            .map(|(a, b)| a * b)
158            .sum()
159    }
160
161    fn project(&self, x: &Multivector<P, Q, R>, y: &Multivector<P, Q, R>) -> Multivector<P, Q, R> {
162        let ip_xy = self.inner_product(x, y);
163        let ip_yy = self.inner_product(y, y);
164        if ip_yy.abs() < 1e-15 {
165            self.zero()
166        } else {
167            self.scale(ip_xy / ip_yy, y)
168        }
169    }
170
171    fn gram_schmidt(&self, vectors: &[Multivector<P, Q, R>]) -> Vec<Multivector<P, Q, R>> {
172        let mut orthonormal = Vec::new();
173        for v in vectors {
174            let mut u = v.clone();
175            for q in &orthonormal {
176                let proj = self.project(&u, q);
177                u = self.sub(&u, &proj);
178            }
179            if let Some(normalized) = self.normalize(&u) {
180                orthonormal.push(normalized);
181            }
182        }
183        orthonormal
184    }
185}
186
187impl<const P: usize, const Q: usize, const R: usize>
188    BanachSpace<Multivector<P, Q, R>, f64, Complete> for MultivectorHilbertSpace<P, Q, R>
189{
190    fn is_cauchy_sequence(&self, sequence: &[Multivector<P, Q, R>], tolerance: f64) -> bool {
191        if sequence.len() < 2 {
192            return true;
193        }
194
195        // Check if the last few terms are getting closer
196        let n = sequence.len();
197        for i in (n.saturating_sub(5))..n {
198            for j in (i + 1)..n {
199                if self.distance(&sequence[i], &sequence[j]) > tolerance {
200                    return false;
201                }
202            }
203        }
204        true
205    }
206
207    fn sequence_limit(
208        &self,
209        sequence: &[Multivector<P, Q, R>],
210        tolerance: f64,
211    ) -> Result<Multivector<P, Q, R>> {
212        if sequence.is_empty() {
213            return Err(FunctionalError::convergence_error(
214                0,
215                "Empty sequence has no limit",
216            ));
217        }
218
219        if !self.is_cauchy_sequence(sequence, tolerance) {
220            return Err(FunctionalError::convergence_error(
221                sequence.len(),
222                "Sequence is not Cauchy",
223            ));
224        }
225
226        // For finite-dimensional spaces, Cauchy sequences converge
227        // Return the last element as the limit approximation
228        Ok(sequence.last().unwrap().clone())
229    }
230}
231
232impl<const P: usize, const Q: usize, const R: usize>
233    HilbertSpace<Multivector<P, Q, R>, f64, Complete> for MultivectorHilbertSpace<P, Q, R>
234{
235    fn riesz_representative<F>(&self, functional: F) -> Result<Multivector<P, Q, R>>
236    where
237        F: Fn(&Multivector<P, Q, R>) -> f64,
238    {
239        // In finite dimensions, the Riesz representative is just
240        // the vector of functional values on the basis
241        let mut coeffs = Vec::with_capacity(Self::DIM);
242        for i in 0..Self::DIM {
243            let basis_i = self.basis_vector(i)?;
244            coeffs.push(functional(&basis_i));
245        }
246        self.from_coefficients(&coeffs)
247    }
248}
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253
254    #[test]
255    fn test_hilbert_space_creation() {
256        let space: MultivectorHilbertSpace<3, 0, 0> = MultivectorHilbertSpace::new();
257        assert_eq!(space.algebra_dimension(), 8);
258        assert_eq!(space.signature(), (3, 0, 0));
259    }
260
261    #[test]
262    fn test_basis_vectors() {
263        let space: MultivectorHilbertSpace<2, 0, 0> = MultivectorHilbertSpace::new();
264        let basis = space.basis();
265        assert_eq!(basis.len(), 4); // 2^2 = 4
266
267        // Verify orthonormality
268        for (i, bi) in basis.iter().enumerate() {
269            assert!((space.norm(bi) - 1.0).abs() < 1e-10);
270            for (j, bj) in basis.iter().enumerate() {
271                if i != j {
272                    assert!(space.inner_product(bi, bj).abs() < 1e-10);
273                }
274            }
275        }
276    }
277
278    #[test]
279    fn test_vector_space_operations() {
280        let space: MultivectorHilbertSpace<2, 0, 0> = MultivectorHilbertSpace::new();
281
282        let x = space.from_coefficients(&[1.0, 2.0, 3.0, 4.0]).unwrap();
283        let y = space.from_coefficients(&[5.0, 6.0, 7.0, 8.0]).unwrap();
284
285        let sum = space.add(&x, &y);
286        let sum_coeffs = space.to_coefficients(&sum);
287        assert_eq!(sum_coeffs, vec![6.0, 8.0, 10.0, 12.0]);
288
289        let scaled = space.scale(2.0, &x);
290        let scaled_coeffs = space.to_coefficients(&scaled);
291        assert_eq!(scaled_coeffs, vec![2.0, 4.0, 6.0, 8.0]);
292    }
293
294    #[test]
295    fn test_inner_product() {
296        let space: MultivectorHilbertSpace<2, 0, 0> = MultivectorHilbertSpace::new();
297
298        let x = space.from_coefficients(&[1.0, 0.0, 0.0, 0.0]).unwrap();
299        let y = space.from_coefficients(&[0.0, 1.0, 0.0, 0.0]).unwrap();
300        let z = space.from_coefficients(&[1.0, 1.0, 0.0, 0.0]).unwrap();
301
302        // Orthogonality
303        assert!(space.inner_product(&x, &y).abs() < 1e-10);
304        assert!(!space.are_orthogonal(&x, &z, 1e-10));
305
306        // Self inner product equals squared norm
307        assert!((space.inner_product(&z, &z) - 2.0).abs() < 1e-10);
308        assert!((space.norm(&z) - 2.0_f64.sqrt()).abs() < 1e-10);
309    }
310
311    #[test]
312    fn test_gram_schmidt() {
313        let space: MultivectorHilbertSpace<2, 0, 0> = MultivectorHilbertSpace::new();
314
315        let v1 = space.from_coefficients(&[1.0, 1.0, 0.0, 0.0]).unwrap();
316        let v2 = space.from_coefficients(&[1.0, 0.0, 1.0, 0.0]).unwrap();
317        let v3 = space.from_coefficients(&[0.0, 1.0, 1.0, 0.0]).unwrap();
318
319        let orthonormal = space.gram_schmidt(&[v1, v2, v3]);
320        assert_eq!(orthonormal.len(), 3);
321
322        // Verify orthonormality
323        assert!(space.is_orthonormal(&orthonormal, 1e-10));
324    }
325
326    #[test]
327    fn test_riesz_representation() {
328        let space: MultivectorHilbertSpace<2, 0, 0> = MultivectorHilbertSpace::new();
329
330        // Define a linear functional: f(x) = x₀ + 2x₁
331        let functional = |x: &Multivector<2, 0, 0>| {
332            let coeffs = x.to_vec();
333            coeffs[0] + 2.0 * coeffs[1]
334        };
335
336        let repr = space.riesz_representative(functional).unwrap();
337        let repr_coeffs = space.to_coefficients(&repr);
338
339        // The Riesz representative should be [1, 2, 0, 0]
340        assert!((repr_coeffs[0] - 1.0).abs() < 1e-10);
341        assert!((repr_coeffs[1] - 2.0).abs() < 1e-10);
342        assert!(repr_coeffs[2].abs() < 1e-10);
343        assert!(repr_coeffs[3].abs() < 1e-10);
344    }
345
346    #[test]
347    fn test_projection() {
348        let space: MultivectorHilbertSpace<2, 0, 0> = MultivectorHilbertSpace::new();
349
350        let x = space.from_coefficients(&[3.0, 4.0, 0.0, 0.0]).unwrap();
351        let y = space.from_coefficients(&[1.0, 0.0, 0.0, 0.0]).unwrap();
352
353        let proj = space.project(&x, &y);
354        let proj_coeffs = space.to_coefficients(&proj);
355
356        // Projection of (3,4,0,0) onto (1,0,0,0) should be (3,0,0,0)
357        assert!((proj_coeffs[0] - 3.0).abs() < 1e-10);
358        assert!(proj_coeffs[1].abs() < 1e-10);
359    }
360
361    #[test]
362    fn test_best_approximation() {
363        let space: MultivectorHilbertSpace<2, 0, 0> = MultivectorHilbertSpace::new();
364
365        let x = space.from_coefficients(&[1.0, 2.0, 3.0, 4.0]).unwrap();
366
367        // Project onto the subspace spanned by first two basis vectors
368        let e0 = space.basis_vector(0).unwrap();
369        let e1 = space.basis_vector(1).unwrap();
370
371        let approx = space.best_approximation(&x, &[e0, e1]);
372        let approx_coeffs = space.to_coefficients(&approx);
373
374        // Best approximation should be (1, 2, 0, 0)
375        assert!((approx_coeffs[0] - 1.0).abs() < 1e-10);
376        assert!((approx_coeffs[1] - 2.0).abs() < 1e-10);
377        assert!(approx_coeffs[2].abs() < 1e-10);
378        assert!(approx_coeffs[3].abs() < 1e-10);
379    }
380}