bruner/
bruner.rs

1//! This script converts between our basis and Bruner's basis. At the moment, most inputs are
2//! hardcoded, and this only works for the sphere.
3//!
4//! The script performs the following procedure:
5//!
6//! 1. Compute our own resolution with the Milnor basis
7//! 2. Create Bruner's resolution as a [`FiniteChainComplex`](FCC) object
8//! 3. Use a [`ResolutionHomomorphism`] to lift the identity to a chain map from Bruner's resolution
9//!    to our resolution. We should do it in this direction because we have stored the
10//!    quasi-inverses for our resolution, but not Bruner's.
11//! 4. Read off the transformation matrix we need
12//!
13//! The main extra work to put in is step (2), where we have to parse Bruner's differentials and
14//! interpret it as a chain complex. Bruner's resolution can be found at
15//! <https://archive.sigma2.no/pages/public/datasetDetail.jsf?id=10.11582/2022.00015> while the
16//! descirption of his save file is at <https://arxiv.org/abs/2109.13117>.
17
18use std::{
19    fs::File,
20    io,
21    path::{Path, PathBuf},
22    str::FromStr,
23    sync::Arc,
24};
25
26use algebra::{
27    Algebra, MilnorAlgebra,
28    milnor_algebra::MilnorBasisElement,
29    module::{FreeModule as FM, Module, homomorphism::FreeModuleHomomorphism as FMH},
30};
31use anyhow::{Context, Error, Result};
32use ext::{
33    chain_complex::{ChainComplex, FiniteChainComplex as FCC},
34    resolution_homomorphism::ResolutionHomomorphism,
35};
36use fp::{matrix::Matrix, prime::TWO, vector::FpVector};
37use sseq::coordinates::{Bidegree, BidegreeGenerator};
38
39#[cfg(feature = "nassau")]
40type FreeModule = FM<MilnorAlgebra>;
41#[cfg(not(feature = "nassau"))]
42type FreeModule = FM<algebra::SteenrodAlgebra>;
43
44type FreeModuleHomomorphism = FMH<FreeModule>;
45type FiniteChainComplex = FCC<FreeModule, FreeModuleHomomorphism>;
46
47/// Read the first non-empty line of `data` into `buf`. Returns whether a line is read
48fn read_line(data: &mut impl io::BufRead, buf: &mut String) -> Result<bool> {
49    buf.clear();
50    while buf.is_empty() {
51        let num_bytes = data.read_line(buf)?;
52        if num_bytes == 0 {
53            return Ok(false);
54        }
55        // Remove newline character
56        buf.pop();
57    }
58    Ok(true)
59}
60
61/// Viewing `s` as a whitespace-delimited array, take the first item and parse it into T.
62fn entry<T>(x: &str) -> Result<(&str, T)>
63where
64    T: FromStr,
65    Error: From<<T as FromStr>::Err>,
66{
67    let x = x.trim();
68    match x.find(' ') {
69        Some(k) => Ok((&x[k..], x[..k].parse()?)),
70        None => Ok(("", x.parse()?)),
71    }
72}
73
74/// Read an algebra element, where input contains
75/// ```text
76/// $op_deg _ $op
77/// ```
78/// This returns an iterator of indices of the operators whose sum is the element sought.
79fn get_algebra_element<'a>(
80    a: &'a MilnorAlgebra,
81    input: &'a str,
82) -> Result<impl Iterator<Item = usize> + 'a> {
83    let (input, t) = entry(input)?;
84    let (input, _) = entry::<u32>(input)?;
85
86    let input = input.trim();
87    assert_eq!(&input[0..1], "i");
88
89    // Remove the i
90    let input = &input[1..];
91    // Remove the trailing ).
92    let input = &input[..input.len() - 2];
93
94    Ok(input.split(')').map(move |entry| {
95        let entry = &entry[1..];
96        let elt = MilnorBasisElement {
97            q_part: 0,
98            p_part: entry.split(',').map(|x| x.parse().unwrap()).collect(),
99            degree: t,
100        };
101        a.basis_element_to_index(&elt)
102    }))
103}
104
105/// Get a block describing a generator. Returns the degree and the value of the differential.
106fn get_element(
107    a: &MilnorAlgebra,
108    m: &FreeModule,
109    input: &mut impl io::BufRead,
110) -> Result<Option<(i32, FpVector)>> {
111    let mut buf = String::new();
112    if !read_line(input, &mut buf)? {
113        return Ok(None);
114    }
115    let degree: i32 = buf.trim().parse()?;
116    a.compute_basis(degree);
117    m.compute_basis(degree);
118
119    read_line(input, &mut buf)?;
120    let num_lines: usize = buf.trim().parse()?;
121
122    let mut result = FpVector::new(TWO, m.dimension(degree));
123
124    for _ in 0..num_lines {
125        read_line(input, &mut buf)?;
126        let (rem, gen_idx) = entry::<usize>(&buf)?;
127        let offset = m.internal_generator_offset(degree, gen_idx);
128        for op in get_algebra_element(a, &rem[1..])? {
129            result.add_basis_element(offset + op, 1);
130        }
131    }
132    Ok(Some((degree, result)))
133}
134
135/// Create a new `FiniteChainComplex` with `num_s` many non-zero modules.
136fn create_chain_complex(num_s: usize) -> FiniteChainComplex {
137    #[cfg(feature = "nassau")]
138    let algebra: Arc<MilnorAlgebra> = Arc::new(MilnorAlgebra::new(TWO, false));
139
140    #[cfg(not(feature = "nassau"))]
141    let algebra: Arc<algebra::SteenrodAlgebra> = Arc::new(algebra::SteenrodAlgebra::MilnorAlgebra(
142        MilnorAlgebra::new(TWO, false),
143    ));
144
145    let mut modules: Vec<Arc<FreeModule>> = Vec::with_capacity(num_s);
146    let mut differentials: Vec<Arc<FreeModuleHomomorphism>> = Vec::with_capacity(num_s - 1);
147    for _ in 0..num_s {
148        modules.push(Arc::new(FreeModule::new(
149            Arc::clone(&algebra),
150            String::new(),
151            0,
152        )));
153    }
154    for s in 1..num_s {
155        differentials.push(Arc::new(FreeModuleHomomorphism::new(
156            Arc::clone(&modules[s]),
157            Arc::clone(&modules[s - 1]),
158            0,
159        )));
160    }
161    FiniteChainComplex::new(modules, differentials)
162}
163
164/// Read the Diff.$N files in `data_dir` and produce the corresponding chain complex object.
165fn read_bruner_resolution(data_dir: &Path, max_n: i32) -> Result<(i32, FiniteChainComplex)> {
166    let num_s = data_dir.read_dir()?.count() as i32;
167
168    let cc = create_chain_complex(num_s as usize);
169    let algebra = cc.algebra();
170
171    let algebra: &MilnorAlgebra = algebra.as_ref().try_into()?;
172
173    let mut buf = String::new();
174    let s = num_s - 1;
175
176    algebra.compute_basis(max_n + s + 1);
177    // Handle s = 0
178    {
179        // TODO: actually parse file
180        let m = cc.module(0);
181        m.add_generators(0, 1, None);
182        m.extend_by_zero(max_n + 1);
183    }
184
185    for s in 1..num_s {
186        let m = cc.module(s);
187        let d = cc.differential(s);
188
189        let mut f = io::BufReader::new(
190            File::open(data_dir.join(format!("hDiff.{s}")))
191                .with_context(|| format!("Failed to read hDiff.{s}"))?,
192        );
193
194        read_line(&mut f, &mut buf)?;
195
196        let mut entries: Vec<FpVector> = Vec::new();
197        let mut cur_degree: i32 = 0;
198
199        while let Some((t, g)) = get_element(algebra, cc.module(s - 1).as_ref(), &mut f)? {
200            if t == cur_degree {
201                entries.push(g);
202            } else {
203                m.add_generators(cur_degree, entries.len(), None);
204                d.add_generators_from_rows(cur_degree, entries);
205
206                m.extend_by_zero(t - 1);
207                d.extend_by_zero(t - 1);
208
209                entries = vec![g];
210                cur_degree = t;
211            }
212        }
213        m.add_generators(cur_degree, entries.len(), None);
214        d.add_generators_from_rows(cur_degree, entries);
215
216        m.extend_by_zero(max_n + s + 1);
217        d.extend_by_zero(max_n + s);
218    }
219
220    Ok((s, cc))
221}
222
223fn main() -> anyhow::Result<()> {
224    ext::utils::init_logging()?;
225
226    let data_dir = Path::new(file!()).parent().unwrap().join("bruner_data");
227    let max_n: i32 = query::with_default("Max n", "20", str::parse);
228
229    // Read in Bruner's resolution
230    let (max_s, cc) = read_bruner_resolution(&data_dir, max_n).unwrap();
231    let max = Bidegree::n_s(max_n, max_s);
232    let cc = Arc::new(cc);
233
234    let save_dir = query::optional("Save directory", |x| {
235        core::result::Result::<PathBuf, std::convert::Infallible>::Ok(PathBuf::from(x))
236    });
237
238    #[cfg(feature = "nassau")]
239    assert!(
240        save_dir.is_some(),
241        "A save directory is required for comparison between Bruner and Nassau resolutions."
242    );
243
244    let resolution = ext::utils::construct("S_2@milnor", save_dir).unwrap();
245
246    resolution.compute_through_stem(max);
247
248    let resolution = Arc::new(resolution);
249
250    // Create a ResolutionHomomorphism object
251    let hom = ResolutionHomomorphism::new(String::new(), cc, resolution, Bidegree::zero());
252
253    // We have to explicitly tell it what to do at (0, 0)
254    hom.extend_step(Bidegree::zero(), Some(&Matrix::from_vec(TWO, &[vec![1]])));
255    hom.extend_all();
256
257    // Now print the results
258    println!("sseq_basis | bruner_basis");
259    for b in hom.target.iter_stem() {
260        let matrix = hom.get_map(b.s()).hom_k(b.t());
261
262        for (i, row) in matrix.into_iter().enumerate() {
263            let g = BidegreeGenerator::new(b, i);
264            println!("x_{g:#} = {row:?}");
265        }
266    }
267
268    Ok(())
269}