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 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 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 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 pub fn reduce_by_quotient(&self, elt: FpSliceMut) {
66 self.quotient.reduce(elt)
67 }
68
69 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 pub fn subspace_dimension(&self) -> usize {
89 self.dimension + self.quotient.dimension()
90 }
91
92 pub fn subspace_gens(&self) -> impl Iterator<Item = FpSlice<'_>> {
94 self.gens().chain(self.quotient.iter())
95 }
96
97 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 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}