algebra/module/
module_trait.rs

1use std::sync::Arc;
2
3use auto_impl::auto_impl;
4use fp::{
5    prime::ValidPrime,
6    vector::{FpSlice, FpSliceMut},
7};
8use itertools::Itertools;
9
10use crate::algebra::Algebra;
11
12/// A bounded below module over an algebra.
13///
14/// To accommodate for infinite modules (e.g. modules in a free resolution), every module is
15/// potentially only define up to a degree. The extent to which the module is defined is kept track
16/// by two functions:
17///
18///  - [`Module::max_computed_degree`] gives the maximum degree for which the module is fully
19///    defined. It is guaranteed that the module will never change up to this degree in the future.
20///
21///  - [`Module::compute_basis`] extends the internal data to support querying data up to (and
22///    including) a given degree. In general, we can run this beyond the max computed degree.
23///
24/// A useful example to keep in mind is a [`FreeModule`](crate::module::FreeModule), where we have
25/// specified the generators up to some degree `t`. Then `t` is the max computed degree, while
26/// `compute_basis` computes data such as the offset of existing generators in potentially higher
27/// degrees.
28#[auto_impl(Box)]
29pub trait Module: std::fmt::Display + std::any::Any + Send + Sync {
30    type Algebra: Algebra;
31
32    /// The algebra the module is over.
33    fn algebra(&self) -> Arc<Self::Algebra>;
34
35    /// The minimum degree of the module, which is required to be bounded below
36    fn min_degree(&self) -> i32;
37
38    /// Compute internal data of the module so that we can query information up to degree `degree`.
39    /// This should be run by the user whenever they want to query such information.
40    ///
41    /// This function must be idempotent, and defaults to a no-op.
42    ///
43    /// See [`Module`] documentation for more details.
44    #[allow(unused_variables)]
45    fn compute_basis(&self, degree: i32) {}
46
47    /// The maximum `t` for which the module is fully defined at `t`. See [`Module`] documentation
48    /// for more details.
49    fn max_computed_degree(&self) -> i32;
50
51    /// The dimension of a module at the given degree
52    fn dimension(&self, degree: i32) -> usize;
53    fn act_on_basis(
54        &self,
55        result: FpSliceMut,
56        coeff: u32,
57        op_degree: i32,
58        op_index: usize,
59        mod_degree: i32,
60        mod_index: usize,
61    );
62
63    /// The name of a basis element. This is useful for debugging and printing results.
64    fn basis_element_to_string(&self, degree: i32, idx: usize) -> String;
65
66    /// Whether this is the unit module.
67    fn is_unit(&self) -> bool {
68        self.min_degree() == 0 && self.max_degree() == Some(0) && self.dimension(0) == 1
69    }
70
71    /// The prime the module is over, which should be equal to the prime of the algebra.
72    fn prime(&self) -> ValidPrime {
73        self.algebra().prime()
74    }
75
76    /// `max_degree` is the a degree such that if t > `max_degree`, then `self.dimension(t) = 0`.
77    fn max_degree(&self) -> Option<i32> {
78        None
79    }
80
81    /// Maximum degree of a generator under the Steenrod action. Every element in higher degree
82    /// must be obtainable from applying a Steenrod action to a lower degree element.
83    fn max_generator_degree(&self) -> Option<i32> {
84        self.max_degree()
85    }
86
87    fn total_dimension(&self) -> usize {
88        let max_degree = self
89            .max_degree()
90            .expect("total_dimension requires module to be bounded");
91
92        (self.min_degree()..=max_degree)
93            .map(|i| self.dimension(i))
94            .sum()
95    }
96
97    /// The length of `input` need not be equal to the dimension of the module in said degree.
98    /// Missing entries are interpreted to be 0, while extra entries must be zero.
99    ///
100    /// This flexibility is useful when resolving to a stem. The point is that we have elements in
101    /// degree `t` that are guaranteed to not contain generators of degree `t`, and we don't know
102    /// what generators will be added in degree `t` yet.
103    fn act(
104        &self,
105        mut result: FpSliceMut,
106        coeff: u32,
107        op_degree: i32,
108        op_index: usize,
109        input_degree: i32,
110        input: FpSlice,
111    ) {
112        assert!(input.len() <= self.dimension(input_degree));
113        let p = self.prime();
114        for (i, v) in input.iter_nonzero() {
115            self.act_on_basis(
116                result.copy(),
117                (coeff * v) % p,
118                op_degree,
119                op_index,
120                input_degree,
121                i,
122            );
123        }
124    }
125
126    fn act_by_element(
127        &self,
128        mut result: FpSliceMut,
129        coeff: u32,
130        op_degree: i32,
131        op: FpSlice,
132        input_degree: i32,
133        input: FpSlice,
134    ) {
135        assert_eq!(input.len(), self.dimension(input_degree));
136        assert_eq!(op.len(), self.algebra().dimension(op_degree));
137        let p = self.prime();
138        for (i, v) in op.iter_nonzero() {
139            self.act(
140                result.copy(),
141                (coeff * v) % p,
142                op_degree,
143                i,
144                input_degree,
145                input,
146            );
147        }
148    }
149
150    fn act_by_element_on_basis(
151        &self,
152        mut result: FpSliceMut,
153        coeff: u32,
154        op_degree: i32,
155        op: FpSlice,
156        input_degree: i32,
157        input_index: usize,
158    ) {
159        assert_eq!(op.len(), self.algebra().dimension(op_degree));
160        let p = self.prime();
161        for (i, v) in op.iter_nonzero() {
162            self.act_on_basis(
163                result.copy(),
164                (coeff * v) % p,
165                op_degree,
166                i,
167                input_degree,
168                input_index,
169            );
170        }
171    }
172
173    /// Gives the name of an element. The default implementation is derived from
174    /// [`Module::basis_element_to_string`] in the obvious way.
175    fn element_to_string(&self, degree: i32, element: FpSlice) -> String {
176        let result = element
177            .iter_nonzero()
178            .map(|(idx, value)| {
179                let coeff = if value == 1 {
180                    "".to_string()
181                } else {
182                    format!("{value} ")
183                };
184                let basis_elt = self.basis_element_to_string(degree, idx);
185                format!("{coeff}{basis_elt}")
186            })
187            .join(" + ");
188        if result.is_empty() {
189            "0".to_string()
190        } else {
191            result
192        }
193    }
194}
195
196#[derive(Debug)]
197pub struct ModuleFailedRelationError {
198    pub relation: String,
199    pub value: String,
200}
201
202impl std::fmt::Display for ModuleFailedRelationError {
203    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204        write!(
205            f,
206            "Relation failed:\n    {}  !=  0\nInstead it is equal to {}\n",
207            &self.relation, &self.value
208        )
209    }
210}
211
212impl std::error::Error for ModuleFailedRelationError {}