fp/matrix/
subquotient.rs

1use super::Subspace;
2use crate::{
3    matrix::Matrix,
4    prime::ValidPrime,
5    vector::{FpSlice, FpSliceMut, FpVector},
6};
7
8#[derive(Debug, Clone)]
9pub struct Subquotient {
10    gens: Subspace,
11    quotient: Subspace,
12    dimension: usize,
13}
14
15impl std::fmt::Display for Subquotient {
16    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
17        writeln!(f, "Generators:\n{}", self.gens)?;
18        writeln!(f, "Zeros:\n{}", self.quotient)
19    }
20}
21
22impl Subquotient {
23    /// Create a new subquotient of an ambient space of dimension `dim`. This defaults to the zero
24    /// subspace.
25    pub fn new(p: ValidPrime, dim: usize) -> Self {
26        Self {
27            gens: Subspace::new(p, dim),
28            quotient: Subspace::new(p, dim),
29            dimension: 0,
30        }
31    }
32
33    /// Create a new subquotient of an ambient space of dimension `dim`, where the subspace is the
34    /// full subspace and quotient is trivial.
35    pub fn new_full(p: ValidPrime, dim: usize) -> Self {
36        let mut result = Self::new(p, dim);
37        result.gens.set_to_entire();
38        result.dimension = dim;
39        result
40    }
41
42    /// Given a vector `elt`, project `elt` to the complement and express
43    /// as a linear combination of the basis. The result is returned as a list of coefficients.
44    /// If elt is nonzero afterwards, this means the vector was not in the subspace to begin with.
45    pub fn reduce(&self, mut elt: FpSliceMut) -> Vec<u32> {
46        self.quotient.reduce(elt.copy());
47        let mut result = Vec::with_capacity(self.gens.ambient_dimension());
48        for i in 0..self.gens.ambient_dimension() {
49            if self.gens.pivots()[i] < 0 {
50                continue;
51            }
52            let c = elt.as_slice().entry(i);
53            result.push(c);
54            if c != 0 {
55                elt.add(
56                    self.gens.row(self.gens.pivots()[i] as usize),
57                    ((elt.prime() - 1) * c) % elt.prime(),
58                );
59            }
60        }
61        result
62    }
63
64    /// Project the vector onto the complement of the quotient part of the subquotient.
65    pub fn reduce_by_quotient(&self, elt: FpSliceMut) {
66        self.quotient.reduce(elt)
67    }
68
69    /// Set the subquotient to be the full ambient space quotiented by zero
70    pub fn set_to_full(&mut self) {
71        self.quotient.set_to_zero();
72        self.gens.set_to_entire();
73    }
74
75    pub fn zeros(&self) -> &Subspace {
76        &self.quotient
77    }
78
79    pub fn gens(&self) -> impl Iterator<Item = FpSlice<'_>> {
80        self.gens.iter()
81    }
82
83    pub fn quotient_dimension(&self) -> usize {
84        self.ambient_dimension() - self.quotient.dimension()
85    }
86
87    /// The dimension of the subspace part of the subquotient.
88    pub fn subspace_dimension(&self) -> usize {
89        self.dimension + self.quotient.dimension()
90    }
91
92    /// The generators of the subspace part of the subquotient.
93    pub fn subspace_gens(&self) -> impl Iterator<Item = FpSlice<'_>> {
94        self.gens().chain(self.quotient.iter())
95    }
96
97    /// The pivot columns of the complement to the subspace
98    pub fn complement_pivots(&self) -> impl Iterator<Item = usize> + '_ {
99        (0..self.ambient_dimension())
100            .filter(|&i| self.quotient.pivots()[i] < 0 && self.gens.pivots()[i] < 0)
101    }
102
103    pub fn quotient(&mut self, elt: FpSlice) {
104        self.quotient.add_vector(elt);
105
106        self.gens.update_then_row_reduce(|gens_matrix| {
107            for elt in gens_matrix.iter_mut().take(self.dimension) {
108                self.quotient.reduce(elt);
109            }
110        });
111
112        self.dimension = self.gens.dimension();
113    }
114
115    pub fn dimension(&self) -> usize {
116        self.dimension
117    }
118
119    pub fn ambient_dimension(&self) -> usize {
120        self.gens.ambient_dimension()
121    }
122
123    pub fn prime(&self) -> ValidPrime {
124        self.gens.prime()
125    }
126
127    pub fn is_empty(&self) -> bool {
128        self.dimension == 0
129    }
130
131    pub fn clear_gens(&mut self) {
132        self.gens.set_to_zero();
133        self.dimension = 0;
134    }
135
136    pub fn add_gen(&mut self, g: FpSlice) {
137        self.gens.update_then_row_reduce(|gens_matrix| {
138            let mut new_row = gens_matrix.row_mut(self.dimension);
139            new_row.assign(g);
140            self.quotient.reduce(new_row);
141        });
142        self.dimension = self.gens.dimension();
143    }
144
145    pub fn reduce_matrix(matrix: &Matrix, source: &Self, target: &Self) -> Vec<Vec<u32>> {
146        let mut result = Vec::with_capacity(source.dimension());
147        let mut temp = FpVector::new(source.prime(), target.ambient_dimension());
148        for v in source.gens() {
149            matrix.apply(temp.as_slice_mut(), 1, v);
150            result.push(target.reduce(temp.as_slice_mut()));
151            temp.set_to_zero()
152        }
153        result
154    }
155
156    /// Given a chain of subspaces `quotient` < `sub` in some ambient space, compute the subquotient
157    /// `sub`/`quotient`. The answer is expressed as a list of basis vectors of `sub` whose image in
158    /// `sub`/`quotient` forms a basis, and a basis vector of `sub` is described by its index in the
159    /// list of basis vectors of `sub` (not the ambient space).
160    ///
161    /// Note that the `quotient` argument does not need to be a subspace of the `sub` argument, nor
162    /// do they need to be disjoint. Mathematically, this method constructs the space `(sub +
163    /// quotient) / quotient`.
164    pub fn from_parts(mut sub: Subspace, quotient: Subspace) -> Self {
165        let dim = sub.dimension();
166
167        sub.update_then_row_reduce(|sub_matrix| {
168            for row in sub_matrix.iter_mut().take(dim) {
169                quotient.reduce(row);
170            }
171        });
172
173        Self {
174            dimension: sub.dimension(),
175            gens: sub,
176            quotient,
177        }
178    }
179
180    pub fn quotient_pivots(&self) -> &[isize] {
181        self.quotient.pivots()
182    }
183}
184
185#[cfg(feature = "proptest")]
186pub mod arbitrary {
187    use proptest::prelude::*;
188
189    use super::*;
190    pub use crate::matrix::subspace::arbitrary::MAX_DIM;
191    use crate::matrix::subspace::arbitrary::SubspaceArbParams;
192
193    #[derive(Debug, Clone)]
194    pub struct SubquotientArbParams {
195        pub p: Option<ValidPrime>,
196        pub dim: BoxedStrategy<usize>,
197    }
198
199    impl Default for SubquotientArbParams {
200        fn default() -> Self {
201            Self {
202                p: None,
203                dim: (0..=MAX_DIM).boxed(),
204            }
205        }
206    }
207
208    impl Arbitrary for Subquotient {
209        type Parameters = SubquotientArbParams;
210        type Strategy = BoxedStrategy<Self>;
211
212        fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
213            let p = match args.p {
214                Some(p) => Just(p).boxed(),
215                None => any::<ValidPrime>().boxed(),
216            };
217
218            (p, args.dim)
219                .prop_flat_map(|(p, dim)| {
220                    let sub = Subspace::arbitrary_with(SubspaceArbParams {
221                        p: Some(p),
222                        dim: Just(dim).boxed(),
223                    });
224                    let quotient = Subspace::arbitrary_with(SubspaceArbParams {
225                        p: Some(p),
226                        dim: Just(dim).boxed(),
227                    });
228
229                    (sub, quotient)
230                })
231                .prop_map(|(sub, quotient)| Self::from_parts(sub, quotient))
232                .boxed()
233        }
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use expect_test::expect;
240    use proptest::prelude::*;
241
242    use super::*;
243
244    #[test]
245    fn test_add_gen() {
246        let p = ValidPrime::new(3);
247
248        let mut sq = Subquotient::new(p, 5);
249        sq.quotient(FpVector::from_slice(p, &[1, 1, 0, 0, 1]).as_slice());
250        sq.quotient(FpVector::from_slice(p, &[0, 2, 0, 0, 1]).as_slice());
251        sq.add_gen(FpVector::from_slice(p, &[1, 1, 0, 0, 0]).as_slice());
252        sq.add_gen(FpVector::from_slice(p, &[0, 1, 0, 0, 0]).as_slice());
253
254        expect![[r#"
255            Generators:
256            [0, 0, 0, 0, 1]
257
258            Zeros:
259            [1, 0, 0, 0, 2]
260            [0, 1, 0, 0, 2]
261
262        "#]]
263        .assert_eq(&sq.to_string());
264
265        expect![[r#"
266            [
267                2,
268            ]
269        "#]]
270        .assert_debug_eq(&sq.reduce(FpVector::from_slice(p, &[2, 0, 0, 0, 0]).as_slice_mut()));
271
272        assert_eq!(sq.gens().count(), 1);
273    }
274
275    proptest! {
276        #[test]
277        fn test_sum_quotient_gens_complement_is_ambient(sq: Subquotient) {
278            let quotient_dim = sq.zeros().dimension();
279            let gens_dim = sq.gens().count();
280            let complement_dim = sq.complement_pivots().count();
281            assert_eq!(quotient_dim + gens_dim + complement_dim, sq.ambient_dimension());
282        }
283    }
284}