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*/