algebra/module/
rpn.rs

1use std::sync::Arc;
2
3use fp::{
4    prime::{Binomial, TWO},
5    vector::FpSliceMut,
6};
7use serde::Deserialize;
8use serde_json::Value;
9
10use crate::{
11    algebra::{
12        AdemAlgebra, Algebra, MilnorAlgebra, SteenrodAlgebra,
13        adem_algebra::AdemBasisElement,
14        milnor_algebra::{MilnorBasisElement, PPartEntry},
15    },
16    module::{Module, ZeroModule},
17};
18
19/// This is $\mathbb{RP}_{\mathrm{min}}^{\mathrm{max}}$. The cohomology is the subquotient of
20/// $\mathbb{F}_2[x^\pm]$ given by elements of degree between min and max (inclusive)
21///
22/// The `clear_bottom` option, if selected, mods out by the elements in the *$A(2)$ submodule*
23/// generated by degrees less than `min`. This is useful for approximating $\mathrm{tmf} \wedge
24/// \mathbb{RP}_{-\infty}^n$, c.f. Proposition 2.2 of Bailey and Ricka. Note that this quotient
25/// always has minimum degree -1 mod 8.
26pub struct RealProjectiveSpace<A: Algebra> {
27    algebra: Arc<A>,
28    pub min: i32,
29    pub max: Option<i32>, // If None,  then RP^oo
30    pub clear_bottom: bool,
31}
32
33impl<A: Algebra> std::fmt::Display for RealProjectiveSpace<A> {
34    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
35        let clear = if self.clear_bottom {
36            " (clear_bottom)"
37        } else {
38            ""
39        };
40
41        if let Some(max) = self.max {
42            write!(f, "RP^{max}_{}{clear}", self.min)
43        } else {
44            write!(f, "RP_{}{clear}", self.min)
45        }
46    }
47}
48
49impl<A: Algebra> PartialEq for RealProjectiveSpace<A> {
50    fn eq(&self, other: &Self) -> bool {
51        self.min == other.min && self.max == other.max
52    }
53}
54
55impl<A: Algebra> Eq for RealProjectiveSpace<A> {}
56
57impl<A: Algebra> Module for RealProjectiveSpace<A>
58where
59    for<'a> &'a A: TryInto<&'a SteenrodAlgebra>,
60{
61    type Algebra = A;
62
63    fn algebra(&self) -> Arc<A> {
64        Arc::clone(&self.algebra)
65    }
66
67    fn min_degree(&self) -> i32 {
68        self.min
69    }
70
71    fn max_computed_degree(&self) -> i32 {
72        i32::MAX
73    }
74
75    fn dimension(&self, degree: i32) -> usize {
76        if degree < self.min {
77            return 0;
78        }
79        if let Some(m) = self.max
80            && degree > m
81        {
82            return 0;
83        }
84
85        if self.clear_bottom
86            && (degree == self.min + 1
87                || degree == self.min + 1 + 1
88                || degree == self.min + 1 + 2
89                || degree == self.min + 1 + 4
90                || degree == self.min + 1 + 8)
91        {
92            return 0;
93        }
94        1
95    }
96
97    fn basis_element_to_string(&self, degree: i32, _idx: usize) -> String {
98        // It is an error to call the function if self.dimension(degree) == 0
99        format!("x^{{{degree}}}")
100    }
101
102    fn act_on_basis(
103        &self,
104        mut result: FpSliceMut,
105        coeff: u32,
106        op_degree: i32,
107        op_index: usize,
108        mod_degree: i32,
109        mod_index: usize,
110    ) {
111        assert!(op_index < self.algebra().dimension(op_degree));
112        assert!(mod_index < self.dimension(mod_degree));
113
114        let output_degree = mod_degree + op_degree;
115
116        if op_degree == 0 || coeff == 0 || self.dimension(output_degree) == 0 {
117            return;
118        }
119
120        if match &(&*self.algebra).try_into() {
121            Ok(SteenrodAlgebra::AdemAlgebra(a)) => coef_adem(a, op_degree, op_index, mod_degree),
122            Ok(SteenrodAlgebra::MilnorAlgebra(a)) => {
123                coef_milnor(a, op_degree, op_index, mod_degree)
124            }
125            Err(_) => unreachable!(),
126        } {
127            result.add_basis_element(0, 1);
128        }
129    }
130
131    fn max_degree(&self) -> Option<i32> {
132        self.max
133    }
134}
135
136// Compute the coefficient of the operation on x^j.
137fn coef_adem(algebra: &AdemAlgebra, op_deg: i32, op_idx: usize, mut j: i32) -> bool {
138    let elt: &AdemBasisElement = algebra.basis_element_from_index(op_deg, op_idx);
139    // Apply Sq^i to x^j and see if it is zero
140    for i in elt.ps.iter().rev() {
141        let c = if j >= 0 {
142            i32::binomial(TWO, j, *i as i32)
143        } else {
144            i32::binomial(TWO, -j + (*i as i32) - 1, *i as i32)
145        };
146        if c == 0 {
147            return false;
148        }
149        // Somehow j += 1 produces the same answer...
150        j += *i as i32;
151    }
152    true
153}
154
155fn coef_milnor(algebra: &MilnorAlgebra, op_deg: i32, op_idx: usize, mut mod_degree: i32) -> bool {
156    if mod_degree == 0 {
157        return false;
158    }
159
160    let elt: &MilnorBasisElement = algebra.basis_element_from_index(op_deg, op_idx);
161
162    let sum: PPartEntry = elt.p_part.iter().sum();
163    if mod_degree < 0 {
164        mod_degree = sum as i32 - mod_degree - 1;
165    } else if mod_degree < sum as i32 {
166        return false;
167    }
168
169    let mod_degree = mod_degree as PPartEntry;
170
171    let mut list = Vec::with_capacity(elt.p_part.len() + 1);
172    list.push(mod_degree - sum);
173    list.extend_from_slice(&elt.p_part);
174
175    PPartEntry::multinomial2(&list) == 1
176}
177
178impl<A: Algebra> ZeroModule for RealProjectiveSpace<A>
179where
180    for<'a> &'a A: TryInto<&'a SteenrodAlgebra>,
181{
182    fn zero_module(algebra: Arc<A>, min_degree: i32) -> Self {
183        Self::new(algebra, min_degree, Some(min_degree - 1), false)
184    }
185}
186
187impl<A: Algebra> RealProjectiveSpace<A>
188where
189    for<'a> &'a A: TryInto<&'a SteenrodAlgebra>,
190{
191    pub fn new(algebra: Arc<A>, min: i32, max: Option<i32>, clear_bottom: bool) -> Self {
192        assert_eq!(algebra.prime(), 2);
193        assert!(
194            (&*algebra).try_into().is_ok(),
195            "Real Projective Space only supports Steenrod Algebra"
196        );
197
198        if let Some(max) = max {
199            assert!(max >= min);
200        }
201        Self {
202            algebra,
203            min,
204            max,
205            clear_bottom,
206        }
207    }
208}
209
210#[derive(Deserialize, Debug)]
211struct RPSpec {
212    min: i32,
213    clear_bottom: Option<bool>,
214    max: Option<i32>,
215}
216
217impl<A: Algebra> RealProjectiveSpace<A> {
218    pub fn from_json(algebra: Arc<A>, json: &Value) -> anyhow::Result<Self> {
219        let spec: RPSpec = RPSpec::deserialize(json)?;
220        let clear_bottom = spec.clear_bottom.unwrap_or(false);
221        let mut min = spec.min;
222        if clear_bottom {
223            let x = (spec.min + 1).rem_euclid(8);
224            if x != 0 {
225                min += 8 - x;
226            }
227        }
228
229        Ok(Self {
230            algebra,
231            min,
232            clear_bottom,
233            max: spec.max,
234        })
235    }
236
237    pub fn to_json(&self, json: &mut Value) {
238        json["name"] = Value::String(self.to_string());
239        json["type"] = Value::from("real projective space");
240        json["min"] = Value::from(self.min);
241        if let Some(max) = self.max {
242            json["max"] = Value::from(max);
243        }
244        if self.clear_bottom {
245            json["clear_bottom"] = Value::Bool(true);
246        }
247    }
248}