1use std::{fmt, iter, num::NonZeroI32, path::PathBuf, sync::Arc};
47
48use algebra::{
49 AlgebraType, SteenrodAlgebra,
50 module::{Module, homomorphism::ModuleHomomorphism},
51};
52use anyhow::Result;
53use ext::{
54 chain_complex::{ChainComplex, FiniteChainComplex, FreeChainComplex},
55 resolution::MuResolution,
56 resolution_homomorphism::{MuResolutionHomomorphism, ResolutionHomomorphism},
57 utils,
58};
59use fp::{
60 matrix::Matrix,
61 prime::TWO,
62 vector::{FpSlice, FpVector},
63};
64use serde_json::json;
65use sseq::coordinates::{Bidegree, BidegreeElement, BidegreeGenerator};
66
67fn main() -> Result<()> {
68 ext::utils::init_logging()?;
69
70 let s_2_path: Option<PathBuf> = query::optional("Save directory for S_2", str::parse);
71 let p_k_prefix: Option<PathBuf> = query::optional(
72 "Directory containing save directories for RP_-k_inf's",
73 str::parse,
74 );
75 let k_max = query::with_default("Max k (positive)", "25", str::parse::<NonZeroI32>).get();
78
79 let s_2_resolution = resolve_s_2(s_2_path, k_max)?;
80
81 println!("M({{basis element}}) = {{mahowald_invariant}}[ mod {{indeterminacy}}]");
82 for k in 1..=k_max {
83 let p_k = PKData::try_new(k, &p_k_prefix, &s_2_resolution)?;
84 for mi in p_k.mahowald_invariants() {
85 println!("{mi}")
86 }
87 }
88
89 Ok(())
90}
91
92type Resolution =
93 MuResolution<false, FiniteChainComplex<Box<dyn Module<Algebra = SteenrodAlgebra>>>>;
94
95type Homomorphism = MuResolutionHomomorphism<false, Resolution, Resolution>;
96
97struct PKData {
98 k: i32,
99 resolution: Arc<Resolution>,
100 bottom_cell: Homomorphism,
101 minus_one_cell: Homomorphism,
102 s_2_resolution: Arc<Resolution>,
103}
104
105struct MahowaldInvariant {
106 g: BidegreeGenerator,
107 output_t: i32,
108 invariant: FpVector,
109 indeterminacy_basis: Vec<FpVector>,
110}
111
112fn resolve_s_2(s_2_path: Option<PathBuf>, k_max: i32) -> Result<Arc<Resolution>> {
113 let s_2_resolution = Arc::new(utils::construct_standard("S_2", s_2_path)?);
114 s_2_resolution.compute_through_stem(Bidegree::n_s(2 * k_max - 2, k_max / 2 + 1));
128 Ok(s_2_resolution)
129}
130
131impl PKData {
132 fn try_new(
133 k: i32,
134 p_k_prefix: &Option<PathBuf>,
135 s_2_resolution: &Arc<Resolution>,
136 ) -> Result<Self> {
137 let p_k_config = json! ({
138 "p": 2,
139 "type": "real projective space",
140 "min": -k,
141 });
142 let mut p_k_path = p_k_prefix.clone();
143 if let Some(p) = p_k_path.as_mut() {
144 p.push(PathBuf::from(&format!("RP_{minus_k}_inf", minus_k = -k)));
145 };
146 let resolution = Arc::new(utils::construct_standard(
147 (p_k_config, AlgebraType::Milnor),
148 p_k_path,
149 )?);
150 resolution.compute_through_stem(Bidegree::n_s(k - 2, k / 2 + 1));
153
154 let bottom_cell = ResolutionHomomorphism::from_class(
155 String::from("bottom_cell"),
156 resolution.clone(),
157 s_2_resolution.clone(),
158 Bidegree::s_t(0, -k),
159 &[1],
160 );
161 bottom_cell.extend_all();
162
163 let minus_one_cell = ResolutionHomomorphism::from_class(
164 String::from("minus_one_cell"),
165 resolution.clone(),
166 s_2_resolution.clone(),
167 Bidegree::s_t(0, -1),
168 &[1],
169 );
170 minus_one_cell.extend_all();
171
172 Ok(PKData {
173 k,
174 resolution,
175 bottom_cell,
176 minus_one_cell,
177 s_2_resolution: s_2_resolution.clone(),
178 })
179 }
180
181 fn mahowald_invariants(&self) -> impl Iterator<Item = MahowaldInvariant> + '_ {
182 self.s_2_resolution
183 .iter_stem()
184 .flat_map(|b| self.mahowald_invariants_for_bidegree(b))
185 }
186
187 fn mahowald_invariants_for_bidegree(
188 &self,
189 b: Bidegree,
190 ) -> Box<dyn Iterator<Item = MahowaldInvariant> + '_> {
191 let b_p_k = b - Bidegree::s_t(0, 1);
192 if self.resolution.has_computed_bidegree(b_p_k) {
193 let b_bottom = b_p_k + Bidegree::s_t(0, self.k);
194 let bottom_s_2_gens = self.s_2_resolution.number_of_gens_in_bidegree(b_bottom);
195 let minus_one_s_2_gens = self.s_2_resolution.number_of_gens_in_bidegree(b);
196 let p_k_gens = self.resolution.number_of_gens_in_bidegree(b_p_k);
197 if bottom_s_2_gens > 0 && minus_one_s_2_gens > 0 && p_k_gens > 0 {
198 let bottom_cell_map = self.bottom_cell.get_map(b_bottom.s());
199 let mut matrix = vec![vec![0; p_k_gens]; bottom_s_2_gens];
200 for p_k_gen in 0..p_k_gens {
201 let output = bottom_cell_map.output(b_p_k.t(), p_k_gen);
202 for (s_2_gen, row) in matrix.iter_mut().enumerate() {
203 let index = bottom_cell_map.target().operation_generator_to_index(
204 0,
205 0,
206 b_bottom.t(),
207 s_2_gen,
208 );
209 row[p_k_gen] = output.entry(index);
210 }
211 }
212 let (padded_columns, mut matrix) = Matrix::augmented_from_vec(TWO, &matrix);
213 let rank = matrix.row_reduce();
214
215 if rank > 0 {
216 let kernel_subspace = matrix.compute_kernel(padded_columns);
217 let indeterminacy_basis: Vec<FpVector> =
218 kernel_subspace.basis().map(FpSlice::to_owned).collect();
219 let image_subspace = matrix.compute_image(p_k_gens, padded_columns);
220 let quasi_inverse = matrix.compute_quasi_inverse(p_k_gens, padded_columns);
221
222 let it = (0..minus_one_s_2_gens).filter_map(move |i| {
223 let mut image = FpVector::new(TWO, p_k_gens);
224 let g = BidegreeGenerator::new(b, i);
225 self.minus_one_cell.act(image.as_slice_mut(), 1, g);
226 if !image.is_zero() && image_subspace.contains(image.as_slice()) {
227 let mut invariant = FpVector::new(TWO, bottom_s_2_gens);
228 quasi_inverse.apply(invariant.as_slice_mut(), 1, image.as_slice());
229 Some(MahowaldInvariant {
230 g,
231 output_t: b_bottom.t(),
232 invariant,
233 indeterminacy_basis: indeterminacy_basis.clone(),
234 })
235 } else {
236 None
237 }
238 });
239 return Box::new(it);
240 }
241 }
242 }
243
244 Box::new(iter::empty())
245 }
246}
247
248impl fmt::Display for MahowaldInvariant {
249 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
250 let output_t = self.output_t;
251 let f2_vec_to_sum = |v: &FpVector| {
252 let elt = BidegreeElement::new(Bidegree::s_t(self.g.s(), output_t), v.clone());
253 elt.to_basis_string()
254 };
255 let indeterminacy_info = if self.indeterminacy_basis.is_empty() {
256 String::new()
257 } else {
258 format!(
259 " mod <{inner}>",
260 inner = self
261 .indeterminacy_basis
262 .iter()
263 .map(f2_vec_to_sum)
264 .collect::<Vec<_>>()
265 .join(", ")
266 )
267 };
268 let invariant = f2_vec_to_sum(&self.invariant);
269 write!(f, "M(x_{g}) = {invariant}{indeterminacy_info}", g = self.g)
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use rstest::rstest;
276
277 use super::*;
278
279 #[rstest]
280 #[case(1, 0, 0, 0, 0, vec![1], 0)]
281 #[case(5, 1, 4, 0, 8, vec![1], 0)]
282 #[case(18, 3, 17, 0, 34, vec![0, 1], 0)]
283 #[case(25, 6, 20, 0, 44, vec![1, 0], 1)]
284 fn test_mahowald_invariants(
285 #[case] k: i32,
286 #[case] s: i32,
287 #[case] input_t: i32,
288 #[case] input_i: usize,
289 #[case] output_t: i32,
290 #[case] invariant: Vec<u32>,
291 #[case] indeterminacy_dim: usize,
292 ) {
293 let g = BidegreeGenerator::new(Bidegree::s_t(s, input_t), input_i);
294 let s_2_resolution = resolve_s_2(None, k).unwrap();
295 let p_k = PKData::try_new(k, &None, &s_2_resolution).unwrap();
296 for mi in p_k.mahowald_invariants_for_bidegree(g.degree()) {
297 if mi.g.idx() == g.idx() {
298 assert_eq!(mi.output_t, output_t);
299 assert_eq!(Vec::from(&mi.invariant), invariant);
300 assert_eq!(mi.indeterminacy_basis.len(), indeterminacy_dim);
301 return;
302 }
303 }
304 panic!("could not find Mahowald invariant")
305 }
306}