algebra/module/
tensor_module.rs

1use std::sync::Arc;
2
3use bivec::BiVec;
4use fp::{
5    prime::minus_one_to_the_n,
6    vector::{FpSlice, FpSliceMut, FpVector},
7};
8use once::OnceBiVec;
9
10use crate::{
11    algebra::{Algebra, Bialgebra},
12    module::{Module, ZeroModule, block_structure::BlockStructure},
13};
14
15// This really only makes sense when the algebra is a bialgebra, but associated type bounds are
16// unstable. Since the methods are only defined when A is a bialgebra, this is not too much of a
17// problem.
18pub struct TensorModule<M: Module, N: Module<Algebra = M::Algebra>> {
19    pub left: Arc<M>,
20    pub right: Arc<N>,
21    block_structures: OnceBiVec<BlockStructure>,
22}
23
24impl<M: Module, N: Module<Algebra = M::Algebra>> std::fmt::Display for TensorModule<M, N> {
25    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
26        write!(f, "{} (x) {}", self.left, self.right)
27    }
28}
29
30impl<A, M, N> TensorModule<M, N>
31where
32    A: Algebra + Bialgebra,
33    M: Module<Algebra = A>,
34    N: Module<Algebra = A>,
35{
36    pub fn new(left: Arc<M>, right: Arc<N>) -> Self {
37        Self {
38            block_structures: OnceBiVec::new(left.min_degree() + right.min_degree()),
39            left,
40            right,
41        }
42    }
43
44    pub fn seek_module_num(&self, degree: i32, index: usize) -> i32 {
45        self.block_structures[degree]
46            .index_to_generator_basis_elt(index)
47            .generator_degree
48    }
49
50    pub fn offset(&self, degree: i32, left_degree: i32) -> usize {
51        self.block_structures[degree]
52            .generator_to_block(left_degree, 0)
53            .start
54    }
55
56    fn act_helper(
57        &self,
58        mut result: FpSliceMut,
59        coeff: u32,
60        op_degree: i32,
61        op_index: usize,
62        mod_degree: i32,
63        input: FpSlice,
64    ) {
65        let algebra = self.algebra();
66        let p = self.prime();
67
68        let coproduct = algebra.coproduct(op_degree, op_index).into_iter();
69        let output_degree = mod_degree + op_degree;
70
71        let mut left_result = FpVector::new(p, 0);
72        let mut right_result = FpVector::new(p, 0);
73
74        for (op_deg_l, op_idx_l, op_deg_r, op_idx_r) in coproduct {
75            let mut idx = 0;
76            for left_deg in self.left.min_degree()..=(mod_degree - self.right.min_degree()) {
77                let right_deg = mod_degree - left_deg;
78
79                // Here we use `Module::dimension(&*m, i)` instead of `m.dimension(i)` because there are
80                // multiple `dimension` methods in scope and rust-analyzer gets confused if we're not
81                // explicit enough.
82                let left_source_dim = Module::dimension(&*self.left, left_deg);
83                let right_source_dim = Module::dimension(&*self.right, right_deg);
84
85                let left_target_dim = Module::dimension(&*self.left, left_deg + op_deg_l);
86                let right_target_dim = Module::dimension(&*self.right, right_deg + op_deg_r);
87
88                if left_target_dim == 0
89                    || right_target_dim == 0
90                    || left_source_dim == 0
91                    || right_source_dim == 0
92                {
93                    idx += left_source_dim * right_source_dim;
94                    continue;
95                }
96
97                left_result.set_scratch_vector_size(left_target_dim);
98                right_result.set_scratch_vector_size(right_target_dim);
99
100                for i in 0..left_source_dim {
101                    self.left.act_on_basis(
102                        left_result.as_slice_mut(),
103                        coeff,
104                        op_deg_l,
105                        op_idx_l,
106                        left_deg,
107                        i,
108                    );
109
110                    if left_result.is_zero() {
111                        idx += right_source_dim;
112                        continue;
113                    }
114
115                    for j in 0..right_source_dim {
116                        let entry = input.entry(idx);
117                        idx += 1;
118                        if entry == 0 {
119                            continue;
120                        }
121                        self.right.act_on_basis(
122                            right_result.as_slice_mut(),
123                            entry,
124                            op_deg_r,
125                            op_idx_r,
126                            right_deg,
127                            j,
128                        );
129
130                        if right_result.is_zero() {
131                            continue;
132                        }
133                        result.add_tensor(
134                            self.offset(output_degree, left_deg + op_deg_l),
135                            minus_one_to_the_n(self.prime(), op_deg_r * left_deg),
136                            left_result.as_slice(),
137                            right_result.as_slice(),
138                        );
139
140                        right_result.set_to_zero();
141                    }
142                    left_result.set_to_zero();
143                }
144            }
145        }
146    }
147}
148impl<A, M, N> Module for TensorModule<M, N>
149where
150    A: Algebra + Bialgebra,
151    M: Module<Algebra = A>,
152    N: Module<Algebra = A>,
153{
154    type Algebra = A;
155
156    fn algebra(&self) -> Arc<A> {
157        self.left.algebra()
158    }
159
160    fn min_degree(&self) -> i32 {
161        self.left.min_degree() + self.right.min_degree()
162    }
163
164    fn max_computed_degree(&self) -> i32 {
165        self.block_structures.len()
166    }
167
168    fn compute_basis(&self, degree: i32) {
169        self.left.compute_basis(degree - self.right.min_degree());
170        self.right.compute_basis(degree - self.left.min_degree());
171        self.block_structures.extend(degree, |i| {
172            let mut block_sizes =
173                BiVec::with_capacity(self.left.min_degree(), i - self.right.min_degree() + 1);
174            for j in self.left.min_degree()..=i - self.right.min_degree() {
175                // Here we use `Module::dimension(&*m, i)` instead of `m.dimension(i)` because there are
176                // multiple `dimension` methods in scope and rust-analyzer gets confused if we're not
177                // explicit enough.
178                let mut block_sizes_entry = Vec::with_capacity(Module::dimension(&*self.left, j));
179                for _ in 0..Module::dimension(&*self.left, j) {
180                    block_sizes_entry.push(Module::dimension(&*self.right, i - j))
181                }
182                block_sizes.push(block_sizes_entry);
183            }
184            assert_eq!(block_sizes.len(), i - self.right.min_degree() + 1);
185            BlockStructure::new(&block_sizes)
186        });
187    }
188
189    fn dimension(&self, degree: i32) -> usize {
190        self.block_structures[degree].total_dimension()
191    }
192
193    fn act_on_basis(
194        &self,
195        result: FpSliceMut,
196        coeff: u32,
197        op_degree: i32,
198        op_index: usize,
199        mod_degree: i32,
200        mod_index: usize,
201    ) {
202        let mut working_element = FpVector::new(self.prime(), self.dimension(mod_degree));
203        working_element.set_entry(mod_index, 1);
204
205        self.act(
206            result,
207            coeff,
208            op_degree,
209            op_index,
210            mod_degree,
211            working_element.as_slice(),
212        );
213    }
214
215    fn act(
216        &self,
217        mut result: FpSliceMut,
218        coeff: u32,
219        op_degree: i32,
220        op_index: usize,
221        mod_degree: i32,
222        input: FpSlice,
223    ) {
224        if op_degree == 0 {
225            result.add(input, coeff);
226            return;
227        }
228
229        let algebra = self.algebra();
230        let p = self.prime();
231        let decomposition = algebra.decompose(op_degree, op_index);
232        match decomposition.len() {
233            0 => panic!("Decomposition has length 0"),
234            1 => self.act_helper(result.copy(), coeff, op_degree, op_index, mod_degree, input),
235            n => {
236                let (op_degree, op_index) = decomposition[0];
237
238                let mut working_degree = mod_degree;
239                let mut working_element =
240                    FpVector::new(p, self.dimension(working_degree + op_degree));
241                self.act_helper(
242                    working_element.as_slice_mut(),
243                    coeff,
244                    op_degree,
245                    op_index,
246                    working_degree,
247                    input,
248                );
249                working_degree += op_degree;
250
251                for &(op_degree, op_index) in &decomposition[1..n - 1] {
252                    let mut new_element =
253                        FpVector::new(p, self.dimension(working_degree + op_degree));
254                    self.act_helper(
255                        new_element.as_slice_mut(),
256                        coeff,
257                        op_degree,
258                        op_index,
259                        working_degree,
260                        working_element.as_slice(),
261                    );
262                    working_element = new_element;
263                    working_degree += op_degree;
264                }
265
266                let (op_degree, op_index) = decomposition[n - 1];
267                self.act_helper(
268                    result.copy(),
269                    coeff,
270                    op_degree,
271                    op_index,
272                    working_degree,
273                    working_element.as_slice(),
274                );
275            }
276        }
277    }
278
279    fn basis_element_to_string(&self, degree: i32, idx: usize) -> String {
280        let left_degree = self.seek_module_num(degree, idx);
281        let right_degree = degree - left_degree;
282        let inner_index = idx - self.offset(degree, left_degree);
283
284        // Here we use `Module::dimension(&*m, i)` instead of `m.dimension(i)` because there are
285        // multiple `dimension` methods in scope and rust-analyzer gets confused if we're not
286        // explicit enough.
287        let right_dim = Module::dimension(&*self.right, right_degree);
288
289        let left_index = inner_index / right_dim;
290        let right_index = inner_index % right_dim;
291
292        format!(
293            "{}.{}",
294            self.left.basis_element_to_string(left_degree, left_index),
295            self.right
296                .basis_element_to_string(right_degree, right_index)
297        )
298    }
299
300    fn max_degree(&self) -> Option<i32> {
301        Some(self.left.max_degree()? + self.right.max_degree()?)
302    }
303}
304
305impl<A, M, N> ZeroModule for TensorModule<M, N>
306where
307    A: Algebra + Bialgebra,
308    M: Module<Algebra = A> + ZeroModule,
309    N: Module<Algebra = A> + ZeroModule,
310{
311    fn zero_module(algebra: Arc<A>, min_degree: i32) -> Self {
312        Self::new(
313            Arc::new(M::zero_module(Arc::clone(&algebra), min_degree)),
314            Arc::new(N::zero_module(algebra, min_degree)),
315        )
316    }
317}