secondary_massey/
secondary_massey.rs

1//! Computes massey products in $\Mod_{C\lambda^2}$.
2//!
3//! # Usage
4//! This computes all Massey products of the form $\langle -, b, a\rangle$, where $a \in \Ext^{\*,
5//! \*}(M, k)$ and $b, (-) \in \Ext^{\*, \*}(k, k)$. It does not verify that the Massey product is
6//! valid, i.e. $a$ and $b$ both lift to $\Mod_{C\lambda^2}$ and have trivial product.
7//!
8//! Since we must choose $a$ and $b$ to have trivial product, it is necessary to be able to specify
9//! the $\lambda$ part of them, and not insist that they are standard lifts of the $\Ext$ classes.
10//! Thus, the user is first prompted for the $\Ext$ part, then the $\lambda$ part of each class. To
11//! set a part to zero, supply an empty name. Note that if the bidegree right above the class is
12//! empty, the user is not prompted for the $\lambda$ part.
13//!
14//! # Output
15//! This computes the Massey products up to a sign. We write our output in the category
16//! $\Mod_{C\lambda^2}$, so the format is $\langle a, b, -\rangle$ instead of $\langle -, b,
17//! a\rangle$. Brave souls are encouraged to figure out the correct sign for the products.
18
19use std::sync::Arc;
20
21use algebra::{module::Module, pair_algebra::PairAlgebra};
22use ext::{
23    chain_complex::{ChainComplex, ChainHomotopy, FreeChainComplex},
24    resolution_homomorphism::ResolutionHomomorphism,
25    secondary::*,
26    utils::{QueryModuleResolution, query_module},
27};
28use fp::{
29    matrix::{Matrix, Subspace},
30    vector::FpVector,
31};
32use itertools::Itertools;
33use sseq::coordinates::{Bidegree, BidegreeElement};
34
35struct HomData {
36    name: String,
37    class: FpVector,
38    hom_lift: Arc<SecondaryResolutionHomomorphism<QueryModuleResolution, QueryModuleResolution>>,
39    lambda_part: Option<Arc<ResolutionHomomorphism<QueryModuleResolution, QueryModuleResolution>>>,
40}
41
42fn get_hom(
43    name: &str,
44    source: Arc<SecondaryResolution<QueryModuleResolution>>,
45    target: Arc<SecondaryResolution<QueryModuleResolution>>,
46) -> HomData {
47    let p = source.prime();
48
49    let shift = Bidegree::n_s(
50        query::raw(&format!("n of {name}"), str::parse),
51        query::raw(&format!("s of {name}"), str::parse),
52    );
53
54    let ext_name: String = query::raw(&format!("Name of Ext part of {name}"), str::parse);
55
56    source
57        .underlying()
58        .compute_through_stem(shift + LAMBDA_BIDEGREE);
59
60    let hom = Arc::new(ResolutionHomomorphism::new(
61        ext_name.clone(),
62        source.underlying(),
63        target.underlying(),
64        shift,
65    ));
66
67    let num_gens = source.underlying().number_of_gens_in_bidegree(shift);
68    let num_lambda_gens = hom
69        .source
70        .number_of_gens_in_bidegree(shift + LAMBDA_BIDEGREE);
71
72    let mut class = FpVector::new(p, num_gens + num_lambda_gens);
73
74    let mut matrix = Matrix::new(p, num_gens, 1);
75
76    if !hom.name().is_empty() {
77        if matrix.rows() == 0 {
78            eprintln!("No classes in this bidegree");
79        } else {
80            let v: Vec<u32> = query::vector(&format!("Input Ext class {ext_name}"), num_gens);
81            for (i, &x) in v.iter().enumerate() {
82                matrix.row_mut(i).set_entry(0, x);
83                class.set_entry(i, x);
84            }
85        }
86    }
87
88    hom.extend_step(shift, Some(&matrix));
89
90    let hom_lift = Arc::new(SecondaryResolutionHomomorphism::new(source, target, hom));
91
92    let lambda_part = if num_lambda_gens > 0 {
93        let lambda_name: String = query::raw(&format!("Name of λ part of {name}"), str::parse);
94        if lambda_name.is_empty() {
95            None
96        } else {
97            let v = query::vector(&format!("Input Ext class {lambda_name}"), num_lambda_gens);
98            for (i, &x) in v.iter().enumerate() {
99                class.set_entry(num_gens + i, x);
100            }
101            Some(Arc::new(ResolutionHomomorphism::from_class(
102                lambda_name,
103                hom_lift.source(),
104                hom_lift.target(),
105                shift + LAMBDA_BIDEGREE,
106                &v,
107            )))
108        }
109    } else {
110        None
111    };
112
113    let name = match (&*ext_name, lambda_part.as_ref().map_or("", |x| x.name())) {
114        ("", "") => panic!("Do not compute zero Massey product"),
115        ("", x) => format!("λ{x}"),
116        (x, "") => format!("[{x}]"),
117        (x, y) => format!("[{x}] + λ{y}"),
118    };
119    HomData {
120        name,
121        class,
122        hom_lift,
123        lambda_part,
124    }
125}
126
127fn main() -> anyhow::Result<()> {
128    ext::utils::init_logging()?;
129
130    eprintln!(
131        "We are going to compute <-, b, a> for all (-), where a is an element in Ext(M, k) and b \
132         and (-) are elements in Ext(k, k)."
133    );
134
135    let resolution = Arc::new(query_module(Some(algebra::AlgebraType::Milnor), true)?);
136
137    let (is_unit, unit) = ext::utils::get_unit(Arc::clone(&resolution))?;
138
139    let p = resolution.prime();
140
141    let res_lift = Arc::new(SecondaryResolution::new(Arc::clone(&resolution)));
142    let unit_lift = if is_unit {
143        Arc::clone(&res_lift)
144    } else {
145        let lift = SecondaryResolution::new(Arc::clone(&unit));
146        Arc::new(lift)
147    };
148
149    let HomData {
150        name: a_name,
151        class: _,
152        hom_lift: a,
153        lambda_part: a_lambda,
154    } = get_hom("a", Arc::clone(&res_lift), Arc::clone(&unit_lift));
155    let HomData {
156        name: b_name,
157        class: b_class,
158        hom_lift: b,
159        lambda_part: b_lambda,
160    } = get_hom("b", Arc::clone(&unit_lift), Arc::clone(&unit_lift));
161
162    let shift = Bidegree::s_t(
163        (a.underlying().shift + b.underlying().shift).s(),
164        (a.shift() + b.shift()).t(),
165    );
166
167    // Extend resolutions
168    if !is_unit {
169        let res_max = Bidegree::n_s(
170            resolution.module(0).max_computed_degree(),
171            resolution.next_homological_degree() - 1,
172        );
173        unit.compute_through_stem(res_max - a.underlying().shift);
174    }
175
176    if is_unit {
177        res_lift.extend_all();
178    } else {
179        maybe_rayon::join(|| res_lift.extend_all(), || unit_lift.extend_all());
180    }
181
182    // Now extend homomorphisms
183    maybe_rayon::scope(|s| {
184        s.spawn(|_| {
185            a.underlying().extend_all();
186            a.extend_all();
187        });
188        s.spawn(|_| {
189            b.underlying().extend_all();
190            b.extend_all();
191        });
192        if let Some(a_lambda) = &a_lambda {
193            s.spawn(|_| a_lambda.extend_all());
194        }
195        if let Some(b_lambda) = &b_lambda {
196            s.spawn(|_| b_lambda.extend_all());
197        }
198    });
199
200    let res_sseq = Arc::new(res_lift.e3_page());
201    let unit_sseq = if is_unit {
202        Arc::clone(&res_sseq)
203    } else {
204        Arc::new(res_lift.e3_page())
205    };
206
207    let b_shift = b.underlying().shift;
208
209    let chain_homotopy = Arc::new(ChainHomotopy::new(a.underlying(), b.underlying()));
210    chain_homotopy.initialize_homotopies((b_shift + a.underlying().shift).s());
211
212    // Compute first homotopy
213    {
214        let v = a.product_nullhomotopy(a_lambda.as_deref(), &res_sseq, b_shift, b_class.as_slice());
215        let homotopy = chain_homotopy.homotopy(b_shift.s() + a.underlying().shift.s() - 1);
216        let htpy_source = a.shift() + b_shift;
217        homotopy.extend_by_zero(htpy_source.t() - 1);
218        homotopy.add_generators_from_rows(
219            htpy_source.t(),
220            v.into_iter()
221                .map(|x| FpVector::from_slice(p, &[x]))
222                .collect(),
223        );
224    }
225
226    chain_homotopy.extend_all();
227
228    let ch_lift = SecondaryChainHomotopy::new(
229        Arc::clone(&a),
230        Arc::clone(&b),
231        a_lambda.clone(),
232        b_lambda.clone(),
233        Arc::clone(&chain_homotopy),
234    );
235
236    if let Some(s) = ext::utils::secondary_job() {
237        ch_lift.compute_partial(s);
238        return Ok(());
239    }
240
241    ch_lift.extend_all();
242
243    fn get_page_data(sseq: &sseq::Sseq<2, sseq::Adams>, b: Bidegree) -> &fp::matrix::Subquotient {
244        let d = sseq.page_data(b);
245        &d[std::cmp::min(3, d.len() - 1)]
246    }
247
248    let mut scratch0: Vec<u32> = Vec::new();
249    let mut scratch1 = FpVector::new(p, 0);
250
251    let h_0 = ch_lift.algebra().p_tilde();
252
253    // Iterate through the multiplicand
254    for c in unit.iter_stem() {
255        if !resolution.has_computed_bidegree(c + shift - Bidegree::s_t(2, 0))
256            || !resolution.has_computed_bidegree(c + shift + Bidegree::s_t(0, 1))
257        {
258            continue;
259        }
260
261        // Now read off the products
262        let source = c + shift - Bidegree::s_t(1, 0);
263
264        let source_num_gens = resolution.number_of_gens_in_bidegree(source);
265        let source_lambda_num_gens =
266            resolution.number_of_gens_in_bidegree(source + LAMBDA_BIDEGREE);
267
268        if source_num_gens + source_lambda_num_gens == 0 {
269            continue;
270        }
271
272        // We find the kernel of multiplication by b.
273        let target_num_gens = unit.number_of_gens_in_bidegree(c);
274        let target_lambda_num_gens = unit.number_of_gens_in_bidegree(c + LAMBDA_BIDEGREE);
275        let target_all_gens = target_num_gens + target_lambda_num_gens;
276
277        let prod_num_gens = unit.number_of_gens_in_bidegree(c + b_shift);
278        let prod_lambda_num_gens = unit.number_of_gens_in_bidegree(c + b_shift + LAMBDA_BIDEGREE);
279        let prod_all_gens = prod_num_gens + prod_lambda_num_gens;
280
281        let e3_kernel = {
282            let target_page_data = get_page_data(&unit_sseq, c);
283            let target_lambda_page_data = get_page_data(&unit_sseq, c + LAMBDA_BIDEGREE);
284            let product_lambda_page_data = get_page_data(&unit_sseq, c + b_shift + LAMBDA_BIDEGREE);
285
286            // We first compute elements whose product vanish mod lambda, and later see what the
287            // possible lifts are. We do it this way to avoid Z/p^2 problems
288
289            let e2_kernel: Subspace = {
290                let mut product_matrix = Matrix::new(
291                    p,
292                    target_page_data.subspace_dimension(),
293                    target_num_gens + prod_num_gens,
294                );
295
296                let m0 = Matrix::from_vec(
297                    p,
298                    &b.underlying()
299                        .get_map(c.s() + b.underlying().shift.s())
300                        .hom_k(c.t()),
301                );
302                for (g, mut out) in target_page_data
303                    .subspace_gens()
304                    .zip_eq(product_matrix.iter_mut())
305                {
306                    out.slice_mut(prod_num_gens, prod_num_gens + target_num_gens)
307                        .add(g, 1);
308                    for (i, v) in g.iter_nonzero() {
309                        out.slice_mut(0, prod_num_gens).add(m0.row(i), v);
310                    }
311                }
312                product_matrix.row_reduce();
313                product_matrix.compute_kernel(prod_num_gens)
314            };
315
316            // Now compute the e3 kernel
317            {
318                // First add the lifts from Ext
319                let e2_ker_dim = e2_kernel.dimension();
320                let mut product_matrix = Matrix::new(
321                    p,
322                    e2_ker_dim + target_lambda_page_data.quotient_dimension(),
323                    target_all_gens + prod_all_gens,
324                );
325
326                b.hom_k_with(
327                    b_lambda.as_deref(),
328                    Some(&unit_sseq),
329                    c,
330                    e2_kernel.basis(),
331                    product_matrix
332                        .slice_mut(0, e2_ker_dim, 0, prod_all_gens)
333                        .iter_mut(),
334                );
335                for (v, mut t) in e2_kernel.basis().zip(product_matrix.iter_mut()) {
336                    t.slice_mut(prod_all_gens, prod_all_gens + target_num_gens)
337                        .assign(v);
338                }
339
340                // Now add the lambda multiples
341                let m = Matrix::from_vec(
342                    p,
343                    &b.underlying()
344                        .get_map(b_shift.s() + c.s() + 1)
345                        .hom_k(c.t() + 1),
346                );
347
348                let mut count = 0;
349                for (i, &v) in target_lambda_page_data.quotient_pivots().iter().enumerate() {
350                    if v >= 0 {
351                        continue;
352                    }
353                    let mut row = product_matrix.row_mut(e2_ker_dim + count as usize);
354                    row.add_basis_element(prod_all_gens + target_num_gens + i, 1);
355                    row.slice_mut(prod_num_gens, prod_all_gens).add(m.row(i), 1);
356                    product_lambda_page_data
357                        .reduce_by_quotient(row.slice_mut(prod_num_gens, prod_all_gens));
358                    count += 1;
359                }
360
361                product_matrix.row_reduce();
362                product_matrix.compute_kernel(prod_all_gens)
363            }
364        };
365
366        if e3_kernel.dimension() == 0 {
367            continue;
368        }
369
370        let m0 = chain_homotopy.homotopy(source.s()).hom_k(c.t());
371        let mt = Matrix::from_vec(p, &chain_homotopy.homotopy(source.s() + 1).hom_k(c.t() + 1));
372        let m1 = Matrix::from_vec(
373            p,
374            &ch_lift.homotopies()[source.s() + 1].homotopies.hom_k(c.t()),
375        );
376        let mp = Matrix::from_vec(
377            p,
378            &resolution
379                .filtration_one_product(1, h_0, Bidegree::s_t(source.s(), c.t() + shift.t()))
380                .unwrap(),
381        );
382        let ma = a
383            .underlying()
384            .get_map(source.s())
385            .hom_k(c.t() + b_shift.t());
386        let mb = b.underlying().get_map(c.s() + b_shift.s()).hom_k(c.t());
387
388        for g in e3_kernel.iter() {
389            // Print name
390            {
391                print!("<{a_name}, {b_name}, ");
392                let has_ext = {
393                    let ext_part = g.restrict(0, target_num_gens);
394                    if ext_part.iter_nonzero().count() > 0 {
395                        print!(
396                            "[{basis_string}]",
397                            basis_string =
398                                BidegreeElement::new(c, ext_part.to_owned()).to_basis_string()
399                        );
400                        true
401                    } else {
402                        false
403                    }
404                };
405
406                let lambda_part = g.restrict(target_num_gens, target_all_gens);
407                let num_entries = lambda_part.iter_nonzero().count();
408                if num_entries > 0 {
409                    if has_ext {
410                        print!(" + ");
411                    }
412                    print!("λ");
413
414                    let basis_string = BidegreeElement::new(
415                        c + LAMBDA_BIDEGREE,
416                        g.restrict(target_num_gens, target_all_gens).to_owned(),
417                    )
418                    .to_basis_string();
419                    if num_entries == 1 {
420                        print!("{basis_string}",);
421                    } else {
422                        print!("({basis_string})",);
423                    }
424                }
425                print!("> = ±");
426            }
427
428            scratch0.clear();
429            scratch0.resize(source_num_gens, 0);
430            scratch1.set_scratch_vector_size(source_lambda_num_gens);
431
432            // First deal with the null-homotopy of ab
433            for (i, v) in g.restrict(0, target_num_gens).iter_nonzero() {
434                scratch0
435                    .iter_mut()
436                    .zip_eq(&m0[i])
437                    .for_each(|(a, b)| *a += v * b);
438                scratch1.as_slice_mut().add(m1.row(i), v);
439            }
440            for (i, v) in g.restrict(target_num_gens, target_all_gens).iter_nonzero() {
441                scratch1.as_slice_mut().add(mt.row(i), v);
442            }
443            // Now do the -1 part of the null-homotopy of bc.
444            {
445                let sign = p * p - 1;
446                let out = b.product_nullhomotopy(b_lambda.as_deref(), &unit_sseq, c, g);
447                for (i, v) in out.iter_nonzero() {
448                    scratch0
449                        .iter_mut()
450                        .zip_eq(&ma[i])
451                        .for_each(|(a, b)| *a += v * b * sign);
452                }
453            }
454
455            for (i, v) in scratch0.iter().enumerate() {
456                let extra = *v / p;
457                scratch1.as_slice_mut().add(mp.row(i), extra % p);
458            }
459
460            print!("[{}]", scratch0.iter().map(|x| *x % p).format(", "));
461
462            // Then deal with the rest of the null-homotopy of bc. This is just the null-homotopy of
463            // 2.
464            scratch0.clear();
465            scratch0.resize(prod_num_gens, 0);
466
467            for (i, v) in g.restrict(0, target_num_gens).iter_nonzero() {
468                scratch0
469                    .iter_mut()
470                    .zip_eq(&mb[i])
471                    .for_each(|(a, b)| *a += v * b);
472            }
473            for (i, v) in scratch0.iter().enumerate() {
474                let extra = (*v / p) % p;
475                if extra == 0 {
476                    continue;
477                }
478                for gen_idx in 0..source_lambda_num_gens {
479                    let m = a.underlying().get_map((source + LAMBDA_BIDEGREE).s());
480                    let dx = m.output((source + LAMBDA_BIDEGREE).t(), gen_idx);
481                    let idx = unit.module((c + shift).s()).operation_generator_to_index(
482                        1,
483                        h_0,
484                        (c + shift).t(),
485                        i,
486                    );
487                    scratch1.add_basis_element(gen_idx, dx.entry(idx));
488                }
489            }
490            println!(" + λ{scratch1}");
491        }
492    }
493    Ok(())
494}