ext/
nassau.rs

1//! This module implements [Nassau's algorithm](https://arxiv.org/abs/1910.04063).
2//!
3//! The main export is the [`Resolution`] object, which is a resolution of the sphere at the prime 2
4//! using Nassau's algorithm. It aims to provide an API similar to
5//! [`resolution::Resolution`](crate::resolution::Resolution). From an API point of view, the main
6//! difference between the two is that our `Resolution` is a chain complex over [`MilnorAlgebra`]
7//! over [`SteenrodAlgebra`](algebra::SteenrodAlgebra).
8//!
9//! To make use of this resolution in the example scripts, enable the `nassau` feature. This will
10//! cause [`utils::query_module`](crate::utils::query_module) to return the `Resolution` from this
11//! module instead of [`resolution`](crate::resolution). There is no formal polymorphism involved;
12//! the feature changes the return type of the function. While this is an incorrect use of features,
13//! we find that this the easiest way to make all scripts support both types of resolutions.
14
15use std::{
16    fmt::Display,
17    io,
18    sync::{Arc, Mutex, mpsc},
19};
20
21use algebra::{
22    Algebra, combinatorics,
23    milnor_algebra::{MilnorAlgebra, PPartEntry},
24    module::{
25        FreeModule, GeneratorData, Module, ZeroModule,
26        homomorphism::{FreeModuleHomomorphism, FullModuleHomomorphism, ModuleHomomorphism},
27    },
28};
29use anyhow::anyhow;
30use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
31use fp::{
32    matrix::{AugmentedMatrix, Matrix},
33    prime::{TWO, ValidPrime},
34    vector::{FpSlice, FpSliceMut, FpVector},
35};
36use itertools::Itertools;
37use once::OnceBiVec;
38use sseq::coordinates::{Bidegree, BidegreeGenerator};
39
40use crate::{
41    chain_complex::{AugmentedChainComplex, ChainComplex, FiniteChainComplex, FreeChainComplex},
42    save::{SaveDirectory, SaveKind},
43    utils::{LogWriter, parallel::ParallelGuard},
44};
45
46/// See [`resolution::SenderData`](../resolution/struct.SenderData.html). This differs by not having the `new` field.
47struct SenderData {
48    b: Bidegree,
49    retry: bool,
50    sender: mpsc::Sender<Self>,
51}
52
53impl SenderData {
54    pub(crate) fn send(b: Bidegree, sender: mpsc::Sender<Self>) {
55        sender
56            .send(Self {
57                b,
58                retry: false,
59                sender: sender.clone(),
60            })
61            .unwrap()
62    }
63
64    pub(crate) fn send_retry(b: Bidegree, sender: mpsc::Sender<Self>) {
65        sender
66            .send(Self {
67                b,
68                retry: true,
69                sender: sender.clone(),
70            })
71            .unwrap()
72    }
73}
74
75const MAX_NEW_GENS: usize = 10;
76
77/// A Milnor subalgebra to be used in [Nassau's algorithm](https://arxiv.org/abs/1910.04063). This
78/// is equipped with an ordering of the signature as in Lemma 2.4 of the paper.
79///
80/// To simplify implementation, we pick the ordering so that the (reverse) lexicographic ordering
81/// in Lemma 2.4 is just the (reverse) lexicographic ordering of the P parts. This corresponds to
82/// the ordering of $\mathcal{P}$ where $P^s_t < P^{s'}_t$ if $s < s'$).
83#[derive(Clone)]
84struct MilnorSubalgebra {
85    profile: Vec<u8>,
86}
87
88impl MilnorSubalgebra {
89    /// This should be used when you want an entry of the profile to be infinity
90    #[allow(dead_code)]
91    const INFINITY: u8 = (std::mem::size_of::<PPartEntry>() * 4 - 1) as u8;
92
93    fn new(profile: Vec<u8>) -> Self {
94        Self { profile }
95    }
96
97    /// The algebra with trivial profile, corresponding to the trivial algebra.
98    fn zero_algebra() -> Self {
99        Self { profile: vec![] }
100    }
101
102    /// Computes the signature of an element
103    fn has_signature(&self, ppart: &[PPartEntry], signature: &[PPartEntry]) -> bool {
104        for (i, (&profile, &signature)) in self.profile.iter().zip(signature).enumerate() {
105            let ppart = ppart.get(i).copied().unwrap_or(0);
106            if ppart & ((1 << profile) - 1) != signature {
107                return false;
108            }
109        }
110        true
111    }
112
113    fn zero_signature(&self) -> Vec<PPartEntry> {
114        vec![0; self.profile.len()]
115    }
116
117    /// Give a list of basis elements in degree `degree` that has signature `signature`.
118    ///
119    /// This requires passing the algebra for borrow checker reasons.
120    fn signature_mask<'a>(
121        &'a self,
122        algebra: &'a MilnorAlgebra,
123        module: &'a FreeModule<MilnorAlgebra>,
124        degree: i32,
125        signature: &'a [PPartEntry],
126    ) -> impl Iterator<Item = usize> + 'a {
127        module.iter_gen_offsets([degree]).flat_map(
128            move |GeneratorData {
129                      gen_deg,
130                      start: [offset],
131                      end: _,
132                  }| {
133                algebra
134                    .ppart_table(degree - gen_deg)
135                    .iter()
136                    .enumerate()
137                    .filter_map(move |(n, op)| {
138                        if self.has_signature(op, signature) {
139                            Some(offset + n)
140                        } else {
141                            None
142                        }
143                    })
144            },
145        )
146    }
147
148    /// Get the matrix of a free module homomorphism when restricted to the subquotient given by
149    /// the signature.
150    fn signature_matrix(
151        &self,
152        hom: &FreeModuleHomomorphism<FreeModule<MilnorAlgebra>>,
153        degree: i32,
154        signature: &[PPartEntry],
155    ) -> Matrix {
156        let p = hom.prime();
157        let source = hom.source();
158        let target = hom.target();
159        let algebra = target.algebra();
160        let target_degree = degree - hom.degree_shift();
161
162        let target_mask: Vec<usize> = self
163            .signature_mask(&algebra, &target, degree - hom.degree_shift(), signature)
164            .collect();
165
166        let source_mask: Vec<usize> = self
167            .signature_mask(&algebra, &source, degree, signature)
168            .collect();
169
170        let mut scratch = FpVector::new(p, target.dimension(target_degree));
171        let mut result = Matrix::new(p, source_mask.len(), target_mask.len());
172
173        for (mut row, &masked_index) in std::iter::zip(result.iter_mut(), &source_mask) {
174            scratch.set_to_zero();
175            hom.apply_to_basis_element(scratch.as_slice_mut(), 1, degree, masked_index);
176
177            row.add_masked(scratch.as_slice(), 1, &target_mask);
178        }
179        result
180    }
181
182    /// Iterate through all signatures of this algebra that contain elements of degree at most
183    /// `degree` (inclusive). This skips the initial zero signature.
184    fn iter_signatures(&self, degree: i32) -> impl Iterator<Item = Vec<PPartEntry>> + '_ {
185        SignatureIterator::new(self, degree)
186    }
187
188    fn top_degree(&self) -> i32 {
189        self.profile
190            .iter()
191            .map(|&entry| (1 << entry) - 1)
192            .enumerate()
193            .map(|(idx, entry)| ((1 << (idx + 1)) - 1) * entry)
194            .sum()
195    }
196
197    fn optimal_for(b: Bidegree) -> Self {
198        let b_is_in_vanishing_region = |subalgebra: &Self| {
199            let coeff = (1 << subalgebra.profile.len()) - 1;
200            b.t() >= coeff * (b.s() + 1) + subalgebra.top_degree()
201        };
202        SubalgebraIterator::new()
203            .take_while(b_is_in_vanishing_region)
204            .last()
205            .unwrap_or(Self::zero_algebra())
206    }
207
208    fn to_bytes(&self, buffer: &mut impl io::Write) -> io::Result<()> {
209        buffer.write_u64::<LittleEndian>(self.profile.len() as u64)?;
210        buffer.write_all(&self.profile)?;
211
212        let len = self.profile.len();
213        let zeros = [0; 8];
214        let padding = len - ((len / 8) * 8);
215        buffer.write_all(&zeros[0..padding])
216    }
217
218    fn from_bytes(data: &mut impl io::Read) -> io::Result<Self> {
219        let len = data.read_u64::<LittleEndian>()? as usize;
220        let mut profile = vec![0; len];
221
222        data.read_exact(&mut profile)?;
223
224        let padding = len - ((len / 8) * 8);
225        if padding > 0 {
226            let mut buf: [u8; 8] = [0; 8];
227            data.read_exact(&mut buf[0..padding])?;
228            assert_eq!(buf, [0; 8]);
229        }
230        Ok(Self { profile })
231    }
232
233    fn signature_to_bytes(signature: &[PPartEntry], buffer: &mut impl io::Write) -> io::Result<()> {
234        if cfg!(target_endian = "little") && std::mem::size_of::<PPartEntry>() == 2 {
235            unsafe {
236                let buf: &[u8] = std::slice::from_raw_parts(
237                    signature.as_ptr() as *const u8,
238                    signature.len() * 2,
239                );
240                buffer.write_all(buf).unwrap();
241            }
242        } else {
243            for &entry in signature {
244                buffer.write_u16::<LittleEndian>(entry as u16)?;
245            }
246        }
247
248        let len = signature.len();
249        let zeros = [0; 8];
250        let padding = len - ((len / 4) * 4);
251
252        if padding > 0 {
253            buffer.write_all(&zeros[0..padding * 2])?;
254        }
255        Ok(())
256    }
257
258    fn signature_from_bytes(&self, data: &mut impl io::Read) -> io::Result<Vec<PPartEntry>> {
259        let len = self.profile.len();
260        let mut signature: Vec<PPartEntry> = vec![0; len];
261
262        if cfg!(target_endian = "little") && std::mem::size_of::<PPartEntry>() == 2 {
263            unsafe {
264                let buf: &mut [u8] =
265                    std::slice::from_raw_parts_mut(signature.as_mut_ptr() as *mut u8, len * 2);
266                data.read_exact(buf).unwrap();
267            }
268        } else {
269            for entry in &mut signature {
270                *entry = data.read_u16::<LittleEndian>()? as PPartEntry;
271            }
272        }
273
274        let padding = len - ((len / 4) * 4);
275        if padding > 0 {
276            let mut buffer: [u8; 8] = [0; 8];
277            data.read_exact(&mut buffer[0..padding * 2])?;
278            assert_eq!(buffer, [0; 8]);
279        }
280        Ok(signature)
281    }
282}
283
284impl Display for MilnorSubalgebra {
285    fn fmt(&self, out: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
286        if self.profile.is_empty() {
287            write!(out, "F_2")
288        } else if self.profile.len() as u8 == self.profile[0] {
289            write!(out, "A({})", self.profile.len() - 1)
290        } else {
291            write!(out, "Algebra with profile {:?}", self.profile)
292        }
293    }
294}
295
296/// An iterator that iterates through a sequence of [`MilnorSubalgebra`] of increasing size. This
297/// is used by [`MilnorSubalgebra::optimal_for`] to find the largest subalgebra in this sequence
298/// that is applicable to a bidegree.
299struct SubalgebraIterator {
300    current: MilnorSubalgebra,
301}
302
303impl SubalgebraIterator {
304    fn new() -> Self {
305        Self {
306            current: MilnorSubalgebra::new(vec![]),
307        }
308    }
309}
310
311impl Iterator for SubalgebraIterator {
312    type Item = MilnorSubalgebra;
313
314    fn next(&mut self) -> Option<Self::Item> {
315        if self.current.profile.is_empty()
316            || self.current.profile[0] == self.current.profile.len() as u8
317        {
318            // We are at F_2 or at A(n) where n = self.current.profile.len() - 1.
319            self.current.profile.push(1);
320            Some(self.current.clone())
321        } else {
322            // We find the first entry that can be incremented and increment it
323            if let Some((_, entry)) = self
324                .current
325                .profile
326                .iter_mut()
327                .rev()
328                .enumerate()
329                .find(|(idx, entry)| **entry == *idx as u8)
330            {
331                *entry += 1;
332            }
333            Some(self.current.clone())
334        }
335    }
336}
337
338/// See [`MilnorSubalgebra::iter_signatures`].
339struct SignatureIterator<'a> {
340    subalgebra: &'a MilnorSubalgebra,
341    current: Vec<PPartEntry>,
342    signature_degree: i32,
343    degree: i32,
344}
345
346impl<'a> SignatureIterator<'a> {
347    fn new(subalgebra: &'a MilnorSubalgebra, degree: i32) -> Self {
348        Self {
349            current: vec![0; subalgebra.profile.len()],
350            degree,
351            subalgebra,
352            signature_degree: 0,
353        }
354    }
355}
356
357impl Iterator for SignatureIterator<'_> {
358    type Item = Vec<PPartEntry>;
359
360    fn next(&mut self) -> Option<Self::Item> {
361        let xi_degrees = combinatorics::xi_degrees(TWO);
362        let len = self.current.len();
363        for (i, current) in self.current.iter_mut().enumerate() {
364            *current += 1;
365            self.signature_degree += xi_degrees[i];
366
367            if self.signature_degree > self.degree || *current == 1 << self.subalgebra.profile[i] {
368                self.signature_degree -= xi_degrees[i] * *current as i32;
369                *current = 0;
370                if i + 1 == len {
371                    return None;
372                }
373            } else {
374                return Some(self.current.clone());
375            }
376        }
377        // This only happens when the profile is trivial
378        assert!(self.current.is_empty());
379        None
380    }
381}
382
383/// Some magic constants used in the save file
384enum Magic {
385    End = -1,
386    Signature = -2,
387    Fix = -3,
388}
389
390/// A resolution of `S_2` using Nassau's algorithm.
391///
392/// This aims to have an API similar to that of
393/// [`resolution::Resolution`](crate::resolution::Resolution). From an API point of view, the main
394/// difference between the two is that this is a chain complex over [`MilnorAlgebra`] over
395/// [`SteenrodAlgebra`](algebra::SteenrodAlgebra).
396pub struct Resolution<M: ZeroModule<Algebra = MilnorAlgebra>> {
397    lock: Mutex<()>,
398    name: String,
399    max_degree: i32,
400    modules: OnceBiVec<Arc<FreeModule<MilnorAlgebra>>>,
401    zero_module: Arc<FreeModule<MilnorAlgebra>>,
402    differentials: OnceBiVec<Arc<FreeModuleHomomorphism<FreeModule<MilnorAlgebra>>>>,
403    target: Arc<FiniteChainComplex<M>>,
404    chain_maps: OnceBiVec<Arc<FreeModuleHomomorphism<M>>>,
405    save_dir: SaveDirectory,
406}
407
408impl<M: ZeroModule<Algebra = MilnorAlgebra>> Resolution<M> {
409    pub fn name(&self) -> &str {
410        &self.name
411    }
412
413    pub fn set_name(&mut self, name: String) {
414        self.name = name;
415    }
416
417    pub fn new(module: Arc<M>) -> Self {
418        Self::new_with_save(module, None).unwrap()
419    }
420
421    pub fn new_with_save(
422        module: Arc<M>,
423        save_dir: impl Into<SaveDirectory>,
424    ) -> anyhow::Result<Self> {
425        let save_dir = save_dir.into();
426        let max_degree = module
427            .max_degree()
428            .ok_or_else(|| anyhow!("Nassau's algorithm requires bounded module"))?;
429        let target = Arc::new(FiniteChainComplex::ccdz(module));
430
431        if let Some(p) = save_dir.write() {
432            for subdir in SaveKind::nassau_data() {
433                subdir.create_dir(p)?;
434            }
435        }
436
437        Ok(Self {
438            lock: Mutex::new(()),
439            zero_module: Arc::new(FreeModule::new(target.algebra(), "F_{-1}".to_string(), 0)),
440            name: String::new(),
441            modules: OnceBiVec::new(0),
442            differentials: OnceBiVec::new(0),
443            chain_maps: OnceBiVec::new(0),
444            target,
445            max_degree,
446            save_dir,
447        })
448    }
449
450    fn add_generators(&self, b: Bidegree, num_new_gens: usize) {
451        let gen_names = (0..num_new_gens)
452            .map(|idx| format!("x_{:#}", BidegreeGenerator::new(b, idx)))
453            .collect();
454        self.module(b.s())
455            .add_generators(b.t(), num_new_gens, Some(gen_names));
456    }
457
458    /// This function prepares the Resolution object to perform computations up to the
459    /// specified s degree. It does *not* perform any computations by itself. It simply lengthens
460    /// the `OnceVec`s `modules`, `chain_maps`, etc. to the right length.
461    fn extend_through_degree(&self, max_s: i32) {
462        let min_degree = self.min_degree();
463
464        self.modules.extend(max_s, |i| {
465            Arc::new(FreeModule::new(
466                Arc::clone(&self.algebra()),
467                format!("F{i}"),
468                min_degree,
469            ))
470        });
471
472        self.differentials.extend(0, |_| {
473            Arc::new(FreeModuleHomomorphism::new(
474                Arc::clone(&self.modules[0]),
475                Arc::clone(&self.zero_module),
476                0,
477            ))
478        });
479
480        self.differentials.extend(max_s, |i| {
481            Arc::new(FreeModuleHomomorphism::new(
482                Arc::clone(&self.modules[i]),
483                Arc::clone(&self.modules[i - 1]),
484                0,
485            ))
486        });
487
488        self.chain_maps.extend(max_s, |i| {
489            Arc::new(FreeModuleHomomorphism::new(
490                Arc::clone(&self.modules[i]),
491                self.target.module(i),
492                0,
493            ))
494        });
495    }
496
497    #[tracing::instrument(skip_all, fields(throughput))]
498    fn write_qi(
499        f: &mut Option<impl io::Write>,
500        scratch: &mut FpVector,
501        signature: &[PPartEntry],
502        next_mask: &[usize],
503        full_matrix: &Matrix,
504        masked_matrix: &AugmentedMatrix<2>,
505    ) -> io::Result<()> {
506        let f = match f {
507            Some(f) => f,
508            None => return Ok(()),
509        };
510
511        let mut own_f = LogWriter::new(f);
512        let f = &mut own_f;
513
514        let pivots = &masked_matrix.pivots()[0..masked_matrix.end[0]];
515        if !pivots.iter().any(|&x| x >= 0) {
516            return Ok(());
517        }
518
519        // Write signature if non-zero.
520        if signature.iter().any(|&x| x > 0) {
521            f.write_u64::<LittleEndian>(Magic::Signature as u64)?;
522            MilnorSubalgebra::signature_to_bytes(signature, f)?;
523        }
524
525        // Write quasi-inverses
526        for (col, &row) in pivots.iter().enumerate() {
527            if row < 0 {
528                continue;
529            }
530            f.write_u64::<LittleEndian>(next_mask[col] as u64)?;
531            let preimage = masked_matrix.row_segment(row as usize, 1, 1);
532            scratch.set_scratch_vector_size(preimage.len());
533            scratch.as_slice_mut().assign(preimage);
534            scratch.to_bytes(f)?;
535
536            scratch.set_scratch_vector_size(full_matrix.columns());
537            for (i, _) in preimage.iter_nonzero() {
538                scratch.as_slice_mut().add(full_matrix.row(i), 1);
539            }
540            scratch.to_bytes(f)?;
541        }
542
543        tracing::Span::current().record(
544            "throughput",
545            tracing::field::display(own_f.into_throughput()),
546        );
547        Ok(())
548    }
549
550    fn write_differential(
551        &self,
552        b: Bidegree,
553        num_new_gens: usize,
554        target_dim: usize,
555    ) -> anyhow::Result<()> {
556        if let Some(dir) = self.save_dir.write() {
557            let mut f = self
558                .save_file(SaveKind::NassauDifferential, b)
559                .create_file(dir.clone(), false);
560            f.write_u64::<LittleEndian>(num_new_gens as u64)?;
561            f.write_u64::<LittleEndian>(target_dim as u64)?;
562
563            for n in 0..num_new_gens {
564                self.differential(b.s()).output(b.t(), n).to_bytes(&mut f)?;
565            }
566        }
567        Ok(())
568    }
569
570    #[tracing::instrument(skip(self), fields(%b, %subalgebra, num_new_gens, density))]
571    fn step_resolution_with_subalgebra(
572        &self,
573        b: Bidegree,
574        subalgebra: MilnorSubalgebra,
575    ) -> anyhow::Result<()> {
576        let end = || {
577            tracing::Span::current().record("num_new_gens", self.number_of_gens_in_bidegree(b));
578            tracing::Span::current().record(
579                "density",
580                self.differentials[b.s()].differential_density(b.t()) * 100.0,
581            );
582        };
583
584        let p = self.prime();
585        let mut scratch = FpVector::new(p, 0);
586
587        let target = &*self.modules[b.s() - 1];
588        let algebra = target.algebra();
589
590        let zero_sig = subalgebra.zero_signature();
591        let target_dim = target.dimension(b.t());
592        let target_mask: Vec<usize> = subalgebra
593            .signature_mask(&algebra, target, b.t(), &zero_sig)
594            .collect();
595        let target_masked_dim = target_mask.len();
596
597        let next = &self.modules[b.s() - 2];
598        next.compute_basis(b.t());
599
600        let mut f = if let Some(dir) = self.save_dir().write() {
601            let mut f = self
602                .save_file(SaveKind::NassauQi, b - Bidegree::s_t(1, 0))
603                .create_file(dir.to_owned(), true);
604            f.write_u64::<LittleEndian>(next.dimension(b.t()) as u64)?;
605            f.write_u64::<LittleEndian>(target_masked_dim as u64)?;
606            subalgebra.to_bytes(&mut f)?;
607            Some(f)
608        } else {
609            None
610        };
611
612        let guard = tracing::info_span!("step", signature = ?zero_sig).entered();
613        let next_mask: Vec<usize> = subalgebra
614            .signature_mask(&algebra, &self.modules[b.s() - 2], b.t(), &zero_sig)
615            .collect();
616        let next_masked_dim = next_mask.len();
617
618        let full_matrix = {
619            let _guard = ParallelGuard::new();
620            self.differentials[b.s() - 1].get_partial_matrix(b.t(), &target_mask)
621        };
622        let mut masked_matrix =
623            AugmentedMatrix::new(p, target_masked_dim, [next_masked_dim, target_masked_dim]);
624
625        masked_matrix
626            .segment(0, 0)
627            .add_masked(&full_matrix, &next_mask);
628        masked_matrix.segment(1, 1).add_identity();
629        masked_matrix.row_reduce();
630        let kernel = masked_matrix.compute_kernel();
631
632        Self::write_qi(
633            &mut f,
634            &mut scratch,
635            &zero_sig,
636            &next_mask,
637            &full_matrix,
638            &masked_matrix,
639        )?;
640
641        if let Some(f) = &mut f
642            && target.max_computed_degree() < b.t()
643        {
644            f.write_u64::<LittleEndian>(Magic::Fix as u64)?;
645        }
646
647        // Compute image
648        let mut n = subalgebra.signature_matrix(&self.differentials[b.s()], b.t(), &zero_sig);
649        n.row_reduce();
650        let next_row = n.rows();
651
652        let num_new_gens = n.extend_image(0, n.columns(), &kernel, 0).len();
653
654        if b.t() < b.s() {
655            assert_eq!(num_new_gens, 0, "Adding generators at {b}");
656        }
657
658        self.add_generators(b, num_new_gens);
659
660        let mut xs = vec![FpVector::new(p, target_dim); num_new_gens];
661        let mut dxs = vec![FpVector::new(p, next.dimension(b.t())); num_new_gens];
662
663        for ((x, x_masked), dx) in xs
664            .iter_mut()
665            .zip_eq(n.iter().skip(next_row))
666            .zip_eq(&mut dxs)
667        {
668            x.as_slice_mut().add_unmasked(x_masked, 1, &target_mask);
669            for (i, _) in x_masked.iter_nonzero() {
670                dx.as_slice_mut().add(full_matrix.row(i), 1);
671            }
672        }
673
674        // Now add correction terms
675        let mut target_mask: Vec<usize> = Vec::new();
676        let mut next_mask: Vec<usize> = Vec::new();
677
678        drop(guard);
679
680        for signature in subalgebra.iter_signatures(b.t()) {
681            let _guard = tracing::info_span!("step", ?signature).entered();
682            target_mask.clear();
683            next_mask.clear();
684            target_mask.extend(subalgebra.signature_mask(&algebra, target, b.t(), &signature));
685            next_mask.extend(subalgebra.signature_mask(&algebra, next, b.t(), &signature));
686
687            let full_matrix = {
688                let _guard = ParallelGuard::new();
689                self.differential(b.s() - 1)
690                    .get_partial_matrix(b.t(), &target_mask)
691            };
692
693            let mut masked_matrix =
694                AugmentedMatrix::new(p, target_mask.len(), [next_mask.len(), target_mask.len()]);
695            masked_matrix
696                .segment(0, 0)
697                .add_masked(&full_matrix, &next_mask);
698            masked_matrix.segment(1, 1).add_identity();
699            masked_matrix.row_reduce();
700
701            let qi = masked_matrix.compute_quasi_inverse();
702            let pivots = qi.pivots().unwrap();
703            let preimage = qi.preimage();
704
705            for (x, dx) in xs.iter_mut().zip(&mut dxs) {
706                scratch.set_scratch_vector_size(target_mask.len());
707                let mut row = 0;
708                for (i, &v) in next_mask.iter().enumerate() {
709                    if pivots[i] < 0 {
710                        continue;
711                    }
712                    if dx.entry(v) != 0 {
713                        scratch.as_slice_mut().add(preimage.row(row), 1);
714                    }
715                    row += 1;
716                }
717                for (i, _) in scratch.iter_nonzero() {
718                    x.add_basis_element(target_mask[i], 1);
719                    dx.as_slice_mut().add(full_matrix.row(i), 1);
720                }
721            }
722            Self::write_qi(
723                &mut f,
724                &mut scratch,
725                &signature,
726                &next_mask,
727                &full_matrix,
728                &masked_matrix,
729            )?;
730        }
731        for dx in &dxs {
732            assert!(dx.is_zero(), "dx non-zero at {b}");
733        }
734        self.differential(b.s()).add_generators_from_rows(b.t(), xs);
735
736        end();
737
738        if let Some(f) = &mut f {
739            f.write_u64::<LittleEndian>(Magic::End as u64)?;
740        }
741
742        self.write_differential(b, num_new_gens, target_dim)?;
743        Ok(())
744    }
745
746    /// Step resolution for s = 0
747    #[tracing::instrument(skip(self))]
748    fn step0(&self, t: i32) {
749        self.zero_module.extend_by_zero(t);
750
751        let source_module = &self.modules[0];
752        let target_module = self.target.module(0);
753
754        let chain_map = &self.chain_maps[0];
755        let d = &self.differentials[0];
756
757        let source_dim = source_module.dimension(t);
758        let target_dim = target_module.dimension(t);
759
760        source_module.compute_basis(t);
761        target_module.compute_basis(t);
762
763        if target_dim == 0 {
764            source_module.extend_by_zero(t);
765            chain_map.extend_by_zero(t);
766        } else {
767            let mut matrix = AugmentedMatrix::<2>::new_with_capacity(
768                self.prime(),
769                source_dim,
770                &[target_dim, source_dim],
771                source_dim + target_dim,
772                0,
773            );
774            {
775                let _guard = ParallelGuard::new();
776                chain_map.get_matrix(matrix.segment(0, 0), t);
777            }
778            matrix.segment(1, 1).add_identity();
779
780            matrix.row_reduce();
781
782            let num_new_gens = matrix.extend_to_surjection(0, target_dim, 0).len();
783
784            self.add_generators(Bidegree::s_t(0, t), num_new_gens);
785
786            chain_map.add_generators_from_matrix_rows(
787                t,
788                matrix
789                    .segment(0, 0)
790                    .row_slice(source_dim, source_dim + num_new_gens),
791            );
792        }
793        chain_map.compute_auxiliary_data_through_degree(t);
794
795        d.set_kernel(t, None);
796        d.set_image(t, None);
797        d.set_quasi_inverse(t, None);
798        d.extend_by_zero(t);
799    }
800
801    /// Step resolution for s = 1
802    #[tracing::instrument(skip(self))]
803    fn step1(&self, t: i32) -> anyhow::Result<()> {
804        let p = self.prime();
805
806        let source_module = &self.modules[1];
807        let target_module = &self.modules[0];
808        let cc_module = self.target.module(0);
809
810        let source_dim = source_module.dimension(t);
811        let target_dim = target_module.dimension(t);
812
813        let mut matrix =
814            AugmentedMatrix::<2>::new(p, target_dim, [cc_module.dimension(t), target_dim]);
815        {
816            let _guard = ParallelGuard::new();
817            self.chain_maps[0].get_matrix(matrix.segment(0, 0), t);
818        }
819        matrix.segment(1, 1).add_identity();
820        matrix.row_reduce();
821        let desired_image = matrix.compute_kernel();
822
823        let mut matrix = AugmentedMatrix::<2>::new_with_capacity(
824            p,
825            source_dim,
826            &[target_dim, source_dim],
827            source_dim + MAX_NEW_GENS,
828            0,
829        );
830        {
831            let _guard = ParallelGuard::new();
832            self.differentials[1].get_matrix(matrix.segment(0, 0), t);
833        }
834        matrix.segment(1, 1).add_identity();
835        matrix.row_reduce();
836
837        let num_new_gens = matrix.extend_image(0, target_dim, &desired_image, 0).len();
838
839        self.add_generators(Bidegree::s_t(1, t), num_new_gens);
840
841        self.differentials[1].add_generators_from_matrix_rows(
842            t,
843            matrix
844                .segment(0, 0)
845                .row_slice(source_dim, source_dim + num_new_gens),
846        );
847
848        self.write_differential(Bidegree::s_t(1, t), num_new_gens, target_dim)?;
849        Ok(())
850    }
851
852    fn step_resolution_with_result(&self, b: Bidegree) -> anyhow::Result<()> {
853        let p = self.prime();
854        let set_data = || {
855            let d = &self.differentials[b.s()];
856            let c = &self.chain_maps[b.s()];
857
858            d.set_kernel(b.t(), None);
859            d.set_image(b.t(), None);
860            d.set_quasi_inverse(b.t(), None);
861
862            c.set_kernel(b.t(), None);
863            c.set_image(b.t(), None);
864            c.set_quasi_inverse(b.t(), None);
865        };
866        self.modules[b.s()].compute_basis(b.t());
867        if b.s() > 0 {
868            self.modules[b.s() - 1].compute_basis(b.t());
869        }
870
871        if b.s() == 0 {
872            self.step0(b.t());
873            return Ok(());
874        }
875
876        if let Some(dir) = self.save_dir.read()
877            && let Some(mut f) = self
878                .save_file(SaveKind::NassauDifferential, b)
879                .open_file(dir.clone())
880        {
881            tracing::info!(%b, "Loading differential");
882
883            let num_new_gens = f.read_u64::<LittleEndian>()? as usize;
884            // This need not be equal to `target_res_dimension`. If we saved a big resolution
885            // and now only want to load up to a small stem, then `target_res_dimension` will
886            // be smaller. If we have previously saved a small resolution up to a stem and now
887            // want to resolve further, it will be bigger.
888            let saved_target_res_dimension = f.read_u64::<LittleEndian>()? as usize;
889
890            self.add_generators(b, num_new_gens);
891
892            let mut d_targets = Vec::with_capacity(num_new_gens);
893
894            for _ in 0..num_new_gens {
895                d_targets.push(FpVector::from_bytes(p, saved_target_res_dimension, &mut f)?);
896            }
897
898            self.differentials[b.s()].add_generators_from_rows(b.t(), d_targets);
899
900            set_data();
901
902            return Ok(());
903        }
904
905        if b.s() == 1 {
906            self.step1(b.t())?;
907            set_data();
908            return Ok(());
909        }
910
911        self.step_resolution_with_subalgebra(
912            b,
913            MilnorSubalgebra::optimal_for(b - Bidegree::s_t(0, self.max_degree)),
914        )?;
915        self.chain_maps[b.s()].extend_by_zero(b.t());
916
917        set_data();
918        Ok(())
919    }
920
921    fn step_resolution(&self, b: Bidegree) {
922        self.step_resolution_with_result(b)
923            .unwrap_or_else(|e| panic!("Error computing bidegree {b}: {e}"));
924    }
925
926    /// This function resolves up till a fixed stem instead of a fixed t.
927    #[tracing::instrument(skip(self), fields(self = self.name, %max))]
928    pub fn compute_through_stem(&self, max: Bidegree) {
929        let _lock = self.lock.lock();
930
931        self.extend_through_degree(max.s());
932        self.algebra().compute_basis(max.t());
933
934        let tracing_span = tracing::Span::current();
935        maybe_rayon::in_place_scope(|scope| {
936            let _tracing_guard = tracing_span.enter();
937
938            // This algorithm is not optimal, as we compute (s, t) only after computing (s - 1, t)
939            // and (s, t - 1). In theory, it suffices to wait for (s, t - 1) and (s - 1, t - 1),
940            // but having the dimensions of the modules change halfway through the computation is
941            // annoying to do correctly. It seems more prudent to improve parallelism elsewhere.
942
943            // Things that we have finished computing.
944            let mut progress: Vec<i32> = vec![-1; max.s() as usize + 1];
945            // We will kickstart the process by pretending we have computed (0, - 1). So
946            // we must pretend we have only computed up to (0, - 2);
947            progress[0] = -2;
948
949            let (sender, receiver) = mpsc::channel();
950            SenderData::send(Bidegree::s_t(0, -1), sender);
951
952            let f = |b, sender| {
953                if self.has_computed_bidegree(b) {
954                    SenderData::send(b, sender);
955                } else {
956                    let tracing_span = tracing_span.clone();
957                    scope.spawn(move |_| {
958                        let _tracing_guard = tracing_span.enter();
959                        if crate::utils::parallel::is_in_parallel() {
960                            SenderData::send_retry(b, sender);
961                            return;
962                        }
963                        self.step_resolution(b);
964                        SenderData::send(b, sender);
965                    });
966                }
967            };
968
969            while let Ok(SenderData { b, retry, sender }) = receiver.recv() {
970                if retry {
971                    f(b, sender);
972                    continue;
973                }
974                assert!(progress[b.s() as usize] == b.t() - 1);
975                progress[b.s() as usize] = b.t();
976
977                // How far we are from the last one for this s.
978                let distance = max.n() - b.n() + 1;
979
980                if b.s() < max.s() && progress[b.s() as usize + 1] == b.t() - 1 {
981                    f(b + Bidegree::s_t(1, 0), sender.clone());
982                }
983
984                if distance > 1 && (b.s() == 0 || progress[b.s() as usize - 1] > b.t()) {
985                    // We are computing a normal step
986                    f(b + Bidegree::s_t(0, 1), sender);
987                } else if distance == 1 && b.s() < max.s() {
988                    SenderData::send(b + Bidegree::s_t(0, 1), sender);
989                }
990            }
991        });
992    }
993}
994
995impl<M: ZeroModule<Algebra = MilnorAlgebra>> ChainComplex for Resolution<M> {
996    type Algebra = MilnorAlgebra;
997    type Homomorphism = FreeModuleHomomorphism<FreeModule<Self::Algebra>>;
998    type Module = FreeModule<Self::Algebra>;
999
1000    fn prime(&self) -> ValidPrime {
1001        TWO
1002    }
1003
1004    fn algebra(&self) -> Arc<Self::Algebra> {
1005        self.zero_module.algebra()
1006    }
1007
1008    fn module(&self, s: i32) -> Arc<Self::Module> {
1009        Arc::clone(&self.modules[s])
1010    }
1011
1012    fn zero_module(&self) -> Arc<Self::Module> {
1013        Arc::clone(&self.zero_module)
1014    }
1015
1016    fn min_degree(&self) -> i32 {
1017        0
1018    }
1019
1020    fn has_computed_bidegree(&self, b: Bidegree) -> bool {
1021        self.differentials.len() > b.s() && self.differential(b.s()).next_degree() > b.t()
1022    }
1023
1024    fn differential(&self, s: i32) -> Arc<Self::Homomorphism> {
1025        Arc::clone(&self.differentials[s])
1026    }
1027
1028    #[tracing::instrument(skip(self), fields(self = self.name, %max))]
1029    fn compute_through_bidegree(&self, max: Bidegree) {
1030        let _lock = self.lock.lock();
1031
1032        self.extend_through_degree(max.s());
1033        self.algebra().compute_basis(max.t());
1034
1035        for t in 0..=max.t() {
1036            for s in 0..=max.s() {
1037                let b = Bidegree::s_t(s, t);
1038                if self.has_computed_bidegree(b) {
1039                    continue;
1040                }
1041                self.step_resolution(b);
1042            }
1043        }
1044    }
1045
1046    fn next_homological_degree(&self) -> i32 {
1047        self.modules.len()
1048    }
1049
1050    fn save_dir(&self) -> &SaveDirectory {
1051        &self.save_dir
1052    }
1053
1054    fn apply_quasi_inverse<T, S>(&self, results: &mut [T], b: Bidegree, inputs: &[S]) -> bool
1055    where
1056        for<'a> &'a mut T: Into<FpSliceMut<'a>>,
1057        for<'a> &'a S: Into<FpSlice<'a>>,
1058    {
1059        let mut f = if let Some(dir) = self.save_dir.read() {
1060            if let Some(f) = self.save_file(SaveKind::NassauQi, b).open_file(dir.clone()) {
1061                f
1062            } else {
1063                return false;
1064            }
1065        } else {
1066            return false;
1067        };
1068
1069        let p = self.prime();
1070
1071        let target_dim = f.read_u64::<LittleEndian>().unwrap() as usize;
1072        let zero_mask_dim = f.read_u64::<LittleEndian>().unwrap() as usize;
1073        let subalgebra = MilnorSubalgebra::from_bytes(&mut f).unwrap();
1074        let source = &self.modules[b.s()];
1075        let target = &self.modules[b.s() - 1];
1076        let algebra = target.algebra();
1077
1078        let mut inputs: Vec<FpVector> = inputs.iter().map(|x| x.into().to_owned()).collect();
1079        let mut mask: Vec<usize> = Vec::with_capacity(zero_mask_dim + 8);
1080        mask.extend(subalgebra.signature_mask(
1081            &algebra,
1082            source,
1083            b.t(),
1084            &subalgebra.zero_signature(),
1085        ));
1086
1087        let mut scratch0 = FpVector::new(p, zero_mask_dim);
1088        let mut scratch1 = FpVector::new(p, target_dim);
1089
1090        // If the quasi-inverse was computed using incomplete information, we need to figure out
1091        // what the differentials in this bidegree hit and use them to lift. these variables are
1092        // trivial if there is no such problem.
1093        //
1094        // target_zero_mask is the signature mask of the target under the zero signature.
1095        //
1096        // dx_matrix is an AugmentedMatrix::<3>.
1097        //
1098        // Each row of this matrix is of the form [r; dx; x], where x is an element of the source
1099        // of signature zero, expressed in the masked basis, and dx is the value of the
1100        // differential on x. Then r is the entries of dx that have zero signature, which we
1101        // include so that the rref of the matix is nice. In practice, we keep r empty until the
1102        // very end, and then populate it manually.
1103        //
1104        // At the beginning the x's will be the new generators in this bidegree. As we read in the
1105        // quasi-inverses for the zero signature, we keep on reducing this so that dx is zero in
1106        // the pivot columns of the quasi-inverse. We can then use (the rref of) this matrix to
1107        // lift remaining elements with zero signature.
1108        let (mut target_zero_mask, mut dx_matrix) = if zero_mask_dim != mask.len() {
1109            let num_new_gens = source.number_of_gens_in_degree(b.t());
1110            assert_eq!(mask.len(), zero_mask_dim + num_new_gens);
1111
1112            let target_zero_mask: Vec<usize> = subalgebra
1113                .signature_mask(&algebra, target, b.t(), &subalgebra.zero_signature())
1114                .collect();
1115            let mut matrix = AugmentedMatrix::<3>::new(
1116                p,
1117                num_new_gens,
1118                [target_zero_mask.len(), target.dimension(b.t()), mask.len()],
1119            );
1120
1121            for i in 0..num_new_gens {
1122                let dx = self.differentials[b.s()].output(b.t(), i);
1123                matrix
1124                    .row_segment_mut(i, 1, 1)
1125                    .slice_mut(0, dx.len())
1126                    .add(dx.as_slice(), 1);
1127                matrix
1128                    .row_segment_mut(i, 2, 2)
1129                    .add_basis_element(zero_mask_dim + i, 1);
1130            }
1131
1132            (target_zero_mask, matrix)
1133        } else {
1134            (Vec::new(), AugmentedMatrix::<3>::new(p, 0, [0, 0, 0]))
1135        };
1136
1137        loop {
1138            let col = f.read_u64::<LittleEndian>().unwrap() as usize;
1139            if col == Magic::End as usize {
1140                break;
1141            } else if col == Magic::Signature as usize {
1142                let signature = subalgebra.signature_from_bytes(&mut f).unwrap();
1143
1144                mask.clear();
1145                mask.extend(subalgebra.signature_mask(&algebra, source, b.t(), &signature));
1146                scratch0.set_scratch_vector_size(mask.len());
1147            } else if col == Magic::Fix as usize {
1148                // We need to fix the differential problem
1149                //
1150                // First manually add_masked the second segment to the first, which we use for
1151                // row reduction. We do this manually for borrow checker reasons.
1152                for (j, &k) in target_zero_mask.iter().enumerate() {
1153                    for i in 0..dx_matrix.rows() {
1154                        if dx_matrix.row_segment(i, 1, 1).entry(k) != 0 {
1155                            dx_matrix.row_segment_mut(i, 0, 0).add_basis_element(j, 1);
1156                        }
1157                    }
1158                }
1159                dx_matrix.row_reduce();
1160
1161                // Now reduce by these elements
1162                for i in 0..dx_matrix.rows() {
1163                    let masked_col = dx_matrix.row(i).first_nonzero().unwrap().0;
1164                    assert_eq!(dx_matrix.pivots()[masked_col], i as isize);
1165                    let col = target_zero_mask[masked_col];
1166
1167                    for (input, output) in inputs.iter_mut().zip(results.iter_mut()) {
1168                        let entry = input.entry(col);
1169                        if entry != 0 {
1170                            output
1171                                .into()
1172                                .add_unmasked(dx_matrix.row_segment(i, 2, 2), 1, &mask);
1173                            input.as_slice_mut().add(dx_matrix.row_segment(i, 1, 1), 1);
1174                        }
1175                    }
1176                }
1177
1178                // Drop these objects to save a bit of memory
1179                target_zero_mask = Vec::new();
1180                dx_matrix = AugmentedMatrix::<3>::new(p, 0, [0, 0, 0]);
1181            } else {
1182                scratch0.update_from_bytes(&mut f).unwrap();
1183                scratch1.update_from_bytes(&mut f).unwrap();
1184                for (input, output) in inputs.iter_mut().zip(results.iter_mut()) {
1185                    let entry = input.entry(col);
1186                    if entry != 0 {
1187                        output.into().add_unmasked(scratch0.as_slice(), 1, &mask);
1188                        // If we resume a resolve_through_stem, input may be longer than scratch1.
1189                        input
1190                            .slice_mut(0, scratch1.len())
1191                            .add(scratch1.as_slice(), 1);
1192                    }
1193                }
1194
1195                // Row reduce the differentials
1196                if !target_zero_mask.is_empty() {
1197                    for i in 0..dx_matrix.rows() {
1198                        if dx_matrix.row_segment(i, 1, 1).entry(col) != 0 {
1199                            dx_matrix
1200                                .row_segment_mut(i, 2, 2)
1201                                .slice_mut(0, zero_mask_dim)
1202                                .add(scratch0.as_slice(), 1);
1203                            dx_matrix
1204                                .row_segment_mut(i, 1, 1)
1205                                .slice_mut(0, target_dim)
1206                                .add(scratch1.as_slice(), 1);
1207                        }
1208                    }
1209                }
1210            }
1211        }
1212        // Make sure we have finished reading everything
1213        drop(f);
1214
1215        for dx in inputs {
1216            assert!(
1217                dx.is_zero(),
1218                "remainder non-zero at {b}\nAlgebra: {subalgebra}\ndx: {}",
1219                target.element_to_string(b.t(), dx.as_slice())
1220            );
1221        }
1222        true
1223    }
1224}
1225
1226impl<M: ZeroModule<Algebra = MilnorAlgebra>> AugmentedChainComplex for Resolution<M> {
1227    type ChainMap = FreeModuleHomomorphism<M>;
1228    type TargetComplex = FiniteChainComplex<M, FullModuleHomomorphism<M, M>>;
1229
1230    fn target(&self) -> Arc<Self::TargetComplex> {
1231        Arc::clone(&self.target)
1232    }
1233
1234    fn chain_map(&self, s: i32) -> Arc<Self::ChainMap> {
1235        Arc::clone(&self.chain_maps[s])
1236    }
1237}
1238
1239#[cfg(test)]
1240mod tests {
1241    use expect_test::expect;
1242
1243    use super::*;
1244
1245    #[test]
1246    fn test_restart_stem() {
1247        let res = crate::utils::construct_nassau("S_2", None).unwrap();
1248        res.compute_through_stem(Bidegree::n_s(14, 8));
1249        res.compute_through_bidegree(Bidegree::s_t(5, 19));
1250
1251        expect![[r#"
1252            ·                             
1253            ·                     ·       
1254            ·                   · ·     · 
1255            ·                 ·   ·     · 
1256            ·             ·   ·         · · 
1257            ·     ·       · · ·         · ·   
1258            ·   · ·     · · ·           · · ·   
1259            · ·   ·       ·               ·       
1260            ·                                       
1261        "#]]
1262        .assert_eq(&res.graded_dimension_string());
1263    }
1264
1265    #[test]
1266    fn test_signature_iterator() {
1267        let subalgebra = MilnorSubalgebra::new(vec![2, 1]);
1268        assert_eq!(
1269            subalgebra.iter_signatures(6).collect::<Vec<_>>(),
1270            vec![
1271                vec![1, 0],
1272                vec![2, 0],
1273                vec![3, 0],
1274                vec![0, 1],
1275                vec![1, 1],
1276                vec![2, 1],
1277                vec![3, 1],
1278            ]
1279        );
1280
1281        assert_eq!(
1282            subalgebra.iter_signatures(5).collect::<Vec<_>>(),
1283            vec![
1284                vec![1, 0],
1285                vec![2, 0],
1286                vec![3, 0],
1287                vec![0, 1],
1288                vec![1, 1],
1289                vec![2, 1],
1290            ]
1291        );
1292        assert_eq!(
1293            subalgebra.iter_signatures(4).collect::<Vec<_>>(),
1294            vec![vec![1, 0], vec![2, 0], vec![3, 0], vec![0, 1], vec![1, 1],]
1295        );
1296        assert_eq!(
1297            subalgebra.iter_signatures(3).collect::<Vec<_>>(),
1298            vec![vec![1, 0], vec![2, 0], vec![3, 0], vec![0, 1],]
1299        );
1300        assert_eq!(
1301            subalgebra.iter_signatures(2).collect::<Vec<_>>(),
1302            vec![vec![1, 0], vec![2, 0],]
1303        );
1304        assert_eq!(
1305            subalgebra.iter_signatures(1).collect::<Vec<_>>(),
1306            vec![vec![1, 0],]
1307        );
1308        assert_eq!(
1309            subalgebra.iter_signatures(0).collect::<Vec<_>>(),
1310            Vec::<Vec<PPartEntry>>::new()
1311        );
1312    }
1313
1314    #[test]
1315    fn test_signature_iterator_large() {
1316        let subalgebra = MilnorSubalgebra::new(vec![
1317            0,
1318            MilnorSubalgebra::INFINITY,
1319            MilnorSubalgebra::INFINITY,
1320            MilnorSubalgebra::INFINITY,
1321        ]);
1322        assert_eq!(
1323            subalgebra.iter_signatures(7).collect::<Vec<_>>(),
1324            vec![vec![0, 1, 0, 0], vec![0, 2, 0, 0], vec![0, 0, 1, 0],]
1325        );
1326    }
1327}