amari_functional/space/
traits.rs

1//! Core traits for function spaces.
2//!
3//! This module defines the fundamental traits that characterize
4//! different types of function spaces in functional analysis.
5
6use crate::error::Result;
7use crate::phantom::{Complete, CompletenessProperty};
8use core::marker::PhantomData;
9
10/// A vector space over a scalar field.
11///
12/// This is the most fundamental algebraic structure, providing
13/// addition and scalar multiplication.
14///
15/// # Type Parameters
16///
17/// * `V` - The type of vectors in the space
18/// * `S` - The scalar field (typically `f64` or `Complex<f64>`)
19pub trait VectorSpace<V, S = f64> {
20    /// Add two vectors.
21    fn add(&self, x: &V, y: &V) -> V;
22
23    /// Subtract two vectors.
24    fn sub(&self, x: &V, y: &V) -> V;
25
26    /// Multiply a vector by a scalar.
27    fn scale(&self, scalar: S, x: &V) -> V;
28
29    /// Return the zero vector.
30    fn zero(&self) -> V;
31
32    /// Return the dimension of the space (may be infinite).
33    fn dimension(&self) -> Option<usize>;
34}
35
36/// A normed vector space.
37///
38/// Adds a norm ||·|| to a vector space, satisfying:
39/// - ||x|| ≥ 0 and ||x|| = 0 iff x = 0
40/// - ||αx|| = |α| ||x||
41/// - ||x + y|| ≤ ||x|| + ||y|| (triangle inequality)
42///
43/// # Type Parameters
44///
45/// * `V` - The type of vectors in the space
46/// * `S` - The scalar field
47pub trait NormedSpace<V, S = f64>: VectorSpace<V, S> {
48    /// Compute the norm of a vector.
49    fn norm(&self, x: &V) -> f64;
50
51    /// Compute the distance between two vectors.
52    fn distance(&self, x: &V, y: &V) -> f64 {
53        self.norm(&self.sub(x, y))
54    }
55
56    /// Normalize a vector to unit length.
57    ///
58    /// Returns `None` if the vector is zero.
59    fn normalize(&self, x: &V) -> Option<V>
60    where
61        S: From<f64>,
62        V: Clone;
63}
64
65/// A complete normed space (Banach space).
66///
67/// A normed space where every Cauchy sequence converges.
68/// This is essential for many existence theorems in analysis.
69///
70/// # Type Parameters
71///
72/// * `V` - The type of vectors in the space
73/// * `S` - The scalar field
74/// * `C` - Completeness phantom type (must be `Complete`)
75pub trait BanachSpace<V, S = f64, C = Complete>: NormedSpace<V, S>
76where
77    C: CompletenessProperty,
78{
79    /// Check if a sequence appears to be Cauchy.
80    ///
81    /// This is a numerical approximation based on the last few terms.
82    fn is_cauchy_sequence(&self, sequence: &[V], tolerance: f64) -> bool;
83
84    /// Compute the limit of a Cauchy sequence if it exists.
85    fn sequence_limit(&self, sequence: &[V], tolerance: f64) -> Result<V>;
86}
87
88/// An inner product space.
89///
90/// Adds an inner product ⟨·,·⟩ satisfying:
91/// - ⟨x,y⟩ = conj(⟨y,x⟩) (conjugate symmetry)
92/// - ⟨αx+βy,z⟩ = α⟨x,z⟩ + β⟨y,z⟩ (linearity)
93/// - ⟨x,x⟩ ≥ 0 and ⟨x,x⟩ = 0 iff x = 0 (positive definiteness)
94///
95/// The norm is induced by ||x|| = √⟨x,x⟩.
96///
97/// # Type Parameters
98///
99/// * `V` - The type of vectors in the space
100/// * `S` - The scalar field (inner product values)
101pub trait InnerProductSpace<V, S = f64>: NormedSpace<V, S> {
102    /// Compute the inner product of two vectors.
103    fn inner_product(&self, x: &V, y: &V) -> S;
104
105    /// Check if two vectors are orthogonal.
106    fn are_orthogonal(&self, x: &V, y: &V, tolerance: f64) -> bool
107    where
108        S: Into<f64>,
109    {
110        let ip: f64 = self.inner_product(x, y).into();
111        ip.abs() < tolerance
112    }
113
114    /// Project x onto y.
115    ///
116    /// Returns the component of x in the direction of y.
117    fn project(&self, x: &V, y: &V) -> V
118    where
119        S: Into<f64> + From<f64>,
120        V: Clone;
121
122    /// Gram-Schmidt orthogonalization of a set of vectors.
123    fn gram_schmidt(&self, vectors: &[V]) -> Vec<V>
124    where
125        S: Into<f64> + From<f64>,
126        V: Clone;
127}
128
129/// A complete inner product space (Hilbert space).
130///
131/// The most important space in functional analysis, combining
132/// the geometric structure of inner products with completeness.
133///
134/// # Type Parameters
135///
136/// * `V` - The type of vectors in the space
137/// * `S` - The scalar field
138/// * `C` - Completeness phantom type (must be `Complete`)
139pub trait HilbertSpace<V, S = f64, C = Complete>:
140    InnerProductSpace<V, S> + BanachSpace<V, S, C>
141where
142    C: CompletenessProperty,
143{
144    /// Apply the Riesz representation theorem.
145    ///
146    /// Given a continuous linear functional f, returns the unique
147    /// vector v such that f(x) = ⟨x, v⟩ for all x.
148    fn riesz_representative<F>(&self, functional: F) -> Result<V>
149    where
150        F: Fn(&V) -> S;
151
152    /// Compute the orthogonal complement projection.
153    ///
154    /// Given a closed subspace (represented by an orthonormal basis),
155    /// project onto the orthogonal complement.
156    fn orthogonal_complement_projection(&self, x: &V, subspace_basis: &[V]) -> V
157    where
158        S: Into<f64> + From<f64>,
159        V: Clone,
160    {
161        let mut result = x.clone();
162        for basis_vec in subspace_basis {
163            let proj = self.project(&result, basis_vec);
164            result = self.sub(&result, &proj);
165        }
166        result
167    }
168
169    /// Compute the best approximation to x from a subspace.
170    ///
171    /// Returns the element of the subspace closest to x.
172    fn best_approximation(&self, x: &V, subspace_basis: &[V]) -> V
173    where
174        S: Into<f64> + From<f64>,
175        V: Clone,
176    {
177        let mut result = self.zero();
178        for basis_vec in subspace_basis {
179            let proj = self.project(x, basis_vec);
180            result = self.add(&result, &proj);
181        }
182        result
183    }
184
185    /// Check if a set of vectors forms an orthonormal system.
186    fn is_orthonormal(&self, vectors: &[V], tolerance: f64) -> bool
187    where
188        S: Into<f64>,
189    {
190        for (i, vi) in vectors.iter().enumerate() {
191            // Check normalization
192            let norm_i = self.norm(vi);
193            if (norm_i - 1.0).abs() > tolerance {
194                return false;
195            }
196
197            // Check orthogonality
198            for vj in vectors.iter().skip(i + 1) {
199                if !self.are_orthogonal(vi, vj, tolerance) {
200                    return false;
201                }
202            }
203        }
204        true
205    }
206}
207
208/// Marker struct for spaces with specific completeness properties.
209#[derive(Debug, Clone, Copy)]
210pub struct SpaceWithCompleteness<S, C: CompletenessProperty> {
211    space: S,
212    _completeness: PhantomData<C>,
213}
214
215impl<S, C: CompletenessProperty> SpaceWithCompleteness<S, C> {
216    /// Create a new space with completeness marker.
217    pub fn new(space: S) -> Self {
218        Self {
219            space,
220            _completeness: PhantomData,
221        }
222    }
223
224    /// Get a reference to the underlying space.
225    pub fn space(&self) -> &S {
226        &self.space
227    }
228
229    /// Get a mutable reference to the underlying space.
230    pub fn space_mut(&mut self) -> &mut S {
231        &mut self.space
232    }
233
234    /// Consume and return the underlying space.
235    pub fn into_inner(self) -> S {
236        self.space
237    }
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243
244    // Simple finite-dimensional test space
245    struct R3Space;
246
247    impl VectorSpace<[f64; 3], f64> for R3Space {
248        fn add(&self, x: &[f64; 3], y: &[f64; 3]) -> [f64; 3] {
249            [x[0] + y[0], x[1] + y[1], x[2] + y[2]]
250        }
251
252        fn sub(&self, x: &[f64; 3], y: &[f64; 3]) -> [f64; 3] {
253            [x[0] - y[0], x[1] - y[1], x[2] - y[2]]
254        }
255
256        fn scale(&self, scalar: f64, x: &[f64; 3]) -> [f64; 3] {
257            [scalar * x[0], scalar * x[1], scalar * x[2]]
258        }
259
260        fn zero(&self) -> [f64; 3] {
261            [0.0, 0.0, 0.0]
262        }
263
264        fn dimension(&self) -> Option<usize> {
265            Some(3)
266        }
267    }
268
269    impl NormedSpace<[f64; 3], f64> for R3Space {
270        fn norm(&self, x: &[f64; 3]) -> f64 {
271            (x[0] * x[0] + x[1] * x[1] + x[2] * x[2]).sqrt()
272        }
273
274        fn normalize(&self, x: &[f64; 3]) -> Option<[f64; 3]> {
275            let n = self.norm(x);
276            if n < 1e-15 {
277                None
278            } else {
279                Some(self.scale(1.0 / n, x))
280            }
281        }
282    }
283
284    impl InnerProductSpace<[f64; 3], f64> for R3Space {
285        fn inner_product(&self, x: &[f64; 3], y: &[f64; 3]) -> f64 {
286            x[0] * y[0] + x[1] * y[1] + x[2] * y[2]
287        }
288
289        fn project(&self, x: &[f64; 3], y: &[f64; 3]) -> [f64; 3] {
290            let ip_xy = self.inner_product(x, y);
291            let ip_yy = self.inner_product(y, y);
292            if ip_yy.abs() < 1e-15 {
293                self.zero()
294            } else {
295                self.scale(ip_xy / ip_yy, y)
296            }
297        }
298
299        fn gram_schmidt(&self, vectors: &[[f64; 3]]) -> Vec<[f64; 3]> {
300            let mut orthonormal = Vec::new();
301            for v in vectors {
302                let mut u = *v;
303                for q in &orthonormal {
304                    let proj = self.project(&u, q);
305                    u = self.sub(&u, &proj);
306                }
307                if let Some(normalized) = self.normalize(&u) {
308                    orthonormal.push(normalized);
309                }
310            }
311            orthonormal
312        }
313    }
314
315    #[test]
316    fn test_vector_space_operations() {
317        let space = R3Space;
318
319        let x = [1.0, 2.0, 3.0];
320        let y = [4.0, 5.0, 6.0];
321
322        let sum = space.add(&x, &y);
323        assert_eq!(sum, [5.0, 7.0, 9.0]);
324
325        let diff = space.sub(&x, &y);
326        assert_eq!(diff, [-3.0, -3.0, -3.0]);
327
328        let scaled = space.scale(2.0, &x);
329        assert_eq!(scaled, [2.0, 4.0, 6.0]);
330
331        assert_eq!(space.dimension(), Some(3));
332    }
333
334    #[test]
335    fn test_normed_space_operations() {
336        let space = R3Space;
337
338        let x = [3.0, 4.0, 0.0];
339        assert!((space.norm(&x) - 5.0).abs() < 1e-10);
340
341        let normalized = space.normalize(&x).unwrap();
342        assert!((space.norm(&normalized) - 1.0).abs() < 1e-10);
343
344        let y = [1.0, 0.0, 0.0];
345        let dist = space.distance(&x, &y);
346        assert!((dist - (4.0_f64 + 16.0).sqrt()).abs() < 1e-10);
347    }
348
349    #[test]
350    fn test_inner_product_space_operations() {
351        let space = R3Space;
352
353        let x = [1.0, 0.0, 0.0];
354        let y = [0.0, 1.0, 0.0];
355        let z = [1.0, 1.0, 0.0];
356
357        // Orthogonality
358        assert!(space.are_orthogonal(&x, &y, 1e-10));
359        assert!(!space.are_orthogonal(&x, &z, 1e-10));
360
361        // Projection
362        let proj = space.project(&z, &x);
363        assert!((proj[0] - 1.0).abs() < 1e-10);
364        assert!(proj[1].abs() < 1e-10);
365        assert!(proj[2].abs() < 1e-10);
366    }
367
368    #[test]
369    fn test_gram_schmidt() {
370        let space = R3Space;
371
372        let vectors = [[1.0, 1.0, 0.0], [1.0, 0.0, 1.0], [0.0, 1.0, 1.0]];
373        let orthonormal = space.gram_schmidt(&vectors);
374
375        assert_eq!(orthonormal.len(), 3);
376
377        // Check orthonormality
378        for (i, vi) in orthonormal.iter().enumerate() {
379            // Check unit length
380            assert!((space.norm(vi) - 1.0).abs() < 1e-10);
381
382            // Check orthogonality
383            for (j, vj) in orthonormal.iter().enumerate() {
384                if i != j {
385                    assert!(space.are_orthogonal(vi, vj, 1e-10));
386                }
387            }
388        }
389    }
390}