Skip to main content

sim_lib_discrete_algebra/
views.rs

1//! Implicit matrix views that act without materializing dense storage.
2//!
3//! Each view provides a `matvec` (apply to a vector) and an explicit
4//! `materialize` guarded by [`AlgebraLimits`], so a large view never silently
5//! allocates a dense matrix. The spectral crate's Hadamard view reuses this
6//! contract.
7
8use crate::error::AlgebraError;
9use crate::matrix::{AlgebraLimits, Matrix};
10use crate::semiring::Semiring;
11
12fn check_len<S: Semiring>(view_dim: usize, v: &[S]) -> Result<(), AlgebraError> {
13    if v.len() != view_dim {
14        return Err(AlgebraError::ShapeMismatch(format!(
15            "matvec: vector len {} != view dimension {view_dim}",
16            v.len()
17        )));
18    }
19    Ok(())
20}
21
22fn check_dim(n: usize, limits: AlgebraLimits) -> Result<(), AlgebraError> {
23    if n > limits.max_dim {
24        return Err(AlgebraError::LimitExceeded(format!(
25            "materialize: dimension {n} exceeds max_dim {}",
26            limits.max_dim
27        )));
28    }
29    Ok(())
30}
31
32/// The `n x n` identity, applied without storage.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct IdentityView {
35    /// The dimension `n`.
36    pub n: usize,
37}
38
39impl IdentityView {
40    /// Apply to a vector: returns a clone of `v`.
41    pub fn matvec<S: Semiring>(&self, v: &[S]) -> Result<Vec<S>, AlgebraError> {
42        check_len(self.n, v)?;
43        Ok(v.to_vec())
44    }
45
46    /// Materialize as a dense identity, guarded by `limits`.
47    pub fn materialize<S: Semiring>(
48        &self,
49        limits: AlgebraLimits,
50    ) -> Result<Matrix<S>, AlgebraError> {
51        check_dim(self.n, limits)?;
52        Matrix::try_identity_with_limits(self.n, limits)
53    }
54}
55
56/// A diagonal matrix view with entries `diag`.
57#[derive(Debug, Clone, PartialEq)]
58pub struct DiagonalView<S: Semiring> {
59    /// Diagonal entries; the view is `diag.len() x diag.len()`.
60    pub diag: Vec<S>,
61}
62
63impl<S: Semiring> DiagonalView<S> {
64    /// Apply to a vector: `result[i] = diag[i] * v[i]`.
65    pub fn matvec(&self, v: &[S]) -> Result<Vec<S>, AlgebraError> {
66        check_len(self.diag.len(), v)?;
67        Ok(self
68            .diag
69            .iter()
70            .zip(v.iter())
71            .map(|(d, x)| d.mul(x))
72            .collect())
73    }
74
75    /// Materialize as a dense diagonal matrix, guarded by `limits`.
76    pub fn materialize(&self, limits: AlgebraLimits) -> Result<Matrix<S>, AlgebraError> {
77        let n = self.diag.len();
78        check_dim(n, limits)?;
79        let mut m = Matrix::try_new_with_limits(n, n, limits)?;
80        for (i, d) in self.diag.iter().enumerate() {
81            m.set(i, i, d.clone())?;
82        }
83        Ok(m)
84    }
85}
86
87/// A permutation matrix view: row `i` has its `one` in column `perm[i]`.
88#[derive(Debug, Clone, PartialEq, Eq)]
89pub struct PermutationView {
90    /// `perm[i]` is the column holding the `one` in row `i`.
91    pub perm: Vec<usize>,
92}
93
94impl PermutationView {
95    /// Apply to a vector: `result[i] = v[perm[i]]`.
96    pub fn matvec<S: Semiring>(&self, v: &[S]) -> Result<Vec<S>, AlgebraError> {
97        check_len(self.perm.len(), v)?;
98        let mut out = Vec::with_capacity(self.perm.len());
99        for &p in &self.perm {
100            let val = v
101                .get(p)
102                .ok_or(AlgebraError::IndexOutOfBounds {
103                    index: p,
104                    len: v.len(),
105                })?
106                .clone();
107            out.push(val);
108        }
109        Ok(out)
110    }
111
112    /// Materialize as a dense permutation matrix, guarded by `limits`.
113    pub fn materialize<S: Semiring>(
114        &self,
115        limits: AlgebraLimits,
116    ) -> Result<Matrix<S>, AlgebraError> {
117        let n = self.perm.len();
118        check_dim(n, limits)?;
119        let mut m: Matrix<S> = Matrix::try_new_with_limits(n, n, limits)?;
120        for (i, &p) in self.perm.iter().enumerate() {
121            if p >= n {
122                return Err(AlgebraError::IndexOutOfBounds { index: p, len: n });
123            }
124            m.set(i, p, S::one())?;
125        }
126        Ok(m)
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133    use crate::Counting;
134
135    fn vec_u64(xs: &[u64]) -> Vec<Counting> {
136        xs.iter().map(|&x| Counting::from_u64(x)).collect()
137    }
138
139    #[test]
140    fn identity_matvec_returns_input() {
141        let v = vec_u64(&[3, 5, 7]);
142        assert_eq!(IdentityView { n: 3 }.matvec(&v).unwrap(), v);
143    }
144
145    #[test]
146    fn diagonal_matvec_scales() {
147        let d = DiagonalView {
148            diag: vec_u64(&[2, 3]),
149        };
150        assert_eq!(d.matvec(&vec_u64(&[5, 7])).unwrap(), vec_u64(&[10, 21]));
151    }
152
153    #[test]
154    fn permutation_matvec_permutes() {
155        // perm = [2,0,1] gathers v[2], v[0], v[1].
156        let p = PermutationView {
157            perm: vec![2, 0, 1],
158        };
159        assert_eq!(p.matvec(&vec_u64(&[1, 2, 3])).unwrap(), vec_u64(&[3, 1, 2]));
160    }
161
162    #[test]
163    fn permutation_materialize_matches_matvec() {
164        let p = PermutationView {
165            perm: vec![2, 0, 1],
166        };
167        let m: Matrix<Counting> = p.materialize(AlgebraLimits::default()).unwrap();
168        let v = vec_u64(&[1, 2, 3]);
169        // Dense P * v equals the view's matvec.
170        let dense_col = Matrix::from_rows(v.iter().map(|x| vec![x.clone()]).collect()).unwrap();
171        let prod = m.matmul(&dense_col).unwrap();
172        assert_eq!(prod.data, p.matvec(&v).unwrap());
173    }
174
175    #[test]
176    fn materialize_respects_limit() {
177        let id = IdentityView { n: 100 };
178        let limited = AlgebraLimits { max_dim: 10 };
179        assert!(matches!(
180            id.materialize::<Counting>(limited),
181            Err(AlgebraError::LimitExceeded(_))
182        ));
183    }
184}