ext/
resolution.rs

1//! This module exports the [`Resolution`] object, which is a chain complex resolving a module. In
2//! particular, this contains the core logic that compute minimal resolutions.
3use std::sync::{Arc, Mutex, mpsc};
4
5use algebra::{
6    Algebra, MuAlgebra,
7    module::{
8        Module, MuFreeModule,
9        homomorphism::{ModuleHomomorphism, MuFreeModuleHomomorphism},
10    },
11};
12use anyhow::Context;
13use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
14use dashmap::DashMap;
15use fp::{
16    matrix::{AugmentedMatrix, QuasiInverse, Subspace},
17    vector::{FpSlice, FpSliceMut, FpVector},
18};
19use itertools::Itertools;
20use once::{OnceBiVec, OnceVec};
21use sseq::coordinates::{Bidegree, BidegreeGenerator};
22
23use crate::{
24    chain_complex::{AugmentedChainComplex, ChainComplex},
25    save::{SaveDirectory, SaveKind},
26    utils::parallel::ParallelGuard,
27};
28
29/// In [`MuResolution::compute_through_stem`] and [`MuResolution::compute_through_bidegree`], we pass
30/// this struct around to inform the supervisor what bidegrees have been computed. We use an
31/// explicit struct instead of a tuple to avoid an infinite type problem.
32struct SenderData {
33    b: Bidegree,
34    /// Whether this bidegree was newly calculated or have already been calculated.
35    new: bool,
36    /// Whether this job should be retried due to priority inversion avoidance.
37    retry: bool,
38    /// The sender object used to send the `SenderData`. We put this in the struct and pass it
39    /// around the mpsc, so that when all senders are dropped, we know the computation has
40    /// completed. Compared to keeping track of calculations manually, this has the advantage of
41    /// behaving correctly when a thread panicking.
42    sender: mpsc::Sender<Self>,
43}
44
45impl SenderData {
46    fn send(b: Bidegree, new: bool, sender: mpsc::Sender<Self>) {
47        sender
48            .send(Self {
49                b,
50                new,
51                retry: false,
52                sender: sender.clone(),
53            })
54            .unwrap()
55    }
56
57    fn send_retry(b: Bidegree, sender: mpsc::Sender<Self>) {
58        sender
59            .send(Self {
60                b,
61                new: false,
62                retry: true,
63                sender: sender.clone(),
64            })
65            .unwrap()
66    }
67}
68
69/// This is the maximum number of new generators we expect in each bidegree. This affects how much
70/// space we allocate when we are extending our resolutions. Having more than this many new
71/// generators will result in a slowdown but not an error. It is relatively cheap to increment this
72/// number if needs be, but up to the 140th stem we only see at most 8 new generators.
73const MAX_NEW_GENS: usize = 10;
74
75pub type Resolution<CC> = MuResolution<false, CC>;
76pub type UnstableResolution<CC> = MuResolution<true, CC>;
77
78/// A minimal resolution of a chain complex. The functions [`MuResolution::compute_through_stem`] and
79/// [`MuResolution::compute_through_bidegree`] extends the minimal resolution to the given bidegree.
80pub struct MuResolution<const U: bool, CC: ChainComplex>
81where
82    CC::Algebra: MuAlgebra<U>,
83{
84    name: String,
85    lock: Mutex<()>,
86    complex: Arc<CC>,
87    modules: OnceBiVec<Arc<MuFreeModule<U, CC::Algebra>>>,
88    zero_module: Arc<MuFreeModule<U, CC::Algebra>>,
89    chain_maps: OnceBiVec<Arc<MuFreeModuleHomomorphism<U, CC::Module>>>,
90    differentials: OnceVec<Arc<MuFreeModuleHomomorphism<U, MuFreeModule<U, CC::Algebra>>>>,
91
92    ///  For each *internal* degree, store the kernel of the most recently calculated chain map as
93    ///  returned by `generate_old_kernel_and_compute_new_kernel`, to be used if we run
94    ///  compute_through_degree again.
95    kernels: DashMap<Bidegree, Subspace>,
96    save_dir: SaveDirectory,
97
98    /// Whether we should save newly computed data to the disk. This has no effect if there is no
99    /// save file. Defaults to `self.save_dir.is_some()`.
100    pub should_save: bool,
101
102    /// Whether we should keep the quasi-inverses of the differentials.
103    ///
104    /// If set to false,
105    ///  - If there is no save file, then the quasi-inverse will not be computed.
106    ///  - If there is a save file, then the quasi-inverse will be computed, written to disk, and
107    ///    dropped from memory. We will not load quasi-inverses from save files.
108    ///
109    /// Note that this only applies to quasi-inverses of differentials. The quasi-inverses to the
110    /// augmentation map are useful when the target chain complex is not concentrated in one
111    /// degree, and they tend to be quite small anyway.
112    pub load_quasi_inverse: bool,
113}
114
115impl<const U: bool, CC: ChainComplex> MuResolution<U, CC>
116where
117    CC::Algebra: MuAlgebra<U>,
118{
119    pub fn new(complex: Arc<CC>) -> Self {
120        // It doesn't error if the save file is None
121        Self::new_with_save(complex, None).unwrap()
122    }
123
124    pub fn new_with_save(
125        complex: Arc<CC>,
126        save_dir: impl Into<SaveDirectory>,
127    ) -> anyhow::Result<Self> {
128        let save_dir = save_dir.into();
129        let algebra = complex.algebra();
130        let min_degree = complex.min_degree();
131        let zero_module = Arc::new(MuFreeModule::new(algebra, "F_{-1}".to_string(), min_degree));
132
133        if let Some(p) = save_dir.write() {
134            for subdir in SaveKind::resolution_data() {
135                subdir.create_dir(p)?;
136            }
137        }
138
139        Ok(Self {
140            name: String::new(),
141            complex,
142            zero_module,
143            should_save: save_dir.is_some(),
144            save_dir,
145            lock: Mutex::new(()),
146
147            chain_maps: OnceBiVec::new(0),
148            modules: OnceBiVec::new(0),
149            differentials: OnceVec::new(),
150            kernels: DashMap::new(),
151            load_quasi_inverse: true,
152        })
153    }
154
155    pub fn set_name(&mut self, name: String) {
156        self.name = name;
157    }
158
159    pub fn name(&self) -> &str {
160        &self.name
161    }
162
163    fn add_generators(&self, b: Bidegree, num_new_gens: usize) {
164        let gen_names = (0..num_new_gens)
165            .map(|idx| format!("x_{:#}", BidegreeGenerator::new(b, idx)))
166            .collect();
167        self.module(b.s())
168            .add_generators(b.t(), num_new_gens, Some(gen_names));
169    }
170
171    /// This function prepares the Resolution object to perform computations up to the
172    /// specified s degree. It does *not* perform any computations by itself. It simply lengthens
173    /// the `OnceVec`s `modules`, `chain_maps`, etc. to the right length.
174    fn extend_through_degree(&self, max_s: i32) {
175        let min_degree = self.min_degree();
176
177        for i in self.modules.len()..=max_s {
178            self.modules.push(Arc::new(MuFreeModule::new(
179                Arc::clone(&self.algebra()),
180                format!("F{i}"),
181                min_degree,
182            )));
183            self.chain_maps.push(Arc::new(MuFreeModuleHomomorphism::new(
184                Arc::clone(&self.modules[i]),
185                Arc::clone(&self.complex.module(i)),
186                0,
187            )));
188        }
189
190        if self.differentials.is_empty() {
191            self.differentials
192                .push(Arc::new(MuFreeModuleHomomorphism::new(
193                    Arc::clone(&self.modules[0]),
194                    Arc::clone(&self.zero_module),
195                    0,
196                )));
197        }
198
199        for i in self.differentials.len() as i32..=max_s {
200            self.differentials
201                .push(Arc::new(MuFreeModuleHomomorphism::new(
202                    Arc::clone(&self.modules[i]),
203                    Arc::clone(&self.modules[i - 1]),
204                    0,
205                )));
206        }
207    }
208
209    /// Gets the kernel of the differential starting at $(s, t)$. If this was previously computed,
210    /// we simply retrieve the value (and remove it from the cache). Otherwise, we compute the
211    /// kernel. This requires the differential to be computed at $(s, t - 1)$, but not $(s, t)$
212    /// itself. Indeed, the new generators added to $(s, t)$ are by construction not in the kernel.
213    #[tracing::instrument(skip(self), fields(%b))]
214    fn get_kernel(&self, b: Bidegree) -> Subspace {
215        if let Some((_, v)) = self.kernels.remove(&b) {
216            return v;
217        }
218
219        if b.s() == 0 {
220            self.zero_module.extend_by_zero(b.t());
221        }
222
223        let p = self.prime();
224
225        if let Some(dir) = self.save_dir.read()
226            && let Some(mut f) = self.save_file(SaveKind::Kernel, b).open_file(dir.clone())
227        {
228            return Subspace::from_bytes(p, &mut f)
229                .with_context(|| format!("Failed to read kernel at {b}"))
230                .unwrap();
231        }
232
233        let complex = self.target();
234        complex.compute_through_bidegree(b);
235
236        let current_differential = self.differential(b.s());
237        let current_chain_map = self.chain_map(b.s());
238
239        let source = self.module(b.s());
240        let target_cc = complex.module(b.s());
241        let target_res = current_differential.target(); // This is self.module(s - 1) unless s = 0.
242
243        source.compute_basis(b.t());
244        target_res.compute_basis(b.t());
245
246        let source_dimension = source.dimension(b.t());
247        let target_cc_dimension = target_cc.dimension(b.t());
248        let target_res_dimension = target_res.dimension(b.t());
249
250        let mut matrix = AugmentedMatrix::<3>::new(
251            p,
252            source_dimension,
253            [target_cc_dimension, target_res_dimension, source_dimension],
254        );
255
256        {
257            let _guard = ParallelGuard::new();
258            current_chain_map.get_matrix(matrix.segment(0, 0), b.t());
259            current_differential.get_matrix(matrix.segment(1, 1), b.t());
260        }
261        matrix.segment(2, 2).add_identity();
262        matrix.row_reduce();
263
264        let kernel = matrix.compute_kernel();
265
266        if self.should_save
267            && let Some(dir) = self.save_dir.write()
268        {
269            let mut f = self
270                .save_file(SaveKind::Kernel, b)
271                .create_file(dir.clone(), true);
272            kernel
273                .to_bytes(&mut f)
274                .with_context(|| format!("Failed to write kernel at {b}"))
275                .unwrap();
276        }
277        kernel
278    }
279
280    /// Call our resolution $X$, and the chain complex to resolve $C$. This is a legitimate
281    /// resolution if the map $f: X \to C$ induces an isomorphism on homology. This is the same as
282    /// saying the cofiber is exact. The cofiber is given by the complex
283    ///
284    /// $$ X_s \oplus C_{s+1} \to X_{s-1} \oplus C_s \to X_{s-2} \oplus C_{s-1} \to \cdots $$
285    ///
286    /// where the differentials are given by
287    ///
288    /// $$ \begin{pmatrix} d_X & 0 \\\\ (-1)^s f & d_C \end{pmatrix} $$
289    ///
290    /// Our method of producing $X_{s, t}$ and the chain maps are as follows. Suppose we have already
291    /// built the chain map and differential for $X_{s-1, t}$ and $X_{s, t-1}$. Since $X_s$ is a
292    /// free module, the generators in degree $< t$ gives us a bunch of elements in $X_s$ already,
293    /// and we know exactly where they get mapped to. Let $T$ be the $\\mathbb{F}_p$ vector space
294    /// generated by these elements. Then we already have a map
295    ///
296    /// $$ T \to X_{s-1, t} \oplus C_{s, t}$$
297    ///
298    /// and we know this hits the kernel of the map
299    ///
300    /// $$ D = X_{s-1, t} \oplus C_{s, t} \to X_{s-2, t} \oplus C_{s-1, t}. $$
301    ///
302    /// What we need to do now is to add generators to $X_{s, t}$ to hit the entirity of this
303    /// kernel.  Note that we don't *have* to do this. Some of the elements in the kernel might be
304    /// hit by $C_{s+1, t}$ and we don't have to hit them, but we opt to add generators to hit it
305    /// anyway.
306    ///
307    /// If we do it this way, then we know the composite of the map
308    ///
309    /// $$ T \to X_{s-1, t} \oplus C_{s, t} \to C_{s, t} $$
310    ///
311    /// has to be surjective, since the image of $C_{s, t}$ under $D$ is also in the image of $X_{s-1, t}$.
312    /// So our first step is to add generators to $X_{s, t}$ such that this composite is
313    /// surjective.
314    ///
315    /// After adding these generators, we need to decide where to send them to. We know their
316    /// values in the $C_{s, t}$ component, but we need to use a quasi-inverse to find the element in
317    /// $X_{s-1, t}$ that hits the corresponding image of $C_{s, t}$. This tells us the $X_{s-1,
318    /// t}$ component.
319    ///
320    /// Finally, we need to add further generators to $X_{s, t}$ to hit all the elements in the
321    /// kernel of
322    ///
323    /// $$ X_{s-1, t} \to X_{s-2, t} \oplus C_{s-1, t}. $$
324    ///
325    /// This kernel was recorded by the previous iteration of the method in `old_kernel`, so this
326    /// step is doable as well.
327    ///
328    /// Note that if we add our new generators conservatively, then the kernel of the maps
329    ///
330    /// $$
331    /// \begin{aligned}
332    /// T &\to X_{s-1, t} \oplus C_{s, t} \\\\
333    /// X_{s, t} &\to X_{s-1, t} \oplus C_{s, t}
334    /// \end{aligned}
335    /// $$
336    /// agree.
337    ///
338    /// In the code, we first row reduce the matrix of the map from $T$. This lets us record
339    /// the kernel which is what the function returns at the end. This computation helps us perform
340    /// the future steps since we need to know about the cokernel of this map.
341    ///
342    /// # Arguments
343    ///  * `s` - The s degree to calculate
344    ///  * `t` - The t degree to calculate
345    ///
346    /// To run `step_resolution(s, t)`, we must have already had run `step_resolution(s, t - 1)`
347    /// and `step_resolution(s - 1, t - 1)`. It is more efficient if we have in fact run
348    /// `step_resolution(s - 1, t)`, so try your best to arrange calls to be run in this order.
349    #[tracing::instrument(skip(self), fields(%b, num_new_gens, density))]
350    fn step_resolution(&self, b: Bidegree) {
351        if b.s() == 0 {
352            self.zero_module.extend_by_zero(b.t());
353        }
354
355        let p = self.prime();
356
357        //                           current_chain_map
358        //                X_{s, t} --------------------> C_{s, t}
359        //                   |                               |
360        //                   | current_differential          |
361        //                   v                               v
362        // old_kernel <= X_{s-1, t} -------------------> C_{s-1, t}
363
364        let complex = self.target();
365        complex.compute_through_bidegree(b);
366
367        let current_differential = self.differential(b.s());
368        let current_chain_map = self.chain_map(b.s());
369        let complex_cur_differential = complex.differential(b.s());
370
371        match current_differential.next_degree().cmp(&b.t()) {
372            std::cmp::Ordering::Greater => {
373                // Already computed this degree.
374                return;
375            }
376            std::cmp::Ordering::Less => {
377                // Haven't computed far enough yet
378                panic!("We're not ready to compute bidegree {b} yet.");
379            }
380            std::cmp::Ordering::Equal => (),
381        };
382
383        let source = self.module(b.s());
384        let target_cc = complex.module(b.s());
385        let target_res = current_differential.target(); // This is self.module(s - 1) unless s = 0.
386
387        source.compute_basis(b.t());
388
389        // The Homomorphism matrix has size source_dimension x target_dimension, but we are going to augment it with an
390        // identity matrix so that gives a matrix with dimensions source_dimension x (target_dimension + source_dimension).
391        // Later we're going to write into this same matrix an isomorphism source/image + new vectors --> kernel
392        // This has size target_dimension x (2*target_dimension).
393        // This latter matrix may be used to find a preimage of an element under the differential.
394        let source_dimension = source.dimension(b.t());
395        let target_cc_dimension = target_cc.dimension(b.t());
396        target_res.compute_basis(b.t());
397        let target_res_dimension = target_res.dimension(b.t());
398
399        if let Some(dir) = self.save_dir.read()
400            && let Some(mut f) = self
401                .save_file(SaveKind::Differential, b)
402                .open_file(dir.clone())
403        {
404            let num_new_gens = f.read_u64::<LittleEndian>().unwrap() as usize;
405            // This need not be equal to `target_res_dimension`. If we saved a big resolution
406            // and now only want to load up to a small stem, then `target_res_dimension` will
407            // be smaller. If we have previously saved a small resolution up to a stem and now
408            // want to resolve further, it will be bigger.
409            let saved_target_res_dimension = f.read_u64::<LittleEndian>().unwrap() as usize;
410            assert_eq!(
411                target_cc_dimension,
412                f.read_u64::<LittleEndian>().unwrap() as usize,
413                "Malformed data: mismatched augmentation target dimension"
414            );
415
416            self.add_generators(b, num_new_gens);
417
418            let mut d_targets = Vec::with_capacity(num_new_gens);
419            let mut a_targets = Vec::with_capacity(num_new_gens);
420
421            for _ in 0..num_new_gens {
422                d_targets
423                    .push(FpVector::from_bytes(p, saved_target_res_dimension, &mut f).unwrap());
424            }
425            for _ in 0..num_new_gens {
426                a_targets.push(FpVector::from_bytes(p, target_cc_dimension, &mut f).unwrap());
427            }
428            drop(f);
429            current_differential.add_generators_from_rows(b.t(), d_targets);
430            current_chain_map.add_generators_from_rows(b.t(), a_targets);
431
432            // res qi
433            if self.load_quasi_inverse {
434                if let Some(mut f) = self.save_file(SaveKind::ResQi, b).open_file(dir.clone()) {
435                    let res_qi = QuasiInverse::from_bytes(p, &mut f).unwrap();
436
437                    assert_eq!(
438                        res_qi.source_dimension(),
439                        source_dimension + num_new_gens,
440                        "Malformed data: mismatched source dimension in resolution qi at {b}"
441                    );
442
443                    current_differential.set_quasi_inverse(b.t(), Some(res_qi));
444                } else {
445                    current_differential.set_quasi_inverse(b.t(), None);
446                }
447            } else {
448                current_differential.set_quasi_inverse(b.t(), None);
449            }
450
451            if let Some(mut f) = self
452                .save_file(SaveKind::AugmentationQi, b)
453                .open_file(dir.clone())
454            {
455                let cm_qi = QuasiInverse::from_bytes(p, &mut f).unwrap();
456
457                assert_eq!(
458                    cm_qi.target_dimension(),
459                    target_cc_dimension,
460                    "Malformed data: mismatched augmentation target dimension in qi at {b}"
461                );
462                assert_eq!(
463                    cm_qi.source_dimension(),
464                    source_dimension + num_new_gens,
465                    "Malformed data: mismatched source dimension in augmentation qi at {b}"
466                );
467
468                current_chain_map.set_quasi_inverse(b.t(), Some(cm_qi));
469            } else {
470                current_chain_map.set_quasi_inverse(b.t(), None);
471            }
472
473            current_differential.set_kernel(b.t(), None);
474            current_differential.set_image(b.t(), None);
475
476            current_chain_map.set_kernel(b.t(), None);
477            current_chain_map.set_image(b.t(), None);
478            return;
479        }
480
481        let mut matrix = AugmentedMatrix::<3>::new_with_capacity(
482            p,
483            source_dimension,
484            &[target_cc_dimension, target_res_dimension, source_dimension],
485            source_dimension + MAX_NEW_GENS,
486            MAX_NEW_GENS,
487        );
488        // Get the map (d, f) : X_{s, t} -> X_{s-1, t} (+) C_{s, t} into matrix
489
490        {
491            let _guard = ParallelGuard::new();
492            current_chain_map.get_matrix(matrix.segment(0, 0), b.t());
493            current_differential.get_matrix(matrix.segment(1, 1), b.t());
494        }
495        matrix.segment(2, 2).add_identity();
496
497        matrix.row_reduce();
498
499        if !self.has_computed_bidegree(b + Bidegree::s_t(1, 0)) {
500            let kernel = matrix.compute_kernel();
501            if self.should_save
502                && let Some(dir) = self.save_dir.write()
503            {
504                let mut f = self
505                    .save_file(SaveKind::Kernel, b)
506                    .create_file(dir.clone(), true);
507
508                kernel
509                    .to_bytes(&mut f)
510                    .with_context(|| format!("Failed to write kernel at {b}"))
511                    .unwrap();
512            }
513
514            self.kernels.insert(b, kernel);
515        }
516
517        // Now add generators to surject onto C_{s, t}.
518        // (For now we are just adding the eventual images of the new generators into matrix, we will update
519        // X_{s,t} and f later).
520        // We record which pivots exactly we added so that we can walk over the added genrators in a moment and
521        // work out what dX should to to each of them.
522        let cc_new_gens = matrix.extend_to_surjection(0, target_cc_dimension, MAX_NEW_GENS);
523
524        let mut res_new_gens = Vec::new();
525
526        if b.s() > 0 {
527            if !cc_new_gens.is_empty() {
528                // Now we need to make sure that we have a chain homomorphism. Each generator x we just added to
529                // X_{s,t} has a nontrivial image f(x) \in C_{s,t}. We need to set d(x) so that f(dX(x)) = dC(f(x)).
530                // So we set dX(x) = f^{-1}(dC(f(x)))
531                let prev_chain_map = self.chain_map(b.s() - 1);
532                let quasi_inverse = prev_chain_map.quasi_inverse(b.t()).unwrap();
533
534                let dfx_dim = complex_cur_differential.target().dimension(b.t());
535                let mut dfx = FpVector::new(self.prime(), dfx_dim);
536
537                for (i, &column) in cc_new_gens.iter().enumerate() {
538                    complex_cur_differential.apply_to_basis_element(
539                        dfx.as_slice_mut(),
540                        1,
541                        b.t(),
542                        column,
543                    );
544                    quasi_inverse.apply(
545                        matrix.row_segment_mut(source_dimension + i, 1, 1),
546                        1,
547                        dfx.as_slice(),
548                    );
549                    dfx.set_to_zero();
550                }
551            }
552
553            // Now we add new generators to hit any cycles in old_kernel that we don't want in our homology.
554            //
555            // At this point the matrix is not quite row reduced and the pivots are not correct.
556            // However, extend_image only needs the sign of the pivots within the column range,
557            // which are still correct. The point is that the rows we added all have pivot columns
558            // in the first segment.
559            res_new_gens = matrix.inner.extend_image(
560                matrix.start[1],
561                matrix.end[1],
562                &self.get_kernel(b - Bidegree::s_t(1, 0)),
563                MAX_NEW_GENS,
564            );
565        }
566        let num_new_gens = cc_new_gens.len() + res_new_gens.len();
567        self.add_generators(b, num_new_gens);
568
569        let new_rows = source_dimension + num_new_gens;
570
571        current_chain_map.add_generators_from_matrix_rows(
572            b.t(),
573            matrix.segment(0, 0).row_slice(source_dimension, new_rows),
574        );
575        current_differential.add_generators_from_matrix_rows(
576            b.t(),
577            matrix.segment(1, 1).row_slice(source_dimension, new_rows),
578        );
579
580        if num_new_gens > 0 {
581            // Fix up the augmentation
582            let columns = matrix.columns();
583            matrix.extend_column_dimension(columns + num_new_gens);
584
585            for i in source_dimension..new_rows {
586                matrix.inner.row_mut(i).set_entry(matrix.start[2] + i, 1);
587            }
588
589            // We are now supposed to row reduce the matrix. However, running the full row
590            // reduction algorithm is wasteful, since we have only added a few rows and the rest is
591            // intact.
592            //
593            // The new resolution rows are all zero in the existing pivot columns. Indeed,
594            // the resolution generators are mapped to generators of the kernel, which are zero in
595            // pivot columns of the kernel matrix. But the old image is a subspace of the kernel,
596            // so its pivot columns are a subset of the pivot columns of the kernel matrix.
597            //
598            // So we clear the new cc rows using the old rows.
599            for k in source_dimension..source_dimension + cc_new_gens.len() {
600                for column in matrix.start[1]..matrix.end[1] {
601                    let row = matrix.pivots()[column];
602                    if row < 0 {
603                        continue;
604                    }
605                    let row = row as usize;
606                    unsafe {
607                        matrix.row_op(k, row, column, p);
608                    }
609                }
610            }
611
612            // Now use the new resolution rows to reduce the old rows and the cc rows.
613            let first_res_row = source_dimension + cc_new_gens.len();
614            for (source_row, &pivot_col) in res_new_gens.iter().enumerate() {
615                for target_row in 0..first_res_row {
616                    unsafe {
617                        matrix.row_op(target_row, source_row + first_res_row, pivot_col, p);
618                    }
619                }
620            }
621
622            // We are now almost in RREF, except we need to permute the rows.
623            let mut new_gens = cc_new_gens.into_iter().chain(res_new_gens).enumerate();
624            let (mut next_new_row, mut next_new_col) = new_gens.next().unwrap();
625            let mut next_old_row = 0;
626
627            for old_col in 0..matrix.columns() {
628                if old_col == next_new_col {
629                    matrix.rotate_down(next_old_row..source_dimension + next_new_row + 1, 1);
630                    matrix.pivots_mut()[old_col] = next_old_row as isize;
631                    match new_gens.next() {
632                        Some((x, y)) => {
633                            next_new_row = x;
634                            next_new_col = y;
635                        }
636                        None => {
637                            for entry in &mut matrix.pivots_mut()[old_col + 1..] {
638                                if *entry >= 0 {
639                                    *entry += next_new_row as isize + 1;
640                                }
641                            }
642                            break;
643                        }
644                    }
645                    next_old_row += 1;
646                } else if matrix.pivots()[old_col] >= 0 {
647                    matrix.pivots_mut()[old_col] += next_new_row as isize;
648                    next_old_row += 1;
649                }
650            }
651        }
652        let (cm_qi, res_qi) = matrix.compute_quasi_inverses();
653
654        tracing::Span::current().record("num_new_gens", num_new_gens);
655        tracing::Span::current().record(
656            "density",
657            current_differential.differential_density(b.t()) * 100.0,
658        );
659
660        if self.should_save
661            && let Some(dir) = self.save_dir.write()
662        {
663            // Write differentials last, because if we were terminated halfway, we want the
664            // differentials to exist iff everything has been written. However, we start by
665            // opening the differentials first to make sure we are not overwriting anything.
666
667            // Open differentials file
668            let mut f = self
669                .save_file(SaveKind::Differential, b)
670                .create_file(dir.clone(), false);
671
672            // Write resolution qi
673            res_qi
674                .to_bytes(
675                    &mut self
676                        .save_file(SaveKind::ResQi, b)
677                        .create_file(dir.clone(), true),
678                )
679                .unwrap();
680
681            // Write augmentation qi
682            cm_qi
683                .to_bytes(
684                    &mut self
685                        .save_file(SaveKind::AugmentationQi, b)
686                        .create_file(dir.clone(), true),
687                )
688                .unwrap();
689
690            // Write differentials
691            f.write_u64::<LittleEndian>(num_new_gens as u64).unwrap();
692            f.write_u64::<LittleEndian>(target_res_dimension as u64)
693                .unwrap();
694            f.write_u64::<LittleEndian>(target_cc_dimension as u64)
695                .unwrap();
696
697            for n in 0..num_new_gens {
698                current_differential
699                    .output(b.t(), n)
700                    .to_bytes(&mut f)
701                    .unwrap();
702            }
703            for n in 0..num_new_gens {
704                current_chain_map.output(b.t(), n).to_bytes(&mut f).unwrap();
705            }
706            drop(f);
707
708            // Delete kernel
709            if b.s() > 0 {
710                self.save_file(SaveKind::Kernel, b - Bidegree::s_t(1, 0))
711                    .delete_file(dir.clone())
712                    .unwrap();
713            }
714        }
715
716        if self.load_quasi_inverse {
717            current_differential.set_quasi_inverse(b.t(), Some(res_qi));
718        } else {
719            current_differential.set_quasi_inverse(b.t(), None);
720        }
721
722        // This tends to be small and is always needed if the target is not concentrated in a
723        // single homological degree
724        current_chain_map.set_quasi_inverse(b.t(), Some(cm_qi));
725        current_chain_map.set_kernel(b.t(), None);
726        current_chain_map.set_image(b.t(), None);
727
728        current_differential.set_kernel(b.t(), None);
729        current_differential.set_image(b.t(), None);
730    }
731
732    pub fn compute_through_bidegree_with_callback(
733        &self,
734        max: Bidegree,
735        mut cb: impl FnMut(Bidegree),
736    ) {
737        let min_degree = self.min_degree();
738        let _lock = self.lock.lock();
739
740        self.target().compute_through_bidegree(max);
741        self.extend_through_degree(max.s());
742        self.algebra().compute_basis(max.t() - min_degree);
743
744        let tracing_span = tracing::Span::current();
745        maybe_rayon::in_place_scope(|scope| {
746            let _tracing_guard = tracing_span.enter();
747
748            // Things that we have finished computing.
749            let mut progress: Vec<i32> = vec![min_degree - 1; max.s() as usize + 1];
750            // We will kickstart the process by pretending we have computed (0, min_degree - 1). So
751            // we must pretend we have only computed up to (0, min_degree - 2);
752            progress[0] = min_degree - 2;
753
754            let (sender, receiver) = mpsc::channel();
755            SenderData::send(Bidegree::s_t(0, min_degree - 1), false, sender);
756
757            let f = |b, sender| {
758                if self.has_computed_bidegree(b) {
759                    SenderData::send(b, false, sender);
760                } else {
761                    let tracing_span = tracing_span.clone();
762                    scope.spawn(move |_| {
763                        let _tracing_guard = tracing_span.enter();
764                        if crate::utils::parallel::is_in_parallel() {
765                            SenderData::send_retry(b, sender);
766                            return;
767                        }
768                        self.step_resolution(b);
769                        SenderData::send(b, true, sender);
770                    });
771                }
772            };
773
774            while let Ok(SenderData {
775                b,
776                new,
777                retry,
778                sender,
779            }) = receiver.recv()
780            {
781                if retry {
782                    f(b, sender);
783                    continue;
784                }
785                assert!(progress[b.s() as usize] == b.t() - 1);
786                progress[b.s() as usize] = b.t();
787
788                if b.t() < max.t() && (b.s() == 0 || progress[b.s() as usize - 1] > b.t()) {
789                    // We are computing a normal step
790                    f(b + Bidegree::s_t(0, 1), sender.clone());
791                }
792                if b.s() < max.s() && progress[b.s() as usize + 1] == b.t() - 1 {
793                    f(b + Bidegree::s_t(1, 0), sender);
794                }
795                if new {
796                    cb(b);
797                }
798            }
799        });
800    }
801
802    /// This function resolves up till a fixed stem instead of a fixed t.
803    #[tracing::instrument(skip(self), fields(self = self.name, %max))]
804    pub fn compute_through_stem(&self, max: Bidegree) {
805        self.compute_through_stem_with_callback(max, |_| ());
806    }
807
808    pub fn compute_through_stem_with_callback(&self, max: Bidegree, mut cb: impl FnMut(Bidegree)) {
809        let min_degree = self.min_degree();
810        let _lock = self.lock.lock();
811
812        self.target().compute_through_bidegree(max);
813        self.extend_through_degree(max.s());
814        self.algebra().compute_basis(max.t() - min_degree);
815
816        let tracing_span = tracing::Span::current();
817        maybe_rayon::in_place_scope(|scope| {
818            let _tracing_guard = tracing_span.enter();
819
820            // Things that we have finished computing.
821            let mut progress: Vec<i32> = vec![min_degree - 1; max.s() as usize + 1];
822            // We will kickstart the process by pretending we have computed (0, min_degree - 1). So
823            // we must pretend we have only computed up to (0, min_degree - 2);
824            progress[0] = min_degree - 2;
825
826            let (sender, receiver) = mpsc::channel();
827            SenderData::send(Bidegree::s_t(0, min_degree - 1), false, sender);
828
829            let f = |b, sender| {
830                if self.has_computed_bidegree(b) {
831                    SenderData::send(b, false, sender);
832                } else {
833                    let tracing_span = tracing_span.clone();
834                    scope.spawn(move |_| {
835                        let _tracing_guard = tracing_span.enter();
836                        if crate::utils::parallel::is_in_parallel() {
837                            SenderData::send_retry(b, sender);
838                            return;
839                        }
840                        self.step_resolution(b);
841                        SenderData::send(b, true, sender);
842                    });
843                }
844            };
845
846            while let Ok(SenderData {
847                b,
848                new,
849                retry,
850                sender,
851            }) = receiver.recv()
852            {
853                if retry {
854                    f(b, sender);
855                    continue;
856                }
857                assert!(progress[b.s() as usize] == b.t() - 1);
858                progress[b.s() as usize] = b.t();
859
860                // How far we are from the last one for this s.
861                let distance = max.n() - b.n() + 1;
862
863                if b.s() < max.s() && progress[b.s() as usize + 1] == b.t() - 1 {
864                    f(b + Bidegree::s_t(1, 0), sender.clone());
865                }
866
867                if distance > 1 && (b.s() == 0 || progress[b.s() as usize - 1] > b.t()) {
868                    // We are computing a normal step
869                    f(b + Bidegree::s_t(0, 1), sender);
870                } else if distance == 1 && b.s() < max.s() {
871                    // We compute the kernel at the edge if necessary
872                    let next_b = b + Bidegree::s_t(0, 1);
873                    if !self.has_computed_bidegree(b + Bidegree::s_t(1, 1))
874                        && (self.save_dir.is_none()
875                            || !self
876                                .save_file(SaveKind::Differential, b + Bidegree::s_t(1, 1))
877                                .exists(self.save_dir.read().cloned().unwrap()))
878                    {
879                        scope.spawn(move |_| {
880                            self.kernels.insert(next_b, self.get_kernel(next_b));
881                            SenderData::send(next_b, false, sender);
882                        });
883                    } else {
884                        SenderData::send(next_b, false, sender);
885                    }
886                }
887                if new {
888                    cb(b);
889                }
890            }
891        });
892    }
893}
894
895impl<const U: bool, CC: ChainComplex> ChainComplex for MuResolution<U, CC>
896where
897    CC::Algebra: MuAlgebra<U>,
898{
899    type Algebra = CC::Algebra;
900    type Homomorphism = MuFreeModuleHomomorphism<U, MuFreeModule<U, Self::Algebra>>;
901    type Module = MuFreeModule<U, Self::Algebra>;
902
903    fn algebra(&self) -> Arc<Self::Algebra> {
904        self.target().algebra()
905    }
906
907    fn module(&self, s: i32) -> Arc<Self::Module> {
908        Arc::clone(&self.modules[s])
909    }
910
911    fn zero_module(&self) -> Arc<Self::Module> {
912        Arc::clone(&self.zero_module)
913    }
914
915    fn min_degree(&self) -> i32 {
916        self.target().min_degree()
917    }
918
919    fn has_computed_bidegree(&self, b: Bidegree) -> bool {
920        self.differentials.len() > b.s() as usize && self.differential(b.s()).next_degree() > b.t()
921    }
922
923    fn differential(&self, s: i32) -> Arc<Self::Homomorphism> {
924        Arc::clone(&self.differentials[s as usize])
925    }
926
927    #[tracing::instrument(skip(self), fields(self = self.name, %b))]
928    fn compute_through_bidegree(&self, b: Bidegree) {
929        self.compute_through_bidegree_with_callback(b, |_| ())
930    }
931
932    fn next_homological_degree(&self) -> i32 {
933        self.modules.len()
934    }
935
936    fn apply_quasi_inverse<T, S>(&self, results: &mut [T], b: Bidegree, inputs: &[S]) -> bool
937    where
938        for<'a> &'a mut T: Into<FpSliceMut<'a>>,
939        for<'a> &'a S: Into<FpSlice<'a>>,
940    {
941        assert_eq!(results.len(), inputs.len());
942
943        if let Some(qi) = self.differential(b.s()).quasi_inverse(b.t()) {
944            for (input, result) in inputs.iter().zip_eq(results) {
945                qi.apply(result.into(), 1, input.into());
946            }
947            true
948        } else if let Some(dir) = self.save_dir.read() {
949            if let Some(mut f) = self.save_file(SaveKind::ResQi, b).open_file(dir.clone()) {
950                QuasiInverse::stream_quasi_inverse(self.prime(), &mut f, results, inputs).unwrap();
951                true
952            } else {
953                false
954            }
955        } else {
956            false
957        }
958    }
959
960    fn save_dir(&self) -> &SaveDirectory {
961        &self.save_dir
962    }
963}
964
965impl<const U: bool, CC: ChainComplex> AugmentedChainComplex for MuResolution<U, CC>
966where
967    CC::Algebra: MuAlgebra<U>,
968{
969    type ChainMap = MuFreeModuleHomomorphism<U, CC::Module>;
970    type TargetComplex = CC;
971
972    fn target(&self) -> Arc<Self::TargetComplex> {
973        Arc::clone(&self.complex)
974    }
975
976    fn chain_map(&self, s: i32) -> Arc<Self::ChainMap> {
977        Arc::clone(&self.chain_maps[s])
978    }
979}
980
981#[cfg(test)]
982mod tests {
983    use expect_test::expect;
984
985    use super::*;
986    use crate::{chain_complex::FreeChainComplex, utils::construct_standard};
987
988    #[test]
989    fn test_restart_stem() {
990        let res = construct_standard::<false, _, _>("S_2", None).unwrap();
991        res.compute_through_stem(Bidegree::n_s(14, 8));
992        res.compute_through_bidegree(Bidegree::s_t(5, 19));
993
994        expect![[r#"
995            ·                             
996            ·                     ·       
997            ·                   · ·     · 
998            ·                 ·   ·     · 
999            ·             ·   ·         · · 
1000            ·     ·       · · ·         · ·   
1001            ·   · ·     · · ·           · · ·   
1002            · ·   ·       ·               ·       
1003            ·                                       
1004        "#]]
1005        .assert_eq(&res.graded_dimension_string());
1006    }
1007
1008    #[test]
1009    fn test_apply_quasi_inverse() {
1010        let tempdir = tempfile::TempDir::new().unwrap();
1011
1012        let mut res =
1013            construct_standard::<false, _, _>("S_2", Some(tempdir.path().into())).unwrap();
1014        res.load_quasi_inverse = false;
1015
1016        let b = Bidegree::s_t(8, 8);
1017        res.compute_through_bidegree(b);
1018
1019        assert!(res.differential(8).quasi_inverse(8).is_none());
1020
1021        let v = FpVector::new(res.prime(), res.module(7).dimension(8));
1022        let mut w = FpVector::new(res.prime(), res.module(8).dimension(8));
1023
1024        assert!(res.apply_quasi_inverse(&mut [w.as_slice_mut()], b, &[v.as_slice()]));
1025        assert!(w.is_zero());
1026    }
1027}