algebra/module/free_module.rs
1use std::sync::Arc;
2
3use fp::vector::{FpSlice, FpSliceMut};
4use once::{OnceBiVec, OnceVec};
5
6use crate::{
7 algebra::MuAlgebra,
8 module::{Module, ZeroModule},
9};
10
11#[derive(Clone, Debug)]
12pub struct OperationGeneratorPair {
13 pub operation_degree: i32,
14 pub operation_index: usize,
15 pub generator_degree: i32,
16 pub generator_index: usize,
17}
18
19pub type FreeModule<A> = MuFreeModule<false, A>;
20pub type UnstableFreeModule<A> = MuFreeModule<true, A>;
21
22/// A free module.
23///
24/// A free module is uniquely determined by its list of generators. The generators are listed in
25/// increasing degrees, and the index in this list is the internal index.
26pub struct MuFreeModule<const U: bool, A: MuAlgebra<U>> {
27 algebra: Arc<A>,
28 name: String,
29 min_degree: i32,
30 gen_names: OnceBiVec<Vec<String>>,
31 /// degree -> internal index of first generator in degree
32 gen_deg_idx_to_internal_idx: OnceBiVec<usize>,
33 num_gens: OnceBiVec<usize>,
34 basis_element_to_opgen: OnceBiVec<OnceVec<OperationGeneratorPair>>,
35 /// degree -> internal_gen_idx -> the offset of the generator in degree
36 generator_to_index: OnceBiVec<OnceVec<usize>>,
37}
38
39impl<const U: bool, A: MuAlgebra<U>> std::fmt::Display for MuFreeModule<U, A> {
40 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
41 write!(f, "{}", self.name)
42 }
43}
44
45impl<const U: bool, A: MuAlgebra<U>> MuFreeModule<U, A> {
46 pub fn new(algebra: Arc<A>, name: String, min_degree: i32) -> Self {
47 let gen_deg_idx_to_internal_idx = OnceBiVec::new(min_degree);
48 gen_deg_idx_to_internal_idx.push(0);
49 Self {
50 algebra,
51 name,
52 min_degree,
53 gen_names: OnceBiVec::new(min_degree),
54 gen_deg_idx_to_internal_idx,
55 num_gens: OnceBiVec::new(min_degree),
56 basis_element_to_opgen: OnceBiVec::new(min_degree),
57 generator_to_index: OnceBiVec::new(min_degree),
58 }
59 }
60}
61
62impl<const U: bool, A: MuAlgebra<U>> Module for MuFreeModule<U, A> {
63 type Algebra = A;
64
65 fn algebra(&self) -> Arc<A> {
66 Arc::clone(&self.algebra)
67 }
68
69 fn min_degree(&self) -> i32 {
70 self.min_degree
71 }
72
73 fn max_computed_degree(&self) -> i32 {
74 self.num_gens.max_degree()
75 }
76
77 fn max_generator_degree(&self) -> Option<i32> {
78 for i in (0..self.num_gens.len()).rev() {
79 if self.num_gens[i] > 0 {
80 return Some(i);
81 }
82 }
83 Some(self.min_degree)
84 }
85
86 fn compute_basis(&self, max_degree: i32) {
87 let algebra = self.algebra();
88 self.basis_element_to_opgen.extend(max_degree, |degree| {
89 let new_row = OnceVec::new();
90 self.generator_to_index.push_checked(OnceVec::new(), degree);
91
92 let mut offset = 0;
93 for (gen_deg, &num_gens) in &self.num_gens {
94 let op_deg = degree - gen_deg;
95 let num_ops = algebra.dimension_unstable(op_deg, gen_deg);
96 for gen_idx in 0..num_gens {
97 self.generator_to_index[degree].push(offset);
98 offset += num_ops;
99 for op_idx in 0..num_ops {
100 new_row.push(OperationGeneratorPair {
101 generator_degree: gen_deg,
102 generator_index: gen_idx,
103 operation_degree: op_deg,
104 operation_index: op_idx,
105 });
106 }
107 }
108 }
109 new_row
110 });
111 }
112
113 fn dimension(&self, degree: i32) -> usize {
114 if degree < self.min_degree {
115 return 0;
116 }
117 assert!(
118 degree < self.basis_element_to_opgen.len(),
119 "Free Module {self} not computed through degree {degree}"
120 );
121 self.basis_element_to_opgen[degree].len()
122 }
123
124 fn basis_element_to_string(&self, degree: i32, idx: usize) -> String {
125 let opgen = self.index_to_op_gen(degree, idx);
126 let mut op_str = self
127 .algebra()
128 .basis_element_to_string(opgen.operation_degree, opgen.operation_index);
129 if &*op_str == "1" {
130 op_str = "".to_string();
131 } else {
132 op_str.push(' ');
133 }
134 format!(
135 "{}{}",
136 op_str, self.gen_names[opgen.generator_degree][opgen.generator_index]
137 )
138 }
139
140 fn act_on_basis(
141 &self,
142 mut result: FpSliceMut,
143 coeff: u32,
144 op_degree: i32,
145 op_index: usize,
146 mod_degree: i32,
147 mod_index: usize,
148 ) {
149 let OperationGeneratorPair {
150 operation_degree: module_operation_degree,
151 operation_index: module_operation_index,
152 generator_degree,
153 generator_index,
154 } = *self.index_to_op_gen(mod_degree, mod_index);
155
156 // Now all of the output elements are going to be of the form s * x. Find where such things go in the output vector.
157 let num_ops = self
158 .algebra()
159 .dimension(module_operation_degree + op_degree);
160 let output_block_min = self.operation_generator_to_index(
161 module_operation_degree + op_degree,
162 0,
163 generator_degree,
164 generator_index,
165 );
166 let output_block_max = output_block_min + num_ops;
167
168 // Now we multiply s * r and write the result to the appropriate position.
169 self.algebra().multiply_basis_elements_unstable(
170 result.slice_mut(output_block_min, output_block_max),
171 coeff,
172 op_degree,
173 op_index,
174 module_operation_degree,
175 module_operation_index,
176 generator_degree,
177 );
178 }
179
180 fn act(
181 &self,
182 mut result: FpSliceMut,
183 coeff: u32,
184 op_degree: i32,
185 op_index: usize,
186 input_degree: i32,
187 input: FpSlice,
188 ) {
189 for GeneratorData {
190 gen_deg,
191 start: [input_start, output_start],
192 end: [input_end, output_end],
193 } in self.iter_gen_offsets([input_degree, input_degree + op_degree])
194 {
195 if input_start >= input.len() {
196 break;
197 }
198 let input_slice = input.restrict(input_start, input_end);
199 self.algebra.multiply_basis_element_by_element_unstable(
200 result.slice_mut(output_start, output_end),
201 coeff,
202 op_degree,
203 op_index,
204 input_degree - gen_deg,
205 input_slice,
206 gen_deg,
207 );
208 }
209 }
210
211 // Will need specialization
212 /* #[cfg(not(feature = "cache-multiplication"))]
213 fn act(&self, result : FpSliceMut, coeff : u32, op_degree : i32, op_index : usize, input_degree : i32, input : FpSlice){
214 if *self.prime() == 2 {
215 if let SteenrodAlgebra::MilnorAlgebra(m) = &*self.algebra() {
216 self.custom_milnor_act(m, result, coeff, op_degree, op_index, input_degree, input);
217 } else {
218 self.standard_act(result, coeff, op_degree, op_index, input_degree, input);
219 }
220 } else {
221 self.standard_act(result, coeff, op_degree, op_index, input_degree, input);
222 }
223 }*/
224}
225
226impl<const U: bool, A: MuAlgebra<U>> ZeroModule for MuFreeModule<U, A> {
227 fn zero_module(algebra: Arc<A>, min_degree: i32) -> Self {
228 let m = Self::new(algebra, String::from("0"), min_degree);
229 m.add_generators(0, 0, None);
230 m
231 }
232}
233
234impl<const U: bool, A: MuAlgebra<U>> MuFreeModule<U, A> {
235 pub fn gen_names(&self) -> &OnceBiVec<Vec<String>> {
236 &self.gen_names
237 }
238
239 pub fn number_of_gens_in_degree(&self, degree: i32) -> usize {
240 if degree < self.min_degree {
241 return 0;
242 }
243 self.num_gens[degree]
244 }
245
246 pub fn add_generators(&self, degree: i32, num_gens: usize, names: Option<Vec<String>>) {
247 // We need to acquire the lock because changing num_gens modifies the behaviour of
248 // extend_table_entries, and the two cannot happen concurrently.
249 let _lock = self.basis_element_to_opgen.lock();
250 assert!(degree >= self.min_degree);
251
252 // println!("add_gens == degree : {}, num_gens : {}", degree, num_gens);
253 // self.ensure_next_table_entry(degree);
254 let gen_names = names.unwrap_or_else(|| {
255 (0..num_gens)
256 .map(|i| format!("x_{{{degree},{i}}}"))
257 .collect()
258 });
259
260 self.gen_names.push_checked(gen_names, degree);
261 self.num_gens.push_checked(num_gens, degree);
262
263 let internal_gen_idx = self.gen_deg_idx_to_internal_idx[degree];
264 // After adding generators in degree `t`, we now know when the generators for degree `t +
265 // 1` starts.
266 self.gen_deg_idx_to_internal_idx
267 .push_checked(internal_gen_idx + num_gens, degree + 1);
268
269 let algebra = self.algebra();
270 let gen_deg = degree;
271 for total_degree in degree..self.basis_element_to_opgen.len() {
272 let op_deg = total_degree - gen_deg;
273 let mut offset = self.basis_element_to_opgen[total_degree].len();
274 let num_ops = algebra.dimension_unstable(op_deg, gen_deg);
275 for gen_idx in 0..num_gens {
276 self.generator_to_index[total_degree].push(offset);
277 offset += num_ops;
278 for op_idx in 0..num_ops {
279 self.basis_element_to_opgen[total_degree].push(OperationGeneratorPair {
280 generator_degree: gen_deg,
281 generator_index: gen_idx,
282 operation_degree: op_deg,
283 operation_index: op_idx,
284 });
285 }
286 }
287 }
288 }
289
290 /// Given a generator `(gen_deg, gen_idx)`, find the first index in degree `degree` with
291 /// elements from the generator.
292 pub fn internal_generator_offset(&self, degree: i32, internal_gen_idx: usize) -> usize {
293 self.generator_to_index[degree][internal_gen_idx]
294 }
295
296 /// Iterate the degrees and indices of each generator up to degree `degree`.
297 pub fn iter_gens(&self, degree: i32) -> impl Iterator<Item = (i32, usize)> + '_ {
298 self.num_gens
299 .iter()
300 .take((degree - self.min_degree + 1) as usize)
301 .flat_map(|(t, &n)| (0..n).map(move |k| (t, k)))
302 }
303
304 /// Iterate the degrees and offsets of each generator up to degree `degree`.
305 pub fn iter_gen_offsets<const N: usize>(
306 &self,
307 degree: [i32; N],
308 ) -> impl Iterator<Item = GeneratorData<N>> + '_ {
309 OffsetIterator {
310 module: self,
311 degree,
312 offset: [0; N],
313 gen_deg: self
314 .iter_gens(degree.into_iter().min().unwrap())
315 .map(|(t, _)| t),
316 }
317 }
318
319 /// Given a generator `(gen_deg, gen_idx)`, find the first index in degree `degree` with
320 /// elements from the generator.
321 pub fn generator_offset(&self, degree: i32, gen_deg: i32, gen_idx: usize) -> usize {
322 assert!(gen_deg >= self.min_degree);
323 assert!(gen_idx < self.num_gens[gen_deg]);
324 self.internal_generator_offset(degree, self.gen_deg_idx_to_internal_idx[gen_deg] + gen_idx)
325 }
326
327 pub fn operation_generator_to_index(
328 &self,
329 op_deg: i32,
330 op_idx: usize,
331 gen_deg: i32,
332 gen_idx: usize,
333 ) -> usize {
334 assert!(op_deg >= 0);
335 assert!(gen_deg >= self.min_degree);
336 let internal_gen_idx = self.gen_deg_idx_to_internal_idx[gen_deg] + gen_idx;
337 assert!(internal_gen_idx <= self.gen_deg_idx_to_internal_idx[gen_deg + 1]);
338 self.generator_to_index[op_deg + gen_deg][internal_gen_idx] + op_idx
339 }
340
341 pub fn index_to_op_gen(&self, degree: i32, index: usize) -> &OperationGeneratorPair {
342 assert!(degree >= self.min_degree);
343 &self.basis_element_to_opgen[degree][index]
344 }
345
346 pub fn extend_by_zero(&self, degree: i32) {
347 self.algebra.compute_basis(degree - self.min_degree);
348 self.compute_basis(degree);
349 for i in self.num_gens.len()..=degree {
350 self.add_generators(i, 0, None)
351 }
352 }
353
354 /// Given a vector that represents an element in degree `degree`, slice it to the part that
355 /// represents the terms that correspond to the specified generator.
356 pub fn slice_vector<'a>(
357 &self,
358 degree: i32,
359 gen_degree: i32,
360 gen_index: usize,
361 v: FpSlice<'a>,
362 ) -> FpSlice<'a> {
363 let start = self.generator_offset(degree, gen_degree, gen_index);
364 let len = self
365 .algebra()
366 .dimension_unstable(degree - gen_degree, gen_degree);
367 v.restrict(
368 std::cmp::min(v.len(), start),
369 std::cmp::min(v.len(), start + len),
370 )
371 }
372
373 /// Given an element in a degree, iterate through the slices corresponding to each generator.
374 /// Each item of the iterator is `(gen_degree, gen_index, op_degree, slice)`. This skips slices
375 /// that are zero length.
376 pub fn iter_slices<'a>(
377 &'a self,
378 degree: i32,
379 slice: FpSlice<'a>,
380 ) -> impl Iterator<Item = (i32, usize, i32, FpSlice<'a>)> + 'a {
381 (self.min_degree..=degree)
382 .flat_map(|t| (0..self.num_gens.get(t).copied().unwrap_or(0)).map(move |n| (t, n)))
383 .map(move |(t, n)| (t, n, degree - t, self.slice_vector(degree, t, n, slice)))
384 .filter(|(_, _, _, v)| !v.is_empty())
385 }
386}
387
388pub struct GeneratorData<const N: usize> {
389 pub gen_deg: i32,
390 pub start: [usize; N],
391 pub end: [usize; N],
392}
393
394struct OffsetIterator<
395 'a,
396 const U: bool,
397 A: MuAlgebra<U>,
398 T: Iterator<Item = i32> + 'a,
399 const N: usize,
400> {
401 module: &'a MuFreeModule<U, A>,
402 degree: [i32; N],
403 offset: [usize; N],
404 gen_deg: T,
405}
406
407impl<'a, const U: bool, A: MuAlgebra<U>, T: Iterator<Item = i32> + 'a, const N: usize> Iterator
408 for OffsetIterator<'a, U, A, T, N>
409{
410 type Item = GeneratorData<N>;
411
412 fn next(&mut self) -> Option<Self::Item> {
413 let mut retval = GeneratorData {
414 gen_deg: self.gen_deg.next()?,
415 start: [0; N],
416 end: [0; N],
417 };
418
419 for i in 0..N {
420 retval.start[i] = self.offset[i];
421 retval.end[i] = retval.start[i]
422 + self
423 .module
424 .algebra
425 .dimension_unstable(self.degree[i] - retval.gen_deg, retval.gen_deg);
426 self.offset[i] = retval.end[i];
427 }
428 Some(retval)
429 }
430}
431
432/*
433#[cfg(not(feature = "cache-multiplication"))]
434impl<A: Algebra> MuFreeModule<A> {
435 fn standard_act(&self, result : FpSliceMut, coeff : u32, op_degree : i32, op_index : usize, input_degree : i32, input : FpSlice) {
436 assert!(input.dimension() == self.dimension(input_degree));
437 let p = *self.prime();
438 for (i, v) in input.iter_nonzer() {
439 self.act_on_basis(result, (coeff * v) % p, op_degree, op_index, input_degree, i);
440 }
441 }
442
443 /// For the Milnor algebra, there is a faster algorithm for computing the action, which I
444 /// learnt from Christian Nassau. This is only implemented for p = 2 for now.
445 ///
446 /// To compute $\mathrm{Sq}(R) \mathrm{Sq}(S)$, we need to iterate over all admissible
447 /// matrices, namely the $x_{i, j}$ such that $r_i = \sum x_{i, j} p^j$ and the column sums are
448 /// the $s_j$.
449 ///
450 /// Now if we want to compute $\mathrm{Sq}(R) (\mathrm{Sq}(S^{(1)}) + \cdots)$, we can omit the
451 /// 0th row of the matrix and the column sum condition. The such a matrix $x_{i, j}$
452 /// contributes to $\mathrm{Sq}(R) \mathrm{Sq}(S^{(k)})$ iff the column sum is at most
453 /// $s_{j}^{(k)}$. There are also some bitwise disjointness conditions we have to check to
454 /// ensure the coefficient is non-zero.
455 fn custom_milnor_act(&self, algebra: &MilnorAlgebra, result : FpSliceMut, coeff : u32, op_degree : i32, op_index : usize, input_degree : i32, input : FpSlice) {
456 if coeff % 2 == 0 {
457 return;
458 }
459 if op_degree == 0 {
460 *result += input;
461 }
462 let op = algebra.basis_element_from_index(op_degree, op_index);
463 let p_part = &op.p_part;
464 let mut matrix = AdmissibleMatrix::new(p_part);
465
466 let output_degree = input_degree + op_degree;
467 let mut working_elt = MilnorBasisElement {
468 q_part: 0,
469 p_part: Vec::with_capacity(matrix.cols - 1),
470 degree: 0,
471 };
472
473 let terms : Vec<usize> = input.iter_nonzero()
474 .map(|(i, _)| i)
475 .collect();
476
477 loop {
478 'outer: for &i in &terms {
479 let elt = self.index_to_op_gen(input_degree, i);
480 let basis = algebra.basis_element_from_index(elt.operation_degree, elt.operation_index);
481
482 working_elt.p_part.clear();
483 working_elt.p_part.reserve(max(basis.p_part.len(), matrix.masks.len()));
484
485 for j in 0 .. min(basis.p_part.len(), matrix.col_sums.len()) {
486 if matrix.col_sums[j] > basis.p_part[j] {
487 continue 'outer;
488 }
489 if (basis.p_part[j] - matrix.col_sums[j]) & matrix.masks[j] != 0 {
490 continue 'outer;
491 }
492 working_elt.p_part.push((basis.p_part[j] - matrix.col_sums[j]) | matrix.masks[j]); // We are supposed to add the diagonal sum, but that is equal to the mask, and since there are no bit conflicts, this is the same as doing a bitwise or.
493 }
494 if basis.p_part.len() < matrix.col_sums.len() {
495 for j in basis.p_part.len() .. matrix.col_sums.len() {
496 if matrix.col_sums[j] > 0 {
497 continue 'outer;
498 }
499 }
500 for j in basis.p_part.len() .. matrix.masks.len() {
501 working_elt.p_part.push(matrix.masks[j])
502 }
503 } else {
504 for j in matrix.col_sums.len() .. min(basis.p_part.len(), matrix.masks.len()) {
505 if basis.p_part[j] & matrix.masks[j] != 0 {
506 continue 'outer;
507 }
508 working_elt.p_part.push(basis.p_part[j] | matrix.masks[j]);
509 }
510 if basis.p_part.len() < matrix.masks.len() {
511 for j in basis.p_part.len() .. matrix.masks.len() {
512 working_elt.p_part.push(matrix.masks[j]);
513 }
514 } else {
515 for j in matrix.masks.len() .. basis.p_part.len() {
516 working_elt.p_part.push(basis.p_part[j])
517 }
518 }
519 }
520 while let Some(0) = working_elt.p_part.last() {
521 working_elt.p_part.pop();
522 }
523 working_elt.degree = output_degree - elt.generator_degree;
524
525 let idx = self.operation_generator_to_index(
526 working_elt.degree,
527 algebra.basis_element_to_index(&working_elt),
528 elt.generator_degree,
529 elt.generator_index
530 );
531 result.add_basis_element(idx, 1);
532 }
533 if !matrix.next() {
534 break;
535 }
536 }
537 }
538}
539
540#[cfg(not(feature = "cache-multiplication"))]
541struct AdmissibleMatrix {
542 cols: usize,
543 rows: usize,
544 matrix: Vec<u32>,
545 totals: Vec<u32>,
546 col_sums: Vec<u32>,
547 masks: Vec<u32>,
548}
549
550#[cfg(not(feature = "cache-multiplication"))]
551impl AdmissibleMatrix {
552 fn new(ps: &[u32]) -> Self {
553 let rows = ps.len();
554 let cols = ps.iter().map(|x| 32 - x.leading_zeros()).max().unwrap() as usize;
555 let mut matrix = vec![0; rows * cols];
556 for (i, &x) in ps.iter().enumerate() {
557 matrix[i * cols] = x;
558 }
559
560 let mut masks = Vec::with_capacity(rows + cols - 1);
561 masks.extend_from_slice(ps);
562 masks.resize(rows + cols - 1, 0);
563
564 Self {
565 rows,
566 cols,
567 totals: vec![0; rows], // totals is only used next_matrix. No need to initialize
568 col_sums: vec![0; cols - 1],
569 matrix,
570 masks,
571 }
572 }
573
574 fn next(&mut self) -> bool {
575 let mut p_to_the_j;
576 for row in 0 .. self.rows {
577 p_to_the_j = 1;
578 self.totals[row] = self[row][0];
579 'mid: for col in 1 .. self.cols {
580 p_to_the_j *= 2;
581 // We do a quick check before computing the bitsums.
582 if p_to_the_j <= self.totals[row] {
583 // Compute bitsum
584 let mut d = 0;
585 for c in (row + col + 1).saturating_sub(self.rows) .. col {
586 d |= self[row + col - c][c];
587 }
588 // Magic - find next number greater than self[row][col] whose bitwise and with
589 // d is 0.
590 let new_entry = ((self[row][col] | d) + 1) & !d;
591 let inc = new_entry - self[row][col];
592 let sub = inc * p_to_the_j;
593 if self.totals[row] < sub {
594 self.totals[row] += p_to_the_j * self[row][col];
595 continue 'mid;
596 }
597 self[row][0] = self.totals[row] - sub;
598 self.masks[row] = self[row][0];
599 self.col_sums[col - 1] += inc;
600 for j in 1 .. col {
601 self.masks[row + j] &= !self[row][j];
602 self.col_sums[j - 1] -= self[row][j];
603 self[row][j] = 0;
604 }
605 self[row][col] = new_entry;
606
607 for i in 0 .. row {
608 self[i][0] = self.totals[i];
609 self.masks[i] = self.totals[i];
610 for j in 1 .. self.cols {
611 if i + j > row {
612 self.masks[i + j] &= !self[i][j];
613 }
614 self.col_sums[j - 1] -= self[i][j];
615 self[i][j] = 0;
616 }
617 }
618 self.masks[row + col] = d | new_entry;
619 return true;
620 }
621 self.totals[row] += p_to_the_j * self[row][col];
622 }
623 }
624 false
625 }
626}
627
628#[cfg(not(feature = "cache-multiplication"))]
629impl std::ops::Index<usize> for AdmissibleMatrix {
630 type Output = [u32];
631
632 fn index(&self, row: usize) -> &Self::Output {
633 &self.matrix[row * self.cols .. (row + 1) * self.cols]
634 }
635}
636
637#[cfg(not(feature = "cache-multiplication"))]
638impl std::ops::IndexMut<usize> for AdmissibleMatrix {
639 fn index_mut(&mut self, row: usize) -> &mut Self::Output {
640 &mut self.matrix[row * self.cols .. (row + 1) * self.cols]
641 }
642}
643*/