amari_functional/operator/
traits.rs

1//! Core traits for linear operators.
2//!
3//! This module defines the fundamental traits for linear operators
4//! on Hilbert spaces.
5
6use crate::error::Result;
7use crate::phantom::{Bounded, BoundednessProperty, SelfAdjoint, SymmetryProperty};
8
9/// A linear operator between vector spaces.
10///
11/// A linear operator T: V → W satisfies:
12/// - T(x + y) = T(x) + T(y)
13/// - T(αx) = αT(x)
14///
15/// # Type Parameters
16///
17/// * `V` - Domain space element type
18/// * `W` - Codomain space element type
19pub trait LinearOperator<V, W = V> {
20    /// Apply the operator to an element.
21    fn apply(&self, x: &V) -> Result<W>;
22
23    /// Get the domain dimension (None if infinite).
24    fn domain_dimension(&self) -> Option<usize>;
25
26    /// Get the codomain dimension (None if infinite).
27    fn codomain_dimension(&self) -> Option<usize>;
28}
29
30/// A bounded linear operator.
31///
32/// A linear operator T is bounded if there exists M > 0 such that
33/// ||Tx|| ≤ M||x|| for all x.
34///
35/// The operator norm is ||T|| = sup{||Tx|| : ||x|| = 1}.
36///
37/// # Type Parameters
38///
39/// * `V` - Domain space element type
40/// * `W` - Codomain space element type
41/// * `B` - Boundedness phantom marker
42pub trait BoundedOperator<V, W = V, B = Bounded>: LinearOperator<V, W>
43where
44    B: BoundednessProperty,
45{
46    /// Compute or estimate the operator norm ||T||.
47    fn operator_norm(&self) -> f64;
48
49    /// Check if the operator is bounded by a given constant.
50    fn is_bounded_by(&self, bound: f64) -> bool {
51        self.operator_norm() <= bound
52    }
53}
54
55/// Trait for computing operator norms.
56pub trait OperatorNorm {
57    /// Compute the operator norm.
58    fn norm(&self) -> f64;
59
60    /// Compute the Frobenius norm (for matrix operators).
61    fn frobenius_norm(&self) -> Option<f64> {
62        None
63    }
64}
65
66/// An operator that has an adjoint.
67///
68/// The adjoint T* of an operator T satisfies:
69/// ⟨Tx, y⟩ = ⟨x, T*y⟩
70///
71/// # Type Parameters
72///
73/// * `V` - The Hilbert space element type
74pub trait AdjointableOperator<V>: LinearOperator<V, V> {
75    /// The type of the adjoint operator.
76    type Adjoint: LinearOperator<V, V>;
77
78    /// Compute the adjoint operator.
79    fn adjoint(&self) -> Self::Adjoint;
80
81    /// Check if this operator is self-adjoint (T = T*).
82    fn is_self_adjoint(&self) -> bool;
83
84    /// Check if this operator is normal (TT* = T*T).
85    fn is_normal(&self) -> bool;
86}
87
88/// A self-adjoint operator (T = T*).
89///
90/// Self-adjoint operators have:
91/// - Real spectrum
92/// - Orthogonal eigenvectors
93/// - Spectral decomposition
94///
95/// # Type Parameters
96///
97/// * `V` - The Hilbert space element type
98/// * `S` - Symmetry phantom marker (should be SelfAdjoint)
99pub trait SelfAdjointOperator<V, S = SelfAdjoint>: LinearOperator<V, V>
100where
101    S: SymmetryProperty,
102{
103    /// Check if all eigenvalues are real (always true for self-adjoint).
104    fn has_real_spectrum(&self) -> bool {
105        true
106    }
107
108    /// Compute the quadratic form ⟨Tx, x⟩.
109    fn quadratic_form(&self, x: &V) -> Result<f64>;
110
111    /// Check if the operator is positive (⟨Tx, x⟩ ≥ 0).
112    fn is_positive(&self, x: &V) -> Result<bool> {
113        Ok(self.quadratic_form(x)? >= 0.0)
114    }
115
116    /// Check if the operator is positive definite (⟨Tx, x⟩ > 0 for x ≠ 0).
117    fn is_positive_definite(&self, x: &V, tolerance: f64) -> Result<bool> {
118        Ok(self.quadratic_form(x)? > tolerance)
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use amari_core::Multivector;
126
127    // Simple test operator: identity
128    struct TestIdentity;
129
130    impl LinearOperator<Multivector<2, 0, 0>> for TestIdentity {
131        fn apply(&self, x: &Multivector<2, 0, 0>) -> Result<Multivector<2, 0, 0>> {
132            Ok(x.clone())
133        }
134
135        fn domain_dimension(&self) -> Option<usize> {
136            Some(4) // 2^2 = 4
137        }
138
139        fn codomain_dimension(&self) -> Option<usize> {
140            Some(4)
141        }
142    }
143
144    impl BoundedOperator<Multivector<2, 0, 0>> for TestIdentity {
145        fn operator_norm(&self) -> f64 {
146            1.0
147        }
148    }
149
150    #[test]
151    fn test_linear_operator_apply() {
152        let identity = TestIdentity;
153        let x = Multivector::<2, 0, 0>::from_slice(&[1.0, 2.0, 3.0, 4.0]);
154        let y = identity.apply(&x).unwrap();
155        assert_eq!(x.to_vec(), y.to_vec());
156    }
157
158    #[test]
159    fn test_bounded_operator_norm() {
160        let identity = TestIdentity;
161        assert!((identity.operator_norm() - 1.0).abs() < 1e-10);
162        assert!(identity.is_bounded_by(1.0));
163        assert!(identity.is_bounded_by(2.0));
164        assert!(!identity.is_bounded_by(0.5));
165    }
166
167    #[test]
168    fn test_operator_dimensions() {
169        let identity = TestIdentity;
170        assert_eq!(identity.domain_dimension(), Some(4));
171        assert_eq!(identity.codomain_dimension(), Some(4));
172    }
173}