ext/
resolution_homomorphism.rs

1//! This module defines [`MuResolutionHomomorphism`], which is a chain map from a
2//! [`FreeChainComplex`].
3use std::{ops::Range, sync::Arc};
4
5use algebra::{
6    MuAlgebra,
7    module::{
8        Module,
9        homomorphism::{ModuleHomomorphism, MuFreeModuleHomomorphism},
10    },
11};
12use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
13use fp::{
14    matrix::Matrix,
15    vector::{FpSliceMut, FpVector},
16};
17use maybe_rayon::prelude::*;
18use once::OnceBiVec;
19use sseq::coordinates::{Bidegree, BidegreeGenerator, BidegreeRange};
20
21use crate::{
22    chain_complex::{AugmentedChainComplex, BoundedChainComplex, ChainComplex, FreeChainComplex},
23    save::{SaveDirectory, SaveKind},
24};
25
26pub type ResolutionHomomorphism<CC1, CC2> = MuResolutionHomomorphism<false, CC1, CC2>;
27pub type UnstableResolutionHomomorphism<CC1, CC2> = MuResolutionHomomorphism<true, CC1, CC2>;
28
29/// A chain complex homomorphims from a [`FreeChainComplex`]. This contains logic to lift chain
30/// maps using the freeness.
31pub struct MuResolutionHomomorphism<const U: bool, CC1, CC2>
32where
33    CC1: FreeChainComplex<U>,
34    CC1::Algebra: MuAlgebra<U>,
35    CC2: ChainComplex<Algebra = CC1::Algebra>,
36{
37    name: String,
38    pub source: Arc<CC1>,
39    pub target: Arc<CC2>,
40    maps: OnceBiVec<Arc<MuFreeModuleHomomorphism<U, CC2::Module>>>,
41    pub shift: Bidegree,
42    save_dir: SaveDirectory,
43}
44
45impl<const U: bool, CC1, CC2> MuResolutionHomomorphism<U, CC1, CC2>
46where
47    CC1: FreeChainComplex<U>,
48    CC1::Algebra: MuAlgebra<U>,
49    CC2: ChainComplex<Algebra = CC1::Algebra>,
50{
51    pub fn new(name: String, source: Arc<CC1>, target: Arc<CC2>, shift: Bidegree) -> Self {
52        let save_dir = if source.save_dir().is_some() && !name.is_empty() {
53            let mut save_dir = source.save_dir().clone();
54            save_dir.push(format!("products/{name}"));
55            SaveKind::ChainMap
56                .create_dir(save_dir.write().unwrap())
57                .unwrap();
58            save_dir
59        } else {
60            SaveDirectory::None
61        };
62
63        Self {
64            name,
65            source,
66            target,
67            maps: OnceBiVec::new(shift.s()),
68            shift,
69            save_dir,
70        }
71    }
72
73    pub fn name(&self) -> &str {
74        &self.name
75    }
76
77    pub fn algebra(&self) -> Arc<CC1::Algebra> {
78        self.source.algebra()
79    }
80
81    pub fn next_homological_degree(&self) -> i32 {
82        self.maps.len()
83    }
84
85    fn get_map_ensure_length(&self, input_s: i32) -> &MuFreeModuleHomomorphism<U, CC2::Module> {
86        self.maps.extend(input_s, |input_s| {
87            let output_s = input_s - self.shift.s();
88            Arc::new(MuFreeModuleHomomorphism::new(
89                self.source.module(input_s),
90                self.target.module(output_s),
91                self.shift.t(),
92            ))
93        });
94        &self.maps[input_s]
95    }
96
97    /// Returns the chain map on the `s`th source module.
98    pub fn get_map(&self, input_s: i32) -> Arc<MuFreeModuleHomomorphism<U, CC2::Module>> {
99        Arc::clone(&self.maps[input_s])
100    }
101
102    pub fn save_dir(&self) -> &SaveDirectory {
103        &self.save_dir
104    }
105}
106
107impl<const U: bool, CC1, CC2> MuResolutionHomomorphism<U, CC1, CC2>
108where
109    CC1: FreeChainComplex<U>,
110    CC1::Algebra: MuAlgebra<U>,
111    CC2: ChainComplex<Algebra = CC1::Algebra>,
112{
113    /// Extend the resolution homomorphism such that it is defined on degrees
114    /// (`max_s`, `max_t`).
115    ///
116    /// This assumes in yet-uncomputed bidegrees, the homology of the source consists only of
117    /// decomposables (e.g. it is trivial). More precisely, we assume
118    /// [`MuResolutionHomomorphism::extend_step_raw`] can be called with `extra_images = None`.
119    #[tracing::instrument(fields(self = self.name, %max))]
120    pub fn extend(&self, max: Bidegree) {
121        self.extend_profile(BidegreeRange::new(&(), max.s() + 1, &|_, _| max.t() + 1))
122    }
123
124    /// Extend the resolution homomorphism such that it is defined on degrees
125    /// (`max_n`, `max_s`).
126    ///
127    /// This assumes in yet-uncomputed bidegrees, the homology of the source consists only of
128    /// decomposables (e.g. it is trivial). More precisely, we assume
129    /// [`MuResolutionHomomorphism::extend_step_raw`] can be called with `extra_images = None`.
130    #[tracing::instrument(fields(self = self.name, %max))]
131    pub fn extend_through_stem(&self, max: Bidegree) {
132        self.extend_profile(BidegreeRange::new(&(), max.s() + 1, &|_, s| {
133            max.n() + s + 1
134        }))
135    }
136
137    /// Extend the resolution homomorphism as far as possible, as constrained by how much the
138    /// source and target have been resolved.
139    ///
140    /// This assumes in yet-uncomputed bidegrees, the homology of the source consists only of
141    /// decomposables (e.g. it is trivial). More precisely, we assume
142    /// [`MuResolutionHomomorphism::extend_step_raw`] can be called with `extra_images = None`.
143    #[tracing::instrument(fields(self = self.name))]
144    pub fn extend_all(&self) {
145        self.extend_profile(BidegreeRange::new(
146            self,
147            std::cmp::min(
148                self.target.next_homological_degree() + self.shift.s(),
149                self.source.next_homological_degree(),
150            ),
151            &|selff, s| {
152                std::cmp::min(
153                    selff
154                        .target
155                        .module(s - selff.shift.s())
156                        .max_computed_degree()
157                        + selff.shift.t(),
158                    selff.source.module(s).max_computed_degree(),
159                ) + 1
160            },
161        ));
162    }
163
164    /// Extends the resolution homomorphism up to a given range. This range is first specified by
165    /// the maximum `s`, then the maximum `t` for each `s`. This should rarely be used directly;
166    /// instead one should use [`MuResolutionHomomorphism::extend`],
167    /// [`MuResolutionHomomorphism::extend_through_stem`] and [`ResolutionHomomorphism::extend_all`]
168    /// as appropriate.
169    ///
170    /// Note that unlike the more specific versions of this function, the bounds `max_s` and
171    /// `max_t` are exclusive.
172    ///
173    /// This assumes in yet-uncomputed bidegrees, the homology of the source consists only of
174    /// decomposables (e.g. it is trivial). More precisely, we assume
175    /// [`MuResolutionHomomorphism::extend_step_raw`] can be called with `extra_images = None`.
176    pub fn extend_profile<AUX: Sync>(&self, max: BidegreeRange<AUX>) {
177        self.get_map_ensure_length(max.s() - 1);
178
179        sseq::coordinates::iter_s_t(
180            &|b| self.extend_step_raw(b, None),
181            Bidegree::s_t(
182                self.shift.s(),
183                self.get_map_ensure_length(self.shift.s()).min_degree(),
184            ),
185            max,
186        );
187
188        for s in self.shift.s()..max.s() {
189            assert_eq!(
190                Vec::<i32>::new(),
191                self.maps[s].ooo_outputs(),
192                "Map {s} has out of order elements"
193            );
194        }
195    }
196
197    /// Extend the [`MuResolutionHomomorphism`] to be defined on `(input_s, input_t)`. The resulting
198    /// homomorphism `f` is a chain map such that if `g` is the `k`th generator in the source such
199    /// that `d(g) = 0`, then `f(g)` is the `k`th row of `extra_images`.
200    ///
201    /// The user should call this function explicitly to manually define the chain map where the
202    /// chain complex is not exact, and then call [`MuResolutionHomomorphism::extend_all`] to extend
203    /// the rest by exactness.
204    #[tracing::instrument(skip(self, extra_images), fields(self = self.name, %input))]
205    pub fn extend_step_raw(
206        &self,
207        input: Bidegree,
208        extra_images: Option<Vec<FpVector>>,
209    ) -> Range<i32> {
210        let output = input - self.shift;
211        assert!(self.target.has_computed_bidegree(output));
212        assert!(self.source.has_computed_bidegree(input));
213        assert!(input.s() >= self.shift.s());
214
215        let f_cur = self.get_map_ensure_length(input.s());
216        if input.t() < f_cur.next_degree() {
217            assert!(extra_images.is_none());
218            // We need to signal to compute the dependents of this
219            return input.t()..input.t() + 1;
220        }
221
222        let p = self.source.prime();
223
224        let num_gens = f_cur.source().number_of_gens_in_degree(input.t());
225        let fx_dimension = f_cur.target().dimension(output.t());
226
227        if num_gens == 0 || fx_dimension == 0 {
228            return f_cur.add_generators_from_rows_ooo(
229                input.t(),
230                vec![FpVector::new(p, fx_dimension); num_gens],
231            );
232        }
233
234        if let Some(dir) = self.save_dir.read() {
235            let mut outputs = Vec::with_capacity(num_gens);
236
237            if let Some(mut f) = self
238                .source
239                .save_file(SaveKind::ChainMap, input)
240                .open_file(dir.to_owned())
241            {
242                let fx_dimension = f.read_u64::<LittleEndian>().unwrap() as usize;
243                for _ in 0..num_gens {
244                    outputs.push(FpVector::from_bytes(p, fx_dimension, &mut f).unwrap());
245                }
246                return f_cur.add_generators_from_rows_ooo(input.t(), outputs);
247            }
248        }
249
250        if output.s() == 0 {
251            let outputs =
252                extra_images.unwrap_or_else(|| vec![FpVector::new(p, fx_dimension); num_gens]);
253
254            if let Some(dir) = self.save_dir.write() {
255                let mut f = self
256                    .source
257                    .save_file(SaveKind::ChainMap, input)
258                    .create_file(dir.clone(), false);
259                f.write_u64::<LittleEndian>(fx_dimension as u64).unwrap();
260                for row in &outputs {
261                    row.to_bytes(&mut f).unwrap();
262                }
263            }
264
265            return f_cur.add_generators_from_rows_ooo(input.t(), outputs);
266        }
267        let mut outputs = vec![FpVector::new(p, fx_dimension); num_gens];
268        let d_source = self.source.differential(input.s());
269        let d_target = self.target.differential(output.s());
270        let f_prev = self.get_map(input.s() - 1);
271        assert!(Arc::ptr_eq(&d_source.source(), &f_cur.source()));
272        assert!(Arc::ptr_eq(&d_source.target(), &f_prev.source()));
273        assert!(Arc::ptr_eq(&d_target.source(), &f_cur.target()));
274        assert!(Arc::ptr_eq(&d_target.target(), &f_prev.target()));
275        let fdx_dimension = f_prev.target().dimension(output.t());
276
277        // First take care of generators that hit the target chain complex.
278        let mut extra_image_row = 0;
279        for (k, output_row) in outputs.iter_mut().enumerate() {
280            if d_source.output(input.t(), k).is_zero() {
281                let extra_image_matrix = extra_images.as_ref().expect("Missing extra image rows");
282                output_row.assign(&extra_image_matrix[extra_image_row]);
283                extra_image_row += 1;
284            }
285        }
286
287        // Now do the rest
288        d_target.compute_auxiliary_data_through_degree(output.t());
289
290        let compute_fdx_vector = |k| {
291            let dx_vector = d_source.output(input.t(), k);
292            if dx_vector.is_zero() {
293                None
294            } else {
295                let mut fdx_vector = FpVector::new(p, fdx_dimension);
296                f_prev.apply(
297                    fdx_vector.as_slice_mut(),
298                    1,
299                    input.t(),
300                    dx_vector.as_slice(),
301                );
302                Some(fdx_vector)
303            }
304        };
305
306        let fdx_vectors: Vec<FpVector> = (0..num_gens)
307            .into_maybe_par_iter()
308            .filter_map(compute_fdx_vector)
309            .collect();
310
311        let mut qi_outputs: Vec<_> = outputs
312            .iter_mut()
313            .enumerate()
314            .filter_map(|(k, v)| {
315                if d_source.output(input.t(), k).is_zero() {
316                    None
317                } else {
318                    Some(v.as_slice_mut())
319                }
320            })
321            .collect();
322
323        if !fdx_vectors.is_empty() {
324            assert!(
325                self.target
326                    .apply_quasi_inverse(&mut qi_outputs, output, &fdx_vectors)
327            );
328        }
329
330        if let Some(dir) = self.save_dir.write() {
331            let mut f = self
332                .source
333                .save_file(SaveKind::ChainMap, input)
334                .create_file(dir.clone(), false);
335            f.write_u64::<LittleEndian>(fx_dimension as u64).unwrap();
336            for row in &outputs {
337                row.to_bytes(&mut f).unwrap();
338            }
339        }
340        f_cur.add_generators_from_rows_ooo(input.t(), outputs)
341    }
342}
343
344impl<const U: bool, CC1, CC2> MuResolutionHomomorphism<U, CC1, CC2>
345where
346    CC1: FreeChainComplex<U>,
347    CC1::Algebra: MuAlgebra<U>,
348    CC2: AugmentedChainComplex<Algebra = CC1::Algebra>,
349{
350    pub fn from_class(
351        name: String,
352        source: Arc<CC1>,
353        target: Arc<CC2>,
354        shift: Bidegree,
355        class: &[u32],
356    ) -> Self {
357        let result = Self::new(name, source, target, shift);
358
359        let num_gens = result
360            .source
361            .module(shift.s())
362            .number_of_gens_in_degree(shift.t());
363        assert_eq!(num_gens, class.len());
364
365        let mut matrix = Matrix::new(result.source.prime(), num_gens, 1);
366        for (k, &v) in class.iter().enumerate() {
367            matrix.row_mut(k).set_entry(0, v);
368        }
369
370        result.extend_step(shift, Some(&matrix));
371        result
372    }
373
374    /// Extend the [`MuResolutionHomomorphism`] to be defined on `(input_s, input_t)`. The resulting
375    /// homomorphism `f` is a chain map such that if `g` is the `k`th generator in the source such
376    /// that `d(g) = 0`, then the image of `f(g)` in the augmentation of the target is the `k`th
377    /// row of `extra_images`.
378    ///
379    /// The user should call this function explicitly to manually define the chain map where the
380    /// chain complex is not exact, and then call [`MuResolutionHomomorphism::extend_all`] to extend
381    /// the rest by exactness.
382    pub fn extend_step(&self, input: Bidegree, extra_images: Option<&Matrix>) -> Range<i32> {
383        self.extend_step_raw(
384            input,
385            extra_images.map(|m| {
386                let p = self.target.prime();
387                let output = input - self.shift;
388
389                let mut outputs =
390                    vec![
391                        FpVector::new(p, self.target.module(output.s()).dimension(output.t()));
392                        m.rows()
393                    ];
394                let chain_map = self.target.chain_map(output.s());
395                chain_map.compute_auxiliary_data_through_degree(output.t());
396                for (output_vec, input) in std::iter::zip(&mut outputs, m.iter()) {
397                    assert!(chain_map.apply_quasi_inverse(
398                        output_vec.as_slice_mut(),
399                        output.t(),
400                        input,
401                    ));
402                }
403                outputs
404            }),
405        )
406    }
407}
408
409impl<const U: bool, CC1, CC2> MuResolutionHomomorphism<U, CC1, CC2>
410where
411    CC1: AugmentedChainComplex + FreeChainComplex<U>,
412    CC1::Algebra: MuAlgebra<U>,
413    CC2: AugmentedChainComplex<Algebra = CC1::Algebra>,
414    CC1::TargetComplex: BoundedChainComplex,
415    CC2::TargetComplex: BoundedChainComplex,
416{
417    /// Construct a chain map that lifts a given module homomorphism.
418    pub fn from_module_homomorphism(
419        name: String,
420        source: Arc<CC1>,
421        target: Arc<CC2>,
422        f: &impl ModuleHomomorphism<
423            Source = <<CC1 as AugmentedChainComplex>::TargetComplex as ChainComplex>::Module,
424            Target = <<CC2 as AugmentedChainComplex>::TargetComplex as ChainComplex>::Module,
425        >,
426    ) -> Self {
427        assert_eq!(source.target().max_s(), 1);
428        assert_eq!(target.target().max_s(), 1);
429
430        let source_module = source.target().module(0);
431        let target_module = target.target().module(0);
432        assert!(Arc::ptr_eq(&source_module, &f.source()));
433        assert!(Arc::ptr_eq(&target_module, &f.target()));
434
435        let p = source.prime();
436        let shift = Bidegree::s_t(0, f.degree_shift());
437
438        let max_degree = source_module.max_generator_degree().expect(
439            "MuResolutionHomomorphism::from_module_homomorphism requires finite \
440             max_generator_degree",
441        );
442
443        let hom = Self::new(name, source, target, shift);
444
445        source_module.compute_basis(max_degree);
446        target_module.compute_basis(shift.t() + max_degree);
447
448        let max = Bidegree::s_t(0, max_degree);
449        hom.source.compute_through_bidegree(max);
450        hom.target.compute_through_bidegree(max + shift);
451
452        for t in source_module.min_degree()..=max_degree {
453            let mut m = Matrix::new(
454                p,
455                source_module.dimension(t),
456                target_module.dimension(t + shift.t()),
457            );
458
459            f.get_matrix(m.as_slice_mut(), t);
460            hom.extend_step(Bidegree::s_t(0, t), Some(&m));
461        }
462        hom
463    }
464}
465
466impl<const U: bool, CC1, CC2> MuResolutionHomomorphism<U, CC1, CC2>
467where
468    CC1: FreeChainComplex<U>,
469    CC1::Algebra: MuAlgebra<U>,
470    CC2: FreeChainComplex<U, Algebra = CC1::Algebra>,
471{
472    /// Given a chain map $f: C \to C'$ between free chain complexes, apply
473    /// $$ \Hom(f, k): \Hom(C', k) \to \Hom(C, k) $$
474    /// to the specified generator of $\Hom(C', k)$.
475    pub fn act(&self, mut result: FpSliceMut, coef: u32, g: BidegreeGenerator) {
476        let source = g.degree() + self.shift;
477
478        assert_eq!(
479            result.as_slice().len(),
480            self.source
481                .module(source.s())
482                .number_of_gens_in_degree(source.t())
483        );
484
485        let target_module = self.target.module(g.s());
486
487        let map = self.get_map(source.s());
488        let j = target_module.operation_generator_to_index(0, 0, g.t(), g.idx());
489        for i in 0..result.as_slice().len() {
490            result.add_basis_element(i, coef * map.output(source.t(), i).entry(j));
491        }
492    }
493}